Add parameter validation and sanitization helper functions for MOQT messages. Declares functions in moqt_messages.h to check if parameters are allowed and to sanitize disallowed parameters for standard MOQT message types, REQUEST_UPDATE, and REQUEST_OK. PiperOrigin-RevId: 996570379
diff --git a/quiche/quic/moqt/moqt_bidi_stream.cc b/quiche/quic/moqt/moqt_bidi_stream.cc index 461b636..6eac61a 100644 --- a/quiche/quic/moqt/moqt_bidi_stream.cc +++ b/quiche/quic/moqt/moqt_bidi_stream.cc
@@ -59,9 +59,10 @@ } absl::Status MoqtBidiStreamBase::SendRequestOk( - const MessageParameters& parameters) { - return SendOrBufferMessage( - framer_->SerializeRequestOk(MoqtRequestOk(parameters)), false); + const MessageParameters& parameters, MoqtMessageType type_of_ok) { + MoqtRequestOk request_ok(parameters); + SanitizeRequestOkParameters(request_ok.parameters, type_of_ok); + return SendOrBufferMessage(framer_->SerializeRequestOk(request_ok), false); } absl::Status MoqtBidiStreamBase::SendRequestError( @@ -81,12 +82,15 @@ absl::Status MoqtBidiStreamBase::SendRequestUpdate( uint64_t request_id, uint64_t existing_request_id, - const MessageParameters& parameters, MoqtResponseCallback callback) { + const MessageParameters& parameters, MoqtResponseCallback callback, + MoqtMessageType updated_type) { MoqtRequestUpdate request_update; request_update.request_id = request_id; request_update.existing_request_id = existing_request_id; request_update.parameters = parameters; - outgoing_update_queue_.Enqueue(parameters, std::move(callback)); + SanitizeUpdateParameters(request_update.parameters, updated_type); + outgoing_update_queue_.Enqueue(request_update.parameters, + std::move(callback)); return SendOrBufferMessage(framer_->SerializeRequestUpdate(request_update), /*fin=*/false); }
diff --git a/quiche/quic/moqt/moqt_bidi_stream.h b/quiche/quic/moqt/moqt_bidi_stream.h index 4188d2a..afa56bf 100644 --- a/quiche/quic/moqt/moqt_bidi_stream.h +++ b/quiche/quic/moqt/moqt_bidi_stream.h
@@ -101,7 +101,8 @@ // Do not use for TRACK_STATUS_OK because that should also contain // TrackProperties. - absl::Status SendRequestOk(const MessageParameters& parameters); + absl::Status SendRequestOk(const MessageParameters& parameters, + MoqtMessageType type_of_ok); absl::Status SendRequestError( RequestErrorCode error_code, std::optional<quic::QuicTimeDelta> retry_interval, @@ -111,7 +112,8 @@ virtual absl::Status SendRequestUpdate(uint64_t request_id, uint64_t existing_request_id, const MessageParameters& parameters, - MoqtResponseCallback callback); + MoqtResponseCallback callback, + MoqtMessageType updated_type); void Fin() { CheckStatus(outgoing_message_queue_.Fin()); Detach();
diff --git a/quiche/quic/moqt/moqt_bidi_stream_test.cc b/quiche/quic/moqt/moqt_bidi_stream_test.cc index e56ea76..1cdae55 100644 --- a/quiche/quic/moqt/moqt_bidi_stream_test.cc +++ b/quiche/quic/moqt/moqt_bidi_stream_test.cc
@@ -155,12 +155,16 @@ TEST_F(MoqtBidiStreamTest, SendRequestOk) { stream_->BindStream(&mock_stream_); EXPECT_CALL(mock_stream_, CanWrite).WillRepeatedly(testing::Return(true)); - EXPECT_CALL( - mock_stream_, - Writev(ControlMessageOfType(MoqtMessageType::kRequestOk), testing::_)); + MoqtRequestOk expected_ok; + expected_ok.parameters.subscriber_priority = 20; + EXPECT_CALL(mock_stream_, + Writev(SerializedControlMessage(expected_ok), testing::_)); MessageParameters parameters; parameters.subscriber_priority = 20; - QUICHE_EXPECT_OK(stream_->SendRequestOk(parameters)); + // Disallowed for PUBLISH_OK, should be sanitized out. + parameters.largest_object = Location(1, 2); + QUICHE_EXPECT_OK( + stream_->SendRequestOk(parameters, MoqtMessageType::kPublish)); EXPECT_FALSE(stream_->detached_); // No FIN. } @@ -178,11 +182,16 @@ TEST_F(MoqtBidiStreamTest, SendRequestUpdateAndReceiveOk) { stream_->BindStream(&mock_stream_); EXPECT_CALL(mock_stream_, CanWrite).WillRepeatedly(testing::Return(true)); + MoqtRequestUpdate expected_update; + expected_update.request_id = 1; + expected_update.existing_request_id = 0; + expected_update.parameters.subscriber_priority = 20; EXPECT_CALL(mock_stream_, - Writev(ControlMessageOfType(MoqtMessageType::kRequestUpdate), - testing::_)); + Writev(SerializedControlMessage(expected_update), testing::_)); MessageParameters parameters; parameters.subscriber_priority = 20; + // Disallowed in REQUEST_UPDATE for SUBSCRIBE, should be sanitized out. + parameters.expires = quic::QuicTimeDelta::FromSeconds(10); bool callback_called = false; MoqtResponseCallback callback = [&](std::variant<MessageParameters, MoqtRequestErrorInfo> res) { @@ -190,8 +199,8 @@ ASSERT_TRUE(std::holds_alternative<MessageParameters>(res)); EXPECT_EQ(std::get<MessageParameters>(res).subscriber_priority, 30); }; - QUICHE_EXPECT_OK( - stream_->SendRequestUpdate(1, 0, parameters, std::move(callback))); + QUICHE_EXPECT_OK(stream_->SendRequestUpdate( + 1, 0, parameters, std::move(callback), MoqtMessageType::kSubscribe)); // Simulate receiving RequestOk MoqtRequestOk request_ok; request_ok.parameters.subscriber_priority = 30; @@ -215,8 +224,8 @@ EXPECT_EQ(std::get<MoqtRequestErrorInfo>(res).error_code, RequestErrorCode::kUnauthorized); }; - QUICHE_EXPECT_OK( - stream_->SendRequestUpdate(1, 0, parameters, std::move(callback))); + QUICHE_EXPECT_OK(stream_->SendRequestUpdate( + 1, 0, parameters, std::move(callback), MoqtMessageType::kSubscribe)); // Simulate receiving RequestError MoqtRequestError request_error(RequestErrorCode::kUnauthorized, std::nullopt, "unauthorized");
diff --git a/quiche/quic/moqt/moqt_fetch_stream.cc b/quiche/quic/moqt/moqt_fetch_stream.cc index 1bac6fc..657bdf3 100644 --- a/quiche/quic/moqt/moqt_fetch_stream.cc +++ b/quiche/quic/moqt/moqt_fetch_stream.cc
@@ -171,6 +171,11 @@ "REQUEST_UPDATE_OK received with properties")); return absl::OkStatus(); } + if (!ParametersAllowedByRequestOk(message.parameters, + MoqtMessageType::kRequestUpdate)) { + return absl::InvalidArgumentError( + "REQUEST_OK contains invalid parameters for REQUEST_UPDATE"); + } absl::StatusOr<MessageParameters> old_parameters = request_update_queue().NextParameters(); if (!old_parameters.ok()) { @@ -263,9 +268,10 @@ return; } if (std::holds_alternative<FetchOkData>(result)) { - const auto& ok_data = std::get<FetchOkData>(result); + auto& ok_data = std::get<FetchOkData>(result); stream->default_publisher_priority_ = ok_data.properties.default_publisher_priority(); + SanitizeParameters(ok_data.parameters, MoqtMessageType::kFetchOk); stream->parameters_.Update(ok_data.parameters); stream->SendOrBufferMessageOrFatal( stream->framer()->SerializeFetchOk(ok_data)); @@ -407,11 +413,16 @@ absl::Status MoqtFetchResponseStream::OnControlMessage( const MoqtRequestUpdate& message) { QUICHE_RETURN_IF_ERROR(validate_request_id_(message.request_id)); + if (!ParametersAllowedByRequestUpdate(message.parameters, + MoqtMessageType::kFetch)) { + return absl::InvalidArgumentError( + "REQUEST_UPDATE contains invalid parameters for FETCH"); + } if (data_stream_ != nullptr && message.parameters.subscriber_priority.has_value()) { data_stream_->UpdatePriority(*message.parameters.subscriber_priority); } - return SendRequestOk(MessageParameters()); + return SendRequestOk(MessageParameters(), MoqtMessageType::kRequestUpdate); } void MoqtFetchResponseStream::OnDataStreamOpen(
diff --git a/quiche/quic/moqt/moqt_fetch_stream_test.cc b/quiche/quic/moqt/moqt_fetch_stream_test.cc index be1d855..6ff54b1 100644 --- a/quiche/quic/moqt/moqt_fetch_stream_test.cc +++ b/quiche/quic/moqt/moqt_fetch_stream_test.cc
@@ -312,18 +312,21 @@ [&](std::variant<MessageParameters, MoqtRequestErrorInfo> res) { update_callback_called = true; ASSERT_TRUE(std::holds_alternative<MessageParameters>(res)); - EXPECT_EQ(std::get<MessageParameters>(res).subscriber_priority, 50); + EXPECT_EQ(std::get<MessageParameters>(res).expires, + quic::QuicTimeDelta::FromSeconds(10)); }; QUICHE_EXPECT_OK(stream->SendRequestUpdate( /*request_id=*/2, /*joining_start=*/0, update_params, - std::move(update_callback))); + std::move(update_callback), MoqtMessageType::kFetch)); // Receive REQUEST_OK for the update. MoqtRequestOk request_ok; - request_ok.parameters.subscriber_priority = 50; + request_ok.parameters.expires = quic::QuicTimeDelta::FromSeconds(10); QUICHE_EXPECT_OK(stream->OnControlMessage(request_ok)); EXPECT_TRUE(update_callback_called); EXPECT_EQ(stream->const_parameters().subscriber_priority, 50); + EXPECT_EQ(stream->const_parameters().expires, + quic::QuicTimeDelta::FromSeconds(10)); } TEST_F(MoqtFetchRequestStreamTest, ReceiveRequestOkWithoutPendingUpdate) { @@ -365,7 +368,7 @@ }; QUICHE_EXPECT_OK(stream->SendRequestUpdate( /*request_id=*/2, /*joining_start=*/0, update_params, - std::move(update_callback))); + std::move(update_callback), MoqtMessageType::kFetch)); ExpectFin(mock_stream_); QUICHE_EXPECT_OK(stream->OnControlMessage(request_error));
diff --git a/quiche/quic/moqt/moqt_framer.cc b/quiche/quic/moqt/moqt_framer.cc index c7e8af3..95a6d2c 100644 --- a/quiche/quic/moqt/moqt_framer.cc +++ b/quiche/quic/moqt/moqt_framer.cc
@@ -749,21 +749,21 @@ } quiche::QuicheBuffer MoqtFramer::SerializeSubscribe( - const MoqtSubscribe& message, MoqtMessageType message_type) { - return SerializeControlMessage(message_type, + const MoqtSubscribe& message) { + return SerializeControlMessage(MoqtMessageType::kSubscribe, WireMoqVarInt(message.request_id), WireFullTrackName(message.full_track_name), WireMessageParameters(message.parameters)); } quiche::QuicheBuffer MoqtFramer::SerializeSubscribeOk( - const MoqtSubscribeOk& message, MoqtMessageType message_type) { + const MoqtSubscribeOk& message) { if (!message.properties.Validate()) { QUICHE_BUG(QUICHE_BUG_serialize_subscribe_ok_01) << "Subscribe OK properties are ill-formed"; return quiche::QuicheBuffer(); } - return SerializeControlMessage(message_type, + return SerializeControlMessage(MoqtMessageType::kSubscribeOk, WireMoqVarInt(message.track_alias), WireMessageParameters(message.parameters), WireKeyValuePairList(message.properties)); @@ -825,7 +825,10 @@ quiche::QuicheBuffer MoqtFramer::SerializeTrackStatus( const MoqtTrackStatus& message) { - return SerializeSubscribe(message, MoqtMessageType::kTrackStatus); + return SerializeControlMessage(MoqtMessageType::kTrackStatus, + WireMoqVarInt(message.request_id), + WireFullTrackName(message.full_track_name), + WireMessageParameters(message.parameters)); } quiche::QuicheBuffer MoqtFramer::SerializeGoAway(const MoqtGoAway& message) { @@ -916,7 +919,6 @@ WireKeyValuePairList(message.properties)); } - quiche::QuicheBuffer MoqtFramer::SerializePublish(const MoqtPublish& message) { return SerializeControlMessage(MoqtMessageType::kPublish, WireMoqVarInt(message.request_id),
diff --git a/quiche/quic/moqt/moqt_framer.h b/quiche/quic/moqt/moqt_framer.h index c5a1ac4..b742280 100644 --- a/quiche/quic/moqt/moqt_framer.h +++ b/quiche/quic/moqt/moqt_framer.h
@@ -48,12 +48,8 @@ quiche::QuicheBuffer SerializeRequestOk(const MoqtRequestOk& message); quiche::QuicheBuffer SerializeRequestError(const MoqtRequestError& message); // Returns an empty buffer if there is an illegal combination of locations. - quiche::QuicheBuffer SerializeSubscribe( - const MoqtSubscribe& message, - MoqtMessageType message_type = MoqtMessageType::kSubscribe); - quiche::QuicheBuffer SerializeSubscribeOk( - const MoqtSubscribeOk& message, - MoqtMessageType message_type = MoqtMessageType::kSubscribeOk); + quiche::QuicheBuffer SerializeSubscribe(const MoqtSubscribe& message); + quiche::QuicheBuffer SerializeSubscribeOk(const MoqtSubscribeOk& message); quiche::QuicheBuffer SerializePublishDone(const MoqtPublishDone& message); quiche::QuicheBuffer SerializeRequestUpdate(const MoqtRequestUpdate& message); quiche::QuicheBuffer SerializePublishNamespace(
diff --git a/quiche/quic/moqt/moqt_framer_test.cc b/quiche/quic/moqt/moqt_framer_test.cc index f4932a4..f8f8a1e 100644 --- a/quiche/quic/moqt/moqt_framer_test.cc +++ b/quiche/quic/moqt/moqt_framer_test.cc
@@ -436,16 +436,16 @@ TEST_F(MoqtFramerSimpleTest, RelativeJoiningFetch) { RelativeJoiningFetchMessage message; - quiche::QuicheBuffer buffer = - framer_.SerializeFetch(std::get<MoqtFetch>(message.structured_data())); + MoqtFetch fetch = std::get<MoqtFetch>(message.structured_data()); + quiche::QuicheBuffer buffer = framer_.SerializeFetch(fetch); EXPECT_EQ(buffer.size(), message.total_message_size()); EXPECT_EQ(buffer.AsStringView(), message.PacketSample()); } TEST_F(MoqtFramerSimpleTest, AbsoluteJoiningFetch) { AbsoluteJoiningFetchMessage message; - quiche::QuicheBuffer buffer = - framer_.SerializeFetch(std::get<MoqtFetch>(message.structured_data())); + MoqtFetch fetch = std::get<MoqtFetch>(message.structured_data()); + quiche::QuicheBuffer buffer = framer_.SerializeFetch(fetch); EXPECT_EQ(buffer.size(), message.total_message_size()); EXPECT_EQ(buffer.AsStringView(), message.PacketSample()); }
diff --git a/quiche/quic/moqt/moqt_key_value_pair.h b/quiche/quic/moqt/moqt_key_value_pair.h index aafb24c..e7b04fa 100644 --- a/quiche/quic/moqt/moqt_key_value_pair.h +++ b/quiche/quic/moqt/moqt_key_value_pair.h
@@ -243,6 +243,7 @@ std::optional<quic::QuicTimeDelta> fill_timeout; bool forward() const { return forward_.value_or(kDefaultForward); } void set_forward(bool forward) { forward_ = forward; } + void clear_forward() { forward_.reset(); } bool forward_has_value() const { return forward_.has_value(); } std::optional<MoqtPriority> subscriber_priority; std::optional<SubscriptionFilter> subscription_filter;
diff --git a/quiche/quic/moqt/moqt_live_publisher.cc b/quiche/quic/moqt/moqt_live_publisher.cc index 0b4820d..ce52a21 100644 --- a/quiche/quic/moqt/moqt_live_publisher.cc +++ b/quiche/quic/moqt/moqt_live_publisher.cc
@@ -133,7 +133,8 @@ } } if (!from_request_ok) { - bidi_stream_->CheckStatus(bidi_stream_->SendRequestOk(response)); + bidi_stream_->CheckStatus( + bidi_stream_->SendRequestOk(response, MoqtMessageType::kRequestUpdate)); } }
diff --git a/quiche/quic/moqt/moqt_messages.cc b/quiche/quic/moqt/moqt_messages.cc index f2f7652..522ec85 100644 --- a/quiche/quic/moqt/moqt_messages.cc +++ b/quiche/quic/moqt/moqt_messages.cc
@@ -12,10 +12,258 @@ #include "quiche/quic/moqt/moqt_error.h" #include "quiche/quic/moqt/moqt_key_value_pair.h" #include "quiche/quic/moqt/moqt_types.h" -#include "quiche/quic/platform/api/quic_bug_tracker.h" +#include "quiche/common/platform/api/quiche_bug_tracker.h" namespace moqt { +namespace { + +struct AllowedParameters { + bool subgroup_delivery_timeout = false; + bool authorization_tokens = false; + bool rendezvous_timeout = false; + bool object_delivery_timeout = false; + bool expires = false; + bool largest_object = false; + bool fill_timeout = false; + bool forward = false; + bool subscriber_priority = false; + bool subscription_filter = false; + bool group_order = false; + bool new_group_request = false; + bool track_namespace_prefix = false; + bool oack_window_size = false; +}; + +constexpr AllowedParameters kCommonSubscriberParameters{ + .subgroup_delivery_timeout = true, + .object_delivery_timeout = true, + .forward = true, + .subscriber_priority = true, + .subscription_filter = true, + .new_group_request = true, + .oack_window_size = true, +}; + +AllowedParameters GetAllowedParametersForMessage(MoqtMessageType message_type) { + switch (message_type) { + case MoqtMessageType::kSubscribe: { + AllowedParameters allowed = kCommonSubscriberParameters; + allowed.authorization_tokens = true; + allowed.rendezvous_timeout = true; + allowed.group_order = true; + return allowed; + } + case MoqtMessageType::kSubscribeOk: + return AllowedParameters{ + .expires = true, + .largest_object = true, + }; + case MoqtMessageType::kPublish: + return AllowedParameters{ + .authorization_tokens = true, + .expires = true, + .largest_object = true, + .forward = true, + }; + case MoqtMessageType::kFetch: + return AllowedParameters{ + .authorization_tokens = true, + .fill_timeout = true, + .subscriber_priority = true, + .group_order = true, + }; + case MoqtMessageType::kFetchOk: + return AllowedParameters{}; + case MoqtMessageType::kSubscribeTracks: + return AllowedParameters{ + .authorization_tokens = true, + .forward = true, + }; + case MoqtMessageType::kTrackStatus: + case MoqtMessageType::kPublishNamespace: + case MoqtMessageType::kSubscribeNamespace: + return AllowedParameters{ + .authorization_tokens = true, + }; + case MoqtMessageType::kRequestUpdate: + case MoqtMessageType::kRequestError: + case MoqtMessageType::kRequestOk: + case MoqtMessageType::kNamespace: + case MoqtMessageType::kPublishDone: + case MoqtMessageType::kNamespaceDone: + case MoqtMessageType::kPublishSkipped: + case MoqtMessageType::kGoAway: + case MoqtMessageType::kSetup: + case MoqtMessageType::kObjectAck: + QUICHE_BUG(moqt_sanitize_parameters_invalid_type) + << "Unexpected message type: " + << MoqtMessageTypeToString(message_type); + return AllowedParameters{}; + } + return AllowedParameters{}; +} + +AllowedParameters GetAllowedParametersForUpdate(MoqtMessageType updated_type) { + switch (updated_type) { + case MoqtMessageType::kSubscribe: + case MoqtMessageType::kRequestOk: { // PUBLISH_OK + AllowedParameters allowed = kCommonSubscriberParameters; + allowed.authorization_tokens = true; + return allowed; + } + case MoqtMessageType::kFetch: + return AllowedParameters{ + .authorization_tokens = true, + .subscriber_priority = true, + }; + case MoqtMessageType::kPublish: + case MoqtMessageType::kPublishNamespace: + return AllowedParameters{ + .authorization_tokens = true, + }; + case MoqtMessageType::kSubscribeNamespace: + case MoqtMessageType::kSubscribeTracks: + return AllowedParameters{ + .authorization_tokens = true, + .track_namespace_prefix = true, + }; + case MoqtMessageType::kRequestUpdate: + case MoqtMessageType::kSubscribeOk: + case MoqtMessageType::kRequestError: + case MoqtMessageType::kNamespace: + case MoqtMessageType::kPublishDone: + case MoqtMessageType::kTrackStatus: + case MoqtMessageType::kNamespaceDone: + case MoqtMessageType::kPublishSkipped: + case MoqtMessageType::kGoAway: + case MoqtMessageType::kFetchOk: + case MoqtMessageType::kSetup: + case MoqtMessageType::kObjectAck: + QUICHE_BUG(moqt_sanitize_update_parameters_invalid_type) + << "Unexpected updated message type: " + << MoqtMessageTypeToString(updated_type); + return AllowedParameters{}; + } + return AllowedParameters{}; +} + +AllowedParameters GetAllowedParametersForRequestOk(MoqtMessageType type_of_ok) { + switch (type_of_ok) { + case MoqtMessageType::kPublish: { + AllowedParameters allowed = kCommonSubscriberParameters; + allowed.expires = true; + allowed.group_order = true; + return allowed; + } + case MoqtMessageType::kRequestUpdate: + return AllowedParameters{ + .expires = true, + .largest_object = true, + }; + case MoqtMessageType::kTrackStatus: + return AllowedParameters{ + .largest_object = true, + }; + case MoqtMessageType::kPublishNamespace: + case MoqtMessageType::kSubscribeNamespace: + case MoqtMessageType::kSubscribeTracks: + return AllowedParameters{}; + case MoqtMessageType::kSubscribe: + case MoqtMessageType::kSubscribeOk: + case MoqtMessageType::kRequestError: + case MoqtMessageType::kRequestOk: + case MoqtMessageType::kNamespace: + case MoqtMessageType::kPublishDone: + case MoqtMessageType::kNamespaceDone: + case MoqtMessageType::kPublishSkipped: + case MoqtMessageType::kGoAway: + case MoqtMessageType::kFetch: + case MoqtMessageType::kFetchOk: + case MoqtMessageType::kSetup: + case MoqtMessageType::kObjectAck: + QUICHE_BUG(moqt_sanitize_request_ok_parameters_invalid_type) + << "Unexpected request type for REQUEST_OK: " + << MoqtMessageTypeToString(type_of_ok); + return AllowedParameters{}; + } + return AllowedParameters{}; +} + +bool CheckAllowedParameters(const MessageParameters& parameters, + const AllowedParameters& allowed) { + return (allowed.subgroup_delivery_timeout || + !parameters.subgroup_delivery_timeout.has_value()) && + (allowed.authorization_tokens || + parameters.authorization_tokens.empty()) && + (allowed.rendezvous_timeout || + !parameters.rendezvous_timeout.has_value()) && + (allowed.object_delivery_timeout || + !parameters.object_delivery_timeout.has_value()) && + (allowed.expires || !parameters.expires.has_value()) && + (allowed.largest_object || !parameters.largest_object.has_value()) && + (allowed.fill_timeout || !parameters.fill_timeout.has_value()) && + (allowed.forward || !parameters.forward_has_value()) && + (allowed.subscriber_priority || + !parameters.subscriber_priority.has_value()) && + (allowed.subscription_filter || + !parameters.subscription_filter.has_value()) && + (allowed.group_order || !parameters.group_order.has_value()) && + (allowed.new_group_request || + !parameters.new_group_request.has_value()) && + (allowed.track_namespace_prefix || + !parameters.track_namespace_prefix.has_value()) && + (allowed.oack_window_size || !parameters.oack_window_size.has_value()); +} + +void ApplyAllowedParameters(MessageParameters& parameters, + const AllowedParameters& allowed) { + if (!allowed.subgroup_delivery_timeout) { + parameters.subgroup_delivery_timeout.reset(); + } + if (!allowed.authorization_tokens) { + parameters.authorization_tokens.clear(); + } + if (!allowed.rendezvous_timeout) { + parameters.rendezvous_timeout.reset(); + } + if (!allowed.object_delivery_timeout) { + parameters.object_delivery_timeout.reset(); + } + if (!allowed.expires) { + parameters.expires.reset(); + } + if (!allowed.largest_object) { + parameters.largest_object.reset(); + } + if (!allowed.fill_timeout) { + parameters.fill_timeout.reset(); + } + if (!allowed.forward) { + parameters.clear_forward(); + } + if (!allowed.subscriber_priority) { + parameters.subscriber_priority.reset(); + } + if (!allowed.subscription_filter) { + parameters.subscription_filter.reset(); + } + if (!allowed.group_order) { + parameters.group_order.reset(); + } + if (!allowed.new_group_request) { + parameters.new_group_request.reset(); + } + if (!allowed.track_namespace_prefix) { + parameters.track_namespace_prefix.reset(); + } + if (!allowed.oack_window_size) { + parameters.oack_window_size.reset(); + } +} + +} // namespace + MoqtObjectStatus IntegerToObjectStatus(uint64_t integer) { if (integer >= static_cast<uint64_t>(MoqtObjectStatus::kInvalidObjectStatus)) { @@ -38,36 +286,41 @@ return MoqtError::kNoError; } -// Parameter types are not enforced by message in draft-16, but apparently this -// is coming back later. -#if 0 -const std::array<MoqtMessageType, 9> kAllowsAuthorization = { - MoqtMessageType::kClientSetup, - MoqtMessageType::kServerSetup, - MoqtMessageType::kPublish, - MoqtMessageType::kSubscribe, - MoqtMessageType::kRequestUpdate, - MoqtMessageType::kSubscribeNamespace, - MoqtMessageType::kPublishNamespace, - MoqtMessageType::kTrackStatus, - MoqtMessageType::kFetch}; -const std::array<MoqtMessageType, 6> kAllowsDeliveryTimeout = { - MoqtMessageType::kTrackStatus, MoqtMessageType::kRequestOk, - MoqtMessageType::kPublish, MoqtMessageType::kSubscribe, - MoqtMessageType::kSubscribeOk, MoqtMessageType::kRequestUpdate}; -bool MessageParametersAllowedByMessage( - const MessageParameters& parameters, MoqtMessageType message_type) { - if (!parameters.authorization_tokens.empty() && - !absl::c_linear_search(kAllowsAuthorization, message_type)) { - return false; - } - if (parameters.delivery_timeout != quic::QuicTimeDelta::Infinite() && - !absl::c_linear_search(kAllowsDeliveryTimeout, message_type)) { - return false; - } - return true; +bool ParametersAllowedByMessage(const MessageParameters& parameters, + MoqtMessageType message_type) { + return CheckAllowedParameters(parameters, + GetAllowedParametersForMessage(message_type)); } -#endif + +void SanitizeParameters(MessageParameters& parameters, + MoqtMessageType message_type) { + ApplyAllowedParameters(parameters, + GetAllowedParametersForMessage(message_type)); +} + +bool ParametersAllowedByRequestUpdate(const MessageParameters& parameters, + MoqtMessageType updated_type) { + return CheckAllowedParameters(parameters, + GetAllowedParametersForUpdate(updated_type)); +} + +void SanitizeUpdateParameters(MessageParameters& parameters, + MoqtMessageType updated_type) { + ApplyAllowedParameters(parameters, + GetAllowedParametersForUpdate(updated_type)); +} + +bool ParametersAllowedByRequestOk(const MessageParameters& parameters, + MoqtMessageType type_of_ok) { + return CheckAllowedParameters(parameters, + GetAllowedParametersForRequestOk(type_of_ok)); +} + +void SanitizeRequestOkParameters(MessageParameters& parameters, + MoqtMessageType type_of_ok) { + ApplyAllowedParameters(parameters, + GetAllowedParametersForRequestOk(type_of_ok)); +} std::string MoqtMessageTypeToString(const MoqtMessageType message_type) { switch (message_type) { @@ -135,7 +388,7 @@ case MoqtForwardingPreference::kSubgroup: return "SUBGROUP"; } - QUIC_BUG(quic_bug_bad_moqt_message_type_01) + QUICHE_BUG(quic_bug_bad_moqt_message_type_01) << "Unknown preference " << std::to_string(static_cast<int>(preference)); return "Unknown preference " + std::to_string(static_cast<int>(preference)); }
diff --git a/quiche/quic/moqt/moqt_messages.h b/quiche/quic/moqt/moqt_messages.h index 8488fdb..de875a9 100644 --- a/quiche/quic/moqt/moqt_messages.h +++ b/quiche/quic/moqt/moqt_messages.h
@@ -401,9 +401,10 @@ using MoqtRequestOk = TrackStatusOkData; -struct QUICHE_EXPORT MoqtTrackStatus : public MoqtSubscribe { - MoqtTrackStatus() = default; - MoqtTrackStatus(MoqtSubscribe subscribe) : MoqtSubscribe(subscribe) {} +struct QUICHE_EXPORT MoqtTrackStatus { + uint64_t request_id; + FullTrackName full_track_name; + MessageParameters parameters; }; struct QUICHE_EXPORT MoqtGoAway { @@ -510,6 +511,40 @@ MoqtError SetupOptionsAllowedByMessage(const SetupOptions& options, quic::Perspective sender_perspective, bool webtrans); +// Returns false if |parameters| cannot be in |message_type|. Will trigger +// QUICHE_BUG if |message_type| is REQUEST_OK, REQUEST_UPDATE, or a message +// without parameters. Called by MoqtParser. +bool ParametersAllowedByMessage(const MessageParameters& parameters, + MoqtMessageType message_type); +// Will remove from |parameters| any parameters that are not allowed in +// |message_type|. Will trigger QUICHE_BUG if |message_type| is REQUEST_OK, +// REQUEST_UPDATE, or a message without parameters. Called by MoqtParser. +void SanitizeParameters(MessageParameters& parameters, + MoqtMessageType message_type); +// Returns false if |parameters| cannot be in a REQUEST_UPDATE for +// |updated_type|. Called by the bidi stream visitor. If |updated_type| is +// REQUEST_OK, that corresponds to PUBLISH_OK (as other OK messages cannot be +// updated). +bool ParametersAllowedByRequestUpdate(const MessageParameters& parameters, + MoqtMessageType updated_type); +// Will remove from |parameters| any parameters that are not allowed in a +// REQUEST_UPDATE for |updated_type|. Will trigger QUICHE_BUG if |updated_type| +// is REQUEST_OK or a message without parameters. Called by the bidi stream +// visitor. If |updated_type| is REQUEST_OK, that corresponds to PUBLISH_OK (as +// other OK messages cannot be updated). +void SanitizeUpdateParameters(MessageParameters& parameters, + MoqtMessageType updated_type); +// Returns false if |parameters| cannot be in a REQUEST_OK in response to +// |request_type|. Called by the bidi stream visitor. SUBSCRIBE_OK and FETCH_OK +// are distinct message types to be handled by ParametersAllowedByMessage(). +bool ParametersAllowedByRequestOk(const MessageParameters& parameters, + MoqtMessageType type_of_ok); +// Will remove from |parameters| any parameters that are not allowed in a +// REQUEST_OK in response to |type_of_ok|. Called by the bidi stream visitor. +// SUBSCRIBE_OK and FETCH_OK are distinct message types to be handled by +// SanitizeParameters(). +void SanitizeRequestOkParameters(MessageParameters& parameters, + MoqtMessageType type_of_ok); std::string MoqtMessageTypeToString(MoqtMessageType message_type); std::string MoqtDataStreamTypeToString(MoqtDataStreamType type);
diff --git a/quiche/quic/moqt/moqt_messages_test.cc b/quiche/quic/moqt/moqt_messages_test.cc index 4d7b79d..807d3dc 100644 --- a/quiche/quic/moqt/moqt_messages_test.cc +++ b/quiche/quic/moqt/moqt_messages_test.cc
@@ -4,13 +4,118 @@ #include "quiche/quic/moqt/moqt_messages.h" +#include <initializer_list> #include <optional> +#include "quiche/quic/core/quic_time.h" +#include "quiche/quic/moqt/moqt_key_value_pair.h" +#include "quiche/quic/moqt/moqt_names.h" +#include "quiche/quic/moqt/moqt_priority.h" +#include "quiche/quic/moqt/moqt_types.h" +#include "quiche/common/platform/api/quiche_expect_bug.h" #include "quiche/common/platform/api/quiche_test.h" namespace moqt::test { namespace { +using ParameterMutator = void (*)(MessageParameters&); + +void SetSubgroupDeliveryTimeout(MessageParameters& p) { + p.subgroup_delivery_timeout = quic::QuicTimeDelta::FromSeconds(1); +} +void SetAuthorizationTokens(MessageParameters& p) { + p.authorization_tokens = {AuthToken(AuthTokenType::kOutOfBand, "token")}; +} +void SetRendezvousTimeout(MessageParameters& p) { + p.rendezvous_timeout = quic::QuicTimeDelta::FromSeconds(2); +} +void SetObjectDeliveryTimeout(MessageParameters& p) { + p.object_delivery_timeout = quic::QuicTimeDelta::FromSeconds(3); +} +void SetExpires(MessageParameters& p) { + p.expires = quic::QuicTimeDelta::FromSeconds(4); +} +void SetLargestObject(MessageParameters& p) { + p.largest_object = Location(5, 6); +} +void SetFillTimeout(MessageParameters& p) { + p.fill_timeout = quic::QuicTimeDelta::FromSeconds(7); +} +void SetForward(MessageParameters& p) { p.set_forward(true); } +void SetSubscriberPriority(MessageParameters& p) { p.subscriber_priority = 8; } +void SetSubscriptionFilter(MessageParameters& p) { + p.subscription_filter = SubscriptionFilter(MoqtFilterType::kNextGroupStart); +} +void SetGroupOrder(MessageParameters& p) { + p.group_order = MoqtDeliveryOrder::kAscending; +} +void SetNewGroupRequest(MessageParameters& p) { p.new_group_request = 9; } +void SetTrackNamespacePrefix(MessageParameters& p) { + p.track_namespace_prefix = TrackNamespace({"foo"}); +} +void SetOackWindowSize(MessageParameters& p) { + p.oack_window_size = quic::QuicTimeDelta::FromSeconds(10); +} + +constexpr ParameterMutator kAllParameterMutators[] = { + SetSubgroupDeliveryTimeout, + SetAuthorizationTokens, + SetRendezvousTimeout, + SetObjectDeliveryTimeout, + SetExpires, + SetLargestObject, + SetFillTimeout, + SetForward, + SetSubscriberPriority, + SetSubscriptionFilter, + SetGroupOrder, + SetNewGroupRequest, + SetTrackNamespacePrefix, + SetOackWindowSize, +}; + +MessageParameters MakeParameters( + std::initializer_list<ParameterMutator> mutators) { + MessageParameters params; + for (ParameterMutator mutate : mutators) { + mutate(params); + } + return params; +} + +MessageParameters AllParameters() { + MessageParameters params; + for (ParameterMutator mutate : kAllParameterMutators) { + mutate(params); + } + return params; +} + +template <typename AllowedFn, typename SanitizeFn> +void VerifyParameterHandling(MoqtMessageType type, + const MessageParameters& expected, + AllowedFn allowed_fn, SanitizeFn sanitize_fn) { + SCOPED_TRACE(MoqtMessageTypeToString(type)); + MessageParameters sanitized = AllParameters(); + // No message can accept all parameters. + EXPECT_FALSE(allowed_fn(sanitized, type)); + sanitize_fn(sanitized, type); + EXPECT_EQ(sanitized, expected); + EXPECT_TRUE(allowed_fn(MessageParameters(), type)); + EXPECT_TRUE(allowed_fn(expected, type)); + + for (ParameterMutator mutate : kAllParameterMutators) { + MessageParameters single_param; + mutate(single_param); + + MessageParameters with_param = expected; + mutate(with_param); + const bool is_allowed = (with_param == expected); + EXPECT_EQ(allowed_fn(single_param, type), is_allowed); + EXPECT_EQ(allowed_fn(with_param, type), is_allowed); + } +} + TEST(MoqtMessagesTest, MoqtDatagramType) { for (bool payload : {false, true}) { for (bool properties : {false, true}) { @@ -37,5 +142,151 @@ } } +TEST(MoqtMessagesTest, ParametersAllowedAndSanitizedByMessage) { + VerifyParameterHandling( + MoqtMessageType::kSubscribe, + MakeParameters({SetSubgroupDeliveryTimeout, SetAuthorizationTokens, + SetRendezvousTimeout, SetObjectDeliveryTimeout, + SetForward, SetSubscriberPriority, SetSubscriptionFilter, + SetGroupOrder, SetNewGroupRequest, SetOackWindowSize}), + ParametersAllowedByMessage, SanitizeParameters); + + VerifyParameterHandling(MoqtMessageType::kSubscribeOk, + MakeParameters({SetExpires, SetLargestObject}), + ParametersAllowedByMessage, SanitizeParameters); + + VerifyParameterHandling(MoqtMessageType::kPublish, + MakeParameters({SetAuthorizationTokens, SetExpires, + SetLargestObject, SetForward}), + ParametersAllowedByMessage, SanitizeParameters); + + VerifyParameterHandling( + MoqtMessageType::kFetch, + MakeParameters({SetAuthorizationTokens, SetFillTimeout, + SetSubscriberPriority, SetGroupOrder}), + ParametersAllowedByMessage, SanitizeParameters); + + VerifyParameterHandling(MoqtMessageType::kFetchOk, MessageParameters(), + ParametersAllowedByMessage, SanitizeParameters); + + VerifyParameterHandling(MoqtMessageType::kSubscribeTracks, + MakeParameters({SetAuthorizationTokens, SetForward}), + ParametersAllowedByMessage, SanitizeParameters); + + for (MoqtMessageType type : + {MoqtMessageType::kTrackStatus, MoqtMessageType::kPublishNamespace, + MoqtMessageType::kSubscribeNamespace}) { + VerifyParameterHandling(type, MakeParameters({SetAuthorizationTokens}), + ParametersAllowedByMessage, SanitizeParameters); + } + + for (MoqtMessageType invalid_type : + {MoqtMessageType::kRequestUpdate, MoqtMessageType::kRequestError, + MoqtMessageType::kRequestOk, MoqtMessageType::kNamespace, + MoqtMessageType::kPublishDone, MoqtMessageType::kNamespaceDone, + MoqtMessageType::kPublishSkipped, MoqtMessageType::kGoAway, + MoqtMessageType::kSetup, MoqtMessageType::kObjectAck}) { + MessageParameters params = AllParameters(); + EXPECT_QUICHE_BUG( + EXPECT_FALSE(ParametersAllowedByMessage(params, invalid_type)), + "Unexpected message type"); + EXPECT_QUICHE_BUG(SanitizeParameters(params, invalid_type), + "Unexpected message type"); + EXPECT_EQ(params, MessageParameters()); + } +} + +TEST(MoqtMessagesTest, ParametersAllowedAndSanitizedByRequestUpdate) { + for (MoqtMessageType type : + {MoqtMessageType::kSubscribe, MoqtMessageType::kRequestOk}) { + VerifyParameterHandling( + type, + MakeParameters({SetSubgroupDeliveryTimeout, SetAuthorizationTokens, + SetObjectDeliveryTimeout, SetForward, + SetSubscriberPriority, SetSubscriptionFilter, + SetNewGroupRequest, SetOackWindowSize}), + ParametersAllowedByRequestUpdate, SanitizeUpdateParameters); + } + + VerifyParameterHandling( + MoqtMessageType::kFetch, + MakeParameters({SetAuthorizationTokens, SetSubscriberPriority}), + ParametersAllowedByRequestUpdate, SanitizeUpdateParameters); + + for (MoqtMessageType type : + {MoqtMessageType::kPublish, MoqtMessageType::kPublishNamespace}) { + VerifyParameterHandling(type, MakeParameters({SetAuthorizationTokens}), + ParametersAllowedByRequestUpdate, + SanitizeUpdateParameters); + } + + for (MoqtMessageType type : {MoqtMessageType::kSubscribeNamespace, + MoqtMessageType::kSubscribeTracks}) { + VerifyParameterHandling( + type, MakeParameters({SetAuthorizationTokens, SetTrackNamespacePrefix}), + ParametersAllowedByRequestUpdate, SanitizeUpdateParameters); + } + + for (MoqtMessageType invalid_type : + {MoqtMessageType::kRequestUpdate, MoqtMessageType::kSubscribeOk, + MoqtMessageType::kRequestError, MoqtMessageType::kNamespace, + MoqtMessageType::kPublishDone, MoqtMessageType::kTrackStatus, + MoqtMessageType::kNamespaceDone, MoqtMessageType::kPublishSkipped, + MoqtMessageType::kGoAway, MoqtMessageType::kFetchOk, + MoqtMessageType::kSetup, MoqtMessageType::kObjectAck}) { + MessageParameters params = AllParameters(); + EXPECT_QUICHE_BUG( + EXPECT_FALSE(ParametersAllowedByRequestUpdate(params, invalid_type)), + "Unexpected updated message type"); + EXPECT_QUICHE_BUG(SanitizeUpdateParameters(params, invalid_type), + "Unexpected updated message type"); + EXPECT_EQ(params, MessageParameters()); + } +} + +TEST(MoqtMessagesTest, ParametersAllowedAndSanitizedByRequestOk) { + VerifyParameterHandling( + MoqtMessageType::kPublish, + MakeParameters({SetSubgroupDeliveryTimeout, SetObjectDeliveryTimeout, + SetExpires, SetForward, SetSubscriberPriority, + SetSubscriptionFilter, SetGroupOrder, SetNewGroupRequest, + SetOackWindowSize}), + ParametersAllowedByRequestOk, SanitizeRequestOkParameters); + + VerifyParameterHandling(MoqtMessageType::kRequestUpdate, + MakeParameters({SetExpires, SetLargestObject}), + ParametersAllowedByRequestOk, + SanitizeRequestOkParameters); + + VerifyParameterHandling( + MoqtMessageType::kTrackStatus, MakeParameters({SetLargestObject}), + ParametersAllowedByRequestOk, SanitizeRequestOkParameters); + + for (MoqtMessageType type : {MoqtMessageType::kPublishNamespace, + MoqtMessageType::kSubscribeNamespace, + MoqtMessageType::kSubscribeTracks}) { + VerifyParameterHandling(type, MessageParameters(), + ParametersAllowedByRequestOk, + SanitizeRequestOkParameters); + } + + for (MoqtMessageType invalid_type : + {MoqtMessageType::kSubscribe, MoqtMessageType::kSubscribeOk, + MoqtMessageType::kRequestError, MoqtMessageType::kRequestOk, + MoqtMessageType::kNamespace, MoqtMessageType::kPublishDone, + MoqtMessageType::kNamespaceDone, MoqtMessageType::kPublishSkipped, + MoqtMessageType::kGoAway, MoqtMessageType::kFetch, + MoqtMessageType::kFetchOk, MoqtMessageType::kSetup, + MoqtMessageType::kObjectAck}) { + MessageParameters params = AllParameters(); + EXPECT_QUICHE_BUG( + EXPECT_FALSE(ParametersAllowedByRequestOk(params, invalid_type)), + "Unexpected request type for REQUEST_OK"); + EXPECT_QUICHE_BUG(SanitizeRequestOkParameters(params, invalid_type), + "Unexpected request type for REQUEST_OK"); + EXPECT_EQ(params, MessageParameters()); + } +} + } // namespace } // namespace moqt::test
diff --git a/quiche/quic/moqt/moqt_namespace_stream.cc b/quiche/quic/moqt/moqt_namespace_stream.cc index bbf63ef..6562f56 100644 --- a/quiche/quic/moqt/moqt_namespace_stream.cc +++ b/quiche/quic/moqt/moqt_namespace_stream.cc
@@ -54,11 +54,21 @@ return absl::OkStatus(); } if (response_callback_ != nullptr) { + if (!ParametersAllowedByRequestOk(message.parameters, + MoqtMessageType::kSubscribeNamespace)) { + return absl::InvalidArgumentError( + "REQUEST_OK contains invalid parameters for SUBSCRIBE_NAMESPACE"); + } MoqtResponseCallback callback = std::move(response_callback_); response_callback_ = nullptr; std::move(callback)(message.parameters); return absl::OkStatus(); } + if (!ParametersAllowedByRequestOk(message.parameters, + MoqtMessageType::kRequestUpdate)) { + return absl::InvalidArgumentError( + "REQUEST_OK contains invalid parameters for REQUEST_UPDATE"); + } NamespaceTask* task = task_.GetIfAvailable(); if (task == nullptr) { // The application has already unsubscribed, and the stream has been reset. @@ -173,11 +183,9 @@ "Stream has been reset"}); return; } - MoqtRequestUpdate message{next_request_id_, state_->request_id_, parameters}; - state_->request_update_queue().Enqueue(parameters, - std::move(response_callback)); - state_->SendOrBufferMessageOrFatal( - state_->framer()->SerializeRequestUpdate(message)); + state_->CheckStatus(state_->SendRequestUpdate( + next_request_id_, state_->request_id_, parameters, + std::move(response_callback), MoqtMessageType::kSubscribeNamespace)); next_request_id_ += 2; } @@ -265,7 +273,7 @@ add_callback_ = nullptr; QUICHE_DCHECK(task_ == nullptr); task_ = application_(message.track_namespace_prefix, message.parameters, - ResponseCallback()); + ResponseCallback(MoqtMessageType::kSubscribeNamespace)); if (task_ != nullptr) { task_->SetObjectsAvailableCallback([this]() { ProcessNamespaces(); }); } @@ -275,11 +283,17 @@ absl::Status MoqtSubscribeNamespaceResponseStream::OnControlMessage( const MoqtRequestUpdate& message) { QUICHE_RETURN_IF_ERROR(validate_request_id_(message.request_id)); + if (!ParametersAllowedByRequestUpdate(message.parameters, + MoqtMessageType::kSubscribeNamespace)) { + return absl::InvalidArgumentError( + "REQUEST_UPDATE contains invalid parameters for SUBSCRIBE_NAMESPACE"); + } if (task_ == nullptr) { // This stream is dying. return absl::OkStatus(); } - task_->Update(message.parameters, ResponseCallback()); + task_->Update(message.parameters, + ResponseCallback(MoqtMessageType::kRequestUpdate)); return absl::OkStatus(); } @@ -352,19 +366,21 @@ } } -MoqtResponseCallback MoqtSubscribeNamespaceResponseStream::ResponseCallback() { - return [this]( +MoqtResponseCallback MoqtSubscribeNamespaceResponseStream::ResponseCallback( + MoqtMessageType type_of_ok) { + return [this, type_of_ok]( std::variant<MessageParameters, MoqtRequestErrorInfo> response) { - std::visit(absl::Overload{[this](const MessageParameters& parameters) { - // In draft-18, there are no useful parameters - // in SUBSCRIBE_NAMESPACE_OK, but Issue #1639 - // would change that. - CheckStatus(SendRequestOk(parameters)); - }, - [this](const MoqtRequestErrorInfo& error_info) { - CheckStatus(SendRequestError(error_info)); - }}, - response); + std::visit( + absl::Overload{[this, type_of_ok](const MessageParameters& parameters) { + // In draft-18, there are no useful parameters + // in SUBSCRIBE_NAMESPACE_OK, but Issue #1639 + // would change that. + CheckStatus(SendRequestOk(parameters, type_of_ok)); + }, + [this](const MoqtRequestErrorInfo& error_info) { + CheckStatus(SendRequestError(error_info)); + }}, + response); }; }
diff --git a/quiche/quic/moqt/moqt_namespace_stream.h b/quiche/quic/moqt/moqt_namespace_stream.h index 49c9178..5cbaad0 100644 --- a/quiche/quic/moqt/moqt_namespace_stream.h +++ b/quiche/quic/moqt/moqt_namespace_stream.h
@@ -167,7 +167,7 @@ private: void ProcessNamespaces(); - MoqtResponseCallback ResponseCallback(); + MoqtResponseCallback ResponseCallback(MoqtMessageType type_of_ok); uint64_t request_id_; TrackNamespace prefix_;
diff --git a/quiche/quic/moqt/moqt_namespace_stream_test.cc b/quiche/quic/moqt/moqt_namespace_stream_test.cc index 8de9273..3427eca 100644 --- a/quiche/quic/moqt/moqt_namespace_stream_test.cc +++ b/quiche/quic/moqt/moqt_namespace_stream_test.cc
@@ -260,11 +260,10 @@ } TEST_F(MoqtSubscribeNamespaceRequestStreamTest, UpdateAndRequestError) { - MessageParameters ok_params; - ok_params.expires = quic::QuicTimeDelta::FromSeconds(60); - EXPECT_CALL(response_callback_, - Call(testing::VariantWith<MessageParameters>(Eq(ok_params)))); - ReceiveControlMessage(MoqtRequestOk(ok_params)); + EXPECT_CALL( + response_callback_, + Call(testing::VariantWith<MessageParameters>(Eq(MessageParameters())))); + ReceiveControlMessage(MoqtRequestOk()); EXPECT_CALL(mock_stream_, Writev(ControlMessageOfType(MoqtMessageType::kRequestUpdate), _)); MessageParameters update_params; @@ -320,19 +319,20 @@ }; ObjectsAvailableCallback callback; MockNamespaceTask* task_ptr = nullptr; - MoqtRequestOk ok; - ok.parameters.expires = quic::QuicTimeDelta::FromSeconds(60); + MessageParameters ok_params; + ok_params.expires = quic::QuicTimeDelta::FromSeconds(60); EXPECT_CALL(add_callback_, Call).WillOnce(Return(true)); EXPECT_CALL(mock_application_, Call) .WillOnce([&](const TrackNamespace&, const MessageParameters&, MoqtResponseCallback response_callback) { - std::move(response_callback)(ok.parameters); + std::move(response_callback)(ok_params); auto task = std::make_unique<MockNamespaceTask>(message.track_namespace_prefix); task_ptr = task.get(); return task; }); - EXPECT_CALL(mock_stream_, Writev(SerializedControlMessage(ok), _)); + EXPECT_CALL(mock_stream_, + Writev(SerializedControlMessage(MoqtRequestOk()), _)); ReceiveControlMessage(message); ASSERT_TRUE(task_ptr != nullptr); EXPECT_EQ(task_ptr->prefix(), message.track_namespace_prefix); @@ -389,19 +389,20 @@ }; ObjectsAvailableCallback callback; MockNamespaceTask* task_ptr = nullptr; - MoqtRequestOk ok; - ok.parameters.expires = quic::QuicTimeDelta::FromSeconds(60); + MessageParameters ok_params; + ok_params.expires = quic::QuicTimeDelta::FromSeconds(60); EXPECT_CALL(add_callback_, Call).WillOnce(Return(true)); EXPECT_CALL(mock_application_, Call) .WillOnce([&](const TrackNamespace&, const MessageParameters&, MoqtResponseCallback response_callback) { - std::move(response_callback)(ok.parameters); + std::move(response_callback)(ok_params); auto task = std::make_unique<MockNamespaceTask>(message.track_namespace_prefix); task_ptr = task.get(); return task; }); - EXPECT_CALL(mock_stream_, Writev(SerializedControlMessage(ok), _)); + EXPECT_CALL(mock_stream_, + Writev(SerializedControlMessage(MoqtRequestOk()), _)); ReceiveControlMessage(message); ASSERT_TRUE(task_ptr != nullptr); EXPECT_EQ(task_ptr->prefix(), message.track_namespace_prefix); @@ -469,12 +470,14 @@ kRequestId, MessageParameters(), }; - update_message.parameters.subscriber_priority = 10; + update_message.parameters.track_namespace_prefix = + TrackNamespace({"foo", "bar"}); MoqtRequestOk ok_response; ok_response.parameters.expires = quic::QuicTimeDelta::FromSeconds(60); EXPECT_CALL(*task_ptr, Update(_, _)) .WillOnce([&](const MessageParameters& params, MoqtResponseCallback cb) { - EXPECT_EQ(params.subscriber_priority, 10); + EXPECT_EQ(params.track_namespace_prefix, + TrackNamespace({"foo", "bar"})); std::move(cb)(ok_response.parameters); }); EXPECT_CALL(mock_stream_, Writev(SerializedControlMessage(ok_response), _)); @@ -509,10 +512,12 @@ kRequestId, MessageParameters(), }; - update_message.parameters.subscriber_priority = 10; + update_message.parameters.track_namespace_prefix = + TrackNamespace({"foo", "bar"}); EXPECT_CALL(*task_ptr, Update(_, _)) .WillOnce([&](const MessageParameters& params, MoqtResponseCallback cb) { - EXPECT_EQ(params.subscriber_priority, 10); + EXPECT_EQ(params.track_namespace_prefix, + TrackNamespace({"foo", "bar"})); std::move(cb)(MoqtRequestErrorInfo{ RequestErrorCode::kInternalError, quic::QuicTimeDelta::FromMilliseconds(100), "bar"});
diff --git a/quiche/quic/moqt/moqt_parser.cc b/quiche/quic/moqt/moqt_parser.cc index 49625e4..a7318b2 100644 --- a/quiche/quic/moqt/moqt_parser.cc +++ b/quiche/quic/moqt/moqt_parser.cc
@@ -709,6 +709,10 @@ } QUICHE_RETURN_IF_ERROR(ReadFullTrackName(reader, subscribe.full_track_name)); QUICHE_ASSIGN_OR_RETURN(subscribe.parameters, ParseMessageParameters(reader)); + if (!ParametersAllowedByMessage(subscribe.parameters, + MoqtMessageType::kSubscribe)) { + return absl::InvalidArgumentError("Invalid SUBSCRIBE message parameters"); + } QUICHE_RETURN_IF_ERROR(CheckForTrailingData(reader)); return subscribe; } @@ -722,6 +726,11 @@ } QUICHE_ASSIGN_OR_RETURN(subscribe_ok.parameters, ParseMessageParameters(reader)); + if (!ParametersAllowedByMessage(subscribe_ok.parameters, + MoqtMessageType::kSubscribeOk)) { + return absl::InvalidArgumentError( + "Invalid SUBSCRIBE_OK message parameters"); + } QUICHE_RETURN_IF_ERROR( ParseKeyValuePairList(reader, subscribe_ok.properties)); if (!subscribe_ok.properties.Validate()) { @@ -794,6 +803,11 @@ ReadTrackNamespace(reader, publish_namespace.track_namespace)); QUICHE_ASSIGN_OR_RETURN(publish_namespace.parameters, ParseMessageParameters(reader)); + if (!ParametersAllowedByMessage(publish_namespace.parameters, + MoqtMessageType::kPublishNamespace)) { + return absl::InvalidArgumentError( + "Invalid PUBLISH_NAMESPACE message parameters"); + } QUICHE_RETURN_IF_ERROR(CheckForTrailingData(reader)); return publish_namespace; } @@ -843,7 +857,22 @@ absl::StatusOr<MoqtTrackStatus> MoqtControlMessageParser::ProcessTrackStatus( absl::string_view data) const { - return ProcessSubscribe(data); + quic::QuicDataReader reader(data); + MoqtTrackStatus track_status; + if (!reader.ReadMoqVarInt(&track_status.request_id)) { + return absl::InvalidArgumentError("Failed to read request ID"); + } + QUICHE_RETURN_IF_ERROR( + ReadFullTrackName(reader, track_status.full_track_name)); + QUICHE_ASSIGN_OR_RETURN(track_status.parameters, + ParseMessageParameters(reader)); + if (!ParametersAllowedByMessage(track_status.parameters, + MoqtMessageType::kTrackStatus)) { + return absl::InvalidArgumentError( + "Invalid TRACK_STATUS message parameters"); + } + QUICHE_RETURN_IF_ERROR(CheckForTrailingData(reader)); + return track_status; } absl::StatusOr<MoqtGoAway> MoqtControlMessageParser::ProcessGoAway( @@ -890,6 +919,11 @@ ReadTrackNamespace(reader, subscribe_namespace.track_namespace_prefix)); QUICHE_ASSIGN_OR_RETURN(subscribe_namespace.parameters, ParseMessageParameters(reader)); + if (!ParametersAllowedByMessage(subscribe_namespace.parameters, + MoqtMessageType::kSubscribeNamespace)) { + return absl::InvalidArgumentError( + "Invalid SUBSCRIBE_NAMESPACE message parameters"); + } QUICHE_RETURN_IF_ERROR(CheckForTrailingData(reader)); return subscribe_namespace; } @@ -905,6 +939,11 @@ ReadTrackNamespace(reader, subscribe_tracks.track_namespace_prefix)); QUICHE_ASSIGN_OR_RETURN(subscribe_tracks.parameters, ParseMessageParameters(reader)); + if (!ParametersAllowedByMessage(subscribe_tracks.parameters, + MoqtMessageType::kSubscribeTracks)) { + return absl::InvalidArgumentError( + "Invalid SUBSCRIBE_TRACKS message parameters"); + } QUICHE_RETURN_IF_ERROR(CheckForTrailingData(reader)); return subscribe_tracks; } @@ -969,6 +1008,9 @@ return absl::InvalidArgumentError("Invalid FETCH type"); } QUICHE_ASSIGN_OR_RETURN(fetch.parameters, ParseMessageParameters(reader)); + if (!ParametersAllowedByMessage(fetch.parameters, MoqtMessageType::kFetch)) { + return absl::InvalidArgumentError("Invalid FETCH message parameters"); + } QUICHE_RETURN_IF_ERROR(CheckForTrailingData(reader)); return fetch; } @@ -993,6 +1035,10 @@ } fetch_ok.end_of_track = end_of_track == 1; QUICHE_ASSIGN_OR_RETURN(fetch_ok.parameters, ParseMessageParameters(reader)); + if (!ParametersAllowedByMessage(fetch_ok.parameters, + MoqtMessageType::kFetchOk)) { + return absl::InvalidArgumentError("Invalid FETCH_OK message parameters"); + } QUICHE_RETURN_IF_ERROR(ParseKeyValuePairList(reader, fetch_ok.properties)); if (!fetch_ok.properties.Validate()) { return absl::InvalidArgumentError("Invalid FETCH_OK track properties"); @@ -1014,6 +1060,10 @@ return absl::InvalidArgumentError("Track alias missing"); } QUICHE_ASSIGN_OR_RETURN(publish.parameters, ParseMessageParameters(reader)); + if (!ParametersAllowedByMessage(publish.parameters, + MoqtMessageType::kPublish)) { + return absl::InvalidArgumentError("Invalid PUBLISH message parameters"); + } QUICHE_RETURN_IF_ERROR(ParseKeyValuePairList(reader, publish.properties)); if (!publish.properties.Validate()) { return absl::InvalidArgumentError("Invalid PUBLISH track properties");
diff --git a/quiche/quic/moqt/moqt_parser_test.cc b/quiche/quic/moqt/moqt_parser_test.cc index 3f655cd..34384b5 100644 --- a/quiche/quic/moqt/moqt_parser_test.cc +++ b/quiche/quic/moqt/moqt_parser_test.cc
@@ -25,6 +25,7 @@ #include "quiche/quic/moqt/moqt_error.h" #include "quiche/quic/moqt/moqt_key_value_pair.h" #include "quiche/quic/moqt/moqt_messages.h" +#include "quiche/quic/moqt/moqt_names.h" #include "quiche/quic/moqt/moqt_session_interface.h" #include "quiche/quic/moqt/moqt_types.h" #include "quiche/quic/moqt/test_tools/moqt_framer_utils.h" @@ -1948,4 +1949,106 @@ HasSubstr("New session URI too long"))); } +TEST_F(MoqtMessageSpecificTest, DisallowedParameters) { + char subscribe[] = { + 0x03, 0x00, 0x0e, 0x01, // request_id = 1 + 0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo" + 0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd" + 0x01, 0x08, 0x01, // expires = 1 + }; + EXPECT_EQ( + ExtractMoqtErrorForStatus( + ParseAllMessages(absl::string_view(subscribe, sizeof(subscribe))) + .status()), + MoqtError::kProtocolViolation); + + char subscribe_ok[] = { + 0x04, 0x00, 0x04, 0x01, // track_alias = 1 + 0x01, 0x10, 0x01, // forward = 1 + }; + EXPECT_EQ(ExtractMoqtErrorForStatus( + ParseAllMessages( + absl::string_view(subscribe_ok, sizeof(subscribe_ok))) + .status()), + MoqtError::kProtocolViolation); + + char publish_namespace[] = { + 0x06, 0x00, 0x09, 0x01, // request_id = 1 + 0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo" + 0x01, 0x10, 0x01, // forward = 1 + }; + EXPECT_EQ(ExtractMoqtErrorForStatus( + ParseAllMessages(absl::string_view(publish_namespace, + sizeof(publish_namespace))) + .status()), + MoqtError::kProtocolViolation); + + char track_status[] = { + 0x0d, 0x00, 0x0e, 0x01, // request_id = 1 + 0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo" + 0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd" + 0x01, 0x10, 0x01, // forward = 1 + }; + EXPECT_EQ(ExtractMoqtErrorForStatus( + ParseAllMessages( + absl::string_view(track_status, sizeof(track_status))) + .status()), + MoqtError::kProtocolViolation); + + char subscribe_namespace[] = { + 0x50, 0x00, 0x09, 0x01, // request_id = 1 + 0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo" + 0x01, 0x10, 0x01, // forward = 1 + }; + EXPECT_EQ(ExtractMoqtErrorForStatus( + ParseAllMessages(absl::string_view(subscribe_namespace, + sizeof(subscribe_namespace))) + .status()), + MoqtError::kProtocolViolation); + + char subscribe_tracks[] = { + 0x51, 0x00, 0x09, 0x01, // request_id = 1 + 0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo" + 0x01, 0x08, 0x01, // expires = 1 + }; + EXPECT_EQ(ExtractMoqtErrorForStatus( + ParseAllMessages(absl::string_view(subscribe_tracks, + sizeof(subscribe_tracks))) + .status()), + MoqtError::kProtocolViolation); + + char fetch[] = { + 0x16, 0x00, 0x07, 0x01, // request_id = 1 + 0x02, 0x02, 0x02, // kRelativeJoining, joining_request_id = 2, 2 + 0x01, 0x10, 0x01, // forward = 1 + }; + EXPECT_EQ( + ExtractMoqtErrorForStatus( + ParseAllMessages(absl::string_view(fetch, sizeof(fetch))).status()), + MoqtError::kProtocolViolation); + + char fetch_ok[] = { + 0x18, 0x00, 0x06, + 0x00, // end_of_track = false + 0x05, 0x04, // end_location = 5, 3 + 0x01, 0x10, 0x01, // forward = 1 + }; + EXPECT_EQ(ExtractMoqtErrorForStatus( + ParseAllMessages(absl::string_view(fetch_ok, sizeof(fetch_ok))) + .status()), + MoqtError::kProtocolViolation); + + char publish[] = { + 0x1d, 0x00, 0x0f, 0x01, // request_id = 1 + 0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo" + 0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd" + 0x04, // track_alias = 4 + 0x01, 0x20, 0x01, // subscriber_priority = 1 + }; + EXPECT_EQ(ExtractMoqtErrorForStatus( + ParseAllMessages(absl::string_view(publish, sizeof(publish))) + .status()), + MoqtError::kProtocolViolation); +} + } // namespace moqt::test
diff --git a/quiche/quic/moqt/moqt_publish_namespace_stream.cc b/quiche/quic/moqt/moqt_publish_namespace_stream.cc index 9300ff5..1f90ca0 100644 --- a/quiche/quic/moqt/moqt_publish_namespace_stream.cc +++ b/quiche/quic/moqt/moqt_publish_namespace_stream.cc
@@ -21,10 +21,9 @@ void MoqtPublishNamespaceRequestStream::OnStreamBound() { // TODO(martinduke): Set the priority for this stream. - SendOrBufferMessageOrFatal( - framer()->SerializePublishNamespace( - MoqtPublishNamespace{request_id_, prefix_, parameters_}), - false); + MoqtPublishNamespace message{request_id_, prefix_, parameters_}; + SendOrBufferMessageOrFatal(framer()->SerializePublishNamespace(message), + false); stream_parser()->set_allow_fin(true); QUIC_DLOG(INFO) << "Sent PUBLISH_NAMESPACE message for " << prefix_; } @@ -44,11 +43,21 @@ } if (response_callback_ != nullptr) { // Response to the initial PUBLISH_NAMESPACE. + if (!ParametersAllowedByRequestOk(message.parameters, + MoqtMessageType::kPublishNamespace)) { + return absl::InvalidArgumentError( + "REQUEST_OK contains invalid parameters for PUBLISH_NAMESPACE"); + } auto callback = std::move(response_callback_); response_callback_ = nullptr; std::move(callback)(message.parameters); return absl::OkStatus(); } + if (!ParametersAllowedByRequestOk(message.parameters, + MoqtMessageType::kRequestUpdate)) { + return absl::InvalidArgumentError( + "REQUEST_OK contains invalid parameters for REQUEST_UPDATE"); + } absl::StatusOr<MessageParameters> old_parameters = request_update_queue().NextParameters(); if (!old_parameters.ok()) { @@ -121,7 +130,8 @@ } std::visit(absl::Overload{ [&](const MessageParameters& parameters) { - stream->CheckStatus(stream->SendRequestOk(parameters)); + stream->CheckStatus(stream->SendRequestOk( + parameters, MoqtMessageType::kPublishNamespace)); }, [&](const MoqtRequestErrorInfo& error) { stream->CheckStatus(stream->SendRequestError(error)); @@ -134,6 +144,11 @@ absl::Status MoqtPublishNamespaceResponseStream::OnControlMessage( const MoqtRequestUpdate& message) { QUICHE_RETURN_IF_ERROR(validate_request_id_(message.request_id)); + if (!ParametersAllowedByRequestUpdate(message.parameters, + MoqtMessageType::kPublishNamespace)) { + return absl::InvalidArgumentError( + "REQUEST_UPDATE contains invalid parameters for PUBLISH_NAMESPACE"); + } if (!prefix_.has_value()) { return absl::InvalidArgumentError( "REQUEST_UPDATE before PUBLISH_NAMESPACE on a PN stream"); @@ -148,7 +163,8 @@ } std::visit(absl::Overload{ [&](const MessageParameters& parameters) { - stream->CheckStatus(stream->SendRequestOk(parameters)); + stream->CheckStatus(stream->SendRequestOk( + parameters, MoqtMessageType::kRequestUpdate)); }, [&](const MoqtRequestErrorInfo& error) { stream->CheckStatus(stream->SendRequestError(error));
diff --git a/quiche/quic/moqt/moqt_publish_namespace_stream_test.cc b/quiche/quic/moqt/moqt_publish_namespace_stream_test.cc index af228db..dc6b1cc 100644 --- a/quiche/quic/moqt/moqt_publish_namespace_stream_test.cc +++ b/quiche/quic/moqt/moqt_publish_namespace_stream_test.cc
@@ -126,20 +126,23 @@ EXPECT_CALL(mock_stream_, Writev(ControlMessageOfType(MoqtMessageType::kRequestUpdate), _)); MessageParameters parameters; - parameters.subscriber_priority = 50; + parameters.authorization_tokens.emplace_back(AuthTokenType::kOutOfBand, + "token"); bool update_callback_called = false; MoqtResponseCallback update_callback = [&](std::variant<MessageParameters, MoqtRequestErrorInfo> res) { update_callback_called = true; ASSERT_TRUE(std::holds_alternative<MessageParameters>(res)); - EXPECT_EQ(std::get<MessageParameters>(res).subscriber_priority, 50); + EXPECT_EQ(std::get<MessageParameters>(res).expires, + quic::QuicTimeDelta::FromSeconds(10)); }; QUICHE_EXPECT_OK(request_stream->SendRequestUpdate( - 11, 0, parameters, std::move(update_callback))); + 11, 0, parameters, std::move(update_callback), + MoqtMessageType::kPublishNamespace)); // Receive OK for update. MoqtRequestOk ok; - ok.parameters.subscriber_priority = 50; + ok.parameters.expires = quic::QuicTimeDelta::FromSeconds(10); QUICHE_EXPECT_OK(request_stream->OnControlMessage(ok)); EXPECT_TRUE(update_callback_called); } @@ -360,7 +363,8 @@ // Now send a RequestUpdate. MoqtRequestUpdate update; update.request_id = 12; - update.parameters.subscriber_priority = 40; + update.parameters.authorization_tokens.emplace_back(AuthTokenType::kOutOfBand, + "token"); MoqtResponseCallback update_callback; EXPECT_CALL(application_, @@ -415,7 +419,8 @@ // Now send a RequestUpdate. MoqtRequestUpdate update; update.request_id = 12; - update.parameters.subscriber_priority = 40; + update.parameters.authorization_tokens.emplace_back(AuthTokenType::kOutOfBand, + "token"); MoqtResponseCallback update_callback; EXPECT_CALL(application_,
diff --git a/quiche/quic/moqt/moqt_publish_stream.cc b/quiche/quic/moqt/moqt_publish_stream.cc index 71ac1d7..9e1f8a4 100644 --- a/quiche/quic/moqt/moqt_publish_stream.cc +++ b/quiche/quic/moqt/moqt_publish_stream.cc
@@ -53,13 +53,12 @@ publisher_->parameters().largest_object = publisher_->publisher().largest_location(); publisher_->parameters().expires = publisher_->publisher().expiration(); - SendOrBufferMessageOrFatal(framer()->SerializePublish(MoqtPublish{ - publisher_->request_id(), publisher_->publisher().GetTrackName(), - publisher_->track_alias(), publisher_->parameters(), - publisher_->publisher().properties()})); - // Use the default group order. - publisher_->parameters().group_order = - publisher_->publisher().properties().default_publisher_group_order(); + MoqtPublish publish{publisher_->request_id(), + publisher_->publisher().GetTrackName(), + publisher_->track_alias(), publisher_->parameters(), + publisher_->publisher().properties()}; + publish.parameters.group_order.reset(); + SendOrBufferMessageOrFatal(framer()->SerializePublish(publish)); } absl::Status MoqtPublishRequestStream::OnRawControlMessage( @@ -77,6 +76,11 @@ } if (response_callback_ != nullptr) { // PUBLISH_OK + if (!ParametersAllowedByRequestOk(message.parameters, + MoqtMessageType::kPublish)) { + return absl::InvalidArgumentError( + "REQUEST_OK contains invalid parameters for PUBLISH"); + } publisher_->Update(message.parameters, /*from_request_ok=*/true); // In draft-18, PUBLISH_OK can update the group order. This has been // eliminated since. This is not implemented because it is likely to be @@ -87,6 +91,11 @@ return absl::OkStatus(); } // REQUEST_UPDATE_OK + if (!ParametersAllowedByRequestOk(message.parameters, + MoqtMessageType::kRequestUpdate)) { + return absl::InvalidArgumentError( + "REQUEST_OK contains invalid parameters for REQUEST_UPDATE"); + } QUICHE_ASSIGN_OR_RETURN(MessageParameters parameters, request_update_queue().NextParameters()); // Apply the pending parameters to the subscription. @@ -110,6 +119,11 @@ absl::Status MoqtPublishRequestStream::OnControlMessage( const MoqtRequestUpdate& message) { QUICHE_RETURN_IF_ERROR(validate_request_id_(message.request_id)); + if (!ParametersAllowedByRequestUpdate(message.parameters, + MoqtMessageType::kRequestOk)) { + return absl::InvalidArgumentError( + "REQUEST_UPDATE contains invalid parameters for PUBLISH_OK"); + } publisher_->Update(message.parameters, /*from_request_ok=*/false); return absl::OkStatus(); } @@ -190,8 +204,8 @@ update_parameters.new_group_request.reset(); } response_stream->subscriber_->Update(update_parameters); - response_stream->CheckStatus( - response_stream->SendRequestOk(update_parameters)); + response_stream->CheckStatus(response_stream->SendRequestOk( + update_parameters, MoqtMessageType::kPublish)); }, [response_stream = stream](const MoqtRequestErrorInfo& error_info) { @@ -203,7 +217,8 @@ } else { // Since the application already called SUBSCRIBE, there will be no // invocation of the request callback. Send REQUEST_OK immediately. - CheckStatus(SendRequestOk(subscriber_->const_parameters())); + CheckStatus(SendRequestOk(subscriber_->const_parameters(), + MoqtMessageType::kPublish)); } incoming_publish_callback_ = nullptr; if (subscriber_->visitor() == nullptr) { @@ -221,12 +236,18 @@ absl::Status MoqtPublishResponseStream::OnControlMessage( const MoqtRequestUpdate& message) { QUICHE_RETURN_IF_ERROR(validate_request_id_(message.request_id)); + if (!ParametersAllowedByRequestUpdate(message.parameters, + MoqtMessageType::kPublish)) { + return absl::InvalidArgumentError( + "REQUEST_UPDATE contains invalid parameters for PUBLISH"); + } if (subscriber_ == nullptr) { // Stream is already closing. return absl::OkStatus(); } subscriber_->Update(message.parameters); - CheckStatus(SendRequestOk(MessageParameters())); + CheckStatus( + SendRequestOk(MessageParameters(), MoqtMessageType::kRequestUpdate)); return absl::OkStatus(); } @@ -237,6 +258,11 @@ absl::InvalidArgumentError("REQUEST_OK received with properties")); return absl::OkStatus(); } + if (!ParametersAllowedByRequestOk(message.parameters, + MoqtMessageType::kRequestUpdate)) { + return absl::InvalidArgumentError( + "REQUEST_OK contains invalid parameters for REQUEST_UPDATE"); + } // TODO(martinduke): Process REQUEST_OK parameters. return request_update_queue().OnControlMessage(message); }
diff --git a/quiche/quic/moqt/moqt_publish_stream_test.cc b/quiche/quic/moqt/moqt_publish_stream_test.cc index e896e57..122bacd 100644 --- a/quiche/quic/moqt/moqt_publish_stream_test.cc +++ b/quiche/quic/moqt/moqt_publish_stream_test.cc
@@ -470,8 +470,8 @@ MoqtRequestUpdate request_update; request_update.request_id = kRequestId + 2; request_update.existing_request_id = kRequestId; - request_update.parameters.object_delivery_timeout = - quic::QuicTimeDelta::FromSeconds(3); + request_update.parameters.authorization_tokens.emplace_back( + AuthTokenType::kOutOfBand, "token"); EXPECT_CALL(mock_stream_, Writev(ControlMessageOfType(MoqtMessageType::kRequestOk), _)) .WillOnce(Return(absl::OkStatus())); @@ -481,8 +481,8 @@ ASSERT_NE(captured_subscriber, nullptr); const MessageParameters& sub_params = LiveSubscriberPeer::parameters(*captured_subscriber); - EXPECT_EQ(sub_params.object_delivery_timeout, - request_update.parameters.object_delivery_timeout); + EXPECT_EQ(sub_params.authorization_tokens, + request_update.parameters.authorization_tokens); EXPECT_CALL(mock_subscribe_visitor_, OnPublishDone(kTrackName)); }
diff --git a/quiche/quic/moqt/moqt_session.cc b/quiche/quic/moqt/moqt_session.cc index b62d294..6f40b68 100644 --- a/quiche/quic/moqt/moqt_session.cc +++ b/quiche/quic/moqt/moqt_session.cc
@@ -325,6 +325,7 @@ message.request_id = NextRequestId(); message.track_namespace_prefix = prefix; message.parameters = parameters; + SanitizeParameters(message.parameters, MoqtMessageType::kSubscribeNamespace); state_ptr->SendOrBufferMessageOrFatal( framer_.SerializeSubscribeNamespace(message)); QUIC_DLOG(INFO) << ENDPOINT << "Sent SUBSCRIBE_NAMESPACE message for " @@ -365,9 +366,11 @@ return false; } + MessageParameters sanitized_parameters = parameters; + SanitizeParameters(sanitized_parameters, MoqtMessageType::kTrackStatus); uint64_t request_id = NextRequestId(); auto stream_visitor = std::make_unique<MoqtTrackStatusRequestStream>( - &framer_, ControlMessageParser(), request_id, name, parameters, + &framer_, ControlMessageParser(), request_id, name, sanitized_parameters, [session_weak = GetWeakPtr()](MoqtError code, absl::string_view reason) { MoqtSession* session = MoqtSessionFromWeakPtr(session_weak); if (session != nullptr) { @@ -401,8 +404,10 @@ if (stream == nullptr) { return false; } + MessageParameters sanitized_parameters = parameters; + SanitizeParameters(sanitized_parameters, MoqtMessageType::kPublishNamespace); auto stream_visitor = std::make_unique<MoqtPublishNamespaceRequestStream>( - track_namespace, parameters, &framer_, ControlMessageParser(), + track_namespace, sanitized_parameters, &framer_, ControlMessageParser(), NextRequestId(), [weakptr = GetWeakPtr(), callback = std::move(cancel_callback)]( const TrackNamespace& prefix) mutable { @@ -442,7 +447,8 @@ return false; } it->second->CheckStatus(it->second->SendRequestUpdate( - NextRequestId(), 0, parameters, std::move(response_callback))); + NextRequestId(), 0, parameters, std::move(response_callback), + MoqtMessageType::kPublishNamespace)); return true; } @@ -500,6 +506,8 @@ << "Tried to subscribe to a reserved track name"; return false; } + MessageParameters sanitized_parameters = parameters; + SanitizeParameters(sanitized_parameters, MoqtMessageType::kSubscribe); auto stream_visitor = std::make_unique<MoqtSubscribeRequestStream>( &framer_, ControlMessageParser(), NextRequestId(), [weak_session = GetWeakPtr()](MoqtError code, absl::string_view reason) { @@ -509,7 +517,7 @@ } session->Error(code, reason); }, - name, visitor, parameters, + name, visitor, sanitized_parameters, [weakptr = GetWeakPtr()](LiveSubscriber* track) { MoqtSession* session = MoqtSessionFromWeakPtr(weakptr); if (session == nullptr || !track->track_alias().has_value()) { @@ -567,7 +575,8 @@ // sending zero because related request ID is ignored for SUBSCRIBE. return it->second->request_stream() ->SendRequestUpdate(NextRequestId(), 0, update_parameters, - std::move(response_callback)) + std::move(response_callback), + MoqtMessageType::kSubscribe) .ok(); } @@ -644,9 +653,11 @@ return session->ValidateNewIncomingRequestId(request_id); }, std::move(response_callback)); + MessageParameters sanitized_parameters = parameters; + SanitizeParameters(sanitized_parameters, MoqtMessageType::kPublish); auto publish_state = std::make_unique<LivePublisher>( framer_, publisher, stream_visitor.get(), next_request_id_, - next_local_track_alias_, parameters, + next_local_track_alias_, sanitized_parameters, weak_ptr_factory_for_publishers_.Create(), true); LivePublisher* publisher_ptr = publish_state.get(); stream_visitor->SetPublisher(std::move(publish_state)); @@ -656,7 +667,7 @@ stream_visitor_ptr->BindStream(stream); next_request_id_ += 2; ++next_local_track_alias_; - publisher->AddObjectListener(publisher_ptr, parameters); + publisher->AddObjectListener(publisher_ptr, sanitized_parameters); return true; } @@ -678,12 +689,14 @@ QUIC_DLOG(INFO) << ENDPOINT << "Tried to send FETCH but no more streams"; return nullptr; } + MessageParameters sanitized_parameters = parameters; + SanitizeParameters(sanitized_parameters, MoqtMessageType::kFetch); uint64_t request_id = NextRequestId(); auto task = std::make_unique<UpstreamFetchTask>(); auto fetch = std::make_unique<MoqtFetchRequestStream>( &framer_, ControlMessageParser(), request_id, name, start, - Location(end_group, end_object.value_or(kMaxObjectId)), parameters, - task.get(), + Location(end_group, end_object.value_or(kMaxObjectId)), + sanitized_parameters, task.get(), [weak_session = GetWeakPtr()](MoqtError code, absl::string_view reason) { MoqtSession* session = MoqtSessionFromWeakPtr(weak_session); if (session == nullptr) { @@ -753,11 +766,13 @@ return nullptr; } QUIC_DLOG(INFO) << ENDPOINT << "Sent Joining FETCH message for " << name; + MessageParameters fetch_parameters = parameters; + SanitizeParameters(fetch_parameters, MoqtMessageType::kFetch); uint64_t request_id = NextRequestId(); auto task = std::make_unique<UpstreamFetchTask>(); auto fetch = std::make_unique<MoqtFetchRequestStream>( &framer_, ControlMessageParser(), request_id, name, subscribe_request_id, - num_previous_groups, /*relative=*/true, parameters, task.get(), + num_previous_groups, /*relative=*/true, fetch_parameters, task.get(), [weak_session = GetWeakPtr()](MoqtError code, absl::string_view reason) { MoqtSession* session = MoqtSessionFromWeakPtr(weak_session); if (session == nullptr) {
diff --git a/quiche/quic/moqt/moqt_session_test.cc b/quiche/quic/moqt/moqt_session_test.cc index e4961b5..dbeb457 100644 --- a/quiche/quic/moqt/moqt_session_test.cc +++ b/quiche/quic/moqt/moqt_session_test.cc
@@ -116,6 +116,22 @@ return DefaultSubscribe(kDefaultLocalRequestId); } +MoqtTrackStatus DefaultTrackStatus() { + return MoqtTrackStatus{ + kDefaultPeerRequestId, + kDefaultTrackName(), + MessageParameters(), + }; +} + +MoqtTrackStatus DefaultLocalTrackStatus() { + return MoqtTrackStatus{ + kDefaultLocalRequestId, + kDefaultTrackName(), + MessageParameters(), + }; +} + MoqtFetch DefaultFetch() { MoqtFetch fetch = { kDefaultPeerRequestId, @@ -2022,13 +2038,13 @@ MoqtSubscribeNamespace subscribe_namespace = {/*request_id=*/1, prefix, parameters}; quiche::QuicheWeakPtr<MockNamespaceTask> task; - MoqtRequestOk expected_ok; - expected_ok.parameters.expires = quic::QuicTimeDelta::FromSeconds(60); + MessageParameters ok_params; + ok_params.expires = quic::QuicTimeDelta::FromSeconds(60); EXPECT_CALL(session_callbacks_.incoming_subscribe_namespace_callback, Call(prefix, parameters, _)) .WillOnce([&](const TrackNamespace& prefix, const MessageParameters&, MoqtResponseCallback response_callback) { - std::move(response_callback)(expected_ok.parameters); + std::move(response_callback)(ok_params); auto task_ptr = std::make_unique<MockNamespaceTask>(prefix); task = task_ptr->GetWeakPtr(); return task_ptr; @@ -2036,7 +2052,7 @@ bidi_wrapper_ = std::make_unique<MoqtBidiStreamTestWrapper>( ResponseStream(kSubscribeNamespaceByte)); EXPECT_CALL(mock_bidi_stream_, - Writev(SerializedControlMessage(expected_ok), _)) + Writev(SerializedControlMessage(MoqtRequestOk()), _)) .WillOnce(Return(absl::OkStatus())); bidi_wrapper_->ReceiveMessage(subscribe_namespace); @@ -2516,7 +2532,7 @@ webtransport::test::InMemoryStreamWithWriteBuffer bidi_stream(0); MoqtFramer client_framer(session_parameters.using_webtrans, quic::Perspective::IS_CLIENT); - MoqtTrackStatus track_status = DefaultLocalSubscribe(); + MoqtTrackStatus track_status = DefaultLocalTrackStatus(); quiche::QuicheBuffer serialized_track_status = client_framer.SerializeTrackStatus(track_status); bidi_stream.Receive(serialized_track_status.AsStringView(), @@ -2557,17 +2573,12 @@ ResponseStream(kTrackStatusByte)); auto* track = CreateTrackPublisher(); - MoqtTrackStatus track_status = DefaultSubscribe(); + MoqtTrackStatus track_status = DefaultTrackStatus(); EXPECT_CALL(*track, AddObjectListener) .WillOnce([&](MoqtObjectListener* listener, const MessageParameters&) { - EXPECT_CALL(*track, expiration) - .WillRepeatedly( - Return(quic::QuicTimeDelta::FromMilliseconds(10000))); EXPECT_CALL(*track, largest_location) .WillRepeatedly(Return(Location(5, 30))); MoqtRequestOk expected_ok; - expected_ok.parameters.expires = - quic::QuicTimeDelta::FromMilliseconds(10000); expected_ok.parameters.largest_object = Location(5, 30); EXPECT_CALL(mock_bidi_stream_, Writev(SerializedControlMessage(expected_ok), _)); @@ -2582,17 +2593,14 @@ ResponseStream(kTrackStatusByte)); auto* track = CreateTrackPublisher(); - MoqtTrackStatus track_status = DefaultSubscribe(); + MoqtTrackStatus track_status = DefaultTrackStatus(); MoqtObjectListener* listener = nullptr; EXPECT_CALL(*track, AddObjectListener) .WillOnce(testing::SaveArg<0>(&listener)); bidi_wrapper_->ReceiveMessage(track_status); ASSERT_NE(listener, nullptr); - EXPECT_CALL(*track, expiration) - .WillRepeatedly(Return(quic::QuicTimeDelta::FromMilliseconds(10000))); EXPECT_CALL(*track, largest_location).WillRepeatedly(Return(Location(5, 30))); MoqtRequestOk expected_ok; - expected_ok.parameters.expires = quic::QuicTimeDelta::FromMilliseconds(10000); expected_ok.parameters.largest_object = Location(5, 30); EXPECT_CALL(mock_bidi_stream_, Writev(SerializedControlMessage(expected_ok), _)); @@ -2605,7 +2613,7 @@ ResponseStream(kTrackStatusByte)); auto* track = CreateTrackPublisher(); - MoqtTrackStatus track_status = DefaultSubscribe(); + MoqtTrackStatus track_status = DefaultTrackStatus(); bool executed_AddObjectListener = false; EXPECT_CALL(*track, AddObjectListener) .WillOnce([&](MoqtObjectListener* listener, const MessageParameters&) { @@ -2626,7 +2634,7 @@ ResponseStream(kTrackStatusByte)); auto* track = CreateTrackPublisher(); - MoqtTrackStatus track_status = DefaultSubscribe(); + MoqtTrackStatus track_status = DefaultTrackStatus(); MoqtObjectListener* listener; EXPECT_CALL(*track, AddObjectListener) .WillOnce(testing::SaveArg<0>(&listener)); @@ -2725,16 +2733,16 @@ // Register two incoming PUBLISH_NAMESPACE. MoqtPublishNamespace publish_namespace{ /*request_id=*/1, TrackNamespace{"foo"}, MessageParameters()}; - MoqtRequestOk expected_ok; - expected_ok.parameters.expires = quic::QuicTimeDelta::FromSeconds(60); + MessageParameters ok_params; + ok_params.expires = quic::QuicTimeDelta::FromSeconds(60); EXPECT_CALL(session_callbacks_.incoming_publish_namespace_callback, Call(TrackNamespace{"foo"}, _, _)) .WillOnce([&](const TrackNamespace&, const MessageParameters*, MoqtResponseCallback callback) { - std::move(callback)(expected_ok.parameters); + std::move(callback)(ok_params); }); EXPECT_CALL(mock_bidi_stream_, - Writev(SerializedControlMessage(expected_ok), _)); + Writev(SerializedControlMessage(MoqtRequestOk()), _)); bidi_wrapper_->ReceiveMessage(publish_namespace); auto bidi_wrapper_2 = std::make_unique<MoqtBidiStreamTestWrapper>( @@ -2943,8 +2951,10 @@ EXPECT_CALL(mock_bidi_stream_, ResetWithUserCode(kResetCodeCancelled)); MoqtRequestOk expected_request_ok; expected_request_ok.parameters = parameters; // params from the SUBSCRIBE. - // group_order can be in SUBSCRIBE but not REQUEST_OK. + // group_order is not stored in ObjectSubscriber::parameters_, and + // authorization_tokens is sanitized out of PUBLISH_OK. expected_request_ok.parameters.group_order = std::nullopt; + expected_request_ok.parameters.authorization_tokens.clear(); EXPECT_CALL(publish_stream, Writev(SerializedControlMessage(expected_request_ok), _)); // remote_track_visitor_ is reused, not destroyed.
diff --git a/quiche/quic/moqt/moqt_subscribe_stream.cc b/quiche/quic/moqt/moqt_subscribe_stream.cc index ad69cd5..8151e1a 100644 --- a/quiche/quic/moqt/moqt_subscribe_stream.cc +++ b/quiche/quic/moqt/moqt_subscribe_stream.cc
@@ -50,9 +50,9 @@ void MoqtSubscribeRequestStream::OnStreamBound() { stream_parser()->set_allow_fin(true); - SendOrBufferMessageOrFatal(framer()->SerializeSubscribe( - MoqtSubscribe{track_->request_id(), track_->full_track_name(), - track_->const_parameters()})); + MoqtSubscribe subscribe{track_->request_id(), track_->full_track_name(), + track_->const_parameters()}; + SendOrBufferMessageOrFatal(framer()->SerializeSubscribe(subscribe)); } absl::Status MoqtSubscribeRequestStream::OnRawControlMessage( @@ -114,6 +114,11 @@ "REQUEST_UPDATE_OK received with properties")); return absl::OkStatus(); } + if (!ParametersAllowedByRequestOk(message.parameters, + MoqtMessageType::kRequestUpdate)) { + return absl::InvalidArgumentError( + "REQUEST_OK contains invalid parameters for REQUEST_UPDATE"); + } absl::StatusOr<MessageParameters> old_parameters = request_update_queue().NextParameters(); if (!old_parameters.ok()) { @@ -240,6 +245,11 @@ absl::Status MoqtSubscribeResponseStream::OnControlMessage( const MoqtRequestUpdate& message) { QUICHE_RETURN_IF_ERROR(validate_request_id_(message.request_id)); + if (!ParametersAllowedByRequestUpdate(message.parameters, + MoqtMessageType::kSubscribe)) { + return absl::InvalidArgumentError( + "REQUEST_UPDATE contains invalid parameters for SUBSCRIBE"); + } if (subscription_ == nullptr) { QUICHE_BUG(INFO) << "Received REQUEST_UPDATE, no subscription state"; return SendRequestError(RequestErrorCode::kInternalError, std::nullopt,
diff --git a/quiche/quic/moqt/moqt_subscribe_stream_test.cc b/quiche/quic/moqt/moqt_subscribe_stream_test.cc index 36d24b3..49facbc 100644 --- a/quiche/quic/moqt/moqt_subscribe_stream_test.cc +++ b/quiche/quic/moqt/moqt_subscribe_stream_test.cc
@@ -193,8 +193,9 @@ quic::QuicTimeDelta::FromSeconds(30)); }; parameters_.subscriber_priority = 20; - QUICHE_EXPECT_OK(stream_->SendRequestUpdate( - kRequestId, kRequestId, parameters_, std::move(callback))); + QUICHE_EXPECT_OK(stream_->SendRequestUpdate(kRequestId, kRequestId, + parameters_, std::move(callback), + MoqtMessageType::kSubscribe)); // Params not yet updated. EXPECT_EQ(stream_->track()->const_parameters().subscriber_priority, std::nullopt);
diff --git a/quiche/quic/moqt/moqt_track_status_stream.cc b/quiche/quic/moqt/moqt_track_status_stream.cc index 44be988..bde2ed1 100644 --- a/quiche/quic/moqt/moqt_track_status_stream.cc +++ b/quiche/quic/moqt/moqt_track_status_stream.cc
@@ -60,6 +60,11 @@ if (response_callback_ == nullptr) { return absl::InvalidArgumentError("Duplicate REQUEST_OK"); } + if (!ParametersAllowedByRequestOk(message.parameters, + MoqtMessageType::kTrackStatus)) { + return absl::InvalidArgumentError( + "REQUEST_OK contains invalid parameters for TRACK_STATUS"); + } TrackStatusResponseCallback callback = std::move(response_callback_); response_callback_ = nullptr; Fin(); @@ -139,7 +144,6 @@ return; } MessageParameters parameters; - parameters.expires = publisher_->expiration(); parameters.largest_object = publisher_->largest_location(); // Since `fin` is true, this will also reset `publisher_`. CheckStatus(SendRequestOk(parameters, publisher_->properties())); @@ -171,9 +175,11 @@ absl::Status MoqtTrackStatusResponseStream::SendRequestOk( const MessageParameters& parameters, const TrackProperties& properties) { - return SendOrBufferMessage( - framer()->SerializeRequestOk(MoqtRequestOk(parameters, properties)), - /*fin=*/true); + MoqtRequestOk request_ok(parameters, properties); + SanitizeRequestOkParameters(request_ok.parameters, + MoqtMessageType::kTrackStatus); + return SendOrBufferMessage(framer()->SerializeRequestOk(request_ok), + /*fin=*/true); } } // namespace moqt
diff --git a/quiche/quic/moqt/moqt_track_status_stream_test.cc b/quiche/quic/moqt/moqt_track_status_stream_test.cc index 5fda85a..000b31c 100644 --- a/quiche/quic/moqt/moqt_track_status_stream_test.cc +++ b/quiche/quic/moqt/moqt_track_status_stream_test.cc
@@ -121,7 +121,6 @@ quic::QuicTimeDelta::FromSeconds(10), quic::QuicTimeDelta::FromSeconds(5), std::nullopt, std::nullopt, std::nullopt, std::nullopt)); - ok.parameters.expires = quic::QuicTimeDelta::FromSeconds(10); ok.parameters.largest_object = Location(1, 2); EXPECT_CALL(response_callback_, Call) .WillOnce([&](std::variant<TrackStatusOkData, MoqtRequestErrorInfo> v) { @@ -245,8 +244,6 @@ GenericMessageToRawControlMessage(track_status))); ASSERT_NE(listener, nullptr); - EXPECT_CALL(mock_publisher_, expiration) - .WillRepeatedly(Return(quic::QuicTimeDelta::FromSeconds(5))); EXPECT_CALL(mock_publisher_, largest_location) .WillRepeatedly(Return(Location(10, 20))); EXPECT_CALL(mock_stream_,
diff --git a/quiche/quic/moqt/test_tools/mock_moqt_session.h b/quiche/quic/moqt/test_tools/mock_moqt_session.h index 55d5220..8dfdad2 100644 --- a/quiche/quic/moqt/test_tools/mock_moqt_session.h +++ b/quiche/quic/moqt/test_tools/mock_moqt_session.h
@@ -163,7 +163,7 @@ MOCK_METHOD(absl::Status, SendRequestUpdate, (uint64_t request_id, uint64_t existing_request_id, const MessageParameters& parameters, - MoqtResponseCallback callback), + MoqtResponseCallback callback, MoqtMessageType updated_type), (override)); MOCK_METHOD(void, Detach, (), (override)); MOCK_METHOD(void, OnStreamBound, (), (override));
diff --git a/quiche/quic/moqt/test_tools/moqt_test_message.h b/quiche/quic/moqt/test_tools/moqt_test_message.h index ba6f96e..879c842 100644 --- a/quiche/quic/moqt/test_tools/moqt_test_message.h +++ b/quiche/quic/moqt/test_tools/moqt_test_message.h
@@ -1153,23 +1153,52 @@ }; }; -class QUICHE_NO_EXPORT TrackStatusMessage : public SubscribeMessage { +class QUICHE_NO_EXPORT TrackStatusMessage : public TestMessageBase { public: - TrackStatusMessage() : SubscribeMessage() { - SetByte(0, static_cast<uint8_t>(MoqtMessageType::kTrackStatus)); + TrackStatusMessage() : TestMessageBase() { + SetWireImage(raw_packet_, sizeof(raw_packet_)); + track_status_.parameters = MessageParameters(); + track_status_.parameters.authorization_tokens.emplace_back( + AuthTokenType::kOutOfBand, "bar"); } bool EqualFieldValues(const MessageStructuredData& values) const override { - auto value = std::get<MoqtTrackStatus>(values); - auto* subscribe = reinterpret_cast<MoqtSubscribe*>(&value); - MessageStructuredData structured_data = - TestMessageBase::MessageStructuredData(*subscribe); - return SubscribeMessage::EqualFieldValues(structured_data); + auto cast = std::get<MoqtTrackStatus>(values); + if (cast.request_id != track_status_.request_id) { + QUIC_LOG(INFO) << "TRACK_STATUS request_id mismatch"; + return false; + } + if (cast.full_track_name != track_status_.full_track_name) { + QUIC_LOG(INFO) << "TRACK_STATUS full_track_name mismatch"; + return false; + } + if (cast.parameters != track_status_.parameters) { + QUIC_LOG(INFO) << "TRACK_STATUS parameters mismatch"; + return false; + } + return true; } + void ExpandVarints() override { ExpandVarintsImpl("vvv---v----vvv-----"); } + MessageStructuredData structured_data() const override { - return TestMessageBase::MessageStructuredData(MoqtTrackStatus(subscribe_)); + return TestMessageBase::MessageStructuredData(track_status_); } + + private: + MoqtTrackStatus track_status_ = { + /*request_id=*/1, + FullTrackName("foo", "abcd"), + MessageParameters(), // Set in the constructor. + }; + + uint8_t raw_packet_[22] = { + 0x0d, 0x00, 0x13, 0x01, // request_id = 1 + 0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo" + 0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd" + 0x01, // 1 parameter + 0x03, 0x05, 0x03, 0x00, 0x62, 0x61, 0x72, // authorization_tag = "bar" + }; }; class QUICHE_NO_EXPORT GoAwayMessage : public TestMessageBase { @@ -1224,7 +1253,6 @@ SubscribeNamespaceMessage() : TestMessageBase() { subscribe_namespace_.parameters.authorization_tokens.push_back( AuthToken(AuthTokenType::kOutOfBand, "bar")); - subscribe_namespace_.parameters.set_forward(true); SetWireImage(raw_packet_, sizeof(raw_packet_)); } @@ -1246,19 +1274,18 @@ return true; } - void ExpandVarints() override { ExpandVarintsImpl("vvv---vvv-----v-"); } + void ExpandVarints() override { ExpandVarintsImpl("vvv---vvv-----"); } MessageStructuredData structured_data() const override { return TestMessageBase::MessageStructuredData(subscribe_namespace_); } private: - uint8_t raw_packet_[19] = { - 0x50, 0x00, 0x10, 0x01, // request_id = 1 + uint8_t raw_packet_[17] = { + 0x50, 0x00, 0x0e, 0x01, // request_id = 1 0x01, 0x03, 0x66, 0x6f, 0x6f, // namespace = "foo" - 0x02, // 2 parameters + 0x01, // 1 parameter 0x03, 0x05, 0x03, 0x00, 0x62, 0x61, 0x72, // authorization_tag = "bar" - 0x0d, 0x01, // forward = true }; MoqtSubscribeNamespace subscribe_namespace_ = {