| // Copyright (c) 2012 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 "net/spdy/spdy_framer.h" |
| |
| #include <string.h> |
| |
| #include <algorithm> |
| #include <ios> |
| #include <iterator> |
| #include <list> |
| #include <memory> |
| #include <new> |
| #include <string> |
| #include <vector> |
| |
| #include "base/lazy_instance.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "net/quic/core/quic_flags.h" |
| #include "net/spdy/hpack/hpack_constants.h" |
| #include "net/spdy/spdy_bitmasks.h" |
| #include "net/spdy/spdy_bug_tracker.h" |
| #include "net/spdy/spdy_flags.h" |
| #include "net/spdy/spdy_frame_builder.h" |
| #include "net/spdy/spdy_frame_reader.h" |
| #include "net/spdy/spdy_framer_decoder_adapter.h" |
| #include "net/spdy/spdy_headers_block_parser.h" |
| #include "third_party/zlib/zlib.h" |
| |
| using base::StringPiece; |
| using std::hex; |
| using std::string; |
| using std::vector; |
| |
| namespace net { |
| |
| namespace { |
| |
| // Compute the id of our dictionary so that we know we're using the |
| // right one when asked for it. |
| uLong CalculateDictionaryId(const char* dictionary, |
| const size_t dictionary_size) { |
| uLong initial_value = adler32(0L, Z_NULL, 0); |
| return adler32(initial_value, |
| reinterpret_cast<const Bytef*>(dictionary), |
| dictionary_size); |
| } |
| |
| #if !defined(USE_SYSTEM_ZLIB) |
| // Check to see if the name and value of a cookie are both empty. |
| bool IsCookieEmpty(const base::StringPiece& cookie) { |
| if (cookie.size() == 0) { |
| return true; |
| } |
| size_t pos = cookie.find('='); |
| if (pos == base::StringPiece::npos) { |
| return false; |
| } |
| // Ignore leading whitespaces of cookie value. |
| size_t value_start = pos + 1; |
| for (; value_start < cookie.size(); value_start++) { |
| if (!(cookie[value_start] == ' ' || cookie[value_start] == '\t')) { |
| break; |
| } |
| } |
| return (pos == 0) && ((cookie.size() - value_start) == 0); |
| } |
| #endif // !defined(USE_SYSTEM_ZLIB) |
| |
| // Pack parent stream ID and exclusive flag into the format used by HTTP/2 |
| // headers and priority frames. |
| uint32_t PackStreamDependencyValues(bool exclusive, |
| SpdyStreamId parent_stream_id) { |
| // Make sure the highest-order bit in the parent stream id is zeroed out. |
| uint32_t parent = parent_stream_id & 0x7fffffff; |
| // Set the one-bit exclusivity flag. |
| uint32_t e_bit = exclusive ? 0x80000000 : 0; |
| return parent | e_bit; |
| } |
| |
| // Unpack parent stream ID and exclusive flag from the format used by HTTP/2 |
| // headers and priority frames. |
| void UnpackStreamDependencyValues(uint32_t packed, |
| bool* exclusive, |
| SpdyStreamId* parent_stream_id) { |
| *exclusive = (packed >> 31) != 0; |
| // Zero out the highest-order bit to get the parent stream id. |
| *parent_stream_id = packed & 0x7fffffff; |
| } |
| |
| // Creates a SpdyFramerDecoderAdapter if flags indicate that one should be |
| // used. This code is isolated to hopefully make merging into Chromium easier. |
| std::unique_ptr<SpdyFramerDecoderAdapter> DecoderAdapterFactory( |
| SpdyFramer* outer) { |
| if (FLAGS_use_nested_spdy_framer_decoder) { |
| DVLOG(1) << "Creating NestedSpdyFramerDecoder."; |
| return CreateNestedSpdyFramerDecoder(outer); |
| } |
| return nullptr; |
| } |
| |
| struct DictionaryIds { |
| DictionaryIds() |
| : v3_dictionary_id( |
| CalculateDictionaryId(kV3Dictionary, kV3DictionarySize)) {} |
| const uLong v3_dictionary_id; |
| }; |
| |
| // Adler ID for the SPDY header compressor dictionaries. Note that they are |
| // initialized lazily to avoid static initializers. |
| base::LazyInstance<DictionaryIds>::Leaky g_dictionary_ids; |
| |
| // Used to indicate no flags in a SPDY flags field. |
| const uint8_t kNoFlags = 0; |
| |
| // Wire sizes of priority payloads. |
| const size_t kPriorityDependencyPayloadSize = 4; |
| const size_t kPriorityWeightPayloadSize = 1; |
| |
| // Wire size of pad length field. |
| const size_t kPadLengthFieldSize = 1; |
| |
| } // namespace |
| |
| const SpdyStreamId SpdyFramer::kInvalidStream = static_cast<SpdyStreamId>(-1); |
| const size_t SpdyFramer::kHeaderDataChunkMaxSize = 1024; |
| // Even though the length field is 24 bits, we keep this 16 kB |
| // limit on control frame size for legacy reasons and to |
| // mitigate DOS attacks. |
| const size_t SpdyFramer::kMaxControlFrameSize = (1 << 14) - 1; |
| const size_t SpdyFramer::kMaxDataPayloadSendSize = 1 << 14; |
| // The size of the control frame buffer. Must be >= the minimum size of the |
| // largest control frame, which is SYN_STREAM. See GetSynStreamMinimumSize() for |
| // calculation details. |
| const size_t SpdyFramer::kControlFrameBufferSize = 19; |
| |
| #ifdef DEBUG_SPDY_STATE_CHANGES |
| #define CHANGE_STATE(newstate) \ |
| do { \ |
| DVLOG(1) << "Changing state from: " \ |
| << StateToString(state_) \ |
| << " to " << StateToString(newstate) << "\n"; \ |
| DCHECK(state_ != SPDY_ERROR); \ |
| DCHECK_EQ(previous_state_, state_); \ |
| previous_state_ = state_; \ |
| state_ = newstate; \ |
| } while (false) |
| #else |
| #define CHANGE_STATE(newstate) \ |
| do { \ |
| DCHECK(state_ != SPDY_ERROR); \ |
| DCHECK_EQ(previous_state_, state_); \ |
| previous_state_ = state_; \ |
| state_ = newstate; \ |
| } while (false) |
| #endif |
| |
| SettingsFlagsAndId SettingsFlagsAndId::FromWireFormat(SpdyMajorVersion version, |
| uint32_t wire) { |
| return SettingsFlagsAndId(base::NetToHost32(wire) >> 24, |
| base::NetToHost32(wire) & 0x00ffffff); |
| } |
| |
| SettingsFlagsAndId::SettingsFlagsAndId(uint8_t flags, uint32_t id) |
| : flags_(flags), id_(id & 0x00ffffff) { |
| SPDY_BUG_IF(id > (1u << 24)) << "SPDY setting ID too large: " << id; |
| } |
| |
| uint32_t SettingsFlagsAndId::GetWireFormat(SpdyMajorVersion version) const { |
| return base::HostToNet32(id_ & 0x00ffffff) | base::HostToNet32(flags_ << 24); |
| } |
| |
| bool SpdyFramerVisitorInterface::OnGoAwayFrameData(const char* goaway_data, |
| size_t len) { |
| return true; |
| } |
| |
| bool SpdyFramerVisitorInterface::OnRstStreamFrameData( |
| const char* rst_stream_data, |
| size_t len) { |
| return true; |
| } |
| |
| SpdyFramer::SpdyFramer(SpdyMajorVersion version, |
| SpdyFramer::DecoderAdapterFactoryFn adapter_factory) |
| : current_frame_buffer_(kControlFrameBufferSize), |
| expect_continuation_(0), |
| visitor_(NULL), |
| debug_visitor_(NULL), |
| header_handler_(nullptr), |
| display_protocol_("SPDY"), |
| protocol_version_(version), |
| enable_compression_(true), |
| syn_frame_processed_(false), |
| probable_http_response_(false), |
| end_stream_when_done_(false) { |
| DCHECK(protocol_version_ == SPDY3 || protocol_version_ == HTTP2); |
| // TODO(bnc): The way kMaxControlFrameSize is currently interpreted, it |
| // includes the frame header, whereas kSpdyInitialFrameSizeLimit does not. |
| // Therefore this assertion is unnecessarily strict. |
| static_assert(kMaxControlFrameSize <= kSpdyInitialFrameSizeLimit, |
| "Our send limit should be at most our receive limit"); |
| Reset(); |
| |
| if (version == HTTP2 && adapter_factory != nullptr) { |
| decoder_adapter_ = adapter_factory(this); |
| } |
| } |
| |
| SpdyFramer::SpdyFramer(SpdyMajorVersion version) |
| : SpdyFramer(version, &DecoderAdapterFactory) {} |
| |
| SpdyFramer::~SpdyFramer() { |
| if (header_compressor_.get()) { |
| deflateEnd(header_compressor_.get()); |
| } |
| if (header_decompressor_.get()) { |
| inflateEnd(header_decompressor_.get()); |
| } |
| } |
| |
| void SpdyFramer::Reset() { |
| if (decoder_adapter_ != nullptr) { |
| decoder_adapter_->Reset(); |
| } |
| state_ = SPDY_READY_FOR_FRAME; |
| previous_state_ = SPDY_READY_FOR_FRAME; |
| error_code_ = SPDY_NO_ERROR; |
| remaining_data_length_ = 0; |
| remaining_control_header_ = 0; |
| current_frame_buffer_.Rewind(); |
| current_frame_type_ = DATA; |
| current_frame_flags_ = 0; |
| current_frame_length_ = 0; |
| current_frame_stream_id_ = kInvalidStream; |
| settings_scratch_.Reset(); |
| altsvc_scratch_.reset(); |
| remaining_padding_payload_length_ = 0; |
| } |
| |
| void SpdyFramer::set_visitor(SpdyFramerVisitorInterface* visitor) { |
| if (decoder_adapter_ != nullptr) { |
| decoder_adapter_->set_visitor(visitor); |
| } |
| visitor_ = visitor; |
| } |
| |
| void SpdyFramer::set_debug_visitor( |
| SpdyFramerDebugVisitorInterface* debug_visitor) { |
| if (decoder_adapter_ != nullptr) { |
| decoder_adapter_->set_debug_visitor(debug_visitor); |
| } |
| debug_visitor_ = debug_visitor; |
| } |
| |
| void SpdyFramer::set_process_single_input_frame(bool v) { |
| if (decoder_adapter_ != nullptr) { |
| decoder_adapter_->set_process_single_input_frame(v); |
| } |
| process_single_input_frame_ = v; |
| } |
| |
| bool SpdyFramer::probable_http_response() const { |
| if (decoder_adapter_) { |
| return decoder_adapter_->probable_http_response(); |
| } |
| return probable_http_response_; |
| } |
| |
| SpdyFramer::SpdyError SpdyFramer::error_code() const { |
| if (decoder_adapter_ != nullptr) { |
| return decoder_adapter_->error_code(); |
| } |
| return error_code_; |
| } |
| |
| SpdyFramer::SpdyState SpdyFramer::state() const { |
| if (decoder_adapter_ != nullptr) { |
| return decoder_adapter_->state(); |
| } |
| return state_; |
| } |
| |
| size_t SpdyFramer::GetDataFrameMinimumSize() const { |
| return SpdyConstants::GetDataFrameMinimumSize(protocol_version_); |
| } |
| |
| // Size, in bytes, of the control frame header. |
| size_t SpdyFramer::GetFrameHeaderSize() const { |
| return SpdyConstants::GetFrameHeaderSize(protocol_version_); |
| } |
| |
| size_t SpdyFramer::GetSynStreamMinimumSize() const { |
| // Size, in bytes, of a SYN_STREAM frame not including the variable-length |
| // header block. |
| if (protocol_version_ == SPDY3) { |
| // Calculated as: |
| // control frame header + 2 * 4 (stream IDs) + 1 (priority) |
| // + 1 (unused) |
| return GetFrameHeaderSize() + 10; |
| } else { |
| return GetFrameHeaderSize() + kPriorityDependencyPayloadSize + |
| kPriorityWeightPayloadSize; |
| } |
| } |
| |
| size_t SpdyFramer::GetSynReplyMinimumSize() const { |
| // Size, in bytes, of a SYN_REPLY frame not including the variable-length |
| // header block. |
| size_t size = GetFrameHeaderSize(); |
| if (protocol_version_ == SPDY3) { |
| // Calculated as: |
| // control frame header + 4 (stream IDs) |
| size += 4; |
| } |
| |
| return size; |
| } |
| |
| // TODO(jamessynge): Rename this to GetRstStreamSize as the frame is fixed size. |
| size_t SpdyFramer::GetRstStreamMinimumSize() const { |
| // Size, in bytes, of a RST_STREAM frame. |
| if (protocol_version_ == SPDY3) { |
| // Calculated as: |
| // control frame header + 4 (stream id) + 4 (status code) |
| return GetFrameHeaderSize() + 8; |
| } else { |
| // Calculated as: |
| // frame prefix + 4 (status code) |
| return GetFrameHeaderSize() + 4; |
| } |
| } |
| |
| size_t SpdyFramer::GetSettingsMinimumSize() const { |
| // Size, in bytes, of a SETTINGS frame not including the IDs and values |
| // from the variable-length value block. Calculated as: |
| // control frame header + 4 (number of ID/value pairs) |
| if (protocol_version_ == SPDY3) { |
| return GetFrameHeaderSize() + 4; |
| } else { |
| return GetFrameHeaderSize(); |
| } |
| } |
| |
| size_t SpdyFramer::GetPingSize() const { |
| // Size, in bytes, of this PING frame. |
| if (protocol_version_ == SPDY3) { |
| // Calculated as: |
| // control frame header + 4 (id) |
| return GetFrameHeaderSize() + 4; |
| } else { |
| // Calculated as: |
| // control frame header + 8 (id) |
| return GetFrameHeaderSize() + 8; |
| } |
| } |
| |
| size_t SpdyFramer::GetGoAwayMinimumSize() const { |
| // Size, in bytes, of this GOAWAY frame. Calculated as: |
| // Control frame header + last stream id (4 bytes) + error code (4 bytes). |
| return GetFrameHeaderSize() + 8; |
| } |
| |
| size_t SpdyFramer::GetHeadersMinimumSize() const { |
| // Size, in bytes, of a HEADERS frame not including the variable-length |
| // header block. |
| size_t size = GetFrameHeaderSize(); |
| if (protocol_version_ == SPDY3) { |
| // Calculated as: |
| // control frame header + 4 (stream IDs) |
| size += 4; |
| } |
| |
| return size; |
| } |
| |
| size_t SpdyFramer::GetWindowUpdateSize() const { |
| // Size, in bytes, of a WINDOW_UPDATE frame. |
| if (protocol_version_ == SPDY3) { |
| // Calculated as: |
| // control frame header + 4 (stream id) + 4 (delta) |
| return GetFrameHeaderSize() + 8; |
| } else { |
| // Calculated as: |
| // frame prefix + 4 (delta) |
| return GetFrameHeaderSize() + 4; |
| } |
| } |
| |
| size_t SpdyFramer::GetBlockedSize() const { |
| DCHECK_EQ(HTTP2, protocol_version_); |
| // Size, in bytes, of a BLOCKED frame. |
| // The BLOCKED frame has no payload beyond the control frame header. |
| return GetFrameHeaderSize(); |
| } |
| |
| size_t SpdyFramer::GetPushPromiseMinimumSize() const { |
| DCHECK_EQ(HTTP2, protocol_version_); |
| // Size, in bytes, of a PUSH_PROMISE frame, sans the embedded header block. |
| // Calculated as frame prefix + 4 (promised stream id) |
| return GetFrameHeaderSize() + 4; |
| } |
| |
| size_t SpdyFramer::GetContinuationMinimumSize() const { |
| // Size, in bytes, of a CONTINUATION frame not including the variable-length |
| // headers fragments. |
| return GetFrameHeaderSize(); |
| } |
| |
| size_t SpdyFramer::GetAltSvcMinimumSize() const { |
| // Size, in bytes, of an ALTSVC frame not including the Field-Value and |
| // (optional) Origin fields, both of which can vary in length. Note that this |
| // gives a lower bound on the frame size rather than a true minimum; the |
| // actual frame should always be larger than this. |
| // Calculated as frame prefix + 2 (origin_len). |
| return GetFrameHeaderSize() + 2; |
| } |
| |
| size_t SpdyFramer::GetPrioritySize() const { |
| // Size, in bytes, of a PRIORITY frame. |
| return GetFrameHeaderSize() + kPriorityDependencyPayloadSize + |
| kPriorityWeightPayloadSize; |
| } |
| |
| size_t SpdyFramer::GetFrameMinimumSize() const { |
| return GetFrameHeaderSize(); |
| } |
| |
| size_t SpdyFramer::GetFrameMaximumSize() const { |
| if (protocol_version_ == HTTP2) { |
| return send_frame_size_limit_ + |
| SpdyConstants::GetFrameHeaderSize(protocol_version_); |
| } else { |
| return SpdyConstants::GetMaxFrameSizeLimit(protocol_version_); |
| } |
| } |
| |
| size_t SpdyFramer::GetDataFrameMaximumPayload() const { |
| if (protocol_version_ == HTTP2) { |
| return std::min(kMaxDataPayloadSendSize, |
| GetFrameMaximumSize() - GetDataFrameMinimumSize()); |
| } else { |
| return GetFrameMaximumSize() - GetDataFrameMinimumSize(); |
| } |
| } |
| |
| const char* SpdyFramer::StateToString(int state) { |
| switch (state) { |
| case SPDY_ERROR: |
| return "ERROR"; |
| case SPDY_FRAME_COMPLETE: |
| return "FRAME_COMPLETE"; |
| case SPDY_READY_FOR_FRAME: |
| return "READY_FOR_FRAME"; |
| case SPDY_READING_COMMON_HEADER: |
| return "READING_COMMON_HEADER"; |
| case SPDY_CONTROL_FRAME_PAYLOAD: |
| return "CONTROL_FRAME_PAYLOAD"; |
| case SPDY_READ_DATA_FRAME_PADDING_LENGTH: |
| return "SPDY_READ_DATA_FRAME_PADDING_LENGTH"; |
| case SPDY_CONSUME_PADDING: |
| return "SPDY_CONSUME_PADDING"; |
| case SPDY_IGNORE_REMAINING_PAYLOAD: |
| return "IGNORE_REMAINING_PAYLOAD"; |
| case SPDY_FORWARD_STREAM_FRAME: |
| return "FORWARD_STREAM_FRAME"; |
| case SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK: |
| return "SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK"; |
| case SPDY_CONTROL_FRAME_HEADER_BLOCK: |
| return "SPDY_CONTROL_FRAME_HEADER_BLOCK"; |
| case SPDY_GOAWAY_FRAME_PAYLOAD: |
| return "SPDY_GOAWAY_FRAME_PAYLOAD"; |
| case SPDY_RST_STREAM_FRAME_PAYLOAD: |
| return "SPDY_RST_STREAM_FRAME_PAYLOAD"; |
| case SPDY_SETTINGS_FRAME_HEADER: |
| return "SPDY_SETTINGS_FRAME_HEADER"; |
| case SPDY_SETTINGS_FRAME_PAYLOAD: |
| return "SPDY_SETTINGS_FRAME_PAYLOAD"; |
| case SPDY_ALTSVC_FRAME_PAYLOAD: |
| return "SPDY_ALTSVC_FRAME_PAYLOAD"; |
| } |
| return "UNKNOWN_STATE"; |
| } |
| |
| void SpdyFramer::set_error(SpdyError error) { |
| DCHECK(visitor_); |
| error_code_ = error; |
| // These values will usually get reset once we come to the end |
| // of a header block, but if we run into an error that |
| // might not happen, so reset them here. |
| expect_continuation_ = 0; |
| end_stream_when_done_ = false; |
| |
| CHANGE_STATE(SPDY_ERROR); |
| visitor_->OnError(this); |
| } |
| |
| const char* SpdyFramer::ErrorCodeToString(int error_code) { |
| switch (error_code) { |
| case SPDY_NO_ERROR: |
| return "NO_ERROR"; |
| case SPDY_INVALID_STREAM_ID: |
| return "INVALID_STREAM_ID"; |
| case SPDY_INVALID_CONTROL_FRAME: |
| return "INVALID_CONTROL_FRAME"; |
| case SPDY_CONTROL_PAYLOAD_TOO_LARGE: |
| return "CONTROL_PAYLOAD_TOO_LARGE"; |
| case SPDY_INVALID_CONTROL_FRAME_SIZE: |
| return "INVALID_CONTROL_FRAME_SIZE"; |
| case SPDY_OVERSIZED_PAYLOAD: |
| return "OVERSIZED_PAYLOAD"; |
| case SPDY_ZLIB_INIT_FAILURE: |
| return "ZLIB_INIT_FAILURE"; |
| case SPDY_UNSUPPORTED_VERSION: |
| return "UNSUPPORTED_VERSION"; |
| case SPDY_DECOMPRESS_FAILURE: |
| return "DECOMPRESS_FAILURE"; |
| case SPDY_COMPRESS_FAILURE: |
| return "COMPRESS_FAILURE"; |
| case SPDY_INVALID_PADDING: |
| return "SPDY_INVALID_PADDING"; |
| case SPDY_INVALID_DATA_FRAME_FLAGS: |
| return "SPDY_INVALID_DATA_FRAME_FLAGS"; |
| case SPDY_INVALID_CONTROL_FRAME_FLAGS: |
| return "SPDY_INVALID_CONTROL_FRAME_FLAGS"; |
| case SPDY_UNEXPECTED_FRAME: |
| return "UNEXPECTED_FRAME"; |
| case SPDY_INTERNAL_FRAMER_ERROR: |
| return "SPDY_INTERNAL_FRAMER_ERROR"; |
| } |
| return "UNKNOWN_ERROR"; |
| } |
| |
| const char* SpdyFramer::StatusCodeToString(int status_code) { |
| switch (status_code) { |
| case RST_STREAM_INVALID: |
| return "INVALID"; |
| case RST_STREAM_PROTOCOL_ERROR: |
| return "PROTOCOL_ERROR"; |
| case RST_STREAM_INVALID_STREAM: |
| return "INVALID_STREAM"; |
| case RST_STREAM_REFUSED_STREAM: |
| return "REFUSED_STREAM"; |
| case RST_STREAM_UNSUPPORTED_VERSION: |
| return "UNSUPPORTED_VERSION"; |
| case RST_STREAM_CANCEL: |
| return "CANCEL"; |
| case RST_STREAM_INTERNAL_ERROR: |
| return "INTERNAL_ERROR"; |
| case RST_STREAM_FLOW_CONTROL_ERROR: |
| return "FLOW_CONTROL_ERROR"; |
| case RST_STREAM_STREAM_IN_USE: |
| return "STREAM_IN_USE"; |
| case RST_STREAM_STREAM_ALREADY_CLOSED: |
| return "STREAM_ALREADY_CLOSED"; |
| case RST_STREAM_FRAME_TOO_LARGE: |
| return "FRAME_TOO_LARGE"; |
| case RST_STREAM_CONNECT_ERROR: |
| return "CONNECT_ERROR"; |
| case RST_STREAM_ENHANCE_YOUR_CALM: |
| return "ENHANCE_YOUR_CALM"; |
| case RST_STREAM_INADEQUATE_SECURITY: |
| return "INADEQUATE_SECURITY"; |
| case RST_STREAM_HTTP_1_1_REQUIRED: |
| return "HTTP_1_1_REQUIRED"; |
| } |
| return "UNKNOWN_STATUS"; |
| } |
| |
| const char* SpdyFramer::FrameTypeToString(SpdyFrameType type) { |
| switch (type) { |
| case DATA: |
| return "DATA"; |
| case SYN_STREAM: |
| return "SYN_STREAM"; |
| case SYN_REPLY: |
| return "SYN_REPLY"; |
| case RST_STREAM: |
| return "RST_STREAM"; |
| case SETTINGS: |
| return "SETTINGS"; |
| case PING: |
| return "PING"; |
| case GOAWAY: |
| return "GOAWAY"; |
| case HEADERS: |
| return "HEADERS"; |
| case WINDOW_UPDATE: |
| return "WINDOW_UPDATE"; |
| case PUSH_PROMISE: |
| return "PUSH_PROMISE"; |
| case CONTINUATION: |
| return "CONTINUATION"; |
| case PRIORITY: |
| return "PRIORITY"; |
| case ALTSVC: |
| return "ALTSVC"; |
| case BLOCKED: |
| return "BLOCKED"; |
| } |
| return "UNKNOWN_CONTROL_TYPE"; |
| } |
| |
| size_t SpdyFramer::ProcessInput(const char* data, size_t len) { |
| DCHECK(visitor_); |
| DCHECK(data); |
| |
| if (decoder_adapter_ != nullptr) { |
| return decoder_adapter_->ProcessInput(data, len); |
| } |
| const size_t original_len = len; |
| do { |
| previous_state_ = state_; |
| switch (state_) { |
| case SPDY_ERROR: |
| goto bottom; |
| |
| case SPDY_FRAME_COMPLETE: |
| // Should not enter in this state. |
| DCHECK_LT(len, original_len); |
| Reset(); |
| if (len > 0 && !process_single_input_frame_) { |
| CHANGE_STATE(SPDY_READING_COMMON_HEADER); |
| } |
| break; |
| |
| case SPDY_READY_FOR_FRAME: |
| if (len > 0) { |
| CHANGE_STATE(SPDY_READING_COMMON_HEADER); |
| } |
| break; |
| |
| case SPDY_READING_COMMON_HEADER: { |
| size_t bytes_read = ProcessCommonHeader(data, len); |
| len -= bytes_read; |
| data += bytes_read; |
| break; |
| } |
| |
| case SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK: { |
| // Control frames that contain header blocks |
| // (SYN_STREAM, SYN_REPLY, HEADERS, PUSH_PROMISE, CONTINUATION) |
| // take a special path through the state machine - they |
| // will go: |
| // 1. SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK |
| // 2. SPDY_CONTROL_FRAME_HEADER_BLOCK |
| int bytes_read = ProcessControlFrameBeforeHeaderBlock(data, len); |
| len -= bytes_read; |
| data += bytes_read; |
| break; |
| } |
| |
| case SPDY_SETTINGS_FRAME_HEADER: { |
| int bytes_read = ProcessSettingsFrameHeader(data, len); |
| len -= bytes_read; |
| data += bytes_read; |
| break; |
| } |
| |
| case SPDY_SETTINGS_FRAME_PAYLOAD: { |
| int bytes_read = ProcessSettingsFramePayload(data, len); |
| len -= bytes_read; |
| data += bytes_read; |
| break; |
| } |
| |
| case SPDY_CONTROL_FRAME_HEADER_BLOCK: { |
| int bytes_read = ProcessControlFrameHeaderBlock( |
| data, len, protocol_version_ == HTTP2); |
| len -= bytes_read; |
| data += bytes_read; |
| break; |
| } |
| |
| case SPDY_RST_STREAM_FRAME_PAYLOAD: { |
| size_t bytes_read = ProcessRstStreamFramePayload(data, len); |
| len -= bytes_read; |
| data += bytes_read; |
| break; |
| } |
| |
| case SPDY_GOAWAY_FRAME_PAYLOAD: { |
| size_t bytes_read = ProcessGoAwayFramePayload(data, len); |
| len -= bytes_read; |
| data += bytes_read; |
| break; |
| } |
| |
| case SPDY_ALTSVC_FRAME_PAYLOAD: { |
| size_t bytes_read = ProcessAltSvcFramePayload(data, len); |
| len -= bytes_read; |
| data += bytes_read; |
| break; |
| } |
| |
| case SPDY_CONTROL_FRAME_PAYLOAD: { |
| size_t bytes_read = ProcessControlFramePayload(data, len); |
| len -= bytes_read; |
| data += bytes_read; |
| break; |
| } |
| |
| case SPDY_READ_DATA_FRAME_PADDING_LENGTH: { |
| size_t bytes_read = ProcessDataFramePaddingLength(data, len); |
| len -= bytes_read; |
| data += bytes_read; |
| break; |
| } |
| |
| case SPDY_CONSUME_PADDING: { |
| size_t bytes_read = ProcessFramePadding(data, len); |
| len -= bytes_read; |
| data += bytes_read; |
| break; |
| } |
| |
| case SPDY_IGNORE_REMAINING_PAYLOAD: { |
| size_t bytes_read = ProcessIgnoredControlFramePayload(/*data,*/ len); |
| len -= bytes_read; |
| data += bytes_read; |
| break; |
| } |
| |
| case SPDY_FORWARD_STREAM_FRAME: { |
| size_t bytes_read = ProcessDataFramePayload(data, len); |
| len -= bytes_read; |
| data += bytes_read; |
| break; |
| } |
| |
| default: |
| SPDY_BUG << "Invalid value for " << display_protocol_ |
| << " framer state: " << state_; |
| // This ensures that we don't infinite-loop if state_ gets an |
| // invalid value somehow, such as due to a SpdyFramer getting deleted |
| // from a callback it calls. |
| goto bottom; |
| } |
| } while (state_ != previous_state_); |
| bottom: |
| DCHECK(len == 0 || state_ == SPDY_ERROR || process_single_input_frame_) |
| << "len: " << len << " state: " << state_ |
| << " process single input frame: " << process_single_input_frame_; |
| if (current_frame_buffer_.len() == 0 && remaining_data_length_ == 0 && |
| remaining_control_header_ == 0) { |
| DCHECK(state_ == SPDY_READY_FOR_FRAME || state_ == SPDY_ERROR) |
| << "State: " << StateToString(state_); |
| } |
| |
| return original_len - len; |
| } |
| |
| SpdyFramer::CharBuffer::CharBuffer(size_t capacity) |
| : buffer_(new char[capacity]), capacity_(capacity), len_(0) {} |
| SpdyFramer::CharBuffer::~CharBuffer() {} |
| |
| void SpdyFramer::CharBuffer::CopyFrom(const char* data, size_t size) { |
| DCHECK_GE(capacity_, len_ + size); |
| memcpy(buffer_.get() + len_, data, size); |
| len_ += size; |
| } |
| |
| void SpdyFramer::CharBuffer::Rewind() { |
| len_ = 0; |
| } |
| |
| SpdyFramer::SpdySettingsScratch::SpdySettingsScratch() |
| : buffer(8), last_setting_id(-1) {} |
| |
| void SpdyFramer::SpdySettingsScratch::Reset() { |
| buffer.Rewind(); |
| last_setting_id = -1; |
| } |
| |
| SpdyFrameType SpdyFramer::ValidateFrameHeader(bool is_control_frame, |
| int frame_type_field, |
| size_t payload_length_field) { |
| if (!SpdyConstants::IsValidFrameType(protocol_version_, frame_type_field)) { |
| if (protocol_version_ == SPDY3) { |
| if (is_control_frame) { |
| DLOG(WARNING) << "Invalid control frame type " << frame_type_field |
| << " (protocol version: " << protocol_version_ << ")"; |
| set_error(SPDY_INVALID_CONTROL_FRAME); |
| } else { |
| // Else it's a SPDY3 data frame which we don't validate further here |
| } |
| } else { |
| // In HTTP2 we ignore unknown frame types for extensibility, as long as |
| // the rest of the control frame header is valid. |
| // We rely on the visitor to check validity of current_frame_stream_id_. |
| bool valid_stream = |
| visitor_->OnUnknownFrame(current_frame_stream_id_, frame_type_field); |
| if (expect_continuation_) { |
| // Report an unexpected frame error and close the connection |
| // if we expect a continuation and receive an unknown frame. |
| DLOG(ERROR) << "The framer was expecting to receive a CONTINUATION " |
| << "frame, but instead received an unknown frame of type " |
| << frame_type_field; |
| set_error(SPDY_UNEXPECTED_FRAME); |
| } else if (!valid_stream) { |
| // Report an invalid frame error and close the stream if the |
| // stream_id is not valid. |
| DLOG(WARNING) << "Unknown control frame type " << frame_type_field |
| << " received on invalid stream " |
| << current_frame_stream_id_; |
| set_error(SPDY_INVALID_CONTROL_FRAME); |
| } else { |
| DVLOG(1) << "Ignoring unknown frame type."; |
| CHANGE_STATE(SPDY_IGNORE_REMAINING_PAYLOAD); |
| } |
| } |
| return DATA; |
| } |
| |
| SpdyFrameType frame_type = |
| SpdyConstants::ParseFrameType(protocol_version_, frame_type_field); |
| |
| if (protocol_version_ == HTTP2) { |
| if (!SpdyConstants::IsValidHTTP2FrameStreamId(current_frame_stream_id_, |
| frame_type)) { |
| DLOG(ERROR) << "The framer received an invalid streamID of " |
| << current_frame_stream_id_ << " for a frame of type " |
| << FrameTypeToString(frame_type); |
| set_error(SPDY_INVALID_STREAM_ID); |
| return frame_type; |
| } |
| |
| // Ensure that we see a CONTINUATION frame iff we expect to. |
| if ((frame_type == CONTINUATION) != (expect_continuation_ != 0)) { |
| if (expect_continuation_ != 0) { |
| DLOG(ERROR) << "The framer was expecting to receive a CONTINUATION " |
| << "frame, but instead received a frame of type " |
| << FrameTypeToString(frame_type); |
| } else { |
| DLOG(ERROR) << "The framer received an unexpected CONTINUATION frame."; |
| } |
| set_error(SPDY_UNEXPECTED_FRAME); |
| return frame_type; |
| } |
| } |
| |
| if (protocol_version_ == HTTP2 && |
| payload_length_field > recv_frame_size_limit_) { |
| set_error(SPDY_OVERSIZED_PAYLOAD); |
| } |
| |
| return frame_type; |
| } |
| |
| size_t SpdyFramer::ProcessCommonHeader(const char* data, size_t len) { |
| // This should only be called when we're in the SPDY_READING_COMMON_HEADER |
| // state. |
| DCHECK_EQ(state_, SPDY_READING_COMMON_HEADER); |
| |
| size_t original_len = len; |
| |
| // Update current frame buffer as needed. |
| if (current_frame_buffer_.len() < GetFrameHeaderSize()) { |
| size_t bytes_desired = GetFrameHeaderSize() - current_frame_buffer_.len(); |
| UpdateCurrentFrameBuffer(&data, &len, bytes_desired); |
| } |
| |
| if (current_frame_buffer_.len() < GetFrameHeaderSize()) { |
| // Not enough information to do anything meaningful. |
| return original_len - len; |
| } |
| |
| SpdyFrameReader reader(current_frame_buffer_.data(), |
| current_frame_buffer_.len()); |
| bool is_control_frame = false; |
| |
| int control_frame_type_field = |
| SpdyConstants::DataFrameType(protocol_version_); |
| // ProcessControlFrameHeader() will set current_frame_type_ to the |
| // correct value if this is a valid control frame. |
| current_frame_type_ = DATA; |
| if (protocol_version_ == SPDY3) { |
| uint16_t version = 0; |
| bool successful_read = reader.ReadUInt16(&version); |
| DCHECK(successful_read); |
| is_control_frame = (version & kControlFlagMask) != 0; |
| if (is_control_frame) { |
| version &= ~kControlFlagMask; |
| if (version != kSpdy3Version) { |
| // Version does not match the version the framer was initialized with. |
| DVLOG(1) << "Unsupported SPDY version " << version << " (expected " |
| << kSpdy3Version << ")"; |
| set_error(SPDY_UNSUPPORTED_VERSION); |
| return 0; |
| } |
| uint16_t control_frame_type_field_uint16; |
| successful_read = reader.ReadUInt16(&control_frame_type_field_uint16); |
| control_frame_type_field = control_frame_type_field_uint16; |
| } else { |
| reader.Rewind(); |
| successful_read = reader.ReadUInt31(¤t_frame_stream_id_); |
| } |
| DCHECK(successful_read); |
| |
| successful_read = reader.ReadUInt8(¤t_frame_flags_); |
| DCHECK(successful_read); |
| |
| uint32_t length_field = 0; |
| successful_read = reader.ReadUInt24(&length_field); |
| DCHECK(successful_read); |
| remaining_data_length_ = length_field; |
| current_frame_length_ = remaining_data_length_ + reader.GetBytesConsumed(); |
| } else { |
| uint32_t length_field = 0; |
| bool successful_read = reader.ReadUInt24(&length_field); |
| DCHECK(successful_read); |
| |
| uint8_t control_frame_type_field_uint8; |
| successful_read = reader.ReadUInt8(&control_frame_type_field_uint8); |
| DCHECK(successful_read); |
| // We check control_frame_type_field's validity in |
| // ProcessControlFrameHeader(). |
| control_frame_type_field = control_frame_type_field_uint8; |
| is_control_frame = |
| control_frame_type_field != |
| SpdyConstants::SerializeFrameType(protocol_version_, DATA); |
| |
| current_frame_length_ = length_field + GetFrameHeaderSize(); |
| |
| successful_read = reader.ReadUInt8(¤t_frame_flags_); |
| DCHECK(successful_read); |
| |
| successful_read = reader.ReadUInt31(¤t_frame_stream_id_); |
| DCHECK(successful_read); |
| |
| remaining_data_length_ = current_frame_length_ - reader.GetBytesConsumed(); |
| } |
| |
| DCHECK_EQ(GetFrameHeaderSize(), reader.GetBytesConsumed()); |
| DCHECK_EQ(current_frame_length_, |
| remaining_data_length_ + reader.GetBytesConsumed()); |
| |
| // This is just a sanity check for help debugging early frame errors. |
| if (remaining_data_length_ > 1000000u) { |
| // The strncmp for 5 is safe because we only hit this point if we |
| // have kMinCommonHeader (8) bytes |
| if (!syn_frame_processed_ && |
| strncmp(current_frame_buffer_.data(), "HTTP/", 5) == 0) { |
| LOG(WARNING) << "Unexpected HTTP response to " << display_protocol_ |
| << " request"; |
| probable_http_response_ = true; |
| } else { |
| LOG(WARNING) << "Unexpectedly large frame. " << display_protocol_ |
| << " session is likely corrupt."; |
| } |
| } |
| |
| current_frame_type_ = ValidateFrameHeader( |
| is_control_frame, control_frame_type_field, remaining_data_length_); |
| |
| if (state_ == SPDY_ERROR || state_ == SPDY_IGNORE_REMAINING_PAYLOAD) { |
| return original_len - len; |
| } |
| |
| // if we're here, then we have the common header all received. |
| if (!is_control_frame) { |
| uint8_t valid_data_flags = 0; |
| if (protocol_version_ == SPDY3) { |
| valid_data_flags = DATA_FLAG_FIN; |
| } else { |
| valid_data_flags = DATA_FLAG_FIN | DATA_FLAG_PADDED; |
| } |
| |
| if (current_frame_flags_ & ~valid_data_flags) { |
| set_error(SPDY_INVALID_DATA_FRAME_FLAGS); |
| } else { |
| visitor_->OnDataFrameHeader(current_frame_stream_id_, |
| remaining_data_length_, |
| current_frame_flags_ & DATA_FLAG_FIN); |
| if (remaining_data_length_ > 0) { |
| CHANGE_STATE(SPDY_READ_DATA_FRAME_PADDING_LENGTH); |
| } else { |
| // Empty data frame. |
| if (current_frame_flags_ & DATA_FLAG_FIN) { |
| visitor_->OnStreamEnd(current_frame_stream_id_); |
| } |
| CHANGE_STATE(SPDY_FRAME_COMPLETE); |
| } |
| } |
| } else { |
| ProcessControlFrameHeader(control_frame_type_field); |
| } |
| |
| return original_len - len; |
| } |
| |
| void SpdyFramer::ProcessControlFrameHeader(int control_frame_type_field) { |
| DCHECK_EQ(SPDY_NO_ERROR, error_code_); |
| DCHECK_LE(GetFrameHeaderSize(), current_frame_buffer_.len()); |
| |
| // Do some sanity checking on the control frame sizes and flags. |
| switch (current_frame_type_) { |
| case SYN_STREAM: |
| if (current_frame_length_ < GetSynStreamMinimumSize()) { |
| set_error(SPDY_INVALID_CONTROL_FRAME); |
| } else if (current_frame_flags_ & |
| ~(CONTROL_FLAG_FIN | CONTROL_FLAG_UNIDIRECTIONAL)) { |
| set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); |
| } |
| break; |
| case SYN_REPLY: |
| if (current_frame_length_ < GetSynReplyMinimumSize()) { |
| set_error(SPDY_INVALID_CONTROL_FRAME); |
| } else if (current_frame_flags_ & ~CONTROL_FLAG_FIN) { |
| set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); |
| } |
| break; |
| case RST_STREAM: |
| if (current_frame_length_ != GetRstStreamMinimumSize()) { |
| set_error(SPDY_INVALID_CONTROL_FRAME_SIZE); |
| } else if (current_frame_flags_ != 0) { |
| VLOG(1) << "Undefined frame flags for RST_STREAM frame: " << hex |
| << static_cast<int>(current_frame_flags_); |
| current_frame_flags_ = 0; |
| } |
| break; |
| case SETTINGS: |
| { |
| // Make sure that we have an integral number of 8-byte key/value pairs, |
| // plus a 4-byte length field in SPDY3 and below. |
| size_t values_prefix_size = (protocol_version_ == SPDY3 ? 4 : 0); |
| // Size of each key/value pair in bytes. |
| size_t setting_size = SpdyConstants::GetSettingSize(protocol_version_); |
| if (current_frame_length_ < GetSettingsMinimumSize() || |
| (current_frame_length_ - GetFrameHeaderSize()) % setting_size != |
| values_prefix_size) { |
| DLOG(WARNING) << "Invalid length for SETTINGS frame: " |
| << current_frame_length_; |
| set_error(SPDY_INVALID_CONTROL_FRAME_SIZE); |
| } else if (protocol_version_ == SPDY3 && |
| current_frame_flags_ & |
| ~SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS) { |
| set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); |
| } else if (protocol_version_ == HTTP2 && |
| current_frame_flags_ & SETTINGS_FLAG_ACK && |
| current_frame_length_ > GetSettingsMinimumSize()) { |
| set_error(SPDY_INVALID_CONTROL_FRAME_SIZE); |
| } else if (protocol_version_ == HTTP2 && |
| current_frame_flags_ & ~SETTINGS_FLAG_ACK) { |
| VLOG(1) << "Undefined frame flags for SETTINGS frame: " << hex |
| << static_cast<int>(current_frame_flags_); |
| current_frame_flags_ &= SETTINGS_FLAG_ACK; |
| } |
| break; |
| } |
| case PING: |
| if (current_frame_length_ != GetPingSize()) { |
| set_error(SPDY_INVALID_CONTROL_FRAME_SIZE); |
| } else { |
| if (protocol_version_ == SPDY3 && current_frame_flags_ != 0) { |
| VLOG(1) << "Undefined frame flags for PING frame: " << hex |
| << static_cast<int>(current_frame_flags_); |
| current_frame_flags_ = 0; |
| } else if (protocol_version_ == HTTP2 && |
| current_frame_flags_ & ~PING_FLAG_ACK) { |
| VLOG(1) << "Undefined frame flags for PING frame: " << hex |
| << static_cast<int>(current_frame_flags_); |
| current_frame_flags_ &= PING_FLAG_ACK; |
| } |
| } |
| break; |
| case GOAWAY: |
| { |
| // For SPDY/3, there are only mandatory fields and the header has a |
| // fixed length. For HTTP/2, optional opaque data may be appended to the |
| // GOAWAY frame, thus there is only a minimal length restriction. |
| if ((protocol_version_ == SPDY3 && |
| current_frame_length_ != GetGoAwayMinimumSize()) || |
| (protocol_version_ == HTTP2 && |
| current_frame_length_ < GetGoAwayMinimumSize())) { |
| set_error(SPDY_INVALID_CONTROL_FRAME); |
| } else if (current_frame_flags_ != 0) { |
| VLOG(1) << "Undefined frame flags for GOAWAY frame: " << hex |
| << static_cast<int>(current_frame_flags_); |
| current_frame_flags_ = 0; |
| } |
| break; |
| } |
| case HEADERS: |
| { |
| size_t min_size = GetHeadersMinimumSize(); |
| if (protocol_version_ == HTTP2 && |
| (current_frame_flags_ & HEADERS_FLAG_PRIORITY)) { |
| min_size += 4; |
| } |
| if (current_frame_length_ < min_size) { |
| // TODO(mlavan): check here for HEADERS with no payload? |
| // (not allowed in HTTP2) |
| set_error(SPDY_INVALID_CONTROL_FRAME); |
| } else if (protocol_version_ == SPDY3 && |
| current_frame_flags_ & ~CONTROL_FLAG_FIN) { |
| VLOG(1) << "Undefined frame flags for HEADERS frame: " << hex |
| << static_cast<int>(current_frame_flags_); |
| current_frame_flags_ &= CONTROL_FLAG_FIN; |
| } else if (protocol_version_ == HTTP2 && |
| current_frame_flags_ & |
| ~(CONTROL_FLAG_FIN | HEADERS_FLAG_PRIORITY | |
| HEADERS_FLAG_END_HEADERS | HEADERS_FLAG_PADDED)) { |
| VLOG(1) << "Undefined frame flags for HEADERS frame: " << hex |
| << static_cast<int>(current_frame_flags_); |
| current_frame_flags_ &= |
| (CONTROL_FLAG_FIN | HEADERS_FLAG_PRIORITY | |
| HEADERS_FLAG_END_HEADERS | HEADERS_FLAG_PADDED); |
| } |
| } |
| break; |
| case WINDOW_UPDATE: |
| if (current_frame_length_ != GetWindowUpdateSize()) { |
| set_error(SPDY_INVALID_CONTROL_FRAME_SIZE); |
| } else if (current_frame_flags_ != 0) { |
| VLOG(1) << "Undefined frame flags for WINDOW_UPDATE frame: " << hex |
| << static_cast<int>(current_frame_flags_); |
| current_frame_flags_ = 0; |
| } |
| break; |
| case BLOCKED: |
| if (protocol_version_ == SPDY3 || |
| current_frame_length_ != GetBlockedSize()) { |
| set_error(SPDY_INVALID_CONTROL_FRAME); |
| } else if (current_frame_flags_ != 0) { |
| VLOG(1) << "Undefined frame flags for BLOCKED frame: " << hex |
| << static_cast<int>(current_frame_flags_); |
| current_frame_flags_ = 0; |
| } |
| break; |
| case PUSH_PROMISE: |
| if (current_frame_length_ < GetPushPromiseMinimumSize()) { |
| set_error(SPDY_INVALID_CONTROL_FRAME); |
| } else if (protocol_version_ == SPDY3 && current_frame_flags_ != 0) { |
| VLOG(1) << "Undefined frame flags for PUSH_PROMISE frame: " << hex |
| << static_cast<int>(current_frame_flags_); |
| current_frame_flags_ = 0; |
| } else if (protocol_version_ == HTTP2 && |
| current_frame_flags_ & |
| ~(PUSH_PROMISE_FLAG_END_PUSH_PROMISE | |
| HEADERS_FLAG_PADDED)) { |
| VLOG(1) << "Undefined frame flags for PUSH_PROMISE frame: " << hex |
| << static_cast<int>(current_frame_flags_); |
| current_frame_flags_ &= |
| (PUSH_PROMISE_FLAG_END_PUSH_PROMISE | HEADERS_FLAG_PADDED); |
| } |
| break; |
| case CONTINUATION: |
| if (protocol_version_ == SPDY3 || |
| current_frame_length_ < GetContinuationMinimumSize()) { |
| set_error(SPDY_INVALID_CONTROL_FRAME); |
| } else if (current_frame_flags_ & ~HEADERS_FLAG_END_HEADERS) { |
| VLOG(1) << "Undefined frame flags for CONTINUATION frame: " << hex |
| << static_cast<int>(current_frame_flags_); |
| current_frame_flags_ &= HEADERS_FLAG_END_HEADERS; |
| } |
| break; |
| case ALTSVC: |
| if (current_frame_length_ <= GetAltSvcMinimumSize()) { |
| set_error(SPDY_INVALID_CONTROL_FRAME); |
| } else if (current_frame_flags_ != 0) { |
| VLOG(1) << "Undefined frame flags for ALTSVC frame: " << hex |
| << static_cast<int>(current_frame_flags_); |
| current_frame_flags_ = 0; |
| } |
| break; |
| case PRIORITY: |
| if (protocol_version_ == SPDY3 || |
| current_frame_length_ != GetPrioritySize()) { |
| set_error(SPDY_INVALID_CONTROL_FRAME_SIZE); |
| } else if (current_frame_flags_ != 0) { |
| VLOG(1) << "Undefined frame flags for PRIORITY frame: " << hex |
| << static_cast<int>(current_frame_flags_); |
| current_frame_flags_ = 0; |
| } |
| break; |
| default: |
| LOG(WARNING) << "Valid " << display_protocol_ |
| << " control frame with unhandled type: " |
| << current_frame_type_; |
| // This branch should be unreachable because of the frame type bounds |
| // check above. However, we DLOG(FATAL) here in an effort to painfully |
| // club the head of the developer who failed to keep this file in sync |
| // with spdy_protocol.h. |
| set_error(SPDY_INVALID_CONTROL_FRAME); |
| DLOG(FATAL); |
| break; |
| } |
| |
| if (state_ == SPDY_ERROR) { |
| return; |
| } |
| |
| if (protocol_version_ == SPDY3 && |
| current_frame_length_ > |
| kSpdyInitialFrameSizeLimit + |
| SpdyConstants::GetFrameHeaderSize(protocol_version_)) { |
| DLOG(WARNING) << "Received control frame of type " << current_frame_type_ |
| << " with way too big of a payload: " |
| << current_frame_length_; |
| set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE); |
| return; |
| } |
| |
| if (current_frame_type_ == GOAWAY) { |
| CHANGE_STATE(SPDY_GOAWAY_FRAME_PAYLOAD); |
| return; |
| } |
| |
| if (current_frame_type_ == RST_STREAM) { |
| CHANGE_STATE(SPDY_RST_STREAM_FRAME_PAYLOAD); |
| return; |
| } |
| |
| if (current_frame_type_ == ALTSVC) { |
| CHANGE_STATE(SPDY_ALTSVC_FRAME_PAYLOAD); |
| return; |
| } |
| // Determine the frame size without variable-length data. |
| int32_t frame_size_without_variable_data; |
| switch (current_frame_type_) { |
| case SYN_STREAM: |
| syn_frame_processed_ = true; |
| frame_size_without_variable_data = GetSynStreamMinimumSize(); |
| break; |
| case SYN_REPLY: |
| syn_frame_processed_ = true; |
| frame_size_without_variable_data = GetSynReplyMinimumSize(); |
| break; |
| case SETTINGS: |
| frame_size_without_variable_data = GetSettingsMinimumSize(); |
| break; |
| case HEADERS: |
| frame_size_without_variable_data = GetHeadersMinimumSize(); |
| if (protocol_version_ == HTTP2) { |
| if (current_frame_flags_ & HEADERS_FLAG_PADDED) { |
| frame_size_without_variable_data += kPadLengthFieldSize; |
| } |
| if (current_frame_flags_ & HEADERS_FLAG_PRIORITY) { |
| frame_size_without_variable_data += |
| kPriorityDependencyPayloadSize + |
| kPriorityWeightPayloadSize; |
| } |
| } |
| break; |
| case PUSH_PROMISE: |
| frame_size_without_variable_data = GetPushPromiseMinimumSize(); |
| if (protocol_version_ == HTTP2 && |
| current_frame_flags_ & PUSH_PROMISE_FLAG_PADDED) { |
| frame_size_without_variable_data += kPadLengthFieldSize; |
| } |
| break; |
| case CONTINUATION: |
| frame_size_without_variable_data = GetContinuationMinimumSize(); |
| break; |
| default: |
| frame_size_without_variable_data = -1; |
| break; |
| } |
| |
| if ((frame_size_without_variable_data == -1) && |
| (current_frame_length_ > kControlFrameBufferSize)) { |
| // We should already be in an error state. Double-check. |
| DCHECK_EQ(SPDY_ERROR, state_); |
| if (state_ != SPDY_ERROR) { |
| SPDY_BUG << display_protocol_ |
| << " control frame buffer too small for fixed-length frame."; |
| set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE); |
| } |
| return; |
| } |
| |
| if (frame_size_without_variable_data > 0) { |
| // We have a control frame with variable-size data. We need to parse the |
| // remainder of the control frame's header before we can parse the payload. |
| // The start of the payload varies with the control frame type. |
| DCHECK_GE(frame_size_without_variable_data, |
| static_cast<int32_t>(current_frame_buffer_.len())); |
| remaining_control_header_ = |
| frame_size_without_variable_data - current_frame_buffer_.len(); |
| |
| if (current_frame_type_ == SETTINGS) { |
| CHANGE_STATE(SPDY_SETTINGS_FRAME_HEADER); |
| } else { |
| CHANGE_STATE(SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK); |
| } |
| return; |
| } |
| |
| CHANGE_STATE(SPDY_CONTROL_FRAME_PAYLOAD); |
| } |
| |
| size_t SpdyFramer::UpdateCurrentFrameBuffer(const char** data, size_t* len, |
| size_t max_bytes) { |
| size_t bytes_to_read = std::min(*len, max_bytes); |
| if (bytes_to_read > 0) { |
| current_frame_buffer_.CopyFrom(*data, bytes_to_read); |
| *data += bytes_to_read; |
| *len -= bytes_to_read; |
| } |
| return bytes_to_read; |
| } |
| |
| size_t SpdyFramer::GetSerializedLength( |
| const SpdyMajorVersion spdy_version, |
| const SpdyHeaderBlock* headers) { |
| const size_t num_name_value_pairs_size = sizeof(uint32_t); |
| const size_t length_of_name_size = num_name_value_pairs_size; |
| const size_t length_of_value_size = num_name_value_pairs_size; |
| |
| size_t total_length = num_name_value_pairs_size; |
| for (const auto& header : *headers) { |
| // We add space for the length of the name and the length of the value as |
| // well as the length of the name and the length of the value. |
| total_length += length_of_name_size + header.first.size() + |
| length_of_value_size + header.second.size(); |
| } |
| return total_length; |
| } |
| |
| // TODO(phajdan.jr): Clean up after we no longer need |
| // to workaround http://crbug.com/139744. |
| #if !defined(USE_SYSTEM_ZLIB) |
| |
| // These constants are used by zlib to differentiate between normal data and |
| // cookie data. Cookie data is handled specially by zlib when compressing. |
| enum ZDataClass { |
| // kZStandardData is compressed normally, save that it will never match |
| // against any other class of data in the window. |
| kZStandardData = Z_CLASS_STANDARD, |
| // kZCookieData is compressed in its own Huffman blocks and only matches in |
| // its entirety and only against other kZCookieData blocks. Any matches must |
| // be preceeded by a kZStandardData byte, or a semicolon to prevent matching |
| // a suffix. It's assumed that kZCookieData ends in a semicolon to prevent |
| // prefix matches. |
| kZCookieData = Z_CLASS_COOKIE, |
| // kZHuffmanOnlyData is only Huffman compressed - no matches are performed |
| // against the window. |
| kZHuffmanOnlyData = Z_CLASS_HUFFMAN_ONLY, |
| }; |
| |
| // WriteZ writes |data| to the deflate context |out|. WriteZ will flush as |
| // needed when switching between classes of data. |
| static void WriteZ(const base::StringPiece& data, |
| ZDataClass clas, |
| z_stream* out) { |
| int rv; |
| |
| // If we are switching from standard to non-standard data then we need to end |
| // the current Huffman context to avoid it leaking between them. |
| if (out->clas == kZStandardData && |
| clas != kZStandardData) { |
| out->avail_in = 0; |
| rv = deflate(out, Z_PARTIAL_FLUSH); |
| DCHECK_EQ(Z_OK, rv); |
| DCHECK_EQ(0u, out->avail_in); |
| DCHECK_LT(0u, out->avail_out); |
| } |
| |
| out->next_in = reinterpret_cast<Bytef*>(const_cast<char*>(data.data())); |
| out->avail_in = data.size(); |
| out->clas = clas; |
| if (clas == kZStandardData) { |
| rv = deflate(out, Z_NO_FLUSH); |
| } else { |
| rv = deflate(out, Z_PARTIAL_FLUSH); |
| } |
| if (!data.empty()) { |
| // If we didn't provide any data then zlib will return Z_BUF_ERROR. |
| DCHECK_EQ(Z_OK, rv); |
| } |
| DCHECK_EQ(0u, out->avail_in); |
| DCHECK_LT(0u, out->avail_out); |
| } |
| |
| // WriteLengthZ writes |n| as a |length|-byte, big-endian number to |out|. |
| static void WriteLengthZ(size_t n, |
| unsigned length, |
| ZDataClass clas, |
| z_stream* out) { |
| char buf[4]; |
| DCHECK_LE(length, sizeof(buf)); |
| for (unsigned i = 1; i <= length; i++) { |
| buf[length - i] = static_cast<char>(n); |
| n >>= 8; |
| } |
| WriteZ(base::StringPiece(buf, length), clas, out); |
| } |
| |
| // WriteHeaderBlockToZ serialises |headers| to the deflate context |z| in a |
| // manner that resists the length of the compressed data from compromising |
| // cookie data. |
| void SpdyFramer::WriteHeaderBlockToZ(const SpdyHeaderBlock* headers, |
| z_stream* z) const { |
| const size_t length_length = 4; |
| WriteLengthZ(headers->size(), length_length, kZStandardData, z); |
| |
| SpdyHeaderBlock::const_iterator it; |
| for (it = headers->begin(); it != headers->end(); ++it) { |
| WriteLengthZ(it->first.size(), length_length, kZStandardData, z); |
| WriteZ(it->first, kZStandardData, z); |
| |
| if (it->first == "cookie") { |
| // We require the cookie values (save for the last) to end with a |
| // semicolon and (save for the first) to start with a space. This is |
| // typically the format that we are given them in but we reserialize them |
| // to be sure. |
| |
| std::vector<base::StringPiece> cookie_values; |
| size_t cookie_length = 0; |
| base::StringPiece cookie_data(it->second); |
| |
| for (;;) { |
| while (!cookie_data.empty() && |
| (cookie_data[0] == ' ' || cookie_data[0] == '\t')) { |
| cookie_data.remove_prefix(1); |
| } |
| if (cookie_data.empty()) |
| break; |
| |
| size_t i; |
| for (i = 0; i < cookie_data.size(); i++) { |
| if (cookie_data[i] == ';') |
| break; |
| } |
| if (i < cookie_data.size()) { |
| if (!IsCookieEmpty(cookie_data.substr(0, i))) { |
| cookie_values.push_back(cookie_data.substr(0, i)); |
| cookie_length += i + 2 /* semicolon and space */; |
| } |
| cookie_data.remove_prefix(i + 1); |
| } else { |
| if (!IsCookieEmpty(cookie_data)) { |
| cookie_values.push_back(cookie_data); |
| cookie_length += cookie_data.size(); |
| } else if (cookie_length > 2) { |
| cookie_length -= 2 /* compensate for previously added length */; |
| } |
| cookie_data.remove_prefix(i); |
| } |
| } |
| |
| WriteLengthZ(cookie_length, length_length, kZStandardData, z); |
| for (size_t i = 0; i < cookie_values.size(); i++) { |
| std::string cookie; |
| // Since zlib will only back-reference complete cookies, a cookie that |
| // is currently last (and so doesn't have a trailing semicolon) won't |
| // match if it's later in a non-final position. The same is true of |
| // the first cookie. |
| if (i == 0 && cookie_values.size() == 1) { |
| cookie = cookie_values[i].as_string(); |
| } else if (i == 0) { |
| cookie = cookie_values[i].as_string() + ";"; |
| } else if (i < cookie_values.size() - 1) { |
| cookie = " " + cookie_values[i].as_string() + ";"; |
| } else { |
| cookie = " " + cookie_values[i].as_string(); |
| } |
| WriteZ(cookie, kZCookieData, z); |
| } |
| } else if (it->first == "accept" || |
| it->first == "accept-charset" || |
| it->first == "accept-encoding" || |
| it->first == "accept-language" || |
| it->first == "host" || |
| it->first == "version" || |
| it->first == "method" || |
| it->first == "scheme" || |
| it->first == ":host" || |
| it->first == ":version" || |
| it->first == ":method" || |
| it->first == ":scheme" || |
| it->first == "user-agent") { |
| WriteLengthZ(it->second.size(), length_length, kZStandardData, z); |
| WriteZ(it->second, kZStandardData, z); |
| } else { |
| // Non-whitelisted headers are Huffman compressed in their own block, but |
| // don't match against the window. |
| WriteLengthZ(it->second.size(), length_length, kZStandardData, z); |
| WriteZ(it->second, kZHuffmanOnlyData, z); |
| } |
| } |
| |
| z->avail_in = 0; |
| int rv = deflate(z, Z_SYNC_FLUSH); |
| DCHECK_EQ(Z_OK, rv); |
| z->clas = kZStandardData; |
| } |
| |
| #endif // !defined(USE_SYSTEM_ZLIB) |
| |
| size_t SpdyFramer::ProcessControlFrameBeforeHeaderBlock(const char* data, |
| size_t len) { |
| DCHECK_EQ(SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK, state_); |
| const size_t original_len = len; |
| |
| if (remaining_control_header_ > 0) { |
| size_t bytes_read = UpdateCurrentFrameBuffer(&data, &len, |
| remaining_control_header_); |
| remaining_control_header_ -= bytes_read; |
| remaining_data_length_ -= bytes_read; |
| } |
| |
| if (remaining_control_header_ == 0) { |
| SpdyFrameReader reader(current_frame_buffer_.data(), |
| current_frame_buffer_.len()); |
| reader.Seek(GetFrameHeaderSize()); // Seek past frame header. |
| |
| switch (current_frame_type_) { |
| case SYN_STREAM: |
| { |
| DCHECK_EQ(SPDY3, protocol_version_); |
| bool successful_read = true; |
| successful_read = reader.ReadUInt31(¤t_frame_stream_id_); |
| DCHECK(successful_read); |
| if (current_frame_stream_id_ == 0) { |
| set_error(SPDY_INVALID_CONTROL_FRAME); |
| return original_len - len; |
| } |
| |
| SpdyStreamId associated_to_stream_id = kInvalidStream; |
| successful_read = reader.ReadUInt31(&associated_to_stream_id); |
| DCHECK(successful_read); |
| |
| SpdyPriority priority = 0; |
| successful_read = reader.ReadUInt8(&priority); |
| DCHECK(successful_read); |
| priority = priority >> 5; |
| |
| // Seek past unused byte. |
| reader.Seek(1); |
| |
| DCHECK(reader.IsDoneReading()); |
| if (debug_visitor_) { |
| debug_visitor_->OnReceiveCompressedFrame( |
| current_frame_stream_id_, |
| current_frame_type_, |
| current_frame_length_); |
| } |
| visitor_->OnSynStream( |
| current_frame_stream_id_, |
| associated_to_stream_id, |
| priority, |
| (current_frame_flags_ & CONTROL_FLAG_FIN) != 0, |
| (current_frame_flags_ & CONTROL_FLAG_UNIDIRECTIONAL) != 0); |
| } |
| break; |
| case SYN_REPLY: |
| DCHECK_EQ(SPDY3, protocol_version_); |
| /* FALLTHROUGH */ |
| case HEADERS: |
| // SYN_REPLY and HEADERS are the same, save for the visitor call. |
| { |
| bool successful_read = true; |
| if (protocol_version_ == SPDY3) { |
| successful_read = reader.ReadUInt31(¤t_frame_stream_id_); |
| DCHECK(successful_read); |
| } |
| if (current_frame_stream_id_ == 0) { |
| set_error(SPDY_INVALID_CONTROL_FRAME); |
| return original_len - len; |
| } |
| if (protocol_version_ == HTTP2 && |
| !(current_frame_flags_ & HEADERS_FLAG_END_HEADERS) && |
| current_frame_type_ == HEADERS) { |
| expect_continuation_ = current_frame_stream_id_; |
| end_stream_when_done_ = current_frame_flags_ & CONTROL_FLAG_FIN; |
| } |
| if (protocol_version_ == HTTP2 && |
| current_frame_flags_ & HEADERS_FLAG_PADDED) { |
| uint8_t pad_payload_len = 0; |
| DCHECK_EQ(remaining_padding_payload_length_, 0u); |
| successful_read = reader.ReadUInt8(&pad_payload_len); |
| DCHECK(successful_read); |
| remaining_padding_payload_length_ = pad_payload_len; |
| } |
| const bool has_priority = |
| (current_frame_flags_ & HEADERS_FLAG_PRIORITY) != 0; |
| int weight = 0; |
| uint32_t parent_stream_id = 0; |
| bool exclusive = false; |
| if (protocol_version_ == HTTP2 && has_priority) { |
| uint32_t stream_dependency; |
| successful_read = reader.ReadUInt32(&stream_dependency); |
| DCHECK(successful_read); |
| UnpackStreamDependencyValues(stream_dependency, &exclusive, |
| &parent_stream_id); |
| |
| uint8_t serialized_weight = 0; |
| successful_read = reader.ReadUInt8(&serialized_weight); |
| if (successful_read) { |
| // Per RFC 7540 section 6.3, serialized weight value is actual |
| // value - 1. |
| weight = serialized_weight + 1; |
| } |
| } |
| DCHECK(reader.IsDoneReading()); |
| if (debug_visitor_) { |
| debug_visitor_->OnReceiveCompressedFrame( |
| current_frame_stream_id_, |
| current_frame_type_, |
| current_frame_length_); |
| } |
| if (current_frame_type_ == SYN_REPLY) { |
| visitor_->OnSynReply( |
| current_frame_stream_id_, |
| (current_frame_flags_ & CONTROL_FLAG_FIN) != 0); |
| } else { |
| visitor_->OnHeaders( |
| current_frame_stream_id_, |
| (current_frame_flags_ & HEADERS_FLAG_PRIORITY) != 0, weight, |
| parent_stream_id, exclusive, |
| (current_frame_flags_ & CONTROL_FLAG_FIN) != 0, |
| expect_continuation_ == 0); |
| } |
| } |
| break; |
| case PUSH_PROMISE: |
| { |
| DCHECK_EQ(HTTP2, protocol_version_); |
| if (current_frame_stream_id_ == 0) { |
| set_error(SPDY_INVALID_CONTROL_FRAME); |
| return original_len - len; |
| } |
| bool successful_read = true; |
| if (protocol_version_ == HTTP2 && |
| current_frame_flags_ & PUSH_PROMISE_FLAG_PADDED) { |
| DCHECK_EQ(remaining_padding_payload_length_, 0u); |
| uint8_t pad_payload_len = 0; |
| successful_read = reader.ReadUInt8(&pad_payload_len); |
| DCHECK(successful_read); |
| remaining_padding_payload_length_ = pad_payload_len; |
| } |
| } |
| { |
| SpdyStreamId promised_stream_id = kInvalidStream; |
| bool successful_read = reader.ReadUInt31(&promised_stream_id); |
| DCHECK(successful_read); |
| DCHECK(reader.IsDoneReading()); |
| if (promised_stream_id == 0) { |
| set_error(SPDY_INVALID_CONTROL_FRAME); |
| return original_len - len; |
| } |
| if (!(current_frame_flags_ & PUSH_PROMISE_FLAG_END_PUSH_PROMISE)) { |
| expect_continuation_ = current_frame_stream_id_; |
| } |
| if (debug_visitor_) { |
| debug_visitor_->OnReceiveCompressedFrame( |
| current_frame_stream_id_, |
| current_frame_type_, |
| current_frame_length_); |
| } |
| visitor_->OnPushPromise(current_frame_stream_id_, |
| promised_stream_id, |
| (current_frame_flags_ & |
| PUSH_PROMISE_FLAG_END_PUSH_PROMISE) != 0); |
| } |
| break; |
| case CONTINUATION: |
| { |
| // Check to make sure the stream id of the current frame is |
| // the same as that of the preceding frame. |
| // If we're at this point we should already know that |
| // expect_continuation_ != 0, so this doubles as a check |
| // that current_frame_stream_id != 0. |
| if (current_frame_stream_id_ != expect_continuation_) { |
| set_error(SPDY_UNEXPECTED_FRAME); |
| return original_len - len; |
| } |
| if (current_frame_flags_ & HEADERS_FLAG_END_HEADERS) { |
| expect_continuation_ = 0; |
| } |
| if (debug_visitor_) { |
| debug_visitor_->OnReceiveCompressedFrame( |
| current_frame_stream_id_, |
| current_frame_type_, |
| current_frame_length_); |
| } |
| visitor_->OnContinuation(current_frame_stream_id_, |
| (current_frame_flags_ & |
| HEADERS_FLAG_END_HEADERS) != 0); |
| } |
| break; |
| default: |
| #ifndef NDEBUG |
| LOG(FATAL) << "Invalid control frame type: " << current_frame_type_; |
| #else |
| set_error(SPDY_INVALID_CONTROL_FRAME); |
| return original_len - len; |
| #endif |
| } |
| |
| if (use_new_methods_ && current_frame_type_ != CONTINUATION) { |
| header_handler_ = visitor_->OnHeaderFrameStart(current_frame_stream_id_); |
| if (header_handler_ == nullptr) { |
| SPDY_BUG << "visitor_->OnHeaderFrameStart returned nullptr"; |
| set_error(SPDY_INTERNAL_FRAMER_ERROR); |
| return original_len - len; |
| } |
| if (protocol_version() == SPDY3) { |
| header_parser_.reset( |
| new SpdyHeadersBlockParser(protocol_version(), header_handler_)); |
| } else { |
| GetHpackDecoder()->HandleControlFrameHeadersStart(header_handler_); |
| } |
| } |
| CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK); |
| } |
| return original_len - len; |
| } |
| |
| // Does not buffer the control payload. Instead, either passes directly to the |
| // visitor or decompresses and then passes directly to the visitor, via |
| // IncrementallyDeliverControlFrameHeaderData() or |
| // IncrementallyDecompressControlFrameHeaderData() respectively. |
| size_t SpdyFramer::ProcessControlFrameHeaderBlock(const char* data, |
| size_t data_len, |
| bool is_hpack_header_block) { |
| DCHECK_EQ(SPDY_CONTROL_FRAME_HEADER_BLOCK, state_); |
| |
| bool processed_successfully = true; |
| if (current_frame_type_ != SYN_STREAM && |
| current_frame_type_ != SYN_REPLY && |
| current_frame_type_ != HEADERS && |
| current_frame_type_ != PUSH_PROMISE && |
| current_frame_type_ != CONTINUATION) { |
| SPDY_BUG << "Unhandled frame type in ProcessControlFrameHeaderBlock."; |
| } |
| |
| if (remaining_padding_payload_length_ > remaining_data_length_) { |
| set_error(SPDY_INVALID_PADDING); |
| return data_len; |
| } |
| |
| size_t process_bytes = std::min( |
| data_len, remaining_data_length_ - remaining_padding_payload_length_); |
| if (is_hpack_header_block) { |
| if (!GetHpackDecoder()->HandleControlFrameHeadersData(data, |
| process_bytes)) { |
| // TODO(jgraettinger): Finer-grained HPACK error codes. |
| set_error(SPDY_DECOMPRESS_FAILURE); |
| processed_successfully = false; |
| } |
| } else if (process_bytes > 0) { |
| if (protocol_version_ == SPDY3 && enable_compression_) { |
| processed_successfully = IncrementallyDecompressControlFrameHeaderData( |
| current_frame_stream_id_, data, process_bytes); |
| } else { |
| processed_successfully = IncrementallyDeliverControlFrameHeaderData( |
| current_frame_stream_id_, data, process_bytes); |
| } |
| } |
| remaining_data_length_ -= process_bytes; |
| |
| // Handle the case that there is no futher data in this frame. |
| if (remaining_data_length_ == remaining_padding_payload_length_ && |
| processed_successfully) { |
| if (expect_continuation_ == 0) { |
| if (is_hpack_header_block) { |
| size_t compressed_len = 0; |
| if (GetHpackDecoder()->HandleControlFrameHeadersComplete( |
| &compressed_len)) { |
| if (use_new_methods_) { |
| visitor_->OnHeaderFrameEnd(current_frame_stream_id_, true); |
| if (state_ == SPDY_ERROR) { |
| return data_len; |
| } |
| } else { |
| // TODO(jgraettinger): To be removed with migration to |
| // SpdyHeadersHandlerInterface. Serializes the HPACK block as a |
| // SPDY3 block, delivered via reentrant call to |
| // ProcessControlFrameHeaderBlock(). |
| DeliverHpackBlockAsSpdy3Block(compressed_len); |
| return process_bytes; |
| } |
| } else { |
| set_error(SPDY_DECOMPRESS_FAILURE); |
| processed_successfully = false; |
| } |
| } else { |
| if (use_new_methods_) { |
| visitor_->OnHeaderFrameEnd(current_frame_stream_id_, true); |
| if (state_ == SPDY_ERROR) { |
| return data_len; |
| } |
| } else { |
| // The complete header block has been delivered. We send a zero-length |
| // OnControlFrameHeaderData() to indicated this. |
| visitor_->OnControlFrameHeaderData(current_frame_stream_id_, nullptr, |
| 0); |
| } |
| } |
| } |
| if (processed_successfully) { |
| CHANGE_STATE(SPDY_CONSUME_PADDING); |
| } |
| } |
| |
| // Handle error. |
| if (!processed_successfully) { |
| return data_len; |
| } |
| |
| // Return amount processed. |
| return process_bytes; |
| } |
| |
| size_t SpdyFramer::ProcessSettingsFrameHeader(const char* data, size_t len) { |
| // TODO(birenroy): Remove this state when removing SPDY3. I think it only |
| // exists to read the number of settings in the frame for SPDY3. This value |
| // is never parsed or used. |
| size_t bytes_read = 0; |
| if (remaining_control_header_ > 0) { |
| bytes_read = |
| UpdateCurrentFrameBuffer(&data, &len, remaining_control_header_); |
| remaining_control_header_ -= bytes_read; |
| remaining_data_length_ -= bytes_read; |
| } |
| if (remaining_control_header_ == 0) { |
| if (protocol_version_ == HTTP2 && |
| current_frame_flags_ & SETTINGS_FLAG_ACK) { |
| visitor_->OnSettingsAck(); |
| CHANGE_STATE(SPDY_FRAME_COMPLETE); |
| } else { |
| visitor_->OnSettings(current_frame_flags_ & |
| SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS); |
| CHANGE_STATE(SPDY_SETTINGS_FRAME_PAYLOAD); |
| } |
| } |
| return bytes_read; |
| } |
| |
| size_t SpdyFramer::ProcessSettingsFramePayload(const char* data, |
| size_t data_len) { |
| DCHECK_EQ(SPDY_SETTINGS_FRAME_PAYLOAD, state_); |
| DCHECK_EQ(SETTINGS, current_frame_type_); |
| size_t unprocessed_bytes = std::min(data_len, remaining_data_length_); |
| size_t processed_bytes = 0; |
| |
| size_t setting_size = SpdyConstants::GetSettingSize(protocol_version_); |
| |
| // Loop over our incoming data. |
| while (unprocessed_bytes > 0) { |
| // Process up to one setting at a time. |
| size_t processing = std::min(unprocessed_bytes, |
| setting_size - settings_scratch_.buffer.len()); |
| |
| // Check if we have a complete setting in our input. |
| if (processing == setting_size) { |
| // Parse the setting directly out of the input without buffering. |
| if (!ProcessSetting(data + processed_bytes)) { |
| set_error(SPDY_INVALID_CONTROL_FRAME); |
| return processed_bytes; |
| } |
| } else { |
| // Continue updating settings_scratch_.setting_buf. |
| settings_scratch_.buffer.CopyFrom(data + processed_bytes, processing); |
| |
| // Check if we have a complete setting buffered. |
| if (settings_scratch_.buffer.len() == setting_size) { |
| if (!ProcessSetting(settings_scratch_.buffer.data())) { |
| set_error(SPDY_INVALID_CONTROL_FRAME); |
| return processed_bytes; |
| } |
| // Rewind settings buffer for our next setting. |
| settings_scratch_.buffer.Rewind(); |
| } |
| } |
| |
| // Iterate. |
| unprocessed_bytes -= processing; |
| processed_bytes += processing; |
| } |
| |
| // Check if we're done handling this SETTINGS frame. |
| remaining_data_length_ -= processed_bytes; |
| if (remaining_data_length_ == 0) { |
| visitor_->OnSettingsEnd(); |
| CHANGE_STATE(SPDY_FRAME_COMPLETE); |
| } |
| |
| return processed_bytes; |
| } |
| |
| void SpdyFramer::DeliverHpackBlockAsSpdy3Block(size_t compressed_len) { |
| DCHECK_EQ(HTTP2, protocol_version_); |
| DCHECK_EQ(remaining_padding_payload_length_, remaining_data_length_); |
| |
| const SpdyHeaderBlock& block = GetHpackDecoder()->decoded_block(); |
| if (block.empty()) { |
| // Special-case this to make tests happy. |
| ProcessControlFrameHeaderBlock(NULL, 0, false); |
| return; |
| } |
| size_t payload_len = GetSerializedLength(protocol_version_, &block); |
| SpdyFrameBuilder builder(payload_len, SPDY3); |
| |
| SerializeHeaderBlockWithoutCompression(&builder, block); |
| SpdySerializedFrame frame = builder.take(); |
| |
| // Preserve padding length, and reset it after the re-entrant call. |
| size_t remaining_padding = remaining_padding_payload_length_; |
| |
| remaining_padding_payload_length_ = 0; |
| remaining_data_length_ = frame.size(); |
| |
| if (payload_len != 0) { |
| int compression_pct = 100 - (100 * compressed_len) / payload_len; |
| DVLOG(1) << "Net.SpdyHpackDecompressionPercentage: " << compression_pct; |
| UMA_HISTOGRAM_PERCENTAGE("Net.SpdyHpackDecompressionPercentage", |
| compression_pct); |
| } |
| |
| ProcessControlFrameHeaderBlock(frame.data(), frame.size(), false); |
| |
| remaining_padding_payload_length_ = remaining_padding; |
| remaining_data_length_ = remaining_padding; |
| } |
| |
| bool SpdyFramer::ProcessSetting(const char* data) { |
| int id_field; |
| SpdySettingsIds id; |
| uint8_t flags = 0; |
| uint32_t value; |
| |
| // Extract fields. |
| // Maintain behavior of old SPDY 2 bug with byte ordering of flags/id. |
| if (protocol_version_ == SPDY3) { |
| const uint32_t id_and_flags_wire = |
| *(reinterpret_cast<const uint32_t*>(data)); |
| SettingsFlagsAndId id_and_flags = SettingsFlagsAndId::FromWireFormat( |
| protocol_version_, id_and_flags_wire); |
| id_field = id_and_flags.id(); |
| flags = id_and_flags.flags(); |
| value = base::NetToHost32(*(reinterpret_cast<const uint32_t*>(data + 4))); |
| } else { |
| id_field = base::NetToHost16(*(reinterpret_cast<const uint16_t*>(data))); |
| value = base::NetToHost32(*(reinterpret_cast<const uint32_t*>(data + 2))); |
| } |
| |
| // Validate id. |
| if (!SpdyConstants::IsValidSettingId(protocol_version_, id_field)) { |
| DLOG(WARNING) << "Unknown SETTINGS ID: " << id_field; |
| if (protocol_version_ == SPDY3) { |
| return false; |
| } else { |
| // In HTTP2 we ignore unknown settings for extensibility. |
| return true; |
| } |
| } |
| id = SpdyConstants::ParseSettingId(protocol_version_, id_field); |
| |
| if (protocol_version_ == SPDY3) { |
| // Detect duplicates. |
| if (id <= settings_scratch_.last_setting_id) { |
| DLOG(WARNING) << "Duplicate entry or invalid ordering for id " << id |
| << " in " << display_protocol_ << " SETTINGS frame " |
| << "(last setting id was " |
| << settings_scratch_.last_setting_id << ")."; |
| return false; |
| } |
| settings_scratch_.last_setting_id = id; |
| |
| // Validate flags. |
| uint8_t kFlagsMask = SETTINGS_FLAG_PLEASE_PERSIST | SETTINGS_FLAG_PERSISTED; |
| if ((flags & ~(kFlagsMask)) != 0) { |
| DLOG(WARNING) << "Unknown SETTINGS flags provided for id " << id << ": " |
| << flags; |
| return false; |
| } |
| } |
| |
| // Validation succeeded. Pass on to visitor. |
| visitor_->OnSetting(id, flags, value); |
| return true; |
| } |
| |
| size_t SpdyFramer::ProcessControlFramePayload(const char* data, size_t len) { |
| size_t original_len = len; |
| size_t bytes_read = UpdateCurrentFrameBuffer(&data, &len, |
| remaining_data_length_); |
| remaining_data_length_ -= bytes_read; |
| if (remaining_data_length_ == 0) { |
| SpdyFrameReader reader(current_frame_buffer_.data(), |
| current_frame_buffer_.len()); |
| reader.Seek(GetFrameHeaderSize()); // Skip frame header. |
| |
| // Use frame-specific handlers. |
| switch (current_frame_type_) { |
| case PING: { |
| SpdyPingId id = 0; |
| bool is_ack = protocol_version_ == HTTP2 && |
| (current_frame_flags_ & PING_FLAG_ACK); |
| bool successful_read = true; |
| if (protocol_version_ == SPDY3) { |
| uint32_t id32 = 0; |
| successful_read = reader.ReadUInt32(&id32); |
| id = id32; |
| } else { |
| successful_read = reader.ReadUInt64(&id); |
| } |
| DCHECK(successful_read); |
| DCHECK(reader.IsDoneReading()); |
| visitor_->OnPing(id, is_ack); |
| } |
| break; |
| case WINDOW_UPDATE: { |
| uint32_t delta_window_size = 0; |
| bool successful_read = true; |
| if (protocol_version_ == SPDY3) { |
| successful_read = reader.ReadUInt31(¤t_frame_stream_id_); |
| DCHECK(successful_read); |
| } |
| successful_read = reader.ReadUInt32(&delta_window_size); |
| DCHECK(successful_read); |
| DCHECK(reader.IsDoneReading()); |
| visitor_->OnWindowUpdate(current_frame_stream_id_, |
| delta_window_size); |
| } |
| break; |
| case BLOCKED: { |
| DCHECK_EQ(HTTP2, protocol_version_); |
| DCHECK(reader.IsDoneReading()); |
| visitor_->OnBlocked(current_frame_stream_id_); |
| } |
| break; |
| case PRIORITY: { |
| DCHECK_EQ(HTTP2, protocol_version_); |
| uint32_t stream_dependency; |
| uint32_t parent_stream_id; |
| bool exclusive; |
| uint8_t serialized_weight; |
| bool successful_read = reader.ReadUInt32(&stream_dependency); |
| DCHECK(successful_read); |
| UnpackStreamDependencyValues(stream_dependency, &exclusive, |
| &parent_stream_id); |
| |
| successful_read = reader.ReadUInt8(&serialized_weight); |
| DCHECK(successful_read); |
| DCHECK(reader.IsDoneReading()); |
| // Per RFC 7540 section 6.3, serialized weight value is |
| // actual value - 1. |
| int weight = serialized_weight + 1; |
| visitor_->OnPriority( |
| current_frame_stream_id_, parent_stream_id, weight, exclusive); |
| } |
| break; |
| default: |
| // Unreachable. |
| LOG(FATAL) << "Unhandled control frame " << current_frame_type_; |
| } |
| |
| CHANGE_STATE(SPDY_IGNORE_REMAINING_PAYLOAD); |
| } |
| return original_len - len; |
| } |
| |
| size_t SpdyFramer::ProcessGoAwayFramePayload(const char* data, size_t len) { |
| if (len == 0) { |
| return 0; |
| } |
| // Clamp to the actual remaining payload. |
| if (len > remaining_data_length_) { |
| len = remaining_data_length_; |
| } |
| size_t original_len = len; |
| |
| // Check if we had already read enough bytes to parse the GOAWAY header. |
| const size_t header_size = GetGoAwayMinimumSize(); |
| size_t unread_header_bytes = header_size - current_frame_buffer_.len(); |
| bool already_parsed_header = (unread_header_bytes == 0); |
| if (!already_parsed_header) { |
| // Buffer the new GOAWAY header bytes we got. |
| UpdateCurrentFrameBuffer(&data, &len, unread_header_bytes); |
| |
| // Do we have enough to parse the constant size GOAWAY header? |
| if (current_frame_buffer_.len() == header_size) { |
| // Parse out the last good stream id. |
| SpdyFrameReader reader(current_frame_buffer_.data(), |
| current_frame_buffer_.len()); |
| reader.Seek(GetFrameHeaderSize()); // Seek past frame header. |
| bool successful_read = reader.ReadUInt31(¤t_frame_stream_id_); |
| DCHECK(successful_read); |
| |
| // Parse status code. |
| SpdyGoAwayStatus status = GOAWAY_OK; |
| uint32_t status_raw = GOAWAY_OK; |
| successful_read = reader.ReadUInt32(&status_raw); |
| DCHECK(successful_read); |
| if (SpdyConstants::IsValidGoAwayStatus(protocol_version_, status_raw)) { |
| status = |
| SpdyConstants::ParseGoAwayStatus(protocol_version_, status_raw); |
| } else { |
| if (protocol_version_ == HTTP2) { |
| // Treat unrecognized status codes as INTERNAL_ERROR as |
| // recommended by the HTTP/2 spec. |
| status = GOAWAY_INTERNAL_ERROR; |
| } |
| } |
| // Finished parsing the GOAWAY header, call frame handler. |
| visitor_->OnGoAway(current_frame_stream_id_, status); |
| } |
| } |
| |
| // Handle remaining data as opaque. |
| bool processed_successfully = true; |
| if (len > 0) { |
| processed_successfully = visitor_->OnGoAwayFrameData(data, len); |
| } |
| remaining_data_length_ -= original_len; |
| if (!processed_successfully) { |
| set_error(SPDY_GOAWAY_FRAME_CORRUPT); |
| } else if (remaining_data_length_ == 0) { |
| // Signal that there is not more opaque data. |
| visitor_->OnGoAwayFrameData(NULL, 0); |
| CHANGE_STATE(SPDY_FRAME_COMPLETE); |
| } |
| return original_len; |
| } |
| |
| size_t SpdyFramer::ProcessRstStreamFramePayload(const char* data, size_t len) { |
| if (len == 0) { |
| return 0; |
| } |
| // Clamp to the actual remaining payload. |
| if (len > remaining_data_length_) { |
| len = remaining_data_length_; |
| } |
| size_t original_len = len; |
| |
| // Check if we had already read enough bytes to parse the fixed-length portion |
| // of the RST_STREAM frame. |
| const size_t header_size = GetRstStreamMinimumSize(); |
| size_t unread_header_bytes = header_size - current_frame_buffer_.len(); |
| bool already_parsed_header = (unread_header_bytes == 0); |
| if (!already_parsed_header) { |
| // Buffer the new RST_STREAM header bytes we got. |
| UpdateCurrentFrameBuffer(&data, &len, unread_header_bytes); |
| |
| // Do we have enough to parse the constant size RST_STREAM header? |
| if (current_frame_buffer_.len() == header_size) { |
| // Parse out the last good stream id. |
| SpdyFrameReader reader(current_frame_buffer_.data(), |
| current_frame_buffer_.len()); |
| reader.Seek(GetFrameHeaderSize()); // Seek past frame header. |
| if (protocol_version_ == SPDY3) { |
| bool successful_read = reader.ReadUInt31(¤t_frame_stream_id_); |
| DCHECK(successful_read); |
| } |
| |
| SpdyRstStreamStatus status = RST_STREAM_INVALID; |
| uint32_t status_raw = status; |
| bool successful_read = reader.ReadUInt32(&status_raw); |
| DCHECK(successful_read); |
| if (SpdyConstants::IsValidRstStreamStatus(protocol_version_, |
| status_raw)) { |
| status = |
| SpdyConstants::ParseRstStreamStatus(protocol_version_, status_raw); |
| } else { |
| if (protocol_version_ == HTTP2) { |
| // Treat unrecognized status codes as INTERNAL_ERROR as |
| // recommended by the HTTP/2 spec. |
| status = RST_STREAM_INTERNAL_ERROR; |
| } |
| } |
| // Finished parsing the RST_STREAM header, call frame handler. |
| visitor_->OnRstStream(current_frame_stream_id_, status); |
| } |
| } |
| |
| // Handle remaining data as opaque. |
| // TODO(jamessynge): Remove support for variable length/opaque trailer. |
| bool processed_successfully = true; |
| if (len > 0) { |
| processed_successfully = visitor_->OnRstStreamFrameData(data, len); |
| } |
| remaining_data_length_ -= original_len; |
| if (!processed_successfully) { |
| set_error(SPDY_RST_STREAM_FRAME_CORRUPT); |
| } else if (remaining_data_length_ == 0) { |
| // Signal that there is not more opaque data. |
| visitor_->OnRstStreamFrameData(NULL, 0); |
| CHANGE_STATE(SPDY_FRAME_COMPLETE); |
| } |
| return original_len; |
| } |
| |
| size_t SpdyFramer::ProcessAltSvcFramePayload(const char* data, size_t len) { |
| if (len == 0) { |
| return 0; |
| } |
| |
| // Clamp to the actual remaining payload. |
| len = std::min(len, remaining_data_length_); |
| |
| if (altsvc_scratch_ == nullptr) { |
| size_t capacity = current_frame_length_ - GetFrameHeaderSize(); |
| altsvc_scratch_.reset(new CharBuffer(capacity)); |
| } |
| altsvc_scratch_->CopyFrom(data, len); |
| remaining_data_length_ -= len; |
| if (remaining_data_length_ > 0) { |
| return len; |
| } |
| |
| SpdyFrameReader reader(altsvc_scratch_->data(), altsvc_scratch_->len()); |
| StringPiece origin; |
| bool successful_read = reader.ReadStringPiece16(&origin); |
| if (!successful_read) { |
| set_error(SPDY_INVALID_CONTROL_FRAME); |
| return 0; |
| } |
| StringPiece value(altsvc_scratch_->data() + reader.GetBytesConsumed(), |
| altsvc_scratch_->len() - reader.GetBytesConsumed()); |
| |
| SpdyAltSvcWireFormat::AlternativeServiceVector altsvc_vector; |
| bool success = |
| SpdyAltSvcWireFormat::ParseHeaderFieldValue(value, &altsvc_vector); |
| if (!success) { |
| set_error(SPDY_INVALID_CONTROL_FRAME); |
| return 0; |
| } |
| |
| visitor_->OnAltSvc(current_frame_stream_id_, origin, altsvc_vector); |
| CHANGE_STATE(SPDY_FRAME_COMPLETE); |
| return len; |
| } |
| |
| size_t SpdyFramer::ProcessDataFramePaddingLength(const char* data, size_t len) { |
| DCHECK_EQ(SPDY_READ_DATA_FRAME_PADDING_LENGTH, state_); |
| DCHECK_EQ(0u, remaining_padding_payload_length_); |
| DCHECK_EQ(DATA, current_frame_type_); |
| |
| size_t original_len = len; |
| if (current_frame_flags_ & DATA_FLAG_PADDED) { |
| if (len != 0) { |
| if (remaining_data_length_ < kPadLengthFieldSize) { |
| set_error(SPDY_INVALID_DATA_FRAME_FLAGS); |
| return 0; |
| } |
| |
| static_assert(kPadLengthFieldSize == 1, |
| "Unexpected pad length field size."); |
| remaining_padding_payload_length_ = |
| *reinterpret_cast<const uint8_t*>(data); |
| ++data; |
| --len; |
| --remaining_data_length_; |
| visitor_->OnStreamPadding(current_frame_stream_id_, kPadLengthFieldSize); |
| } else { |
| // We don't have the data available for parsing the pad length field. Keep |
| // waiting. |
| return 0; |
| } |
| } |
| |
| if (remaining_padding_payload_length_ > remaining_data_length_) { |
| set_error(SPDY_INVALID_PADDING); |
| return 0; |
| } |
| CHANGE_STATE(SPDY_FORWARD_STREAM_FRAME); |
| return original_len - len; |
| } |
| |
| size_t SpdyFramer::ProcessFramePadding(const char* data, size_t len) { |
| DCHECK_EQ(SPDY_CONSUME_PADDING, state_); |
| |
| size_t original_len = len; |
| if (remaining_padding_payload_length_ > 0) { |
| DCHECK_EQ(remaining_padding_payload_length_, remaining_data_length_); |
| size_t amount_to_discard = std::min(remaining_padding_payload_length_, len); |
| if (current_frame_type_ == DATA && amount_to_discard > 0) { |
| SPDY_BUG_IF(protocol_version_ == SPDY3) |
| << "Padding invalid for SPDY version " << protocol_version_; |
| visitor_->OnStreamPadding(current_frame_stream_id_, amount_to_discard); |
| } |
| data += amount_to_discard; |
| len -= amount_to_discard; |
| remaining_padding_payload_length_ -= amount_to_discard; |
| remaining_data_length_ -= amount_to_discard; |
| } |
| |
| if (remaining_data_length_ == 0) { |
| // If the FIN flag is set, or this ends a header block which set FIN, |
| // inform the visitor of EOF via a 0-length data frame. |
| if (expect_continuation_ == 0 && |
| ((current_frame_flags_ & CONTROL_FLAG_FIN) != 0 || |
| end_stream_when_done_)) { |
| end_stream_when_done_ = false; |
| visitor_->OnStreamEnd(current_frame_stream_id_); |
| } |
| CHANGE_STATE(SPDY_FRAME_COMPLETE); |
| } |
| return original_len - len; |
| } |
| |
| size_t SpdyFramer::ProcessDataFramePayload(const char* data, size_t len) { |
| size_t original_len = len; |
| if (remaining_data_length_ - remaining_padding_payload_length_ > 0) { |
| size_t amount_to_forward = std::min( |
| remaining_data_length_ - remaining_padding_payload_length_, len); |
| if (amount_to_forward && state_ != SPDY_IGNORE_REMAINING_PAYLOAD) { |
| // Only inform the visitor if there is data. |
| if (amount_to_forward) { |
| visitor_->OnStreamFrameData(current_frame_stream_id_, data, |
| amount_to_forward); |
| } |
| } |
| data += amount_to_forward; |
| len -= amount_to_forward; |
| remaining_data_length_ -= amount_to_forward; |
| } |
| |
| if (remaining_data_length_ == remaining_padding_payload_length_) { |
| CHANGE_STATE(SPDY_CONSUME_PADDING); |
| } |
| return original_len - len; |
| } |
| |
| size_t SpdyFramer::ProcessIgnoredControlFramePayload(/*const char* data,*/ |
| size_t len) { |
| size_t original_len = len; |
| if (remaining_data_length_ > 0) { |
| size_t amount_to_ignore = std::min(remaining_data_length_, len); |
| len -= amount_to_ignore; |
| remaining_data_length_ -= amount_to_ignore; |
| } |
| |
| if (remaining_data_length_ == 0) { |
| CHANGE_STATE(SPDY_FRAME_COMPLETE); |
| } |
| return original_len - len; |
| } |
| |
| bool SpdyFramer::ParseHeaderBlockInBuffer(const char* header_data, |
| size_t header_length, |
| SpdyHeaderBlock* block) const { |
| SpdyFrameReader reader(header_data, header_length); |
| |
| // Read number of headers. |
| uint32_t num_headers; |
| if (!reader.ReadUInt32(&num_headers)) { |
| DVLOG(1) << "Unable to read number of headers."; |
| return false; |
| } |
| |
| // Read each header. |
| for (uint32_t index = 0; index < num_headers; ++index) { |
| base::StringPiece temp; |
| |
| // Read header name. |
| if (!reader.ReadStringPiece32(&temp)) { |
| DVLOG(1) << "Unable to read header name (" << index + 1 << " of " |
| << num_headers << ")."; |
| return false; |
| } |
| std::string name = temp.as_string(); |
| |
| // Read header value. |
| if (!reader.ReadStringPiece32(&temp)) { |
| DVLOG(1) << "Unable to read header value (" << index + 1 << " of " |
| << num_headers << ")."; |
| return false; |
| } |
| std::string value = temp.as_string(); |
| |
| // Ensure no duplicates. |
| if (block->find(name) != block->end()) { |
| DVLOG(1) << "Duplicate header '" << name << "' (" << index + 1 << " of " |
| << num_headers << ")."; |
| return false; |
| } |
| |
| // Store header. |
| (*block)[name] = value; |
| } |
| if (reader.GetBytesConsumed() != header_length) { |
| SPDY_BUG << "Buffer expected to consist entirely of headers, but only " |
| << reader.GetBytesConsumed() << " bytes consumed, from " |
| << header_length; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeData(const SpdyDataIR& data_ir) const { |
| uint8_t flags = DATA_FLAG_NONE; |
| if (data_ir.fin()) { |
| flags = DATA_FLAG_FIN; |
| } |
| |
| if (protocol_version_ == SPDY3) { |
| const size_t size = GetDataFrameMinimumSize() + data_ir.data().length(); |
| SpdyFrameBuilder builder(size, protocol_version_); |
| builder.WriteDataFrameHeader(*this, data_ir.stream_id(), flags); |
| builder.WriteBytes(data_ir.data().data(), data_ir.data().length()); |
| DCHECK_EQ(size, builder.length()); |
| return builder.take(); |
| } else { |
| int num_padding_fields = 0; |
| if (data_ir.padded()) { |
| flags |= DATA_FLAG_PADDED; |
| ++num_padding_fields; |
| } |
| |
| const size_t size_with_padding = num_padding_fields + |
| data_ir.data().length() + data_ir.padding_payload_len() + |
| GetDataFrameMinimumSize(); |
| SpdyFrameBuilder builder(size_with_padding, protocol_version_); |
| builder.WriteDataFrameHeader(*this, data_ir.stream_id(), flags); |
| if (data_ir.padded()) { |
| builder.WriteUInt8(data_ir.padding_payload_len() & 0xff); |
| } |
| builder.WriteBytes(data_ir.data().data(), data_ir.data().length()); |
| if (data_ir.padding_payload_len() > 0) { |
| string padding(data_ir.padding_payload_len(), 0); |
| builder.WriteBytes(padding.data(), padding.length()); |
| } |
| DCHECK_EQ(size_with_padding, builder.length()); |
| return builder.take(); |
| } |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeDataFrameHeaderWithPaddingLengthField( |
| const SpdyDataIR& data_ir) const { |
| uint8_t flags = DATA_FLAG_NONE; |
| if (data_ir.fin()) { |
| flags = DATA_FLAG_FIN; |
| } |
| |
| size_t frame_size = GetDataFrameMinimumSize(); |
| size_t num_padding_fields = 0; |
| if (protocol_version_ == HTTP2) { |
| if (data_ir.padded()) { |
| flags |= DATA_FLAG_PADDED; |
| ++num_padding_fields; |
| } |
| frame_size += num_padding_fields; |
| } |
| |
| SpdyFrameBuilder builder(frame_size, protocol_version_); |
| builder.WriteDataFrameHeader(*this, data_ir.stream_id(), flags); |
| if (protocol_version_ == HTTP2) { |
| if (data_ir.padded()) { |
| builder.WriteUInt8(data_ir.padding_payload_len() & 0xff); |
| } |
| builder.OverwriteLength(*this, num_padding_fields + |
| data_ir.data().length() + data_ir.padding_payload_len()); |
| } else { |
| builder.OverwriteLength(*this, data_ir.data().length()); |
| } |
| DCHECK_EQ(frame_size, builder.length()); |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeSynStream( |
| const SpdySynStreamIR& syn_stream) { |
| DCHECK_EQ(SPDY3, protocol_version_); |
| uint8_t flags = 0; |
| if (syn_stream.fin()) { |
| flags |= CONTROL_FLAG_FIN; |
| } |
| if (syn_stream.unidirectional()) { |
| flags |= CONTROL_FLAG_UNIDIRECTIONAL; |
| } |
| |
| // Sanitize priority. |
| uint8_t priority = syn_stream.priority(); |
| if (priority > GetLowestPriority()) { |
| SPDY_BUG << "Priority out-of-bounds."; |
| priority = GetLowestPriority(); |
| } |
| |
| // The size of this frame, including variable-length header block. |
| size_t size = GetSynStreamMinimumSize() + |
| GetSerializedLength(syn_stream.header_block()); |
| |
| SpdyFrameBuilder builder(size, protocol_version_); |
| builder.WriteControlFrameHeader(*this, SYN_STREAM, flags); |
| builder.WriteUInt32(syn_stream.stream_id()); |
| builder.WriteUInt32(syn_stream.associated_to_stream_id()); |
| builder.WriteUInt8(priority << 5); |
| builder.WriteUInt8(0); // Unused byte. |
| DCHECK_EQ(GetSynStreamMinimumSize(), builder.length()); |
| SerializeHeaderBlock(&builder, syn_stream); |
| |
| if (debug_visitor_) { |
| const size_t payload_len = |
| GetSerializedLength(protocol_version_, &(syn_stream.header_block())); |
| debug_visitor_->OnSendCompressedFrame(syn_stream.stream_id(), |
| SYN_STREAM, |
| payload_len, |
| builder.length()); |
| } |
| |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeSynReply( |
| const SpdySynReplyIR& syn_reply) { |
| DCHECK_EQ(SPDY3, protocol_version_); |
| uint8_t flags = 0; |
| if (syn_reply.fin()) { |
| flags |= CONTROL_FLAG_FIN; |
| } |
| |
| // The size of this frame, including variable-length header block. |
| const size_t size = |
| GetSynReplyMinimumSize() + GetSerializedLength(syn_reply.header_block()); |
| |
| SpdyFrameBuilder builder(size, protocol_version_); |
| builder.WriteControlFrameHeader(*this, SYN_REPLY, flags); |
| builder.WriteUInt32(syn_reply.stream_id()); |
| DCHECK_EQ(GetSynReplyMinimumSize(), builder.length()); |
| SerializeHeaderBlock(&builder, syn_reply); |
| |
| if (debug_visitor_) { |
| const size_t payload_len = |
| GetSerializedLength(protocol_version_, &(syn_reply.header_block())); |
| debug_visitor_->OnSendCompressedFrame(syn_reply.stream_id(), |
| SYN_REPLY, |
| payload_len, |
| builder.length()); |
| } |
| |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeRstStream( |
| const SpdyRstStreamIR& rst_stream) const { |
| // TODO(jgraettinger): For now, Chromium will support parsing RST_STREAM |
| // payloads, but will not emit them. SPDY4 is used for draft HTTP/2, |
| // which doesn't currently include RST_STREAM payloads. GFE flags have been |
| // commented but left in place to simplify future patching. |
| // Compute the output buffer size, taking opaque data into account. |
| size_t expected_length = GetRstStreamMinimumSize(); |
| SpdyFrameBuilder builder(expected_length, protocol_version_); |
| |
| // Serialize the RST_STREAM frame. |
| if (protocol_version_ == SPDY3) { |
| builder.WriteControlFrameHeader(*this, RST_STREAM, 0); |
| builder.WriteUInt32(rst_stream.stream_id()); |
| } else { |
| builder.BeginNewFrame(*this, RST_STREAM, 0, rst_stream.stream_id()); |
| } |
| |
| builder.WriteUInt32(SpdyConstants::SerializeRstStreamStatus( |
| protocol_version_, rst_stream.status())); |
| |
| DCHECK_EQ(expected_length, builder.length()); |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeSettings( |
| const SpdySettingsIR& settings) const { |
| uint8_t flags = 0; |
| |
| if (protocol_version_ == SPDY3) { |
| if (settings.clear_settings()) { |
| flags |= SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS; |
| } |
| } else { |
| if (settings.is_ack()) { |
| flags |= SETTINGS_FLAG_ACK; |
| } |
| } |
| const SpdySettingsIR::ValueMap* values = &(settings.values()); |
| |
| size_t setting_size = SpdyConstants::GetSettingSize(protocol_version_); |
| // Size, in bytes, of this SETTINGS frame. |
| const size_t size = GetSettingsMinimumSize() + |
| (values->size() * setting_size); |
| SpdyFrameBuilder builder(size, protocol_version_); |
| if (protocol_version_ == SPDY3) { |
| builder.WriteControlFrameHeader(*this, SETTINGS, flags); |
| } else { |
| builder.BeginNewFrame(*this, SETTINGS, flags, 0); |
| } |
| |
| // If this is an ACK, payload should be empty. |
| if (protocol_version_ == HTTP2 && settings.is_ack()) { |
| return builder.take(); |
| } |
| |
| if (protocol_version_ == SPDY3) { |
| builder.WriteUInt32(values->size()); |
| } |
| DCHECK_EQ(GetSettingsMinimumSize(), builder.length()); |
| for (SpdySettingsIR::ValueMap::const_iterator it = values->begin(); |
| it != values->end(); |
| ++it) { |
| int setting_id = |
| SpdyConstants::SerializeSettingId(protocol_version_, it->first); |
| DCHECK_GE(setting_id, 0); |
| if (protocol_version_ == SPDY3) { |
| uint8_t setting_flags = 0; |
| if (it->second.persist_value) { |
| setting_flags |= SETTINGS_FLAG_PLEASE_PERSIST; |
| } |
| if (it->second.persisted) { |
| setting_flags |= SETTINGS_FLAG_PERSISTED; |
| } |
| SettingsFlagsAndId flags_and_id(setting_flags, setting_id); |
| uint32_t id_and_flags_wire = |
| flags_and_id.GetWireFormat(protocol_version_); |
| builder.WriteBytes(&id_and_flags_wire, 4); |
| } else { |
| builder.WriteUInt16(static_cast<uint16_t>(setting_id)); |
| } |
| builder.WriteUInt32(it->second.value); |
| } |
| DCHECK_EQ(size, builder.length()); |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializePing(const SpdyPingIR& ping) const { |
| SpdyFrameBuilder builder(GetPingSize(), protocol_version_); |
| if (protocol_version_ == SPDY3) { |
| builder.WriteControlFrameHeader(*this, PING, kNoFlags); |
| builder.WriteUInt32(static_cast<uint32_t>(ping.id())); |
| } else { |
| uint8_t flags = 0; |
| if (ping.is_ack()) { |
| flags |= PING_FLAG_ACK; |
| } |
| builder.BeginNewFrame(*this, PING, flags, 0); |
| builder.WriteUInt64(ping.id()); |
| } |
| DCHECK_EQ(GetPingSize(), builder.length()); |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeGoAway( |
| const SpdyGoAwayIR& goaway) const { |
| // Compute the output buffer size, take opaque data into account. |
| size_t expected_length = GetGoAwayMinimumSize(); |
| if (protocol_version_ == HTTP2) { |
| expected_length += goaway.description().size(); |
| } |
| SpdyFrameBuilder builder(expected_length, protocol_version_); |
| |
| // Serialize the GOAWAY frame. |
| if (protocol_version_ == SPDY3) { |
| builder.WriteControlFrameHeader(*this, GOAWAY, kNoFlags); |
| } else { |
| builder.BeginNewFrame(*this, GOAWAY, 0, 0); |
| } |
| |
| // GOAWAY frames specify the last good stream id for all SPDY versions. |
| builder.WriteUInt32(goaway.last_good_stream_id()); |
| |
| // GOAWAY frames also specify the error status code. |
| builder.WriteUInt32( |
| SpdyConstants::SerializeGoAwayStatus(protocol_version_, goaway.status())); |
| |
| // In HTTP2, GOAWAY frames may also specify opaque data. |
| if ((protocol_version_ == HTTP2) && (goaway.description().size() > 0)) { |
| builder.WriteBytes(goaway.description().data(), |
| goaway.description().size()); |
| } |
| |
| DCHECK_EQ(expected_length, builder.length()); |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeHeaders(const SpdyHeadersIR& headers) { |
| uint8_t flags = 0; |
| if (headers.fin()) { |
| flags |= CONTROL_FLAG_FIN; |
| } |
| if (protocol_version_ == HTTP2) { |
| // This will get overwritten if we overflow into a CONTINUATION frame. |
| flags |= HEADERS_FLAG_END_HEADERS; |
| if (headers.has_priority()) { |
| flags |= HEADERS_FLAG_PRIORITY; |
| } |
| if (headers.padded()) { |
| flags |= HEADERS_FLAG_PADDED; |
| } |
| } |
| |
| // The size of this frame, including padding (if there is any) and |
| // variable-length header block. |
| size_t size = GetHeadersMinimumSize(); |
| |
| if (protocol_version_ == HTTP2 && headers.padded()) { |
| size += kPadLengthFieldSize; |
| size += headers.padding_payload_len(); |
| } |
| |
| int weight = 0; |
| if (headers.has_priority()) { |
| weight = ClampHttp2Weight(headers.weight()); |
| size += 5; |
| } |
| |
| string hpack_encoding; |
| if (protocol_version_ == SPDY3) { |
| size += GetSerializedLength(headers.header_block()); |
| } else { |
| if (enable_compression_) { |
| GetHpackEncoder()->EncodeHeaderSet(headers.header_block(), |
| &hpack_encoding); |
| } else { |
| GetHpackEncoder()->EncodeHeaderSetWithoutCompression( |
| headers.header_block(), &hpack_encoding); |
| } |
| size += hpack_encoding.size(); |
| if (size > kMaxControlFrameSize) { |
| size += GetNumberRequiredContinuationFrames(size) * |
| GetContinuationMinimumSize(); |
| flags &= ~HEADERS_FLAG_END_HEADERS; |
| } |
| } |
| |
| SpdyFrameBuilder builder(size, protocol_version_); |
| if (protocol_version_ == SPDY3) { |
| builder.WriteControlFrameHeader(*this, HEADERS, flags); |
| builder.WriteUInt32(headers.stream_id()); |
| } else { |
| builder.BeginNewFrame(*this, |
| HEADERS, |
| flags, |
| headers.stream_id()); |
| } |
| DCHECK_EQ(GetHeadersMinimumSize(), builder.length()); |
| |
| if (protocol_version_ == SPDY3) { |
| SerializeHeaderBlock(&builder, headers); |
| } else { |
| int padding_payload_len = 0; |
| if (headers.padded()) { |
| builder.WriteUInt8(headers.padding_payload_len()); |
| padding_payload_len = headers.padding_payload_len(); |
| } |
| if (headers.has_priority()) { |
| builder.WriteUInt32(PackStreamDependencyValues( |
| headers.exclusive(), headers.parent_stream_id())); |
| // Per RFC 7540 section 6.3, serialized weight value is actual value - 1. |
| builder.WriteUInt8(weight - 1); |
| } |
| WritePayloadWithContinuation(&builder, |
| hpack_encoding, |
| headers.stream_id(), |
| HEADERS, |
| padding_payload_len); |
| } |
| |
| if (debug_visitor_) { |
| // HTTP2 uses HPACK for header compression. However, continue to |
| // use GetSerializedLength() for an apples-to-apples comparision of |
| // compression performance between HPACK and SPDY w/ deflate. |
| const size_t payload_len = |
| GetSerializedLength(protocol_version_, &(headers.header_block())); |
| debug_visitor_->OnSendCompressedFrame(headers.stream_id(), |
| HEADERS, |
| payload_len, |
| builder.length()); |
| } |
| |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeWindowUpdate( |
| const SpdyWindowUpdateIR& window_update) const { |
| SpdyFrameBuilder builder(GetWindowUpdateSize(), protocol_version_); |
| if (protocol_version_ == SPDY3) { |
| builder.WriteControlFrameHeader(*this, WINDOW_UPDATE, kNoFlags); |
| builder.WriteUInt32(window_update.stream_id()); |
| } else { |
| builder.BeginNewFrame(*this, |
| WINDOW_UPDATE, |
| kNoFlags, |
| window_update.stream_id()); |
| } |
| builder.WriteUInt32(window_update.delta()); |
| DCHECK_EQ(GetWindowUpdateSize(), builder.length()); |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeBlocked( |
| const SpdyBlockedIR& blocked) const { |
| DCHECK_EQ(HTTP2, protocol_version_); |
| SpdyFrameBuilder builder(GetBlockedSize(), protocol_version_); |
| builder.BeginNewFrame(*this, BLOCKED, kNoFlags, blocked.stream_id()); |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializePushPromise( |
| const SpdyPushPromiseIR& push_promise) { |
| DCHECK_EQ(HTTP2, protocol_version_); |
| uint8_t flags = 0; |
| // This will get overwritten if we overflow into a CONTINUATION frame. |
| flags |= PUSH_PROMISE_FLAG_END_PUSH_PROMISE; |
| // The size of this frame, including variable-length name-value block. |
| size_t size = GetPushPromiseMinimumSize(); |
| |
| if (push_promise.padded()) { |
| flags |= PUSH_PROMISE_FLAG_PADDED; |
| size += kPadLengthFieldSize; |
| size += push_promise.padding_payload_len(); |
| } |
| |
| string hpack_encoding; |
| if (enable_compression_) { |
| GetHpackEncoder()->EncodeHeaderSet(push_promise.header_block(), |
| &hpack_encoding); |
| } else { |
| GetHpackEncoder()->EncodeHeaderSetWithoutCompression( |
| push_promise.header_block(), &hpack_encoding); |
| } |
| size += hpack_encoding.size(); |
| if (size > kMaxControlFrameSize) { |
| size += GetNumberRequiredContinuationFrames(size) * |
| GetContinuationMinimumSize(); |
| flags &= ~PUSH_PROMISE_FLAG_END_PUSH_PROMISE; |
| } |
| |
| SpdyFrameBuilder builder(size, protocol_version_); |
| builder.BeginNewFrame(*this, |
| PUSH_PROMISE, |
| flags, |
| push_promise.stream_id()); |
| int padding_payload_len = 0; |
| if (push_promise.padded()) { |
| builder.WriteUInt8(push_promise.padding_payload_len()); |
| builder.WriteUInt32(push_promise.promised_stream_id()); |
| DCHECK_EQ(GetPushPromiseMinimumSize() + kPadLengthFieldSize, |
| builder.length()); |
| |
| padding_payload_len = push_promise.padding_payload_len(); |
| } else { |
| builder.WriteUInt32(push_promise.promised_stream_id()); |
| DCHECK_EQ(GetPushPromiseMinimumSize(), builder.length()); |
| } |
| |
| WritePayloadWithContinuation(&builder, |
| hpack_encoding, |
| push_promise.stream_id(), |
| PUSH_PROMISE, |
| padding_payload_len); |
| |
| if (debug_visitor_) { |
| // HTTP2 uses HPACK for header compression. However, continue to |
| // use GetSerializedLength() for an apples-to-apples comparision of |
| // compression performance between HPACK and SPDY w/ deflate. |
| const size_t payload_len = |
| GetSerializedLength(protocol_version_, &(push_promise.header_block())); |
| debug_visitor_->OnSendCompressedFrame(push_promise.stream_id(), |
| PUSH_PROMISE, |
| payload_len, |
| builder.length()); |
| } |
| |
| return builder.take(); |
| } |
| |
| // TODO(jgraettinger): This implementation is incorrect. The continuation |
| // frame continues a previously-begun HPACK encoding; it doesn't begin a |
| // new one. Figure out whether it makes sense to keep SerializeContinuation(). |
| SpdySerializedFrame SpdyFramer::SerializeContinuation( |
| const SpdyContinuationIR& continuation) { |
| CHECK_EQ(HTTP2, protocol_version_); |
| uint8_t flags = 0; |
| if (continuation.end_headers()) { |
| flags |= HEADERS_FLAG_END_HEADERS; |
| } |
| |
| // The size of this frame, including variable-length name-value block. |
| size_t size = GetContinuationMinimumSize(); |
| string hpack_encoding; |
| if (enable_compression_) { |
| GetHpackEncoder()->EncodeHeaderSet(continuation.header_block(), |
| &hpack_encoding); |
| } else { |
| GetHpackEncoder()->EncodeHeaderSetWithoutCompression( |
| continuation.header_block(), &hpack_encoding); |
| } |
| size += hpack_encoding.size(); |
| |
| SpdyFrameBuilder builder(size, protocol_version_); |
| builder.BeginNewFrame(*this, CONTINUATION, flags, |
| continuation.stream_id()); |
| DCHECK_EQ(GetContinuationMinimumSize(), builder.length()); |
| |
| builder.WriteBytes(&hpack_encoding[0], hpack_encoding.size()); |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializeAltSvc(const SpdyAltSvcIR& altsvc_ir) { |
| DCHECK_EQ(HTTP2, protocol_version_); |
| |
| size_t size = GetAltSvcMinimumSize(); |
| size += altsvc_ir.origin().length(); |
| string value = SpdyAltSvcWireFormat::SerializeHeaderFieldValue( |
| altsvc_ir.altsvc_vector()); |
| size += value.length(); |
| |
| SpdyFrameBuilder builder(size, protocol_version_); |
| builder.BeginNewFrame(*this, ALTSVC, kNoFlags, altsvc_ir.stream_id()); |
| |
| builder.WriteUInt16(altsvc_ir.origin().length()); |
| builder.WriteBytes(altsvc_ir.origin().data(), altsvc_ir.origin().length()); |
| builder.WriteBytes(value.data(), value.length()); |
| DCHECK_LT(GetAltSvcMinimumSize(), builder.length()); |
| return builder.take(); |
| } |
| |
| SpdySerializedFrame SpdyFramer::SerializePriority( |
| const SpdyPriorityIR& priority) const { |
| DCHECK_EQ(HTTP2, protocol_version_); |
| size_t size = GetPrioritySize(); |
| |
| SpdyFrameBuilder builder(size, protocol_version_); |
| builder.BeginNewFrame(*this, PRIORITY, kNoFlags, priority.stream_id()); |
| |
| builder.WriteUInt32(PackStreamDependencyValues(priority.exclusive(), |
| priority.parent_stream_id())); |
| // Per RFC 7540 section 6.3, serialized weight value is actual value - 1. |
| builder.WriteUInt8(priority.weight() - 1); |
| DCHECK_EQ(GetPrioritySize(), builder.length()); |
| return builder.take(); |
| } |
| |
| namespace { |
| |
| class FrameSerializationVisitor : public SpdyFrameVisitor { |
| public: |
| explicit FrameSerializationVisitor(SpdyFramer* framer) |
| : framer_(framer), frame_() {} |
| ~FrameSerializationVisitor() override {} |
| |
| SpdySerializedFrame ReleaseSerializedFrame() { return std::move(frame_); } |
| |
| void VisitData(const SpdyDataIR& data) override { |
| frame_ = framer_->SerializeData(data); |
| } |
| void VisitSynStream(const SpdySynStreamIR& syn_stream) override { |
| frame_ = framer_->SerializeSynStream(syn_stream); |
| } |
|