diff options
author | agayev@chromium.org <agayev@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-20 14:16:27 +0000 |
---|---|---|
committer | agayev@chromium.org <agayev@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-20 14:16:27 +0000 |
commit | 5af3c573076a8f04c285d6a0c40237462375e7c5 (patch) | |
tree | 3161f03d9e617a1499a83c2413faa435682c10a7 /net | |
parent | 1442b29a275b076203b5e0a78206fe3feda3b739 (diff) | |
download | chromium_src-5af3c573076a8f04c285d6a0c40237462375e7c5.zip chromium_src-5af3c573076a8f04c285d6a0c40237462375e7c5.tar.gz chromium_src-5af3c573076a8f04c285d6a0c40237462375e7c5.tar.bz2 |
Initial SPDY flow control support
BUG=48100
TEST=net_unittests --gtest_filter="SpdyProtocolTest.ControlFrameStructs:SpdyNetworkTransactionTest.WindowSizeChange:SpdyNetworkTransactionTest.WindowSizeOverflow"
Contributed by: agayev@google.com
Review URL: http://codereview.chromium.org/2805083
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@53039 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/base/net_error_list.h | 3 | ||||
-rw-r--r-- | net/spdy/spdy_framer.cc | 13 | ||||
-rw-r--r-- | net/spdy/spdy_framer_test.cc | 4 | ||||
-rw-r--r-- | net/spdy/spdy_network_transaction.h | 3 | ||||
-rw-r--r-- | net/spdy/spdy_network_transaction_unittest.cc | 131 | ||||
-rw-r--r-- | net/spdy/spdy_protocol.h | 11 | ||||
-rw-r--r-- | net/spdy/spdy_session.cc | 49 | ||||
-rw-r--r-- | net/spdy/spdy_session.h | 13 | ||||
-rw-r--r-- | net/spdy/spdy_session_pool.h | 2 | ||||
-rw-r--r-- | net/spdy/spdy_stream.cc | 19 | ||||
-rw-r--r-- | net/spdy/spdy_stream.h | 9 | ||||
-rw-r--r-- | net/spdy/spdy_test_util.cc | 16 | ||||
-rw-r--r-- | net/spdy/spdy_test_util.h | 10 |
13 files changed, 267 insertions, 16 deletions
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h index 38b5e143..e302a9f 100644 --- a/net/base/net_error_list.h +++ b/net/base/net_error_list.h @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// This file intentionally does not have header guards, it's included +// inside a macro to generate enum. + // This file contains the list of network errors. // diff --git a/net/spdy/spdy_framer.cc b/net/spdy/spdy_framer.cc index c0f4a86..cd622b8 100644 --- a/net/spdy/spdy_framer.cc +++ b/net/spdy/spdy_framer.cc @@ -304,6 +304,11 @@ void SpdyFramer::ProcessControlFrameHeader() { SpdySettingsControlFrame::size() - SpdyControlFrame::size()) set_error(SPDY_INVALID_CONTROL_FRAME); break; + case WINDOW_UPDATE: + if (current_control_frame.length() != + SpdyWindowUpdateControlFrame::size() - SpdyFrame::size()) + set_error(SPDY_INVALID_CONTROL_FRAME); + break; default: LOG(WARNING) << "Valid spdy control frame with unknown type: " << current_control_frame.type(); @@ -312,12 +317,6 @@ void SpdyFramer::ProcessControlFrameHeader() { break; } - // We only support version 1 of this protocol. - if (current_control_frame.version() != kSpdyProtocolVersion) { - set_error(SPDY_UNSUPPORTED_VERSION); - return; - } - remaining_control_payload_ = current_control_frame.length(); if (remaining_control_payload_ > kControlFrameBufferMaxSize) { set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE); @@ -592,7 +591,7 @@ SpdyWindowUpdateControlFrame* SpdyFramer::CreateWindowUpdate( DCHECK_GT(stream_id, 0u); DCHECK_EQ(0u, stream_id & ~kStreamIdMask); DCHECK_GT(delta_window_size, 0u); - DCHECK_LE(delta_window_size, 0x80000000u); // 2^31 + DCHECK_LT(delta_window_size, 0x80000000u); // 2^31 SpdyFrameBuilder frame; frame.WriteUInt16(kControlFlagMask | kSpdyProtocolVersion); diff --git a/net/spdy/spdy_framer_test.cc b/net/spdy/spdy_framer_test.cc index 5791a67..4e976ab 100644 --- a/net/spdy/spdy_framer_test.cc +++ b/net/spdy/spdy_framer_test.cc @@ -1173,9 +1173,9 @@ TEST_F(SpdyFramerTest, CreateWindowUpdate) { 0x80, 0x01, 0x00, 0x09, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, - 0x80, 0x00, 0x00, 0x00, + 0x7f, 0xff, 0xff, 0xff, }; - scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(1, 0x80000000)); + scoped_ptr<SpdyFrame> frame(framer.CreateWindowUpdate(1, 0x7FFFFFFF)); CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData)); } } diff --git a/net/spdy/spdy_network_transaction.h b/net/spdy/spdy_network_transaction.h index 686c3f7..4611cda 100644 --- a/net/spdy/spdy_network_transaction.h +++ b/net/spdy/spdy_network_transaction.h @@ -53,6 +53,9 @@ class SpdyNetworkTransaction : public HttpTransaction { virtual uint64 GetUploadProgress() const; private: + FRIEND_TEST(SpdyNetworkTransactionTest, WindowUpdate); + FRIEND_TEST(SpdyNetworkTransactionTest, WindowUpdateOverflow); + enum State { STATE_INIT_CONNECTION, STATE_INIT_CONNECTION_COMPLETE, diff --git a/net/spdy/spdy_network_transaction_unittest.cc b/net/spdy/spdy_network_transaction_unittest.cc index 54453bf..72d65bd 100644 --- a/net/spdy/spdy_network_transaction_unittest.cc +++ b/net/spdy/spdy_network_transaction_unittest.cc @@ -1,5 +1,5 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. Use -// of this source code is governed by a BSD-style license that can be +// Copyright (c) 2010 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 "net/spdy/spdy_network_transaction.h" @@ -989,6 +989,133 @@ TEST_F(SpdyNetworkTransactionTest, ResponseWithTwoSynReplies) { helper.VerifyDataConsumed(); } +// Test that WINDOW_UPDATE frames change window_size correctly. +TEST_F(SpdyNetworkTransactionTest, WindowUpdate) { + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session = CreateSession(&session_deps); + + // We disable SSL for this test. + SpdySession::SetSSLMode(false); + + // Setup the request + static const char upload[] = { "hello!" }; + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + request.upload_data = new UploadData(); + request.upload_data->AppendBytes(upload, strlen(upload)); + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPost(NULL, 0)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*body), + }; + + // Response frames, send WINDOW_UPDATE first + static const int kDeltaWindowSize = 0xff; + scoped_ptr<spdy::SpdyFrame> window_update( + ConstructSpdyWindowUpdate(1, kDeltaWindowSize)); + scoped_ptr<spdy::SpdyFrame> reply(ConstructSpdyPostSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*window_update), + CreateMockRead(*reply), + CreateMockRead(*body), + MockRead(true, 0, 0) // EOF + }; + + scoped_refptr<DelayedSocketData> data( + new DelayedSocketData(2, reads, arraysize(reads), + writes, arraysize(writes))); + session_deps.socket_factory.AddSocketDataProvider(data.get()); + + scoped_ptr<SpdyNetworkTransaction> trans( + new SpdyNetworkTransaction(session)); + + TestCompletionCallback callback; + int rv = trans->Start(&request, &callback, BoundNetLog()); + + ASSERT_TRUE(trans->stream_ != NULL); + ASSERT_TRUE(trans->stream_->stream() != NULL); + EXPECT_EQ(spdy::kInitialWindowSize, trans->stream_->stream()->window_size()); + + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + ASSERT_TRUE(trans->stream_ != NULL); + ASSERT_TRUE(trans->stream_->stream() != NULL); + EXPECT_EQ(spdy::kInitialWindowSize + kDeltaWindowSize, + trans->stream_->stream()->window_size()); + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); +} + +// Test that WINDOW_UPDATE frame causing overflow is handled correctly. +TEST_F(SpdyNetworkTransactionTest, WindowUpdateOverflow) { + SessionDependencies session_deps; + scoped_refptr<HttpNetworkSession> session = CreateSession(&session_deps); + + // We disable SSL for this test. + SpdySession::SetSSLMode(false); + + // Setup the request + static const char upload[] = { "hello!" }; + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.google.com/"); + request.upload_data = new UploadData(); + request.upload_data->AppendBytes(upload, strlen(upload)); + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPost(NULL, 0)); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> rst( + ConstructSpdyRstStream(1, spdy::FLOW_CONTROL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*req), + CreateMockWrite(*body), + CreateMockWrite(*rst), + }; + + // Response frames, send WINDOW_UPDATE first + static const int kDeltaWindowSize = 0x7fffffff; // cause an overflow + scoped_ptr<spdy::SpdyFrame> window_update( + ConstructSpdyWindowUpdate(1, kDeltaWindowSize)); + scoped_ptr<spdy::SpdyFrame> reply(ConstructSpdyPostSynReply(NULL, 0)); + MockRead reads[] = { + CreateMockRead(*window_update), + CreateMockRead(*reply), + CreateMockRead(*body), + MockRead(true, 0, 0) // EOF + }; + + scoped_refptr<DelayedSocketData> data( + new DelayedSocketData(2, reads, arraysize(reads), + writes, arraysize(writes))); + session_deps.socket_factory.AddSocketDataProvider(data.get()); + + scoped_ptr<SpdyNetworkTransaction> trans( + new SpdyNetworkTransaction(session)); + + TestCompletionCallback callback; + int rv = trans->Start(&request, &callback, BoundNetLog()); + + ASSERT_TRUE(trans->stream_ != NULL); + ASSERT_TRUE(trans->stream_->stream() != NULL); + EXPECT_EQ(spdy::kInitialWindowSize, trans->stream_->stream()->window_size()); + + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, rv); + + ASSERT_TRUE(session != NULL); + ASSERT_TRUE(session->spdy_session_pool() != NULL); + session->spdy_session_pool()->ClearSessions(); + + EXPECT_FALSE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); +} + TEST_F(SpdyNetworkTransactionTest, CancelledTransaction) { // Construct the request. scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); diff --git a/net/spdy/spdy_protocol.h b/net/spdy/spdy_protocol.h index 49d8bdc..810d5b3 100644 --- a/net/spdy/spdy_protocol.h +++ b/net/spdy/spdy_protocol.h @@ -121,6 +121,9 @@ namespace spdy { // This implementation of Spdy is version 1. const int kSpdyProtocolVersion = 1; +// Default initial window size. +const int kInitialWindowSize = 64 * 1024; + // Note: all protocol data structures are on-the-wire format. That means that // data is stored in network-normalized order. Readers must use the // accessors provided or call ntohX() functions. @@ -184,7 +187,8 @@ enum SpdyStatusCodes { UNSUPPORTED_VERSION = 4, CANCEL = 5, INTERNAL_ERROR = 6, - NUM_STATUS_CODES = 7 + FLOW_CONTROL_ERROR = 7, + NUM_STATUS_CODES = 8 }; // A SPDY stream id is a 31 bit entity. @@ -403,9 +407,8 @@ class SpdyControlFrame : public SpdyFrame { } void set_version(uint16 version) { - const uint16 kControlBit = 0x80; - DCHECK_EQ(0, version & kControlBit); - mutable_block()->control_.version_ = kControlBit | htons(version); + DCHECK_EQ(0u, version & kControlFlagMask); + mutable_block()->control_.version_ = htons(kControlFlagMask | version); } SpdyControlType type() const { diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc index bbbf9ba..a45289f 100644 --- a/net/spdy/spdy_session.cc +++ b/net/spdy/spdy_session.cc @@ -164,6 +164,7 @@ SpdySession::SpdySession(const HostPortPair& host_port_pair, sent_settings_(false), received_settings_(false), in_session_pool_(true), + initial_window_size_(spdy::kInitialWindowSize), net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SPDY_SESSION)) { net_log_.BeginEvent( NetLog::TYPE_SPDY_SESSION, @@ -372,6 +373,7 @@ int SpdySession::CreateStreamImpl( stream->set_priority(priority); stream->set_path(path); stream->set_net_log(stream_net_log); + stream->set_window_size(initial_window_size_); ActivateStream(stream); UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyPriorityCount", @@ -458,6 +460,22 @@ void SpdySession::CloseStream(spdy::SpdyStreamId stream_id, int status) { DeleteStream(stream_id, status); } +void SpdySession::ResetStream( + spdy::SpdyStreamId stream_id, spdy::SpdyStatusCodes status) { + DCHECK(IsStreamActive(stream_id)); + scoped_refptr<SpdyStream> stream = active_streams_[stream_id]; + CHECK_EQ(stream->stream_id(), stream_id); + + LOG(INFO) << "Sending a RST_STREAM frame for stream " << stream_id + << " with status " << status; + + scoped_ptr<spdy::SpdyRstStreamControlFrame> rst_frame( + spdy_framer_.CreateRstStream(stream_id, status)); + QueueFrame(rst_frame.get(), stream->priority(), stream); + + DeleteStream(stream_id, ERR_SPDY_PROTOCOL_ERROR); +} + bool SpdySession::IsStreamActive(spdy::SpdyStreamId stream_id) const { return ContainsKey(active_streams_, stream_id); } @@ -1125,6 +1143,10 @@ void SpdySession::OnControl(const spdy::SpdyControlFrame* frame) { *reinterpret_cast<const spdy::SpdySynReplyControlFrame*>(frame), headers); break; + case spdy::WINDOW_UPDATE: + OnWindowUpdate( + *reinterpret_cast<const spdy::SpdyWindowUpdateControlFrame*>(frame)); + break; default: DCHECK(false); // Error! } @@ -1187,6 +1209,8 @@ void SpdySession::OnSettings(const spdy::SpdySettingsControlFrame& frame) { settings_storage->Set(host_port_pair_, settings); } + // TODO(agayev): Implement initial and per stream window size update. + received_settings_ = true; net_log_.AddEvent( @@ -1194,6 +1218,31 @@ void SpdySession::OnSettings(const spdy::SpdySettingsControlFrame& frame) { new NetLogSpdySettingsParameter(settings)); } +void SpdySession::OnWindowUpdate( + const spdy::SpdyWindowUpdateControlFrame& frame) { + spdy::SpdyStreamId stream_id = frame.stream_id(); + LOG(INFO) << "Spdy WINDOW_UPDATE for stream " << stream_id; + + if (!IsStreamActive(stream_id)) { + LOG(WARNING) << "Received WINDOW_UPDATE for invalid stream " << stream_id; + return; + } + + int delta_window_size = static_cast<int>(frame.delta_window_size()); + if (delta_window_size < 1) { + LOG(WARNING) << "Received WINDOW_UPDATE with an invalid delta_window_size " + << delta_window_size; + ResetStream(stream_id, spdy::FLOW_CONTROL_ERROR); + return; + } + + scoped_refptr<SpdyStream> stream = active_streams_[stream_id]; + CHECK_EQ(stream->stream_id(), stream_id); + CHECK(!stream->cancelled()); + + stream->UpdateWindowSize(delta_window_size); +} + void SpdySession::SendSettings() { const SpdySettingsStorage& settings_storage = session_->spdy_settings(); const spdy::SpdySettings& settings = settings_storage.Get(host_port_pair_); diff --git a/net/spdy/spdy_session.h b/net/spdy/spdy_session.h index 00ac0a8..ec938c6 100644 --- a/net/spdy/spdy_session.h +++ b/net/spdy/spdy_session.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 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. @@ -101,6 +101,11 @@ class SpdySession : public base::RefCounted<SpdySession>, // Close a stream. void CloseStream(spdy::SpdyStreamId stream_id, int status); + // Reset a stream by sending a RST_STREAM frame with given status code. + // Also closes the stream. Was not piggybacked to CloseStream since not + // all of the calls to CloseStream necessitate sending a RST_STREAM. + void ResetStream(spdy::SpdyStreamId stream_id, spdy::SpdyStatusCodes status); + // Check if a stream is active. bool IsStreamActive(spdy::SpdyStreamId stream_id) const; @@ -187,6 +192,7 @@ class SpdySession : public base::RefCounted<SpdySession>, void OnFin(const spdy::SpdyRstStreamControlFrame& frame); void OnGoAway(const spdy::SpdyGoAwayControlFrame& frame); void OnSettings(const spdy::SpdySettingsControlFrame& frame); + void OnWindowUpdate(const spdy::SpdyWindowUpdateControlFrame& frame); // IO Callbacks void OnTCPConnect(int result); @@ -327,6 +333,11 @@ class SpdySession : public base::RefCounted<SpdySession>, bool in_session_pool_; // True if the session is currently in the pool. + int initial_window_size_; // Initial window size for the session; can be + // changed by an arriving SETTINGS frame; newly + // created streams use this value for the initial + // window size. + BoundNetLog net_log_; static bool use_ssl_; diff --git a/net/spdy/spdy_session_pool.h b/net/spdy/spdy_session_pool.h index 7574154..1004169 100644 --- a/net/spdy/spdy_session_pool.h +++ b/net/spdy/spdy_session_pool.h @@ -15,6 +15,7 @@ #include "net/base/host_port_pair.h" #include "net/base/net_errors.h" #include "net/base/network_change_notifier.h" +#include "testing/gtest/include/gtest/gtest_prod.h" // For FRIEND_TEST namespace net { @@ -80,6 +81,7 @@ class SpdySessionPool friend class base::RefCounted<SpdySessionPool>; friend class SpdySessionPoolPeer; // For testing. friend class SpdyNetworkTransactionTest; // For testing. + FRIEND_TEST(SpdyNetworkTransactionTest, WindowUpdateOverflow); typedef std::list<scoped_refptr<SpdySession> > SpdySessionList; typedef std::map<HostPortPair, SpdySessionList*> SpdySessionsMap; diff --git a/net/spdy/spdy_stream.cc b/net/spdy/spdy_stream.cc index 7218699..a5f9d1f 100644 --- a/net/spdy/spdy_stream.cc +++ b/net/spdy/spdy_stream.cc @@ -71,6 +71,25 @@ void SpdyStream::set_spdy_headers( request_ = headers; } +void SpdyStream::UpdateWindowSize(int delta_window_size) { + DCHECK_GE(delta_window_size, 1); + int new_window_size = window_size_ + delta_window_size; + + // it's valid for window_size_ to become negative (via an incoming + // SETTINGS frame which is handled in SpdySession::OnSettings), in which + // case incoming WINDOW_UPDATEs will eventually make it positive; + // however, if window_size_ is positive and incoming WINDOW_UPDATE makes + // it negative, we have an overflow. + if (window_size_ > 0 && new_window_size < 0) { + LOG(WARNING) << "Received WINDOW_UPDATE [delta:" << delta_window_size + << "] for stream " << stream_id_ + << " overflows window size [current:" << window_size_ << "]"; + session_->ResetStream(stream_id_, spdy::FLOW_CONTROL_ERROR); + return; + } + window_size_ = new_window_size; +} + base::Time SpdyStream::GetRequestTime() const { return request_time_; } diff --git a/net/spdy/spdy_stream.h b/net/spdy/spdy_stream.h index bcb38c7..13a6644 100644 --- a/net/spdy/spdy_stream.h +++ b/net/spdy/spdy_stream.h @@ -102,6 +102,14 @@ class SpdyStream : public base::RefCounted<SpdyStream> { int priority() const { return priority_; } void set_priority(int priority) { priority_ = priority; } + int window_size() const { return window_size_; } + void set_window_size(int window_size) { window_size_ = window_size; } + + // Updates |window_size_| with delta extracted from a WINDOW_UPDATE + // frame; sends a RST_STREAM if delta overflows |window_size_| and + // removes the stream from the session. + void UpdateWindowSize(int delta_window_size); + const BoundNetLog& net_log() const { return net_log_; } void set_net_log(const BoundNetLog& log) { net_log_ = log; } @@ -200,6 +208,7 @@ class SpdyStream : public base::RefCounted<SpdyStream> { spdy::SpdyStreamId stream_id_; std::string path_; int priority_; + int window_size_; const bool pushed_; ScopedBandwidthMetrics metrics_; bool syn_reply_received_; diff --git a/net/spdy/spdy_test_util.cc b/net/spdy/spdy_test_util.cc index 89e3343..99af4f8 100644 --- a/net/spdy/spdy_test_util.cc +++ b/net/spdy/spdy_test_util.cc @@ -192,6 +192,22 @@ spdy::SpdyFrame* ConstructSpdyGoAway() { return framer.CreateGoAway(0); } +// Construct a SPDY WINDOW_UPDATE frame. +// Returns the constructed frame. The caller takes ownership of the frame. +spdy::SpdyFrame* ConstructSpdyWindowUpdate( + const spdy::SpdyStreamId stream_id, uint32 delta_window_size) { + spdy::SpdyFramer framer; + return framer.CreateWindowUpdate(stream_id, delta_window_size); +} + +// Construct a SPDY RST_STREAM frame. +// Returns the constructed frame. The caller takes ownership of the frame. +spdy::SpdyFrame* ConstructSpdyRstStream(spdy::SpdyStreamId stream_id, + spdy::SpdyStatusCodes status) { + spdy::SpdyFramer framer; + return framer.CreateRstStream(stream_id, status); +} + // Construct a single SPDY header entry, for validation. // |extra_headers| are the extra header-value pairs. // |buffer| is the buffer we're filling in. diff --git a/net/spdy/spdy_test_util.h b/net/spdy/spdy_test_util.h index b01f7bb..7549b15 100644 --- a/net/spdy/spdy_test_util.h +++ b/net/spdy/spdy_test_util.h @@ -121,6 +121,16 @@ spdy::SpdyFrame* ConstructSpdySettings(spdy::SpdySettings settings); // Returns the constructed frame. The caller takes ownership of the frame. spdy::SpdyFrame* ConstructSpdyGoAway(); +// Construct a SPDY WINDOW_UPDATE frame. +// Returns the constructed frame. The caller takes ownership of the frame. +spdy::SpdyFrame* ConstructSpdyWindowUpdate(spdy::SpdyStreamId, + uint32 delta_window_size); + +// Construct a SPDY RST_STREAM frame. +// Returns the constructed frame. The caller takes ownership of the frame. +spdy::SpdyFrame* ConstructSpdyRstStream(spdy::SpdyStreamId stream_id, + spdy::SpdyStatusCodes status); + // Construct a single SPDY header entry, for validation. // |extra_headers| are the extra header-value pairs. // |buffer| is the buffer we're filling in. |