blob: 6614417094f64405a9f33055196965530cd3a3a1 [file] [log] [blame]
// Copyright (c) 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef QUICHE_QUIC_MOQT_TEST_TOOLS_MOQT_TEST_MESSAGE_H_
#define QUICHE_QUIC_MOQT_TEST_TOOLS_MOQT_TEST_MESSAGE_H_
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "absl/strings/string_view.h"
#include "quiche/quic/core/quic_data_reader.h"
#include "quiche/quic/core/quic_data_writer.h"
#include "quiche/quic/core/quic_time.h"
#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/moqt/moqt_messages.h"
#include "quiche/quic/moqt/moqt_priority.h"
#include "quiche/quic/platform/api/quic_logging.h"
#include "quiche/common/platform/api/quiche_export.h"
#include "quiche/common/quiche_endian.h"
namespace moqt::test {
inline constexpr absl::string_view kDefaultExtensionBlob(
"\x00\x0c\x01\x03\x66\x6f\x6f", 7);
// Base class containing a wire image and the corresponding structured
// representation of an example of each message. It allows parser and framer
// tests to iterate through all message types without much specialized code.
class QUICHE_NO_EXPORT TestMessageBase {
public:
virtual ~TestMessageBase() = default;
using MessageStructuredData =
std::variant<MoqtClientSetup, MoqtServerSetup, MoqtObject, MoqtSubscribe,
MoqtSubscribeOk, MoqtSubscribeError, MoqtUnsubscribe,
MoqtSubscribeDone, MoqtSubscribeUpdate, MoqtAnnounce,
MoqtAnnounceOk, MoqtAnnounceError, MoqtAnnounceCancel,
MoqtTrackStatusRequest, MoqtUnannounce, MoqtTrackStatus,
MoqtGoAway, MoqtSubscribeAnnounces, MoqtSubscribeAnnouncesOk,
MoqtSubscribeAnnouncesError, MoqtUnsubscribeAnnounces,
MoqtMaxRequestId, MoqtFetch, MoqtFetchCancel, MoqtFetchOk,
MoqtFetchError, MoqtRequestsBlocked, MoqtObjectAck>;
// The total actual size of the message.
size_t total_message_size() const { return wire_image_size_; }
absl::string_view PacketSample() const {
return absl::string_view(wire_image_, wire_image_size_);
}
void set_wire_image_size(size_t wire_image_size) {
wire_image_size_ = wire_image_size;
}
// Returns a copy of the structured data for the message.
virtual MessageStructuredData structured_data() const = 0;
// Compares |values| to the derived class's structured data to make sure
// they are equal.
virtual bool EqualFieldValues(MessageStructuredData& values) const = 0;
// Expand all varints in the message. This is pure virtual because each
// message has a different layout of varints.
virtual void ExpandVarints() = 0;
// This will cause a parsing error. Do not call this on Object Messages.
void DecreasePayloadLengthByOne() {
size_t length_offset =
0x1 << ((static_cast<uint8_t>(wire_image_[0]) & 0xc0) >> 6);
wire_image_[length_offset + 1]--;
}
void IncreasePayloadLengthByOne() {
size_t length_offset =
0x1 << ((static_cast<uint8_t>(wire_image_[0]) & 0xc0) >> 6);
wire_image_[length_offset + 1]++;
set_wire_image_size(wire_image_size_ + 1);
}
protected:
void SetWireImage(uint8_t* wire_image, size_t wire_image_size) {
memcpy(wire_image_, wire_image, wire_image_size);
wire_image_size_ = wire_image_size;
}
// Expands all the varints in the message, alternating between making them 2,
// 4, and 8 bytes long. Updates length fields accordingly.
// Each character in |varints| corresponds to a byte in the original message
// payload.
// If there is a 'v', it is a varint that should be expanded. If '-', skip
// to the next byte.
void ExpandVarintsImpl(absl::string_view varints,
bool is_control_message = true) {
int next_varint_len = 2;
char new_wire_image[kMaxMessageHeaderSize + 1];
quic::QuicDataReader reader(
absl::string_view(wire_image_, wire_image_size_));
quic::QuicDataWriter writer(sizeof(new_wire_image), new_wire_image);
size_t length_field = 0;
if (is_control_message) {
uint8_t type_length = static_cast<uint8_t>(reader.PeekVarInt62Length());
uint64_t type;
reader.ReadVarInt62(&type);
if (type_length == 1) {
// Expand the message type.
type_length = next_varint_len;
writer.WriteVarInt62WithForcedLength(
type, static_cast<quiche::QuicheVariableLengthIntegerLength>(
type_length));
next_varint_len = 4;
} else {
writer.WriteVarInt62(type);
}
length_field = writer.length();
uint16_t size;
reader.ReadUInt16(&size);
writer.WriteUInt16(size);
}
size_t i = 0;
while (!reader.IsDoneReading()) {
if (i >= (varints.length()) || varints[i++] == '-') {
uint8_t byte;
reader.ReadUInt8(&byte);
writer.WriteUInt8(byte);
continue;
}
uint64_t value;
reader.ReadVarInt62(&value);
writer.WriteVarInt62WithForcedLength(
value, static_cast<quiche::QuicheVariableLengthIntegerLength>(
next_varint_len));
next_varint_len *= 2;
if (next_varint_len == 16) {
next_varint_len = 2;
}
}
memcpy(wire_image_, new_wire_image, writer.length());
wire_image_size_ = writer.length();
if (is_control_message) { // First byte will be empty.
quic::QuicDataWriter length_writer(writer.length(),
&wire_image_[length_field]);
length_writer.WriteUInt16(writer.length() - length_field - 2);
}
}
private:
char wire_image_[kMaxMessageHeaderSize + 20];
size_t wire_image_size_;
};
// Base class for the two subtypes of Object Message.
class QUICHE_NO_EXPORT ObjectMessage : public TestMessageBase {
public:
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::move(std::get<MoqtObject>(values));
if (cast.track_alias != object_.track_alias) {
QUIC_LOG(INFO) << "OBJECT Track ID mismatch";
return false;
}
if (cast.group_id != object_.group_id) {
QUIC_LOG(INFO) << "OBJECT Group Sequence mismatch";
return false;
}
if (cast.object_id != object_.object_id) {
QUIC_LOG(INFO) << "OBJECT Object Sequence mismatch";
return false;
}
if (cast.publisher_priority != object_.publisher_priority) {
QUIC_LOG(INFO) << "OBJECT Publisher Priority mismatch";
return false;
}
if (cast.extension_headers != object_.extension_headers) {
QUIC_LOG(INFO) << "OBJECT Extension Header mismatch";
return false;
}
if (cast.object_status != object_.object_status) {
QUIC_LOG(INFO) << "OBJECT Object Status mismatch";
return false;
}
if (cast.subgroup_id != object_.subgroup_id) {
QUIC_LOG(INFO) << "OBJECT Subgroup ID mismatch";
return false;
}
if (cast.payload_length != object_.payload_length) {
QUIC_LOG(INFO) << "OBJECT Payload Length mismatch";
return false;
}
return true;
}
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(object_);
}
protected:
MoqtObject object_ = {
/*track_alias=*/4,
/*group_id*/ 5,
/*object_id=*/6,
/*publisher_priority=*/7,
std::string(kDefaultExtensionBlob),
/*object_status=*/MoqtObjectStatus::kNormal,
/*subgroup_id=*/std::nullopt,
/*payload_length=*/3,
};
};
class QUICHE_NO_EXPORT ObjectDatagramMessage : public ObjectMessage {
public:
ObjectDatagramMessage() : ObjectMessage() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
void ExpandVarints() override {
ExpandVarintsImpl("vvvv-v-------v---", false);
}
private:
uint8_t raw_packet_[17] = {
0x01, 0x04, 0x05, 0x06, // varints
0x07, 0x07, // publisher priority, 7B extensions
0x00, 0x0c, 0x01, 0x03, 0x66, 0x6f, 0x6f, // extensions
0x03, 0x66, 0x6f, 0x6f, // payload = "foo"
};
};
class QUICHE_NO_EXPORT ObjectStatusDatagramMessage : public ObjectMessage {
public:
ObjectStatusDatagramMessage() : ObjectMessage() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
object_.object_status = MoqtObjectStatus::kEndOfGroup;
object_.payload_length = 0;
}
void ExpandVarints() override { ExpandVarintsImpl("vvvv-v-------v", false); }
private:
uint8_t raw_packet_[14] = {
0x02, 0x04, 0x05, 0x06, // varints
0x07, // publisher priority
0x07, // 7B extensions
0x00, 0x0c, 0x01, 0x03, 0x66, 0x6f, 0x6f, // extensions
0x03, // kEndOfGroup
};
};
// Concatenation of the base header and the object-specific header. Follow-on
// object headers are handled in a different class.
class QUICHE_NO_EXPORT StreamHeaderSubgroupMessage : public ObjectMessage {
public:
StreamHeaderSubgroupMessage() : ObjectMessage() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
object_.subgroup_id = 8;
}
void ExpandVarints() override {
ExpandVarintsImpl("vvvv-vv-------v---", false);
}
bool SetPayloadLength(uint8_t payload_length) {
if (payload_length > 63) {
// This only supports one-byte varints.
return false;
}
object_.payload_length = payload_length;
raw_packet_[14] = payload_length;
SetWireImage(raw_packet_, sizeof(raw_packet_));
return true;
}
private:
uint8_t raw_packet_[18] = {
0x04, // type field
0x04, 0x05, 0x08, // varints
0x07, // publisher priority
0x06, 0x07, // object ID, 7B extensions
0x00, 0x0c, 0x01, 0x03, 0x66, 0x6f, 0x6f, // extensions
0x03, 0x66, 0x6f, 0x6f, // payload = "foo"
};
};
// Used only for tests that process multiple objects on one stream.
class QUICHE_NO_EXPORT StreamMiddlerSubgroupMessage : public ObjectMessage {
public:
StreamMiddlerSubgroupMessage() : ObjectMessage() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
object_.subgroup_id = 8;
object_.object_id = 9;
}
void ExpandVarints() override { ExpandVarintsImpl("vv-------v---", false); }
private:
uint8_t raw_packet_[13] = {
0x09, 0x07, // object ID; 7B extensions
0x00, 0x0c, 0x01, 0x03, 0x66, 0x6f, 0x6f, // extensions
0x03, 0x62, 0x61, 0x72, // payload = "bar"
};
};
class QUICHE_NO_EXPORT StreamHeaderFetchMessage : public ObjectMessage {
public:
StreamHeaderFetchMessage() : ObjectMessage() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
object_.subgroup_id = 8;
}
void ExpandVarints() override {
ExpandVarintsImpl("vvvvv-v-------v---", false);
}
bool SetPayloadLength(uint8_t payload_length) {
if (payload_length > 63) {
// This only supports one-byte varints.
return false;
}
object_.payload_length = payload_length;
raw_packet_[14] = payload_length;
SetWireImage(raw_packet_, sizeof(raw_packet_));
return true;
}
private:
uint8_t raw_packet_[18] = {
0x05, // type field
0x04, // subscribe ID
// object middler:
0x05, 0x08, 0x06, // sequence
0x07, 0x07, // publisher priority, 7B extensions
0x00, 0x0c, 0x01, 0x03, 0x66, 0x6f, 0x6f, // extensions
0x03, 0x66, 0x6f, 0x6f, // payload = "foo"
};
};
// Used only for tests that process multiple objects on one stream.
class QUICHE_NO_EXPORT StreamMiddlerFetchMessage : public ObjectMessage {
public:
StreamMiddlerFetchMessage() : ObjectMessage() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
object_.subgroup_id = 8;
object_.object_id = 9;
}
void ExpandVarints() override {
ExpandVarintsImpl("vvv-v-------v---", false);
}
private:
uint8_t raw_packet_[16] = {
0x05, 0x08, 0x09, 0x07, 0x07, // Object metadata
0x00, 0x0c, 0x01, 0x03, 0x66, 0x6f, 0x6f, // extensions
0x03, 0x62, 0x61, 0x72, // Payload = "bar"
};
};
class QUICHE_NO_EXPORT ClientSetupMessage : public TestMessageBase {
public:
explicit ClientSetupMessage(bool webtrans) : TestMessageBase() {
client_setup_.parameters.using_webtrans = webtrans;
if (webtrans) {
// Should not send PATH.
client_setup_.parameters.path = "";
raw_packet_[2] = 0x06; // adjust payload length (-5)
raw_packet_[6] = 0x01; // only one parameter
SetWireImage(raw_packet_, sizeof(raw_packet_) - 5);
} else {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtClientSetup>(values);
if (cast.supported_versions.size() !=
client_setup_.supported_versions.size()) {
QUIC_LOG(INFO) << "CLIENT_SETUP number of supported versions mismatch";
return false;
}
for (uint64_t i = 0; i < cast.supported_versions.size(); ++i) {
// Listed versions are 1 and 2, in that order.
if (cast.supported_versions[i] != client_setup_.supported_versions[i]) {
QUIC_LOG(INFO) << "CLIENT_SETUP supported version mismatch";
return false;
}
}
if (cast.parameters != client_setup_.parameters) {
QUIC_LOG(INFO) << "CLIENT_SETUP parameter mismatch";
return false;
}
return true;
}
void ExpandVarints() override {
if (!client_setup_.parameters.path.empty()) {
ExpandVarintsImpl("vvvvvvvv---");
} else {
ExpandVarintsImpl("vvvvvv");
}
}
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(client_setup_);
}
private:
uint8_t raw_packet_[14] = {
0x20, 0x00, 0x0b, // type
0x02, 0x01, 0x02, // versions
0x02, // 2 parameters
0x02, 0x32, // max_request_id = 50
0x01, 0x03, 0x66, 0x6f, 0x6f, // path = "foo"
};
MoqtClientSetup client_setup_ = {
/*supported_versions=*/std::vector<MoqtVersion>(
{static_cast<MoqtVersion>(1), static_cast<MoqtVersion>(2)}),
MoqtSessionParameters(quic::Perspective::IS_CLIENT, "foo", 50),
};
};
class QUICHE_NO_EXPORT ServerSetupMessage : public TestMessageBase {
public:
explicit ServerSetupMessage(bool webtrans) : TestMessageBase() {
server_setup_.parameters.using_webtrans = webtrans;
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtServerSetup>(values);
if (cast.parameters != server_setup_.parameters) {
QUIC_LOG(INFO) << "SERVER_SETUP parameter mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("vvvv"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(server_setup_);
}
private:
uint8_t raw_packet_[7] = {
0x21, 0x00, 0x04, // type
0x01, 0x01, // version, one parameter
0x02, 0x32, // max_subscribe_id = 50
};
MoqtServerSetup server_setup_ = {
/*selected_version=*/static_cast<MoqtVersion>(1),
MoqtSessionParameters(quic::Perspective::IS_SERVER, 50),
};
};
class QUICHE_NO_EXPORT SubscribeMessage : public TestMessageBase {
public:
SubscribeMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtSubscribe>(values);
if (cast.request_id != subscribe_.request_id) {
QUIC_LOG(INFO) << "SUBSCRIBE subscribe ID mismatch";
return false;
}
if (cast.track_alias != subscribe_.track_alias) {
QUIC_LOG(INFO) << "SUBSCRIBE track alias mismatch";
return false;
}
if (cast.full_track_name != subscribe_.full_track_name) {
QUIC_LOG(INFO) << "SUBSCRIBE track name mismatch";
return false;
}
if (cast.subscriber_priority != subscribe_.subscriber_priority) {
QUIC_LOG(INFO) << "SUBSCRIBE subscriber priority mismatch";
return false;
}
if (cast.group_order != subscribe_.group_order) {
QUIC_LOG(INFO) << "SUBSCRIBE group order mismatch";
return false;
}
if (cast.forward != subscribe_.forward) {
QUIC_LOG(INFO) << "SUBSCRIBE forward mismatch";
return false;
}
if (cast.filter_type != subscribe_.filter_type) {
QUIC_LOG(INFO) << "SUBSCRIBE filter type mismatch";
return false;
}
if (cast.start != subscribe_.start) {
QUIC_LOG(INFO) << "SUBSCRIBE start mismatch";
return false;
}
if (cast.end_group != subscribe_.end_group) {
QUIC_LOG(INFO) << "SUBSCRIBE end group mismatch";
return false;
}
if (cast.parameters != subscribe_.parameters) {
QUIC_LOG(INFO) << "SUBSCRIBE parameter mismatch";
return false;
}
return true;
}
void ExpandVarints() override {
ExpandVarintsImpl("vvvv---v-------vvvvv--vv-----");
}
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(subscribe_);
}
private:
uint8_t raw_packet_[32] = {
0x03, 0x00, 0x1d, 0x01, 0x02, // id and alias
0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x20, // subscriber priority = 0x20
0x02, // group order = descending
0x01, // forward = true
0x03, // Filter type: Absolute Start
0x04, // start_group = 4
0x01, // start_object = 1
// No EndGroup or EndObject
0x02, // 2 parameters
0x02, 0x67, 0x10, // delivery_timeout = 10000 ms
0x01, 0x05, 0x03, 0x00, 0x62, 0x61, 0x72, // authorization_tag = "bar"
};
MoqtSubscribe subscribe_ = {
/*subscribe_id=*/1,
/*track_alias=*/2,
FullTrackName("foo", "abcd"),
/*subscriber_priority=*/0x20,
/*group_order=*/MoqtDeliveryOrder::kDescending,
/*forward=*/true,
/*filter_type=*/MoqtFilterType::kAbsoluteStart,
/*start=*/Location(4, 1),
/*end_group=*/std::nullopt,
VersionSpecificParameters(quic::QuicTimeDelta::FromMilliseconds(10000),
AuthTokenType::kOutOfBand, "bar"),
};
};
class QUICHE_NO_EXPORT SubscribeOkMessage : public TestMessageBase {
public:
SubscribeOkMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtSubscribeOk>(values);
if (cast.request_id != subscribe_ok_.request_id) {
QUIC_LOG(INFO) << "SUBSCRIBE OK subscribe ID mismatch";
return false;
}
if (cast.expires != subscribe_ok_.expires) {
QUIC_LOG(INFO) << "SUBSCRIBE OK expiration mismatch";
return false;
}
if (cast.group_order != subscribe_ok_.group_order) {
QUIC_LOG(INFO) << "SUBSCRIBE OK group order mismatch";
return false;
}
if (cast.largest_location != subscribe_ok_.largest_location) {
QUIC_LOG(INFO) << "SUBSCRIBE OK largest ID mismatch";
return false;
}
if (cast.parameters != subscribe_ok_.parameters) {
QUIC_LOG(INFO) << "SUBSCRIBE OK parameter mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("vv--vvvv--v--"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(subscribe_ok_);
}
void SetInvalidContentExists() {
raw_packet_[6] = 0x02;
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
void SetInvalidDeliveryOrder() {
raw_packet_[5] = 0x10;
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
private:
uint8_t raw_packet_[16] = {
0x04, 0x00, 0x0d, 0x01, 0x03, // request_id = 1, expires = 3
0x02, 0x01, // group_order = 2, content exists
0x0c, 0x14, // largest_location = (12, 20)
0x02, // 2 parameters
0x02, 0x67, 0x10, // delivery_timeout = 10000
0x04, 0x67, 0x10, // max_cache_duration = 10000
};
MoqtSubscribeOk subscribe_ok_ = {
/*request_id=*/1,
/*expires=*/quic::QuicTimeDelta::FromMilliseconds(3),
/*group_order=*/MoqtDeliveryOrder::kDescending,
/*largest_location=*/Location(12, 20),
VersionSpecificParameters(quic::QuicTimeDelta::FromMilliseconds(10000),
quic::QuicTimeDelta::FromMilliseconds(10000)),
};
};
class QUICHE_NO_EXPORT SubscribeErrorMessage : public TestMessageBase {
public:
SubscribeErrorMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtSubscribeError>(values);
if (cast.request_id != subscribe_error_.request_id) {
QUIC_LOG(INFO) << "SUBSCRIBE ERROR request_id mismatch";
return false;
}
if (cast.error_code != subscribe_error_.error_code) {
QUIC_LOG(INFO) << "SUBSCRIBE ERROR error code mismatch";
return false;
}
if (cast.reason_phrase != subscribe_error_.reason_phrase) {
QUIC_LOG(INFO) << "SUBSCRIBE ERROR reason phrase mismatch";
return false;
}
if (cast.track_alias != subscribe_error_.track_alias) {
QUIC_LOG(INFO) << "SUBSCRIBE ERROR track alias mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("vvv---v"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(subscribe_error_);
}
private:
uint8_t raw_packet_[10] = {
0x05, 0x00, 0x07,
0x02, // request_id = 2
0x05, // error_code = 5
0x03, 0x62, 0x61, 0x72, // reason_phrase = "bar"
0x04, // track_alias = 4
};
MoqtSubscribeError subscribe_error_ = {
/*request_id=*/2,
/*error_code=*/RequestErrorCode::kInvalidRange,
/*reason_phrase=*/"bar",
/*track_alias=*/4,
};
};
class QUICHE_NO_EXPORT UnsubscribeMessage : public TestMessageBase {
public:
UnsubscribeMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtUnsubscribe>(values);
if (cast.subscribe_id != unsubscribe_.subscribe_id) {
QUIC_LOG(INFO) << "UNSUBSCRIBE subscribe ID mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("v"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(unsubscribe_);
}
private:
uint8_t raw_packet_[4] = {
0x0a, 0x00, 0x01, 0x03, // subscribe_id = 3
};
MoqtUnsubscribe unsubscribe_ = {
/*subscribe_id=*/3,
};
};
class QUICHE_NO_EXPORT SubscribeDoneMessage : public TestMessageBase {
public:
SubscribeDoneMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtSubscribeDone>(values);
if (cast.subscribe_id != subscribe_done_.subscribe_id) {
QUIC_LOG(INFO) << "SUBSCRIBE_DONE subscribe ID mismatch";
return false;
}
if (cast.status_code != subscribe_done_.status_code) {
QUIC_LOG(INFO) << "SUBSCRIBE_DONE status code mismatch";
return false;
}
if (cast.stream_count != subscribe_done_.stream_count) {
QUIC_LOG(INFO) << "SUBSCRIBE_DONE stream count mismatch";
return false;
}
if (cast.reason_phrase != subscribe_done_.reason_phrase) {
QUIC_LOG(INFO) << "SUBSCRIBE_DONE reason phrase mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("vvvv--"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(subscribe_done_);
}
private:
uint8_t raw_packet_[9] = {
0x0b, 0x00, 0x06, 0x02, 0x02, // subscribe_id = 2, error_code = 2,
0x05, // stream_count = 5
0x02, 0x68, 0x69, // reason_phrase = "hi"
};
MoqtSubscribeDone subscribe_done_ = {
/*subscribe_id=*/2,
/*error_code=*/SubscribeDoneCode::kTrackEnded,
/*stream_count=*/5,
/*reason_phrase=*/"hi",
};
};
class QUICHE_NO_EXPORT SubscribeUpdateMessage : public TestMessageBase {
public:
SubscribeUpdateMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtSubscribeUpdate>(values);
if (cast.request_id != subscribe_update_.request_id) {
QUIC_LOG(INFO) << "SUBSCRIBE_UPDATE subscribe ID mismatch";
return false;
}
if (cast.start != subscribe_update_.start) {
QUIC_LOG(INFO) << "SUBSCRIBE_UPDATE start group mismatch";
return false;
}
if (cast.end_group != subscribe_update_.end_group) {
QUIC_LOG(INFO) << "SUBSCRIBE_UPDATE end group mismatch";
return false;
}
if (cast.subscriber_priority != subscribe_update_.subscriber_priority) {
QUIC_LOG(INFO) << "SUBSCRIBE_UPDATE subscriber priority mismatch";
return false;
}
if (cast.forward != subscribe_update_.forward) {
QUIC_LOG(INFO) << "SUBSCRIBE_UPDATE forward mismatch";
return false;
}
if (cast.parameters != subscribe_update_.parameters) {
QUIC_LOG(INFO) << "SUBSCRIBE_UPDATE parameter mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("vvvv--vv--"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(subscribe_update_);
}
private:
uint8_t raw_packet_[13] = {
0x02, 0x00, 0x0a, 0x02, 0x03, 0x01, 0x05, // start and end sequences
0xaa, 0x01, // subscriber_priority, forward
0x01, // 1 parameter
0x02, 0x67, 0x10, // delivery_timeout = 10000
};
MoqtSubscribeUpdate subscribe_update_ = {
/*request_id=*/2,
/*start=*/Location(3, 1),
/*end_group=*/4,
/*subscriber_priority=*/0xaa,
/*forward=*/true,
VersionSpecificParameters(quic::QuicTimeDelta::FromMilliseconds(10000),
quic::QuicTimeDelta::Infinite()),
};
};
class QUICHE_NO_EXPORT AnnounceMessage : public TestMessageBase {
public:
AnnounceMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtAnnounce>(values);
if (cast.track_namespace != announce_.track_namespace) {
QUIC_LOG(INFO) << "ANNOUNCE MESSAGE track namespace mismatch";
return false;
}
if (cast.parameters != announce_.parameters) {
QUIC_LOG(INFO) << "ANNOUNCE MESSAGE parameter mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("vv---vvv-----"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(announce_);
}
private:
uint8_t raw_packet_[16] = {
0x06, 0x00, 0x0d, 0x01, 0x03, 0x66, 0x6f,
0x6f, // track_namespace = "foo"
0x01, // 1 parameter
0x01, 0x05, 0x03, 0x00, 0x62, 0x61, 0x72, // authorization_tag = "bar"
};
MoqtAnnounce announce_ = {
TrackNamespace{"foo"},
VersionSpecificParameters(AuthTokenType::kOutOfBand, "bar"),
};
};
class QUICHE_NO_EXPORT AnnounceOkMessage : public TestMessageBase {
public:
AnnounceOkMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtAnnounceOk>(values);
if (cast.track_namespace != announce_ok_.track_namespace) {
QUIC_LOG(INFO) << "ANNOUNCE OK MESSAGE track namespace mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("vv---"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(announce_ok_);
}
private:
uint8_t raw_packet_[8] = {
0x07, 0x00, 0x05, 0x01,
0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
};
MoqtAnnounceOk announce_ok_ = {
TrackNamespace("foo"),
};
};
class QUICHE_NO_EXPORT AnnounceErrorMessage : public TestMessageBase {
public:
AnnounceErrorMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtAnnounceError>(values);
if (cast.track_namespace != announce_error_.track_namespace) {
QUIC_LOG(INFO) << "ANNOUNCE ERROR track namespace mismatch";
return false;
}
if (cast.error_code != announce_error_.error_code) {
QUIC_LOG(INFO) << "ANNOUNCE ERROR error code mismatch";
return false;
}
if (cast.reason_phrase != announce_error_.reason_phrase) {
QUIC_LOG(INFO) << "ANNOUNCE ERROR reason phrase mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("vv---vv---"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(announce_error_);
}
private:
uint8_t raw_packet_[13] = {
0x08, 0x00, 0x0a, 0x01,
0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x03, // error_code = 3
0x03, 0x62, 0x61, 0x72, // reason_phrase = "bar"
};
MoqtAnnounceError announce_error_ = {
TrackNamespace("foo"),
RequestErrorCode::kNotSupported,
/*reason_phrase=*/"bar",
};
};
class QUICHE_NO_EXPORT AnnounceCancelMessage : public TestMessageBase {
public:
AnnounceCancelMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtAnnounceCancel>(values);
if (cast.track_namespace != announce_cancel_.track_namespace) {
QUIC_LOG(INFO) << "ANNOUNCE CANCEL track namespace mismatch";
return false;
}
if (cast.error_code != announce_cancel_.error_code) {
QUIC_LOG(INFO) << "ANNOUNCE CANCEL error code mismatch";
return false;
}
if (cast.reason_phrase != announce_cancel_.reason_phrase) {
QUIC_LOG(INFO) << "ANNOUNCE CANCEL reason phrase mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("vv---vv---"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(announce_cancel_);
}
private:
uint8_t raw_packet_[13] = {
0x0c, 0x00, 0x0a, 0x01,
0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x03, // error_code = 3
0x03, 0x62, 0x61, 0x72, // reason_phrase = "bar"
};
MoqtAnnounceCancel announce_cancel_ = {
TrackNamespace("foo"),
RequestErrorCode::kNotSupported,
/*reason_phrase=*/"bar",
};
};
class QUICHE_NO_EXPORT TrackStatusRequestMessage : public TestMessageBase {
public:
TrackStatusRequestMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtTrackStatusRequest>(values);
if (cast.full_track_name != track_status_request_.full_track_name) {
QUIC_LOG(INFO) << "TRACK STATUS REQUEST track name mismatch";
return false;
}
if (cast.parameters != track_status_request_.parameters) {
QUIC_LOG(INFO) << "TRACK STATUS REQUEST parameter mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("vv---v----vvv-----"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(track_status_request_);
}
private:
uint8_t raw_packet_[21] = {
0x0d, 0x00, 0x12, 0x01, 0x03, 0x66, 0x6f,
0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x01, // 1 parameter
0x01, 0x05, 0x03, 0x00, 0x62, 0x61, 0x72, // authorization_tag = "bar"
};
MoqtTrackStatusRequest track_status_request_ = {
FullTrackName("foo", "abcd"),
VersionSpecificParameters(AuthTokenType::kOutOfBand, "bar"),
};
};
class QUICHE_NO_EXPORT UnannounceMessage : public TestMessageBase {
public:
UnannounceMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtUnannounce>(values);
if (cast.track_namespace != unannounce_.track_namespace) {
QUIC_LOG(INFO) << "UNANNOUNCE track namespace mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("vv---"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(unannounce_);
}
private:
uint8_t raw_packet_[8] = {
0x09, 0x00, 0x05, 0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace
};
MoqtUnannounce unannounce_ = {
TrackNamespace("foo"),
};
};
class QUICHE_NO_EXPORT TrackStatusMessage : public TestMessageBase {
public:
TrackStatusMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtTrackStatus>(values);
if (cast.full_track_name != track_status_.full_track_name) {
QUIC_LOG(INFO) << "TRACK STATUS track name mismatch";
return false;
}
if (cast.status_code != track_status_.status_code) {
QUIC_LOG(INFO) << "TRACK STATUS code mismatch";
return false;
}
if (cast.last_group != track_status_.last_group) {
QUIC_LOG(INFO) << "TRACK STATUS last group mismatch";
return false;
}
if (cast.last_object != track_status_.last_object) {
QUIC_LOG(INFO) << "TRACK STATUS last object mismatch";
return false;
}
if (cast.parameters != track_status_.parameters) {
QUIC_LOG(INFO) << "TRACK STATUS parameters mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("vv---v----vvvvv--v--"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(track_status_);
}
private:
uint8_t raw_packet_[23] = {
0x0e, 0x00, 0x14, 0x01, 0x03,
0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x00, 0x0c, 0x14, // status, last_group, last_object
0x02, // 2 parameters
0x02, 0x67, 0x10, // Delivery Timeout = 10000
0x04, 0x67, 0x10, // Max Cache Duration = 10000
};
MoqtTrackStatus track_status_ = {
FullTrackName("foo", "abcd"),
/*status_code=*/MoqtTrackStatusCode::kInProgress,
/*last_group=*/12,
/*last_object=*/20,
VersionSpecificParameters(quic::QuicTimeDelta::FromMilliseconds(10000),
quic::QuicTimeDelta::FromMilliseconds(10000)),
};
};
class QUICHE_NO_EXPORT GoAwayMessage : public TestMessageBase {
public:
GoAwayMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtGoAway>(values);
if (cast.new_session_uri != goaway_.new_session_uri) {
QUIC_LOG(INFO) << "GOAWAY full track name mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("v---"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(goaway_);
}
private:
uint8_t raw_packet_[7] = {
0x10, 0x00, 0x04, 0x03, 0x66, 0x6f, 0x6f,
};
MoqtGoAway goaway_ = {
/*new_session_uri=*/"foo",
};
};
class QUICHE_NO_EXPORT SubscribeAnnouncesMessage : public TestMessageBase {
public:
SubscribeAnnouncesMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtSubscribeAnnounces>(values);
if (cast.track_namespace != subscribe_namespace_.track_namespace) {
QUIC_LOG(INFO) << "SUBSCRIBE_NAMESPACE track namespace mismatch";
return false;
}
if (cast.parameters != subscribe_namespace_.parameters) {
QUIC_LOG(INFO) << "SUBSCRIBE_NAMESPACE parameters mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("vv---vvv-----"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(subscribe_namespace_);
}
private:
uint8_t raw_packet_[16] = {
0x11, 0x00, 0x0d, 0x01, 0x03, 0x66, 0x6f, 0x6f, // namespace = "foo"
0x01, // 1 parameter
0x01, 0x05, 0x03, 0x00, 0x62, 0x61, 0x72, // authorization_tag = "bar"
};
MoqtSubscribeAnnounces subscribe_namespace_ = {
TrackNamespace("foo"),
VersionSpecificParameters(AuthTokenType::kOutOfBand, "bar"),
};
};
class QUICHE_NO_EXPORT SubscribeAnnouncesOkMessage : public TestMessageBase {
public:
SubscribeAnnouncesOkMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtSubscribeAnnouncesOk>(values);
if (cast.track_namespace != subscribe_namespace_ok_.track_namespace) {
QUIC_LOG(INFO) << "SUBSCRIBE_NAMESPACE_OK track namespace mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("vv---"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(subscribe_namespace_ok_);
}
private:
uint8_t raw_packet_[8] = {
0x12, 0x00, 0x05, 0x01, 0x03, 0x66, 0x6f, 0x6f, // namespace = "foo"
};
MoqtSubscribeAnnouncesOk subscribe_namespace_ok_ = {
TrackNamespace("foo"),
};
};
class QUICHE_NO_EXPORT SubscribeAnnouncesErrorMessage : public TestMessageBase {
public:
SubscribeAnnouncesErrorMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtSubscribeAnnouncesError>(values);
if (cast.track_namespace != subscribe_namespace_error_.track_namespace) {
QUIC_LOG(INFO) << "SUBSCRIBE_NAMESPACE_ERROR track namespace mismatch";
return false;
}
if (cast.error_code != subscribe_namespace_error_.error_code) {
QUIC_LOG(INFO) << "SUBSCRIBE_NAMESPACE_ERROR error code mismatch";
return false;
}
if (cast.reason_phrase != subscribe_namespace_error_.reason_phrase) {
QUIC_LOG(INFO) << "SUBSCRIBE_NAMESPACE_ERROR reason phrase mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("vv---vv---"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(subscribe_namespace_error_);
}
private:
uint8_t raw_packet_[13] = {
0x13, 0x00, 0x0a, 0x01,
0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x01, // error_code = 1
0x03, 0x62, 0x61, 0x72, // reason_phrase = "bar"
};
MoqtSubscribeAnnouncesError subscribe_namespace_error_ = {
TrackNamespace("foo"),
/*error_code=*/RequestErrorCode::kUnauthorized,
/*reason_phrase=*/"bar",
};
};
class QUICHE_NO_EXPORT UnsubscribeAnnouncesMessage : public TestMessageBase {
public:
UnsubscribeAnnouncesMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtUnsubscribeAnnounces>(values);
if (cast.track_namespace != unsubscribe_namespace_.track_namespace) {
QUIC_LOG(INFO) << "UNSUBSCRIBE_NAMESPACE track namespace mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("vv---"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(unsubscribe_namespace_);
}
private:
uint8_t raw_packet_[8] = {
0x14, 0x00, 0x05, 0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace
};
MoqtUnsubscribeAnnounces unsubscribe_namespace_ = {
TrackNamespace("foo"),
};
};
class QUICHE_NO_EXPORT MaxRequestIdMessage : public TestMessageBase {
public:
MaxRequestIdMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtMaxRequestId>(values);
if (cast.max_request_id != max_request_id_.max_request_id) {
QUIC_LOG(INFO) << "MAX_REQUEST_ID mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("v"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(max_request_id_);
}
private:
uint8_t raw_packet_[4] = {
0x15,
0x00,
0x01,
0x0b,
};
MoqtMaxRequestId max_request_id_ = {
/*max_request_id =*/11,
};
};
class QUICHE_NO_EXPORT FetchMessage : public TestMessageBase {
public:
FetchMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtFetch>(values);
if (cast.fetch_id != fetch_.fetch_id) {
QUIC_LOG(INFO) << "FETCH fetch_id mismatch";
return false;
}
if (cast.subscriber_priority != fetch_.subscriber_priority) {
QUIC_LOG(INFO) << "FETCH subscriber_priority mismatch";
return false;
}
if (cast.group_order != fetch_.group_order) {
QUIC_LOG(INFO) << "FETCH group_order mismatch";
return false;
}
if (cast.joining_fetch.has_value() != fetch_.joining_fetch.has_value()) {
QUIC_LOG(INFO) << "FETCH type mismatch";
return false;
}
if (cast.joining_fetch.has_value()) {
if (cast.joining_fetch->joining_subscribe_id !=
fetch_.joining_fetch->joining_subscribe_id) {
QUIC_LOG(INFO) << "FETCH joining_subscribe_id mismatch";
return false;
}
if (cast.joining_fetch->preceding_group_offset !=
fetch_.joining_fetch->preceding_group_offset) {
QUIC_LOG(INFO) << "FETCH preceding_group_offset mismatch";
return false;
}
} else {
if (cast.full_track_name != fetch_.full_track_name) {
QUIC_LOG(INFO) << "FETCH full_track_name mismatch";
return false;
}
if (cast.start_object != fetch_.start_object) {
QUIC_LOG(INFO) << "FETCH start_object mismatch";
return false;
}
if (cast.end_group != fetch_.end_group) {
QUIC_LOG(INFO) << "FETCH end_group mismatch";
return false;
}
if (cast.end_object != fetch_.end_object) {
QUIC_LOG(INFO) << "FETCH end_object mismatch";
return false;
}
}
if (cast.parameters != fetch_.parameters) {
QUIC_LOG(INFO) << "FETCH parameters mismatch";
return false;
}
return true;
}
void ExpandVarints() override {
ExpandVarintsImpl("v--vvv---v---vvvvvv-----");
}
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(fetch_);
}
void SetEndObject(uint64_t group, std::optional<uint64_t> object) {
// Avoid varint nonsense.
QUICHE_CHECK(group < 64);
QUICHE_CHECK(!object.has_value() || *object < 64);
fetch_.end_group = group;
fetch_.end_object = object;
raw_packet_[18] = group;
raw_packet_[19] = object.has_value() ? (*object + 1) : 0;
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
void SetGroupOrder(uint8_t group_order) {
raw_packet_[5] = static_cast<uint8_t>(group_order);
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
private:
uint8_t raw_packet_[28] = {
0x16, 0x00, 0x19,
0x01, // fetch_id = 1
0x02, // priority = kHigh
0x01, // group_order = kAscending
0x01, // type = kStandalone
0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x03, 0x62, 0x61, 0x72, // track_name = "bar"
0x01, 0x02, // start_object = 1, 2
0x05, 0x07, // end_object = 5, 6
0x01, 0x01, 0x05, 0x03, 0x00, 0x62, 0x61, 0x7a, // parameters = "baz"
};
MoqtFetch fetch_ = {
/*fetch_id =*/1,
/*subscriber_priority=*/2,
/*group_order=*/MoqtDeliveryOrder::kAscending,
/*joining_fetch=*/std::optional<JoiningFetch>(),
FullTrackName("foo", "bar"),
/*start_object=*/Location{1, 2},
/*end_group=*/5,
/*end_object=*/6,
VersionSpecificParameters(AuthTokenType::kOutOfBand, "baz"),
};
};
// This is not used in the parameterized Parser and Framer tests, because it
// does not have its own MoqtMessageType.
class QUICHE_NO_EXPORT JoiningFetchMessage : public TestMessageBase {
public:
JoiningFetchMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtFetch>(values);
if (cast.fetch_id != fetch_.fetch_id) {
QUIC_LOG(INFO) << "FETCH fetch_id mismatch";
return false;
}
if (cast.subscriber_priority != fetch_.subscriber_priority) {
QUIC_LOG(INFO) << "FETCH subscriber_priority mismatch";
return false;
}
if (cast.group_order != fetch_.group_order) {
QUIC_LOG(INFO) << "FETCH group_order mismatch";
return false;
}
if (cast.joining_fetch.has_value() != fetch_.joining_fetch.has_value()) {
QUIC_LOG(INFO) << "FETCH type mismatch";
return false;
}
if (cast.joining_fetch.has_value()) {
if (cast.joining_fetch->joining_subscribe_id !=
fetch_.joining_fetch->joining_subscribe_id) {
QUIC_LOG(INFO) << "FETCH joining_subscribe_id mismatch";
return false;
}
if (cast.joining_fetch->preceding_group_offset !=
fetch_.joining_fetch->preceding_group_offset) {
QUIC_LOG(INFO) << "FETCH preceding_group_offset mismatch";
return false;
}
} else {
if (cast.full_track_name != fetch_.full_track_name) {
QUIC_LOG(INFO) << "FETCH full_track_name mismatch";
return false;
}
if (cast.start_object != fetch_.start_object) {
QUIC_LOG(INFO) << "FETCH start_object mismatch";
return false;
}
if (cast.end_group != fetch_.end_group) {
QUIC_LOG(INFO) << "FETCH end_group mismatch";
return false;
}
if (cast.end_object != fetch_.end_object) {
QUIC_LOG(INFO) << "FETCH end_object mismatch";
return false;
}
}
if (cast.parameters != fetch_.parameters) {
QUIC_LOG(INFO) << "FETCH parameters mismatch";
return false;
}
return true;
}
void ExpandVarints() override {
ExpandVarintsImpl("v--vvv---v---vvvvvv-----");
}
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(fetch_);
}
void SetGroupOrder(uint8_t group_order) {
raw_packet_[5] = static_cast<uint8_t>(group_order);
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
private:
uint8_t raw_packet_[17] = {
0x16, 0x00, 0x0e,
0x01, // fetch_id = 1
0x02, // priority = kHigh
0x01, // group_order = kAscending
0x02, // type = kJoining
0x02, 0x02, // joining_subscribe_id = 2, 2 groups
0x01, 0x01, 0x05, 0x03, 0x00, 0x62, 0x61, 0x7a, // parameters = "baz"
};
MoqtFetch fetch_ = {
/*fetch_id =*/1,
/*subscriber_priority=*/2,
/*group_order=*/MoqtDeliveryOrder::kAscending,
/*joining_fetch=*/JoiningFetch{2, 2},
/* the next four are ignored for joining fetches*/
FullTrackName("foo", "bar"),
/*start_object=*/Location{1, 2},
/*end_group=*/5,
/*end_object=*/6,
VersionSpecificParameters(AuthTokenType::kOutOfBand, "baz"),
};
};
class QUICHE_NO_EXPORT FetchCancelMessage : public TestMessageBase {
public:
FetchCancelMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtFetchCancel>(values);
if (cast.subscribe_id != fetch_cancel_.subscribe_id) {
QUIC_LOG(INFO) << "FETCH_CANCEL subscribe_id mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("v"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(fetch_cancel_);
}
private:
uint8_t raw_packet_[4] = {
0x17, 0x00, 0x01,
0x01, // subscribe_id = 1
};
MoqtFetchCancel fetch_cancel_ = {
/*subscribe_id =*/1,
};
};
class QUICHE_NO_EXPORT FetchOkMessage : public TestMessageBase {
public:
FetchOkMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtFetchOk>(values);
if (cast.subscribe_id != fetch_ok_.subscribe_id) {
QUIC_LOG(INFO) << "FETCH_OK subscribe_id mismatch";
return false;
}
if (cast.group_order != fetch_ok_.group_order) {
QUIC_LOG(INFO) << "FETCH_OK group_order mismatch";
return false;
}
if (cast.largest_id != fetch_ok_.largest_id) {
QUIC_LOG(INFO) << "FETCH_OK start_object mismatch";
return false;
}
if (cast.parameters != fetch_ok_.parameters) {
QUIC_LOG(INFO) << "FETCH_OK parameters mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("v-vvvvv---"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(fetch_ok_);
}
private:
uint8_t raw_packet_[11] = {
0x18, 0x00, 0x08,
0x01, // subscribe_id = 1
0x01, // group_order = kAscending
0x05, 0x04, // largest_object = 5, 4
0x01, 0x04, 0x67, 0x10, // MaxCacheDuration = 10000
};
MoqtFetchOk fetch_ok_ = {
/*subscribe_id =*/1,
/*group_order=*/MoqtDeliveryOrder::kAscending,
/*start_object=*/Location{5, 4},
VersionSpecificParameters(quic::QuicTimeDelta::Infinite(),
quic::QuicTimeDelta::FromMilliseconds(10000)),
};
};
class QUICHE_NO_EXPORT FetchErrorMessage : public TestMessageBase {
public:
FetchErrorMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtFetchError>(values);
if (cast.subscribe_id != fetch_error_.subscribe_id) {
QUIC_LOG(INFO) << "FETCH_ERROR subscribe_id mismatch";
return false;
}
if (cast.error_code != fetch_error_.error_code) {
QUIC_LOG(INFO) << "FETCH_ERROR group_order mismatch";
return false;
}
if (cast.reason_phrase != fetch_error_.reason_phrase) {
QUIC_LOG(INFO) << "FETCH_ERROR reason_phrase mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("vvv---"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(fetch_error_);
}
private:
uint8_t raw_packet_[9] = {
0x19, 0x00, 0x06,
0x01, // subscribe_id = 1
0x01, // error_code = kUnauthorized
0x03, 0x62, 0x61, 0x72, // reason_phrase = "bar"
};
MoqtFetchError fetch_error_ = {
/*subscribe_id =*/1,
/*error_code=*/RequestErrorCode::kUnauthorized,
/*reason_phrase=*/"bar",
};
};
class QUICHE_NO_EXPORT RequestsBlockedMessage : public TestMessageBase {
public:
RequestsBlockedMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtRequestsBlocked>(values);
if (cast.max_request_id != requests_blocked_.max_request_id) {
QUIC_LOG(INFO) << "SUBSCRIBES_BLOCKED max_subscribe_id mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("v"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(requests_blocked_);
}
private:
uint8_t raw_packet_[4] = {
0x1a, 0x00, 0x01,
0x0b, // max_request_id = 11
};
MoqtRequestsBlocked requests_blocked_ = {
/*max_request_id=*/11,
};
};
class QUICHE_NO_EXPORT ObjectAckMessage : public TestMessageBase {
public:
ObjectAckMessage() : TestMessageBase() {
SetWireImage(raw_packet_, sizeof(raw_packet_));
}
bool EqualFieldValues(MessageStructuredData& values) const override {
auto cast = std::get<MoqtObjectAck>(values);
if (cast.subscribe_id != object_ack_.subscribe_id) {
QUIC_LOG(INFO) << "OBJECT_ACK subscribe ID mismatch";
return false;
}
if (cast.group_id != object_ack_.group_id) {
QUIC_LOG(INFO) << "OBJECT_ACK group ID mismatch";
return false;
}
if (cast.object_id != object_ack_.object_id) {
QUIC_LOG(INFO) << "OBJECT_ACK object ID mismatch";
return false;
}
if (cast.delta_from_deadline != object_ack_.delta_from_deadline) {
QUIC_LOG(INFO) << "OBJECT_ACK delta from deadline mismatch";
return false;
}
return true;
}
void ExpandVarints() override { ExpandVarintsImpl("vvvv"); }
MessageStructuredData structured_data() const override {
return TestMessageBase::MessageStructuredData(object_ack_);
}
private:
uint8_t raw_packet_[8] = {
0x71, 0x84, 0x00, 0x04, // type
0x01, 0x10, 0x20, // subscribe ID, group, object
0x20, // 0x10 time delta
};
MoqtObjectAck object_ack_ = {
/*subscribe_id=*/0x01,
/*group_id=*/0x10,
/*object_id=*/0x20,
/*delta_from_deadline=*/quic::QuicTimeDelta::FromMicroseconds(0x10),
};
};
// Factory function for test messages.
static inline std::unique_ptr<TestMessageBase> CreateTestMessage(
MoqtMessageType message_type, bool is_webtrans) {
switch (message_type) {
case MoqtMessageType::kSubscribe:
return std::make_unique<SubscribeMessage>();
case MoqtMessageType::kSubscribeOk:
return std::make_unique<SubscribeOkMessage>();
case MoqtMessageType::kSubscribeError:
return std::make_unique<SubscribeErrorMessage>();
case MoqtMessageType::kUnsubscribe:
return std::make_unique<UnsubscribeMessage>();
case MoqtMessageType::kSubscribeDone:
return std::make_unique<SubscribeDoneMessage>();
case MoqtMessageType::kSubscribeUpdate:
return std::make_unique<SubscribeUpdateMessage>();
case MoqtMessageType::kAnnounce:
return std::make_unique<AnnounceMessage>();
case MoqtMessageType::kAnnounceOk:
return std::make_unique<AnnounceOkMessage>();
case MoqtMessageType::kAnnounceError:
return std::make_unique<AnnounceErrorMessage>();
case MoqtMessageType::kAnnounceCancel:
return std::make_unique<AnnounceCancelMessage>();
case MoqtMessageType::kTrackStatusRequest:
return std::make_unique<TrackStatusRequestMessage>();
case MoqtMessageType::kUnannounce:
return std::make_unique<UnannounceMessage>();
case MoqtMessageType::kTrackStatus:
return std::make_unique<TrackStatusMessage>();
case MoqtMessageType::kGoAway:
return std::make_unique<GoAwayMessage>();
case MoqtMessageType::kSubscribeAnnounces:
return std::make_unique<SubscribeAnnouncesMessage>();
case MoqtMessageType::kSubscribeAnnouncesOk:
return std::make_unique<SubscribeAnnouncesOkMessage>();
case MoqtMessageType::kSubscribeAnnouncesError:
return std::make_unique<SubscribeAnnouncesErrorMessage>();
case MoqtMessageType::kUnsubscribeAnnounces:
return std::make_unique<UnsubscribeAnnouncesMessage>();
case MoqtMessageType::kMaxRequestId:
return std::make_unique<MaxRequestIdMessage>();
case MoqtMessageType::kFetch:
return std::make_unique<FetchMessage>();
case MoqtMessageType::kFetchCancel:
return std::make_unique<FetchCancelMessage>();
case MoqtMessageType::kFetchOk:
return std::make_unique<FetchOkMessage>();
case MoqtMessageType::kFetchError:
return std::make_unique<FetchErrorMessage>();
case MoqtMessageType::kRequestsBlocked:
return std::make_unique<RequestsBlockedMessage>();
case MoqtMessageType::kObjectAck:
return std::make_unique<ObjectAckMessage>();
case MoqtMessageType::kClientSetup:
return std::make_unique<ClientSetupMessage>(is_webtrans);
case MoqtMessageType::kServerSetup:
return std::make_unique<ServerSetupMessage>(is_webtrans);
default:
return nullptr;
}
}
static inline std::unique_ptr<TestMessageBase> CreateTestDataStream(
MoqtDataStreamType type) {
switch (type) {
case MoqtDataStreamType::kStreamHeaderSubgroup:
return std::make_unique<StreamHeaderSubgroupMessage>();
case MoqtDataStreamType::kStreamHeaderFetch:
return std::make_unique<StreamHeaderFetchMessage>();
case MoqtDataStreamType::kPadding:
return nullptr;
}
return nullptr;
}
} // namespace moqt::test
#endif // QUICHE_QUIC_MOQT_TEST_TOOLS_MOQT_TEST_MESSAGE_H_