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;