Add TRACK_NAMESPACE_PREFIX message parameter support to MoQT, framer and parser only. PiperOrigin-RevId: 994759228
diff --git a/quiche/quic/moqt/moqt_framer.cc b/quiche/quic/moqt/moqt_framer.cc index e947a8a..c7e8af3 100644 --- a/quiche/quic/moqt/moqt_framer.cc +++ b/quiche/quic/moqt/moqt_framer.cc
@@ -210,6 +210,31 @@ const SubscriptionFilter& filter_; }; +class WireTrackNamespace { + public: + WireTrackNamespace(const TrackNamespace& name) : namespace_(name) {} + + size_t GetLengthOnWire() { + absl::FixedArray<absl::string_view> tuple(namespace_.tuple().begin(), + namespace_.tuple().end()); + return quiche::ComputeLengthOnWire( + WireMoqVarInt(namespace_.number_of_elements()), + WireSpan<WireStringWithMoqVarIntLength, absl::string_view>( + absl::MakeSpan(tuple))); + } + absl::Status SerializeIntoWriter(quiche::QuicheDataWriter& writer) { + absl::FixedArray<absl::string_view> tuple(namespace_.tuple().begin(), + namespace_.tuple().end()); + return quiche::SerializeIntoWriter( + writer, WireMoqVarInt(namespace_.number_of_elements()), + WireSpan<WireStringWithMoqVarIntLength, absl::string_view>( + absl::MakeSpan(tuple))); + } + + private: + const TrackNamespace& namespace_; +}; + uint64_t TimeDeltaToMilliseconds(const quic::QuicTimeDelta& time_delta) { if (time_delta == quic::QuicTimeDelta::Infinite()) { return 0ULL; @@ -255,6 +280,9 @@ if (parameters_.new_group_request.has_value()) { ++num_parameters_; } + if (parameters_.track_namespace_prefix.has_value()) { + ++num_parameters_; + } if (parameters_.oack_window_size.has_value()) { ++num_parameters_; } @@ -333,6 +361,11 @@ *parameters_.new_group_request) .GetLengthOnWire(); } + if (parameters_.track_namespace_prefix.has_value()) { + length += quiche::ComputeLengthOnWire( + WireMoqVarInt(key_delta(MessageParameter::kTrackNamespacePrefix)), + WireTrackNamespace(*parameters_.track_namespace_prefix)); + } if (parameters_.oack_window_size.has_value()) { length += WireKeyVarIntPair(key_delta(MessageParameter::kOackWindowSize), @@ -424,6 +457,12 @@ WireKeyVarIntPair(key_delta(MessageParameter::kNewGroupRequest), *parameters_.new_group_request))); } + if (parameters_.track_namespace_prefix.has_value()) { + QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter( + writer, + WireMoqVarInt(key_delta(MessageParameter::kTrackNamespacePrefix)), + WireTrackNamespace(*parameters_.track_namespace_prefix))); + } if (parameters_.oack_window_size.has_value()) { QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter( writer, @@ -438,31 +477,6 @@ uint64_t num_parameters_; }; -class WireTrackNamespace { - public: - WireTrackNamespace(const TrackNamespace& name) : namespace_(name) {} - - size_t GetLengthOnWire() { - absl::FixedArray<absl::string_view> tuple(namespace_.tuple().begin(), - namespace_.tuple().end()); - return quiche::ComputeLengthOnWire( - WireMoqVarInt(namespace_.number_of_elements()), - WireSpan<WireStringWithMoqVarIntLength, absl::string_view>( - absl::MakeSpan(tuple))); - } - absl::Status SerializeIntoWriter(quiche::QuicheDataWriter& writer) { - absl::FixedArray<absl::string_view> tuple(namespace_.tuple().begin(), - namespace_.tuple().end()); - return quiche::SerializeIntoWriter( - writer, WireMoqVarInt(namespace_.number_of_elements()), - WireSpan<WireStringWithMoqVarIntLength, absl::string_view>( - absl::MakeSpan(tuple))); - } - - private: - const TrackNamespace& namespace_; -}; - class WireFullTrackName { public: WireFullTrackName(const FullTrackName& name) : name_(name) {}
diff --git a/quiche/quic/moqt/moqt_key_value_pair.cc b/quiche/quic/moqt/moqt_key_value_pair.cc index 7422ee6..acee90e 100644 --- a/quiche/quic/moqt/moqt_key_value_pair.cc +++ b/quiche/quic/moqt/moqt_key_value_pair.cc
@@ -102,6 +102,9 @@ if (other.new_group_request.has_value()) { new_group_request = other.new_group_request; } + if (other.track_namespace_prefix.has_value()) { + track_namespace_prefix = other.track_namespace_prefix; + } if (other.oack_window_size.has_value()) { oack_window_size = other.oack_window_size; }
diff --git a/quiche/quic/moqt/moqt_key_value_pair.h b/quiche/quic/moqt/moqt_key_value_pair.h index ef82924..aafb24c 100644 --- a/quiche/quic/moqt/moqt_key_value_pair.h +++ b/quiche/quic/moqt/moqt_key_value_pair.h
@@ -17,6 +17,7 @@ #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "quiche/quic/core/quic_time.h" +#include "quiche/quic/moqt/moqt_names.h" #include "quiche/quic/moqt/moqt_priority.h" #include "quiche/quic/moqt/moqt_types.h" #include "quiche/common/platform/api/quiche_export.h" @@ -203,6 +204,7 @@ kSubscriptionFilter = 0x21, kGroupOrder = 0x22, kNewGroupRequest = 0x32, + kTrackNamespacePrefix = 0x34, kOackWindowSize = 0xbbF1438, }; @@ -246,6 +248,7 @@ std::optional<SubscriptionFilter> subscription_filter; std::optional<MoqtDeliveryOrder> group_order; std::optional<uint64_t> new_group_request; + std::optional<TrackNamespace> track_namespace_prefix; // QUICHE-specific parameters. std::optional<quic::QuicTimeDelta> oack_window_size;
diff --git a/quiche/quic/moqt/moqt_key_value_pair_test.cc b/quiche/quic/moqt/moqt_key_value_pair_test.cc index d13d4c6..cf30e29 100644 --- a/quiche/quic/moqt/moqt_key_value_pair_test.cc +++ b/quiche/quic/moqt/moqt_key_value_pair_test.cc
@@ -10,6 +10,7 @@ #include "absl/status/status.h" #include "absl/strings/string_view.h" #include "quiche/quic/core/quic_time.h" +#include "quiche/quic/moqt/moqt_names.h" #include "quiche/quic/moqt/moqt_priority.h" #include "quiche/quic/moqt/moqt_types.h" #include "quiche/quic/platform/api/quic_test.h" @@ -139,12 +140,14 @@ p1.set_forward(false); p1.subscriber_priority = 100; p1.new_group_request = 1; + p1.track_namespace_prefix = TrackNamespace({"foo"}); MessageParameters p2; p2.object_delivery_timeout = quic::QuicTimeDelta::FromMilliseconds(20); p2.authorization_tokens.push_back( AuthToken(AuthTokenType::kOutOfBand, "token")); p2.set_forward(true); p2.group_order = MoqtDeliveryOrder::kDescending; + p2.track_namespace_prefix = TrackNamespace({"bar"}); p1.Update(p2); EXPECT_EQ(p1.object_delivery_timeout, quic::QuicTimeDelta::FromMilliseconds(20)); @@ -156,6 +159,7 @@ EXPECT_EQ(p1.subscriber_priority, 100); EXPECT_EQ(p1.group_order, std::nullopt); EXPECT_EQ(p1.new_group_request, 1); + EXPECT_EQ(p1.track_namespace_prefix, TrackNamespace({"bar"})); } class TrackPropertiesTest : public quic::test::QuicTest {};
diff --git a/quiche/quic/moqt/moqt_parser.cc b/quiche/quic/moqt/moqt_parser.cc index fffa94a..49625e4 100644 --- a/quiche/quic/moqt/moqt_parser.cc +++ b/quiche/quic/moqt/moqt_parser.cc
@@ -193,6 +193,49 @@ return absl::OkStatus(); } +absl::Status ReadTrackNamespace(quic::QuicDataReader& reader, + TrackNamespace& track_namespace) { + QUICHE_DCHECK(track_namespace.empty()); + uint64_t num_elements; + if (!reader.ReadMoqVarInt(&num_elements)) { + return absl::InvalidArgumentError( + "Unable to parse the number of namespace elements"); + } + if (num_elements > kMaxNamespaceElements) { + return absl::InvalidArgumentError("Invalid number of namespace elements"); + } + if (num_elements == 0) { + return absl::OkStatus(); + } + absl::FixedArray<absl::string_view> elements(num_elements); + for (uint64_t i = 0; i < num_elements; ++i) { + if (!reader.ReadStringPieceMoqVarInt(&elements[i])) { + return absl::InvalidArgumentError( + "Namespace element shorter than specified"); + } + } + if (!track_namespace.Append(elements)) { + return absl::InvalidArgumentError("Track namespace is too large"); + } + return absl::OkStatus(); +} + +absl::Status ReadFullTrackName(quic::QuicDataReader& reader, + FullTrackName& full_track_name) { + QUICHE_DCHECK(!full_track_name.IsValid()); + TrackNamespace track_namespace; + QUICHE_RETURN_IF_ERROR(ReadTrackNamespace(reader, track_namespace)); + absl::string_view name; + if (!reader.ReadStringPieceMoqVarInt(&name)) { + return absl::InvalidArgumentError("Unable to parse track name"); + } + absl::StatusOr<FullTrackName> full_track_name_or = + FullTrackName::Create(std::move(track_namespace), std::string(name)); + QUICHE_RETURN_IF_ERROR(full_track_name_or.status()); + full_track_name = *std::move(full_track_name_or); + return absl::OkStatus(); +} + absl::StatusOr<MessageParameters> ParseMessageParameters( quic::QuicDataReader& reader) { MessageParameters params; @@ -351,6 +394,14 @@ } params.new_group_request = value64; break; + case MessageParameter::kTrackNamespacePrefix: + if (params.track_namespace_prefix.has_value()) { + return absl::InvalidArgumentError("Duplicate Message Parameter"); + } + params.track_namespace_prefix.emplace(); + QUICHE_RETURN_IF_ERROR( + ReadTrackNamespace(reader, *params.track_namespace_prefix)); + break; case MessageParameter::kOackWindowSize: if (params.oack_window_size.has_value()) { return absl::InvalidArgumentError("Duplicate Message Parameter"); @@ -987,49 +1038,6 @@ return object_ack; } -absl::Status MoqtControlMessageParser::ReadTrackNamespace( - quic::QuicDataReader& reader, TrackNamespace& track_namespace) const { - QUICHE_DCHECK(track_namespace.empty()); - uint64_t num_elements; - if (!reader.ReadMoqVarInt(&num_elements)) { - return absl::InvalidArgumentError( - "Unable to parse the number of namespace elements"); - } - if (num_elements > kMaxNamespaceElements) { - return absl::InvalidArgumentError("Invalid number of namespace elements"); - } - if (num_elements == 0) { - return absl::OkStatus(); - } - absl::FixedArray<absl::string_view> elements(num_elements); - for (uint64_t i = 0; i < num_elements; ++i) { - if (!reader.ReadStringPieceMoqVarInt(&elements[i])) { - return absl::InvalidArgumentError( - "Namespace element shorter than specified"); - } - } - if (!track_namespace.Append(elements)) { - return absl::InvalidArgumentError("Track namespace is too large"); - } - return absl::OkStatus(); -} - -absl::Status MoqtControlMessageParser::ReadFullTrackName( - quic::QuicDataReader& reader, FullTrackName& full_track_name) const { - QUICHE_DCHECK(!full_track_name.IsValid()); - TrackNamespace track_namespace; - QUICHE_RETURN_IF_ERROR(ReadTrackNamespace(reader, track_namespace)); - absl::string_view name; - if (!reader.ReadStringPieceMoqVarInt(&name)) { - return absl::InvalidArgumentError("Unable to parse track name"); - } - absl::StatusOr<FullTrackName> full_track_name_or = - FullTrackName::Create(std::move(track_namespace), std::string(name)); - QUICHE_RETURN_IF_ERROR(full_track_name_or.status()); - full_track_name = *std::move(full_track_name_or); - return absl::OkStatus(); -} - absl::Status MoqtControlMessageParser::FillAndValidateSetupOptions( const KeyValuePairList& in, SetupOptions& out) const { QUICHE_RETURN_IF_ERROR(out.FromKeyValuePairList(in));
diff --git a/quiche/quic/moqt/moqt_parser.h b/quiche/quic/moqt/moqt_parser.h index 2acb81b..4fdc359 100644 --- a/quiche/quic/moqt/moqt_parser.h +++ b/quiche/quic/moqt/moqt_parser.h
@@ -240,14 +240,6 @@ } private: - // Reads a TrackNamespace from the reader. Returns false if the namespace is - // too large. Sets a ParseError if the namespace is malformed. - absl::Status ReadTrackNamespace(quic::QuicDataReader& reader, - TrackNamespace& track_namespace) const; - // Reads a FullTrackName from the reader. Returns false if the name is too - // large. Sets a ParseError if the name is malformed. - absl::Status ReadFullTrackName(quic::QuicDataReader& reader, - FullTrackName& full_track_name) const; absl::Status FillAndValidateSetupOptions(const KeyValuePairList& in, SetupOptions& out) const;
diff --git a/quiche/quic/moqt/moqt_parser_test.cc b/quiche/quic/moqt/moqt_parser_test.cc index 826d012..3f655cd 100644 --- a/quiche/quic/moqt/moqt_parser_test.cc +++ b/quiche/quic/moqt/moqt_parser_test.cc
@@ -1135,6 +1135,7 @@ EXPECT_FALSE(message.parameters.subscriber_priority); EXPECT_FALSE(message.parameters.largest_object.has_value()); EXPECT_FALSE(message.parameters.new_group_request.has_value()); + EXPECT_FALSE(message.parameters.track_namespace_prefix.has_value()); } TEST_F(MoqtMessageSpecificTest, SubscribeUnknownParameter) {
diff --git a/quiche/quic/moqt/test_tools/moqt_test_message.h b/quiche/quic/moqt/test_tools/moqt_test_message.h index 94c2af2..ba6f96e 100644 --- a/quiche/quic/moqt/test_tools/moqt_test_message.h +++ b/quiche/quic/moqt/test_tools/moqt_test_message.h
@@ -915,6 +915,7 @@ request_update_.parameters.set_forward(true); request_update_.parameters.subscriber_priority = 0xaa; request_update_.parameters.subscription_filter.emplace(Location(3, 1), 5); + request_update_.parameters.track_namespace_prefix = TrackNamespace({"bar"}); } bool EqualFieldValues(const MessageStructuredData& values) const override { @@ -934,20 +935,21 @@ return true; } - void ExpandVarints() override { ExpandVarintsImpl("vvvv--v-v-vv----"); } + void ExpandVarints() override { ExpandVarintsImpl("vvvv--v-v-vv----vvv---"); } MessageStructuredData structured_data() const override { return TestMessageBase::MessageStructuredData(request_update_); } private: - uint8_t raw_packet_[19] = { - 0x02, 0x00, 0x10, 0x02, 0x00, // request IDs 2 and 0 - 0x04, // Four parameters + uint8_t raw_packet_[25] = { + 0x02, 0x00, 0x16, 0x02, 0x00, // request IDs 2 and 0 + 0x05, // Five parameters 0x02, 0xa7, 0x10, // object_delivery_timeout = 10000 0x0e, 0x01, // forward = true 0x10, 0xaa, // subscriber_priority = 0xaa 0x01, 0x04, 0x04, 0x03, 0x01, 0x02, // Absolute Range: (3, 1) to 5 + 0x13, 0x01, 0x03, 0x62, 0x61, 0x72, // prefix = "bar" }; MoqtRequestUpdate request_update_ = {