Refactor MoQT interfaces to consistently pass FullTrackName around.

This should make us well-positioned for tuple namespaces and potentially #508.

PiperOrigin-RevId: 678730997
diff --git a/build/source_list.bzl b/build/source_list.bzl
index e32fc09..bce0f0f 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -1545,6 +1545,7 @@
     "quic/moqt/moqt_live_relay_queue.cc",
     "quic/moqt/moqt_live_relay_queue_test.cc",
     "quic/moqt/moqt_messages.cc",
+    "quic/moqt/moqt_messages_test.cc",
     "quic/moqt/moqt_outgoing_queue.cc",
     "quic/moqt/moqt_outgoing_queue_test.cc",
     "quic/moqt/moqt_parser.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index 98d1fc3..9fe8d75 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -1549,6 +1549,7 @@
     "src/quiche/quic/moqt/moqt_live_relay_queue.cc",
     "src/quiche/quic/moqt/moqt_live_relay_queue_test.cc",
     "src/quiche/quic/moqt/moqt_messages.cc",
+    "src/quiche/quic/moqt/moqt_messages_test.cc",
     "src/quiche/quic/moqt/moqt_outgoing_queue.cc",
     "src/quiche/quic/moqt/moqt_outgoing_queue_test.cc",
     "src/quiche/quic/moqt/moqt_parser.cc",
diff --git a/build/source_list.json b/build/source_list.json
index e1ed864..e02ea5d 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -1548,6 +1548,7 @@
     "quiche/quic/moqt/moqt_live_relay_queue.cc",
     "quiche/quic/moqt/moqt_live_relay_queue_test.cc",
     "quiche/quic/moqt/moqt_messages.cc",
+    "quiche/quic/moqt/moqt_messages_test.cc",
     "quiche/quic/moqt/moqt_outgoing_queue.cc",
     "quiche/quic/moqt/moqt_outgoing_queue_test.cc",
     "quiche/quic/moqt/moqt_parser.cc",
diff --git a/quiche/quic/moqt/moqt_framer.cc b/quiche/quic/moqt/moqt_framer.cc
index f4ddd7a..c4fe1e1 100644
--- a/quiche/quic/moqt/moqt_framer.cc
+++ b/quiche/quic/moqt/moqt_framer.cc
@@ -112,6 +112,27 @@
   const IntParameter& parameter_;
 };
 
+class WireFullTrackName {
+ public:
+  using DataType = FullTrackName;
+
+  explicit WireFullTrackName(const FullTrackName& name) : name_(name) {}
+
+  size_t GetLengthOnWire() {
+    return quiche::ComputeLengthOnWire(
+        WireStringWithVarInt62Length(name_.track_namespace()),
+        WireStringWithVarInt62Length(name_.track_name()));
+  }
+  absl::Status SerializeIntoWriter(quiche::QuicheDataWriter& writer) {
+    return quiche::SerializeIntoWriter(
+        writer, WireStringWithVarInt62Length(name_.track_namespace()),
+        WireStringWithVarInt62Length(name_.track_name()));
+  }
+
+ private:
+  const FullTrackName& name_;
+};
+
 // Serializes data into buffer using the default allocator.  Invokes QUICHE_BUG
 // on failure.
 template <typename... Ts>
@@ -332,8 +353,7 @@
       return Serialize(
           WireVarInt62(MoqtMessageType::kSubscribe),
           WireVarInt62(message.subscribe_id), WireVarInt62(message.track_alias),
-          WireStringWithVarInt62Length(message.track_namespace),
-          WireStringWithVarInt62Length(message.track_name),
+          WireFullTrackName(message.full_track_name),
           WireUint8(message.subscriber_priority),
           WireDeliveryOrder(message.group_order), WireVarInt62(filter_type),
           WireVarInt62(string_params.size() + int_params.size()),
@@ -343,8 +363,7 @@
       return Serialize(
           WireVarInt62(MoqtMessageType::kSubscribe),
           WireVarInt62(message.subscribe_id), WireVarInt62(message.track_alias),
-          WireStringWithVarInt62Length(message.track_namespace),
-          WireStringWithVarInt62Length(message.track_name),
+          WireFullTrackName(message.full_track_name),
           WireUint8(message.subscriber_priority),
           WireDeliveryOrder(message.group_order), WireVarInt62(filter_type),
           WireVarInt62(*message.start_group),
@@ -356,8 +375,7 @@
       return Serialize(
           WireVarInt62(MoqtMessageType::kSubscribe),
           WireVarInt62(message.subscribe_id), WireVarInt62(message.track_alias),
-          WireStringWithVarInt62Length(message.track_namespace),
-          WireStringWithVarInt62Length(message.track_name),
+          WireFullTrackName(message.full_track_name),
           WireUint8(message.subscriber_priority),
           WireDeliveryOrder(message.group_order), WireVarInt62(filter_type),
           WireVarInt62(*message.start_group),
@@ -482,8 +500,7 @@
 quiche::QuicheBuffer MoqtFramer::SerializeTrackStatusRequest(
     const MoqtTrackStatusRequest& message) {
   return Serialize(WireVarInt62(MoqtMessageType::kTrackStatusRequest),
-                   WireStringWithVarInt62Length(message.track_namespace),
-                   WireStringWithVarInt62Length(message.track_name));
+                   WireFullTrackName(message.full_track_name));
 }
 
 quiche::QuicheBuffer MoqtFramer::SerializeUnannounce(
@@ -495,8 +512,7 @@
 quiche::QuicheBuffer MoqtFramer::SerializeTrackStatus(
     const MoqtTrackStatus& message) {
   return Serialize(WireVarInt62(MoqtMessageType::kTrackStatus),
-                   WireStringWithVarInt62Length(message.track_namespace),
-                   WireStringWithVarInt62Length(message.track_name),
+                   WireFullTrackName(message.full_track_name),
                    WireVarInt62(message.status_code),
                    WireVarInt62(message.last_group),
                    WireVarInt62(message.last_object));
diff --git a/quiche/quic/moqt/moqt_framer_test.cc b/quiche/quic/moqt/moqt_framer_test.cc
index bd19955..55f6944 100644
--- a/quiche/quic/moqt/moqt_framer_test.cc
+++ b/quiche/quic/moqt/moqt_framer_test.cc
@@ -302,8 +302,7 @@
           MoqtSubscribe subscribe = {
               /*subscribe_id=*/3,
               /*track_alias=*/4,
-              /*track_namespace=*/"foo",
-              /*track_name=*/"abcd",
+              /*full_track_name=*/FullTrackName({"foo", "abcd"}),
               /*subscriber_priority=*/0x20,
               /*group_order=*/std::nullopt,
               start_group,
@@ -354,8 +353,7 @@
   MoqtSubscribe subscribe = {
       /*subscribe_id=*/3,
       /*track_alias=*/4,
-      /*track_namespace=*/"foo",
-      /*track_name=*/"abcd",
+      /*full_track_name=*/FullTrackName({"foo", "abcd"}),
       /*subscriber_priority=*/0x20,
       /*group_order=*/std::nullopt,
       /*start_group=*/std::optional<uint64_t>(4),
@@ -379,8 +377,7 @@
   MoqtSubscribe subscribe = {
       /*subscribe_id=*/3,
       /*track_alias=*/4,
-      /*track_namespace=*/"foo",
-      /*track_name=*/"abcd",
+      /*full_track_name=*/FullTrackName({"foo", "abcd"}),
       /*subscriber_priority=*/0x20,
       /*group_order=*/std::nullopt,
       /*start_group=*/std::nullopt,
diff --git a/quiche/quic/moqt/moqt_integration_test.cc b/quiche/quic/moqt/moqt_integration_test.cc
index a8a67e7..b6cb671 100644
--- a/quiche/quic/moqt/moqt_integration_test.cc
+++ b/quiche/quic/moqt/moqt_integration_test.cc
@@ -158,8 +158,8 @@
                     std::optional<MoqtAnnounceErrorReason> error) {
         EXPECT_EQ(track_namespace, "foo");
         EXPECT_FALSE(error.has_value());
-        server_->session()->SubscribeCurrentGroup(track_namespace, "/catalog",
-                                                  &server_visitor);
+        server_->session()->SubscribeCurrentGroup(
+            FullTrackName(track_namespace, "/catalog"), &server_visitor);
       });
   EXPECT_CALL(server_visitor, OnReply(_, _)).WillOnce([&]() {
     matches = true;
@@ -178,7 +178,7 @@
   EXPECT_CALL(server_callbacks_.incoming_announce_callback, Call(_))
       .WillOnce([&](absl::string_view track_namespace) {
         server_->session()->SubscribeAbsolute(
-            track_namespace, "data", /*start_group=*/0,
+            FullTrackName(track_namespace, "data"), /*start_group=*/0,
             /*start_object=*/0, &server_visitor);
         return std::optional<MoqtAnnounceErrorReason>();
       });
@@ -204,8 +204,7 @@
                     MoqtObjectStatus status,
                     MoqtForwardingPreference forwarding_preference,
                     absl::string_view object, bool end_of_message) {
-        EXPECT_EQ(full_track_name.track_namespace, "test");
-        EXPECT_EQ(full_track_name.track_name, "data");
+        EXPECT_EQ(full_track_name, FullTrackName("test", "data"));
         EXPECT_EQ(group_sequence, 0u);
         EXPECT_EQ(object_sequence, 0u);
         EXPECT_EQ(status, MoqtObjectStatus::kNormal);
@@ -242,7 +241,8 @@
     queue->AddObject(MemSliceFromString("object 4"), /*key=*/true);
     queue->AddObject(MemSliceFromString("object 5"), /*key=*/false);
 
-    client_->session()->SubscribeCurrentGroup("test", name, &client_visitor);
+    client_->session()->SubscribeCurrentGroup(FullTrackName("test", name),
+                                              &client_visitor);
     int received = 0;
     EXPECT_CALL(client_visitor,
                 OnObjectFragment(_, 1, 0, _, MoqtObjectStatus::kNormal, _,
@@ -301,7 +301,8 @@
       queue->AddObject(MemSliceFromString("object"), /*key=*/true);
     }
 
-    client_->session()->SubscribeAbsolute("test", name, 0, 0, &client_visitor);
+    client_->session()->SubscribeAbsolute(FullTrackName("test", name), 0, 0,
+                                          &client_visitor);
     int received = 0;
     // Those won't arrive since they have expired.
     EXPECT_CALL(client_visitor, OnObjectFragment(_, 0, 0, _, _, _, _, true))
@@ -367,9 +368,7 @@
   bool received_ok = false;
   EXPECT_CALL(client_visitor, OnReply(full_track_name, expected_reason))
       .WillOnce([&]() { received_ok = true; });
-  client_->session()->SubscribeAbsolute(full_track_name.track_namespace,
-                                        full_track_name.track_name, 0, 0,
-                                        &client_visitor);
+  client_->session()->SubscribeAbsolute(full_track_name, 0, 0, &client_visitor);
   bool success =
       test_harness_.RunUntilWithDefaultTimeout([&]() { return received_ok; });
   EXPECT_TRUE(success);
@@ -389,9 +388,7 @@
   bool received_ok = false;
   EXPECT_CALL(client_visitor, OnReply(full_track_name, expected_reason))
       .WillOnce([&]() { received_ok = true; });
-  client_->session()->SubscribeCurrentObject(full_track_name.track_namespace,
-                                             full_track_name.track_name,
-                                             &client_visitor);
+  client_->session()->SubscribeCurrentObject(full_track_name, &client_visitor);
   bool success =
       test_harness_.RunUntilWithDefaultTimeout([&]() { return received_ok; });
   EXPECT_TRUE(success);
@@ -411,9 +408,7 @@
   bool received_ok = false;
   EXPECT_CALL(client_visitor, OnReply(full_track_name, expected_reason))
       .WillOnce([&]() { received_ok = true; });
-  client_->session()->SubscribeCurrentGroup(full_track_name.track_namespace,
-                                            full_track_name.track_name,
-                                            &client_visitor);
+  client_->session()->SubscribeCurrentGroup(full_track_name, &client_visitor);
   bool success =
       test_harness_.RunUntilWithDefaultTimeout([&]() { return received_ok; });
   EXPECT_TRUE(success);
@@ -427,9 +422,7 @@
   bool received_ok = false;
   EXPECT_CALL(client_visitor, OnReply(full_track_name, expected_reason))
       .WillOnce([&]() { received_ok = true; });
-  client_->session()->SubscribeCurrentObject(full_track_name.track_namespace,
-                                             full_track_name.track_name,
-                                             &client_visitor);
+  client_->session()->SubscribeCurrentObject(full_track_name, &client_visitor);
   bool success =
       test_harness_.RunUntilWithDefaultTimeout([&]() { return received_ok; });
   EXPECT_TRUE(success);
@@ -467,9 +460,8 @@
 
   MoqtSubscribeParameters parameters;
   parameters.object_ack_window = quic::QuicTimeDelta::FromMilliseconds(100);
-  client_->session()->SubscribeCurrentObject(full_track_name.track_namespace,
-                                             full_track_name.track_name,
-                                             &client_visitor, parameters);
+  client_->session()->SubscribeCurrentObject(full_track_name, &client_visitor,
+                                             parameters);
   EXPECT_CALL(monitoring, OnObjectAckSupportKnown(true));
   EXPECT_CALL(
       monitoring,
diff --git a/quiche/quic/moqt/moqt_messages.cc b/quiche/quic/moqt/moqt_messages.cc
index df357fe..26acbac 100644
--- a/quiche/quic/moqt/moqt_messages.cc
+++ b/quiche/quic/moqt/moqt_messages.cc
@@ -5,9 +5,16 @@
 #include "quiche/quic/moqt/moqt_messages.h"
 
 #include <string>
+#include <vector>
 
+#include "absl/algorithm/container.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/platform/api/quic_bug_tracker.h"
+#include "quiche/common/platform/api/quiche_bug_tracker.h"
 
 namespace moqt {
 
@@ -164,4 +171,35 @@
   return MoqtDataStreamType::kObjectStream;
 }
 
+std::string FullTrackName::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, ", "), "}");
+}
+
+bool FullTrackName::operator==(const FullTrackName& other) const {
+  if (tuple_.size() != other.tuple_.size()) {
+    return false;
+  }
+  return absl::c_equal(tuple_, other.tuple_);
+}
+bool FullTrackName::operator<(const FullTrackName& other) const {
+  return absl::c_lexicographical_compare(tuple_, other.tuple_);
+}
+FullTrackName::FullTrackName(absl::Span<const absl::string_view> elements)
+    : tuple_(elements.begin(), elements.end()) {
+  if (tuple_.size() < 2) {
+    QUICHE_BUG(FullTrackName_too_short)
+        << "Full track name should be at least two elements long";
+    // Failsafe.
+    while (tuple_.size() < 2) {
+      tuple_.push_back("");
+    }
+    return;
+  }
+}
+
 }  // namespace moqt
diff --git a/quiche/quic/moqt/moqt_messages.h b/quiche/quic/moqt/moqt_messages.h
index aaf151d..b3db5b7 100644
--- a/quiche/quic/moqt/moqt_messages.h
+++ b/quiche/quic/moqt/moqt_messages.h
@@ -9,13 +9,16 @@
 
 #include <cstddef>
 #include <cstdint>
+#include <initializer_list>
 #include <optional>
 #include <string>
 #include <utility>
 #include <vector>
 
+#include "absl/container/inlined_vector.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"
@@ -151,39 +154,45 @@
   std::string reason_phrase;
 };
 
-struct FullTrackName {
-  std::string track_namespace;
-  std::string track_name;
-  FullTrackName(absl::string_view ns, absl::string_view name)
-      : track_namespace(ns), track_name(name) {}
-  bool operator==(const FullTrackName& other) const {
-    return track_namespace == other.track_namespace &&
-           track_name == other.track_name;
+// Full track name represents a tuple of the track namespace and the the track
+// name.  (TODO) After draft-06, multiple elements in track namespace will be
+// supported; if https://github.com/moq-wg/moq-transport/issues/508 goes
+// through, the distinction between different parts will disappear.
+class FullTrackName {
+ public:
+  explicit FullTrackName(absl::Span<const absl::string_view> elements);
+  explicit FullTrackName(
+      std::initializer_list<const absl::string_view> elements)
+      : FullTrackName(absl::Span<const absl::string_view>(
+            std::data(elements), std::size(elements))) {}
+  explicit FullTrackName(absl::string_view ns, absl::string_view name)
+      : FullTrackName({ns, name}) {}
+  FullTrackName() : FullTrackName({"", ""}) {}
+
+  std::string ToString() const;
+
+  absl::string_view track_namespace() const {
+    // TODO: turn into a tuple for draft-06.
+    return tuple_[0];
   }
-  bool operator<(const FullTrackName& other) const {
-    return track_namespace < other.track_namespace ||
-           (track_namespace == other.track_namespace &&
-            track_name < other.track_name);
-  }
-  FullTrackName& operator=(const FullTrackName& other) {
-    track_namespace = other.track_namespace;
-    track_name = other.track_name;
-    return *this;
-  }
+  absl::string_view track_name() const { return tuple_[tuple_.size() - 1]; }
+
+  bool operator==(const FullTrackName& other) const;
+  bool operator<(const FullTrackName& other) const;
+
   template <typename H>
-  friend H AbslHashValue(H h, const FullTrackName& m);
+  friend H AbslHashValue(H h, const FullTrackName& m) {
+    return H::combine(std::move(h), m.tuple_);
+  }
 
   template <typename Sink>
   friend void AbslStringify(Sink& sink, const FullTrackName& track_name) {
-    absl::Format(&sink, "(%s; %s)", track_name.track_namespace,
-                 track_name.track_name);
+    sink.Append(track_name.ToString());
   }
-};
 
-template <typename H>
-H AbslHashValue(H h, const FullTrackName& m) {
-  return H::combine(std::move(h), m.track_namespace, m.track_name);
-}
+ private:
+  absl::InlinedVector<std::string, 2> tuple_;
+};
 
 // These are absolute sequence numbers.
 struct FullSequence {
@@ -290,8 +299,7 @@
 struct QUICHE_EXPORT MoqtSubscribe {
   uint64_t subscribe_id;
   uint64_t track_alias;
-  std::string track_namespace;
-  std::string track_name;
+  FullTrackName full_track_name;
   MoqtPriority subscriber_priority;
   std::optional<MoqtDeliveryOrder> group_order;
 
@@ -410,8 +418,7 @@
 }
 
 struct QUICHE_EXPORT MoqtTrackStatus {
-  std::string track_namespace;
-  std::string track_name;
+  FullTrackName full_track_name;
   MoqtTrackStatusCode status_code;
   uint64_t last_group;
   uint64_t last_object;
@@ -422,8 +429,7 @@
 };
 
 struct QUICHE_EXPORT MoqtTrackStatusRequest {
-  std::string track_namespace;
-  std::string track_name;
+  FullTrackName full_track_name;
 };
 
 struct QUICHE_EXPORT MoqtGoAway {
diff --git a/quiche/quic/moqt/moqt_messages_test.cc b/quiche/quic/moqt/moqt_messages_test.cc
new file mode 100644
index 0000000..bec3c4b
--- /dev/null
+++ b/quiche/quic/moqt/moqt_messages_test.cc
@@ -0,0 +1,42 @@
+// 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_messages.h"
+
+#include <vector>
+
+#include "absl/hash/hash.h"
+#include "absl/strings/string_view.h"
+#include "quiche/common/platform/api/quiche_test.h"
+
+namespace moqt::test {
+namespace {
+
+TEST(MoqtMessagesTest, FullTrackNameConstructors) {
+  FullTrackName name1({"foo", "bar"});
+  std::vector<absl::string_view> list = {"foo", "bar"};
+  FullTrackName name2(list);
+  EXPECT_EQ(name1, name2);
+  EXPECT_EQ(absl::HashOf(name1), absl::HashOf(name2));
+}
+
+TEST(MoqtMessagesTest, FullTrackNameOrder) {
+  FullTrackName name1({"a", "b"});
+  FullTrackName name2({"a", "b", "c"});
+  FullTrackName name3({"b", "a"});
+  EXPECT_LT(name1, name2);
+  EXPECT_LT(name2, name3);
+  EXPECT_LT(name1, name3);
+}
+
+TEST(MoqtMessagesTest, FullTrackNameToString) {
+  FullTrackName name1({"a", "b"});
+  EXPECT_EQ(name1.ToString(), R"({"a", "b"})");
+
+  FullTrackName name2({"\xff", "\x61"});
+  EXPECT_EQ(name2.ToString(), R"({"\xff", "a"})");
+}
+
+}  // namespace
+}  // namespace moqt::test
diff --git a/quiche/quic/moqt/moqt_parser.cc b/quiche/quic/moqt/moqt_parser.cc
index 87c45b9..a00416b 100644
--- a/quiche/quic/moqt/moqt_parser.cc
+++ b/quiche/quic/moqt/moqt_parser.cc
@@ -25,6 +25,16 @@
 
 namespace {
 
+bool ReadFullTrackName(quic::QuicDataReader& reader, FullTrackName& out) {
+  absl::string_view track_namespace, track_name;
+  if (!reader.ReadStringPieceVarInt62(&track_namespace) ||
+      !reader.ReadStringPieceVarInt62(&track_name)) {
+    return false;
+  }
+  out = FullTrackName({track_namespace, track_name});
+  return true;
+}
+
 bool ParseDeliveryOrder(uint8_t raw_value,
                         std::optional<MoqtDeliveryOrder>& output) {
   switch (raw_value) {
@@ -400,8 +410,7 @@
   uint8_t group_order;
   if (!reader.ReadVarInt62(&subscribe_request.subscribe_id) ||
       !reader.ReadVarInt62(&subscribe_request.track_alias) ||
-      !reader.ReadStringVarInt62(subscribe_request.track_namespace) ||
-      !reader.ReadStringVarInt62(subscribe_request.track_name) ||
+      !ReadFullTrackName(reader, subscribe_request.full_track_name) ||
       !reader.ReadUInt8(&subscribe_request.subscriber_priority) ||
       !reader.ReadUInt8(&group_order) || !reader.ReadVarInt62(&filter)) {
     return 0;
@@ -706,10 +715,7 @@
 size_t MoqtControlParser::ProcessTrackStatusRequest(
     quic::QuicDataReader& reader) {
   MoqtTrackStatusRequest track_status_request;
-  if (!reader.ReadStringVarInt62(track_status_request.track_namespace)) {
-    return 0;
-  }
-  if (!reader.ReadStringVarInt62(track_status_request.track_name)) {
+  if (!ReadFullTrackName(reader, track_status_request.full_track_name)) {
     return 0;
   }
   visitor_.OnTrackStatusRequestMessage(track_status_request);
@@ -728,8 +734,7 @@
 size_t MoqtControlParser::ProcessTrackStatus(quic::QuicDataReader& reader) {
   MoqtTrackStatus track_status;
   uint64_t value;
-  if (!reader.ReadStringVarInt62(track_status.track_namespace) ||
-      !reader.ReadStringVarInt62(track_status.track_name) ||
+  if (!ReadFullTrackName(reader, track_status.full_track_name) ||
       !reader.ReadVarInt62(&value) ||
       !reader.ReadVarInt62(&track_status.last_group) ||
       !reader.ReadVarInt62(&track_status.last_object)) {
diff --git a/quiche/quic/moqt/moqt_session.cc b/quiche/quic/moqt/moqt_session.cc
index d11f9d2..a411269 100644
--- a/quiche/quic/moqt/moqt_session.cc
+++ b/quiche/quic/moqt/moqt_session.cc
@@ -250,14 +250,12 @@
   pending_outgoing_announces_[track_namespace] = std::move(announce_callback);
 }
 
-bool MoqtSession::SubscribeAbsolute(absl::string_view track_namespace,
-                                    absl::string_view name,
+bool MoqtSession::SubscribeAbsolute(const FullTrackName& name,
                                     uint64_t start_group, uint64_t start_object,
                                     RemoteTrack::Visitor* visitor,
                                     MoqtSubscribeParameters parameters) {
   MoqtSubscribe message;
-  message.track_namespace = track_namespace;
-  message.track_name = name;
+  message.full_track_name = name;
   message.subscriber_priority = kDefaultSubscriberPriority;
   message.group_order = std::nullopt;
   message.start_group = start_group;
@@ -268,8 +266,7 @@
   return Subscribe(message, visitor);
 }
 
-bool MoqtSession::SubscribeAbsolute(absl::string_view track_namespace,
-                                    absl::string_view name,
+bool MoqtSession::SubscribeAbsolute(const FullTrackName& name,
                                     uint64_t start_group, uint64_t start_object,
                                     uint64_t end_group,
                                     RemoteTrack::Visitor* visitor,
@@ -279,8 +276,7 @@
     return false;
   }
   MoqtSubscribe message;
-  message.track_namespace = track_namespace;
-  message.track_name = name;
+  message.full_track_name = name;
   message.subscriber_priority = kDefaultSubscriberPriority;
   message.group_order = std::nullopt;
   message.start_group = start_group;
@@ -291,8 +287,7 @@
   return Subscribe(message, visitor);
 }
 
-bool MoqtSession::SubscribeAbsolute(absl::string_view track_namespace,
-                                    absl::string_view name,
+bool MoqtSession::SubscribeAbsolute(const FullTrackName& name,
                                     uint64_t start_group, uint64_t start_object,
                                     uint64_t end_group, uint64_t end_object,
                                     RemoteTrack::Visitor* visitor,
@@ -306,8 +301,7 @@
     return false;
   }
   MoqtSubscribe message;
-  message.track_namespace = track_namespace;
-  message.track_name = name;
+  message.full_track_name = name;
   message.subscriber_priority = kDefaultSubscriberPriority;
   message.group_order = std::nullopt;
   message.start_group = start_group;
@@ -318,13 +312,11 @@
   return Subscribe(message, visitor);
 }
 
-bool MoqtSession::SubscribeCurrentObject(absl::string_view track_namespace,
-                                         absl::string_view name,
+bool MoqtSession::SubscribeCurrentObject(const FullTrackName& name,
                                          RemoteTrack::Visitor* visitor,
                                          MoqtSubscribeParameters parameters) {
   MoqtSubscribe message;
-  message.track_namespace = track_namespace;
-  message.track_name = name;
+  message.full_track_name = name;
   message.subscriber_priority = kDefaultSubscriberPriority;
   message.group_order = std::nullopt;
   message.start_group = std::nullopt;
@@ -335,13 +327,11 @@
   return Subscribe(message, visitor);
 }
 
-bool MoqtSession::SubscribeCurrentGroup(absl::string_view track_namespace,
-                                        absl::string_view name,
+bool MoqtSession::SubscribeCurrentGroup(const FullTrackName& name,
                                         RemoteTrack::Visitor* visitor,
                                         MoqtSubscribeParameters parameters) {
   MoqtSubscribe message;
-  message.track_namespace = track_namespace;
-  message.track_name = name;
+  message.full_track_name = name;
   message.subscriber_priority = kDefaultSubscriberPriority;
   message.group_order = std::nullopt;
   // First object of current group.
@@ -399,9 +389,7 @@
     return false;
   }
   message.subscribe_id = next_subscribe_id_++;
-  FullTrackName ftn(std::string(message.track_namespace),
-                    std::string(message.track_name));
-  auto it = remote_track_aliases_.find(ftn);
+  auto it = remote_track_aliases_.find(message.full_track_name);
   if (it != remote_track_aliases_.end()) {
     message.track_alias = it->second;
     if (message.track_alias >= next_remote_track_alias_) {
@@ -423,7 +411,7 @@
   }
   SendControlMessage(framer_.SerializeSubscribe(message));
   QUIC_DLOG(INFO) << ENDPOINT << "Sent SUBSCRIBE message for "
-                  << message.track_namespace << ":" << message.track_name;
+                  << message.full_track_name;
   active_subscribes_.try_emplace(message.subscribe_id, message, visitor);
   return true;
 }
@@ -517,7 +505,7 @@
     auto subscribe_it = active_subscribes_.find(message.subscribe_id);
     if (subscribe_it == active_subscribes_.end()) {
       return std::pair<FullTrackName, RemoteTrack::Visitor*>(
-          {{"", ""}, nullptr});
+          {FullTrackName{"", ""}, nullptr});
     }
     ActiveSubscribe& subscribe = subscribe_it->second;
     visitor = subscribe.visitor;
@@ -527,26 +515,22 @@
         Error(MoqtError::kProtocolViolation,
               "Forwarding preference changes mid-track");
         return std::pair<FullTrackName, RemoteTrack::Visitor*>(
-            {{"", ""}, nullptr});
+            {FullTrackName{"", ""}, nullptr});
       }
     } else {
       subscribe.forwarding_preference = message.forwarding_preference;
     }
-    return std::pair<FullTrackName, RemoteTrack::Visitor*>(
-        {{subscribe.message.track_namespace, subscribe.message.track_name},
-         subscribe.visitor});
+    return std::make_pair(subscribe.message.full_track_name, subscribe.visitor);
   }
   RemoteTrack& track = it->second;
   if (!track.CheckForwardingPreference(message.forwarding_preference)) {
     // Incorrect forwarding preference.
     Error(MoqtError::kProtocolViolation,
           "Forwarding preference changes mid-track");
-    return std::pair<FullTrackName, RemoteTrack::Visitor*>({{"", ""}, nullptr});
+    return std::pair<FullTrackName, RemoteTrack::Visitor*>(
+        {FullTrackName{"", ""}, nullptr});
   }
-  return std::pair<FullTrackName, RemoteTrack::Visitor*>(
-      {{track.full_track_name().track_namespace,
-        track.full_track_name().track_name},
-       track.visitor()});
+  return std::make_pair(track.full_track_name(), track.visitor());
 }
 
 template <class Parser>
@@ -682,10 +666,9 @@
     return;
   }
   QUIC_DLOG(INFO) << ENDPOINT << "Received a SUBSCRIBE for "
-                  << message.track_namespace << ":" << message.track_name;
+                  << message.full_track_name;
 
-  FullTrackName track_name =
-      FullTrackName{message.track_namespace, message.track_name};
+  const FullTrackName& track_name = message.full_track_name;
   absl::StatusOr<std::shared_ptr<MoqtTrackPublisher>> track_publisher =
       session_->publisher_->GetTrack(track_name);
   if (!track_publisher.ok()) {
@@ -742,13 +725,13 @@
   MoqtSubscribe& subscribe = it->second.message;
   QUIC_DLOG(INFO) << ENDPOINT << "Received the SUBSCRIBE_OK for "
                   << "subscribe_id = " << message.subscribe_id << " "
-                  << subscribe.track_namespace << ":" << subscribe.track_name;
+                  << subscribe.full_track_name;
   // Copy the Remote Track from session_->active_subscribes_ to
   // session_->remote_tracks_.
-  FullTrackName ftn(subscribe.track_namespace, subscribe.track_name);
   RemoteTrack::Visitor* visitor = it->second.visitor;
   auto [track_iter, new_entry] = session_->remote_tracks_.try_emplace(
-      subscribe.track_alias, ftn, subscribe.track_alias, visitor);
+      subscribe.track_alias, subscribe.full_track_name, subscribe.track_alias,
+      visitor);
   if (it->second.forwarding_preference.has_value()) {
     if (!track_iter->second.CheckForwardingPreference(
             *it->second.forwarding_preference)) {
@@ -759,7 +742,7 @@
   }
   // TODO: handle expires.
   if (visitor != nullptr) {
-    visitor->OnReply(ftn, std::nullopt);
+    visitor->OnReply(subscribe.full_track_name, std::nullopt);
   }
   session_->active_subscribes_.erase(it);
 }
@@ -780,17 +763,17 @@
   MoqtSubscribe& subscribe = it->second.message;
   QUIC_DLOG(INFO) << ENDPOINT << "Received the SUBSCRIBE_ERROR for "
                   << "subscribe_id = " << message.subscribe_id << " ("
-                  << subscribe.track_namespace << ":" << subscribe.track_name
-                  << ")" << ", error = " << static_cast<int>(message.error_code)
+                  << subscribe.full_track_name << ")"
+                  << ", error = " << static_cast<int>(message.error_code)
                   << " (" << message.reason_phrase << ")";
   RemoteTrack::Visitor* visitor = it->second.visitor;
-  FullTrackName ftn(subscribe.track_namespace, subscribe.track_name);
   if (message.error_code == SubscribeErrorCode::kRetryTrackAlias) {
     // Automatically resubscribe with new alias.
-    session_->remote_track_aliases_[ftn] = message.track_alias;
+    session_->remote_track_aliases_[subscribe.full_track_name] =
+        message.track_alias;
     session_->Subscribe(subscribe, visitor);
   } else if (visitor != nullptr) {
-    visitor->OnReply(ftn, message.reason_phrase);
+    visitor->OnReply(subscribe.full_track_name, message.reason_phrase);
   }
   session_->active_subscribes_.erase(it);
 }
@@ -983,7 +966,7 @@
         subscribe.parameters.object_ack_window.has_value());
   }
   QUIC_DLOG(INFO) << ENDPOINT << "Created subscription for "
-                  << subscribe.track_namespace << ":" << subscribe.track_name;
+                  << subscribe.full_track_name;
 }
 
 MoqtSession::PublishedSubscription::~PublishedSubscription() {
diff --git a/quiche/quic/moqt/moqt_session.h b/quiche/quic/moqt/moqt_session.h
index 79104c6..b3124a9 100644
--- a/quiche/quic/moqt/moqt_session.h
+++ b/quiche/quic/moqt/moqt_session.h
@@ -116,29 +116,24 @@
   // ignored.
   // Subscribe from (start_group, start_object) to the end of the track.
   bool SubscribeAbsolute(
-      absl::string_view track_namespace, absl::string_view name,
-      uint64_t start_group, uint64_t start_object,
+      const FullTrackName& name, uint64_t start_group, uint64_t start_object,
       RemoteTrack::Visitor* visitor,
       MoqtSubscribeParameters parameters = MoqtSubscribeParameters());
   // Subscribe from (start_group, start_object) to the end of end_group.
   bool SubscribeAbsolute(
-      absl::string_view track_namespace, absl::string_view name,
-      uint64_t start_group, uint64_t start_object, uint64_t end_group,
-      RemoteTrack::Visitor* visitor,
+      const FullTrackName& name, uint64_t start_group, uint64_t start_object,
+      uint64_t end_group, RemoteTrack::Visitor* visitor,
       MoqtSubscribeParameters parameters = MoqtSubscribeParameters());
   // Subscribe from (start_group, start_object) to (end_group, end_object).
   bool SubscribeAbsolute(
-      absl::string_view track_namespace, absl::string_view name,
-      uint64_t start_group, uint64_t start_object, uint64_t end_group,
-      uint64_t end_object, RemoteTrack::Visitor* visitor,
+      const FullTrackName& name, uint64_t start_group, uint64_t start_object,
+      uint64_t end_group, uint64_t end_object, RemoteTrack::Visitor* visitor,
       MoqtSubscribeParameters parameters = MoqtSubscribeParameters());
   bool SubscribeCurrentObject(
-      absl::string_view track_namespace, absl::string_view name,
-      RemoteTrack::Visitor* visitor,
+      const FullTrackName& name, RemoteTrack::Visitor* visitor,
       MoqtSubscribeParameters parameters = MoqtSubscribeParameters());
   bool SubscribeCurrentGroup(
-      absl::string_view track_namespace, absl::string_view name,
-      RemoteTrack::Visitor* visitor,
+      const FullTrackName& name, RemoteTrack::Visitor* visitor,
       MoqtSubscribeParameters parameters = MoqtSubscribeParameters());
 
   webtransport::Session* session() { return session_; }
diff --git a/quiche/quic/moqt/moqt_session_test.cc b/quiche/quic/moqt/moqt_session_test.cc
index 25783a3..6c29a4b 100644
--- a/quiche/quic/moqt/moqt_session_test.cc
+++ b/quiche/quic/moqt/moqt_session_test.cc
@@ -126,8 +126,7 @@
       uint64_t subscribe_id, uint64_t track_alias, uint64_t start_group,
       uint64_t start_object) {
     MoqtSubscribe subscribe;
-    subscribe.track_namespace = publisher->GetTrackName().track_namespace;
-    subscribe.track_name = publisher->GetTrackName().track_name;
+    subscribe.full_track_name = publisher->GetTrackName();
     subscribe.track_alias = track_alias;
     subscribe.subscribe_id = subscribe_id;
     subscribe.start_group = start_group;
@@ -314,8 +313,7 @@
   MoqtSubscribe request = {
       /*subscribe_id=*/1,
       /*track_alias=*/2,
-      /*track_namespace=*/"foo",
-      /*track_name=*/"bar",
+      /*full_track_name=*/FullTrackName({"foo", "bar"}),
       /*subscriber_priority=*/0x80,
       /*group_order=*/std::nullopt,
       /*start_group=*/0,
@@ -450,8 +448,7 @@
   MoqtSubscribe request = {
       /*subscribe_id=*/1,
       /*track_alias=*/2,
-      /*track_namespace=*/"foo",
-      /*track_name=*/"bar",
+      /*full_track_name=*/FullTrackName({"foo", "bar"}),
       /*subscriber_priority=*/0x80,
       /*group_order=*/std::nullopt,
       /*start_group=*/0,
@@ -480,8 +477,7 @@
   MoqtSubscribe request = {
       /*subscribe_id=*/kDefaultInitialMaxSubscribeId + 1,
       /*track_alias=*/2,
-      /*track_namespace=*/"foo",
-      /*track_name=*/"bar",
+      /*full_track_name=*/FullTrackName({"foo", "bar"}),
       /*subscriber_priority=*/0x80,
       /*group_order=*/std::nullopt,
       /*start_group=*/0,
@@ -516,10 +512,10 @@
         EXPECT_EQ(*ExtractMessageType(data[0]), MoqtMessageType::kSubscribe);
         return absl::OkStatus();
       });
-  EXPECT_TRUE(
-      session_.SubscribeCurrentGroup("foo", "bar", &remote_track_visitor));
-  EXPECT_FALSE(
-      session_.SubscribeCurrentGroup("foo", "bar", &remote_track_visitor));
+  EXPECT_TRUE(session_.SubscribeCurrentGroup(FullTrackName("foo", "bar"),
+                                             &remote_track_visitor));
+  EXPECT_FALSE(session_.SubscribeCurrentGroup(FullTrackName("foo", "bar"),
+                                              &remote_track_visitor));
 }
 
 TEST_F(MoqtSessionTest, SubscribeWithOk) {
@@ -536,7 +532,8 @@
         EXPECT_EQ(*ExtractMessageType(data[0]), MoqtMessageType::kSubscribe);
         return absl::OkStatus();
       });
-  session_.SubscribeCurrentGroup("foo", "bar", &remote_track_visitor);
+  session_.SubscribeCurrentGroup(FullTrackName("foo", "bar"),
+                                 &remote_track_visitor);
 
   MoqtSubscribeOk ok = {
       /*subscribe_id=*/0,
@@ -558,8 +555,8 @@
   MoqtSessionPeer::set_next_subscribe_id(&session_,
                                          kDefaultInitialMaxSubscribeId + 1);
   MockRemoteTrackVisitor remote_track_visitor;
-  EXPECT_FALSE(
-      session_.SubscribeCurrentGroup("foo", "bar", &remote_track_visitor));
+  EXPECT_FALSE(session_.SubscribeCurrentGroup(FullTrackName("foo", "bar"),
+                                              &remote_track_visitor));
   MoqtMaxSubscribeId max_subscribe_id = {
       /*max_subscribe_id=*/kDefaultInitialMaxSubscribeId + 1,
   };
@@ -576,8 +573,8 @@
         EXPECT_EQ(*ExtractMessageType(data[0]), MoqtMessageType::kSubscribe);
         return absl::OkStatus();
       });
-  EXPECT_TRUE(
-      session_.SubscribeCurrentGroup("foo", "bar", &remote_track_visitor));
+  EXPECT_TRUE(session_.SubscribeCurrentGroup(FullTrackName("foo", "bar"),
+                                             &remote_track_visitor));
   EXPECT_TRUE(correct_message);
 }
 
@@ -616,8 +613,7 @@
   MoqtSubscribe request = {
       /*subscribe_id=*/kDefaultInitialMaxSubscribeId + 1,
       /*track_alias=*/2,
-      /*track_namespace=*/"foo",
-      /*track_name=*/"bar",
+      /*full_track_name=*/FullTrackName({"foo", "bar"}),
       /*subscriber_priority=*/0x80,
       /*group_order=*/std::nullopt,
       /*start_group=*/0,
@@ -664,7 +660,8 @@
         EXPECT_EQ(*ExtractMessageType(data[0]), MoqtMessageType::kSubscribe);
         return absl::OkStatus();
       });
-  session_.SubscribeCurrentGroup("foo", "bar", &remote_track_visitor);
+  session_.SubscribeCurrentGroup(FullTrackName("foo", "bar"),
+                                 &remote_track_visitor);
 
   MoqtSubscribeError error = {
       /*subscribe_id=*/0,
@@ -793,8 +790,7 @@
   MoqtSubscribe subscribe = {
       /*subscribe_id=*/1,
       /*track_alias=*/2,
-      /*track_namespace=*/ftn.track_namespace,
-      /*track_name=*/ftn.track_name,
+      /*full_track_name=*/ftn,
       /*subscriber_priority=*/0x80,
       /*group_order=*/std::nullopt,
       /*start_group=*/0,
@@ -852,8 +848,7 @@
   MoqtSubscribe subscribe = {
       /*subscribe_id=*/1,
       /*track_alias=*/2,
-      /*track_namespace=*/ftn.track_namespace,
-      /*track_name=*/ftn.track_name,
+      /*full_track_name=*/ftn,
       /*subscriber_priority=*/0x80,
       /*group_order=*/std::nullopt,
       /*start_group=*/0,
@@ -914,8 +909,7 @@
   MoqtSubscribe subscribe = {
       /*subscribe_id=*/1,
       /*track_alias=*/2,
-      /*track_namespace=*/ftn.track_namespace,
-      /*track_name=*/ftn.track_name,
+      /*full_track_name=*/ftn,
       /*subscriber_priority=*/0x80,
       /*group_order=*/std::nullopt,
       /*start_group=*/0,
@@ -967,8 +961,7 @@
   MoqtSubscribe subscribe = {
       /*subscribe_id=*/1,
       /*track_alias=*/2,
-      /*track_namespace=*/ftn.track_namespace,
-      /*track_name=*/ftn.track_name,
+      /*full_track_name=*/ftn,
       /*subscriber_priority=*/0x80,
       /*group_order=*/std::nullopt,
       /*start_group=*/0,
@@ -1374,8 +1367,7 @@
   MoqtSubscribe request = {
       /*subscribe_id=*/1,
       /*track_alias=*/2,
-      /*track_namespace=*/"foo",
-      /*track_name=*/"bar",
+      /*full_track_name=*/FullTrackName({"foo", "bar"}),
       /*subscriber_priority=*/0x80,
       /*group_order=*/std::nullopt,
       /*start_group=*/0,
diff --git a/quiche/quic/moqt/test_tools/moqt_test_message.h b/quiche/quic/moqt/test_tools/moqt_test_message.h
index 813d4e8..1be7b08 100644
--- a/quiche/quic/moqt/test_tools/moqt_test_message.h
+++ b/quiche/quic/moqt/test_tools/moqt_test_message.h
@@ -411,11 +411,7 @@
       QUIC_LOG(INFO) << "SUBSCRIBE track alias mismatch";
       return false;
     }
-    if (cast.track_namespace != subscribe_.track_namespace) {
-      QUIC_LOG(INFO) << "SUBSCRIBE track namespace mismatch";
-      return false;
-    }
-    if (cast.track_name != subscribe_.track_name) {
+    if (cast.full_track_name != subscribe_.full_track_name) {
       QUIC_LOG(INFO) << "SUBSCRIBE track name mismatch";
       return false;
     }
@@ -481,8 +477,7 @@
   MoqtSubscribe subscribe_ = {
       /*subscribe_id=*/1,
       /*track_alias=*/2,
-      /*track_namespace=*/"foo",
-      /*track_name=*/"abcd",
+      /*full_track_name=*/FullTrackName({"foo", "abcd"}),
       /*subscriber_priority=*/0x20,
       /*group_order=*/MoqtDeliveryOrder::kDescending,
       /*start_group=*/4,
@@ -899,11 +894,7 @@
 
   bool EqualFieldValues(MessageStructuredData& values) const override {
     auto cast = std::get<MoqtTrackStatusRequest>(values);
-    if (cast.track_namespace != track_status_request_.track_namespace) {
-      QUIC_LOG(INFO) << "TRACK STATUS REQUEST track namespace mismatch";
-      return false;
-    }
-    if (cast.track_name != track_status_request_.track_name) {
+    if (cast.full_track_name != track_status_request_.full_track_name) {
       QUIC_LOG(INFO) << "TRACK STATUS REQUEST track name mismatch";
       return false;
     }
@@ -923,8 +914,7 @@
   };
 
   MoqtTrackStatusRequest track_status_request_ = {
-      /*track_namespace=*/"foo",
-      /*track_name=*/"abcd",
+      /*full_track_name=*/FullTrackName({"foo", "abcd"}),
   };
 };
 
@@ -967,11 +957,7 @@
 
   bool EqualFieldValues(MessageStructuredData& values) const override {
     auto cast = std::get<MoqtTrackStatus>(values);
-    if (cast.track_namespace != track_status_.track_namespace) {
-      QUIC_LOG(INFO) << "TRACK STATUS track namespace mismatch";
-      return false;
-    }
-    if (cast.track_name != track_status_.track_name) {
+    if (cast.full_track_name != track_status_.full_track_name) {
       QUIC_LOG(INFO) << "TRACK STATUS track name mismatch";
       return false;
     }
@@ -1004,8 +990,7 @@
   };
 
   MoqtTrackStatus track_status_ = {
-      /*track_namespace=*/"foo",
-      /*track_name=*/"abcd",
+      /*full_track_name=*/FullTrackName({"foo", "abcd"}),
       /*status_code=*/MoqtTrackStatusCode::kInProgress,
       /*last_group=*/12,
       /*last_object=*/20,
diff --git a/quiche/quic/moqt/tools/chat_client.cc b/quiche/quic/moqt/tools/chat_client.cc
index f5bc8df..9e43485 100644
--- a/quiche/quic/moqt/tools/chat_client.cc
+++ b/quiche/quic/moqt/tools/chat_client.cc
@@ -114,7 +114,7 @@
   if (full_track_name == client_->chat_strings_->GetCatalogName()) {
     std::cout << "Subscription to catalog ";
   } else {
-    std::cout << "Subscription to user " << full_track_name.track_namespace
+    std::cout << "Subscription to user " << full_track_name.track_namespace()
               << " ";
   }
   if (reason_phrase.has_value()) {
@@ -142,7 +142,7 @@
     client_->ProcessCatalog(object, this, group_sequence, object_sequence);
     return;
   }
-  std::string username = full_track_name.track_namespace;
+  std::string username(full_track_name.track_namespace());
   username = username.substr(username.find_last_of('/') + 1);
   if (!client_->other_users_.contains(username)) {
     std::cout << "Username " << username << "doesn't exist\n";
@@ -181,15 +181,14 @@
           std::cout << "ANNOUNCE for " << track_namespace << " accepted\n";
           return;
         };
-    std::cout << "Announcing " << my_track_name.track_namespace << "\n";
-    session_->Announce(my_track_name.track_namespace,
+    std::cout << "Announcing " << my_track_name.track_namespace() << "\n";
+    session_->Announce(my_track_name.track_namespace(),
                        std::move(announce_callback));
   }
   remote_track_visitor_ = std::make_unique<RemoteTrackVisitor>(this);
   FullTrackName catalog_name = chat_strings_->GetCatalogName();
   if (!session_->SubscribeCurrentGroup(
-          catalog_name.track_namespace, catalog_name.track_name,
-          remote_track_visitor_.get(),
+          catalog_name, remote_track_visitor_.get(),
           MoqtSubscribeParameters{username_, std::nullopt})) {
     std::cout << "Failed to get catalog\n";
     return false;
@@ -263,9 +262,7 @@
       auto new_user = other_users_.emplace(
           std::make_pair(user, ChatUser(to_subscribe, group_sequence)));
       ChatUser& user_record = new_user.first->second;
-      session_->SubscribeCurrentGroup(
-          user_record.full_track_name.track_namespace,
-          user_record.full_track_name.track_name, visitor);
+      session_->SubscribeCurrentGroup(user_record.full_track_name, visitor);
       subscribes_to_make_++;
     } else {
       if (it->second.from_group == group_sequence) {
diff --git a/quiche/quic/moqt/tools/chat_server.cc b/quiche/quic/moqt/tools/chat_server.cc
index b1b7b0a..c7c6ce5 100644
--- a/quiche/quic/moqt/tools/chat_server.cc
+++ b/quiche/quic/moqt/tools/chat_server.cc
@@ -41,7 +41,7 @@
           std::cout << "Malformed ANNOUNCE namespace\n";
           return std::nullopt;
         }
-        session_->SubscribeCurrentGroup(track_namespace, "",
+        session_->SubscribeCurrentGroup(FullTrackName({track_namespace, ""}),
                                         server_->remote_track_visitor());
         server_->AddUser(*username_);
         return std::nullopt;
@@ -69,7 +69,7 @@
 void ChatServer::RemoteTrackVisitor::OnReply(
     const moqt::FullTrackName& full_track_name,
     std::optional<absl::string_view> reason_phrase) {
-  std::cout << "Subscription to user " << full_track_name.track_namespace
+  std::cout << "Subscription to user " << full_track_name.track_namespace()
             << " ";
   if (reason_phrase.has_value()) {
     std::cout << "REJECTED, reason = " << *reason_phrase << "\n";
diff --git a/quiche/quic/moqt/tools/moq_chat.h b/quiche/quic/moqt/tools/moq_chat.h
index e2f9041..1e6ac66 100644
--- a/quiche/quic/moqt/tools/moq_chat.h
+++ b/quiche/quic/moqt/tools/moq_chat.h
@@ -47,10 +47,10 @@
   std::string GetUsernameFromFullTrackName(
       FullTrackName full_track_name) const {
     // Check the full path
-    if (!full_track_name.track_name.empty()) {
+    if (!full_track_name.track_name().empty()) {
       return "";
     }
-    return GetUsernameFromTrackNamespace(full_track_name.track_namespace);
+    return GetUsernameFromTrackNamespace(full_track_name.track_namespace());
   }
 
   FullTrackName GetFullTrackNameFromUsername(absl::string_view username) const {
diff --git a/quiche/quic/moqt/tools/moqt_ingestion_server_bin.cc b/quiche/quic/moqt/tools/moqt_ingestion_server_bin.cc
index a66eb8d..4e4ac10 100644
--- a/quiche/quic/moqt/tools/moqt_ingestion_server_bin.cc
+++ b/quiche/quic/moqt/tools/moqt_ingestion_server_bin.cc
@@ -142,7 +142,8 @@
     std::vector<absl::string_view> tracks_to_subscribe =
         absl::StrSplit(track_list, ',', absl::AllowEmpty());
     for (absl::string_view track : tracks_to_subscribe) {
-      session_->SubscribeCurrentGroup(track_namespace, track, &it->second);
+      session_->SubscribeCurrentGroup(FullTrackName({track_namespace, track}),
+                                      &it->second);
     }
 
     return std::nullopt;
@@ -159,9 +160,7 @@
         std::optional<absl::string_view> error_reason_phrase) override {
       if (error_reason_phrase.has_value()) {
         QUICHE_LOG(ERROR) << "Failed to subscribe to the peer track "
-                          << full_track_name.track_namespace << " "
-                          << full_track_name.track_name << ": "
-                          << *error_reason_phrase;
+                          << full_track_name << ": " << *error_reason_phrase;
       }
     }
 
@@ -175,7 +174,7 @@
                           absl::string_view object,
                           bool /*end_of_message*/) override {
       std::string file_name = absl::StrCat(group_sequence, "-", object_sequence,
-                                           ".", full_track_name.track_name);
+                                           ".", full_track_name.track_name());
       std::string file_path = quiche::JoinPath(directory_, file_name);
       std::ofstream output(file_path, std::ios::binary | std::ios::ate);
       output.write(object.data(), object.size());
diff --git a/quiche/quic/moqt/tools/moqt_simulator_bin.cc b/quiche/quic/moqt/tools/moqt_simulator_bin.cc
index 9916eca..6d08147 100644
--- a/quiche/quic/moqt/tools/moqt_simulator_bin.cc
+++ b/quiche/quic/moqt/tools/moqt_simulator_bin.cc
@@ -357,8 +357,7 @@
     //       server does not yet have an active subscription, so the client has
     //       some catching up to do.
     generator_.Start();
-    server_session()->SubscribeCurrentGroup(TrackName().track_namespace,
-                                            TrackName().track_name, &receiver_);
+    server_session()->SubscribeCurrentGroup(TrackName(), &receiver_);
     simulator_.RunFor(parameters_.duration);
 
     // At the end, we wait for eight RTTs until the connection settles down.