gfe-relnote: Emit H3_SETTINGS_ERROR and H3_MISSING_SETTINGS when appropriate. Protected by gfe2_reloadable_flag_quic_enable_version_draft_25_v3 and gfe2_reloadable_flag_quic_enable_version_draft_27.
Emit H3_SETTINGS_ERROR when receiving a SETTINGS frame with duplicate setting
identifier. Emit H3_MISSING_SETTINGS when receiving a CANCEL_PUSH, GOAWAY,
MAX_PUSH_ID, PRIORITY_UPDATE, or unknown frame before a SETTINGS frame on the
control stream. Before this CL, H3_FRAME_UNEXPECTED was emitted for duplicate
SETTINGS or a PRIORITY_UPDATE or unknow frame before a SETTINGS, and no error
was signalled for a CANCEL_PUSH, GOAWAY, or MAX_PUSH_ID being the first frame on
the control stream.
If a DATA, HEADERS, or PUSH_PROMISE frame arrives on the control stream, keep
the error code H3_FRAME_UNEXPECTED, even if no SETTINGS frame has been received
yet. The specification has conflicting MUSTs, so either error code is
acceptable.
PiperOrigin-RevId: 304391962
Change-Id: If47b3940761e7955ee645bd59e4969302164a051
diff --git a/quic/core/http/http_decoder.cc b/quic/core/http/http_decoder.cc
index c1fa96c..87f9919 100644
--- a/quic/core/http/http_decoder.cc
+++ b/quic/core/http/http_decoder.cc
@@ -504,7 +504,8 @@
}
auto result = frame->values.insert({id, content});
if (!result.second) {
- RaiseError(QUIC_HTTP_FRAME_ERROR, "Duplicate setting identifier.");
+ RaiseError(QUIC_HTTP_DUPLICATE_SETTING_IDENTIFIER,
+ "Duplicate setting identifier.");
return false;
}
}
diff --git a/quic/core/http/http_decoder_test.cc b/quic/core/http/http_decoder_test.cc
index d97cbc1..2e8885c 100644
--- a/quic/core/http/http_decoder_test.cc
+++ b/quic/core/http/http_decoder_test.cc
@@ -448,7 +448,8 @@
EXPECT_EQ(input.size(), ProcessInput(input));
- EXPECT_THAT(decoder_.error(), IsError(QUIC_HTTP_FRAME_ERROR));
+ EXPECT_THAT(decoder_.error(),
+ IsError(QUIC_HTTP_DUPLICATE_SETTING_IDENTIFIER));
EXPECT_EQ("Duplicate setting identifier.", decoder_.error_detail());
}
diff --git a/quic/core/http/quic_receive_control_stream.cc b/quic/core/http/quic_receive_control_stream.cc
index 4e361f2..17c6ecc 100644
--- a/quic/core/http/quic_receive_control_stream.cc
+++ b/quic/core/http/quic_receive_control_stream.cc
@@ -63,6 +63,13 @@
spdy_session()->debug_visitor()->OnCancelPushFrameReceived(frame);
}
+ if (!settings_frame_received_) {
+ stream_delegate()->OnStreamError(
+ QUIC_HTTP_MISSING_SETTINGS_FRAME,
+ "CANCEL_PUSH frame received before SETTINGS.");
+ return false;
+ }
+
// TODO(b/151841240): Handle CANCEL_PUSH frames instead of ignoring them.
return true;
}
@@ -72,6 +79,13 @@
spdy_session()->debug_visitor()->OnMaxPushIdFrameReceived(frame);
}
+ if (!settings_frame_received_) {
+ stream_delegate()->OnStreamError(
+ QUIC_HTTP_MISSING_SETTINGS_FRAME,
+ "MAX_PUSH_ID frame received before SETTINGS.");
+ return false;
+ }
+
if (spdy_session()->perspective() == Perspective::IS_CLIENT) {
OnWrongFrame("Max Push Id");
return false;
@@ -84,11 +98,16 @@
}
bool QuicReceiveControlStream::OnGoAwayFrame(const GoAwayFrame& frame) {
- // TODO(bnc): Check if SETTINGS frame has been received.
if (spdy_session()->debug_visitor()) {
spdy_session()->debug_visitor()->OnGoAwayFrameReceived(frame);
}
+ if (!settings_frame_received_) {
+ stream_delegate()->OnStreamError(QUIC_HTTP_MISSING_SETTINGS_FRAME,
+ "GOAWAY frame received before SETTINGS.");
+ return false;
+ }
+
if (spdy_session()->perspective() == Perspective::IS_SERVER) {
OnWrongFrame("Go Away");
return false;
@@ -190,7 +209,7 @@
QuicByteCount /*header_length*/) {
if (!settings_frame_received_) {
stream_delegate()->OnStreamError(
- QUIC_HTTP_INVALID_FRAME_SEQUENCE_ON_CONTROL_STREAM,
+ QUIC_HTTP_MISSING_SETTINGS_FRAME,
"PRIORITY_UPDATE frame received before SETTINGS.");
return false;
}
@@ -250,9 +269,8 @@
}
if (!settings_frame_received_) {
- stream_delegate()->OnStreamError(
- QUIC_HTTP_INVALID_FRAME_SEQUENCE_ON_CONTROL_STREAM,
- "Unknown frame received before SETTINGS.");
+ stream_delegate()->OnStreamError(QUIC_HTTP_MISSING_SETTINGS_FRAME,
+ "Unknown frame received before SETTINGS.");
return false;
}
diff --git a/quic/core/http/quic_receive_control_stream_test.cc b/quic/core/http/quic_receive_control_stream_test.cc
index d9583ae..2914c92 100644
--- a/quic/core/http/quic_receive_control_stream_test.cc
+++ b/quic/core/http/quic_receive_control_stream_test.cc
@@ -256,7 +256,7 @@
EXPECT_CALL(
*connection_,
- CloseConnection(QUIC_HTTP_INVALID_FRAME_SEQUENCE_ON_CONTROL_STREAM,
+ CloseConnection(QUIC_HTTP_MISSING_SETTINGS_FRAME,
"PRIORITY_UPDATE frame received before SETTINGS.", _))
.WillOnce(
Invoke(connection_, &MockQuicConnection::ReallyCloseConnection));
@@ -270,15 +270,25 @@
StrictMock<MockHttp3DebugVisitor> debug_visitor;
session_.set_debug_visitor(&debug_visitor);
- GoAwayFrame goaway;
- goaway.stream_id = 0x00;
+ QuicStreamOffset offset = 1;
+
+ // Receive SETTINGS frame.
+ SettingsFrame settings;
+ std::string settings_frame = EncodeSettings(settings);
+ EXPECT_CALL(debug_visitor, OnSettingsFrameReceived(settings));
+ receive_control_stream_->OnStreamFrame(
+ QuicStreamFrame(receive_control_stream_->id(), /* fin = */ false, offset,
+ settings_frame));
+ offset += settings_frame.length();
+
+ GoAwayFrame goaway{/* stream_id = */ 0};
std::unique_ptr<char[]> buffer;
QuicByteCount header_length =
HttpEncoder::SerializeGoAwayFrame(goaway, &buffer);
std::string data = std::string(buffer.get(), header_length);
- QuicStreamFrame frame(receive_control_stream_->id(), false, 1, data);
+ QuicStreamFrame frame(receive_control_stream_->id(), false, offset, data);
EXPECT_FALSE(session_.http3_goaway_received());
EXPECT_CALL(debug_visitor, OnGoAwayFrameReceived(goaway));
@@ -370,16 +380,34 @@
/* offset = */ 1 + settings_frame.size(), unknown_frame));
}
+TEST_P(QuicReceiveControlStreamTest, CancelPushFrameBeforeSettings) {
+ std::string cancel_push_frame = quiche::QuicheTextUtils::HexDecode(
+ "03" // type CANCEL_PUSH
+ "01" // payload length
+ "01"); // push ID
+
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_HTTP_MISSING_SETTINGS_FRAME,
+ "CANCEL_PUSH frame received before SETTINGS.", _))
+ .WillOnce(
+ Invoke(connection_, &MockQuicConnection::ReallyCloseConnection));
+ EXPECT_CALL(*connection_, SendConnectionClosePacket(_, _));
+ EXPECT_CALL(session_, OnConnectionClosed(_, _));
+
+ receive_control_stream_->OnStreamFrame(
+ QuicStreamFrame(receive_control_stream_->id(), /* fin = */ false,
+ /* offset = */ 1, cancel_push_frame));
+}
+
TEST_P(QuicReceiveControlStreamTest, UnknownFrameBeforeSettings) {
std::string unknown_frame = quiche::QuicheTextUtils::HexDecode(
"21" // reserved frame type
"03" // payload length
"666f6f"); // payload "foo"
- EXPECT_CALL(
- *connection_,
- CloseConnection(QUIC_HTTP_INVALID_FRAME_SEQUENCE_ON_CONTROL_STREAM,
- "Unknown frame received before SETTINGS.", _))
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_HTTP_MISSING_SETTINGS_FRAME,
+ "Unknown frame received before SETTINGS.", _))
.WillOnce(
Invoke(connection_, &MockQuicConnection::ReallyCloseConnection));
EXPECT_CALL(*connection_, SendConnectionClosePacket(_, _));
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 164e6a1..2007806 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -2797,14 +2797,22 @@
EXPECT_EQ(receive_control_stream_id,
QuicSpdySessionPeer::GetReceiveControlStream(&session_)->id());
+ // First frame has to be SETTINGS.
+ std::string serialized_settings = EncodeSettings({});
+ QuicStreamFrame data2(receive_control_stream_id, /* fin = */ false, offset,
+ serialized_settings);
+ offset += serialized_settings.length();
+ EXPECT_CALL(debug_visitor, OnSettingsFrameReceived(_));
+ session_.OnStreamFrame(data2);
+
CancelPushFrame cancel_push{/* push_id = */ 0};
std::unique_ptr<char[]> buffer;
auto frame_length =
HttpEncoder::SerializeCancelPushFrame(cancel_push, &buffer);
- QuicStreamFrame data2(receive_control_stream_id, /* fin = */ false, offset,
+ QuicStreamFrame data3(receive_control_stream_id, /* fin = */ false, offset,
quiche::QuicheStringPiece(buffer.get(), frame_length));
EXPECT_CALL(debug_visitor, OnCancelPushFrameReceived(_));
- session_.OnStreamFrame(data2);
+ session_.OnStreamFrame(data3);
}
TEST_P(QuicSpdySessionTestServer, ServerPushEnabledDefaultValue) {
@@ -2973,6 +2981,9 @@
return;
}
+ StrictMock<MockHttp3DebugVisitor> debug_visitor;
+ session_.set_debug_visitor(&debug_visitor);
+
// Create control stream.
QuicStreamId receive_control_stream_id =
GetNthClientInitiatedUnidirectionalStreamId(transport_version(), 3);
@@ -2982,17 +2993,28 @@
QuicStreamFrame data1(receive_control_stream_id, /* fin = */ false, offset,
stream_type);
offset += stream_type.length();
+ EXPECT_CALL(debug_visitor,
+ OnPeerControlStreamCreated(receive_control_stream_id));
session_.OnStreamFrame(data1);
EXPECT_EQ(receive_control_stream_id,
QuicSpdySessionPeer::GetReceiveControlStream(&session_)->id());
+ // First frame has to be SETTINGS.
+ std::string serialized_settings = EncodeSettings({});
+ QuicStreamFrame data2(receive_control_stream_id, /* fin = */ false, offset,
+ serialized_settings);
+ offset += serialized_settings.length();
+ EXPECT_CALL(debug_visitor, OnSettingsFrameReceived(_));
+ session_.OnStreamFrame(data2);
+
CancelPushFrame cancel_push{/* push_id = */ 0};
std::unique_ptr<char[]> buffer;
auto frame_length =
HttpEncoder::SerializeCancelPushFrame(cancel_push, &buffer);
- QuicStreamFrame data2(receive_control_stream_id, /* fin = */ false, offset,
+ QuicStreamFrame data3(receive_control_stream_id, /* fin = */ false, offset,
quiche::QuicheStringPiece(buffer.get(), frame_length));
- session_.OnStreamFrame(data2);
+ EXPECT_CALL(debug_visitor, OnCancelPushFrameReceived(_));
+ session_.OnStreamFrame(data3);
}
TEST_P(QuicSpdySessionTestClient, SendInitialMaxPushIdIfSet) {
diff --git a/quic/core/quic_error_codes.cc b/quic/core/quic_error_codes.cc
index d897aac..db97d32 100644
--- a/quic/core/quic_error_codes.cc
+++ b/quic/core/quic_error_codes.cc
@@ -175,6 +175,8 @@
RETURN_STRING_LITERAL(QUIC_HTTP_SERVER_INITIATED_BIDIRECTIONAL_STREAM);
RETURN_STRING_LITERAL(QUIC_HTTP_STREAM_WRONG_DIRECTION);
RETURN_STRING_LITERAL(QUIC_HTTP_CLOSED_CRITICAL_STREAM);
+ RETURN_STRING_LITERAL(QUIC_HTTP_MISSING_SETTINGS_FRAME);
+ RETURN_STRING_LITERAL(QUIC_HTTP_DUPLICATE_SETTING_IDENTIFIER);
RETURN_STRING_LITERAL(QUIC_HPACK_INDEX_VARINT_ERROR);
RETURN_STRING_LITERAL(QUIC_HPACK_NAME_LENGTH_VARINT_ERROR);
RETURN_STRING_LITERAL(QUIC_HPACK_VALUE_LENGTH_VARINT_ERROR);
diff --git a/quic/core/quic_error_codes.h b/quic/core/quic_error_codes.h
index dc575d3..0548e9b 100644
--- a/quic/core/quic_error_codes.h
+++ b/quic/core/quic_error_codes.h
@@ -363,8 +363,7 @@
// An invalid sequence of frames normally allowed on a request stream is
// received.
QUIC_HTTP_INVALID_FRAME_SEQUENCE_ON_SPDY_STREAM = 151,
- // An invalid sequence of frames normally allowed on the control stream is
- // received.
+ // A second SETTINGS frame is received on the control stream.
QUIC_HTTP_INVALID_FRAME_SEQUENCE_ON_CONTROL_STREAM = 152,
// A second instance of a unidirectional stream of a certain type is created.
QUIC_HTTP_DUPLICATE_UNIDIRECTIONAL_STREAM = 153,
@@ -376,6 +375,10 @@
// Peer closes one of the six critical unidirectional streams (control, QPACK
// encoder or decoder, in either direction).
QUIC_HTTP_CLOSED_CRITICAL_STREAM = 156,
+ // The first frame received on the control stream is not a SETTINGS frame.
+ QUIC_HTTP_MISSING_SETTINGS_FRAME = 157,
+ // The received SETTINGS frame contains duplicate setting identifiers.
+ QUIC_HTTP_DUPLICATE_SETTING_IDENTIFIER = 158,
// HPACK header block decoding errors.
// Index varint beyond implementation limit.
@@ -412,7 +415,7 @@
QUIC_HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT = 150,
// No error. Used as bound while iterating.
- QUIC_LAST_ERROR = 157,
+ QUIC_LAST_ERROR = 159,
};
// QuicErrorCodes is encoded as four octets on-the-wire when doing Google QUIC,
// or a varint62 when doing IETF QUIC. Ensure that its value does not exceed
diff --git a/quic/core/quic_types.cc b/quic/core/quic_types.cc
index 5d69250..ce24d57 100644
--- a/quic/core/quic_types.cc
+++ b/quic/core/quic_types.cc
@@ -484,6 +484,14 @@
return {false,
{static_cast<uint64_t>(
QuicHttp3ErrorCode::IETF_QUIC_HTTP3_CLOSED_CRITICAL_STREAM)}};
+ case QUIC_HTTP_MISSING_SETTINGS_FRAME:
+ return {false,
+ {static_cast<uint64_t>(
+ QuicHttp3ErrorCode::IETF_QUIC_HTTP3_MISSING_SETTINGS)}};
+ case QUIC_HTTP_DUPLICATE_SETTING_IDENTIFIER:
+ return {false,
+ {static_cast<uint64_t>(
+ QuicHttp3ErrorCode::IETF_QUIC_HTTP3_SETTINGS_ERROR)}};
case QUIC_HPACK_INDEX_VARINT_ERROR:
return {false, {static_cast<uint64_t>(QUIC_HPACK_INDEX_VARINT_ERROR)}};
case QUIC_HPACK_NAME_LENGTH_VARINT_ERROR: