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_);