| // Copyright (c) 2011 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 "base/memory/scoped_ptr.h" |
| #include "net/spdy/spdy_framer.h" |
| #include "net/spdy/spdy_protocol.h" |
| #include "net/spdy/spdy_frame_builder.h" |
| #include "testing/platform_test.h" |
| |
| namespace spdy { |
| |
| namespace test { |
| |
| std::string HexDumpWithMarks(const unsigned char* data, int length, |
| const bool* marks, int mark_length) { |
| static const char kHexChars[] = "0123456789ABCDEF"; |
| static const int kColumns = 4; |
| |
| std::string hex; |
| for (const unsigned char* row = data; length > 0; |
| row += kColumns, length -= kColumns) { |
| for (const unsigned char *p = row; p < row + 4; ++p) { |
| if (p < row + length) { |
| const bool mark = |
| (marks && (p - data) < mark_length && marks[p - data]); |
| hex += mark ? '*' : ' '; |
| hex += kHexChars[(*p & 0xf0) >> 4]; |
| hex += kHexChars[*p & 0x0f]; |
| hex += mark ? '*' : ' '; |
| } else { |
| hex += " "; |
| } |
| } |
| hex = hex + " "; |
| |
| for (const unsigned char *p = row; p < row + 4 && p < row + length; ++p) |
| hex += (*p >= 0x20 && *p <= 0x7f) ? (*p) : '.'; |
| |
| hex = hex + '\n'; |
| } |
| return hex; |
| } |
| |
| void CompareCharArraysWithHexError( |
| const std::string& description, |
| const unsigned char* actual, |
| const int actual_len, |
| const unsigned char* expected, |
| const int expected_len) { |
| const int min_len = actual_len > expected_len ? expected_len : actual_len; |
| const int max_len = actual_len > expected_len ? actual_len : expected_len; |
| scoped_array<bool> marks(new bool[max_len]); |
| bool identical = (actual_len == expected_len); |
| for (int i = 0; i < min_len; ++i) { |
| if (actual[i] != expected[i]) { |
| marks[i] = true; |
| identical = false; |
| } else { |
| marks[i] = false; |
| } |
| } |
| for (int i = min_len; i < max_len; ++i) { |
| marks[i] = true; |
| } |
| if (identical) return; |
| ADD_FAILURE() |
| << "Description:\n" |
| << description |
| << "\n\nExpected:\n" |
| << HexDumpWithMarks(expected, expected_len, marks.get(), max_len) |
| << "\nActual:\n" |
| << HexDumpWithMarks(actual, actual_len, marks.get(), max_len); |
| } |
| |
| void FramerSetEnableCompressionHelper(SpdyFramer* framer, bool compress) { |
| framer->set_enable_compression(compress); |
| } |
| |
| class TestSpdyVisitor : public SpdyFramerVisitorInterface { |
| public: |
| TestSpdyVisitor() |
| : error_count_(0), |
| syn_frame_count_(0), |
| syn_reply_frame_count_(0), |
| headers_frame_count_(0), |
| data_bytes_(0), |
| fin_frame_count_(0), |
| fin_flag_count_(0), |
| zero_length_data_frame_count_(0) { |
| } |
| |
| void OnError(SpdyFramer* f) { |
| error_count_++; |
| } |
| |
| void OnStreamFrameData(SpdyStreamId stream_id, |
| const char* data, |
| size_t len) { |
| if (len == 0) |
| ++zero_length_data_frame_count_; |
| |
| data_bytes_ += len; |
| std::cerr << "OnStreamFrameData(" << stream_id << ", \""; |
| if (len > 0) { |
| for (size_t i = 0 ; i < len; ++i) { |
| std::cerr << std::hex << (0xFF & (unsigned int)data[i]) << std::dec; |
| } |
| } |
| std::cerr << "\", " << len << ")\n"; |
| } |
| |
| void OnControl(const SpdyControlFrame* frame) { |
| SpdyHeaderBlock headers; |
| bool parsed_headers = false; |
| switch (frame->type()) { |
| case SYN_STREAM: |
| parsed_headers = framer_.ParseHeaderBlock(frame, &headers); |
| DCHECK(parsed_headers); |
| syn_frame_count_++; |
| break; |
| case SYN_REPLY: |
| parsed_headers = framer_.ParseHeaderBlock(frame, &headers); |
| DCHECK(parsed_headers); |
| syn_reply_frame_count_++; |
| break; |
| case RST_STREAM: |
| fin_frame_count_++; |
| break; |
| case HEADERS: |
| parsed_headers = framer_.ParseHeaderBlock(frame, &headers); |
| DCHECK(parsed_headers); |
| headers_frame_count_++; |
| break; |
| default: |
| DCHECK(false); // Error! |
| } |
| if (frame->flags() & CONTROL_FLAG_FIN) |
| ++fin_flag_count_; |
| } |
| |
| bool OnControlFrameHeaderData(SpdyStreamId stream_id, |
| const char* header_data, |
| size_t len) { |
| DCHECK(false); |
| return false; |
| } |
| |
| void OnDataFrameHeader(const SpdyDataFrame* frame) { |
| DCHECK(false); |
| } |
| |
| // Convenience function which runs a framer simulation with particular input. |
| void SimulateInFramer(const unsigned char* input, size_t size) { |
| framer_.set_enable_compression(false); |
| 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; |
| if (framer_.state() == SpdyFramer::SPDY_DONE) |
| framer_.Reset(); |
| } |
| } |
| |
| SpdyFramer framer_; |
| // Counters from the visitor callbacks. |
| int error_count_; |
| int syn_frame_count_; |
| int syn_reply_frame_count_; |
| int headers_frame_count_; |
| int data_bytes_; |
| int fin_frame_count_; // The count of RST_STREAM type frames received. |
| int fin_flag_count_; // The count of frames with the FIN flag set. |
| int zero_length_data_frame_count_; // The count of zero-length data frames. |
| }; |
| |
| } // namespace test |
| |
| } // namespace spdy |
| |
| using spdy::SpdyControlFlags; |
| using spdy::SpdyControlFrame; |
| using spdy::SpdyDataFrame; |
| using spdy::SpdyFrame; |
| using spdy::SpdyFrameBuilder; |
| using spdy::SpdyFramer; |
| using spdy::SpdyHeaderBlock; |
| using spdy::SpdySynStreamControlFrame; |
| using spdy::kControlFlagMask; |
| using spdy::CONTROL_FLAG_NONE; |
| using spdy::DATA_FLAG_COMPRESSED; |
| using spdy::DATA_FLAG_FIN; |
| using spdy::SYN_STREAM; |
| using spdy::test::CompareCharArraysWithHexError; |
| using spdy::test::FramerSetEnableCompressionHelper; |
| using spdy::test::TestSpdyVisitor; |
| |
| namespace spdy { |
| |
| class SpdyFramerTest : public PlatformTest { |
| public: |
| virtual void TearDown() {} |
| |
| protected: |
| void CompareFrame(const std::string& description, |
| const SpdyFrame& actual_frame, |
| const unsigned char* expected, |
| const int expected_len) { |
| const unsigned char* actual = |
| reinterpret_cast<const unsigned char*>(actual_frame.data()); |
| int actual_len = actual_frame.length() + SpdyFrame::size(); |
| CompareCharArraysWithHexError( |
| description, actual, actual_len, expected, expected_len); |
| } |
| }; |
| |
| // Test that we can encode and decode a SpdyHeaderBlock. |
| TEST_F(SpdyFramerTest, HeaderBlock) { |
| SpdyHeaderBlock headers; |
| headers["alpha"] = "beta"; |
| headers["gamma"] = "charlie"; |
| SpdyFramer framer; |
| |
| // Encode the header block into a SynStream frame. |
| scoped_ptr<SpdySynStreamControlFrame> frame( |
| framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, true, &headers)); |
| EXPECT_TRUE(frame.get() != NULL); |
| |
| SpdyHeaderBlock new_headers; |
| EXPECT_TRUE(framer.ParseHeaderBlock(frame.get(), &new_headers)); |
| |
| EXPECT_EQ(headers.size(), new_headers.size()); |
| EXPECT_EQ(headers["alpha"], new_headers["alpha"]); |
| EXPECT_EQ(headers["gamma"], new_headers["gamma"]); |
| } |
| |
| TEST_F(SpdyFramerTest, OutOfOrderHeaders) { |
| SpdyFrameBuilder frame; |
| |
| frame.WriteUInt16(kControlFlagMask | 1); |
| frame.WriteUInt16(SYN_STREAM); |
| frame.WriteUInt32(0); // Placeholder for the length. |
| frame.WriteUInt32(3); // stream_id |
| frame.WriteUInt32(0); // associated stream id |
| frame.WriteUInt16(0); // Priority. |
| |
| frame.WriteUInt16(2); // Number of headers. |
| SpdyHeaderBlock::iterator it; |
| frame.WriteString("gamma"); |
| frame.WriteString("gamma"); |
| frame.WriteString("alpha"); |
| frame.WriteString("alpha"); |
| // write the length |
| frame.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::size()); |
| |
| SpdyHeaderBlock new_headers; |
| scoped_ptr<SpdyFrame> control_frame(frame.take()); |
| SpdyFramer framer; |
| FramerSetEnableCompressionHelper(&framer, false); |
| EXPECT_TRUE(framer.ParseHeaderBlock(control_frame.get(), &new_headers)); |
| } |
| |
| TEST_F(SpdyFramerTest, WrongNumberOfHeaders) { |
| SpdyFrameBuilder frame1; |
| SpdyFrameBuilder frame2; |
| |
| // a frame with smaller number of actual headers |
| frame1.WriteUInt16(kControlFlagMask | 1); |
| frame1.WriteUInt16(SYN_STREAM); |
| frame1.WriteUInt32(0); // Placeholder for the length. |
| frame1.WriteUInt32(3); // stream_id |
| frame1.WriteUInt16(0); // Priority. |
| |
| frame1.WriteUInt16(1); // Wrong number of headers (underflow) |
| frame1.WriteString("gamma"); |
| frame1.WriteString("gamma"); |
| frame1.WriteString("alpha"); |
| frame1.WriteString("alpha"); |
| // write the length |
| frame1.WriteUInt32ToOffset(4, frame1.length() - SpdyFrame::size()); |
| |
| // a frame with larger number of actual headers |
| frame2.WriteUInt16(kControlFlagMask | 1); |
| frame2.WriteUInt16(SYN_STREAM); |
| frame2.WriteUInt32(0); // Placeholder for the length. |
| frame2.WriteUInt32(3); // stream_id |
| frame2.WriteUInt16(0); // Priority. |
| |
| frame2.WriteUInt16(100); // Wrong number of headers (overflow) |
| frame2.WriteString("gamma"); |
| frame2.WriteString("gamma"); |
| frame2.WriteString("alpha"); |
| frame2.WriteString("alpha"); |
| // write the length |
| frame2.WriteUInt32ToOffset(4, frame2.length() - SpdyFrame::size()); |
| |
| SpdyHeaderBlock new_headers; |
| scoped_ptr<SpdyFrame> syn_frame1(frame1.take()); |
| scoped_ptr<SpdyFrame> syn_frame2(frame2.take()); |
| SpdyFramer framer; |
| FramerSetEnableCompressionHelper(&framer, false); |
| EXPECT_FALSE(framer.ParseHeaderBlock(syn_frame1.get(), &new_headers)); |
| EXPECT_FALSE(framer.ParseHeaderBlock(syn_frame2.get(), &new_headers)); |
| } |
| |
| TEST_F(SpdyFramerTest, DuplicateHeader) { |
| SpdyFrameBuilder frame; |
| |
| frame.WriteUInt16(kControlFlagMask | 1); |
| frame.WriteUInt16(SYN_STREAM); |
| frame.WriteUInt32(0); // Placeholder for the length. |
| frame.WriteUInt32(3); // stream_id |
| frame.WriteUInt32(0); // associated stream id |
| frame.WriteUInt16(0); // Priority. |
| |
| frame.WriteUInt16(2); // Number of headers. |
| SpdyHeaderBlock::iterator it; |
| frame.WriteString("name"); |
| frame.WriteString("value1"); |
| frame.WriteString("name"); |
| frame.WriteString("value2"); |
| // write the length |
| frame.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::size()); |
| |
| SpdyHeaderBlock new_headers; |
| scoped_ptr<SpdyFrame> control_frame(frame.take()); |
| SpdyFramer framer; |
| FramerSetEnableCompressionHelper(&framer, false); |
| // This should fail because duplicate headers are verboten by the spec. |
| EXPECT_FALSE(framer.ParseHeaderBlock(control_frame.get(), &new_headers)); |
| } |
| |
| TEST_F(SpdyFramerTest, MultiValueHeader) { |
| SpdyFrameBuilder frame; |
| |
| frame.WriteUInt16(kControlFlagMask | 1); |
| frame.WriteUInt16(SYN_STREAM); |
| frame.WriteUInt32(0); // Placeholder for the length. |
| frame.WriteUInt32(3); // stream_id |
| frame.WriteUInt32(0); // associated stream id |
| frame.WriteUInt16(0); // Priority. |
| |
| frame.WriteUInt16(1); // Number of headers. |
| SpdyHeaderBlock::iterator it; |
| frame.WriteString("name"); |
| std::string value("value1\0value2"); |
| frame.WriteString(value); |
| // write the length |
| frame.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::size()); |
| |
| SpdyHeaderBlock new_headers; |
| scoped_ptr<SpdyFrame> control_frame(frame.take()); |
| SpdyFramer framer; |
| FramerSetEnableCompressionHelper(&framer, false); |
| EXPECT_TRUE(framer.ParseHeaderBlock(control_frame.get(), &new_headers)); |
| EXPECT_TRUE(new_headers.find("name") != new_headers.end()); |
| EXPECT_EQ(value, new_headers.find("name")->second); |
| } |
| |
| TEST_F(SpdyFramerTest, ZeroLengthHeader) { |
| SpdyHeaderBlock header1; |
| SpdyHeaderBlock header2; |
| SpdyHeaderBlock header3; |
| |
| header1[""] = "value2"; |
| header2["name3"] = ""; |
| header3[""] = ""; |
| |
| SpdyFramer framer; |
| SpdyHeaderBlock parsed_headers; |
| |
| scoped_ptr<SpdySynStreamControlFrame> frame1( |
| framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, true, &header1)); |
| EXPECT_TRUE(frame1.get() != NULL); |
| EXPECT_FALSE(framer.ParseHeaderBlock(frame1.get(), &parsed_headers)); |
| |
| scoped_ptr<SpdySynStreamControlFrame> frame2( |
| framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, true, &header2)); |
| EXPECT_TRUE(frame2.get() != NULL); |
| EXPECT_FALSE(framer.ParseHeaderBlock(frame2.get(), &parsed_headers)); |
| |
| scoped_ptr<SpdySynStreamControlFrame> frame3( |
| framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, true, &header3)); |
| EXPECT_TRUE(frame3.get() != NULL); |
| EXPECT_FALSE(framer.ParseHeaderBlock(frame3.get(), &parsed_headers)); |
| } |
| |
| TEST_F(SpdyFramerTest, BasicCompression) { |
| SpdyHeaderBlock headers; |
| headers["server"] = "SpdyServer 1.0"; |
| headers["date"] = "Mon 12 Jan 2009 12:12:12 PST"; |
| headers["status"] = "200"; |
| headers["version"] = "HTTP/1.1"; |
| headers["content-type"] = "text/html"; |
| headers["content-length"] = "12"; |
| |
| SpdyFramer framer; |
| FramerSetEnableCompressionHelper(&framer, true); |
| scoped_ptr<SpdySynStreamControlFrame> |
| frame1(framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, true, |
| &headers)); |
| scoped_ptr<SpdySynStreamControlFrame> |
| frame2(framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, true, |
| &headers)); |
| |
| // Expect the second frame to be more compact than the first. |
| EXPECT_LE(frame2->length(), frame1->length()); |
| |
| // Decompress the first frame |
| scoped_ptr<SpdyFrame> frame3(framer.DecompressFrame(*frame1.get())); |
| |
| // Decompress the second frame |
| scoped_ptr<SpdyFrame> frame4(framer.DecompressFrame(*frame2.get())); |
| |
| // Expect frames 3 & 4 to be the same. |
| EXPECT_EQ(0, |
| memcmp(frame3->data(), frame4->data(), |
| SpdyFrame::size() + frame3->length())); |
| } |
| |
| TEST_F(SpdyFramerTest, DecompressUncompressedFrame) { |
| SpdyHeaderBlock headers; |
| headers["server"] = "SpdyServer 1.0"; |
| headers["date"] = "Mon 12 Jan 2009 12:12:12 PST"; |
| headers["status"] = "200"; |
| headers["version"] = "HTTP/1.1"; |
| headers["content-type"] = "text/html"; |
| headers["content-length"] = "12"; |
| |
| SpdyFramer framer; |
| FramerSetEnableCompressionHelper(&framer, true); |
| scoped_ptr<SpdySynStreamControlFrame> |
| frame1(framer.CreateSynStream(1, 0, 1, CONTROL_FLAG_NONE, false, |
| &headers)); |
| |
| // Decompress the frame |
| scoped_ptr<SpdyFrame> frame2(framer.DecompressFrame(*frame1.get())); |
| |
| EXPECT_EQ(NULL, frame2.get()); |
| } |
| |
| TEST_F(SpdyFramerTest, Basic) { |
| const unsigned char input[] = { |
| 0x80, 0x02, 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, 0x02, 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, 0x02, 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, 0x02, 0x00, 0x03, // RST_STREAM on Stream #1 |
| 0x00, 0x00, 0x00, 0x08, |
| 0x00, 0x00, 0x00, 0x01, |
| 0x00, 0x00, 0x00, 0x00, |
| |
| 0x00, 0x00, 0x00, 0x03, // DATA on Stream #3 |
| 0x00, 0x00, 0x00, 0x00, |
| |
| 0x80, 0x02, 0x00, 0x03, // RST_STREAM on Stream #3 |
| 0x00, 0x00, 0x00, 0x08, |
| 0x00, 0x00, 0x00, 0x03, |
| 0x00, 0x00, 0x00, 0x00, |
| }; |
| |
| TestSpdyVisitor visitor; |
| visitor.SimulateInFramer(input, sizeof(input)); |
| |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(2, visitor.syn_frame_count_); |
| EXPECT_EQ(0, visitor.syn_reply_frame_count_); |
| EXPECT_EQ(1, visitor.headers_frame_count_); |
| EXPECT_EQ(24, visitor.data_bytes_); |
| EXPECT_EQ(2, visitor.fin_frame_count_); |
| EXPECT_EQ(0, visitor.fin_flag_count_); |
| EXPECT_EQ(0, visitor.zero_length_data_frame_count_); |
| } |
| |
| // Test that the FIN flag on a data frame signifies EOF. |
| TEST_F(SpdyFramerTest, FinOnDataFrame) { |
| const unsigned char input[] = { |
| 0x80, 0x02, 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, 0x02, 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, |
| }; |
| |
| TestSpdyVisitor visitor; |
| visitor.SimulateInFramer(input, sizeof(input)); |
| |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.syn_frame_count_); |
| EXPECT_EQ(1, visitor.syn_reply_frame_count_); |
| EXPECT_EQ(0, visitor.headers_frame_count_); |
| EXPECT_EQ(16, visitor.data_bytes_); |
| EXPECT_EQ(0, visitor.fin_frame_count_); |
| EXPECT_EQ(0, visitor.fin_flag_count_); |
| EXPECT_EQ(1, visitor.zero_length_data_frame_count_); |
| } |
| |
| // Test that the FIN flag on a SYN reply frame signifies EOF. |
| TEST_F(SpdyFramerTest, FinOnSynReplyFrame) { |
| const unsigned char input[] = { |
| 0x80, 0x02, 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, 0x02, 0x00, 0x02, // SYN REPLY Stream #1 |
| 0x01, 0x00, 0x00, 0x10, |
| 0x00, 0x00, 0x00, 0x01, |
| 0x00, 0x00, 0x00, 0x01, |
| 0x00, 0x02, 'a', 'a', |
| 0x00, 0x02, 'b', 'b', |
| }; |
| |
| TestSpdyVisitor visitor; |
| visitor.SimulateInFramer(input, sizeof(input)); |
| |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.syn_frame_count_); |
| EXPECT_EQ(1, visitor.syn_reply_frame_count_); |
| EXPECT_EQ(0, visitor.headers_frame_count_); |
| EXPECT_EQ(0, visitor.data_bytes_); |
| EXPECT_EQ(0, visitor.fin_frame_count_); |
| EXPECT_EQ(1, visitor.fin_flag_count_); |
| EXPECT_EQ(1, visitor.zero_length_data_frame_count_); |
| } |
| |
| // Basic compression & decompression |
| TEST_F(SpdyFramerTest, DataCompression) { |
| SpdyFramer send_framer; |
| SpdyFramer recv_framer; |
| |
| FramerSetEnableCompressionHelper(&send_framer, true); |
| FramerSetEnableCompressionHelper(&recv_framer, true); |
| |
| // Mix up some SYNs and DATA frames since they use different compressors. |
| const char kHeader1[] = "header1"; |
| const char kHeader2[] = "header2"; |
| const char kHeader3[] = "header3"; |
| const char kValue1[] = "value1"; |
| const char kValue2[] = "value2"; |
| const char kValue3[] = "value3"; |
| |
| // SYN_STREAM #1 |
| SpdyHeaderBlock block; |
| block[kHeader1] = kValue1; |
| block[kHeader2] = kValue2; |
| SpdyControlFlags flags(CONTROL_FLAG_NONE); |
| scoped_ptr<spdy::SpdyFrame> syn_frame_1( |
| send_framer.CreateSynStream(1, 0, 0, flags, true, &block)); |
| EXPECT_TRUE(syn_frame_1.get() != NULL); |
| |
| // DATA #1 |
| const char bytes[] = "this is a test test test test test!"; |
| scoped_ptr<SpdyFrame> data_frame_1( |
| send_framer.CreateDataFrame(1, bytes, arraysize(bytes), |
| DATA_FLAG_COMPRESSED)); |
| EXPECT_TRUE(data_frame_1.get() != NULL); |
| |
| // SYN_STREAM #2 |
| block[kHeader3] = kValue3; |
| scoped_ptr<SpdyFrame> syn_frame_2( |
| send_framer.CreateSynStream(3, 0, 0, flags, true, &block)); |
| EXPECT_TRUE(syn_frame_2.get() != NULL); |
| |
| // DATA #2 |
| scoped_ptr<SpdyFrame> data_frame_2( |
| send_framer.CreateDataFrame(3, bytes, arraysize(bytes), |
| DATA_FLAG_COMPRESSED)); |
| EXPECT_TRUE(data_frame_2.get() != NULL); |
| |
| // Now start decompressing |
| scoped_ptr<SpdyFrame> decompressed; |
| SpdyControlFrame* control_frame; |
| SpdyDataFrame* data_frame; |
| SpdyHeaderBlock decompressed_headers; |
| |
| decompressed.reset(recv_framer.DuplicateFrame(*syn_frame_1.get())); |
| EXPECT_TRUE(decompressed.get() != NULL); |
| EXPECT_TRUE(decompressed->is_control_frame()); |
| control_frame = reinterpret_cast<SpdyControlFrame*>(decompressed.get()); |
| EXPECT_EQ(SYN_STREAM, control_frame->type()); |
| EXPECT_TRUE(recv_framer.ParseHeaderBlock( |
| control_frame, &decompressed_headers)); |
| EXPECT_EQ(2u, decompressed_headers.size()); |
| EXPECT_EQ(SYN_STREAM, control_frame->type()); |
| EXPECT_EQ(kValue1, decompressed_headers[kHeader1]); |
| EXPECT_EQ(kValue2, decompressed_headers[kHeader2]); |
| |
| decompressed.reset(recv_framer.DecompressFrame(*data_frame_1.get())); |
| EXPECT_TRUE(decompressed.get() != NULL); |
| EXPECT_FALSE(decompressed->is_control_frame()); |
| data_frame = reinterpret_cast<SpdyDataFrame*>(decompressed.get()); |
| EXPECT_EQ(arraysize(bytes), data_frame->length()); |
| EXPECT_EQ(0, memcmp(data_frame->payload(), bytes, data_frame->length())); |
| |
| decompressed.reset(recv_framer.DuplicateFrame(*syn_frame_2.get())); |
| EXPECT_TRUE(decompressed.get() != NULL); |
| EXPECT_TRUE(decompressed->is_control_frame()); |
| control_frame = reinterpret_cast<SpdyControlFrame*>(decompressed.get()); |
| EXPECT_EQ(control_frame->type(), SYN_STREAM); |
| decompressed_headers.clear(); |
| EXPECT_TRUE(recv_framer.ParseHeaderBlock( |
| control_frame, &decompressed_headers)); |
| EXPECT_EQ(3u, decompressed_headers.size()); |
| EXPECT_EQ(SYN_STREAM, control_frame->type()); |
| EXPECT_EQ(kValue1, decompressed_headers[kHeader1]); |
| EXPECT_EQ(kValue2, decompressed_headers[kHeader2]); |
| EXPECT_EQ(kValue3, decompressed_headers[kHeader3]); |
| |
| decompressed.reset(recv_framer.DecompressFrame(*data_frame_2.get())); |
| EXPECT_TRUE(decompressed.get() != NULL); |
| EXPECT_FALSE(decompressed->is_control_frame()); |
| data_frame = reinterpret_cast<SpdyDataFrame*>(decompressed.get()); |
| EXPECT_EQ(arraysize(bytes), data_frame->length()); |
| EXPECT_EQ(0, memcmp(data_frame->payload(), bytes, data_frame->length())); |
| |
| // We didn't close these streams, so the compressors should be active. |
| EXPECT_EQ(2, send_framer.num_stream_compressors()); |
| EXPECT_EQ(0, send_framer.num_stream_decompressors()); |
| EXPECT_EQ(0, recv_framer.num_stream_compressors()); |
| EXPECT_EQ(2, recv_framer.num_stream_decompressors()); |
| } |
| |
| // Verify we don't leak when we leave streams unclosed |
| TEST_F(SpdyFramerTest, UnclosedStreamDataCompressors) { |
| SpdyFramer send_framer; |
| |
| FramerSetEnableCompressionHelper(&send_framer, false); |
| |
| const char kHeader1[] = "header1"; |
| const char kHeader2[] = "header2"; |
| const char kValue1[] = "value1"; |
| const char kValue2[] = "value2"; |
| |
| SpdyHeaderBlock block; |
| block[kHeader1] = kValue1; |
| block[kHeader2] = kValue2; |
| SpdyControlFlags flags(CONTROL_FLAG_NONE); |
| scoped_ptr<spdy::SpdyFrame> syn_frame( |
| send_framer.CreateSynStream(1, 0, 0, flags, true, &block)); |
| EXPECT_TRUE(syn_frame.get() != NULL); |
| |
| const char bytes[] = "this is a test test test test test!"; |
| scoped_ptr<SpdyFrame> send_frame( |
| send_framer.CreateDataFrame(1, |
| bytes, |
| arraysize(bytes), |
| DATA_FLAG_FIN)); |
| EXPECT_TRUE(send_frame.get() != NULL); |
| |
| // Run the inputs through the framer. |
| TestSpdyVisitor visitor; |
| const unsigned char* data; |
| data = reinterpret_cast<const unsigned char*>(syn_frame->data()); |
| visitor.SimulateInFramer(data, syn_frame->length() + SpdyFrame::size()); |
| data = reinterpret_cast<const unsigned char*>(send_frame->data()); |
| visitor.SimulateInFramer(data, send_frame->length() + SpdyFrame::size()); |
| |
| EXPECT_EQ(0, visitor.error_count_); |
| EXPECT_EQ(1, visitor.syn_frame_count_); |
| EXPECT_EQ(0, visitor.syn_reply_frame_count_); |
| EXPECT_EQ(0, visitor.headers_frame_count_); |
| EXPECT_EQ(arraysize(bytes), static_cast<unsigned>(visitor.data_bytes_)); |
| EXPECT_EQ(0, visitor.fin_frame_count_); |
| EXPECT_EQ(0, visitor.fin_flag_count_); |
| EXPECT_EQ(1, visitor.zero_length_data_frame_count_); |
| |
| // We closed the streams, so all compressors should be down. |
| EXPECT_EQ(0, visitor.framer_.num_stream_compressors()); |
| EXPECT_EQ(0, visitor.framer_.num_stream_decompressors()); |
| EXPECT_EQ(0, send_framer.num_stream_compressors()); |
| EXPECT_EQ(0, send_framer.num_stream_decompressors()); |
| } |
| |
| TEST_F(SpdyFramerTest, CreateDataFrame) { |
| SpdyFramer framer; |
| |
| { |
| const char kDescription[] = "'hello' data frame, no FIN"; |
| const unsigned char kFrameData[] = { |
| 0x00, 0x00, 0x00, 0x01, |
| 0x00, 0x00, 0x00, 0x05, |
| 'h', 'e', 'l', 'l', |
| 'o' |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame( |
| 1, "hello", 5, DATA_FLAG_NONE)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| |
| { |
| const char kDescription[] = "Data frame with negative data byte, no FIN"; |
| const unsigned char kFrameData[] = { |
| 0x00, 0x00, 0x00, 0x01, |
| 0x00, 0x00, 0x00, 0x01, |
| 0xff |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame( |
| 1, "\xff", 1, DATA_FLAG_NONE)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| |
| { |
| const char kDescription[] = "'hello' data frame, with FIN"; |
| const unsigned char kFrameData[] = { |
| 0x00, 0x00, 0x00, 0x01, |
| 0x01, 0x00, 0x00, 0x05, |
| 'h', 'e', 'l', 'l', |
| 'o' |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame( |
| 1, "hello", 5, DATA_FLAG_FIN)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| |
| { |
| const char kDescription[] = "Empty data frame"; |
| const unsigned char kFrameData[] = { |
| 0x00, 0x00, 0x00, 0x01, |
| 0x00, 0x00, 0x00, 0x00, |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame( |
| 1, "", 0, DATA_FLAG_NONE)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| |
| { |
| const char kDescription[] = "Data frame with max stream ID"; |
| const unsigned char kFrameData[] = { |
| 0x7f, 0xff, 0xff, 0xff, |
| 0x01, 0x00, 0x00, 0x05, |
| 'h', 'e', 'l', 'l', |
| 'o' |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame( |
| 0x7fffffff, "hello", 5, DATA_FLAG_FIN)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| } |
| |
| TEST_F(SpdyFramerTest, CreateSynStreamUncompressed) { |
| SpdyFramer framer; |
| FramerSetEnableCompressionHelper(&framer, false); |
| |
| { |
| const char kDescription[] = "SYN_STREAM frame, lowest pri, no FIN"; |
| |
| SpdyHeaderBlock headers; |
| headers["bar"] = "foo"; |
| headers["foo"] = "bar"; |
| |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 0x00, 0x01, |
| 0x00, 0x00, 0x00, 0x20, |
| 0x00, 0x00, 0x00, 0x01, |
| 0x00, 0x00, 0x00, 0x00, |
| 0xC0, 0x00, 0x00, 0x02, |
| 0x00, 0x03, 'b', 'a', |
| 'r', 0x00, 0x03, 'f', |
| 'o', 'o', 0x00, 0x03, |
| 'f', 'o', 'o', 0x00, |
| 0x03, 'b', 'a', 'r' |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateSynStream( |
| 1, 0, SPDY_PRIORITY_LOWEST, CONTROL_FLAG_NONE, |
| false, &headers)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| |
| { |
| const char kDescription[] = |
| "SYN_STREAM frame with a 0-length header name, highest pri, FIN, " |
| "max stream ID"; |
| |
| SpdyHeaderBlock headers; |
| headers[""] = "foo"; |
| headers["foo"] = "bar"; |
| |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 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' |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateSynStream( |
| 0x7fffffff, 0x7fffffff, SPDY_PRIORITY_HIGHEST, CONTROL_FLAG_FIN, |
| false, &headers)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| |
| { |
| const char kDescription[] = |
| "SYN_STREAM frame with a 0-length header val, highest pri, FIN, " |
| "max stream ID"; |
| |
| SpdyHeaderBlock headers; |
| headers["bar"] = "foo"; |
| headers["foo"] = ""; |
| |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 0x00, 0x01, |
| 0x01, 0x00, 0x00, 0x1D, |
| 0x7f, 0xff, 0xff, 0xff, |
| 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 |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateSynStream( |
| 0x7fffffff, 0x7fffffff, SPDY_PRIORITY_HIGHEST, CONTROL_FLAG_FIN, |
| false, &headers)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| } |
| |
| TEST_F(SpdyFramerTest, CreateSynStreamCompressed) { |
| SpdyFramer framer; |
| FramerSetEnableCompressionHelper(&framer, true); |
| |
| { |
| const char kDescription[] = |
| "SYN_STREAM frame, lowest pri, no FIN"; |
| |
| SpdyHeaderBlock headers; |
| headers["bar"] = "foo"; |
| headers["foo"] = "bar"; |
| |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 0x00, 0x01, |
| 0x00, 0x00, 0x00, 0x25, |
| 0x00, 0x00, 0x00, 0x01, |
| 0x00, 0x00, 0x00, 0x00, |
| 0xC0, 0x00, 0x38, 0xea, |
| 0xdf, 0xa2, 0x51, 0xb2, |
| 0x62, 0x60, 0x62, 0x60, |
| 0x4e, 0x4a, 0x2c, 0x62, |
| 0x60, 0x4e, 0xcb, 0xcf, |
| 0x87, 0x12, 0x40, 0x2e, |
| 0x00, 0x00, 0x00, 0xff, |
| 0xff |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateSynStream( |
| 1, 0, SPDY_PRIORITY_LOWEST, CONTROL_FLAG_NONE, |
| true, &headers)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| } |
| |
| TEST_F(SpdyFramerTest, CreateSynReplyUncompressed) { |
| SpdyFramer framer; |
| FramerSetEnableCompressionHelper(&framer, false); |
| |
| { |
| const char kDescription[] = "SYN_REPLY frame, no FIN"; |
| |
| SpdyHeaderBlock headers; |
| headers["bar"] = "foo"; |
| headers["foo"] = "bar"; |
| |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 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' |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateSynReply( |
| 1, CONTROL_FLAG_NONE, false, &headers)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| |
| { |
| const char kDescription[] = |
| "SYN_REPLY frame with a 0-length header name, FIN, max stream ID"; |
| |
| SpdyHeaderBlock headers; |
| headers[""] = "foo"; |
| headers["foo"] = "bar"; |
| |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 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' |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateSynReply( |
| 0x7fffffff, CONTROL_FLAG_FIN, false, &headers)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| |
| { |
| const char kDescription[] = |
| "SYN_REPLY frame with a 0-length header val, FIN, max stream ID"; |
| |
| SpdyHeaderBlock headers; |
| headers["bar"] = "foo"; |
| headers["foo"] = ""; |
| |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 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 |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateSynReply( |
| 0x7fffffff, CONTROL_FLAG_FIN, false, &headers)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| } |
| |
| TEST_F(SpdyFramerTest, CreateSynReplyCompressed) { |
| SpdyFramer framer; |
| FramerSetEnableCompressionHelper(&framer, true); |
| |
| { |
| const char kDescription[] = "SYN_REPLY frame, no FIN"; |
| |
| SpdyHeaderBlock headers; |
| headers["bar"] = "foo"; |
| headers["foo"] = "bar"; |
| |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 0x00, 0x02, |
| 0x00, 0x00, 0x00, 0x21, |
| 0x00, 0x00, 0x00, 0x01, |
| 0x00, 0x00, 0x38, 0xea, |
| 0xdf, 0xa2, 0x51, 0xb2, |
| 0x62, 0x60, 0x62, 0x60, |
| 0x4e, 0x4a, 0x2c, 0x62, |
| 0x60, 0x4e, 0xcb, 0xcf, |
| 0x87, 0x12, 0x40, 0x2e, |
| 0x00, 0x00, 0x00, 0xff, |
| 0xff |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateSynReply( |
| 1, CONTROL_FLAG_NONE, true, &headers)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| } |
| |
| TEST_F(SpdyFramerTest, CreateRstStream) { |
| SpdyFramer framer; |
| |
| { |
| const char kDescription[] = "RST_STREAM frame"; |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 0x00, 0x03, |
| 0x00, 0x00, 0x00, 0x08, |
| 0x00, 0x00, 0x00, 0x01, |
| 0x00, 0x00, 0x00, 0x01, |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateRstStream(1, PROTOCOL_ERROR)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| |
| { |
| const char kDescription[] = "RST_STREAM frame with max stream ID"; |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 0x00, 0x03, |
| 0x00, 0x00, 0x00, 0x08, |
| 0x7f, 0xff, 0xff, 0xff, |
| 0x00, 0x00, 0x00, 0x01, |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateRstStream(0x7FFFFFFF, |
| PROTOCOL_ERROR)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| |
| { |
| const char kDescription[] = "RST_STREAM frame with max status code"; |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 0x00, 0x03, |
| 0x00, 0x00, 0x00, 0x08, |
| 0x7f, 0xff, 0xff, 0xff, |
| 0x00, 0x00, 0x00, 0x06, |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateRstStream(0x7FFFFFFF, |
| INTERNAL_ERROR)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| } |
| |
| TEST_F(SpdyFramerTest, CreateSettings) { |
| SpdyFramer framer; |
| |
| { |
| const char kDescription[] = "Basic SETTINGS frame"; |
| |
| SpdySettings settings; |
| settings.push_back(SpdySetting(0x00000000, 0x00000000)); |
| settings.push_back(SpdySetting(0xffffffff, 0x00000001)); |
| settings.push_back(SpdySetting(0xff000001, 0x00000002)); |
| |
| // Duplicates allowed |
| settings.push_back(SpdySetting(0x01000002, 0x00000003)); |
| settings.push_back(SpdySetting(0x01000002, 0x00000003)); |
| |
| settings.push_back(SpdySetting(0x01000003, 0x000000ff)); |
| settings.push_back(SpdySetting(0x01000004, 0xff000001)); |
| settings.push_back(SpdySetting(0x01000004, 0xffffffff)); |
| |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 0x00, 0x04, |
| 0x00, 0x00, 0x00, 0x44, |
| 0x00, 0x00, 0x00, 0x08, |
| 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, |
| 0xff, 0xff, 0xff, 0xff, |
| 0x00, 0x00, 0x00, 0x01, |
| 0xff, 0x00, 0x00, 0x01, |
| 0x00, 0x00, 0x00, 0x02, |
| 0x01, 0x00, 0x00, 0x02, |
| 0x00, 0x00, 0x00, 0x03, |
| 0x01, 0x00, 0x00, 0x02, |
| 0x00, 0x00, 0x00, 0x03, |
| 0x01, 0x00, 0x00, 0x03, |
| 0x00, 0x00, 0x00, 0xff, |
| 0x01, 0x00, 0x00, 0x04, |
| 0xff, 0x00, 0x00, 0x01, |
| 0x01, 0x00, 0x00, 0x04, |
| 0xff, 0xff, 0xff, 0xff, |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateSettings(settings)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| |
| { |
| const char kDescription[] = "Empty SETTINGS frame"; |
| |
| SpdySettings settings; |
| |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 0x00, 0x04, |
| 0x00, 0x00, 0x00, 0x04, |
| 0x00, 0x00, 0x00, 0x00, |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateSettings(settings)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| } |
| |
| TEST_F(SpdyFramerTest, CreateNopFrame) { |
| SpdyFramer framer; |
| |
| { |
| const char kDescription[] = "NOOP frame"; |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 0x00, 0x05, |
| 0x00, 0x00, 0x00, 0x00, |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateNopFrame()); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| } |
| |
| TEST_F(SpdyFramerTest, CreateGoAway) { |
| SpdyFramer framer; |
| |
| { |
| const char kDescription[] = "GOAWAY frame"; |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 0x00, 0x07, |
| 0x00, 0x00, 0x00, 0x04, |
| 0x00, 0x00, 0x00, 0x00, |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateGoAway(0)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| |
| { |
| const char kDescription[] = "GOAWAY frame with max stream ID"; |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 0x00, 0x07, |
| 0x00, 0x00, 0x00, 0x04, |
| 0x7f, 0xff, 0xff, 0xff, |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateGoAway(0x7FFFFFFF)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| } |
| |
| TEST_F(SpdyFramerTest, CreateHeadersUncompressed) { |
| SpdyFramer framer; |
| FramerSetEnableCompressionHelper(&framer, false); |
| |
| { |
| const char kDescription[] = "HEADERS frame, no FIN"; |
| |
| SpdyHeaderBlock headers; |
| headers["bar"] = "foo"; |
| headers["foo"] = "bar"; |
| |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 0x00, 0x08, |
| 0x00, 0x00, 0x00, 0x1C, |
| 0x00, 0x00, 0x00, 0x01, |
| 0x00, 0x00, 0x00, 0x02, |
| 0x00, 0x03, 'b', 'a', |
| 'r', 0x00, 0x03, 'f', |
| 'o', 'o', 0x00, 0x03, |
| 'f', 'o', 'o', 0x00, |
| 0x03, 'b', 'a', 'r' |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateHeaders( |
| 1, CONTROL_FLAG_NONE, false, &headers)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| |
| { |
| const char kDescription[] = |
| "HEADERS frame with a 0-length header name, FIN, max stream ID"; |
| |
| SpdyHeaderBlock headers; |
| headers[""] = "foo"; |
| headers["foo"] = "bar"; |
| |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 0x00, 0x08, |
| 0x01, 0x00, 0x00, 0x19, |
| 0x7f, 0xff, 0xff, 0xff, |
| 0x00, 0x00, 0x00, 0x02, |
| 0x00, 0x00, 0x00, 0x03, |
| 'f', 'o', 'o', 0x00, |
| 0x03, 'f', 'o', 'o', |
| 0x00, 0x03, 'b', 'a', |
| 'r' |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateHeaders( |
| 0x7fffffff, CONTROL_FLAG_FIN, false, &headers)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| |
| { |
| const char kDescription[] = |
| "HEADERS frame with a 0-length header val, FIN, max stream ID"; |
| |
| SpdyHeaderBlock headers; |
| headers["bar"] = "foo"; |
| headers["foo"] = ""; |
| |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 0x00, 0x08, |
| 0x01, 0x00, 0x00, 0x19, |
| 0x7f, 0xff, 0xff, 0xff, |
| 0x00, 0x00, 0x00, 0x02, |
| 0x00, 0x03, 'b', 'a', |
| 'r', 0x00, 0x03, 'f', |
| 'o', 'o', 0x00, 0x03, |
| 'f', 'o', 'o', 0x00, |
| 0x00 |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateHeaders( |
| 0x7fffffff, CONTROL_FLAG_FIN, false, &headers)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| } |
| |
| TEST_F(SpdyFramerTest, CreateHeadersCompressed) { |
| SpdyFramer framer; |
| FramerSetEnableCompressionHelper(&framer, true); |
| |
| { |
| const char kDescription[] = "HEADERS frame, no FIN"; |
| |
| SpdyHeaderBlock headers; |
| headers["bar"] = "foo"; |
| headers["foo"] = "bar"; |
| |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 0x00, 0x08, |
| 0x00, 0x00, 0x00, 0x21, |
| 0x00, 0x00, 0x00, 0x01, |
| 0x00, 0x00, 0x38, 0xea, |
| 0xdf, 0xa2, 0x51, 0xb2, |
| 0x62, 0x60, 0x62, 0x60, |
| 0x4e, 0x4a, 0x2c, 0x62, |
| 0x60, 0x4e, 0xcb, 0xcf, |
| 0x87, 0x12, 0x40, 0x2e, |
| 0x00, 0x00, 0x00, 0xff, |
| 0xff |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateHeaders( |
| 1, CONTROL_FLAG_NONE, true, &headers)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| } |
| |
| TEST_F(SpdyFramerTest, CreateWindowUpdate) { |
| SpdyFramer framer; |
| |
| { |
| const char kDescription[] = "WINDOW_UPDATE frame"; |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 0x00, 0x09, |
| 0x00, 0x00, 0x00, 0x08, |
| 0x00, 0x00, 0x00, 0x01, |
| 0x00, 0x00, 0x00, 0x01, |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(1, 1)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| |
| { |
| const char kDescription[] = "WINDOW_UPDATE frame with max stream ID"; |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 0x00, 0x09, |
| 0x00, 0x00, 0x00, 0x08, |
| 0x7f, 0xff, 0xff, 0xff, |
| 0x00, 0x00, 0x00, 0x01, |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(0x7FFFFFFF, 1)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| |
| { |
| const char kDescription[] = "WINDOW_UPDATE frame with max window delta"; |
| const unsigned char kFrameData[] = { |
| 0x80, 0x02, 0x00, 0x09, |
| 0x00, 0x00, 0x00, 0x08, |
| 0x00, 0x00, 0x00, 0x01, |
| 0x7f, 0xff, 0xff, 0xff, |
| }; |
| scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(1, 0x7FFFFFFF)); |
| CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); |
| } |
| } |
| |
| // This test case reproduces conditions that caused ExpandControlFrameBuffer to |
| // fail to expand the buffer control frame buffer when it should have, allowing |
| // the framer to overrun the buffer, and smash other heap contents. This test |
| // relies on the debug version of the heap manager, which checks for buffer |
| // overrun errors during delete processing. Regression test for b/2974814. |
| TEST_F(SpdyFramerTest, ExpandBuffer_HeapSmash) { |
| // Sweep through the area of problematic values, to make sure we always cover |
| // the danger zone, even if it moves around at bit due to SPDY changes. |
| for (uint16 val2_len = SpdyFramer::kControlFrameBufferInitialSize - 50; |
| val2_len < SpdyFramer::kControlFrameBufferInitialSize; |
| val2_len++) { |
| std::string val2 = std::string(val2_len, 'a'); |
| SpdyHeaderBlock headers; |
| headers["bar"] = "foo"; |
| headers["foo"] = "baz"; |
| headers["grue"] = val2.c_str(); |
| SpdyFramer framer; |
| scoped_ptr<SpdySynStreamControlFrame> template_frame( |
| framer.CreateSynStream(1, // stream_id |
| 0, // associated_stream_id |
| 1, // priority |
| CONTROL_FLAG_NONE, |
| false, // compress |
| &headers)); |
| EXPECT_TRUE(template_frame.get() != NULL); |
| TestSpdyVisitor visitor; |
| visitor.SimulateInFramer( |
| reinterpret_cast<unsigned char*>(template_frame.get()->data()), |
| template_frame.get()->length() + SpdyControlFrame::size()); |
| EXPECT_EQ(1, visitor.syn_frame_count_); |
| } |
| } |
| |
| std::string RandomString(int length) { |
| std::string rv; |
| for (int index = 0; index < length; index++) |
| rv += static_cast<char>('a' + (rand() % 26)); |
| return rv; |
| } |
| |
| // Stress that we can handle a really large header block compression and |
| // decompression. |
| TEST_F(SpdyFramerTest, HugeHeaderBlock) { |
| // Loop targetting various sizes which will potentially jam up the |
| // frame compressor/decompressor. |
| SpdyFramer compress_framer; |
| SpdyFramer decompress_framer; |
| for (size_t target_size = 1024; |
| target_size < SpdyFramer::kControlFrameBufferInitialSize; |
| target_size += 1024) { |
| SpdyHeaderBlock headers; |
| for (size_t index = 0; index < target_size; ++index) { |
| std::string name = RandomString(4); |
| std::string value = RandomString(8); |
| headers[name] = value; |
| } |
| |
| // Encode the header block into a SynStream frame. |
| scoped_ptr<SpdySynStreamControlFrame> frame( |
| compress_framer.CreateSynStream(1, |
| 0, |
| 1, |
| CONTROL_FLAG_NONE, |
| true, |
| &headers)); |
| // The point of this test is to exercise the limits. So, it is ok if the |
| // frame was too large to encode, or if the decompress fails. We just want |
| // to make sure we don't crash. |
| if (frame.get() != NULL) { |
| // Now that same header block should decompress just fine. |
| SpdyHeaderBlock new_headers; |
| decompress_framer.ParseHeaderBlock(frame.get(), &new_headers); |
| } |
| } |
| } |
| |
| } // namespace |