| // 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.request_id != unsubscribe_.request_id) { | 
 |       QUIC_LOG(INFO) << "UNSUBSCRIBE request 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,  // request_id = 3 | 
 |   }; | 
 |  | 
 |   MoqtUnsubscribe unsubscribe_ = { | 
 |       /*request_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.request_id != subscribe_done_.request_id) { | 
 |       QUIC_LOG(INFO) << "SUBSCRIBE_DONE request 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.error_reason != subscribe_done_.error_reason) { | 
 |       QUIC_LOG(INFO) << "SUBSCRIBE_DONE error reason 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,  // request_id = 2, error_code = 2, | 
 |       0x05,                          // stream_count = 5 | 
 |       0x02, 0x68, 0x69,              // error_reason = "hi" | 
 |   }; | 
 |  | 
 |   MoqtSubscribeDone subscribe_done_ = { | 
 |       /*request_id=*/2, | 
 |       /*error_code=*/SubscribeDoneCode::kTrackEnded, | 
 |       /*stream_count=*/5, | 
 |       /*error_reason=*/"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.fetch != fetch_.fetch) { | 
 |       QUIC_LOG(INFO) << "FETCH 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); | 
 |     std::get<StandaloneFetch>(fetch_.fetch).end_group = group; | 
 |     std::get<StandaloneFetch>(fetch_.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, | 
 |       /*fetch =*/ | 
 |       StandaloneFetch{ | 
 |           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 RelativeJoiningFetchMessage : public TestMessageBase { | 
 |  public: | 
 |   RelativeJoiningFetchMessage() : 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.fetch != fetch_.fetch) { | 
 |       QUIC_LOG(INFO) << "FETCH 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 = kRelativeJoining | 
 |       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, | 
 |       /*fetch=*/JoiningFetchRelative{2, 2}, | 
 |       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 AbsoluteJoiningFetchMessage : public TestMessageBase { | 
 |  public: | 
 |   AbsoluteJoiningFetchMessage() : 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.fetch != fetch_.fetch) { | 
 |       QUIC_LOG(INFO) << "FETCH 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 | 
 |       0x03,        // type = kAbsoluteJoining | 
 |       0x02, 0x02,  // joining_subscribe_id = 2, group_id = 2 | 
 |       0x01, 0x01, 0x05, 0x03, 0x00, 0x62, 0x61, 0x7a,  // parameters = "baz" | 
 |   }; | 
 |  | 
 |   MoqtFetch fetch_ = { | 
 |       /*fetch_id =*/1, | 
 |       /*subscriber_priority=*/2, | 
 |       /*group_order=*/MoqtDeliveryOrder::kAscending, | 
 |       /*fetch=*/JoiningFetchAbsolute{2, 2}, | 
 |       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_ |