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_ = {