Add QUICHE support for the HTTP/3 ORIGIN frame. The ORIGIN HTTP/3 frame allows a server to indicate what origin or origins RFC6454] the server would like the client to consider as one or more members of the Origin Set (Section 2.3 of [ORIGIN]) for the connection within which it occurs https://www.rfc-editor.org/rfc/rfc9412.html Protected by FLAGS_gfe2_reloadable_flag_enable_h3_origin_frame. PiperOrigin-RevId: 657272516
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h index 831d27d..bb0a6da 100755 --- a/quiche/common/quiche_feature_flags_list.h +++ b/quiche/common/quiche_feature_flags_list.h
@@ -8,6 +8,7 @@ #if defined(QUICHE_FLAG) +QUICHE_FLAG(bool, quiche_reloadable_flag_enable_h3_origin_frame, false, false, "If true, enables support for parsing HTTP/3 ORIGIN frames.") QUICHE_FLAG(bool, quiche_reloadable_flag_http2_add_hpack_overhead_bytes2, false, true, "If true, HTTP/2 HEADERS frames will use two additional bytes of HPACK overhead per header in their SpdyHeadersIR::size() estimate. This flag is latched in SpdyHeadersIR to ensure a consistent size() value for a const SpdyHeadersIR regardless of flag state.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_act_upon_invalid_header, true, true, "If true, reject or send error response code upon receiving invalid request or response headers.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_add_stream_info_to_idle_close_detail, false, true, "If true, include stream information in idle timeout connection close detail.")
diff --git a/quiche/quic/core/http/http_decoder.cc b/quiche/quic/core/http/http_decoder.cc index 00de94f..a08a00e 100644 --- a/quiche/quic/core/http/http_decoder.cc +++ b/quiche/quic/core/http/http_decoder.cc
@@ -46,7 +46,8 @@ current_type_field_length_(0), remaining_type_field_length_(0), error_(QUIC_NO_ERROR), - error_detail_("") { + error_detail_(""), + enable_origin_frame_(GetQuicReloadableFlag(enable_h3_origin_frame)) { QUICHE_DCHECK(visitor_); } @@ -289,6 +290,12 @@ visitor_->OnMetadataFrameStart(header_length, current_frame_length_); break; default: + if (enable_origin_frame_ && + current_frame_type_ == static_cast<uint64_t>(HttpFrameType::ORIGIN)) { + QUIC_CODE_COUNT_N(enable_h3_origin_frame, 1, 2); + continue_processing = visitor_->OnOriginFrameStart(header_length); + break; + } continue_processing = visitor_->OnUnknownFrameStart( current_frame_type_, header_length, current_frame_length_); break; @@ -316,6 +323,12 @@ return true; case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE_REQUEST_STREAM): return true; + case static_cast<uint64_t>(HttpFrameType::ORIGIN): + if (enable_origin_frame_) { + QUIC_CODE_COUNT_N(enable_h3_origin_frame, 2, 2); + return true; + } + return false; case static_cast<uint64_t>(HttpFrameType::ACCEPT_CH): return true; } @@ -394,6 +407,11 @@ break; } default: { + if (enable_origin_frame_ && + current_frame_type_ == static_cast<uint64_t>(HttpFrameType::ORIGIN)) { + QUICHE_NOTREACHED(); + break; + } continue_processing = HandleUnknownFramePayload(reader); break; } @@ -454,6 +472,11 @@ break; } default: + if (enable_origin_frame_ && + current_frame_type_ == static_cast<uint64_t>(HttpFrameType::ORIGIN)) { + QUICHE_NOTREACHED(); + break; + } continue_processing = visitor_->OnUnknownFrameEnd(); } @@ -569,6 +592,13 @@ } return visitor_->OnPriorityUpdateFrame(frame); } + case static_cast<uint64_t>(HttpFrameType::ORIGIN): { + OriginFrame frame; + if (!ParseOriginFrame(reader, frame)) { + return false; + } + return visitor_->OnOriginFrame(frame); + } case static_cast<uint64_t>(HttpFrameType::ACCEPT_CH): { AcceptChFrame frame; if (!ParseAcceptChFrame(reader, frame)) { @@ -649,6 +679,19 @@ return true; } +bool HttpDecoder::ParseOriginFrame(QuicDataReader& reader, OriginFrame& frame) { + QUICHE_DCHECK(enable_origin_frame_); + while (!reader.IsDoneReading()) { + absl::string_view origin; + if (!reader.ReadStringPiece16(&origin)) { + RaiseError(QUIC_HTTP_FRAME_ERROR, "Unable to read ORIGIN origin."); + return false; + } + frame.origins.push_back(std::string(origin)); + } + return true; +} + bool HttpDecoder::ParseAcceptChFrame(QuicDataReader& reader, AcceptChFrame& frame) { absl::string_view origin; @@ -683,6 +726,8 @@ return kPayloadLengthLimit; case static_cast<uint64_t>(HttpFrameType::ACCEPT_CH): return kPayloadLengthLimit; + case static_cast<uint64_t>(HttpFrameType::ORIGIN): + return kPayloadLengthLimit; default: QUICHE_NOTREACHED(); return 0;
diff --git a/quiche/quic/core/http/http_decoder.h b/quiche/quic/core/http/http_decoder.h index e959807..e928cca 100644 --- a/quiche/quic/core/http/http_decoder.h +++ b/quiche/quic/core/http/http_decoder.h
@@ -83,6 +83,13 @@ // Called when a PRIORITY_UPDATE frame has been successfully parsed. virtual bool OnPriorityUpdateFrame(const PriorityUpdateFrame& frame) = 0; + // Called when an ORIGIN frame has been received. + // |header_length| contains ORIGIN frame length and payload length. + virtual bool OnOriginFrameStart(QuicByteCount header_length) = 0; + + // Called when an ORIGIN frame has been successfully parsed. + virtual bool OnOriginFrame(const OriginFrame& 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; @@ -247,6 +254,9 @@ bool ParsePriorityUpdateFrame(QuicDataReader& reader, PriorityUpdateFrame& frame); + // Parses the payload of an ORIGIN frame from |reader| into |frame|. + bool ParseOriginFrame(QuicDataReader& reader, OriginFrame& frame); + // Parses the payload of an ACCEPT_CH frame from |reader| into |frame|. bool ParseAcceptChFrame(QuicDataReader& reader, AcceptChFrame& frame); @@ -283,6 +293,8 @@ std::array<char, sizeof(uint64_t)> length_buffer_; // Remaining unparsed type field data. std::array<char, sizeof(uint64_t)> type_buffer_; + // Latched value of reloadable flag enable_h3_origin_frame. + bool enable_origin_frame_; }; } // namespace quic
diff --git a/quiche/quic/core/http/http_decoder_test.cc b/quiche/quic/core/http/http_decoder_test.cc index b7f3255..72fdf1f 100644 --- a/quiche/quic/core/http/http_decoder_test.cc +++ b/quiche/quic/core/http/http_decoder_test.cc
@@ -57,6 +57,8 @@ ON_CALL(visitor_, OnPriorityUpdateFrame(_)).WillByDefault(Return(true)); ON_CALL(visitor_, OnAcceptChFrameStart(_)).WillByDefault(Return(true)); ON_CALL(visitor_, OnAcceptChFrame(_)).WillByDefault(Return(true)); + ON_CALL(visitor_, OnOriginFrameStart(_)).WillByDefault(Return(true)); + ON_CALL(visitor_, OnOriginFrame(_)).WillByDefault(Return(true)); ON_CALL(visitor_, OnMetadataFrameStart(_, _)).WillByDefault(Return(true)); ON_CALL(visitor_, OnMetadataFramePayload(_)).WillByDefault(Return(true)); ON_CALL(visitor_, OnMetadataFrameEnd()).WillByDefault(Return(true)); @@ -1095,6 +1097,119 @@ EXPECT_EQ("", decoder_.error_detail()); } +TEST_F(HttpDecoderTest, OriginFrame) { + if (!GetQuicReloadableFlag(enable_h3_origin_frame)) { + return; + } + InSequence s; + std::string input1; + ASSERT_TRUE( + absl::HexStringToBytes("0C" // type (ORIGIN) + "00", // length + &input1)); + + OriginFrame origin1; + + // Visitor pauses processing. + EXPECT_CALL(visitor_, OnOriginFrameStart(2)).WillOnce(Return(false)); + absl::string_view remaining_input(input1); + QuicByteCount processed_bytes = + ProcessInputWithGarbageAppended(remaining_input); + EXPECT_EQ(2u, processed_bytes); + remaining_input = remaining_input.substr(processed_bytes); + + EXPECT_CALL(visitor_, OnOriginFrame(origin1)).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_, OnOriginFrameStart(2)); + EXPECT_CALL(visitor_, OnOriginFrame(origin1)); + EXPECT_EQ(input1.size(), ProcessInput(input1)); + EXPECT_THAT(decoder_.error(), IsQuicNoError()); + EXPECT_EQ("", decoder_.error_detail()); + + // Process the frame incrementally. + EXPECT_CALL(visitor_, OnOriginFrameStart(2)); + EXPECT_CALL(visitor_, OnOriginFrame(origin1)); + ProcessInputCharByChar(input1); + EXPECT_THAT(decoder_.error(), IsQuicNoError()); + EXPECT_EQ("", decoder_.error_detail()); + + std::string input2; + ASSERT_TRUE( + absl::HexStringToBytes("0C" // type (ORIGIN) + "0A" // length + "0003" // length of origin + "666f6f" // origin "foo" + "0003" // length of origin + "626172", // origin "bar" + &input2)); + ASSERT_EQ(12, input2.length()); + + OriginFrame origin2; + origin2.origins = {"foo", "bar"}; + + // Visitor pauses processing. + EXPECT_CALL(visitor_, OnOriginFrameStart(2)).WillOnce(Return(false)); + remaining_input = input2; + processed_bytes = ProcessInputWithGarbageAppended(remaining_input); + EXPECT_EQ(2u, processed_bytes); + remaining_input = remaining_input.substr(processed_bytes); + + EXPECT_CALL(visitor_, OnOriginFrame(origin2)).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_, OnOriginFrameStart(2)); + EXPECT_CALL(visitor_, OnOriginFrame(origin2)); + EXPECT_EQ(input2.size(), ProcessInput(input2)); + EXPECT_THAT(decoder_.error(), IsQuicNoError()); + EXPECT_EQ("", decoder_.error_detail()); + + // Process the frame incrementally. + EXPECT_CALL(visitor_, OnOriginFrameStart(2)); + EXPECT_CALL(visitor_, OnOriginFrame(origin2)); + ProcessInputCharByChar(input2); + EXPECT_THAT(decoder_.error(), IsQuicNoError()); + EXPECT_EQ("", decoder_.error_detail()); +} + +TEST_F(HttpDecoderTest, OriginFrameDisabled) { + if (GetQuicReloadableFlag(enable_h3_origin_frame)) { + return; + } + InSequence s; + + std::string input1; + ASSERT_TRUE( + absl::HexStringToBytes("0C" // type (ORIGIN) + "00", // length + &input1)); + EXPECT_CALL(visitor_, OnUnknownFrameStart(0x0C, 2, 0)); + EXPECT_CALL(visitor_, OnUnknownFrameEnd()); + EXPECT_EQ(ProcessInput(input1), input1.size()); + + std::string input2; + ASSERT_TRUE( + absl::HexStringToBytes("0C" // type (ORIGIN) + "0A" // length + "0003" // length of origin + "666f6f" // origin "foo" + "0003" // length of origin + "626172", // origin "bar" + &input2)); + EXPECT_CALL(visitor_, OnUnknownFrameStart(0x0C, 2, input2.size() - 2)); + EXPECT_CALL(visitor_, OnUnknownFramePayload(input2.substr(2))); + EXPECT_CALL(visitor_, OnUnknownFrameEnd()); + EXPECT_EQ(ProcessInput(input2), input2.size()); +} + TEST_F(HttpDecoderTest, WebTransportStreamDisabled) { InSequence s;
diff --git a/quiche/quic/core/http/http_encoder.cc b/quiche/quic/core/http/http_encoder.cc index 8ea61b9..3edb43a 100644 --- a/quiche/quic/core/http/http_encoder.cc +++ b/quiche/quic/core/http/http_encoder.cc
@@ -196,6 +196,36 @@ return frame; } +std::string HttpEncoder::SerializeOriginFrame(const OriginFrame& origin) { + QuicByteCount payload_length = 0; + for (const std::string& entry : origin.origins) { + constexpr QuicByteCount kLengthFieldOverhead = 2; + payload_length += kLengthFieldOverhead + entry.size(); + } + + QuicByteCount total_length = + GetTotalLength(payload_length, HttpFrameType::ORIGIN); + + std::string frame; + frame.resize(total_length); + QuicDataWriter writer(total_length, frame.data()); + + if (!WriteFrameHeader(payload_length, HttpFrameType::ORIGIN, &writer)) { + QUIC_DLOG(ERROR) << "Http encoder failed to serialize ORIGIN frame header."; + return {}; + } + + for (const std::string& entry : origin.origins) { + if (!writer.WriteStringPiece16(entry)) { + QUIC_DLOG(ERROR) + << "Http encoder failed to serialize ACCEPT_CH frame payload."; + return {}; + } + } + + return frame; +} + std::string HttpEncoder::SerializeGreasingFrame() { uint64_t frame_type; QuicByteCount payload_length;
diff --git a/quiche/quic/core/http/http_encoder.h b/quiche/quic/core/http/http_encoder.h index d0d8408..e00be7d 100644 --- a/quiche/quic/core/http/http_encoder.h +++ b/quiche/quic/core/http/http_encoder.h
@@ -47,6 +47,9 @@ // Serializes an ACCEPT_CH frame. static std::string SerializeAcceptChFrame(const AcceptChFrame& accept_ch); + // Serializes an ORIGIN frame. + static std::string SerializeOriginFrame(const OriginFrame& origin); + // Serializes a frame with reserved frame type specified in // https://tools.ietf.org/html/draft-ietf-quic-http-25#section-7.2.9. static std::string SerializeGreasingFrame();
diff --git a/quiche/quic/core/http/http_encoder_test.cc b/quiche/quic/core/http/http_encoder_test.cc index e252fc5..958e3f9 100644 --- a/quiche/quic/core/http/http_encoder_test.cc +++ b/quiche/quic/core/http/http_encoder_test.cc
@@ -94,6 +94,33 @@ reinterpret_cast<char*>(output2), ABSL_ARRAYSIZE(output2)); } +TEST(HttpEncoderTest, SerializeEmptyOriginFrame) { + OriginFrame frame; + uint8_t expected[] = {0x0C, // type (ACCEPT_CH) + 0x00}; // length + + std::string output = HttpEncoder::SerializeOriginFrame(frame); + quiche::test::CompareCharArraysWithHexError( + "ORIGIN", output.data(), output.length(), + reinterpret_cast<char*>(expected), ABSL_ARRAYSIZE(expected)); +} + +TEST(HttpEncoderTest, SerializeOriginFrame) { + OriginFrame frame; + frame.origins = {"foo", "bar"}; + uint8_t expected[] = {0x0C, // type (ORIGIN) + 0x0A, // length + 0x00, 0x003, // length of origin + 0x66, 0x6f, 0x6f, // origin "foo" + 0x00, 0x003, // length of origin + 0x62, 0x61, 0x72}; // origin "bar" + + std::string output = HttpEncoder::SerializeOriginFrame(frame); + quiche::test::CompareCharArraysWithHexError( + "ORIGIN", output.data(), output.length(), + reinterpret_cast<char*>(expected), ABSL_ARRAYSIZE(expected)); +} + TEST(HttpEncoderTest, SerializeAcceptChFrame) { AcceptChFrame accept_ch; uint8_t output1[] = {0x40, 0x89, // type (ACCEPT_CH)
diff --git a/quiche/quic/core/http/http_frames.h b/quiche/quic/core/http/http_frames.h index 5b72bb0..645f5c0 100644 --- a/quiche/quic/core/http/http_frames.h +++ b/quiche/quic/core/http/http_frames.h
@@ -23,10 +23,12 @@ enum class HttpFrameType { DATA = 0x0, HEADERS = 0x1, - CANCEL_PUSH = 0X3, + CANCEL_PUSH = 0x3, SETTINGS = 0x4, PUSH_PROMISE = 0x5, GOAWAY = 0x7, + // https://www.rfc-editor.org/rfc/rfc9412.html + ORIGIN = 0xC, MAX_PUSH_ID = 0xD, // https://tools.ietf.org/html/draft-davidben-http-client-hint-reliability-02 ACCEPT_CH = 0x89, @@ -99,6 +101,19 @@ bool operator==(const GoAwayFrame& rhs) const { return id == rhs.id; } }; +// https://www.rfc-editor.org/rfc/rfc9412.html +// The ORIGIN HTTP/3 frame allows a server to indicate what origin or origins +// [RFC6454] the server would like the client to consider as one or more +// members of the Origin Set (Section 2.3 of [ORIGIN]) for the connection +// within which it occurs +struct QUICHE_EXPORT OriginFrame { + std::vector<std::string> origins; + + bool operator==(const OriginFrame& rhs) const { + return origins == rhs.origins; + } +}; + // https://httpwg.org/http-extensions/draft-ietf-httpbis-priority.html // // The PRIORITY_UPDATE frame specifies the sender-advised priority of a stream.
diff --git a/quiche/quic/core/http/quic_receive_control_stream.cc b/quiche/quic/core/http/quic_receive_control_stream.cc index 4cba9d5..08edeb2 100644 --- a/quiche/quic/core/http/quic_receive_control_stream.cc +++ b/quiche/quic/core/http/quic_receive_control_stream.cc
@@ -150,6 +150,22 @@ return spdy_session_->OnPriorityUpdateForRequestStream(stream_id, *priority); } +bool QuicReceiveControlStream::OnOriginFrameStart( + QuicByteCount /* header_length */) { + return ValidateFrameType(HttpFrameType::ORIGIN); +} + +bool QuicReceiveControlStream::OnOriginFrame(const OriginFrame& frame) { + QUICHE_DCHECK_EQ(Perspective::IS_CLIENT, spdy_session()->perspective()); + + if (spdy_session()->debug_visitor()) { + spdy_session()->debug_visitor()->OnOriginFrameReceived(frame); + } + + spdy_session()->OnOriginFrame(frame); + return false; +} + bool QuicReceiveControlStream::OnAcceptChFrameStart( QuicByteCount /* header_length */) { return ValidateFrameType(HttpFrameType::ACCEPT_CH); @@ -217,7 +233,9 @@ (spdy_session()->perspective() == Perspective::IS_CLIENT && frame_type == HttpFrameType::MAX_PUSH_ID) || (spdy_session()->perspective() == Perspective::IS_SERVER && - frame_type == HttpFrameType::ACCEPT_CH)) { + ((GetQuicReloadableFlag(enable_h3_origin_frame) && + frame_type == HttpFrameType::ORIGIN) || + frame_type == HttpFrameType::ACCEPT_CH))) { stream_delegate()->OnStreamError( QUIC_HTTP_FRAME_UNEXPECTED_ON_CONTROL_STREAM, absl::StrCat("Invalid frame type ", static_cast<int>(frame_type),
diff --git a/quiche/quic/core/http/quic_receive_control_stream.h b/quiche/quic/core/http/quic_receive_control_stream.h index c7f4ee2..b1cf610 100644 --- a/quiche/quic/core/http/quic_receive_control_stream.h +++ b/quiche/quic/core/http/quic_receive_control_stream.h
@@ -48,6 +48,8 @@ bool OnHeadersFrameEnd() override; bool OnPriorityUpdateFrameStart(QuicByteCount header_length) override; bool OnPriorityUpdateFrame(const PriorityUpdateFrame& frame) override; + bool OnOriginFrameStart(QuicByteCount header_length) override; + bool OnOriginFrame(const OriginFrame& frame) override; bool OnAcceptChFrameStart(QuicByteCount header_length) override; bool OnAcceptChFrame(const AcceptChFrame& frame) override; void OnWebTransportStreamFrameType(QuicByteCount header_length,
diff --git a/quiche/quic/core/http/quic_receive_control_stream_test.cc b/quiche/quic/core/http/quic_receive_control_stream_test.cc index 79773c0..b3c0cc3 100644 --- a/quiche/quic/core/http/quic_receive_control_stream_test.cc +++ b/quiche/quic/core/http/quic_receive_control_stream_test.cc
@@ -455,6 +455,51 @@ QuicStreamFrame(id, /* fin = */ false, offset, accept_ch_frame)); } +TEST_P(QuicReceiveControlStreamTest, ReceiveOriginFrame) { + 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 = HttpEncoder::SerializeSettingsFrame(settings); + EXPECT_CALL(debug_visitor, OnSettingsFrameReceived(settings)); + receive_control_stream_->OnStreamFrame( + QuicStreamFrame(id, /* fin = */ false, offset, settings_frame)); + offset += settings_frame.length(); + + // Receive ORIGIN frame. + std::string origin_frame; + ASSERT_TRUE( + absl::HexStringToBytes("0C" // type (ORIGIN) + "00", // length + &origin_frame)); + + if (GetQuicReloadableFlag(enable_h3_origin_frame)) { + if (perspective() == Perspective::IS_CLIENT) { + EXPECT_CALL(debug_visitor, OnOriginFrameReceived(_)); + } else { + EXPECT_CALL(*connection_, + CloseConnection( + QUIC_HTTP_FRAME_UNEXPECTED_ON_CONTROL_STREAM, + "Invalid frame type 12 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 = */ 0x0c, + /* payload_length = */ 0)); + } + + receive_control_stream_->OnStreamFrame( + QuicStreamFrame(id, /* fin = */ false, offset, origin_frame)); +} + TEST_P(QuicReceiveControlStreamTest, UnknownFrameBeforeSettings) { std::string unknown_frame; ASSERT_TRUE(
diff --git a/quiche/quic/core/http/quic_spdy_session.cc b/quiche/quic/core/http/quic_spdy_session.cc index edfbb4a..e946627 100644 --- a/quiche/quic/core/http/quic_spdy_session.cc +++ b/quiche/quic/core/http/quic_spdy_session.cc
@@ -143,6 +143,11 @@ session_->OnAcceptChFrameReceivedViaAlps(frame); return true; } + bool OnOriginFrameStart(QuicByteCount /*header_length*/) override { + QUICHE_NOTREACHED(); + return true; + } + bool OnOriginFrame(const OriginFrame& /*frame*/) override { return true; } void OnWebTransportStreamFrameType( QuicByteCount /*header_length*/, WebTransportSessionId /*session_id*/) override {
diff --git a/quiche/quic/core/http/quic_spdy_session.h b/quiche/quic/core/http/quic_spdy_session.h index 92cf9e7..3c2c11b 100644 --- a/quiche/quic/core/http/quic_spdy_session.h +++ b/quiche/quic/core/http/quic_spdy_session.h
@@ -82,6 +82,7 @@ virtual void OnGoAwayFrameReceived(const GoAwayFrame& /*frame*/) = 0; virtual void OnPriorityUpdateFrameReceived( const PriorityUpdateFrame& /*frame*/) = 0; + virtual void OnOriginFrameReceived(const OriginFrame& /*frame*/) {} virtual void OnAcceptChFrameReceived(const AcceptChFrame& /*frame*/) {} // Incoming HTTP/3 frames on request or push streams. @@ -197,6 +198,10 @@ bool OnPriorityUpdateForRequestStream(QuicStreamId stream_id, HttpStreamPriority priority); + // Called when an HTTP/3 ORIGIN frame has been received. + // This method will only be called for client sessions. + virtual void OnOriginFrame(const OriginFrame& /*frame*/) {} + // 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*/) {}
diff --git a/quiche/quic/core/http/quic_spdy_stream.cc b/quiche/quic/core/http/quic_spdy_stream.cc index 59b0644..933cd50 100644 --- a/quiche/quic/core/http/quic_spdy_stream.cc +++ b/quiche/quic/core/http/quic_spdy_stream.cc
@@ -129,6 +129,16 @@ return false; } + bool OnOriginFrameStart(QuicByteCount /*header_length*/) override { + CloseConnectionOnWrongFrame("ORIGIN"); + return false; + } + + bool OnOriginFrame(const OriginFrame& /*frame*/) override { + CloseConnectionOnWrongFrame("ORIGIN"); + return false; + } + bool OnAcceptChFrameStart(QuicByteCount /*header_length*/) override { CloseConnectionOnWrongFrame("ACCEPT_CH"); return false;
diff --git a/quiche/quic/test_tools/quic_test_utils.h b/quiche/quic/test_tools/quic_test_utils.h index 276a2ac..d0d98ab 100644 --- a/quiche/quic/test_tools/quic_test_utils.h +++ b/quiche/quic/test_tools/quic_test_utils.h
@@ -1028,6 +1028,7 @@ MOCK_METHOD(void, OnGoAwayFrameReceived, (const GoAwayFrame&), (override)); MOCK_METHOD(void, OnPriorityUpdateFrameReceived, (const PriorityUpdateFrame&), (override)); + MOCK_METHOD(void, OnOriginFrameReceived, (const OriginFrame&), (override)); MOCK_METHOD(void, OnAcceptChFrameReceived, (const AcceptChFrame&), (override)); @@ -1500,6 +1501,9 @@ MOCK_METHOD(bool, OnPriorityUpdateFrame, (const PriorityUpdateFrame& frame), (override)); + MOCK_METHOD(bool, OnOriginFrameStart, (QuicByteCount header_length), + (override)); + MOCK_METHOD(bool, OnOriginFrame, (const OriginFrame& frame), (override)); MOCK_METHOD(bool, OnAcceptChFrameStart, (QuicByteCount header_length), (override)); MOCK_METHOD(bool, OnAcceptChFrame, (const AcceptChFrame& frame), (override));