// Copyright (c) 2011 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/memory/scoped_ptr.h"
#include "base/metrics/stats_counters.h"
#ifndef ANDROID
#include "base/third_party/valgrind/memcheck.h"
#endif
#include "net/spdy/spdy_frame_builder.h"
#include "net/spdy/spdy_bitmasks.h"
#if defined(USE_SYSTEM_ZLIB)
#include <zlib.h>
#else
#include "third_party/zlib/zlib.h"
#endif
namespace {
// 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.
#ifdef ANDROID
const int kCompressorLevel = 0;
#else
const int kCompressorLevel = 9;
#endif
const int kCompressorWindowSizeInBits = 11;
const int kCompressorMemLevel = 1;
// Adler ID for the SPDY header compressor dictionary.
uLong dictionary_id = 0;
} // namespace
namespace spdy {
// This is just a hacked dictionary to use for shrinking HTTP-like headers.
// TODO(mbelshe): Use a scientific methodology for computing the dictionary.
const char SpdyFramer::kDictionary[] =
"optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-"
"languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi"
"f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser"
"-agent10010120020120220320420520630030130230330430530630740040140240340440"
"5406407408409410411412413414415416417500501502503504505accept-rangesageeta"
"glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic"
"ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran"
"sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati"
"oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo"
"ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe"
"pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic"
"ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1"
".1statusversionurl";
const int SpdyFramer::kDictionarySize = arraysize(kDictionary);
// By default is compression on or off.
bool SpdyFramer::compression_default_ = true;
int SpdyFramer::spdy_version_ = kSpdyProtocolVersion;
// The initial size of the control frame buffer; this is used internally
// as we parse through control frames. (It is exposed here for unit test
// purposes.)
size_t SpdyFramer::kControlFrameBufferInitialSize = 8 * 1024;
// The maximum size of the control frame buffer that we support.
// TODO(mbelshe): We should make this stream-based so there are no limits.
size_t SpdyFramer::kControlFrameBufferMaxSize = 16 * 1024;
const SpdyStreamId SpdyFramer::kInvalidStream = -1;
const size_t SpdyFramer::kHeaderDataChunkMaxSize = 1024;
#ifdef DEBUG_SPDY_STATE_CHANGES
#define CHANGE_STATE(newstate) \
{ \
do { \
LOG(INFO) << "Changing state from: " \
<< StateToString(state_) \
<< " to " << StateToString(newstate) << "\n"; \
state_ = newstate; \
} while (false); \
}
#else
#define CHANGE_STATE(newstate) (state_ = newstate)
#endif
int DecompressHeaderBlockInZStream(z_stream* decompressor) {
int rv = inflate(decompressor, Z_SYNC_FLUSH);
if (rv == Z_NEED_DICT) {
// Need to try again with the right dictionary.
if (decompressor->adler == dictionary_id) {
rv = inflateSetDictionary(decompressor,
(const Bytef*)SpdyFramer::kDictionary,
SpdyFramer::kDictionarySize);
if (rv == Z_OK)
rv = inflate(decompressor, Z_SYNC_FLUSH);
}
}
return rv;
}
// Retrieve serialized length of SpdyHeaderBlock.
size_t GetSerializedLength(const SpdyHeaderBlock* headers) {
size_t total_length = SpdyControlFrame::kNumNameValuePairsSize;
SpdyHeaderBlock::const_iterator it;
for (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 += SpdyControlFrame::kLengthOfNameSize +
it->first.size() +
SpdyControlFrame::kLengthOfValueSize +
it->second.size();
}
return total_length;
}
// Serializes a SpdyHeaderBlock.
void WriteHeaderBlock(SpdyFrameBuilder* frame, const SpdyHeaderBlock* headers) {
frame->WriteUInt16(headers->size()); // Number of headers.
SpdyHeaderBlock::const_iterator it;
for (it = headers->begin(); it != headers->end(); ++it) {
bool wrote_header;
wrote_header = frame->WriteString(it->first);
wrote_header &= frame->WriteString(it->second);
DCHECK(wrote_header);
}
}
// Creates a FlagsAndLength.
FlagsAndLength CreateFlagsAndLength(SpdyControlFlags flags, size_t length) {
DCHECK_EQ(0u, length & ~static_cast<size_t>(kLengthMask));
FlagsAndLength flags_length;
flags_length.length_ = htonl(static_cast<uint32>(length));
DCHECK_EQ(0, flags & ~kControlFlagsMask);
flags_length.flags_[0] = flags;
return flags_length;
}
SpdyFramer::SpdyFramer()
: state_(SPDY_RESET),
error_code_(SPDY_NO_ERROR),
remaining_data_(0),
remaining_control_payload_(0),
remaining_control_header_(0),
current_frame_buffer_(NULL),
current_frame_len_(0),
current_frame_capacity_(0),
validate_control_frame_sizes_(true),
enable_compression_(compression_default_),
visitor_(NULL) {
}
SpdyFramer::~SpdyFramer() {
if (header_compressor_.get()) {
deflateEnd(header_compressor_.get());
}
if (header_decompressor_.get()) {
inflateEnd(header_decompressor_.get());
}
CleanupStreamCompressorsAndDecompressors();
delete [] current_frame_buffer_;
}
const char* SpdyFramer::StatusCodeToString(int status_code) {
switch (status_code) {
case INVALID:
return "INVALID";
case PROTOCOL_ERROR:
return "PROTOCOL_ERROR";
case INVALID_STREAM:
return "INVALID_STREAM";
case REFUSED_STREAM:
return "REFUSED_STREAM";
case UNSUPPORTED_VERSION:
return "UNSUPPORTED_VERSION";
case CANCEL:
return "CANCEL";
case INTERNAL_ERROR:
return "INTERNAL_ERROR";
case FLOW_CONTROL_ERROR:
return "FLOW_CONTROL_ERROR";
}
return "UNKNOWN_STATUS";
}
const char* SpdyFramer::ControlTypeToString(SpdyControlType type) {
switch (type) {
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 NUM_CONTROL_FRAME_TYPES:
break;
}
return "UNKNOWN_CONTROL_TYPE";
}
size_t SpdyFramer::ProcessInput(const char* data, size_t len) {
DCHECK(visitor_);
DCHECK(data);
size_t original_len = len;
while (len != 0) {
switch (state_) {
case SPDY_ERROR:
case SPDY_DONE:
goto bottom;
case SPDY_AUTO_RESET:
case SPDY_RESET:
Reset();
CHANGE_STATE(SPDY_READING_COMMON_HEADER);
continue;
case SPDY_READING_COMMON_HEADER: {
size_t bytes_read = ProcessCommonHeader(data, len);
len -= bytes_read;
data += bytes_read;
continue;
}
// Arguably, this case is not necessary, as no bytes are consumed here.
// I felt it was a nice partitioning, however (which probably indicates
// that it should be refactored into its own function!)
// TODO(hkhalil): Remove -- while loop above prevents proper handling of
// zero-length control frames.
case SPDY_INTERPRET_CONTROL_FRAME_COMMON_HEADER:
ProcessControlFrameHeader();
continue;
case SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK: {
// Control frames that contain header blocks (SYN_STREAM, SYN_REPLY,
// HEADERS) take a different path through the state machine - they
// will go:
// 1. SPDY_INTERPRET_CONTROL_FRAME_COMMON HEADER
// 2. SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK
// 3. SPDY_CONTROL_FRAME_HEADER_BLOCK
//
// All other control frames will use the alternate route:
// 1. SPDY_INTERPRET_CONTROL_FRAME_COMMON_HEADER
// 2. SPDY_CONTROL_FRAME_PAYLOAD
int bytes_read = ProcessControlFrameBeforeHeaderBlock(data, len);
len -= bytes_read;
data += bytes_read;
continue;
}
case SPDY_CONTROL_FRAME_HEADER_BLOCK: {
int bytes_read = ProcessControlFrameHeaderBlock(data, len);
len -= bytes_read;
data += bytes_read;
continue;
}
case SPDY_CONTROL_FRAME_PAYLOAD: {
size_t bytes_read = ProcessControlFramePayload(data, len);
len -= bytes_read;
data += bytes_read;
}
// intentional fallthrough
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;
continue;
}
default:
break;
}
}
bottom:
return original_len - len;
}
void SpdyFramer::Reset() {
state_ = SPDY_RESET;
error_code_ = SPDY_NO_ERROR;
remaining_data_ = 0;
remaining_control_payload_ = 0;
remaining_control_header_ = 0;
current_frame_len_ = 0;
if (current_frame_capacity_ != kControlFrameBufferInitialSize) {
delete [] current_frame_buffer_;
current_frame_buffer_ = 0;
current_frame_capacity_ = 0;
ExpandControlFrameBuffer(kControlFrameBufferInitialSize);
}
}
bool SpdyFramer::ParseHeaderBlock(const SpdyFrame* frame,
SpdyHeaderBlock* block) {
SpdyControlFrame control_frame(frame->data(), false);
uint32 type = control_frame.type();
if (type != SYN_STREAM && type != SYN_REPLY && type != HEADERS)
return false;
// Find the header data within the control frame.
scoped_ptr<SpdyFrame> decompressed_frame(DecompressFrame(*frame));
if (!decompressed_frame.get())
return false;
const char *header_data = NULL;
int header_length = 0;
switch (type) {
case SYN_STREAM:
{
SpdySynStreamControlFrame syn_frame(decompressed_frame->data(), false);
header_data = syn_frame.header_block();
header_length = syn_frame.header_block_len();
}
break;
case SYN_REPLY:
{
SpdySynReplyControlFrame syn_frame(decompressed_frame->data(), false);
header_data = syn_frame.header_block();
header_length = syn_frame.header_block_len();
}
break;
case HEADERS:
{
SpdyHeadersControlFrame header_frame(decompressed_frame->data(), false);
header_data = header_frame.header_block();
header_length = header_frame.header_block_len();
}
break;
}
SpdyFrameBuilder builder(header_data, header_length);
void* iter = NULL;
uint16 num_headers;
if (builder.ReadUInt16(&iter, &num_headers)) {
int index;
for (index = 0; index < num_headers; ++index) {
std::string name;
std::string value;
if (!builder.ReadString(&iter, &name))
break;
if (!builder.ReadString(&iter, &value))
break;
if (!name.size() || !value.size())
return false;
if (block->find(name) == block->end()) {
(*block)[name] = value;
} else {
return false;
}
}
return index == num_headers &&
iter == header_data + header_length;
}
return false;
}
size_t SpdyFramer::UpdateCurrentFrameBuffer(const char** data, size_t* len,
size_t max_bytes) {
size_t bytes_to_read = std::min(*len, max_bytes);
DCHECK_GE(current_frame_capacity_, current_frame_len_ + bytes_to_read);
memcpy(¤t_frame_buffer_[current_frame_len_], *data, bytes_to_read);
current_frame_len_ += bytes_to_read;
*data += bytes_to_read;
*len -= bytes_to_read;
return bytes_to_read;
}
size_t SpdyFramer::ProcessControlFrameBeforeHeaderBlock(const char* data,
size_t len) {
DCHECK_EQ(SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK, state_);
DCHECK_GT(remaining_control_header_, 0u);
size_t original_len = len;
if (remaining_control_header_) {
size_t bytes_read = UpdateCurrentFrameBuffer(&data, &len,
remaining_control_header_);
remaining_control_header_ -= bytes_read;
if (remaining_control_header_ == 0) {
SpdyControlFrame control_frame(current_frame_buffer_, false);
DCHECK(control_frame.type() == SYN_STREAM ||
control_frame.type() == SYN_REPLY ||
control_frame.type() == HEADERS);
visitor_->OnControl(&control_frame);
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::NewProcessControlFrameHeaderBlock(const char* data,
size_t data_len) {
DCHECK_EQ(SPDY_CONTROL_FRAME_HEADER_BLOCK, state_);
SpdyControlFrame control_frame(current_frame_buffer_, false);
bool processed_successfully = true;
DCHECK(control_frame.type() == SYN_STREAM ||
control_frame.type() == SYN_REPLY ||
control_frame.type() == HEADERS);
size_t process_bytes = std::min(data_len, remaining_control_payload_);
DCHECK_GT(process_bytes, 0u);
if (enable_compression_) {
processed_successfully = IncrementallyDecompressControlFrameHeaderData(
&control_frame, data, process_bytes);
} else {
processed_successfully = IncrementallyDeliverControlFrameHeaderData(
&control_frame, data, process_bytes);
}
remaining_control_payload_ -= process_bytes;
// Handle the case that there is no futher data in this frame.
if (remaining_control_payload_ == 0 && processed_successfully) {
// The complete header block has been delivered. We send a zero-length
// OnControlFrameHeaderData() to indicate this.
visitor_->OnControlFrameHeaderData(
GetControlFrameStreamId(&control_frame), NULL, 0);
// If this is a FIN, tell the caller.
if (control_frame.flags() & CONTROL_FLAG_FIN) {
visitor_->OnStreamFrameData(GetControlFrameStreamId(&control_frame),
NULL, 0);
}
CHANGE_STATE(SPDY_RESET);
}
// Handle error.
if (!processed_successfully) {
return data_len;
}
// Return amount processed.
return process_bytes;
}
size_t SpdyFramer::ProcessControlFrameHeaderBlock(const char* data,
size_t data_len) {
DCHECK_EQ(SPDY_CONTROL_FRAME_HEADER_BLOCK, state_);
size_t original_data_len = data_len;
SpdyControlFrame control_frame(current_frame_buffer_, false);
bool read_successfully = true;
DCHECK(control_frame.type() == SYN_STREAM ||
control_frame.type() == SYN_REPLY ||
control_frame.type() == HEADERS);
if (enable_compression_) {
// Note that the header block is held in the frame's payload, and is not
// part of the frame's headers.
if (remaining_control_payload_ > 0) {
size_t bytes_read = UpdateCurrentFrameBuffer(
&data,
&data_len,
remaining_control_payload_);
remaining_control_payload_ -= bytes_read;
if (remaining_control_payload_ == 0) {
read_successfully = IncrementallyDecompressControlFrameHeaderData(
&control_frame);
}
}
} else {
size_t bytes_to_send = std::min(data_len, remaining_control_payload_);
DCHECK_GT(bytes_to_send, 0u);
read_successfully = IncrementallyDeliverControlFrameHeaderData(
&control_frame, data, bytes_to_send);
data_len -= bytes_to_send;
remaining_control_payload_ -= bytes_to_send;
}
if (remaining_control_payload_ == 0 && read_successfully) {
// The complete header block has been delivered.
visitor_->OnControlFrameHeaderData(GetControlFrameStreamId(&control_frame),
NULL, 0);
// If this is a FIN, tell the caller.
if (control_frame.flags() & CONTROL_FLAG_FIN) {
visitor_->OnStreamFrameData(GetControlFrameStreamId(&control_frame),
NULL, 0);
}
CHANGE_STATE(SPDY_RESET);
}
if (!read_successfully) {
return original_data_len;
}
return original_data_len - data_len;
}
/* static */
bool SpdyFramer::ParseHeaderBlockInBuffer(const char* header_data,
size_t header_length,
SpdyHeaderBlock* block) {
SpdyFrameBuilder builder(header_data, header_length);
void* iter = NULL;
uint16 num_headers;
if (builder.ReadUInt16(&iter, &num_headers)) {
for (int index = 0; index < num_headers; ++index) {
std::string name;
std::string value;
if (!builder.ReadString(&iter, &name))
return false;
if (!builder.ReadString(&iter, &value))
return false;
if (block->find(name) == block->end()) {
(*block)[name] = value;
} else {
return false;
}
}
return true;
}
return false;
}
SpdySynStreamControlFrame* SpdyFramer::CreateSynStream(
SpdyStreamId stream_id, SpdyStreamId associated_stream_id, int priority,
SpdyControlFlags flags, bool compressed, const SpdyHeaderBlock* headers) {
SpdyFrameBuilder frame;
DCHECK_GT(stream_id, static_cast<SpdyStreamId>(0));
DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
DCHECK_EQ(0u, associated_stream_id & ~kStreamIdMask);
frame.WriteUInt16(kControlFlagMask | spdy_version_);
frame.WriteUInt16(SYN_STREAM);
frame.WriteUInt32(0); // Placeholder for the length and flags
frame.WriteUInt32(stream_id);
frame.WriteUInt32(associated_stream_id);
frame.WriteUInt16(ntohs(priority) << 6); // Priority.
frame.WriteUInt16(headers->size()); // Number of headers.
SpdyHeaderBlock::const_iterator it;
for (it = headers->begin(); it != headers->end(); ++it) {
bool wrote_header;
wrote_header = frame.WriteString(it->first);
wrote_header &= frame.WriteString(it->second);
DCHECK(wrote_header);
}
// Write the length and flags.
size_t length = frame.length() - SpdyFrame::size();
DCHECK_EQ(0u, length & ~static_cast<size_t>(kLengthMask));
FlagsAndLength flags_length;
flags_length.length_ = htonl(static_cast<uint32>(length));
DCHECK_EQ(0, flags & ~kControlFlagsMask);
flags_length.flags_[0] = flags;
frame.WriteBytesToOffset(4, &flags_length, sizeof(flags_length));
scoped_ptr<SpdySynStreamControlFrame> syn_frame(
reinterpret_cast<SpdySynStreamControlFrame*>(frame.take()));
if (compressed) {
return reinterpret_cast<SpdySynStreamControlFrame*>(
CompressControlFrame(*syn_frame.get()));
}
return syn_frame.release();
}
SpdySynReplyControlFrame* SpdyFramer::CreateSynReply(SpdyStreamId stream_id,
SpdyControlFlags flags, bool compressed, const SpdyHeaderBlock* headers) {
DCHECK_GT(stream_id, 0u);
DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
SpdyFrameBuilder frame;
frame.WriteUInt16(kControlFlagMask | spdy_version_);
frame.WriteUInt16(SYN_REPLY);
frame.WriteUInt32(0); // Placeholder for the length and flags.
frame.WriteUInt32(stream_id);
frame.WriteUInt16(0); // Unused
frame.WriteUInt16(headers->size()); // Number of headers.
SpdyHeaderBlock::const_iterator it;
for (it = headers->begin(); it != headers->end(); ++it) {
bool wrote_header;
wrote_header = frame.WriteString(it->first);
wrote_header &= frame.WriteString(it->second);
DCHECK(wrote_header);
}
// Write the length and flags.
size_t length = frame.length() - SpdyFrame::size();
DCHECK_EQ(0u, length & ~static_cast<size_t>(kLengthMask));
FlagsAndLength flags_length;
flags_length.length_ = htonl(static_cast<uint32>(length));
DCHECK_EQ(0, flags & ~kControlFlagsMask);
flags_length.flags_[0] = flags;
frame.WriteBytesToOffset(4, &flags_length, sizeof(flags_length));
scoped_ptr<SpdySynReplyControlFrame> reply_frame(
reinterpret_cast<SpdySynReplyControlFrame*>(frame.take()));
if (compressed) {
return reinterpret_cast<SpdySynReplyControlFrame*>(
CompressControlFrame(*reply_frame.get()));
}
return reply_frame.release();
}
/* static */
SpdyRstStreamControlFrame* SpdyFramer::CreateRstStream(SpdyStreamId stream_id,
SpdyStatusCodes status) {
DCHECK_GT(stream_id, 0u);
DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
DCHECK_NE(status, INVALID);
DCHECK_LT(status, NUM_STATUS_CODES);
SpdyFrameBuilder frame;
frame.WriteUInt16(kControlFlagMask | spdy_version_);
frame.WriteUInt16(RST_STREAM);
frame.WriteUInt32(8);
frame.WriteUInt32(stream_id);
frame.WriteUInt32(status);
return reinterpret_cast<SpdyRstStreamControlFrame*>(frame.take());
}
/* static */
SpdySettingsControlFrame* SpdyFramer::CreateSettings(
const SpdySettings& values) {
SpdyFrameBuilder frame;
frame.WriteUInt16(kControlFlagMask | spdy_version_);
frame.WriteUInt16(SETTINGS);
size_t settings_size = SpdySettingsControlFrame::size() - SpdyFrame::size() +
8 * values.size();
frame.WriteUInt32(settings_size);
frame.WriteUInt32(values.size());
SpdySettings::const_iterator it = values.begin();
while (it != values.end()) {
frame.WriteUInt32(it->first.id_);
frame.WriteUInt32(it->second);
++it;
}
return reinterpret_cast<SpdySettingsControlFrame*>(frame.take());
}
/* static */
SpdyNoOpControlFrame* SpdyFramer::CreateNopFrame() {
SpdyFrameBuilder frame;
frame.WriteUInt16(kControlFlagMask | spdy_version_);
frame.WriteUInt16(NOOP);
frame.WriteUInt32(0);
return reinterpret_cast<SpdyNoOpControlFrame*>(frame.take());
}
/* static */
SpdyPingControlFrame* SpdyFramer::CreatePingFrame(uint32 unique_id) {
SpdyFrameBuilder frame;
frame.WriteUInt16(kControlFlagMask | kSpdyProtocolVersion);
frame.WriteUInt16(PING);
size_t ping_size = SpdyPingControlFrame::size() - SpdyFrame::size();
frame.WriteUInt32(ping_size);
frame.WriteUInt32(unique_id);
return reinterpret_cast<SpdyPingControlFrame*>(frame.take());
}
/* static */
SpdyGoAwayControlFrame* SpdyFramer::CreateGoAway(
SpdyStreamId last_accepted_stream_id) {
DCHECK_EQ(0u, last_accepted_stream_id & ~kStreamIdMask);
SpdyFrameBuilder frame;
frame.WriteUInt16(kControlFlagMask | spdy_version_);
frame.WriteUInt16(GOAWAY);
size_t go_away_size = SpdyGoAwayControlFrame::size() - SpdyFrame::size();
frame.WriteUInt32(go_away_size);
frame.WriteUInt32(last_accepted_stream_id);
return reinterpret_cast<SpdyGoAwayControlFrame*>(frame.take());
}
SpdyHeadersControlFrame* SpdyFramer::CreateHeaders(SpdyStreamId stream_id,
SpdyControlFlags flags, bool compressed, const SpdyHeaderBlock* headers) {
// Basically the same as CreateSynReply().
DCHECK_GT(stream_id, 0u);
DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
SpdyFrameBuilder frame;
frame.WriteUInt16(kControlFlagMask | kSpdyProtocolVersion);
frame.WriteUInt16(HEADERS);
frame.WriteUInt32(0); // Placeholder for the length and flags.
frame.WriteUInt32(stream_id);
frame.WriteUInt16(0); // Unused
frame.WriteUInt16(headers->size()); // Number of headers.
SpdyHeaderBlock::const_iterator it;
for (it = headers->begin(); it != headers->end(); ++it) {
bool wrote_header;
wrote_header = frame.WriteString(it->first);
wrote_header &= frame.WriteString(it->second);
DCHECK(wrote_header);
}
// Write the length and flags.
size_t length = frame.length() - SpdyFrame::size();
DCHECK_EQ(0u, length & ~static_cast<size_t>(kLengthMask));
FlagsAndLength flags_length;
flags_length.length_ = htonl(static_cast<uint32>(length));
DCHECK_EQ(0, flags & ~kControlFlagsMask);
flags_length.flags_[0] = flags;
frame.WriteBytesToOffset(4, &flags_length, sizeof(flags_length));
scoped_ptr<SpdyHeadersControlFrame> headers_frame(
reinterpret_cast<SpdyHeadersControlFrame*>(frame.take()));
if (compressed) {
return reinterpret_cast<SpdyHeadersControlFrame*>(
CompressControlFrame(*headers_frame.get()));
}
return headers_frame.release();
}
/* static */
SpdyWindowUpdateControlFrame* SpdyFramer::CreateWindowUpdate(
SpdyStreamId stream_id,
uint32 delta_window_size) {
DCHECK_GT(stream_id, 0u);
DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
DCHECK_GT(delta_window_size, 0u);
DCHECK_LE(delta_window_size, spdy::kSpdyStreamMaximumWindowSize);
SpdyFrameBuilder frame;
frame.WriteUInt16(kControlFlagMask | spdy_version_);
frame.WriteUInt16(WINDOW_UPDATE);
size_t window_update_size = SpdyWindowUpdateControlFrame::size() -
SpdyFrame::size();
frame.WriteUInt32(window_update_size);
frame.WriteUInt32(stream_id);
frame.WriteUInt32(delta_window_size);
return reinterpret_cast<SpdyWindowUpdateControlFrame*>(frame.take());
}
/* static */
bool SpdyFramer::ParseSettings(const SpdySettingsControlFrame* frame,
SpdySettings* settings) {
DCHECK_EQ(frame->type(), SETTINGS);
DCHECK(settings);
SpdyFrameBuilder parser(frame->header_block(), frame->header_block_len());
void* iter = NULL;
for (size_t index = 0; index < frame->num_entries(); ++index) {
uint32 id;
uint32 value;
if (!parser.ReadUInt32(&iter, &id))
return false;
if (!parser.ReadUInt32(&iter, &value))
return false;
settings->insert(settings->end(), std::make_pair(id, value));
}
return true;
}
SpdyDataFrame* SpdyFramer::CreateDataFrame(SpdyStreamId stream_id,
const char* data,
uint32 len, SpdyDataFlags flags) {
SpdyFrameBuilder frame;
DCHECK_GT(stream_id, 0u);
DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
frame.WriteUInt32(stream_id);
DCHECK_EQ(0u, len & ~static_cast<size_t>(kLengthMask));
FlagsAndLength flags_length;
flags_length.length_ = htonl(len);
DCHECK_EQ(0, flags & ~kDataFlagsMask);
flags_length.flags_[0] = flags;
frame.WriteBytes(&flags_length, sizeof(flags_length));
frame.WriteBytes(data, len);
scoped_ptr<SpdyFrame> data_frame(frame.take());
SpdyDataFrame* rv;
if (flags & DATA_FLAG_COMPRESSED) {
rv = reinterpret_cast<SpdyDataFrame*>(CompressFrame(*data_frame.get()));
} else {
rv = reinterpret_cast<SpdyDataFrame*>(data_frame.release());
}
if (flags & DATA_FLAG_FIN) {
CleanupCompressorForStream(stream_id);
}
return rv;
}
SpdyFrame* SpdyFramer::CompressFrame(const SpdyFrame& frame) {
if (frame.is_control_frame()) {
return CompressControlFrame(
reinterpret_cast<const SpdyControlFrame&>(frame));
}
return CompressDataFrame(reinterpret_cast<const SpdyDataFrame&>(frame));
}
SpdyFrame* SpdyFramer::DecompressFrame(const SpdyFrame& frame) {
if (frame.is_control_frame()) {
return DecompressControlFrame(
reinterpret_cast<const SpdyControlFrame&>(frame));
}
return DecompressDataFrame(reinterpret_cast<const SpdyDataFrame&>(frame));
}
SpdyFrame* SpdyFramer::DuplicateFrame(const SpdyFrame& frame) {
int size = SpdyFrame::size() + frame.length();
SpdyFrame* new_frame = new SpdyFrame(size);
memcpy(new_frame->data(), frame.data(), size);
return new_frame;
}
bool SpdyFramer::IsCompressible(const SpdyFrame& frame) const {
// The important frames to compress are those which contain large
// amounts of compressible data - namely the headers in the SYN_STREAM
// and SYN_REPLY.
// TODO(mbelshe): Reconcile this with the spec when the spec is
// explicit about which frames compress and which do not.
if (frame.is_control_frame()) {
const SpdyControlFrame& control_frame =
reinterpret_cast<const SpdyControlFrame&>(frame);
return control_frame.type() == SYN_STREAM ||
control_frame.type() == SYN_REPLY;
}
const SpdyDataFrame& data_frame =
reinterpret_cast<const SpdyDataFrame&>(frame);
return (data_frame.flags() & DATA_FLAG_COMPRESSED) != 0;
}
const char* SpdyFramer::StateToString(int state) {
switch (state) {
case SPDY_ERROR:
return "ERROR";
case SPDY_DONE:
return "DONE";
case SPDY_AUTO_RESET:
return "AUTO_RESET";
case SPDY_RESET:
return "RESET";
case SPDY_READING_COMMON_HEADER:
return "READING_COMMON_HEADER";
case SPDY_INTERPRET_CONTROL_FRAME_COMMON_HEADER:
return "INTERPRET_CONTROL_FRAME_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";
}
return "UNKNOWN_STATE";
}
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";
}
return "UNKNOWN_ERROR";
}
void SpdyFramer::set_enable_compression(bool value) {
enable_compression_ = value;
}
void SpdyFramer::set_enable_compression_default(bool value) {
compression_default_ = value;
}
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;
SpdyFrame current_frame(current_frame_buffer_, false);
do {
if (current_frame_len_ < SpdyFrame::size()) {
size_t bytes_desired = SpdyFrame::size() - current_frame_len_;
UpdateCurrentFrameBuffer(&data, &len, bytes_desired);
// Check for an empty data frame.
if (current_frame_len_ == SpdyFrame::size() &&
!current_frame.is_control_frame() &&
current_frame.length() == 0) {
if (current_frame.flags() & CONTROL_FLAG_FIN) {
SpdyDataFrame data_frame(current_frame_buffer_, false);
visitor_->OnStreamFrameData(data_frame.stream_id(), NULL, 0);
}
CHANGE_STATE(SPDY_AUTO_RESET);
}
break;
}
remaining_data_ = current_frame.length();
// This is just a sanity check for help debugging early frame errors.
if (remaining_data_ > 1000000u) {
LOG(WARNING) <<
"Unexpectedly large frame. Spdy session is likely corrupt.";
}
// if we're here, then we have the common header all received.
if (!current_frame.is_control_frame())
CHANGE_STATE(SPDY_FORWARD_STREAM_FRAME);
else
CHANGE_STATE(SPDY_INTERPRET_CONTROL_FRAME_COMMON_HEADER);
} while (false);
return original_len - len;
}
void SpdyFramer::ProcessControlFrameHeader() {
DCHECK_EQ(SPDY_NO_ERROR, error_code_);
DCHECK_LE(SpdyFrame::size(), current_frame_len_);
SpdyControlFrame current_control_frame(current_frame_buffer_, false);
// We check version before we check validity: version can never be 'invalid',
// it can only be unsupported.
if (current_control_frame.version() != spdy_version_) {
set_error(SPDY_UNSUPPORTED_VERSION);
return;
}
// Next up, check to see if we have valid data. This should be after version
// checking (otherwise if the the type were out of bounds due to a version
// upgrade we would misclassify the error) and before checking the type
// (type can definitely be out of bounds)
if (!current_control_frame.AppearsToBeAValidControlFrame()) {
set_error(SPDY_INVALID_CONTROL_FRAME);
return;
}
// Do some sanity checking on the control frame sizes.
switch (current_control_frame.type()) {
case SYN_STREAM:
if (current_control_frame.length() <
SpdySynStreamControlFrame::size() - SpdyControlFrame::size())
set_error(SPDY_INVALID_CONTROL_FRAME);
break;
case SYN_REPLY:
if (current_control_frame.length() <
SpdySynReplyControlFrame::size() - SpdyControlFrame::size())
set_error(SPDY_INVALID_CONTROL_FRAME);
break;
case RST_STREAM:
if (current_control_frame.length() !=
SpdyRstStreamControlFrame::size() - SpdyFrame::size())
set_error(SPDY_INVALID_CONTROL_FRAME);
break;
case SETTINGS:
if (current_control_frame.length() <
SpdySettingsControlFrame::size() - SpdyControlFrame::size())
set_error(SPDY_INVALID_CONTROL_FRAME);
break;
// TODO(hkhalil): Remove NOOP.
case NOOP:
// NOOP. Swallow it.
DLOG(INFO) << "Attempted frame size validation for NOOP. Resetting.";
CHANGE_STATE(SPDY_AUTO_RESET);
return;
case GOAWAY:
if (current_control_frame.length() !=
SpdyGoAwayControlFrame::size() - SpdyFrame::size())
set_error(SPDY_INVALID_CONTROL_FRAME);
break;
case HEADERS:
if (current_control_frame.length() <
SpdyHeadersControlFrame::size() - SpdyControlFrame::size())
set_error(SPDY_INVALID_CONTROL_FRAME);
break;
case WINDOW_UPDATE:
if (current_control_frame.length() !=
SpdyWindowUpdateControlFrame::size() - SpdyControlFrame::size())
set_error(SPDY_INVALID_CONTROL_FRAME);
break;
case PING:
if (current_control_frame.length() !=
SpdyPingControlFrame::size() - SpdyControlFrame::size())
set_error(SPDY_INVALID_CONTROL_FRAME);
break;
default:
LOG(WARNING) << "Valid spdy control frame with unhandled type: "
<< current_control_frame.type();
DCHECK(false);
set_error(SPDY_INVALID_CONTROL_FRAME);
break;
}
remaining_control_payload_ = current_control_frame.length();
if (remaining_control_payload_ > kControlFrameBufferMaxSize) {
set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE);
return;
}
ExpandControlFrameBuffer(remaining_control_payload_);
CHANGE_STATE(SPDY_CONTROL_FRAME_PAYLOAD);
}
size_t SpdyFramer::ProcessControlFramePayload(const char* data, size_t len) {
size_t original_len = len;
do {
if (remaining_control_payload_) {
size_t bytes_read = UpdateCurrentFrameBuffer(&data, &len,
remaining_control_payload_);
remaining_control_payload_ -= bytes_read;
remaining_data_ -= bytes_read;
if (remaining_control_payload_)
break;
}
SpdyControlFrame control_frame(current_frame_buffer_, false);
visitor_->OnControl(&control_frame);
// If this is a FIN, tell the caller.
if (control_frame.type() == SYN_REPLY &&
control_frame.flags() & CONTROL_FLAG_FIN) {
visitor_->OnStreamFrameData(reinterpret_cast<SpdySynReplyControlFrame*>(
&control_frame)->stream_id(),
NULL, 0);
}
CHANGE_STATE(SPDY_IGNORE_REMAINING_PAYLOAD);
} while (false);
return original_len - len;
}
size_t SpdyFramer::ProcessDataFramePayload(const char* data, size_t len) {
size_t original_len = len;
SpdyDataFrame current_data_frame(current_frame_buffer_, false);
if (remaining_data_) {
size_t amount_to_forward = std::min(remaining_data_, len);
if (amount_to_forward && state_ != SPDY_IGNORE_REMAINING_PAYLOAD) {
if (current_data_frame.flags() & DATA_FLAG_COMPRESSED) {
z_stream* decompressor =
GetStreamDecompressor(current_data_frame.stream_id());
if (!decompressor)
return 0;
size_t decompressed_max_size = amount_to_forward * 100;
scoped_array<char> decompressed(new char[decompressed_max_size]);
decompressor->next_in = reinterpret_cast<Bytef*>(
const_cast<char*>(data));
decompressor->avail_in = amount_to_forward;
decompressor->next_out =
reinterpret_cast<Bytef*>(decompressed.get());
decompressor->avail_out = decompressed_max_size;
int rv = inflate(decompressor, Z_SYNC_FLUSH);
if (rv != Z_OK) {
LOG(WARNING) << "inflate failure: " << rv;
set_error(SPDY_DECOMPRESS_FAILURE);
return 0;
}
size_t decompressed_size = decompressed_max_size -
decompressor->avail_out;
// Only inform the visitor if there is data.
if (decompressed_size)
visitor_->OnStreamFrameData(current_data_frame.stream_id(),
decompressed.get(),
decompressed_size);
amount_to_forward -= decompressor->avail_in;
} else {
// The data frame was not compressed.
// Only inform the visitor if there is data.
if (amount_to_forward)
visitor_->OnStreamFrameData(current_data_frame.stream_id(),
data, amount_to_forward);
}
}
data += amount_to_forward;
len -= amount_to_forward;
remaining_data_ -= 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_ &&
current_data_frame.flags() & DATA_FLAG_FIN) {
visitor_->OnStreamFrameData(current_data_frame.stream_id(), NULL, 0);
CleanupDecompressorForStream(current_data_frame.stream_id());
}
} else {
CHANGE_STATE(SPDY_AUTO_RESET);
}
return original_len - len;
}
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)
success = deflateSetDictionary(header_compressor_.get(),
reinterpret_cast<const Bytef*>(kDictionary),
kDictionarySize);
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));
// Compute the id of our dictionary so that we know we're using the
// right one when asked for it.
if (dictionary_id == 0) {
dictionary_id = adler32(0L, Z_NULL, 0);
dictionary_id = adler32(dictionary_id,
reinterpret_cast<const Bytef*>(kDictionary),
kDictionarySize);
}
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();
}
z_stream* SpdyFramer::GetStreamCompressor(SpdyStreamId stream_id) {
CompressorMap::iterator it = stream_compressors_.find(stream_id);
if (it != stream_compressors_.end())
return it->second; // Already initialized.
scoped_ptr<z_stream> compressor(new z_stream);
memset(compressor.get(), 0, sizeof(z_stream));
int success = deflateInit2(compressor.get(),
kCompressorLevel,
Z_DEFLATED,
kCompressorWindowSizeInBits,
kCompressorMemLevel,
Z_DEFAULT_STRATEGY);
if (success != Z_OK) {
LOG(WARNING) << "deflateInit failure: " << success;
return NULL;
}
return stream_compressors_[stream_id] = compressor.release();
}
z_stream* SpdyFramer::GetStreamDecompressor(SpdyStreamId stream_id) {
CompressorMap::iterator it = stream_decompressors_.find(stream_id);
if (it != stream_decompressors_.end())
return it->second; // Already initialized.
scoped_ptr<z_stream> decompressor(new z_stream);
memset(decompressor.get(), 0, sizeof(z_stream));
int success = inflateInit(decompressor.get());
if (success != Z_OK) {
LOG(WARNING) << "inflateInit failure: " << success;
return NULL;
}
return stream_decompressors_[stream_id] = decompressor.release();
}
SpdyControlFrame* SpdyFramer::CompressControlFrame(
const SpdyControlFrame& frame) {
z_stream* compressor = GetHeaderCompressor();
if (!compressor)
return NULL;
return reinterpret_cast<SpdyControlFrame*>(
CompressFrameWithZStream(frame, compressor));
}
SpdyDataFrame* SpdyFramer::CompressDataFrame(const SpdyDataFrame& frame) {
z_stream* compressor = GetStreamCompressor(frame.stream_id());
if (!compressor)
return NULL;
return reinterpret_cast<SpdyDataFrame*>(
CompressFrameWithZStream(frame, compressor));
}
SpdyControlFrame* SpdyFramer::DecompressControlFrame(
const SpdyControlFrame& frame) {
z_stream* decompressor = GetHeaderDecompressor();
if (!decompressor)
return NULL;
return reinterpret_cast<SpdyControlFrame*>(
DecompressFrameWithZStream(frame, decompressor));
}
// 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(
const SpdyControlFrame* control_frame) {
z_stream* decomp = GetHeaderDecompressor();
int payload_length;
int header_length;
const char* payload;
bool read_successfully = true;
bool more = true;
char buffer[kHeaderDataChunkMaxSize];
if (!GetFrameBoundaries(
*control_frame, &payload_length, &header_length, &payload)) {
DLOG(ERROR) << "Control frame of type "
<< SpdyFramer::ControlTypeToString(control_frame->type())
<<" doesn't have headers";
return false;
}
decomp->next_in = reinterpret_cast<Bytef*>(const_cast<char*>(payload));
decomp->avail_in = payload_length;
const SpdyStreamId stream_id = GetControlFrameStreamId(control_frame);
DCHECK_LT(0u, stream_id);
while (more && read_successfully) {
decomp->next_out = reinterpret_cast<Bytef*>(buffer);
decomp->avail_out = arraysize(buffer);
int rv = DecompressHeaderBlockInZStream(decomp);
if (rv != Z_OK) {
set_error(SPDY_DECOMPRESS_FAILURE);
DLOG(WARNING) << "inflate failure: " << rv;
more = read_successfully = false;
} else {
DCHECK_GT(arraysize(buffer), decomp->avail_out);
size_t len = arraysize(buffer) - decomp->avail_out;
read_successfully = visitor_->OnControlFrameHeaderData(stream_id, buffer,
len);
if (!read_successfully) {
// Assume that the problem was the header block was too large for the
// visitor.
set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE);
}
more = decomp->avail_in > 0;
}
}
return read_successfully;
}
// 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(
const SpdyControlFrame* control_frame,
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;
const SpdyStreamId stream_id = GetControlFrameStreamId(control_frame);
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 = DecompressHeaderBlockInZStream(decomp);
if (rv != Z_OK) {
set_error(SPDY_DECOMPRESS_FAILURE);
DLOG(WARNING) << "inflate failure: " << rv;
processed_successfully = false;
} else {
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);
}
}
}
return processed_successfully;
}
bool SpdyFramer::IncrementallyDeliverControlFrameHeaderData(
const SpdyControlFrame* control_frame, const char* data, size_t len) {
bool read_successfully = true;
const SpdyStreamId stream_id = GetControlFrameStreamId(control_frame);
DCHECK_LT(0u, stream_id);
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;
}
size_t SpdyFramer::GetMinimumControlFrameSize(SpdyControlType type) {
switch (type) {
case SYN_STREAM:
return SpdySynStreamControlFrame::size();
case SYN_REPLY:
return SpdySynReplyControlFrame::size();
case RST_STREAM:
return SpdyRstStreamControlFrame::size();
case SETTINGS:
return SpdySettingsControlFrame::size();
case NOOP:
return SpdyNoOpControlFrame::size();
case PING:
return SpdyPingControlFrame::size();
case GOAWAY:
return SpdyGoAwayControlFrame::size();
case HEADERS:
return SpdyHeadersControlFrame::size();
case WINDOW_UPDATE:
return SpdyWindowUpdateControlFrame::size();
case NUM_CONTROL_FRAME_TYPES:
break;
}
LOG(ERROR) << "Unknown SPDY control frame type " << type;
return 0x7FFFFFFF; // Max signed 32bit int
}
/* static */
SpdyStreamId SpdyFramer::GetControlFrameStreamId(
const SpdyControlFrame* control_frame) {
SpdyStreamId stream_id = kInvalidStream;
if (control_frame != NULL) {
switch (control_frame->type()) {
case SYN_STREAM:
stream_id = reinterpret_cast<const SpdySynStreamControlFrame*>(
control_frame)->stream_id();
break;
case SYN_REPLY:
stream_id = reinterpret_cast<const SpdySynReplyControlFrame*>(
control_frame)->stream_id();
break;
case HEADERS:
stream_id = reinterpret_cast<const SpdyHeadersControlFrame*>(
control_frame)->stream_id();
break;
case RST_STREAM:
stream_id = reinterpret_cast<const SpdyRstStreamControlFrame*>(
control_frame)->stream_id();
break;
case WINDOW_UPDATE:
stream_id = reinterpret_cast<const SpdyWindowUpdateControlFrame*>(
control_frame)->stream_id();
break;
// All of the following types are not part of a particular stream.
// They all fall through to the invalid control frame type case.
// (The default case isn't used so that the compile will break if a new
// control frame type is added but not included here.)
case SETTINGS:
case NOOP:
case PING:
case GOAWAY:
case NUM_CONTROL_FRAME_TYPES: // makes compiler happy
break;
}
}
return stream_id;
}
void SpdyFramer::set_validate_control_frame_sizes(bool value) {
validate_control_frame_sizes_ = value;
}
SpdyDataFrame* SpdyFramer::DecompressDataFrame(const SpdyDataFrame& frame) {
z_stream* decompressor = GetStreamDecompressor(frame.stream_id());
if (!decompressor)
return NULL;
return reinterpret_cast<SpdyDataFrame*>(
DecompressFrameWithZStream(frame, decompressor));
}
SpdyFrame* SpdyFramer::CompressFrameWithZStream(const SpdyFrame& frame,
z_stream* compressor) {
int payload_length;
int header_length;
const char* payload;
base::StatsCounter compressed_frames("spdy.CompressedFrames");
base::StatsCounter pre_compress_bytes("spdy.PreCompressSize");
base::StatsCounter post_compress_bytes("spdy.PostCompressSize");
if (!enable_compression_)
return DuplicateFrame(frame);
if (!GetFrameBoundaries(frame, &payload_length, &header_length, &payload))
return NULL;
// Create an output frame.
int compressed_max_size = deflateBound(compressor, payload_length);
int new_frame_size = header_length + compressed_max_size;
scoped_ptr<SpdyFrame> new_frame(new SpdyFrame(new_frame_size));
memcpy(new_frame->data(), frame.data(), frame.length() + SpdyFrame::size());
compressor->next_in = reinterpret_cast<Bytef*>(const_cast<char*>(payload));
compressor->avail_in = payload_length;
compressor->next_out = reinterpret_cast<Bytef*>(new_frame->data()) +
header_length;
compressor->avail_out = compressed_max_size;
// Data packets have a 'compressed' flag.
if (!new_frame->is_control_frame()) {
SpdyDataFrame* data_frame =
reinterpret_cast<SpdyDataFrame*>(new_frame.get());
data_frame->set_flags(data_frame->flags() | DATA_FLAG_COMPRESSED);
}
#ifndef ANDROID
// Make sure that all the data we pass to zlib is defined.
// This way, all Valgrind reports on the compressed data are zlib's fault.
(void)VALGRIND_CHECK_MEM_IS_DEFINED(compressor->next_in,
compressor->avail_in);
#endif
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;
return NULL;
}
int compressed_size = compressed_max_size - compressor->avail_out;
#ifndef ANDROID
// We trust zlib. Also, we can't do anything about it.
// See http://www.zlib.net/zlib_faq.html#faq36
(void)VALGRIND_MAKE_MEM_DEFINED(new_frame->data() + header_length,
compressed_size);
#endif
new_frame->set_length(header_length + compressed_size - SpdyFrame::size());
pre_compress_bytes.Add(payload_length);
post_compress_bytes.Add(new_frame->length());
compressed_frames.Increment();
return new_frame.release();
}
SpdyFrame* SpdyFramer::DecompressFrameWithZStream(const SpdyFrame& frame,
z_stream* decompressor) {
int payload_length;
int header_length;
const char* payload;
base::StatsCounter decompressed_frames("spdy.DecompressedFrames");
base::StatsCounter pre_decompress_bytes("spdy.PreDeCompressSize");
base::StatsCounter post_decompress_bytes("spdy.PostDeCompressSize");
if (!enable_compression_)
return DuplicateFrame(frame);
if (!GetFrameBoundaries(frame, &payload_length, &header_length, &payload))
return NULL;
if (!frame.is_control_frame()) {
const SpdyDataFrame& data_frame =
reinterpret_cast<const SpdyDataFrame&>(frame);
if ((data_frame.flags() & DATA_FLAG_COMPRESSED) == 0)
return DuplicateFrame(frame);
}
// Create an output frame. Assume it does not need to be longer than
// the input data.
size_t decompressed_max_size = kControlFrameBufferInitialSize;
int new_frame_size = header_length + decompressed_max_size;
if (frame.length() > decompressed_max_size)
return NULL;
scoped_ptr<SpdyFrame> new_frame(new SpdyFrame(new_frame_size));
memcpy(new_frame->data(), frame.data(), frame.length() + SpdyFrame::size());
decompressor->next_in = reinterpret_cast<Bytef*>(const_cast<char*>(payload));
decompressor->avail_in = payload_length;
decompressor->next_out = reinterpret_cast<Bytef*>(new_frame->data()) +
header_length;
decompressor->avail_out = decompressed_max_size;
int rv = inflate(decompressor, Z_SYNC_FLUSH);
if (rv == Z_NEED_DICT) {
// Need to try again with the right dictionary.
if (decompressor->adler == dictionary_id) {
rv = inflateSetDictionary(decompressor, (const Bytef*)kDictionary,
kDictionarySize);
if (rv == Z_OK)
rv = inflate(decompressor, Z_SYNC_FLUSH);
}
}
if (rv != Z_OK) { // How can we know that it decompressed everything?
LOG(WARNING) << "inflate failure: " << rv;
return NULL;
}
// Unset the compressed flag for data frames.
if (!new_frame->is_control_frame()) {
SpdyDataFrame* data_frame =
reinterpret_cast<SpdyDataFrame*>(new_frame.get());
data_frame->set_flags(data_frame->flags() & ~DATA_FLAG_COMPRESSED);
}
int decompressed_size = decompressed_max_size - decompressor->avail_out;
new_frame->set_length(header_length + decompressed_size - SpdyFrame::size());
// If there is data left, then the frame didn't fully decompress. This
// means that there is stranded data at the end of this frame buffer which
// will be ignored.
DCHECK_EQ(decompressor->avail_in, 0u);
pre_decompress_bytes.Add(frame.length());
post_decompress_bytes.Add(new_frame->length());
decompressed_frames.Increment();
return new_frame.release();
}
void SpdyFramer::CleanupCompressorForStream(SpdyStreamId id) {
CompressorMap::iterator it = stream_compressors_.find(id);
if (it != stream_compressors_.end()) {
z_stream* compressor = it->second;
deflateEnd(compressor);
delete compressor;
stream_compressors_.erase(it);
}
}
void SpdyFramer::CleanupDecompressorForStream(SpdyStreamId id) {
CompressorMap::iterator it = stream_decompressors_.find(id);
if (it != stream_decompressors_.end()) {
z_stream* decompressor = it->second;
inflateEnd(decompressor);
delete decompressor;
stream_decompressors_.erase(it);
}
}
void SpdyFramer::CleanupStreamCompressorsAndDecompressors() {
CompressorMap::iterator it;
it = stream_compressors_.begin();
while (it != stream_compressors_.end()) {
z_stream* compressor = it->second;
deflateEnd(compressor);
delete compressor;
++it;
}
stream_compressors_.clear();
it = stream_decompressors_.begin();
while (it != stream_decompressors_.end()) {
z_stream* decompressor = it->second;
inflateEnd(decompressor);
delete decompressor;
++it;
}
stream_decompressors_.clear();
}
size_t SpdyFramer::BytesSafeToRead() const {
switch (state_) {
case SPDY_ERROR:
case SPDY_DONE:
case SPDY_AUTO_RESET:
case SPDY_RESET:
return 0;
case SPDY_READING_COMMON_HEADER:
DCHECK_LT(current_frame_len_, SpdyFrame::size());
return SpdyFrame::size() - current_frame_len_;
case SPDY_INTERPRET_CONTROL_FRAME_COMMON_HEADER:
return 0;
// TODO(rtenneti): Add support for SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK
// and SPDY_CONTROL_FRAME_HEADER_BLOCK.
case SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK:
case SPDY_CONTROL_FRAME_HEADER_BLOCK:
return 0;
case SPDY_CONTROL_FRAME_PAYLOAD:
case SPDY_IGNORE_REMAINING_PAYLOAD:
case SPDY_FORWARD_STREAM_FRAME:
return remaining_data_;
}
// We should never get to here.
return 0;
}
void SpdyFramer::set_error(SpdyError error) {
DCHECK(visitor_);
error_code_ = error;
CHANGE_STATE(SPDY_ERROR);
visitor_->OnError(this);
}
void SpdyFramer::ExpandControlFrameBuffer(size_t size) {
size_t alloc_size = size + SpdyFrame::size();
DCHECK_LE(alloc_size, kControlFrameBufferMaxSize);
if (alloc_size <= current_frame_capacity_)
return;
char* new_buffer = new char[alloc_size];
memcpy(new_buffer, current_frame_buffer_, current_frame_len_);
delete [] current_frame_buffer_;
current_frame_capacity_ = alloc_size;
current_frame_buffer_ = new_buffer;
}
bool SpdyFramer::GetFrameBoundaries(const SpdyFrame& frame,
int* payload_length,
int* header_length,
const char** payload) const {
size_t frame_size;
if (frame.is_control_frame()) {
const SpdyControlFrame& control_frame =
reinterpret_cast<const SpdyControlFrame&>(frame);
switch (control_frame.type()) {
case SYN_STREAM:
{
const SpdySynStreamControlFrame& syn_frame =
reinterpret_cast<const SpdySynStreamControlFrame&>(frame);
frame_size = SpdySynStreamControlFrame::size();
*payload_length = syn_frame.header_block_len();
*header_length = frame_size;
*payload = frame.data() + *header_length;
}
break;
case SYN_REPLY:
{
const SpdySynReplyControlFrame& syn_frame =
reinterpret_cast<const SpdySynReplyControlFrame&>(frame);
frame_size = SpdySynReplyControlFrame::size();
*payload_length = syn_frame.header_block_len();
*header_length = frame_size;
*payload = frame.data() + *header_length;
}
break;
case HEADERS:
{
const SpdyHeadersControlFrame& headers_frame =
reinterpret_cast<const SpdyHeadersControlFrame&>(frame);
frame_size = SpdyHeadersControlFrame::size();
*payload_length = headers_frame.header_block_len();
*header_length = frame_size;
*payload = frame.data() + *header_length;
}
break;
default:
// TODO(mbelshe): set an error?
return false; // We can't compress this frame!
}
} else {
frame_size = SpdyFrame::size();
*header_length = frame_size;
*payload_length = frame.length();
*payload = frame.data() + SpdyFrame::size();
}
return true;
}
} // namespace spdy