gfe-relnote: Add and call Http3DebugVisitor methods. Not protected. This is so that Chromium can record NetLog events, see https://crbug.com/1062700. PiperOrigin-RevId: 302134482 Change-Id: I9c330ad2aa62115e3c24f068f011ada272148ad3
diff --git a/quic/core/http/quic_receive_control_stream.cc b/quic/core/http/quic_receive_control_stream.cc index 7f8007a..f9fdef9 100644 --- a/quic/core/http/quic_receive_control_stream.cc +++ b/quic/core/http/quic_receive_control_stream.cc
@@ -29,25 +29,41 @@ stream_->OnUnrecoverableError(decoder->error(), decoder->error_detail()); } - bool OnCancelPushFrame(const CancelPushFrame& /*frame*/) override { + bool OnCancelPushFrame(const CancelPushFrame& frame) override { + if (stream_->spdy_session()->debug_visitor()) { + stream_->spdy_session()->debug_visitor()->OnCancelPushFrameReceived( + frame); + } + // TODO(b/151841240): Handle CANCEL_PUSH frames instead of ignoring them. return true; } bool OnMaxPushIdFrame(const MaxPushIdFrame& frame) override { - if (stream_->spdy_session()->perspective() == Perspective::IS_SERVER) { - stream_->spdy_session()->SetMaxAllowedPushId(frame.push_id); - return true; + if (stream_->spdy_session()->perspective() == Perspective::IS_CLIENT) { + OnWrongFrame("Max Push Id"); + return false; } - OnWrongFrame("Max Push Id"); - return false; + + if (stream_->spdy_session()->debug_visitor()) { + stream_->spdy_session()->debug_visitor()->OnMaxPushIdFrameReceived(frame); + } + + stream_->spdy_session()->SetMaxAllowedPushId(frame.push_id); + return true; } bool OnGoAwayFrame(const GoAwayFrame& frame) override { + // TODO(bnc): Check if SETTINGS frame has been received. if (stream_->spdy_session()->perspective() == Perspective::IS_SERVER) { OnWrongFrame("Go Away"); return false; } + + if (stream_->spdy_session()->debug_visitor()) { + stream_->spdy_session()->debug_visitor()->OnGoAwayFrameReceived(frame); + } + stream_->spdy_session()->OnHttp3GoAway(frame.stream_id); return true; } @@ -120,17 +136,32 @@ return stream_->OnPriorityUpdateFrame(frame); } - bool OnUnknownFrameStart(uint64_t /* frame_type */, + bool OnUnknownFrameStart(uint64_t frame_type, QuicByteCount /* header_length */) override { + if (stream_->spdy_session()->debug_visitor()) { + stream_->spdy_session()->debug_visitor()->OnUnknownFrameStart( + stream_->id(), frame_type); + } + return stream_->OnUnknownFrameStart(); } - bool OnUnknownFramePayload(quiche::QuicheStringPiece /* payload */) override { + bool OnUnknownFramePayload(quiche::QuicheStringPiece payload) override { + if (stream_->spdy_session()->debug_visitor()) { + stream_->spdy_session()->debug_visitor()->OnUnknownFramePayload( + stream_->id(), payload.length()); + } + // Ignore unknown frame types. return true; } bool OnUnknownFrameEnd() override { + if (stream_->spdy_session()->debug_visitor()) { + stream_->spdy_session()->debug_visitor()->OnUnknownFrameEnd( + stream_->id()); + } + // Ignore unknown frame types. return true; } @@ -224,6 +255,10 @@ bool QuicReceiveControlStream::OnPriorityUpdateFrame( const PriorityUpdateFrame& priority) { + if (spdy_session()->debug_visitor()) { + spdy_session()->debug_visitor()->OnPriorityUpdateFrameReceived(priority); + } + // TODO(b/147306124): Use a proper structured headers parser instead. for (auto key_value : quiche::QuicheTextUtils::Split(priority.priority_field_value, ',')) {
diff --git a/quic/core/http/quic_receive_control_stream_test.cc b/quic/core/http/quic_receive_control_stream_test.cc index 61d8229..77692a5 100644 --- a/quic/core/http/quic_receive_control_stream_test.cc +++ b/quic/core/http/quic_receive_control_stream_test.cc
@@ -267,6 +267,9 @@ } TEST_P(QuicReceiveControlStreamTest, ReceiveGoAwayFrame) { + StrictMock<MockHttp3DebugVisitor> debug_visitor; + session_.set_debug_visitor(&debug_visitor); + GoAwayFrame goaway; goaway.stream_id = 0x00; @@ -282,6 +285,8 @@ EXPECT_CALL( *connection_, CloseConnection(QUIC_HTTP_FRAME_UNEXPECTED_ON_CONTROL_STREAM, _, _)); + } else { + EXPECT_CALL(debug_visitor, OnGoAwayFrameReceived(goaway)); } receive_control_stream_->OnStreamFrame(frame); @@ -338,6 +343,35 @@ NumBytesConsumed()); } +TEST_P(QuicReceiveControlStreamTest, ReceiveUnknownFrame) { + StrictMock<MockHttp3DebugVisitor> debug_visitor; + session_.set_debug_visitor(&debug_visitor); + + const QuicStreamId id = receive_control_stream_->id(); + + // 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 = */ 1, + settings_frame)); + + // Receive unknown frame. + std::string unknown_frame = quiche::QuicheTextUtils::HexDecode( + "21" // reserved frame type + "03" // payload length + "666f6f"); // payload "foo" + + EXPECT_CALL(debug_visitor, OnUnknownFrameStart(id, /* frame_type = */ 0x21)); + EXPECT_CALL(debug_visitor, + OnUnknownFramePayload(id, /* payload_length = */ 3)); + EXPECT_CALL(debug_visitor, OnUnknownFrameEnd(id)); + receive_control_stream_->OnStreamFrame( + QuicStreamFrame(id, /* fin = */ false, + /* offset = */ 1 + settings_frame.size(), unknown_frame)); +} + TEST_P(QuicReceiveControlStreamTest, UnknownFrameBeforeSettings) { std::string unknown_frame = quiche::QuicheTextUtils::HexDecode( "21" // reserved frame type
diff --git a/quic/core/http/quic_send_control_stream.cc b/quic/core/http/quic_send_control_stream.cc index 01c03dd..0ea3d6a 100644 --- a/quic/core/http/quic_send_control_stream.cc +++ b/quic/core/http/quic_send_control_stream.cc
@@ -20,16 +20,17 @@ QuicSendControlStream::QuicSendControlStream( QuicStreamId id, - QuicSession* session, + QuicSpdySession* spdy_session, uint64_t qpack_maximum_dynamic_table_capacity, uint64_t qpack_maximum_blocked_streams, uint64_t max_inbound_header_list_size) - : QuicStream(id, session, /*is_static = */ true, WRITE_UNIDIRECTIONAL), + : QuicStream(id, spdy_session, /*is_static = */ true, WRITE_UNIDIRECTIONAL), settings_sent_(false), qpack_maximum_dynamic_table_capacity_( qpack_maximum_dynamic_table_capacity), qpack_maximum_blocked_streams_(qpack_maximum_blocked_streams), - max_inbound_header_list_size_(max_inbound_header_list_size) {} + max_inbound_header_list_size_(max_inbound_header_list_size), + spdy_session_(spdy_session) {} void QuicSendControlStream::OnStreamReset(const QuicRstStreamFrame& /*frame*/) { QUIC_BUG << "OnStreamReset() called for write unidirectional stream."; @@ -80,9 +81,8 @@ HttpEncoder::SerializeSettingsFrame(settings, &buffer); QUIC_DVLOG(1) << "Control stream " << id() << " is writing settings frame " << settings; - QuicSpdySession* spdy_session = static_cast<QuicSpdySession*>(session()); - if (spdy_session->debug_visitor() != nullptr) { - spdy_session->debug_visitor()->OnSettingsFrameSent(settings); + if (spdy_session_->debug_visitor()) { + spdy_session_->debug_visitor()->OnSettingsFrameSent(settings); } WriteOrBufferData(quiche::QuicheStringPiece(buffer.get(), frame_length), /*fin = */ false, nullptr); @@ -101,6 +101,11 @@ const PriorityUpdateFrame& priority_update) { QuicConnection::ScopedPacketFlusher flusher(session()->connection()); MaybeSendSettingsFrame(); + + if (spdy_session_->debug_visitor()) { + spdy_session_->debug_visitor()->OnPriorityUpdateFrameSent(priority_update); + } + std::unique_ptr<char[]> buffer; QuicByteCount frame_length = HttpEncoder::SerializePriorityUpdateFrame(priority_update, &buffer); @@ -112,10 +117,14 @@ void QuicSendControlStream::SendMaxPushIdFrame(PushId max_push_id) { QuicConnection::ScopedPacketFlusher flusher(session()->connection()); - MaybeSendSettingsFrame(); + MaxPushIdFrame frame; frame.push_id = max_push_id; + if (spdy_session_->debug_visitor()) { + spdy_session_->debug_visitor()->OnMaxPushIdFrameSent(frame); + } + std::unique_ptr<char[]> buffer; QuicByteCount frame_length = HttpEncoder::SerializeMaxPushIdFrame(frame, &buffer); @@ -125,8 +134,8 @@ void QuicSendControlStream::SendGoAway(QuicStreamId stream_id) { QuicConnection::ScopedPacketFlusher flusher(session()->connection()); - MaybeSendSettingsFrame(); + GoAwayFrame frame; // If the peer hasn't created any stream yet. Use stream id 0 to indicate no // request is accepted. @@ -135,6 +144,10 @@ stream_id = 0; } frame.stream_id = stream_id; + if (spdy_session_->debug_visitor()) { + spdy_session_->debug_visitor()->OnGoAwayFrameSent(stream_id); + } + std::unique_ptr<char[]> buffer; QuicByteCount frame_length = HttpEncoder::SerializeGoAwayFrame(frame, &buffer);
diff --git a/quic/core/http/quic_send_control_stream.h b/quic/core/http/quic_send_control_stream.h index 899abb9..3771cdd 100644 --- a/quic/core/http/quic_send_control_stream.h +++ b/quic/core/http/quic_send_control_stream.h
@@ -13,16 +13,16 @@ namespace quic { -class QuicSession; +class QuicSpdySession; -// 3.2.1 Control Stream. +// 6.2.1 Control Stream. // The send control stream is self initiated and is write only. class QUIC_EXPORT_PRIVATE QuicSendControlStream : public QuicStream { public: // |session| can't be nullptr, and the ownership is not passed. The stream can // only be accessed through the session. QuicSendControlStream(QuicStreamId id, - QuicSession* session, + QuicSpdySession* session, uint64_t qpack_maximum_dynamic_table_capacity, uint64_t qpack_maximum_blocked_streams, uint64_t max_inbound_header_list_size); @@ -65,6 +65,8 @@ const uint64_t qpack_maximum_blocked_streams_; // SETTINGS_MAX_HEADER_LIST_SIZE value to send. const uint64_t max_inbound_header_list_size_; + + QuicSpdySession* const spdy_session_; }; } // namespace quic
diff --git a/quic/core/http/quic_send_control_stream_test.cc b/quic/core/http/quic_send_control_stream_test.cc index 8332cec..fb53b84 100644 --- a/quic/core/http/quic_send_control_stream_test.cc +++ b/quic/core/http/quic_send_control_stream_test.cc
@@ -19,6 +19,7 @@ namespace { using ::testing::_; +using ::testing::AnyNumber; using ::testing::Invoke; using ::testing::StrictMock; @@ -201,6 +202,22 @@ send_control_stream_->OnStreamFrame(frame); } +TEST_P(QuicSendControlStreamTest, SendGoAway) { + Initialize(); + + StrictMock<MockHttp3DebugVisitor> debug_visitor; + session_.set_debug_visitor(&debug_visitor); + + QuicStreamId stream_id = 4; + + EXPECT_CALL(session_, WritevData(send_control_stream_->id(), _, _, _, _, _)) + .Times(AnyNumber()); + EXPECT_CALL(debug_visitor, OnSettingsFrameSent(_)); + EXPECT_CALL(debug_visitor, OnGoAwayFrameSent(stream_id)); + + send_control_stream_->SendGoAway(stream_id); +} + } // namespace } // namespace test } // namespace quic
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc index 42a1dd5..713a8d0 100644 --- a/quic/core/http/quic_spdy_session.cc +++ b/quic/core/http/quic_spdy_session.cc
@@ -672,13 +672,6 @@ return; } - if (VersionUsesHttp3(transport_version()) && - promised_stream_id > max_allowed_push_id()) { - QUIC_BUG - << "Server shouldn't send push id higher than client's MAX_PUSH_ID."; - return; - } - if (!VersionUsesHttp3(transport_version())) { SpdyPushPromiseIR push_promise(original_stream_id, promised_stream_id, std::move(headers)); @@ -692,9 +685,21 @@ return; } + if (promised_stream_id > max_allowed_push_id()) { + QUIC_BUG + << "Server shouldn't send push id higher than client's MAX_PUSH_ID."; + return; + } + // Encode header list. std::string encoded_headers = qpack_encoder_->EncodeHeaderList(original_stream_id, headers, nullptr); + + if (debug_visitor_) { + debug_visitor_->OnPushPromiseFrameSent(original_stream_id, + promised_stream_id, headers); + } + PushPromiseFrame frame; frame.push_id = promised_stream_id; frame.headers = encoded_headers; @@ -1149,6 +1154,9 @@ max_inbound_header_list_size_); send_control_stream_ = send_control.get(); ActivateStream(std::move(send_control)); + if (debug_visitor_) { + debug_visitor_->OnControlStreamCreated(send_control_stream_->id()); + } } if (!qpack_decoder_send_stream_ && @@ -1159,6 +1167,10 @@ ActivateStream(std::move(decoder_send)); qpack_decoder_->set_qpack_stream_sender_delegate( qpack_decoder_send_stream_); + if (debug_visitor_) { + debug_visitor_->OnQpackDecoderStreamCreated( + qpack_decoder_send_stream_->id()); + } } if (!qpack_encoder_send_stream_ && @@ -1169,6 +1181,10 @@ ActivateStream(std::move(encoder_send)); qpack_encoder_->set_qpack_stream_sender_delegate( qpack_encoder_send_stream_); + if (debug_visitor_) { + debug_visitor_->OnQpackEncoderStreamCreated( + qpack_encoder_send_stream_->id()); + } } }
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h index eefb5ef..71a001d 100644 --- a/quic/core/http/quic_spdy_session.h +++ b/quic/core/http/quic_spdy_session.h
@@ -60,20 +60,73 @@ virtual ~Http3DebugVisitor(); + // TODO(https://crbug.com/1062700): Remove default implementation of all + // methods after Chrome's QuicHttp3Logger has overrides. This is to make sure + // QUICHE merge is not blocked on having to add those overrides, they can + // happen asynchronously. + + // Creation of unidirectional streams. + + // Called when locally-initiated control stream is created. + virtual void OnControlStreamCreated(QuicStreamId /*stream_id*/) {} + // Called when locally-initiated QPACK encoder stream is created. + virtual void OnQpackEncoderStreamCreated(QuicStreamId /*stream_id*/) {} + // Called when locally-initiated QPACK decoder stream is created. + virtual void OnQpackDecoderStreamCreated(QuicStreamId /*stream_id*/) {} // Called when peer's control stream type is received. virtual void OnPeerControlStreamCreated(QuicStreamId /*stream_id*/) = 0; - // Called when peer's QPACK encoder stream type is received. virtual void OnPeerQpackEncoderStreamCreated(QuicStreamId /*stream_id*/) = 0; - // Called when peer's QPACK decoder stream type is received. virtual void OnPeerQpackDecoderStreamCreated(QuicStreamId /*stream_id*/) = 0; - // Called when SETTINGS frame is received. + // Incoming HTTP/3 frames on the control stream. + virtual void OnCancelPushFrameReceived(CancelPushFrame /*frame*/) {} virtual void OnSettingsFrameReceived(const SettingsFrame& /*frame*/) = 0; + virtual void OnGoAwayFrameReceived(GoAwayFrame /*frame*/) {} + virtual void OnMaxPushIdFrameReceived(MaxPushIdFrame /*frame*/) {} + virtual void OnPriorityUpdateFrameReceived(PriorityUpdateFrame /*frame*/) {} - // Called when SETTINGS frame is sent. + // Incoming HTTP/3 frames on request or push streams. + virtual void OnDataFrameStart(QuicStreamId /*stream_id*/) {} + virtual void OnDataFramePayload(QuicStreamId /*stream_id*/, + QuicByteCount /*payload_fragment_length*/) {} + virtual void OnDataFrameEnd(QuicStreamId /*stream_id*/) {} + virtual void OnHeadersFrameReceived(QuicStreamId /*stream_id*/, + QuicByteCount /*payload_length*/) {} + virtual void OnHeadersDecoded(QuicStreamId /*stream_id*/, + QuicHeaderList /*headers*/) {} + virtual void OnPushPromiseFrameReceived(QuicStreamId /*stream_id*/, + QuicStreamId /*push_id*/) {} + virtual void OnPushPromiseDecoded(QuicStreamId /*stream_id*/, + QuicStreamId /*push_id*/, + QuicHeaderList /*headers*/) {} + + // Incoming HTTP/3 frames of unknown type on any stream. + virtual void OnUnknownFrameStart(QuicStreamId /*stream_id*/, + uint64_t /*frame_type*/) {} + virtual void OnUnknownFramePayload( + QuicStreamId /*stream_id*/, + QuicByteCount /*payload_fragment_length*/) {} + virtual void OnUnknownFrameEnd(QuicStreamId /*stream_id*/) {} + + // Outgoing HTTP/3 frames on the control stream. virtual void OnSettingsFrameSent(const SettingsFrame& /*frame*/) = 0; + virtual void OnGoAwayFrameSent(QuicStreamId /*stream_id*/) {} + virtual void OnMaxPushIdFrameSent(MaxPushIdFrame /*frame*/) {} + virtual void OnPriorityUpdateFrameSent(PriorityUpdateFrame /*frame*/) {} + + // Outgoing HTTP/3 frames on request or push streams. + virtual void OnDataFrameSent(QuicStreamId /*stream_id*/, + QuicByteCount /*payload_length*/) {} + virtual void OnHeadersFrameSent( + QuicStreamId /*stream_id*/, + const spdy::SpdyHeaderBlock& /*header_block*/) {} + virtual void OnPushPromiseFrameSent( + QuicStreamId /*stream_id*/, + QuicStreamId + /*push_id*/, + const spdy::SpdyHeaderBlock& /*header_block*/) {} }; // A QUIC session for HTTP.
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc index ec91ff6..fb6ebfc 100644 --- a/quic/core/http/quic_spdy_session_test.cc +++ b/quic/core/http/quic_spdy_session_test.cc
@@ -160,19 +160,6 @@ MOCK_METHOD0(OnCanWrite, void()); }; -class MockHttp3DebugVisitor : public Http3DebugVisitor { - public: - MOCK_METHOD1(OnPeerControlStreamCreated, void(QuicStreamId)); - - MOCK_METHOD1(OnPeerQpackEncoderStreamCreated, void(QuicStreamId)); - - MOCK_METHOD1(OnPeerQpackDecoderStreamCreated, void(QuicStreamId)); - - MOCK_METHOD1(OnSettingsFrameReceived, void(const SettingsFrame&)); - - MOCK_METHOD1(OnSettingsFrameSent, void(const SettingsFrame&)); -}; - class TestStream : public QuicSpdyStream { public: TestStream(QuicStreamId id, QuicSpdySession* session, StreamType type) @@ -1040,6 +1027,10 @@ if (!VersionUsesHttp3(transport_version())) { return; } + + StrictMock<MockHttp3DebugVisitor> debug_visitor; + session_.set_debug_visitor(&debug_visitor); + connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); MockPacketWriter* writer = static_cast<MockPacketWriter*>( QuicConnectionPeer::GetWriter(session_.connection())); @@ -1048,9 +1039,12 @@ if (connection_->version().HasHandshakeDone()) { EXPECT_CALL(*connection_, SendControlFrame(_)); } + CryptoHandshakeMessage message; + EXPECT_CALL(debug_visitor, OnSettingsFrameSent(_)); session_.GetMutableCryptoStream()->OnHandshakeMessage(message); + EXPECT_CALL(debug_visitor, OnGoAwayFrameSent(_)); session_.SendHttp3GoAway(); EXPECT_TRUE(session_.http3_goaway_sent()); @@ -2227,6 +2221,9 @@ return; } + StrictMock<MockHttp3DebugVisitor> debug_visitor; + session_.set_debug_visitor(&debug_visitor); + // Create control stream. QuicStreamId receive_control_stream_id = GetNthClientInitiatedUnidirectionalStreamId(transport_version(), 3); @@ -2235,6 +2232,8 @@ QuicStreamOffset offset = 0; QuicStreamFrame data1(receive_control_stream_id, 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()); @@ -2244,6 +2243,7 @@ QuicStreamFrame data2(receive_control_stream_id, false, offset, serialized_settings); offset += serialized_settings.length(); + EXPECT_CALL(debug_visitor, OnSettingsFrameReceived(_)); session_.OnStreamFrame(data2); // PRIORITY_UPDATE frame for first request stream. @@ -2262,6 +2262,7 @@ TestStream* stream1 = session_.CreateIncomingStream(stream_id1); EXPECT_EQ(QuicStream::kDefaultUrgency, stream1->precedence().spdy3_priority()); + EXPECT_CALL(debug_visitor, OnPriorityUpdateFrameReceived(priority_update1)); session_.OnStreamFrame(data3); EXPECT_EQ(2u, stream1->precedence().spdy3_priority()); @@ -2279,6 +2280,7 @@ // PRIORITY_UPDATE frame arrives before stream creation, // priority value is buffered. + EXPECT_CALL(debug_visitor, OnPriorityUpdateFrameReceived(priority_update2)); session_.OnStreamFrame(stream_frame3); // Priority is applied upon stream construction. TestStream* stream2 = session_.CreateIncomingStream(stream_id2); @@ -2420,16 +2422,21 @@ if (!VersionUsesHttp3(transport_version())) { return; } + + StrictMock<MockHttp3DebugVisitor> debug_visitor; + session_.set_debug_visitor(&debug_visitor); + MockPacketWriter* writer = static_cast<MockPacketWriter*>( QuicConnectionPeer::GetWriter(session_.connection())); EXPECT_CALL(*writer, WritePacket(_, _, _, _, _)) .WillRepeatedly(Return(WriteResult(WRITE_STATUS_OK, 0))); + EXPECT_CALL(debug_visitor, OnSettingsFrameSent(_)); EXPECT_CALL(*connection_, SendControlFrame(_)) .WillRepeatedly(Invoke(&ClearControlFrame)); CryptoHandshakeMessage message; session_.GetMutableCryptoStream()->OnHandshakeMessage(message); - MockHttp3DebugVisitor debug_visitor; + // Use an arbitrary stream id. QuicStreamId stream_id = GetNthClientInitiatedUnidirectionalStreamId(transport_version(), 3); @@ -2437,12 +2444,11 @@ QuicStreamFrame data1(stream_id, false, 0, quiche::QuicheStringPiece(type, 1)); - EXPECT_CALL(debug_visitor, OnPeerControlStreamCreated(stream_id)).Times(0); + EXPECT_CALL(debug_visitor, OnPeerControlStreamCreated(stream_id)); session_.OnStreamFrame(data1); EXPECT_EQ(stream_id, QuicSpdySessionPeer::GetReceiveControlStream(&session_)->id()); - session_.set_debug_visitor(&debug_visitor); SettingsFrame settings; settings.values[SETTINGS_QPACK_MAX_TABLE_CAPACITY] = 512; settings.values[SETTINGS_MAX_HEADER_LIST_SIZE] = 5; @@ -2694,7 +2700,7 @@ return; } - MockHttp3DebugVisitor debug_visitor; + StrictMock<MockHttp3DebugVisitor> debug_visitor; session_.set_debug_visitor(&debug_visitor); QuicStreamId id1 = @@ -2818,6 +2824,9 @@ return; } + StrictMock<MockHttp3DebugVisitor> debug_visitor; + session_.set_debug_visitor(&debug_visitor); + // Create control stream. QuicStreamId receive_control_stream_id = GetNthServerInitiatedUnidirectionalStreamId(transport_version(), 3); @@ -2827,6 +2836,8 @@ 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()); @@ -2837,6 +2848,7 @@ HttpEncoder::SerializeCancelPushFrame(cancel_push, &buffer); QuicStreamFrame data2(receive_control_stream_id, /* fin = */ false, offset, quiche::QuicheStringPiece(buffer.get(), frame_length)); + EXPECT_CALL(debug_visitor, OnCancelPushFrameReceived(_)); session_.OnStreamFrame(data2); }
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc index 9d2772a..3a4c146 100644 --- a/quic/core/http/quic_spdy_stream.cc +++ b/quic/core/http/quic_spdy_stream.cc
@@ -293,6 +293,10 @@ } QuicConnection::ScopedPacketFlusher flusher(spdy_session_->connection()); + if (spdy_session_->debug_visitor()) { + spdy_session_->debug_visitor()->OnDataFrameSent(id(), data.length()); + } + // Write frame header. std::unique_ptr<char[]> buffer; QuicByteCount header_length = @@ -406,6 +410,11 @@ return {0, false}; } + if (spdy_session_->debug_visitor()) { + spdy_session_->debug_visitor()->OnDataFrameSent(id(), + slices.total_length()); + } + QuicConnection::ScopedPacketFlusher flusher(spdy_session_->connection()); // Write frame header. @@ -546,13 +555,23 @@ /* is_sent = */ false, headers.compressed_header_bytes(), headers.uncompressed_header_bytes()); - if (spdy_session_->promised_stream_id() == - QuicUtils::GetInvalidStreamId(session()->transport_version())) { + const QuicStreamId promised_stream_id = spdy_session()->promised_stream_id(); + Http3DebugVisitor* const debug_visitor = spdy_session()->debug_visitor(); + if (promised_stream_id == + QuicUtils::GetInvalidStreamId(transport_version())) { + if (debug_visitor) { + debug_visitor->OnHeadersDecoded(id(), headers); + } + const QuicByteCount frame_length = headers_decompressed_ ? trailers_payload_length_ : headers_payload_length_; OnStreamHeaderList(/* fin = */ false, frame_length, headers); } else { + if (debug_visitor) { + debug_visitor->OnPushPromiseDecoded(id(), promised_stream_id, headers); + } + spdy_session_->OnHeaderList(headers); } @@ -850,6 +869,10 @@ return false; } + if (spdy_session_->debug_visitor()) { + spdy_session_->debug_visitor()->OnDataFrameStart(id()); + } + sequencer()->MarkConsumed(body_manager_.OnNonBody(header_length)); return true; @@ -858,6 +881,10 @@ bool QuicSpdyStream::OnDataFramePayload(quiche::QuicheStringPiece payload) { DCHECK(VersionUsesHttp3(transport_version())); + if (spdy_session_->debug_visitor()) { + spdy_session_->debug_visitor()->OnDataFramePayload(id(), payload.length()); + } + body_manager_.OnBody(payload); return true; @@ -865,6 +892,11 @@ bool QuicSpdyStream::OnDataFrameEnd() { DCHECK(VersionUsesHttp3(transport_version())); + + if (spdy_session_->debug_visitor()) { + spdy_session_->debug_visitor()->OnDataFrameEnd(id()); + } + QUIC_DVLOG(1) << ENDPOINT << "Reaches the end of a data frame. Total bytes received are " << body_manager_.total_body_bytes_received(); @@ -966,6 +998,18 @@ DCHECK(VersionUsesHttp3(transport_version())); DCHECK(qpack_decoded_headers_accumulator_); + if (spdy_session_->debug_visitor()) { + if (spdy_session_->promised_stream_id() == + QuicUtils::GetInvalidStreamId(transport_version())) { + spdy_session_->debug_visitor()->OnHeadersFrameReceived( + id(), headers_decompressed_ ? trailers_payload_length_ + : headers_payload_length_); + } else { + spdy_session_->debug_visitor()->OnPushPromiseFrameReceived( + id(), spdy_session_->promised_stream_id()); + } + } + qpack_decoded_headers_accumulator_->EndHeaderBlock(); // If decoding is complete or an error is detected, then @@ -1018,6 +1062,10 @@ bool QuicSpdyStream::OnUnknownFrameStart(uint64_t frame_type, QuicByteCount header_length) { + if (spdy_session_->debug_visitor()) { + spdy_session_->debug_visitor()->OnUnknownFrameStart(id(), frame_type); + } + // Ignore unknown frames, but consume frame header. QUIC_DVLOG(1) << ENDPOINT << "Discarding " << header_length << " byte long frame header of frame of unknown type " @@ -1027,6 +1075,10 @@ } bool QuicSpdyStream::OnUnknownFramePayload(quiche::QuicheStringPiece payload) { + if (spdy_session_->debug_visitor()) { + spdy_session_->debug_visitor()->OnUnknownFramePayload(id(), payload.size()); + } + // Ignore unknown frames, but consume frame payload. QUIC_DVLOG(1) << ENDPOINT << "Discarding " << payload.size() << " bytes of payload of frame of unknown type."; @@ -1035,6 +1087,10 @@ } bool QuicSpdyStream::OnUnknownFrameEnd() { + if (spdy_session_->debug_visitor()) { + spdy_session_->debug_visitor()->OnUnknownFrameEnd(id()); + } + return true; } @@ -1054,6 +1110,10 @@ spdy_session_->qpack_encoder()->EncodeHeaderList( id(), header_block, &encoder_stream_sent_byte_count); + if (spdy_session_->debug_visitor()) { + spdy_session_->debug_visitor()->OnHeadersFrameSent(id(), header_block); + } + // Write HEADERS frame. std::unique_ptr<char[]> headers_frame_header; const size_t headers_frame_header_length =
diff --git a/quic/core/http/quic_spdy_stream_test.cc b/quic/core/http/quic_spdy_stream_test.cc index 98b7e85..09fb664 100644 --- a/quic/core/http/quic_spdy_stream_test.cc +++ b/quic/core/http/quic_spdy_stream_test.cc
@@ -4,6 +4,7 @@ #include "net/third_party/quiche/src/quic/core/http/quic_spdy_stream.h" +#include <cstring> #include <memory> #include <string> #include <utility> @@ -1421,6 +1422,8 @@ } InitializeWithPerspective(kShouldProcessData, Perspective::IS_CLIENT); + StrictMock<MockHttp3DebugVisitor> debug_visitor; + session_->set_debug_visitor(&debug_visitor); // Four writes on the request stream: HEADERS frame header and payload both // for headers and trailers. @@ -1435,12 +1438,14 @@ // Write the initial headers, without a FIN. EXPECT_CALL(*stream_, WriteHeadersMock(false)); + EXPECT_CALL(debug_visitor, OnHeadersFrameSent(stream_->id(), _)); stream_->WriteHeaders(SpdyHeaderBlock(), /*fin=*/false, nullptr); // Writing trailers implicitly sends a FIN. SpdyHeaderBlock trailers; trailers["trailer key"] = "trailer value"; EXPECT_CALL(*stream_, WriteHeadersMock(true)); + EXPECT_CALL(debug_visitor, OnHeadersFrameSent(stream_->id(), _)); stream_->WriteTrailers(std::move(trailers), nullptr); EXPECT_TRUE(stream_->fin_sent()); } @@ -1451,16 +1456,23 @@ } InitializeWithPerspective(kShouldProcessData, Perspective::IS_CLIENT); + StrictMock<MockHttp3DebugVisitor> debug_visitor; + session_->set_debug_visitor(&debug_visitor); // Two writes on the request stream: HEADERS frame header and payload. EXPECT_CALL(*session_, WritevData(stream_->id(), _, _, _, _, _)).Times(2); EXPECT_CALL(*stream_, WriteHeadersMock(false)); + EXPECT_CALL(debug_visitor, OnHeadersFrameSent(stream_->id(), _)); stream_->WriteHeaders(SpdyHeaderBlock(), /*fin=*/false, nullptr); // PRIORITY_UPDATE frame on the control stream. auto send_control_stream = QuicSpdySessionPeer::GetSendControlStream(session_.get()); EXPECT_CALL(*session_, WritevData(send_control_stream->id(), _, _, _, _, _)); + PriorityUpdateFrame priority_update; + priority_update.prioritized_element_id = 0; + priority_update.priority_field_value = "u=0"; + EXPECT_CALL(debug_visitor, OnPriorityUpdateFrameSent(priority_update)); stream_->SetPriority(spdy::SpdyStreamPrecedence(kV3HighestPriority)); } @@ -2126,16 +2138,20 @@ Initialize(kShouldProcessData); testing::InSequence s; session_->qpack_decoder()->OnSetDynamicTableCapacity(1024); + StrictMock<MockHttp3DebugVisitor> debug_visitor; + session_->set_debug_visitor(&debug_visitor); auto decoder_send_stream = QuicSpdySessionPeer::GetQpackDecoderSendStream(session_.get()); - // The stream byte will be written in the first byte. - EXPECT_CALL(*session_, WritevData(decoder_send_stream->id(), _, _, _, _, _)); // Deliver dynamic table entry to decoder. session_->qpack_decoder()->OnInsertWithoutNameReference("foo", "bar"); // HEADERS frame referencing first dynamic table entry. + EXPECT_CALL(debug_visitor, OnHeadersFrameReceived(stream_->id(), _)); + // Decoder stream type and header acknowledgement. + EXPECT_CALL(*session_, WritevData(decoder_send_stream->id(), _, _, _, _, _)); + EXPECT_CALL(debug_visitor, OnHeadersDecoded(stream_->id(), _)); std::string headers = HeadersFrame(quiche::QuicheTextUtils::HexDecode("020080")); stream_->OnStreamFrame(QuicStreamFrame(stream_->id(), false, 0, headers)); @@ -2149,15 +2165,22 @@ // DATA frame. std::string data = DataFrame(kDataFramePayload); + EXPECT_CALL(debug_visitor, OnDataFrameStart(stream_->id())); + EXPECT_CALL(debug_visitor, + OnDataFramePayload(stream_->id(), strlen(kDataFramePayload))); + EXPECT_CALL(debug_visitor, OnDataFrameEnd(stream_->id())); stream_->OnStreamFrame(QuicStreamFrame(stream_->id(), false, /* offset = */ headers.length(), data)); EXPECT_EQ(kDataFramePayload, stream_->data()); - EXPECT_CALL(*session_, WritevData(decoder_send_stream->id(), _, _, _, _, _)); // Deliver second dynamic table entry to decoder. session_->qpack_decoder()->OnInsertWithoutNameReference("trailing", "foobar"); // Trailing HEADERS frame referencing second dynamic table entry. + EXPECT_CALL(debug_visitor, OnHeadersFrameReceived(stream_->id(), _)); + // Header acknowledgement. + EXPECT_CALL(*session_, WritevData(decoder_send_stream->id(), _, _, _, _, _)); + EXPECT_CALL(debug_visitor, OnHeadersDecoded(stream_->id(), _)); std::string trailers = HeadersFrame(quiche::QuicheTextUtils::HexDecode("030080")); stream_->OnStreamFrame(QuicStreamFrame(stream_->id(), true, /* offset = */ @@ -2181,10 +2204,13 @@ Initialize(kShouldProcessData); testing::InSequence s; session_->qpack_decoder()->OnSetDynamicTableCapacity(1024); + StrictMock<MockHttp3DebugVisitor> debug_visitor; + session_->set_debug_visitor(&debug_visitor); // HEADERS frame referencing first dynamic table entry. std::string headers = HeadersFrame(quiche::QuicheTextUtils::HexDecode("020080")); + EXPECT_CALL(debug_visitor, OnHeadersFrameReceived(stream_->id(), _)); stream_->OnStreamFrame(QuicStreamFrame(stream_->id(), false, 0, headers)); // Decoding is blocked because dynamic table entry has not been received yet. @@ -2193,8 +2219,9 @@ auto decoder_send_stream = QuicSpdySessionPeer::GetQpackDecoderSendStream(session_.get()); - // The stream byte will be written in the first byte. + // Decoder stream type and header acknowledgement. EXPECT_CALL(*session_, WritevData(decoder_send_stream->id(), _, _, _, _, _)); + EXPECT_CALL(debug_visitor, OnHeadersDecoded(stream_->id(), _)); // Deliver dynamic table entry to decoder. session_->qpack_decoder()->OnInsertWithoutNameReference("foo", "bar"); EXPECT_TRUE(stream_->headers_decompressed()); @@ -2205,6 +2232,10 @@ // DATA frame. std::string data = DataFrame(kDataFramePayload); + EXPECT_CALL(debug_visitor, OnDataFrameStart(stream_->id())); + EXPECT_CALL(debug_visitor, + OnDataFramePayload(stream_->id(), strlen(kDataFramePayload))); + EXPECT_CALL(debug_visitor, OnDataFrameEnd(stream_->id())); stream_->OnStreamFrame(QuicStreamFrame(stream_->id(), false, /* offset = */ headers.length(), data)); EXPECT_EQ(kDataFramePayload, stream_->data()); @@ -2212,6 +2243,7 @@ // Trailing HEADERS frame referencing second dynamic table entry. std::string trailers = HeadersFrame(quiche::QuicheTextUtils::HexDecode("030080")); + EXPECT_CALL(debug_visitor, OnHeadersFrameReceived(stream_->id(), _)); stream_->OnStreamFrame(QuicStreamFrame(stream_->id(), true, /* offset = */ headers.length() + data.length(), trailers)); @@ -2220,6 +2252,7 @@ EXPECT_FALSE(stream_->trailers_decompressed()); EXPECT_CALL(*session_, WritevData(decoder_send_stream->id(), _, _, _, _, _)); + EXPECT_CALL(debug_visitor, OnHeadersDecoded(stream_->id(), _)); // Deliver second dynamic table entry to decoder. session_->qpack_decoder()->OnInsertWithoutNameReference("trailing", "foobar"); EXPECT_TRUE(stream_->trailers_decompressed()); @@ -2463,6 +2496,25 @@ ElementsAre(Pair("custom-key", "custom-value"))); } +TEST_P(QuicSpdyStreamIncrementalConsumptionTest, ReceiveUnknownFrame) { + if (!UsesHttp3()) { + return; + } + + Initialize(kShouldProcessData); + StrictMock<MockHttp3DebugVisitor> debug_visitor; + session_->set_debug_visitor(&debug_visitor); + + EXPECT_CALL(debug_visitor, + OnUnknownFrameStart(stream_->id(), /* frame_type = */ 0x21)); + EXPECT_CALL(debug_visitor, + OnUnknownFramePayload(stream_->id(), /* payload_length = */ 3)); + EXPECT_CALL(debug_visitor, OnUnknownFrameEnd(stream_->id())); + + std::string unknown_frame = UnknownFrame(0x21, "foo"); + OnStreamFrame(unknown_frame); +} + TEST_P(QuicSpdyStreamIncrementalConsumptionTest, UnknownFramesInterleaved) { if (!UsesHttp3()) { return; @@ -2554,10 +2606,16 @@ return; } - std::string headers = EncodeQpackHeaders({{"foo", "bar"}}); + StrictMock<MockHttp3DebugVisitor> debug_visitor; + session_->set_debug_visitor(&debug_visitor); + SpdyHeaderBlock pushed_headers; + pushed_headers["foo"] = "bar"; + std::string headers = EncodeQpackHeaders(pushed_headers); + + const QuicStreamId push_id = 1; PushPromiseFrame push_promise; - push_promise.push_id = 0x01; + push_promise.push_id = push_id; push_promise.headers = headers; std::unique_ptr<char[]> buffer; uint64_t length = HttpEncoder::SerializePushPromiseFrameWithOnlyPushId( @@ -2565,6 +2623,11 @@ std::string data = std::string(buffer.get(), length) + headers; QuicStreamFrame frame(stream_->id(), false, 0, data); + EXPECT_CALL(debug_visitor, + OnPushPromiseFrameReceived(stream_->id(), push_id)); + EXPECT_CALL(debug_visitor, + OnPushPromiseDecoded(stream_->id(), push_id, + AsHeaderList(pushed_headers))); EXPECT_CALL(*session_, OnPromiseHeaderList(stream_->id(), push_promise.push_id, headers.length(), _));
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h index 5c4fa0a..224263f 100644 --- a/quic/test_tools/quic_test_utils.h +++ b/quic/test_tools/quic_test_utils.h
@@ -17,6 +17,7 @@ #include "net/third_party/quiche/src/quic/core/congestion_control/send_algorithm_interface.h" #include "net/third_party/quiche/src/quic/core/http/quic_client_push_promise_index.h" #include "net/third_party/quiche/src/quic/core/http/quic_server_session_base.h" +#include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h" #include "net/third_party/quiche/src/quic/core/quic_connection.h" #include "net/third_party/quiche/src/quic/core/quic_framer.h" #include "net/third_party/quiche/src/quic/core/quic_packet_writer.h" @@ -802,6 +803,48 @@ std::unique_ptr<QuicCryptoStream> crypto_stream_; }; +class MockHttp3DebugVisitor : public Http3DebugVisitor { + public: + MOCK_METHOD1(OnControlStreamCreated, void(QuicStreamId)); + MOCK_METHOD1(OnQpackEncoderStreamCreated, void(QuicStreamId)); + MOCK_METHOD1(OnQpackDecoderStreamCreated, void(QuicStreamId)); + MOCK_METHOD1(OnPeerControlStreamCreated, void(QuicStreamId)); + MOCK_METHOD1(OnPeerQpackEncoderStreamCreated, void(QuicStreamId)); + MOCK_METHOD1(OnPeerQpackDecoderStreamCreated, void(QuicStreamId)); + + MOCK_METHOD1(OnCancelPushFrameReceived, void(CancelPushFrame)); + MOCK_METHOD1(OnSettingsFrameReceived, void(const SettingsFrame&)); + MOCK_METHOD1(OnGoAwayFrameReceived, void(GoAwayFrame)); + MOCK_METHOD1(OnMaxPushIdFrameReceived, void(MaxPushIdFrame)); + MOCK_METHOD1(OnPriorityUpdateFrameReceived, void(PriorityUpdateFrame)); + + MOCK_METHOD1(OnDataFrameStart, void(QuicStreamId)); + MOCK_METHOD2(OnDataFramePayload, void(QuicStreamId, QuicByteCount)); + MOCK_METHOD1(OnDataFrameEnd, void(QuicStreamId)); + + MOCK_METHOD2(OnHeadersFrameReceived, void(QuicStreamId, QuicByteCount)); + MOCK_METHOD2(OnHeadersDecoded, void(QuicStreamId, QuicHeaderList)); + + MOCK_METHOD2(OnPushPromiseFrameReceived, void(QuicStreamId, QuicStreamId)); + MOCK_METHOD3(OnPushPromiseDecoded, + void(QuicStreamId, QuicStreamId, QuicHeaderList)); + + MOCK_METHOD2(OnUnknownFrameStart, void(QuicStreamId, uint64_t)); + MOCK_METHOD2(OnUnknownFramePayload, void(QuicStreamId, QuicByteCount)); + MOCK_METHOD1(OnUnknownFrameEnd, void(QuicStreamId)); + + MOCK_METHOD1(OnSettingsFrameSent, void(const SettingsFrame&)); + MOCK_METHOD1(OnGoAwayFrameSent, void(QuicStreamId)); + MOCK_METHOD1(OnMaxPushIdFrameSent, void(MaxPushIdFrame)); + MOCK_METHOD1(OnPriorityUpdateFrameSent, void(PriorityUpdateFrame)); + + MOCK_METHOD2(OnDataFrameSent, void(QuicStreamId, QuicByteCount)); + MOCK_METHOD2(OnHeadersFrameSent, + void(QuicStreamId, const spdy::SpdyHeaderBlock&)); + MOCK_METHOD3(OnPushPromiseFrameSent, + void(QuicStreamId, QuicStreamId, const spdy::SpdyHeaderBlock&)); +}; + class TestQuicSpdyServerSession : public QuicServerSessionBase { public: // Takes ownership of |connection|.