diff options
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.cc | 1949 |
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 |