|  | // 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 "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 "quic/core/http/http_constants.h" | 
|  | #include "quic/core/http/http_decoder.h" | 
|  | #include "quic/core/http/http_frames.h" | 
|  | #include "quic/core/http/quic_headers_stream.h" | 
|  | #include "quic/core/http/web_transport_http3.h" | 
|  | #include "quic/core/quic_error_codes.h" | 
|  | #include "quic/core/quic_types.h" | 
|  | #include "quic/core/quic_utils.h" | 
|  | #include "quic/core/quic_versions.h" | 
|  | #include "quic/platform/api/quic_bug_tracker.h" | 
|  | #include "quic/platform/api/quic_exported_stats.h" | 
|  | #include "quic/platform/api/quic_flag_utils.h" | 
|  | #include "quic/platform/api/quic_flags.h" | 
|  | #include "quic/platform/api/quic_logging.h" | 
|  | #include "quic/platform/api/quic_stack_trace.h" | 
|  | #include "common/platform/api/quiche_mem_slice.h" | 
|  | #include "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 { | 
|  |  | 
|  | #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; | 
|  | 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 |