Increment Request ID on SubscribeNamespace.

PiperOrigin-RevId: 941211337
diff --git a/quiche/quic/moqt/moqt_session.cc b/quiche/quic/moqt/moqt_session.cc
index 1b4d14b..e9c62b6 100644
--- a/quiche/quic/moqt/moqt_session.cc
+++ b/quiche/quic/moqt/moqt_session.cc
@@ -320,6 +320,7 @@
   }
   MoqtSubscribeNamespace message;
   message.request_id = next_request_id_;
+  next_request_id_ += 2;
   message.track_namespace_prefix = prefix;
   message.subscribe_options = SubscribeNamespaceOption::kNamespace;
   message.parameters = parameters;
diff --git a/quiche/quic/moqt/moqt_session_test.cc b/quiche/quic/moqt/moqt_session_test.cc
index f704f8f..79f9e73 100644
--- a/quiche/quic/moqt/moqt_session_test.cc
+++ b/quiche/quic/moqt/moqt_session_test.cc
@@ -2979,6 +2979,114 @@
       testing::Mock::VerifyAndClearExpectations(&remote_track_visitor_));
 }
 
+TEST_F(MoqtSessionTest, IncrementRequestId) {
+  // Set up writable control stream.
+  webtransport::test::InMemoryStreamWithWriteBuffer control_stream(0);
+  EXPECT_CALL(mock_session_, GetNegotiatedSubprotocol)
+      .WillOnce(Return(std::string(kDefaultMoqtVersion)));
+  EXPECT_CALL(mock_session_, CanOpenNextOutgoingBidirectionalStream)
+      .WillOnce(Return(true));
+  EXPECT_CALL(mock_session_, OpenOutgoingBidirectionalStream)
+      .WillOnce(Return(&control_stream));
+  session_.OnSessionReady();
+  control_stream.write_buffer().clear();
+
+  // Helper lambda to parse request ID from written_data.
+  auto get_request_id =
+      [&](webtransport::test::InMemoryStreamWithWriteBuffer& stream) {
+        quiche::QuicheDataReader reader(stream.write_buffer());
+        uint64_t type;
+        uint16_t length;
+        uint64_t request_id;
+        bool type_read = reader.ReadVarInt62(&type);
+        bool length_read = reader.ReadUInt16(&length);
+        bool req_id_read = reader.ReadVarInt62(&request_id);
+        EXPECT_TRUE(type_read) << "Failed to read type, written_data.size()="
+                               << stream.write_buffer().length();
+        EXPECT_TRUE(length_read);
+        EXPECT_TRUE(req_id_read);
+        return request_id;
+      };
+
+  uint64_t next_request_id = 0;
+  // 1. SubscribeNamespace
+  webtransport::test::InMemoryStreamWithWriteBuffer sub_ns_stream(4);
+  EXPECT_CALL(mock_session_, CanOpenNextOutgoingBidirectionalStream())
+      .WillOnce(Return(true));
+  EXPECT_CALL(mock_session_, OpenOutgoingBidirectionalStream())
+      .WillOnce(Return(&sub_ns_stream));
+  TrackNamespace namespace1({"namespace1"});
+  std::unique_ptr<MoqtNamespaceTask> task1 = session_.SubscribeNamespace(
+      namespace1, SubscribeNamespaceOption::kNamespace, MessageParameters(),
+      [](std::variant<MessageParameters, MoqtRequestErrorInfo>) {});
+  ASSERT_NE(task1, nullptr);
+  EXPECT_EQ(get_request_id(sub_ns_stream), next_request_id);
+  next_request_id += 2;
+
+  // 2. PublishNamespace
+  TrackNamespace namespace2({"namespace2"});
+  bool p1 = session_.PublishNamespace(
+      namespace2, MessageParameters(),
+      [](std::variant<MessageParameters, MoqtRequestErrorInfo>) {},
+      [](MoqtRequestErrorInfo) {});
+  EXPECT_TRUE(p1);
+  EXPECT_EQ(next_request_id, get_request_id(control_stream));
+  next_request_id += 2;
+  control_stream.write_buffer().clear();
+
+  // 3. PublishNamespaceUpdate
+  MessageParameters params_update;
+  bool p_update = session_.PublishNamespaceUpdate(
+      namespace2, params_update,
+      [](std::variant<MessageParameters, MoqtRequestErrorInfo>) {});
+  EXPECT_TRUE(p_update);
+  EXPECT_EQ(get_request_id(control_stream), next_request_id);
+  next_request_id += 2;
+  control_stream.write_buffer().clear();
+
+  // 4. Subscribe
+  FullTrackName track_name1("namespace2", "track1");
+  bool s1 = session_.Subscribe(track_name1, &remote_track_visitor_,
+                               MessageParameters());
+  EXPECT_TRUE(s1);
+  EXPECT_EQ(get_request_id(control_stream), next_request_id);
+  next_request_id += 2;
+  control_stream.write_buffer().clear();
+
+  // 5. SubscribeUpdate
+  bool s_update = session_.SubscribeUpdate(
+      track_name1, MessageParameters(),
+      [](std::variant<MessageParameters, MoqtRequestErrorInfo>) {});
+  EXPECT_TRUE(s_update);
+  EXPECT_EQ(get_request_id(control_stream), next_request_id);
+  next_request_id += 2;
+  control_stream.write_buffer().clear();
+
+  // 6. Fetch
+  FullTrackName fetch_track("namespace2", "fetch_track");
+  bool f1 = session_.Fetch(
+      fetch_track, [](std::unique_ptr<MoqtFetchTask>) {}, Location(0, 0), 1,
+      std::nullopt, MessageParameters());
+  EXPECT_TRUE(f1);
+  EXPECT_EQ(get_request_id(control_stream), next_request_id);
+  next_request_id += 2;
+  control_stream.write_buffer().clear();
+
+  // 7. SubscribeNamespace (duplicating the first call)
+  webtransport::test::InMemoryStreamWithWriteBuffer sub_ns_stream_2(8);
+  EXPECT_CALL(mock_session_, CanOpenNextOutgoingBidirectionalStream)
+      .WillOnce(Return(true));
+  EXPECT_CALL(mock_session_, OpenOutgoingBidirectionalStream)
+      .WillOnce(Return(&sub_ns_stream_2));
+  TrackNamespace namespace_dup({"namespace_dup"});
+  std::unique_ptr<MoqtNamespaceTask> task_dup = session_.SubscribeNamespace(
+      namespace_dup, SubscribeNamespaceOption::kNamespace, MessageParameters(),
+      [](std::variant<MessageParameters, MoqtRequestErrorInfo>) {});
+  ASSERT_NE(task_dup, nullptr);
+  EXPECT_EQ(next_request_id, get_request_id(sub_ns_stream_2));
+  sub_ns_stream_2.write_buffer().clear();
+}
+
 }  // namespace test
 
 }  // namespace moqt
diff --git a/quiche/web_transport/test_tools/in_memory_stream.h b/quiche/web_transport/test_tools/in_memory_stream.h
index 0cfe34e..f22b9f9 100644
--- a/quiche/web_transport/test_tools/in_memory_stream.h
+++ b/quiche/web_transport/test_tools/in_memory_stream.h
@@ -118,6 +118,10 @@
     return absl::OkStatus();
   }
 
+  void ResetWithUserCode(StreamErrorCode) override {}
+  void ResetDueToInternalError() override {}
+  void MaybeResetDueToStreamObjectGone() override {}
+
   std::string& write_buffer() { return write_buffer_; }
 
  private: