Read and interpret PRIORITY_UPDATE frame. gfe-relnote: n/a, change to QUIC v99-only code. Protected by existing disabled gfe2_reloadable_flag_quic_enable_version_99. PiperOrigin-RevId: 290869051 Change-Id: Ie5c016bd4254bffad7910d248e483976a249a27d
diff --git a/quic/core/http/quic_receive_control_stream.cc b/quic/core/http/quic_receive_control_stream.cc index f07e900..c66e696 100644 --- a/quic/core/http/quic_receive_control_stream.cc +++ b/quic/core/http/quic_receive_control_stream.cc
@@ -11,6 +11,7 @@ #include "net/third_party/quiche/src/quic/core/http/quic_spdy_session.h" #include "net/third_party/quiche/src/quic/core/quic_types.h" #include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h" +#include "net/third_party/quiche/src/common/platform/api/quiche_text_utils.h" namespace quic { @@ -36,10 +37,8 @@ } bool OnMaxPushIdFrame(const MaxPushIdFrame& frame) override { - if (stream_->session()->perspective() == Perspective::IS_SERVER) { - QuicSpdySession* spdy_session = - static_cast<QuicSpdySession*>(stream_->session()); - spdy_session->SetMaxAllowedPushId(frame.push_id); + if (stream_->spdy_session()->perspective() == Perspective::IS_SERVER) { + stream_->spdy_session()->SetMaxAllowedPushId(frame.push_id); return true; } CloseConnectionOnWrongFrame("Max Push Id"); @@ -47,13 +46,11 @@ } bool OnGoAwayFrame(const GoAwayFrame& frame) override { - QuicSpdySession* spdy_session = - static_cast<QuicSpdySession*>(stream_->session()); - if (spdy_session->perspective() == Perspective::IS_SERVER) { + if (stream_->spdy_session()->perspective() == Perspective::IS_SERVER) { CloseConnectionOnWrongFrame("Go Away"); return false; } - spdy_session->OnHttp3GoAway(frame.stream_id); + stream_->spdy_session()->OnHttp3GoAway(frame.stream_id); return true; } @@ -122,14 +119,12 @@ return false; } - bool OnPriorityUpdateFrameStart(QuicByteCount /*header_length*/) override { - // TODO(b/147306124): Implement. - return true; + bool OnPriorityUpdateFrameStart(QuicByteCount header_length) override { + return stream_->OnPriorityUpdateFrameStart(header_length); } - bool OnPriorityUpdateFrame(const PriorityUpdateFrame& /*frame*/) override { - // TODO(b/147306124): Implement. - return true; + bool OnPriorityUpdateFrame(const PriorityUpdateFrame& frame) override { + return stream_->OnPriorityUpdateFrame(frame); } bool OnUnknownFrameStart(uint64_t /* frame_type */, @@ -159,11 +154,14 @@ QuicReceiveControlStream* stream_; }; -QuicReceiveControlStream::QuicReceiveControlStream(PendingStream* pending) +QuicReceiveControlStream::QuicReceiveControlStream( + PendingStream* pending, + QuicSpdySession* spdy_session) : QuicStream(pending, READ_UNIDIRECTIONAL, /*is_static=*/true), settings_frame_received_(false), http_decoder_visitor_(std::make_unique<HttpDecoderVisitor>(this)), - decoder_(http_decoder_visitor_.get()) { + decoder_(http_decoder_visitor_.get()), + spdy_session_(spdy_session) { sequencer()->set_level_triggered(true); } @@ -216,14 +214,65 @@ bool QuicReceiveControlStream::OnSettingsFrame(const SettingsFrame& settings) { QUIC_DVLOG(1) << "Control Stream " << id() << " received settings frame: " << settings; - QuicSpdySession* spdy_session = static_cast<QuicSpdySession*>(session()); - if (spdy_session->debug_visitor() != nullptr) { - spdy_session->debug_visitor()->OnSettingsFrameReceived(settings); + if (spdy_session_->debug_visitor() != nullptr) { + spdy_session_->debug_visitor()->OnSettingsFrameReceived(settings); } for (const auto& setting : settings.values) { - spdy_session->OnSetting(setting.first, setting.second); + spdy_session_->OnSetting(setting.first, setting.second); } return true; } +bool QuicReceiveControlStream::OnPriorityUpdateFrameStart( + QuicByteCount /* header_length */) { + if (!settings_frame_received_) { + session()->connection()->CloseConnection( + QUIC_INVALID_STREAM_ID, + "PRIORITY_UPDATE frame received before SETTINGS.", + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + return false; + } + return true; +} + +bool QuicReceiveControlStream::OnPriorityUpdateFrame( + const PriorityUpdateFrame& priority) { + // TODO(b/147306124): Use a proper structured headers parser instead. + for (auto key_value : + quiche::QuicheTextUtils::Split(priority.priority_field_value, ',')) { + auto key_and_value = quiche::QuicheTextUtils::Split(key_value, '='); + if (key_and_value.size() != 2) { + continue; + } + + quiche::QuicheStringPiece key = key_and_value[0]; + quiche::QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(&key); + if (key != "u") { + continue; + } + + quiche::QuicheStringPiece value = key_and_value[1]; + int urgency; + if (!quiche::QuicheTextUtils::StringToInt(value, &urgency) || urgency < 0 || + urgency > 7) { + session()->connection()->CloseConnection( + QUIC_INVALID_STREAM_ID, + "Invalid value for PRIORITY_UPDATE urgency parameter.", + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + return false; + } + + if (priority.prioritized_element_type == REQUEST_STREAM) { + return spdy_session_->OnPriorityUpdateForRequestStream( + priority.prioritized_element_id, urgency); + } else { + return spdy_session_->OnPriorityUpdateForPushStream( + priority.prioritized_element_id, urgency); + } + } + + // Ignore frame if no urgency parameter can be parsed. + return true; +} + } // namespace quic
diff --git a/quic/core/http/quic_receive_control_stream.h b/quic/core/http/quic_receive_control_stream.h index 52f9663..e77ccf9 100644 --- a/quic/core/http/quic_receive_control_stream.h +++ b/quic/core/http/quic_receive_control_stream.h
@@ -18,7 +18,8 @@ // The receive control stream is peer initiated and is read only. class QUIC_EXPORT_PRIVATE QuicReceiveControlStream : public QuicStream { public: - explicit QuicReceiveControlStream(PendingStream* pending); + explicit QuicReceiveControlStream(PendingStream* pending, + QuicSpdySession* spdy_session); QuicReceiveControlStream(const QuicReceiveControlStream&) = delete; QuicReceiveControlStream& operator=(const QuicReceiveControlStream&) = delete; ~QuicReceiveControlStream() override; @@ -32,6 +33,8 @@ void SetUnblocked() { sequencer()->SetUnblocked(); } + QuicSpdySession* spdy_session() { return spdy_session_; } + private: class HttpDecoderVisitor; @@ -47,6 +50,8 @@ // HttpDecoder and its visitor. std::unique_ptr<HttpDecoderVisitor> http_decoder_visitor_; HttpDecoder decoder_; + + QuicSpdySession* const spdy_session_; }; } // namespace quic
diff --git a/quic/core/http/quic_receive_control_stream_test.cc b/quic/core/http/quic_receive_control_stream_test.cc index c669f85..489d983 100644 --- a/quic/core/http/quic_receive_control_stream_test.cc +++ b/quic/core/http/quic_receive_control_stream_test.cc
@@ -110,6 +110,15 @@ return std::string(buffer.get(), settings_frame_length); } + std::string SerializePriorityUpdateFrame( + const PriorityUpdateFrame& priority_update) { + std::unique_ptr<char[]> priority_buffer; + QuicByteCount priority_frame_length = + HttpEncoder::SerializePriorityUpdateFrame(priority_update, + &priority_buffer); + return std::string(priority_buffer.get(), priority_frame_length); + } + QuicStreamOffset NumBytesConsumed() { return QuicStreamPeer::sequencer(receive_control_stream_) ->NumBytesConsumed(); @@ -220,6 +229,24 @@ receive_control_stream_->OnStreamFrame(frame); } +TEST_P(QuicReceiveControlStreamTest, + ReceivePriorityUpdateFrameBeforeSettingsFrame) { + std::string serialized_frame = SerializePriorityUpdateFrame({}); + QuicStreamFrame data(receive_control_stream_->id(), /* fin = */ false, + /* offset = */ 1, serialized_frame); + + EXPECT_CALL( + *connection_, + CloseConnection(QUIC_INVALID_STREAM_ID, + "PRIORITY_UPDATE frame received before SETTINGS.", _)) + .WillOnce( + Invoke(connection_, &MockQuicConnection::ReallyCloseConnection)); + EXPECT_CALL(*connection_, SendConnectionClosePacket(_, _)); + EXPECT_CALL(session_, OnConnectionClosed(_, _)); + + receive_control_stream_->OnStreamFrame(data); +} + TEST_P(QuicReceiveControlStreamTest, ReceiveGoAwayFrame) { GoAwayFrame goaway; goaway.stream_id = 0x00;
diff --git a/quic/core/http/quic_spdy_session.cc b/quic/core/http/quic_spdy_session.cc index d177419..301e1d1 100644 --- a/quic/core/http/quic_spdy_session.cc +++ b/quic/core/http/quic_spdy_session.cc
@@ -496,6 +496,29 @@ stream->OnPriorityFrame(precedence); } +bool QuicSpdySession::OnPriorityUpdateForRequestStream(QuicStreamId stream_id, + int urgency) { + if (perspective() == Perspective::IS_CLIENT || + !QuicUtils::IsBidirectionalStreamId(stream_id) || + !QuicUtils::IsClientInitiatedStreamId(transport_version(), stream_id)) { + return true; + } + + // TODO(b/147306124): Signal error on invalid stream_id. + + MaybeSetStreamPriority(stream_id, spdy::SpdyStreamPrecedence(urgency)); + + // TODO(b/147306124): Buffer |urgency| for streams not open yet. + + return true; +} + +bool QuicSpdySession::OnPriorityUpdateForPushStream(QuicStreamId /*push_id*/, + int /*urgency*/) { + // TODO(b/147306124): Implement PRIORITY_UPDATE frames for pushed streams. + return true; +} + size_t QuicSpdySession::ProcessHeaderData(const struct iovec& iov) { QUIC_BUG_IF(destruction_indicator_ != 123456789) << "QuicSpdyStream use after free. " << destruction_indicator_ @@ -941,7 +964,8 @@ CloseConnectionOnDuplicateHttp3UnidirectionalStreams("Control"); return false; } - auto receive_stream = std::make_unique<QuicReceiveControlStream>(pending); + auto receive_stream = + std::make_unique<QuicReceiveControlStream>(pending, this); receive_control_stream_ = receive_stream.get(); ActivateStream(std::move(receive_stream)); receive_control_stream_->SetUnblocked();
diff --git a/quic/core/http/quic_spdy_session.h b/quic/core/http/quic_spdy_session.h index 9f7349f..53c7c09 100644 --- a/quic/core/http/quic_spdy_session.h +++ b/quic/core/http/quic_spdy_session.h
@@ -127,6 +127,14 @@ virtual void OnPriorityFrame(QuicStreamId stream_id, const spdy::SpdyStreamPrecedence& precedence); + // Called when an HTTP/3 PRIORITY_UPDATE frame has been received for a request + // stream. Returns false and closes connection if |stream_id| is invalid. + bool OnPriorityUpdateForRequestStream(QuicStreamId stream_id, int urgency); + + // Called when an HTTP/3 PRIORITY_UPDATE frame has been received for a push + // stream. Returns false and closes connection if |push_id| is invalid. + bool OnPriorityUpdateForPushStream(QuicStreamId push_id, int urgency); + // Sends contents of |iov| to h2_deframer_, returns number of bytes processed. size_t ProcessHeaderData(const struct iovec& iov);
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc index 319f3f4..5f3ea29 100644 --- a/quic/core/quic_session.cc +++ b/quic/core/quic_session.cc
@@ -1644,6 +1644,18 @@ } } +bool QuicSession::MaybeSetStreamPriority( + QuicStreamId stream_id, + const spdy::SpdyStreamPrecedence& precedence) { + auto active_stream = stream_map_.find(stream_id); + if (active_stream != stream_map_.end()) { + active_stream->second->SetPriority(precedence); + return true; + } + + return false; +} + bool QuicSession::IsClosedStream(QuicStreamId id) { DCHECK_NE(QuicUtils::GetInvalidStreamId(transport_version()), id); if (IsOpenStream(id)) {
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h index 6aa2b0a..f473156 100644 --- a/quic/core/quic_session.h +++ b/quic/core/quic_session.h
@@ -636,6 +636,10 @@ // times is safe. void DeleteConnection(); + // Call SetPriority() on stream id |id| and return true if stream is active. + bool MaybeSetStreamPriority(QuicStreamId stream_id, + const spdy::SpdyStreamPrecedence& precedence); + private: friend class test::QuicSessionPeer;