| // 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); |
| } |
| |
| 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; |
| std::cerr << "OnStreamFrameData(" << stream_id << ", \""; |
| if (len > 0) { |
| for (size_t i = 0 ; i < len; ++i) { |
| std::cerr << std::hex << (0xFF & static_cast<unsigned int>(data[i])) |
| << std::dec; |
| } |
| } |
| std::cerr << "\", " << 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, |
| uint32 max_age, |
| uint16 port, |
| StringPiece protocol_id, |
| StringPiece host, |
| StringPiece origin) override { |
| test_altsvc_ir_.set_stream_id(stream_id); |
| test_altsvc_ir_.set_max_age(max_age); |
| test_altsvc_ir_.set_port(port); |
| test_altsvc_ir_.set_protocol_id(protocol_id.as_string()); |
| test_altsvc_ir_.set_host(host.as_string()); |
| if (origin.length() > 0) { |
| test_altsvc_ir_.set_origin(origin.as_string()); |
| } |
| ++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_; |
| }; |
| |
| // 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()); |
| } |
| } |
| |
| } // namespace test |
| |
| } // namespace net |
| |
| using net::test::SetFrameLength; |
| using net::test::SetFrameFlags; |
| using net::test::CompareCharArraysWithHexError; |
| using net::test::SpdyFramerTestUtil; |
| using net::test::TestSpdyVisitor; |
| using net::test::GetSerializedHeaders; |
| |
| namespace net { |
| |
| 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.WriteString("name"); |
| frame.WriteString("value1"); |
| frame.WriteString("name"); |
| frame.WriteString("value2"); |
| } else { |
| frame.WriteUInt32(2); // Number of headers. |
| frame.WriteStringPiece32("name"); |
| frame.WriteStringPiece32("value1"); |
| frame.WriteStringPiece32("name"); |
| frame.WriteStringPiece32("value2"); |
| } |
| // write the length |
| frame.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.WriteString("name"); |
| frame.WriteString(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, StringPiece(bytes, strlen(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(base::StringPiece(bytes, strlen(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, StringPiece(bytes, strlen(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, StringPiece(bytes, strlen(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, StringPiece(bytes, strlen(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, StringPiece("\xff", 1)); |
| 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, StringPiece("hello", 5)); |
| 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, StringPiece()); |
| 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, StringPiece(kData.data(), kData.size())); |
| 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, 0x62, |
| 0x60, 0x06, 0x08, 0xa0, |
| 0xb4, 0xfc, 0x7c, 0x80, |
| 0x00, 0x62, 0x60, 0x06, |
| 0x13, 0x00, 0x01, 0x94, |
| |