From 5af3c573076a8f04c285d6a0c40237462375e7c5 Mon Sep 17 00:00:00 2001
From: "agayev@chromium.org"
 <agayev@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>
Date: Tue, 20 Jul 2010 14:16:27 +0000
Subject: 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
---
 net/spdy/spdy_framer.cc                       |  13 ++-
 net/spdy/spdy_framer_test.cc                  |   4 +-
 net/spdy/spdy_network_transaction.h           |   3 +
 net/spdy/spdy_network_transaction_unittest.cc | 131 +++++++++++++++++++++++++-
 net/spdy/spdy_protocol.h                      |  11 ++-
 net/spdy/spdy_session.cc                      |  49 ++++++++++
 net/spdy/spdy_session.h                       |  13 ++-
 net/spdy/spdy_session_pool.h                  |   2 +
 net/spdy/spdy_stream.cc                       |  19 ++++
 net/spdy/spdy_stream.h                        |   9 ++
 net/spdy/spdy_test_util.cc                    |  16 ++++
 net/spdy/spdy_test_util.h                     |  10 ++
 12 files changed, 264 insertions(+), 16 deletions(-)

(limited to 'net/spdy')

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.
-- 
cgit v1.1