Move moqt::TrackNamespace and friends into a separate header.

PiperOrigin-RevId: 852458341
diff --git a/build/source_list.bzl b/build/source_list.bzl
index 88d0861..bde91f4 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -1568,6 +1568,7 @@
     "quic/moqt/moqt_framer.h",
     "quic/moqt/moqt_known_track_publisher.h",
     "quic/moqt/moqt_messages.h",
+    "quic/moqt/moqt_names.h",
     "quic/moqt/moqt_object.h",
     "quic/moqt/moqt_outgoing_queue.h",
     "quic/moqt/moqt_outstanding_objects.h",
@@ -1597,6 +1598,7 @@
     "quic/moqt/moqt_framer.cc",
     "quic/moqt/moqt_known_track_publisher.cc",
     "quic/moqt/moqt_messages.cc",
+    "quic/moqt/moqt_names.cc",
     "quic/moqt/moqt_object.cc",
     "quic/moqt/moqt_outgoing_queue.cc",
     "quic/moqt/moqt_outstanding_objects.cc",
@@ -1623,6 +1625,7 @@
     "quic/moqt/moqt_framer_test.cc",
     "quic/moqt/moqt_integration_test.cc",
     "quic/moqt/moqt_messages_test.cc",
+    "quic/moqt/moqt_names_test.cc",
     "quic/moqt/moqt_outgoing_queue_test.cc",
     "quic/moqt/moqt_outstanding_objects_test.cc",
     "quic/moqt/moqt_parser_fuzz_test.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index 1ca3b89..42c0a86 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -1572,6 +1572,7 @@
     "src/quiche/quic/moqt/moqt_framer.h",
     "src/quiche/quic/moqt/moqt_known_track_publisher.h",
     "src/quiche/quic/moqt/moqt_messages.h",
+    "src/quiche/quic/moqt/moqt_names.h",
     "src/quiche/quic/moqt/moqt_object.h",
     "src/quiche/quic/moqt/moqt_outgoing_queue.h",
     "src/quiche/quic/moqt/moqt_outstanding_objects.h",
@@ -1601,6 +1602,7 @@
     "src/quiche/quic/moqt/moqt_framer.cc",
     "src/quiche/quic/moqt/moqt_known_track_publisher.cc",
     "src/quiche/quic/moqt/moqt_messages.cc",
+    "src/quiche/quic/moqt/moqt_names.cc",
     "src/quiche/quic/moqt/moqt_object.cc",
     "src/quiche/quic/moqt/moqt_outgoing_queue.cc",
     "src/quiche/quic/moqt/moqt_outstanding_objects.cc",
@@ -1628,6 +1630,7 @@
     "src/quiche/quic/moqt/moqt_framer_test.cc",
     "src/quiche/quic/moqt/moqt_integration_test.cc",
     "src/quiche/quic/moqt/moqt_messages_test.cc",
+    "src/quiche/quic/moqt/moqt_names_test.cc",
     "src/quiche/quic/moqt/moqt_outgoing_queue_test.cc",
     "src/quiche/quic/moqt/moqt_outstanding_objects_test.cc",
     "src/quiche/quic/moqt/moqt_parser_fuzz_test.cc",
diff --git a/build/source_list.json b/build/source_list.json
index 1f8c1a7..da12550 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -1571,6 +1571,7 @@
     "quiche/quic/moqt/moqt_framer.h",
     "quiche/quic/moqt/moqt_known_track_publisher.h",
     "quiche/quic/moqt/moqt_messages.h",
+    "quiche/quic/moqt/moqt_names.h",
     "quiche/quic/moqt/moqt_object.h",
     "quiche/quic/moqt/moqt_outgoing_queue.h",
     "quiche/quic/moqt/moqt_outstanding_objects.h",
@@ -1600,6 +1601,7 @@
     "quiche/quic/moqt/moqt_framer.cc",
     "quiche/quic/moqt/moqt_known_track_publisher.cc",
     "quiche/quic/moqt/moqt_messages.cc",
+    "quiche/quic/moqt/moqt_names.cc",
     "quiche/quic/moqt/moqt_object.cc",
     "quiche/quic/moqt/moqt_outgoing_queue.cc",
     "quiche/quic/moqt/moqt_outstanding_objects.cc",
@@ -1627,6 +1629,7 @@
     "quiche/quic/moqt/moqt_framer_test.cc",
     "quiche/quic/moqt/moqt_integration_test.cc",
     "quiche/quic/moqt/moqt_messages_test.cc",
+    "quiche/quic/moqt/moqt_names_test.cc",
     "quiche/quic/moqt/moqt_outgoing_queue_test.cc",
     "quiche/quic/moqt/moqt_outstanding_objects_test.cc",
     "quiche/quic/moqt/moqt_parser_fuzz_test.cc",
diff --git a/quiche/quic/moqt/moqt_messages.cc b/quiche/quic/moqt/moqt_messages.cc
index e449bcc..2283c6f 100644
--- a/quiche/quic/moqt/moqt_messages.cc
+++ b/quiche/quic/moqt/moqt_messages.cc
@@ -14,11 +14,8 @@
 #include "absl/algorithm/container.h"
 #include "absl/container/btree_map.h"
 #include "absl/status/status.h"
-#include "absl/strings/escaping.h"
 #include "absl/strings/str_cat.h"
-#include "absl/strings/str_join.h"
 #include "absl/strings/string_view.h"
-#include "absl/types/span.h"
 #include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/platform/api/quic_bug_tracker.h"
 #include "quiche/common/platform/api/quiche_bug_tracker.h"
@@ -318,62 +315,6 @@
   return "Unknown preference " + std::to_string(static_cast<int>(preference));
 }
 
-TrackNamespace::TrackNamespace(absl::Span<const absl::string_view> elements)
-    : tuple_(elements.begin(), elements.end()) {
-  if (std::size(elements) > kMaxNamespaceElements) {
-    tuple_.clear();
-    QUICHE_BUG(Moqt_namespace_too_large_01)
-        << "Constructing a namespace that is too large.";
-    return;
-  }
-  for (auto it : elements) {
-    length_ += it.size();
-    if (length_ > kMaxFullTrackNameSize) {
-      tuple_.clear();
-      QUICHE_BUG(Moqt_namespace_too_large_02)
-          << "Constructing a namespace that is too large.";
-      return;
-    }
-  }
-}
-
-bool TrackNamespace::InNamespace(const TrackNamespace& other) const {
-  if (tuple_.size() < other.tuple_.size()) {
-    return false;
-  }
-  for (int i = 0; i < other.tuple_.size(); ++i) {
-    if (tuple_[i] != other.tuple_[i]) {
-      return false;
-    }
-  }
-  return true;
-}
-
-void TrackNamespace::AddElement(absl::string_view element) {
-  if (!CanAddElement(element)) {
-    QUICHE_BUG(Moqt_namespace_too_large_03)
-        << "Constructing a namespace that is too large.";
-    return;
-  }
-  length_ += element.length();
-  tuple_.push_back(std::string(element));
-}
-
-std::string TrackNamespace::ToString() const {
-  std::vector<std::string> bits;
-  bits.reserve(tuple_.size());
-  for (absl::string_view raw_bit : tuple_) {
-    bits.push_back(absl::StrCat("\"", absl::CHexEscape(raw_bit), "\""));
-  }
-  return absl::StrCat("{", absl::StrJoin(bits, "::"), "}");
-}
-
-void FullTrackName::set_name(absl::string_view name) {
-  QUIC_BUG_IF(Moqt_name_too_large_03, !CanAddName(name))
-      << "Setting a name that is too large.";
-  name_ = name;
-}
-
 absl::Status MoqtStreamErrorToStatus(webtransport::StreamErrorCode error_code,
                                      absl::string_view reason_phrase) {
   switch (error_code) {
diff --git a/quiche/quic/moqt/moqt_messages.h b/quiche/quic/moqt/moqt_messages.h
index 37061d0..2308763 100644
--- a/quiche/quic/moqt/moqt_messages.h
+++ b/quiche/quic/moqt/moqt_messages.h
@@ -19,15 +19,13 @@
 
 #include "absl/container/btree_map.h"
 #include "absl/status/status.h"
-#include "absl/strings/str_cat.h"
 #include "absl/strings/str_format.h"
 #include "absl/strings/string_view.h"
-#include "absl/types/span.h"
 #include "quiche/quic/core/quic_time.h"
 #include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/core/quic_versions.h"
+#include "quiche/quic/moqt/moqt_names.h"
 #include "quiche/quic/moqt/moqt_priority.h"
-#include "quiche/common/platform/api/quiche_bug_tracker.h"
 #include "quiche/common/platform/api/quiche_export.h"
 #include "quiche/common/platform/api/quiche_logging.h"
 #include "quiche/common/quiche_callbacks.h"
@@ -51,9 +49,6 @@
 inline constexpr uint64_t kDefaultInitialMaxRequestId = 100;
 // TODO(martinduke): Implement an auth token cache.
 inline constexpr uint64_t kDefaultMaxAuthTokenCacheSize = 0;
-inline constexpr uint64_t kMinNamespaceElements = 1;
-inline constexpr uint64_t kMaxNamespaceElements = 32;
-inline constexpr size_t kMaxFullTrackNameSize = 1024;
 inline constexpr uint64_t kMaxObjectId = quiche::kVarInt62MaxValue;
 
 enum AuthTokenType : uint64_t {
@@ -399,114 +394,6 @@
 using MoqtSubscribeErrorReason = MoqtRequestError;
 using MoqtPublishNamespaceErrorReason = MoqtSubscribeErrorReason;
 
-class TrackNamespace {
- public:
-  explicit TrackNamespace(absl::Span<const absl::string_view> elements);
-  explicit TrackNamespace(
-      std::initializer_list<const absl::string_view> elements)
-      : TrackNamespace(absl::Span<const absl::string_view>(
-            std::data(elements), std::size(elements))) {}
-  explicit TrackNamespace(absl::string_view ns) : TrackNamespace({ns}) {}
-  TrackNamespace() : TrackNamespace({}) {}
-
-  bool IsValid() const {
-    return !tuple_.empty() && tuple_.size() <= kMaxNamespaceElements &&
-           length_ <= kMaxFullTrackNameSize;
-  }
-  bool InNamespace(const TrackNamespace& other) const;
-  // Check if adding an element will exceed limits, without triggering a
-  // bug. Useful for the parser, which has to be robust to malformed data.
-  bool CanAddElement(absl::string_view element) {
-    return (tuple_.size() < kMaxNamespaceElements &&
-            length_ + element.length() <= kMaxFullTrackNameSize);
-  }
-  void AddElement(absl::string_view element);
-  bool PopElement() {
-    if (tuple_.size() == 1) {
-      return false;
-    }
-    length_ -= tuple_.back().length();
-    tuple_.pop_back();
-    return true;
-  }
-  std::string ToString() const;
-  // Returns the number of elements in the tuple.
-  size_t number_of_elements() const { return tuple_.size(); }
-  // Returns the sum of the lengths of all elements in the tuple.
-  size_t total_length() const { return length_; }
-
-  auto operator<=>(const TrackNamespace& other) const {
-    return std::lexicographical_compare_three_way(
-        tuple_.cbegin(), tuple_.cend(), other.tuple_.cbegin(),
-        other.tuple_.cend());
-  }
-  bool operator==(const TrackNamespace&) const = default;
-
-  const std::vector<std::string>& tuple() const { return tuple_; }
-
-  template <typename H>
-  friend H AbslHashValue(H h, const TrackNamespace& m) {
-    return H::combine(std::move(h), m.tuple_);
-  }
-  template <typename Sink>
-  friend void AbslStringify(Sink& sink, const TrackNamespace& track_namespace) {
-    sink.Append(track_namespace.ToString());
-  }
-
- private:
-  std::vector<std::string> tuple_;
-  size_t length_ = 0;  // size in bytes.
-};
-
-class FullTrackName {
- public:
-  FullTrackName(absl::string_view ns, absl::string_view name)
-      : namespace_(ns), name_(name) {
-    QUICHE_BUG_IF(Moqt_full_track_name_too_large_01, !IsValid())
-        << "Constructing a Full Track Name that is too large.";
-  }
-  FullTrackName(TrackNamespace ns, absl::string_view name)
-      : namespace_(ns), name_(name) {
-    QUICHE_BUG_IF(Moqt_full_track_name_too_large_02, !IsValid())
-        << "Constructing a Full Track Name that is too large.";
-  }
-  FullTrackName() = default;
-
-  bool IsValid() const {
-    return namespace_.IsValid() && length() <= kMaxFullTrackNameSize;
-  }
-  const TrackNamespace& track_namespace() const { return namespace_; }
-  TrackNamespace& track_namespace() { return namespace_; }
-  absl::string_view name() const { return name_; }
-  void AddElement(absl::string_view element) {
-    return namespace_.AddElement(element);
-  }
-  std::string ToString() const {
-    return absl::StrCat(namespace_.ToString(), "::", name_);
-  }
-  // Check if the name will exceed limits, without triggering a bug. Useful for
-  // the parser, which has to be robust to malformed data.
-  bool CanAddName(absl::string_view name) {
-    return (namespace_.total_length() + name.length() <= kMaxFullTrackNameSize);
-  }
-  void set_name(absl::string_view name);
-  size_t length() const { return namespace_.total_length() + name_.length(); }
-
-  auto operator<=>(const FullTrackName&) const = default;
-  template <typename H>
-  friend H AbslHashValue(H h, const FullTrackName& m) {
-    return H::combine(std::move(h), m.namespace_.tuple(), m.name_);
-  }
-  template <typename Sink>
-  friend void AbslStringify(Sink& sink, const FullTrackName& full_track_name) {
-    sink.Append(full_track_name.ToString());
-  }
-
- private:
-  TrackNamespace namespace_;
-  std::string name_ = "";
-};
-
 // Location as defined in
 // https://moq-wg.github.io/moq-transport/draft-ietf-moq-transport.html#location-structure
 struct Location {
diff --git a/quiche/quic/moqt/moqt_messages_test.cc b/quiche/quic/moqt/moqt_messages_test.cc
index 4372ffd..4597b0f 100644
--- a/quiche/quic/moqt/moqt_messages_test.cc
+++ b/quiche/quic/moqt/moqt_messages_test.cc
@@ -5,107 +5,12 @@
 #include "quiche/quic/moqt/moqt_messages.h"
 
 #include <optional>
-#include <vector>
 
-#include "absl/hash/hash.h"
-#include "absl/strings/string_view.h"
-#include "quiche/common/platform/api/quiche_expect_bug.h"
 #include "quiche/common/platform/api/quiche_test.h"
 
 namespace moqt::test {
 namespace {
 
-TEST(MoqtMessagesTest, TrackNamespaceConstructors) {
-  TrackNamespace name1({"foo", "bar"});
-  std::vector<absl::string_view> list = {"foo", "bar"};
-  TrackNamespace name2(list);
-  EXPECT_EQ(name1, name2);
-  EXPECT_EQ(absl::HashOf(name1), absl::HashOf(name2));
-}
-
-TEST(MoqtMessagesTest, TrackNamespaceOrder) {
-  TrackNamespace name1({"a", "b"});
-  TrackNamespace name2({"a", "b", "c"});
-  TrackNamespace name3({"b", "a"});
-  EXPECT_LT(name1, name2);
-  EXPECT_LT(name2, name3);
-  EXPECT_LT(name1, name3);
-}
-
-TEST(MoqtMessagesTest, TrackNamespaceInNamespace) {
-  TrackNamespace name1({"a", "b"});
-  TrackNamespace name2({"a", "b", "c"});
-  TrackNamespace name3({"d", "b"});
-  EXPECT_TRUE(name2.InNamespace(name1));
-  EXPECT_FALSE(name1.InNamespace(name2));
-  EXPECT_TRUE(name1.InNamespace(name1));
-  EXPECT_FALSE(name2.InNamespace(name3));
-}
-
-TEST(MoqtMessagesTest, TrackNamespacePushPop) {
-  TrackNamespace name({"a"});
-  TrackNamespace original = name;
-  name.AddElement("b");
-  EXPECT_TRUE(name.InNamespace(original));
-  EXPECT_FALSE(original.InNamespace(name));
-  EXPECT_TRUE(name.PopElement());
-  EXPECT_EQ(name, original);
-  EXPECT_FALSE(name.PopElement());
-}
-
-TEST(MoqtMessagesTest, TrackNamespaceToString) {
-  TrackNamespace name1({"a", "b"});
-  EXPECT_EQ(name1.ToString(), R"({"a"::"b"})");
-
-  TrackNamespace name2({"\xff", "\x61"});
-  EXPECT_EQ(name2.ToString(), R"({"\xff"::"a"})");
-}
-
-TEST(MoqtMessagesTest, FullTrackNameToString) {
-  FullTrackName name1(TrackNamespace{"a", "b"}, "c");
-  EXPECT_EQ(name1.ToString(), R"({"a"::"b"}::c)");
-}
-
-TEST(MoqtMessagesTest, TooManyNamespaceElements) {
-  // 32 elements work.
-  TrackNamespace name1({"a", "b", "c",  "d",  "e",  "f",  "g",  "h",
-                        "i", "j", "k",  "l",  "m",  "n",  "o",  "p",
-                        "q", "r", "s",  "t",  "u",  "v",  "w",  "x",
-                        "y", "z", "aa", "bb", "cc", "dd", "ee", "ff"});
-  EXPECT_TRUE(name1.IsValid());
-  EXPECT_QUICHE_BUG(name1.AddElement("a"),
-                    "Constructing a namespace that is too large.");
-  EXPECT_EQ(name1.number_of_elements(), kMaxNamespaceElements);
-
-  // 33 elements fail,
-  TrackNamespace name2;
-  EXPECT_QUICHE_BUG(
-      name2 = TrackNamespace({"a",  "b",  "c",  "d",  "e",  "f", "g", "h", "i",
-                              "j",  "k",  "l",  "m",  "n",  "o", "p", "q", "r",
-                              "s",  "t",  "u",  "v",  "w",  "x", "y", "z", "aa",
-                              "bb", "cc", "dd", "ee", "ff", "gg"}),
-      "Constructing a namespace that is too large.");
-  EXPECT_FALSE(name2.IsValid());
-}
-
-TEST(MoqtMessagesTest, FullTrackNameTooLong) {
-  char raw_name[kMaxFullTrackNameSize + 1];
-  absl::string_view track_namespace(raw_name, kMaxFullTrackNameSize);
-  // Adding an element takes it over the length limit.
-  TrackNamespace max_length_namespace({track_namespace});
-  EXPECT_TRUE(max_length_namespace.IsValid());
-  EXPECT_QUICHE_BUG(max_length_namespace.AddElement("f"),
-                    "Constructing a namespace that is too large.");
-  // Constructing a FullTrackName where the name brings it over the length
-  // limit.
-  EXPECT_QUICHE_BUG(FullTrackName(max_length_namespace, "f"),
-                    "Constructing a Full Track Name that is too large.");
-  // The namespace is too long by itself..
-  absl::string_view big_namespace(raw_name, kMaxFullTrackNameSize + 1);
-  EXPECT_QUICHE_BUG(TrackNamespace({big_namespace}),
-                    "Constructing a namespace that is too large.");
-}
-
 TEST(MoqtMessagesTest, MoqtDatagramType) {
   for (bool payload : {false, true}) {
     for (bool extension : {false, true}) {
diff --git a/quiche/quic/moqt/moqt_names.cc b/quiche/quic/moqt/moqt_names.cc
new file mode 100644
index 0000000..510e740
--- /dev/null
+++ b/quiche/quic/moqt/moqt_names.cc
@@ -0,0 +1,90 @@
+// Copyright 2025 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.
+
+#include "quiche/quic/moqt/moqt_names.h"
+
+#include <iterator>
+#include <string>
+#include <vector>
+
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_join.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
+#include "quiche/common/platform/api/quiche_bug_tracker.h"
+
+namespace moqt {
+
+TrackNamespace::TrackNamespace(absl::Span<const absl::string_view> elements)
+    : tuple_(elements.begin(), elements.end()) {
+  if (std::size(elements) > kMaxNamespaceElements) {
+    tuple_.clear();
+    QUICHE_BUG(Moqt_namespace_too_large_01)
+        << "Constructing a namespace that is too large.";
+    return;
+  }
+  for (auto it : elements) {
+    length_ += it.size();
+    if (length_ > kMaxFullTrackNameSize) {
+      tuple_.clear();
+      QUICHE_BUG(Moqt_namespace_too_large_02)
+          << "Constructing a namespace that is too large.";
+      return;
+    }
+  }
+}
+
+bool TrackNamespace::InNamespace(const TrackNamespace& other) const {
+  if (tuple_.size() < other.tuple_.size()) {
+    return false;
+  }
+  for (int i = 0; i < other.tuple_.size(); ++i) {
+    if (tuple_[i] != other.tuple_[i]) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void TrackNamespace::AddElement(absl::string_view element) {
+  if (!CanAddElement(element)) {
+    QUICHE_BUG(Moqt_namespace_too_large_03)
+        << "Constructing a namespace that is too large.";
+    return;
+  }
+  length_ += element.length();
+  tuple_.push_back(std::string(element));
+}
+
+std::string TrackNamespace::ToString() const {
+  std::vector<std::string> bits;
+  bits.reserve(tuple_.size());
+  for (absl::string_view raw_bit : tuple_) {
+    bits.push_back(absl::StrCat("\"", absl::CHexEscape(raw_bit), "\""));
+  }
+  return absl::StrCat("{", absl::StrJoin(bits, "::"), "}");
+}
+
+FullTrackName::FullTrackName(absl::string_view ns, absl::string_view name)
+    : namespace_(ns), name_(name) {
+  QUICHE_BUG_IF(Moqt_full_track_name_too_large_01, !IsValid())
+      << "Constructing a Full Track Name that is too large.";
+}
+FullTrackName::FullTrackName(TrackNamespace ns, absl::string_view name)
+    : namespace_(ns), name_(name) {
+  QUICHE_BUG_IF(Moqt_full_track_name_too_large_02, !IsValid())
+      << "Constructing a Full Track Name that is too large.";
+}
+
+std::string FullTrackName::ToString() const {
+  return absl::StrCat(namespace_.ToString(), "::", name_);
+}
+void FullTrackName::set_name(absl::string_view name) {
+  QUICHE_BUG_IF(Moqt_name_too_large_03, !CanAddName(name))
+      << "Setting a name that is too large.";
+  name_ = name;
+}
+
+}  // namespace moqt
diff --git a/quiche/quic/moqt/moqt_names.h b/quiche/quic/moqt/moqt_names.h
new file mode 100644
index 0000000..0102eed
--- /dev/null
+++ b/quiche/quic/moqt/moqt_names.h
@@ -0,0 +1,126 @@
+// Copyright 2025 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_MOQT_NAMES_H_
+#define QUICHE_QUIC_MOQT_MOQT_NAMES_H_
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <initializer_list>
+#include <string>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
+
+namespace moqt {
+
+// Protocol-specified limits on the length and structure of MoQT namespaces.
+inline constexpr uint64_t kMinNamespaceElements = 1;
+inline constexpr uint64_t kMaxNamespaceElements = 32;
+inline constexpr size_t kMaxFullTrackNameSize = 1024;
+
+class TrackNamespace {
+ public:
+  explicit TrackNamespace(absl::Span<const absl::string_view> elements);
+  explicit TrackNamespace(
+      std::initializer_list<const absl::string_view> elements)
+      : TrackNamespace(absl::Span<const absl::string_view>(
+            std::data(elements), std::size(elements))) {}
+  explicit TrackNamespace(absl::string_view ns) : TrackNamespace({ns}) {}
+  TrackNamespace() : TrackNamespace({}) {}
+
+  bool IsValid() const {
+    return !tuple_.empty() && tuple_.size() <= kMaxNamespaceElements &&
+           length_ <= kMaxFullTrackNameSize;
+  }
+  bool InNamespace(const TrackNamespace& other) const;
+  // Check if adding an element will exceed limits, without triggering a
+  // bug. Useful for the parser, which has to be robust to malformed data.
+  bool CanAddElement(absl::string_view element) {
+    return (tuple_.size() < kMaxNamespaceElements &&
+            length_ + element.length() <= kMaxFullTrackNameSize);
+  }
+  void AddElement(absl::string_view element);
+  bool PopElement() {
+    if (tuple_.size() == 1) {
+      return false;
+    }
+    length_ -= tuple_.back().length();
+    tuple_.pop_back();
+    return true;
+  }
+  std::string ToString() const;
+  // Returns the number of elements in the tuple.
+  size_t number_of_elements() const { return tuple_.size(); }
+  // Returns the sum of the lengths of all elements in the tuple.
+  size_t total_length() const { return length_; }
+
+  auto operator<=>(const TrackNamespace& other) const {
+    return std::lexicographical_compare_three_way(
+        tuple_.cbegin(), tuple_.cend(), other.tuple_.cbegin(),
+        other.tuple_.cend());
+  }
+  bool operator==(const TrackNamespace&) const = default;
+
+  const std::vector<std::string>& tuple() const { return tuple_; }
+
+  template <typename H>
+  friend H AbslHashValue(H h, const TrackNamespace& m) {
+    return H::combine(std::move(h), m.tuple_);
+  }
+  template <typename Sink>
+  friend void AbslStringify(Sink& sink, const TrackNamespace& track_namespace) {
+    sink.Append(track_namespace.ToString());
+  }
+
+ private:
+  std::vector<std::string> tuple_;
+  size_t length_ = 0;  // size in bytes.
+};
+
+class FullTrackName {
+ public:
+  FullTrackName(absl::string_view ns, absl::string_view name);
+  FullTrackName(TrackNamespace ns, absl::string_view name);
+  FullTrackName() = default;
+
+  bool IsValid() const {
+    return namespace_.IsValid() && length() <= kMaxFullTrackNameSize;
+  }
+  const TrackNamespace& track_namespace() const { return namespace_; }
+  TrackNamespace& track_namespace() { return namespace_; }
+  absl::string_view name() const { return name_; }
+  void AddElement(absl::string_view element) {
+    return namespace_.AddElement(element);
+  }
+  std::string ToString() const;
+  // Check if the name will exceed limits, without triggering a bug. Useful for
+  // the parser, which has to be robust to malformed data.
+  bool CanAddName(absl::string_view name) {
+    return (namespace_.total_length() + name.length() <= kMaxFullTrackNameSize);
+  }
+  void set_name(absl::string_view name);
+  size_t length() const { return namespace_.total_length() + name_.length(); }
+
+  auto operator<=>(const FullTrackName&) const = default;
+  template <typename H>
+  friend H AbslHashValue(H h, const FullTrackName& m) {
+    return H::combine(std::move(h), m.namespace_.tuple(), m.name_);
+  }
+  template <typename Sink>
+  friend void AbslStringify(Sink& sink, const FullTrackName& full_track_name) {
+    sink.Append(full_track_name.ToString());
+  }
+
+ private:
+  TrackNamespace namespace_;
+  std::string name_ = "";
+};
+
+}  // namespace moqt
+
+#endif  // QUICHE_QUIC_MOQT_MOQT_NAMES_H_
diff --git a/quiche/quic/moqt/moqt_names_test.cc b/quiche/quic/moqt/moqt_names_test.cc
new file mode 100644
index 0000000..cffdaf0
--- /dev/null
+++ b/quiche/quic/moqt/moqt_names_test.cc
@@ -0,0 +1,109 @@
+// Copyright 2024 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.
+
+#include "quiche/quic/moqt/moqt_names.h"
+
+#include <vector>
+
+#include "absl/hash/hash.h"
+#include "absl/strings/string_view.h"
+#include "quiche/common/platform/api/quiche_expect_bug.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace moqt::test {
+namespace {
+
+TEST(MoqtNamesTest, TrackNamespaceConstructors) {
+  TrackNamespace name1({"foo", "bar"});
+  std::vector<absl::string_view> list = {"foo", "bar"};
+  TrackNamespace name2(list);
+  EXPECT_EQ(name1, name2);
+  EXPECT_EQ(absl::HashOf(name1), absl::HashOf(name2));
+}
+
+TEST(MoqtNamesTest, TrackNamespaceOrder) {
+  TrackNamespace name1({"a", "b"});
+  TrackNamespace name2({"a", "b", "c"});
+  TrackNamespace name3({"b", "a"});
+  EXPECT_LT(name1, name2);
+  EXPECT_LT(name2, name3);
+  EXPECT_LT(name1, name3);
+}
+
+TEST(MoqtNamesTest, TrackNamespaceInNamespace) {
+  TrackNamespace name1({"a", "b"});
+  TrackNamespace name2({"a", "b", "c"});
+  TrackNamespace name3({"d", "b"});
+  EXPECT_TRUE(name2.InNamespace(name1));
+  EXPECT_FALSE(name1.InNamespace(name2));
+  EXPECT_TRUE(name1.InNamespace(name1));
+  EXPECT_FALSE(name2.InNamespace(name3));
+}
+
+TEST(MoqtNamesTest, TrackNamespacePushPop) {
+  TrackNamespace name({"a"});
+  TrackNamespace original = name;
+  name.AddElement("b");
+  EXPECT_TRUE(name.InNamespace(original));
+  EXPECT_FALSE(original.InNamespace(name));
+  EXPECT_TRUE(name.PopElement());
+  EXPECT_EQ(name, original);
+  EXPECT_FALSE(name.PopElement());
+}
+
+TEST(MoqtNamesTest, TrackNamespaceToString) {
+  TrackNamespace name1({"a", "b"});
+  EXPECT_EQ(name1.ToString(), R"({"a"::"b"})");
+
+  TrackNamespace name2({"\xff", "\x61"});
+  EXPECT_EQ(name2.ToString(), R"({"\xff"::"a"})");
+}
+
+TEST(MoqtNamesTest, FullTrackNameToString) {
+  FullTrackName name1(TrackNamespace{"a", "b"}, "c");
+  EXPECT_EQ(name1.ToString(), R"({"a"::"b"}::c)");
+}
+
+TEST(MoqtNamesTest, TooManyNamespaceElements) {
+  // 32 elements work.
+  TrackNamespace name1({"a", "b", "c",  "d",  "e",  "f",  "g",  "h",
+                        "i", "j", "k",  "l",  "m",  "n",  "o",  "p",
+                        "q", "r", "s",  "t",  "u",  "v",  "w",  "x",
+                        "y", "z", "aa", "bb", "cc", "dd", "ee", "ff"});
+  EXPECT_TRUE(name1.IsValid());
+  EXPECT_QUICHE_BUG(name1.AddElement("a"),
+                    "Constructing a namespace that is too large.");
+  EXPECT_EQ(name1.number_of_elements(), kMaxNamespaceElements);
+
+  // 33 elements fail,
+  TrackNamespace name2;
+  EXPECT_QUICHE_BUG(
+      name2 = TrackNamespace({"a",  "b",  "c",  "d",  "e",  "f", "g", "h", "i",
+                              "j",  "k",  "l",  "m",  "n",  "o", "p", "q", "r",
+                              "s",  "t",  "u",  "v",  "w",  "x", "y", "z", "aa",
+                              "bb", "cc", "dd", "ee", "ff", "gg"}),
+      "Constructing a namespace that is too large.");
+  EXPECT_FALSE(name2.IsValid());
+}
+
+TEST(MoqtNamesTest, FullTrackNameTooLong) {
+  char raw_name[kMaxFullTrackNameSize + 1];
+  absl::string_view track_namespace(raw_name, kMaxFullTrackNameSize);
+  // Adding an element takes it over the length limit.
+  TrackNamespace max_length_namespace({track_namespace});
+  EXPECT_TRUE(max_length_namespace.IsValid());
+  EXPECT_QUICHE_BUG(max_length_namespace.AddElement("f"),
+                    "Constructing a namespace that is too large.");
+  // Constructing a FullTrackName where the name brings it over the length
+  // limit.
+  EXPECT_QUICHE_BUG(FullTrackName(max_length_namespace, "f"),
+                    "Constructing a Full Track Name that is too large.");
+  // The namespace is too long by itself..
+  absl::string_view big_namespace(raw_name, kMaxFullTrackNameSize + 1);
+  EXPECT_QUICHE_BUG(TrackNamespace({big_namespace}),
+                    "Constructing a namespace that is too large.");
+}
+
+}  // namespace
+}  // namespace moqt::test
diff --git a/quiche/quic/moqt/moqt_relay_track_publisher.cc b/quiche/quic/moqt/moqt_relay_track_publisher.cc
index ff027db..5dfd187 100644
--- a/quiche/quic/moqt/moqt_relay_track_publisher.cc
+++ b/quiche/quic/moqt/moqt_relay_track_publisher.cc
@@ -17,6 +17,7 @@
 #include "quiche/quic/moqt/moqt_object.h"
 #include "quiche/quic/moqt/moqt_publisher.h"
 #include "quiche/quic/moqt/moqt_session_interface.h"
+#include "quiche/common/platform/api/quiche_bug_tracker.h"
 #include "quiche/common/platform/api/quiche_logging.h"
 #include "quiche/common/quiche_callbacks.h"
 #include "quiche/common/quiche_mem_slice.h"
diff --git a/quiche/quic/moqt/relay_namespace_tree.h b/quiche/quic/moqt/relay_namespace_tree.h
index 5047418..bdee98e 100644
--- a/quiche/quic/moqt/relay_namespace_tree.h
+++ b/quiche/quic/moqt/relay_namespace_tree.h
@@ -16,6 +16,7 @@
 #include "absl/strings/string_view.h"
 #include "quiche/quic/moqt/moqt_messages.h"
 #include "quiche/quic/moqt/moqt_session_interface.h"
+#include "quiche/common/platform/api/quiche_bug_tracker.h"
 #include "quiche/common/quiche_weak_ptr.h"
 
 namespace moqt {