Index: net/spdy/spdy_session_unittest.cc |
diff --git a/net/spdy/spdy_session_unittest.cc b/net/spdy/spdy_session_unittest.cc |
index 8e943cae0dffbfdfeed4e1c649f51fd9b5299cdc..9ebc5376317baaa6bc56a2f1fcc75aed9f6ac6b8 100644 |
--- a/net/spdy/spdy_session_unittest.cc |
+++ b/net/spdy/spdy_session_unittest.cc |
@@ -3704,6 +3704,152 @@ TEST_P(SpdySessionTest, StreamFlowControlTooMuchData) { |
EXPECT_EQ(nullptr, spdy_stream.get()); |
} |
+// Regression test for a bug that was caused by including unsent WINDOW_UPDATE |
+// deltas in the receiving window size when checking incoming frames for flow |
+// control errors at session level. |
+TEST_P(SpdySessionTest, SessionFlowControlTooMuchDataTwoDataFrames) { |
+ if (GetParam() < kProtoSPDY31) |
+ return; |
+ |
+ const int32 session_max_recv_window_size = 500; |
+ const int32 first_data_frame_size = 200; |
+ const int32 second_data_frame_size = 400; |
+ |
+ // First data frame should not trigger a WINDOW_UPDATE. |
+ ASSERT_GT(session_max_recv_window_size / 2, first_data_frame_size); |
+ // Second data frame would be fine had there been a WINDOW_UPDATE. |
+ ASSERT_GT(session_max_recv_window_size, second_data_frame_size); |
+ // But in fact, the two data frames together overflow the receiving window at |
+ // session level. |
+ ASSERT_LT(session_max_recv_window_size, |
+ first_data_frame_size + second_data_frame_size); |
+ |
+ session_deps_.host_resolver->set_synchronous_mode(true); |
+ |
+ const std::string first_data_frame(first_data_frame_size, 'a'); |
+ scoped_ptr<SpdyFrame> first(spdy_util_.ConstructSpdyBodyFrame( |
+ 1, first_data_frame.data(), first_data_frame_size, false)); |
+ const std::string second_data_frame(second_data_frame_size, 'b'); |
+ scoped_ptr<SpdyFrame> second(spdy_util_.ConstructSpdyBodyFrame( |
+ 1, second_data_frame.data(), second_data_frame_size, false)); |
+ MockRead reads[] = { |
+ CreateMockRead(*first, 0), |
+ CreateMockRead(*second, 1), |
+ MockRead(ASYNC, 0, 2), |
+ }; |
+ DeterministicSocketData data(reads, arraysize(reads), NULL, 0); |
+ data.set_connect_data(MockConnect(SYNCHRONOUS, OK)); |
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); |
+ |
+ CreateDeterministicNetworkSession(); |
+ base::WeakPtr<SpdySession> session = |
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); |
+ EXPECT_EQ(SpdySession::FLOW_CONTROL_STREAM_AND_SESSION, |
+ session->flow_control_state()); |
+ // Setting session level receiving window size to smaller than initial is not |
+ // possible via SpdySessionPoolPeer. |
+ session->session_recv_window_size_ = session_max_recv_window_size; |
+ |
+ // First data frame is immediately consumed and does not trigger |
+ // WINDOW_UPDATE. |
+ data.RunFor(1); |
+ EXPECT_EQ(first_data_frame_size, session->session_unacked_recv_window_bytes_); |
+ EXPECT_EQ(session_max_recv_window_size, session->session_recv_window_size_); |
+ EXPECT_EQ(SpdySession::STATE_AVAILABLE, session->availability_state_); |
+ |
+ // Second data frame overflows receiving window, causes session to close. |
+ data.RunFor(1); |
+ EXPECT_EQ(SpdySession::STATE_DRAINING, session->availability_state_); |
+} |
+ |
+// Regression test for a bug that was caused by including unsent WINDOW_UPDATE |
+// deltas in the receiving window size when checking incoming data frames for |
+// flow control errors at stream level. |
+TEST_P(SpdySessionTest, StreamFlowControlTooMuchDataTwoDataFrames) { |
+ if (GetParam() < kProtoSPDY3) |
+ return; |
+ |
+ const int32 stream_max_recv_window_size = 500; |
+ const int32 first_data_frame_size = 200; |
+ const int32 second_data_frame_size = 400; |
+ |
+ // First data frame should not trigger a WINDOW_UPDATE. |
+ ASSERT_GT(stream_max_recv_window_size / 2, first_data_frame_size); |
+ // Second data frame would be fine had there been a WINDOW_UPDATE. |
+ ASSERT_GT(stream_max_recv_window_size, second_data_frame_size); |
+ // But in fact, they should overflow the receiving window at stream level. |
+ ASSERT_LT(stream_max_recv_window_size, |
+ first_data_frame_size + second_data_frame_size); |
+ |
+ scoped_ptr<SpdyFrame> req( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ scoped_ptr<SpdyFrame> rst( |
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_FLOW_CONTROL_ERROR)); |
+ MockWrite writes[] = { |
+ CreateMockWrite(*req, 0), CreateMockWrite(*rst, 4), |
+ }; |
+ |
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ const std::string first_data_frame(first_data_frame_size, 'a'); |
+ scoped_ptr<SpdyFrame> first(spdy_util_.ConstructSpdyBodyFrame( |
+ 1, first_data_frame.data(), first_data_frame_size, false)); |
+ const std::string second_data_frame(second_data_frame_size, 'b'); |
+ scoped_ptr<SpdyFrame> second(spdy_util_.ConstructSpdyBodyFrame( |
+ 1, second_data_frame.data(), second_data_frame_size, false)); |
+ MockRead reads[] = { |
+ CreateMockRead(*resp, 1), |
+ CreateMockRead(*first, 2), |
+ CreateMockRead(*second, 3), |
+ MockRead(ASYNC, 0, 5), |
+ }; |
+ |
+ DeterministicSocketData data(reads, arraysize(reads), writes, |
+ arraysize(writes)); |
+ data.set_connect_data(MockConnect(SYNCHRONOUS, OK)); |
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data); |
+ |
+ CreateDeterministicNetworkSession(); |
+ SpdySessionPoolPeer pool_peer(spdy_session_pool_); |
+ pool_peer.SetStreamInitialRecvWindowSize(stream_max_recv_window_size); |
+ |
+ base::WeakPtr<SpdySession> session = |
+ CreateInsecureSpdySession(http_session_, key_, BoundNetLog()); |
+ EXPECT_LE(SpdySession::FLOW_CONTROL_STREAM, session->flow_control_state()); |
+ |
+ base::WeakPtr<SpdyStream> spdy_stream = CreateStreamSynchronously( |
+ SPDY_REQUEST_RESPONSE_STREAM, session, test_url_, LOWEST, BoundNetLog()); |
+ test::StreamDelegateDoNothing delegate(spdy_stream); |
+ spdy_stream->SetDelegate(&delegate); |
+ |
+ scoped_ptr<SpdyHeaderBlock> headers( |
+ spdy_util_.ConstructGetHeaderBlock(kDefaultURL)); |
+ EXPECT_EQ(ERR_IO_PENDING, spdy_stream->SendRequestHeaders( |
+ headers.Pass(), NO_MORE_DATA_TO_SEND)); |
+ |
+ // Request and response. |
+ data.RunFor(2); |
+ EXPECT_TRUE(spdy_stream->IsLocallyClosed()); |
+ EXPECT_EQ(stream_max_recv_window_size, spdy_stream->recv_window_size()); |
+ |
+ // First data frame. |
+ data.RunFor(1); |
+ EXPECT_TRUE(spdy_stream->IsLocallyClosed()); |
+ EXPECT_EQ(stream_max_recv_window_size - first_data_frame_size, |
+ spdy_stream->recv_window_size()); |
+ |
+ // Consume first data frame. This does not trigger a WINDOW_UPDATE. |
+ std::string received_data = delegate.TakeReceivedData(); |
+ EXPECT_EQ(static_cast<size_t>(first_data_frame_size), received_data.size()); |
+ EXPECT_EQ(stream_max_recv_window_size, spdy_stream->recv_window_size()); |
+ |
+ // Second data frame overflows receiving window, causes the stream to close. |
+ data.RunFor(1); |
+ EXPECT_FALSE(spdy_stream.get()); |
+ |
+ // RST_STREAM |
+ data.RunFor(1); |
+} |
+ |
// A delegate that drops any received data. |
class DropReceivedDataDelegate : public test::StreamDelegateSendImmediate { |
public: |