// 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