Moqt MessageParameters are no longer Key-Value Pairs.

SETUP message no longer contains number of options (Key-Value Pairs are now never length-prefixed).

Part of the draft-18 update.

PiperOrigin-RevId: 994738534
diff --git a/quiche/quic/moqt/moqt_framer.cc b/quiche/quic/moqt/moqt_framer.cc
index e02f090..9c7abb0 100644
--- a/quiche/quic/moqt/moqt_framer.cc
+++ b/quiche/quic/moqt/moqt_framer.cc
@@ -86,13 +86,10 @@
 
 class WireKeyValuePairList {
  public:
-  explicit WireKeyValuePairList(const KeyValuePairList& list,
-                                bool length_prefix = true)
-      : list_(list), length_prefix_(length_prefix) {}
+  explicit WireKeyValuePairList(const KeyValuePairList& list) : list_(list) {}
 
   size_t GetLengthOnWire() {
-    size_t total =
-        length_prefix_ ? WireMoqVarInt(list_.size()).GetLengthOnWire() : 0;
+    size_t total = 0;
     uint64_t last_key = 0;
     list_.ForEach([&](uint64_t key,
                       std::variant<uint64_t, absl::string_view> value) {
@@ -111,9 +108,6 @@
     return total;
   }
   absl::Status SerializeIntoWriter(quiche::QuicheDataWriter& writer) {
-    if (length_prefix_) {
-      WireMoqVarInt(list_.size()).SerializeIntoWriter(writer);
-    }
     uint64_t last_key = 0;
     list_.ForEach(
         [&](uint64_t key, std::variant<uint64_t, absl::string_view> value) {
@@ -135,7 +129,283 @@
 
  private:
   const KeyValuePairList& list_;
-  const bool length_prefix_;
+};
+
+class WireLocation {
+ public:
+  explicit WireLocation(const Location& location) : location_(location) {}
+  size_t GetLengthOnWire() {
+    return quiche::ComputeLengthOnWire(WireMoqVarInt(location_.group),
+                                       WireMoqVarInt(location_.object));
+  }
+  absl::Status SerializeIntoWriter(quiche::QuicheDataWriter& writer) {
+    return quiche::SerializeIntoWriter(writer, WireMoqVarInt(location_.group),
+                                       WireMoqVarInt(location_.object));
+  }
+
+ private:
+  const Location& location_;
+};
+
+class WireAuthToken {
+ public:
+  explicit WireAuthToken(const AuthToken& token) : token_(token) {}
+  size_t GetLengthOnWire() {
+    return quiche::ComputeLengthOnWire(
+        WireMoqVarInt(token_.alias_type),
+        WireOptional<WireMoqVarInt>(token_.alias),
+        WireOptional<WireMoqVarInt>(token_.type),
+        WireOptional<WireBytes>(token_.value));
+  }
+  absl::Status SerializeIntoWriter(quiche::QuicheDataWriter& writer) {
+    return quiche::SerializeIntoWriter(
+        writer, WireMoqVarInt(token_.alias_type),
+        WireOptional<WireMoqVarInt>(token_.alias),
+        WireOptional<WireMoqVarInt>(token_.type),
+        WireOptional<WireBytes>(token_.value));
+  }
+
+ private:
+  const AuthToken& token_;
+};
+
+class WireSubscriptionFilter {
+ public:
+  explicit WireSubscriptionFilter(const SubscriptionFilter& filter)
+      : filter_(filter) {}
+  size_t GetLengthOnWire() {
+    switch (filter_.type()) {
+      case MoqtFilterType::kNextGroupStart:
+      case MoqtFilterType::kLargestObject:
+        return quiche::ComputeLengthOnWire(WireMoqVarInt(filter_.type()));
+      case MoqtFilterType::kAbsoluteStart:
+        return quiche::ComputeLengthOnWire(WireMoqVarInt(filter_.type()),
+                                           WireLocation(filter_.start()));
+      case MoqtFilterType::kAbsoluteRange:
+        return quiche::ComputeLengthOnWire(
+            WireMoqVarInt(filter_.type()), WireLocation(filter_.start()),
+            WireMoqVarInt(filter_.end_group() - filter_.start().group));
+    }
+  }
+  absl::Status SerializeIntoWriter(quiche::QuicheDataWriter& writer) {
+    switch (filter_.type()) {
+      case MoqtFilterType::kNextGroupStart:
+      case MoqtFilterType::kLargestObject:
+        return quiche::SerializeIntoWriter(writer,
+                                           WireMoqVarInt(filter_.type()));
+      case MoqtFilterType::kAbsoluteStart:
+        return quiche::SerializeIntoWriter(writer,
+                                           WireMoqVarInt(filter_.type()),
+                                           WireLocation(filter_.start()));
+      case MoqtFilterType::kAbsoluteRange:
+        return quiche::SerializeIntoWriter(
+            writer, WireMoqVarInt(filter_.type()),
+            WireLocation(filter_.start()),
+            WireMoqVarInt(filter_.end_group() - filter_.start().group));
+    }
+  }
+
+ private:
+  const SubscriptionFilter& filter_;
+};
+
+uint64_t TimeDeltaToMilliseconds(const quic::QuicTimeDelta& time_delta) {
+  if (time_delta == quic::QuicTimeDelta::Infinite()) {
+    return 0ULL;
+  }
+  return std::max(time_delta.ToMilliseconds(), int64_t{1});
+}
+
+class WireMessageParameters {
+ public:
+  explicit WireMessageParameters(const MessageParameters& parameters)
+      : parameters_(parameters), num_parameters_(0) {
+    if (parameters_.object_delivery_timeout.has_value()) {
+      ++num_parameters_;
+    }
+    num_parameters_ += parameters_.authorization_tokens.size();
+    if (parameters_.subgroup_delivery_timeout.has_value()) {
+      ++num_parameters_;
+    }
+    if (parameters_.expires.has_value()) {
+      ++num_parameters_;
+    }
+    if (parameters_.largest_object.has_value()) {
+      ++num_parameters_;
+    }
+    if (parameters_.forward_has_value()) {
+      ++num_parameters_;
+    }
+    if (parameters_.subscriber_priority.has_value()) {
+      ++num_parameters_;
+    }
+    if (parameters_.subscription_filter.has_value()) {
+      ++num_parameters_;
+    }
+    if (parameters_.group_order.has_value()) {
+      ++num_parameters_;
+    }
+    if (parameters_.new_group_request.has_value()) {
+      ++num_parameters_;
+    }
+    if (parameters_.oack_window_size.has_value()) {
+      ++num_parameters_;
+    }
+  }
+
+  size_t GetLengthOnWire() {
+    size_t length = WireMoqVarInt(num_parameters_).GetLengthOnWire();
+    uint64_t last_key = 0;
+    auto key_delta = [&](MessageParameter key) {
+      uint64_t delta = static_cast<uint64_t>(key) - last_key;
+      last_key = static_cast<uint64_t>(key);
+      return delta;
+    };
+    if (parameters_.object_delivery_timeout.has_value()) {
+      length += quiche::ComputeLengthOnWire(WireKeyVarIntPair(
+          key_delta(MessageParameter::kObjectDeliveryTimeout),
+          TimeDeltaToMilliseconds(*parameters_.object_delivery_timeout)));
+    }
+    for (const AuthToken& token : parameters_.authorization_tokens) {
+      WireAuthToken wire_token(token);
+      length += quiche::ComputeLengthOnWire(
+          WireMoqVarInt(key_delta(MessageParameter::kAuthorizationToken)),
+          WireMoqVarInt(wire_token.GetLengthOnWire()), wire_token);
+    }
+    if (parameters_.subgroup_delivery_timeout.has_value()) {
+      length +=
+          WireKeyVarIntPair(
+              key_delta(MessageParameter::kSubgroupDeliveryTimeout),
+              TimeDeltaToMilliseconds(*parameters_.subgroup_delivery_timeout))
+              .GetLengthOnWire();
+    }
+    if (parameters_.expires.has_value()) {
+      length += WireKeyVarIntPair(key_delta(MessageParameter::kExpires),
+                                  TimeDeltaToMilliseconds(*parameters_.expires))
+                    .GetLengthOnWire();
+    }
+    if (parameters_.largest_object.has_value()) {
+      length += quiche::ComputeLengthOnWire(
+          WireMoqVarInt(key_delta(MessageParameter::kLargestObject)),
+          WireLocation(*parameters_.largest_object));
+    }
+    if (parameters_.forward_has_value()) {
+      length += quiche::ComputeLengthOnWire(
+          WireMoqVarInt(key_delta(MessageParameter::kForward)),
+          WireUint8(parameters_.forward() ? 1ULL : 0ULL));
+    }
+    if (parameters_.subscriber_priority.has_value()) {
+      length += quiche::ComputeLengthOnWire(
+          WireMoqVarInt(key_delta(MessageParameter::kSubscriberPriority)),
+          WireUint8(*parameters_.subscriber_priority));
+    }
+    if (parameters_.subscription_filter.has_value()) {
+      WireSubscriptionFilter filter(*parameters_.subscription_filter);
+      length += quiche::ComputeLengthOnWire(
+          WireMoqVarInt(key_delta(MessageParameter::kSubscriptionFilter)),
+          WireMoqVarInt(filter.GetLengthOnWire()), filter);
+    }
+    if (parameters_.group_order.has_value()) {
+      length += quiche::ComputeLengthOnWire(
+          WireMoqVarInt(key_delta(MessageParameter::kGroupOrder)),
+          WireUint8(static_cast<uint8_t>(*parameters_.group_order)));
+    }
+    if (parameters_.new_group_request.has_value()) {
+      length += WireKeyVarIntPair(key_delta(MessageParameter::kNewGroupRequest),
+                                  *parameters_.new_group_request)
+                    .GetLengthOnWire();
+    }
+    if (parameters_.oack_window_size.has_value()) {
+      length +=
+          WireKeyVarIntPair(key_delta(MessageParameter::kOackWindowSize),
+                            parameters_.oack_window_size->ToMicroseconds())
+              .GetLengthOnWire();
+    }
+    return length;
+  }
+
+  absl::Status SerializeIntoWriter(quiche::QuicheDataWriter& writer) {
+    QUICHE_RETURN_IF_ERROR(
+        quiche::SerializeIntoWriter(writer, WireMoqVarInt(num_parameters_)));
+    uint64_t last_key = 0;
+    auto key_delta = [&](MessageParameter key) {
+      uint64_t delta = static_cast<uint64_t>(key) - last_key;
+      last_key = static_cast<uint64_t>(key);
+      return delta;
+    };
+    if (parameters_.object_delivery_timeout.has_value()) {
+      QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
+          writer,
+          WireKeyVarIntPair(
+              key_delta(MessageParameter::kObjectDeliveryTimeout),
+              TimeDeltaToMilliseconds(*parameters_.object_delivery_timeout))));
+    }
+    for (const AuthToken& token : parameters_.authorization_tokens) {
+      WireAuthToken wire_token(token);
+      QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
+          writer,
+          WireMoqVarInt(key_delta(MessageParameter::kAuthorizationToken)),
+          WireMoqVarInt(wire_token.GetLengthOnWire()), wire_token));
+    }
+    if (parameters_.subgroup_delivery_timeout.has_value()) {
+      QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
+          writer, WireKeyVarIntPair(
+                      key_delta(MessageParameter::kSubgroupDeliveryTimeout),
+                      TimeDeltaToMilliseconds(
+                          *parameters_.subgroup_delivery_timeout))));
+    }
+    if (parameters_.expires.has_value()) {
+      QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
+          writer,
+          WireKeyVarIntPair(key_delta(MessageParameter::kExpires),
+                            TimeDeltaToMilliseconds(*parameters_.expires))));
+    }
+    if (parameters_.largest_object.has_value()) {
+      QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
+          writer, WireMoqVarInt(key_delta(MessageParameter::kLargestObject)),
+          WireLocation(*parameters_.largest_object)));
+    }
+    if (parameters_.forward_has_value()) {
+      QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
+          writer, WireMoqVarInt(key_delta(MessageParameter::kForward)),
+          WireUint8(parameters_.forward() ? 1ULL : 0ULL)));
+    }
+    if (parameters_.subscriber_priority.has_value()) {
+      QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
+          writer,
+          WireMoqVarInt(key_delta(MessageParameter::kSubscriberPriority)),
+          WireUint8(*parameters_.subscriber_priority)));
+    }
+    if (parameters_.subscription_filter.has_value()) {
+      WireSubscriptionFilter filter(*parameters_.subscription_filter);
+      QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
+          writer,
+          WireMoqVarInt(key_delta(MessageParameter::kSubscriptionFilter)),
+          WireMoqVarInt(filter.GetLengthOnWire()), filter));
+    }
+    if (parameters_.group_order.has_value()) {
+      QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
+          writer, WireMoqVarInt(key_delta(MessageParameter::kGroupOrder)),
+          WireUint8(static_cast<uint8_t>(*parameters_.group_order))));
+    }
+    if (parameters_.new_group_request.has_value()) {
+      QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
+          writer,
+          WireKeyVarIntPair(key_delta(MessageParameter::kNewGroupRequest),
+                            *parameters_.new_group_request)));
+    }
+    if (parameters_.oack_window_size.has_value()) {
+      QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
+          writer,
+          WireKeyVarIntPair(key_delta(MessageParameter::kOackWindowSize),
+                            parameters_.oack_window_size->ToMicroseconds())));
+    }
+    return absl::OkStatus();
+  }
+
+ private:
+  const MessageParameters& parameters_;
+  uint64_t num_parameters_;
 };
 
 class WireTrackNamespace {
@@ -252,30 +522,6 @@
                    WireOptional<WireBytes>(token.value));
 }
 
-quiche::QuicheBuffer SerializeSubscriptionFilter(
-    const SubscriptionFilter& filter) {
-  switch (filter.type()) {
-    case MoqtFilterType::kNextGroupStart:
-      return Serialize(WireMoqVarInt(filter.type()));
-    case MoqtFilterType::kLargestObject:
-      return Serialize(WireMoqVarInt(filter.type()));
-    case MoqtFilterType::kAbsoluteStart:
-      return Serialize(
-          WireMoqVarInt(filter.type()),
-          WireKeyVarIntPair(filter.start().group, filter.start().object));
-    case MoqtFilterType::kAbsoluteRange:
-      QUICHE_DCHECK_LE(filter.start().group, filter.end_group());
-      return Serialize(
-          WireMoqVarInt(filter.type()),
-          WireKeyVarIntPair(filter.start().group, filter.start().object),
-          WireMoqVarInt(filter.end_group() - filter.start().group));
-  }
-}
-
-quiche::QuicheBuffer SerializeLocation(const Location& location) {
-  return Serialize(WireKeyVarIntPair(location.group, location.object));
-}
-
 }  // namespace
 
 KeyValuePairList SetupOptions::ToKeyValuePairList() const {
@@ -305,64 +551,6 @@
   return out;
 }
 
-uint64_t TimeDeltaToMilliseconds(const quic::QuicTimeDelta& time_delta) {
-  if (time_delta == quic::QuicTimeDelta::Infinite()) {
-    return 0ULL;
-  }
-  return std::max(time_delta.ToMilliseconds(), int64_t{1});
-}
-
-KeyValuePairList MessageParameters::ToKeyValuePairList() const {
-  KeyValuePairList list;
-  if (object_delivery_timeout.has_value()) {
-    list.insert(static_cast<uint64_t>(MessageParameter::kObjectDeliveryTimeout),
-                TimeDeltaToMilliseconds(*object_delivery_timeout));
-  }
-  for (const AuthToken& token : authorization_tokens) {
-    list.insert(static_cast<uint64_t>(MessageParameter::kAuthorizationToken),
-                SerializeAuthToken(token).AsStringView());
-  }
-  if (subgroup_delivery_timeout.has_value()) {
-    list.insert(
-        static_cast<uint64_t>(MessageParameter::kSubgroupDeliveryTimeout),
-        TimeDeltaToMilliseconds(*subgroup_delivery_timeout));
-  }
-  if (expires.has_value()) {
-    list.insert(static_cast<uint64_t>(MessageParameter::kExpires),
-                TimeDeltaToMilliseconds(*expires));
-  }
-  if (largest_object.has_value()) {
-    list.insert(static_cast<uint64_t>(MessageParameter::kLargestObject),
-                SerializeLocation(*largest_object).AsStringView());
-  }
-  if (forward_has_value()) {
-    list.insert(static_cast<uint64_t>(MessageParameter::kForward),
-                forward() ? 1ULL : 0ULL);
-  }
-  if (subscriber_priority.has_value()) {
-    list.insert(static_cast<uint64_t>(MessageParameter::kSubscriberPriority),
-                *subscriber_priority);
-  }
-  if (subscription_filter.has_value()) {
-    list.insert(
-        static_cast<uint64_t>(MessageParameter::kSubscriptionFilter),
-        SerializeSubscriptionFilter(*subscription_filter).AsStringView());
-  }
-  if (group_order.has_value()) {
-    list.insert(static_cast<uint64_t>(MessageParameter::kGroupOrder),
-                static_cast<uint64_t>(*group_order));
-  }
-  if (new_group_request.has_value()) {
-    list.insert(static_cast<uint64_t>(MessageParameter::kNewGroupRequest),
-                *new_group_request);
-  }
-  if (oack_window_size.has_value()) {
-    list.insert(static_cast<uint64_t>(MessageParameter::kOackWindowSize),
-                static_cast<uint64_t>(oack_window_size->ToMicroseconds()));
-  }
-  return list;
-}
-
 quiche::QuicheBuffer MoqtFramer::SerializeObjectHeader(
     const MoqtObject& message, MoqtDataStreamType message_type,
     std::optional<PublishedObjectMetadata>& previous_object_in_stream) {
@@ -511,18 +699,17 @@
 
 quiche::QuicheBuffer MoqtFramer::SerializeRequestOk(
     const MoqtRequestOk& message) {
-  return SerializeControlMessage(
-      MoqtMessageType::kRequestOk,
-      WireKeyValuePairList(message.parameters.ToKeyValuePairList()),
-      WireKeyValuePairList(message.properties, false));
+  return SerializeControlMessage(MoqtMessageType::kRequestOk,
+                                 WireMessageParameters(message.parameters),
+                                 WireKeyValuePairList(message.properties));
 }
 
 quiche::QuicheBuffer MoqtFramer::SerializeSubscribe(
     const MoqtSubscribe& message, MoqtMessageType message_type) {
-  return SerializeControlMessage(
-      message_type, WireMoqVarInt(message.request_id),
-      WireFullTrackName(message.full_track_name),
-      WireKeyValuePairList(message.parameters.ToKeyValuePairList()));
+  return SerializeControlMessage(message_type,
+                                 WireMoqVarInt(message.request_id),
+                                 WireFullTrackName(message.full_track_name),
+                                 WireMessageParameters(message.parameters));
 }
 
 quiche::QuicheBuffer MoqtFramer::SerializeSubscribeOk(
@@ -532,10 +719,10 @@
         << "Subscribe OK properties are ill-formed";
     return quiche::QuicheBuffer();
   }
-  return SerializeControlMessage(
-      message_type, WireMoqVarInt(message.track_alias),
-      WireKeyValuePairList(message.parameters.ToKeyValuePairList()),
-      WireKeyValuePairList(message.properties, false));
+  return SerializeControlMessage(message_type,
+                                 WireMoqVarInt(message.track_alias),
+                                 WireMessageParameters(message.parameters),
+                                 WireKeyValuePairList(message.properties));
 }
 
 quiche::QuicheBuffer MoqtFramer::SerializeRequestError(
@@ -558,18 +745,18 @@
 
 quiche::QuicheBuffer MoqtFramer::SerializeRequestUpdate(
     const MoqtRequestUpdate& message) {
-  return SerializeControlMessage(
-      MoqtMessageType::kRequestUpdate, WireMoqVarInt(message.request_id),
-      WireMoqVarInt(message.existing_request_id),
-      WireKeyValuePairList(message.parameters.ToKeyValuePairList()));
+  return SerializeControlMessage(MoqtMessageType::kRequestUpdate,
+                                 WireMoqVarInt(message.request_id),
+                                 WireMoqVarInt(message.existing_request_id),
+                                 WireMessageParameters(message.parameters));
 }
 
 quiche::QuicheBuffer MoqtFramer::SerializePublishNamespace(
     const MoqtPublishNamespace& message) {
-  return SerializeControlMessage(
-      MoqtMessageType::kPublishNamespace, WireMoqVarInt(message.request_id),
-      WireTrackNamespace(message.track_namespace),
-      WireKeyValuePairList(message.parameters.ToKeyValuePairList()));
+  return SerializeControlMessage(MoqtMessageType::kPublishNamespace,
+                                 WireMoqVarInt(message.request_id),
+                                 WireTrackNamespace(message.track_namespace),
+                                 WireMessageParameters(message.parameters));
 }
 
 quiche::QuicheBuffer MoqtFramer::SerializeNamespace(
@@ -621,7 +808,7 @@
   return SerializeControlMessage(
       MoqtMessageType::kSubscribeNamespace, WireMoqVarInt(message.request_id),
       WireTrackNamespace(message.track_namespace_prefix),
-      WireKeyValuePairList(message.parameters.ToKeyValuePairList()));
+      WireMessageParameters(message.parameters));
 }
 
 quiche::QuicheBuffer MoqtFramer::SerializeSubscribeTracks(
@@ -629,7 +816,7 @@
   return SerializeControlMessage(
       MoqtMessageType::kSubscribeTracks, WireMoqVarInt(message.request_id),
       WireTrackNamespace(message.track_namespace_prefix),
-      WireKeyValuePairList(message.parameters.ToKeyValuePairList()));
+      WireMessageParameters(message.parameters));
 }
 
 quiche::QuicheBuffer MoqtFramer::SerializeFetch(const MoqtFetch& message) {
@@ -654,7 +841,7 @@
         WireMoqVarInt(standalone_fetch.end_location.object == kMaxObjectId
                           ? 0
                           : standalone_fetch.end_location.object + 1),
-        WireKeyValuePairList(message.parameters.ToKeyValuePairList()));
+        WireMessageParameters(message.parameters));
   }
   uint64_t request_id, joining_start;
   if (std::holds_alternative<JoiningFetchRelative>(message.fetch)) {
@@ -671,8 +858,7 @@
   return SerializeControlMessage(
       MoqtMessageType::kFetch, WireMoqVarInt(message.request_id),
       WireMoqVarInt(message.fetch.index() + 1), WireMoqVarInt(request_id),
-      WireMoqVarInt(joining_start),
-      WireKeyValuePairList(message.parameters.ToKeyValuePairList()));
+      WireMoqVarInt(joining_start), WireMessageParameters(message.parameters));
 }
 
 quiche::QuicheBuffer MoqtFramer::SerializeFetchOk(const MoqtFetchOk& message) {
@@ -682,18 +868,18 @@
       WireMoqVarInt(message.end_location.object == kMaxObjectId
                         ? 0
                         : (message.end_location.object + 1)),
-      WireKeyValuePairList(message.parameters.ToKeyValuePairList()),
-      WireKeyValuePairList(message.properties, false));
+      WireMessageParameters(message.parameters),
+      WireKeyValuePairList(message.properties));
 }
 
 
 quiche::QuicheBuffer MoqtFramer::SerializePublish(const MoqtPublish& message) {
-  return SerializeControlMessage(
-      MoqtMessageType::kPublish, WireMoqVarInt(message.request_id),
-      WireFullTrackName(message.full_track_name),
-      WireMoqVarInt(message.track_alias),
-      WireKeyValuePairList(message.parameters.ToKeyValuePairList()),
-      WireKeyValuePairList(message.properties, false));
+  return SerializeControlMessage(MoqtMessageType::kPublish,
+                                 WireMoqVarInt(message.request_id),
+                                 WireFullTrackName(message.full_track_name),
+                                 WireMoqVarInt(message.track_alias),
+                                 WireMessageParameters(message.parameters),
+                                 WireKeyValuePairList(message.properties));
 }
 
 quiche::QuicheBuffer MoqtFramer::SerializeObjectAck(
diff --git a/quiche/quic/moqt/moqt_framer_test.cc b/quiche/quic/moqt/moqt_framer_test.cc
index 941c286..f4932a4 100644
--- a/quiche/quic/moqt/moqt_framer_test.cc
+++ b/quiche/quic/moqt/moqt_framer_test.cc
@@ -43,15 +43,25 @@
 std::vector<MoqtFramerTestParams> GetMoqtFramerTestParams() {
   std::vector<MoqtFramerTestParams> params;
   std::vector<MoqtMessageType> message_types = {
-      MoqtMessageType::kRequestOk,       MoqtMessageType::kRequestError,
-      MoqtMessageType::kSubscribe,       MoqtMessageType::kSubscribeOk,
-      MoqtMessageType::kPublishDone,     MoqtMessageType::kPublishNamespace,
-      MoqtMessageType::kNamespace,       MoqtMessageType::kNamespaceDone,
-      MoqtMessageType::kPublishSkipped,  MoqtMessageType::kTrackStatus,
-      MoqtMessageType::kGoAway,          MoqtMessageType::kSubscribeNamespace,
-      MoqtMessageType::kSubscribeTracks, MoqtMessageType::kFetch,
-      MoqtMessageType::kFetchOk,         MoqtMessageType::kPublish,
-      MoqtMessageType::kObjectAck,       MoqtMessageType::kSetup,
+      MoqtMessageType::kRequestOk,
+      MoqtMessageType::kRequestError,
+      MoqtMessageType::kSubscribe,
+      MoqtMessageType::kSubscribeOk,
+      MoqtMessageType::kRequestUpdate,
+      MoqtMessageType::kPublishDone,
+      MoqtMessageType::kPublishNamespace,
+      MoqtMessageType::kNamespace,
+      MoqtMessageType::kNamespaceDone,
+      MoqtMessageType::kPublishSkipped,
+      MoqtMessageType::kTrackStatus,
+      MoqtMessageType::kGoAway,
+      MoqtMessageType::kSubscribeNamespace,
+      MoqtMessageType::kSubscribeTracks,
+      MoqtMessageType::kFetch,
+      MoqtMessageType::kFetchOk,
+      MoqtMessageType::kPublish,
+      MoqtMessageType::kObjectAck,
+      MoqtMessageType::kSetup,
   };
   for (const MoqtMessageType message_type : message_types) {
     if (message_type == MoqtMessageType::kSetup) {
@@ -145,6 +155,10 @@
         auto data = std::get<MoqtSubscribeOk>(structured_data);
         return framer_.SerializeSubscribeOk(data);
       }
+      case MoqtMessageType::kRequestUpdate: {
+        auto data = std::get<MoqtRequestUpdate>(structured_data);
+        return framer_.SerializeRequestUpdate(data);
+      }
       case MoqtMessageType::kPublishDone: {
         auto data = std::get<MoqtPublishDone>(structured_data);
         return framer_.SerializePublishDone(data);
@@ -474,38 +488,35 @@
   params.subgroup_delivery_timeout = quic::QuicTimeDelta::FromMicroseconds(500);
   params.expires = quic::QuicTimeDelta::Infinite();
 
-  KeyValuePairList list = params.ToKeyValuePairList();
-  std::optional<uint64_t> object_timeout;
-  std::optional<uint64_t> subgroup_timeout;
-  std::optional<uint64_t> expires;
-  list.ForEach(
-      [&](uint64_t key, std::variant<uint64_t, absl::string_view> value) {
-        if (key ==
-            static_cast<uint64_t>(MessageParameter::kObjectDeliveryTimeout)) {
-          object_timeout = std::get<uint64_t>(value);
-        } else if (key == static_cast<uint64_t>(
-                              MessageParameter::kSubgroupDeliveryTimeout)) {
-          subgroup_timeout = std::get<uint64_t>(value);
-        } else if (key == static_cast<uint64_t>(MessageParameter::kExpires)) {
-          expires = std::get<uint64_t>(value);
-        }
-        return true;
-      });
-  EXPECT_EQ(object_timeout, 0ULL);
-  EXPECT_EQ(subgroup_timeout, 1ULL);
-  EXPECT_EQ(expires, 0ULL);
+  quiche::QuicheBuffer buffer =
+      framer_.SerializeRequestOk(MoqtRequestOk{params, TrackProperties()});
+  const uint8_t expected_infinite_expires[] = {
+      0x07,        // MoqtMessageType::kRequestOk
+      0x00, 0x07,  // Payload length = 7
+      0x03,        // 3 parameters
+      0x02, 0x00,  // kObjectDeliveryTimeout (0x02): Infinite -> 0
+      0x04, 0x01,  // kSubgroupDeliveryTimeout (+0x04 = 0x06): 500us -> 1ms
+      0x02, 0x00,  // kExpires (+0x02 = 0x08): Infinite -> 0
+  };
+  quiche::test::CompareCharArraysWithHexError(
+      "frame encoding", buffer.data(), buffer.size(),
+      reinterpret_cast<const char*>(expected_infinite_expires),
+      sizeof(expected_infinite_expires));
 
   params.expires = quic::QuicTimeDelta::FromMicroseconds(200);
-  list = params.ToKeyValuePairList();
-  expires.reset();
-  list.ForEach(
-      [&](uint64_t key, std::variant<uint64_t, absl::string_view> value) {
-        if (key == static_cast<uint64_t>(MessageParameter::kExpires)) {
-          expires = std::get<uint64_t>(value);
-        }
-        return true;
-      });
-  EXPECT_EQ(expires, 1ULL);
+  buffer = framer_.SerializeRequestOk(MoqtRequestOk{params, TrackProperties()});
+  const uint8_t expected_clamped_expires[] = {
+      0x07,        // MoqtMessageType::kRequestOk
+      0x00, 0x07,  // Payload length = 7
+      0x03,        // 3 parameters
+      0x02, 0x00,  // kObjectDeliveryTimeout (0x02): Infinite -> 0
+      0x04, 0x01,  // kSubgroupDeliveryTimeout (+0x04 = 0x06): 500us -> 1ms
+      0x02, 0x01,  // kExpires (+0x02 = 0x08): 200us -> 1ms
+  };
+  quiche::test::CompareCharArraysWithHexError(
+      "frame encoding", buffer.data(), buffer.size(),
+      reinterpret_cast<const char*>(expected_clamped_expires),
+      sizeof(expected_clamped_expires));
 }
 
 }  // namespace moqt::test
diff --git a/quiche/quic/moqt/moqt_key_value_pair.h b/quiche/quic/moqt/moqt_key_value_pair.h
index 10d7631..8a2eb5b 100644
--- a/quiche/quic/moqt/moqt_key_value_pair.h
+++ b/quiche/quic/moqt/moqt_key_value_pair.h
@@ -25,7 +25,7 @@
 
 namespace moqt {
 
-// Encodes a list of key-value pairs common to both parameters and properties.
+// Encodes a list of key-value pairs common to both Options and Properties.
 // If the key is odd, it is a length-prefixed string (which may encode further
 // item-specific structure). If the key is even, it is a varint.
 // This class does not interpret the semantic meaning of the keys and values.
@@ -208,7 +208,6 @@
     quic::QuicTimeDelta::Infinite();
 constexpr quic::QuicTimeDelta kDefaultExpires = quic::QuicTimeDelta::Infinite();
 constexpr bool kDefaultForward = true;
-// TODO(martinduke): Refactor this to be more like TrackProperties.
 struct MessageParameters {
   MessageParameters() = default;
   MessageParameters(const MessageParameters&) = default;
@@ -241,13 +240,6 @@
   std::optional<quic::QuicTimeDelta> oack_window_size;
   bool operator==(const MessageParameters& other) const = default;
 
-  // Defined in moqt_framer.cc.
-  KeyValuePairList ToKeyValuePairList() const;
-  // Defined in moqt_parser.cc.
-  // If the class is not initialized with the default constructor, it is likely
-  // to return an error if a non-default field duplicates what is in |list|.
-  absl::Status FromKeyValuePairList(const KeyValuePairList& list);
-
  private:
   // "if (forward)" is bug-prone because it returns forward_.has_value(). Make
   // it private and use public accessors instead.
diff --git a/quiche/quic/moqt/moqt_key_value_pair_test.cc b/quiche/quic/moqt/moqt_key_value_pair_test.cc
index c04cb22..d13d4c6 100644
--- a/quiche/quic/moqt/moqt_key_value_pair_test.cc
+++ b/quiche/quic/moqt/moqt_key_value_pair_test.cc
@@ -17,8 +17,6 @@
 
 namespace moqt::test {
 
-using ::quiche::test::StatusIs;
-
 class LocationTest : public quic::test::QuicTest {};
 
 TEST_F(LocationTest, LocationTests) {
@@ -134,103 +132,6 @@
 
 class MessageParametersTest : public quic::test::QuicTest {};
 
-TEST_F(MessageParametersTest, FromKeyValuePairList) {
-  KeyValuePairList list;
-  list.insert(static_cast<uint64_t>(MessageParameter::kObjectDeliveryTimeout),
-              1ULL);
-  list.insert(static_cast<uint64_t>(MessageParameter::kForward), 0ULL);
-  list.insert(static_cast<uint64_t>(MessageParameter::kOackWindowSize),
-              12345678ULL);
-  MessageParameters parameters;
-  QUICHE_EXPECT_OK(parameters.FromKeyValuePairList(list));
-  EXPECT_EQ(parameters.object_delivery_timeout,
-            quic::QuicTimeDelta::FromMilliseconds(1));
-  EXPECT_FALSE(parameters.forward());
-  EXPECT_EQ(parameters.oack_window_size,
-            quic::QuicTimeDelta::FromMicroseconds(12345678));
-}
-
-TEST_F(MessageParametersTest, IllegalKeyValuePairs) {
-  KeyValuePairList list;
-  MessageParameters parameters;
-  list.insert(static_cast<uint64_t>(MessageParameter::kForward), 2ULL);
-  EXPECT_THAT(parameters.FromKeyValuePairList(list),
-              StatusIs(absl::StatusCode::kInvalidArgument));
-  list.clear();
-  list.insert(static_cast<uint64_t>(MessageParameter::kSubscriberPriority),
-              256ULL);
-  EXPECT_THAT(parameters.FromKeyValuePairList(list),
-              StatusIs(absl::StatusCode::kInvalidArgument));
-  list.clear();
-  list.insert(static_cast<uint64_t>(MessageParameter::kGroupOrder), 0ULL);
-  EXPECT_THAT(parameters.FromKeyValuePairList(list),
-              StatusIs(absl::StatusCode::kInvalidArgument));
-  list.clear();
-  list.insert(static_cast<uint64_t>(MessageParameter::kGroupOrder), 3ULL);
-  EXPECT_THAT(parameters.FromKeyValuePairList(list),
-              StatusIs(absl::StatusCode::kInvalidArgument));
-  // Unknown MessageParameter.
-  list.clear();
-  list.insert(0x12345678, 12345678ULL);
-  EXPECT_THAT(parameters.FromKeyValuePairList(list),
-              StatusIs(absl::StatusCode::kInvalidArgument));
-}
-
-TEST_F(MessageParametersTest, DuplicateParameters) {
-  for (MessageParameter param :
-       {MessageParameter::kObjectDeliveryTimeout,
-        MessageParameter::kSubgroupDeliveryTimeout,
-        // Auth token can be repeated.
-        MessageParameter::kExpires, MessageParameter::kLargestObject,
-        MessageParameter::kForward, MessageParameter::kSubscriberPriority,
-        MessageParameter::kSubscriptionFilter, MessageParameter::kGroupOrder,
-        MessageParameter::kNewGroupRequest,
-        MessageParameter::kOackWindowSize}) {
-    KeyValuePairList list;
-    MessageParameters parameters;
-    switch (param) {
-      case MessageParameter::kLargestObject: {
-        char largest_object[] = {0x00, 0x01};
-        list.insert(static_cast<uint64_t>(param),
-                    absl::string_view(largest_object, 2));
-        largest_object[1] = 0x02;
-        list.insert(static_cast<uint64_t>(param),
-                    absl::string_view(largest_object, 2));
-        break;
-      }
-      case MessageParameter::kForward: {
-        list.insert(static_cast<uint64_t>(param), 0ULL);
-        list.insert(static_cast<uint64_t>(param), 1ULL);
-        break;
-      }
-      case MessageParameter::kSubscriberPriority: {
-        list.insert(static_cast<uint64_t>(param), 127ULL);
-        list.insert(static_cast<uint64_t>(param), 128ULL);
-        break;
-      }
-      case MessageParameter::kSubscriptionFilter: {
-        char filter[] = {0x01};  // kNextGroupStart
-        list.insert(static_cast<uint64_t>(param), absl::string_view(filter, 1));
-        filter[0] = 0x02;  // kLargestObject
-        list.insert(static_cast<uint64_t>(param), absl::string_view(filter, 1));
-        break;
-      }
-      case MessageParameter::kGroupOrder: {
-        list.insert(static_cast<uint64_t>(param), 1ULL);
-        list.insert(static_cast<uint64_t>(param), 2ULL);
-        break;
-      }
-      default: {
-        list.insert(static_cast<uint64_t>(param), 1024ULL);
-        list.insert(static_cast<uint64_t>(param), 2048ULL);
-        break;
-      }
-    }
-    EXPECT_THAT(parameters.FromKeyValuePairList(list),
-                StatusIs(absl::StatusCode::kInvalidArgument));
-  }
-}
-
 TEST_F(MessageParametersTest, Update) {
   MessageParameters p1;
   p1.object_delivery_timeout = quic::QuicTimeDelta::FromMilliseconds(10);
diff --git a/quiche/quic/moqt/moqt_parser.cc b/quiche/quic/moqt/moqt_parser.cc
index 7a17db0..2f6ea27 100644
--- a/quiche/quic/moqt/moqt_parser.cc
+++ b/quiche/quic/moqt/moqt_parser.cc
@@ -9,6 +9,7 @@
 #include <cstddef>
 #include <cstdint>
 #include <cstring>
+#include <limits>
 #include <optional>
 #include <string>
 #include <utility>
@@ -112,74 +113,7 @@
   return result;
 }
 
-// Reads from |reader| to list. Returns false if there is a read error.
-absl::Status ParseKeyValuePairList(quic::QuicDataReader& reader,
-                                   KeyValuePairList& list) {
-  list.clear();
-  uint64_t num_params;
-  if (!reader.ReadMoqVarInt(&num_params)) {
-    return absl::InvalidArgumentError(
-        "Unable to parse key-value pair list element count");
-  }
-  uint64_t type = 0;
-  for (uint64_t i = 0; i < num_params; ++i) {
-    uint64_t type_diff;
-    if (!reader.ReadMoqVarInt(&type_diff)) {
-      return absl::InvalidArgumentError(
-          "Unable to parse the key in a key-value pair");
-    }
-    QUICHE_RETURN_IF_ERROR(SafeIncrementByWithStatus(type, type_diff));
-    if (type % 2 == 1) {
-      absl::string_view bytes;
-      if (!reader.ReadStringPieceMoqVarInt(&bytes)) {
-        return absl::InvalidArgumentError(
-            "Unable to read the string value in a key-value pair");
-      }
-      list.insert(type, bytes);
-      continue;
-    }
-    uint64_t value;
-    if (!reader.ReadMoqVarInt(&value)) {
-      return absl::InvalidArgumentError(
-          "Unable to read the integer value in a key-value pair");
-    }
-    list.insert(type, value);
-  }
-  return absl::OkStatus();
-}
-
-absl::Status ParseKeyValuePairListWithNoPrefix(quic::QuicDataReader& reader,
-                                               KeyValuePairList& list) {
-  list.clear();
-  uint64_t type = 0;
-  while (reader.BytesRemaining() > 0) {
-    uint64_t type_diff;
-    if (!reader.ReadMoqVarInt(&type_diff)) {
-      return absl::InvalidArgumentError(
-          "Unable to parse the key in a key-value pair");
-    }
-    QUICHE_RETURN_IF_ERROR(SafeIncrementByWithStatus(type, type_diff));
-    if (type % 2 == 1) {
-      absl::string_view bytes;
-      if (!reader.ReadStringPieceMoqVarInt(&bytes)) {
-        return absl::InvalidArgumentError(
-            "Unable to read the string value in a key-value pair");
-      }
-      list.insert(type, bytes);
-      continue;
-    }
-    uint64_t value;
-    if (!reader.ReadMoqVarInt(&value)) {
-      return absl::InvalidArgumentError(
-          "Unable to read the integer value in a key-value pair");
-    }
-    list.insert(type, value);
-  }
-  return absl::OkStatus();
-}
-
-bool ParseAuthTokenOption(absl::string_view field,
-                          std::vector<AuthToken>& out) {
+bool ParseAuthToken(absl::string_view field, std::vector<AuthToken>& out) {
   quic::QuicDataReader reader(field);
   AuthTokenAliasType alias_type;
   uint64_t alias;
@@ -226,12 +160,6 @@
   return true;
 }
 
-bool ParseLocation(absl::string_view field, Location& out) {
-  quic::QuicDataReader reader(field);
-  return reader.ReadMoqVarInt(&out.group) &&
-         reader.ReadMoqVarInt(&out.object) && reader.IsDoneReading();
-}
-
 absl::Status ParseSubscriptionFilter(absl::string_view field,
                                      std::optional<SubscriptionFilter>& out) {
   quic::QuicDataReader reader(field);
@@ -265,6 +193,194 @@
   return absl::OkStatus();
 }
 
+absl::StatusOr<MessageParameters> ParseMessageParameters(
+    quic::QuicDataReader& reader) {
+  MessageParameters params;
+  uint64_t num_params;
+  if (!reader.ReadMoqVarInt(&num_params)) {
+    return absl::InvalidArgumentError(
+        "Unable to parse key-value pair list element count");
+  }
+  uint64_t type = 0;
+  uint8_t value8;
+  uint64_t value64;
+  absl::string_view value_str;
+  for (uint64_t i = 0; i < num_params; ++i) {
+    uint64_t type_diff;
+    if (!reader.ReadMoqVarInt(&type_diff)) {
+      return absl::InvalidArgumentError("Unable to parse the parameter type");
+    }
+    QUICHE_RETURN_IF_ERROR(SafeIncrementByWithStatus(type, type_diff));
+    switch (static_cast<MessageParameter>(type)) {
+      case MessageParameter::kObjectDeliveryTimeout:
+        if (params.object_delivery_timeout.has_value()) {
+          return absl::InvalidArgumentError("Duplicate Message Parameter");
+        }
+        if (!reader.ReadMoqVarInt(&value64)) {
+          return absl::InvalidArgumentError("Parameter parsing error");
+        }
+        if (value64 == 0) {
+          params.object_delivery_timeout = quic::QuicTimeDelta::Infinite();
+          break;
+        }
+        params.object_delivery_timeout =
+            quic::QuicTimeDelta::TryFromMilliseconds(value64).value_or(
+                quic::QuicTimeDelta::Infinite());
+        break;
+      case MessageParameter::kAuthorizationToken:
+        if (!reader.ReadStringPieceMoqVarInt(&value_str)) {
+          return absl::InvalidArgumentError("Parameter parsing error");
+        }
+        if (!ParseAuthToken(value_str, params.authorization_tokens)) {
+          return absl::InvalidArgumentError("Malformed auth token parameter");
+        }
+        break;
+      case MessageParameter::kSubgroupDeliveryTimeout:
+        if (params.subgroup_delivery_timeout.has_value()) {
+          return absl::InvalidArgumentError("Duplicate Message Parameter");
+        }
+        if (!reader.ReadMoqVarInt(&value64)) {
+          return absl::InvalidArgumentError("Parameter parsing error");
+        }
+        if (value64 == 0) {
+          params.subgroup_delivery_timeout = quic::QuicTimeDelta::Infinite();
+          break;
+        }
+        params.subgroup_delivery_timeout =
+            quic::QuicTimeDelta::TryFromMilliseconds(value64).value_or(
+                quic::QuicTimeDelta::Infinite());
+        break;
+      case MessageParameter::kExpires:
+        if (params.expires.has_value()) {
+          return absl::InvalidArgumentError("Duplicate Message Parameter");
+        }
+        if (!reader.ReadMoqVarInt(&value64)) {
+          return absl::InvalidArgumentError("Parameter parsing error");
+        }
+        params.expires =
+            quic::QuicTimeDelta::TryFromMilliseconds(value64).value_or(
+                quic::QuicTimeDelta::Infinite());
+        if (params.expires->IsZero()) {
+          params.expires = quic::QuicTimeDelta::Infinite();
+        }
+        break;
+      case MessageParameter::kLargestObject: {
+        uint64_t group, object;
+        if (params.largest_object.has_value()) {
+          return absl::InvalidArgumentError("Duplicate Message Parameter");
+        }
+        if (!reader.ReadMoqVarInt(&group) || !reader.ReadMoqVarInt(&object)) {
+          return absl::InvalidArgumentError("Parameter parsing error");
+        }
+        params.largest_object = Location(group, object);
+        break;
+      }
+      case MessageParameter::kForward:
+        if (params.forward_has_value()) {
+          return absl::InvalidArgumentError("Duplicate Message Parameter");
+        }
+        if (!reader.ReadUInt8(&value8)) {
+          return absl::InvalidArgumentError("Parameter parsing error");
+        }
+        if (value8 > 1) {
+          return absl::InvalidArgumentError("FORWARD must be 0 or 1");
+        }
+        params.set_forward(value8 != 0);
+        break;
+      case MessageParameter::kSubscriberPriority:
+        if (params.subscriber_priority.has_value()) {
+          return absl::InvalidArgumentError("Duplicate Message Parameter");
+        }
+        if (!reader.ReadUInt8(&value8)) {
+          return absl::InvalidArgumentError("Parameter parsing error");
+        }
+        params.subscriber_priority = static_cast<MoqtPriority>(value8);
+        break;
+      case MessageParameter::kSubscriptionFilter:
+        if (params.subscription_filter.has_value()) {
+          return absl::InvalidArgumentError("Duplicate Message Parameter");
+        }
+        if (!reader.ReadStringPieceMoqVarInt(&value_str)) {
+          return absl::InvalidArgumentError("Parameter parsing error");
+        }
+        QUICHE_RETURN_IF_ERROR(
+            ParseSubscriptionFilter(value_str, params.subscription_filter));
+        break;
+      case MessageParameter::kGroupOrder:
+        if (params.group_order.has_value()) {
+          return absl::InvalidArgumentError("Duplicate Message Parameter");
+        }
+        if (!reader.ReadUInt8(&value8)) {
+          return absl::InvalidArgumentError("Parameter parsing error");
+        }
+        if (value8 > kMaxMoqtDeliveryOrder || value8 < kMinMoqtDeliveryOrder) {
+          return absl::InvalidArgumentError(
+              "GROUP_ORDER is outside the valid range");
+        }
+        params.group_order = static_cast<MoqtDeliveryOrder>(value8);
+        break;
+      case MessageParameter::kNewGroupRequest:
+        if (params.new_group_request.has_value()) {
+          return absl::InvalidArgumentError("Duplicate Message Parameter");
+        }
+        if (!reader.ReadMoqVarInt(&value64)) {
+          return absl::InvalidArgumentError("Parameter parsing error");
+        }
+        params.new_group_request = value64;
+        break;
+      case MessageParameter::kOackWindowSize:
+        if (params.oack_window_size.has_value()) {
+          return absl::InvalidArgumentError("Duplicate Message Parameter");
+        }
+        if (!reader.ReadMoqVarInt(&value64)) {
+          return absl::InvalidArgumentError("Parameter parsing error");
+        }
+        // Since QuicTimeDelta stores an int64_t value in microseconds, it is
+        // possible for the value to be out of range.
+        params.oack_window_size =
+            value64 > std::numeric_limits<int64_t>::max()
+                ? quic::QuicTimeDelta::Infinite()
+                : quic::QuicTimeDelta::FromMicroseconds(value64);
+        break;
+      default:
+        // Unknown MessageParameters not allowed!
+        return absl::InvalidArgumentError(
+            absl::StrCat("Unknown message parameter 0x", absl::Hex(type)));
+    }
+  }
+  return params;
+}
+
+absl::Status ParseKeyValuePairList(quic::QuicDataReader& reader,
+                                   KeyValuePairList& list) {
+  list.clear();
+  uint64_t type = 0;
+  while (reader.BytesRemaining() > 0) {
+    uint64_t type_diff;
+    if (!reader.ReadMoqVarInt(&type_diff)) {
+      return absl::InvalidArgumentError(
+          "Unable to parse the key in a key-value pair");
+    }
+    QUICHE_RETURN_IF_ERROR(SafeIncrementByWithStatus(type, type_diff));
+    if (type % 2 == 1) {
+      absl::string_view bytes;
+      if (!reader.ReadStringPieceMoqVarInt(&bytes)) {
+        return absl::InvalidArgumentError(
+            "Unable to read the string value in a key-value pair");
+      }
+      list.insert(type, bytes);
+      continue;
+    }
+    uint64_t value;
+    if (!reader.ReadMoqVarInt(&value)) {
+      return absl::InvalidArgumentError(
+          "Unable to read the integer value in a key-value pair");
+    }
+    list.insert(type, value);
+  }
+  return absl::OkStatus();
+}
+
 }  // namespace
 
 absl::Status SetupOptions::FromKeyValuePairList(const KeyValuePairList& list) {
@@ -296,8 +412,8 @@
             path = std::get<absl::string_view>(value);
             break;
           case SetupOption::kAuthorizationToken:
-            if (!ParseAuthTokenOption(std::get<absl::string_view>(value),
-                                      authorization_tokens)) {
+            if (!ParseAuthToken(std::get<absl::string_view>(value),
+                                authorization_tokens)) {
               status = KeyValueFormatError("Malformed auth token option");
               return false;
             }
@@ -345,154 +461,6 @@
   return status;
 }
 
-absl::Status MessageParameters::FromKeyValuePairList(
-    const KeyValuePairList& list) {
-  absl::Status status = absl::OkStatus();
-  uint64_t last_key;
-  bool result = list.ForEach([&](uint64_t key,
-                                 std::variant<uint64_t, absl::string_view>
-                                     value) {
-    last_key = key;
-    switch (static_cast<MessageParameter>(key)) {
-      case MessageParameter::kObjectDeliveryTimeout:
-        if (object_delivery_timeout.has_value()) {
-          status = absl::InvalidArgumentError("Duplicate Message Parameter");
-          return false;
-        }
-        if (std::get<uint64_t>(value) == 0) {
-          object_delivery_timeout = quic::QuicTimeDelta::Infinite();
-          break;
-        }
-        object_delivery_timeout =
-            quic::QuicTimeDelta::TryFromMilliseconds(std::get<uint64_t>(value))
-                .value_or(quic::QuicTimeDelta::Infinite());
-        break;
-      case MessageParameter::kAuthorizationToken:
-        if (!ParseAuthTokenOption(std::get<absl::string_view>(value),
-                                  authorization_tokens)) {
-          status = KeyValueFormatError("Malformed auth token parameter");
-          return false;
-        }
-        break;
-      case MessageParameter::kSubgroupDeliveryTimeout:
-        if (subgroup_delivery_timeout.has_value()) {
-          status = absl::InvalidArgumentError("Duplicate Message Parameter");
-          return false;
-        }
-        if (std::get<uint64_t>(value) == 0) {
-          subgroup_delivery_timeout = quic::QuicTimeDelta::Infinite();
-          break;
-        }
-        subgroup_delivery_timeout =
-            quic::QuicTimeDelta::TryFromMilliseconds(std::get<uint64_t>(value))
-                .value_or(quic::QuicTimeDelta::Infinite());
-        break;
-      case MessageParameter::kExpires:
-        if (expires.has_value()) {
-          status = absl::InvalidArgumentError("Duplicate Message Parameter");
-          return false;
-        }
-        expires =
-            quic::QuicTimeDelta::TryFromMilliseconds(std::get<uint64_t>(value))
-                .value_or(quic::QuicTimeDelta::Infinite());
-        if (expires->IsZero()) {
-          expires = quic::QuicTimeDelta::Infinite();
-        }
-        break;
-      case MessageParameter::kLargestObject:
-        if (largest_object.has_value()) {
-          status = absl::InvalidArgumentError("Duplicate Message Parameter");
-          return false;
-        }
-        largest_object = Location();
-        if (!ParseLocation(std::get<absl::string_view>(value),
-                           *largest_object)) {
-          status = KeyValueFormatError(
-              "Failed to parse location of the largest object");
-          return false;
-        }
-        break;
-      case MessageParameter::kForward:
-        if (forward_has_value()) {
-          status = absl::InvalidArgumentError("Duplicate Message Parameter");
-          return false;
-        }
-        if (std::get<uint64_t>(value) > 1) {
-          status = absl::InvalidArgumentError("FORWARD must be 0 or 1");
-          return false;
-        }
-        set_forward(std::get<uint64_t>(value) != 0);
-        break;
-      case MessageParameter::kSubscriberPriority:
-        if (subscriber_priority.has_value()) {
-          status = absl::InvalidArgumentError("Duplicate Message Parameter");
-          return false;
-        }
-        if (std::get<uint64_t>(value) > kMaxPriority) {
-          status =
-              absl::InvalidArgumentError("Subscriber priority exceeds maximum");
-          return false;
-        }
-        subscriber_priority =
-            static_cast<MoqtPriority>(std::get<uint64_t>(value));
-        break;
-      case MessageParameter::kSubscriptionFilter:
-        if (subscription_filter.has_value()) {
-          status = absl::InvalidArgumentError("Duplicate Message Parameter");
-          // TODO(martinduke): Support multiple subscription filters.
-          return false;
-        }
-        status = ParseSubscriptionFilter(std::get<absl::string_view>(value),
-                                         subscription_filter);
-        if (!status.ok()) {
-          return false;
-        }
-        break;
-      case MessageParameter::kGroupOrder:
-        if (group_order.has_value()) {
-          status = absl::InvalidArgumentError("Duplicate Message Parameter");
-          return false;
-        }
-        if (std::get<uint64_t>(value) > kMaxMoqtDeliveryOrder ||
-            std::get<uint64_t>(value) < kMinMoqtDeliveryOrder) {
-          status = absl::InvalidArgumentError(
-              "GROUP_ORDER is outside the valid range");
-          return false;
-        }
-        group_order = static_cast<MoqtDeliveryOrder>(std::get<uint64_t>(value));
-        break;
-      case MessageParameter::kNewGroupRequest:
-        if (new_group_request.has_value()) {
-          status = absl::InvalidArgumentError("Duplicate Message Parameter");
-          return false;
-        }
-        new_group_request = std::get<uint64_t>(value);
-        break;
-      case MessageParameter::kOackWindowSize:
-        if (oack_window_size.has_value()) {
-          status = absl::InvalidArgumentError("Duplicate Message Parameter");
-          return false;
-        }
-        oack_window_size =
-            quic::QuicTimeDelta::FromMicroseconds(std::get<uint64_t>(value));
-        break;
-      default:
-        // Unknown MessageParameters not allowed!
-        status = absl::InvalidArgumentError(
-            absl::StrCat("Unknown message parameter 0x",
-                         absl::Hex(static_cast<uint64_t>(key))));
-        return false;
-    }
-    return true;
-  });
-  if (!result && status.ok()) {
-    return absl::InvalidArgumentError(absl::StrCat(
-        "Failed to parse the value for the message parameter key 0x",
-        absl::Hex(static_cast<uint64_t>(last_key))));
-  }
-  return status;
-}
-
 MoqtStreamTypeParser::MoqtStreamTypeParser(
     MoqtStreamTypeParser&& other) noexcept
     : stream_(other.stream_), type_(other.type_), status_(other.status_) {
@@ -666,8 +634,7 @@
     return absl::InvalidArgumentError("Failed to read request ID");
   }
   QUICHE_RETURN_IF_ERROR(ReadFullTrackName(reader, subscribe.full_track_name));
-  QUICHE_RETURN_IF_ERROR(
-      FillAndValidateMessageParameters(reader, subscribe.parameters));
+  QUICHE_ASSIGN_OR_RETURN(subscribe.parameters, ParseMessageParameters(reader));
   QUICHE_RETURN_IF_ERROR(CheckForTrailingData(reader));
   return subscribe;
 }
@@ -679,11 +646,10 @@
   if (!reader.ReadMoqVarInt(&subscribe_ok.track_alias)) {
     return absl::InvalidArgumentError("Failed to read the track alias");
   }
-  KeyValuePairList pairs;
-  QUICHE_RETURN_IF_ERROR(ParseKeyValuePairList(reader, pairs));
-  QUICHE_RETURN_IF_ERROR(subscribe_ok.parameters.FromKeyValuePairList(pairs));
+  QUICHE_ASSIGN_OR_RETURN(subscribe_ok.parameters,
+                          ParseMessageParameters(reader));
   QUICHE_RETURN_IF_ERROR(
-      ParseKeyValuePairListWithNoPrefix(reader, subscribe_ok.properties));
+      ParseKeyValuePairList(reader, subscribe_ok.properties));
   if (!subscribe_ok.properties.Validate()) {
     return absl::InvalidArgumentError("Invalid SUBSCRIBE_OK track properties");
   }
@@ -736,8 +702,8 @@
       !reader.ReadMoqVarInt(&request_update.existing_request_id)) {
     return absl::InvalidArgumentError("Message missing request IDs");
   }
-  QUICHE_RETURN_IF_ERROR(
-      FillAndValidateMessageParameters(reader, request_update.parameters));
+  QUICHE_ASSIGN_OR_RETURN(request_update.parameters,
+                          ParseMessageParameters(reader));
   QUICHE_RETURN_IF_ERROR(CheckForTrailingData(reader));
   return request_update;
 }
@@ -752,8 +718,8 @@
   }
   QUICHE_RETURN_IF_ERROR(
       ReadTrackNamespace(reader, publish_namespace.track_namespace));
-  QUICHE_RETURN_IF_ERROR(
-      FillAndValidateMessageParameters(reader, publish_namespace.parameters));
+  QUICHE_ASSIGN_OR_RETURN(publish_namespace.parameters,
+                          ParseMessageParameters(reader));
   QUICHE_RETURN_IF_ERROR(CheckForTrailingData(reader));
   return publish_namespace;
 }
@@ -791,10 +757,9 @@
     absl::string_view data) const {
   quic::QuicDataReader reader(data);
   MoqtRequestOk request_ok;
-  QUICHE_RETURN_IF_ERROR(
-      FillAndValidateMessageParameters(reader, request_ok.parameters));
-  QUICHE_RETURN_IF_ERROR(
-      ParseKeyValuePairListWithNoPrefix(reader, request_ok.properties));
+  QUICHE_ASSIGN_OR_RETURN(request_ok.parameters,
+                          ParseMessageParameters(reader));
+  QUICHE_RETURN_IF_ERROR(ParseKeyValuePairList(reader, request_ok.properties));
   if (!request_ok.properties.Validate()) {
     return absl::InvalidArgumentError("Invalid REQUEST_OK track properties");
   }
@@ -849,8 +814,8 @@
   }
   QUICHE_RETURN_IF_ERROR(
       ReadTrackNamespace(reader, subscribe_namespace.track_namespace_prefix));
-  QUICHE_RETURN_IF_ERROR(
-      FillAndValidateMessageParameters(reader, subscribe_namespace.parameters));
+  QUICHE_ASSIGN_OR_RETURN(subscribe_namespace.parameters,
+                          ParseMessageParameters(reader));
   QUICHE_RETURN_IF_ERROR(CheckForTrailingData(reader));
   return subscribe_namespace;
 }
@@ -864,8 +829,8 @@
   }
   QUICHE_RETURN_IF_ERROR(
       ReadTrackNamespace(reader, subscribe_tracks.track_namespace_prefix));
-  QUICHE_RETURN_IF_ERROR(
-      FillAndValidateMessageParameters(reader, subscribe_tracks.parameters));
+  QUICHE_ASSIGN_OR_RETURN(subscribe_tracks.parameters,
+                          ParseMessageParameters(reader));
   QUICHE_RETURN_IF_ERROR(CheckForTrailingData(reader));
   return subscribe_tracks;
 }
@@ -929,8 +894,7 @@
     default:
       return absl::InvalidArgumentError("Invalid FETCH type");
   }
-  QUICHE_RETURN_IF_ERROR(
-      FillAndValidateMessageParameters(reader, fetch.parameters));
+  QUICHE_ASSIGN_OR_RETURN(fetch.parameters, ParseMessageParameters(reader));
   QUICHE_RETURN_IF_ERROR(CheckForTrailingData(reader));
   return fetch;
 }
@@ -954,10 +918,8 @@
     --fetch_ok.end_location.object;
   }
   fetch_ok.end_of_track = end_of_track == 1;
-  QUICHE_RETURN_IF_ERROR(
-      FillAndValidateMessageParameters(reader, fetch_ok.parameters));
-  QUICHE_RETURN_IF_ERROR(
-      ParseKeyValuePairListWithNoPrefix(reader, fetch_ok.properties));
+  QUICHE_ASSIGN_OR_RETURN(fetch_ok.parameters, ParseMessageParameters(reader));
+  QUICHE_RETURN_IF_ERROR(ParseKeyValuePairList(reader, fetch_ok.properties));
   if (!fetch_ok.properties.Validate()) {
     return absl::InvalidArgumentError("Invalid FETCH_OK track properties");
   }
@@ -977,10 +939,8 @@
   if (!reader.ReadMoqVarInt(&publish.track_alias)) {
     return absl::InvalidArgumentError("Track alias missing");
   }
-  QUICHE_RETURN_IF_ERROR(
-      FillAndValidateMessageParameters(reader, publish.parameters));
-  QUICHE_RETURN_IF_ERROR(
-      ParseKeyValuePairListWithNoPrefix(reader, publish.properties));
+  QUICHE_ASSIGN_OR_RETURN(publish.parameters, ParseMessageParameters(reader));
+  QUICHE_RETURN_IF_ERROR(ParseKeyValuePairList(reader, publish.properties));
   if (!publish.properties.Validate()) {
     return absl::InvalidArgumentError("Invalid PUBLISH track properties");
   }
@@ -1058,15 +1018,6 @@
   return absl::OkStatus();
 }
 
-absl::Status MoqtControlMessageParser::FillAndValidateMessageParameters(
-    quic::QuicDataReader& reader, MessageParameters& out) const {
-  KeyValuePairList pairs;
-  QUICHE_RETURN_IF_ERROR(ParseKeyValuePairList(reader, pairs));
-  // All parameter types are allowed in all messages.
-  QUICHE_RETURN_IF_ERROR(out.FromKeyValuePairList(pairs));
-  return absl::OkStatus();
-}
-
 MoqtDataParser::MoqtDataParser(MoqtStreamTypeParser type_parser,
                                MoqtDataParserVisitor* visitor)
     : stream_(*type_parser.stream()), visitor_(*visitor) {
diff --git a/quiche/quic/moqt/moqt_parser.h b/quiche/quic/moqt/moqt_parser.h
index cda423a..2acb81b 100644
--- a/quiche/quic/moqt/moqt_parser.h
+++ b/quiche/quic/moqt/moqt_parser.h
@@ -250,11 +250,6 @@
                                  FullTrackName& full_track_name) const;
   absl::Status FillAndValidateSetupOptions(const KeyValuePairList& in,
                                            SetupOptions& out) const;
-  // |reader| points to the beginning of a KeyValuePairList. Returns false if
-  // there is any sort of error. (The function calls ParseError(), so the
-  // caller has no need to do so.)
-  absl::Status FillAndValidateMessageParameters(quic::QuicDataReader& reader,
-                                                MessageParameters& out) const;
 
   bool uses_web_transport_;
   const quic::Perspective perspective_;
diff --git a/quiche/quic/moqt/moqt_parser_test.cc b/quiche/quic/moqt/moqt_parser_test.cc
index 78e7252..69a0210 100644
--- a/quiche/quic/moqt/moqt_parser_test.cc
+++ b/quiche/quic/moqt/moqt_parser_test.cc
@@ -618,11 +618,9 @@
 
 TEST_F(MoqtMessageSpecificTest, ClientSetupMaxAuthTokenCacheSizeAppearsTwice) {
   char setup[] = {
-      0xaf, 0x00, 0x00, 0x0a,
-      0x03,                          // 3 options
-      0x01, 0x03, 0x66, 0x6f, 0x6f,  // path = "foo"
-      0x03, 0x32,                    // max_auth_token_cache_size = 50
-      0x00, 0x32,                    // max_auth_token_cache_size = 50
+      0xaf, 0x00, 0x00, 0x09, 0x01, 0x03, 0x66, 0x6f, 0x6f,  // path = "foo"
+      0x03, 0x32,  // max_auth_token_cache_size = 50
+      0x00, 0x32,  // max_auth_token_cache_size = 50
   };
   absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
       ParseAllMessages(absl::string_view(setup, sizeof(setup)));
@@ -632,9 +630,8 @@
 
 TEST_F(MoqtMessageSpecificTest, ServerSetupAuthorizationTokenTagRegister) {
   char setup[] = {
-      0xaf, 0x00, 0x00, 0x09,
-      0x01,                                            // 1 option
-      0x03, 0x06, 0x01, 0x10, 0x00, 0x62, 0x61, 0x72,  // REGISTER 0x01
+      0xaf, 0x00, 0x00, 0x08, 0x03, 0x06,
+      0x01, 0x10, 0x00, 0x62, 0x61, 0x72,  // REGISTER 0x01
   };
   absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
       ParseAllMessages(absl::string_view(setup, sizeof(setup)),
@@ -645,9 +642,7 @@
 
 TEST_F(MoqtMessageSpecificTest, SetupPathFromServer) {
   char setup[] = {
-      0xaf, 0x00, 0x00, 0x06,
-      0x01,                          // 1 option
-      0x01, 0x03, 0x66, 0x6f, 0x6f,  // path = "foo"
+      0xaf, 0x00, 0x00, 0x05, 0x01, 0x03, 0x66, 0x6f, 0x6f,  // path = "foo"
   };
   absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
       ParseAllMessages(absl::string_view(setup, sizeof(setup)),
@@ -661,9 +656,8 @@
 
 TEST_F(MoqtMessageSpecificTest, SetupAuthorityFromServer) {
   char setup[] = {
-      0xaf, 0x00, 0x00, 0x06,
-      0x01,                          // 1 option
-      0x05, 0x03, 0x66, 0x6f, 0x6f,  // authority = "foo"
+      0xaf, 0x00, 0x00, 0x05, 0x05,
+      0x03, 0x66, 0x6f, 0x6f,  // authority = "foo"
   };
   absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
       ParseAllMessages(absl::string_view(setup, sizeof(setup)),
@@ -674,10 +668,8 @@
 
 TEST_F(MoqtMessageSpecificTest, SetupPathAppearsTwice) {
   char setup[] = {
-      0xaf, 0x00, 0x00, 0x0b,
-      0x02,                          // 2 options
-      0x01, 0x03, 0x66, 0x6f, 0x6f,  // path = "foo"
-      0x00, 0x03, 0x66, 0x6f, 0x6f,  // path = "foo"
+      0xaf, 0x00, 0x00, 0x0a, 0x01, 0x03, 0x66, 0x6f, 0x6f,  // path = "foo"
+      0x00, 0x03, 0x66, 0x6f, 0x6f,                          // path = "foo"
   };
   absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
       absl::string_view(setup, sizeof(setup)), kDefaultMoqtVersion, kRawQuic);
@@ -687,9 +679,7 @@
 
 TEST_F(MoqtMessageSpecificTest, SetupPathOverWebtrans) {
   char setup[] = {
-      0xaf, 0x00, 0x00, 0x06,
-      0x01,                          // 1 option
-      0x01, 0x03, 0x66, 0x6f, 0x6f,  // path = "foo"
+      0xaf, 0x00, 0x00, 0x05, 0x01, 0x03, 0x66, 0x6f, 0x6f,  // path = "foo"
   };
   absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
       absl::string_view(setup, sizeof(setup)), kDefaultMoqtVersion, kWebTrans);
@@ -699,9 +689,8 @@
 
 TEST_F(MoqtMessageSpecificTest, SetupAuthorityOverWebtrans) {
   char setup[] = {
-      0xaf, 0x00, 0x00, 0x06,
-      0x01,                          // 1 option
-      0x05, 0x03, 0x66, 0x6f, 0x6f,  // authority = "foo"
+      0xaf, 0x00, 0x00, 0x05, 0x05,
+      0x03, 0x66, 0x6f, 0x6f,  // authority = "foo"
   };
   absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
       absl::string_view(setup, sizeof(setup)), kDefaultMoqtVersion, kWebTrans);
@@ -711,8 +700,10 @@
 
 TEST_F(MoqtMessageSpecificTest, SetupPathMissing) {
   char setup[] = {
-      0xaf, 0x00, 0x00, 0x01,
-      0x00,  // no options
+      0xaf,
+      0x00,
+      0x00,
+      0x00,  // Empty SETUP message.
   };
   absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
       absl::string_view(setup, sizeof(setup)), kDefaultMoqtVersion, kRawQuic);
@@ -722,9 +713,7 @@
 
 TEST_F(MoqtMessageSpecificTest, ClientSetupMalformedPath) {
   char setup[] = {
-      0xaf, 0x00, 0x00, 0x06,
-      0x01,                          // 1 option
-      0x01, 0x03, 0x66, 0x5c, 0x6f,  // path = "f\o"
+      0xaf, 0x00, 0x00, 0x05, 0x01, 0x03, 0x66, 0x5c, 0x6f,  // path = "f\o"
   };
   absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
       absl::string_view(setup, sizeof(setup)), kDefaultMoqtVersion, kRawQuic);
@@ -734,9 +723,7 @@
 
 TEST_F(MoqtMessageSpecificTest, ClientSetupMalformedAuthority) {
   char setup[] = {
-      0xaf, 0x00, 0x00, 0x0b,
-      0x02,                          // 2 options
-      0x01, 0x03, 0x66, 0x6f, 0x6f,  // path = "foo"
+      0xaf, 0x00, 0x00, 0x0a, 0x01, 0x03, 0x66, 0x6f, 0x6f,  // path = "foo"
       0x04, 0x03, 0x66, 0x5c, 0x6f,  // authority = "f\o"
   };
   absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
@@ -747,10 +734,8 @@
 
 TEST_F(MoqtMessageSpecificTest, ServerSetupUnknownOptionIsOk) {
   char setup[] = {
-      0xaf, 0x00, 0x00, 0x0b,
-      0x02,                          // 2 options
-      0x1f, 0x03, 0x62, 0x61, 0x72,  // 0x1f = "bar"
-      0x00, 0x03, 0x62, 0x61, 0x72,  // 0x1f = "bar"
+      0xaf, 0x00, 0x00, 0x0a, 0x1f, 0x03, 0x62, 0x61, 0x72,  // 0x1f = "bar"
+      0x00, 0x03, 0x62, 0x61, 0x72,                          // 0x1f = "bar"
   };
   absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
       absl::string_view(setup, sizeof(setup)), kDefaultMoqtVersion, kRawQuic,
@@ -828,7 +813,7 @@
       ParseAllMessages(absl::string_view(subscribe, sizeof(subscribe)),
                        kDefaultMoqtVersion, kRawQuic);
   EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
-            MoqtError::kKeyValueFormattingError);
+            MoqtError::kProtocolViolation);
 }
 
 TEST_F(MoqtMessageSpecificTest,
@@ -844,7 +829,7 @@
       ParseAllMessages(absl::string_view(subscribe, sizeof(subscribe)),
                        kDefaultMoqtVersion, kRawQuic);
   EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
-            MoqtError::kKeyValueFormattingError);
+            MoqtError::kProtocolViolation);
 }
 
 TEST_F(MoqtMessageSpecificTest, SubscribeInvalidForward) {
@@ -1549,8 +1534,7 @@
 
 TEST_F(MoqtMessageSpecificTest, ParseKeyValuePairListIntegerOverflow) {
   char setup[] = {
-      0xaf, 0x00, 0x00, 0x0c,  // kSetup, length = 12
-      0x02,                    // num_params
+      0xaf, 0x00, 0x00, 0x0b,  // kSetup, length = 11
       0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,  // type_diff = max
       0x00,  // string length = 0
       0x01,  // type_diff = 1 (overflows)
diff --git a/quiche/quic/moqt/test_tools/moqt_test_message.h b/quiche/quic/moqt/test_tools/moqt_test_message.h
index 5325c92..5a60089 100644
--- a/quiche/quic/moqt/test_tools/moqt_test_message.h
+++ b/quiche/quic/moqt/test_tools/moqt_test_message.h
@@ -608,12 +608,11 @@
       // Should not send PATH or AUTHORITY.
       client_setup_.options.path = std::nullopt;
       client_setup_.options.authority = std::nullopt;
-      raw_packet_[3] -= 17;   // adjust payload length
-      raw_packet_[4] = 0x01;  // only one option
+      raw_packet_[3] -= 17;  // adjust payload length
       // Move MoqtImplementation up in the packet.
-      memmove(raw_packet_ + 5, raw_packet_ + 22,
+      memmove(raw_packet_ + 4, raw_packet_ + 21,
               kTestImplementationString.length() + 2);
-      raw_packet_[5] = 0x07;  // Diff from 0.
+      raw_packet_[4] = 0x07;  // Diff from 0.
       SetWireImage(raw_packet_, sizeof(raw_packet_) - 17);
     } else {
       SetWireImage(raw_packet_, sizeof(raw_packet_));
@@ -631,9 +630,9 @@
 
   void ExpandVarints() override {
     if (client_setup_.options.path.has_value()) {
-      ExpandVarintsImpl("vvv----vv---------vv---------------------------");
+      ExpandVarintsImpl("vv----vv---------vv---------------------------");
     } else {
-      ExpandVarintsImpl("vvv---------------------------");
+      ExpandVarintsImpl("vv---------------------------");
     }
   }
 
@@ -646,9 +645,8 @@
   // string options in order. Unfortunately, this means that
   // kMoqtImplementation goes last even though it is always present, while
   // kPath and KAuthority aren't.
-  uint8_t raw_packet_[52] = {
-      0xaf, 0x00, 0x00, 0x30,              // type, length
-      0x03,                                // 3 options
+  uint8_t raw_packet_[51] = {
+      0xaf, 0x00, 0x00, 0x2f,              // type, length
       0x01, 0x04, 0x70, 0x61, 0x74, 0x68,  // path = "path"
       0x04, 0x09, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74,
       0x79,  // authority = "authority"
@@ -677,16 +675,15 @@
     return true;
   }
 
-  void ExpandVarints() override { ExpandVarintsImpl("vvv"); }
+  void ExpandVarints() override { ExpandVarintsImpl("vv"); }
 
   MessageStructuredData structured_data() const override {
     return TestMessageBase::MessageStructuredData(server_setup_);
   }
 
  private:
-  uint8_t raw_packet_[35] = {0xaf, 0x00, 0x00, 0x1f,  // type, length
-                             0x01,                    // one option
-                             // moqt_implementation:
+  uint8_t raw_packet_[34] = {0xaf, 0x00, 0x00, 0x1e,  // type, length
+                                                      // moqt_implementation:
                              0x07, 0x1c, 0x4d, 0x6f, 0x71, 0x20, 0x54, 0x65,
                              0x73, 0x74, 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65,
                              0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f,
@@ -719,7 +716,7 @@
   }
 
   void ExpandVarints() override {
-    ExpandVarintsImpl("vvv---v----vv--vv-----vvvvvv---vv");
+    ExpandVarintsImpl("vvv---v----vv--vv-----v-v-vv---v-");
   }
 
   MessageStructuredData structured_data() const override {
@@ -773,14 +770,14 @@
     return true;
   }
 
-  void ExpandVarints() override { ExpandVarintsImpl("vvvvvv--v--v--vv"); }
+  void ExpandVarints() override { ExpandVarintsImpl("vvvvvvvv--v--vv"); }
 
   MessageStructuredData structured_data() const override {
     return TestMessageBase::MessageStructuredData(subscribe_ok_);
   }
 
   void SetInvalidDeliveryOrder() {
-    raw_packet_[18] = 0x10;
+    raw_packet_[17] = 0x10;
     SetWireImage(raw_packet_, sizeof(raw_packet_));
   }
 
@@ -801,10 +798,10 @@
   };
 
  private:
-  uint8_t raw_packet_[19] = {
-      0x04, 0x00, 0x10, 0x02, 0x02,  // alias, 2 params
+  uint8_t raw_packet_[18] = {
+      0x04, 0x00, 0x0f, 0x02, 0x02,  // alias, 2 params
       0x08, 0x03,                    // expires = 3
-      0x01, 0x02, 0x0c, 0x14,        // largest_location = (12, 20)
+      0x01, 0x0c, 0x14,              // largest_location = (12, 20)
       // Properties
       0x02, 0xa7, 0x10,  // object_delivery_timeout = 10000
       0x02, 0xa7, 0x10,  // max_cache_duration = 10000
@@ -935,19 +932,19 @@
     return true;
   }
 
-  void ExpandVarints() override { ExpandVarintsImpl("vvvv--vvv--vv----"); }
+  void ExpandVarints() override { ExpandVarintsImpl("vvvv--v-v-vv----"); }
 
   MessageStructuredData structured_data() const override {
     return TestMessageBase::MessageStructuredData(request_update_);
   }
 
  private:
-  uint8_t raw_packet_[20] = {
-      0x02, 0x00, 0x11, 0x02, 0x00,        // request IDs 2 and 0
+  uint8_t raw_packet_[19] = {
+      0x02, 0x00, 0x10, 0x02, 0x00,        // request IDs 2 and 0
       0x04,                                // Four parameters
       0x02, 0xa7, 0x10,                    // object_delivery_timeout = 10000
       0x0e, 0x01,                          // forward = true
-      0x10, 0x80, 0xaa,                    // subscriber_priority = 0xaa
+      0x10, 0xaa,                          // subscriber_priority = 0xaa
       0x01, 0x04, 0x04, 0x03, 0x01, 0x02,  // Absolute Range: (3, 1) to 5
   };
 
@@ -1121,17 +1118,17 @@
     return true;
   }
 
-  void ExpandVarints() override { ExpandVarintsImpl("vvv--v--v--vv"); }
+  void ExpandVarints() override { ExpandVarintsImpl("vvvvv--v--v-"); }
 
   MessageStructuredData structured_data() const override {
     return TestMessageBase::MessageStructuredData(request_ok_);
   }
 
  private:
-  uint8_t raw_packet_[16] = {
-      0x07, 0x00, 0x0d,
-      0x01,                    // 1 parameter
-      0x09, 0x02, 0x05, 0x01,  // Largest Object = (5, 1)
+  uint8_t raw_packet_[15] = {
+      0x07, 0x00, 0x0c,
+      0x01,              // 1 parameter
+      0x09, 0x05, 0x01,  // Largest Object = (5, 1)
       // Properties
       0x02, 0xa7, 0x10,  // object_delivery_timeout = 10000
       0x02, 0xa7, 0x10,  // max_cache_duration = 10000
@@ -1245,7 +1242,7 @@
     return true;
   }
 
-  void ExpandVarints() override { ExpandVarintsImpl("vvv---vvv-----vv"); }
+  void ExpandVarints() override { ExpandVarintsImpl("vvv---vvv-----v-"); }
 
   MessageStructuredData structured_data() const override {
     return TestMessageBase::MessageStructuredData(subscribe_namespace_);
@@ -1294,7 +1291,7 @@
     return true;
   }
 
-  void ExpandVarints() override { ExpandVarintsImpl("vvv---vvv-----vv"); }
+  void ExpandVarints() override { ExpandVarintsImpl("vvv---vvv-----v-"); }
 
   MessageStructuredData structured_data() const override {
     return TestMessageBase::MessageStructuredData(subscribe_tracks_);
@@ -1343,7 +1340,7 @@
   }
 
   void ExpandVarints() override {
-    ExpandVarintsImpl("vvvv---v---vvvvvvv-----vvvv");
+    ExpandVarintsImpl("vvvv---v---vvvvvvv-----v-v-");
   }
 
   MessageStructuredData structured_data() const override {
@@ -1421,7 +1418,7 @@
     return true;
   }
 
-  void ExpandVarints() override { ExpandVarintsImpl("vvvvvvv-----vvvv"); }
+  void ExpandVarints() override { ExpandVarintsImpl("vvvvvvv-----v-v-"); }
 
   MessageStructuredData structured_data() const override {
     return TestMessageBase::MessageStructuredData(fetch_);
@@ -1479,7 +1476,7 @@
     return true;
   }
 
-  void ExpandVarints() override { ExpandVarintsImpl("vvvvvvv-----vvvv"); }
+  void ExpandVarints() override { ExpandVarintsImpl("vvvvvvv-----v-v-"); }
 
   MessageStructuredData structured_data() const override {
     return TestMessageBase::MessageStructuredData(fetch_);
@@ -1597,7 +1594,7 @@
   }
 
   void ExpandVarints() override {
-    ExpandVarintsImpl("vvv---v---vvvv-----vv--vvvv");
+    ExpandVarintsImpl("vvv---v---vvvv-----vvvv-v-");
   }
 
   MessageStructuredData structured_data() const override {
@@ -1605,15 +1602,15 @@
   }
 
  private:
-  uint8_t raw_packet_[30] = {
-      0x1d, 0x00, 0x1b,
+  uint8_t raw_packet_[29] = {
+      0x1d, 0x00, 0x1a,
       0x01,                                      // request_id = 1
       0x01, 0x03, 0x66, 0x6f, 0x6f,              // track_namespace = "foo"
       0x03, 0x62, 0x61, 0x72,                    // track_name = "bar"
       0x04,                                      // track_alias = 4
       0x03,                                      // 3 parameters
       0x03, 0x05, 0x03, 0x00, 0x62, 0x61, 0x7a,  // token = "baz"
-      0x06, 0x02, 0x0a, 0x01,                    // largest_object = 10, 1
+      0x06, 0x0a, 0x01,                          // largest_object = 10, 1
       0x07, 0x01,                                // forward = 1
       0x22, 0x02,                                // group_order = kAscending
   };