Prevent duplicate HTTP/3 control stream.
gfe-relnote: v99 only, not protected.
PiperOrigin-RevId: 263618125
Change-Id: I456046655711cae92f28aff4d648a15bd7184dc0
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc
index e6ec954..1da419e 100644
--- a/quic/core/http/quic_spdy_session.cc
+++ b/quic/core/http/quic_spdy_session.cc
@@ -880,6 +880,14 @@
switch (stream_type) {
case kControlStream: { // HTTP/3 control stream.
+ if (receive_control_stream_) {
+ QUIC_PEER_BUG
+ << "Received a duplicate control stream: Closing connection.";
+ // TODO(renjietang): Change to HTTP_STREAM_CREATION_ERROR.
+ CloseConnectionWithDetails(QUIC_INVALID_STREAM_ID,
+ "Control stream is received twice.");
+ return false;
+ }
auto receive_stream = QuicMakeUnique<QuicReceiveControlStream>(pending);
receive_control_stream_ = receive_stream.get();
RegisterStaticStream(std::move(receive_stream),
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index a9bcfcc..e58a651 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -2377,6 +2377,27 @@
EXPECT_FALSE(QuicSessionPeer::GetPendingStream(&session_, stream_id));
}
+TEST_P(QuicSpdySessionTestClient, DuplicateHttp3UnidirectionalStreams) {
+ if (!VersionHasStreamType(transport_version())) {
+ return;
+ }
+ QuicStreamId id1 =
+ GetNthServerInitiatedUnidirectionalStreamId(transport_version(), 0);
+ char type[] = {kControlStream};
+
+ QuicStreamFrame data1(id1, false, 0, QuicStringPiece(type, 1));
+ session_.OnStreamFrame(data1);
+ QuicStreamId id2 =
+ GetNthServerInitiatedUnidirectionalStreamId(transport_version(), 1);
+ QuicStreamFrame data2(id2, false, 0, QuicStringPiece(type, 1));
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_INVALID_STREAM_ID,
+ "Control stream is received twice.", _));
+ EXPECT_QUIC_PEER_BUG(
+ session_.OnStreamFrame(data2),
+ "Received a duplicate control stream: Closing connection.");
+}
+
} // namespace
} // namespace test
} // namespace quic