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_ = {