Support standard encoding for track names and namespaces.

PiperOrigin-RevId: 869284787
diff --git a/quiche/quic/moqt/moqt_names.cc b/quiche/quic/moqt/moqt_names.cc
index fa52970..4b135da 100644
--- a/quiche/quic/moqt/moqt_names.cc
+++ b/quiche/quic/moqt/moqt_names.cc
@@ -11,6 +11,7 @@
 
 #include "absl/status/status.h"
 #include "absl/status/statusor.h"
+#include "absl/strings/ascii.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/str_format.h"
 #include "absl/strings/string_view.h"
@@ -19,6 +20,41 @@
 
 namespace moqt {
 
+namespace {
+
+bool IsTrackNameSafeCharacter(char c) {
+  return absl::ascii_isalnum(c) || c == '_';
+}
+
+// Appends escaped version of a track name component into `output`.  It is up to
+// the caller to reserve() an appropriate amount of space in advance.  The text
+// format is defined in
+// https://www.ietf.org/archive/id/draft-ietf-moq-transport-16.html#name-representing-namespace-and-amount
+void EscapeTrackNameComponent(absl::string_view input, std::string& output) {
+  for (char c : input) {
+    if (IsTrackNameSafeCharacter(c)) {
+      output.push_back(c);
+    } else {
+      output.push_back('.');
+      absl::StrAppend(&output, absl::Hex(c, absl::kZeroPad2));
+    }
+  }
+}
+
+// Similarly to the function above, the caller should call reserve() on `output`
+// before calling.
+void AppendEscapedTrackNameTuple(const MoqtStringTuple& tuple,
+                                 std::string& output) {
+  for (size_t i = 0; i < tuple.size(); ++i) {
+    EscapeTrackNameComponent(tuple[i], output);
+    if (i < (tuple.size() - 1)) {
+      output.push_back('-');
+    }
+  }
+}
+
+}  // namespace
+
 absl::StatusOr<TrackNamespace> TrackNamespace::Create(MoqtStringTuple tuple) {
   if (tuple.size() > kMaxNamespaceElements) {
     return absl::OutOfRangeError(
@@ -56,8 +92,10 @@
 }
 
 std::string TrackNamespace::ToString() const {
-  // TODO(vasilvv): switch to the standard encoding.
-  return absl::StrCat(tuple_);
+  std::string output;
+  output.reserve(3 * tuple_.TotalBytes() + tuple_.size());
+  AppendEscapedTrackNameTuple(tuple_, output);
+  return output;
 }
 
 absl::StatusOr<FullTrackName> FullTrackName::Create(TrackNamespace ns,
@@ -84,7 +122,7 @@
 }
 FullTrackName::FullTrackName(absl::string_view ns, absl::string_view name)
     : namespace_(TrackNamespace({ns})), name_(name) {
-  if (ns.size() + name.size() > kMaxFullTrackNameSize) {
+  if (namespace_.total_length() + name.size() > kMaxFullTrackNameSize) {
     QUICHE_BUG(Moqt_full_track_name_too_large_02)
         << "Constructing a Full Track Name that is too large.";
     namespace_.Clear();
@@ -107,8 +145,13 @@
     : namespace_(std::move(ns)), name_(std::move(name)) {}
 
 std::string FullTrackName::ToString() const {
-  // TODO(vasilvv): switch to the standard encoding.
-  return absl::StrCat(namespace_.ToString(), "::", name_);
+  std::string output;
+  output.reserve(3 * namespace_.total_length() +
+                 namespace_.number_of_elements() + 3 * name_.size() + 2);
+  AppendEscapedTrackNameTuple(namespace_.tuple(), output);
+  output.append("--");
+  EscapeTrackNameComponent(name_, output);
+  return output;
 }
 
 }  // namespace moqt
diff --git a/quiche/quic/moqt/moqt_names.h b/quiche/quic/moqt/moqt_names.h
index 7d91579..c447233 100644
--- a/quiche/quic/moqt/moqt_names.h
+++ b/quiche/quic/moqt/moqt_names.h
@@ -18,7 +18,6 @@
 #include "absl/status/statusor.h"
 #include "absl/strings/string_view.h"
 #include "absl/types/span.h"
-#include "quiche/common/quiche_status_utils.h"
 #include "quiche/common/quiche_string_tuple.h"
 
 namespace moqt {
@@ -73,7 +72,10 @@
     return result;
   }
 
+  // Encodes the string representation of MOQT track namespace in the format
+  // prescribed by the MOQT specification.
   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.
@@ -128,6 +130,8 @@
   absl::string_view name() const ABSL_ATTRIBUTE_LIFETIME_BOUND { return name_; }
   size_t length() const { return namespace_.total_length() + name_.length(); }
 
+  // Encodes the string representation of MOQT full track name in the format
+  // prescribed by the MOQT specification.
   std::string ToString() const;
 
   auto operator<=>(const FullTrackName&) const = default;
diff --git a/quiche/quic/moqt/moqt_names_test.cc b/quiche/quic/moqt/moqt_names_test.cc
index dd879cf..d10b986 100644
--- a/quiche/quic/moqt/moqt_names_test.cc
+++ b/quiche/quic/moqt/moqt_names_test.cc
@@ -5,7 +5,6 @@
 #include "quiche/quic/moqt/moqt_names.h"
 
 #include <utility>
-#include <vector>
 
 #include "absl/hash/hash.h"
 #include "absl/status/status.h"
@@ -65,15 +64,18 @@
 
 TEST(MoqtNamesTest, TrackNamespaceToString) {
   TrackNamespace name1({"a", "b"});
-  EXPECT_EQ(name1.ToString(), R"({"a", "b"})");
+  EXPECT_EQ(name1.ToString(), "a-b");
 
-  TrackNamespace name2({"\xff", "\x61"});
-  EXPECT_EQ(name2.ToString(), R"({"\xff", "a"})");
+  TrackNamespace name2({"\xff\x01", "\x61"});
+  EXPECT_EQ(name2.ToString(), ".ff.01-a");
+
+  TrackNamespace name3({"a_b", "c_d?"});
+  EXPECT_EQ(name3.ToString(), "a_b-c_d.3f");
 }
 
 TEST(MoqtNamesTest, FullTrackNameToString) {
-  FullTrackName name1({"a", "b"}, "c");
-  EXPECT_EQ(name1.ToString(), R"({"a", "b"}::c)");
+  FullTrackName name1(TrackNamespace{"a", "b"}, "c");
+  EXPECT_EQ(name1.ToString(), "a-b--c");
 }
 
 TEST(MoqtNamesTest, TrackNamespaceSuffixes) {