blob: 103af39d8dee3d61a6d1e9010da704819f26cb4e [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/compiler_specific.h"
#include "base/memory/scoped_ptr.h"
#include "net/spdy/hpack_output_stream.h"
#include "net/spdy/mock_spdy_framer_visitor.h"
#include "net/spdy/spdy_frame_builder.h"
#include "net/spdy/spdy_frame_reader.h"
#include "net/spdy/spdy_framer.h"
#include "net/spdy/spdy_protocol.h"
#include "net/spdy/spdy_test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/platform_test.h"
using std::string;
using testing::_;
namespace net {
namespace test {
static const size_t kMaxDecompressedSize = 1024;
class MockDebugVisitor : public SpdyFramerDebugVisitorInterface {
public:
MOCK_METHOD4(OnSendCompressedFrame, void(SpdyStreamId stream_id,
SpdyFrameType type,
size_t payload_len,
size_t frame_len));
MOCK_METHOD3(OnReceiveCompressedFrame, void(SpdyStreamId stream_id,
SpdyFrameType type,
size_t frame_len));
};
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);
CHECK_EQ(frame.size(), framer->ProcessInput(frame.data(), frame.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, visitor.size(), true);
SetFrameLength(decompressed_frame,
visitor.size() - framer->GetControlFrameHeaderSize(),
framer->protocol_version());
return decompressed_frame;
}
class DecompressionVisitor : public SpdyFramerVisitorInterface {
public:
explicit DecompressionVisitor(SpdyMajorVersion version)
: version_(version), size_(0), finished_(false) {}
void ResetBuffer() {
CHECK(buffer_.get() == NULL);
CHECK_EQ(0u, size_);
CHECK(!finished_);
buffer_.reset(new char[kMaxDecompressedSize]);
}
void OnError(SpdyFramer* framer) override { LOG(FATAL); }
void OnDataFrameHeader(SpdyStreamId stream_id,
size_t length,
bool fin) override {
LOG(FATAL) << "Unexpected data frame header";
}
void OnStreamFrameData(SpdyStreamId stream_id,
const char* data,
size_t len,
bool fin) override {
LOG(FATAL);
}
void OnStreamPadding(SpdyStreamId stream_id, size_t len) override {
LOG(FATAL);
}
bool OnControlFrameHeaderData(SpdyStreamId stream_id,
const char* header_data,
size_t len) override {
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;
}
void OnSynStream(SpdyStreamId stream_id,
SpdyStreamId associated_stream_id,
SpdyPriority priority,
bool fin,
bool unidirectional) override {
SpdyFramer framer(version_);
framer.set_enable_compression(false);
SpdySynStreamIR syn_stream(stream_id);
syn_stream.set_associated_to_stream_id(associated_stream_id);
syn_stream.set_priority(priority);
syn_stream.set_fin(fin);
syn_stream.set_unidirectional(unidirectional);
scoped_ptr<SpdyFrame> frame(framer.SerializeSynStream(syn_stream));
ResetBuffer();
memcpy(buffer_.get(), frame->data(), framer.GetSynStreamMinimumSize());
size_ += framer.GetSynStreamMinimumSize();
}
void OnSynReply(SpdyStreamId stream_id, bool fin) override {
SpdyFramer framer(version_);
framer.set_enable_compression(false);
SpdyHeadersIR headers(stream_id);
headers.set_fin(fin);
scoped_ptr<SpdyFrame> frame(framer.SerializeHeaders(headers));
ResetBuffer();
memcpy(buffer_.get(), frame->data(), framer.GetHeadersMinimumSize());
size_ += framer.GetSynStreamMinimumSize();
}
void OnRstStream(SpdyStreamId stream_id,
SpdyRstStreamStatus status) override {
LOG(FATAL);
}
void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) override {
LOG(FATAL);
}
void OnPing(SpdyPingId unique_id, bool is_ack) override { LOG(FATAL); }
void OnSettingsEnd() override { LOG(FATAL); }
void OnGoAway(SpdyStreamId last_accepted_stream_id,
SpdyGoAwayStatus status) override {
LOG(FATAL);
}
void OnHeaders(SpdyStreamId stream_id, bool has_priority,
SpdyPriority priority, bool fin, bool end) override {
SpdyFramer framer(version_);
framer.set_enable_compression(false);
SpdyHeadersIR headers(stream_id);
headers.set_has_priority(has_priority);
if (headers.has_priority()) {
headers.set_priority(priority);
}
headers.set_fin(fin);
scoped_ptr<SpdyFrame> frame(framer.SerializeHeaders(headers));
ResetBuffer();
memcpy(buffer_.get(), frame->data(), framer.GetHeadersMinimumSize());
size_ += framer.GetHeadersMinimumSize();
}
virtual void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) {
LOG(FATAL);
}
void OnPushPromise(SpdyStreamId stream_id,
SpdyStreamId promised_stream_id,
bool end) override {
SpdyFramer framer(version_);
framer.set_enable_compression(false);
SpdyPushPromiseIR push_promise(stream_id, promised_stream_id);
scoped_ptr<SpdyFrame> frame(framer.SerializePushPromise(push_promise));
ResetBuffer();
memcpy(buffer_.get(), frame->data(), framer.GetPushPromiseMinimumSize());
size_ += framer.GetPushPromiseMinimumSize();
}
void OnContinuation(SpdyStreamId stream_id, bool end) override {
LOG(FATAL);
}
void OnPriority(SpdyStreamId stream_id,
SpdyStreamId parent_stream_id,
uint8 weight,
bool exclusive) override {
// Do nothing.
}
bool OnUnknownFrame(SpdyStreamId stream_id, int frame_type) override {
LOG(FATAL);
return false;
}
char* ReleaseBuffer() {
CHECK(finished_);
return buffer_.release();
}
void OnWindowUpdate(SpdyStreamId stream_id,
uint32 delta_window_size) override {
LOG(FATAL);
}
size_t size() const {
CHECK(finished_);
return size_;
}
private:
SpdyMajorVersion version_;
scoped_ptr<char[]> buffer_;
size_t size_;
bool finished_;
DISALLOW_COPY_AND_ASSIGN(DecompressionVisitor);
};
private:
DISALLOW_COPY_AND_ASSIGN(SpdyFramerTestUtil);
};
class TestSpdyVisitor : public SpdyFramerVisitorInterface,
public SpdyFramerDebugVisitorInterface {
public:
// This is larger than our max frame size because header blocks that
// are too long can spill over into CONTINUATION frames.
static const size_t kDefaultHeaderBufferSize = 16 * 1024 * 1024;
explicit TestSpdyVisitor(SpdyMajorVersion version)
: framer_(version),
use_compression_(false),
error_count_(0),
syn_frame_count_(0),
syn_reply_frame_count_(0),
headers_frame_count_(0),
push_promise_frame_count_(0),
goaway_count_(0),
setting_count_(0),
settings_ack_sent_(0),
settings_ack_received_(0),
continuation_count_(0),
altsvc_count_(0),
priority_count_(0),
test_altsvc_ir_(0),
on_unknown_frame_result_(false),
last_window_update_stream_(0),
last_window_update_delta_(0),
last_push_promise_stream_(0),
last_push_promise_promised_stream_(0),
data_bytes_(0),
fin_frame_count_(0),
fin_opaque_data_(),
fin_flag_count_(0),
zero_length_data_frame_count_(0),
control_frame_header_data_count_(0),
zero_length_control_frame_header_data_count_(0),
data_frame_count_(0),
last_payload_len_(0),
last_frame_len_(0),
header_buffer_(new char[kDefaultHeaderBufferSize]),
header_buffer_length_(0),
header_buffer_size_(kDefaultHeaderBufferSize),
header_stream_id_(static_cast<SpdyStreamId>(-1)),
header_control_type_(DATA),
header_buffer_valid_(false) {}
void OnError(SpdyFramer* f) override {
LOG(INFO) << "SpdyFramer Error: "
<< SpdyFramer::ErrorCodeToString(f->error_code());
++error_count_;
}
void OnDataFrameHeader(SpdyStreamId stream_id,
size_t length,
bool fin) override {
++data_frame_count_;
header_stream_id_ = stream_id;
}
void OnStreamFrameData(SpdyStreamId stream_id,
const char* data,
size_t len,
bool fin) override {
EXPECT_EQ(header_stream_id_, stream_id);
if (len == 0)
++zero_length_data_frame_count_;
data_bytes_ += len;
LOG(INFO) << "OnStreamFrameData(" << stream_id << ", \"";
if (len > 0) {
for (size_t i = 0 ; i < len; ++i) {
LOG(INFO) << std::hex << (0xFF & static_cast<unsigned int>(data[i]))
<< std::dec;
}
}
LOG(INFO) << "\", " << len << ")\n";
}
void OnStreamPadding(SpdyStreamId stream_id, size_t len) override {
EXPECT_EQ(header_stream_id_, stream_id);
data_bytes_ += len;
LOG(INFO) << "OnStreamPadding(" << stream_id << ", " << len << ")\n";
}
bool OnControlFrameHeaderData(SpdyStreamId stream_id,
const char* header_data,
size_t len) override {
++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.
headers_.clear();
CHECK(header_buffer_valid_);
size_t parsed_length = framer_.ParseHeaderBlockInBuffer(
header_buffer_.get(), header_buffer_length_, &headers_);
LOG_IF(DFATAL, header_buffer_length_ != parsed_length)
<< "Check failed: header_buffer_length_ == parsed_length "
<< "(" << header_buffer_length_ << " vs. " << parsed_length << ")";
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;
}
void OnSynStream(SpdyStreamId stream_id,
SpdyStreamId associated_stream_id,
SpdyPriority priority,
bool fin,
bool unidirectional) override {
++syn_frame_count_;
if (framer_.protocol_version() > SPDY3) {
InitHeaderStreaming(HEADERS, stream_id);
} else {
InitHeaderStreaming(SYN_STREAM, stream_id);
}
if (fin) {
++fin_flag_count_;
}
}
void OnSynReply(SpdyStreamId stream_id, bool fin) override {
++syn_reply_frame_count_;
if (framer_.protocol_version() > SPDY3) {
InitHeaderStreaming(HEADERS, stream_id);
} else {
InitHeaderStreaming(SYN_REPLY, stream_id);
}
if (fin) {
++fin_flag_count_;
}
}
void OnRstStream(SpdyStreamId stream_id,
SpdyRstStreamStatus status) override {
++fin_frame_count_;
}
bool OnRstStreamFrameData(const char* rst_stream_data, size_t len) override {
if ((rst_stream_data != NULL) && (len > 0)) {
fin_opaque_data_ += string(rst_stream_data, len);
}
return true;
}
void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) override {
++setting_count_;
}
void OnSettingsAck() override {
DCHECK_LT(SPDY3, framer_.protocol_version());
++settings_ack_received_;
}
void OnSettingsEnd() override {
if (framer_.protocol_version() <= SPDY3) { return; }
++settings_ack_sent_;
}
void OnPing(SpdyPingId unique_id, bool is_ack) override { DLOG(FATAL); }
void OnGoAway(SpdyStreamId last_accepted_stream_id,
SpdyGoAwayStatus status) override {
++goaway_count_;
}
void OnHeaders(SpdyStreamId stream_id, bool has_priority,
SpdyPriority priority, bool fin, bool end) override {
++headers_frame_count_;
InitHeaderStreaming(HEADERS, stream_id);
if (fin) {
++fin_flag_count_;
}
}
void OnWindowUpdate(SpdyStreamId stream_id,
uint32 delta_window_size) override {
last_window_update_stream_ = stream_id;
last_window_update_delta_ = delta_window_size;
}
void OnPushPromise(SpdyStreamId stream_id,
SpdyStreamId promised_stream_id,
bool end) override {
++push_promise_frame_count_;
InitHeaderStreaming(PUSH_PROMISE, stream_id);
last_push_promise_stream_ = stream_id;
last_push_promise_promised_stream_ = promised_stream_id;
}
void OnContinuation(SpdyStreamId stream_id, bool end) override {
++continuation_count_;
}
void OnAltSvc(
SpdyStreamId stream_id,
StringPiece origin,
const SpdyAltSvcWireFormat::AlternativeService& altsvc) override {
test_altsvc_ir_.set_stream_id(stream_id);
if (origin.length() > 0) {
test_altsvc_ir_.set_origin(origin.as_string());
}
test_altsvc_ir_.set_protocol_id(altsvc.protocol_id);
test_altsvc_ir_.set_host(altsvc.host);
test_altsvc_ir_.set_port(altsvc.port);
test_altsvc_ir_.set_max_age(altsvc.max_age);
test_altsvc_ir_.set_p(altsvc.p);
++altsvc_count_;
}
void OnPriority(SpdyStreamId stream_id,
SpdyStreamId parent_stream_id,
uint8 weight,
bool exclusive) override {
++priority_count_;
}
bool OnUnknownFrame(SpdyStreamId stream_id, int frame_type) override {
DLOG(INFO) << "Unknown frame type " << frame_type;
return on_unknown_frame_result_;
}
void OnSendCompressedFrame(SpdyStreamId stream_id,
SpdyFrameType type,
size_t payload_len,
size_t frame_len) override {
last_payload_len_ = payload_len;
last_frame_len_ = frame_len;
}
void OnReceiveCompressedFrame(SpdyStreamId stream_id,
SpdyFrameType type,
size_t frame_len) override {
last_frame_len_ = frame_len;
}
// 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() % std::min(input_remaining, kMaxReadSize)) + 1;
size_t bytes_processed = framer_.ProcessInput(input_ptr, bytes_read);
input_remaining -= bytes_processed;
input_ptr += bytes_processed;
}
}
void InitHeaderStreaming(SpdyFrameType header_control_type,
SpdyStreamId stream_id) {
if (!SpdyConstants::IsValidFrameType(framer_.protocol_version(),
SpdyConstants::SerializeFrameType(framer_.protocol_version(),
header_control_type))) {
DLOG(FATAL) << "Attempted to init header streaming with "
<< "invalid control frame type: "
<< header_control_type;
}
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]);
}
// Largest control frame that the SPDY implementation sends, including the
// size of the header.
static size_t sent_control_frame_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 push_promise_frame_count_;
int goaway_count_;
int setting_count_;
int settings_ack_sent_;
int settings_ack_received_;
int continuation_count_;
int altsvc_count_;
int priority_count_;
SpdyAltSvcIR test_altsvc_ir_;
bool on_unknown_frame_result_;
SpdyStreamId last_window_update_stream_;
uint32 last_window_update_delta_;
SpdyStreamId last_push_promise_stream_;
SpdyStreamId last_push_promise_promised_stream_;
int data_bytes_;
int fin_frame_count_; // The count of RST_STREAM type frames received.
string fin_opaque_data_;
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 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_;
size_t last_payload_len_;
size_t last_frame_len_;
// Header block streaming state:
scoped_ptr<char[]> header_buffer_;
size_t header_buffer_length_;
size_t header_buffer_size_;
SpdyStreamId header_stream_id_;
SpdyFrameType header_control_type_;
bool header_buffer_valid_;
SpdyHeaderBlock headers_;
};
class SpdyFramerPeer {
public:
static size_t ControlFrameBufferSize() {
return SpdyFramer::kControlFrameBufferSize;
}
static size_t GetNumberRequiredContinuationFrames(SpdyFramer* framer,
size_t size) {
return framer->GetNumberRequiredContinuationFrames(size);
}
};
// Retrieves serialized headers from a HEADERS or SYN_STREAM frame.
base::StringPiece GetSerializedHeaders(const SpdyFrame* frame,
const SpdyFramer& framer) {
SpdyFrameReader reader(frame->data(), frame->size());
if (framer.protocol_version() > SPDY3) {
reader.Seek(3); // Seek past the frame length.
} else {
reader.Seek(2); // Seek past the frame length.
}
SpdyFrameType frame_type;
if (framer.protocol_version() > SPDY3) {
uint8 serialized_type;
reader.ReadUInt8(&serialized_type);
frame_type = SpdyConstants::ParseFrameType(framer.protocol_version(),
serialized_type);
DCHECK_EQ(HEADERS, frame_type);
uint8 flags;
reader.ReadUInt8(&flags);
if (flags & HEADERS_FLAG_PRIORITY) {
frame_type = SYN_STREAM;
}
} else {
uint16 serialized_type;
reader.ReadUInt16(&serialized_type);
frame_type = SpdyConstants::ParseFrameType(framer.protocol_version(),
serialized_type);
DCHECK(frame_type == HEADERS ||
frame_type == SYN_STREAM) << frame_type;
}
if (frame_type == SYN_STREAM) {
return StringPiece(frame->data() + framer.GetSynStreamMinimumSize(),
frame->size() - framer.GetSynStreamMinimumSize());
} else {
return StringPiece(frame->data() + framer.GetHeadersMinimumSize(),
frame->size() - framer.GetHeadersMinimumSize());
}
}
class SpdyFramerTest : public ::testing::TestWithParam<SpdyMajorVersion> {
protected:
void SetUp() override {
spdy_version_ = GetParam();
spdy_version_ch_ = static_cast<unsigned char>(
SpdyConstants::SerializeMajorVersion(spdy_version_));
}
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());
CompareCharArraysWithHexError(
description, actual, actual_frame.size(), expected, expected_len);
}
void CompareFrames(const string& description,
const SpdyFrame& expected_frame,
const SpdyFrame& actual_frame) {
CompareCharArraysWithHexError(
description,
reinterpret_cast<const unsigned char*>(expected_frame.data()),
expected_frame.size(),
reinterpret_cast<const unsigned char*>(actual_frame.data()),
actual_frame.size());
}
// 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;
}
bool IsSpdy2() { return spdy_version_ == SPDY2; }
bool IsSpdy3() { return spdy_version_ == SPDY3; }
bool IsSpdy4() { return spdy_version_ == SPDY4; }
// Version of SPDY protocol to be used.
SpdyMajorVersion spdy_version_;
unsigned char spdy_version_ch_;
};
// All tests are run with 3 different SPDY versions: SPDY/2, SPDY/3, SPDY/4.
INSTANTIATE_TEST_CASE_P(SpdyFramerTests,
SpdyFramerTest,
::testing::Values(SPDY2, SPDY3, SPDY4));
// Test that we ignore cookie where both name and value are empty.
TEST_P(SpdyFramerTest, HeaderBlockWithEmptyCookie) {
if (spdy_version_ > SPDY3) {
// Not implemented for hpack.
return;
}
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(true);
SpdyHeadersIR headers(1);
headers.set_priority(1);
headers.SetHeader("cookie",
"=; key=value; ; = ; foo; bar=; ; = ; k2=v2 ; =");
scoped_ptr<SpdyFrame> frame(framer.SerializeHeaders(headers));
EXPECT_TRUE(frame.get() != NULL);
TestSpdyVisitor visitor(spdy_version_);
visitor.use_compression_ = true;
visitor.SimulateInFramer(
reinterpret_cast<unsigned char*>(frame->data()),
frame->size());
EXPECT_EQ(1, visitor.zero_length_control_frame_header_data_count_);
EXPECT_FALSE(CompareHeaderBlocks(&headers.name_value_block(),
&visitor.headers_));
EXPECT_EQ(1u, visitor.headers_.size());
EXPECT_EQ("key=value; foo; bar=; k2=v2 ", visitor.headers_["cookie"]);
}
// Test that we can encode and decode a SpdyHeaderBlock in serialized form.
TEST_P(SpdyFramerTest, HeaderBlockInBuffer) {
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(false);
// Encode the header block into a Headers frame.
SpdyHeadersIR headers(1);
headers.set_priority(1);
headers.SetHeader("alpha", "beta");
headers.SetHeader("gamma", "charlie");
headers.SetHeader("cookie", "key1=value1; key2=value2");
scoped_ptr<SpdyFrame> frame(framer.SerializeHeaders(headers));
EXPECT_TRUE(frame.get() != NULL);
TestSpdyVisitor visitor(spdy_version_);
visitor.use_compression_ = false;
visitor.SimulateInFramer(
reinterpret_cast<unsigned char*>(frame->data()),
frame->size());
EXPECT_EQ(1, visitor.zero_length_control_frame_header_data_count_);
EXPECT_TRUE(CompareHeaderBlocks(&headers.name_value_block(),
&visitor.headers_));
}
// Test that if there's not a full frame, we fail to parse it.
TEST_P(SpdyFramerTest, UndersizedHeaderBlockInBuffer) {
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(false);
// Encode the header block into a Headers frame.
SpdyHeadersIR headers(1);
headers.set_priority(1);
headers.SetHeader("alpha", "beta");
headers.SetHeader("gamma", "charlie");
scoped_ptr<SpdyFrame> frame(framer.SerializeHeaders(headers));
EXPECT_TRUE(frame.get() != NULL);
TestSpdyVisitor visitor(spdy_version_);
visitor.use_compression_ = false;
visitor.SimulateInFramer(
reinterpret_cast<unsigned char*>(frame->data()),
frame->size() - 2);
EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
EXPECT_EQ(0u, visitor.headers_.size());
}
// Test that if we receive a SYN_REPLY with stream ID zero, we signal an error
// (but don't crash).
TEST_P(SpdyFramerTest, SynReplyWithStreamIdZero) {
if (spdy_version_ > SPDY3) {
return;
}
testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
SpdyFramer framer(spdy_version_);
framer.set_visitor(&visitor);
SpdySynReplyIR syn_reply(0);
syn_reply.SetHeader("alpha", "beta");
scoped_ptr<SpdySerializedFrame> frame(framer.SerializeSynReply(syn_reply));
ASSERT_TRUE(frame.get() != NULL);
// We shouldn't have to read the whole frame before we signal an error.
EXPECT_CALL(visitor, OnError(testing::Eq(&framer)));
EXPECT_GT(frame->size(), framer.ProcessInput(frame->data(), frame->size()));
EXPECT_TRUE(framer.HasError());
EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME, framer.error_code())
<< SpdyFramer::ErrorCodeToString(framer.error_code());
}
// Test that if we receive a HEADERS with stream ID zero, we signal an error
// (but don't crash).
TEST_P(SpdyFramerTest, HeadersWithStreamIdZero) {
testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
SpdyFramer framer(spdy_version_);
framer.set_visitor(&visitor);
SpdyHeadersIR headers_ir(0);
headers_ir.SetHeader("alpha", "beta");
scoped_ptr<SpdySerializedFrame> frame(framer.SerializeHeaders(headers_ir));
ASSERT_TRUE(frame.get() != NULL);
// We shouldn't have to read the whole frame before we signal an error.
EXPECT_CALL(visitor, OnError(testing::Eq(&framer)));
EXPECT_GT(frame->size(), framer.ProcessInput(frame->data(), frame->size()));
EXPECT_TRUE(framer.HasError());
EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME, framer.error_code())
<< SpdyFramer::ErrorCodeToString(framer.error_code());
}
// Test that if we receive a PUSH_PROMISE with stream ID zero, we signal an
// error (but don't crash).
TEST_P(SpdyFramerTest, PushPromiseWithStreamIdZero) {
if (spdy_version_ <= SPDY3) {
return;
}
testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
SpdyFramer framer(spdy_version_);
framer.set_visitor(&visitor);
SpdyPushPromiseIR push_promise(0, 4);
push_promise.SetHeader("alpha", "beta");
scoped_ptr<SpdySerializedFrame> frame(
framer.SerializePushPromise(push_promise));
ASSERT_TRUE(frame.get() != NULL);
// We shouldn't have to read the whole frame before we signal an error.
EXPECT_CALL(visitor, OnError(testing::Eq(&framer)));
EXPECT_GT(frame->size(), framer.ProcessInput(frame->data(), frame->size()));
EXPECT_TRUE(framer.HasError());
EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME, framer.error_code())
<< SpdyFramer::ErrorCodeToString(framer.error_code());
}
// Test that if we receive a PUSH_PROMISE with promised stream ID zero, we
// signal an error (but don't crash).
TEST_P(SpdyFramerTest, PushPromiseWithPromisedStreamIdZero) {
if (spdy_version_ <= SPDY3) {
return;
}
testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
SpdyFramer framer(spdy_version_);
framer.set_visitor(&visitor);
SpdyPushPromiseIR push_promise(3, 0);
push_promise.SetHeader("alpha", "beta");
scoped_ptr<SpdySerializedFrame> frame(
framer.SerializePushPromise(push_promise));
ASSERT_TRUE(frame.get() != NULL);
// We shouldn't have to read the whole frame before we signal an error.
EXPECT_CALL(visitor, OnError(testing::Eq(&framer)));
EXPECT_GT(frame->size(), framer.ProcessInput(frame->data(), frame->size()));
EXPECT_TRUE(framer.HasError());
EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME, framer.error_code())
<< SpdyFramer::ErrorCodeToString(framer.error_code());
}
TEST_P(SpdyFramerTest, DuplicateHeader) {
if (spdy_version_ > SPDY3) {
// TODO(jgraettinger): Punting on this because we haven't determined
// whether duplicate HPACK headers other than Cookie are an error.
// If they are, this will need to be updated to use HpackOutputStream.
return;
}
SpdyFramer framer(spdy_version_);
// Frame builder with plentiful buffer size.
SpdyFrameBuilder frame(1024, spdy_version_);
if (spdy_version_ <= SPDY3) {
frame.WriteControlFrameHeader(framer, SYN_STREAM, CONTROL_FLAG_NONE);
frame.WriteUInt32(3); // stream_id
frame.WriteUInt32(0); // associated stream id
frame.WriteUInt16(0); // Priority.
} else {
frame.BeginNewFrame(framer, HEADERS, HEADERS_FLAG_PRIORITY, 3);
frame.WriteUInt32(framer.GetHighestPriority());
}
if (IsSpdy2()) {
frame.WriteUInt16(2); // Number of headers.
frame.WriteStringPiece16("name");
frame.WriteStringPiece16("value1");
frame.WriteStringPiece16("name");
frame.WriteStringPiece16("value2");
} else {
frame.WriteUInt32(2); // Number of headers.
frame.WriteStringPiece32("name");
frame.WriteStringPiece32("value1");
frame.WriteStringPiece32("name");
frame.WriteStringPiece32("value2");
}
// write the length
frame.RewriteLength(framer);
SpdyHeaderBlock new_headers;
framer.set_enable_compression(false);
scoped_ptr<SpdyFrame> control_frame(frame.take());
base::StringPiece serialized_headers =
GetSerializedHeaders(control_frame.get(), framer);
// This should fail because duplicate headers are verboten by the spec.
EXPECT_FALSE(framer.ParseHeaderBlockInBuffer(serialized_headers.data(),
serialized_headers.size(),
&new_headers));
}
TEST_P(SpdyFramerTest, MultiValueHeader) {
SpdyFramer framer(spdy_version_);
// Frame builder with plentiful buffer size.
SpdyFrameBuilder frame(1024, spdy_version_);
if (spdy_version_ <= SPDY3) {
frame.WriteControlFrameHeader(framer, SYN_STREAM, CONTROL_FLAG_NONE);
frame.WriteUInt32(3); // stream_id
frame.WriteUInt32(0); // associated stream id
frame.WriteUInt16(0); // Priority.
} else {
frame.BeginNewFrame(framer,
HEADERS,
HEADERS_FLAG_PRIORITY | HEADERS_FLAG_END_HEADERS,
3);
frame.WriteUInt32(0); // Priority exclusivity and dependent stream.
frame.WriteUInt8(255); // Priority weight.
}
string value("value1\0value2", 13);
if (IsSpdy2()) {
frame.WriteUInt16(1); // Number of headers.
frame.WriteStringPiece16("name");
frame.WriteStringPiece16(value);
} else if (spdy_version_ > SPDY3) {
// TODO(jgraettinger): If this pattern appears again, move to test class.
std::map<string, string> header_set;
header_set["name"] = value;
string buffer;
HpackEncoder encoder(ObtainHpackHuffmanTable());
encoder.EncodeHeaderSetWithoutCompression(header_set, &buffer);
frame.WriteBytes(&buffer[0], buffer.size());
} else {
frame.WriteUInt32(1); // Number of headers.
frame.WriteStringPiece32("name");
frame.WriteStringPiece32(value);
}
// write the length
frame.RewriteLength(framer);
framer.set_enable_compression(false);
scoped_ptr<SpdyFrame> control_frame(frame.take());
TestSpdyVisitor visitor(spdy_version_);
visitor.use_compression_ = false;
visitor.SimulateInFramer(
reinterpret_cast<unsigned char*>(control_frame->data()),
control_frame->size());
EXPECT_THAT(visitor.headers_,
testing::ElementsAre(testing::Pair("name", value)));
}
TEST_P(SpdyFramerTest, BasicCompression) {
if (spdy_version_ > SPDY3) {
// Deflate compression doesn't apply to HPACK.
return;
}
scoped_ptr<TestSpdyVisitor> visitor(new TestSpdyVisitor(spdy_version_));
SpdyFramer framer(spdy_version_);
framer.set_debug_visitor(visitor.get());
SpdySynStreamIR syn_stream(1);
syn_stream.set_priority(1);
syn_stream.SetHeader("server", "SpdyServer 1.0");
syn_stream.SetHeader("date", "Mon 12 Jan 2009 12:12:12 PST");
syn_stream.SetHeader("status", "200");
syn_stream.SetHeader("version", "HTTP/1.1");
syn_stream.SetHeader("content-type", "text/html");
syn_stream.SetHeader("content-length", "12");
scoped_ptr<SpdyFrame> frame1(framer.SerializeSynStream(syn_stream));
size_t uncompressed_size1 = visitor->last_payload_len_;
size_t compressed_size1 =
visitor->last_frame_len_ - framer.GetSynStreamMinimumSize();
if (IsSpdy2()) {
EXPECT_EQ(139u, uncompressed_size1);
#if defined(USE_SYSTEM_ZLIB)
EXPECT_EQ(155u, compressed_size1);
#else // !defined(USE_SYSTEM_ZLIB)
EXPECT_EQ(135u, compressed_size1);
#endif // !defined(USE_SYSTEM_ZLIB)
} else {
EXPECT_EQ(165u, uncompressed_size1);
#if defined(USE_SYSTEM_ZLIB)
EXPECT_EQ(181u, compressed_size1);
#else // !defined(USE_SYSTEM_ZLIB)
EXPECT_EQ(117u, compressed_size1);
#endif // !defined(USE_SYSTEM_ZLIB)
}
scoped_ptr<SpdyFrame> frame2(framer.SerializeSynStream(syn_stream));
size_t uncompressed_size2 = visitor->last_payload_len_;
size_t compressed_size2 =
visitor->last_frame_len_ - framer.GetSynStreamMinimumSize();
// Expect the second frame to be more compact than the first.
EXPECT_LE(frame2->size(), frame1->size());
// Decompress the first frame
scoped_ptr<SpdyFrame> frame3(
SpdyFramerTestUtil::DecompressFrame(&framer, *frame1));
// Decompress the second frame
visitor.reset(new TestSpdyVisitor(spdy_version_));
framer.set_debug_visitor(visitor.get());
scoped_ptr<SpdyFrame> frame4(
SpdyFramerTestUtil::DecompressFrame(&framer, *frame2));
size_t uncompressed_size4 =
frame4->size() - framer.GetSynStreamMinimumSize();
size_t compressed_size4 =
visitor->last_frame_len_ - framer.GetSynStreamMinimumSize();
if (IsSpdy2()) {
EXPECT_EQ(139u, uncompressed_size4);
#if defined(USE_SYSTEM_ZLIB)
EXPECT_EQ(149u, compressed_size4);
#else // !defined(USE_SYSTEM_ZLIB)
EXPECT_EQ(101u, compressed_size4);
#endif // !defined(USE_SYSTEM_ZLIB)
} else {
EXPECT_EQ(165u, uncompressed_size4);
#if defined(USE_SYSTEM_ZLIB)
EXPECT_EQ(175u, compressed_size4);
#else // !defined(USE_SYSTEM_ZLIB)
EXPECT_EQ(102u, compressed_size4);
#endif // !defined(USE_SYSTEM_ZLIB)
}
EXPECT_EQ(uncompressed_size1, uncompressed_size2);
EXPECT_EQ(uncompressed_size1, uncompressed_size4);
EXPECT_EQ(compressed_size2, compressed_size4);
// Expect frames 3 & 4 to be the same.
CompareFrames("Uncompressed SYN_STREAM", *frame3, *frame4);
// Expect frames 3 to be the same as a uncompressed frame created
// from scratch.
framer.set_enable_compression(false);
scoped_ptr<SpdyFrame> uncompressed_frame(
framer.SerializeSynStream(syn_stream));
CompareFrames("Uncompressed SYN_STREAM", *frame3, *uncompressed_frame);
}
TEST_P(SpdyFramerTest, CompressEmptyHeaders) {
// See crbug.com/172383
SpdyHeadersIR headers(1);
headers.SetHeader("server", "SpdyServer 1.0");
headers.SetHeader("date", "Mon 12 Jan 2009 12:12:12 PST");
headers.SetHeader("status", "200");
headers.SetHeader("version", "HTTP/1.1");
headers.SetHeader("content-type", "text/html");
headers.SetHeader("content-length", "12");
headers.SetHeader("x-empty-header", "");
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(true);
scoped_ptr<SpdyFrame> frame1(framer.SerializeHeaders(headers));
}
TEST_P(SpdyFramerTest, Basic) {
const unsigned char kV2Input[] = {
0x80, spdy_version_ch_, 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_ch_, 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_ch_, 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_ch_, 0x00, 0x03, // RST_STREAM on Stream #1
0x00, 0x00, 0x00, 0x08,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x05, // RST_STREAM_CANCEL
0x00, 0x00, 0x00, 0x03, // DATA on Stream #3
0x00, 0x00, 0x00, 0x00,
0x80, spdy_version_ch_, 0x00, 0x03, // RST_STREAM on Stream #3
0x00, 0x00, 0x00, 0x08,
0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x05, // RST_STREAM_CANCEL
};
const unsigned char kV3Input[] = {
0x80, spdy_version_ch_, 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_ch_, 0x00, 0x08, // HEADERS on Stream #1
0x00, 0x00, 0x00, 0x20,
0x00, 0x00, 0x00, 0x01,
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_ch_, 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_ch_, 0x00, 0x03, // RST_STREAM on Stream #1
0x00, 0x00, 0x00, 0x08,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x05, // RST_STREAM_CANCEL
0x00, 0x00, 0x00, 0x03, // DATA on Stream #3
0x00, 0x00, 0x00, 0x00,
0x80, spdy_version_ch_, 0x00, 0x03, // RST_STREAM on Stream #3
0x00, 0x00, 0x00, 0x08,
0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x05, // RST_STREAM_CANCEL
};
// SYN_STREAM doesn't exist in SPDY4, so instead we send
// HEADERS frames with PRIORITY and END_HEADERS set.
const unsigned char kV4Input[] = {
0x00, 0x00, 0x05, 0x01, // HEADERS: PRIORITY | END_HEADERS
0x24, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, // Stream 1, Priority 0
0x00, 0x82, // :method: GET
0x00, 0x00, 0x01, 0x01, // HEADERS: END_HEADERS
0x04, 0x00, 0x00, 0x00, // Stream 1
0x01, 0x8c, // :status: 200
0x00, 0x00, 0x0c, 0x00, // DATA on Stream #1
0x00, 0x00, 0x00, 0x00,
0x01, 0xde, 0xad, 0xbe,
0xef, 0xde, 0xad, 0xbe,
0xef, 0xde, 0xad, 0xbe,
0xef,
0x00, 0x00, 0x05, 0x01, // HEADERS: PRIORITY | END_HEADERS
0x24, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, // Stream 3, Priority 0
0x00, 0x82, // :method: GET
0x00, 0x00, 0x08, 0x00, // DATA on Stream #3
0x00, 0x00, 0x00, 0x00,
0x03, 0xde, 0xad, 0xbe,
0xef, 0xde, 0xad, 0xbe,
0xef,
0x00, 0x00, 0x04, 0x00, // DATA on Stream #1
0x00, 0x00, 0x00, 0x00,
0x01, 0xde, 0xad, 0xbe,
0xef,
0x00, 0x00, 0x04, 0x03, // RST_STREAM on Stream #1
0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00,
0x08, // RST_STREAM_CANCEL
0x00, 0x00, 0x00, 0x00, // DATA on Stream #3
0x00, 0x00, 0x00, 0x00,
0x03,
0x00, 0x00, 0x0f, 0x03, // RST_STREAM on Stream #3
0x00, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, // RST_STREAM_CANCEL
0x08, 0x52, 0x45, 0x53, // opaque data
0x45, 0x54, 0x53, 0x54,
0x52, 0x45, 0x41, 0x4d,
};
TestSpdyVisitor visitor(spdy_version_);
if (IsSpdy2()) {
visitor.SimulateInFramer(kV2Input, sizeof(kV2Input));
} else if (IsSpdy3()) {
visitor.SimulateInFramer(kV3Input, sizeof(kV3Input));
} else {
visitor.SimulateInFramer(kV4Input, sizeof(kV4Input));
}
EXPECT_EQ(0, visitor.syn_reply_frame_count_);
EXPECT_EQ(24, visitor.data_bytes_);
EXPECT_EQ(0, visitor.error_count_);
EXPECT_EQ(2, visitor.fin_frame_count_);
if (IsSpdy4()) {
EXPECT_EQ(3, visitor.headers_frame_count_);
EXPECT_EQ(0, visitor.syn_frame_count_);
base::StringPiece reset_stream = "RESETSTREAM";
EXPECT_EQ(reset_stream, visitor.fin_opaque_data_);
} else {
EXPECT_EQ(1, visitor.headers_frame_count_);
EXPECT_EQ(2, visitor.syn_frame_count_);
EXPECT_TRUE(visitor.fin_opaque_data_.empty());
}
EXPECT_EQ(0, visitor.fin_flag_count_);
EXPECT_EQ(0, visitor.zero_length_data_frame_count_);
EXPECT_EQ(4, visitor.data_frame_count_);
visitor.fin_opaque_data_.clear();
}
// Test that the FIN flag on a data frame signifies EOF.
TEST_P(SpdyFramerTest, FinOnDataFrame) {
const unsigned char kV2Input[] = {
0x80, spdy_version_ch_, 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_ch_, 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_ch_, 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_ch_, 0x00, 0x02, // SYN REPLY Stream #1
0x00, 0x00, 0x00, 0x14,
0x00, 0x00, 0x00, 0x01,
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,
};
// SYN_STREAM and SYN_REPLY don't exist in SPDY4, so instead we send
// HEADERS frames with PRIORITY(SYN_STREAM only) and END_HEADERS set.
const unsigned char kV4Input[] = {
0x00, 0x00, 0x05, 0x01, // HEADERS: PRIORITY | END_HEADERS
0x24, 0x00, 0x00, 0x00, // Stream 1
0x01, 0x00, 0x00, 0x00, // Priority 0
0x00, 0x82, // :method: GET
0x00, 0x00, 0x01, 0x01, // HEADERS: END_HEADERS
0x04, 0x00, 0x00, 0x00, // Stream 1
0x01, 0x8c, // :status: 200
0x00, 0x00, 0x0c, 0x00, // DATA on Stream #1
0x00, 0x00, 0x00, 0x00,
0x01, 0xde, 0xad, 0xbe,
0xef, 0xde, 0xad, 0xbe,
0xef, 0xde, 0xad, 0xbe,
0xef,
0x00, 0x00, 0x04, 0x00, // DATA on Stream #1, with FIN
0x01, 0x00, 0x00, 0x00,
0x01, 0xde, 0xad, 0xbe,
0xef,
};
TestSpdyVisitor visitor(spdy_version_);
if (IsSpdy2()) {
visitor.SimulateInFramer(kV2Input, sizeof(kV2Input));
} else if (IsSpdy3()) {
visitor.SimulateInFramer(kV3Input, sizeof(kV3Input));
} else {
visitor.SimulateInFramer(kV4Input, sizeof(kV4Input));
}
EXPECT_EQ(0, visitor.error_count_);
if (IsSpdy4()) {
EXPECT_EQ(0, visitor.syn_frame_count_);
EXPECT_EQ(0, visitor.syn_reply_frame_count_);
EXPECT_EQ(2, visitor.headers_frame_count_);
} else {
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_ch_, 0x00, // SYN Stream #1
0x01, 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_ch_, 0x00, // SYN REPLY Stream #1
0x02, 0x01, 0x00, 0x00,
0x10, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00,
0x01, 0x00, 0x02, 'a',
'a', 0x00, 0x02, 'b',
'b',
};
const unsigned char kV3Input[] = {
0x80, spdy_version_ch_, 0x00, // SYN Stream #1
0x01, 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_ch_, 0x00, // SYN REPLY Stream #1
0x02, 0x01, 0x00, 0x00,
0x14, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00,
0x02, 'a', 'a', 0x00,
0x00, 0x00, 0x02, 'b',
'b',
};
// SYN_STREAM and SYN_REPLY don't exist in SPDY4, so instead we send
// HEADERS frames with PRIORITY(SYN_STREAM only) and END_HEADERS set.
const unsigned char kV4Input[] = {
0x00, 0x00, 0x05, 0x01, // HEADERS: PRIORITY | END_HEADERS
0x24, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, // Stream 1, Priority 0
0x00, 0x82, // :method: GET
0x00, 0x00, 0x01, 0x01, // HEADERS: FIN | END_HEADERS
0x05, 0x00, 0x00, 0x00,
0x01, 0x8c, // Stream 1, :status: 200
};
TestSpdyVisitor visitor(spdy_version_);
if (IsSpdy2()) {
visitor.SimulateInFramer(kV2Input, sizeof(kV2Input));
} else if (IsSpdy3()) {
visitor.SimulateInFramer(kV3Input, sizeof(kV3Input));
} else {
visitor.SimulateInFramer(kV4Input, sizeof(kV4Input));
}
EXPECT_EQ(0, visitor.error_count_);
if (IsSpdy4()) {
EXPECT_EQ(0, visitor.syn_frame_count_);
EXPECT_EQ(0, visitor.syn_reply_frame_count_);
EXPECT_EQ(2, visitor.headers_frame_count_);
} else {
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) {
if (spdy_version_ > SPDY3) {
// Deflate compression doesn't apply to HPACK.
return;
}
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;
SpdySynStreamIR syn_ir_1(1);
syn_ir_1.set_name_value_block(block);
scoped_ptr<SpdyFrame> syn_frame_1(send_framer.SerializeFrame(syn_ir_1));
EXPECT_TRUE(syn_frame_1.get() != NULL);
// SYN_STREAM #2
block[kHeader3] = kValue3;
SpdySynStreamIR syn_stream(3);
syn_stream.set_name_value_block(block);
scoped_ptr<SpdyFrame> syn_frame_2(send_framer.SerializeSynStream(syn_stream));
EXPECT_TRUE(syn_frame_2.get() != NULL);
// Now start decompressing
scoped_ptr<SpdyFrame> decompressed;
scoped_ptr<SpdyFrame> uncompressed;
base::StringPiece serialized_headers;
SpdyHeaderBlock decompressed_headers;
// Decompress SYN_STREAM #1
decompressed.reset(
SpdyFramerTestUtil::DecompressFrame(&recv_framer, *syn_frame_1));
EXPECT_TRUE(decompressed.get() != NULL);
serialized_headers = GetSerializedHeaders(decompressed.get(), send_framer);
EXPECT_TRUE(recv_framer.ParseHeaderBlockInBuffer(serialized_headers.data(),
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));
EXPECT_TRUE(decompressed.get() != NULL);
serialized_headers = GetSerializedHeaders(decompressed.get(), send_framer);
decompressed_headers.clear();
EXPECT_TRUE(recv_framer.ParseHeaderBlockInBuffer(serialized_headers.data(),
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 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";
SpdyHeadersIR headers(1);
headers.SetHeader(kHeader1, kValue1);
headers.SetHeader(kHeader2, kValue2);
scoped_ptr<SpdyFrame> headers_frame(send_framer.SerializeHeaders(headers));
EXPECT_TRUE(headers_frame.get() != NULL);
const char bytes[] = "this is a test test test test test!";
SpdyDataIR data_ir(1, StringPiece(bytes, arraysize(bytes)));
data_ir.set_fin(true);
scoped_ptr<SpdyFrame> send_frame(send_framer.SerializeData(data_ir));
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*>(headers_frame->data());
for (size_t idx = 0; idx < headers_frame->size(); ++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->size(); ++idx) {
visitor.SimulateInFramer(data + idx, 1);
ASSERT_EQ(0, visitor.error_count_);
}
EXPECT_EQ(0, visitor.error_count_);
EXPECT_EQ(0, visitor.syn_frame_count_);
EXPECT_EQ(0, visitor.syn_reply_frame_count_);
EXPECT_EQ(1, 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<SpdyFrame> frame(framer.SerializeWindowUpdate(
SpdyWindowUpdateIR(1, 0x12345678)));
const char kDescription[] = "WINDOW_UPDATE frame, stream 1, delta 0x12345678";
const unsigned char kV3FrameData[] = { // Also applies for V2.
0x80, spdy_version_ch_, 0x00, 0x09,
0x00, 0x00, 0x00, 0x08,
0x00, 0x00, 0x00, 0x01,
0x12, 0x34, 0x56, 0x78
};
const unsigned char kV4FrameData[] = {
0x00, 0x00, 0x04, 0x08,
0x00, 0x00, 0x00, 0x00,
0x01, 0x12, 0x34, 0x56,
0x78
};
if (IsSpdy4()) {
CompareFrame(kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
} else {
CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
}
}
TEST_P(SpdyFramerTest, CreateDataFrame) {
SpdyFramer framer(spdy_version_);
{
const char kDescription[] = "'hello' data frame, no FIN";
const unsigned char kV3FrameData[] = { // Also applies for V2.
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x05,
'h', 'e', 'l', 'l',
'o'
};
const unsigned char kV4FrameData[] = {
0x00, 0x00, 0x05, 0x00,
0x00, 0x00, 0x00, 0x00,
0x01, 'h', 'e', 'l',
'l', 'o'
};
const char bytes[] = "hello";
SpdyDataIR data_ir(1, bytes);
scoped_ptr<SpdyFrame> frame(framer.SerializeData(data_ir));
if (IsSpdy4()) {
CompareFrame(
kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
} else {
CompareFrame(
kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
}
SpdyDataIR data_header_ir(1);
data_header_ir.SetDataShallow(bytes);
frame.reset(framer.SerializeDataFrameHeaderWithPaddingLengthField(
data_header_ir));
CompareCharArraysWithHexError(
kDescription,
reinterpret_cast<const unsigned char*>(frame->data()),
framer.GetDataFrameMinimumSize(),
IsSpdy4() ? kV4FrameData : kV3FrameData,
framer.GetDataFrameMinimumSize());
}
{
const char kDescription[] = "'hello' data frame with more padding, no FIN";
const unsigned char kV3FrameData[] = { // Also applies for V2.
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x05,
'h', 'e', 'l', 'l',
'o'
};
const unsigned char kV4FrameData[] = {
0x00, 0x00, 0xfd, 0x00, // Length = 253. PADDED set.
0x08, 0x00, 0x00, 0x00,
0x01, 0xf7, // Pad length field.
'h', 'e', 'l', 'l', // Data
'o',
// Padding of 247 0x00(s).
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
const char bytes[] = "hello";
SpdyDataIR data_ir(1, bytes);
// 247 zeros and the pad length field make the overall padding to be 248
// bytes.
data_ir.set_padding_len(248);
scoped_ptr<SpdyFrame> frame(framer.SerializeData(data_ir));
if (IsSpdy4()) {
CompareFrame(
kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
} else {
CompareFrame(
kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
}
frame.reset(framer.SerializeDataFrameHeaderWithPaddingLengthField(data_ir));
CompareCharArraysWithHexError(
kDescription,
reinterpret_cast<const unsigned char*>(frame->data()),
framer.GetDataFrameMinimumSize(),
IsSpdy4() ? kV4FrameData : kV3FrameData,
framer.GetDataFrameMinimumSize());
}
{
const char kDescription[] = "'hello' data frame with few padding, no FIN";
const unsigned char kV3FrameData[] = { // Also applies for V2.
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x05,
'h', 'e', 'l', 'l',
'o'
};
const unsigned char kV4FrameData[] = {
0x00, 0x00, 0x0d, 0x00, // Length = 13. PADDED set.
0x08, 0x00, 0x00, 0x00,
0x01, 0x07, // Pad length field.
'h', 'e', 'l', 'l', // Data
'o',
0x00, 0x00, 0x00, 0x00, // Padding
0x00, 0x00, 0x00
};
const char bytes[] = "hello";
SpdyDataIR data_ir(1, bytes);
// 7 zeros and the pad length field make the overall padding to be 8 bytes.
data_ir.set_padding_len(8);
scoped_ptr<SpdyFrame> frame(framer.SerializeData(data_ir));
if (IsSpdy4()) {
CompareFrame(
kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
} else {
CompareFrame(
kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
}
}
{
const char kDescription[] =
"'hello' data frame with 1 byte padding, no FIN";
const unsigned char kV3FrameData[] = { // Also applies for V2.
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x05,
'h', 'e', 'l', 'l',
'o'
};
const unsigned char kV4FrameData[] = {
0x00, 0x00, 0x06, 0x00, // Length = 6. PADDED set.
0x08, 0x00, 0x00, 0x00,
0x01, 0x00, // Pad length field.
'h', 'e', 'l', 'l', // Data
'o',
};
const char bytes[] = "hello";
SpdyDataIR data_ir(1, bytes);
// The pad length field itself is used for the 1-byte padding and no padding
// payload is needed.
data_ir.set_padding_len(1);
scoped_ptr<SpdyFrame> frame(framer.SerializeData(data_ir));
if (IsSpdy4()) {
CompareFrame(
kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
} else {
CompareFrame(
kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
}
frame.reset(framer.SerializeDataFrameHeaderWithPaddingLengthField(data_ir));
CompareCharArraysWithHexError(
kDescription,
reinterpret_cast<const unsigned char*>(frame->data()),
framer.GetDataFrameMinimumSize(),
IsSpdy4() ? kV4FrameData : kV3FrameData,
framer.GetDataFrameMinimumSize());
}
{
const char kDescription[] = "Data frame with negative data byte, no FIN";
const unsigned char kV3FrameData[] = { // Also applies for V2.
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x01,
0xff
};
const unsigned char kV4FrameData[] = {
0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01,
0xff
};
SpdyDataIR data_ir(1, "\xff");
scoped_ptr<SpdyFrame> frame(framer.SerializeData(data_ir));
if (IsSpdy4()) {
CompareFrame(
kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
} else {
CompareFrame(
kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
}
}
{
const char kDescription[] = "'hello' data frame, with FIN";
const unsigned char kV3FrameData[] = { // Also applies for V2.
0x00, 0x00, 0x00, 0x01,
0x01, 0x00, 0x00, 0x05,
'h', 'e', 'l', 'l',
'o'
};
const unsigned char kV4FrameData[] = {
0x00, 0x00, 0x05, 0x00,
0x01, 0x00, 0x00, 0x00,
0x01, 'h', 'e', 'l',
'l', 'o'
};
SpdyDataIR data_ir(1, "hello");
data_ir.set_fin(true);
scoped_ptr<SpdyFrame> frame(framer.SerializeData(data_ir));
if (IsSpdy4()) {
CompareFrame(
kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
} else {
CompareFrame(
kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
}
}
{
const char kDescription[] = "Empty data frame";
const unsigned char kV3FrameData[] = { // Also applies for V2.
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
};
const unsigned char kV4FrameData[] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x01,
};
SpdyDataIR data_ir(1, "");
scoped_ptr<SpdyFrame> frame(framer.SerializeData(data_ir));
if (IsSpdy4()) {
CompareFrame(
kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
} else {
CompareFrame(
kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
}
frame.reset(framer.SerializeDataFrameHeaderWithPaddingLengthField(data_ir));
CompareCharArraysWithHexError(
kDescription,
reinterpret_cast<const unsigned char*>(frame->data()),
framer.GetDataFrameMinimumSize(),
IsSpdy4() ? kV4FrameData : kV3FrameData,
framer.GetDataFrameMinimumSize());
}
{
const char kDescription[] = "Data frame with max stream ID";
const unsigned char kV3FrameData[] = { // Also applies for V2.
0x7f, 0xff, 0xff, 0xff,
0x01, 0x00, 0x00, 0x05,
'h', 'e', 'l', 'l',
'o'
};
const unsigned char kV4FrameData[] = {
0x00, 0x00, 0x05, 0x00,
0x01, 0x7f, 0xff, 0xff,
0xff, 'h', 'e', 'l',
'l', 'o'
};
SpdyDataIR data_ir(0x7fffffff, "hello");
data_ir.set_fin(true);
scoped_ptr<SpdyFrame> frame(framer.SerializeData(data_ir));
if (IsSpdy4()) {
CompareFrame(
kDescription, *frame, kV4FrameData, arraysize(kV4FrameData));
} else {
CompareFrame(
kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
}
}
if (!IsSpdy4()) {
// This test does not apply to SPDY 4 because the max frame size is smaller
// than 4MB.
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_ptr<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);
SpdyDataIR data_ir(1, kData);
data_ir.set_fin(true);
scoped_ptr<SpdyFrame> frame(framer.SerializeData(data_ir));
CompareFrame(kDescription, *frame, expected_frame_data.get(), kFrameSize);
}
}
TEST_P(SpdyFramerTest, CreateSynStreamUncompressed) {
if (!IsSpdy2() && !IsSpdy3()) {
// SYN_STREAM unsupported in SPDY>3
return;
}
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(false);
{
const char kDescription[] = "SYN_STREAM frame, lowest pri, no FIN";
const unsigned char kPri = IsSpdy2() ? 0xC0 : 0xE0;
const unsigned char kV2FrameData[] = {
0x80, spdy_version_ch_, 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_ch_, 0x00, 0x01,
0x00, 0x00, 0x00, 0x2a,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
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, 0x03, 'b',
'a', 'r'
};
SpdySynStreamIR syn_stream(1);
syn_stream.set_priority(framer.GetLowestPriority());
syn_stream.SetHeader("bar", "foo");
syn_stream.SetHeader("foo", "bar");
scoped_ptr<SpdyFrame> frame(framer.SerializeSynStream(syn_stream));
if (IsSpdy2()) {
CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData));
} else if (IsSpdy3()) {
CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
} else {
LOG(FATAL) << "Unsupported version in test.";
}
}
{
const char kDescription[] =
"SYN_STREAM frame with a 0-length header name, highest pri, FIN, "
"max stream ID";
const unsigned char kV2FrameData[] = {
0x80, spdy_version_ch_, 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_ch_, 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'
};
SpdySynStreamIR syn_stream(0x7fffffff);
syn_stream.set_associated_to_stream_id(0x7fffffff);
syn_stream.set_priority(framer.GetHighestPriority());
syn_stream.set_fin(true);
syn_stream.SetHeader("", "foo");
syn_stream.SetHeader("foo", "bar");
scoped_ptr<SpdyFrame> frame(framer.SerializeSynStream(syn_stream));
if (IsSpdy2()) {
CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData));
} else if (IsSpdy3()) {
CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
} else {
LOG(FATAL) << "Unsupported version in test.";
}
}
{
const char kDescription[] =
"SYN_STREAM frame with a 0-length header val, high pri, FIN, "
"max stream ID";
const unsigned char kPri = IsSpdy2() ? 0x40 : 0x20;
const unsigned char kV2FrameData[] = {
0x80, spdy_version_ch_, 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_ch_, 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
};
SpdySynStreamIR syn_stream(0x7fffffff);
syn_stream.set_associated_to_stream_id(0x7fffffff);
syn_stream.set_priority(1);
syn_stream.set_fin(true);
syn_stream.SetHeader("bar", "foo");
syn_stream.SetHeader("foo", "");
scoped_ptr<SpdyFrame> frame(framer.SerializeSynStream(syn_stream));
if (IsSpdy2()) {
CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData));
} else if (IsSpdy3()) {
CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
} else {
LOG(FATAL) << "Unsupported version in test.";
}
}
}
// 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) {
if (!IsSpdy2() && !IsSpdy3()) {
// SYN_STREAM not supported for SPDY>3
return;
}
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(true);
{
const char kDescription[] =
"SYN_STREAM frame, low pri, no FIN";
const SpdyPriority priority = IsSpdy2() ? 2 : 4;
const unsigned char kV2FrameData[] = {
0x80, spdy_version_ch_, 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_ch_, 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,
};
const unsigned char kV2SIMDFrameData[] = {
0x80, spdy_version_ch_, 0x00, 0x01,
0x00, 0x00, 0x00, 0x33,
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, 0x06,
0x13, 0x00, 0x01, 0x94,
0x94, 0x58, 0x04, 0x10,
0x40, 0x00, 0x00, 0x00,
0x00, 0xff, 0xff,
};
const unsigned char kV3SIMDFrameData[] = {
0x80, spdy_version_ch_, 0x00, 0x01,
0x00, 0x00, 0x00, 0x32,
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, 0x24,
0x0a, 0x20, 0x80, 0x92,
0x12, 0x8b, 0x00, 0x02,
0x08, 0x00, 0x00, 0x00,
0xff, 0xff,
};
SpdySynStreamIR syn_stream(1);
syn_stream.set_priority(priority);
syn_stream.SetHeader("bar", "foo");
syn_stream.SetHeader("foo", "bar");
scoped_ptr<SpdyFrame> frame(framer.SerializeSynStream(syn_stream));
const unsigned char* frame_data =
reinterpret_cast<const unsigned char*>(frame->data());
if (IsSpdy2()) {
// Try comparing with SIMD version, if that fails, do a failing check
// with pretty printing against non-SIMD version
if (memcmp(frame_data,
kV2SIMDFrameData,
std::min(arraysize(kV2SIMDFrameData), frame->size())) != 0) {
CompareCharArraysWithHexError(kDescription,
frame_data,
frame->size(),
kV2FrameData,
arraysize(kV2FrameData));
}
} else if (IsSpdy3()) {
if (memcmp(frame_data,
kV3SIMDFrameData,
std::min(arraysize(kV3SIMDFrameData), frame->size())) != 0) {
CompareCharArraysWithHexError(kDescription,
frame_data,
frame->size(),
kV3FrameData,
arraysize(kV3FrameData));
}
} else {
LOG(FATAL) << "Unsupported version in test.";
}
}
}
#endif // !defined(USE_SYSTEM_ZLIB)
TEST_P(SpdyFramerTest, CreateSynReplyUncompressed) {
if (spdy_version_ > SPDY3) {
// SYN_REPLY unsupported in SPDY>3
return;
}
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(false);
{
const char kDescription[] = "SYN_REPLY frame, no FIN";
const unsigned char kV2FrameData[] = {
0x80, spdy_version_ch_, 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_ch_, 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'
};
SpdySynReplyIR syn_reply(1);
syn_reply.SetHeader("bar", "foo");
syn_reply.SetHeader("foo", "bar");
scoped_ptr<SpdyFrame> frame(framer.SerializeSynReply(syn_reply));
if (IsSpdy2()) {
CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData));
} else if (IsSpdy3()) {
CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
} else {
LOG(FATAL) << "Unsupported version in test.";
}
}
{
const char kDescription[] =
"SYN_REPLY frame with a 0-length header name, FIN, max stream ID";
const unsigned char kV2FrameData[] = {
0x80, spdy_version_ch_, 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_ch_, 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'
};
SpdySynReplyIR syn_reply(0x7fffffff);
syn_reply.set_fin(true);
syn_reply.SetHeader("", "foo");
syn_reply.SetHeader("foo", "bar");
scoped_ptr<SpdyFrame> frame(framer.SerializeSynReply(syn_reply));
if (IsSpdy2()) {
CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData));
} else if (IsSpdy3()) {
CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
} else {
LOG(FATAL) << "Unsupported version in test.";
}
}
{
const char kDescription[] =
"SYN_REPLY frame with a 0-length header val, FIN, max stream ID";
const unsigned char kV2FrameData[] = {
0x80, spdy_version_ch_, 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_ch_, 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
};
SpdySynReplyIR syn_reply(0x7fffffff);
syn_reply.set_fin(true);
syn_reply.SetHeader("bar", "foo");
syn_reply.SetHeader("foo", "");
scoped_ptr<SpdyFrame> frame(framer.SerializeSynReply(syn_reply));
if (IsSpdy2()) {
CompareFrame(kDescription, *frame, kV2FrameData, arraysize(kV2FrameData));
} else if (IsSpdy3()) {
CompareFrame(kDescription, *frame, kV3FrameData, arraysize(kV3FrameData));
} else {
LOG(FATAL) << "Unsupported version in test.";
}
}
}
// 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) {
if (spdy_version_ > SPDY3) {
// SYN_REPLY unsupported in SPDY>3
return;
}
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(true);
{
const char kDescription[] = "SYN_REPLY frame, no FIN";
const unsigned char kV2FrameData[] = {
0x80, spdy_version_ch_, 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_ch_, 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,
};
const unsigned char kV2SIMDFrameData[] = {
0x80, spdy_version_ch_, 0x00, 0x02,
0x00, 0x00, 0x00, 0x2f,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x38, 0xea,
0xdf, 0xa2, 0x51, 0xb2,
0x62, 0x60, 0x62, 0x60,
0x4e, 0x4a, 0x2c,