blob: d9976d55ab06b31d7c78aaecddb139bdaeb901d2 [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 "net/spdy/spdy_framer.h"
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/strings/string_number_conversions.h"
#include "net/quic/core/quic_flags.h"
#include "net/spdy/hpack/hpack_constants.h"
#include "net/spdy/mock_spdy_framer_visitor.h"
#include "net/spdy/spdy_flags.h"
#include "net/spdy/spdy_frame_builder.h"
#include "net/spdy/spdy_frame_reader.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 base::StringPiece;
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 SpdySerializedFrame.
template <class SpdyFrameType>
static SpdySerializedFrame DecompressFrame(SpdyFramer* framer,
const SpdyFrameType& frame) {
NewDecompressionVisitor visitor;
framer->set_visitor(&visitor);
CHECK_EQ(frame.size(), framer->ProcessInput(frame.data(), frame.size()));
CHECK_EQ(SpdyFramer::SPDY_READY_FOR_FRAME, framer->state());
framer->set_visitor(nullptr);
SpdyFramer serializer(framer->protocol_version());
serializer.set_enable_compression(false);
return serializer.SerializeFrame(visitor.GetFrame());
}
class NewDecompressionVisitor : public SpdyFramerVisitorInterface {
public:
NewDecompressionVisitor() : finished_(false) {}
const SpdyFrameIR& GetFrame() const {
CHECK(finished_);
return *frame_;
}
SpdyHeadersHandlerInterface* OnHeaderFrameStart(
SpdyStreamId stream_id) override {
if (headers_handler_ == nullptr) {
headers_handler_.reset(new TestHeadersHandler);
}
return headers_handler_.get();
}
void OnHeaderFrameEnd(SpdyStreamId stream_id, bool end_headers) override {
CHECK(!finished_);
frame_->set_header_block(headers_handler_->decoded_block().Clone());
finished_ = true;
if (end_headers) {
headers_handler_.reset();
}
}
void OnSynStream(SpdyStreamId stream_id,
SpdyStreamId associated_stream_id,
SpdyPriority priority,
bool fin,
bool unidirectional) override {
SpdySynStreamIR* syn_stream = new SpdySynStreamIR(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);
frame_.reset(syn_stream);
}
void OnSynReply(SpdyStreamId stream_id, bool fin) override {
SpdyHeadersIR* headers = new SpdyHeadersIR(stream_id);
headers->set_fin(fin);
frame_.reset(headers);
}
void OnHeaders(SpdyStreamId stream_id,
bool has_priority,
int weight,
SpdyStreamId parent_stream_id,
bool exclusive,
bool fin,
bool end) override {
SpdyHeadersIR* headers = new SpdyHeadersIR(stream_id);
headers->set_has_priority(has_priority);
headers->set_weight(weight);
headers->set_parent_stream_id(parent_stream_id);
headers->set_exclusive(exclusive);
headers->set_fin(fin);
frame_.reset(headers);
}
void OnPushPromise(SpdyStreamId stream_id,
SpdyStreamId promised_stream_id,
bool end) override {
SpdyPushPromiseIR* push_promise =
new SpdyPushPromiseIR(stream_id, promised_stream_id);
frame_.reset(push_promise);
}
// TODO(birenroy): Add support for CONTINUATION.
void OnContinuation(SpdyStreamId stream_id, bool end) override {
LOG(FATAL);
}
// All other methods just LOG(FATAL).
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) override {
LOG(FATAL);
}
void OnStreamEnd(SpdyStreamId stream_id) 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 {
LOG(FATAL);
return true;
}
void OnRstStream(SpdyStreamId stream_id,
SpdyRstStreamStatus status) override {
LOG(FATAL);
}
void OnSetting(SpdySettingsIds id, uint8_t flags, uint32_t 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 OnWindowUpdate(SpdyStreamId stream_id,
int delta_window_size) override {
LOG(FATAL);
}
void OnPriority(SpdyStreamId stream_id,
SpdyStreamId parent_stream_id,
int weight,
bool exclusive) override {
// Do nothing.
}
bool OnUnknownFrame(SpdyStreamId stream_id, int frame_type) override {
LOG(FATAL);
return false;
}
private:
std::unique_ptr<TestHeadersHandler> headers_handler_;
std::unique_ptr<SpdyFrameWithHeaderBlockIR> frame_;
bool finished_;
DISALLOW_COPY_AND_ASSIGN(NewDecompressionVisitor);
};
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) override {
LOG(FATAL);
}
void OnStreamEnd(SpdyStreamId stream_id) override { LOG(FATAL); }
void OnStreamPadding(SpdyStreamId stream_id, size_t len) override {
LOG(FATAL);
}
SpdyHeadersHandlerInterface* OnHeaderFrameStart(
SpdyStreamId stream_id) override {
LOG(FATAL) << "Not implemented.";
return nullptr;
}
void OnHeaderFrameEnd(SpdyStreamId stream_id, bool end_headers) override {
LOG(FATAL) << "Not implemented.";
}
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);
SpdySerializedFrame 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);
SpdySerializedFrame 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_t flags, uint32_t 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,
int weight,
SpdyStreamId parent_stream_id,
bool exclusive,
bool fin,
bool end) override {
SpdyFramer framer(version_);
framer.set_enable_compression(false);
SpdyHeadersIR headers(stream_id);
headers.set_has_priority(has_priority);
headers.set_weight(weight);
headers.set_parent_stream_id(parent_stream_id);
headers.set_exclusive(exclusive);
headers.set_fin(fin);
SpdySerializedFrame frame(framer.SerializeHeaders(headers));
ResetBuffer();
memcpy(buffer_.get(), frame.data(), framer.GetHeadersMinimumSize());
size_ += framer.GetHeadersMinimumSize();
}
void OnWindowUpdate(SpdyStreamId stream_id,
int delta_window_size) override {
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);
SpdySerializedFrame 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,
int 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();
}
size_t size() const {
CHECK(finished_);
return size_;
}
private:
SpdyMajorVersion version_;
std::unique_ptr<char[]> buffer_;
size_t size_;
bool finished_;
DISALLOW_COPY_AND_ASSIGN(DecompressionVisitor);
};
private:
DISALLOW_COPY_AND_ASSIGN(SpdyFramerTestUtil);
};
class SpdyFramerPeer {
public:
static size_t ControlFrameBufferSize() {
return SpdyFramer::kControlFrameBufferSize;
}
static size_t GetNumberRequiredContinuationFrames(SpdyFramer* framer,
size_t size) {
return framer->GetNumberRequiredContinuationFrames(size);
}
static void SetError(SpdyFramer* framer, SpdyFramer::SpdyError error) {
framer->set_error(error);
}
};
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),
end_of_stream_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 {
VLOG(1) << "SpdyFramer Error: "
<< SpdyFramer::ErrorCodeToString(f->error_code());
++error_count_;
}
void OnDataFrameHeader(SpdyStreamId stream_id,
size_t length,
bool fin) override {
VLOG(1) << "OnDataFrameHeader(" << stream_id << ", " << length << ", "
<< fin << ")";
++data_frame_count_;
header_stream_id_ = stream_id;
}
void OnStreamFrameData(SpdyStreamId stream_id,
const char* data,
size_t len) override {
VLOG(1) << "OnStreamFrameData(" << stream_id << ", data, " << len << ", "
<< ") data:\n"
<< base::HexEncode(data, len);
EXPECT_EQ(header_stream_id_, stream_id);
data_bytes_ += len;
}
void OnStreamEnd(SpdyStreamId stream_id) override {
VLOG(1) << "OnStreamEnd(" << stream_id << ")";
EXPECT_EQ(header_stream_id_, stream_id);
++end_of_stream_count_;
}
void OnStreamPadding(SpdyStreamId stream_id, size_t len) override {
VLOG(1) << "OnStreamPadding(" << stream_id << ", " << len << ")\n";
EXPECT_EQ(header_stream_id_, stream_id);
data_bytes_ += len;
}
SpdyHeadersHandlerInterface* OnHeaderFrameStart(
SpdyStreamId stream_id) override {
if (headers_handler_ == nullptr) {
headers_handler_.reset(new TestHeadersHandler);
}
return headers_handler_.get();
}
void OnHeaderFrameEnd(SpdyStreamId stream_id, bool end_headers) override {
CHECK(headers_handler_ != nullptr);
headers_ = headers_handler_->decoded_block().Clone();
header_bytes_received_ = headers_handler_->header_bytes_parsed();
if (end_headers) {
headers_handler_.reset();
}
}
bool OnControlFrameHeaderData(SpdyStreamId stream_id,
const char* header_data,
size_t len) override {
VLOG(1) << "OnControlFrameHeaderData(" << stream_id << ", data, " << 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.
headers_.clear();
CHECK(header_buffer_valid_);
return framer_.ParseHeaderBlockInBuffer(header_buffer_.get(),
header_buffer_length_, &headers_);
}
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 {
VLOG(1) << "OnSynStream(" << stream_id << ", " << associated_stream_id
<< ", " << priority << ", " << (fin ? 1 : 0) << ", "
<< (unidirectional ? 1 : 0) << ")";
++syn_frame_count_;
if (framer_.protocol_version() == SPDY3) {
InitHeaderStreaming(SYN_STREAM, stream_id);
} else {
InitHeaderStreaming(HEADERS, 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(SYN_REPLY, stream_id);
} else {
InitHeaderStreaming(HEADERS, stream_id);
}
if (fin) {
++fin_flag_count_;
}
}
void OnRstStream(SpdyStreamId stream_id,
SpdyRstStreamStatus status) override {
VLOG(1) << "OnRstStream(" << stream_id << ", " << status << ")";
++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_t flags, uint32_t value) override {
VLOG(1) << "OnSetting(" << id << ", " << std::hex << flags << ", " << value
<< ")";
++setting_count_;
}
void OnSettingsAck() override {
VLOG(1) << "OnSettingsAck";
DCHECK_EQ(HTTP2, framer_.protocol_version());
++settings_ack_received_;
}
void OnSettingsEnd() override {
VLOG(1) << "OnSettingsEnd";
if (framer_.protocol_version() == HTTP2) {
++settings_ack_sent_;
}
}
void OnPing(SpdyPingId unique_id, bool is_ack) override {
LOG(DFATAL) << "OnPing(" << unique_id << ", " << (is_ack ? 1 : 0) << ")";
}
void OnGoAway(SpdyStreamId last_accepted_stream_id,
SpdyGoAwayStatus status) override {
VLOG(1) << "OnGoAway(" << last_accepted_stream_id << ", " << status << ")";
++goaway_count_;
}
void OnHeaders(SpdyStreamId stream_id,
bool has_priority,
int weight,
SpdyStreamId parent_stream_id,
bool exclusive,
bool fin,
bool end) override {
VLOG(1) << "OnHeaders(" << stream_id << ", " << has_priority << ", "
<< weight << ", " << parent_stream_id << ", " << exclusive << ", "
<< fin << ", " << end << ")";
++headers_frame_count_;
InitHeaderStreaming(HEADERS, stream_id);
if (fin) {
++fin_flag_count_;
}
header_has_priority_ = has_priority;
header_parent_stream_id_ = parent_stream_id;
header_exclusive_ = exclusive;
}
void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) override {
VLOG(1) << "OnWindowUpdate(" << stream_id << ", " << delta_window_size
<< ")";
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 {
VLOG(1) << "OnPushPromise(" << stream_id << ", " << promised_stream_id
<< ", " << end << ")";
++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 {
VLOG(1) << "OnContinuation(" << stream_id << ", " << end << ")";
++continuation_count_;
}
void OnAltSvc(SpdyStreamId stream_id,
StringPiece origin,
const SpdyAltSvcWireFormat::AlternativeServiceVector&
altsvc_vector) override {
VLOG(1) << "OnAltSvc(" << stream_id << ", \"" << origin
<< "\", altsvc_vector)";
test_altsvc_ir_.set_stream_id(stream_id);
if (origin.length() > 0) {
test_altsvc_ir_.set_origin(origin.as_string());
}
for (const SpdyAltSvcWireFormat::AlternativeService& altsvc :
altsvc_vector) {
test_altsvc_ir_.add_altsvc(altsvc);
}
++altsvc_count_;
}
void OnPriority(SpdyStreamId stream_id,
SpdyStreamId parent_stream_id,
int weight,
bool exclusive) override {
VLOG(1) << "OnPriority(" << stream_id << ", " << parent_stream_id << ", "
<< weight << ", " << (exclusive ? 1 : 0) << ")";
++priority_count_;
}
bool OnUnknownFrame(SpdyStreamId stream_id, int frame_type) override {
VLOG(1) << "OnUnknownFrame(" << stream_id << ", " << frame_type << ")";
return on_unknown_frame_result_;
}
void OnSendCompressedFrame(SpdyStreamId stream_id,
SpdyFrameType type,
size_t payload_len,
size_t frame_len) override {
VLOG(1) << "OnSendCompressedFrame(" << stream_id << ", " << type << ", "
<< payload_len << ", " << frame_len << ")";
last_payload_len_ = payload_len;
last_frame_len_ = frame_len;
}
void OnReceiveCompressedFrame(SpdyStreamId stream_id,
SpdyFrameType type,
size_t frame_len) override {
VLOG(1) << "OnReceiveCompressedFrame(" << stream_id << ", " << type << ", "
<< frame_len << ")";
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 (and 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;
}
// Largest control frame that the SPDY implementation is willing to receive,
// excluding the size of the header.
static size_t received_control_frame_max_size() {
return kSpdyInitialFrameSizeLimit;
}
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_;
int 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 end_of_stream_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:
std::unique_ptr<char[]> header_buffer_;
size_t header_buffer_length_;
size_t header_buffer_size_;
size_t header_bytes_received_;
SpdyStreamId header_stream_id_;
SpdyFrameType header_control_type_;
bool header_buffer_valid_;
std::unique_ptr<TestHeadersHandler> headers_handler_;
SpdyHeaderBlock headers_;
bool header_has_priority_;
SpdyStreamId header_parent_stream_id_;
bool header_exclusive_;
};
// Retrieves serialized headers from a HEADERS or SYN_STREAM frame.
StringPiece GetSerializedHeaders(const SpdySerializedFrame& frame,
const SpdyFramer& framer) {
SpdyFrameReader reader(frame.data(), frame.size());
if (framer.protocol_version() == SPDY3) {
reader.Seek(2); // Seek past the frame length.
} else {
reader.Seek(3); // Seek past the frame length.
}
SpdyFrameType frame_type;
if (framer.protocol_version() == SPDY3) {
uint16_t 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;
} else {
uint8_t serialized_type;
reader.ReadUInt8(&serialized_type);
frame_type = SpdyConstants::ParseFrameType(framer.protocol_version(),
serialized_type);
DCHECK_EQ(HEADERS, frame_type);
uint8_t flags;
reader.ReadUInt8(&flags);
if (flags & HEADERS_FLAG_PRIORITY) {
frame_type = SYN_STREAM;
}
}
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(); }
void CompareFrame(const string& description,
const SpdySerializedFrame& 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 SpdySerializedFrame& expected_frame,
const SpdySerializedFrame& 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());
}
bool IsSpdy3() { return spdy_version_ == SPDY3; }
bool IsHttp2() { return spdy_version_ == HTTP2; }
// Version of SPDY protocol to be used.
SpdyMajorVersion spdy_version_;
};
// All tests are run with SPDY/3 and HTTP/2.
INSTANTIATE_TEST_CASE_P(SpdyFramerTests,
SpdyFramerTest,
::testing::Values(SPDY3, HTTP2));
// Test that we ignore cookie where both name and value are empty.
TEST_P(SpdyFramerTest, HeaderBlockWithEmptyCookie) {
if (!IsSpdy3()) {
// Not implemented for hpack.
return;
}
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(true);
SpdyHeadersIR headers(1);
headers.SetHeader("cookie",
"=; key=value; ; = ; foo; bar=; ; = ; k2=v2 ; =");
SpdySerializedFrame frame(framer.SerializeHeaders(headers));
TestSpdyVisitor visitor(spdy_version_);
visitor.use_compression_ = true;
visitor.SimulateInFramer(reinterpret_cast<unsigned char*>(frame.data()),
frame.size());
EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
EXPECT_NE(headers.header_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.SetHeader("alpha", "beta");
headers.SetHeader("gamma", "charlie");
headers.SetHeader("cookie", "key1=value1; key2=value2");
SpdySerializedFrame frame(framer.SerializeHeaders(headers));
TestSpdyVisitor visitor(spdy_version_);
visitor.use_compression_ = false;
visitor.SimulateInFramer(reinterpret_cast<unsigned char*>(frame.data()),
frame.size());
EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_);
EXPECT_EQ(headers.header_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.SetHeader("alpha", "beta");
headers.SetHeader("gamma", "charlie");
SpdySerializedFrame frame(framer.SerializeHeaders(headers));
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 we can encode and decode stream dependency values in a header
// frame.
TEST_P(SpdyFramerTest, HeaderStreamDependencyValues) {
if (!IsHttp2()) {
return;
}
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(false);
const SpdyStreamId parent_stream_id_test_array[] = {0, 3};
for (SpdyStreamId parent_stream_id : parent_stream_id_test_array) {
const bool exclusive_test_array[] = {true, false};
for (bool exclusive : exclusive_test_array) {
SpdyHeadersIR headers(1);
headers.set_has_priority(true);
headers.set_parent_stream_id(parent_stream_id);
headers.set_exclusive(exclusive);
SpdySerializedFrame frame(framer.SerializeHeaders(headers));
TestSpdyVisitor visitor(spdy_version_);
visitor.use_compression_ = false;
visitor.SimulateInFramer(reinterpret_cast<unsigned char*>(frame.data()),
frame.size());
EXPECT_TRUE(visitor.header_has_priority_);
EXPECT_EQ(parent_stream_id, visitor.header_parent_stream_id_);
EXPECT_EQ(exclusive, visitor.header_exclusive_);
}
}
}
// Test that if we receive a frame with payload length field at the
// advertised max size, we do not set an error in ProcessInput.
TEST_P(SpdyFramerTest, AcceptMaxFrameSizeSetting) {
if (!IsHttp2()) {
return;
}
testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
SpdyFramer framer(spdy_version_);
framer.set_visitor(&visitor);
// DATA frame with maximum allowed payload length.
unsigned char kH2FrameData[] = {
0x00, 0x40, 0x00, // Length: 2^14
0x00, // Type: HEADERS
0x00, // Flags: None
0x00, 0x00, 0x00, 0x01, // Stream: 1
0x00, 0x00, 0x00, 0x00, // Junk payload
};
SpdySerializedFrame frame(reinterpret_cast<char*>(kH2FrameData),
sizeof(kH2FrameData), false);
EXPECT_CALL(visitor, OnDataFrameHeader(1, 1 << 14, false));
EXPECT_CALL(visitor, OnStreamFrameData(1, _, 4));
framer.ProcessInput(frame.data(), frame.size());
EXPECT_FALSE(framer.HasError());
}
// Test that if we receive a frame with payload length larger than the
// advertised max size, we set an error of SPDY_INVALID_CONTROL_FRAME_SIZE.
TEST_P(SpdyFramerTest, ExceedMaxFrameSizeSetting) {
if (!IsHttp2()) {
return;
}
testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
SpdyFramer framer(spdy_version_);
framer.set_visitor(&visitor);
// DATA frame with too large payload length.
unsigned char kH2FrameData[] = {
0x00, 0x40, 0x01, // Length: 2^14 + 1
0x00, // Type: HEADERS
0x00, // Flags: None
0x00, 0x00, 0x00, 0x01, // Stream: 1
0x00, 0x00, 0x00, 0x00, // Junk payload
};
SpdySerializedFrame frame(reinterpret_cast<char*>(kH2FrameData),
sizeof(kH2FrameData), false);
EXPECT_CALL(visitor, OnError(testing::Eq(&framer)));
framer.ProcessInput(frame.data(), frame.size());
EXPECT_TRUE(framer.HasError());
EXPECT_EQ(SpdyFramer::SPDY_OVERSIZED_PAYLOAD, framer.error_code())
<< SpdyFramer::ErrorCodeToString(framer.error_code());
}
// Test that if we receive a DATA frame with padding length larger than the
// payload length, we set an error of SPDY_INVALID_PADDING
TEST_P(SpdyFramerTest, OversizedDataPaddingError) {
if (!IsHttp2()) {
return;
}
testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
SpdyFramer framer(spdy_version_);
framer.set_visitor(&visitor);
// DATA frame with invalid padding length.
// |kH2FrameData| has to be |unsigned char|, because Chromium on Windows uses
// MSVC, where |char| is signed by default, which would not compile because of
// the element exceeding 127.
unsigned char kH2FrameData[] = {
0x00, 0x00, 0x05, // Length: 5
0x00, // Type: DATA
0x09, // Flags: END_STREAM|PADDED
0x00, 0x00, 0x00, 0x01, // Stream: 1
0xff, // PadLen: 255 trailing bytes (Too Long)
0x00, 0x00, 0x00, 0x00, // Padding
};
SpdySerializedFrame frame(reinterpret_cast<char*>(kH2FrameData),
sizeof(kH2FrameData), false);
{
testing::InSequence seq;
EXPECT_CALL(visitor, OnDataFrameHeader(1, 5, 1));
EXPECT_CALL(visitor, OnStreamPadding(1, 1));
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_PADDING, framer.error_code())
<< SpdyFramer::ErrorCodeToString(framer.error_code());
}
// Test that if we receive a DATA frame with padding length not larger than the
// payload length, we do not set an error of SPDY_INVALID_PADDING
TEST_P(SpdyFramerTest, CorrectlySizedDataPaddingNoError) {
if (!IsHttp2()) {
return;
}
testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
SpdyFramer framer(spdy_version_);
framer.set_visitor(&visitor);
// DATA frame with valid Padding length
char kH2FrameData[] = {
0x00, 0x00, 0x05, // Length: 5
0x00, // Type: DATA
0x08, // Flags: PADDED
0x00, 0x00, 0x00, 0x01, // Stream: 1
0x04, // PadLen: 4 trailing bytes
0x00, 0x00, 0x00, 0x00, // Padding
};
SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false);
{
testing::InSequence seq;
EXPECT_CALL(visitor, OnDataFrameHeader(1, 5, false));
EXPECT_CALL(visitor, OnStreamPadding(1, 1));
EXPECT_CALL(visitor, OnError(testing::Eq(&framer))).Times(0);
// Note that OnStreamFrameData(1, _, 1)) is never called
// since there is no data, only padding
EXPECT_CALL(visitor, OnStreamPadding(1, 4));
}
EXPECT_EQ(frame.size(), framer.ProcessInput(frame.data(), frame.size()));
EXPECT_FALSE(framer.HasError());
EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code())
<< SpdyFramer::ErrorCodeToString(framer.error_code());
}
// Test that if we receive a HEADERS frame with padding length larger than the
// payload length, we set an error of SPDY_INVALID_PADDING
TEST_P(SpdyFramerTest, OversizedHeadersPaddingError) {
if (!IsHttp2()) {
return;
}
testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
SpdyFramer framer(spdy_version_);
framer.set_visitor(&visitor);
// HEADERS frame with invalid padding length.
// |kH2FrameData| has to be |unsigned char|, because Chromium on Windows uses
// MSVC, where |char| is signed by default, which would not compile because of
// the element exceeding 127.
unsigned char kH2FrameData[] = {
0x00, 0x00, 0x05, // Length: 5
0x01, // Type: HEADERS
0x08, // Flags: PADDED
0x00, 0x00, 0x00, 0x01, // Stream: 1
0xff, // PadLen: 255 trailing bytes (Too Long)
0x00, 0x00, 0x00, 0x00, // Padding
};
SpdySerializedFrame frame(reinterpret_cast<char*>(kH2FrameData),
sizeof(kH2FrameData), false);
EXPECT_CALL(visitor, OnHeaders(1, false, 0, 0, false, false, false));
EXPECT_CALL(visitor, OnHeaderFrameStart(1)).Times(1);
EXPECT_CALL(visitor, OnError(testing::Eq(&framer)));
EXPECT_EQ(frame.size(), framer.ProcessInput(frame.data(), frame.size()));
EXPECT_TRUE(framer.HasError());
EXPECT_EQ(SpdyFramer::SPDY_INVALID_PADDING, framer.error_code())
<< SpdyFramer::ErrorCodeToString(framer.error_code());
}
// Test that if we receive a HEADERS frame with padding length not larger
// than the payload length, we do not set an error of SPDY_INVALID_PADDING
TEST_P(SpdyFramerTest, CorrectlySizedHeadersPaddingNoError) {
if (!IsHttp2()) {
return;
}
testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
SpdyFramer framer(spdy_version_);
framer.set_visitor(&visitor);
// HEADERS frame with invalid Padding length
char kH2FrameData[] = {
0x00, 0x00, 0x05, // Length: 5
0x01, // Type: HEADERS
0x08, // Flags: PADDED
0x00, 0x00, 0x00, 0x01, // Stream: 1
0x04, // PadLen: 4 trailing bytes
0x00, 0x00, 0x00, 0x00, // Padding
};
SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false);
EXPECT_CALL(visitor, OnHeaders(1, false, 0, 0, false, false, false));
EXPECT_CALL(visitor, OnHeaderFrameStart(1)).Times(1);
EXPECT_EQ(frame.size(), framer.ProcessInput(frame.data(), frame.size()));
EXPECT_FALSE(framer.HasError());
EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code())
<< SpdyFramer::ErrorCodeToString(framer.error_code());
}
// 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 (!IsSpdy3()) {
return;
}
testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
SpdyFramer framer(spdy_version_);
framer.set_visitor(&visitor);
SpdySynReplyIR syn_reply(0);
syn_reply.SetHeader("alpha", "beta");
SpdySerializedFrame frame(framer.SerializeSynReply(syn_reply));
// 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 DATA with stream ID zero, we signal an error
// (but don't crash).
TEST_P(SpdyFramerTest, DataWithStreamIdZero) {
if (!IsHttp2()) {
return;
}
testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
SpdyFramer framer(spdy_version_);
framer.set_visitor(&visitor);
const char bytes[] = "hello";
SpdyDataIR data_ir(0, bytes);
SpdySerializedFrame frame(framer.SerializeData(data_ir));
// 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_STREAM_ID, 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");
SpdySerializedFrame frame(framer.SerializeHeaders(headers_ir));
// 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());
if (IsHttp2()) {
EXPECT_EQ(SpdyFramer::SPDY_INVALID_STREAM_ID, framer.error_code())
<< SpdyFramer::ErrorCodeToString(framer.error_code());
} else {
EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME, framer.error_code())
<< SpdyFramer::ErrorCodeToString(framer.error_code());
}
}
// Test that if we receive a PRIORITY with stream ID zero, we signal an error
// (but don't crash).
TEST_P(SpdyFramerTest, PriorityWithStreamIdZero) {
if (!IsHttp2()) {
return;
}
testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
SpdyFramer framer(spdy_version_);
framer.set_visitor(&visitor);
SpdyPriorityIR priority_ir(0, 1, 16, true);
SpdySerializedFrame frame(framer.SerializeFrame(priority_ir));
// 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_STREAM_ID, framer.error_code())
<< SpdyFramer::ErrorCodeToString(framer.error_code());
}
// Test that if we receive a RST_STREAM with stream ID zero, we signal an error
// (but don't crash).
TEST_P(SpdyFramerTest, RstStreamWithStreamIdZero) {
if (!IsHttp2()) {
return;
}
testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
SpdyFramer framer(spdy_version_);
framer.set_visitor(&visitor);
SpdyRstStreamIR rst_stream_ir(0, RST_STREAM_PROTOCOL_ERROR);
SpdySerializedFrame frame(framer.SerializeRstStream(rst_stream_ir));
// 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_STREAM_ID, framer.error_code())
<< SpdyFramer::ErrorCodeToString(framer.error_code());
}
// Test that if we receive a SETTINGS with stream ID other than zero,
// we signal an error (but don't crash).
TEST_P(SpdyFramerTest, SettingsWithStreamIdNotZero) {
if (!IsHttp2()) {
return;
}
testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
SpdyFramer framer(spdy_version_);
framer.set_visitor(&visitor);
// Settings frame with invalid StreamID of 0x01
char kH2FrameData[] = {
0x00, 0x00, 0x06, // Length: 6
0x04, // Type: SETTINGS
0x00, // Flags: none
0x00, 0x00, 0x00, 0x01, // Stream: 1
0x00, 0x04, // Param: INITIAL_WINDOW_SIZE
0x0a, 0x0b, 0x0c, 0x0d, // Value: 168496141
};
SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false);
// 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_STREAM_ID, framer.error_code())
<< SpdyFramer::ErrorCodeToString(framer.error_code());
}
// Test that if we receive a GOAWAY with stream ID other than zero,
// we signal an error (but don't crash).
TEST_P(SpdyFramerTest, GoawayWithStreamIdNotZero) {
if (!IsHttp2()) {
return;
}
testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
SpdyFramer framer(spdy_version_);
framer.set_visitor(&visitor);
// GOAWAY frame with invalid StreamID of 0x01
char kH2FrameData[] = {
0x00, 0x00, 0x0a, // Length: 10
0x07, // Type: GOAWAY
0x00, // Flags: none
0x00, 0x00, 0x00, 0x01, // Stream: 1
0x00, 0x00, 0x00, 0x00, // Last: 0
0x00, 0x00, 0x00, 0x00, // Error: NO_ERROR
0x47, 0x41, // Description
};
SpdySerializedFrame frame(kH2FrameData, sizeof(kH2FrameData), false);
// 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_STREAM_ID, framer.error_code())
<< SpdyFramer::ErrorCodeToString(framer.error_code());
}
// Test that if we receive a CONTINUATION with stream ID zero, we signal an
// SPDY_INVALID_STREAM_ID.
TEST_P(SpdyFramerTest, ContinuationWithStreamIdZero) {
if (!IsHttp2()) {
return;
}
testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
SpdyFramer framer(spdy_version_);
framer.set_visitor(&visitor);
SpdyContinuationIR continuation(0);
continuation.SetHeader("bar", "foo");
continuation.SetHeader("foo", "bar");
continuation.set_end_headers(true);
SpdySerializedFrame frame(framer.SerializeContinuation(continuation));
// 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_STREAM_ID, framer.error_code())
<< SpdyFramer::ErrorCodeToString(framer.error_code());
}
// Test that if we receive a PUSH_PROMISE with stream ID zero, we signal an
// SPDY_INVALID_STREAM_ID.
TEST_P(SpdyFramerTest, PushPromiseWithStreamIdZero) {
if (!IsHttp2()) {
return;
}
testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
SpdyFramer framer(spdy_version_);
framer.set_visitor(&visitor);
SpdyPushPromiseIR push_promise(0, 4);
push_promise.SetHeader("alpha", "beta");
SpdySerializedFrame frame(framer.SerializePushPromise(push_promise));
// 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_STREAM_ID, framer.error_code())
<< SpdyFramer::ErrorCodeToString(framer.error_code());
}
// Test that if we receive a PUSH_PROMISE with promised stream ID zero, we
// signal SPDY_INVALID_STREAM_ID.
TEST_P(SpdyFramerTest, PushPromiseWithPromisedStreamIdZero) {
if (!IsHttp2()) {
return;
}
testing::StrictMock<test::MockSpdyFramerVisitor> visitor;
SpdyFramer framer(spdy_version_);
framer.set_visitor(&visitor);
SpdyPushPromiseIR push_promise(3, 0);
push_promise.SetHeader("alpha", "beta");
SpdySerializedFrame frame(framer.SerializePushPromise(push_promise));
// 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 (!IsSpdy3()) {
// 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());
}
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);
SpdySerializedFrame control_frame(frame.take());
StringPiece serialized_headers = GetSerializedHeaders(control_frame, 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 (IsSpdy3()) {
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 (IsSpdy3()) {
frame.WriteUInt32(1); // Number of headers.
frame.WriteStringPiece32("name");
frame.WriteStringPiece32(value);
} else {
// TODO(jgraettinger): If this pattern appears again, move to test class.
SpdyHeaderBlock header_set;
header_set["name"] = value;
string buffer;
HpackEncoder encoder(ObtainHpackHuffmanTable());
encoder.EncodeHeaderSetWithoutCompression(header_set, &buffer);
frame.WriteBytes(&buffer[0], buffer.size());
}
// write the length
frame.RewriteLength(framer);
framer.set_enable_compression(false);
SpdySerializedFrame 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", StringPiece(value))));
}
TEST_P(SpdyFramerTest, BasicCompression) {
if (!IsSpdy3()) {
// Deflate compression doesn't apply to HPACK.
return;
}
std::unique_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");
SpdySerializedFrame frame1(framer.SerializeSynStream(syn_stream));
size_t uncompressed_size1 = visitor->last_payload_len_;
size_t compressed_size1 =
visitor->last_frame_len_ - framer.GetSynStreamMinimumSize();
EXPECT_EQ(165u, uncompressed_size1);
#if defined(USE_SYSTEM_ZLIB)
EXPECT_EQ(181u, compressed_size1);
#else // !defined(USE_SYSTEM_ZLIB)
EXPECT_EQ(116u, compressed_size1);
#endif // !defined(USE_SYSTEM_ZLIB)
SpdySerializedFrame 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
SpdySerializedFrame frame3(
SpdyFramerTestUtil::DecompressFrame(&framer, frame1));
// Decompress the second frame
visitor.reset(new TestSpdyVisitor(spdy_version_));
framer.set_debug_visitor(visitor.get());
SpdySerializedFrame frame4(
SpdyFramerTestUtil::DecompressFrame(&framer, frame2));
size_t uncompressed_size4 = frame4.size() - framer.GetSynStreamMinimumSize();
size_t compressed_size4 =
visitor->last_frame_len_ - framer.GetSynStreamMinimumSize();
EXPECT_EQ(165u, uncompressed_size4);
#if defined(USE_SYSTEM_ZLIB)
EXPECT_EQ(175u, compressed_size4);
#else // !defined(USE_SYSTEM_ZLIB)
EXPECT_EQ(98u, 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);
SpdySerializedFrame 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);
SpdySerializedFrame frame1(framer.SerializeHeaders(headers));
}
TEST_P(SpdyFramerTest, Basic) {
// clang-format off
const unsigned char kV3Input[] = {
0x80, 0x03, 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, 0x03, 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, 0x03, 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, 0x03, 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, 0x03, 0x00, 0x03, // RST_STREAM on Stream #3
0x00, 0x00, 0x00, 0x08,
0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x05, // RST_STREAM_CANCEL
};
// clang-format on
// SYN_STREAM doesn't exist in HTTP/2, so instead we send
// HEADERS frames with PRIORITY and END_HEADERS set.
// frame-format off
const unsigned char kH2Input[] = {
0x00, 0x00, 0x05, // Length: 5
0x01, // Type: HEADERS
0x24, // Flags: END_HEADERS|PRIORITY
0x00, 0x00, 0x00, 0x01, // Stream: 1
0x00, 0x00, 0x00, 0x00, // Parent: 0
0x82, // Weight: 131
0x00, 0x00, 0x01, // Length: 1
0x01, // Type: HEADERS
0x04, // Flags: END_HEADERS
0x00, 0x00, 0x00, 0x01, // Stream: 1
0x8c, // :status: 200
0x00, 0x00, 0x0c, // Length: 12
0x00, // Type: DATA
0x00, // Flags: none
0x00, 0x00, 0x00, 0x01, // Stream: 1
0xde, 0xad, 0xbe, 0xef, // Payload
0xde, 0xad, 0xbe, 0xef, //
0xde, 0xad, 0xbe, 0xef, //
0x00, 0x00, 0x05, // Length: 5
0x01, // Type: HEADERS
0x24, // Flags: END_HEADERS|PRIORITY
0x00, 0x00, 0x00, 0x03, // Stream: 3
0x00, 0x00, 0x00, 0x00, // Parent: 0
0x82, // Weight: 131
0x00, 0x00, 0x08, // Length: 8
0x00, // Type: DATA
0x00, // Flags: none
0x00, 0x00, 0x00, 0x03, // Stream: 3
0xde, 0xad, 0xbe, 0xef, // Payload
0xde, 0xad, 0xbe, 0xef, //
0x00, 0x00, 0x04, // Length: 4
0x00, // Type: DATA
0x00, // Flags: none
0x00, 0x00, 0x00, 0x01, // Stream: 1
0xde, 0xad, 0xbe, 0xef, // Payload
0x00, 0x00, 0x04, // Length: 4
0x03, // Type: RST_STREAM
0x00, // Flags: none
0x00, 0x00, 0x00, 0x01, // Stream: 1
0x00, 0x00, 0x00, 0x08, // Error: CANCEL
0x00, 0x00, 0x00, // Length: 0
0x00, // Type: DATA
0x00, // Flags: none
0x00, 0x00, 0x00, 0x03, // Stream: 3
0x00, 0x00, 0x04, // Length: 4
0x03, // Type: RST_STREAM
0x00, // Flags: none
0x00, 0x00, 0x00, 0x03, // Stream: 3
0x00, 0x00, 0x00, 0x08, // Error: CANCEL
};
// frame-format on
TestSpdyVisitor visitor(spdy_version_);
if (IsSpdy3()) {
visitor.SimulateInFramer(kV3Input, sizeof(kV3Input));
} else {
visitor.SimulateInFramer(kH2Input, sizeof(kH2Input));
}
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 (IsSpdy3()) {
EXPECT_EQ(1, visitor.headers_frame_count_);
EXPECT_EQ(2, visitor.syn_frame_count_);
EXPECT_TRUE(visitor.fin_opaque_data_.empty());
} else {
EXPECT_EQ(3, visitor.headers_frame_count_);
EXPECT_EQ(0, visitor.syn_frame_count_);
EXPECT_TRUE(visitor.fin_opaque_data_.empty());
}
EXPECT_EQ(0, visitor.fin_flag_count_);
EXPECT_EQ(0, visitor.end_of_stream_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) {
// clang-format off
const unsigned char kV3Input[] = {
0x80, 0x03, 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, 0x03, 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,
};
// clang-format on
// SYN_STREAM and SYN_REPLY don't exist in HTTP2, so instead we send
// HEADERS frames with PRIORITY(SYN_STREAM only) and END_HEADERS set.
// frame-format off
const unsigned char kH2Input[] = {
0x00, 0x00, 0x05, // Length: 5
0x01, // Type: HEADERS
0x24, // Flags: END_HEADERS|PRIORITY
0x00, 0x00, 0x00, 0x01, // Stream: 1
0x00, 0x00, 0x00, 0x00, // Parent: 0
0x82, // Weight: 131
0x00, 0x00, 0x01, // Length: 1
0x01, // Type: HEADERS
0x04, // Flags: END_HEADERS
0x00, 0x00, 0x00, 0x01, // Stream: 1
0x8c, // :status: 200
0x00, 0x00, 0x0c, // Length: 12
0x00, // Type: DATA
0x00, // Flags: none
0x00, 0x00, 0x00, 0x01, // Stream: 1
0xde, 0xad, 0xbe, 0xef, // Payload
0xde, 0xad, 0xbe, 0xef, //
0xde, 0xad, 0xbe, 0xef, //
0x00, 0x00, 0x04, // Length: 4
0x00, // Type: DATA
0x01, // Flags: END_STREAM
0x00, 0x00, 0x00, 0x01, // Stream: 1
0xde, 0xad, 0xbe, 0xef, // Payload
};
// frame-format on
TestSpdyVisitor visitor(spdy_version_);
if (IsSpdy3()) {
visitor.SimulateInFramer(kV3Input, sizeof(kV3Input));
} else {
visitor.SimulateInFramer(kH2Input, sizeof(kH2Input));
}
EXPECT_EQ(0, visitor.error_count_);
if (IsSpdy3()) {
EXPECT_EQ(1, visitor.syn_frame_count_);
EXPECT_EQ(1, visitor.syn_reply_frame_count_);
EXPECT_EQ(0, visitor.headers_frame_count_);
} else {
EXPECT_EQ(0, visitor.syn_frame_count_);
EXPECT_EQ(0, visitor.syn_reply_frame_count_);
EXPECT_EQ(2, 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.end_of_stream_count_);
EXPECT_EQ(2, visitor.data_frame_count_);
}
// Test that the FIN flag on a SYN reply frame signifies EOF.
TEST_P(SpdyFramerTest, FinOnSynReplyFrame) {
// clang-format off
const unsigned char kV3Input[] = {
0x80, 0x03, 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, 0x03, 0x00, 0x02, // SYN REPLY Stream #1
0x01, 0x00, 0x00, 0x14,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x02,
'a', 'a', 0x00, 0x00,
0x00, 0x02, 'b', 'b',
};
// clang-format on
// SYN_STREAM and SYN_REPLY don't exist in HTTP2, so instead we send
// HEADERS frames with PRIORITY(SYN_STREAM only) and END_HEADERS set.
// frame-format off
const unsigned char kH2Input[] = {
0x00, 0x00, 0x05, // Length: 5
0x01, // Type: HEADERS
0x24, // Flags: END_HEADERS|PRIORITY
0x00, 0x00, 0x00, 0x01, // Stream: 1
0x00, 0x00, 0x00, 0x00, // Parent: 0
0x82, // Weight: 131
0x00, 0x00, 0x01, // Length: 1
0x01, // Type: HEADERS
0x05, // Flags: END_STREAM|END_HEADERS
0x00, 0x00, 0x00, 0x01, // Stream: 1
0x8c, // :status: 200
};
// frame-format on
TestSpdyVisitor visitor(spdy_version_);
if (IsSpdy3()) {
visitor.SimulateInFramer(kV3Input, sizeof(kV3Input));
} else {
visitor.SimulateInFramer(kH2Input, sizeof(kH2Input));
}
EXPECT_EQ(0, visitor.error_count_);
if (IsSpdy3()) {
EXPECT_EQ(1, visitor.syn_frame_count_);
EXPECT_EQ(1, visitor.syn_reply_frame_count_);
EXPECT_EQ(0, visitor.headers_frame_count_);
} else {
EXPECT_EQ(0, visitor.syn_frame_count_);
EXPECT_EQ(0, visitor.syn_reply_frame_count_);
EXPECT_EQ(2, 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.end_of_stream_count_);
EXPECT_EQ(0, visitor.data_frame_count_);
}
TEST_P(SpdyFramerTest, HeaderCompression) {
if (!IsSpdy3()) {
// 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, block.Clone());
SpdySerializedFrame syn_frame_1(send_framer.SerializeFrame(syn_ir_1));
// SYN_STREAM #2
block[kHeader3] = kValue3;
SpdySynStreamIR syn_stream(3, std::move(block));
SpdySerializedFrame syn_frame_2(send_framer.SerializeSynStream(syn_stream));
// Decompress SYN_STREAM #1
SpdySerializedFrame decompressed(
SpdyFramerTestUtil::DecompressFrame(&recv_framer, syn_frame_1));
StringPiece serialized_headers =
GetSerializedHeaders(decompressed, send_framer);
SpdyHeaderBlock decompressed_headers;
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 = SpdyFramerTestUtil::DecompressFrame(&recv_framer, syn_frame_2);
serialized_headers = GetSerializedHeaders(decompressed, 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);
SpdySerializedFrame headers_frame(send_framer.SerializeHeaders(headers));
const char bytes[] = "this is a test test test test test!";
SpdyDataIR data_ir(1, StringPiece(bytes, arraysize(bytes)));
data_ir.set_fin(true);
SpdySerializedFrame send_frame(send_framer.SerializeData(data_ir));
// 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.end_of_stream_count_);
EXPECT_EQ(1, visitor.data_frame_count_);
}
TEST_P(SpdyFramerTest, WindowUpdateFrame) {
SpdyFramer framer(spdy_version_);
SpdySerializedFrame frame(
framer.SerializeWindowUpdate(SpdyWindowUpdateIR(1, 0x12345678)));
const char kDescription[] = "WINDOW_UPDATE frame, stream 1, delta 0x12345678";
// clang-format off
const unsigned char kV3FrameData[] = {
0x80, 0x03, 0x00, 0x09,
0x00, 0x00, 0x00, 0x08,
0x00, 0x00, 0x00, 0x01,
0x12, 0x34, 0x56, 0x78
};
// clang-format on
const unsigned char kH2FrameData[] = {
0x00, 0x00, 0x04, // Length: 4
0x08, // Type: WINDOW_UPDATE
0x00, // Flags: none
0x00, 0x00, 0x00, 0x01, // Stream: 1
0x12, 0x34, 0x56, 0x78, // Increment: 305419896
};
if (IsSpdy3()) {
CompareFrame(kDescription, frame, kV3FrameData, arraysize(kV3FrameData));
} else {
CompareFrame(kDescription, frame, kH2FrameData, arraysize(kH2FrameData));
}
}
TEST_P(SpdyFramerTest, CreateDataFrame) {
SpdyFramer framer(spdy_version_);
{
const char kDescription[] = "'hello' data frame, no FIN";
// clang-format off
const unsigned char kV3FrameData[] = {
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x05,
'h', 'e', 'l', 'l',
'o'
};
// clang-format on
// frame-format off
const unsigned char kH2FrameData[] = {
0x00, 0x00, 0x05, // Length: 5
0x00, // Type: DATA
0x00, // Flags: none
0x00, 0x00, 0x00, 0x01, // Stream: 1
'h', 'e', 'l', 'l', // Payload
'o', //
};
// frame-format on
const char bytes[] = "hello";
SpdyDataIR data_ir(1, bytes);
SpdySerializedFrame frame(framer.SerializeData(data_ir));
if (IsSpdy3()) {
CompareFrame(kDescription, frame, kV3FrameData, arraysize(kV3FrameData));
} else {
CompareFrame(kDescription, frame, kH2FrameData, arraysize(kH2FrameData));
}
SpdyDataIR data_header_ir(1);
data_header_ir.SetDataShallow(bytes);
frame =
framer.SerializeDataFrameHeaderWithPaddingLengthField(data_header_ir);
CompareCharArraysWithHexError(
kDescription, reinterpret_cast<const unsigned char*>(frame.data()),
framer.GetDataFrameMinimumSize(),
IsSpdy3() ? kV3FrameData : kH2FrameData,
framer.GetDataFrameMinimumSize());
}
{
const char kDescription[] = "'hello' data frame with more padding, no FIN";
// clang-format off
const unsigned char kV3FrameData[] = {
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x05,
'h', 'e', 'l', 'l',
'o'
};
// frame-format off
const unsigned char kH2FrameData[] = {
0x00, 0x00, 0xfd, // Length: 253
0x00, // Type: DATA
0x08, // Flags: PADDED
0x00, 0x00, 0x00, 0x01, // Stream: 1
0xf7, // PadLen: 247 trailing bytes
'h', 'e', 'l', 'l', // Payload
'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,
};
// frame-format on
// clang-format on
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);
SpdySerializedFrame frame(framer.SerializeData(data_ir));
if (IsSpdy3()) {
CompareFrame(kDescription, frame, kV3FrameData, arraysize(kV3FrameData));
} else {
CompareFrame(kDescription, frame, kH2FrameData, arraysize(kH2FrameData));
}
frame = framer.SerializeDataFrameHeaderWithPaddingLengthField(data_ir);
CompareCharArraysWithHexError(
kDescription, reinterpret_cast<const unsigned char*>(frame.data()),
framer.GetDataFrameMinimumSize(),
IsSpdy3() ? kV3FrameData : kH2FrameData,
framer.GetDataFrameMinimumSize());
}
{
const char kDescription[] = "'hello' data frame with few padding, no FIN";
// clang-format off
const unsigned char kV3FrameData[] = {
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x05,
'h', 'e', 'l', 'l',
'o'
};
// clang-format on
// frame-format off
const unsigned char kH2FrameData[] = {
0x00, 0x00, 0x0d, // Length: 13
0x00, // Type: DATA
0x08, // Flags: PADDED
0x00, 0x00, 0x00, 0x01, // Stream: 1
0x07, // PadLen: 7 trailing bytes
'h', 'e', 'l', 'l', // Payload
'o', //
0x00, 0x00, 0x00, 0x00, // Padding
0x00, 0x00, 0x00, // Padding
};
// frame-format on
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);
SpdySerializedFrame frame(framer.SerializeData(data_ir));
if (IsSpdy3()) {
CompareFrame(kDescription, frame, kV3FrameData, arraysize(kV3FrameData));
} else {
CompareFrame(kDescription, frame, kH2FrameData, arraysize(kH2FrameData));
}
}
{
const char kDescription[] =
"'hello' data frame with 1 byte padding, no FIN";
// clang-format off
const unsigned char kV3FrameData[] = {
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x05,
'h', 'e', 'l', 'l',
'o'
};
// clang-format on
// frame-format off
const unsigned char kH2FrameData[] = {
0x00, 0x00, 0x06, // Length: 6
0x00, // Type: DATA
0x08, // Flags: PADDED
0x00, 0x00, 0x00, 0x01, // Stream: 1
0x00, // PadLen: 0 trailing bytes
'h', 'e', 'l', 'l', // Payload
'o', //
};
// frame-format on
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);
SpdySerializedFrame frame(framer.SerializeData(data_ir));
if (IsSpdy3()) {
CompareFrame(kDescription, frame, kV3FrameData, arraysize(kV3FrameData));
} else {
CompareFrame(kDescription, frame, kH2FrameData, arraysize(kH2FrameData));
}
frame = framer.SerializeDataFrameHeaderWithPaddingLengthField(data_ir);
CompareCharArraysWithHexError(
kDescription, reinterpret_cast<const unsigned char*>(frame.data()),
framer.GetDataFrameMinimumSize(),
IsSpdy3() ? kV3FrameData : kH2FrameData,
framer.GetDataFrameMinimumSize());
}
{
const char kDescription[] = "Data frame with negative data byte, no FIN";
// clang-format off
const unsigned char kV3FrameData[] = {
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x01,
0xff
};
// clang-format on
const unsigned char kH2FrameData[] = {
0x00, 0x00, 0x01, // Length: 1
0x00, // Type: DATA
0x00, // Flags: none
0x00, 0x00, 0x00, 0x01, // Stream: 1
0xff, // Payload
};
SpdyDataIR data_ir(1, "\xff");
SpdySerializedFrame frame(framer.SerializeData(data_ir));
if (IsSpdy3()) {
CompareFrame(kDescription, frame, kV3FrameData, arraysize(kV3FrameData));
} else {
CompareFrame(kDescription, frame, kH2FrameData, arraysize(kH2FrameData));
}
}
{
const char kDescription[] = "'hello' data frame, with FIN";
// clang-format off
const unsigned char kV3FrameData[] = {
0x00, 0x00, 0x00, 0x01,
0x01, 0x00, 0x00, 0x05,
'h', 'e', 'l', 'l',
'o'
};
// clang-format on
const unsigned char kH2FrameData[] = {
0x00, 0x00, 0x05, // Length: 5
0x00, // Type: DATA
0x01, // Flags: END_STREAM
0x00, 0x00, 0x00, 0x01, // Stream: 1
0x68, 0x65, 0x6c, 0x6c, // Payload
0x6f, //
};
SpdyDataIR data_ir(1, "hello");
data_ir.set_fin(true);
SpdySerializedFrame frame(framer.SerializeData(data_ir));
if (IsSpdy3()) {
CompareFrame(kDescription, frame, kV3FrameData, arraysize(kV3FrameData));
} else {
CompareFrame(kDescription, frame, kH2FrameData, arraysize(kH2FrameData));
}
}
{
const char kDescription[] = "Empty data frame";
// clang-format off
const unsigned char kV3FrameData[] = {
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
};
// clang-format on
const unsigned char kH2FrameData[] = {
0x00, 0x00, 0x00, // Length: 0
0x00, // Type: DATA
0x00, // Flags: none
0x00, 0x00, 0x00, 0x01, // Stream: 1
};
SpdyDataIR data_ir(1, "");
SpdySerializedFrame frame(framer.SerializeData(data_ir));
if (IsSpdy3()) {
CompareFrame(kDescription, frame, kV3FrameData, arraysize(kV3FrameData));
} else {
CompareFrame(kDescription, frame, kH2FrameData, arraysize(kH2FrameData));
}
frame = framer.SerializeDataFrameHeaderWithPaddingLengthField(data_ir);
CompareCharArraysWithHexError(
kDescription, reinterpret_cast<const unsigned char*>(frame.data()),
framer.GetDataFrameMinimumSize(),
IsSpdy3() ? kV3FrameData : kH2FrameData,
framer.GetDataFrameMinimumSize());
}
{
const char kDescription[] = "Data frame with max stream ID";
// clang-format off
const unsigned char kV3FrameData[] = {
0x7f, 0xff, 0xff, 0xff,
0x01, 0x00, 0x00, 0x05,
'h', 'e', 'l', 'l',
'o'
};
// clang-format on
const unsigned char kH2FrameData[] = {
0x00, 0x00, 0x05, // Length: 5
0x00, // Type: DATA
0x01, // Flags: END_STREAM
0x7f, 0xff, 0xff, 0xff, // Stream: 0x7fffffff
0x68, 0x65, 0x6c, 0x6c, // Payload
0x6f, //
};
SpdyDataIR data_ir(0x7fffffff, "hello");
data_ir.set_fin(true);
SpdySerializedFrame frame(framer.SerializeData(data_ir));
if (IsSpdy3()) {
CompareFrame(kDescription, frame, kV3FrameData, arraysize(kV3FrameData));
} else {
CompareFrame(kDescription, frame, kH2FrameData, arraysize(kH2FrameData));
}
}
if (!IsHttp2()) {
// This test does not apply to HTTP/2 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');
// clang-format off
const unsigned char kFrameHeader[] = {
0x00, 0x00, 0x00, 0x01,
0x01, 0x40, 0x00, 0x00,
};
// clang-format on
const int kFrameSize = arraysize(kFrameHeader) + kDataSize;
std::unique_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);
SpdySerializedFrame frame(framer.SerializeData(data_ir));
CompareFrame(kDescription, frame, expected_frame_data.get(), kFrameSize);
}
}
TEST_P(SpdyFramerTest, CreateSynStreamUncompressed) {
if (!IsSpdy3()) {
return;
}
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(false);
{
const char kDescription[] = "SYN_STREAM frame, lowest pri, no FIN";
// clang-format off
const unsigned char kV3FrameData[] = {
0x80, 0x03, 0x00, 0x01,
0x00, 0x00, 0x00, 0x2a,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00,
0xE0, 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'
};
// clang-format on
SpdySynStreamIR syn_stream(1);
syn_stream.set_priority(framer.GetLowestPriority());
syn_stream.SetHeader("bar", "foo");
syn_stream.SetHeader("foo", "bar");
SpdySerializedFrame frame(framer.SerializeSynStream(syn_stream));
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";
// clang-format off
const unsigned char kV3FrameData[] = {
0x80, 0x03, 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'
};
// clang-format on
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");
SpdySerializedFrame frame(framer.SerializeSynStream(syn_stream));
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";
// clang-format off
const unsigned char kV3FrameData[] = {
0x80, 0x03, 0x00, 0x01,
0x01, 0x00, 0x00, 0x27,
0x7f, 0xff, 0xff, 0xff,
0x7f, 0xff, 0xff, 0xff,
0x20, 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
};
// clang-format on
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", "");
SpdySerializedFrame frame(framer.SerializeSynStream(syn_stream));
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 (!IsSpdy3()) {
return;
}
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(true);
{
const char kDescription[] = "SYN_STREAM frame, low pri, no FIN";
// clang-format off
const unsigned char kV3FrameData[] = {
0x80, 0x03, 0x00, 0x01,
0x00, 0x00, 0x00, 0x36,
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,
0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF,
};
const unsigned char kV3SIMDFrameData[] = {
0x80, 0x03, 0x00, 0x01,
0x00, 0x00, 0x00, 0x31,
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,
0x00, 0x00, 0x00, 0xff,
0xff,
};
// clang-format on
SpdySynStreamIR syn_stream(1);
syn_stream.set_priority(4);
syn_stream.SetHeader("bar", "foo");
syn_stream.SetHeader("foo", "bar");
SpdySerializedFrame frame(framer.SerializeSynStream(syn_stream));
const unsigned char* frame_data =
reinterpret_cast<const unsigned char*>(frame.data());
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 (!IsSpdy3()) {
return;
}
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(false);
{
const char kDescription[] = "SYN_REPLY frame, no FIN";
// clang-format off
const unsigned char kV3FrameData[] = {
0x80, 0x03, 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'
};
// clang-format on
SpdySynReplyIR syn_reply(1);
syn_reply.SetHeader("bar", "foo");
syn_reply.SetHeader("foo", "bar");
SpdySerializedFrame frame(framer.SerializeSynReply(syn_reply));
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";
// clang-format off
const unsigned char kV3FrameData[] = {
0x80, 0x03, 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'
};
// clang-format on
SpdySynReplyIR syn_reply(0x7fffffff);
syn_reply.set_fin(true);
syn_reply.SetHeader("", "foo");
syn_reply.SetHeader("foo", "bar");
SpdySerializedFrame frame(framer.SerializeSynReply(syn_reply));
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";
// clang-format off
const unsigned char kV3FrameData[] = {
0x80, 0x03, 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
};
// clang-format on
SpdySynReplyIR syn_reply(0x7fffffff);
syn_reply.set_fin(true);
syn_reply.SetHeader("bar", "foo");
syn_reply.SetHeader("foo", "");
SpdySerializedFrame frame(framer.SerializeSynReply(syn_reply));
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 (!IsSpdy3()) {
return;
}
SpdyFramer framer(spdy_version_);
framer.set_enable_compression(true);
{
const char kDescription[] = "SYN_REPLY frame, no FIN";
// clang-format off
const unsigned char kV3FrameData[] = {
0x80, 0x03, 0x00, 0x02,
0x00, 0x00, 0x00, 0x30,
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, 0x00, 0x00,
0x00, 0x00, 0xff, 0xff,
};
const unsigned char kV3SIMDFrameData[] = {
0x80, 0x03, 0x00, 0x02,
0x00, 0x00, 0x00, 0x2b,
0x00, 0x00, 0x00, 0x01,
0x38, 0xea, 0xe3, 0xc6,
0xa7, 0xc2, 0x02, 0xe5,
0x0e, 0x50, 0xc2