Index: net/spdy/spdy_framer_test.cc |
diff --git a/net/spdy/spdy_framer_test.cc b/net/spdy/spdy_framer_test.cc |
index 927953980a9bda94bdd058f0d2cb3f6f487508c6..410fea4b83031786eeb4ac7a046c14b52eb2af8d 100644 |
--- a/net/spdy/spdy_framer_test.cc |
+++ b/net/spdy/spdy_framer_test.cc |
@@ -400,7 +400,7 @@ class TestSpdyVisitor : public SpdyFramerVisitorInterface { |
} |
static size_t control_frame_buffer_max_size() { |
- return SpdyFramer::kControlFrameBufferSize; |
+ return SpdyFramer::kMaxControlFrameSize; |
} |
static size_t header_data_chunk_max_size() { |
@@ -2236,6 +2236,245 @@ TEST_P(SpdyFramerTest, DuplicateFrame) { |
} |
} |
+TEST_P(SpdyFramerTest, ReadCompressedSynStreamHeaderBlock) { |
+ SpdyHeaderBlock headers; |
+ headers["aa"] = "vv"; |
+ headers["bb"] = "ww"; |
+ SpdyFramer framer(spdy_version_); |
+ scoped_ptr<SpdySynStreamControlFrame> control_frame( |
+ framer.CreateSynStream(1, // stream_id |
+ 0, // associated_stream_id |
+ 1, // priority |
+ 0, // credential_slot |
+ CONTROL_FLAG_NONE, |
+ true, // compress |
+ &headers)); |
+ EXPECT_TRUE(control_frame.get() != NULL); |
+ TestSpdyVisitor visitor(spdy_version_); |
+ visitor.use_compression_ = true; |
+ visitor.SimulateInFramer( |
+ reinterpret_cast<unsigned char*>(control_frame->data()), |
+ control_frame->length() + SpdyControlFrame::kHeaderSize); |
+ EXPECT_EQ(1, visitor.syn_frame_count_); |
+ EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_)); |
+} |
+ |
+TEST_P(SpdyFramerTest, ReadCompressedSynReplyHeaderBlock) { |
+ SpdyHeaderBlock headers; |
+ headers["alpha"] = "beta"; |
+ headers["gamma"] = "delta"; |
+ SpdyFramer framer(spdy_version_); |
+ scoped_ptr<SpdySynReplyControlFrame> control_frame( |
+ framer.CreateSynReply(1, // stream_id |
+ CONTROL_FLAG_NONE, |
+ true, // compress |
+ &headers)); |
+ EXPECT_TRUE(control_frame.get() != NULL); |
+ TestSpdyVisitor visitor(spdy_version_); |
+ visitor.use_compression_ = true; |
+ visitor.SimulateInFramer( |
+ reinterpret_cast<unsigned char*>(control_frame->data()), |
+ control_frame.get()->length() + SpdyControlFrame::kHeaderSize); |
+ EXPECT_EQ(1, visitor.syn_reply_frame_count_); |
+ EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_)); |
+} |
+ |
+TEST_P(SpdyFramerTest, ReadCompressedHeadersHeaderBlock) { |
+ SpdyHeaderBlock headers; |
+ headers["alpha"] = "beta"; |
+ headers["gamma"] = "delta"; |
+ SpdyFramer framer(spdy_version_); |
+ scoped_ptr<SpdyHeadersControlFrame> control_frame( |
+ framer.CreateHeaders(1, // stream_id |
+ CONTROL_FLAG_NONE, |
+ true, // compress |
+ &headers)); |
+ EXPECT_TRUE(control_frame.get() != NULL); |
+ TestSpdyVisitor visitor(spdy_version_); |
+ visitor.use_compression_ = true; |
+ visitor.SimulateInFramer( |
+ reinterpret_cast<unsigned char*>(control_frame->data()), |
+ control_frame.get()->length() + SpdyControlFrame::kHeaderSize); |
+ EXPECT_EQ(1, visitor.headers_frame_count_); |
+ // control_frame_header_data_count_ depends on the random sequence |
+ // produced by rand(), so adding, removing or running single tests |
+ // alters this value. The best we can do is assert that it happens |
+ // at least twice. |
+ EXPECT_LE(2, visitor.control_frame_header_data_count_); |
+ EXPECT_EQ(1, visitor.zero_length_control_frame_header_data_count_); |
+ EXPECT_EQ(0, visitor.zero_length_data_frame_count_); |
+ EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_)); |
+} |
+ |
+TEST_P(SpdyFramerTest, ReadCompressedHeadersHeaderBlockWithHalfClose) { |
+ SpdyHeaderBlock headers; |
+ headers["alpha"] = "beta"; |
+ headers["gamma"] = "delta"; |
+ SpdyFramer framer(spdy_version_); |
+ scoped_ptr<SpdyHeadersControlFrame> control_frame( |
+ framer.CreateHeaders(1, // stream_id |
+ CONTROL_FLAG_FIN, |
+ true, // compress |
+ &headers)); |
+ EXPECT_TRUE(control_frame.get() != NULL); |
+ TestSpdyVisitor visitor(spdy_version_); |
+ visitor.use_compression_ = true; |
+ visitor.SimulateInFramer( |
+ reinterpret_cast<unsigned char*>(control_frame->data()), |
+ control_frame->length() + SpdyControlFrame::kHeaderSize); |
+ EXPECT_EQ(1, visitor.headers_frame_count_); |
+ // control_frame_header_data_count_ depends on the random sequence |
+ // produced by rand(), so adding, removing or running single tests |
+ // alters this value. The best we can do is assert that it happens |
+ // at least twice. |
+ EXPECT_LE(2, visitor.control_frame_header_data_count_); |
+ EXPECT_EQ(1, visitor.zero_length_control_frame_header_data_count_); |
+ EXPECT_EQ(1, visitor.zero_length_data_frame_count_); |
+ EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_)); |
+} |
+ |
+TEST_P(SpdyFramerTest, ControlFrameAtMaxSizeLimit) { |
+ SpdyHeaderBlock headers; |
+ // Size a header value to just fit inside the control frame buffer: |
+ // SPDY 2 SPDY 3 |
+ // SYN_STREAM header: 18 bytes 18 bytes |
+ // Serialized header block: |
+ // # headers 2 bytes (uint16) 4 bytes (uint32) |
+ // name length 2 bytes (uint16) 4 bytes (uint32) |
+ // name text ("aa") 2 bytes 2 bytes |
+ // value length 2 bytes (uint16) 4 bytes (uint32) |
+ // --- --- |
+ // 26 bytes 32 bytes |
+ const size_t overhead = IsSpdy2() ? 26 : 32; |
+ const size_t big_value_size = |
+ TestSpdyVisitor::control_frame_buffer_max_size() - overhead; |
+ std::string big_value(big_value_size, 'x'); |
+ headers["aa"] = big_value.c_str(); |
+ SpdyFramer framer(spdy_version_); |
+ scoped_ptr<SpdySynStreamControlFrame> control_frame( |
+ framer.CreateSynStream(1, // stream_id |
+ 0, // associated_stream_id |
+ 1, // priority |
+ 0, // credential_slot |
+ CONTROL_FLAG_NONE, |
+ false, // compress |
+ &headers)); |
+ EXPECT_TRUE(control_frame.get() != NULL); |
+ TestSpdyVisitor visitor(spdy_version_); |
+ visitor.SimulateInFramer( |
+ reinterpret_cast<unsigned char*>(control_frame->data()), |
+ control_frame->length() + SpdyControlFrame::kHeaderSize); |
+ EXPECT_TRUE(visitor.header_buffer_valid_); |
+ EXPECT_EQ(0, visitor.error_count_); |
+ EXPECT_EQ(1, visitor.syn_frame_count_); |
+ EXPECT_EQ(1, visitor.zero_length_control_frame_header_data_count_); |
+ EXPECT_EQ(0, visitor.zero_length_data_frame_count_); |
+ EXPECT_LT(big_value_size, visitor.header_buffer_length_); |
+} |
+ |
+TEST_P(SpdyFramerTest, ControlFrameTooLarge) { |
+ SpdyHeaderBlock headers; |
+ // See size calculation for test above. This is one byte larger, which |
+ // should exceed the control frame buffer capacity by that one byte. |
+ const size_t overhead = IsSpdy2() ? 25 : 31; |
+ const size_t kBigValueSize = |
+ TestSpdyVisitor::control_frame_buffer_max_size() - overhead; |
+ std::string big_value(kBigValueSize, 'x'); |
+ headers["aa"] = big_value.c_str(); |
+ SpdyFramer framer(spdy_version_); |
+ scoped_ptr<SpdySynStreamControlFrame> control_frame( |
+ framer.CreateSynStream(1, // stream_id |
+ 0, // associated_stream_id |
+ 1, // priority |
+ 0, // credential_slot |
+ CONTROL_FLAG_NONE, |
+ false, // compress |
+ &headers)); |
+ EXPECT_TRUE(control_frame.get() != NULL); |
+ TestSpdyVisitor visitor(spdy_version_); |
+ visitor.SimulateInFramer( |
+ reinterpret_cast<unsigned char*>(control_frame->data()), |
+ control_frame.get()->length() + SpdyControlFrame::kHeaderSize); |
+ EXPECT_FALSE(visitor.header_buffer_valid_); |
+ EXPECT_EQ(1, visitor.error_count_); |
+ EXPECT_EQ(SpdyFramer::SPDY_CONTROL_PAYLOAD_TOO_LARGE, |
+ visitor.framer_.error_code()); |
+ EXPECT_EQ(0, visitor.syn_frame_count_); |
+ EXPECT_EQ(0u, visitor.header_buffer_length_); |
+} |
+ |
+// Check that the framer stops delivering header data chunks once the visitor |
+// declares it doesn't want any more. This is important to guard against |
+// "zip bomb" types of attacks. |
+TEST_P(SpdyFramerTest, ControlFrameMuchTooLarge) { |
+ SpdyHeaderBlock headers; |
+ const size_t kHeaderBufferChunks = 4; |
+ const size_t kHeaderBufferSize = |
+ TestSpdyVisitor::header_data_chunk_max_size() * kHeaderBufferChunks; |
+ const size_t big_value_size = kHeaderBufferSize * 2; |
+ std::string big_value(big_value_size, 'x'); |
+ headers["aa"] = big_value.c_str(); |
+ SpdyFramer framer(spdy_version_); |
+ scoped_ptr<SpdySynStreamControlFrame> control_frame( |
+ framer.CreateSynStream(1, // stream_id |
+ 0, // associated_stream_id |
+ 1, // priority |
+ 0, // credential_slot |
+ CONTROL_FLAG_FIN, // half close |
+ true, // compress |
+ &headers)); |
+ EXPECT_TRUE(control_frame.get() != NULL); |
+ TestSpdyVisitor visitor(spdy_version_); |
+ visitor.set_header_buffer_size(kHeaderBufferSize); |
+ visitor.use_compression_ = true; |
+ visitor.SimulateInFramer( |
+ reinterpret_cast<unsigned char*>(control_frame->data()), |
+ control_frame->length() + SpdyControlFrame::kHeaderSize); |
+ EXPECT_FALSE(visitor.header_buffer_valid_); |
+ EXPECT_EQ(1, visitor.error_count_); |
+ EXPECT_EQ(SpdyFramer::SPDY_CONTROL_PAYLOAD_TOO_LARGE, |
+ visitor.framer_.error_code()); |
+ |
+ // The framer should have stoped delivering chunks after the visitor |
+ // signaled "stop" by returning false from OnControlFrameHeaderData(). |
+ // |
+ // control_frame_header_data_count_ depends on the random sequence |
+ // produced by rand(), so adding, removing or running single tests |
+ // alters this value. The best we can do is assert that it happens |
+ // at least kHeaderBufferChunks + 1. |
+ EXPECT_LE(kHeaderBufferChunks + 1, |
+ static_cast<unsigned>(visitor.control_frame_header_data_count_)); |
+ EXPECT_EQ(0, visitor.zero_length_control_frame_header_data_count_); |
+ |
+ // The framer should not have sent half-close to the visitor. |
+ EXPECT_EQ(0, visitor.zero_length_data_frame_count_); |
+} |
+ |
+TEST_P(SpdyFramerTest, DecompressCorruptHeaderBlock) { |
+ SpdyHeaderBlock headers; |
+ headers["aa"] = "alpha beta gamma delta"; |
+ SpdyFramer framer(spdy_version_); |
+ // Construct a SYN_STREAM control frame without compressing the header block, |
+ // and have the framer try to decompress it. This will cause the framer to |
+ // deal with a decompression error. |
+ scoped_ptr<SpdySynStreamControlFrame> control_frame( |
+ framer.CreateSynStream(1, // stream_id |
+ 0, // associated_stream_id |
+ 1, // priority |
+ 0, // credential_slot |
+ CONTROL_FLAG_NONE, |
+ false, // compress |
+ &headers)); |
+ TestSpdyVisitor visitor(spdy_version_); |
+ visitor.use_compression_ = true; |
+ visitor.SimulateInFramer( |
+ reinterpret_cast<unsigned char*>(control_frame->data()), |
+ control_frame->length() + SpdyControlFrame::kHeaderSize); |
+ EXPECT_EQ(1, visitor.error_count_); |
+ EXPECT_EQ(SpdyFramer::SPDY_DECOMPRESS_FAILURE, visitor.framer_.error_code()); |
+ EXPECT_EQ(0u, visitor.header_buffer_length_); |
+} |
+ |
TEST_P(SpdyFramerTest, ControlFrameSizesAreValidated) { |
// Create a GoAway frame that has a few extra bytes at the end. |
// We create enough overhead to overflow the framer's control frame buffer. |
@@ -2361,6 +2600,59 @@ TEST_P(SpdyFramerTest, ReadCredentialFrame) { |
} |
} |
+TEST_P(SpdyFramerTest, ReadCredentialFrameOneByteAtATime) { |
+ SpdyCredential credential; |
+ credential.slot = 3; |
+ credential.proof = "proof"; |
+ credential.certs.push_back("a cert"); |
+ credential.certs.push_back("another cert"); |
+ credential.certs.push_back("final cert"); |
+ SpdyFramer framer(spdy_version_); |
+ scoped_ptr<SpdyFrame> control_frame( |
+ framer.CreateCredentialFrame(credential)); |
+ EXPECT_TRUE(control_frame.get() != NULL); |
+ TestSpdyVisitor visitor(spdy_version_); |
+ visitor.use_compression_ = false; |
+ // Read one byte at a time to make sure we handle edge cases |
+ unsigned char* data = |
+ reinterpret_cast<unsigned char*>(control_frame->data()); |
+ for (size_t idx = 0; |
+ idx < control_frame->length() + SpdyFrame::kHeaderSize; |
+ ++idx) { |
+ visitor.SimulateInFramer(data + idx, 1); |
+ ASSERT_EQ(0, visitor.error_count_); |
+ } |
+ EXPECT_EQ(0, visitor.error_count_); |
+ EXPECT_EQ(1, visitor.credential_count_); |
+ EXPECT_EQ(control_frame->length(), visitor.credential_buffer_length_); |
+ EXPECT_EQ(credential.slot, visitor.credential_.slot); |
+ EXPECT_EQ(credential.proof, visitor.credential_.proof); |
+ EXPECT_EQ(credential.certs.size(), visitor.credential_.certs.size()); |
+ for (size_t i = 0; i < credential.certs.size(); i++) { |
+ EXPECT_EQ(credential.certs[i], visitor.credential_.certs[i]); |
+ } |
+} |
+ |
+TEST_P(SpdyFramerTest, ReadCredentialFrameWithNoPayload) { |
+ SpdyCredential credential; |
+ credential.slot = 3; |
+ credential.proof = "proof"; |
+ credential.certs.push_back("a cert"); |
+ credential.certs.push_back("another cert"); |
+ credential.certs.push_back("final cert"); |
+ SpdyFramer framer(spdy_version_); |
+ scoped_ptr<SpdyFrame> control_frame( |
+ framer.CreateCredentialFrame(credential)); |
+ EXPECT_TRUE(control_frame.get() != NULL); |
+ TestSpdyVisitor visitor(spdy_version_); |
+ visitor.use_compression_ = false; |
+ control_frame->set_length(0); |
+ unsigned char* data = |
+ reinterpret_cast<unsigned char*>(control_frame->data()); |
+ visitor.SimulateInFramer(data, SpdyControlFrame::kHeaderSize); |
+ EXPECT_EQ(1, visitor.error_count_); |
+} |
+ |
TEST_P(SpdyFramerTest, ReadCredentialFrameWithCorruptProof) { |
SpdyCredential credential; |
credential.slot = 3; |