Parse ACCEPT_CH frame in HttpDecoder.
Protected by FLAGS_quic_reloadable_flag_quic_parse_accept_ch_frame.
PiperOrigin-RevId: 347054156
Change-Id: If456d341c156b0f761f63558f95d5664d4a0a193
diff --git a/quic/core/http/http_decoder.cc b/quic/core/http/http_decoder.cc
index 590f2fc..dc67302 100644
--- a/quic/core/http/http_decoder.cc
+++ b/quic/core/http/http_decoder.cc
@@ -238,9 +238,20 @@
QUIC_CODE_COUNT_N(quic_new_priority_update_frame, 2, 2);
continue_processing =
visitor_->OnPriorityUpdateFrameStart(header_length);
- break;
+ } else {
+ continue_processing = visitor_->OnUnknownFrameStart(
+ current_frame_type_, header_length, current_frame_length_);
}
- ABSL_FALLTHROUGH_INTENDED;
+ break;
+ case static_cast<uint64_t>(HttpFrameType::ACCEPT_CH):
+ if (GetQuicReloadableFlag(quic_parse_accept_ch_frame)) {
+ QUIC_RELOADABLE_FLAG_COUNT(quic_parse_accept_ch_frame);
+ continue_processing = visitor_->OnAcceptChFrameStart(header_length);
+ } else {
+ continue_processing = visitor_->OnUnknownFrameStart(
+ current_frame_type_, header_length, current_frame_length_);
+ }
+ break;
default:
continue_processing = visitor_->OnUnknownFrameStart(
current_frame_type_, header_length, current_frame_length_);
@@ -374,19 +385,23 @@
// TODO(bnc): Avoid buffering if the entire frame is present, and
// instead parse directly out of |reader|.
BufferFramePayload(reader);
- break;
+ } else {
+ continue_processing = HandleUnknownFramePayload(reader);
}
- ABSL_FALLTHROUGH_INTENDED;
+ break;
+ }
+ case static_cast<uint64_t>(HttpFrameType::ACCEPT_CH): {
+ if (GetQuicReloadableFlag(quic_parse_accept_ch_frame)) {
+ // TODO(bnc): Avoid buffering if the entire frame is present, and
+ // instead parse directly out of |reader|.
+ BufferFramePayload(reader);
+ } else {
+ continue_processing = HandleUnknownFramePayload(reader);
+ }
+ break;
}
default: {
- QuicByteCount bytes_to_read = std::min<QuicByteCount>(
- remaining_frame_length_, reader->BytesRemaining());
- absl::string_view payload;
- bool success = reader->ReadStringPiece(&payload, bytes_to_read);
- DCHECK(success);
- DCHECK(!payload.empty());
- continue_processing = visitor_->OnUnknownFramePayload(payload);
- remaining_frame_length_ -= payload.length();
+ continue_processing = HandleUnknownFramePayload(reader);
break;
}
}
@@ -492,14 +507,28 @@
return false;
}
continue_processing = visitor_->OnPriorityUpdateFrame(frame);
- break;
+ } else {
+ continue_processing = visitor_->OnUnknownFrameEnd();
}
- ABSL_FALLTHROUGH_INTENDED;
- }
- default: {
- continue_processing = visitor_->OnUnknownFrameEnd();
break;
}
+ case static_cast<uint64_t>(HttpFrameType::ACCEPT_CH): {
+ if (GetQuicReloadableFlag(quic_parse_accept_ch_frame)) {
+ // TODO(bnc): Avoid buffering if the entire frame is present, and
+ // instead parse directly out of |reader|.
+ AcceptChFrame frame;
+ QuicDataReader reader(buffer_.data(), current_frame_length_);
+ if (!ParseAcceptChFrame(&reader, &frame)) {
+ return false;
+ }
+ continue_processing = visitor_->OnAcceptChFrame(frame);
+ } else {
+ continue_processing = visitor_->OnUnknownFrameEnd();
+ }
+ break;
+ }
+ default:
+ continue_processing = visitor_->OnUnknownFrameEnd();
}
current_length_field_length_ = 0;
@@ -508,6 +537,17 @@
return continue_processing;
}
+bool HttpDecoder::HandleUnknownFramePayload(QuicDataReader* reader) {
+ QuicByteCount bytes_to_read = std::min<QuicByteCount>(
+ remaining_frame_length_, reader->BytesRemaining());
+ absl::string_view payload;
+ bool success = reader->ReadStringPiece(&payload, bytes_to_read);
+ DCHECK(success);
+ DCHECK(!payload.empty());
+ remaining_frame_length_ -= payload.length();
+ return visitor_->OnUnknownFramePayload(payload);
+}
+
void HttpDecoder::DiscardFramePayload(QuicDataReader* reader) {
QuicByteCount bytes_to_read = std::min<QuicByteCount>(
remaining_frame_length_, reader->BytesRemaining());
@@ -647,6 +687,26 @@
return true;
}
+bool HttpDecoder::ParseAcceptChFrame(QuicDataReader* reader,
+ AcceptChFrame* frame) {
+ absl::string_view origin;
+ absl::string_view value;
+ while (!reader->IsDoneReading()) {
+ if (!reader->ReadStringPieceVarInt62(&origin)) {
+ RaiseError(QUIC_HTTP_FRAME_ERROR, "Unable to read ACCEPT_CH origin.");
+ return false;
+ }
+ if (!reader->ReadStringPieceVarInt62(&value)) {
+ RaiseError(QUIC_HTTP_FRAME_ERROR, "Unable to read ACCEPT_CH value.");
+ return false;
+ }
+ // Copy data.
+ frame->entries.push_back({std::string(origin.data(), origin.size()),
+ std::string(value.data(), value.size())});
+ }
+ return true;
+}
+
QuicByteCount HttpDecoder::MaxFrameLength(uint64_t frame_type) {
switch (frame_type) {
case static_cast<uint64_t>(HttpFrameType::CANCEL_PUSH):
@@ -664,6 +724,9 @@
case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE_REQUEST_STREAM):
// This limit is arbitrary.
return 1024 * 1024;
+ case static_cast<uint64_t>(HttpFrameType::ACCEPT_CH):
+ // This limit is arbitrary.
+ return 1024 * 1024;
default:
// Other frames require no data buffering, so it's safe to have no limit.
return std::numeric_limits<QuicByteCount>::max();
diff --git a/quic/core/http/http_decoder.h b/quic/core/http/http_decoder.h
index 479f444..86f3f58 100644
--- a/quic/core/http/http_decoder.h
+++ b/quic/core/http/http_decoder.h
@@ -102,6 +102,13 @@
// Called when a PRIORITY_UPDATE frame has been successfully parsed.
virtual bool OnPriorityUpdateFrame(const PriorityUpdateFrame& frame) = 0;
+ // Called when an ACCEPT_CH frame has been received.
+ // |header_length| contains ACCEPT_CH frame length and payload length.
+ virtual bool OnAcceptChFrameStart(QuicByteCount header_length) = 0;
+
+ // Called when an ACCEPT_CH frame has been successfully parsed.
+ virtual bool OnAcceptChFrame(const AcceptChFrame& frame) = 0;
+
// Called when a frame of unknown type |frame_type| has been received.
// Frame type might be reserved, Visitor must make sure to ignore.
// |header_length| and |payload_length| are the length of the frame header
@@ -174,6 +181,11 @@
// had been parsed completely. Returns whether processing should continue.
bool FinishParsing();
+ // Read payload of unknown frame from |reader| and call
+ // Visitor::OnUnknownFramePayload(). Returns true decoding should continue,
+ // false if it should be paused.
+ bool HandleUnknownFramePayload(QuicDataReader* reader);
+
// Discards any remaining frame payload from |reader|.
void DiscardFramePayload(QuicDataReader* reader);
@@ -207,6 +219,9 @@
bool ParseNewPriorityUpdateFrame(QuicDataReader* reader,
PriorityUpdateFrame* frame);
+ // Parses the payload of an ACCEPT_CH frame from |reader| into |frame|.
+ bool ParseAcceptChFrame(QuicDataReader* reader, AcceptChFrame* frame);
+
// Returns the max frame size of a given |frame_type|.
QuicByteCount MaxFrameLength(uint64_t frame_type);
diff --git a/quic/core/http/http_decoder_test.cc b/quic/core/http/http_decoder_test.cc
index 68f9d7f..68248ea 100644
--- a/quic/core/http/http_decoder_test.cc
+++ b/quic/core/http/http_decoder_test.cc
@@ -21,6 +21,7 @@
#include "net/third_party/quiche/src/common/platform/api/quiche_text_utils.h"
using ::testing::_;
+using ::testing::AnyNumber;
using ::testing::Eq;
using ::testing::InSequence;
using ::testing::Return;
@@ -104,6 +105,12 @@
(override));
MOCK_METHOD(bool,
+ OnAcceptChFrameStart,
+ (QuicByteCount header_length),
+ (override));
+ MOCK_METHOD(bool, OnAcceptChFrame, (const AcceptChFrame& frame), (override));
+
+ MOCK_METHOD(bool,
OnUnknownFrameStart,
(uint64_t frame_type,
QuicByteCount header_length,
@@ -138,6 +145,8 @@
ON_CALL(visitor_, OnPriorityUpdateFrameStart(_))
.WillByDefault(Return(true));
ON_CALL(visitor_, OnPriorityUpdateFrame(_)).WillByDefault(Return(true));
+ ON_CALL(visitor_, OnAcceptChFrameStart(_)).WillByDefault(Return(true));
+ ON_CALL(visitor_, OnAcceptChFrame(_)).WillByDefault(Return(true));
ON_CALL(visitor_, OnUnknownFrameStart(_, _, _)).WillByDefault(Return(true));
ON_CALL(visitor_, OnUnknownFramePayload(_)).WillByDefault(Return(true));
ON_CALL(visitor_, OnUnknownFrameEnd()).WillByDefault(Return(true));
@@ -1192,6 +1201,153 @@
}
}
+TEST_F(HttpDecoderTest, AcceptChFrame) {
+ if (!GetQuicReloadableFlag(quic_parse_accept_ch_frame)) {
+ return;
+ }
+
+ InSequence s;
+ std::string input1 = absl::HexStringToBytes(
+ "4089" // type (ACCEPT_CH)
+ "00"); // length
+
+ AcceptChFrame accept_ch1;
+
+ // Visitor pauses processing.
+ EXPECT_CALL(visitor_, OnAcceptChFrameStart(3)).WillOnce(Return(false));
+ absl::string_view remaining_input(input1);
+ QuicByteCount processed_bytes =
+ ProcessInputWithGarbageAppended(remaining_input);
+ EXPECT_EQ(3u, processed_bytes);
+ remaining_input = remaining_input.substr(processed_bytes);
+
+ EXPECT_CALL(visitor_, OnAcceptChFrame(accept_ch1)).WillOnce(Return(false));
+ processed_bytes = ProcessInputWithGarbageAppended(remaining_input);
+ EXPECT_EQ(remaining_input.size(), processed_bytes);
+ EXPECT_THAT(decoder_.error(), IsQuicNoError());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ // Process the full frame.
+ EXPECT_CALL(visitor_, OnAcceptChFrameStart(3));
+ EXPECT_CALL(visitor_, OnAcceptChFrame(accept_ch1));
+ EXPECT_EQ(input1.size(), ProcessInput(input1));
+ EXPECT_THAT(decoder_.error(), IsQuicNoError());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ // Process the frame incrementally.
+ EXPECT_CALL(visitor_, OnAcceptChFrameStart(3));
+ EXPECT_CALL(visitor_, OnAcceptChFrame(accept_ch1));
+ ProcessInputCharByChar(input1);
+ EXPECT_THAT(decoder_.error(), IsQuicNoError());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ std::string input2 = absl::HexStringToBytes(
+ "4089" // type (ACCEPT_CH)
+ "08" // length
+ "03" // length of origin
+ "666f6f" // origin "foo"
+ "03" // length of value
+ "626172"); // value "bar"
+
+ AcceptChFrame accept_ch2;
+ accept_ch2.entries.push_back({"foo", "bar"});
+
+ // Visitor pauses processing.
+ EXPECT_CALL(visitor_, OnAcceptChFrameStart(3)).WillOnce(Return(false));
+ remaining_input = input2;
+ processed_bytes = ProcessInputWithGarbageAppended(remaining_input);
+ EXPECT_EQ(3u, processed_bytes);
+ remaining_input = remaining_input.substr(processed_bytes);
+
+ EXPECT_CALL(visitor_, OnAcceptChFrame(accept_ch2)).WillOnce(Return(false));
+ processed_bytes = ProcessInputWithGarbageAppended(remaining_input);
+ EXPECT_EQ(remaining_input.size(), processed_bytes);
+ EXPECT_THAT(decoder_.error(), IsQuicNoError());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ // Process the full frame.
+ EXPECT_CALL(visitor_, OnAcceptChFrameStart(3));
+ EXPECT_CALL(visitor_, OnAcceptChFrame(accept_ch2));
+ EXPECT_EQ(input2.size(), ProcessInput(input2));
+ EXPECT_THAT(decoder_.error(), IsQuicNoError());
+ EXPECT_EQ("", decoder_.error_detail());
+
+ // Process the frame incrementally.
+ EXPECT_CALL(visitor_, OnAcceptChFrameStart(3));
+ EXPECT_CALL(visitor_, OnAcceptChFrame(accept_ch2));
+ ProcessInputCharByChar(input2);
+ EXPECT_THAT(decoder_.error(), IsQuicNoError());
+ EXPECT_EQ("", decoder_.error_detail());
+}
+
+TEST_F(HttpDecoderTest, CorruptAcceptChFrame) {
+ if (!GetQuicReloadableFlag(quic_parse_accept_ch_frame)) {
+ // TODO(bnc): merge this test into HttpDecoderTest.CorruptFrame when
+ // deprecating flag.
+ return;
+ }
+
+ EXPECT_CALL(visitor_, OnAcceptChFrameStart(_)).Times(AnyNumber());
+
+ struct {
+ const char* const input;
+ const char* const error_message;
+ } kTestData[] = {{"\x40\x89" // type (ACCEPT_CH)
+ "\x01" // length
+ "\x40", // first byte of two-byte varint origin length
+ "Unable to read ACCEPT_CH origin."},
+ {"\x40\x89" // type (ACCEPT_CH)
+ "\x01" // length
+ "\x05", // valid origin length but no origin string
+ "Unable to read ACCEPT_CH origin."},
+ {"\x40\x89" // type (ACCEPT_CH)
+ "\x04" // length
+ "\x05" // valid origin length
+ "foo", // payload ends before origin ends
+ "Unable to read ACCEPT_CH origin."},
+ {"\x40\x89" // type (ACCEPT_CH)
+ "\x04" // length
+ "\x03" // valid origin length
+ "foo", // payload ends at end of origin: no value
+ "Unable to read ACCEPT_CH value."},
+ {"\x40\x89" // type (ACCEPT_CH)
+ "\x05" // length
+ "\x03" // valid origin length
+ "foo" // payload ends at end of origin: no value
+ "\x40", // first byte of two-byte varint value length
+ "Unable to read ACCEPT_CH value."},
+ {"\x40\x89" // type (ACCEPT_CH)
+ "\x08" // length
+ "\x03" // valid origin length
+ "foo" // origin
+ "\x05" // valid value length
+ "bar", // payload ends before value ends
+ "Unable to read ACCEPT_CH value."}};
+
+ for (const auto& test_data : kTestData) {
+ {
+ HttpDecoder decoder(&visitor_);
+ EXPECT_CALL(visitor_, OnError(&decoder));
+
+ absl::string_view input(test_data.input);
+ decoder.ProcessInput(input.data(), input.size());
+ EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR));
+ EXPECT_EQ(test_data.error_message, decoder.error_detail());
+ }
+ {
+ HttpDecoder decoder(&visitor_);
+ EXPECT_CALL(visitor_, OnError(&decoder));
+
+ absl::string_view input(test_data.input);
+ for (auto c : input) {
+ decoder.ProcessInput(&c, 1);
+ }
+ EXPECT_THAT(decoder.error(), IsError(QUIC_HTTP_FRAME_ERROR));
+ EXPECT_EQ(test_data.error_message, decoder.error_detail());
+ }
+ }
+}
+
TEST_F(HttpDecoderTest, DecodeSettings) {
std::string input = absl::HexStringToBytes(
"04" // type (SETTINGS)
diff --git a/quic/core/http/quic_receive_control_stream.cc b/quic/core/http/quic_receive_control_stream.cc
index cc6857f..17c588d 100644
--- a/quic/core/http/quic_receive_control_stream.cc
+++ b/quic/core/http/quic_receive_control_stream.cc
@@ -252,6 +252,34 @@
return true;
}
+bool QuicReceiveControlStream::OnAcceptChFrameStart(
+ QuicByteCount /* header_length */) {
+ if (!settings_frame_received_) {
+ stream_delegate()->OnStreamError(
+ QUIC_HTTP_MISSING_SETTINGS_FRAME,
+ "ACCEPT_CH frame received before SETTINGS.");
+ return false;
+ }
+
+ if (spdy_session()->perspective() == Perspective::IS_SERVER) {
+ OnWrongFrame("ACCEPT_CH");
+ return false;
+ }
+
+ return true;
+}
+
+bool QuicReceiveControlStream::OnAcceptChFrame(const AcceptChFrame& frame) {
+ DCHECK_EQ(Perspective::IS_CLIENT, spdy_session()->perspective());
+
+ if (spdy_session()->debug_visitor()) {
+ spdy_session()->debug_visitor()->OnAcceptChFrameReceived(frame);
+ }
+
+ spdy_session()->OnAcceptChFrame(frame);
+ return true;
+}
+
bool QuicReceiveControlStream::OnUnknownFrameStart(
uint64_t frame_type,
QuicByteCount /*header_length*/,
diff --git a/quic/core/http/quic_receive_control_stream.h b/quic/core/http/quic_receive_control_stream.h
index 2654bb8..728aa31 100644
--- a/quic/core/http/quic_receive_control_stream.h
+++ b/quic/core/http/quic_receive_control_stream.h
@@ -56,6 +56,8 @@
bool OnPushPromiseFrameEnd() override;
bool OnPriorityUpdateFrameStart(QuicByteCount header_length) override;
bool OnPriorityUpdateFrame(const PriorityUpdateFrame& frame) override;
+ bool OnAcceptChFrameStart(QuicByteCount header_length) override;
+ bool OnAcceptChFrame(const AcceptChFrame& frame) override;
bool OnUnknownFrameStart(uint64_t frame_type,
QuicByteCount header_length,
QuicByteCount payload_length) override;
diff --git a/quic/core/http/quic_receive_control_stream_test.cc b/quic/core/http/quic_receive_control_stream_test.cc
index 51948e6..949a0f4 100644
--- a/quic/core/http/quic_receive_control_stream_test.cc
+++ b/quic/core/http/quic_receive_control_stream_test.cc
@@ -396,6 +396,70 @@
/* offset = */ 1, cancel_push_frame));
}
+TEST_P(QuicReceiveControlStreamTest, AcceptChFrameBeforeSettings) {
+ std::string accept_ch_frame = absl::HexStringToBytes(
+ "4089" // type (ACCEPT_CH)
+ "00"); // length
+
+ EXPECT_CALL(*connection_,
+ CloseConnection(QUIC_HTTP_MISSING_SETTINGS_FRAME,
+ GetQuicReloadableFlag(quic_parse_accept_ch_frame)
+ ? "ACCEPT_CH frame received before SETTINGS."
+ : "Unknown 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, accept_ch_frame));
+}
+
+TEST_P(QuicReceiveControlStreamTest, ReceiveAcceptChFrame) {
+ StrictMock<MockHttp3DebugVisitor> debug_visitor;
+ session_.set_debug_visitor(&debug_visitor);
+
+ const QuicStreamId id = receive_control_stream_->id();
+ 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(id, /* fin = */ false, offset, settings_frame));
+ offset += settings_frame.length();
+
+ // Receive ACCEPT_CH frame.
+ std::string accept_ch_frame = absl::HexStringToBytes(
+ "4089" // type (ACCEPT_CH)
+ "00"); // length
+
+ if (GetQuicReloadableFlag(quic_parse_accept_ch_frame)) {
+ if (perspective() == Perspective::IS_CLIENT) {
+ EXPECT_CALL(debug_visitor, OnAcceptChFrameReceived(_));
+ } else {
+ EXPECT_CALL(
+ *connection_,
+ CloseConnection(QUIC_HTTP_FRAME_UNEXPECTED_ON_CONTROL_STREAM,
+ "ACCEPT_CH frame received on control stream", _))
+ .WillOnce(
+ Invoke(connection_, &MockQuicConnection::ReallyCloseConnection));
+ EXPECT_CALL(*connection_, SendConnectionClosePacket(_, _));
+ EXPECT_CALL(session_, OnConnectionClosed(_, _));
+ }
+ } else {
+ EXPECT_CALL(debug_visitor,
+ OnUnknownFrameReceived(id, /* frame_type = */ 0x89,
+ /* payload_length = */ 0));
+ }
+
+ receive_control_stream_->OnStreamFrame(
+ QuicStreamFrame(id, /* fin = */ false, offset, accept_ch_frame));
+}
+
TEST_P(QuicReceiveControlStreamTest, UnknownFrameBeforeSettings) {
std::string unknown_frame = absl::HexStringToBytes(
"21" // reserved frame type
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h
index 4ab4cfd..a90befa 100644
--- a/quic/core/http/quic_spdy_session.h
+++ b/quic/core/http/quic_spdy_session.h
@@ -90,6 +90,7 @@
virtual void OnMaxPushIdFrameReceived(const MaxPushIdFrame& /*frame*/) {}
virtual void OnPriorityUpdateFrameReceived(
const PriorityUpdateFrame& /*frame*/) {}
+ virtual void OnAcceptChFrameReceived(const AcceptChFrame& /*frame*/) {}
// Incoming HTTP/3 frames on request or push streams.
virtual void OnDataFrameReceived(QuicStreamId /*stream_id*/,
@@ -196,6 +197,10 @@
// stream. Returns false and closes connection if |push_id| is invalid.
bool OnPriorityUpdateForPushStream(QuicStreamId push_id, int urgency);
+ // Called when an HTTP/3 ACCEPT_CH frame has been received.
+ // This method will only be called for client sessions.
+ virtual void OnAcceptChFrame(const AcceptChFrame& /*frame*/) {}
+
// Sends contents of |iov| to h2_deframer_, returns number of bytes processed.
size_t ProcessHeaderData(const struct iovec& iov);
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 3606949..e4b5dc0 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -351,6 +351,8 @@
GetEncryptionLevelToSendApplicationData());
}
+ MOCK_METHOD(void, OnAcceptChFrame, (const AcceptChFrame&), (override));
+
using QuicSession::closed_streams;
using QuicSession::ShouldKeepConnectionAlive;
using QuicSpdySession::ProcessPendingStream;
@@ -3260,6 +3262,57 @@
session_.OnSettingsFrame(frame);
}
+TEST_P(QuicSpdySessionTestClient, ReceiveAcceptChFrame) {
+ if (!VersionUsesHttp3(transport_version())) {
+ return;
+ }
+
+ if (!GetQuicReloadableFlag(quic_parse_accept_ch_frame)) {
+ return;
+ }
+
+ StrictMock<MockHttp3DebugVisitor> debug_visitor;
+ session_.set_debug_visitor(&debug_visitor);
+
+ // Create control stream.
+ QuicStreamId receive_control_stream_id =
+ GetNthServerInitiatedUnidirectionalStreamId(transport_version(), 3);
+ char type[] = {kControlStream};
+ absl::string_view stream_type(type, 1);
+ QuicStreamOffset offset = 0;
+ 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);
+
+ // Receive ACCEPT_CH frame.
+ AcceptChFrame accept_ch;
+ accept_ch.entries.push_back({"foo", "bar"});
+ std::unique_ptr<char[]> buffer;
+ auto frame_length = HttpEncoder::SerializeAcceptChFrame(accept_ch, &buffer);
+ QuicStreamFrame data3(receive_control_stream_id, /* fin = */ false, offset,
+ absl::string_view(buffer.get(), frame_length));
+
+ EXPECT_CALL(debug_visitor, OnAcceptChFrameReceived(accept_ch));
+ EXPECT_CALL(session_, OnAcceptChFrame(accept_ch));
+
+ session_.OnStreamFrame(data3);
+}
+
} // namespace
} // namespace test
} // namespace quic
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index 62a81ae..8489954 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -154,6 +154,16 @@
return false;
}
+ bool OnAcceptChFrameStart(QuicByteCount /*header_length*/) override {
+ CloseConnectionOnWrongFrame("ACCEPT_CH");
+ return false;
+ }
+
+ bool OnAcceptChFrame(const AcceptChFrame& /*frame*/) override {
+ CloseConnectionOnWrongFrame("ACCEPT_CH");
+ return false;
+ }
+
bool OnUnknownFrameStart(uint64_t frame_type,
QuicByteCount header_length,
QuicByteCount payload_length) override {
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index 28082aa..def3a5c 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -44,6 +44,7 @@
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_goaway_with_max_stream_id, false)
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_granular_qpack_error_codes, true)
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_new_priority_update_frame, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_parse_accept_ch_frame, true)
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator, false)
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_require_handshake_confirmation, false)
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_round_up_tiny_bandwidth, true)
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h
index 6fbcc17..50afd2d 100644
--- a/quic/test_tools/quic_test_utils.h
+++ b/quic/test_tools/quic_test_utils.h
@@ -1110,6 +1110,10 @@
OnPriorityUpdateFrameReceived,
(const PriorityUpdateFrame&),
(override));
+ MOCK_METHOD(void,
+ OnAcceptChFrameReceived,
+ (const AcceptChFrame&),
+ (override));
MOCK_METHOD(void,
OnDataFrameReceived,