Implement MOQT draft-16 version negotiation.

Breaks interop with all hosts, as no other version-16 features are yet supported.

Integration tests will fail until WebTransport APIs are in place.

PiperOrigin-RevId: 853922338
diff --git a/quiche/quic/moqt/moqt_framer.cc b/quiche/quic/moqt/moqt_framer.cc
index 5b54788..918b0e0 100644
--- a/quiche/quic/moqt/moqt_framer.cc
+++ b/quiche/quic/moqt/moqt_framer.cc
@@ -401,8 +401,6 @@
   }
   return SerializeControlMessage(
       MoqtMessageType::kClientSetup,
-      WireVarInt62(message.supported_versions.size()),
-      WireSpan<WireVarInt62, MoqtVersion>(message.supported_versions),
       WireKeyValuePairList(parameters));
 }
 
@@ -418,7 +416,6 @@
     return quiche::QuicheBuffer();
   }
   return SerializeControlMessage(MoqtMessageType::kServerSetup,
-                                 WireVarInt62(message.selected_version),
                                  WireKeyValuePairList(parameters));
 }
 
diff --git a/quiche/quic/moqt/moqt_integration_test.cc b/quiche/quic/moqt/moqt_integration_test.cc
index a887b43..e3c05f4 100644
--- a/quiche/quic/moqt/moqt_integration_test.cc
+++ b/quiche/quic/moqt/moqt_integration_test.cc
@@ -142,9 +142,9 @@
 }
 
 TEST_F(MoqtIntegrationTest, VersionMismatch) {
-  client_ = std::make_unique<MoqtClientEndpoint>(
-      &test_harness_.simulator(), "Client", "Server",
-      MoqtVersion::kUnrecognizedVersionForTests);
+  client_ = std::make_unique<MoqtClientEndpoint>(&test_harness_.simulator(),
+                                                 "Client", "Server",
+                                                 kUnrecognizedVersionForTests);
   server_ = std::make_unique<MoqtServerEndpoint>(
       &test_harness_.simulator(), "Server", "Client", kDefaultMoqtVersion);
   SetupCallbacks();
diff --git a/quiche/quic/moqt/moqt_messages.h b/quiche/quic/moqt/moqt_messages.h
index c55b86e..1ed2b9b 100644
--- a/quiche/quic/moqt/moqt_messages.h
+++ b/quiche/quic/moqt/moqt_messages.h
@@ -38,14 +38,12 @@
   return quic::ParsedQuicVersionVector{quic::ParsedQuicVersion::RFCv1()};
 }
 
-enum class MoqtVersion : uint64_t {
-  kDraft14 = 0xff00000e,
-  kUnrecognizedVersionForTests = 0xfe0000ff,
-};
+inline constexpr absl::string_view kDraft16 = "moqt-16";
+inline constexpr absl::string_view kDefaultMoqtVersion = kDraft16;
+inline constexpr absl::string_view kImplementationName =
+    "Google QUICHE MOQT draft 16";
+inline constexpr absl::string_view kUnrecognizedVersionForTests = "moqt-15";
 
-inline constexpr MoqtVersion kDefaultMoqtVersion = MoqtVersion::kDraft14;
-inline constexpr absl::string_view kVersionString =
-    "Google QUICHE MOQT draft 14";
 inline constexpr uint64_t kDefaultInitialMaxRequestId = 100;
 // TODO(martinduke): Implement an auth token cache.
 inline constexpr uint64_t kDefaultMaxAuthTokenCacheSize = 0;
@@ -97,7 +95,7 @@
       : perspective(perspective), max_request_id(max_request_id) {}
   bool operator==(const MoqtSessionParameters& other) const = default;
 
-  MoqtVersion version = kDefaultMoqtVersion;
+  std::string version = std::string(kDefaultMoqtVersion);
   bool deliver_partial_objects = false;
   quic::Perspective perspective = quic::Perspective::IS_SERVER;
   bool using_webtrans = true;
@@ -535,12 +533,10 @@
 
 // TODO(martinduke): Collapse both Setup messages into MoqtSessionParameters.
 struct QUICHE_EXPORT MoqtClientSetup {
-  std::vector<MoqtVersion> supported_versions;
   MoqtSessionParameters parameters;
 };
 
 struct QUICHE_EXPORT MoqtServerSetup {
-  MoqtVersion selected_version;
   MoqtSessionParameters parameters;
 };
 
diff --git a/quiche/quic/moqt/moqt_parser.cc b/quiche/quic/moqt/moqt_parser.cc
index 3017ff9..c1ac1ac 100644
--- a/quiche/quic/moqt/moqt_parser.cc
+++ b/quiche/quic/moqt/moqt_parser.cc
@@ -325,17 +325,6 @@
   MoqtClientSetup setup;
   setup.parameters.using_webtrans = uses_web_transport_;
   setup.parameters.perspective = quic::Perspective::IS_CLIENT;
-  uint64_t number_of_supported_versions;
-  if (!reader.ReadVarInt62(&number_of_supported_versions)) {
-    return 0;
-  }
-  uint64_t version;
-  for (uint64_t i = 0; i < number_of_supported_versions; ++i) {
-    if (!reader.ReadVarInt62(&version)) {
-      return 0;
-    }
-    setup.supported_versions.push_back(static_cast<MoqtVersion>(version));
-  }
   KeyValuePairList parameters;
   if (!ParseKeyValuePairList(reader, parameters)) {
     return 0;
@@ -358,11 +347,6 @@
   MoqtServerSetup setup;
   setup.parameters.using_webtrans = uses_web_transport_;
   setup.parameters.perspective = quic::Perspective::IS_SERVER;
-  uint64_t version;
-  if (!reader.ReadVarInt62(&version)) {
-    return 0;
-  }
-  setup.selected_version = static_cast<MoqtVersion>(version);
   KeyValuePairList parameters;
   if (!ParseKeyValuePairList(reader, parameters)) {
     return 0;
diff --git a/quiche/quic/moqt/moqt_parser_test.cc b/quiche/quic/moqt/moqt_parser_test.cc
index 3af3cf0..057c3bf 100644
--- a/quiche/quic/moqt/moqt_parser_test.cc
+++ b/quiche/quic/moqt/moqt_parser_test.cc
@@ -491,11 +491,11 @@
   webtransport::test::InMemoryStream stream(/*stream_id=*/0);
   MoqtControlParser parser(kRawQuic, &stream, visitor_);
   char setup[] = {
-      0x20, 0x00, 0x0d, 0x02, 0x01, 0x02,  // versions
-      0x03,                                // 3 params
-      0x01, 0x03, 0x66, 0x6f, 0x6f,        // path = "foo"
-      0x01, 0x32,                          // max_request_id = 50
-      0x00, 0x32,                          // max_request_id = 50
+      0x20, 0x00, 0x0a,
+      0x03,                          // 3 params
+      0x01, 0x03, 0x66, 0x6f, 0x6f,  // path = "foo"
+      0x01, 0x32,                    // max_request_id = 50
+      0x00, 0x32,                    // max_request_id = 50
   };
   stream.Receive(absl::string_view(setup, sizeof(setup)), false);
   parser.ReadAndDispatchMessages();
@@ -509,7 +509,7 @@
   webtransport::test::InMemoryStream stream(/*stream_id=*/0);
   MoqtControlParser parser(kRawQuic, &stream, visitor_);
   char setup[] = {
-      0x20, 0x00, 0x13, 0x02, 0x01, 0x02,              // versions
+      0x20, 0x00, 0x10,
       0x03,                                            // 3 params
       0x01, 0x03, 0x66, 0x6f, 0x6f,                    // path = "foo"
       0x01, 0x32,                                      // max_request_id = 50
@@ -525,8 +525,7 @@
   webtransport::test::InMemoryStream stream(/*stream_id=*/0);
   MoqtControlParser parser(kRawQuic, &stream, visitor_);
   char setup[] = {
-      0x21, 0x00, 0x07,
-      0x01,                          // version = 1
+      0x21, 0x00, 0x06,
       0x01,                          // 1 param
       0x01, 0x03, 0x66, 0x6f, 0x6f,  // path = "foo"
   };
@@ -542,8 +541,7 @@
   webtransport::test::InMemoryStream stream(/*stream_id=*/0);
   MoqtControlParser parser(kRawQuic, &stream, visitor_);
   char setup[] = {
-      0x21, 0x00, 0x07,
-      0x01,                          // version = 1
+      0x21, 0x00, 0x06,
       0x01,                          // 1 param
       0x05, 0x03, 0x66, 0x6f, 0x6f,  // authority = "foo"
   };
@@ -559,10 +557,10 @@
   webtransport::test::InMemoryStream stream(/*stream_id=*/0);
   MoqtControlParser parser(kRawQuic, &stream, visitor_);
   char setup[] = {
-      0x20, 0x00, 0x0e, 0x02, 0x01, 0x02,  // versions = 1, 2
-      0x02,                                // 2 params
-      0x01, 0x03, 0x66, 0x6f, 0x6f,        // path = "foo"
-      0x00, 0x03, 0x66, 0x6f, 0x6f,        // path = "foo"
+      0x20, 0x00, 0x0b,
+      0x02,                          // 2 params
+      0x01, 0x03, 0x66, 0x6f, 0x6f,  // path = "foo"
+      0x00, 0x03, 0x66, 0x6f, 0x6f,  // path = "foo"
   };
   stream.Receive(absl::string_view(setup, sizeof(setup)), false);
   parser.ReadAndDispatchMessages();
@@ -576,9 +574,9 @@
   webtransport::test::InMemoryStream stream(/*stream_id=*/0);
   MoqtControlParser parser(kWebTrans, &stream, visitor_);
   char setup[] = {
-      0x20, 0x00, 0x09, 0x02, 0x01, 0x02,  // versions = 1, 2
-      0x01,                                // 1 param
-      0x01, 0x03, 0x66, 0x6f, 0x6f,        // path = "foo"
+      0x20, 0x00, 0x06,
+      0x01,                          // 1 param
+      0x01, 0x03, 0x66, 0x6f, 0x6f,  // path = "foo"
   };
   stream.Receive(absl::string_view(setup, sizeof(setup)), false);
   parser.ReadAndDispatchMessages();
@@ -592,9 +590,9 @@
   webtransport::test::InMemoryStream stream(/*stream_id=*/0);
   MoqtControlParser parser(kWebTrans, &stream, visitor_);
   char setup[] = {
-      0x20, 0x00, 0x09, 0x02, 0x01, 0x02,  // versions = 1, 2
-      0x01,                                // 1 param
-      0x05, 0x03, 0x66, 0x6f, 0x6f,        // authority = "foo"
+      0x20, 0x00, 0x06,
+      0x01,                          // 1 param
+      0x05, 0x03, 0x66, 0x6f, 0x6f,  // authority = "foo"
   };
   stream.Receive(absl::string_view(setup, sizeof(setup)), false);
   parser.ReadAndDispatchMessages();
@@ -608,8 +606,10 @@
   webtransport::test::InMemoryStream stream(/*stream_id=*/0);
   MoqtControlParser parser(kRawQuic, &stream, visitor_);
   char setup[] = {
-      0x20, 0x00, 0x04, 0x02, 0x01, 0x02,  // versions = 1, 2
-      0x00,                                // no param
+      0x20,
+      0x00,
+      0x01,
+      0x00,  // no param
   };
   stream.Receive(absl::string_view(setup, sizeof(setup)), false);
   parser.ReadAndDispatchMessages();
@@ -623,11 +623,11 @@
   webtransport::test::InMemoryStream stream(/*stream_id=*/0);
   MoqtControlParser parser(kRawQuic, &stream, visitor_);
   char setup[] = {
-      0x20, 0x00, 0x0d, 0x02, 0x01, 0x02,  // versions = 1, 2
-      0x03,                                // 4 params
-      0x01, 0x03, 0x66, 0x6f, 0x6f,        // path = "foo"
-      0x01, 0x32,                          // max_request_id = 50
-      0x00, 0x32,                          // max_request_id = 50
+      0x20, 0x00, 0x0a,
+      0x03,                          // 3 params
+      0x01, 0x03, 0x66, 0x6f, 0x6f,  // path = "foo"
+      0x01, 0x32,                    // max_request_id = 50
+      0x00, 0x32,                    // max_request_id = 50
   };
   stream.Receive(absl::string_view(setup, sizeof(setup)), false);
   parser.ReadAndDispatchMessages();
@@ -641,9 +641,9 @@
   webtransport::test::InMemoryStream stream(/*stream_id=*/0);
   MoqtControlParser parser(kRawQuic, &stream, visitor_);
   char setup[] = {
-      0x20, 0x00, 0x09, 0x02, 0x01, 0x02,  // versions = 1, 2
-      0x01,                                // 1 param
-      0x01, 0x03, 0x66, 0x5c, 0x6f,        // path = "f\o"
+      0x20, 0x00, 0x06,
+      0x01,                          // 1 param
+      0x01, 0x03, 0x66, 0x5c, 0x6f,  // path = "f\o"
   };
   stream.Receive(absl::string_view(setup, sizeof(setup)), false);
   parser.ReadAndDispatchMessages();
@@ -656,10 +656,10 @@
   webtransport::test::InMemoryStream stream(/*stream_id=*/0);
   MoqtControlParser parser(kRawQuic, &stream, visitor_);
   char setup[] = {
-      0x20, 0x00, 0x0e, 0x02, 0x01, 0x02,  // versions = 1, 2
-      0x02,                                // 2 params
-      0x01, 0x03, 0x66, 0x6f, 0x6f,        // path = "foo"
-      0x04, 0x03, 0x66, 0x5c, 0x6f,        // authority = "f\o"
+      0x20, 0x00, 0x0b,
+      0x02,                          // 2 params
+      0x01, 0x03, 0x66, 0x6f, 0x6f,  // path = "foo"
+      0x04, 0x03, 0x66, 0x5c, 0x6f,  // authority = "f\o"
   };
   stream.Receive(absl::string_view(setup, sizeof(setup)), false);
   parser.ReadAndDispatchMessages();
diff --git a/quiche/quic/moqt/moqt_session.cc b/quiche/quic/moqt/moqt_session.cc
index c5db83c..1e2ea27 100644
--- a/quiche/quic/moqt/moqt_session.cc
+++ b/quiche/quic/moqt/moqt_session.cc
@@ -15,7 +15,6 @@
 #include <vector>
 
 
-#include "absl/algorithm/container.h"
 #include "absl/base/nullability.h"
 #include "absl/container/btree_map.h"
 #include "absl/container/flat_hash_map.h"
@@ -121,7 +120,7 @@
     next_incoming_request_id_ = 1;
   }
   QUICHE_DCHECK(parameters_.moqt_implementation.empty());
-  parameters_.moqt_implementation = kVersionString;
+  parameters_.moqt_implementation = kImplementationName;
 }
 
 MoqtSession::ControlStream* MoqtSession::GetControlStream() {
@@ -147,10 +146,15 @@
 
 void MoqtSession::OnSessionReady() {
   QUICHE_DLOG(INFO) << ENDPOINT << "Underlying session ready";
+  std::optional<std::string> version = session_->GetNegotiatedSubprotocol();
+  if (version != parameters_.version) {
+    Error(MoqtError::kVersionNegotiationFailed,
+          "MOQT peer chose wrong subprotocol");
+    return;
+  }
   if (parameters_.perspective == Perspective::IS_SERVER) {
     return;
   }
-
   webtransport::Stream* control_stream =
       session_->OpenOutgoingBidirectionalStream();
   if (control_stream == nullptr) {
@@ -161,7 +165,6 @@
       std::make_unique<ControlStream>(this, control_stream));
   control_stream_ = control_stream->GetStreamId();
   MoqtClientSetup setup = MoqtClientSetup{
-      .supported_versions = std::vector<MoqtVersion>{parameters_.version},
       .parameters = parameters_,
   };
   SendControlMessage(framer_.SerializeClientSetup(setup));
@@ -990,20 +993,11 @@
                     "Received CLIENT_SETUP from server");
     return;
   }
-  if (absl::c_find(message.supported_versions, session_->parameters_.version) ==
-      message.supported_versions.end()) {
-    // TODO(martinduke): Is this the right error code? See issue #346.
-    session_->Error(MoqtError::kVersionNegotiationFailed,
-                    absl::StrCat("Version mismatch: expected 0x",
-                                 absl::Hex(session_->parameters_.version)));
-    return;
-  }
   session_->peer_supports_object_ack_ = message.parameters.support_object_acks;
   QUICHE_DLOG(INFO) << ENDPOINT << "Received the SETUP message";
   if (session_->parameters_.perspective == Perspective::IS_SERVER) {
     MoqtServerSetup response;
     response.parameters = session_->parameters_;
-    response.selected_version = session_->parameters_.version;
     SendOrBufferMessage(session_->framer_.SerializeServerSetup(response));
     QUIC_DLOG(INFO) << ENDPOINT << "Sent the SETUP message";
   }
@@ -1019,13 +1013,6 @@
                     "Received SERVER_SETUP from client");
     return;
   }
-  if (message.selected_version != session_->parameters_.version) {
-    // TODO(martinduke): Is this the right error code? See issue #346.
-    session_->Error(MoqtError::kProtocolViolation,
-                    absl::StrCat("Version mismatch: expected 0x",
-                                 absl::Hex(session_->parameters_.version)));
-    return;
-  }
   session_->peer_supports_object_ack_ = message.parameters.support_object_acks;
   QUIC_DLOG(INFO) << ENDPOINT << "Received the SETUP message";
   // TODO: handle path.
diff --git a/quiche/quic/moqt/moqt_session_test.cc b/quiche/quic/moqt/moqt_session_test.cc
index 0e6755c..f414556 100644
--- a/quiche/quic/moqt/moqt_session_test.cc
+++ b/quiche/quic/moqt/moqt_session_test.cc
@@ -37,6 +37,7 @@
 #include "quiche/quic/moqt/test_tools/moqt_session_peer.h"
 #include "quiche/quic/platform/api/quic_test.h"
 #include "quiche/quic/test_tools/quic_test_utils.h"
+#include "quiche/common/platform/api/quiche_expect_bug.h"
 #include "quiche/common/quiche_buffer_allocator.h"
 #include "quiche/common/quiche_mem_slice.h"
 #include "quiche/common/quiche_stream.h"
@@ -134,7 +135,7 @@
                                              kDefaultInitialMaxRequestId);
     ON_CALL(mock_session_, GetStreamById).WillByDefault(Return(&mock_stream_));
     EXPECT_EQ(MoqtSessionPeer::GetImplementationString(&session_),
-              kVersionString);
+              kImplementationName);
   }
   ~MoqtSessionTest() {
     EXPECT_CALL(session_callbacks_.session_deleted_callback, Call());
@@ -258,6 +259,8 @@
 
 // Verify the session sends CLIENT_SETUP on the control stream.
 TEST_F(MoqtSessionTest, OnSessionReady) {
+  EXPECT_CALL(mock_session_, GetNegotiatedSubprotocol)
+      .WillOnce(Return(std::optional<std::string>(kDefaultMoqtVersion)));
   EXPECT_CALL(mock_session_, OpenOutgoingBidirectionalStream())
       .WillOnce(Return(&mock_stream_));
   std::unique_ptr<webtransport::StreamVisitor> visitor;
@@ -279,9 +282,7 @@
       MoqtSessionPeer::FetchParserVisitorFromWebtransportStreamVisitor(
           &session_, visitor.get());
   // Handle the server setup
-  MoqtServerSetup setup = {
-      kDefaultMoqtVersion,
-  };
+  MoqtServerSetup setup;  // No fields are set.
   EXPECT_CALL(session_callbacks_.session_established_callback, Call()).Times(1);
   stream_input->OnServerSetupMessage(setup);
 }
@@ -294,7 +295,6 @@
   std::unique_ptr<MoqtControlParserVisitor> stream_input =
       MoqtSessionPeer::CreateControlStream(&server_session, &mock_stream_);
   MoqtClientSetup setup = {
-      /*supported_versions=*/{kDefaultMoqtVersion},
       MoqtSessionParameters(quic::Perspective::IS_CLIENT),
   };
   EXPECT_CALL(mock_stream_,
@@ -1927,6 +1927,8 @@
 }
 
 TEST_F(MoqtSessionTest, OneBidirectionalStreamClient) {
+  EXPECT_CALL(mock_session_, GetNegotiatedSubprotocol)
+      .WillOnce(Return(std::optional<std::string>(kDefaultMoqtVersion)));
   EXPECT_CALL(mock_session_, OpenOutgoingBidirectionalStream())
       .WillOnce(Return(&mock_stream_));
   std::unique_ptr<webtransport::StreamVisitor> visitor;
@@ -1967,7 +1969,6 @@
   std::unique_ptr<MoqtControlParserVisitor> stream_input =
       MoqtSessionPeer::CreateControlStream(&server_session, &mock_stream_);
   MoqtClientSetup setup = {
-      /*supported_versions*/ {kDefaultMoqtVersion},
       MoqtSessionParameters(),
   };
   EXPECT_CALL(mock_stream_,
@@ -4158,6 +4159,23 @@
   // Test teardown will destroy session_, triggering removal of "foo".
 }
 
+TEST_F(MoqtSessionTest, WrongSubprotocol) {
+  EXPECT_CALL(mock_session_, GetNegotiatedSubprotocol)
+      .WillOnce(
+          Return(std::optional<std::string>(kUnrecognizedVersionForTests)));
+  EXPECT_CALL(mock_session_, CloseSession);
+  EXPECT_CALL(session_callbacks_.session_terminated_callback, Call);
+  session_.OnSessionReady();
+}
+
+TEST_F(MoqtSessionTest, NoSubprotocol) {
+  EXPECT_CALL(mock_session_, GetNegotiatedSubprotocol)
+      .WillOnce(Return(std::optional<std::string>()));
+  EXPECT_CALL(mock_session_, CloseSession);
+  EXPECT_CALL(session_callbacks_.session_terminated_callback, Call);
+  session_.OnSessionReady();
+}
+
 // TODO: re-enable this test once this behavior is re-implemented.
 #if 0
 TEST_F(MoqtSessionTest, SubscribeUpdateClosesSubscription) {
diff --git a/quiche/quic/moqt/test_tools/moqt_simulator.cc b/quiche/quic/moqt/test_tools/moqt_simulator.cc
index e7f32bd..c1448ec 100644
--- a/quiche/quic/moqt/test_tools/moqt_simulator.cc
+++ b/quiche/quic/moqt/test_tools/moqt_simulator.cc
@@ -68,7 +68,7 @@
 // value just has to be sufficiently larger than the server link bandwidth.
 constexpr QuicBandwidth kClientLinkBandwidth =
     QuicBandwidth::FromBitsPerSecond(10.0e6);
-constexpr MoqtVersion kMoqtVersion = kDefaultMoqtVersion;
+constexpr absl::string_view kMoqtVersion = kDefaultMoqtVersion;
 
 // Track name used by the simulator.
 FullTrackName TrackName() { return FullTrackName("test", "track"); }
diff --git a/quiche/quic/moqt/test_tools/moqt_simulator_harness.cc b/quiche/quic/moqt/test_tools/moqt_simulator_harness.cc
index 43aad64..59d29d8 100644
--- a/quiche/quic/moqt/test_tools/moqt_simulator_harness.cc
+++ b/quiche/quic/moqt/test_tools/moqt_simulator_harness.cc
@@ -30,7 +30,7 @@
 
 namespace {
 MoqtSessionParameters CreateParameters(quic::Perspective perspective,
-                                       MoqtVersion version) {
+                                       absl::string_view version) {
   MoqtSessionParameters parameters(perspective, "", "");
   parameters.version = version;
   parameters.deliver_partial_objects = false;
@@ -48,14 +48,15 @@
 MoqtClientEndpoint::MoqtClientEndpoint(quic::simulator::Simulator* simulator,
                                        const std::string& name,
                                        const std::string& peer_name,
-                                       MoqtVersion version)
+                                       absl::string_view version)
     : QuicEndpointWithConnection(simulator, name, peer_name,
                                  quic::Perspective::IS_CLIENT,
                                  quic::GetQuicVersionsForGenericSession()),
       crypto_config_(quic::test::crypto_test_utils::ProofVerifierForTesting()),
       quic_session_(connection_.get(), false, nullptr, GenerateQuicConfig(),
-                    "test.example.com", 443, "moqt", &session_,
-                    /*visitor_owned=*/false, nullptr, &crypto_config_),
+                    "test.example.com", 443, std::string(kDefaultMoqtVersion),
+                    &session_, /*visitor_owned=*/false, nullptr,
+                    &crypto_config_),
       session_(&quic_session_,
                CreateParameters(quic::Perspective::IS_CLIENT, version),
                std::make_unique<quic::QuicAlarmFactoryProxy>(
@@ -67,7 +68,7 @@
 MoqtServerEndpoint::MoqtServerEndpoint(quic::simulator::Simulator* simulator,
                                        const std::string& name,
                                        const std::string& peer_name,
-                                       MoqtVersion version)
+                                       absl::string_view version)
     : QuicEndpointWithConnection(simulator, name, peer_name,
                                  quic::Perspective::IS_SERVER,
                                  quic::GetQuicVersionsForGenericSession()),
@@ -78,7 +79,7 @@
                      quic::test::crypto_test_utils::ProofSourceForTesting(),
                      quic::KeyExchangeSource::Default()),
       quic_session_(connection_.get(), false, nullptr, GenerateQuicConfig(),
-                    "moqt", &session_,
+                    std::string(kDefaultMoqtVersion), &session_,
                     /*visitor_owned=*/false, nullptr, &crypto_config_,
                     &compressed_certs_cache_),
       session_(&quic_session_,
diff --git a/quiche/quic/moqt/test_tools/moqt_simulator_harness.h b/quiche/quic/moqt/test_tools/moqt_simulator_harness.h
index 3293eb6..3430d36 100644
--- a/quiche/quic/moqt/test_tools/moqt_simulator_harness.h
+++ b/quiche/quic/moqt/test_tools/moqt_simulator_harness.h
@@ -8,12 +8,12 @@
 #include <optional>
 #include <string>
 
+#include "absl/strings/string_view.h"
 #include "quiche/quic/core/crypto/quic_compressed_certs_cache.h"
 #include "quiche/quic/core/crypto/quic_crypto_client_config.h"
 #include "quiche/quic/core/crypto/quic_crypto_server_config.h"
 #include "quiche/quic/core/quic_generic_session.h"
 #include "quiche/quic/core/quic_time.h"
-#include "quiche/quic/moqt/moqt_messages.h"
 #include "quiche/quic/moqt/moqt_session.h"
 #include "quiche/quic/test_tools/simulator/simulator.h"
 #include "quiche/quic/test_tools/simulator/test_harness.h"
@@ -25,7 +25,7 @@
  public:
   MoqtClientEndpoint(quic::simulator::Simulator* simulator,
                      const std::string& name, const std::string& peer_name,
-                     MoqtVersion version);
+                     absl::string_view version);
 
   MoqtSession* session() { return &session_; }
   quic::QuicGenericClientSession* quic_session() { return &quic_session_; }
@@ -41,7 +41,7 @@
  public:
   MoqtServerEndpoint(quic::simulator::Simulator* simulator,
                      const std::string& name, const std::string& peer_name,
-                     MoqtVersion version);
+                     absl::string_view version);
 
   MoqtSession* session() { return &session_; }
   quic::QuicGenericServerSession* quic_session() { return &quic_session_; }
diff --git a/quiche/quic/moqt/test_tools/moqt_test_message.h b/quiche/quic/moqt/test_tools/moqt_test_message.h
index 436f720..842495d 100644
--- a/quiche/quic/moqt/test_tools/moqt_test_message.h
+++ b/quiche/quic/moqt/test_tools/moqt_test_message.h
@@ -502,15 +502,15 @@
       // Should not send PATH or AUTHORITY.
       client_setup_.parameters.path = "";
       client_setup_.parameters.authority = "";
-      raw_packet_[2] = 0x24;  // adjust payload length (-17)
-      raw_packet_[6] = 0x02;  // only two parameters
+      raw_packet_[2] -= 17;   // adjust payload length
+      raw_packet_[3] = 0x02;  // only two parameters
       // Move MaxRequestId up in the packet.
-      memmove(raw_packet_ + 7, raw_packet_ + 13, 2);
+      memmove(raw_packet_ + 4, raw_packet_ + 10, 2);
       // Move MoqtImplementation up in the packet.
-      memmove(raw_packet_ + 9, raw_packet_ + 26,
+      memmove(raw_packet_ + 6, raw_packet_ + 23,
               kTestImplementationString.length() + 2);
-      raw_packet_[7] = 0x02;  // Diff from 0.
-      raw_packet_[9] = 0x05;  // Diff from 2.
+      raw_packet_[4] = 0x02;  // Diff from 0.
+      raw_packet_[6] = 0x05;  // Diff from 2.
       SetWireImage(raw_packet_, sizeof(raw_packet_) - 17);
     } else {
       SetWireImage(raw_packet_, sizeof(raw_packet_));
@@ -519,18 +519,6 @@
 
   bool EqualFieldValues(MessageStructuredData& values) const override {
     auto cast = std::get<MoqtClientSetup>(values);
-    if (cast.supported_versions.size() !=
-        client_setup_.supported_versions.size()) {
-      QUIC_LOG(INFO) << "CLIENT_SETUP number of supported versions mismatch";
-      return false;
-    }
-    for (uint64_t i = 0; i < cast.supported_versions.size(); ++i) {
-      // Listed versions are 1 and 2, in that order.
-      if (cast.supported_versions[i] != client_setup_.supported_versions[i]) {
-        QUIC_LOG(INFO) << "CLIENT_SETUP supported version mismatch";
-        return false;
-      }
-    }
     if (cast.parameters != client_setup_.parameters) {
       QUIC_LOG(INFO) << "CLIENT_SETUP parameter mismatch";
       return false;
@@ -540,9 +528,9 @@
 
   void ExpandVarints() override {
     if (!client_setup_.parameters.path.empty()) {
-      ExpandVarintsImpl("vvvvvv----vvvv---------vv---------------------------");
+      ExpandVarintsImpl("vvv----vvvv---------vv---------------------------");
     } else {
-      ExpandVarintsImpl("vvvvvvvv---------------------------");
+      ExpandVarintsImpl("vvvvv---------------------------");
     }
   }
 
@@ -555,9 +543,8 @@
   // string parameters in order. Unfortunately, this means that
   // kMoqtImplementation goes last even though it is always present, while
   // kPath and KAuthority aren't.
-  uint8_t raw_packet_[56] = {
-      0x20, 0x00, 0x35,                    // type, length
-      0x02, 0x01, 0x02,                    // versions
+  uint8_t raw_packet_[53] = {
+      0x20, 0x00, 0x32,                    // type, length
       0x04,                                // 4 parameters
       0x01, 0x04, 0x70, 0x61, 0x74, 0x68,  // path = "path"
       0x01, 0x32,                          // max_request_id = 50
@@ -568,8 +555,6 @@
       0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f,
       0x6e, 0x20, 0x54, 0x79, 0x70, 0x65};
   MoqtClientSetup client_setup_ = {
-      /*supported_versions=*/std::vector<MoqtVersion>(
-          {static_cast<MoqtVersion>(1), static_cast<MoqtVersion>(2)}),
       MoqtSessionParameters(quic::Perspective::IS_CLIENT, "path", "authority",
                             50),
   };
@@ -592,25 +577,22 @@
     return true;
   }
 
-  void ExpandVarints() override { ExpandVarintsImpl("vvvv"); }
+  void ExpandVarints() override { ExpandVarintsImpl("vvv"); }
 
   MessageStructuredData structured_data() const override {
     return TestMessageBase::MessageStructuredData(server_setup_);
   }
 
  private:
-  uint8_t raw_packet_[37] = {0x21, 0x00,
-                             0x22,  // type
-                             0x01,
-                             0x02,        // version, two parameters
-                             0x02, 0x32,  // max_subscribe_id = 50
+  uint8_t raw_packet_[36] = {0x21, 0x00, 0x21,  // type, length
+                             0x02,              // two parameters
+                             0x02, 0x32,        // max_subscribe_id = 50
                              // moqt_implementation:
                              0x05, 0x1c, 0x4d, 0x6f, 0x71, 0x20, 0x54, 0x65,
                              0x73, 0x74, 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65,
                              0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f,
                              0x6e, 0x20, 0x54, 0x79, 0x70, 0x65};
   MoqtServerSetup server_setup_ = {
-      /*selected_version=*/static_cast<MoqtVersion>(1),
       MoqtSessionParameters(quic::Perspective::IS_SERVER, 50),
   };
 };
diff --git a/quiche/quic/moqt/tools/moqt_client.cc b/quiche/quic/moqt/tools/moqt_client.cc
index 2742431..5140204 100644
--- a/quiche/quic/moqt/tools/moqt_client.cc
+++ b/quiche/quic/moqt/tools/moqt_client.cc
@@ -9,7 +9,9 @@
 #include <utility>
 
 #include "absl/status/status.h"
+#include "absl/status/statusor.h"
 #include "absl/strings/string_view.h"
+#include "absl/types/span.h"
 #include "quiche/quic/core/crypto/proof_verifier.h"
 #include "quiche/quic/core/http/quic_spdy_client_stream.h"
 #include "quiche/quic/core/http/web_transport_http3.h"
@@ -25,6 +27,7 @@
 #include "quiche/quic/tools/quic_name_lookup.h"
 #include "quiche/common/http/http_header_block.h"
 #include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/web_transport/web_transport_headers.h"
 
 namespace moqt {
 
@@ -83,6 +86,14 @@
   headers[":path"] = path;
   headers[":method"] = "CONNECT";
   headers[":protocol"] = "webtransport";
+  std::string version = std::string(kDefaultMoqtVersion);
+  absl::StatusOr<std::string> serialized_version =
+      webtransport::SerializeSubprotocolRequestHeader(
+          absl::MakeSpan(&version, 1));
+  if (!serialized_version.ok()) {
+    return serialized_version.status();
+  }
+  headers["wt-available-protocols"] = *serialized_version;
   stream->SendRequest(std::move(headers), "", false);
 
   quic::WebTransportHttp3* web_transport = stream->web_transport();
diff --git a/quiche/quic/moqt/tools/moqt_server.cc b/quiche/quic/moqt/tools/moqt_server.cc
index ffe3b85..d4e7052 100644
--- a/quiche/quic/moqt/tools/moqt_server.cc
+++ b/quiche/quic/moqt/tools/moqt_server.cc
@@ -9,9 +9,11 @@
 #include <string>
 #include <utility>
 
+#include "absl/algorithm/container.h"
 #include "absl/status/status.h"
 #include "absl/status/statusor.h"
 #include "absl/strings/string_view.h"
+#include "absl/types/span.h"
 #include "quiche/quic/core/crypto/proof_source.h"
 #include "quiche/quic/core/crypto/quic_crypto_server_config.h"
 #include "quiche/quic/core/crypto/quic_random.h"
@@ -87,6 +89,11 @@
                   connection_id_generator_) {
   dispatcher_.parameters().handler_factory =
       CreateWebTransportCallback(std::move(callback), event_loop_.get());
+  dispatcher_.parameters().subprotocol_callback =
+      +[](absl::Span<const absl::string_view> subprotocols) {
+        return absl::c_find(subprotocols, kDefaultMoqtVersion) -
+               subprotocols.begin();
+      };
 }
 
 absl::Status MoqtServer::CreateUDPSocketAndListen(