summaryrefslogtreecommitdiff
path: root/chromium/net/third_party/quiche/src/quiche/quic/core/http/quic_spdy_session.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/net/third_party/quiche/src/quiche/quic/core/http/quic_spdy_session.cc')
-rw-r--r--chromium/net/third_party/quiche/src/quiche/quic/core/http/quic_spdy_session.cc1949
1 files changed, 1949 insertions, 0 deletions
diff --git a/chromium/net/third_party/quiche/src/quiche/quic/core/http/quic_spdy_session.cc b/chromium/net/third_party/quiche/src/quiche/quic/core/http/quic_spdy_session.cc
new file mode 100644
index 00000000000..14550db5393
--- /dev/null
+++ b/chromium/net/third_party/quiche/src/quiche/quic/core/http/quic_spdy_session.cc
@@ -0,0 +1,1949 @@
+// Copyright (c) 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/quic/core/http/quic_spdy_session.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <limits>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "absl/base/attributes.h"
+#include "absl/strings/numbers.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/http/http_constants.h"
+#include "quiche/quic/core/http/http_decoder.h"
+#include "quiche/quic/core/http/http_frames.h"
+#include "quiche/quic/core/http/quic_headers_stream.h"
+#include "quiche/quic/core/http/web_transport_http3.h"
+#include "quiche/quic/core/quic_error_codes.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/quic/core/quic_utils.h"
+#include "quiche/quic/core/quic_versions.h"
+#include "quiche/quic/platform/api/quic_bug_tracker.h"
+#include "quiche/quic/platform/api/quic_exported_stats.h"
+#include "quiche/quic/platform/api/quic_flag_utils.h"
+#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+#include "quiche/quic/platform/api/quic_stack_trace.h"
+#include "quiche/common/platform/api/quiche_mem_slice.h"
+#include "quiche/spdy/core/http2_frame_decoder_adapter.h"
+
+using http2::Http2DecoderAdapter;
+using spdy::Http2WeightToSpdy3Priority;
+using spdy::Spdy3PriorityToHttp2Weight;
+using spdy::SpdyErrorCode;
+using spdy::SpdyFramer;
+using spdy::SpdyFramerDebugVisitorInterface;
+using spdy::SpdyFramerVisitorInterface;
+using spdy::SpdyFrameType;
+using spdy::SpdyHeaderBlock;
+using spdy::SpdyHeadersHandlerInterface;
+using spdy::SpdyHeadersIR;
+using spdy::SpdyPingId;
+using spdy::SpdyPriority;
+using spdy::SpdyPriorityIR;
+using spdy::SpdyPushPromiseIR;
+using spdy::SpdySerializedFrame;
+using spdy::SpdySettingsId;
+using spdy::SpdyStreamId;
+
+namespace quic {
+
+ABSL_CONST_INIT const size_t kMaxUnassociatedWebTransportStreams = 24;
+
+namespace {
+
+// Limit on HPACK encoder dynamic table size.
+// Only used for Google QUIC, not IETF QUIC.
+constexpr uint64_t kHpackEncoderDynamicTableSizeLimit = 16384;
+
+#define ENDPOINT \
+ (perspective() == Perspective::IS_SERVER ? "Server: " : "Client: ")
+
+// Class to forward ACCEPT_CH frame to QuicSpdySession,
+// and ignore every other frame.
+class AlpsFrameDecoder : public HttpDecoder::Visitor {
+ public:
+ explicit AlpsFrameDecoder(QuicSpdySession* session) : session_(session) {}
+ ~AlpsFrameDecoder() override = default;
+
+ // HttpDecoder::Visitor implementation.
+ void OnError(HttpDecoder* /*decoder*/) override {}
+ bool OnMaxPushIdFrame(const MaxPushIdFrame& /*frame*/) override {
+ error_detail_ = "MAX_PUSH_ID frame forbidden";
+ return false;
+ }
+ bool OnGoAwayFrame(const GoAwayFrame& /*frame*/) override {
+ error_detail_ = "GOAWAY frame forbidden";
+ return false;
+ }
+ bool OnSettingsFrameStart(QuicByteCount /*header_length*/) override {
+ return true;
+ }
+ bool OnSettingsFrame(const SettingsFrame& frame) override {
+ if (settings_frame_received_via_alps_) {
+ error_detail_ = "multiple SETTINGS frames";
+ return false;
+ }
+
+ settings_frame_received_via_alps_ = true;
+
+ error_detail_ = session_->OnSettingsFrameViaAlps(frame);
+ return !error_detail_;
+ }
+ bool OnDataFrameStart(QuicByteCount /*header_length*/, QuicByteCount
+ /*payload_length*/) override {
+ error_detail_ = "DATA frame forbidden";
+ return false;
+ }
+ bool OnDataFramePayload(absl::string_view /*payload*/) override {
+ QUICHE_NOTREACHED();
+ return false;
+ }
+ bool OnDataFrameEnd() override {
+ QUICHE_NOTREACHED();
+ return false;
+ }
+ bool OnHeadersFrameStart(QuicByteCount /*header_length*/,
+ QuicByteCount /*payload_length*/) override {
+ error_detail_ = "HEADERS frame forbidden";
+ return false;
+ }
+ bool OnHeadersFramePayload(absl::string_view /*payload*/) override {
+ QUICHE_NOTREACHED();
+ return false;
+ }
+ bool OnHeadersFrameEnd() override {
+ QUICHE_NOTREACHED();
+ return false;
+ }
+ bool OnPriorityUpdateFrameStart(QuicByteCount /*header_length*/) override {
+ error_detail_ = "PRIORITY_UPDATE frame forbidden";
+ return false;
+ }
+ bool OnPriorityUpdateFrame(const PriorityUpdateFrame& /*frame*/) override {
+ QUICHE_NOTREACHED();
+ return false;
+ }
+ bool OnAcceptChFrameStart(QuicByteCount /*header_length*/) override {
+ return true;
+ }
+ bool OnAcceptChFrame(const AcceptChFrame& frame) override {
+ session_->OnAcceptChFrameReceivedViaAlps(frame);
+ return true;
+ }
+ void OnWebTransportStreamFrameType(
+ QuicByteCount /*header_length*/,
+ WebTransportSessionId /*session_id*/) override {
+ QUICHE_NOTREACHED();
+ }
+ bool OnUnknownFrameStart(uint64_t /*frame_type*/,
+ QuicByteCount
+ /*header_length*/,
+ QuicByteCount /*payload_length*/) override {
+ return true;
+ }
+ bool OnUnknownFramePayload(absl::string_view /*payload*/) override {
+ return true;
+ }
+ bool OnUnknownFrameEnd() override { return true; }
+
+ const absl::optional<std::string>& error_detail() const {
+ return error_detail_;
+ }
+
+ private:
+ QuicSpdySession* const session_;
+ absl::optional<std::string> error_detail_;
+
+ // True if SETTINGS frame has been received via ALPS.
+ bool settings_frame_received_via_alps_ = false;
+};
+
+} // namespace
+
+// A SpdyFramerVisitor that passes HEADERS frames to the QuicSpdyStream, and
+// closes the connection if any unexpected frames are received.
+class QuicSpdySession::SpdyFramerVisitor
+ : public SpdyFramerVisitorInterface,
+ public SpdyFramerDebugVisitorInterface {
+ public:
+ explicit SpdyFramerVisitor(QuicSpdySession* session) : session_(session) {}
+ SpdyFramerVisitor(const SpdyFramerVisitor&) = delete;
+ SpdyFramerVisitor& operator=(const SpdyFramerVisitor&) = delete;
+
+ SpdyHeadersHandlerInterface* OnHeaderFrameStart(
+ SpdyStreamId /* stream_id */) override {
+ QUICHE_DCHECK(!VersionUsesHttp3(session_->transport_version()));
+ return &header_list_;
+ }
+
+ void OnHeaderFrameEnd(SpdyStreamId /* stream_id */) override {
+ QUICHE_DCHECK(!VersionUsesHttp3(session_->transport_version()));
+
+ LogHeaderCompressionRatioHistogram(
+ /* using_qpack = */ false,
+ /* is_sent = */ false, header_list_.compressed_header_bytes(),
+ header_list_.uncompressed_header_bytes());
+
+ if (session_->IsConnected()) {
+ session_->OnHeaderList(header_list_);
+ }
+ header_list_.Clear();
+ }
+
+ void OnStreamFrameData(SpdyStreamId /*stream_id*/, const char* /*data*/,
+ size_t /*len*/) override {
+ QUICHE_DCHECK(!VersionUsesHttp3(session_->transport_version()));
+ CloseConnection("SPDY DATA frame received.",
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ }
+
+ void OnStreamEnd(SpdyStreamId /*stream_id*/) override {
+ // The framer invokes OnStreamEnd after processing a frame that had the fin
+ // bit set.
+ }
+
+ void OnStreamPadding(SpdyStreamId /*stream_id*/, size_t /*len*/) override {
+ CloseConnection("SPDY frame padding received.",
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ }
+
+ void OnError(Http2DecoderAdapter::SpdyFramerError error,
+ std::string detailed_error) override {
+ QuicErrorCode code;
+ switch (error) {
+ case Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_INDEX_VARINT_ERROR:
+ code = QUIC_HPACK_INDEX_VARINT_ERROR;
+ break;
+ case Http2DecoderAdapter::SpdyFramerError::
+ SPDY_HPACK_NAME_LENGTH_VARINT_ERROR:
+ code = QUIC_HPACK_NAME_LENGTH_VARINT_ERROR;
+ break;
+ case Http2DecoderAdapter::SpdyFramerError::
+ SPDY_HPACK_VALUE_LENGTH_VARINT_ERROR:
+ code = QUIC_HPACK_VALUE_LENGTH_VARINT_ERROR;
+ break;
+ case Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_NAME_TOO_LONG:
+ code = QUIC_HPACK_NAME_TOO_LONG;
+ break;
+ case Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_VALUE_TOO_LONG:
+ code = QUIC_HPACK_VALUE_TOO_LONG;
+ break;
+ case Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_NAME_HUFFMAN_ERROR:
+ code = QUIC_HPACK_NAME_HUFFMAN_ERROR;
+ break;
+ case Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_VALUE_HUFFMAN_ERROR:
+ code = QUIC_HPACK_VALUE_HUFFMAN_ERROR;
+ break;
+ case Http2DecoderAdapter::SpdyFramerError::
+ SPDY_HPACK_MISSING_DYNAMIC_TABLE_SIZE_UPDATE:
+ code = QUIC_HPACK_MISSING_DYNAMIC_TABLE_SIZE_UPDATE;
+ break;
+ case Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_INVALID_INDEX:
+ code = QUIC_HPACK_INVALID_INDEX;
+ break;
+ case Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_INVALID_NAME_INDEX:
+ code = QUIC_HPACK_INVALID_NAME_INDEX;
+ break;
+ case Http2DecoderAdapter::SpdyFramerError::
+ SPDY_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_NOT_ALLOWED:
+ code = QUIC_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_NOT_ALLOWED;
+ break;
+ case Http2DecoderAdapter::SpdyFramerError::
+ SPDY_HPACK_INITIAL_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_LOW_WATER_MARK:
+ code = QUIC_HPACK_INITIAL_TABLE_SIZE_UPDATE_IS_ABOVE_LOW_WATER_MARK;
+ break;
+ case Http2DecoderAdapter::SpdyFramerError::
+ SPDY_HPACK_DYNAMIC_TABLE_SIZE_UPDATE_IS_ABOVE_ACKNOWLEDGED_SETTING:
+ code = QUIC_HPACK_TABLE_SIZE_UPDATE_IS_ABOVE_ACKNOWLEDGED_SETTING;
+ break;
+ case Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_TRUNCATED_BLOCK:
+ code = QUIC_HPACK_TRUNCATED_BLOCK;
+ break;
+ case Http2DecoderAdapter::SpdyFramerError::SPDY_HPACK_FRAGMENT_TOO_LONG:
+ code = QUIC_HPACK_FRAGMENT_TOO_LONG;
+ break;
+ case Http2DecoderAdapter::SpdyFramerError::
+ SPDY_HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT:
+ code = QUIC_HPACK_COMPRESSED_HEADER_SIZE_EXCEEDS_LIMIT;
+ break;
+ case Http2DecoderAdapter::SpdyFramerError::SPDY_DECOMPRESS_FAILURE:
+ code = QUIC_HEADERS_STREAM_DATA_DECOMPRESS_FAILURE;
+ break;
+ default:
+ code = QUIC_INVALID_HEADERS_STREAM_DATA;
+ }
+ CloseConnection(
+ absl::StrCat("SPDY framing error: ", detailed_error,
+ Http2DecoderAdapter::SpdyFramerErrorToString(error)),
+ code);
+ }
+
+ void OnDataFrameHeader(SpdyStreamId /*stream_id*/, size_t /*length*/,
+ bool /*fin*/) override {
+ QUICHE_DCHECK(!VersionUsesHttp3(session_->transport_version()));
+ CloseConnection("SPDY DATA frame received.",
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ }
+
+ void OnRstStream(SpdyStreamId /*stream_id*/,
+ SpdyErrorCode /*error_code*/) override {
+ CloseConnection("SPDY RST_STREAM frame received.",
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ }
+
+ void OnSetting(SpdySettingsId id, uint32_t value) override {
+ QUICHE_DCHECK(!VersionUsesHttp3(session_->transport_version()));
+ session_->OnSetting(id, value);
+ }
+
+ void OnSettingsEnd() override {
+ QUICHE_DCHECK(!VersionUsesHttp3(session_->transport_version()));
+ }
+
+ void OnPing(SpdyPingId /*unique_id*/, bool /*is_ack*/) override {
+ CloseConnection("SPDY PING frame received.",
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ }
+
+ void OnGoAway(SpdyStreamId /*last_accepted_stream_id*/,
+ SpdyErrorCode /*error_code*/) override {
+ CloseConnection("SPDY GOAWAY frame received.",
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ }
+
+ void OnHeaders(SpdyStreamId stream_id, bool has_priority, int weight,
+ SpdyStreamId /* parent_stream_id */, bool /* exclusive */,
+ bool fin, bool /*end*/) override {
+ if (!session_->IsConnected()) {
+ return;
+ }
+
+ if (VersionUsesHttp3(session_->transport_version())) {
+ CloseConnection("HEADERS frame not allowed on headers stream.",
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ return;
+ }
+
+ QUIC_BUG_IF(quic_bug_12477_1,
+ session_->destruction_indicator() != 123456789)
+ << "QuicSpdyStream use after free. "
+ << session_->destruction_indicator() << QuicStackTrace();
+
+ SpdyPriority priority =
+ has_priority ? Http2WeightToSpdy3Priority(weight) : 0;
+ session_->OnHeaders(stream_id, has_priority,
+ spdy::SpdyStreamPrecedence(priority), fin);
+ }
+
+ void OnWindowUpdate(SpdyStreamId /*stream_id*/,
+ int /*delta_window_size*/) override {
+ CloseConnection("SPDY WINDOW_UPDATE frame received.",
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ }
+
+ void OnPushPromise(SpdyStreamId stream_id, SpdyStreamId promised_stream_id,
+ bool /*end*/) override {
+ QUICHE_DCHECK(!VersionUsesHttp3(session_->transport_version()));
+ if (session_->perspective() != Perspective::IS_CLIENT) {
+ CloseConnection("PUSH_PROMISE not supported.",
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ return;
+ }
+ if (!session_->IsConnected()) {
+ return;
+ }
+ session_->OnPushPromise(stream_id, promised_stream_id);
+ }
+
+ void OnContinuation(SpdyStreamId /*stream_id*/, bool /*end*/) override {}
+
+ void OnPriority(SpdyStreamId stream_id, SpdyStreamId /* parent_id */,
+ int weight, bool /* exclusive */) override {
+ QUICHE_DCHECK(!VersionUsesHttp3(session_->transport_version()));
+ if (!session_->IsConnected()) {
+ return;
+ }
+ SpdyPriority priority = Http2WeightToSpdy3Priority(weight);
+ session_->OnPriority(stream_id, spdy::SpdyStreamPrecedence(priority));
+ }
+
+ void OnPriorityUpdate(SpdyStreamId /*prioritized_stream_id*/,
+ absl::string_view /*priority_field_value*/) override {
+ // TODO(b/171470299): Parse and call
+ // QuicSpdySession::OnPriorityUpdateForRequestStream().
+ }
+
+ bool OnUnknownFrame(SpdyStreamId /*stream_id*/,
+ uint8_t /*frame_type*/) override {
+ CloseConnection("Unknown frame type received.",
+ QUIC_INVALID_HEADERS_STREAM_DATA);
+ return false;
+ }
+
+ // SpdyFramerDebugVisitorInterface implementation
+ void OnSendCompressedFrame(SpdyStreamId /*stream_id*/, SpdyFrameType /*type*/,
+ size_t payload_len, size_t frame_len) override {
+ if (payload_len == 0) {
+ QUIC_BUG(quic_bug_10360_1) << "Zero payload length.";
+ return;
+ }
+ int compression_pct = 100 - (100 * frame_len) / payload_len;
+ QUIC_DVLOG(1) << "Net.QuicHpackCompressionPercentage: " << compression_pct;
+ }
+
+ void OnReceiveCompressedFrame(SpdyStreamId /*stream_id*/,
+ SpdyFrameType /*type*/,
+ size_t frame_len) override {
+ if (session_->IsConnected()) {
+ session_->OnCompressedFrameSize(frame_len);
+ }
+ }
+
+ void set_max_header_list_size(size_t max_header_list_size) {
+ header_list_.set_max_header_list_size(max_header_list_size);
+ }
+
+ private:
+ void CloseConnection(const std::string& details, QuicErrorCode code) {
+ if (session_->IsConnected()) {
+ session_->CloseConnectionWithDetails(code, details);
+ }
+ }
+
+ QuicSpdySession* session_;
+ QuicHeaderList header_list_;
+};
+
+Http3DebugVisitor::Http3DebugVisitor() {}
+
+Http3DebugVisitor::~Http3DebugVisitor() {}
+
+// Expected unidirectional static streams Requirement can be found at
+// https://tools.ietf.org/html/draft-ietf-quic-http-22#section-6.2.
+QuicSpdySession::QuicSpdySession(
+ QuicConnection* connection, QuicSession::Visitor* visitor,
+ const QuicConfig& config, const ParsedQuicVersionVector& supported_versions)
+ : QuicSession(connection, visitor, config, supported_versions,
+ /*num_expected_unidirectional_static_streams = */
+ VersionUsesHttp3(connection->transport_version())
+ ? static_cast<QuicStreamCount>(
+ kHttp3StaticUnidirectionalStreamCount)
+ : 0u,
+ std::make_unique<DatagramObserver>(this)),
+ send_control_stream_(nullptr),
+ receive_control_stream_(nullptr),
+ qpack_encoder_receive_stream_(nullptr),
+ qpack_decoder_receive_stream_(nullptr),
+ qpack_encoder_send_stream_(nullptr),
+ qpack_decoder_send_stream_(nullptr),
+ qpack_maximum_dynamic_table_capacity_(
+ kDefaultQpackMaxDynamicTableCapacity),
+ qpack_maximum_blocked_streams_(kDefaultMaximumBlockedStreams),
+ max_inbound_header_list_size_(kDefaultMaxUncompressedHeaderSize),
+ max_outbound_header_list_size_(std::numeric_limits<size_t>::max()),
+ stream_id_(
+ QuicUtils::GetInvalidStreamId(connection->transport_version())),
+ promised_stream_id_(
+ QuicUtils::GetInvalidStreamId(connection->transport_version())),
+ frame_len_(0),
+ fin_(false),
+ spdy_framer_(SpdyFramer::ENABLE_COMPRESSION),
+ spdy_framer_visitor_(new SpdyFramerVisitor(this)),
+ debug_visitor_(nullptr),
+ destruction_indicator_(123456789),
+ allow_extended_connect_(
+ GetQuicReloadableFlag(quic_verify_request_headers_2) &&
+ perspective() == Perspective::IS_SERVER &&
+ VersionUsesHttp3(transport_version())) {
+ h2_deframer_.set_visitor(spdy_framer_visitor_.get());
+ h2_deframer_.set_debug_visitor(spdy_framer_visitor_.get());
+ spdy_framer_.set_debug_visitor(spdy_framer_visitor_.get());
+}
+
+QuicSpdySession::~QuicSpdySession() {
+ QUIC_BUG_IF(quic_bug_12477_2, destruction_indicator_ != 123456789)
+ << "QuicSpdySession use after free. " << destruction_indicator_
+ << QuicStackTrace();
+ destruction_indicator_ = 987654321;
+}
+
+void QuicSpdySession::Initialize() {
+ QuicSession::Initialize();
+
+ FillSettingsFrame();
+ if (!VersionUsesHttp3(transport_version())) {
+ if (perspective() == Perspective::IS_SERVER) {
+ set_largest_peer_created_stream_id(
+ QuicUtils::GetHeadersStreamId(transport_version()));
+ } else {
+ QuicStreamId headers_stream_id = GetNextOutgoingBidirectionalStreamId();
+ QUICHE_DCHECK_EQ(headers_stream_id,
+ QuicUtils::GetHeadersStreamId(transport_version()));
+ }
+ auto headers_stream = std::make_unique<QuicHeadersStream>((this));
+ QUICHE_DCHECK_EQ(QuicUtils::GetHeadersStreamId(transport_version()),
+ headers_stream->id());
+
+ headers_stream_ = headers_stream.get();
+ ActivateStream(std::move(headers_stream));
+ } else {
+ qpack_encoder_ = std::make_unique<QpackEncoder>(this);
+ qpack_decoder_ =
+ std::make_unique<QpackDecoder>(qpack_maximum_dynamic_table_capacity_,
+ qpack_maximum_blocked_streams_, this);
+ MaybeInitializeHttp3UnidirectionalStreams();
+ }
+
+ spdy_framer_visitor_->set_max_header_list_size(max_inbound_header_list_size_);
+
+ // Limit HPACK buffering to 2x header list size limit.
+ h2_deframer_.GetHpackDecoder()->set_max_decode_buffer_size_bytes(
+ 2 * max_inbound_header_list_size_);
+}
+
+void QuicSpdySession::FillSettingsFrame() {
+ settings_.values[SETTINGS_QPACK_MAX_TABLE_CAPACITY] =
+ qpack_maximum_dynamic_table_capacity_;
+ settings_.values[SETTINGS_QPACK_BLOCKED_STREAMS] =
+ qpack_maximum_blocked_streams_;
+ settings_.values[SETTINGS_MAX_FIELD_SECTION_SIZE] =
+ max_inbound_header_list_size_;
+ if (version().UsesHttp3()) {
+ HttpDatagramSupport local_http_datagram_support =
+ LocalHttpDatagramSupport();
+ if (local_http_datagram_support == HttpDatagramSupport::kDraft00 ||
+ local_http_datagram_support == HttpDatagramSupport::kDraft00And04) {
+ settings_.values[SETTINGS_H3_DATAGRAM_DRAFT00] = 1;
+ }
+ if (local_http_datagram_support == HttpDatagramSupport::kDraft04 ||
+ local_http_datagram_support == HttpDatagramSupport::kDraft00And04) {
+ settings_.values[SETTINGS_H3_DATAGRAM_DRAFT04] = 1;
+ }
+ }
+ if (WillNegotiateWebTransport()) {
+ settings_.values[SETTINGS_WEBTRANS_DRAFT00] = 1;
+ }
+ if (allow_extended_connect()) {
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_verify_request_headers_2, 1, 3);
+ settings_.values[SETTINGS_ENABLE_CONNECT_PROTOCOL] = 1;
+ }
+}
+
+void QuicSpdySession::OnDecoderStreamError(QuicErrorCode error_code,
+ absl::string_view error_message) {
+ QUICHE_DCHECK(VersionUsesHttp3(transport_version()));
+
+ CloseConnectionWithDetails(
+ error_code, absl::StrCat("Decoder stream error: ", error_message));
+}
+
+void QuicSpdySession::OnEncoderStreamError(QuicErrorCode error_code,
+ absl::string_view error_message) {
+ QUICHE_DCHECK(VersionUsesHttp3(transport_version()));
+
+ CloseConnectionWithDetails(
+ error_code, absl::StrCat("Encoder stream error: ", error_message));
+}
+
+void QuicSpdySession::OnStreamHeadersPriority(
+ QuicStreamId stream_id, const spdy::SpdyStreamPrecedence& precedence) {
+ QuicSpdyStream* stream = GetOrCreateSpdyDataStream(stream_id);
+ if (!stream) {
+ // It's quite possible to receive headers after a stream has been reset.
+ return;
+ }
+ stream->OnStreamHeadersPriority(precedence);
+}
+
+void QuicSpdySession::OnStreamHeaderList(QuicStreamId stream_id, bool fin,
+ size_t frame_len,
+ const QuicHeaderList& header_list) {
+ if (IsStaticStream(stream_id)) {
+ connection()->CloseConnection(
+ QUIC_INVALID_HEADERS_STREAM_DATA, "stream is static",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
+ QuicSpdyStream* stream = GetOrCreateSpdyDataStream(stream_id);
+ if (stream == nullptr) {
+ // The stream no longer exists, but trailing headers may contain the final
+ // byte offset necessary for flow control and open stream accounting.
+ size_t final_byte_offset = 0;
+ for (const auto& header : header_list) {
+ const std::string& header_key = header.first;
+ const std::string& header_value = header.second;
+ if (header_key == kFinalOffsetHeaderKey) {
+ if (!absl::SimpleAtoi(header_value, &final_byte_offset)) {
+ connection()->CloseConnection(
+ QUIC_INVALID_HEADERS_STREAM_DATA,
+ "Trailers are malformed (no final offset)",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
+ QUIC_DVLOG(1) << ENDPOINT
+ << "Received final byte offset in trailers for stream "
+ << stream_id << ", which no longer exists.";
+ OnFinalByteOffsetReceived(stream_id, final_byte_offset);
+ }
+ }
+
+ // It's quite possible to receive headers after a stream has been reset.
+ return;
+ }
+ stream->OnStreamHeaderList(fin, frame_len, header_list);
+}
+
+void QuicSpdySession::OnPriorityFrame(
+ QuicStreamId stream_id, const spdy::SpdyStreamPrecedence& precedence) {
+ QuicSpdyStream* stream = GetOrCreateSpdyDataStream(stream_id);
+ if (!stream) {
+ // It's quite possible to receive a PRIORITY frame after a stream has been
+ // reset.
+ return;
+ }
+ stream->OnPriorityFrame(precedence);
+}
+
+bool QuicSpdySession::OnPriorityUpdateForRequestStream(QuicStreamId stream_id,
+ int urgency) {
+ if (perspective() == Perspective::IS_CLIENT ||
+ !QuicUtils::IsBidirectionalStreamId(stream_id, version()) ||
+ !QuicUtils::IsClientInitiatedStreamId(transport_version(), stream_id)) {
+ return true;
+ }
+
+ QuicStreamCount advertised_max_incoming_bidirectional_streams =
+ GetAdvertisedMaxIncomingBidirectionalStreams();
+ if (advertised_max_incoming_bidirectional_streams == 0 ||
+ stream_id > QuicUtils::GetFirstBidirectionalStreamId(
+ transport_version(), Perspective::IS_CLIENT) +
+ QuicUtils::StreamIdDelta(transport_version()) *
+ (advertised_max_incoming_bidirectional_streams - 1)) {
+ connection()->CloseConnection(
+ QUIC_INVALID_STREAM_ID,
+ "PRIORITY_UPDATE frame received for invalid stream.",
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return false;
+ }
+
+ if (MaybeSetStreamPriority(stream_id, spdy::SpdyStreamPrecedence(urgency))) {
+ return true;
+ }
+
+ if (IsClosedStream(stream_id)) {
+ return true;
+ }
+
+ buffered_stream_priorities_[stream_id] = urgency;
+
+ if (buffered_stream_priorities_.size() >
+ 10 * max_open_incoming_bidirectional_streams()) {
+ // This should never happen, because |buffered_stream_priorities_| should
+ // only contain entries for streams that are allowed to be open by the peer
+ // but have not been opened yet.
+ std::string error_message =
+ absl::StrCat("Too many stream priority values buffered: ",
+ buffered_stream_priorities_.size(),
+ ", which should not exceed the incoming stream limit of ",
+ max_open_incoming_bidirectional_streams());
+ QUIC_BUG(quic_bug_10360_2) << error_message;
+ connection()->CloseConnection(
+ QUIC_INTERNAL_ERROR, error_message,
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return false;
+ }
+
+ 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(quic_bug_12477_4, destruction_indicator_ != 123456789)
+ << "QuicSpdyStream use after free. " << destruction_indicator_
+ << QuicStackTrace();
+ return h2_deframer_.ProcessInput(static_cast<char*>(iov.iov_base),
+ iov.iov_len);
+}
+
+size_t QuicSpdySession::WriteHeadersOnHeadersStream(
+ QuicStreamId id, SpdyHeaderBlock headers, bool fin,
+ const spdy::SpdyStreamPrecedence& precedence,
+ quiche::QuicheReferenceCountedPointer<QuicAckListenerInterface>
+ ack_listener) {
+ QUICHE_DCHECK(!VersionUsesHttp3(transport_version()));
+
+ return WriteHeadersOnHeadersStreamImpl(
+ id, std::move(headers), fin,
+ /* parent_stream_id = */ 0,
+ Spdy3PriorityToHttp2Weight(precedence.spdy3_priority()),
+ /* exclusive = */ false, std::move(ack_listener));
+}
+
+size_t QuicSpdySession::WritePriority(QuicStreamId id,
+ QuicStreamId parent_stream_id, int weight,
+ bool exclusive) {
+ QUICHE_DCHECK(!VersionUsesHttp3(transport_version()));
+ SpdyPriorityIR priority_frame(id, parent_stream_id, weight, exclusive);
+ SpdySerializedFrame frame(spdy_framer_.SerializeFrame(priority_frame));
+ headers_stream()->WriteOrBufferData(
+ absl::string_view(frame.data(), frame.size()), false, nullptr);
+ return frame.size();
+}
+
+void QuicSpdySession::WriteHttp3PriorityUpdate(
+ const PriorityUpdateFrame& priority_update) {
+ QUICHE_DCHECK(VersionUsesHttp3(transport_version()));
+
+ send_control_stream_->WritePriorityUpdate(priority_update);
+}
+
+void QuicSpdySession::OnHttp3GoAway(uint64_t id) {
+ QUIC_BUG_IF(quic_bug_12477_5, !version().UsesHttp3())
+ << "HTTP/3 GOAWAY received on version " << version();
+
+ if (last_received_http3_goaway_id_.has_value() &&
+ id > last_received_http3_goaway_id_.value()) {
+ CloseConnectionWithDetails(
+ QUIC_HTTP_GOAWAY_ID_LARGER_THAN_PREVIOUS,
+ absl::StrCat("GOAWAY received with ID ", id,
+ " greater than previously received ID ",
+ last_received_http3_goaway_id_.value()));
+ return;
+ }
+ last_received_http3_goaway_id_ = id;
+
+ if (perspective() == Perspective::IS_SERVER) {
+ // TODO(b/151749109): Cancel server pushes with push ID larger than |id|.
+ return;
+ }
+
+ // QuicStreamId is uint32_t. Casting to this narrower type is well-defined
+ // and preserves the lower 32 bits. Both IsBidirectionalStreamId() and
+ // IsIncomingStream() give correct results, because their return value is
+ // determined by the least significant two bits.
+ QuicStreamId stream_id = static_cast<QuicStreamId>(id);
+ if (!QuicUtils::IsBidirectionalStreamId(stream_id, version()) ||
+ IsIncomingStream(stream_id)) {
+ CloseConnectionWithDetails(QUIC_HTTP_GOAWAY_INVALID_STREAM_ID,
+ "GOAWAY with invalid stream ID");
+ return;
+ }
+
+ // TODO(b/161252736): Cancel client requests with ID larger than |id|.
+ // If |id| is larger than numeric_limits<QuicStreamId>::max(), then use
+ // max() instead of downcast value.
+}
+
+bool QuicSpdySession::OnStreamsBlockedFrame(
+ const QuicStreamsBlockedFrame& frame) {
+ if (!QuicSession::OnStreamsBlockedFrame(frame)) {
+ return false;
+ }
+
+ // The peer asked for stream space more than this implementation has. Send
+ // goaway.
+ if (perspective() == Perspective::IS_SERVER &&
+ frame.stream_count >= QuicUtils::GetMaxStreamCount()) {
+ QUICHE_DCHECK_EQ(frame.stream_count, QuicUtils::GetMaxStreamCount());
+ SendHttp3GoAway(QUIC_PEER_GOING_AWAY, "stream count too large");
+ }
+ return true;
+}
+
+void QuicSpdySession::SendHttp3GoAway(QuicErrorCode error_code,
+ const std::string& reason) {
+ QUICHE_DCHECK(VersionUsesHttp3(transport_version()));
+ if (!IsEncryptionEstablished()) {
+ QUIC_CODE_COUNT(quic_h3_goaway_before_encryption_established);
+ connection()->CloseConnection(
+ error_code, reason,
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
+ QuicStreamId stream_id;
+
+ stream_id = QuicUtils::GetMaxClientInitiatedBidirectionalStreamId(
+ transport_version());
+ if (last_sent_http3_goaway_id_.has_value()) {
+ if (last_sent_http3_goaway_id_.value() == stream_id) {
+ // Do not send GOAWAY twice.
+ return;
+ }
+ if (last_sent_http3_goaway_id_.value() < stream_id) {
+ // A previous GOAWAY frame was sent with smaller stream ID. This is not
+ // possible, because the only time a GOAWAY frame with non-maximal
+ // stream ID is sent is right before closing connection.
+ QUIC_BUG(quic_bug_10360_3)
+ << "Not sending GOAWAY frame with " << stream_id
+ << " because one with " << last_sent_http3_goaway_id_.value()
+ << " already sent on connection " << connection()->connection_id();
+ return;
+ }
+ }
+
+ send_control_stream_->SendGoAway(stream_id);
+ last_sent_http3_goaway_id_ = stream_id;
+}
+
+void QuicSpdySession::WritePushPromise(QuicStreamId original_stream_id,
+ QuicStreamId promised_stream_id,
+ SpdyHeaderBlock headers) {
+ if (perspective() == Perspective::IS_CLIENT) {
+ QUIC_BUG(quic_bug_10360_4) << "Client shouldn't send PUSH_PROMISE";
+ return;
+ }
+
+ if (VersionUsesHttp3(transport_version())) {
+ QUIC_BUG(quic_bug_12477_6)
+ << "Support for server push over HTTP/3 has been removed.";
+ return;
+ }
+
+ SpdyPushPromiseIR push_promise(original_stream_id, promised_stream_id,
+ std::move(headers));
+ // PUSH_PROMISE must not be the last frame sent out, at least followed by
+ // response headers.
+ push_promise.set_fin(false);
+
+ SpdySerializedFrame frame(spdy_framer_.SerializeFrame(push_promise));
+ headers_stream()->WriteOrBufferData(
+ absl::string_view(frame.data(), frame.size()), false, nullptr);
+}
+
+void QuicSpdySession::SendInitialData() {
+ if (!VersionUsesHttp3(transport_version())) {
+ return;
+ }
+ QuicConnection::ScopedPacketFlusher flusher(connection());
+ send_control_stream_->MaybeSendSettingsFrame();
+}
+
+QpackEncoder* QuicSpdySession::qpack_encoder() {
+ QUICHE_DCHECK(VersionUsesHttp3(transport_version()));
+
+ return qpack_encoder_.get();
+}
+
+QpackDecoder* QuicSpdySession::qpack_decoder() {
+ QUICHE_DCHECK(VersionUsesHttp3(transport_version()));
+
+ return qpack_decoder_.get();
+}
+
+void QuicSpdySession::OnStreamCreated(QuicSpdyStream* stream) {
+ auto it = buffered_stream_priorities_.find(stream->id());
+ if (it == buffered_stream_priorities_.end()) {
+ return;
+ }
+
+ stream->SetPriority(spdy::SpdyStreamPrecedence(it->second));
+ buffered_stream_priorities_.erase(it);
+}
+
+QuicSpdyStream* QuicSpdySession::GetOrCreateSpdyDataStream(
+ const QuicStreamId stream_id) {
+ QuicStream* stream = GetOrCreateStream(stream_id);
+ if (stream && stream->is_static()) {
+ QUIC_BUG(quic_bug_10360_5)
+ << "GetOrCreateSpdyDataStream returns static stream " << stream_id
+ << " in version " << transport_version() << "\n"
+ << QuicStackTrace();
+ connection()->CloseConnection(
+ QUIC_INVALID_STREAM_ID,
+ absl::StrCat("stream ", stream_id, " is static"),
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return nullptr;
+ }
+ return static_cast<QuicSpdyStream*>(stream);
+}
+
+void QuicSpdySession::OnNewEncryptionKeyAvailable(
+ EncryptionLevel level, std::unique_ptr<QuicEncrypter> encrypter) {
+ QuicSession::OnNewEncryptionKeyAvailable(level, std::move(encrypter));
+ if (IsEncryptionEstablished()) {
+ // Send H3 SETTINGs once encryption is established.
+ SendInitialData();
+ }
+}
+
+bool QuicSpdySession::ShouldNegotiateWebTransport() { return false; }
+
+bool QuicSpdySession::ShouldNegotiateDatagramContexts() { return false; }
+
+bool QuicSpdySession::ShouldValidateWebTransportVersion() const { return true; }
+
+bool QuicSpdySession::WillNegotiateWebTransport() {
+ return LocalHttpDatagramSupport() != HttpDatagramSupport::kNone &&
+ version().UsesHttp3() && ShouldNegotiateWebTransport();
+}
+
+// True if there are open HTTP requests.
+bool QuicSpdySession::ShouldKeepConnectionAlive() const {
+ QUICHE_DCHECK(VersionUsesHttp3(transport_version()) ||
+ 0u == pending_streams_size());
+ return GetNumActiveStreams() + pending_streams_size() > 0;
+}
+
+bool QuicSpdySession::UsesPendingStreamForFrame(QuicFrameType type,
+ QuicStreamId stream_id) const {
+ // Pending streams can only be used to handle unidirectional stream with
+ // STREAM & RESET_STREAM frames in IETF QUIC.
+ return VersionUsesHttp3(transport_version()) &&
+ (type == STREAM_FRAME || type == RST_STREAM_FRAME) &&
+ QuicUtils::GetStreamType(stream_id, perspective(),
+ IsIncomingStream(stream_id),
+ version()) == READ_UNIDIRECTIONAL;
+}
+
+size_t QuicSpdySession::WriteHeadersOnHeadersStreamImpl(
+ QuicStreamId id, spdy::SpdyHeaderBlock headers, bool fin,
+ QuicStreamId parent_stream_id, int weight, bool exclusive,
+ quiche::QuicheReferenceCountedPointer<QuicAckListenerInterface>
+ ack_listener) {
+ QUICHE_DCHECK(!VersionUsesHttp3(transport_version()));
+
+ const QuicByteCount uncompressed_size = headers.TotalBytesUsed();
+ SpdyHeadersIR headers_frame(id, std::move(headers));
+ headers_frame.set_fin(fin);
+ if (perspective() == Perspective::IS_CLIENT) {
+ headers_frame.set_has_priority(true);
+ headers_frame.set_parent_stream_id(parent_stream_id);
+ headers_frame.set_weight(weight);
+ headers_frame.set_exclusive(exclusive);
+ }
+ SpdySerializedFrame frame(spdy_framer_.SerializeFrame(headers_frame));
+ headers_stream()->WriteOrBufferData(
+ absl::string_view(frame.data(), frame.size()), false,
+ std::move(ack_listener));
+
+ // Calculate compressed header block size without framing overhead.
+ QuicByteCount compressed_size = frame.size();
+ compressed_size -= spdy::kFrameHeaderSize;
+ if (perspective() == Perspective::IS_CLIENT) {
+ // Exclusive bit and Stream Dependency are four bytes, weight is one more.
+ compressed_size -= 5;
+ }
+
+ LogHeaderCompressionRatioHistogram(
+ /* using_qpack = */ false,
+ /* is_sent = */ true, compressed_size, uncompressed_size);
+
+ return frame.size();
+}
+
+void QuicSpdySession::OnPromiseHeaderList(
+ QuicStreamId /*stream_id*/, QuicStreamId /*promised_stream_id*/,
+ size_t /*frame_len*/, const QuicHeaderList& /*header_list*/) {
+ std::string error =
+ "OnPromiseHeaderList should be overridden in client code.";
+ QUIC_BUG(quic_bug_10360_6) << error;
+ connection()->CloseConnection(QUIC_INTERNAL_ERROR, error,
+ ConnectionCloseBehavior::SILENT_CLOSE);
+}
+
+bool QuicSpdySession::ResumeApplicationState(ApplicationState* cached_state) {
+ QUICHE_DCHECK_EQ(perspective(), Perspective::IS_CLIENT);
+ QUICHE_DCHECK(VersionUsesHttp3(transport_version()));
+
+ SettingsFrame out;
+ if (!HttpDecoder::DecodeSettings(
+ reinterpret_cast<char*>(cached_state->data()), cached_state->size(),
+ &out)) {
+ return false;
+ }
+
+ if (debug_visitor_ != nullptr) {
+ debug_visitor_->OnSettingsFrameResumed(out);
+ }
+ QUICHE_DCHECK(streams_waiting_for_settings_.empty());
+ for (const auto& setting : out.values) {
+ OnSetting(setting.first, setting.second);
+ }
+ return true;
+}
+
+absl::optional<std::string> QuicSpdySession::OnAlpsData(
+ const uint8_t* alps_data, size_t alps_length) {
+ AlpsFrameDecoder alps_frame_decoder(this);
+ HttpDecoder decoder(&alps_frame_decoder);
+ decoder.ProcessInput(reinterpret_cast<const char*>(alps_data), alps_length);
+ if (alps_frame_decoder.error_detail()) {
+ return alps_frame_decoder.error_detail();
+ }
+
+ if (decoder.error() != QUIC_NO_ERROR) {
+ return decoder.error_detail();
+ }
+
+ if (!decoder.AtFrameBoundary()) {
+ return "incomplete HTTP/3 frame";
+ }
+
+ return absl::nullopt;
+}
+
+void QuicSpdySession::OnAcceptChFrameReceivedViaAlps(
+ const AcceptChFrame& frame) {
+ if (debug_visitor_) {
+ debug_visitor_->OnAcceptChFrameReceivedViaAlps(frame);
+ }
+}
+
+bool QuicSpdySession::OnSettingsFrame(const SettingsFrame& frame) {
+ QUICHE_DCHECK(VersionUsesHttp3(transport_version()));
+ if (debug_visitor_ != nullptr) {
+ debug_visitor_->OnSettingsFrameReceived(frame);
+ }
+ for (const auto& setting : frame.values) {
+ if (!OnSetting(setting.first, setting.second)) {
+ return false;
+ }
+ }
+ for (QuicStreamId stream_id : streams_waiting_for_settings_) {
+ QUICHE_DCHECK(ShouldBufferRequestsUntilSettings());
+ QuicSpdyStream* stream = GetOrCreateSpdyDataStream(stream_id);
+ if (stream == nullptr) {
+ // The stream may no longer exist, since it is possible for a stream to
+ // get reset while waiting for the SETTINGS frame.
+ continue;
+ }
+ stream->OnDataAvailable();
+ }
+ streams_waiting_for_settings_.clear();
+ return true;
+}
+
+absl::optional<std::string> QuicSpdySession::OnSettingsFrameViaAlps(
+ const SettingsFrame& frame) {
+ QUICHE_DCHECK(VersionUsesHttp3(transport_version()));
+
+ if (debug_visitor_ != nullptr) {
+ debug_visitor_->OnSettingsFrameReceivedViaAlps(frame);
+ }
+ for (const auto& setting : frame.values) {
+ if (!OnSetting(setting.first, setting.second)) {
+ // Do not bother adding the setting identifier or value to the error
+ // message, because OnSetting() already closed the connection, therefore
+ // the error message will be ignored.
+ return "error parsing setting";
+ }
+ }
+ return absl::nullopt;
+}
+
+bool QuicSpdySession::VerifySettingIsZeroOrOne(uint64_t id, uint64_t value) {
+ if (value == 0 || value == 1) {
+ return true;
+ }
+ std::string error_details = absl::StrCat(
+ "Received ",
+ H3SettingsToString(static_cast<Http3AndQpackSettingsIdentifiers>(id)),
+ " with invalid value ", value);
+ QUIC_PEER_BUG(bad received setting) << ENDPOINT << error_details;
+ CloseConnectionWithDetails(QUIC_HTTP_INVALID_SETTING_VALUE, error_details);
+ return false;
+}
+
+bool QuicSpdySession::OnSetting(uint64_t id, uint64_t value) {
+ any_settings_received_ = true;
+
+ if (VersionUsesHttp3(transport_version())) {
+ // SETTINGS frame received on the control stream.
+ switch (id) {
+ case SETTINGS_QPACK_MAX_TABLE_CAPACITY: {
+ QUIC_DVLOG(1)
+ << ENDPOINT
+ << "SETTINGS_QPACK_MAX_TABLE_CAPACITY received with value "
+ << value;
+ // Communicate |value| to encoder, because it is used for encoding
+ // Required Insert Count.
+ if (!qpack_encoder_->SetMaximumDynamicTableCapacity(value)) {
+ CloseConnectionWithDetails(
+ was_zero_rtt_rejected()
+ ? QUIC_HTTP_ZERO_RTT_REJECTION_SETTINGS_MISMATCH
+ : QUIC_HTTP_ZERO_RTT_RESUMPTION_SETTINGS_MISMATCH,
+ absl::StrCat(was_zero_rtt_rejected()
+ ? "Server rejected 0-RTT, aborting because "
+ : "",
+ "Server sent an SETTINGS_QPACK_MAX_TABLE_CAPACITY: ",
+ value, " while current value is: ",
+ qpack_encoder_->MaximumDynamicTableCapacity()));
+ return false;
+ }
+ // However, limit the dynamic table capacity to
+ // |qpack_maximum_dynamic_table_capacity_|.
+ qpack_encoder_->SetDynamicTableCapacity(
+ std::min(value, qpack_maximum_dynamic_table_capacity_));
+ break;
+ }
+ case SETTINGS_MAX_FIELD_SECTION_SIZE:
+ QUIC_DVLOG(1) << ENDPOINT
+ << "SETTINGS_MAX_FIELD_SECTION_SIZE received with value "
+ << value;
+ if (max_outbound_header_list_size_ !=
+ std::numeric_limits<size_t>::max() &&
+ max_outbound_header_list_size_ > value) {
+ CloseConnectionWithDetails(
+ was_zero_rtt_rejected()
+ ? QUIC_HTTP_ZERO_RTT_REJECTION_SETTINGS_MISMATCH
+ : QUIC_HTTP_ZERO_RTT_RESUMPTION_SETTINGS_MISMATCH,
+ absl::StrCat(was_zero_rtt_rejected()
+ ? "Server rejected 0-RTT, aborting because "
+ : "",
+ "Server sent an SETTINGS_MAX_FIELD_SECTION_SIZE: ",
+ value, " which reduces current value: ",
+ max_outbound_header_list_size_));
+ return false;
+ }
+ max_outbound_header_list_size_ = value;
+ break;
+ case SETTINGS_QPACK_BLOCKED_STREAMS: {
+ QUIC_DVLOG(1) << ENDPOINT
+ << "SETTINGS_QPACK_BLOCKED_STREAMS received with value "
+ << value;
+ if (!qpack_encoder_->SetMaximumBlockedStreams(value)) {
+ CloseConnectionWithDetails(
+ was_zero_rtt_rejected()
+ ? QUIC_HTTP_ZERO_RTT_REJECTION_SETTINGS_MISMATCH
+ : QUIC_HTTP_ZERO_RTT_RESUMPTION_SETTINGS_MISMATCH,
+ absl::StrCat(was_zero_rtt_rejected()
+ ? "Server rejected 0-RTT, aborting because "
+ : "",
+ "Server sent an SETTINGS_QPACK_BLOCKED_STREAMS: ",
+ value, " which reduces current value: ",
+ qpack_encoder_->maximum_blocked_streams()));
+ return false;
+ }
+ break;
+ }
+ case SETTINGS_ENABLE_CONNECT_PROTOCOL: {
+ QUIC_DVLOG(1) << ENDPOINT
+ << "SETTINGS_ENABLE_CONNECT_PROTOCOL received with value "
+ << value;
+ if (!VerifySettingIsZeroOrOne(id, value)) {
+ return false;
+ }
+ if (perspective() == Perspective::IS_CLIENT) {
+ allow_extended_connect_ = value != 0;
+ }
+ break;
+ }
+ case spdy::SETTINGS_ENABLE_PUSH:
+ ABSL_FALLTHROUGH_INTENDED;
+ case spdy::SETTINGS_MAX_CONCURRENT_STREAMS:
+ ABSL_FALLTHROUGH_INTENDED;
+ case spdy::SETTINGS_INITIAL_WINDOW_SIZE:
+ ABSL_FALLTHROUGH_INTENDED;
+ case spdy::SETTINGS_MAX_FRAME_SIZE:
+ CloseConnectionWithDetails(
+ QUIC_HTTP_RECEIVE_SPDY_SETTING,
+ absl::StrCat("received HTTP/2 specific setting in HTTP/3 session: ",
+ id));
+ return false;
+ case SETTINGS_H3_DATAGRAM_DRAFT00: {
+ HttpDatagramSupport local_http_datagram_support =
+ LocalHttpDatagramSupport();
+ if (local_http_datagram_support != HttpDatagramSupport::kDraft00 &&
+ local_http_datagram_support != HttpDatagramSupport::kDraft00And04) {
+ break;
+ }
+ QUIC_DVLOG(1) << ENDPOINT
+ << "SETTINGS_H3_DATAGRAM_DRAFT00 received with value "
+ << value;
+ if (!version().UsesHttp3()) {
+ break;
+ }
+ if (!VerifySettingIsZeroOrOne(id, value)) {
+ return false;
+ }
+ if (value && http_datagram_support_ != HttpDatagramSupport::kDraft04) {
+ // If both draft-00 and draft-04 are supported, use draft-04.
+ http_datagram_support_ = HttpDatagramSupport::kDraft00;
+ }
+ break;
+ }
+ case SETTINGS_H3_DATAGRAM_DRAFT04: {
+ HttpDatagramSupport local_http_datagram_support =
+ LocalHttpDatagramSupport();
+ if (local_http_datagram_support != HttpDatagramSupport::kDraft04 &&
+ local_http_datagram_support != HttpDatagramSupport::kDraft00And04) {
+ break;
+ }
+ QUIC_DVLOG(1) << ENDPOINT
+ << "SETTINGS_H3_DATAGRAM_DRAFT04 received with value "
+ << value;
+ if (!version().UsesHttp3()) {
+ break;
+ }
+ if (!VerifySettingIsZeroOrOne(id, value)) {
+ return false;
+ }
+ if (value) {
+ http_datagram_support_ = HttpDatagramSupport::kDraft04;
+ }
+ break;
+ }
+ case SETTINGS_WEBTRANS_DRAFT00:
+ if (!WillNegotiateWebTransport()) {
+ break;
+ }
+ QUIC_DVLOG(1) << ENDPOINT
+ << "SETTINGS_ENABLE_WEBTRANSPORT received with value "
+ << value;
+ if (!VerifySettingIsZeroOrOne(id, value)) {
+ return false;
+ }
+ peer_supports_webtransport_ = (value == 1);
+ if (perspective() == Perspective::IS_CLIENT && value == 1) {
+ allow_extended_connect_ = true;
+ }
+ break;
+ default:
+ QUIC_DVLOG(1) << ENDPOINT << "Unknown setting identifier " << id
+ << " received with value " << value;
+ // Ignore unknown settings.
+ break;
+ }
+ return true;
+ }
+
+ // SETTINGS frame received on the headers stream.
+ switch (id) {
+ case spdy::SETTINGS_HEADER_TABLE_SIZE:
+ QUIC_DVLOG(1) << ENDPOINT
+ << "SETTINGS_HEADER_TABLE_SIZE received with value "
+ << value;
+ if (GetQuicReloadableFlag(quic_limit_encoder_dynamic_table_size)) {
+ QUIC_RELOADABLE_FLAG_COUNT(quic_limit_encoder_dynamic_table_size);
+ spdy_framer_.UpdateHeaderEncoderTableSize(
+ std::min<uint64_t>(value, kHpackEncoderDynamicTableSizeLimit));
+ break;
+ }
+ spdy_framer_.UpdateHeaderEncoderTableSize(value);
+ break;
+ case spdy::SETTINGS_ENABLE_PUSH:
+ if (perspective() == Perspective::IS_SERVER) {
+ // See rfc7540, Section 6.5.2.
+ if (value > 1) {
+ QUIC_DLOG(ERROR) << ENDPOINT << "Invalid value " << value
+ << " received for SETTINGS_ENABLE_PUSH.";
+ if (IsConnected()) {
+ CloseConnectionWithDetails(
+ QUIC_INVALID_HEADERS_STREAM_DATA,
+ absl::StrCat("Invalid value for SETTINGS_ENABLE_PUSH: ",
+ value));
+ }
+ return true;
+ }
+ QUIC_DVLOG(1) << ENDPOINT << "SETTINGS_ENABLE_PUSH received with value "
+ << value << ", ignoring.";
+ break;
+ } else {
+ QUIC_DLOG(ERROR)
+ << ENDPOINT
+ << "Invalid SETTINGS_ENABLE_PUSH received by client with value "
+ << value;
+ if (IsConnected()) {
+ CloseConnectionWithDetails(
+ QUIC_INVALID_HEADERS_STREAM_DATA,
+ absl::StrCat("Unsupported field of HTTP/2 SETTINGS frame: ", id));
+ }
+ }
+ break;
+ case spdy::SETTINGS_MAX_HEADER_LIST_SIZE:
+ QUIC_DVLOG(1) << ENDPOINT
+ << "SETTINGS_MAX_HEADER_LIST_SIZE received with value "
+ << value;
+ max_outbound_header_list_size_ = value;
+ break;
+ default:
+ QUIC_DLOG(ERROR) << ENDPOINT << "Unknown setting identifier " << id
+ << " received with value " << value;
+ if (IsConnected()) {
+ CloseConnectionWithDetails(
+ QUIC_INVALID_HEADERS_STREAM_DATA,
+ absl::StrCat("Unsupported field of HTTP/2 SETTINGS frame: ", id));
+ }
+ }
+ return true;
+}
+
+bool QuicSpdySession::ShouldReleaseHeadersStreamSequencerBuffer() {
+ return false;
+}
+
+void QuicSpdySession::OnHeaders(SpdyStreamId stream_id, bool has_priority,
+ const spdy::SpdyStreamPrecedence& precedence,
+ bool fin) {
+ if (has_priority) {
+ if (perspective() == Perspective::IS_CLIENT) {
+ CloseConnectionWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "Server must not send priorities.");
+ return;
+ }
+ OnStreamHeadersPriority(stream_id, precedence);
+ } else {
+ if (perspective() == Perspective::IS_SERVER) {
+ CloseConnectionWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "Client must send priorities.");
+ return;
+ }
+ }
+ QUICHE_DCHECK_EQ(QuicUtils::GetInvalidStreamId(transport_version()),
+ stream_id_);
+ QUICHE_DCHECK_EQ(QuicUtils::GetInvalidStreamId(transport_version()),
+ promised_stream_id_);
+ stream_id_ = stream_id;
+ fin_ = fin;
+}
+
+void QuicSpdySession::OnPushPromise(SpdyStreamId stream_id,
+ SpdyStreamId promised_stream_id) {
+ QUICHE_DCHECK_EQ(QuicUtils::GetInvalidStreamId(transport_version()),
+ stream_id_);
+ QUICHE_DCHECK_EQ(QuicUtils::GetInvalidStreamId(transport_version()),
+ promised_stream_id_);
+ stream_id_ = stream_id;
+ promised_stream_id_ = promised_stream_id;
+}
+
+// TODO (wangyix): Why is SpdyStreamId used instead of QuicStreamId?
+// This occurs in many places in this file.
+void QuicSpdySession::OnPriority(SpdyStreamId stream_id,
+ const spdy::SpdyStreamPrecedence& precedence) {
+ if (perspective() == Perspective::IS_CLIENT) {
+ CloseConnectionWithDetails(QUIC_INVALID_HEADERS_STREAM_DATA,
+ "Server must not send PRIORITY frames.");
+ return;
+ }
+ OnPriorityFrame(stream_id, precedence);
+}
+
+void QuicSpdySession::OnHeaderList(const QuicHeaderList& header_list) {
+ QUIC_DVLOG(1) << ENDPOINT << "Received header list for stream " << stream_id_
+ << ": " << header_list.DebugString();
+ // This code path is only executed for push promise in IETF QUIC.
+ if (VersionUsesHttp3(transport_version())) {
+ QUICHE_DCHECK(promised_stream_id_ !=
+ QuicUtils::GetInvalidStreamId(transport_version()));
+ }
+ if (promised_stream_id_ ==
+ QuicUtils::GetInvalidStreamId(transport_version())) {
+ OnStreamHeaderList(stream_id_, fin_, frame_len_, header_list);
+ } else {
+ OnPromiseHeaderList(stream_id_, promised_stream_id_, frame_len_,
+ header_list);
+ }
+ // Reset state for the next frame.
+ promised_stream_id_ = QuicUtils::GetInvalidStreamId(transport_version());
+ stream_id_ = QuicUtils::GetInvalidStreamId(transport_version());
+ fin_ = false;
+ frame_len_ = 0;
+}
+
+void QuicSpdySession::OnCompressedFrameSize(size_t frame_len) {
+ frame_len_ += frame_len;
+}
+
+void QuicSpdySession::CloseConnectionWithDetails(QuicErrorCode error,
+ const std::string& details) {
+ connection()->CloseConnection(
+ error, details, ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+}
+
+bool QuicSpdySession::HasActiveRequestStreams() const {
+ return GetNumActiveStreams() + num_draining_streams() > 0;
+}
+
+QuicStream* QuicSpdySession::ProcessPendingStream(PendingStream* pending) {
+ QUICHE_DCHECK(VersionUsesHttp3(transport_version()));
+ QUICHE_DCHECK(connection()->connected());
+ struct iovec iov;
+ if (!pending->sequencer()->GetReadableRegion(&iov)) {
+ // The first byte hasn't been received yet.
+ return nullptr;
+ }
+
+ QuicDataReader reader(static_cast<char*>(iov.iov_base), iov.iov_len);
+ uint8_t stream_type_length = reader.PeekVarInt62Length();
+ uint64_t stream_type = 0;
+ if (!reader.ReadVarInt62(&stream_type)) {
+ if (pending->sequencer()->NumBytesBuffered() ==
+ pending->sequencer()->close_offset()) {
+ // Stream received FIN but there are not enough bytes for stream type.
+ // Mark all bytes consumed in order to close stream.
+ pending->MarkConsumed(pending->sequencer()->close_offset());
+ }
+ return nullptr;
+ }
+ pending->MarkConsumed(stream_type_length);
+
+ switch (stream_type) {
+ case kControlStream: { // HTTP/3 control stream.
+ if (receive_control_stream_) {
+ CloseConnectionOnDuplicateHttp3UnidirectionalStreams("Control");
+ return nullptr;
+ }
+ auto receive_stream =
+ std::make_unique<QuicReceiveControlStream>(pending, this);
+ receive_control_stream_ = receive_stream.get();
+ ActivateStream(std::move(receive_stream));
+ QUIC_DVLOG(1) << ENDPOINT << "Receive Control stream is created";
+ if (debug_visitor_ != nullptr) {
+ debug_visitor_->OnPeerControlStreamCreated(
+ receive_control_stream_->id());
+ }
+ return receive_control_stream_;
+ }
+ case kServerPushStream: { // Push Stream.
+ CloseConnectionWithDetails(QUIC_HTTP_RECEIVE_SERVER_PUSH,
+ "Received server push stream");
+ return nullptr;
+ }
+ case kQpackEncoderStream: { // QPACK encoder stream.
+ if (qpack_encoder_receive_stream_) {
+ CloseConnectionOnDuplicateHttp3UnidirectionalStreams("QPACK encoder");
+ return nullptr;
+ }
+ auto encoder_receive = std::make_unique<QpackReceiveStream>(
+ pending, this, qpack_decoder_->encoder_stream_receiver());
+ qpack_encoder_receive_stream_ = encoder_receive.get();
+ ActivateStream(std::move(encoder_receive));
+ QUIC_DVLOG(1) << ENDPOINT << "Receive QPACK Encoder stream is created";
+ if (debug_visitor_ != nullptr) {
+ debug_visitor_->OnPeerQpackEncoderStreamCreated(
+ qpack_encoder_receive_stream_->id());
+ }
+ return qpack_encoder_receive_stream_;
+ }
+ case kQpackDecoderStream: { // QPACK decoder stream.
+ if (qpack_decoder_receive_stream_) {
+ CloseConnectionOnDuplicateHttp3UnidirectionalStreams("QPACK decoder");
+ return nullptr;
+ }
+ auto decoder_receive = std::make_unique<QpackReceiveStream>(
+ pending, this, qpack_encoder_->decoder_stream_receiver());
+ qpack_decoder_receive_stream_ = decoder_receive.get();
+ ActivateStream(std::move(decoder_receive));
+ QUIC_DVLOG(1) << ENDPOINT << "Receive QPACK Decoder stream is created";
+ if (debug_visitor_ != nullptr) {
+ debug_visitor_->OnPeerQpackDecoderStreamCreated(
+ qpack_decoder_receive_stream_->id());
+ }
+ return qpack_decoder_receive_stream_;
+ }
+ case kWebTransportUnidirectionalStream: {
+ // Note that this checks whether WebTransport is enabled on the receiver
+ // side, as we may receive WebTransport streams before peer's SETTINGS are
+ // received.
+ // TODO(b/184156476): consider whether this means we should drop buffered
+ // streams if we don't receive indication of WebTransport support.
+ if (!WillNegotiateWebTransport()) {
+ // Treat as unknown stream type.
+ break;
+ }
+ QUIC_DVLOG(1) << ENDPOINT << "Created an incoming WebTransport stream "
+ << pending->id();
+ auto stream_owned =
+ std::make_unique<WebTransportHttp3UnidirectionalStream>(pending,
+ this);
+ WebTransportHttp3UnidirectionalStream* stream = stream_owned.get();
+ ActivateStream(std::move(stream_owned));
+ return stream;
+ }
+ default:
+ break;
+ }
+ MaybeSendStopSendingFrame(
+ pending->id(),
+ QuicResetStreamError::FromInternal(QUIC_STREAM_STREAM_CREATION_ERROR));
+ pending->StopReading();
+ return nullptr;
+}
+
+void QuicSpdySession::MaybeInitializeHttp3UnidirectionalStreams() {
+ QUICHE_DCHECK(VersionUsesHttp3(transport_version()));
+ if (!send_control_stream_ && CanOpenNextOutgoingUnidirectionalStream()) {
+ auto send_control = std::make_unique<QuicSendControlStream>(
+ GetNextOutgoingUnidirectionalStreamId(), this, settings_);
+ 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_ &&
+ CanOpenNextOutgoingUnidirectionalStream()) {
+ auto decoder_send = std::make_unique<QpackSendStream>(
+ GetNextOutgoingUnidirectionalStreamId(), this, kQpackDecoderStream);
+ qpack_decoder_send_stream_ = decoder_send.get();
+ 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_ &&
+ CanOpenNextOutgoingUnidirectionalStream()) {
+ auto encoder_send = std::make_unique<QpackSendStream>(
+ GetNextOutgoingUnidirectionalStreamId(), this, kQpackEncoderStream);
+ qpack_encoder_send_stream_ = encoder_send.get();
+ 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());
+ }
+ }
+}
+
+void QuicSpdySession::BeforeConnectionCloseSent() {
+ if (!VersionUsesHttp3(transport_version()) || !IsEncryptionEstablished()) {
+ return;
+ }
+
+ QUICHE_DCHECK_EQ(perspective(), Perspective::IS_SERVER);
+
+ QuicStreamId stream_id =
+ GetLargestPeerCreatedStreamId(/*unidirectional = */ false);
+
+ if (stream_id == QuicUtils::GetInvalidStreamId(transport_version())) {
+ // No client-initiated bidirectional streams received yet.
+ // Send 0 to let client know that all requests can be retried.
+ stream_id = 0;
+ } else {
+ // Tell client that streams starting with the next after the largest
+ // received one can be retried.
+ stream_id += QuicUtils::StreamIdDelta(transport_version());
+ }
+ if (last_sent_http3_goaway_id_.has_value() &&
+ last_sent_http3_goaway_id_.value() <= stream_id) {
+ // A previous GOAWAY frame was sent with smaller stream ID. This is not
+ // possible, because this is the only method sending a GOAWAY frame with
+ // non-maximal stream ID, and this must only be called once, right
+ // before closing connection.
+ QUIC_BUG(QuicGoawayFrameAlreadySent)
+ << "Not sending GOAWAY frame with " << stream_id << " because one with "
+ << last_sent_http3_goaway_id_.value() << " already sent on connection "
+ << connection()->connection_id();
+
+ // MUST not send GOAWAY with identifier larger than previously sent.
+ // Do not bother sending one with same identifier as before, since GOAWAY
+ // frames on the control stream are guaranteed to be processed in order.
+ return;
+ }
+
+ send_control_stream_->SendGoAway(stream_id);
+ last_sent_http3_goaway_id_ = stream_id;
+}
+
+void QuicSpdySession::OnCanCreateNewOutgoingStream(bool unidirectional) {
+ if (unidirectional && VersionUsesHttp3(transport_version())) {
+ MaybeInitializeHttp3UnidirectionalStreams();
+ }
+}
+
+bool QuicSpdySession::OnMaxPushIdFrame(PushId max_push_id) {
+ QUICHE_DCHECK(VersionUsesHttp3(transport_version()));
+ QUICHE_DCHECK_EQ(Perspective::IS_SERVER, perspective());
+
+ if (max_push_id_.has_value()) {
+ QUIC_DVLOG(1) << "Setting max_push_id to: " << max_push_id
+ << " from: " << max_push_id_.value();
+ } else {
+ QUIC_DVLOG(1) << "Setting max_push_id to: " << max_push_id
+ << " from unset";
+ }
+ absl::optional<PushId> old_max_push_id = max_push_id_;
+ max_push_id_ = max_push_id;
+
+ if (!old_max_push_id.has_value() || max_push_id > old_max_push_id.value()) {
+ OnCanCreateNewOutgoingStream(true);
+ return true;
+ }
+
+ // Equal value is not considered an error.
+ if (max_push_id < old_max_push_id.value()) {
+ CloseConnectionWithDetails(
+ QUIC_HTTP_INVALID_MAX_PUSH_ID,
+ absl::StrCat("MAX_PUSH_ID received with value ", max_push_id,
+ " which is smaller that previously received value ",
+ old_max_push_id.value()));
+ return false;
+ }
+
+ return true;
+}
+
+bool QuicSpdySession::goaway_received() const {
+ return VersionUsesHttp3(transport_version())
+ ? last_received_http3_goaway_id_.has_value()
+ : transport_goaway_received();
+}
+
+bool QuicSpdySession::goaway_sent() const {
+ return VersionUsesHttp3(transport_version())
+ ? last_sent_http3_goaway_id_.has_value()
+ : transport_goaway_sent();
+}
+
+void QuicSpdySession::CloseConnectionOnDuplicateHttp3UnidirectionalStreams(
+ absl::string_view type) {
+ QUIC_PEER_BUG(quic_peer_bug_10360_9) << absl::StrCat(
+ "Received a duplicate ", type, " stream: Closing connection.");
+ CloseConnectionWithDetails(QUIC_HTTP_DUPLICATE_UNIDIRECTIONAL_STREAM,
+ absl::StrCat(type, " stream is received twice."));
+}
+
+// static
+void QuicSpdySession::LogHeaderCompressionRatioHistogram(
+ bool using_qpack, bool is_sent, QuicByteCount compressed,
+ QuicByteCount uncompressed) {
+ if (compressed <= 0 || uncompressed <= 0) {
+ return;
+ }
+
+ int ratio = 100 * (compressed) / (uncompressed);
+ if (ratio < 1) {
+ ratio = 1;
+ } else if (ratio > 200) {
+ ratio = 200;
+ }
+
+ // Note that when using histogram macros in Chromium, the histogram name must
+ // be the same across calls for any given call site.
+ if (using_qpack) {
+ if (is_sent) {
+ QUIC_HISTOGRAM_COUNTS("QuicSession.HeaderCompressionRatioQpackSent",
+ ratio, 1, 200, 200,
+ "Header compression ratio as percentage for sent "
+ "headers using QPACK.");
+ } else {
+ QUIC_HISTOGRAM_COUNTS("QuicSession.HeaderCompressionRatioQpackReceived",
+ ratio, 1, 200, 200,
+ "Header compression ratio as percentage for "
+ "received headers using QPACK.");
+ }
+ } else {
+ if (is_sent) {
+ QUIC_HISTOGRAM_COUNTS("QuicSession.HeaderCompressionRatioHpackSent",
+ ratio, 1, 200, 200,
+ "Header compression ratio as percentage for sent "
+ "headers using HPACK.");
+ } else {
+ QUIC_HISTOGRAM_COUNTS("QuicSession.HeaderCompressionRatioHpackReceived",
+ ratio, 1, 200, 200,
+ "Header compression ratio as percentage for "
+ "received headers using HPACK.");
+ }
+ }
+}
+
+MessageStatus QuicSpdySession::SendHttp3Datagram(
+ QuicDatagramStreamId stream_id,
+ absl::optional<QuicDatagramContextId> context_id,
+ absl::string_view payload) {
+ if (!SupportsH3Datagram()) {
+ QUIC_BUG(send http datagram too early)
+ << "Refusing to send HTTP Datagram before SETTINGS received";
+ return MESSAGE_STATUS_INTERNAL_ERROR;
+ }
+ uint64_t stream_id_to_write = stream_id;
+ if (http_datagram_support_ != HttpDatagramSupport::kDraft00) {
+ // Stream ID is sent divided by four as per the specification.
+ stream_id_to_write /= kHttpDatagramStreamIdDivisor;
+ }
+ size_t slice_length =
+ QuicDataWriter::GetVarInt62Len(stream_id_to_write) + payload.length();
+ if (context_id.has_value()) {
+ slice_length += QuicDataWriter::GetVarInt62Len(context_id.value());
+ }
+ quiche::QuicheBuffer buffer(
+ connection()->helper()->GetStreamSendBufferAllocator(), slice_length);
+ QuicDataWriter writer(slice_length, buffer.data());
+ if (!writer.WriteVarInt62(stream_id_to_write)) {
+ QUIC_BUG(h3 datagram stream ID write fail)
+ << "Failed to write HTTP/3 datagram stream ID";
+ return MESSAGE_STATUS_INTERNAL_ERROR;
+ }
+ if (context_id.has_value()) {
+ if (!writer.WriteVarInt62(context_id.value())) {
+ QUIC_BUG(h3 datagram context ID write fail)
+ << "Failed to write HTTP/3 datagram context ID";
+ return MESSAGE_STATUS_INTERNAL_ERROR;
+ }
+ }
+ if (!writer.WriteBytes(payload.data(), payload.length())) {
+ QUIC_BUG(h3 datagram payload write fail)
+ << "Failed to write HTTP/3 datagram payload";
+ return MESSAGE_STATUS_INTERNAL_ERROR;
+ }
+
+ quiche::QuicheMemSlice slice(std::move(buffer));
+ return datagram_queue()->SendOrQueueDatagram(std::move(slice));
+}
+
+void QuicSpdySession::SetMaxDatagramTimeInQueueForStreamId(
+ QuicStreamId /*stream_id*/, QuicTime::Delta max_time_in_queue) {
+ // TODO(b/184598230): implement this in a way that works for multiple sessions
+ // on a same connection.
+ datagram_queue()->SetMaxTimeInQueue(max_time_in_queue);
+}
+
+void QuicSpdySession::RegisterHttp3DatagramFlowId(QuicDatagramStreamId flow_id,
+ QuicStreamId stream_id) {
+ h3_datagram_flow_id_to_stream_id_map_[flow_id] = stream_id;
+}
+
+void QuicSpdySession::UnregisterHttp3DatagramFlowId(
+ QuicDatagramStreamId flow_id) {
+ h3_datagram_flow_id_to_stream_id_map_.erase(flow_id);
+}
+
+void QuicSpdySession::OnMessageReceived(absl::string_view message) {
+ QuicSession::OnMessageReceived(message);
+ if (!SupportsH3Datagram()) {
+ QUIC_DLOG(INFO) << "Ignoring unexpected received HTTP/3 datagram";
+ return;
+ }
+ QuicDataReader reader(message);
+ uint64_t stream_id64;
+ if (!reader.ReadVarInt62(&stream_id64)) {
+ QUIC_DLOG(ERROR) << "Failed to parse stream ID in received HTTP/3 datagram";
+ return;
+ }
+ if (http_datagram_support_ != HttpDatagramSupport::kDraft00) {
+ // Stream ID is sent divided by four as per the specification.
+ stream_id64 *= kHttpDatagramStreamIdDivisor;
+ }
+ if (perspective() == Perspective::IS_SERVER &&
+ http_datagram_support_ == HttpDatagramSupport::kDraft00) {
+ auto it = h3_datagram_flow_id_to_stream_id_map_.find(stream_id64);
+ if (it == h3_datagram_flow_id_to_stream_id_map_.end()) {
+ QUIC_DLOG(INFO) << "Received unknown HTTP/3 datagram flow ID "
+ << stream_id64;
+ return;
+ }
+ stream_id64 = it->second;
+ }
+ if (stream_id64 > std::numeric_limits<QuicStreamId>::max()) {
+ // TODO(b/181256914) make this a connection close once we deprecate
+ // draft-ietf-masque-h3-datagram-00 in favor of later drafts.
+ QUIC_DLOG(ERROR) << "Received unexpectedly high HTTP/3 datagram stream ID "
+ << stream_id64;
+ return;
+ }
+ QuicStreamId stream_id = static_cast<QuicStreamId>(stream_id64);
+ QuicSpdyStream* stream =
+ static_cast<QuicSpdyStream*>(GetActiveStream(stream_id));
+ if (stream == nullptr) {
+ QUIC_DLOG(INFO) << "Received HTTP/3 datagram for unknown stream ID "
+ << stream_id;
+ // TODO(b/181256914) buffer unknown HTTP/3 datagram flow IDs for a short
+ // period of time in case they were reordered.
+ return;
+ }
+ stream->OnDatagramReceived(&reader);
+}
+
+bool QuicSpdySession::SupportsWebTransport() {
+ return WillNegotiateWebTransport() && SupportsH3Datagram() &&
+ peer_supports_webtransport_ &&
+ (!GetQuicReloadableFlag(quic_verify_request_headers_2) ||
+ allow_extended_connect_);
+}
+
+bool QuicSpdySession::SupportsH3Datagram() const {
+ return http_datagram_support_ != HttpDatagramSupport::kNone;
+}
+
+WebTransportHttp3* QuicSpdySession::GetWebTransportSession(
+ WebTransportSessionId id) {
+ if (!SupportsWebTransport()) {
+ return nullptr;
+ }
+ if (!IsValidWebTransportSessionId(id, version())) {
+ return nullptr;
+ }
+ QuicSpdyStream* connect_stream = GetOrCreateSpdyDataStream(id);
+ if (connect_stream == nullptr) {
+ return nullptr;
+ }
+ return connect_stream->web_transport();
+}
+
+bool QuicSpdySession::ShouldProcessIncomingRequests() {
+ if (!ShouldBufferRequestsUntilSettings()) {
+ return true;
+ }
+
+ return any_settings_received_;
+}
+
+void QuicSpdySession::OnStreamWaitingForClientSettings(QuicStreamId id) {
+ QUICHE_DCHECK(ShouldBufferRequestsUntilSettings());
+ QUICHE_DCHECK(QuicUtils::IsBidirectionalStreamId(id, version()));
+ streams_waiting_for_settings_.insert(id);
+}
+
+void QuicSpdySession::AssociateIncomingWebTransportStreamWithSession(
+ WebTransportSessionId session_id, QuicStreamId stream_id) {
+ if (QuicUtils::IsOutgoingStreamId(version(), stream_id, perspective())) {
+ QUIC_BUG(AssociateIncomingWebTransportStreamWithSession got outgoing stream)
+ << ENDPOINT
+ << "AssociateIncomingWebTransportStreamWithSession() got an outgoing "
+ "stream ID: "
+ << stream_id;
+ return;
+ }
+ WebTransportHttp3* session = GetWebTransportSession(session_id);
+ if (session != nullptr) {
+ QUIC_DVLOG(1) << ENDPOINT
+ << "Successfully associated incoming WebTransport stream "
+ << stream_id << " with session ID " << session_id;
+
+ session->AssociateStream(stream_id);
+ return;
+ }
+ // Evict the oldest streams until we are under the limit.
+ while (buffered_streams_.size() >= kMaxUnassociatedWebTransportStreams) {
+ QUIC_DVLOG(1) << ENDPOINT << "Removing stream "
+ << buffered_streams_.front().stream_id
+ << " from buffered streams as the queue is full.";
+ ResetStream(buffered_streams_.front().stream_id,
+ QUIC_STREAM_WEBTRANSPORT_BUFFERED_STREAMS_LIMIT_EXCEEDED);
+ buffered_streams_.pop_front();
+ }
+ QUIC_DVLOG(1) << ENDPOINT << "Received a WebTransport stream " << stream_id
+ << " for session ID " << session_id
+ << " but cannot associate it; buffering instead.";
+ buffered_streams_.push_back(
+ BufferedWebTransportStream{session_id, stream_id});
+}
+
+void QuicSpdySession::ProcessBufferedWebTransportStreamsForSession(
+ WebTransportHttp3* session) {
+ const WebTransportSessionId session_id = session->id();
+ QUIC_DVLOG(1) << "Processing buffered WebTransport streams for "
+ << session_id;
+ auto it = buffered_streams_.begin();
+ while (it != buffered_streams_.end()) {
+ if (it->session_id == session_id) {
+ QUIC_DVLOG(1) << "Unbuffered and associated WebTransport stream "
+ << it->stream_id << " with session " << it->session_id;
+ session->AssociateStream(it->stream_id);
+ it = buffered_streams_.erase(it);
+ } else {
+ it++;
+ }
+ }
+}
+
+WebTransportHttp3UnidirectionalStream*
+QuicSpdySession::CreateOutgoingUnidirectionalWebTransportStream(
+ WebTransportHttp3* session) {
+ if (!CanOpenNextOutgoingUnidirectionalStream()) {
+ return nullptr;
+ }
+
+ QuicStreamId stream_id = GetNextOutgoingUnidirectionalStreamId();
+ auto stream_owned = std::make_unique<WebTransportHttp3UnidirectionalStream>(
+ stream_id, this, session->id());
+ WebTransportHttp3UnidirectionalStream* stream = stream_owned.get();
+ ActivateStream(std::move(stream_owned));
+ stream->WritePreamble();
+ session->AssociateStream(stream_id);
+ return stream;
+}
+
+QuicSpdyStream* QuicSpdySession::CreateOutgoingBidirectionalWebTransportStream(
+ WebTransportHttp3* session) {
+ QuicSpdyStream* stream = CreateOutgoingBidirectionalStream();
+ if (stream == nullptr) {
+ return nullptr;
+ }
+ QuicStreamId stream_id = stream->id();
+ stream->ConvertToWebTransportDataStream(session->id());
+ if (stream->web_transport_stream() == nullptr) {
+ // An error in ConvertToWebTransportDataStream() would result in
+ // CONNECTION_CLOSE, thus we don't need to do anything here.
+ return nullptr;
+ }
+ session->AssociateStream(stream_id);
+ return stream;
+}
+
+void QuicSpdySession::OnDatagramProcessed(
+ absl::optional<MessageStatus> /*status*/) {
+ // TODO(b/184598230): make this work with multiple datagram flows.
+}
+
+void QuicSpdySession::DatagramObserver::OnDatagramProcessed(
+ absl::optional<MessageStatus> status) {
+ session_->OnDatagramProcessed(status);
+}
+
+HttpDatagramSupport QuicSpdySession::LocalHttpDatagramSupport() {
+ return HttpDatagramSupport::kNone;
+}
+
+std::string HttpDatagramSupportToString(
+ HttpDatagramSupport http_datagram_support) {
+ switch (http_datagram_support) {
+ case HttpDatagramSupport::kNone:
+ return "None";
+ case HttpDatagramSupport::kDraft00:
+ return "Draft00";
+ case HttpDatagramSupport::kDraft04:
+ return "Draft04";
+ case HttpDatagramSupport::kDraft00And04:
+ return "Draft00And04";
+ }
+ return absl::StrCat("Unknown(", static_cast<int>(http_datagram_support), ")");
+}
+
+std::ostream& operator<<(std::ostream& os,
+ const HttpDatagramSupport& http_datagram_support) {
+ os << HttpDatagramSupportToString(http_datagram_support);
+ return os;
+}
+
+// Must not be called after Initialize().
+void QuicSpdySession::set_allow_extended_connect(bool allow_extended_connect) {
+ QUIC_BUG_IF(extended connect wrong version,
+ !GetQuicReloadableFlag(quic_verify_request_headers_2) ||
+ !VersionUsesHttp3(transport_version()))
+ << "Try to enable/disable extended CONNECT in Google QUIC";
+ QUIC_BUG_IF(extended connect on client,
+ !GetQuicReloadableFlag(quic_verify_request_headers_2) ||
+ perspective() == Perspective::IS_CLIENT)
+ << "Enabling/disabling extended CONNECT on the client side has no effect";
+ if (ShouldNegotiateWebTransport()) {
+ QUIC_BUG_IF(disable extended connect, !allow_extended_connect)
+ << "Disabling extended CONNECT with web transport enabled has no "
+ "effect.";
+ return;
+ }
+ allow_extended_connect_ = allow_extended_connect;
+}
+
+#undef ENDPOINT // undef for jumbo builds
+
+} // namespace quic