Add support for MOQT Authority and Implementation parameters.

PiperOrigin-RevId: 803529615
diff --git a/quiche/quic/moqt/moqt_framer.cc b/quiche/quic/moqt/moqt_framer.cc
index 7ba6ab5..203f8b3 100644
--- a/quiche/quic/moqt/moqt_framer.cc
+++ b/quiche/quic/moqt/moqt_framer.cc
@@ -220,7 +220,10 @@
   if (!parameters.using_webtrans &&
       parameters.perspective == quic::Perspective::IS_CLIENT) {
     out.insert(SetupParameter::kPath, parameters.path);
+    out.insert(SetupParameter::kAuthority, parameters.authority);
   }
+  out.insert(SetupParameter::kMoqtImplementation,
+             parameters.moqt_implementation);
   if (parameters.max_request_id > 0) {
     out.insert(SetupParameter::kMaxRequestId, parameters.max_request_id);
   }
diff --git a/quiche/quic/moqt/moqt_messages.cc b/quiche/quic/moqt/moqt_messages.cc
index 4fb893c..c28ad79 100644
--- a/quiche/quic/moqt/moqt_messages.cc
+++ b/quiche/quic/moqt/moqt_messages.cc
@@ -161,6 +161,11 @@
     // Only non-webtrans servers should receive kPath.
     return MoqtError::kInvalidPath;
   }
+  if ((webtrans || perspective == quic::Perspective::IS_CLIENT) &&
+      parameters.contains(SetupParameter::kAuthority)) {
+    // Only non-webtrans servers should receive kAuthority.
+    return MoqtError::kInvalidAuthority;
+  }
   if (!parameters.contains(SetupParameter::kSupportObjectAcks)) {
     return MoqtError::kNoError;
   }
@@ -173,15 +178,21 @@
   return MoqtError::kNoError;
 }
 
-const std::array<MoqtMessageType, 8> kAllowsAuthorization = {
-    MoqtMessageType::kClientSetup, MoqtMessageType::kServerSetup,
-    MoqtMessageType::kSubscribe,   MoqtMessageType::kSubscribeNamespace,
-    MoqtMessageType::kAnnounce,    MoqtMessageType::kTrackStatus,
-    MoqtMessageType::kFetch,       MoqtMessageType::kPublish};
-const std::array<MoqtMessageType, 6> kAllowsDeliveryTimeout = {
-    MoqtMessageType::kSubscribe,       MoqtMessageType::kSubscribeOk,
-    MoqtMessageType::kSubscribeUpdate, MoqtMessageType::kTrackStatusOk,
-    MoqtMessageType::kPublish,         MoqtMessageType::kPublishOk};
+const std::array<MoqtMessageType, 9> kAllowsAuthorization = {
+    MoqtMessageType::kClientSetup,
+    MoqtMessageType::kServerSetup,
+    MoqtMessageType::kPublish,
+    MoqtMessageType::kSubscribe,
+    MoqtMessageType::kSubscribeUpdate,
+    MoqtMessageType::kSubscribeNamespace,
+    MoqtMessageType::kAnnounce,
+    MoqtMessageType::kTrackStatus,
+    MoqtMessageType::kFetch};
+const std::array<MoqtMessageType, 7> kAllowsDeliveryTimeout = {
+    MoqtMessageType::kTrackStatus,    MoqtMessageType::kTrackStatusOk,
+    MoqtMessageType::kPublish,        MoqtMessageType::kPublishOk,
+    MoqtMessageType::kSubscribe,      MoqtMessageType::kSubscribeOk,
+    MoqtMessageType::kSubscribeUpdate};
 const std::array<MoqtMessageType, 4> kAllowsMaxCacheDuration = {
     MoqtMessageType::kSubscribeOk, MoqtMessageType::kTrackStatusOk,
     MoqtMessageType::kFetchOk, MoqtMessageType::kPublish};
diff --git a/quiche/quic/moqt/moqt_messages.h b/quiche/quic/moqt/moqt_messages.h
index 3d94c44..4b27575 100644
--- a/quiche/quic/moqt/moqt_messages.h
+++ b/quiche/quic/moqt/moqt_messages.h
@@ -83,16 +83,19 @@
   MoqtSessionParameters() = default;
   explicit MoqtSessionParameters(quic::Perspective perspective)
       : perspective(perspective), using_webtrans(true) {}
-  MoqtSessionParameters(quic::Perspective perspective, std::string path)
+  MoqtSessionParameters(quic::Perspective perspective, std::string path,
+                        std::string authority)
       : perspective(perspective),
         using_webtrans(false),
-        path(std::move(path)) {}
+        path(std::move(path)),
+        authority(std::move(authority)) {}
   MoqtSessionParameters(quic::Perspective perspective, std::string path,
-                        uint64_t max_request_id)
+                        std::string authority, uint64_t max_request_id)
       : perspective(perspective),
         using_webtrans(true),
         path(std::move(path)),
-        max_request_id(max_request_id) {}
+        max_request_id(max_request_id),
+        authority(std::move(authority)) {}
   MoqtSessionParameters(quic::Perspective perspective, uint64_t max_request_id)
       : perspective(perspective), max_request_id(max_request_id) {}
   bool operator==(const MoqtSessionParameters& other) const = default;
@@ -101,12 +104,14 @@
   bool deliver_partial_objects = false;
   quic::Perspective perspective = quic::Perspective::IS_SERVER;
   bool using_webtrans = true;
-  std::string path = "";
+  std::string path;
   uint64_t max_request_id = kDefaultInitialMaxRequestId;
   uint64_t max_auth_token_cache_size = kDefaultMaxAuthTokenCacheSize;
   bool support_object_acks = false;
   // TODO(martinduke): Turn authorization_token into structured data.
   std::vector<AuthToken> authorization_token;
+  std::string authority;
+  std::string moqt_implementation = "Google QUICHE MOQT draft 14";
 };
 
 // The maximum length of a message, excluding any OBJECT payload. This prevents
@@ -321,6 +326,8 @@
   kMaxRequestId = 0x2,
   kAuthorizationToken = 0x3,
   kMaxAuthTokenCacheSize = 0x4,
+  kAuthority = 0x5,
+  kMoqtImplementation = 0x7,
 
   // QUICHE-specific extensions.
   // Indicates support for OACK messages.
diff --git a/quiche/quic/moqt/moqt_parser.cc b/quiche/quic/moqt/moqt_parser.cc
index 847292f..0221e5a 100644
--- a/quiche/quic/moqt/moqt_parser.cc
+++ b/quiche/quic/moqt/moqt_parser.cc
@@ -20,6 +20,7 @@
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "absl/types/span.h"
+#include "quiche/http2/adapter/header_validator.h"
 #include "quiche/quic/core/quic_data_reader.h"
 #include "quiche/quic/core/quic_time.h"
 #include "quiche/quic/core/quic_types.h"
@@ -1113,6 +1114,11 @@
         SetupParameter parameter = static_cast<SetupParameter>(key);
         switch (parameter) {
           case SetupParameter::kPath:
+            if (!http2::adapter::HeaderValidator::IsValidPath(
+                    value, /*allow_fragment=*/false)) {
+              ParseError(MoqtError::kMalformedPath, "Malformed path");
+              return false;
+            }
             out.path = value;
             break;
           case SetupParameter::kAuthorizationToken:
@@ -1120,6 +1126,16 @@
               return false;
             }
             break;
+          case SetupParameter::kAuthority:
+            if (!http2::adapter::HeaderValidator::IsValidAuthority(value)) {
+              ParseError(MoqtError::kMalformedAuthority, "Malformed authority");
+              return false;
+            }
+            out.authority = value;
+            break;
+          case SetupParameter::kMoqtImplementation:
+            QUICHE_LOG(INFO) << "Peer MOQT implementation: " << value;
+            break;
           default:
             break;
         }
diff --git a/quiche/quic/moqt/moqt_parser_test.cc b/quiche/quic/moqt/moqt_parser_test.cc
index 039776f..c3e85e1 100644
--- a/quiche/quic/moqt/moqt_parser_test.cc
+++ b/quiche/quic/moqt/moqt_parser_test.cc
@@ -519,6 +519,23 @@
   EXPECT_EQ(visitor_.parsing_error_code_, MoqtError::kInvalidPath);
 }
 
+TEST_F(MoqtMessageSpecificTest, SetupAuthorityFromServer) {
+  webtransport::test::InMemoryStream stream(/*stream_id=*/0);
+  MoqtControlParser parser(kRawQuic, &stream, visitor_);
+  char setup[] = {
+      0x21, 0x00, 0x07,
+      0x01,                          // version = 1
+      0x01,                          // 1 param
+      0x05, 0x03, 0x66, 0x6f, 0x6f,  // authority = "foo"
+  };
+  stream.Receive(absl::string_view(setup, sizeof(setup)), false);
+  parser.ReadAndDispatchMessages();
+  EXPECT_EQ(visitor_.messages_received_, 0);
+  EXPECT_EQ(visitor_.parsing_error_,
+            "Server SETUP contains invalid parameters");
+  EXPECT_EQ(visitor_.parsing_error_code_, MoqtError::kInvalidAuthority);
+}
+
 TEST_F(MoqtMessageSpecificTest, SetupPathAppearsTwice) {
   webtransport::test::InMemoryStream stream(/*stream_id=*/0);
   MoqtControlParser parser(kRawQuic, &stream, visitor_);
@@ -552,6 +569,22 @@
   EXPECT_EQ(visitor_.parsing_error_code_, MoqtError::kInvalidPath);
 }
 
+TEST_F(MoqtMessageSpecificTest, SetupAuthorityOverWebtrans) {
+  webtransport::test::InMemoryStream stream(/*stream_id=*/0);
+  MoqtControlParser parser(kWebTrans, &stream, visitor_);
+  char setup[] = {
+      0x20, 0x00, 0x09, 0x02, 0x01, 0x02,  // versions = 1, 2
+      0x01,                                // 1 param
+      0x05, 0x03, 0x66, 0x6f, 0x6f,        // authority = "foo"
+  };
+  stream.Receive(absl::string_view(setup, sizeof(setup)), false);
+  parser.ReadAndDispatchMessages();
+  EXPECT_EQ(visitor_.messages_received_, 0);
+  EXPECT_EQ(visitor_.parsing_error_,
+            "Client SETUP contains invalid parameters");
+  EXPECT_EQ(visitor_.parsing_error_code_, MoqtError::kInvalidAuthority);
+}
+
 TEST_F(MoqtMessageSpecificTest, SetupPathMissing) {
   webtransport::test::InMemoryStream stream(/*stream_id=*/0);
   MoqtControlParser parser(kRawQuic, &stream, visitor_);
@@ -585,6 +618,37 @@
   EXPECT_EQ(visitor_.parsing_error_code_, MoqtError::kKeyValueFormattingError);
 }
 
+TEST_F(MoqtMessageSpecificTest, ServerSetupMalformedPath) {
+  webtransport::test::InMemoryStream stream(/*stream_id=*/0);
+  MoqtControlParser parser(kRawQuic, &stream, visitor_);
+  char setup[] = {
+      0x20, 0x00, 0x09, 0x02, 0x01, 0x02,  // versions = 1, 2
+      0x01,                                // 1 param
+      0x01, 0x03, 0x66, 0x5c, 0x6f,        // path = "f\o"
+  };
+  stream.Receive(absl::string_view(setup, sizeof(setup)), false);
+  parser.ReadAndDispatchMessages();
+  EXPECT_EQ(visitor_.messages_received_, 0);
+  EXPECT_EQ(visitor_.parsing_error_, "Malformed path");
+  EXPECT_EQ(visitor_.parsing_error_code_, MoqtError::kMalformedPath);
+}
+
+TEST_F(MoqtMessageSpecificTest, ServerSetupMalformedAuthority) {
+  webtransport::test::InMemoryStream stream(/*stream_id=*/0);
+  MoqtControlParser parser(kRawQuic, &stream, visitor_);
+  char setup[] = {
+      0x20, 0x00, 0x0e, 0x02, 0x01, 0x02,  // versions = 1, 2
+      0x02,                                // 2 params
+      0x01, 0x03, 0x66, 0x6f, 0x6f,        // path = "foo"
+      0x05, 0x03, 0x66, 0x5c, 0x6f,        // authority = "f\o"
+  };
+  stream.Receive(absl::string_view(setup, sizeof(setup)), false);
+  parser.ReadAndDispatchMessages();
+  EXPECT_EQ(visitor_.messages_received_, 0);
+  EXPECT_EQ(visitor_.parsing_error_, "Malformed authority");
+  EXPECT_EQ(visitor_.parsing_error_code_, MoqtError::kMalformedAuthority);
+}
+
 TEST_F(MoqtMessageSpecificTest, UnknownParameterTwiceIsOk) {
   webtransport::test::InMemoryStream stream(/*stream_id=*/0);
   MoqtControlParser parser(kWebTrans, &stream, visitor_);
@@ -891,24 +955,6 @@
   EXPECT_EQ(visitor_.parsing_error_code_, MoqtError::kProtocolViolation);
 }
 
-TEST_F(MoqtMessageSpecificTest, SubscribeUpdateHasAuthorizationToken) {
-  webtransport::test::InMemoryStream stream(/*stream_id=*/0);
-  MoqtControlParser parser(kWebTrans, &stream, visitor_);
-  char subscribe_update[] = {
-      0x02, 0x00, 0x0e, 0x02, 0x03, 0x01, 0x05,  // start and end sequences
-      0xaa, 0x01,                                // priority, forward
-      0x01,                                      // 1 parameter
-      0x03, 0x05, 0x03, 0x00, 0x62, 0x61, 0x72,  // authorization_token = "bar"
-  };
-  stream.Receive(absl::string_view(subscribe_update, sizeof(subscribe_update)),
-                 false);
-  parser.ReadAndDispatchMessages();
-  EXPECT_EQ(visitor_.messages_received_, 0);
-  EXPECT_EQ(visitor_.parsing_error_,
-            "SUBSCRIBE_UPDATE contains invalid parameters");
-  EXPECT_EQ(visitor_.parsing_error_code_, MoqtError::kProtocolViolation);
-}
-
 TEST_F(MoqtMessageSpecificTest, AnnounceAuthorizationTokenTwice) {
   webtransport::test::InMemoryStream stream(/*stream_id=*/0);
   MoqtControlParser parser(kWebTrans, &stream, visitor_);
diff --git a/quiche/quic/moqt/moqt_session_test.cc b/quiche/quic/moqt/moqt_session_test.cc
index 56b3a73..6d57458 100644
--- a/quiche/quic/moqt/moqt_session_test.cc
+++ b/quiche/quic/moqt/moqt_session_test.cc
@@ -123,7 +123,7 @@
  public:
   MoqtSessionTest()
       : session_(&mock_session_,
-                 MoqtSessionParameters(quic::Perspective::IS_CLIENT, ""),
+                 MoqtSessionParameters(quic::Perspective::IS_CLIENT, "", ""),
                  std::make_unique<quic::test::TestAlarmFactory>(),
                  session_callbacks_.AsSessionCallbacks()) {
     session_.set_publisher(&publisher_);
diff --git a/quiche/quic/moqt/test_tools/moqt_simulator_harness.cc b/quiche/quic/moqt/test_tools/moqt_simulator_harness.cc
index 5d17a47..9c743eb 100644
--- a/quiche/quic/moqt/test_tools/moqt_simulator_harness.cc
+++ b/quiche/quic/moqt/test_tools/moqt_simulator_harness.cc
@@ -30,7 +30,7 @@
 namespace {
 MoqtSessionParameters CreateParameters(quic::Perspective perspective,
                                        MoqtVersion version) {
-  MoqtSessionParameters parameters(perspective, "");
+  MoqtSessionParameters parameters(perspective, "", "");
   parameters.version = version;
   parameters.deliver_partial_objects = false;
   return parameters;
diff --git a/quiche/quic/moqt/test_tools/moqt_test_message.h b/quiche/quic/moqt/test_tools/moqt_test_message.h
index ad433a1..f6954ed 100644
--- a/quiche/quic/moqt/test_tools/moqt_test_message.h
+++ b/quiche/quic/moqt/test_tools/moqt_test_message.h
@@ -493,11 +493,14 @@
   explicit ClientSetupMessage(bool webtrans) : TestMessageBase() {
     client_setup_.parameters.using_webtrans = webtrans;
     if (webtrans) {
-      // Should not send PATH.
+      // Should not send PATH or AUTHORITY.
       client_setup_.parameters.path = "";
-      raw_packet_[2] = 0x06;  // adjust payload length (-5)
-      raw_packet_[6] = 0x01;  // only one parameter
-      SetWireImage(raw_packet_, sizeof(raw_packet_) - 5);
+      client_setup_.parameters.authority = "";
+      raw_packet_[2] = 0x23;  // adjust payload length (-17)
+      raw_packet_[6] = 0x02;  // only two parameters
+      // Move MoqtImplementation up in the packet.
+      memcpy(raw_packet_ + 9, raw_packet_ + 26, 29);
+      SetWireImage(raw_packet_, sizeof(raw_packet_) - 17);
     } else {
       SetWireImage(raw_packet_, sizeof(raw_packet_));
     }
@@ -526,9 +529,9 @@
 
   void ExpandVarints() override {
     if (!client_setup_.parameters.path.empty()) {
-      ExpandVarintsImpl("vvvvvvvv---");
+      ExpandVarintsImpl("vvvvvvvv----vv---------vv---------------------------");
     } else {
-      ExpandVarintsImpl("vvvvvv");
+      ExpandVarintsImpl("vvvvvvvv---------------------------");
     }
   }
 
@@ -537,17 +540,27 @@
   }
 
  private:
-  uint8_t raw_packet_[14] = {
-      0x20, 0x00, 0x0b,              // type
-      0x02, 0x01, 0x02,              // versions
-      0x02,                          // 2 parameters
-      0x02, 0x32,                    // max_request_id = 50
-      0x01, 0x03, 0x66, 0x6f, 0x6f,  // path = "foo"
-  };
+  // The framer serializes all the integer parameters in order, then all the
+  // string parameters in order. Unfortunately, this means that
+  // kMoqtImplementation goes last even though it is always present, while
+  // kPath and KAuthority aren't.
+  uint8_t raw_packet_[55] = {
+      0x20, 0x00, 0x34,                    // type, length
+      0x02, 0x01, 0x02,                    // versions
+      0x04,                                // 4 parameters
+      0x02, 0x32,                          // max_request_id = 50
+      0x01, 0x04, 0x70, 0x61, 0x74, 0x68,  // path = "path"
+      0x05, 0x09, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74,
+      0x79,  // authority = "authority"
+      // moqt_implementation:
+      0x07, 0x1b, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x51, 0x55, 0x49,
+      0x43, 0x48, 0x45, 0x20, 0x4d, 0x4f, 0x51, 0x54, 0x20, 0x64, 0x72, 0x61,
+      0x66, 0x74, 0x20, 0x31, 0x34};
   MoqtClientSetup client_setup_ = {
       /*supported_versions=*/std::vector<MoqtVersion>(
           {static_cast<MoqtVersion>(1), static_cast<MoqtVersion>(2)}),
-      MoqtSessionParameters(quic::Perspective::IS_CLIENT, "foo", 50),
+      MoqtSessionParameters(quic::Perspective::IS_CLIENT, "path", "authority",
+                            50),
   };
 };
 
@@ -574,11 +587,17 @@
   }
 
  private:
-  uint8_t raw_packet_[7] = {
-      0x21, 0x00, 0x04,  // type
-      0x01, 0x01,        // version, one parameter
-      0x02, 0x32,        // max_subscribe_id = 50
-  };
+  uint8_t raw_packet_[36] = {0x21, 0x00,
+                             0x21,  // type
+                             0x01,
+                             0x02,  // version, two parameters
+                             0x02,
+                             0x32,  // max_subscribe_id = 50
+                             // moqt_implementation:
+                             0x07, 0x1b, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
+                             0x20, 0x51, 0x55, 0x49, 0x43, 0x48, 0x45, 0x20,
+                             0x4d, 0x4f, 0x51, 0x54, 0x20, 0x64, 0x72, 0x61,
+                             0x66, 0x74, 0x20, 0x31, 0x34};
   MoqtServerSetup server_setup_ = {
       /*selected_version=*/static_cast<MoqtVersion>(1),
       MoqtSessionParameters(quic::Perspective::IS_SERVER, 50),