blob: e0b0d57988b04a5eb3c99c0e59cb3345b2849576 [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.
#include <algorithm>
#include <iostream>
#include <limits>
#include "base/memory/scoped_ptr.h"
#include "net/spdy/spdy_framer.h"
#include "net/spdy/spdy_protocol.h"
#include "net/spdy/spdy_frame_builder.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/platform_test.h"
using std::string;
using std::max;
using std::min;
using std::numeric_limits;
using testing::_;
namespace net {
namespace test {
static const size_t kMaxDecompressedSize = 1024;
class MockVisitor : public SpdyFramerVisitorInterface {
public:
MOCK_METHOD1(OnError, void(SpdyFramer* framer));
MOCK_METHOD6(OnSynStream, void(SpdyStreamId stream_id,
SpdyStreamId associated_stream_id,
SpdyPriority priority,
uint8 slot,
bool fin,
bool unidirectional));
MOCK_METHOD2(OnSynReply, void(SpdyStreamId stream_id, bool fin));
MOCK_METHOD2(OnHeaders, void(SpdyStreamId stream_id, bool fin));
MOCK_METHOD3(OnControlFrameHeaderData, bool(SpdyStreamId stream_id,
const char* header_data,
size_t len));
MOCK_METHOD2(OnCredentialFrameData, bool(const char* header_data,
size_t len));
MOCK_METHOD1(OnDataFrameHeader, void(const SpdyDataFrame* frame));
MOCK_METHOD4(OnStreamFrameData, void(SpdyStreamId stream_id,
const char* data,
size_t len,
SpdyDataFlags flags));
MOCK_METHOD3(OnSetting, void(SpdySettingsIds id, uint8 flags, uint32 value));
MOCK_METHOD1(OnPing, void(uint32 unique_id));
MOCK_METHOD2(OnRstStream, void(SpdyStreamId stream_id,
SpdyStatusCodes status));
MOCK_METHOD2(OnGoAway, void(SpdyStreamId last_accepted_stream_id,
SpdyGoAwayStatus status));
MOCK_METHOD2(OnWindowUpdate, void(SpdyStreamId stream_id,
int delta_window_size));
MOCK_METHOD2(OnControlFrameCompressed,
void(const SpdyControlFrame& uncompressed_frame,
const SpdyControlFrame& compressed_frame));
};
class SpdyFramerTestUtil {
public:
// Decompress a single frame using the decompression context held by
// the SpdyFramer. The implemention is meant for use only in tests
// and will CHECK fail if the input is anything other than a single,
// well-formed compressed frame.
//
// Returns a new decompressed SpdyFrame.
template<class SpdyFrameType> static SpdyFrame* DecompressFrame(
SpdyFramer* framer, const SpdyFrameType& frame) {
DecompressionVisitor visitor(framer->protocol_version());
framer->set_visitor(&visitor);
size_t input_size = frame.length() + SpdyFrame::kHeaderSize;
CHECK_EQ(input_size, framer->ProcessInput(frame.data(), input_size));
CHECK_EQ(SpdyFramer::SPDY_RESET, framer->state());
framer->set_visitor(NULL);
char* buffer = visitor.ReleaseBuffer();
CHECK(buffer != NULL);
SpdyFrame* decompressed_frame = new SpdyFrame(buffer, true);
decompressed_frame->set_length(visitor.size() - SpdyFrame::kHeaderSize);
return decompressed_frame;
}
class DecompressionVisitor : public SpdyFramerVisitorInterface {
public:
explicit DecompressionVisitor(int version)
: version_(version), buffer_(NULL), size_(0), finished_(false) {
}
void ResetBuffer() {
CHECK(buffer_.get() == NULL);
CHECK_EQ(0u, size_);
CHECK(!finished_);
buffer_.reset(new char[kMaxDecompressedSize]);
}
virtual void OnSynStream(SpdyStreamId stream_id,
SpdyStreamId associated_stream_id,
SpdyPriority priority,
uint8 slot,
bool fin,
bool unidirectional) {
SpdyFramer framer(version_);
const SpdyHeaderBlock null_headers;
int flags = CONTROL_FLAG_NONE;
if (fin) {
flags &= CONTROL_FLAG_FIN;
}
if (unidirectional) {
flags &= CONTROL_FLAG_UNIDIRECTIONAL;
}
scoped_ptr<SpdySynStreamControlFrame> frame(
framer.CreateSynStream(stream_id,
associated_stream_id,
priority,
slot,
static_cast<SpdyControlFlags>(flags),
false,
&null_headers));
ResetBuffer();
memcpy(buffer_.get(), frame->data(), SpdySynStreamControlFrame::size());
size_ += SpdySynStreamControlFrame::size();
}
virtual void OnSynReply(SpdyStreamId stream_id, bool fin) {
SpdyFramer framer(version_);
const SpdyHeaderBlock null_headers;
int flags = CONTROL_FLAG_NONE;
if (fin) {
flags &= CONTROL_FLAG_FIN;
}
scoped_ptr<SpdyHeadersControlFrame> frame(
framer.CreateHeaders(stream_id,
static_cast<SpdyControlFlags>(flags),
false,
&null_headers));
ResetBuffer();
memcpy(buffer_.get(), frame->data(), SpdyHeadersControlFrame::size());
size_ += SpdySynStreamControlFrame::size();
}
virtual void OnHeaders(SpdyStreamId stream_id, bool fin) {
SpdyFramer framer(version_);
const SpdyHeaderBlock null_headers;
int flags = CONTROL_FLAG_NONE;
if (fin) {
flags &= CONTROL_FLAG_FIN;
}
scoped_ptr<SpdyHeadersControlFrame> frame(
framer.CreateHeaders(stream_id,
static_cast<SpdyControlFlags>(flags),
false,
&null_headers));
ResetBuffer();
memcpy(buffer_.get(), frame->data(), SpdyHeadersControlFrame::size());
size_ += SpdySynStreamControlFrame::size();
}
virtual bool OnControlFrameHeaderData(SpdyStreamId stream_id,
const char* header_data,
size_t len) {
CHECK(buffer_.get() != NULL);
CHECK_GE(kMaxDecompressedSize, size_ + len);
CHECK(!finished_);
if (len != 0) {
memcpy(buffer_.get() + size_, header_data, len);
size_ += len;
} else {
// Done.
finished_ = true;
}
return true;
}
virtual bool OnCredentialFrameData(const char* header_data,
size_t len) {
LOG(FATAL) << "Unexpected CREDENTIAL Frame";
return false;
}
virtual void OnError(SpdyFramer* framer) { LOG(FATAL); }
virtual void OnDataFrameHeader(const SpdyDataFrame* frame) {
LOG(FATAL) << "Unexpected data frame header";
}
virtual void OnStreamFrameData(SpdyStreamId stream_id,
const char* data,
size_t len,
SpdyDataFlags flags) {
LOG(FATAL);
}
virtual void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) {
LOG(FATAL);
}
virtual void OnControlFrameCompressed(
const SpdyControlFrame& uncompressed_frame,
const SpdyControlFrame& compressed_frame) {
}
virtual void OnPing(uint32 unique_id) {
LOG(FATAL);
}
virtual void OnRstStream(SpdyStreamId stream_id, SpdyStatusCodes status) {
LOG(FATAL);
}
virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
SpdyGoAwayStatus status) {
LOG(FATAL);
}
virtual void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) {
LOG(FATAL);
}
char* ReleaseBuffer() {
CHECK(finished_);
return buffer_.release();
}
size_t size() const {
CHECK(finished_);
return size_;
}
private:
int version_;
scoped_array<char> buffer_;
size_t size_;
bool finished_;
DISALLOW_COPY_AND_ASSIGN(DecompressionVisitor);
};
DISALLOW_COPY_AND_ASSIGN(SpdyFramerTestUtil);
};
string HexDumpWithMarks(const unsigned char* data, int length,
const bool* marks, int mark_length) {
static const char kHexChars[] = "0123456789abcdef";
static const int kColumns = 4;
const int kSizeLimit = 1024;
if (length > kSizeLimit || mark_length > kSizeLimit) {
LOG(ERROR) << "Only dumping first " << kSizeLimit << " bytes.";
length = min(length, kSizeLimit);
mark_length = min(mark_length, kSizeLimit);
}
string hex;
for (const unsigned char* row = data; length > 0;
row += kColumns, length -= kColumns) {
for (const unsigned char *p = row; p < row + 4; ++p) {
if (p < row + length) {
const bool mark =
(marks && (p - data) < mark_length && marks[p - data]);
hex += mark ? '*' : ' ';
hex += kHexChars[(*p & 0xf0) >> 4];
hex += kHexChars[*p & 0x0f];
hex += mark ? '*' : ' ';
} else {
hex += " ";
}
}
hex = hex + " ";
for (const unsigned char *p = row; p < row + 4 && p < row + length; ++p)
hex += (*p >= 0x20 && *p <= 0x7f) ? (*p) : '.';
hex = hex + '\n';
}
return hex;
}
void CompareCharArraysWithHexError(
const string& description,
const unsigned char* actual,
const int actual_len,
const unsigned char* expected,
const int expected_len) {
const int min_len = min(actual_len, expected_len);
const int max_len = max(actual_len, expected_len);
scoped_array<bool> marks(new bool[max_len]);
bool identical = (actual_len == expected_len);
for (int i = 0; i < min_len; ++i) {
if (actual[i] != expected[i]) {
marks[i] = true;
identical = false;
} else {
marks[i] = false;
}
}
for (int i = min_len; i < max_len; ++i) {
marks[i] = true;
}
if (identical) return;
ADD_FAILURE()
<< "Description:\n"
<< description
<< "\n\nExpected:\n"
<< HexDumpWithMarks(expected, expected_len, marks.get(), max_len)
<< "\nActual:\n"
<< HexDumpWithMarks(actual, actual_len, marks.get(), max_len);
}
class TestSpdyVisitor : public SpdyFramerVisitorInterface {
public:
static const size_t kDefaultHeaderBufferSize = 16 * 1024;
static const size_t kDefaultCredentialBufferSize = 16 * 1024;
explicit TestSpdyVisitor(int version)
: framer_(version),
use_compression_(false),
error_count_(0),
syn_frame_count_(0),
syn_reply_frame_count_(0),
headers_frame_count_(0),
goaway_count_(0),
setting_count_(0),
data_bytes_(0),
fin_frame_count_(0),
fin_flag_count_(0),
zero_length_data_frame_count_(0),
header_blocks_count_(0),
control_frame_header_data_count_(0),
zero_length_control_frame_header_data_count_(0),
data_frame_count_(0),
header_buffer_(new char[kDefaultHeaderBufferSize]),
header_buffer_length_(0),
header_buffer_size_(kDefaultHeaderBufferSize),
header_stream_id_(-1),
header_control_type_(NUM_CONTROL_FRAME_TYPES),
header_buffer_valid_(false),
credential_buffer_(new char[kDefaultCredentialBufferSize]),
credential_buffer_length_(0),
credential_buffer_size_(kDefaultCredentialBufferSize) {
}
void OnError(SpdyFramer* f) {
LOG(INFO) << "SpdyFramer Error: "
<< SpdyFramer::ErrorCodeToString(f->error_code());
error_count_++;
}
void OnDataFrameHeader(const SpdyDataFrame* frame) {
data_frame_count_++;
header_stream_id_ = frame->stream_id();
}
void OnStreamFrameData(SpdyStreamId stream_id,
const char* data,
size_t len,
SpdyDataFlags flags) {
EXPECT_EQ(header_stream_id_, stream_id);
if (len == 0)
++zero_length_data_frame_count_;
data_bytes_ += len;
std::cerr << "OnStreamFrameData(" << stream_id << ", \"";
if (len > 0) {
for (size_t i = 0 ; i < len; ++i) {
std::cerr << std::hex << (0xFF & (unsigned int)data[i]) << std::dec;
}
}
std::cerr << "\", " << len << ")\n";
}
virtual void OnSynStream(SpdyStreamId stream_id,
SpdyStreamId associated_stream_id,
SpdyPriority priority,
uint8 credential_slot,
bool fin,
bool unidirectional) {
syn_frame_count_++;
InitHeaderStreaming(SYN_STREAM, stream_id);
if (fin) {
fin_flag_count_++;
}
}
virtual void OnSynReply(SpdyStreamId stream_id, bool fin) {
syn_reply_frame_count_++;
InitHeaderStreaming(HEADERS, stream_id);
if (fin) {
fin_flag_count_++;
}
}
virtual void OnHeaders(SpdyStreamId stream_id, bool fin) {
headers_frame_count_++;
InitHeaderStreaming(SYN_REPLY, stream_id);
if (fin) {
fin_flag_count_++;
}
}
virtual void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) {
setting_count_++;
}
virtual void OnControlFrameCompressed(
const SpdyControlFrame& uncompressed_frame,
const SpdyControlFrame& compressed_frame) {
}
virtual void OnPing(uint32 unique_id) {
DLOG(FATAL);
}
virtual void OnRstStream(SpdyStreamId stream_id, SpdyStatusCodes status) {
fin_frame_count_++;
}
virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
SpdyGoAwayStatus status) {
goaway_count_++;
}
virtual void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) {
last_window_update_stream_ = stream_id;
last_window_update_delta_ = delta_window_size;
}
bool OnControlFrameHeaderData(SpdyStreamId stream_id,
const char* header_data,
size_t len) {
++control_frame_header_data_count_;
CHECK_EQ(header_stream_id_, stream_id);
if (len == 0) {
++zero_length_control_frame_header_data_count_;
// Indicates end-of-header-block.
CHECK(header_buffer_valid_);
bool parsed_headers = framer_.ParseHeaderBlockInBuffer(
header_buffer_.get(), header_buffer_length_, &headers_);
DCHECK(parsed_headers);
return true;
}
const size_t available = header_buffer_size_ - header_buffer_length_;
if (len > available) {
header_buffer_valid_ = false;
return false;
}
memcpy(header_buffer_.get() + header_buffer_length_, header_data, len);
header_buffer_length_ += len;
return true;
}
bool OnCredentialFrameData(const char* credential_data,
size_t len) {
if (len == 0) {
if (!framer_.ParseCredentialData(credential_buffer_.get(),
credential_buffer_length_,
&credential_)) {
++error_count_;
}
return true;
}
const size_t available =
credential_buffer_size_ - credential_buffer_length_;
if (len > available) {
return false;
}
memcpy(credential_buffer_.get() + credential_buffer_length_,
credential_data, len);
credential_buffer_length_ += len;
return true;
}
// Convenience function which runs a framer simulation with particular input.
void SimulateInFramer(const unsigned char* input, size_t size) {
framer_.set_enable_compression(use_compression_);
framer_.set_visitor(this);
size_t input_remaining = size;
const char* input_ptr = reinterpret_cast<const char*>(input);
while (input_remaining > 0 &&
framer_.error_code() == SpdyFramer::SPDY_NO_ERROR) {
// To make the tests more interesting, we feed random (amd small) chunks
// into the framer. This simulates getting strange-sized reads from
// the socket.
const size_t kMaxReadSize = 32;
size_t bytes_read =
(rand() % min(input_remaining, kMaxReadSize)) + 1;
size_t bytes_processed = framer_.ProcessInput(input_ptr, bytes_read);
input_remaining -= bytes_processed;
input_ptr += bytes_processed;
if (framer_.state() == SpdyFramer::SPDY_DONE)
framer_.Reset();
}
}
void InitHeaderStreaming(SpdyControlType header_control_type,
SpdyStreamId stream_id) {
memset(header_buffer_.get(), 0, header_buffer_size_);
header_buffer_length_ = 0;
header_stream_id_ = stream_id;
header_control_type_ = header_control_type;
header_buffer_valid_ = true;
DCHECK_NE(header_stream_id_, SpdyFramer::kInvalidStream);
}
// Override the default buffer size (16K). Call before using the framer!
void set_header_buffer_size(size_t header_buffer_size) {
header_buffer_size_ = header_buffer_size;
header_buffer_.reset(new char[header_buffer_size]);
}
static size_t control_frame_buffer_max_size() {
return SpdyFramer::kMaxControlFrameSize;
}
static size_t header_data_chunk_max_size() {
return SpdyFramer::kHeaderDataChunkMaxSize;
}
SpdyFramer framer_;
bool use_compression_;
// Counters from the visitor callbacks.
int error_count_;
int syn_frame_count_;
int syn_reply_frame_count_;
int headers_frame_count_;
int goaway_count_;
int setting_count_;
int last_window_update_stream_;
int last_window_update_delta_;
int data_bytes_;
int fin_frame_count_; // The count of RST_STREAM type frames received.
int fin_flag_count_; // The count of frames with the FIN flag set.
int zero_length_data_frame_count_; // The count of zero-length data frames.
int header_blocks_count_;
int control_frame_header_data_count_; // The count of chunks received.
// The count of zero-length control frame header data chunks received.
int zero_length_control_frame_header_data_count_;
int data_frame_count_;
// Header block streaming state:
scoped_array<char> header_buffer_;
size_t header_buffer_length_;
size_t header_buffer_size_;
SpdyStreamId header_stream_id_;
SpdyControlType header_control_type_;
bool header_buffer_valid_;
SpdyHeaderBlock headers_;
scoped_array<char> credential_buffer_;
size_t credential_buffer_length_;
size_t credential_buffer_size_;
SpdyCredential credential_;
};
} // namespace net
using test::CompareCharArraysWithHexError;
using test::SpdyFramerTestUtil;
using test::TestSpdyVisitor;
TEST(SpdyFrameBuilderTest, WriteLimits) {
SpdyFrameBuilder builder(1, DATA_FLAG_NONE, kLengthMask + 8);
// Data frame header is 8 bytes
EXPECT_EQ(8u, builder.length());
const string kLargeData(kLengthMask, 'A');
builder.WriteUInt32(kLengthMask);
EXPECT_EQ(12u, builder.length());
EXPECT_TRUE(builder.WriteBytes(kLargeData.data(), kLengthMask - 4));
EXPECT_EQ(kLengthMask + 8u, builder.length());
}
enum SpdyFramerTestTypes {
SPDY2 = 2,
SPDY3 = 3,
};
class SpdyFramerTest
: public ::testing::TestWithParam<SpdyFramerTestTypes> {
protected:
virtual void SetUp() {
spdy_version_ = GetParam();
}
void CompareFrame(const string& description,
const SpdyFrame& actual_frame,
const unsigned char* expected,
const int expected_len) {
const unsigned char* actual =
reinterpret_cast<const unsigned char*>(actual_frame.data());
int actual_len = actual_frame.length() + SpdyFrame::kHeaderSize;
CompareCharArraysWithHexError(
description, actual, actual_len, expected, expected_len);
}
// Returns true if the two header blocks have equivalent content.
bool CompareHeaderBlocks(const SpdyHeaderBlock* expected,
const SpdyHeaderBlock* actual) {
if (expected->size() != actual->size()) {
LOG(ERROR) << "Expected " << expected->size() << " headers; actually got "
<< actual->size() << ".";
return false;
}
for (SpdyHeaderBlock::const_iterator it = expected->begin();
it != expected->end();
++it) {
SpdyHeaderBlock::const_iterator it2 = actual->find(it->first);
if (it2 == actual->end()) {
LOG(ERROR) << "Expected header name '" << it->first << "'.";
return false;
}
if (it->second.compare(it2->second) != 0) {
LOG(ERROR) << "Expected header named '" << it->first
<< "' to have a value of '" << it->second
<< "'. The actual value received was '" << it2->second
<< "'.";
return false;
}
}
return true;
}
void AddSpdySettingFromWireFormat(SettingsMap* settings,
uint32 key,
uint32 value) {
SettingsFlagsAndId flags_and_id =
SettingsFlagsAndId::FromWireFormat(spdy_version_, key);
SpdySettingsIds id = static_cast<SpdySettingsIds>(flags_and_id.id());
SpdySettingsFlags flags =
static_cast<SpdySettingsFlags>(flags_and_id.flags());
CHECK(settings->find(id) == settings->end());
settings->insert(std::make_pair(id, SettingsFlagsAndValue(flags, value)));
}
bool IsSpdy2() { return spdy_version_ == SPDY2; }
// Version of SPDY protocol to be used.
unsigned char spdy_version_;
};
// All tests are run with two different SPDY versions: SPDY/2 and SPDY/3.
INSTANTIATE_TEST_CASE_P(SpdyFramerTests,
SpdyFramerTest,
::testing::Values(SPDY2, SPDY3));
TEST_P(SpdyFramerTest, IsCompressible) {
SpdyFramer framer(spdy_version_);
for (SpdyControlType type = SYN_STREAM;
type < NUM_CONTROL_FRAME_TYPES;
type = static_cast<SpdyControlType>(type + 1)) {
SpdyFrameBuilder frame(type, CONTROL_FLAG_NONE, spdy_version_, 1024);
scoped_ptr<SpdyControlFrame> control_frame(
reinterpret_cast<SpdyControlFrame*>(frame.take()));
EXPECT_EQ(control_frame->has_header_block(),
framer.IsCompressible(*control_frame))
<< "Frame type: " << type;
}
}
// Test that we can encode and decode a SpdyHeaderBlock in serialized form.
TEST_P(SpdyFramerTest, HeaderBlockInBuffer) {
SpdyHeaderBlock headers;
headers["alpha"] = "beta";
headers["gamma"] = "charlie";
SpdyFramer framer(spdy_version_);
// Encode the header block into a SynStream frame.
scoped_ptr<SpdySynStreamControlFrame> frame(
framer.CreateSynStream(1, // stream id
0, // associated stream id
1, // priority
0, // credential slot
CONTROL_FLAG_NONE,
false, // compress
&headers));
EXPECT_TRUE(frame.get() != NULL);
string serialized_headers(frame->header_block(), frame->header_block_len());
SpdyHeaderBlock new_headers;
EXPECT_TRUE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(),
serialized_headers.size(),
&new_headers));
EXPECT_EQ(headers.size(), new_headers.size());
EXPECT_EQ(headers["alpha"], new_headers["alpha"]);
EXPECT_EQ(headers["gamma"], new_headers["gamma"]);
}
// Test that if there's not a full frame, we fail to parse it.
TEST_P(SpdyFramerTest, UndersizedHeaderBlockInBuffer) {
SpdyHeaderBlock headers;
headers["alpha"] = "beta";
headers["gamma"] = "charlie";
SpdyFramer framer(spdy_version_);
// Encode the header block into a SynStream frame.
scoped_ptr<SpdySynStreamControlFrame> frame(
framer.CreateSynStream(1, // stream id
0, // associated stream id
1, // priority
0, // credential slot
CONTROL_FLAG_NONE,
false, // compress
&headers));
EXPECT_TRUE(frame.get() != NULL);
string serialized_headers(frame->header_block(), frame->header_block_len());
SpdyHeaderBlock new_headers;
EXPECT_FALSE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(),
serialized_headers.size() - 2,
&new_headers));
}
TEST_P(SpdyFramerTest, OutOfOrderHeaders) {
// Frame builder with plentiful buffer size.
SpdyFrameBuilder frame(SYN_STREAM, CONTROL_FLAG_NONE, 1, 1024);
frame.WriteUInt32(3); // stream_id
frame.WriteUInt32(0); // Associated stream id
frame.WriteUInt16(0); // Priority.
if (IsSpdy2()) {
frame.WriteUInt16(2); // Number of headers.
frame.WriteString("gamma");
frame.WriteString("gamma");
frame.WriteString("alpha");
frame.WriteString("alpha");
} else {
frame.WriteUInt32(2); // Number of headers.
frame.WriteStringPiece32("gamma");
frame.WriteStringPiece32("gamma");
frame.WriteStringPiece32("alpha");
frame.WriteStringPiece32("alpha");
}
// write the length
frame.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::kHeaderSize);
SpdyHeaderBlock new_headers;
scoped_ptr<SpdyFrame> control_frame(frame.take());
SpdySynStreamControlFrame syn_frame(control_frame->data(), false);
string serialized_headers(syn_frame.header_block(),
syn_frame.header_block_len());
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(false);
EXPECT_TRUE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(),
serialized_headers.size(),
&new_headers));
}
TEST_P(SpdyFramerTest, CreateCredential) {
SpdyFramer framer(spdy_version_);
{
const char kDescription[] = "CREDENTIAL frame";
const unsigned char kFrameData[] = {
0x80, spdy_version_, 0x00, 0x0A,
0x00, 0x00, 0x00, 0x33,
0x00, 0x03, 0x00, 0x00,
0x00, 0x05, 'p', 'r',
'o', 'o', 'f', 0x00,
0x00, 0x00, 0x06, 'a',
' ', 'c', 'e', 'r',
't', 0x00, 0x00, 0x00,
0x0C, 'a', 'n', 'o',
't', 'h', 'e', 'r',
' ', 'c', 'e', 'r',
't', 0x00, 0x00, 0x00,
0x0A, 'f', 'i', 'n',
'a', 'l', ' ', 'c',
'e', 'r', 't',
};
SpdyCredential credential;
credential.slot = 3;
credential.proof = "proof";
credential.certs.push_back("a cert");
credential.certs.push_back("another cert");
credential.certs.push_back("final cert");
scoped_ptr<SpdyFrame> frame(framer.CreateCredentialFrame(credential));
CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
}
}
TEST_P(SpdyFramerTest, ParseCredentialFrameData) {
SpdyFramer framer(spdy_version_);
{
unsigned char kFrameData[] = {
0x80, spdy_version_, 0x00, 0x0A,
0x00, 0x00, 0x00, 0x33,
0x00, 0x03, 0x00, 0x00,
0x00, 0x05, 'p', 'r',
'o', 'o', 'f', 0x00,
0x00, 0x00, 0x06, 'a',
' ', 'c', 'e', 'r',
't', 0x00, 0x00, 0x00,
0x0C, 'a', 'n', 'o',
't', 'h', 'e', 'r',
' ', 'c', 'e', 'r',
't', 0x00, 0x00, 0x00,
0x0A, 'f', 'i', 'n',
'a', 'l', ' ', 'c',
'e', 'r', 't',
};
SpdyCredentialControlFrame frame(reinterpret_cast<char*>(kFrameData),
false);
SpdyCredential credential;
EXPECT_TRUE(SpdyFramer::ParseCredentialData(frame.payload(), frame.length(),
&credential));
EXPECT_EQ(3u, credential.slot);
EXPECT_EQ("proof", credential.proof);
EXPECT_EQ("a cert", credential.certs.front());
credential.certs.erase(credential.certs.begin());
EXPECT_EQ("another cert", credential.certs.front());
credential.certs.erase(credential.certs.begin());
EXPECT_EQ("final cert", credential.certs.front());
credential.certs.erase(credential.certs.begin());
EXPECT_TRUE(credential.certs.empty());
}
}
TEST_P(SpdyFramerTest, DuplicateHeader) {
// Frame builder with plentiful buffer size.
SpdyFrameBuilder frame(SYN_STREAM, CONTROL_FLAG_NONE, 1, 1024);
frame.WriteUInt32(3); // stream_id
frame.WriteUInt32(0); // associated stream id
frame.WriteUInt16(0); // Priority.
if (IsSpdy2()) {
frame.WriteUInt16(2); // Number of headers.
frame.WriteString("name");
frame.WriteString("value1");
frame.WriteString("name");
frame.WriteString("value2");
} else {
frame.WriteUInt32(2); // Number of headers.
frame.WriteStringPiece32("name");
frame.WriteStringPiece32("value1");
frame.WriteStringPiece32("name");
frame.WriteStringPiece32("value2");
}
// write the length
frame.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::kHeaderSize);
SpdyHeaderBlock new_headers;
scoped_ptr<SpdyFrame> control_frame(frame.take());
SpdySynStreamControlFrame syn_frame(control_frame->data(), false);
string serialized_headers(syn_frame.header_block(),
syn_frame.header_block_len());
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(false);
// This should fail because duplicate headers are verboten by the spec.
EXPECT_FALSE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(),
serialized_headers.size(),
&new_headers));
}
TEST_P(SpdyFramerTest, MultiValueHeader) {
// Frame builder with plentiful buffer size.
SpdyFrameBuilder frame(SYN_STREAM, CONTROL_FLAG_NONE, 1, 1024);
frame.WriteUInt32(3); // stream_id
frame.WriteUInt32(0); // associated stream id
frame.WriteUInt16(0); // Priority.
string value("value1\0value2");
if (IsSpdy2()) {
frame.WriteUInt16(1); // Number of headers.
frame.WriteString("name");
frame.WriteString(value);
} else {
frame.WriteUInt32(1); // Number of headers.
frame.WriteStringPiece32("name");
frame.WriteStringPiece32(value);
}
// write the length
frame.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::kHeaderSize);
SpdyHeaderBlock new_headers;
scoped_ptr<SpdyFrame> control_frame(frame.take());
SpdySynStreamControlFrame syn_frame(control_frame->data(), false);
string serialized_headers(syn_frame.header_block(),
syn_frame.header_block_len());
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(false);
EXPECT_TRUE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(),
serialized_headers.size(),
&new_headers));
EXPECT_TRUE(new_headers.find("name") != new_headers.end());
EXPECT_EQ(value, new_headers.find("name")->second);
}
TEST_P(SpdyFramerTest, BasicCompression) {
SpdyHeaderBlock headers;
headers["server"] = "SpdyServer 1.0";
headers["date"] = "Mon 12 Jan 2009 12:12:12 PST";
headers["status"] = "200";
headers["version"] = "HTTP/1.1";
headers["content-type"] = "text/html";
headers["content-length"] = "12";
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(true);
scoped_ptr<SpdySynStreamControlFrame> frame1(
framer.CreateSynStream(1, // stream id
0, // associated stream id
1, // priority
0, // credential slot
CONTROL_FLAG_NONE,
true, // compress
&headers));
scoped_ptr<SpdySynStreamControlFrame> frame2(
framer.CreateSynStream(1, // stream id
0, // associated stream id
1, // priority
0, // credential slot
CONTROL_FLAG_NONE,
true, // compress
&headers));
// Expect the second frame to be more compact than the first.
EXPECT_LE(frame2->length(), frame1->length());
// Decompress the first frame
scoped_ptr<SpdyFrame> frame3(SpdyFramerTestUtil::DecompressFrame(
&framer, *frame1.get()));
// Decompress the second frame
scoped_ptr<SpdyFrame> frame4(SpdyFramerTestUtil::DecompressFrame(
&framer, *frame2.get()));
// Expect frames 3 & 4 to be the same.
EXPECT_EQ(0,
memcmp(frame3->data(), frame4->data(),
SpdyFrame::kHeaderSize + frame3->length()));
// Expect frames 3 to be the same as a uncompressed frame created
// from scratch.
scoped_ptr<SpdySynStreamControlFrame> uncompressed_frame(
framer.CreateSynStream(1, // stream id
0, // associated stream id
1, // priority
0, // credential slot
CONTROL_FLAG_NONE,
false, // compress
&headers));
EXPECT_EQ(frame3->length(), uncompressed_frame->length());
EXPECT_EQ(0,
memcmp(frame3->data(), uncompressed_frame->data(),
SpdyFrame::kHeaderSize + uncompressed_frame->length()));
}
TEST_P(SpdyFramerTest, Basic) {
const unsigned char kV2Input[] = {
0x80, spdy_version_, 0x00, 0x01, // SYN Stream #1
0x00, 0x00, 0x00, 0x14,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01,
0x00, 0x02, 'h', 'h',
0x00, 0x02, 'v', 'v',
0x80, spdy_version_, 0x00, 0x08, // HEADERS on Stream #1
0x00, 0x00, 0x00, 0x18,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x02,
0x00, 0x02, 'h', '2',
0x00, 0x02, 'v', '2',
0x00, 0x02, 'h', '3',
0x00, 0x02, 'v', '3',
0x00, 0x00, 0x00, 0x01, // DATA on Stream #1
0x00, 0x00, 0x00, 0x0c,
0xde, 0xad, 0xbe, 0xef,
0xde, 0xad, 0xbe, 0xef,
0xde, 0xad, 0xbe, 0xef,
0x80, spdy_version_, 0x00, 0x01, // SYN Stream #3
0x00, 0x00, 0x00, 0x0c,
0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, // DATA on Stream #3
0x00, 0x00, 0x00, 0x08,
0xde, 0xad, 0xbe, 0xef,
0xde, 0xad, 0xbe, 0xef,
0x00, 0x00, 0x00, 0x01, // DATA on Stream #1
0x00, 0x00, 0x00, 0x04,
0xde, 0xad, 0xbe, 0xef,
0x80, spdy_version_, 0x00, 0x03, // RST_STREAM on Stream #1
0x00, 0x00, 0x00, 0x08,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, // DATA on Stream #3
0x00, 0x00, 0x00, 0x00,
0x80, spdy_version_, 0x00, 0x03, // RST_STREAM on Stream #3
0x00, 0x00, 0x00, 0x08,
0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x00,
};
const unsigned char kV3Input[] = {
0x80, spdy_version_, 0x00, 0x01, // SYN Stream #1
0x00, 0x00, 0x00, 0x1a,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00,
0x00, 0x02, 'h', 'h',
0x00, 0x00, 0x00, 0x02,
'v', 'v',
0x80, spdy_version_, 0x00, 0x08, // HEADERS on Stream #1
0x00, 0x00, 0x00, 0x22,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
0x00, 0x02, 0x00, 0x00,
0x00, 0x02, 'h', '2',
0x00, 0x00, 0x00, 0x02,
'v', '2', 0x00, 0x00,
0x00, 0x02, 'h', '3',
0x00, 0x00, 0x00, 0x02,
'v', '3',
0x00, 0x00, 0x00, 0x01, // DATA on Stream #1
0x00, 0x00, 0x00, 0x0c,
0xde, 0xad, 0xbe, 0xef,
0xde, 0xad, 0xbe, 0xef,
0xde, 0xad, 0xbe, 0xef,
0x80, spdy_version_, 0x00, 0x01, // SYN Stream #3
0x00, 0x00, 0x00, 0x0e,
0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
0x00, 0x00, 0x00, 0x03, // DATA on Stream #3
0x00, 0x00, 0x00, 0x08,
0xde, 0xad, 0xbe, 0xef,
0xde, 0xad, 0xbe, 0xef,
0x00, 0x00, 0x00, 0x01, // DATA on Stream #1
0x00, 0x00, 0x00, 0x04,
0xde, 0xad, 0xbe, 0xef,
0x80, spdy_version_, 0x00, 0x03, // RST_STREAM on Stream #1
0x00, 0x00, 0x00, 0x08,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, // DATA on Stream #3
0x00, 0x00, 0x00, 0x00,
0x80, spdy_version_, 0x00, 0x03, // RST_STREAM on Stream #3
0x00, 0x00, 0x00, 0x08,
0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x00,
};
TestSpdyVisitor visitor(spdy_version_);
if (IsSpdy2()) {
visitor.SimulateInFramer(kV2Input, sizeof(kV2Input));
} else {
visitor.SimulateInFramer(kV3Input, sizeof(kV3Input));
}
EXPECT_EQ(0, visitor.error_count_);
EXPECT_EQ(2, visitor.syn_frame_count_);
EXPECT_EQ(0, visitor.syn_reply_frame_count_);
EXPECT_EQ(1, visitor.headers_frame_count_);
EXPECT_EQ(24, visitor.data_bytes_);
EXPECT_EQ(2, visitor.fin_frame_count_);
EXPECT_EQ(0, visitor.fin_flag_count_);
EXPECT_EQ(0, visitor.zero_length_data_frame_count_);
EXPECT_EQ(4, visitor.data_frame_count_);
}
// Test that the FIN flag on a data frame signifies EOF.
TEST_P(SpdyFramerTest, FinOnDataFrame) {
const unsigned char kV2Input[] = {
0x80, spdy_version_, 0x00, 0x01, // SYN Stream #1
0x00, 0x00, 0x00, 0x14,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01,
0x00, 0x02, 'h', 'h',
0x00, 0x02, 'v', 'v',
0x80, spdy_version_, 0x00, 0x02, // SYN REPLY Stream #1
0x00, 0x00, 0x00, 0x10,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x01,
0x00, 0x02, 'a', 'a',
0x00, 0x02, 'b', 'b',
0x00, 0x00, 0x00, 0x01, // DATA on Stream #1
0x00, 0x00, 0x00, 0x0c,
0xde, 0xad, 0xbe, 0xef,
0xde, 0xad, 0xbe, 0xef,
0xde, 0xad, 0xbe, 0xef,
0x00, 0x00, 0x00, 0x01, // DATA on Stream #1, with EOF
0x01, 0x00, 0x00, 0x04,
0xde, 0xad, 0xbe, 0xef,
};
const unsigned char kV3Input[] = {
0x80, spdy_version_, 0x00, 0x01, // SYN Stream #1
0x00, 0x00, 0x00, 0x1a,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00,
0x00, 0x02, 'h', 'h',
0x00, 0x00, 0x00, 0x02,
'v', 'v',
0x80, spdy_version_, 0x00, 0x02, // SYN REPLY Stream #1
0x00, 0x00, 0x00, 0x16,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00,
0x00, 0x02, 'a', 'a',
0x00, 0x00, 0x00, 0x02,
'b', 'b',
0x00, 0x00, 0x00, 0x01, // DATA on Stream #1
0x00, 0x00, 0x00, 0x0c,
0xde, 0xad, 0xbe, 0xef,
0xde, 0xad, 0xbe, 0xef,
0xde, 0xad, 0xbe, 0xef,
0x00, 0x00, 0x00, 0x01, // DATA on Stream #1, with EOF
0x01, 0x00, 0x00, 0x04,
0xde, 0xad, 0xbe, 0xef,
};
TestSpdyVisitor visitor(spdy_version_);
if (IsSpdy2()) {
visitor.SimulateInFramer(kV2Input, sizeof(kV2Input));
} else {
visitor.SimulateInFramer(kV3Input, sizeof(kV3Input));
}
EXPECT_EQ(0, visitor.error_count_);
EXPECT_EQ(1, visitor.syn_frame_count_);
EXPECT_EQ(1, visitor.syn_reply_frame_count_);
EXPECT_EQ(0, visitor.headers_frame_count_);
EXPECT_EQ(16, visitor.data_bytes_);
EXPECT_EQ(0, visitor.fin_frame_count_);
EXPECT_EQ(0, visitor.fin_flag_count_);
EXPECT_EQ(1, visitor.zero_length_data_frame_count_);
EXPECT_EQ(2, visitor.data_frame_count_);
}
// Test that the FIN flag on a SYN reply frame signifies EOF.
TEST_P(SpdyFramerTest, FinOnSynReplyFrame) {
const unsigned char kV2Input[] = {
0x80, spdy_version_, 0x00, 0x01, // SYN Stream #1
0x00, 0x00, 0x00, 0x14,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01,
0x00, 0x02, 'h', 'h',
0x00, 0x02, 'v', 'v',
0x80, spdy_version_, 0x00, 0x02, // SYN REPLY Stream #1
0x01, 0x00, 0x00, 0x14,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01,
0x00, 0x02, 'a', 'a',
0x00, 0x02, 'b', 'b',
};
const unsigned char kV3Input[] = {
0x80, spdy_version_, 0x00, 0x01, // SYN Stream #1
0x00, 0x00, 0x00, 0x1a,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00,
0x00, 0x02, 'h', 'h',
0x00, 0x00, 0x00, 0x02,
'v', 'v',
0x80, spdy_version_, 0x00, 0x02, // SYN REPLY Stream #1
0x01, 0x00, 0x00, 0x1a,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00,
0x00, 0x02, 'a', 'a',
0x00, 0x00, 0x00, 0x02,
'b', 'b',
};
TestSpdyVisitor visitor(spdy_version_);
if (IsSpdy2()) {
visitor.SimulateInFramer(kV2Input, sizeof(kV2Input));
} else {
visitor.SimulateInFramer(kV3Input, sizeof(kV3Input));
}
EXPECT_EQ(0, visitor.error_count_);
EXPECT_EQ(1, visitor.syn_frame_count_);
EXPECT_EQ(1, visitor.syn_reply_frame_count_);
EXPECT_EQ(0, visitor.headers_frame_count_);
EXPECT_EQ(0, visitor.data_bytes_);
EXPECT_EQ(0, visitor.fin_frame_count_);
EXPECT_EQ(1, visitor.fin_flag_count_);
EXPECT_EQ(1, visitor.zero_length_data_frame_count_);
EXPECT_EQ(0, visitor.data_frame_count_);
}
TEST_P(SpdyFramerTest, HeaderCompression) {
SpdyFramer send_framer(spdy_version_);
SpdyFramer recv_framer(spdy_version_);
send_framer.set_enable_compression(true);
recv_framer.set_enable_compression(true);
const char kHeader1[] = "header1";
const char kHeader2[] = "header2";
const char kHeader3[] = "header3";
const char kValue1[] = "value1";
const char kValue2[] = "value2";
const char kValue3[] = "value3";
// SYN_STREAM #1
SpdyHeaderBlock block;
block[kHeader1] = kValue1;
block[kHeader2] = kValue2;
SpdyControlFlags flags(CONTROL_FLAG_NONE);
scoped_ptr<SpdySynStreamControlFrame> syn_frame_1(
send_framer.CreateSynStream(1, // stream id
0, // associated stream id
0, // priority
0, // credential slot
flags,
true, // compress
&block));
EXPECT_TRUE(syn_frame_1.get() != NULL);
// SYN_STREAM #2
block[kHeader3] = kValue3;
scoped_ptr<SpdySynStreamControlFrame> syn_frame_2(
send_framer.CreateSynStream(3, // stream id
0, // associated stream id
0, // priority
0, // credential slot
flags,
true, // compress
&block));
EXPECT_TRUE(syn_frame_2.get() != NULL);
// Now start decompressing
scoped_ptr<SpdyFrame> decompressed;
scoped_ptr<SpdySynStreamControlFrame> syn_frame;
scoped_ptr<string> serialized_headers;
SpdyHeaderBlock decompressed_headers;
// Decompress SYN_STREAM #1
decompressed.reset(SpdyFramerTestUtil::DecompressFrame(
&recv_framer, *syn_frame_1.get()));
EXPECT_TRUE(decompressed.get() != NULL);
EXPECT_TRUE(decompressed->is_control_frame());
EXPECT_EQ(SYN_STREAM,
reinterpret_cast<SpdyControlFrame*>(decompressed.get())->type());
syn_frame.reset(new SpdySynStreamControlFrame(decompressed->data(), false));
serialized_headers.reset(new string(syn_frame->header_block(),
syn_frame->header_block_len()));
EXPECT_TRUE(recv_framer.ParseHeaderBlockInBuffer(serialized_headers->c_str(),
serialized_headers->size(),
&decompressed_headers));
EXPECT_EQ(2u, decompressed_headers.size());
EXPECT_EQ(kValue1, decompressed_headers[kHeader1]);
EXPECT_EQ(kValue2, decompressed_headers[kHeader2]);
// Decompress SYN_STREAM #2
decompressed.reset(SpdyFramerTestUtil::DecompressFrame(
&recv_framer, *syn_frame_2.get()));
EXPECT_TRUE(decompressed.get() != NULL);
EXPECT_TRUE(decompressed->is_control_frame());
EXPECT_EQ(SYN_STREAM,
reinterpret_cast<SpdyControlFrame*>(decompressed.get())->type());
syn_frame.reset(new SpdySynStreamControlFrame(decompressed->data(), false));
serialized_headers.reset(new string(syn_frame->header_block(),
syn_frame->header_block_len()));
decompressed_headers.clear();
EXPECT_TRUE(recv_framer.ParseHeaderBlockInBuffer(serialized_headers->c_str(),
serialized_headers->size(),
&decompressed_headers));
EXPECT_EQ(3u, decompressed_headers.size());
EXPECT_EQ(kValue1, decompressed_headers[kHeader1]);
EXPECT_EQ(kValue2, decompressed_headers[kHeader2]);
EXPECT_EQ(kValue3, decompressed_headers[kHeader3]);
}
// Verify we don't leak when we leave streams unclosed
TEST_P(SpdyFramerTest, UnclosedStreamDataCompressors) {
SpdyFramer send_framer(spdy_version_);
send_framer.set_enable_compression(true);
const char kHeader1[] = "header1";
const char kHeader2[] = "header2";
const char kValue1[] = "value1";
const char kValue2[] = "value2";
SpdyHeaderBlock block;
block[kHeader1] = kValue1;
block[kHeader2] = kValue2;
SpdyControlFlags flags(CONTROL_FLAG_NONE);
scoped_ptr<SpdyFrame> syn_frame(
send_framer.CreateSynStream(1, // stream id
0, // associated stream id
0, // priority
0, // credential slot
flags,
true, // compress
&block));
EXPECT_TRUE(syn_frame.get() != NULL);
const char bytes[] = "this is a test test test test test!";
scoped_ptr<SpdyFrame> send_frame(
send_framer.CreateDataFrame(
1, bytes, arraysize(bytes),
static_cast<SpdyDataFlags>(DATA_FLAG_FIN)));
EXPECT_TRUE(send_frame.get() != NULL);
// Run the inputs through the framer.
TestSpdyVisitor visitor(spdy_version_);
visitor.use_compression_ = true;
const unsigned char* data;
data = reinterpret_cast<const unsigned char*>(syn_frame->data());
visitor.SimulateInFramer(data, syn_frame->length() + SpdyFrame::kHeaderSize);
data = reinterpret_cast<const unsigned char*>(send_frame->data());
visitor.SimulateInFramer(data, send_frame->length() + SpdyFrame::kHeaderSize);
EXPECT_EQ(0, visitor.error_count_);
EXPECT_EQ(1, visitor.syn_frame_count_);
EXPECT_EQ(0, visitor.syn_reply_frame_count_);
EXPECT_EQ(0, visitor.headers_frame_count_);
EXPECT_EQ(arraysize(bytes), static_cast<unsigned>(visitor.data_bytes_));
EXPECT_EQ(0, visitor.fin_frame_count_);
EXPECT_EQ(0, visitor.fin_flag_count_);
EXPECT_EQ(1, visitor.zero_length_data_frame_count_);
EXPECT_EQ(1, visitor.data_frame_count_);
}
// Verify we can decompress the stream even if handed over to the
// framer 1 byte at a time.
TEST_P(SpdyFramerTest, UnclosedStreamDataCompressorsOneByteAtATime) {
SpdyFramer send_framer(spdy_version_);
send_framer.set_enable_compression(true);
const char kHeader1[] = "header1";
const char kHeader2[] = "header2";
const char kValue1[] = "value1";
const char kValue2[] = "value2";
SpdyHeaderBlock block;
block[kHeader1] = kValue1;
block[kHeader2] = kValue2;
SpdyControlFlags flags(CONTROL_FLAG_NONE);
scoped_ptr<SpdyFrame> syn_frame(
send_framer.CreateSynStream(1, // stream id
0, // associated stream id
0, // priority
0, // credential slot
flags,
true, // compress
&block));
EXPECT_TRUE(syn_frame.get() != NULL);
const char bytes[] = "this is a test test test test test!";
scoped_ptr<SpdyFrame> send_frame(
send_framer.CreateDataFrame(
1, bytes, arraysize(bytes),
static_cast<SpdyDataFlags>(DATA_FLAG_FIN)));
EXPECT_TRUE(send_frame.get() != NULL);
// Run the inputs through the framer.
TestSpdyVisitor visitor(spdy_version_);
visitor.use_compression_ = true;
const unsigned char* data;
data = reinterpret_cast<const unsigned char*>(syn_frame->data());
for (size_t idx = 0;
idx < syn_frame->length() + SpdyFrame::kHeaderSize;
++idx) {
visitor.SimulateInFramer(data + idx, 1);
ASSERT_EQ(0, visitor.error_count_);
}
data = reinterpret_cast<const unsigned char*>(send_frame->data());
for (size_t idx = 0;
idx < send_frame->length() + SpdyFrame::kHeaderSize;
++idx) {
visitor.SimulateInFramer(data + idx, 1);
ASSERT_EQ(0, visitor.error_count_);
}
EXPECT_EQ(0, visitor.error_count_);
EXPECT_EQ(1, visitor.syn_frame_count_);
EXPECT_EQ(0, visitor.syn_reply_frame_count_);
EXPECT_EQ(0, visitor.headers_frame_count_);
EXPECT_EQ(arraysize(bytes), static_cast<unsigned>(visitor.data_bytes_));
EXPECT_EQ(0, visitor.fin_frame_count_);
EXPECT_EQ(0, visitor.fin_flag_count_);
EXPECT_EQ(1, visitor.zero_length_data_frame_count_);
EXPECT_EQ(1, visitor.data_frame_count_);
}
TEST_P(SpdyFramerTest, WindowUpdateFrame) {
SpdyFramer framer(spdy_version_);
scoped_ptr<SpdyWindowUpdateControlFrame> window_update_frame(
framer.CreateWindowUpdate(1, 0x12345678));
const unsigned char expected_data_frame[] = {
0x80, spdy_version_, 0x00, 0x09,
0x00, 0x00, 0x00, 0x08,
0x00, 0x00, 0x00, 0x01,
0x12, 0x34, 0x56, 0x78
};
EXPECT_EQ(16u, window_update_frame->size());
EXPECT_EQ(0,
memcmp(window_update_frame->data(), expected_data_frame, 16));
}
TEST_P(SpdyFramerTest, CreateDataFrame) {
SpdyFramer framer(spdy_version_);
{
const char kDescription[] = "'hello' data frame, no FIN";
const unsigned char kFrameData[] = {
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x05,
'h', 'e', 'l', 'l',
'o'
};
const char bytes[] = "hello";
scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
1, bytes, strlen(bytes), DATA_FLAG_NONE));
CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
}
{
const char kDescription[] = "Data frame with negative data byte, no FIN";
const unsigned char kFrameData[] = {
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x01,
0xff
};
scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
1, "\xff", 1, DATA_FLAG_NONE));
CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
}
{
const char kDescription[] = "'hello' data frame, with FIN";
const unsigned char kFrameData[] = {
0x00, 0x00, 0x00, 0x01,
0x01, 0x00, 0x00, 0x05,
'h', 'e', 'l', 'l',
'o'
};
scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
1, "hello", 5, DATA_FLAG_FIN));
CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
}
{
const char kDescription[] = "Empty data frame";
const unsigned char kFrameData[] = {
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
};
scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
1, "", 0, DATA_FLAG_NONE));
CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
}
{
const char kDescription[] = "Data frame with max stream ID";
const unsigned char kFrameData[] = {
0x7f, 0xff, 0xff, 0xff,
0x01, 0x00, 0x00, 0x05,
'h', 'e', 'l', 'l',
'o'
};
scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
0x7fffffff, "hello", 5, DATA_FLAG_FIN));
CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
}
{
const char kDescription[] = "Large data frame";
const int kDataSize = 4 * 1024 * 1024; // 4 MB
const string kData(kDataSize, 'A');
const unsigned char kFrameHeader[] = {
0x00, 0x00, 0x00, 0x01,
0x01, 0x40, 0x00, 0x00,
};
const int kFrameSize = arraysize(kFrameHeader) + kDataSize;
scoped_array<unsigned char> expected_frame_data(
new unsigned char[kFrameSize]);
memcpy(expected_frame_data.get(), kFrameHeader, arraysize(kFrameHeader));
memset(expected_frame_data.get() + arraysize(kFrameHeader), 'A', kDataSize);
scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
1, kData.data(), kData.size(), DATA_FLAG_FIN));
CompareFrame(kDescription, *frame, expected_frame_data.get(), kFrameSize);
}
}
TEST_P(SpdyFramerTest, CreateSynStreamUncompressed) {
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(false);
{
const char kDescription[] = "SYN_STREAM frame, lowest pri, slot 2, no FIN";
SpdyHeaderBlock headers;
headers["bar"] = "foo";
headers["foo"] = "bar";
const unsigned char kPri = IsSpdy2() ? 0xC0 : 0xE0;
const unsigned char kCre = IsSpdy2() ? 0 : 2;
const unsigned char kV2FrameData[] = {
0x80, spdy_version_, 0x00, 0x01,
0x00, 0x00, 0x00, 0x20,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
kPri, 0x00, 0x00, 0x02,
0x00, 0x03, 'b', 'a',
'r', 0x00, 0x03, 'f',
'o', 'o', 0x00, 0x03,
'f', 'o', 'o', 0x00,
0x03, 'b', 'a', 'r'
};
const unsigned char kV3FrameData[] = {
0x80, spdy_version_, 0x00, 0x01,
0x00, 0x00, 0x00, 0x2a,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
kPri, kCre, 0x00, 0x00,
0x00, 0x02, 0x00, 0x00,
0x00, 0x03, 'b', 'a',
'r', 0x00, 0x00, 0x00,
0x03, 'f', 'o', 'o',
0x00, 0x00, 0x00, 0x03,
'f', 'o', 'o', 0x00,
0x00, 0x00, 0x03, 'b',
'a', 'r'
};
scoped_ptr<SpdySynStreamControlFrame> frame(
framer.CreateSynStream(1, // stream id
0, // associated stream id
framer.GetLowestPriority(),
kCre, // credential slot
CONTROL_FLAG_NONE,
false, // compress
&headers));
CompareFrame(kDescription,
*frame,
IsSpdy2() ? kV2FrameData : kV3FrameData,
IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
EXPECT_EQ(1u, SpdyFramer::GetControlFrameStreamId(frame.get()));
}
{
const char kDescription[] =
"SYN_STREAM frame with a 0-length header name, highest pri, FIN, "
"max stream ID";
SpdyHeaderBlock headers;
headers[""] = "foo";
headers["foo"] = "bar";
const unsigned char kV2FrameData[] = {
0x80, spdy_version_, 0x00, 0x01,
0x01, 0x00, 0x00, 0x1D,
0x7f, 0xff, 0xff, 0xff,
0x7f, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x03,
'f', 'o', 'o', 0x00,
0x03, 'f', 'o', 'o',
0x00, 0x03, 'b', 'a',
'r'
};
const unsigned char kV3FrameData[] = {
0x80, spdy_version_, 0x00, 0x01,
0x01, 0x00, 0x00, 0x27,
0x7f, 0xff, 0xff, 0xff,
0x7f, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00,
0x00, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 'f', 'o',
'o', 0x00, 0x00, 0x00,
0x03, 'f', 'o', 'o',
0x00, 0x00, 0x00, 0x03,
'b', 'a', 'r'
};
scoped_ptr<SpdyFrame> frame(
framer.CreateSynStream(0x7fffffff, // stream id
0x7fffffff, // associated stream id
framer.GetHighestPriority(),
0, // credential slot
CONTROL_FLAG_FIN,
false, // compress
&headers));
CompareFrame(kDescription,
*frame,
IsSpdy2() ? kV2FrameData : kV3FrameData,
IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
}
{
const char kDescription[] =
"SYN_STREAM frame with a 0-length header val, high pri, FIN, "
"max stream ID";
SpdyHeaderBlock headers;
headers["bar"] = "foo";
headers["foo"] = "";
const unsigned char kPri = IsSpdy2() ? 0x40 : 0x20;
const unsigned char kV2FrameData[] = {
0x80, spdy_version_, 0x00, 0x01,
0x01, 0x00, 0x00, 0x1D,
0x7f, 0xff, 0xff, 0xff,
0x7f, 0xff, 0xff, 0xff,
kPri, 0x00, 0x00, 0x02,
0x00, 0x03, 'b', 'a',
'r', 0x00, 0x03, 'f',
'o', 'o', 0x00, 0x03,
'f', 'o', 'o', 0x00,
0x00
};
const unsigned char kV3FrameData[] = {
0x80, spdy_version_, 0x00, 0x01,
0x01, 0x00, 0x00, 0x27,
0x7f, 0xff, 0xff, 0xff,
0x7f, 0xff, 0xff, 0xff,
kPri, 0x00, 0x00, 0x00,
0x00, 0x02, 0x00, 0x00,
0x00, 0x03, 'b', 'a',
'r', 0x00, 0x00, 0x00,
0x03, 'f', 'o', 'o',
0x00, 0x00, 0x00, 0x03,
'f', 'o', 'o', 0x00,
0x00, 0x00, 0x00
};
scoped_ptr<SpdyFrame> frame(
framer.CreateSynStream(0x7fffffff, // stream id
0x7fffffff, // associated stream id
1, // priority
0, // credential slot
CONTROL_FLAG_FIN,
false, // compress
&headers));
CompareFrame(kDescription,
*frame,
IsSpdy2() ? kV2FrameData : kV3FrameData,
IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
}
}
// TODO(phajdan.jr): Clean up after we no longer need
// to workaround http://crbug.com/139744.
#if !defined(USE_SYSTEM_ZLIB)
TEST_P(SpdyFramerTest, CreateSynStreamCompressed) {
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(true);
{
const char kDescription[] =
"SYN_STREAM frame, low pri, no FIN";
SpdyHeaderBlock headers;
headers["bar"] = "foo";
headers["foo"] = "bar";
const SpdyPriority priority = IsSpdy2() ? 2 : 4;
const unsigned char kV2FrameData[] = {
0x80, spdy_version_, 0x00, 0x01,
0x00, 0x00, 0x00, 0x36,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
0x80, 0x00, 0x38, 0xea,
0xdf, 0xa2, 0x51, 0xb2,
0x62, 0x60, 0x62, 0x60,
0x4e, 0x4a, 0x2c, 0x62,
0x60, 0x06, 0x08, 0xa0,
0xb4, 0xfc, 0x7c, 0x80,
0x00, 0x62, 0x60, 0x4e,
0xcb, 0xcf, 0x67, 0x60,
0x06, 0x08, 0xa0, 0xa4,
0xc4, 0x22, 0x80, 0x00,
0x02, 0x00, 0x00, 0x00,
0xff, 0xff,
};
const unsigned char kV3FrameData[] = {
0x80, spdy_version_, 0x00, 0x01,
0x00, 0x00, 0x00, 0x37,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
0x80, 0x00, 0x38, 0xEA,
0xE3, 0xC6, 0xA7, 0xC2,
0x02, 0xE5, 0x0E, 0x50,
0xC2, 0x4B, 0x4A, 0x04,
0xE5, 0x0B, 0x66, 0x80,
0x00, 0x4A, 0xCB, 0xCF,
0x07, 0x08, 0x20, 0x10,
0x95, 0x96, 0x9F, 0x0F,
0xA2, 0x00, 0x02, 0x28,
0x29, 0xB1, 0x08, 0x20,
0x80, 0x00, 0x00, 0x00,
0x00, 0xFF, 0xFF,
};
scoped_ptr<SpdyFrame> frame(
framer.CreateSynStream(1, // stream id
0, // associated stream id
priority,
0, // credential slot
CONTROL_FLAG_NONE,
true, // compress
&headers));
CompareFrame(kDescription,
*frame,
IsSpdy2() ? kV2FrameData : kV3FrameData,
IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
}
}
#endif // !defined(USE_SYSTEM_ZLIB)
TEST_P(SpdyFramerTest, CreateSynReplyUncompressed) {
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(false);
{
const char kDescription[] = "SYN_REPLY frame, no FIN";
SpdyHeaderBlock headers;
headers["bar"] = "foo";
headers["foo"] = "bar";
const unsigned char kV2FrameData[] = {
0x80, spdy_version_, 0x00, 0x02,
0x00, 0x00, 0x00, 0x1C,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x02,
0x00, 0x03, 'b', 'a',
'r', 0x00, 0x03, 'f',
'o', 'o', 0x00, 0x03,
'f', 'o', 'o', 0x00,
0x03, 'b', 'a', 'r'
};
const unsigned char kV3FrameData[] = {
0x80, spdy_version_, 0x00, 0x02,
0x00, 0x00, 0x00, 0x24,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x03,
'b', 'a', 'r', 0x00,
0x00, 0x00, 0x03, 'f',
'o', 'o', 0x00, 0x00,
0x00, 0x03, 'f', 'o',
'o', 0x00, 0x00, 0x00,
0x03, 'b', 'a', 'r'
};
scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
1, CONTROL_FLAG_NONE, false, &headers));
CompareFrame(kDescription,
*frame,
IsSpdy2() ? kV2FrameData : kV3FrameData,
IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
}
{
const char kDescription[] =
"SYN_REPLY frame with a 0-length header name, FIN, max stream ID";
SpdyHeaderBlock headers;
headers[""] = "foo";
headers["foo"] = "bar";
const unsigned char kV2FrameData[] = {
0x80, spdy_version_, 0x00, 0x02,
0x01, 0x00, 0x00, 0x19,
0x7f, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x03,
'f', 'o', 'o', 0x00,
0x03, 'f', 'o', 'o',
0x00, 0x03, 'b', 'a',
'r'
};
const unsigned char kV3FrameData[] = {
0x80, spdy_version_, 0x00, 0x02,
0x01, 0x00, 0x00, 0x21,
0x7f, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03,
'f', 'o', 'o', 0x00,
0x00, 0x00, 0x03, 'f',
'o', 'o', 0x00, 0x00,
0x00, 0x03, 'b', 'a',
'r'
};
scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
CompareFrame(kDescription,
*frame,
IsSpdy2() ? kV2FrameData : kV3FrameData,
IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
}
{
const char kDescription[] =
"SYN_REPLY frame with a 0-length header val, FIN, max stream ID";
SpdyHeaderBlock headers;
headers["bar"] = "foo";
headers["foo"] = "";
const unsigned char kV2FrameData[] = {
0x80, spdy_version_, 0x00, 0x02,
0x01, 0x00, 0x00, 0x19,
0x7f, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x02,
0x00, 0x03, 'b', 'a',
'r', 0x00, 0x03, 'f',
'o', 'o', 0x00, 0x03,
'f', 'o', 'o', 0x00,
0x00
};
const unsigned char kV3FrameData[] = {
0x80, spdy_version_, 0x00, 0x02,
0x01, 0x00, 0x00, 0x21,
0x7f, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x03,
'b', 'a', 'r', 0x00,
0x00, 0x00, 0x03, 'f',
'o', 'o', 0x00, 0x00,
0x00, 0x03, 'f', 'o',
'o', 0x00, 0x00, 0x00,
0x00
};
scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
CompareFrame(kDescription,
*frame,
IsSpdy2() ? kV2FrameData : kV3FrameData,
IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
}
}
// TODO(phajdan.jr): Clean up after we no longer need
// to workaround http://crbug.com/139744.
#if !defined(USE_SYSTEM_ZLIB)
TEST_P(SpdyFramerTest, CreateSynReplyCompressed) {
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(true);
{
const char kDescription[] = "SYN_REPLY frame, no FIN";
SpdyHeaderBlock headers;
headers["bar"] = "foo";
headers["foo"] = "bar";
const unsigned char kV2FrameData[] = {
0x80, spdy_version_, 0x00, 0x02,
0x00, 0x00, 0x00, 0x32,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x38, 0xea,
0xdf, 0xa2, 0x51, 0xb2,
0x62, 0x60, 0x62, 0x60,
0x4e, 0x4a, 0x2c, 0x62,
0x60, 0x06, 0x08, 0xa0,
0xb4, 0xfc, 0x7c, 0x80,
0x00, 0x62, 0x60, 0x4e,
0xcb, 0xcf, 0x67, 0x60,
0x06, 0x08, 0xa0, 0xa4,
0xc4, 0x22, 0x80, 0x00,
0x02, 0x00, 0x00, 0x00,
0xff, 0xff,
};
const unsigned char kV3FrameData[] = {
0x80, spdy_version_, 0x00, 0x02,
0x00, 0x00, 0x00, 0x31,
0x00, 0x00, 0x00, 0x01,
0x38, 0xea, 0xe3, 0xc6,
0xa7, 0xc2, 0x02, 0xe5,
0x0e, 0x50, 0xc2, 0x4b,
0x4a, 0x04, 0xe5, 0x0b,
0x66, 0x80, 0x00, 0x4a,
0xcb, 0xcf, 0x07, 0x08,
0x20, 0x10, 0x95, 0x96,
0x9f, 0x0f, 0xa2, 0x00,
0x02, 0x28, 0x29, 0xb1,
0x08, 0x20, 0x80, 0x00,
0x00, 0x00, 0x00, 0xff,
0xff,
};
scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
1, CONTROL_FLAG_NONE, true, &headers));
CompareFrame(kDescription,
*frame,
IsSpdy2() ? kV2FrameData : kV3FrameData,
IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
}
}
#endif // !defined(USE_SYSTEM_ZLIB)
TEST_P(SpdyFramerTest, CreateRstStream) {
SpdyFramer framer(spdy_version_);
{
const char kDescription[] = "RST_STREAM frame";
const unsigned char kFrameData[] = {
0x80, spdy_version_, 0x00, 0x03,
0x00, 0x00, 0x00, 0x08,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x01,
};
scoped_ptr<SpdyRstStreamControlFrame> frame(
framer.CreateRstStream(1, PROTOCOL_ERROR));
CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
EXPECT_EQ(1u, SpdyFramer::GetControlFrameStreamId(frame.get()));
}
{
const char kDescription[] = "RST_STREAM frame with max stream ID";
const unsigned char kFrameData[] = {
0x80, spdy_version_, 0x00, 0x03,
0x00, 0x00, 0x00, 0x08,
0x7f, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x01,
};
scoped_ptr<SpdyFrame> frame(framer.CreateRstStream(0x7FFFFFFF,
PROTOCOL_ERROR));
CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
}
{
const char kDescription[] = "RST_STREAM frame with max status code";
const unsigned char kFrameData[] = {
0x80, spdy_version_, 0x00, 0x03,
0x00, 0x00, 0x00, 0x08,
0x7f, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x06,
};
scoped_ptr<SpdyFrame> frame(framer.CreateRstStream(0x7FFFFFFF,
INTERNAL_ERROR));
CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
}
}
TEST_P(SpdyFramerTest, CreateSettings) {
SpdyFramer framer(spdy_version_);
{
const char kDescription[] = "Network byte order SETTINGS frame";
uint32 kValue = 0x0a0b0c0d;
SpdySettingsFlags kFlags = static_cast<SpdySettingsFlags>(0x04);
SpdySettingsIds kId = static_cast<SpdySettingsIds>(0x030201);
SettingsMap settings;
settings[kId] = SettingsFlagsAndValue(kFlags, kValue);
EXPECT_EQ(kFlags, settings[kId].first);
EXPECT_EQ(kValue, settings[kId].second);
const unsigned char kFrameDatav2[] = {
0x80, spdy_version_, 0x00, 0x04,
0x00, 0x00, 0x00, 0x0c,
0x00, 0x00, 0x00, 0x01,
0x01, 0x02, 0x03, 0x04,
0x0a, 0x0b, 0x0c, 0x0d,
};
const unsigned char kFrameDatav3[] = {
0x80, spdy_version_, 0x00, 0x04,
0x00, 0x00, 0x00, 0x0c,
0x00, 0x00, 0x00, 0x01,
0x04, 0x03, 0x02, 0x01,
0x0a, 0x0b, 0x0c, 0x0d,
};
scoped_ptr<SpdySettingsControlFrame> frame(framer.CreateSettings(settings));
CompareFrame(kDescription,
*frame,
IsSpdy2() ? kFrameDatav2 : kFrameDatav3,
arraysize(kFrameDatav3)); // Size is unchanged among versions.
EXPECT_EQ(SpdyFramer::kInvalidStream,
SpdyFramer::GetControlFrameStreamId(frame.get()));
// Make sure that ParseSettings also works as advertised.
SettingsMap parsed_settings;
EXPECT_TRUE(framer.ParseSettings(frame.get(), &parsed_settings));
EXPECT_EQ(settings.size(), parsed_settings.size());
EXPECT_EQ(kFlags, parsed_settings[kId].first);
EXPECT_EQ(kValue, parsed_settings[kId].second);
}
{
const char kDescription[] = "Basic SETTINGS frame";
SettingsMap settings;
#if defined(__LB_SHELL__)
// second argument is expected to be in network byte order
AddSpdySettingFromWireFormat(
&settings, htonl(0x00000000), 0x00000001); // 1st Setting
AddSpdySettingFromWireFormat(
&settings, htonl(0x02000001), 0x00000002); // 2nd Setting
AddSpdySettingFromWireFormat(
&settings, htonl(0x03000002), 0x00000003); // 3rd Setting
AddSpdySettingFromWireFormat(
&settings, htonl(0x04000003), 0xff000004); // 4th Setting
AddSpdySettingFromWireFormat(
&settings, htonl(0x050000ff), 0x00000005); // 5th Setting
AddSpdySettingFromWireFormat(
&settings, htonl(0xffffffff), 0x00000006); // 6th Setting
#else
AddSpdySettingFromWireFormat(
&settings, 0x00000000, 0x00000001); // 1st Setting
AddSpdySettingFromWireFormat(
&settings, 0x01000002, 0x00000002); // 2nd Setting
AddSpdySettingFromWireFormat(
&settings, 0x02000003, 0x00000003); // 3rd Setting
AddSpdySettingFromWireFormat(
&settings, 0x03000004, 0xff000004); // 4th Setting
AddSpdySettingFromWireFormat(
&settings, 0xff000005, 0x00000005); // 5th Setting
AddSpdySettingFromWireFormat(
&settings, 0xffffffff, 0x00000006); // 6th Setting
#endif
const unsigned char kFrameData[] = {
0x80, spdy_version_, 0x00, 0x04,
0x00, 0x00, 0x00, 0x34,
0x00, 0x00, 0x00, 0x06,
0x00, 0x00, 0x00, 0x00, // 1st Setting
0x00, 0x00, 0x00, 0x01,
0x02, 0x00, 0x00, 0x01, // 2nd Setting
0x00, 0x00, 0x00, 0x02,
0x03, 0x00, 0x00, 0x02, // 3rd Setting
0x00, 0x00, 0x00, 0x03,
0x04, 0x00, 0x00, 0x03, // 4th Setting
0xff, 0x00, 0x00, 0x04,
0x05, 0x00, 0x00, 0xff, // 5th Setting
0x00, 0x00, 0x00, 0x05,
0xff, 0xff, 0xff, 0xff, // 6th Setting
0x00, 0x00, 0x00, 0x06,
};
scoped_ptr<SpdySettingsControlFrame> frame(framer.CreateSettings(settings));
CompareFrame(kDescription,
*frame,
kFrameData,
arraysize(kFrameData));
EXPECT_EQ(SpdyFramer::kInvalidStream,
SpdyFramer::GetControlFrameStreamId(frame.get()));
}
{
const char kDescription[] = "Empty SETTINGS frame";
SettingsMap settings;
const unsigned char kFrameData[] = {
0x80, spdy_version_, 0x00, 0x04,
0x00, 0x00, 0x00, 0x04,
0x00, 0x00, 0x00, 0x00,
};
scoped_ptr<SpdyFrame> frame(framer.CreateSettings(settings));
CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
}
}
TEST_P(SpdyFramerTest, CreatePingFrame) {
SpdyFramer framer(spdy_version_);
{
const char kDescription[] = "PING frame";
const unsigned char kFrameData[] = {
0x80, spdy_version_, 0x00, 0x06,
0x00, 0x00, 0x00, 0x04,
0x12, 0x34, 0x56, 0x78,
};
scoped_ptr<SpdyPingControlFrame> frame(framer.CreatePingFrame(0x12345678u));
CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
EXPECT_EQ(SpdyFramer::kInvalidStream,
SpdyFramer::GetControlFrameStreamId(frame.get()));
}
}
TEST_P(SpdyFramerTest, CreateGoAway) {
SpdyFramer framer(spdy_version_);
{
const char kDescription[] = "GOAWAY frame";
const unsigned char kV2FrameData[] = {
0x80, spdy_version_, 0x00, 0x07,
0x00, 0x00, 0x00, 0x04,
0x00, 0x00, 0x00, 0x00,
};
const unsigned char kV3FrameData[] = {
0x80, spdy_version_, 0x00, 0x07,
0x00, 0x00, 0x00, 0x08,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};
scoped_ptr<SpdyGoAwayControlFrame> frame(framer.CreateGoAway(0, GOAWAY_OK));
CompareFrame(kDescription,
*frame,
IsSpdy2() ? kV2FrameData : kV3FrameData,
IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
EXPECT_EQ(SpdyFramer::kInvalidStream,
SpdyFramer::GetControlFrameStreamId(frame.get()));
}
{
const char kDescription[] = "GOAWAY frame with max stream ID, status";
const unsigned char kV2FrameData[] = {
0x80, spdy_version_, 0x00, 0x07,
0x00, 0x00, 0x00, 0x04,
0x7f, 0xff, 0xff, 0xff,
};
const unsigned char kV3FrameData[] = {
0x80, spdy_version_, 0x00, 0x07,
0x00, 0x00, 0x00, 0x08,
0x7f, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x02,
};
scoped_ptr<SpdyFrame> frame(framer.CreateGoAway(0x7FFFFFFF,
GOAWAY_INTERNAL_ERROR));
CompareFrame(kDescription,
*frame,
IsSpdy2() ? kV2FrameData : kV3FrameData,
IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
}
}
TEST_P(SpdyFramerTest, CreateHeadersUncompressed) {
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(false);
{
const char kDescription[] = "HEADERS frame, no FIN";
SpdyHeaderBlock headers;
headers["bar"] = "foo";
headers["foo"] = "bar";
const unsigned char kV2FrameData[] = {
0x80, spdy_version_, 0x00, 0x08,
0x00, 0x00, 0x00, 0x1C,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x02,
0x00, 0x03, 'b', 'a',
'r', 0x00, 0x03, 'f',
'o', 'o', 0x00, 0x03,
'f', 'o', 'o', 0x00,
0x03, 'b', 'a', 'r'
};
const unsigned char kV3FrameData[] = {
0x80, spdy_version_, 0x00, 0x08,
0x00, 0x00, 0x00, 0x24,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x03,
'b', 'a', 'r', 0x00,
0x00, 0x00, 0x03, 'f',
'o', 'o', 0x00, 0x00,
0x00, 0x03, 'f', 'o',
'o', 0x00, 0x00, 0x00,
0x03, 'b', 'a', 'r'
};
scoped_ptr<SpdyFrame> frame(framer.CreateHeaders(
1, CONTROL_FLAG_NONE, false, &headers));
CompareFrame(kDescription,
*frame,
IsSpdy2() ? kV2FrameData : kV3FrameData,
IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
}
{
const char kDescription[] =
"HEADERS frame with a 0-length header name, FIN, max stream ID";
SpdyHeaderBlock headers;
headers[""] = "foo";
headers["foo"] = "bar";
const unsigned char kV2FrameData[] = {
0x80, spdy_version_, 0x00, 0x08,
0x01, 0x00, 0x00, 0x19,
0x7f, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x03,
'f', 'o', 'o', 0x00,
0x03, 'f', 'o', 'o',
0x00, 0x03, 'b', 'a',
'r'
};
const unsigned char kV3FrameData[] = {
0x80, spdy_version_, 0x00, 0x08,
0x01, 0x00, 0x00, 0x21,
0x7f, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03,
'f', 'o', 'o', 0x00,
0x00, 0x00, 0x03, 'f',
'o', 'o', 0x00, 0x00,
0x00, 0x03, 'b', 'a',
'r'
};
scoped_ptr<SpdyFrame> frame(framer.CreateHeaders(
0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
CompareFrame(kDescription,
*frame,
IsSpdy2() ? kV2FrameData : kV3FrameData,
IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
}
{
const char kDescription[] =
"HEADERS frame with a 0-length header val, FIN, max stream ID";
SpdyHeaderBlock headers;
headers["bar"] = "foo";
headers["foo"] = "";
const unsigned char kV2FrameData[] = {
0x80, spdy_version_, 0x00, 0x08,
0x01, 0x00, 0x00, 0x19,
0x7f, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x02,
0x00, 0x03, 'b', 'a',
'r', 0x00, 0x03, 'f',
'o', 'o', 0x00, 0x03,
'f', 'o', 'o', 0x00,
0x00
};
const unsigned char kV3FrameData[] = {
0x80, spdy_version_, 0x00, 0x08,
0x01, 0x00, 0x00, 0x21,
0x7f, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x03,
'b', 'a', 'r', 0x00,
0x00, 0x00, 0x03, 'f',
'o', 'o', 0x00, 0x00,
0x00, 0x03, 'f', 'o',
'o', 0x00, 0x00, 0x00,
0x00
};
scoped_ptr<SpdyFrame> frame(framer.CreateHeaders(
0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
CompareFrame(kDescription,
*frame,
IsSpdy2() ? kV2FrameData : kV3FrameData,
IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
}
}
// TODO(phajdan.jr): Clean up after we no longer need
// to workaround http://crbug.com/139744.
#if !defined(USE_SYSTEM_ZLIB)
TEST_P(SpdyFramerTest, CreateHeadersCompressed) {
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(true);
{
const char kDescription[] = "HEADERS frame, no FIN";
SpdyHeaderBlock headers;
headers["bar"] = "foo";
headers["foo"] = "bar";
const unsigned char kV2FrameData[] = {
0x80, spdy_version_, 0x00, 0x08,
0x00, 0x00, 0x00, 0x32,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x38, 0xea,
0xdf, 0xa2, 0x51, 0xb2,
0x62, 0x60, 0x62, 0x60,
0x4e, 0x4a, 0x2c, 0x62,
0x60, 0x06, 0x08, 0xa0,
0xb4, 0xfc, 0x7c, 0x80,
0x00, 0x62, 0x60, 0x4e,
0xcb, 0xcf, 0x67, 0x60,
0x06, 0x08, 0xa0, 0xa4,
0xc4, 0x22, 0x80, 0x00,
0x02, 0x00, 0x00, 0x00,
0xff, 0xff,
};
const unsigned char kV3FrameData[] = {
0x80, spdy_version_, 0x00, 0x08,
0x00, 0x00, 0x00, 0x31,
0x00, 0x00, 0x00, 0x01,
0x38, 0xea, 0xe3, 0xc6,
0xa7, 0xc2, 0x02, 0xe5,
0x0e, 0x50, 0xc2, 0x4b,
0x4a, 0x04, 0xe5, 0x0b,
0x66, 0x80, 0x00, 0x4a,
0xcb, 0xcf, 0x07, 0x08,
0x20, 0x10, 0x95, 0x96,
0x9f, 0x0f, 0xa2, 0x00,
0x02, 0x28, 0x29, 0xb1,
0x08, 0x20, 0x80, 0x00,
0x00, 0x00, 0x00, 0xff,
0xff,
};
scoped_ptr<SpdyFrame> frame(framer.CreateHeaders(
1, CONTROL_FLAG_NONE, true, &headers));
CompareFrame(kDescription,
*frame,
IsSpdy2() ? kV2FrameData : kV3FrameData,
IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
}
}
#endif // !defined(USE_SYSTEM_ZLIB)
TEST_P(SpdyFramerTest, CreateWindowUpdate) {
SpdyFramer framer(spdy_version_);
{
const char kDescription[] = "WINDOW_UPDATE frame";
const unsigned char kFrameData[] = {
0x80, spdy_version_, 0x00, 0x09,
0x00, 0x00, 0x00, 0x08,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x01,
};
scoped_ptr<SpdyWindowUpdateControlFrame> frame(
framer.CreateWindowUpdate(1, 1));
CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
EXPECT_EQ(1u, SpdyFramer::GetControlFrameStreamId(frame.get()));
}
{
const char kDescription[] = "WINDOW_UPDATE frame with max stream ID";
const unsigned char kFrameData[] = {
0x80, spdy_version_, 0x00, 0x09,
0x00, 0x00, 0x00, 0x08,
0x7f, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x01,
};
scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(0x7FFFFFFF, 1));
CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
}
{
const char kDescription[] = "WINDOW_UPDATE frame with max window delta";
const unsigned char kFrameData[] = {
0x80, spdy_version_, 0x00, 0x09,
0x00, 0x00, 0x00, 0x08,
0x00, 0x00, 0x00, 0x01,
0x7f, 0xff, 0xff, 0xff,
};
scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(1, 0x7FFFFFFF));
CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
}
}
TEST_P(SpdyFramerTest, DuplicateFrame) {
SpdyFramer framer(spdy_version_);
{
const char kDescription[] = "PING frame";
const unsigned char kFrameData[] = {
0x80, spdy_version_, 0x00, 0x06,
0x00, 0x00, 0x00, 0x04,
0x12, 0x34, 0x56, 0x78,
};
scoped_ptr<SpdyFrame> frame1(framer.CreatePingFrame(0x12345678u));
CompareFrame(kDescription, *frame1, kFrameData, arraysize(kFrameData));
scoped_ptr<SpdyFrame> frame2(framer.DuplicateFrame(*frame1));
CompareFrame(kDescription, *frame2, kFrameData, arraysize(kFrameData));
}
}
TEST_P(SpdyFramerTest, ReadCompressedSynStreamHeaderBlock) {
SpdyHeaderBlock headers;
headers["aa"] = "vv";
headers["bb"] = "ww";
SpdyFramer framer(spdy_version_);
scoped_ptr<SpdySynStreamControlFrame> control_frame(
framer.CreateSynStream(1, // stream_id
0, // associated_stream_id
1, // priority
0, // credential_slot
CONTROL_FLAG_NONE,
true, // compress
&headers));
EXPECT_TRUE(control_frame.get() != NULL);
TestSpdyVisitor visitor(spdy_version_);
visitor.use_compression_ = true;
visitor.SimulateInFramer(
reinterpret_cast<unsigned char*>(control_frame->data()),
control_frame->length() + SpdyControlFrame::kHeaderSize);
EXPECT_EQ(1, visitor.syn_frame_count_);
EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_));
}
TEST_P(SpdyFramerTest, ReadCompressedSynReplyHeaderBlock) {
SpdyHeaderBlock headers;
headers["alpha"] = "beta";
headers["gamma"] = "delta";
SpdyFramer framer(spdy_version_);
scoped_ptr<SpdySynReplyControlFrame> control_frame(
framer.CreateSynReply(1, // stream_id
CONTROL_FLAG_NONE,
true, // compress
&headers));
EXPECT_TRUE(control_frame.get() != NULL);
TestSpdyVisitor visitor(spdy_version_);
visitor.use_compression_ = true;
visitor.SimulateInFramer(
reinterpret_cast<unsigned char*>(control_frame->data()),
control_frame->length() + SpdyControlFrame::kHeaderSize);
EXPECT_EQ(1, visitor.syn_reply_frame_count_);
EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_));
}
TEST_P(SpdyFramerTest, ReadCompressedHeadersHeaderBlock) {
SpdyHeaderBlock headers;
headers["alpha"] = "beta";
headers["gamma"] = "delta";
SpdyFramer framer(spdy_version_);
scoped_ptr<SpdyHeadersControlFrame> control_frame(
framer.CreateHeaders(1, // stream_id
CONTROL_FLAG_NONE,
true, // compress
&headers));
EXPECT_TRUE(control_frame.get() != NULL);
TestSpdyVisitor visitor(spdy_version_);
visitor.use_compression_ = true;
visitor.SimulateInFramer(
reinterpret_cast<unsigned char*>(control_frame->data()),
control_frame->length() + SpdyControlFrame::kHeaderSize);
EXPECT_EQ(1, visitor.headers_frame_count_);
// control_frame_header_data_count_ depends on the random sequence
// produced by rand(), so adding, removing or running single tests
// alters this value. The best we can do is assert that it happens
// at least twice.
EXPECT_LE(2, visitor.control_frame_header_data_count_);
EXPECT_EQ(1, visitor.zero_length_control_frame_header_data_count_);
EXPECT_EQ(0, visitor.zero_length_data_frame_count_);
EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_));
}
TEST_P(SpdyFramerTest, ReadCompressedHeadersHeaderBlockWithHalfClose) {
SpdyHeaderBlock headers;
headers["alpha"] = "beta";
headers["gamma"] = "delta";
SpdyFramer framer(spdy_version_);
scoped_ptr<SpdyHeadersControlFrame> control_frame(
framer.CreateHeaders(1, // stream_id
CONTROL_FLAG_FIN,
true, // compress
&headers));
EXPECT_TRUE(control_frame.get() != NULL);
TestSpdyVisitor visitor(spdy_version_);
visitor.use_compression_ = true;
visitor.SimulateInFramer(
reinterpret_cast<unsigned char*>(control_frame->data()),
control_frame->length() + SpdyControlFrame::kHeaderSize);
EXPECT_EQ(1, visitor.headers_frame_count_);
// control_frame_header_data_count_ depends on the random sequence
// produced by rand(), so adding, removing or running single tests
// alters this value. The best we can do is assert that it happens
// at least twice.
EXPECT_LE(2, visitor.control_frame_header_data_count_);
EXPECT_EQ(1, visitor.zero_length_control_frame_header_data_count_);
EXPECT_EQ(1, visitor.zero_length_data_frame_count_);
EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_));
}
TEST_P(SpdyFramerTest, ControlFrameAtMaxSizeLimit) {
SpdyHeaderBlock headers;
// Size a header value to just fit inside the control frame buffer:
// SPDY 2 SPDY 3
// SYN_STREAM header: 18 bytes 18 bytes
// Serialized header block:
// # headers 2 bytes (uint16) 4 bytes (uint32)
// name length 2 bytes (uint16) 4 bytes (uint32)
// name text ("aa") 2 bytes 2 bytes
// value length 2 bytes (uint16) 4 bytes (uint32)
// --- ---
// 26 bytes 32 bytes
const size_t overhead = IsSpdy2() ? 26 : 32;
const size_t big_value_size =
TestSpdyVisitor::control_frame_buffer_max_size() - overhead;
std::string big_value(big_value_size, 'x');
headers["aa"] = big_value.c_str();
SpdyFramer framer(spdy_version_);
scoped_ptr<SpdySynStreamControlFrame> control_frame(
framer.CreateSynStream(1, // stream_id
0, // associated_stream_id
1, // priority
0, // credential_slot
CONTROL_FLAG_NONE,
false, // compress
&headers));
EXPECT_TRUE(control_frame.get() != NULL);
TestSpdyVisitor visitor(spdy_version_);
visitor.SimulateInFramer(
reinterpret_cast<unsigned char*>(control_frame->data()),
control_frame->length() + SpdyControlFrame::kHeaderSize);
EXPECT_TRUE(visitor.header_buffer_valid_);
EXPECT_EQ(0, visitor.error_count_);
EXPECT_EQ(1, visitor.syn_frame_count_);
EXPECT_EQ(1, visitor.zero_length_control_frame_header_data_count_);
EXPECT_EQ(0, visitor.zero_length_data_frame_count_);
EXPECT_LT(big_value_size, visitor.header_buffer_length_);
}
TEST_P(SpdyFramerTest, ControlFrameTooLarge) {
SpdyHeaderBlock headers;
// See size calculation for test above. This is one byte larger, which
// should exceed the control frame buffer capacity by that one byte.
const size_t overhead = IsSpdy2() ? 25 : 31;
const size_t kBigValueSize =
TestSpdyVisitor::control_frame_buffer_max_size() - overhead;
std::string big_value(kBigValueSize, 'x');
headers["aa"] = big_value.c_str();
SpdyFramer framer(spdy_version_);
scoped_ptr<SpdySynStreamControlFrame> control_frame(
framer.CreateSynStream(1, // stream_id
0, // associated_stream_id
1, // priority
0, // credential_slot
CONTROL_FLAG_NONE,
false, // compress
&headers));
EXPECT_TRUE(control_frame.get() != NULL);
TestSpdyVisitor visitor(spdy_version_);
visitor.SimulateInFramer(
reinterpret_cast<unsigned char*>(control_frame->data()),
control_frame->length() + SpdyControlFrame::kHeaderSize);
EXPECT_FALSE(visitor.header_buffer_valid_);
EXPECT_EQ(1, visitor.error_count_);
EXPECT_EQ(SpdyFramer::SPDY_CONTROL_PAYLOAD_TOO_LARGE,
visitor.framer_.error_code());
EXPECT_EQ(0, visitor.syn_frame_count_);
EXPECT_EQ(0u, visitor.header_buffer_length_);
}
// Check that the framer stops delivering header data chunks once the visitor
// declares it doesn't want any more. This is important to guard against
// "zip bomb" types of attacks.
TEST_P(SpdyFramerTest, ControlFrameMuchTooLarge) {
SpdyHeaderBlock headers;
const size_t kHeaderBufferChunks = 4;
const size_t kHeaderBufferSize =
TestSpdyVisitor::header_data_chunk_max_size() * kHeaderBufferChunks;
const size_t big_value_size = kHeaderBufferSize * 2;
std::string big_value(big_value_size, 'x');
headers["aa"] = big_value.c_str();
SpdyFramer framer(spdy_version_);
scoped_ptr<SpdySynStreamControlFrame> control_frame(
framer.CreateSynStream(1, // stream_id
0, // associated_stream_id
1, // priority
0, // credential_slot
CONTROL_FLAG_FIN, // half close
true, // compress
&headers));
EXPECT_TRUE(control_frame.get() != NULL);
TestSpdyVisitor visitor(spdy_version_);
visitor.set_header_buffer_size(kHeaderBufferSize);
visitor.use_compression_ = true;
visitor.SimulateInFramer(
reinterpret_cast<unsigned char*>(control_frame->data()),