diff options
Diffstat (limited to 'chromium/net/spdy/spdy_framer.cc')
-rw-r--r-- | chromium/net/spdy/spdy_framer.cc | 2361 |
1 files changed, 2361 insertions, 0 deletions
diff --git a/chromium/net/spdy/spdy_framer.cc b/chromium/net/spdy/spdy_framer.cc new file mode 100644 index 00000000000..89a8309ea44 --- /dev/null +++ b/chromium/net/spdy/spdy_framer.cc @@ -0,0 +1,2361 @@ +// 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. + +// TODO(rtenhove) clean up frame buffer size calculations so that we aren't +// constantly adding and subtracting header sizes; this is ugly and error- +// prone. + +#include "net/spdy/spdy_framer.h" + +#include "base/lazy_instance.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/stats_counters.h" +#include "base/third_party/valgrind/memcheck.h" +#include "net/spdy/spdy_frame_builder.h" +#include "net/spdy/spdy_frame_reader.h" +#include "net/spdy/spdy_bitmasks.h" +#include "third_party/zlib/zlib.h" + +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); +} + +struct DictionaryIds { + DictionaryIds() + : v2_dictionary_id(CalculateDictionaryId(kV2Dictionary, kV2DictionarySize)), + v3_dictionary_id(CalculateDictionaryId(kV3Dictionary, kV3DictionarySize)) + {} + const uLong v2_dictionary_id; + 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 kNoFlags = 0; + +} // namespace + +const SpdyStreamId SpdyFramer::kInvalidStream = -1; +const size_t SpdyFramer::kHeaderDataChunkMaxSize = 1024; +// 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 = 18; + +#ifdef DEBUG_SPDY_STATE_CHANGES +#define CHANGE_STATE(newstate) \ + do { \ + LOG(INFO) << "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(int version, + uint32 wire) { + if (version < 3) { + ConvertFlagsAndIdForSpdy2(&wire); + } + return SettingsFlagsAndId(ntohl(wire) >> 24, ntohl(wire) & 0x00ffffff); +} + +SettingsFlagsAndId::SettingsFlagsAndId(uint8 flags, uint32 id) + : flags_(flags), id_(id & 0x00ffffff) { + DCHECK_GT(1u << 24, id) << "SPDY setting ID too large."; +} + +uint32 SettingsFlagsAndId::GetWireFormat(int version) const { + uint32 wire = htonl(id_ & 0x00ffffff) | htonl(flags_ << 24); + if (version < 3) { + ConvertFlagsAndIdForSpdy2(&wire); + } + return wire; +} + +// SPDY 2 had a bug in it with respect to byte ordering of id/flags field. +// This method is used to preserve buggy behavior and works on both +// little-endian and big-endian hosts. +// This method is also bidirectional (can be used to translate SPDY 2 to SPDY 3 +// as well as vice versa). +void SettingsFlagsAndId::ConvertFlagsAndIdForSpdy2(uint32* val) { + uint8* wire_array = reinterpret_cast<uint8*>(val); + std::swap(wire_array[0], wire_array[3]); + std::swap(wire_array[1], wire_array[2]); +} + +SpdyCredential::SpdyCredential() : slot(0) {} +SpdyCredential::~SpdyCredential() {} + +SpdyFramer::SpdyFramer(SpdyMajorVersion version) + : current_frame_buffer_(new char[kControlFrameBufferSize]), + enable_compression_(true), + visitor_(NULL), + debug_visitor_(NULL), + display_protocol_("SPDY"), + spdy_version_(version), + syn_frame_processed_(false), + probable_http_response_(false) { + DCHECK_GE(spdy_version_, SPDY_MIN_VERSION); + DCHECK_LE(spdy_version_, SPDY_MAX_VERSION); + Reset(); +} + +SpdyFramer::~SpdyFramer() { + if (header_compressor_.get()) { + deflateEnd(header_compressor_.get()); + } + if (header_decompressor_.get()) { + inflateEnd(header_decompressor_.get()); + } +} + +void SpdyFramer::Reset() { + state_ = SPDY_RESET; + previous_state_ = SPDY_RESET; + error_code_ = SPDY_NO_ERROR; + remaining_data_length_ = 0; + remaining_control_header_ = 0; + current_frame_buffer_length_ = 0; + current_frame_type_ = DATA; + current_frame_flags_ = 0; + current_frame_length_ = 0; + current_frame_stream_id_ = kInvalidStream; + settings_scratch_.Reset(); +} + +size_t SpdyFramer::GetDataFrameMinimumSize() const { + // Size, in bytes, of the data frame header. Future versions of SPDY + // will likely vary this, so we allow for the flexibility of a function call + // for this value as opposed to a constant. + return 8; +} + +// Size, in bytes, of the control frame header. +size_t SpdyFramer::GetControlFrameHeaderSize() const { + switch (protocol_version()) { + case SPDY2: + case SPDY3: + case SPDY4: + return 8; + } + LOG(DFATAL) << "Unhandled SPDY version."; + return 0; +} + +size_t SpdyFramer::GetSynStreamMinimumSize() const { + // Size, in bytes, of a SYN_STREAM frame not including the variable-length + // name-value block. + if (spdy_version_ < 4) { + // Calculated as: + // control frame header + 2 * 4 (stream IDs) + 1 (priority) + 1 (slot) + return GetControlFrameHeaderSize() + 10; + } else { + // Calculated as: + // frame prefix + 4 (associated stream ID) + 1 (priority) + 1 (slot) + return GetControlFrameHeaderSize() + 6; + } +} + +size_t SpdyFramer::GetSynReplyMinimumSize() const { + // Size, in bytes, of a SYN_REPLY frame not including the variable-length + // name-value block. + size_t size = GetControlFrameHeaderSize(); + if (spdy_version_ < 4) { + // Calculated as: + // control frame header + 4 (stream IDs) + size += 4; + } + + // In SPDY 2, there were 2 unused bytes before payload. + if (protocol_version() < 3) { + size += 2; + } + + return size; +} + +size_t SpdyFramer::GetRstStreamSize() const { + // Size, in bytes, of a RST_STREAM frame. + if (spdy_version_ < 4) { + // Calculated as: + // control frame header + 4 (stream id) + 4 (status code) + return GetControlFrameHeaderSize() + 8; + } else { + // Calculated as: + // frame prefix + 4 (status code) + return GetControlFrameHeaderSize() + 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) + return GetControlFrameHeaderSize() + 4; +} + +size_t SpdyFramer::GetPingSize() const { + // Size, in bytes, of this PING frame. Calculated as: + // control frame header + 4 (id) + return GetControlFrameHeaderSize() + 4; +} + +size_t SpdyFramer::GetGoAwaySize() const { + // Size, in bytes, of this GOAWAY frame. Calculated as: + // control frame header + 4 (last good stream id) + size_t size = GetControlFrameHeaderSize() + 4; + + // SPDY 3+ GOAWAY frames also contain a status. + if (protocol_version() >= 3) { + size += 4; + } + + return size; +} + +size_t SpdyFramer::GetHeadersMinimumSize() const { + // Size, in bytes, of a HEADERS frame not including the variable-length + // name-value block. + size_t size = GetControlFrameHeaderSize(); + if (spdy_version_ < 4) { + // Calculated as: + // control frame header + 4 (stream IDs) + size += 4; + } + + // In SPDY 2, there were 2 unused bytes before payload. + if (protocol_version() < 3) { + size += 2; + } + + return size; +} + +size_t SpdyFramer::GetWindowUpdateSize() const { + // Size, in bytes, of a WINDOW_UPDATE frame. + if (spdy_version_ < 4) { + // Calculated as: + // control frame header + 4 (stream id) + 4 (delta) + return GetControlFrameHeaderSize() + 8; + } else { + // Calculated as: + // frame prefix + 4 (delta) + return GetControlFrameHeaderSize() + 4; + } +} + +size_t SpdyFramer::GetCredentialMinimumSize() const { + // Size, in bytes, of a CREDENTIAL frame sans variable-length certificate list + // and proof. Calculated as: + // control frame header + 2 (slot) + return GetControlFrameHeaderSize() + 2; +} + +size_t SpdyFramer::GetBlockedSize() const { + DCHECK_LE(4, protocol_version()); + // Size, in bytes, of a BLOCKED frame. + // The BLOCKED frame has no payload beyond the control frame header. + return GetControlFrameHeaderSize(); +} + +size_t SpdyFramer::GetPushPromiseMinimumSize() const { + DCHECK_LE(4, protocol_version()); + // Size, in bytes, of a PUSH_PROMISE frame, sans the embedded header block. + // Calculated as frame prefix + 4 (promised stream id). + return GetControlFrameHeaderSize() + 4; +} + +size_t SpdyFramer::GetFrameMinimumSize() const { + return std::min(GetDataFrameMinimumSize(), GetControlFrameHeaderSize()); +} + +size_t SpdyFramer::GetFrameMaximumSize() const { + return (protocol_version() < 4) ? 0xffffff : 0xffff; +} + +size_t SpdyFramer::GetDataFrameMaximumPayload() const { + return GetFrameMaximumSize() - GetDataFrameMinimumSize(); +} + +const char* SpdyFramer::StateToString(int state) { + switch (state) { + case SPDY_ERROR: + return "ERROR"; + case SPDY_AUTO_RESET: + return "AUTO_RESET"; + case SPDY_RESET: + return "RESET"; + case SPDY_READING_COMMON_HEADER: + return "READING_COMMON_HEADER"; + case SPDY_CONTROL_FRAME_PAYLOAD: + return "CONTROL_FRAME_PAYLOAD"; + 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_CREDENTIAL_FRAME_PAYLOAD: + return "SPDY_CREDENTIAL_FRAME_PAYLOAD"; + case SPDY_SETTINGS_FRAME_PAYLOAD: + return "SPDY_SETTINGS_FRAME_PAYLOAD"; + } + return "UNKNOWN_STATE"; +} + +void SpdyFramer::set_error(SpdyError error) { + DCHECK(visitor_); + error_code_ = error; + 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_CONTROL_FRAME: + return "INVALID_CONTROL_FRAME"; + case SPDY_CONTROL_PAYLOAD_TOO_LARGE: + return "CONTROL_PAYLOAD_TOO_LARGE"; + 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_DATA_FRAME_FLAGS: + return "SPDY_INVALID_DATA_FRAME_FLAGS"; + case SPDY_INVALID_CONTROL_FRAME_FLAGS: + return "SPDY_INVALID_CONTROL_FRAME_FLAGS"; + } + 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_INVALID_CREDENTIALS: + return "INVALID_CREDENTIALS"; + case RST_STREAM_FRAME_TOO_LARGE: + return "FRAME_TOO_LARGE"; + } + 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 NOOP: + return "NOOP"; + case PING: + return "PING"; + case GOAWAY: + return "GOAWAY"; + case HEADERS: + return "HEADERS"; + case WINDOW_UPDATE: + return "WINDOW_UPDATE"; + case CREDENTIAL: + return "CREDENTIAL"; + case BLOCKED: + return "BLOCKED"; + case PUSH_PROMISE: + return "PUSH_PROMISE"; + } + return "UNKNOWN_CONTROL_TYPE"; +} + +size_t SpdyFramer::ProcessInput(const char* data, size_t len) { + DCHECK(visitor_); + DCHECK(data); + + size_t original_len = len; + do { + previous_state_ = state_; + switch (state_) { + case SPDY_ERROR: + goto bottom; + + case SPDY_AUTO_RESET: + case SPDY_RESET: + Reset(); + 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) + // take a different path through the state machine - they + // will go: + // 1. SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK + // 2. SPDY_CONTROL_FRAME_HEADER_BLOCK + // + // SETTINGS frames take a slightly modified route: + // 1. SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK + // 2. SPDY_SETTINGS_FRAME_PAYLOAD + // + // All other control frames will use the alternate route directly to + // SPDY_CONTROL_FRAME_PAYLOAD + int bytes_read = ProcessControlFrameBeforeHeaderBlock(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); + len -= bytes_read; + data += bytes_read; + break; + } + + case SPDY_CREDENTIAL_FRAME_PAYLOAD: { + size_t bytes_read = ProcessCredentialFramePayload(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_IGNORE_REMAINING_PAYLOAD: + // control frame has too-large payload + // intentional fallthrough + case SPDY_FORWARD_STREAM_FRAME: { + size_t bytes_read = ProcessDataFramePayload(data, len); + len -= bytes_read; + data += bytes_read; + break; + } + default: + LOG(DFATAL) << "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); + if (current_frame_buffer_length_ == 0 && + remaining_data_length_ == 0 && + remaining_control_header_ == 0) { + DCHECK(state_ == SPDY_RESET || state_ == SPDY_ERROR) + << "State: " << StateToString(state_); + } + + return original_len - len; +} + +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_length_ < GetControlFrameHeaderSize()) { + size_t bytes_desired = + GetControlFrameHeaderSize() - current_frame_buffer_length_; + UpdateCurrentFrameBuffer(&data, &len, bytes_desired); + } + + if (current_frame_buffer_length_ < GetControlFrameHeaderSize()) { + // Not enough information to do anything meaningful. + return original_len - len; + } + + // Using a scoped_ptr here since we may need to create a new SpdyFrameReader + // when processing DATA frames below. + scoped_ptr<SpdyFrameReader> reader( + new SpdyFrameReader(current_frame_buffer_.get(), + current_frame_buffer_length_)); + + uint16 version = 0; + bool is_control_frame = false; + + uint16 control_frame_type_field = DATA; + // ProcessControlFrameHeader() will set current_frame_type_ to the + // correct value if this is a valid control frame. + current_frame_type_ = DATA; + if (protocol_version() < 4) { + bool successful_read = reader->ReadUInt16(&version); + DCHECK(successful_read); + is_control_frame = (version & kControlFlagMask) != 0; + version &= ~kControlFlagMask; // Only valid for control frames. + + if (is_control_frame) { + // We check control_frame_type_field's validity in + // ProcessControlFrameHeader(). + successful_read = reader->ReadUInt16(&control_frame_type_field); + } 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 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 { + version = protocol_version(); + uint16 length_field = 0; + bool successful_read = reader->ReadUInt16(&length_field); + DCHECK(successful_read); + current_frame_length_ = length_field; + + uint8 control_frame_type_field_uint8 = DATA; + 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 != DATA); + + 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(is_control_frame ? GetControlFrameHeaderSize() + : GetDataFrameMinimumSize(), + 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_.get(), "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."; + } + } + + // if we're here, then we have the common header all received. + if (!is_control_frame) { + if (current_frame_flags_ & ~DATA_FLAG_FIN) { + 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_FORWARD_STREAM_FRAME); + } else { + // Empty data frame. + if (current_frame_flags_ & DATA_FLAG_FIN) { + visitor_->OnStreamFrameData( + current_frame_stream_id_, NULL, 0, true); + } + CHANGE_STATE(SPDY_AUTO_RESET); + } + } + } else if (version != spdy_version_) { + // We check version before we check validity: version can never be + // 'invalid', it can only be unsupported. + DLOG(INFO) << "Unsupported SPDY version " << version + << " (expected " << spdy_version_ << ")"; + set_error(SPDY_UNSUPPORTED_VERSION); + } else { + ProcessControlFrameHeader(control_frame_type_field); + } + + return original_len - len; +} + +void SpdyFramer::ProcessControlFrameHeader(uint16 control_frame_type_field) { + DCHECK_EQ(SPDY_NO_ERROR, error_code_); + DCHECK_LE(GetControlFrameHeaderSize(), current_frame_buffer_length_); + + if (control_frame_type_field < FIRST_CONTROL_TYPE || + control_frame_type_field > LAST_CONTROL_TYPE) { + set_error(SPDY_INVALID_CONTROL_FRAME); + return; + } + + current_frame_type_ = static_cast<SpdyFrameType>(control_frame_type_field); + + if (current_frame_type_ == NOOP) { + DLOG(INFO) << "NOOP control frame found. Ignoring."; + CHANGE_STATE(SPDY_AUTO_RESET); + return; + } + + // 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_ != GetRstStreamSize()) { + set_error(SPDY_INVALID_CONTROL_FRAME); + } else if (current_frame_flags_ != 0) { + set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); + } + break; + case SETTINGS: + // Make sure that we have an integral number of 8-byte key/value pairs, + // plus a 4-byte length field. + if (current_frame_length_ < GetSettingsMinimumSize() || + (current_frame_length_ - GetControlFrameHeaderSize()) % 8 != 4) { + DLOG(WARNING) << "Invalid length for SETTINGS frame: " + << current_frame_length_; + set_error(SPDY_INVALID_CONTROL_FRAME); + } else if (current_frame_flags_ & + ~SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS) { + set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); + } + break; + case PING: + if (current_frame_length_ != GetPingSize()) { + set_error(SPDY_INVALID_CONTROL_FRAME); + } else if (current_frame_flags_ != 0) { + set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); + } + break; + case GOAWAY: + { + if (current_frame_length_ != GetGoAwaySize()) { + set_error(SPDY_INVALID_CONTROL_FRAME); + } else if (current_frame_flags_ != 0) { + set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); + } + break; + } + case HEADERS: + if (current_frame_length_ < GetHeadersMinimumSize()) { + set_error(SPDY_INVALID_CONTROL_FRAME); + } else if (current_frame_flags_ & ~CONTROL_FLAG_FIN) { + set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); + } + break; + case WINDOW_UPDATE: + if (current_frame_length_ != GetWindowUpdateSize()) { + set_error(SPDY_INVALID_CONTROL_FRAME); + } else if (current_frame_flags_ != 0) { + set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); + } + break; + case CREDENTIAL: + if (current_frame_length_ < GetCredentialMinimumSize()) { + set_error(SPDY_INVALID_CONTROL_FRAME); + } else if (current_frame_flags_ != 0) { + set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); + } + break; + case BLOCKED: + if (current_frame_length_ != GetBlockedSize()) { + set_error(SPDY_INVALID_CONTROL_FRAME); + } else if (current_frame_flags_ != 0) { + set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); + } + break; + case PUSH_PROMISE: + if (current_frame_length_ < GetPushPromiseMinimumSize()) { + set_error(SPDY_INVALID_CONTROL_FRAME); + } else if (current_frame_flags_ != 0) { + set_error(SPDY_INVALID_CONTROL_FRAME_FLAGS); + } + 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. + DLOG(FATAL); + set_error(SPDY_INVALID_CONTROL_FRAME); + break; + } + + if (state_ == SPDY_ERROR) { + return; + } + + if (current_frame_length_ > GetControlFrameBufferMaxSize()) { + DLOG(WARNING) << "Received control frame with way too big of a payload: " + << current_frame_length_; + set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE); + return; + } + + if (current_frame_type_ == CREDENTIAL) { + CHANGE_STATE(SPDY_CREDENTIAL_FRAME_PAYLOAD); + return; + } + + // Determine the frame size without variable-length data. + int32 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(); + break; + case PUSH_PROMISE: + frame_size_without_variable_data = GetPushPromiseMinimumSize(); + 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) { + LOG(DFATAL) << 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 a header block. We need to parse the + // remainder of the control frame's header before we can parse the header + // block. The start of the header block varies with the control type. + DCHECK_GE(frame_size_without_variable_data, + static_cast<int32>(current_frame_buffer_length_)); + remaining_control_header_ = frame_size_without_variable_data - + current_frame_buffer_length_; + + 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) { + DCHECK_GE(kControlFrameBufferSize, + current_frame_buffer_length_ + bytes_to_read); + memcpy(current_frame_buffer_.get() + current_frame_buffer_length_, + *data, + bytes_to_read); + current_frame_buffer_length_ += bytes_to_read; + *data += bytes_to_read; + *len -= bytes_to_read; + } + return bytes_to_read; +} + +size_t SpdyFramer::GetSerializedLength(const int spdy_version, + const SpdyHeaderBlock* headers) { + const size_t num_name_value_pairs_size + = (spdy_version < 3) ? sizeof(uint16) : sizeof(uint32); + 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 (SpdyHeaderBlock::const_iterator it = headers->begin(); + it != headers->end(); + ++it) { + // 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 + it->first.size() + + length_of_value_size + it->second.size(); + } + return total_length; +} + +void SpdyFramer::WriteHeaderBlock(SpdyFrameBuilder* frame, + const int spdy_version, + const SpdyHeaderBlock* headers) { + if (spdy_version < 3) { + frame->WriteUInt16(headers->size()); // Number of headers. + } else { + frame->WriteUInt32(headers->size()); // Number of headers. + } + SpdyHeaderBlock::const_iterator it; + for (it = headers->begin(); it != headers->end(); ++it) { + if (spdy_version < 3) { + frame->WriteString(it->first); + frame->WriteString(it->second); + } else { + frame->WriteStringPiece32(it->first); + frame->WriteStringPiece32(it->second); + } + } +} + +// 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] = 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 { + unsigned length_length = 4; + if (spdy_version_ < 3) + length_length = 2; + + WriteLengthZ(headers->size(), length_length, kZStandardData, z); + + std::map<std::string, std::string>::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()) { + cookie_values.push_back(cookie_data.substr(0, i)); + cookie_length += i + 2 /* semicolon and space */; + cookie_data.remove_prefix(i + 1); + } else { + cookie_values.push_back(cookie_data); + cookie_length += cookie_data.size(); + 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_); + 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_.get(), + current_frame_buffer_length_); + reader.Seek(GetControlFrameHeaderSize()); // Seek past frame header. + + switch (current_frame_type_) { + case SYN_STREAM: + { + bool successful_read = true; + if (spdy_version_ < 4) { + successful_read = reader.ReadUInt31(¤t_frame_stream_id_); + DCHECK(successful_read); + } + if (current_frame_stream_id_ == 0) { + set_error(SPDY_INVALID_CONTROL_FRAME); + break; + } + + 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); + if (protocol_version() < 3) { + priority = priority >> 6; + } else { + priority = priority >> 5; + } + + uint8 slot = 0; + if (protocol_version() < 3) { + // SPDY 2 had an unused byte here. Seek past it. + reader.Seek(1); + } else { + successful_read = reader.ReadUInt8(&slot); + DCHECK(successful_read); + } + + 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, + slot, + (current_frame_flags_ & CONTROL_FLAG_FIN) != 0, + (current_frame_flags_ & CONTROL_FLAG_UNIDIRECTIONAL) != 0); + } + CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK); + break; + case SETTINGS: + visitor_->OnSettings(current_frame_flags_ & + SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS); + CHANGE_STATE(SPDY_SETTINGS_FRAME_PAYLOAD); + break; + case SYN_REPLY: + case HEADERS: + // SYN_REPLY and HEADERS are the same, save for the visitor call. + { + bool successful_read = true; + if (spdy_version_ < 4) { + successful_read = reader.ReadUInt31(¤t_frame_stream_id_); + DCHECK(successful_read); + } + if (current_frame_stream_id_ == 0) { + set_error(SPDY_INVALID_CONTROL_FRAME); + break; + } + if (protocol_version() < 3) { + // SPDY 2 had two unused bytes here. Seek past them. + reader.Seek(2); + } + 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_ & CONTROL_FLAG_FIN) != 0); + } + } + CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK); + break; + case PUSH_PROMISE: + { + DCHECK_LE(4, protocol_version()); + if (current_frame_stream_id_ == 0) { + set_error(SPDY_INVALID_CONTROL_FRAME); + break; + } + 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); + break; + } + 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); + } + CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK); + break; + default: + DCHECK(false); + } + } + 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) { + 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) { + LOG(DFATAL) << "Unhandled frame type in ProcessControlFrameHeaderBlock."; + } + size_t process_bytes = std::min(data_len, remaining_data_length_); + if (process_bytes > 0) { + if (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_ == 0 && processed_successfully) { + // The complete header block has been delivered. We send a zero-length + // OnControlFrameHeaderData() to indicate this. + visitor_->OnControlFrameHeaderData(current_frame_stream_id_, NULL, 0); + + // If this is a FIN, tell the caller. + if (current_frame_flags_ & CONTROL_FLAG_FIN) { + visitor_->OnStreamFrameData(current_frame_stream_id_, NULL, 0, true); + } + + CHANGE_STATE(SPDY_AUTO_RESET); + } + + // Handle error. + if (!processed_successfully) { + return data_len; + } + + // Return amount processed. + return process_bytes; +} + +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; + + // Loop over our incoming data. + while (unprocessed_bytes > 0) { + // Process up to one setting at a time. + size_t processing = std::min( + unprocessed_bytes, + static_cast<size_t>(8 - settings_scratch_.setting_buf_len)); + + // Check if we have a complete setting in our input. + if (processing == 8) { + // 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. + memcpy(settings_scratch_.setting_buf + settings_scratch_.setting_buf_len, + data + processed_bytes, + processing); + settings_scratch_.setting_buf_len += processing; + + // Check if we have a complete setting buffered. + if (settings_scratch_.setting_buf_len == 8) { + if (!ProcessSetting(settings_scratch_.setting_buf)) { + set_error(SPDY_INVALID_CONTROL_FRAME); + return processed_bytes; + } + // Reset settings_scratch_.setting_buf for our next setting. + settings_scratch_.setting_buf_len = 0; + } + } + + // 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) { + CHANGE_STATE(SPDY_AUTO_RESET); + } + + return processed_bytes; +} + +bool SpdyFramer::ProcessSetting(const char* data) { + // Extract fields. + // Maintain behavior of old SPDY 2 bug with byte ordering of flags/id. + const uint32 id_and_flags_wire = *(reinterpret_cast<const uint32*>(data)); + SettingsFlagsAndId id_and_flags = + SettingsFlagsAndId::FromWireFormat(spdy_version_, id_and_flags_wire); + uint8 flags = id_and_flags.flags(); + uint32 value = ntohl(*(reinterpret_cast<const uint32*>(data + 4))); + + // Validate id. + switch (id_and_flags.id()) { + case SETTINGS_UPLOAD_BANDWIDTH: + case SETTINGS_DOWNLOAD_BANDWIDTH: + case SETTINGS_ROUND_TRIP_TIME: + case SETTINGS_MAX_CONCURRENT_STREAMS: + case SETTINGS_CURRENT_CWND: + case SETTINGS_DOWNLOAD_RETRANS_RATE: + case SETTINGS_INITIAL_WINDOW_SIZE: + // Valid values. + break; + default: + DLOG(WARNING) << "Unknown SETTINGS ID: " << id_and_flags.id(); + return false; + } + SpdySettingsIds id = static_cast<SpdySettingsIds>(id_and_flags.id()); + + // Detect duplciates. + if (static_cast<uint32>(id) <= settings_scratch_.last_setting_id) { + DLOG(WARNING) << "Duplicate entry or invalid ordering for id " << id + << " in " << display_protocol_ << " SETTINGS frame " + << "(last settikng id was " + << settings_scratch_.last_setting_id << ")."; + return false; + } + settings_scratch_.last_setting_id = id; + + // Validate flags. + uint8 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_.get(), + current_frame_buffer_length_); + reader.Seek(GetControlFrameHeaderSize()); // Skip frame header. + + // Use frame-specific handlers. + switch (current_frame_type_) { + case RST_STREAM: { + bool successful_read = true; + if (spdy_version_ < 4) { + successful_read = reader.ReadUInt31(¤t_frame_stream_id_); + DCHECK(successful_read); + } + SpdyRstStreamStatus status = RST_STREAM_INVALID; + uint32 status_raw = status; + successful_read = reader.ReadUInt32(&status_raw); + DCHECK(successful_read); + if (status_raw > RST_STREAM_INVALID && + status_raw < RST_STREAM_NUM_STATUS_CODES) { + status = static_cast<SpdyRstStreamStatus>(status_raw); + } else { + // TODO(hkhalil): Probably best to OnError here, depending on + // our interpretation of the spec. Keeping with existing liberal + // behavior for now. + } + DCHECK(reader.IsDoneReading()); + visitor_->OnRstStream(current_frame_stream_id_, status); + } + break; + case PING: { + SpdyPingId id = 0; + bool successful_read = reader.ReadUInt32(&id); + DCHECK(successful_read); + DCHECK(reader.IsDoneReading()); + visitor_->OnPing(id); + } + break; + case GOAWAY: { + bool successful_read = reader.ReadUInt31(¤t_frame_stream_id_); + DCHECK(successful_read); + SpdyGoAwayStatus status = GOAWAY_OK; + if (spdy_version_ >= 3) { + uint32 status_raw = GOAWAY_OK; + successful_read = reader.ReadUInt32(&status_raw); + DCHECK(successful_read); + if (status_raw >= GOAWAY_OK && + status_raw < static_cast<uint32>(GOAWAY_NUM_STATUS_CODES)) { + status = static_cast<SpdyGoAwayStatus>(status_raw); + } else { + // TODO(hkhalil): Probably best to OnError here, depending on + // our interpretation of the spec. Keeping with existing liberal + // behavior for now. + } + } + DCHECK(reader.IsDoneReading()); + visitor_->OnGoAway(current_frame_stream_id_, status); + } + break; + case WINDOW_UPDATE: { + uint32 delta_window_size = 0; + bool successful_read = true; + if (spdy_version_ < 4) { + 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_LE(4, protocol_version()); + DCHECK(reader.IsDoneReading()); + visitor_->OnBlocked(current_frame_stream_id_); + } + break; + default: + // Unreachable. + LOG(FATAL) << "Unhandled control frame " << current_frame_type_; + } + + CHANGE_STATE(SPDY_IGNORE_REMAINING_PAYLOAD); + } + return original_len - len; +} + +size_t SpdyFramer::ProcessCredentialFramePayload(const char* data, size_t len) { + if (len > 0) { + // Clamp to the actual remaining payload. + if (len > remaining_data_length_) { + len = remaining_data_length_; + } + bool processed_succesfully = visitor_->OnCredentialFrameData(data, len); + remaining_data_length_ -= len; + if (!processed_succesfully) { + set_error(SPDY_CREDENTIAL_FRAME_CORRUPT); + } else if (remaining_data_length_ == 0) { + visitor_->OnCredentialFrameData(NULL, 0); + CHANGE_STATE(SPDY_AUTO_RESET); + } + } + return len; +} + +size_t SpdyFramer::ProcessDataFramePayload(const char* data, size_t len) { + size_t original_len = len; + + if (remaining_data_length_ > 0) { + size_t amount_to_forward = std::min(remaining_data_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, false); + } + } + data += amount_to_forward; + len -= amount_to_forward; + remaining_data_length_ -= amount_to_forward; + + // If the FIN flag is set, and there is no more data in this data + // frame, inform the visitor of EOF via a 0-length data frame. + if (!remaining_data_length_ && current_frame_flags_ & DATA_FLAG_FIN) { + visitor_->OnStreamFrameData(current_frame_stream_id_, NULL, 0, true); + } + } + + if (remaining_data_length_ == 0) { + CHANGE_STATE(SPDY_AUTO_RESET); + } + return original_len - len; +} + +size_t SpdyFramer::ParseHeaderBlockInBuffer(const char* header_data, + size_t header_length, + SpdyHeaderBlock* block) const { + SpdyFrameReader reader(header_data, header_length); + + // Read number of headers. + uint32 num_headers; + if (spdy_version_ < 3) { + uint16 temp; + if (!reader.ReadUInt16(&temp)) { + DLOG(INFO) << "Unable to read number of headers."; + return 0; + } + num_headers = temp; + } else { + if (!reader.ReadUInt32(&num_headers)) { + DLOG(INFO) << "Unable to read number of headers."; + return 0; + } + } + + // Read each header. + for (uint32 index = 0; index < num_headers; ++index) { + base::StringPiece temp; + + // Read header name. + if ((spdy_version_ < 3) ? !reader.ReadStringPiece16(&temp) + : !reader.ReadStringPiece32(&temp)) { + DLOG(INFO) << "Unable to read header name (" << index + 1 << " of " + << num_headers << ")."; + return 0; + } + std::string name = temp.as_string(); + + // Read header value. + if ((spdy_version_ < 3) ? !reader.ReadStringPiece16(&temp) + : !reader.ReadStringPiece32(&temp)) { + DLOG(INFO) << "Unable to read header value (" << index + 1 << " of " + << num_headers << ")."; + return 0; + } + std::string value = temp.as_string(); + + // Ensure no duplicates. + if (block->find(name) != block->end()) { + DLOG(INFO) << "Duplicate header '" << name << "' (" << index + 1 << " of " + << num_headers << ")."; + return 0; + } + + // Store header. + (*block)[name] = value; + } + return reader.GetBytesConsumed(); +} + +/* static */ +bool SpdyFramer::ParseCredentialData(const char* data, size_t len, + SpdyCredential* credential) { + DCHECK(credential); + + SpdyFrameReader parser(data, len); + base::StringPiece temp; + if (!parser.ReadUInt16(&credential->slot)) { + return false; + } + + if (!parser.ReadStringPiece32(&temp)) { + return false; + } + credential->proof = temp.as_string(); + + while (!parser.IsDoneReading()) { + if (!parser.ReadStringPiece32(&temp)) { + return false; + } + credential->certs.push_back(temp.as_string()); + } + return true; +} + +SpdyFrame* SpdyFramer::CreateDataFrame(SpdyStreamId stream_id, + const char* data, + uint32 len, SpdyDataFlags flags) const { + DCHECK_EQ(0, flags & (!DATA_FLAG_FIN)); + + SpdyDataIR data_ir(stream_id, base::StringPiece(data, len)); + data_ir.set_fin(flags & DATA_FLAG_FIN); + return SerializeData(data_ir); +} + +SpdySerializedFrame* SpdyFramer::SerializeData(const SpdyDataIR& data) const { + const size_t kSize = GetDataFrameMinimumSize() + data.data().length(); + + SpdyDataFlags flags = DATA_FLAG_NONE; + if (data.fin()) { + flags = DATA_FLAG_FIN; + } + + SpdyFrameBuilder builder(kSize); + builder.WriteDataFrameHeader(*this, data.stream_id(), flags); + builder.WriteBytes(data.data().data(), data.data().length()); + DCHECK_EQ(kSize, builder.length()); + return builder.take(); +} + +SpdySerializedFrame* SpdyFramer::SerializeDataFrameHeader( + const SpdyDataIR& data) const { + const size_t kSize = GetDataFrameMinimumSize(); + + SpdyDataFlags flags = DATA_FLAG_NONE; + if (data.fin()) { + flags = DATA_FLAG_FIN; + } + + SpdyFrameBuilder builder(kSize); + builder.WriteDataFrameHeader(*this, data.stream_id(), flags); + if (protocol_version() < 4) { + builder.OverwriteLength(*this, data.data().length()); + } else { + builder.OverwriteLength(*this, data.data().length() + kSize); + } + DCHECK_EQ(kSize, builder.length()); + return builder.take(); +} + +SpdyFrame* SpdyFramer::CreateSynStream( + SpdyStreamId stream_id, + SpdyStreamId associated_stream_id, + SpdyPriority priority, + uint8 credential_slot, + SpdyControlFlags flags, + bool compressed, + const SpdyHeaderBlock* headers) { + DCHECK_EQ(0, flags & ~CONTROL_FLAG_FIN & ~CONTROL_FLAG_UNIDIRECTIONAL); + DCHECK_EQ(enable_compression_, compressed); + + SpdySynStreamIR syn_stream(stream_id); + syn_stream.set_associated_to_stream_id(associated_stream_id); + syn_stream.set_priority(priority); + syn_stream.set_slot(credential_slot); + syn_stream.set_fin((flags & CONTROL_FLAG_FIN) != 0); + syn_stream.set_unidirectional((flags & CONTROL_FLAG_UNIDIRECTIONAL) != 0); + // TODO(hkhalil): Avoid copy here. + *(syn_stream.GetMutableNameValueBlock()) = *headers; + + return SerializeSynStream(syn_stream); +} + +SpdySerializedFrame* SpdyFramer::SerializeSynStream( + const SpdySynStreamIR& syn_stream) { + uint8 flags = 0; + if (syn_stream.fin()) { + flags |= CONTROL_FLAG_FIN; + } + if (syn_stream.unidirectional()) { + flags |= CONTROL_FLAG_UNIDIRECTIONAL; + } + + // The size of this frame, including variable-length name-value block. + const size_t size = GetSynStreamMinimumSize() + + GetSerializedLength(syn_stream.name_value_block()); + + SpdyFrameBuilder builder(size); + if (spdy_version_ < 4) { + builder.WriteControlFrameHeader(*this, SYN_STREAM, flags); + builder.WriteUInt32(syn_stream.stream_id()); + } else { + builder.WriteFramePrefix(*this, + SYN_STREAM, + flags, + syn_stream.stream_id()); + } + builder.WriteUInt32(syn_stream.associated_to_stream_id()); + uint8 priority = syn_stream.priority(); + if (priority > GetLowestPriority()) { + DLOG(DFATAL) << "Priority out-of-bounds."; + priority = GetLowestPriority(); + } + builder.WriteUInt8(priority << ((spdy_version_ < 3) ? 6 : 5)); + builder.WriteUInt8(syn_stream.slot()); + DCHECK_EQ(GetSynStreamMinimumSize(), builder.length()); + SerializeNameValueBlock(&builder, syn_stream); + + if (debug_visitor_) { + const size_t payload_len = GetSerializedLength( + protocol_version(), &(syn_stream.name_value_block())); + debug_visitor_->OnSendCompressedFrame(syn_stream.stream_id(), + SYN_STREAM, + payload_len, + builder.length()); + } + + return builder.take(); +} + +SpdyFrame* SpdyFramer::CreateSynReply( + SpdyStreamId stream_id, + SpdyControlFlags flags, + bool compressed, + const SpdyHeaderBlock* headers) { + DCHECK_EQ(0, flags & ~CONTROL_FLAG_FIN); + DCHECK_EQ(enable_compression_, compressed); + + SpdySynReplyIR syn_reply(stream_id); + syn_reply.set_fin(flags & CONTROL_FLAG_FIN); + // TODO(hkhalil): Avoid copy here. + *(syn_reply.GetMutableNameValueBlock()) = *headers; + + return SerializeSynReply(syn_reply); +} + +SpdySerializedFrame* SpdyFramer::SerializeSynReply( + const SpdySynReplyIR& syn_reply) { + uint8 flags = 0; + if (syn_reply.fin()) { + flags |= CONTROL_FLAG_FIN; + } + + // The size of this frame, including variable-length name-value block. + size_t size = GetSynReplyMinimumSize() + + GetSerializedLength(syn_reply.name_value_block()); + + SpdyFrameBuilder builder(size); + if (spdy_version_ < 4) { + builder.WriteControlFrameHeader(*this, SYN_REPLY, flags); + builder.WriteUInt32(syn_reply.stream_id()); + } else { + builder.WriteFramePrefix(*this, + SYN_REPLY, + flags, + syn_reply.stream_id()); + } + if (protocol_version() < 3) { + builder.WriteUInt16(0); // Unused. + } + DCHECK_EQ(GetSynReplyMinimumSize(), builder.length()); + SerializeNameValueBlock(&builder, syn_reply); + + if (debug_visitor_) { + const size_t payload_len = GetSerializedLength( + protocol_version(), &(syn_reply.name_value_block())); + debug_visitor_->OnSendCompressedFrame(syn_reply.stream_id(), + SYN_REPLY, + payload_len, + builder.length()); + } + + return builder.take(); +} + +SpdyFrame* SpdyFramer::CreateRstStream( + SpdyStreamId stream_id, + SpdyRstStreamStatus status) const { + SpdyRstStreamIR rst_stream(stream_id, status); + return SerializeRstStream(rst_stream); +} + +SpdySerializedFrame* SpdyFramer::SerializeRstStream( + const SpdyRstStreamIR& rst_stream) const { + SpdyFrameBuilder builder(GetRstStreamSize()); + if (spdy_version_ < 4) { + builder.WriteControlFrameHeader(*this, RST_STREAM, 0); + builder.WriteUInt32(rst_stream.stream_id()); + } else { + builder.WriteFramePrefix(*this, + RST_STREAM, + 0, + rst_stream.stream_id()); + } + builder.WriteUInt32(rst_stream.status()); + DCHECK_EQ(GetRstStreamSize(), builder.length()); + return builder.take(); +} + +SpdyFrame* SpdyFramer::CreateSettings( + const SettingsMap& values) const { + SpdySettingsIR settings; + for (SettingsMap::const_iterator it = values.begin(); + it != values.end(); + ++it) { + settings.AddSetting(it->first, + (it->second.first & SETTINGS_FLAG_PLEASE_PERSIST) != 0, + (it->second.first & SETTINGS_FLAG_PERSISTED) != 0, + it->second.second); + } + return SerializeSettings(settings); +} + +SpdySerializedFrame* SpdyFramer::SerializeSettings( + const SpdySettingsIR& settings) const { + uint8 flags = 0; + if (settings.clear_settings()) { + flags |= SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS; + } + const SpdySettingsIR::ValueMap* values = &(settings.values()); + + // Size, in bytes, of this SETTINGS frame. + const size_t size = GetSettingsMinimumSize() + (values->size() * 8); + + SpdyFrameBuilder builder(size); + if (spdy_version_ < 4) { + builder.WriteControlFrameHeader(*this, SETTINGS, flags); + } else { + builder.WriteFramePrefix(*this, SETTINGS, flags, 0); + } + builder.WriteUInt32(values->size()); + DCHECK_EQ(GetSettingsMinimumSize(), builder.length()); + for (SpdySettingsIR::ValueMap::const_iterator it = values->begin(); + it != values->end(); + ++it) { + uint8 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, it->first); + uint32 id_and_flags_wire = flags_and_id.GetWireFormat(protocol_version()); + builder.WriteBytes(&id_and_flags_wire, 4); + builder.WriteUInt32(it->second.value); + } + DCHECK_EQ(size, builder.length()); + return builder.take(); +} + +SpdyFrame* SpdyFramer::SerializeBlocked(const SpdyBlockedIR& blocked) const { + DCHECK_LE(4, protocol_version()); + SpdyFrameBuilder builder(GetBlockedSize()); + builder.WriteFramePrefix(*this, BLOCKED, kNoFlags, blocked.stream_id()); + return builder.take(); +} + +SpdyFrame* SpdyFramer::CreatePingFrame(uint32 unique_id) const { + SpdyPingIR ping(unique_id); + return SerializePing(ping); +} + +SpdySerializedFrame* SpdyFramer::SerializePing(const SpdyPingIR& ping) const { + SpdyFrameBuilder builder(GetPingSize()); + if (spdy_version_ < 4) { + builder.WriteControlFrameHeader(*this, PING, kNoFlags); + } else { + builder.WriteFramePrefix(*this, PING, 0, 0); + } + builder.WriteUInt32(ping.id()); + DCHECK_EQ(GetPingSize(), builder.length()); + return builder.take(); +} + +SpdyFrame* SpdyFramer::CreateGoAway( + SpdyStreamId last_accepted_stream_id, + SpdyGoAwayStatus status) const { + SpdyGoAwayIR goaway(last_accepted_stream_id, status); + return SerializeGoAway(goaway); +} + +SpdySerializedFrame* SpdyFramer::SerializeGoAway( + const SpdyGoAwayIR& goaway) const { + SpdyFrameBuilder builder(GetGoAwaySize()); + if (spdy_version_ < 4) { + builder.WriteControlFrameHeader(*this, GOAWAY, kNoFlags); + } else { + builder.WriteFramePrefix(*this, GOAWAY, 0, 0); + } + builder.WriteUInt32(goaway.last_good_stream_id()); + if (protocol_version() >= 3) { + builder.WriteUInt32(goaway.status()); + } + DCHECK_EQ(GetGoAwaySize(), builder.length()); + return builder.take(); +} + +SpdyFrame* SpdyFramer::CreateHeaders( + SpdyStreamId stream_id, + SpdyControlFlags flags, + bool compressed, + const SpdyHeaderBlock* header_block) { + // Basically the same as CreateSynReply(). + DCHECK_EQ(0, flags & (!CONTROL_FLAG_FIN)); + DCHECK_EQ(enable_compression_, compressed); + + SpdyHeadersIR headers(stream_id); + headers.set_fin(flags & CONTROL_FLAG_FIN); + // TODO(hkhalil): Avoid copy here. + *(headers.GetMutableNameValueBlock()) = *header_block; + + return SerializeHeaders(headers); +} + +SpdySerializedFrame* SpdyFramer::SerializeHeaders( + const SpdyHeadersIR& headers) { + uint8 flags = 0; + if (headers.fin()) { + flags |= CONTROL_FLAG_FIN; + } + + // The size of this frame, including variable-length name-value block. + size_t size = GetHeadersMinimumSize() + + GetSerializedLength(headers.name_value_block()); + + SpdyFrameBuilder builder(size); + if (spdy_version_ < 4) { + builder.WriteControlFrameHeader(*this, HEADERS, flags); + builder.WriteUInt32(headers.stream_id()); + } else { + builder.WriteFramePrefix(*this, + HEADERS, + flags, + headers.stream_id()); + } + if (protocol_version() < 3) { + builder.WriteUInt16(0); // Unused. + } + DCHECK_EQ(GetHeadersMinimumSize(), builder.length()); + + SerializeNameValueBlock(&builder, headers); + + if (debug_visitor_) { + const size_t payload_len = GetSerializedLength( + protocol_version(), &(headers.name_value_block())); + debug_visitor_->OnSendCompressedFrame(headers.stream_id(), + HEADERS, + payload_len, + builder.length()); + } + + return builder.take(); +} + +SpdyFrame* SpdyFramer::CreateWindowUpdate( + SpdyStreamId stream_id, + uint32 delta_window_size) const { + SpdyWindowUpdateIR window_update(stream_id, delta_window_size); + return SerializeWindowUpdate(window_update); +} + +SpdySerializedFrame* SpdyFramer::SerializeWindowUpdate( + const SpdyWindowUpdateIR& window_update) const { + SpdyFrameBuilder builder(GetWindowUpdateSize()); + if (spdy_version_ < 4) { + builder.WriteControlFrameHeader(*this, WINDOW_UPDATE, kNoFlags); + builder.WriteUInt32(window_update.stream_id()); + } else { + builder.WriteFramePrefix(*this, + WINDOW_UPDATE, + kNoFlags, + window_update.stream_id()); + } + builder.WriteUInt32(window_update.delta()); + DCHECK_EQ(GetWindowUpdateSize(), builder.length()); + return builder.take(); +} + +// TODO(hkhalil): Gut with SpdyCredential removal. +SpdyFrame* SpdyFramer::CreateCredentialFrame( + const SpdyCredential& credential) const { + SpdyCredentialIR credential_ir(credential.slot); + credential_ir.set_proof(credential.proof); + for (std::vector<std::string>::const_iterator cert = credential.certs.begin(); + cert != credential.certs.end(); + ++cert) { + credential_ir.AddCertificate(*cert); + } + return SerializeCredential(credential_ir); +} + +SpdySerializedFrame* SpdyFramer::SerializeCredential( + const SpdyCredentialIR& credential) const { + size_t size = GetCredentialMinimumSize(); + size += 4 + credential.proof().length(); // Room for proof. + for (SpdyCredentialIR::CertificateList::const_iterator it = + credential.certificates()->begin(); + it != credential.certificates()->end(); + ++it) { + size += 4 + it->length(); // Room for certificate. + } + + SpdyFrameBuilder builder(size); + if (spdy_version_ < 4) { + builder.WriteControlFrameHeader(*this, CREDENTIAL, kNoFlags); + } else { + builder.WriteFramePrefix(*this, CREDENTIAL, kNoFlags, 0); + } + builder.WriteUInt16(credential.slot()); + DCHECK_EQ(GetCredentialMinimumSize(), builder.length()); + builder.WriteStringPiece32(credential.proof()); + for (SpdyCredentialIR::CertificateList::const_iterator it = + credential.certificates()->begin(); + it != credential.certificates()->end(); + ++it) { + builder.WriteStringPiece32(*it); + } + DCHECK_EQ(size, builder.length()); + return builder.take(); +} + +SpdyFrame* SpdyFramer::CreatePushPromise( + SpdyStreamId stream_id, + SpdyStreamId promised_stream_id, + const SpdyHeaderBlock* header_block) { + SpdyPushPromiseIR push_promise(stream_id, promised_stream_id); + // TODO(hkhalil): Avoid copy here. + *(push_promise.GetMutableNameValueBlock()) = *header_block; + + return SerializePushPromise(push_promise); +} + +SpdyFrame* SpdyFramer::SerializePushPromise( + const SpdyPushPromiseIR& push_promise) { + DCHECK_LE(4, protocol_version()); + // The size of this frame, including variable-length name-value block. + size_t size = GetPushPromiseMinimumSize() + + GetSerializedLength(push_promise.name_value_block()); + + SpdyFrameBuilder builder(size); + builder.WriteFramePrefix(*this, PUSH_PROMISE, kNoFlags, + push_promise.stream_id()); + builder.WriteUInt32(push_promise.promised_stream_id()); + DCHECK_EQ(GetPushPromiseMinimumSize(), builder.length()); + + SerializeNameValueBlock(&builder, push_promise); + + if (debug_visitor_) { + const size_t payload_len = GetSerializedLength( + protocol_version(), &(push_promise.name_value_block())); + debug_visitor_->OnSendCompressedFrame(push_promise.stream_id(), + PUSH_PROMISE, payload_len, builder.length()); + } + + return builder.take(); +} + +namespace { + +class FrameSerializationVisitor : public SpdyFrameVisitor { + public: + explicit FrameSerializationVisitor(SpdyFramer* framer) : framer_(framer) {} + virtual ~FrameSerializationVisitor() {} + + SpdySerializedFrame* ReleaseSerializedFrame() { return frame_.release(); } + + virtual void VisitData(const SpdyDataIR& data) OVERRIDE { + frame_.reset(framer_->SerializeData(data)); + } + virtual void VisitSynStream(const SpdySynStreamIR& syn_stream) OVERRIDE { + frame_.reset(framer_->SerializeSynStream(syn_stream)); + } + virtual void VisitSynReply(const SpdySynReplyIR& syn_reply) OVERRIDE { + frame_.reset(framer_->SerializeSynReply(syn_reply)); + } + virtual void VisitRstStream(const SpdyRstStreamIR& rst_stream) OVERRIDE { + frame_.reset(framer_->SerializeRstStream(rst_stream)); + } + virtual void VisitSettings(const SpdySettingsIR& settings) OVERRIDE { + frame_.reset(framer_->SerializeSettings(settings)); + } + virtual void VisitPing(const SpdyPingIR& ping) OVERRIDE { + frame_.reset(framer_->SerializePing(ping)); + } + virtual void VisitGoAway(const SpdyGoAwayIR& goaway) OVERRIDE { + frame_.reset(framer_->SerializeGoAway(goaway)); + } + virtual void VisitHeaders(const SpdyHeadersIR& headers) OVERRIDE { + frame_.reset(framer_->SerializeHeaders(headers)); + } + virtual void VisitWindowUpdate( + const SpdyWindowUpdateIR& window_update) OVERRIDE { + frame_.reset(framer_->SerializeWindowUpdate(window_update)); + } + virtual void VisitCredential(const SpdyCredentialIR& credential) OVERRIDE { + frame_.reset(framer_->SerializeCredential(credential)); + } + virtual void VisitBlocked(const SpdyBlockedIR& blocked) OVERRIDE { + frame_.reset(framer_->SerializeBlocked(blocked)); + } + virtual void VisitPushPromise( + const SpdyPushPromiseIR& push_promise) OVERRIDE { + frame_.reset(framer_->SerializePushPromise(push_promise)); + } + + private: + SpdyFramer* framer_; + scoped_ptr<SpdySerializedFrame> frame_; +}; + +} // namespace + +SpdySerializedFrame* SpdyFramer::SerializeFrame(const SpdyFrameIR& frame) { + FrameSerializationVisitor visitor(this); + frame.Visit(&visitor); + return visitor.ReleaseSerializedFrame(); +} + +size_t SpdyFramer::GetSerializedLength(const SpdyHeaderBlock& headers) { + const size_t uncompressed_length = + GetSerializedLength(protocol_version(), &headers); + if (!enable_compression_) { + return uncompressed_length; + } + z_stream* compressor = GetHeaderCompressor(); + // Since we'll be performing lots of flushes when compressing the data, + // zlib's lower bounds may be insufficient. + return 2 * deflateBound(compressor, uncompressed_length); +} + +// The following compression setting are based on Brian Olson's analysis. See +// https://groups.google.com/group/spdy-dev/browse_thread/thread/dfaf498542fac792 +// for more details. +#if defined(USE_SYSTEM_ZLIB) +// System zlib is not expected to have workaround for http://crbug.com/139744, +// so disable compression in that case. +// TODO(phajdan.jr): Remove the special case when it's no longer necessary. +static const int kCompressorLevel = 0; +#else // !defined(USE_SYSTEM_ZLIB) +static const int kCompressorLevel = 9; +#endif // !defined(USE_SYSTEM_ZLIB) +static const int kCompressorWindowSizeInBits = 11; +static const int kCompressorMemLevel = 1; + +z_stream* SpdyFramer::GetHeaderCompressor() { + if (header_compressor_.get()) + return header_compressor_.get(); // Already initialized. + + header_compressor_.reset(new z_stream); + memset(header_compressor_.get(), 0, sizeof(z_stream)); + + int success = deflateInit2(header_compressor_.get(), + kCompressorLevel, + Z_DEFLATED, + kCompressorWindowSizeInBits, + kCompressorMemLevel, + Z_DEFAULT_STRATEGY); + if (success == Z_OK) { + const char* dictionary = (spdy_version_ < 3) ? kV2Dictionary + : kV3Dictionary; + const int dictionary_size = (spdy_version_ < 3) ? kV2DictionarySize + : kV3DictionarySize; + success = deflateSetDictionary(header_compressor_.get(), + reinterpret_cast<const Bytef*>(dictionary), + dictionary_size); + } + if (success != Z_OK) { + LOG(WARNING) << "deflateSetDictionary failure: " << success; + header_compressor_.reset(NULL); + return NULL; + } + return header_compressor_.get(); +} + +z_stream* SpdyFramer::GetHeaderDecompressor() { + if (header_decompressor_.get()) + return header_decompressor_.get(); // Already initialized. + + header_decompressor_.reset(new z_stream); + memset(header_decompressor_.get(), 0, sizeof(z_stream)); + + int success = inflateInit(header_decompressor_.get()); + if (success != Z_OK) { + LOG(WARNING) << "inflateInit failure: " << success; + header_decompressor_.reset(NULL); + return NULL; + } + return header_decompressor_.get(); +} + +// Incrementally decompress the control frame's header block, feeding the +// result to the visitor in chunks. Continue this until the visitor +// indicates that it cannot process any more data, or (more commonly) we +// run out of data to deliver. +bool SpdyFramer::IncrementallyDecompressControlFrameHeaderData( + SpdyStreamId stream_id, + const char* data, + size_t len) { + // Get a decompressor or set error. + z_stream* decomp = GetHeaderDecompressor(); + if (decomp == NULL) { + LOG(DFATAL) << "Couldn't get decompressor for handling compressed headers."; + set_error(SPDY_DECOMPRESS_FAILURE); + return false; + } + + bool processed_successfully = true; + char buffer[kHeaderDataChunkMaxSize]; + + decomp->next_in = reinterpret_cast<Bytef*>(const_cast<char*>(data)); + decomp->avail_in = len; + // If we get a SYN_STREAM/SYN_REPLY/HEADERS frame with stream ID zero, we + // signal an error back in ProcessControlFrameBeforeHeaderBlock. So if we've + // reached this method successfully, stream_id should be nonzero. + DCHECK_LT(0u, stream_id); + while (decomp->avail_in > 0 && processed_successfully) { + decomp->next_out = reinterpret_cast<Bytef*>(buffer); + decomp->avail_out = arraysize(buffer); + + int rv = inflate(decomp, Z_SYNC_FLUSH); + if (rv == Z_NEED_DICT) { + const char* dictionary = (spdy_version_ < 3) ? kV2Dictionary + : kV3Dictionary; + const int dictionary_size = (spdy_version_ < 3) ? kV2DictionarySize + : kV3DictionarySize; + const DictionaryIds& ids = g_dictionary_ids.Get(); + const uLong dictionary_id = (spdy_version_ < 3) ? ids.v2_dictionary_id + : ids.v3_dictionary_id; + // Need to try again with the right dictionary. + if (decomp->adler == dictionary_id) { + rv = inflateSetDictionary(decomp, + reinterpret_cast<const Bytef*>(dictionary), + dictionary_size); + if (rv == Z_OK) + rv = inflate(decomp, Z_SYNC_FLUSH); + } + } + + // Inflate will generate a Z_BUF_ERROR if it runs out of input + // without producing any output. The input is consumed and + // buffered internally by zlib so we can detect this condition by + // checking if avail_in is 0 after the call to inflate. + bool input_exhausted = ((rv == Z_BUF_ERROR) && (decomp->avail_in == 0)); + if ((rv == Z_OK) || input_exhausted) { + size_t decompressed_len = arraysize(buffer) - decomp->avail_out; + if (decompressed_len > 0) { + processed_successfully = visitor_->OnControlFrameHeaderData( + stream_id, buffer, decompressed_len); + } + if (!processed_successfully) { + // Assume that the problem was the header block was too large for the + // visitor. + set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE); + } + } else { + DLOG(WARNING) << "inflate failure: " << rv << " " << len; + set_error(SPDY_DECOMPRESS_FAILURE); + processed_successfully = false; + } + } + return processed_successfully; +} + +bool SpdyFramer::IncrementallyDeliverControlFrameHeaderData( + SpdyStreamId stream_id, const char* data, size_t len) { + bool read_successfully = true; + while (read_successfully && len > 0) { + size_t bytes_to_deliver = std::min(len, kHeaderDataChunkMaxSize); + read_successfully = visitor_->OnControlFrameHeaderData(stream_id, data, + bytes_to_deliver); + data += bytes_to_deliver; + len -= bytes_to_deliver; + if (!read_successfully) { + // Assume that the problem was the header block was too large for the + // visitor. + set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE); + } + } + return read_successfully; +} + +void SpdyFramer::SerializeNameValueBlockWithoutCompression( + SpdyFrameBuilder* builder, + const SpdyNameValueBlock& name_value_block) const { + // Serialize number of headers. + if (protocol_version() < 3) { + builder->WriteUInt16(name_value_block.size()); + } else { + builder->WriteUInt32(name_value_block.size()); + } + + // Serialize each header. + for (SpdyHeaderBlock::const_iterator it = name_value_block.begin(); + it != name_value_block.end(); + ++it) { + if (protocol_version() < 3) { + builder->WriteString(it->first); + builder->WriteString(it->second); + } else { + builder->WriteStringPiece32(it->first); + builder->WriteStringPiece32(it->second); + } + } +} + +void SpdyFramer::SerializeNameValueBlock( + SpdyFrameBuilder* builder, + const SpdyFrameWithNameValueBlockIR& frame) { + if (!enable_compression_) { + return SerializeNameValueBlockWithoutCompression(builder, + frame.name_value_block()); + } + + // First build an uncompressed version to be fed into the compressor. + const size_t uncompressed_len = GetSerializedLength( + protocol_version(), &(frame.name_value_block())); + SpdyFrameBuilder uncompressed_builder(uncompressed_len); + SerializeNameValueBlockWithoutCompression(&uncompressed_builder, + frame.name_value_block()); + scoped_ptr<SpdyFrame> uncompressed_payload(uncompressed_builder.take()); + + z_stream* compressor = GetHeaderCompressor(); + if (!compressor) { + LOG(DFATAL) << "Could not obtain compressor."; + return; + } + + base::StatsCounter compressed_frames("spdy.CompressedFrames"); + base::StatsCounter pre_compress_bytes("spdy.PreCompressSize"); + base::StatsCounter post_compress_bytes("spdy.PostCompressSize"); + + // Create an output frame. + // Since we'll be performing lots of flushes when compressing the data, + // zlib's lower bounds may be insufficient. + // + // TODO(akalin): Avoid the duplicate calculation with + // GetSerializedLength(const SpdyHeaderBlock&). + const int compressed_max_size = + 2 * deflateBound(compressor, uncompressed_len); + + // TODO(phajdan.jr): Clean up after we no longer need + // to workaround http://crbug.com/139744. +#if defined(USE_SYSTEM_ZLIB) + compressor->next_in = reinterpret_cast<Bytef*>(uncompressed_payload->data()); + compressor->avail_in = uncompressed_len; +#endif // defined(USE_SYSTEM_ZLIB) + compressor->next_out = reinterpret_cast<Bytef*>( + builder->GetWritableBuffer(compressed_max_size)); + compressor->avail_out = compressed_max_size; + + // TODO(phajdan.jr): Clean up after we no longer need + // to workaround http://crbug.com/139744. +#if defined(USE_SYSTEM_ZLIB) + int rv = deflate(compressor, Z_SYNC_FLUSH); + if (rv != Z_OK) { // How can we know that it compressed everything? + // This shouldn't happen, right? + LOG(WARNING) << "deflate failure: " << rv; + // TODO(akalin): Upstream this return. + return; + } +#else + WriteHeaderBlockToZ(&frame.name_value_block(), compressor); +#endif // defined(USE_SYSTEM_ZLIB) + + int compressed_size = compressed_max_size - compressor->avail_out; + builder->Seek(compressed_size); + builder->RewriteLength(*this); + + pre_compress_bytes.Add(uncompressed_len); + post_compress_bytes.Add(compressed_size); + + compressed_frames.Increment(); +} + +} // namespace net |