blob: 0d9049d52eb0bd498dd138483c705dc7c780e3de [file] [log] [blame]
// 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"
#if defined(USE_SYSTEM_ZLIB)
#include <zlib.h>
#else
#include "third_party/zlib/zlib.h"
#endif
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;
} // namespace
const int SpdyFramer::kMinSpdyVersion = 2;
const int SpdyFramer::kMaxSpdyVersion = 3;
const SpdyStreamId SpdyFramer::kInvalidStream = -1;
const size_t SpdyFramer::kHeaderDataChunkMaxSize = 1024;
const size_t SpdyFramer::kControlFrameBufferSize =
sizeof(SpdySynStreamControlFrameBlock);
const size_t SpdyFramer::kMaxControlFrameSize = 16 * 1024;
#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(int version)
: state_(SPDY_RESET),
previous_state_(SPDY_RESET),
error_code_(SPDY_NO_ERROR),
remaining_data_(0),
remaining_control_payload_(0),
remaining_control_header_(0),
current_frame_buffer_(new char[kControlFrameBufferSize]),
current_frame_len_(0),
enable_compression_(true),
visitor_(NULL),
display_protocol_("SPDY"),
spdy_version_(version),
syn_frame_processed_(false),
probable_http_response_(false) {
DCHECK_GE(kMaxSpdyVersion, version);
DCHECK_LE(kMinSpdyVersion, version);
}
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_ = 0;
remaining_control_payload_ = 0;
remaining_control_header_ = 0;
current_frame_len_ = 0;
settings_scratch_.Reset();
}
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_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";
}
return "UNKNOWN_ERROR";
}
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";
case STREAM_IN_USE:
return "STREAM_IN_USE";
case STREAM_ALREADY_CLOSED:
return "STREAM_ALREADY_CLOSED";
case INVALID_CREDENTIALS:
return "INVALID_CREDENTIALS";
case FRAME_TOO_LARGE:
return "FRAME_TOO_LARGE";
}
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 CREDENTIAL:
return "CREDENTIAL";
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;
do {
previous_state_ = state_;
switch (state_) {
case SPDY_ERROR:
case SPDY_DONE:
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) 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_len_ == 0 &&
remaining_data_ == 0 &&
remaining_control_payload_ == 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;
SpdyFrame current_frame(current_frame_buffer_.get(), false);
// Update current frame buffer as needed.
if (current_frame_len_ < SpdyFrame::kHeaderSize) {
size_t bytes_desired = SpdyFrame::kHeaderSize - current_frame_len_;
UpdateCurrentFrameBuffer(&data, &len, bytes_desired);
}
if (current_frame_len_ < SpdyFrame::kHeaderSize) {
// TODO(rch): remove this empty block
// Do nothing.
} else {
remaining_data_ = current_frame.length();
// This is just a sanity check for help debugging early frame errors.
if (remaining_data_ > 1000000u) {
// The strncmp for 5 is safe because we only hit this point if we
// have SpdyFrame::kHeaderSize (8) bytes
if (!syn_frame_processed_ &&
strncmp(current_frame_buffer_.get(), "HTTP/", 5) == 0) {
LOG(WARNING) << "Unexpected HTTP response to spdy 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 (!current_frame.is_control_frame()) {
SpdyDataFrame data_frame(current_frame_buffer_.get(), false);
visitor_->OnDataFrameHeader(&data_frame);
if (current_frame.length() > 0) {
CHANGE_STATE(SPDY_FORWARD_STREAM_FRAME);
} else {
// Empty data frame.
if (current_frame.flags() & DATA_FLAG_FIN) {
visitor_->OnStreamFrameData(data_frame.stream_id(),
NULL, 0, DATA_FLAG_FIN);
}
CHANGE_STATE(SPDY_AUTO_RESET);
}
} else {
ProcessControlFrameHeader();
}
}
return original_len - len;
}
void SpdyFramer::ProcessControlFrameHeader() {
DCHECK_EQ(SPDY_NO_ERROR, error_code_);
DCHECK_LE(static_cast<size_t>(SpdyFrame::kHeaderSize), current_frame_len_);
SpdyControlFrame current_control_frame(current_frame_buffer_.get(), 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_) {
DLOG(INFO) << "Unsupported SPDY version " << current_control_frame.version()
<< " (expected " << 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;
}
if (current_control_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.
switch (current_control_frame.type()) {
case SYN_STREAM:
if (current_control_frame.length() <
SpdySynStreamControlFrame::size() - SpdyControlFrame::kHeaderSize)
set_error(SPDY_INVALID_CONTROL_FRAME);
break;
case SYN_REPLY:
if (current_control_frame.length() <
SpdySynReplyControlFrame::size() - SpdyControlFrame::kHeaderSize)
set_error(SPDY_INVALID_CONTROL_FRAME);
break;
case RST_STREAM:
if (current_control_frame.length() !=
SpdyRstStreamControlFrame::size() - SpdyFrame::kHeaderSize)
set_error(SPDY_INVALID_CONTROL_FRAME);
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_control_frame.length() <
SpdySettingsControlFrame::size() - SpdyControlFrame::kHeaderSize ||
(current_control_frame.length() % 8 != 4)) {
DLOG(WARNING) << "Invalid length for SETTINGS frame: "
<< current_control_frame.length();
set_error(SPDY_INVALID_CONTROL_FRAME);
}
break;
case GOAWAY:
{
// SPDY 2 GOAWAY frames are 4 bytes smaller than in SPDY 3. We account
// for this difference via a separate offset variable, since
// SpdyGoAwayControlFrame::size() returns the SPDY 3 size.
const size_t goaway_offset = (protocol_version() < 3) ? 4 : 0;
if (current_control_frame.length() + goaway_offset !=
SpdyGoAwayControlFrame::size() - SpdyFrame::kHeaderSize)
set_error(SPDY_INVALID_CONTROL_FRAME);
break;
}
case HEADERS:
if (current_control_frame.length() <
SpdyHeadersControlFrame::size() - SpdyControlFrame::kHeaderSize)
set_error(SPDY_INVALID_CONTROL_FRAME);
break;
case WINDOW_UPDATE:
if (current_control_frame.length() !=
SpdyWindowUpdateControlFrame::size() -
SpdyControlFrame::kHeaderSize)
set_error(SPDY_INVALID_CONTROL_FRAME);
break;
case PING:
if (current_control_frame.length() !=
SpdyPingControlFrame::size() - SpdyControlFrame::kHeaderSize)
set_error(SPDY_INVALID_CONTROL_FRAME);
break;
case CREDENTIAL:
if (current_control_frame.length() <
SpdyCredentialControlFrame::size() - SpdyControlFrame::kHeaderSize)
set_error(SPDY_INVALID_CONTROL_FRAME);
break;
default:
LOG(WARNING) << "Valid " << display_protocol_
<< " control frame with unhandled type: "
<< current_control_frame.type();
DLOG(FATAL);
set_error(SPDY_INVALID_CONTROL_FRAME);
break;
}
if (state_ == SPDY_ERROR) {
return;
}
remaining_control_payload_ = current_control_frame.length();
const size_t total_frame_size =
remaining_control_payload_ + SpdyFrame::kHeaderSize;
if (total_frame_size > kMaxControlFrameSize) {
DLOG(WARNING) << "Received control frame with way too big of a payload: "
<< total_frame_size;
set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE);
return;
}
if (current_control_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_control_frame.type()) {
case SYN_STREAM:
syn_frame_processed_ = true;
frame_size_without_variable_data = SpdySynStreamControlFrame::size();
break;
case SYN_REPLY:
syn_frame_processed_ = true;
frame_size_without_variable_data = SpdySynReplyControlFrame::size();
// SPDY 2 had two bytes of unused space preceeding payload.
if (spdy_version_ < 3) {
frame_size_without_variable_data += 2;
}
break;
case HEADERS:
frame_size_without_variable_data = SpdyHeadersControlFrame::size();
// SPDY 2 had two bytes of unused space preceeding payload.
if (spdy_version_ < 3) {
frame_size_without_variable_data += 2;
}
break;
case SETTINGS:
frame_size_without_variable_data = SpdySettingsControlFrame::size();
break;
default:
frame_size_without_variable_data = -1;
break;
}
if ((frame_size_without_variable_data == -1) &&
(total_frame_size > 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_len_));
remaining_control_header_ = frame_size_without_variable_data -
current_frame_len_;
remaining_control_payload_ += SpdyFrame::kHeaderSize -
frame_size_without_variable_data;
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);
DCHECK_GE(kControlFrameBufferSize, current_frame_len_ + bytes_to_read);
memcpy(current_frame_buffer_.get() + 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::GetSerializedLength(const SpdyHeaderBlock* headers) const {
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 SpdyHeaderBlock* headers) const {
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) {
bool wrote_header;
if (spdy_version_ < 3) {
wrote_header = frame->WriteString(it->first);
wrote_header &= frame->WriteString(it->second);
} else {
wrote_header = frame->WriteStringPiece32(it->first);
wrote_header &= frame->WriteStringPiece32(it->second);
}
DCHECK(wrote_header);
}
}
// 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);
}
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;
}
if (remaining_control_header_ == 0) {
SpdyControlFrame control_frame(current_frame_buffer_.get(), false);
switch (control_frame.type()) {
case SYN_STREAM:
{
SpdySynStreamControlFrame* syn_stream_frame =
reinterpret_cast<SpdySynStreamControlFrame*>(&control_frame);
// TODO(hkhalil): Check that invalid flag bits are unset?
visitor_->OnSynStream(
syn_stream_frame->stream_id(),
syn_stream_frame->associated_stream_id(),
syn_stream_frame->priority(),
syn_stream_frame->credential_slot(),
(syn_stream_frame->flags() & CONTROL_FLAG_FIN) != 0,
(syn_stream_frame->flags() & CONTROL_FLAG_UNIDIRECTIONAL) != 0);
}
CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK);
break;
case SYN_REPLY:
{
SpdySynReplyControlFrame* syn_reply_frame =
reinterpret_cast<SpdySynReplyControlFrame*>(&control_frame);
visitor_->OnSynReply(
syn_reply_frame->stream_id(),
(syn_reply_frame->flags() & CONTROL_FLAG_FIN) != 0);
}
CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK);
break;
case HEADERS:
{
SpdyHeadersControlFrame* headers_frame =
reinterpret_cast<SpdyHeadersControlFrame*>(&control_frame);
visitor_->OnHeaders(
headers_frame->stream_id(),
(headers_frame->flags() & CONTROL_FLAG_FIN) != 0);
}
CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK);
break;
case SETTINGS:
CHANGE_STATE(SPDY_SETTINGS_FRAME_PAYLOAD);
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_);
SpdyControlFrame control_frame(current_frame_buffer_.get(), 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_);
if (process_bytes > 0) {
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;
remaining_data_ -= 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, DATA_FLAG_FIN);
}
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_);
SpdyControlFrame control_frame(current_frame_buffer_.get(), false);
DCHECK_EQ(SETTINGS, control_frame.type());
size_t unprocessed_bytes = std::min(data_len, remaining_control_payload_);
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_control_payload_ -= processed_bytes;
if (remaining_control_payload_ == 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;
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_ == 0) {
SpdyControlFrame control_frame(current_frame_buffer_.get(), false);
DCHECK(!control_frame.has_header_block());
// Use frame-specific handlers.
switch (control_frame.type()) {
case PING: {
SpdyPingControlFrame* ping_frame =
reinterpret_cast<SpdyPingControlFrame*>(&control_frame);
visitor_->OnPing(ping_frame->unique_id());
}
break;
case WINDOW_UPDATE: {
SpdyWindowUpdateControlFrame *window_update_frame =
reinterpret_cast<SpdyWindowUpdateControlFrame*>(&control_frame);
visitor_->OnWindowUpdate(window_update_frame->stream_id(),
window_update_frame->delta_window_size());
}
break;
case RST_STREAM: {
SpdyRstStreamControlFrame* rst_stream_frame =
reinterpret_cast<SpdyRstStreamControlFrame*>(&control_frame);
visitor_->OnRstStream(rst_stream_frame->stream_id(),
rst_stream_frame->status());
}
break;
case GOAWAY: {
SpdyGoAwayControlFrame* go_away_frame =
reinterpret_cast<SpdyGoAwayControlFrame*>(&control_frame);
if (spdy_version_ < 3) {
visitor_->OnGoAway(go_away_frame->last_accepted_stream_id(),
GOAWAY_OK);
} else {
visitor_->OnGoAway(go_away_frame->last_accepted_stream_id(),
go_away_frame->status());
}
}
break;
default:
// Unreachable.
LOG(FATAL) << "Unhandled control frame " << control_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) {
// Process only up to the end of this CREDENTIAL frame.
len = std::min(len, remaining_control_payload_);
bool processed_succesfully = visitor_->OnCredentialFrameData(data, len);
remaining_control_payload_ -= len;
remaining_data_ -= len;
if (!processed_succesfully) {
set_error(SPDY_CREDENTIAL_FRAME_CORRUPT);
} else if (remaining_control_payload_ == 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;
SpdyDataFrame current_data_frame(current_frame_buffer_.get(), false);
if (remaining_data_ > 0) {
size_t amount_to_forward = std::min(remaining_data_, 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_data_frame.stream_id(),
data, amount_to_forward, SpdyDataFlags());
}
}
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, DATA_FLAG_FIN);
}
}
if (remaining_data_ == 0) {
CHANGE_STATE(SPDY_AUTO_RESET);
}
return original_len - len;
}
bool SpdyFramer::ParseHeaderBlockInBuffer(const char* header_data,
size_t header_length,
SpdyHeaderBlock* block) const {
SpdyFrameReader reader(header_data, header_length);
// Read number of headers.
uint32 num_headers;
if (spdy_version_ < 3) {
uint16 temp;
if (!reader.ReadUInt16(&temp)) {
DLOG(INFO) << "Unable to read number of headers.";
return false;
}
num_headers = temp;
} else {
if (!reader.ReadUInt32(&num_headers)) {
DLOG(INFO) << "Unable to read number of headers.";
return false;
}
}
// 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 false;
}
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 false;
}
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 false;
}
// Store header.
(*block)[name] = value;
}
return true;
}
// TODO(hkhalil): Remove, or move to test utils kit.
/* static */
bool SpdyFramer::ParseSettings(const SpdySettingsControlFrame* frame,
SettingsMap* settings) {
DCHECK_EQ(frame->type(), SETTINGS);
DCHECK(settings);
SpdyFrameReader parser(frame->header_block(), frame->header_block_len());
for (size_t index = 0; index < frame->num_entries(); ++index) {
uint32 id_and_flags_wire;
uint32 value;
// SettingsFlagsAndId accepts off-the-wire (network byte order) data, so we
// use ReadBytes() instead of ReadUInt32() as the latter calls ntohl().
if (!parser.ReadBytes(&id_and_flags_wire, 4)) {
return false;
}
if (!parser.ReadUInt32(&value))
return false;
SettingsFlagsAndId flags_and_id =
SettingsFlagsAndId::FromWireFormat(frame->version(), id_and_flags_wire);
SpdySettingsIds id = static_cast<SpdySettingsIds>(flags_and_id.id());
SpdySettingsFlags flags =
static_cast<SpdySettingsFlags>(flags_and_id.flags());
(*settings)[id] = SettingsFlagsAndValue(flags, value);
}
return true;
}
/* 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;
}
SpdySynStreamControlFrame* SpdyFramer::CreateSynStream(
SpdyStreamId stream_id,
SpdyStreamId associated_stream_id,
SpdyPriority priority,
uint8 credential_slot,
SpdyControlFlags flags,
bool compressed,
const SpdyHeaderBlock* headers) {
DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
DCHECK_EQ(0u, associated_stream_id & ~kStreamIdMask);
// Find our length.
size_t frame_size = SpdySynStreamControlFrame::size() +
GetSerializedLength(headers);
SpdyFrameBuilder frame(SYN_STREAM, flags, spdy_version_, frame_size);
frame.WriteUInt32(stream_id);
frame.WriteUInt32(associated_stream_id);
// Cap as appropriate.
if (priority > GetLowestPriority()) {
DLOG(DFATAL) << "Priority out-of-bounds.";
priority = GetLowestPriority();
}
// Priority is 2 bits for <spdy3, 3 bits otherwise.
frame.WriteUInt8(priority << ((spdy_version_ < 3) ? 6 : 5));
frame.WriteUInt8((spdy_version_ < 3) ? 0 : credential_slot);
WriteHeaderBlock(&frame, headers);
DCHECK_EQ(frame.length(), frame_size);
scoped_ptr<SpdySynStreamControlFrame> syn_frame(
reinterpret_cast<SpdySynStreamControlFrame*>(frame.take()));
if (compressed) {
return reinterpret_cast<SpdySynStreamControlFrame*>(
CompressControlFrame(*syn_frame.get(), headers));
}
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);
// Find our length.
size_t frame_size = SpdySynReplyControlFrame::size() +
GetSerializedLength(headers);
// In SPDY 2, there were 2 unused bytes before payload.
if (spdy_version_ < 3) {
frame_size += 2;
}
SpdyFrameBuilder frame(SYN_REPLY, flags, spdy_version_, frame_size);
frame.WriteUInt32(stream_id);
if (spdy_version_ < 3) {
frame.WriteUInt16(0); // Unused
}
WriteHeaderBlock(&frame, headers);
DCHECK_EQ(frame.length(), frame_size);
scoped_ptr<SpdySynReplyControlFrame> reply_frame(
reinterpret_cast<SpdySynReplyControlFrame*>(frame.take()));
if (compressed) {
return reinterpret_cast<SpdySynReplyControlFrame*>(
CompressControlFrame(*reply_frame.get(), headers));
}
return reply_frame.release();
}
SpdyRstStreamControlFrame* SpdyFramer::CreateRstStream(
SpdyStreamId stream_id,
SpdyStatusCodes status) const {
DCHECK_GT(stream_id, 0u);
DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
DCHECK_NE(status, INVALID);
DCHECK_LT(status, NUM_STATUS_CODES);
size_t frame_size = SpdyRstStreamControlFrame::size();
SpdyFrameBuilder frame(RST_STREAM, CONTROL_FLAG_NONE, spdy_version_,
frame_size);
frame.WriteUInt32(stream_id);
frame.WriteUInt32(status);
DCHECK_EQ(frame.length(), frame_size);
return reinterpret_cast<SpdyRstStreamControlFrame*>(frame.take());
}
SpdySettingsControlFrame* SpdyFramer::CreateSettings(
const SettingsMap& values) const {
size_t frame_size = SpdySettingsControlFrame::size() + 8 * values.size();
SpdyFrameBuilder frame(SETTINGS, CONTROL_FLAG_NONE, spdy_version_,
frame_size);
frame.WriteUInt32(values.size());
for (SettingsMap::const_iterator it = values.begin();
it != values.end();
it++) {
SettingsFlagsAndId flags_and_id(it->second.first, it->first);
uint32 id_and_flags_wire = flags_and_id.GetWireFormat(spdy_version_);
frame.WriteBytes(&id_and_flags_wire, 4);
frame.WriteUInt32(it->second.second);
}
DCHECK_EQ(frame.length(), frame_size);
return reinterpret_cast<SpdySettingsControlFrame*>(frame.take());
}
SpdyPingControlFrame* SpdyFramer::CreatePingFrame(uint32 unique_id) const {
size_t frame_size = SpdyPingControlFrame::size();
SpdyFrameBuilder frame(PING, CONTROL_FLAG_NONE, spdy_version_, frame_size);
frame.WriteUInt32(unique_id);
DCHECK_EQ(frame.length(), frame_size);
return reinterpret_cast<SpdyPingControlFrame*>(frame.take());
}
SpdyGoAwayControlFrame* SpdyFramer::CreateGoAway(
SpdyStreamId last_accepted_stream_id,
SpdyGoAwayStatus status) const {
DCHECK_EQ(0u, last_accepted_stream_id & ~kStreamIdMask);
// SPDY 2 GOAWAY frames are 4 bytes smaller than in SPDY 3. We account for
// this difference via a separate offset variable, since
// SpdyGoAwayControlFrame::size() returns the SPDY 3 size.
const size_t goaway_offset = (protocol_version() < 3) ? 4 : 0;
size_t frame_size = SpdyGoAwayControlFrame::size() - goaway_offset;
SpdyFrameBuilder frame(GOAWAY, CONTROL_FLAG_NONE, spdy_version_, frame_size);
frame.WriteUInt32(last_accepted_stream_id);
if (protocol_version() >= 3) {
frame.WriteUInt32(status);
}
DCHECK_EQ(frame.length(), frame_size);
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);
// Find our length.
size_t frame_size = SpdyHeadersControlFrame::size() +
GetSerializedLength(headers);
// In SPDY 2, there were 2 unused bytes before payload.
if (spdy_version_ < 3) {
frame_size += 2;
}
SpdyFrameBuilder frame(HEADERS, flags, spdy_version_, frame_size);
frame.WriteUInt32(stream_id);
if (spdy_version_ < 3) {
frame.WriteUInt16(0); // Unused
}
WriteHeaderBlock(&frame, headers);
DCHECK_EQ(frame.length(), frame_size);
scoped_ptr<SpdyHeadersControlFrame> headers_frame(
reinterpret_cast<SpdyHeadersControlFrame*>(frame.take()));
if (compressed) {
return reinterpret_cast<SpdyHeadersControlFrame*>(
CompressControlFrame(*headers_frame.get(), headers));
}
return headers_frame.release();
}
SpdyWindowUpdateControlFrame* SpdyFramer::CreateWindowUpdate(
SpdyStreamId stream_id,
uint32 delta_window_size) const {
DCHECK_GT(stream_id, 0u);
DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
DCHECK_GT(delta_window_size, 0u);
DCHECK_LE(delta_window_size,
static_cast<uint32>(kSpdyStreamMaximumWindowSize));
size_t frame_size = SpdyWindowUpdateControlFrame::size();
SpdyFrameBuilder frame(WINDOW_UPDATE, CONTROL_FLAG_NONE, spdy_version_,
frame_size);
frame.WriteUInt32(stream_id);
frame.WriteUInt32(delta_window_size);
DCHECK_EQ(frame.length(), frame_size);
return reinterpret_cast<SpdyWindowUpdateControlFrame*>(frame.take());
}
SpdyCredentialControlFrame* SpdyFramer::CreateCredentialFrame(
const SpdyCredential& credential) const {
// Calculate the size of the frame by adding the size of the
// variable length data to the size of the fixed length data.
size_t frame_size = SpdyCredentialControlFrame::size() +
credential.proof.length();
DCHECK_EQ(SpdyCredentialControlFrame::size(), 14u);
for (std::vector<std::string>::const_iterator cert = credential.certs.begin();
cert != credential.certs.end();
++cert) {
frame_size += sizeof(uint32); // size of the cert_length field
frame_size += cert->length(); // size of the cert_data field
}
SpdyFrameBuilder frame(CREDENTIAL, CONTROL_FLAG_NONE, spdy_version_,
frame_size);
frame.WriteUInt16(credential.slot);
frame.WriteUInt32(credential.proof.size());
frame.WriteBytes(credential.proof.c_str(), credential.proof.size());
for (std::vector<std::string>::const_iterator cert = credential.certs.begin();
cert != credential.certs.end();
++cert) {
frame.WriteUInt32(cert->length());
frame.WriteBytes(cert->c_str(), cert->length());
}
DCHECK_EQ(frame.length(), frame_size);
return reinterpret_cast<SpdyCredentialControlFrame*>(frame.take());
}
SpdyDataFrame* SpdyFramer::CreateDataFrame(
SpdyStreamId stream_id,
const char* data,
uint32 len, SpdyDataFlags flags) const {
DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
size_t frame_size = SpdyDataFrame::size() + len;
SpdyFrameBuilder frame(stream_id, flags, frame_size);
frame.WriteBytes(data, len);
DCHECK_EQ(frame.length(), frame_size);
return reinterpret_cast<SpdyDataFrame*>(frame.take());
}
// 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();
}
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;
// SPDY 2 had two bytes of unused space preceeding payload.
if (spdy_version_ < 3) {
*header_length += 2;
*payload += 2;
}
}
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;
// SPDY 2 had two bytes of unused space preceeding payload.
if (spdy_version_ < 3) {
*header_length += 2;
*payload += 2;
}
}
break;
default:
// TODO(mbelshe): set an error?
return false; // We can't compress this frame!
}
} else {
frame_size = SpdyFrame::kHeaderSize;
*header_length = frame_size;
*payload_length = frame.length();
*payload = frame.data() + SpdyFrame::kHeaderSize;
}
return true;
}
SpdyControlFrame* SpdyFramer::CompressControlFrame(
const SpdyControlFrame& frame,
const SpdyHeaderBlock* headers) {
z_stream* compressor = GetHeaderCompressor();
if (!compressor)
return NULL;
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 reinterpret_cast<SpdyControlFrame*>(DuplicateFrame(frame));
if (!GetFrameBoundaries(frame, &payload_length, &header_length, &payload))
return NULL;
// Create an output frame.
int compressed_max_size = deflateBound(compressor, payload_length);
// Since we'll be performing lots of flushes when compressing the data,
// zlib's lower bounds may be insufficient.
compressed_max_size *= 2;
size_t new_frame_size = header_length + compressed_max_size;
if ((frame.type() == SYN_REPLY || frame.type() == HEADERS) &&
spdy_version_ < 3) {
new_frame_size += 2;
}
DCHECK_GE(new_frame_size, frame.length() + SpdyFrame::kHeaderSize);
scoped_ptr<SpdyControlFrame> new_frame(new SpdyControlFrame(new_frame_size));
memcpy(new_frame->data(), frame.data(),
frame.length() + SpdyFrame::kHeaderSize);
// 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*>(const_cast<char*>(payload));
compressor->avail_in = payload_length;
#endif // defined(USE_SYSTEM_ZLIB)
compressor->next_out = reinterpret_cast<Bytef*>(new_frame->data()) +
header_length;
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;
return NULL;
}
#else // !defined(USE_SYSTEM_ZLIB)
WriteHeaderBlockToZ(headers, compressor);
#endif // !defined(USE_SYSTEM_ZLIB)
int compressed_size = compressed_max_size - compressor->avail_out;
// 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);
new_frame->set_length(
header_length + compressed_size - SpdyFrame::kHeaderSize);
pre_compress_bytes.Add(payload_length);
post_compress_bytes.Add(new_frame->length());
compressed_frames.Increment();
if (visitor_)
visitor_->OnControlFrameCompressed(frame, *new_frame);
return new_frame.release();
}
// 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 = 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(
const SpdyControlFrame* control_frame, const char* data, size_t len) {
bool read_successfully = true;
const SpdyStreamId stream_id = GetControlFrameStreamId(control_frame);
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;
}
SpdyFrame* SpdyFramer::DuplicateFrame(const SpdyFrame& frame) {
int size = SpdyFrame::kHeaderSize + 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.
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 ||
control_frame.type() == HEADERS;
}
// We don't compress Data frames.
return false;
}
size_t SpdyFramer::GetMinimumControlFrameSize(int version,
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:
// Even though NOOP is no longer supported, we still correctly report its
// size so that it can be handled correctly as incoming data if
// implementations so desire.
return SpdyFrame::kHeaderSize;
case PING:
return SpdyPingControlFrame::size();
case GOAWAY:
if (version < 3) {
// SPDY 2 GOAWAY is smaller by 32 bits. Since
// SpdyGoAwayControlFrame::size() returns the size for SPDY 3, we adjust
// before returning here.
return SpdyGoAwayControlFrame::size() - 4;
} else {
return SpdyGoAwayControlFrame::size();
}
case HEADERS:
return SpdyHeadersControlFrame::size();
case WINDOW_UPDATE:
return SpdyWindowUpdateControlFrame::size();
case CREDENTIAL:
return SpdyCredentialControlFrame::size();
case NUM_CONTROL_FRAME_TYPES:
break;
}
LOG(ERROR) << "Unknown control frame type " << type;
return std::numeric_limits<size_t>::max();
}
/* 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 CREDENTIAL:
case NUM_CONTROL_FRAME_TYPES: // makes compiler happy
break;
}
}
return stream_id;
}
void SpdyFramer::set_enable_compression(bool value) {
enable_compression_ = value;
}
} // namespace net