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),