Allow MoQT AUTHORIZATION_TOKEN parameters in CLIENT_SETUP to exceed the max cache size.

PiperOrigin-RevId: 788580560
diff --git a/quiche/quic/moqt/moqt_messages.h b/quiche/quic/moqt/moqt_messages.h
index 2a068f3..232c8ab 100644
--- a/quiche/quic/moqt/moqt_messages.h
+++ b/quiche/quic/moqt/moqt_messages.h
@@ -51,6 +51,30 @@
 inline constexpr uint64_t kMaxNamespaceElements = 32;
 inline constexpr size_t kMaxFullTrackNameSize = 1024;
 
+enum AuthTokenType : uint64_t {
+  kOutOfBand = 0x0,
+
+  kMaxAuthTokenType = 0x0,
+};
+
+enum AuthTokenAliasType : uint64_t {
+  kDelete = 0x0,
+  kRegister = 0x1,
+  kUseAlias = 0x2,
+  kUseValue = 0x3,
+
+  kMaxValue = 0x3,
+};
+
+struct AuthToken {
+  AuthToken(AuthTokenType token_type, absl::string_view token)
+      : type(token_type), token(token) {}
+  bool operator==(const AuthToken& other) const = default;
+
+  AuthTokenType type;
+  std::string token;
+};
+
 struct QUICHE_EXPORT MoqtSessionParameters {
   // TODO: support multiple versions.
   MoqtSessionParameters() = default;
@@ -78,6 +102,8 @@
   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;
 };
 
 // The maximum length of a message, excluding any OBJECT payload. This prevents
@@ -257,6 +283,7 @@
 enum class QUICHE_EXPORT SetupParameter : uint64_t {
   kPath = 0x1,
   kMaxRequestId = 0x2,
+  kAuthorizationToken = 0x3,
   kMaxAuthTokenCacheSize = 0x4,
 
   // QUICHE-specific extensions.
@@ -273,30 +300,6 @@
   kOackWindowSize = 0xbbf1438,
 };
 
-enum AuthTokenType : uint64_t {
-  kOutOfBand = 0x0,
-
-  kMaxAuthTokenType = 0x0,
-};
-
-enum AuthTokenAliasType : uint64_t {
-  kDelete = 0x0,
-  kRegister = 0x1,
-  kUseAlias = 0x2,
-  kUseValue = 0x3,
-
-  kMaxValue = 0x3,
-};
-
-struct AuthToken {
-  AuthToken(AuthTokenType token_type, absl::string_view token)
-      : type(token_type), token(token) {}
-  bool operator==(const AuthToken& other) const = default;
-
-  AuthTokenType type;
-  std::string token;
-};
-
 struct VersionSpecificParameters {
   VersionSpecificParameters() = default;
   // Likely parameter combinations.
diff --git a/quiche/quic/moqt/moqt_parser.cc b/quiche/quic/moqt/moqt_parser.cc
index 5186feb..75dcc5f 100644
--- a/quiche/quic/moqt/moqt_parser.cc
+++ b/quiche/quic/moqt/moqt_parser.cc
@@ -12,6 +12,7 @@
 #include <optional>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "absl/base/casts.h"
 #include "absl/cleanup/cleanup.h"
@@ -126,39 +127,6 @@
   return true;
 }
 
-void KeyValuePairListToMoqtSessionParameters(const KeyValuePairList& parameters,
-                                             MoqtSessionParameters& out) {
-  parameters.ForEach(
-      [&](uint64_t key, uint64_t value) {
-        SetupParameter parameter = static_cast<SetupParameter>(key);
-        switch (parameter) {
-          case SetupParameter::kMaxRequestId:
-            out.max_request_id = value;
-            break;
-          case SetupParameter::kMaxAuthTokenCacheSize:
-            out.max_auth_token_cache_size = value;
-            break;
-          case SetupParameter::kSupportObjectAcks:
-            out.support_object_acks = (value == 1);
-            break;
-          default:
-            break;
-        }
-        return true;
-      },
-      [&](uint64_t key, absl::string_view value) {
-        SetupParameter parameter = static_cast<SetupParameter>(key);
-        switch (parameter) {
-          case SetupParameter::kPath:
-            out.path = value;
-            break;
-          default:
-            break;
-        }
-        return true;
-      });
-}
-
 }  // namespace
 
 void MoqtControlParser::ReadAndDispatchMessages() {
@@ -363,7 +331,9 @@
     ParseError(error, "Client SETUP contains invalid parameters");
     return 0;
   }
-  KeyValuePairListToMoqtSessionParameters(parameters, setup.parameters);
+  if (!KeyValuePairListToMoqtSessionParameters(parameters, setup.parameters)) {
+    return 0;
+  }
   // TODO(martinduke): Validate construction of the PATH (Sec 8.3.2.1)
   visitor_.OnClientSetupMessage(setup);
   return reader.PreviouslyReadPayload().length();
@@ -388,7 +358,9 @@
     ParseError(error, "Server SETUP contains invalid parameters");
     return 0;
   }
-  KeyValuePairListToMoqtSessionParameters(parameters, setup.parameters);
+  if (!KeyValuePairListToMoqtSessionParameters(parameters, setup.parameters)) {
+    return 0;
+  }
   visitor_.OnServerSetupMessage(setup);
   return reader.PreviouslyReadPayload().length();
 }
@@ -996,6 +968,44 @@
   return true;
 }
 
+bool MoqtControlParser::KeyValuePairListToMoqtSessionParameters(
+    const KeyValuePairList& parameters, MoqtSessionParameters& out) {
+  return parameters.ForEach(
+      [&](uint64_t key, uint64_t value) {
+        SetupParameter parameter = static_cast<SetupParameter>(key);
+        switch (parameter) {
+          case SetupParameter::kMaxRequestId:
+            out.max_request_id = value;
+            break;
+          case SetupParameter::kMaxAuthTokenCacheSize:
+            out.max_auth_token_cache_size = value;
+            break;
+          case SetupParameter::kSupportObjectAcks:
+            out.support_object_acks = (value == 1);
+            break;
+          default:
+            break;
+        }
+        return true;
+      },
+      [&](uint64_t key, absl::string_view value) {
+        SetupParameter parameter = static_cast<SetupParameter>(key);
+        switch (parameter) {
+          case SetupParameter::kPath:
+            out.path = value;
+            break;
+          case SetupParameter::kAuthorizationToken:
+            if (!ParseAuthTokenParameter(value, out.authorization_token)) {
+              return false;
+            }
+            break;
+          default:
+            break;
+        }
+        return true;
+      });
+}
+
 // Returns false if there is a protocol violation.
 bool MoqtControlParser::KeyValuePairListToVersionSpecificParameters(
     const KeyValuePairList& parameters, VersionSpecificParameters& out) {
@@ -1024,7 +1034,7 @@
             static_cast<VersionSpecificParameter>(key);
         switch (parameter) {
           case VersionSpecificParameter::kAuthorizationToken:
-            if (!ParseAuthTokenParameter(value, out)) {
+            if (!ParseAuthTokenParameter(value, out.authorization_token)) {
               return false;
             }
             break;
@@ -1035,8 +1045,8 @@
       });
 }
 
-bool MoqtControlParser::ParseAuthTokenParameter(
-    absl::string_view field, VersionSpecificParameters& out) {
+bool MoqtControlParser::ParseAuthTokenParameter(absl::string_view field,
+                                                std::vector<AuthToken>& out) {
   quic::QuicDataReader reader(field);
   AuthTokenType token_type;
   absl::string_view token;
@@ -1085,6 +1095,14 @@
       }
       token_type = static_cast<AuthTokenType>(value);
       token = reader.PeekRemainingPayload();
+      if (message_type_.has_value() &&
+          *message_type_ ==
+              static_cast<uint64_t>(MoqtMessageType::kClientSetup)) {
+        // Do not check the max cache size. Since the max size isn't sent until
+        // SERVER_SETUP, it's not yet known. Since draft-12, this is not an
+        // error and tokens in excess of the cache limit are simply ignored.
+        break;
+      }
       if (auth_token_cache_size_ + sizeof(uint64_t) + token.length() >
           max_auth_token_cache_size_) {
         ParseError(MoqtError::kAuthTokenCacheOverflow,
@@ -1108,7 +1126,7 @@
       return false;
   }
   // Validate cache operations.
-  out.authorization_token.push_back(AuthToken(token_type, token));
+  out.push_back(AuthToken(token_type, token));
   return true;
 }
 
diff --git a/quiche/quic/moqt/moqt_parser.h b/quiche/quic/moqt/moqt_parser.h
index 030861d..a84fc38 100644
--- a/quiche/quic/moqt/moqt_parser.h
+++ b/quiche/quic/moqt/moqt_parser.h
@@ -11,6 +11,7 @@
 #include <cstddef>
 #include <cstdint>
 #include <optional>
+#include <vector>
 
 #include "absl/strings/string_view.h"
 #include "quiche/quic/core/quic_data_reader.h"
@@ -145,15 +146,13 @@
   bool ReadFullTrackName(quic::QuicDataReader& reader,
                          FullTrackName& full_track_name);
   // Translates raw key/value pairs into semantically meaningful formats.
-  // The spec defines many encoding errors in AUTHORIZATION TOKEN as
-  // request level. This treats them as session-level, unless they are a result
-  // of expiration, incorrect internal structure, or anything else not defined
-  // in the MoQT spec. It is allowed to promote request errors to session errors
-  // in MoQT. See also https://github.com/moq-wg/moq-transport/issues/964.
+  // Returns false if the parameters contain a protocol violation.
+  bool KeyValuePairListToMoqtSessionParameters(
+      const KeyValuePairList& parameters, MoqtSessionParameters& out);
   bool KeyValuePairListToVersionSpecificParameters(
       const KeyValuePairList& parameters, VersionSpecificParameters& out);
   bool ParseAuthTokenParameter(absl::string_view field,
-                               VersionSpecificParameters& out);
+                               std::vector<AuthToken>& out);
 
   MoqtControlParserVisitor& visitor_;
   quiche::ReadStream& stream_;
diff --git a/quiche/quic/moqt/moqt_parser_test.cc b/quiche/quic/moqt/moqt_parser_test.cc
index 9ff33df..4f6535a 100644
--- a/quiche/quic/moqt/moqt_parser_test.cc
+++ b/quiche/quic/moqt/moqt_parser_test.cc
@@ -477,6 +477,22 @@
   EXPECT_EQ(visitor_.parsing_error_code_, MoqtError::kKeyValueFormattingError);
 }
 
+TEST_F(MoqtMessageSpecificTest, ClientSetupAuthorizationTokenTagRegister) {
+  webtransport::test::InMemoryStream stream(/*stream_id=*/0);
+  MoqtControlParser parser(kRawQuic, &stream, visitor_);
+  char setup[] = {
+      0x20, 0x00, 0x13, 0x02, 0x01, 0x02,              // versions
+      0x03,                                            // 3 params
+      0x01, 0x03, 0x66, 0x6f, 0x6f,                    // path = "foo"
+      0x02, 0x32,                                      // max_request_id = 50
+      0x03, 0x06, 0x01, 0x10, 0x00, 0x62, 0x61, 0x72,  // REGISTER 0x01
+  };
+  stream.Receive(absl::string_view(setup, sizeof(setup)), false);
+  parser.ReadAndDispatchMessages();
+  // No error even though the registration exceeds the max cache size of 0.
+  EXPECT_EQ(visitor_.messages_received_, 1);
+}
+
 TEST_F(MoqtMessageSpecificTest, SetupPathFromServer) {
   webtransport::test::InMemoryStream stream(/*stream_id=*/0);
   MoqtControlParser parser(kRawQuic, &stream, visitor_);