Initial import of Cobalt 2.8885 2016-07-27
diff --git a/src/net/spdy/buffered_spdy_framer.cc b/src/net/spdy/buffered_spdy_framer.cc
new file mode 100644
index 0000000..50a0d93
--- /dev/null
+++ b/src/net/spdy/buffered_spdy_framer.cc
@@ -0,0 +1,301 @@
+// Copyright (c) 2012 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/buffered_spdy_framer.h"
+
+#include "base/logging.h"
+
+namespace net {
+
+BufferedSpdyFramer::BufferedSpdyFramer(int version, bool enable_compression)
+    : spdy_framer_(version),
+      visitor_(NULL),
+      header_buffer_used_(0),
+      header_buffer_valid_(false),
+      header_stream_id_(SpdyFramer::kInvalidStream),
+      frames_received_(0) {
+  spdy_framer_.set_enable_compression(enable_compression);
+  memset(header_buffer_, 0, sizeof(header_buffer_));
+}
+
+BufferedSpdyFramer::~BufferedSpdyFramer() {
+}
+
+void BufferedSpdyFramer::set_visitor(
+    BufferedSpdyFramerVisitorInterface* visitor) {
+  visitor_ = visitor;
+  spdy_framer_.set_visitor(this);
+}
+
+void BufferedSpdyFramer::OnError(SpdyFramer* spdy_framer) {
+  DCHECK(spdy_framer);
+  visitor_->OnError(spdy_framer->error_code());
+}
+
+void BufferedSpdyFramer::OnSynStream(SpdyStreamId stream_id,
+                                     SpdyStreamId associated_stream_id,
+                                     SpdyPriority priority,
+                                     uint8 credential_slot,
+                                     bool fin,
+                                     bool unidirectional) {
+  frames_received_++;
+  DCHECK(!control_frame_fields_.get());
+  control_frame_fields_.reset(new ControlFrameFields());
+  control_frame_fields_->type = SYN_STREAM;
+  control_frame_fields_->stream_id = stream_id;
+  control_frame_fields_->associated_stream_id = associated_stream_id;
+  control_frame_fields_->priority = priority;
+  control_frame_fields_->credential_slot = credential_slot;
+  control_frame_fields_->fin = fin;
+  control_frame_fields_->unidirectional = unidirectional;
+
+  InitHeaderStreaming(stream_id);
+}
+
+void BufferedSpdyFramer::OnHeaders(SpdyStreamId stream_id,
+                                   bool fin) {
+  frames_received_++;
+  DCHECK(!control_frame_fields_.get());
+  control_frame_fields_.reset(new ControlFrameFields());
+  control_frame_fields_->type = HEADERS;
+  control_frame_fields_->stream_id = stream_id;
+  control_frame_fields_->fin = fin;
+
+  InitHeaderStreaming(stream_id);
+}
+
+void BufferedSpdyFramer::OnSynReply(SpdyStreamId stream_id,
+                                    bool fin) {
+  frames_received_++;
+  DCHECK(!control_frame_fields_.get());
+  control_frame_fields_.reset(new ControlFrameFields());
+  control_frame_fields_->type = SYN_REPLY;
+  control_frame_fields_->stream_id = stream_id;
+  control_frame_fields_->fin = fin;
+
+  InitHeaderStreaming(stream_id);
+}
+
+bool BufferedSpdyFramer::OnCredentialFrameData(const char* frame_data,
+                                               size_t len) {
+  DCHECK(false);
+  return false;
+}
+
+bool BufferedSpdyFramer::OnControlFrameHeaderData(SpdyStreamId stream_id,
+                                                  const char* header_data,
+                                                  size_t len) {
+  CHECK_EQ(header_stream_id_, stream_id);
+
+  if (len == 0) {
+    // Indicates end-of-header-block.
+    CHECK(header_buffer_valid_);
+
+    SpdyHeaderBlock headers;
+    bool parsed_headers = spdy_framer_.ParseHeaderBlockInBuffer(
+        header_buffer_, header_buffer_used_, &headers);
+    if (!parsed_headers) {
+      visitor_->OnStreamError(
+          stream_id, "Could not parse Spdy Control Frame Header.");
+      return false;
+    }
+    DCHECK(control_frame_fields_.get());
+    switch (control_frame_fields_->type) {
+      case SYN_STREAM:
+        visitor_->OnSynStream(control_frame_fields_->stream_id,
+                              control_frame_fields_->associated_stream_id,
+                              control_frame_fields_->priority,
+                              control_frame_fields_->credential_slot,
+                              control_frame_fields_->fin,
+                              control_frame_fields_->unidirectional,
+                              headers);
+        break;
+      case SYN_REPLY:
+        visitor_->OnSynReply(control_frame_fields_->stream_id,
+                             control_frame_fields_->fin,
+                             headers);
+        break;
+      case HEADERS:
+        visitor_->OnHeaders(control_frame_fields_->stream_id,
+                            control_frame_fields_->fin,
+                            headers);
+        break;
+      default:
+        DCHECK(false) << "Unexpect control frame type: "
+                      << control_frame_fields_->type;
+        break;
+    }
+    control_frame_fields_.reset(NULL);
+    return true;
+  }
+
+  const size_t available = kHeaderBufferSize - header_buffer_used_;
+  if (len > available) {
+    header_buffer_valid_ = false;
+    visitor_->OnStreamError(
+        stream_id, "Received more data than the allocated size.");
+    return false;
+  }
+  memcpy(header_buffer_ + header_buffer_used_, header_data, len);
+  header_buffer_used_ += len;
+  return true;
+}
+
+void BufferedSpdyFramer::OnDataFrameHeader(const SpdyDataFrame* frame) {
+  frames_received_++;
+  header_stream_id_ = frame->stream_id();
+}
+
+void BufferedSpdyFramer::OnStreamFrameData(SpdyStreamId stream_id,
+                                           const char* data,
+                                           size_t len,
+                                           SpdyDataFlags flags) {
+  visitor_->OnStreamFrameData(stream_id, data, len, flags);
+}
+
+void BufferedSpdyFramer::OnSetting(SpdySettingsIds id,
+                                   uint8 flags,
+                                   uint32 value) {
+  visitor_->OnSetting(id, flags, value);
+}
+
+void BufferedSpdyFramer::OnPing(uint32 unique_id) {
+  visitor_->OnPing(unique_id);
+}
+
+void BufferedSpdyFramer::OnRstStream(SpdyStreamId stream_id,
+                                     SpdyStatusCodes status) {
+  visitor_->OnRstStream(stream_id, status);
+}
+void BufferedSpdyFramer::OnGoAway(SpdyStreamId last_accepted_stream_id,
+                                  SpdyGoAwayStatus status) {
+  visitor_->OnGoAway(last_accepted_stream_id, status);
+}
+
+void BufferedSpdyFramer::OnWindowUpdate(SpdyStreamId stream_id,
+                                        int delta_window_size) {
+  visitor_->OnWindowUpdate(stream_id, delta_window_size);
+}
+
+void BufferedSpdyFramer::OnControlFrameCompressed(
+    const SpdyControlFrame& uncompressed_frame,
+    const SpdyControlFrame& compressed_frame) {
+  visitor_->OnControlFrameCompressed(uncompressed_frame, compressed_frame);
+}
+
+
+int BufferedSpdyFramer::protocol_version() {
+  return spdy_framer_.protocol_version();
+}
+
+size_t BufferedSpdyFramer::ProcessInput(const char* data, size_t len) {
+  return spdy_framer_.ProcessInput(data, len);
+}
+
+void BufferedSpdyFramer::Reset() {
+  spdy_framer_.Reset();
+}
+
+SpdyFramer::SpdyError BufferedSpdyFramer::error_code() const {
+  return spdy_framer_.error_code();
+}
+
+SpdyFramer::SpdyState BufferedSpdyFramer::state() const {
+  return spdy_framer_.state();
+}
+
+bool BufferedSpdyFramer::MessageFullyRead() {
+  return spdy_framer_.MessageFullyRead();
+}
+
+bool BufferedSpdyFramer::HasError() {
+  return spdy_framer_.HasError();
+}
+
+SpdySynStreamControlFrame* BufferedSpdyFramer::CreateSynStream(
+    SpdyStreamId stream_id,
+    SpdyStreamId associated_stream_id,
+    SpdyPriority priority,
+    uint8 credential_slot,
+    SpdyControlFlags flags,
+    bool compressed,
+    const SpdyHeaderBlock* headers) {
+  return spdy_framer_.CreateSynStream(stream_id, associated_stream_id, priority,
+                                      credential_slot, flags, compressed,
+                                      headers);
+}
+
+SpdySynReplyControlFrame* BufferedSpdyFramer::CreateSynReply(
+    SpdyStreamId stream_id,
+    SpdyControlFlags flags,
+    bool compressed,
+    const SpdyHeaderBlock* headers) {
+  return spdy_framer_.CreateSynReply(stream_id, flags, compressed, headers);
+}
+
+SpdyRstStreamControlFrame* BufferedSpdyFramer::CreateRstStream(
+    SpdyStreamId stream_id,
+    SpdyStatusCodes status) const {
+  return spdy_framer_.CreateRstStream(stream_id, status);
+}
+
+SpdySettingsControlFrame* BufferedSpdyFramer::CreateSettings(
+    const SettingsMap& values) const {
+  return spdy_framer_.CreateSettings(values);
+}
+
+SpdyPingControlFrame* BufferedSpdyFramer::CreatePingFrame(
+    uint32 unique_id) const {
+  return spdy_framer_.CreatePingFrame(unique_id);
+}
+
+SpdyGoAwayControlFrame* BufferedSpdyFramer::CreateGoAway(
+    SpdyStreamId last_accepted_stream_id,
+    SpdyGoAwayStatus status) const {
+  return spdy_framer_.CreateGoAway(last_accepted_stream_id, status);
+}
+
+SpdyHeadersControlFrame* BufferedSpdyFramer::CreateHeaders(
+    SpdyStreamId stream_id,
+    SpdyControlFlags flags,
+    bool compressed,
+    const SpdyHeaderBlock* headers) {
+  return spdy_framer_.CreateHeaders(stream_id, flags, compressed, headers);
+}
+
+SpdyWindowUpdateControlFrame* BufferedSpdyFramer::CreateWindowUpdate(
+    SpdyStreamId stream_id,
+    uint32 delta_window_size) const {
+  return spdy_framer_.CreateWindowUpdate(stream_id, delta_window_size);
+}
+
+SpdyCredentialControlFrame* BufferedSpdyFramer::CreateCredentialFrame(
+    const SpdyCredential& credential) const {
+  return spdy_framer_.CreateCredentialFrame(credential);
+}
+
+SpdyDataFrame* BufferedSpdyFramer::CreateDataFrame(SpdyStreamId stream_id,
+                                                   const char* data,
+                                                   uint32 len,
+                                                   SpdyDataFlags flags) {
+  return spdy_framer_.CreateDataFrame(stream_id, data, len, flags);
+}
+
+SpdyPriority BufferedSpdyFramer::GetHighestPriority() const {
+  return spdy_framer_.GetHighestPriority();
+}
+
+bool BufferedSpdyFramer::IsCompressible(const SpdyFrame& frame) const {
+  return spdy_framer_.IsCompressible(frame);
+}
+
+void BufferedSpdyFramer::InitHeaderStreaming(SpdyStreamId stream_id) {
+  memset(header_buffer_, 0, kHeaderBufferSize);
+  header_buffer_used_ = 0;
+  header_buffer_valid_ = true;
+  header_stream_id_ = stream_id;
+  DCHECK_NE(header_stream_id_, SpdyFramer::kInvalidStream);
+}
+
+}  // namespace net
diff --git a/src/net/spdy/buffered_spdy_framer.h b/src/net/spdy/buffered_spdy_framer.h
new file mode 100644
index 0000000..ea64151
--- /dev/null
+++ b/src/net/spdy/buffered_spdy_framer.h
@@ -0,0 +1,219 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_SPDY_BUFFERED_SPDY_FRAMER_H_
+#define NET_SPDY_BUFFERED_SPDY_FRAMER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_header_block.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+
+class NET_EXPORT_PRIVATE BufferedSpdyFramerVisitorInterface {
+ public:
+  BufferedSpdyFramerVisitorInterface() {}
+
+  // Called if an error is detected in the SpdyFrame protocol.
+  virtual void OnError(SpdyFramer::SpdyError error_code) = 0;
+
+  // Called if an error is detected in a SPDY stream.
+  virtual void OnStreamError(SpdyStreamId stream_id,
+                             const std::string& description) = 0;
+
+  // Called after all the header data for SYN_STREAM control frame is received.
+  virtual void OnSynStream(SpdyStreamId stream_id,
+                           SpdyStreamId associated_stream_id,
+                           SpdyPriority priority,
+                           uint8 credential_slot,
+                           bool fin,
+                           bool unidirectional,
+                           const SpdyHeaderBlock& headers) = 0;
+
+  // Called after all the header data for SYN_REPLY control frame is received.
+  virtual void OnSynReply(SpdyStreamId stream_id,
+                          bool fin,
+                          const SpdyHeaderBlock& headers) = 0;
+
+  // Called after all the header data for HEADERS control frame is received.
+  virtual void OnHeaders(SpdyStreamId stream_id,
+                         bool fin,
+                         const SpdyHeaderBlock& headers) = 0;
+
+  // Called when data is received.
+  // |stream_id| The stream receiving data.
+  // |data| A buffer containing the data received.
+  // |len| The length of the data buffer.
+  // When the other side has finished sending data on this stream,
+  // this method will be called with a zero-length buffer.
+  virtual void OnStreamFrameData(SpdyStreamId stream_id,
+                                 const char* data,
+                                 size_t len,
+                                 SpdyDataFlags flags) = 0;
+
+  // Called when an individual setting within a SETTINGS frame has been parsed
+  // and validated.
+  virtual void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) = 0;
+
+  // Called when a PING frame has been parsed.
+  virtual void OnPing(uint32 unique_id) = 0;
+
+  // Called when a RST_STREAM frame has been parsed.
+  virtual void OnRstStream(SpdyStreamId stream_id, SpdyStatusCodes status) = 0;
+
+  // Called when a GOAWAY frame has been parsed.
+  virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+                        SpdyGoAwayStatus status) = 0;
+
+  // Called when a WINDOW_UPDATE frame has been parsed.
+  virtual void OnWindowUpdate(SpdyStreamId stream_id,
+                              int delta_window_size) = 0;
+
+  // Called after a control frame has been compressed to allow the visitor
+  // to record compression statistics.
+  virtual void OnControlFrameCompressed(
+      const SpdyControlFrame& uncompressed_frame,
+      const SpdyControlFrame& compressed_frame) = 0;
+
+ protected:
+  virtual ~BufferedSpdyFramerVisitorInterface() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BufferedSpdyFramerVisitorInterface);
+};
+
+class NET_EXPORT_PRIVATE BufferedSpdyFramer
+    : public SpdyFramerVisitorInterface {
+ public:
+  BufferedSpdyFramer(int version,
+                     bool enable_compression);
+  virtual ~BufferedSpdyFramer();
+
+  // Sets callbacks to be called from the buffered spdy framer.  A visitor must
+  // be set, or else the framer will likely crash.  It is acceptable for the
+  // visitor to do nothing.  If this is called multiple times, only the last
+  // visitor will be used.
+  void set_visitor(BufferedSpdyFramerVisitorInterface* visitor);
+
+  // SpdyFramerVisitorInterface
+  virtual void OnError(SpdyFramer* spdy_framer) OVERRIDE;
+  virtual void OnSynStream(SpdyStreamId stream_id,
+                           SpdyStreamId associated_stream_id,
+                           SpdyPriority priority,
+                           uint8 credential_slot,
+                           bool fin,
+                           bool unidirectional) OVERRIDE;
+  virtual void OnSynReply(SpdyStreamId stream_id, bool fin) OVERRIDE;
+  virtual void OnHeaders(SpdyStreamId stream_id, bool fin) OVERRIDE;
+  virtual bool OnCredentialFrameData(const char* frame_data,
+                                     size_t len) OVERRIDE;
+  virtual bool OnControlFrameHeaderData(SpdyStreamId stream_id,
+                                        const char* header_data,
+                                        size_t len) OVERRIDE;
+  virtual void OnStreamFrameData(SpdyStreamId stream_id,
+                                 const char* data,
+                                 size_t len,
+                                 SpdyDataFlags flags) OVERRIDE;
+  virtual void OnSetting(
+      SpdySettingsIds id, uint8 flags, uint32 value) OVERRIDE;
+  virtual void OnPing(uint32 unique_id) OVERRIDE;
+  virtual void OnRstStream(SpdyStreamId stream_id,
+                           SpdyStatusCodes status) OVERRIDE;
+  virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+                        SpdyGoAwayStatus status) OVERRIDE;
+  virtual void OnWindowUpdate(SpdyStreamId stream_id,
+                              int delta_window_size) OVERRIDE;
+  virtual void OnDataFrameHeader(const SpdyDataFrame* frame) OVERRIDE;
+
+  // Called after a control frame has been compressed to allow the visitor
+  // to record compression statistics.
+  virtual void OnControlFrameCompressed(
+      const SpdyControlFrame& uncompressed_frame,
+      const SpdyControlFrame& compressed_frame) OVERRIDE;
+
+  // SpdyFramer methods.
+  size_t ProcessInput(const char* data, size_t len);
+  int protocol_version();
+  void Reset();
+  SpdyFramer::SpdyError error_code() const;
+  SpdyFramer::SpdyState state() const;
+  bool MessageFullyRead();
+  bool HasError();
+  SpdySynStreamControlFrame* CreateSynStream(SpdyStreamId stream_id,
+                                             SpdyStreamId associated_stream_id,
+                                             SpdyPriority priority,
+                                             uint8 credential_slot,
+                                             SpdyControlFlags flags,
+                                             bool compressed,
+                                             const SpdyHeaderBlock* headers);
+  SpdySynReplyControlFrame* CreateSynReply(SpdyStreamId stream_id,
+                                           SpdyControlFlags flags,
+                                           bool compressed,
+                                           const SpdyHeaderBlock* headers);
+  SpdyRstStreamControlFrame* CreateRstStream(SpdyStreamId stream_id,
+                                             SpdyStatusCodes status) const;
+  SpdySettingsControlFrame* CreateSettings(const SettingsMap& values) const;
+  SpdyPingControlFrame* CreatePingFrame(uint32 unique_id) const;
+  SpdyGoAwayControlFrame* CreateGoAway(
+      SpdyStreamId last_accepted_stream_id,
+      SpdyGoAwayStatus status) const;
+  SpdyHeadersControlFrame* CreateHeaders(SpdyStreamId stream_id,
+                                         SpdyControlFlags flags,
+                                         bool compressed,
+                                         const SpdyHeaderBlock* headers);
+  SpdyWindowUpdateControlFrame* CreateWindowUpdate(
+      SpdyStreamId stream_id,
+      uint32 delta_window_size) const;
+  SpdyCredentialControlFrame* CreateCredentialFrame(
+      const SpdyCredential& credential) const;
+  SpdyDataFrame* CreateDataFrame(SpdyStreamId stream_id,
+                                 const char* data,
+                                 uint32 len,
+                                 SpdyDataFlags flags);
+  SpdyPriority GetHighestPriority() const;
+  bool IsCompressible(const SpdyFrame& frame) const;
+
+  int frames_received() const { return frames_received_; }
+
+ private:
+  // The size of the header_buffer_.
+  enum { kHeaderBufferSize = 32 * 1024 };
+
+  void InitHeaderStreaming(SpdyStreamId stream_id);
+
+  SpdyFramer spdy_framer_;
+  BufferedSpdyFramerVisitorInterface* visitor_;
+
+  // Header block streaming state:
+  char header_buffer_[kHeaderBufferSize];
+  size_t header_buffer_used_;
+  bool header_buffer_valid_;
+  SpdyStreamId header_stream_id_;
+  int frames_received_;
+
+  // Collection of fields from control frames that we need to
+  // buffer up from the spdy framer.
+  struct ControlFrameFields {
+    SpdyControlType type;
+    SpdyStreamId stream_id;
+    SpdyStreamId associated_stream_id;
+    SpdyPriority priority;
+    uint8 credential_slot;
+    bool fin;
+    bool unidirectional;
+  };
+  scoped_ptr<ControlFrameFields> control_frame_fields_;
+
+  DISALLOW_COPY_AND_ASSIGN(BufferedSpdyFramer);
+};
+
+}  // namespace net
+
+#endif  // NET_SPDY_BUFFERED_SPDY_FRAMER_H_
diff --git a/src/net/spdy/buffered_spdy_framer_spdy2_unittest.cc b/src/net/spdy/buffered_spdy_framer_spdy2_unittest.cc
new file mode 100644
index 0000000..4bbdf6e
--- /dev/null
+++ b/src/net/spdy/buffered_spdy_framer_spdy2_unittest.cc
@@ -0,0 +1,268 @@
+// Copyright (c) 2012 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/buffered_spdy_framer.h"
+
+#include "net/spdy/spdy_test_util_spdy2.h"
+#include "testing/platform_test.h"
+
+using namespace net::test_spdy2;
+
+namespace net {
+
+namespace {
+
+class TestBufferedSpdyVisitor : public BufferedSpdyFramerVisitorInterface {
+ public:
+  TestBufferedSpdyVisitor()
+      : buffered_spdy_framer_(2, true),
+        error_count_(0),
+        setting_count_(0),
+        syn_frame_count_(0),
+        syn_reply_frame_count_(0),
+        headers_frame_count_(0),
+        header_stream_id_(-1) {
+  }
+
+  void OnError(SpdyFramer::SpdyError error_code) {
+    LOG(INFO) << "SpdyFramer Error: " << error_code;
+    error_count_++;
+  }
+
+  void OnStreamError(SpdyStreamId stream_id,
+                     const std::string& description) {
+    LOG(INFO) << "SpdyFramer Error on stream: " << stream_id  << " "
+              << description;
+    error_count_++;
+  }
+
+  void OnSynStream(SpdyStreamId stream_id,
+                   SpdyStreamId associated_stream_id,
+                   SpdyPriority priority,
+                   uint8 credential_slot,
+                   bool fin,
+                   bool unidirectional,
+                   const SpdyHeaderBlock& headers) {
+    header_stream_id_ = stream_id;
+    EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream);
+    syn_frame_count_++;
+    headers_ = headers;
+  }
+
+  void OnSynReply(SpdyStreamId stream_id,
+                  bool fin,
+                  const SpdyHeaderBlock& headers) {
+    header_stream_id_ = stream_id;
+    EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream);
+    syn_reply_frame_count_++;
+    headers_ = headers;
+  }
+
+  void OnHeaders(SpdyStreamId stream_id,
+                 bool fin,
+                 const SpdyHeaderBlock& headers) {
+    header_stream_id_ = stream_id;
+    EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream);
+    headers_frame_count_++;
+    headers_ = headers;
+  }
+
+  void OnStreamFrameData(SpdyStreamId stream_id,
+                         const char* data,
+                         size_t len,
+                         SpdyDataFlags flags) {
+    LOG(FATAL) << "Unexpected OnStreamFrameData call.";
+  }
+
+  void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) {
+    setting_count_++;
+  }
+
+  void OnPing(uint32 unique_id) {}
+
+  void OnRstStream(SpdyStreamId stream_id, SpdyStatusCodes status) {}
+
+  void OnGoAway(SpdyStreamId last_accepted_stream_id,
+                SpdyGoAwayStatus status) {}
+
+  void OnControlFrameCompressed(
+      const SpdyControlFrame& uncompressed_frame,
+      const SpdyControlFrame& compressed_frame) {
+  }
+
+  bool OnCredentialFrameData(const char*, size_t) {
+    LOG(FATAL) << "Unexpected OnCredentialFrameData call.";
+    return false;
+  }
+
+  void OnDataFrameHeader(const SpdyDataFrame* frame) {
+    LOG(FATAL) << "Unexpected OnDataFrameHeader call.";
+  }
+
+  void OnRstStream(const SpdyRstStreamControlFrame& frame) {}
+  void OnGoAway(const SpdyGoAwayControlFrame& frame) {}
+  void OnPing(const SpdyPingControlFrame& frame) {}
+  void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) {}
+  void OnCredential(const SpdyCredentialControlFrame& frame) {}
+
+  // Convenience function which runs a framer simulation with particular input.
+  void SimulateInFramer(const unsigned char* input, size_t size) {
+    buffered_spdy_framer_.set_visitor(this);
+    size_t input_remaining = size;
+    const char* input_ptr = reinterpret_cast<const char*>(input);
+    while (input_remaining > 0 &&
+           buffered_spdy_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 =
+          buffered_spdy_framer_.ProcessInput(input_ptr, bytes_read);
+      input_remaining -= bytes_processed;
+      input_ptr += bytes_processed;
+      if (buffered_spdy_framer_.state() == SpdyFramer::SPDY_DONE)
+        buffered_spdy_framer_.Reset();
+    }
+  }
+
+  BufferedSpdyFramer buffered_spdy_framer_;
+
+  // Counters from the visitor callbacks.
+  int error_count_;
+  int setting_count_;
+  int syn_frame_count_;
+  int syn_reply_frame_count_;
+  int headers_frame_count_;
+
+  // Header block streaming state:
+  SpdyStreamId header_stream_id_;
+
+  // Headers from OnSyn, OnSynReply and OnHeaders for verification.
+  SpdyHeaderBlock headers_;
+};
+
+}  // namespace
+
+class BufferedSpdyFramerSpdy2Test : public PlatformTest {
+ protected:
+  // Returns true if the two header blocks have equivalent content.
+  bool CompareHeaderBlocks(const SpdyHeaderBlock* expected,
+                           const SpdyHeaderBlock* actual) {
+    if (expected->size() != actual->size()) {
+      LOG(ERROR) << "Expected " << expected->size() << " headers; actually got "
+                 << actual->size() << ".";
+      return false;
+    }
+    for (SpdyHeaderBlock::const_iterator it = expected->begin();
+         it != expected->end();
+         ++it) {
+      SpdyHeaderBlock::const_iterator it2 = actual->find(it->first);
+      if (it2 == actual->end()) {
+        LOG(ERROR) << "Expected header name '" << it->first << "'.";
+        return false;
+      }
+      if (it->second.compare(it2->second) != 0) {
+        LOG(ERROR) << "Expected header named '" << it->first
+                   << "' to have a value of '" << it->second
+                   << "'. The actual value received was '" << it2->second
+                   << "'.";
+        return false;
+      }
+    }
+    return true;
+  }
+};
+
+TEST_F(BufferedSpdyFramerSpdy2Test, OnSetting) {
+  SpdyFramer framer(2);
+  SettingsMap settings;
+  settings[SETTINGS_UPLOAD_BANDWIDTH] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_NONE, 0x00000002);
+  settings[SETTINGS_DOWNLOAD_BANDWIDTH] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_NONE, 0x00000003);
+
+  scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings));
+  TestBufferedSpdyVisitor visitor;
+
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame->data()),
+      control_frame->length() + SpdyControlFrame::kHeaderSize);
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(2, visitor.setting_count_);
+}
+
+TEST_F(BufferedSpdyFramerSpdy2Test, ReadSynStreamHeaderBlock) {
+  SpdyHeaderBlock headers;
+  headers["aa"] = "vv";
+  headers["bb"] = "ww";
+  BufferedSpdyFramer framer(2, true);
+  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);
+
+  TestBufferedSpdyVisitor visitor;
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame.get()->data()),
+      control_frame.get()->length() + SpdyControlFrame::kHeaderSize);
+  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_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_));
+}
+
+TEST_F(BufferedSpdyFramerSpdy2Test, ReadSynReplyHeaderBlock) {
+  SpdyHeaderBlock headers;
+  headers["alpha"] = "beta";
+  headers["gamma"] = "delta";
+  BufferedSpdyFramer framer(2, true);
+  scoped_ptr<SpdySynReplyControlFrame> control_frame(
+      framer.CreateSynReply(1,                        // stream_id
+                            CONTROL_FLAG_NONE,
+                            true,                     // compress
+                            &headers));
+  EXPECT_TRUE(control_frame.get() != NULL);
+
+  TestBufferedSpdyVisitor visitor;
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame.get()->data()),
+       control_frame.get()->length() + SpdyControlFrame::kHeaderSize);
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(0, visitor.syn_frame_count_);
+  EXPECT_EQ(1, visitor.syn_reply_frame_count_);
+  EXPECT_EQ(0, visitor.headers_frame_count_);
+  EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_));
+}
+
+TEST_F(BufferedSpdyFramerSpdy2Test, ReadHeadersHeaderBlock) {
+  SpdyHeaderBlock headers;
+  headers["alpha"] = "beta";
+  headers["gamma"] = "delta";
+  BufferedSpdyFramer framer(2, true);
+  scoped_ptr<SpdyHeadersControlFrame> control_frame(
+      framer.CreateHeaders(1,                        // stream_id
+                           CONTROL_FLAG_NONE,
+                           true,                    // compress
+                           &headers));
+  EXPECT_TRUE(control_frame.get() != NULL);
+
+  TestBufferedSpdyVisitor visitor;
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame.get()->data()),
+       control_frame.get()->length() + SpdyControlFrame::kHeaderSize);
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(0, visitor.syn_frame_count_);
+  EXPECT_EQ(0, visitor.syn_reply_frame_count_);
+  EXPECT_EQ(1, visitor.headers_frame_count_);
+  EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_));
+}
+}  // namespace net
diff --git a/src/net/spdy/buffered_spdy_framer_spdy3_unittest.cc b/src/net/spdy/buffered_spdy_framer_spdy3_unittest.cc
new file mode 100644
index 0000000..ca9edf3
--- /dev/null
+++ b/src/net/spdy/buffered_spdy_framer_spdy3_unittest.cc
@@ -0,0 +1,269 @@
+// Copyright (c) 2012 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/buffered_spdy_framer.h"
+
+#include "net/spdy/spdy_test_util_spdy3.h"
+#include "testing/platform_test.h"
+
+using namespace net::test_spdy3;
+
+namespace net {
+
+namespace {
+
+class TestBufferedSpdyVisitor : public BufferedSpdyFramerVisitorInterface {
+ public:
+  TestBufferedSpdyVisitor()
+      : buffered_spdy_framer_(3, true),
+        error_count_(0),
+        setting_count_(0),
+        syn_frame_count_(0),
+        syn_reply_frame_count_(0),
+        headers_frame_count_(0),
+        header_stream_id_(-1) {
+  }
+
+  void OnError(SpdyFramer::SpdyError error_code) {
+    LOG(INFO) << "SpdyFramer Error: " << error_code;
+    error_count_++;
+  }
+
+  void OnStreamError(SpdyStreamId stream_id,
+                     const std::string& description) {
+    LOG(INFO) << "SpdyFramer Error on stream: " << stream_id  << " "
+              << description;
+    error_count_++;
+  }
+
+  void OnSynStream(SpdyStreamId stream_id,
+                   SpdyStreamId associated_stream_id,
+                   SpdyPriority priority,
+                   uint8 credential_slot,
+                   bool fin,
+                   bool unidirectional,
+                   const SpdyHeaderBlock& headers) {
+    header_stream_id_ = stream_id;
+    EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream);
+    syn_frame_count_++;
+    headers_ = headers;
+  }
+
+  void OnSynReply(SpdyStreamId stream_id,
+                  bool fin,
+                  const SpdyHeaderBlock& headers) {
+    header_stream_id_ = stream_id;
+    EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream);
+    syn_reply_frame_count_++;
+    headers_ = headers;
+  }
+
+  void OnHeaders(SpdyStreamId stream_id,
+                 bool fin,
+                 const SpdyHeaderBlock& headers) {
+    header_stream_id_ = stream_id;
+    EXPECT_NE(header_stream_id_, SpdyFramer::kInvalidStream);
+    headers_frame_count_++;
+    headers_ = headers;
+  }
+
+  void OnStreamFrameData(SpdyStreamId stream_id,
+                         const char* data,
+                         size_t len,
+                         SpdyDataFlags flags) {
+    LOG(FATAL) << "Unexpected OnStreamFrameData call.";
+  }
+
+  void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) {
+    setting_count_++;
+  }
+
+  void OnPing(uint32 unique_id) {}
+
+  void OnRstStream(SpdyStreamId stream_id, SpdyStatusCodes status) {}
+
+  void OnGoAway(SpdyStreamId last_accepted_stream_id,
+                SpdyGoAwayStatus status) {}
+
+  void OnControlFrameCompressed(
+      const SpdyControlFrame& uncompressed_frame,
+      const SpdyControlFrame& compressed_frame) {
+  }
+
+  bool OnCredentialFrameData(const char*, size_t) {
+    LOG(FATAL) << "Unexpected OnCredentialFrameData call.";
+    return false;
+  }
+
+  void OnDataFrameHeader(const SpdyDataFrame* frame) {
+    LOG(FATAL) << "Unexpected OnDataFrameHeader call.";
+  }
+
+  void OnRstStream(const SpdyRstStreamControlFrame& frame) {}
+  void OnGoAway(const SpdyGoAwayControlFrame& frame) {}
+  void OnPing(const SpdyPingControlFrame& frame) {}
+  void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) {}
+  void OnCredential(const SpdyCredentialControlFrame& frame) {}
+
+  // Convenience function which runs a framer simulation with particular input.
+  void SimulateInFramer(const unsigned char* input, size_t size) {
+    buffered_spdy_framer_.set_visitor(this);
+    size_t input_remaining = size;
+    const char* input_ptr = reinterpret_cast<const char*>(input);
+    while (input_remaining > 0 &&
+           buffered_spdy_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 =
+          buffered_spdy_framer_.ProcessInput(input_ptr, bytes_read);
+      input_remaining -= bytes_processed;
+      input_ptr += bytes_processed;
+      if (buffered_spdy_framer_.state() == SpdyFramer::SPDY_DONE)
+        buffered_spdy_framer_.Reset();
+    }
+  }
+
+  BufferedSpdyFramer buffered_spdy_framer_;
+
+  // Counters from the visitor callbacks.
+  int error_count_;
+  int setting_count_;
+  int syn_frame_count_;
+  int syn_reply_frame_count_;
+  int headers_frame_count_;
+
+  // Header block streaming state:
+  SpdyStreamId header_stream_id_;
+
+  // Headers from OnSyn, OnSynReply and OnHeaders for verification.
+  SpdyHeaderBlock headers_;
+};
+
+}  // namespace
+
+class BufferedSpdyFramerSpdy3Test : public PlatformTest {
+ protected:
+  // Returns true if the two header blocks have equivalent content.
+  bool CompareHeaderBlocks(const SpdyHeaderBlock* expected,
+                           const SpdyHeaderBlock* actual) {
+    if (expected->size() != actual->size()) {
+      LOG(ERROR) << "Expected " << expected->size() << " headers; actually got "
+                 << actual->size() << ".";
+      return false;
+    }
+    for (SpdyHeaderBlock::const_iterator it = expected->begin();
+         it != expected->end();
+         ++it) {
+      SpdyHeaderBlock::const_iterator it2 = actual->find(it->first);
+      if (it2 == actual->end()) {
+        LOG(ERROR) << "Expected header name '" << it->first << "'.";
+        return false;
+      }
+      if (it->second.compare(it2->second) != 0) {
+        LOG(ERROR) << "Expected header named '" << it->first
+                   << "' to have a value of '" << it->second
+                   << "'. The actual value received was '" << it2->second
+                   << "'.";
+        return false;
+      }
+    }
+    return true;
+  }
+};
+
+TEST_F(BufferedSpdyFramerSpdy3Test, OnSetting) {
+  SpdyFramer framer(3);
+  framer.set_enable_compression(false);
+  SettingsMap settings;
+  settings[SETTINGS_UPLOAD_BANDWIDTH] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_NONE, 0x00000002);
+  settings[SETTINGS_DOWNLOAD_BANDWIDTH] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_NONE, 0x00000003);
+
+  scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings));
+  TestBufferedSpdyVisitor visitor;
+
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame->data()),
+      control_frame->length() + SpdyControlFrame::kHeaderSize);
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(2, visitor.setting_count_);
+}
+
+TEST_F(BufferedSpdyFramerSpdy3Test, ReadSynStreamHeaderBlock) {
+  SpdyHeaderBlock headers;
+  headers["aa"] = "vv";
+  headers["bb"] = "ww";
+  BufferedSpdyFramer framer(3, true);
+  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);
+
+  TestBufferedSpdyVisitor visitor;
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame.get()->data()),
+      control_frame.get()->length() + SpdyControlFrame::kHeaderSize);
+  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_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_));
+}
+
+TEST_F(BufferedSpdyFramerSpdy3Test, ReadSynReplyHeaderBlock) {
+  SpdyHeaderBlock headers;
+  headers["alpha"] = "beta";
+  headers["gamma"] = "delta";
+  BufferedSpdyFramer framer(3, true);
+  scoped_ptr<SpdySynReplyControlFrame> control_frame(
+      framer.CreateSynReply(1,                        // stream_id
+                            CONTROL_FLAG_NONE,
+                            true,                     // compress
+                            &headers));
+  EXPECT_TRUE(control_frame.get() != NULL);
+
+  TestBufferedSpdyVisitor visitor;
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame.get()->data()),
+       control_frame.get()->length() + SpdyControlFrame::kHeaderSize);
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(0, visitor.syn_frame_count_);
+  EXPECT_EQ(1, visitor.syn_reply_frame_count_);
+  EXPECT_EQ(0, visitor.headers_frame_count_);
+  EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_));
+}
+
+TEST_F(BufferedSpdyFramerSpdy3Test, ReadHeadersHeaderBlock) {
+  SpdyHeaderBlock headers;
+  headers["alpha"] = "beta";
+  headers["gamma"] = "delta";
+  BufferedSpdyFramer framer(3, true);
+  scoped_ptr<SpdyHeadersControlFrame> control_frame(
+      framer.CreateHeaders(1,                        // stream_id
+                           CONTROL_FLAG_NONE,
+                           true,                    // compress
+                           &headers));
+  EXPECT_TRUE(control_frame.get() != NULL);
+
+  TestBufferedSpdyVisitor visitor;
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame.get()->data()),
+       control_frame.get()->length() + SpdyControlFrame::kHeaderSize);
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(0, visitor.syn_frame_count_);
+  EXPECT_EQ(0, visitor.syn_reply_frame_count_);
+  EXPECT_EQ(1, visitor.headers_frame_count_);
+  EXPECT_TRUE(CompareHeaderBlocks(&headers, &visitor.headers_));
+}
+}  // namespace net
diff --git a/src/net/spdy/spdy_bitmasks.h b/src/net/spdy/spdy_bitmasks.h
new file mode 100644
index 0000000..72fb948
--- /dev/null
+++ b/src/net/spdy/spdy_bitmasks.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_SPDY_SPDY_BITMASKS_H_
+#define NET_SPDY_SPDY_BITMASKS_H_
+
+namespace net {
+
+// StreamId mask from the SpdyHeader
+const unsigned int kStreamIdMask = 0x7fffffff;
+
+// Control flag mask from the SpdyHeader
+const unsigned int kControlFlagMask = 0x8000;
+
+// Priority mask from the SYN_FRAME
+const unsigned int kSpdy3PriorityMask = 0xe0;
+const unsigned int kSpdy2PriorityMask = 0xc0;
+
+// Mask the lower 24 bits.
+const unsigned int kLengthMask = 0xffffff;
+
+// Legal flags on data packets.
+const int kDataFlagsMask = 0x01;
+
+// Legal flags on control packets.
+const int kControlFlagsMask = 0x03;
+
+}  // namespace net
+
+#endif  // NET_SPDY_SPDY_BITMASKS_H_
diff --git a/src/net/spdy/spdy_credential_builder.cc b/src/net/spdy/spdy_credential_builder.cc
new file mode 100644
index 0000000..8ddda97
--- /dev/null
+++ b/src/net/spdy/spdy_credential_builder.cc
@@ -0,0 +1,90 @@
+// Copyright (c) 2012 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_credential_builder.h"
+
+#include "base/logging.h"
+#include "base/string_piece.h"
+#include "crypto/ec_private_key.h"
+#include "crypto/ec_signature_creator.h"
+#include "net/base/asn1_util.h"
+#include "net/base/server_bound_cert_service.h"
+#include "net/base/net_errors.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/spdy/spdy_framer.h"
+
+namespace net {
+
+namespace {
+
+std::vector<uint8> ToVector(base::StringPiece piece) {
+  return std::vector<uint8>(piece.data(), piece.data() + piece.length());
+}
+
+}  // namespace
+
+// static
+int SpdyCredentialBuilder::Build(const std::string& tls_unique,
+                                 SSLClientCertType type,
+                                 const std::string& key,
+                                 const std::string& cert,
+                                 size_t slot,
+                                 SpdyCredential* credential) {
+  if (type != CLIENT_CERT_ECDSA_SIGN)
+    return ERR_BAD_SSL_CLIENT_AUTH_CERT;
+
+  std::string secret = SpdyCredentialBuilder::GetCredentialSecret(tls_unique);
+
+  // Extract the SubjectPublicKeyInfo from the certificate.
+  base::StringPiece public_key_info;
+  if(!asn1::ExtractSPKIFromDERCert(cert, &public_key_info))
+    return ERR_BAD_SSL_CLIENT_AUTH_CERT;
+
+  // Next, extract the SubjectPublicKey data, which will actually
+  // be stored in the cert field of the credential frame.
+  base::StringPiece public_key;
+  if (!asn1::ExtractSubjectPublicKeyFromSPKI(public_key_info, &public_key))
+    return ERR_BAD_SSL_CLIENT_AUTH_CERT;
+  // Drop one byte of padding bits count from the BIT STRING
+  // (this will always be zero).  Drop one byte of X9.62 format specification
+  // (this will always be 4 to indicated an uncompressed point).
+  DCHECK_GT(public_key.length(), 2u);
+  DCHECK_EQ(0, static_cast<int>(public_key[0]));
+  DCHECK_EQ(4, static_cast<int>(public_key[1]));
+  public_key = public_key.substr(2, public_key.length());
+
+  // Convert the strings into a vector<unit8>
+  std::vector<uint8> der_signature;
+  scoped_ptr<crypto::ECPrivateKey> private_key(
+      crypto::ECPrivateKey::CreateFromEncryptedPrivateKeyInfo(
+          ServerBoundCertService::kEPKIPassword,
+          ToVector(key), ToVector(public_key_info)));
+  scoped_ptr<crypto::ECSignatureCreator> creator(
+      crypto::ECSignatureCreator::Create(private_key.get()));
+  creator->Sign(reinterpret_cast<const unsigned char *>(secret.data()),
+                secret.length(), &der_signature);
+
+  std::vector<uint8> proof_vector;
+  if (!creator->DecodeSignature(der_signature, &proof_vector)) {
+    NOTREACHED();
+    return ERR_UNEXPECTED;
+  }
+
+  credential->slot = slot;
+  credential->certs.push_back(public_key.as_string());
+  credential->proof.assign(proof_vector.begin(), proof_vector.end());
+  return OK;
+}
+
+// static
+std::string SpdyCredentialBuilder::GetCredentialSecret(
+    const std::string& tls_unique) {
+  const char prefix[] = "SPDY CREDENTIAL ChannelID\0client -> server";
+  std::string secret(prefix, arraysize(prefix));
+  secret.append(tls_unique);
+
+  return secret;
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_credential_builder.h b/src/net/spdy/spdy_credential_builder.h
new file mode 100644
index 0000000..278d239
--- /dev/null
+++ b/src/net/spdy/spdy_credential_builder.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_SPDY_SPDY_CREDENTIAL_BUILDER_H_
+#define NET_SPDY_SPDY_CREDENTIAL_BUILDER_H_
+
+#include <string>
+
+#include "net/base/net_export.h"
+#include "net/base/ssl_client_cert_type.h"
+
+namespace net {
+
+class SSLClientSocket;
+struct SpdyCredential;
+
+// This class provides facilities for building the various fields of
+// SPDY CREDENTIAL frames.
+class NET_EXPORT_PRIVATE SpdyCredentialBuilder {
+ public:
+  static int Build(const std::string& tls_unique,
+                   SSLClientCertType type,
+                   const std::string& key,
+                   const std::string& cert,
+                   size_t slot,
+                   SpdyCredential* credential);
+
+ private:
+  friend class SpdyCredentialBuilderTest;
+
+  // Returns the secret data to be signed as part of a credential frame.
+  static std::string GetCredentialSecret(const std::string& tls_unique);
+};
+
+}  // namespace net
+
+#endif  // NET_SPDY_SPDY_CREDENTIAL_BUILDER_H_
diff --git a/src/net/spdy/spdy_credential_builder_unittest.cc b/src/net/spdy/spdy_credential_builder_unittest.cc
new file mode 100644
index 0000000..067fdb7
--- /dev/null
+++ b/src/net/spdy/spdy_credential_builder_unittest.cc
@@ -0,0 +1,171 @@
+// Copyright (c) 2012 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_credential_builder.h"
+
+#include "base/threading/sequenced_worker_pool.h"
+#include "crypto/ec_signature_creator.h"
+#include "crypto/ec_private_key.h"
+#include "net/base/asn1_util.h"
+#include "net/base/default_server_bound_cert_store.h"
+#include "net/base/server_bound_cert_service.h"
+#include "net/spdy/spdy_test_util_spdy3.h"
+#include "testing/platform_test.h"
+
+using namespace net::test_spdy3;
+
+namespace net {
+
+namespace {
+
+const static size_t kSlot = 2;
+const static char kSecretPrefix[] =
+    "SPDY CREDENTIAL ChannelID\0client -> server";
+
+void CreateCertAndKey(std::string* cert, std::string* key) {
+  // TODO(rch): Share this code with ServerBoundCertServiceTest.
+  scoped_refptr<base::SequencedWorkerPool> sequenced_worker_pool =
+      new base::SequencedWorkerPool(1, "CreateCertAndKey");
+  scoped_ptr<ServerBoundCertService> server_bound_cert_service(
+      new ServerBoundCertService(new DefaultServerBoundCertStore(NULL),
+                                 sequenced_worker_pool));
+
+  TestCompletionCallback callback;
+  std::vector<uint8> requested_cert_types;
+  requested_cert_types.push_back(CLIENT_CERT_ECDSA_SIGN);
+  SSLClientCertType cert_type;
+  ServerBoundCertService::RequestHandle request_handle;
+  int rv = server_bound_cert_service->GetDomainBoundCert(
+      "https://www.google.com", requested_cert_types, &cert_type, key, cert,
+      callback.callback(), &request_handle);
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  EXPECT_EQ(OK, callback.WaitForResult());
+  EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, cert_type);
+
+  sequenced_worker_pool->Shutdown();
+}
+
+}  // namespace
+
+class SpdyCredentialBuilderTest : public testing::Test {
+ public:
+  SpdyCredentialBuilderTest() {
+    CreateCertAndKey(&cert_, &key_);
+  }
+
+ protected:
+  int BuildWithType(SSLClientCertType type) {
+    return SpdyCredentialBuilder::Build(
+        MockClientSocket::kTlsUnique, type, key_, cert_, kSlot, &credential_);
+  }
+
+  int Build() {
+    return BuildWithType(CLIENT_CERT_ECDSA_SIGN);
+  }
+
+  std::string GetCredentialSecret() {
+    return SpdyCredentialBuilder::GetCredentialSecret(
+        MockClientSocket::kTlsUnique);
+  }
+
+  std::string cert_;
+  std::string key_;
+  SpdyCredential credential_;
+  MockECSignatureCreatorFactory ec_signature_creator_factory_;
+};
+
+// http://crbug.com/142833, http://crbug.com/140991. The following tests fail
+// with OpenSSL due to the unimplemented ec_private_key_openssl.cc.
+#if defined(USE_OPENSSL)
+#define MAYBE_GetCredentialSecret DISABLED_GetCredentialSecret
+#else
+#define MAYBE_GetCredentialSecret GetCredentialSecret
+#endif
+
+TEST_F(SpdyCredentialBuilderTest, MAYBE_GetCredentialSecret) {
+  std::string secret_str(kSecretPrefix, arraysize(kSecretPrefix));
+  secret_str.append(MockClientSocket::kTlsUnique);
+
+  EXPECT_EQ(secret_str, GetCredentialSecret());
+}
+
+#if defined(USE_OPENSSL)
+#define MAYBE_SucceedsWithECDSACert DISABLED_SucceedsWithECDSACert
+#else
+#define MAYBE_SucceedsWithECDSACert SucceedsWithECDSACert
+#endif
+
+TEST_F(SpdyCredentialBuilderTest, MAYBE_SucceedsWithECDSACert) {
+  EXPECT_EQ(OK, BuildWithType(CLIENT_CERT_ECDSA_SIGN));
+}
+
+#if defined(USE_OPENSSL)
+#define MAYBE_FailsWithRSACert DISABLED_FailsWithRSACert
+#else
+#define MAYBE_FailsWithRSACert FailsWithRSACert
+#endif
+
+TEST_F(SpdyCredentialBuilderTest, MAYBE_FailsWithRSACert) {
+  EXPECT_EQ(ERR_BAD_SSL_CLIENT_AUTH_CERT,
+            BuildWithType(CLIENT_CERT_RSA_SIGN));
+}
+
+#if defined(USE_OPENSSL)
+#define MAYBE_SetsSlotCorrectly DISABLED_SetsSlotCorrectly
+#else
+#define MAYBE_SetsSlotCorrectly SetsSlotCorrectly
+#endif
+
+TEST_F(SpdyCredentialBuilderTest, MAYBE_SetsSlotCorrectly) {
+  ASSERT_EQ(OK, Build());
+  EXPECT_EQ(kSlot, credential_.slot);
+}
+
+#if defined(USE_OPENSSL)
+#define MAYBE_SetsCertCorrectly DISABLED_SetsCertCorrectly
+#else
+#define MAYBE_SetsCertCorrectly SetsCertCorrectly
+#endif
+
+TEST_F(SpdyCredentialBuilderTest, MAYBE_SetsCertCorrectly) {
+  ASSERT_EQ(OK, Build());
+  base::StringPiece spki;
+  ASSERT_TRUE(asn1::ExtractSPKIFromDERCert(cert_, &spki));
+  base::StringPiece spk;
+  ASSERT_TRUE(asn1::ExtractSubjectPublicKeyFromSPKI(spki, &spk));
+  EXPECT_EQ(1u, credential_.certs.size());
+  EXPECT_EQ(0, (int)spk[0]);
+  EXPECT_EQ(4, (int)spk[1]);
+  EXPECT_EQ(spk.substr(2, spk.length()).as_string(), credential_.certs[0]);
+}
+
+#if defined(USE_OPENSSL)
+#define MAYBE_SetsProofCorrectly DISABLED_SetsProofCorrectly
+#else
+#define MAYBE_SetsProofCorrectly SetsProofCorrectly
+#endif
+
+TEST_F(SpdyCredentialBuilderTest, MAYBE_SetsProofCorrectly) {
+  ASSERT_EQ(OK, Build());
+  base::StringPiece spki;
+  ASSERT_TRUE(asn1::ExtractSPKIFromDERCert(cert_, &spki));
+  std::vector<uint8> spki_data(spki.data(),
+                               spki.data() + spki.size());
+  std::vector<uint8> key_data(key_.data(),
+                              key_.data() + key_.length());
+  std::vector<uint8> proof_data;
+  scoped_ptr<crypto::ECPrivateKey> private_key(
+      crypto::ECPrivateKey::CreateFromEncryptedPrivateKeyInfo(
+          ServerBoundCertService::kEPKIPassword, key_data, spki_data));
+  scoped_ptr<crypto::ECSignatureCreator> creator(
+      crypto::ECSignatureCreator::Create(private_key.get()));
+  std::string secret = GetCredentialSecret();
+  creator->Sign(reinterpret_cast<const unsigned char *>(secret.data()),
+                secret.length(), &proof_data);
+
+  std::string proof(proof_data.begin(), proof_data.end());
+  EXPECT_EQ(proof, credential_.proof);
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_credential_state.cc b/src/net/spdy/spdy_credential_state.cc
new file mode 100644
index 0000000..943d732
--- /dev/null
+++ b/src/net/spdy/spdy_credential_state.cc
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 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_credential_state.h"
+
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "net/base/server_bound_cert_service.h"
+
+namespace net {
+
+namespace {
+
+GURL GetCanonicalOrigin(const GURL& url) {
+  std::string domain =
+      ServerBoundCertService::GetDomainForHost(url.host());
+  DCHECK(!domain.empty());
+  if (domain == url.host())
+    return url.GetOrigin();
+  return GURL(url.scheme() + "://" + domain + ":" + url.port());
+}
+
+}  // namespace
+
+const size_t SpdyCredentialState::kDefaultNumSlots = 8;
+const size_t SpdyCredentialState::kNoEntry = 0;
+
+SpdyCredentialState::SpdyCredentialState(size_t num_slots)
+  : slots_(num_slots),
+    last_added_(-1) {}
+
+SpdyCredentialState::~SpdyCredentialState() {}
+
+bool SpdyCredentialState::HasCredential(const GURL& origin) const {
+  return FindCredentialSlot(origin) != kNoEntry;
+}
+
+size_t SpdyCredentialState::SetHasCredential(const GURL& origin) {
+  size_t i = FindCredentialSlot(origin);
+  if (i != kNoEntry)
+    return i;
+  // Add the new entry at the next index following the index of the last
+  // entry added, or at index 0 if the last added index is the last index.
+  if (last_added_ + 1 == slots_.size()) {
+    last_added_ = 0;
+  } else {
+    last_added_++;
+  }
+  slots_[last_added_] = GetCanonicalOrigin(origin);
+  return last_added_ + 1;
+}
+
+size_t SpdyCredentialState::FindCredentialSlot(const GURL& origin) const {
+  GURL url = GetCanonicalOrigin(origin);
+  for (size_t i = 0; i < slots_.size(); i++) {
+    if (url == slots_[i])
+      return i + 1;
+  }
+  return kNoEntry;
+}
+
+void SpdyCredentialState::Resize(size_t size) {
+  slots_.resize(size);
+  if (last_added_ >= slots_.size())
+    last_added_ = slots_.size() - 1;
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_credential_state.h b/src/net/spdy/spdy_credential_state.h
new file mode 100644
index 0000000..1460eac
--- /dev/null
+++ b/src/net/spdy/spdy_credential_state.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_SPDY_SPDY_CREDENTIAL_STATE_H_
+#define NET_SPDY_SPDY_CREDENTIAL_STATE_H_
+
+#include <vector>
+
+#include "googleurl/src/gurl.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// A class for tracking the credentials associated with a SPDY session.
+class NET_EXPORT_PRIVATE SpdyCredentialState {
+ public:
+  explicit SpdyCredentialState(size_t num_slots);
+  ~SpdyCredentialState();
+
+  // Changes the number of credentials being tracked.  If the new size is
+  // larger, then empty slots will be added to the end.  If the new size is
+  // smaller than the current size, then the extra slots will be truncated
+  // from the end.
+  void Resize(size_t size);
+
+  // Returns the one-based index in |slots_| for |url| or kNoEntry, if no entry
+  // for |url| exists.
+  size_t FindCredentialSlot(const GURL& url) const;
+
+  // Returns true if there is a credential associated with |url|.
+  bool HasCredential(const GURL& url) const;
+
+  // Adds the new credentials to be associated with all origins matching
+  // |url|.  If there is space, then it will add in the first available
+  // position.  Otherwise, an existing credential will be evicted.  Returns
+  // the slot in which this domain was added.
+  size_t SetHasCredential(const GURL& url);
+
+  // This value is defined as the default initial value in the SPDY spec unless
+  // otherwise negotiated via SETTINGS.
+  static const size_t kDefaultNumSlots;
+
+  // Sentinel value to be returned by FindCredentialSlot when no entry exists.
+  static const size_t kNoEntry;
+
+ private:
+  // Vector of origins that have credentials.
+  std::vector<GURL> slots_;
+  // Index of the last origin added to |slots_|.
+  size_t last_added_;
+};
+
+}  // namespace net
+
+#endif  // NET_SPDY_SPDY_CREDENTIAL_STATE_H_
diff --git a/src/net/spdy/spdy_credential_state_unittest.cc b/src/net/spdy/spdy_credential_state_unittest.cc
new file mode 100644
index 0000000..b512921
--- /dev/null
+++ b/src/net/spdy/spdy_credential_state_unittest.cc
@@ -0,0 +1,108 @@
+// Copyright (c) 2012 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_credential_state.h"
+
+#include "net/base/host_port_pair.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+class SpdyCredentialStateTest : public PlatformTest {
+ public:
+  SpdyCredentialStateTest()
+    : state_(4),
+      origin1_("https://1.com"),
+      origin2_("https://2.com"),
+      origin3_("https://3.com"),
+      origin4_("https://4.com"),
+      origin5_("https://5.com"),
+      origin6_("https://6.com"),
+      origin11_("https://11.com"),
+      host1_("https://www.1.com:443") {
+  }
+
+ protected:
+  SpdyCredentialState state_;
+  const GURL origin1_;
+  const GURL origin2_;
+  const GURL origin3_;
+  const GURL origin4_;
+  const GURL origin5_;
+  const GURL origin6_;
+  const GURL origin11_;
+  const GURL host1_;
+
+  DISALLOW_COPY_AND_ASSIGN(SpdyCredentialStateTest);
+};
+
+TEST_F(SpdyCredentialStateTest, HasCredentialReturnsFalseWhenEmpty) {
+  EXPECT_FALSE(state_.HasCredential(origin1_));
+  EXPECT_FALSE(state_.HasCredential(origin2_));
+  EXPECT_FALSE(state_.HasCredential(origin3_));
+}
+
+TEST_F(SpdyCredentialStateTest, HasCredentialReturnsTrueWhenAdded) {
+  state_.SetHasCredential(origin1_);
+  EXPECT_TRUE(state_.HasCredential(origin1_));
+  EXPECT_TRUE(state_.HasCredential(host1_));
+  EXPECT_FALSE(state_.HasCredential(origin11_));
+  EXPECT_FALSE(state_.HasCredential(origin2_));
+  EXPECT_FALSE(state_.HasCredential(origin3_));
+}
+
+TEST_F(SpdyCredentialStateTest, SetCredentialAddsToEndOfList) {
+  EXPECT_EQ(1u, (state_.SetHasCredential(origin1_)));
+  EXPECT_EQ(2u, (state_.SetHasCredential(origin2_)));
+  EXPECT_EQ(3u, (state_.SetHasCredential(origin3_)));
+}
+
+TEST_F(SpdyCredentialStateTest, SetReturnsPositionIfAlreadyInList) {
+  EXPECT_EQ(1u, (state_.SetHasCredential(origin1_)));
+  EXPECT_EQ(2u, (state_.SetHasCredential(origin2_)));
+  EXPECT_EQ(1u, (state_.SetHasCredential(origin1_)));
+  EXPECT_EQ(2u, (state_.SetHasCredential(origin2_)));
+}
+
+TEST_F(SpdyCredentialStateTest, SetReplacesOldestElementWhenFull) {
+  EXPECT_EQ(1u, (state_.SetHasCredential(origin1_)));
+  EXPECT_EQ(2u, (state_.SetHasCredential(origin2_)));
+  EXPECT_EQ(3u, (state_.SetHasCredential(origin3_)));
+  EXPECT_EQ(4u, (state_.SetHasCredential(origin4_)));
+  EXPECT_EQ(1u, (state_.SetHasCredential(origin5_)));
+  EXPECT_EQ(2u, (state_.SetHasCredential(origin6_)));
+  EXPECT_EQ(3u, (state_.SetHasCredential(origin1_)));
+  EXPECT_EQ(4u, (state_.SetHasCredential(origin2_)));
+}
+
+TEST_F(SpdyCredentialStateTest, ResizeAddsEmptySpaceAtEnd) {
+  EXPECT_EQ(1u, (state_.SetHasCredential(origin1_)));
+  EXPECT_EQ(2u, (state_.SetHasCredential(origin2_)));
+  EXPECT_EQ(3u, (state_.SetHasCredential(origin3_)));
+  EXPECT_EQ(4u, (state_.SetHasCredential(origin4_)));
+  state_.Resize(6);
+  EXPECT_EQ(1u, (state_.SetHasCredential(origin1_)));
+  EXPECT_EQ(2u, (state_.SetHasCredential(origin2_)));
+  EXPECT_EQ(3u, (state_.SetHasCredential(origin3_)));
+  EXPECT_EQ(4u, (state_.SetHasCredential(origin4_)));
+  EXPECT_EQ(5u, (state_.SetHasCredential(origin5_)));
+  EXPECT_EQ(6u, (state_.SetHasCredential(origin6_)));
+}
+
+TEST_F(SpdyCredentialStateTest, ResizeTrunatesFromEnd) {
+  EXPECT_EQ(1u, (state_.SetHasCredential(origin1_)));
+  EXPECT_EQ(2u, (state_.SetHasCredential(origin2_)));
+  EXPECT_EQ(3u, (state_.SetHasCredential(origin3_)));
+  EXPECT_EQ(4u, (state_.SetHasCredential(origin4_)));
+  state_.Resize(2);
+  EXPECT_TRUE(state_.HasCredential(origin1_));
+  EXPECT_TRUE(state_.HasCredential(origin2_));
+  EXPECT_FALSE(state_.HasCredential(origin3_));
+  EXPECT_FALSE(state_.HasCredential(origin4_));
+  EXPECT_EQ(1u, (state_.SetHasCredential(origin5_)));
+  EXPECT_EQ(2u, (state_.SetHasCredential(origin6_)));
+}
+
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_frame_builder.cc b/src/net/spdy/spdy_frame_builder.cc
new file mode 100644
index 0000000..04bf62c
--- /dev/null
+++ b/src/net/spdy/spdy_frame_builder.cc
@@ -0,0 +1,111 @@
+// Copyright (c) 2012 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 <limits>
+
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+
+namespace {
+
+// Creates a FlagsAndLength.
+FlagsAndLength CreateFlagsAndLength(SpdyControlFlags flags, size_t length) {
+  DCHECK_EQ(0u, length & ~static_cast<size_t>(kLengthMask));
+  FlagsAndLength flags_length;
+  flags_length.length_ = htonl(static_cast<uint32>(length));
+  DCHECK_EQ(0, flags & ~kControlFlagsMask);
+  flags_length.flags_[0] = flags;
+  return flags_length;
+}
+
+}  // namespace
+
+SpdyFrameBuilder::SpdyFrameBuilder(SpdyControlType type,
+                                   SpdyControlFlags flags,
+                                   int spdy_version,
+                                   size_t size)
+    : buffer_(new char[size]),
+      capacity_(size),
+      length_(0) {
+  FlagsAndLength flags_length = CreateFlagsAndLength(
+      flags, size - SpdyFrame::kHeaderSize);
+  WriteUInt16(kControlFlagMask | spdy_version);
+  WriteUInt16(type);
+  WriteBytes(&flags_length, sizeof(flags_length));
+}
+
+SpdyFrameBuilder::SpdyFrameBuilder(SpdyStreamId stream_id,
+                                   SpdyDataFlags flags,
+                                   size_t size)
+    : buffer_(new char[size]),
+      capacity_(size),
+      length_(0) {
+  DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+  WriteUInt32(stream_id);
+  size_t length = size - SpdyFrame::kHeaderSize;
+  DCHECK_EQ(0u, length & ~static_cast<size_t>(kLengthMask));
+  FlagsAndLength flags_length;
+  flags_length.length_ = htonl(length);
+  DCHECK_EQ(0, flags & ~kDataFlagsMask);
+  flags_length.flags_[0] = flags;
+  WriteBytes(&flags_length, sizeof(flags_length));
+}
+
+SpdyFrameBuilder::~SpdyFrameBuilder() {
+  delete[] buffer_;
+}
+
+char* SpdyFrameBuilder::BeginWrite(size_t length) {
+  size_t offset = length_;
+  size_t needed_size = length_ + length;
+  if (needed_size > capacity_)
+    return NULL;
+
+#ifdef ARCH_CPU_64_BITS
+  DCHECK_LE(length, std::numeric_limits<uint32>::max());
+#endif
+
+  return buffer_ + offset;
+}
+
+void SpdyFrameBuilder::EndWrite(char* dest, int length) {
+}
+
+bool SpdyFrameBuilder::WriteBytes(const void* data, uint32 data_len) {
+  if (data_len > kLengthMask) {
+    return false;
+  }
+
+  char* dest = BeginWrite(data_len);
+  if (!dest)
+    return false;
+
+  memcpy(dest, data, data_len);
+
+  EndWrite(dest, data_len);
+  length_ += data_len;
+  return true;
+}
+
+bool SpdyFrameBuilder::WriteString(const std::string& value) {
+  if (value.size() > 0xffff)
+    return false;
+
+  if (!WriteUInt16(static_cast<int>(value.size())))
+    return false;
+
+  return WriteBytes(value.data(), static_cast<uint16>(value.size()));
+}
+
+bool SpdyFrameBuilder::WriteStringPiece32(const base::StringPiece& value) {
+  if (!WriteUInt32(value.size())) {
+    return false;
+  }
+
+  return WriteBytes(value.data(), value.size());
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_frame_builder.h b/src/net/spdy/spdy_frame_builder.h
new file mode 100644
index 0000000..affdbe4
--- /dev/null
+++ b/src/net/spdy/spdy_frame_builder.h
@@ -0,0 +1,132 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_SPDY_SPDY_FRAME_BUILDER_H_
+#define NET_SPDY_SPDY_FRAME_BUILDER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/string_piece.h"
+#include "base/sys_byteorder.h"
+#include "net/base/net_export.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+
+// This class provides facilities for basic binary value packing and unpacking
+// into Spdy frames.
+//
+// The SpdyFrameBuilder supports appending primitive values (int, string, etc)
+// to a frame instance.  The SpdyFrameBuilder grows its internal memory buffer
+// dynamically to hold the sequence of primitive values.   The internal memory
+// buffer is exposed as the "data" of the SpdyFrameBuilder.
+class NET_EXPORT_PRIVATE SpdyFrameBuilder {
+ public:
+  ~SpdyFrameBuilder();
+
+  // Initializes a SpdyFrameBuilder with a buffer of given size,
+  // populate with a SPDY control frame header based on
+  // |type|, |flags|, and |spdy_version|.
+  SpdyFrameBuilder(SpdyControlType type, SpdyControlFlags flags,
+                   int spdy_version, size_t size);
+
+  // Initiailizes a SpdyFrameBuilder with a buffer of given size,
+  // populated with a SPDY data frame header based on
+  // |stream_id| and |flags|.
+  SpdyFrameBuilder(SpdyStreamId stream_id, SpdyDataFlags flags,  size_t size);
+
+  // Returns the size of the SpdyFrameBuilder's data.
+  size_t length() const { return length_; }
+
+  // Takes the buffer from the SpdyFrameBuilder.
+  SpdyFrame* take() {
+    SpdyFrame* rv = new SpdyFrame(buffer_, true);
+    buffer_ = NULL;
+    capacity_ = 0;
+    length_ = 0;
+    return rv;
+  }
+
+  // Methods for adding to the payload.  These values are appended to the end
+  // of the SpdyFrameBuilder payload. Note - binary integers are converted from
+  // host to network form.
+  bool WriteUInt8(uint8 value) {
+    return WriteBytes(&value, sizeof(value));
+  }
+  bool WriteUInt16(uint16 value) {
+    value = htons(value);
+    return WriteBytes(&value, sizeof(value));
+  }
+  bool WriteUInt32(uint32 value) {
+    value = htonl(value);
+    return WriteBytes(&value, sizeof(value));
+  }
+  // TODO(hkhalil) Rename to WriteStringPiece16().
+  bool WriteString(const std::string& value);
+  bool WriteStringPiece32(const base::StringPiece& value);
+  bool WriteBytes(const void* data, uint32 data_len);
+
+  // Write an integer to a particular offset in the data buffer.
+  bool WriteUInt32ToOffset(int offset, uint32 value) {
+    value = htonl(value);
+    return WriteBytesToOffset(offset, &value, sizeof(value));
+  }
+
+  // Write to a particular offset in the data buffer.
+  bool WriteBytesToOffset(int offset, const void* data, uint32 data_len) {
+    if (offset + data_len > length_)
+      return false;
+    char *ptr = buffer_ + offset;
+    memcpy(ptr, data, data_len);
+    return true;
+  }
+
+  // Returns true if the given iterator could point to data with the given
+  // length. If there is no room for the given data before the end of the
+  // payload, returns false.
+  bool IteratorHasRoomFor(const void* iter, int len) const {
+    const char* end_of_region = reinterpret_cast<const char*>(iter) + len;
+    if (len < 0 ||
+        iter < buffer_ ||
+        iter > end_of_payload() ||
+        iter > end_of_region ||
+        end_of_region > end_of_payload())
+      return false;
+
+    // Watch out for overflow in pointer calculation, which wraps.
+    return (iter <= end_of_region) && (end_of_region <= end_of_payload());
+  }
+
+ protected:
+  size_t capacity() const {
+    return capacity_;
+  }
+
+  const char* end_of_payload() const { return buffer_ + length_; }
+
+  // Completes the write operation by padding the data with NULL bytes until it
+  // is padded. Should be paired with BeginWrite, but it does not necessarily
+  // have to be called after the data is written.
+  void EndWrite(char* dest, int length);
+
+  // Moves the iterator by the given number of bytes.
+  static void UpdateIter(void** iter, int bytes) {
+    *iter = static_cast<char*>(*iter) + bytes;
+  }
+
+ private:
+  // Returns the location that the data should be written at, or NULL if there
+  // is not enough room. Call EndWrite with the returned offset and the given
+  // length to pad out for the next write.
+  char* BeginWrite(size_t length);
+
+  char* buffer_;
+  size_t capacity_;  // Allocation size of payload (or -1 if buffer is const).
+  size_t length_;    // current length of the buffer
+};
+
+}  // namespace net
+
+#endif  // NET_SPDY_SPDY_FRAME_BUILDER_H_
diff --git a/src/net/spdy/spdy_frame_reader.cc b/src/net/spdy/spdy_frame_reader.cc
new file mode 100644
index 0000000..57bf9eb
--- /dev/null
+++ b/src/net/spdy/spdy_frame_reader.cc
@@ -0,0 +1,126 @@
+// Copyright (c) 2012 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 <limits>
+
+#include "base/sys_byteorder.h"
+#include "net/spdy/spdy_frame_reader.h"
+
+namespace net {
+
+SpdyFrameReader::SpdyFrameReader(const char* data, const size_t len)
+    : data_(data),
+      len_(len),
+      ofs_(0) {
+}
+
+bool SpdyFrameReader::ReadUInt16(uint16* result) {
+  // Make sure that we have the whole uint16.
+  if (!CanRead(2)) {
+    OnFailure();
+    return false;
+  }
+
+  // Read into result.
+  *result = ntohs(*(reinterpret_cast<const uint16*>(data_ + ofs_)));
+
+  // Iterate.
+  ofs_ += 2;
+
+  return true;
+}
+
+bool SpdyFrameReader::ReadUInt32(uint32* result) {
+  // Make sure that we have the whole uint32.
+  if (!CanRead(4)) {
+    OnFailure();
+    return false;
+  }
+
+  // Read into result.
+  *result = ntohl(*(reinterpret_cast<const uint32*>(data_ + ofs_)));
+
+  // Iterate.
+  ofs_ += 4;
+
+  return true;
+}
+
+bool SpdyFrameReader::ReadStringPiece16(base::StringPiece* result) {
+  // Read resultant length.
+  uint16 result_len;
+  if (!ReadUInt16(&result_len)) {
+    // OnFailure() already called.
+    return false;
+  }
+
+  // Make sure that we have the whole string.
+  if (!CanRead(result_len)) {
+    OnFailure();
+    return false;
+  }
+
+  // Set result.
+  result->set(data_ + ofs_, result_len);
+
+  // Iterate.
+  ofs_ += result_len;
+
+  return true;
+}
+
+bool SpdyFrameReader::ReadStringPiece32(base::StringPiece* result) {
+  // Read resultant length.
+  uint32 result_len;
+  if (!ReadUInt32(&result_len)) {
+    // OnFailure() already called.
+    return false;
+  }
+
+  // Make sure that we have the whole string.
+  if (!CanRead(result_len)) {
+    OnFailure();
+    return false;
+  }
+
+  // Set result.
+  result->set(data_ + ofs_, result_len);
+
+  // Iterate.
+  ofs_ += result_len;
+
+  return true;
+}
+
+bool SpdyFrameReader::ReadBytes(void* result, size_t size) {
+  // Make sure that we have enough data to read.
+  if (!CanRead(size)) {
+    OnFailure();
+    return false;
+  }
+
+  // Read into result.
+  memcpy(result, data_ + ofs_, size);
+
+  // Iterate.
+  ofs_ += size;
+
+  return true;
+}
+
+bool SpdyFrameReader::IsDoneReading() const {
+  return len_ == ofs_;
+}
+
+bool SpdyFrameReader::CanRead(size_t bytes) const {
+  return bytes <= (len_ - ofs_);
+}
+
+void SpdyFrameReader::OnFailure() {
+  // Set our iterator to the end of the buffer so that further reads fail
+  // immediately.
+  ofs_ = len_;
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_frame_reader.h b/src/net/spdy/spdy_frame_reader.h
new file mode 100644
index 0000000..6db7a08
--- /dev/null
+++ b/src/net/spdy/spdy_frame_reader.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_SPDY_SPDY_FRAME_READER_H_
+#define NET_SPDY_SPDY_FRAME_READER_H_
+
+#include "base/basictypes.h"
+#include "base/string_piece.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Used for reading SPDY frames. Though there isn't really anything terribly
+// SPDY-specific here, it's a helper class that's useful when doing SPDY
+// framing.
+//
+// To use, simply construct a SpdyFramerReader using the underlying buffer that
+// you'd like to read fields from, then call one of the Read*() methods to
+// actually do some reading.
+//
+// This class keeps an internal iterator to keep track of what's already been
+// read and each successive Read*() call automatically increments said iterator
+// on success. On failure, internal state of the SpdyFrameReader should not be
+// trusted and it is up to the caller to throw away the failed instance and
+// handle the error as appropriate. None of the Read*() methods should ever be
+// called after failure, as they will also fail immediately.
+class NET_EXPORT_PRIVATE SpdyFrameReader {
+ public:
+  // Caller must provide an underlying buffer to work on.
+  SpdyFrameReader(const char* data, const size_t len);
+
+  // Empty destructor.
+  ~SpdyFrameReader() {}
+
+  // Reads a 16-bit unsigned integer into the given output parameter.
+  // Forwards the internal iterater on success.
+  // Returns true on success, false otherwise.
+  bool ReadUInt16(uint16* result);
+
+  // Reads a 32-bit unsigned integer into the given output parameter.
+  // Forwards the internal iterater on success.
+  // Returns true on success, false otherwise.
+  bool ReadUInt32(uint32* result);
+
+  // Reads a string prefixed with 16-bit length into the given output parameter.
+  //
+  // NOTE: Does not copy but rather references strings in the underlying buffer.
+  // This should be kept in mind when handling memory management!
+  //
+  // Forwards the internal iterater on success.
+  // Returns true on success, false otherwise.
+  bool ReadStringPiece16(base::StringPiece* result);
+
+  // Reads a string prefixed with 32-bit length into the given output parameter.
+  //
+  // NOTE: Does not copy but rather references strings in the underlying buffer.
+  // This should be kept in mind when handling memory management!
+  //
+  // Forwards the internal iterater on success.
+  // Returns true on success, false otherwise.
+  bool ReadStringPiece32(base::StringPiece* result);
+
+  // Reads a given number of bytes into the given buffer. The buffer
+  // must be of adequate size.
+  // Forwards the internal iterater on success.
+  // Returns true on success, false otherwise.
+  bool ReadBytes(void* result, size_t size);
+
+  // Returns true if the entirety of the underlying buffer has been read via
+  // Read*() calls.
+  bool IsDoneReading() const;
+
+ private:
+  // Returns true if the underlying buffer has enough room to read the given
+  // amount of bytes.
+  bool CanRead(size_t bytes) const;
+
+  // To be called when a read fails for any reason.
+  void OnFailure();
+
+  // The data buffer that we're reading from.
+  const char* data_;
+
+  // The length of the data buffer that we're reading from.
+  const size_t len_;
+
+  // The location of the next read from our data buffer.
+  size_t ofs_;
+};
+
+}  // namespace net
+
+#endif  // NET_SPDY_SPDY_FRAME_READER_H_
diff --git a/src/net/spdy/spdy_frame_reader_test.cc b/src/net/spdy/spdy_frame_reader_test.cc
new file mode 100644
index 0000000..c72e5be
--- /dev/null
+++ b/src/net/spdy/spdy_frame_reader_test.cc
@@ -0,0 +1,249 @@
+// Copyright (c) 2012 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 "base/sys_byteorder.h"
+#include "net/spdy/spdy_frame_reader.h"
+#include "testing/platform_test.h"
+
+namespace net {
+
+TEST(SpdyFrameReaderTest, ReadUInt16) {
+  // Frame data in network byte order.
+  const uint16 kFrameData[] = {
+    htons(1), htons(1<<15),
+  };
+
+  SpdyFrameReader frame_reader(reinterpret_cast<const char *>(kFrameData),
+                               arraysize(kFrameData) * sizeof(uint16));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  uint16 uint16_val;
+  EXPECT_TRUE(frame_reader.ReadUInt16(&uint16_val));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+  EXPECT_EQ(1, uint16_val);
+
+  EXPECT_TRUE(frame_reader.ReadUInt16(&uint16_val));
+  EXPECT_TRUE(frame_reader.IsDoneReading());
+  EXPECT_EQ(1<<15, uint16_val);
+}
+
+TEST(SpdyFrameReaderTest, ReadUInt32) {
+  // Frame data in network byte order.
+  const uint32 kFrameData[] = {
+    htonl(1), htonl(1<<31),
+  };
+
+  SpdyFrameReader frame_reader(reinterpret_cast<const char *>(kFrameData),
+                               arraysize(kFrameData) * sizeof(uint32));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  uint32 uint32_val;
+  EXPECT_TRUE(frame_reader.ReadUInt32(&uint32_val));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+  EXPECT_EQ(1u, uint32_val);
+
+  EXPECT_TRUE(frame_reader.ReadUInt32(&uint32_val));
+  EXPECT_TRUE(frame_reader.IsDoneReading());
+  EXPECT_EQ(1u<<31, uint32_val);
+}
+
+TEST(SpdyFrameReaderTest, ReadStringPiece16) {
+  // Frame data in network byte order.
+  const char kFrameData[] = {
+    0x00, 0x02,  // uint16(2)
+    0x48, 0x69,  // "Hi"
+    0x00, 0x10,  // uint16(16)
+    0x54, 0x65, 0x73, 0x74,
+    0x69, 0x6e, 0x67, 0x2c,
+    0x20, 0x31, 0x2c, 0x20,
+    0x32, 0x2c, 0x20, 0x33,  // "Testing, 1, 2, 3"
+  };
+
+  SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  base::StringPiece stringpiece_val;
+  EXPECT_TRUE(frame_reader.ReadStringPiece16(&stringpiece_val));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+  EXPECT_EQ(0, stringpiece_val.compare("Hi"));
+
+  EXPECT_TRUE(frame_reader.ReadStringPiece16(&stringpiece_val));
+  EXPECT_TRUE(frame_reader.IsDoneReading());
+  EXPECT_EQ(0, stringpiece_val.compare("Testing, 1, 2, 3"));
+}
+
+TEST(SpdyFrameReaderTest, ReadStringPiece32) {
+  // Frame data in network byte order.
+  const char kFrameData[] = {
+    0x00, 0x00, 0x00, 0x03,  // uint32(3)
+    0x66, 0x6f, 0x6f,  // "foo"
+    0x00, 0x00, 0x00, 0x10,  // uint32(16)
+    0x54, 0x65, 0x73, 0x74,
+    0x69, 0x6e, 0x67, 0x2c,
+    0x20, 0x34, 0x2c, 0x20,
+    0x35, 0x2c, 0x20, 0x36,  // "Testing, 4, 5, 6"
+  };
+
+  SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  base::StringPiece stringpiece_val;
+  EXPECT_TRUE(frame_reader.ReadStringPiece32(&stringpiece_val));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+  EXPECT_EQ(0, stringpiece_val.compare("foo"));
+
+  EXPECT_TRUE(frame_reader.ReadStringPiece32(&stringpiece_val));
+  EXPECT_TRUE(frame_reader.IsDoneReading());
+  EXPECT_EQ(0, stringpiece_val.compare("Testing, 4, 5, 6"));
+}
+
+TEST(SpdyFrameReaderTest, ReadUInt16WithBufferTooSmall) {
+  // Frame data in network byte order.
+  const char kFrameData[] = {
+    0x00,  // part of a uint16
+  };
+
+  SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  uint16 uint16_val;
+  EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+TEST(SpdyFrameReaderTest, ReadUInt32WithBufferTooSmall) {
+  // Frame data in network byte order.
+  const char kFrameData[] = {
+    0x00, 0x00, 0x00,  // part of a uint32
+  };
+
+  SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  uint32 uint32_val;
+  EXPECT_FALSE(frame_reader.ReadUInt32(&uint32_val));
+
+  // Also make sure that trying to read a uint16, which technically could work,
+  // fails immediately due to previously encountered failed read.
+  uint16 uint16_val;
+  EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+// Tests ReadStringPiece16() with a buffer too small to fit the entire string.
+TEST(SpdyFrameReaderTest, ReadStringPiece16WithBufferTooSmall) {
+  // Frame data in network byte order.
+  const char kFrameData[] = {
+    0x00, 0x03,  // uint16(3)
+    0x48, 0x69,  // "Hi"
+  };
+
+  SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  base::StringPiece stringpiece_val;
+  EXPECT_FALSE(frame_reader.ReadStringPiece16(&stringpiece_val));
+
+  // Also make sure that trying to read a uint16, which technically could work,
+  // fails immediately due to previously encountered failed read.
+  uint16 uint16_val;
+  EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+// Tests ReadStringPiece16() with a buffer too small even to fit the length.
+TEST(SpdyFrameReaderTest, ReadStringPiece16WithBufferWayTooSmall) {
+  // Frame data in network byte order.
+  const char kFrameData[] = {
+    0x00,  // part of a uint16
+  };
+
+  SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  base::StringPiece stringpiece_val;
+  EXPECT_FALSE(frame_reader.ReadStringPiece16(&stringpiece_val));
+
+  // Also make sure that trying to read a uint16, which technically could work,
+  // fails immediately due to previously encountered failed read.
+  uint16 uint16_val;
+  EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+// Tests ReadStringPiece32() with a buffer too small to fit the entire string.
+TEST(SpdyFrameReaderTest, ReadStringPiece32WithBufferTooSmall) {
+  // Frame data in network byte order.
+  const char kFrameData[] = {
+    0x00, 0x00, 0x00, 0x03,  // uint32(3)
+    0x48, 0x69,  // "Hi"
+  };
+
+  SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  base::StringPiece stringpiece_val;
+  EXPECT_FALSE(frame_reader.ReadStringPiece32(&stringpiece_val));
+
+  // Also make sure that trying to read a uint16, which technically could work,
+  // fails immediately due to previously encountered failed read.
+  uint16 uint16_val;
+  EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+// Tests ReadStringPiece32() with a buffer too small even to fit the length.
+TEST(SpdyFrameReaderTest, ReadStringPiece32WithBufferWayTooSmall) {
+  // Frame data in network byte order.
+  const char kFrameData[] = {
+    0x00, 0x00, 0x00,  // part of a uint32
+  };
+
+  SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  base::StringPiece stringpiece_val;
+  EXPECT_FALSE(frame_reader.ReadStringPiece32(&stringpiece_val));
+
+  // Also make sure that trying to read a uint16, which technically could work,
+  // fails immediately due to previously encountered failed read.
+  uint16 uint16_val;
+  EXPECT_FALSE(frame_reader.ReadUInt16(&uint16_val));
+}
+
+TEST(SpdyFrameReaderTest, ReadBytes) {
+  // Frame data in network byte order.
+  const char kFrameData[] = {
+    0x66, 0x6f, 0x6f,  // "foo"
+    0x48, 0x69,  // "Hi"
+  };
+
+  SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  char dest1[3] = {};
+  EXPECT_TRUE(frame_reader.ReadBytes(&dest1, arraysize(dest1)));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+  EXPECT_EQ("foo", base::StringPiece(dest1, arraysize(dest1)));
+
+  char dest2[2] = {};
+  EXPECT_TRUE(frame_reader.ReadBytes(&dest2, arraysize(dest2)));
+  EXPECT_TRUE(frame_reader.IsDoneReading());
+  EXPECT_EQ("Hi", base::StringPiece(dest2, arraysize(dest2)));
+}
+
+TEST(SpdyFrameReaderTest, ReadBytesWithBufferTooSmall) {
+  // Frame data in network byte order.
+  const char kFrameData[] = {
+    0x01,
+  };
+
+  SpdyFrameReader frame_reader(kFrameData, arraysize(kFrameData));
+  EXPECT_FALSE(frame_reader.IsDoneReading());
+
+  char dest[arraysize(kFrameData) + 2] = {};
+  EXPECT_FALSE(frame_reader.ReadBytes(&dest, arraysize(kFrameData) + 1));
+  EXPECT_STREQ("", dest);
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_framer.cc b/src/net/spdy/spdy_framer.cc
new file mode 100644
index 0000000..0d9049d
--- /dev/null
+++ b/src/net/spdy/spdy_framer.cc
@@ -0,0 +1,1881 @@
+// Copyright (c) 2012 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.
+
+// TODO(rtenhove) clean up frame buffer size calculations so that we aren't
+// constantly adding and subtracting header sizes; this is ugly and error-
+// prone.
+
+#include "net/spdy/spdy_framer.h"
+
+#include "base/lazy_instance.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/stats_counters.h"
+#include "base/third_party/valgrind/memcheck.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_frame_reader.h"
+#include "net/spdy/spdy_bitmasks.h"
+
+#if defined(USE_SYSTEM_ZLIB)
+#include <zlib.h>
+#else
+#include "third_party/zlib/zlib.h"
+#endif
+
+using std::vector;
+
+namespace net {
+
+namespace {
+
+// Compute the id of our dictionary so that we know we're using the
+// right one when asked for it.
+uLong CalculateDictionaryId(const char* dictionary,
+                            const size_t dictionary_size) {
+  uLong initial_value = adler32(0L, Z_NULL, 0);
+  return adler32(initial_value,
+                 reinterpret_cast<const Bytef*>(dictionary),
+                 dictionary_size);
+}
+
+struct DictionaryIds {
+  DictionaryIds()
+    : v2_dictionary_id(CalculateDictionaryId(kV2Dictionary, kV2DictionarySize)),
+      v3_dictionary_id(CalculateDictionaryId(kV3Dictionary, kV3DictionarySize))
+  {}
+  const uLong v2_dictionary_id;
+  const uLong v3_dictionary_id;
+};
+
+// Adler ID for the SPDY header compressor dictionaries. Note that they are
+// initialized lazily to avoid static initializers.
+base::LazyInstance<DictionaryIds>::Leaky g_dictionary_ids;
+
+}  // namespace
+
+const int SpdyFramer::kMinSpdyVersion = 2;
+const int SpdyFramer::kMaxSpdyVersion = 3;
+const SpdyStreamId SpdyFramer::kInvalidStream = -1;
+const size_t SpdyFramer::kHeaderDataChunkMaxSize = 1024;
+const size_t SpdyFramer::kControlFrameBufferSize =
+    sizeof(SpdySynStreamControlFrameBlock);
+const size_t SpdyFramer::kMaxControlFrameSize = 16 * 1024;
+
+#ifdef DEBUG_SPDY_STATE_CHANGES
+#define CHANGE_STATE(newstate)                                  \
+  do {                                                          \
+    LOG(INFO) << "Changing state from: "                        \
+              << StateToString(state_)                          \
+              << " to " << StateToString(newstate) << "\n";     \
+    DCHECK(state_ != SPDY_ERROR);                               \
+    DCHECK_EQ(previous_state_, state_);                         \
+    previous_state_ = state_;                                   \
+    state_ = newstate;                                          \
+  } while (false)
+#else
+#define CHANGE_STATE(newstate)                                  \
+  do {                                                          \
+    DCHECK(state_ != SPDY_ERROR);                               \
+    DCHECK_EQ(previous_state_, state_);                         \
+    previous_state_ = state_;                                   \
+    state_ = newstate;                                          \
+  } while (false)
+#endif
+
+SettingsFlagsAndId SettingsFlagsAndId::FromWireFormat(int version,
+                                                      uint32 wire) {
+  if (version < 3) {
+    ConvertFlagsAndIdForSpdy2(&wire);
+  }
+  return SettingsFlagsAndId(ntohl(wire) >> 24, ntohl(wire) & 0x00ffffff);
+}
+
+SettingsFlagsAndId::SettingsFlagsAndId(uint8 flags, uint32 id)
+    : flags_(flags), id_(id & 0x00ffffff) {
+  DCHECK_GT(1u << 24, id) << "SPDY setting ID too large.";
+}
+
+uint32 SettingsFlagsAndId::GetWireFormat(int version) const {
+  uint32 wire = htonl(id_ & 0x00ffffff) | htonl(flags_ << 24);
+  if (version < 3) {
+    ConvertFlagsAndIdForSpdy2(&wire);
+  }
+  return wire;
+}
+
+// SPDY 2 had a bug in it with respect to byte ordering of id/flags field.
+// This method is used to preserve buggy behavior and works on both
+// little-endian and big-endian hosts.
+// This method is also bidirectional (can be used to translate SPDY 2 to SPDY 3
+// as well as vice versa).
+void SettingsFlagsAndId::ConvertFlagsAndIdForSpdy2(uint32* val) {
+    uint8* wire_array = reinterpret_cast<uint8*>(val);
+    std::swap(wire_array[0], wire_array[3]);
+    std::swap(wire_array[1], wire_array[2]);
+}
+
+SpdyCredential::SpdyCredential() : slot(0) {}
+SpdyCredential::~SpdyCredential() {}
+
+SpdyFramer::SpdyFramer(int version)
+    : state_(SPDY_RESET),
+      previous_state_(SPDY_RESET),
+      error_code_(SPDY_NO_ERROR),
+      remaining_data_(0),
+      remaining_control_payload_(0),
+      remaining_control_header_(0),
+      current_frame_buffer_(new char[kControlFrameBufferSize]),
+      current_frame_len_(0),
+      enable_compression_(true),
+      visitor_(NULL),
+      display_protocol_("SPDY"),
+      spdy_version_(version),
+      syn_frame_processed_(false),
+      probable_http_response_(false) {
+  DCHECK_GE(kMaxSpdyVersion, version);
+  DCHECK_LE(kMinSpdyVersion, version);
+}
+
+SpdyFramer::~SpdyFramer() {
+  if (header_compressor_.get()) {
+    deflateEnd(header_compressor_.get());
+  }
+  if (header_decompressor_.get()) {
+    inflateEnd(header_decompressor_.get());
+  }
+}
+
+void SpdyFramer::Reset() {
+  state_ = SPDY_RESET;
+  previous_state_ = SPDY_RESET;
+  error_code_ = SPDY_NO_ERROR;
+  remaining_data_ = 0;
+  remaining_control_payload_ = 0;
+  remaining_control_header_ = 0;
+  current_frame_len_ = 0;
+  settings_scratch_.Reset();
+}
+
+const char* SpdyFramer::StateToString(int state) {
+  switch (state) {
+    case SPDY_ERROR:
+      return "ERROR";
+    case SPDY_DONE:
+      return "DONE";
+    case SPDY_AUTO_RESET:
+      return "AUTO_RESET";
+    case SPDY_RESET:
+      return "RESET";
+    case SPDY_READING_COMMON_HEADER:
+      return "READING_COMMON_HEADER";
+    case SPDY_CONTROL_FRAME_PAYLOAD:
+      return "CONTROL_FRAME_PAYLOAD";
+    case SPDY_IGNORE_REMAINING_PAYLOAD:
+      return "IGNORE_REMAINING_PAYLOAD";
+    case SPDY_FORWARD_STREAM_FRAME:
+      return "FORWARD_STREAM_FRAME";
+    case SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK:
+      return "SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK";
+    case SPDY_CONTROL_FRAME_HEADER_BLOCK:
+      return "SPDY_CONTROL_FRAME_HEADER_BLOCK";
+    case SPDY_CREDENTIAL_FRAME_PAYLOAD:
+      return "SPDY_CREDENTIAL_FRAME_PAYLOAD";
+    case SPDY_SETTINGS_FRAME_PAYLOAD:
+      return "SPDY_SETTINGS_FRAME_PAYLOAD";
+  }
+  return "UNKNOWN_STATE";
+}
+
+void SpdyFramer::set_error(SpdyError error) {
+  DCHECK(visitor_);
+  error_code_ = error;
+  CHANGE_STATE(SPDY_ERROR);
+  visitor_->OnError(this);
+}
+
+const char* SpdyFramer::ErrorCodeToString(int error_code) {
+  switch (error_code) {
+    case SPDY_NO_ERROR:
+      return "NO_ERROR";
+    case SPDY_INVALID_CONTROL_FRAME:
+      return "INVALID_CONTROL_FRAME";
+    case SPDY_CONTROL_PAYLOAD_TOO_LARGE:
+      return "CONTROL_PAYLOAD_TOO_LARGE";
+    case SPDY_ZLIB_INIT_FAILURE:
+      return "ZLIB_INIT_FAILURE";
+    case SPDY_UNSUPPORTED_VERSION:
+      return "UNSUPPORTED_VERSION";
+    case SPDY_DECOMPRESS_FAILURE:
+      return "DECOMPRESS_FAILURE";
+    case SPDY_COMPRESS_FAILURE:
+      return "COMPRESS_FAILURE";
+    case SPDY_INVALID_DATA_FRAME_FLAGS:
+      return "SPDY_INVALID_DATA_FRAME_FLAGS";
+  }
+  return "UNKNOWN_ERROR";
+}
+
+const char* SpdyFramer::StatusCodeToString(int status_code) {
+  switch (status_code) {
+    case INVALID:
+      return "INVALID";
+    case PROTOCOL_ERROR:
+      return "PROTOCOL_ERROR";
+    case INVALID_STREAM:
+      return "INVALID_STREAM";
+    case REFUSED_STREAM:
+      return "REFUSED_STREAM";
+    case UNSUPPORTED_VERSION:
+      return "UNSUPPORTED_VERSION";
+    case CANCEL:
+      return "CANCEL";
+    case INTERNAL_ERROR:
+      return "INTERNAL_ERROR";
+    case FLOW_CONTROL_ERROR:
+      return "FLOW_CONTROL_ERROR";
+    case STREAM_IN_USE:
+      return "STREAM_IN_USE";
+    case STREAM_ALREADY_CLOSED:
+      return "STREAM_ALREADY_CLOSED";
+    case INVALID_CREDENTIALS:
+      return "INVALID_CREDENTIALS";
+    case FRAME_TOO_LARGE:
+      return "FRAME_TOO_LARGE";
+  }
+  return "UNKNOWN_STATUS";
+}
+
+const char* SpdyFramer::ControlTypeToString(SpdyControlType type) {
+  switch (type) {
+    case SYN_STREAM:
+      return "SYN_STREAM";
+    case SYN_REPLY:
+      return "SYN_REPLY";
+    case RST_STREAM:
+      return "RST_STREAM";
+    case SETTINGS:
+      return "SETTINGS";
+    case NOOP:
+      return "NOOP";
+    case PING:
+      return "PING";
+    case GOAWAY:
+      return "GOAWAY";
+    case HEADERS:
+      return "HEADERS";
+    case WINDOW_UPDATE:
+      return "WINDOW_UPDATE";
+    case CREDENTIAL:
+      return "CREDENTIAL";
+    case NUM_CONTROL_FRAME_TYPES:
+      break;
+  }
+  return "UNKNOWN_CONTROL_TYPE";
+}
+
+size_t SpdyFramer::ProcessInput(const char* data, size_t len) {
+  DCHECK(visitor_);
+  DCHECK(data);
+
+  size_t original_len = len;
+  do {
+    previous_state_ = state_;
+    switch (state_) {
+      case SPDY_ERROR:
+      case SPDY_DONE:
+        goto bottom;
+
+      case SPDY_AUTO_RESET:
+      case SPDY_RESET:
+        Reset();
+        if (len > 0) {
+          CHANGE_STATE(SPDY_READING_COMMON_HEADER);
+        }
+        break;
+
+      case SPDY_READING_COMMON_HEADER: {
+        size_t bytes_read = ProcessCommonHeader(data, len);
+        len -= bytes_read;
+        data += bytes_read;
+        break;
+      }
+
+      case SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK: {
+        // Control frames that contain header blocks (SYN_STREAM, SYN_REPLY,
+        // HEADERS) take a different path through the state machine - they
+        // will go:
+        //   1. SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK
+        //   2. SPDY_CONTROL_FRAME_HEADER_BLOCK
+        //
+        // SETTINGS frames take a slightly modified route:
+        //   1. SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK
+        //   2. SPDY_SETTINGS_FRAME_PAYLOAD
+        //
+        //  All other control frames will use the alternate route directly to
+        //  SPDY_CONTROL_FRAME_PAYLOAD
+        int bytes_read = ProcessControlFrameBeforeHeaderBlock(data, len);
+        len -= bytes_read;
+        data += bytes_read;
+        break;
+      }
+
+      case SPDY_SETTINGS_FRAME_PAYLOAD: {
+        int bytes_read = ProcessSettingsFramePayload(data, len);
+        len -= bytes_read;
+        data += bytes_read;
+        break;
+      }
+
+      case SPDY_CONTROL_FRAME_HEADER_BLOCK: {
+        int bytes_read = ProcessControlFrameHeaderBlock(data, len);
+        len -= bytes_read;
+        data += bytes_read;
+        break;
+      }
+
+      case SPDY_CREDENTIAL_FRAME_PAYLOAD: {
+        size_t bytes_read = ProcessCredentialFramePayload(data, len);
+        len -= bytes_read;
+        data += bytes_read;
+        break;
+      }
+
+      case SPDY_CONTROL_FRAME_PAYLOAD: {
+        size_t bytes_read = ProcessControlFramePayload(data, len);
+        len -= bytes_read;
+        data += bytes_read;
+        break;
+      }
+
+      case SPDY_IGNORE_REMAINING_PAYLOAD:
+        // control frame has too-large payload
+        // intentional fallthrough
+      case SPDY_FORWARD_STREAM_FRAME: {
+        size_t bytes_read = ProcessDataFramePayload(data, len);
+        len -= bytes_read;
+        data += bytes_read;
+        break;
+      }
+      default:
+        LOG(DFATAL) << "Invalid value for " << display_protocol_
+                    << " framer state: " << state_;
+        // This ensures that we don't infinite-loop if state_ gets an
+        // invalid value somehow, such as due to a SpdyFramer getting deleted
+        // from a callback it calls.
+        goto bottom;
+    }
+  } while (state_ != previous_state_);
+ bottom:
+  DCHECK(len == 0 || state_ == SPDY_ERROR);
+  if (current_frame_len_ == 0 &&
+      remaining_data_ == 0 &&
+      remaining_control_payload_ == 0 &&
+      remaining_control_header_ == 0) {
+    DCHECK(state_ == SPDY_RESET || state_ == SPDY_ERROR)
+        << "State: " << StateToString(state_);
+  }
+
+  return original_len - len;
+}
+
+size_t SpdyFramer::ProcessCommonHeader(const char* data, size_t len) {
+  // This should only be called when we're in the SPDY_READING_COMMON_HEADER
+  // state.
+  DCHECK_EQ(state_, SPDY_READING_COMMON_HEADER);
+
+  size_t original_len = len;
+  SpdyFrame current_frame(current_frame_buffer_.get(), false);
+
+  // Update current frame buffer as needed.
+  if (current_frame_len_ < SpdyFrame::kHeaderSize) {
+    size_t bytes_desired = SpdyFrame::kHeaderSize - current_frame_len_;
+    UpdateCurrentFrameBuffer(&data, &len, bytes_desired);
+  }
+
+  if (current_frame_len_ < SpdyFrame::kHeaderSize) {
+    // TODO(rch): remove this empty block
+    // Do nothing.
+  } else {
+    remaining_data_ = current_frame.length();
+
+    // This is just a sanity check for help debugging early frame errors.
+    if (remaining_data_ > 1000000u) {
+      // The strncmp for 5 is safe because we only hit this point if we
+      // have SpdyFrame::kHeaderSize (8) bytes
+      if (!syn_frame_processed_ &&
+          strncmp(current_frame_buffer_.get(), "HTTP/", 5) == 0) {
+        LOG(WARNING) << "Unexpected HTTP response to spdy request";
+        probable_http_response_ = true;
+      } else {
+        LOG(WARNING) << "Unexpectedly large frame.  " << display_protocol_
+                     << " session is likely corrupt.";
+      }
+    }
+
+    // if we're here, then we have the common header all received.
+    if (!current_frame.is_control_frame()) {
+      SpdyDataFrame data_frame(current_frame_buffer_.get(), false);
+      visitor_->OnDataFrameHeader(&data_frame);
+
+      if (current_frame.length() > 0) {
+        CHANGE_STATE(SPDY_FORWARD_STREAM_FRAME);
+      } else {
+        // Empty data frame.
+        if (current_frame.flags() & DATA_FLAG_FIN) {
+          visitor_->OnStreamFrameData(data_frame.stream_id(),
+                                      NULL, 0, DATA_FLAG_FIN);
+        }
+        CHANGE_STATE(SPDY_AUTO_RESET);
+      }
+    } else {
+      ProcessControlFrameHeader();
+    }
+  }
+  return original_len - len;
+}
+
+void SpdyFramer::ProcessControlFrameHeader() {
+  DCHECK_EQ(SPDY_NO_ERROR, error_code_);
+  DCHECK_LE(static_cast<size_t>(SpdyFrame::kHeaderSize), current_frame_len_);
+  SpdyControlFrame current_control_frame(current_frame_buffer_.get(), false);
+
+  // We check version before we check validity: version can never be 'invalid',
+  // it can only be unsupported.
+  if (current_control_frame.version() != spdy_version_) {
+    DLOG(INFO) << "Unsupported SPDY version " << current_control_frame.version()
+               << " (expected " << spdy_version_ << ")";
+    set_error(SPDY_UNSUPPORTED_VERSION);
+    return;
+  }
+
+  // Next up, check to see if we have valid data.  This should be after version
+  // checking (otherwise if the the type were out of bounds due to a version
+  // upgrade we would misclassify the error) and before checking the type
+  // (type can definitely be out of bounds)
+  if (!current_control_frame.AppearsToBeAValidControlFrame()) {
+    set_error(SPDY_INVALID_CONTROL_FRAME);
+    return;
+  }
+
+  if (current_control_frame.type() == NOOP) {
+    DLOG(INFO) << "NOOP control frame found. Ignoring.";
+    CHANGE_STATE(SPDY_AUTO_RESET);
+    return;
+  }
+
+  // Do some sanity checking on the control frame sizes.
+  switch (current_control_frame.type()) {
+    case SYN_STREAM:
+      if (current_control_frame.length() <
+          SpdySynStreamControlFrame::size() - SpdyControlFrame::kHeaderSize)
+        set_error(SPDY_INVALID_CONTROL_FRAME);
+      break;
+    case SYN_REPLY:
+      if (current_control_frame.length() <
+          SpdySynReplyControlFrame::size() - SpdyControlFrame::kHeaderSize)
+        set_error(SPDY_INVALID_CONTROL_FRAME);
+      break;
+    case RST_STREAM:
+      if (current_control_frame.length() !=
+          SpdyRstStreamControlFrame::size() - SpdyFrame::kHeaderSize)
+        set_error(SPDY_INVALID_CONTROL_FRAME);
+      break;
+    case SETTINGS:
+      // Make sure that we have an integral number of 8-byte key/value pairs,
+      // plus a 4-byte length field.
+      if (current_control_frame.length() <
+          SpdySettingsControlFrame::size() - SpdyControlFrame::kHeaderSize ||
+          (current_control_frame.length() % 8 != 4)) {
+        DLOG(WARNING) << "Invalid length for SETTINGS frame: "
+                      << current_control_frame.length();
+        set_error(SPDY_INVALID_CONTROL_FRAME);
+      }
+      break;
+    case GOAWAY:
+      {
+        // SPDY 2 GOAWAY frames are 4 bytes smaller than in SPDY 3. We account
+        // for this difference via a separate offset variable, since
+        // SpdyGoAwayControlFrame::size() returns the SPDY 3 size.
+        const size_t goaway_offset = (protocol_version() < 3) ? 4 : 0;
+        if (current_control_frame.length() + goaway_offset !=
+            SpdyGoAwayControlFrame::size() - SpdyFrame::kHeaderSize)
+          set_error(SPDY_INVALID_CONTROL_FRAME);
+        break;
+      }
+    case HEADERS:
+      if (current_control_frame.length() <
+          SpdyHeadersControlFrame::size() - SpdyControlFrame::kHeaderSize)
+        set_error(SPDY_INVALID_CONTROL_FRAME);
+      break;
+    case WINDOW_UPDATE:
+      if (current_control_frame.length() !=
+          SpdyWindowUpdateControlFrame::size() -
+          SpdyControlFrame::kHeaderSize)
+        set_error(SPDY_INVALID_CONTROL_FRAME);
+      break;
+    case PING:
+      if (current_control_frame.length() !=
+          SpdyPingControlFrame::size() - SpdyControlFrame::kHeaderSize)
+        set_error(SPDY_INVALID_CONTROL_FRAME);
+      break;
+    case CREDENTIAL:
+      if (current_control_frame.length() <
+          SpdyCredentialControlFrame::size() - SpdyControlFrame::kHeaderSize)
+        set_error(SPDY_INVALID_CONTROL_FRAME);
+      break;
+    default:
+      LOG(WARNING) << "Valid " << display_protocol_
+                   << " control frame with unhandled type: "
+                   << current_control_frame.type();
+      DLOG(FATAL);
+      set_error(SPDY_INVALID_CONTROL_FRAME);
+      break;
+  }
+
+  if (state_ == SPDY_ERROR) {
+    return;
+  }
+
+  remaining_control_payload_ = current_control_frame.length();
+  const size_t total_frame_size =
+      remaining_control_payload_ + SpdyFrame::kHeaderSize;
+  if (total_frame_size > kMaxControlFrameSize) {
+    DLOG(WARNING) << "Received control frame with way too big of a payload: "
+                  << total_frame_size;
+    set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE);
+    return;
+  }
+
+  if (current_control_frame.type() == CREDENTIAL) {
+    CHANGE_STATE(SPDY_CREDENTIAL_FRAME_PAYLOAD);
+    return;
+  }
+
+  // Determine the frame size without variable-length data.
+  int32 frame_size_without_variable_data;
+  switch (current_control_frame.type()) {
+    case SYN_STREAM:
+      syn_frame_processed_ = true;
+      frame_size_without_variable_data = SpdySynStreamControlFrame::size();
+      break;
+    case SYN_REPLY:
+      syn_frame_processed_ = true;
+      frame_size_without_variable_data = SpdySynReplyControlFrame::size();
+      // SPDY 2 had two bytes of unused space preceeding payload.
+      if (spdy_version_ < 3) {
+        frame_size_without_variable_data += 2;
+      }
+      break;
+    case HEADERS:
+      frame_size_without_variable_data = SpdyHeadersControlFrame::size();
+      // SPDY 2 had two bytes of unused space preceeding payload.
+      if (spdy_version_ < 3) {
+        frame_size_without_variable_data += 2;
+      }
+      break;
+    case SETTINGS:
+      frame_size_without_variable_data = SpdySettingsControlFrame::size();
+      break;
+    default:
+      frame_size_without_variable_data = -1;
+      break;
+  }
+
+  if ((frame_size_without_variable_data == -1) &&
+      (total_frame_size > kControlFrameBufferSize)) {
+    // We should already be in an error state. Double-check.
+    DCHECK_EQ(SPDY_ERROR, state_);
+    if (state_ != SPDY_ERROR) {
+      LOG(DFATAL) << display_protocol_
+                  << " control frame buffer too small for fixed-length frame.";
+      set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE);
+    }
+    return;
+  }
+  if (frame_size_without_variable_data > 0) {
+    // We have a control frame with a header block. We need to parse the
+    // remainder of the control frame's header before we can parse the header
+    // block. The start of the header block varies with the control type.
+    DCHECK_GE(frame_size_without_variable_data,
+              static_cast<int32>(current_frame_len_));
+    remaining_control_header_ = frame_size_without_variable_data -
+        current_frame_len_;
+    remaining_control_payload_ += SpdyFrame::kHeaderSize -
+        frame_size_without_variable_data;
+    CHANGE_STATE(SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK);
+    return;
+  }
+
+  CHANGE_STATE(SPDY_CONTROL_FRAME_PAYLOAD);
+}
+
+size_t SpdyFramer::UpdateCurrentFrameBuffer(const char** data, size_t* len,
+                                            size_t max_bytes) {
+  size_t bytes_to_read = std::min(*len, max_bytes);
+  DCHECK_GE(kControlFrameBufferSize, current_frame_len_ + bytes_to_read);
+  memcpy(current_frame_buffer_.get() + current_frame_len_,
+         *data,
+         bytes_to_read);
+  current_frame_len_ += bytes_to_read;
+  *data += bytes_to_read;
+  *len -= bytes_to_read;
+  return bytes_to_read;
+}
+
+size_t SpdyFramer::GetSerializedLength(const SpdyHeaderBlock* headers) const {
+  const size_t num_name_value_pairs_size
+      = (spdy_version_ < 3) ? sizeof(uint16) : sizeof(uint32);
+  const size_t length_of_name_size = num_name_value_pairs_size;
+  const size_t length_of_value_size = num_name_value_pairs_size;
+
+  size_t total_length = num_name_value_pairs_size;
+  for (SpdyHeaderBlock::const_iterator it = headers->begin();
+       it != headers->end();
+       ++it) {
+    // We add space for the length of the name and the length of the value as
+    // well as the length of the name and the length of the value.
+    total_length += length_of_name_size + it->first.size() +
+                    length_of_value_size + it->second.size();
+  }
+  return total_length;
+}
+
+void SpdyFramer::WriteHeaderBlock(SpdyFrameBuilder* frame,
+                                  const SpdyHeaderBlock* headers) const {
+  if (spdy_version_ < 3) {
+    frame->WriteUInt16(headers->size());  // Number of headers.
+  } else {
+    frame->WriteUInt32(headers->size());  // Number of headers.
+  }
+  SpdyHeaderBlock::const_iterator it;
+  for (it = headers->begin(); it != headers->end(); ++it) {
+    bool wrote_header;
+    if (spdy_version_ < 3) {
+      wrote_header = frame->WriteString(it->first);
+      wrote_header &= frame->WriteString(it->second);
+    } else {
+      wrote_header = frame->WriteStringPiece32(it->first);
+      wrote_header &= frame->WriteStringPiece32(it->second);
+    }
+    DCHECK(wrote_header);
+  }
+}
+
+// TODO(phajdan.jr): Clean up after we no longer need
+// to workaround http://crbug.com/139744.
+#if !defined(USE_SYSTEM_ZLIB)
+
+// These constants are used by zlib to differentiate between normal data and
+// cookie data. Cookie data is handled specially by zlib when compressing.
+enum ZDataClass {
+  // kZStandardData is compressed normally, save that it will never match
+  // against any other class of data in the window.
+  kZStandardData = Z_CLASS_STANDARD,
+  // kZCookieData is compressed in its own Huffman blocks and only matches in
+  // its entirety and only against other kZCookieData blocks. Any matches must
+  // be preceeded by a kZStandardData byte, or a semicolon to prevent matching
+  // a suffix. It's assumed that kZCookieData ends in a semicolon to prevent
+  // prefix matches.
+  kZCookieData = Z_CLASS_COOKIE,
+  // kZHuffmanOnlyData is only Huffman compressed - no matches are performed
+  // against the window.
+  kZHuffmanOnlyData = Z_CLASS_HUFFMAN_ONLY,
+};
+
+// WriteZ writes |data| to the deflate context |out|. WriteZ will flush as
+// needed when switching between classes of data.
+static void WriteZ(const base::StringPiece& data,
+                   ZDataClass clas,
+                   z_stream* out) {
+  int rv;
+
+  // If we are switching from standard to non-standard data then we need to end
+  // the current Huffman context to avoid it leaking between them.
+  if (out->clas == kZStandardData &&
+      clas != kZStandardData) {
+    out->avail_in = 0;
+    rv = deflate(out, Z_PARTIAL_FLUSH);
+    DCHECK_EQ(Z_OK, rv);
+    DCHECK_EQ(0u, out->avail_in);
+    DCHECK_LT(0u, out->avail_out);
+  }
+
+  out->next_in = reinterpret_cast<Bytef*>(const_cast<char*>(data.data()));
+  out->avail_in = data.size();
+  out->clas = clas;
+  if (clas == kZStandardData) {
+    rv = deflate(out, Z_NO_FLUSH);
+  } else {
+    rv = deflate(out, Z_PARTIAL_FLUSH);
+  }
+  DCHECK_EQ(Z_OK, rv);
+  DCHECK_EQ(0u, out->avail_in);
+  DCHECK_LT(0u, out->avail_out);
+}
+
+// WriteLengthZ writes |n| as a |length|-byte, big-endian number to |out|.
+static void WriteLengthZ(size_t n,
+                         unsigned length,
+                         ZDataClass clas,
+                         z_stream* out) {
+  char buf[4];
+  DCHECK_LE(length, sizeof(buf));
+  for (unsigned i = 1; i <= length; i++) {
+    buf[length - i] = n;
+    n >>= 8;
+  }
+  WriteZ(base::StringPiece(buf, length), clas, out);
+}
+
+// WriteHeaderBlockToZ serialises |headers| to the deflate context |z| in a
+// manner that resists the length of the compressed data from compromising
+// cookie data.
+void SpdyFramer::WriteHeaderBlockToZ(const SpdyHeaderBlock* headers,
+                                     z_stream* z) const {
+  unsigned length_length = 4;
+  if (spdy_version_ < 3)
+    length_length = 2;
+
+  WriteLengthZ(headers->size(), length_length, kZStandardData, z);
+
+  std::map<std::string, std::string>::const_iterator it;
+  for (it = headers->begin(); it != headers->end(); ++it) {
+    WriteLengthZ(it->first.size(), length_length, kZStandardData, z);
+    WriteZ(it->first, kZStandardData, z);
+
+    if (it->first == "cookie") {
+      // We require the cookie values (save for the last) to end with a
+      // semicolon and (save for the first) to start with a space. This is
+      // typically the format that we are given them in but we reserialize them
+      // to be sure.
+
+      std::vector<base::StringPiece> cookie_values;
+      size_t cookie_length = 0;
+      base::StringPiece cookie_data(it->second);
+
+      for (;;) {
+        while (!cookie_data.empty() &&
+               (cookie_data[0] == ' ' || cookie_data[0] == '\t')) {
+          cookie_data.remove_prefix(1);
+        }
+        if (cookie_data.empty())
+          break;
+
+        size_t i;
+        for (i = 0; i < cookie_data.size(); i++) {
+          if (cookie_data[i] == ';')
+            break;
+        }
+        if (i < cookie_data.size()) {
+          cookie_values.push_back(cookie_data.substr(0, i));
+          cookie_length += i + 2 /* semicolon and space */;
+          cookie_data.remove_prefix(i + 1);
+        } else {
+          cookie_values.push_back(cookie_data);
+          cookie_length += cookie_data.size();
+          cookie_data.remove_prefix(i);
+        }
+      }
+
+      WriteLengthZ(cookie_length, length_length, kZStandardData, z);
+      for (size_t i = 0; i < cookie_values.size(); i++) {
+        std::string cookie;
+        // Since zlib will only back-reference complete cookies, a cookie that
+        // is currently last (and so doesn't have a trailing semicolon) won't
+        // match if it's later in a non-final position. The same is true of
+        // the first cookie.
+        if (i == 0 && cookie_values.size() == 1) {
+          cookie = cookie_values[i].as_string();
+        } else if (i == 0) {
+          cookie = cookie_values[i].as_string() + ";";
+        } else if (i < cookie_values.size() - 1) {
+          cookie = " " + cookie_values[i].as_string() + ";";
+        } else {
+          cookie = " " + cookie_values[i].as_string();
+        }
+        WriteZ(cookie, kZCookieData, z);
+      }
+    } else if (it->first == "accept" ||
+               it->first == "accept-charset" ||
+               it->first == "accept-encoding" ||
+               it->first == "accept-language" ||
+               it->first == "host" ||
+               it->first == "version" ||
+               it->first == "method" ||
+               it->first == "scheme" ||
+               it->first == ":host" ||
+               it->first == ":version" ||
+               it->first == ":method" ||
+               it->first == ":scheme" ||
+               it->first == "user-agent") {
+      WriteLengthZ(it->second.size(), length_length, kZStandardData, z);
+      WriteZ(it->second, kZStandardData, z);
+    } else {
+      // Non-whitelisted headers are Huffman compressed in their own block, but
+      // don't match against the window.
+      WriteLengthZ(it->second.size(), length_length, kZStandardData, z);
+      WriteZ(it->second, kZHuffmanOnlyData, z);
+    }
+  }
+
+  z->avail_in = 0;
+  int rv = deflate(z, Z_SYNC_FLUSH);
+  DCHECK_EQ(Z_OK, rv);
+  z->clas = kZStandardData;
+}
+#endif  // !defined(USE_SYSTEM_ZLIB)
+
+size_t SpdyFramer::ProcessControlFrameBeforeHeaderBlock(const char* data,
+                                                        size_t len) {
+  DCHECK_EQ(SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK, state_);
+  size_t original_len = len;
+
+  if (remaining_control_header_ > 0) {
+    size_t bytes_read = UpdateCurrentFrameBuffer(&data, &len,
+                                                 remaining_control_header_);
+    remaining_control_header_ -= bytes_read;
+  }
+
+  if (remaining_control_header_ == 0) {
+    SpdyControlFrame control_frame(current_frame_buffer_.get(), false);
+    switch (control_frame.type()) {
+      case SYN_STREAM:
+        {
+          SpdySynStreamControlFrame* syn_stream_frame =
+              reinterpret_cast<SpdySynStreamControlFrame*>(&control_frame);
+          // TODO(hkhalil): Check that invalid flag bits are unset?
+          visitor_->OnSynStream(
+              syn_stream_frame->stream_id(),
+              syn_stream_frame->associated_stream_id(),
+              syn_stream_frame->priority(),
+              syn_stream_frame->credential_slot(),
+              (syn_stream_frame->flags() & CONTROL_FLAG_FIN) != 0,
+              (syn_stream_frame->flags() & CONTROL_FLAG_UNIDIRECTIONAL) != 0);
+        }
+        CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK);
+        break;
+      case SYN_REPLY:
+        {
+          SpdySynReplyControlFrame* syn_reply_frame =
+              reinterpret_cast<SpdySynReplyControlFrame*>(&control_frame);
+          visitor_->OnSynReply(
+              syn_reply_frame->stream_id(),
+              (syn_reply_frame->flags() & CONTROL_FLAG_FIN) != 0);
+        }
+        CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK);
+        break;
+      case HEADERS:
+        {
+          SpdyHeadersControlFrame* headers_frame =
+              reinterpret_cast<SpdyHeadersControlFrame*>(&control_frame);
+          visitor_->OnHeaders(
+              headers_frame->stream_id(),
+              (headers_frame->flags() & CONTROL_FLAG_FIN) != 0);
+        }
+        CHANGE_STATE(SPDY_CONTROL_FRAME_HEADER_BLOCK);
+        break;
+      case SETTINGS:
+        CHANGE_STATE(SPDY_SETTINGS_FRAME_PAYLOAD);
+        break;
+      default:
+        DCHECK(false);
+    }
+  }
+  return original_len - len;
+}
+
+// Does not buffer the control payload. Instead, either passes directly to the
+// visitor or decompresses and then passes directly to the visitor, via
+// IncrementallyDeliverControlFrameHeaderData() or
+// IncrementallyDecompressControlFrameHeaderData() respectively.
+size_t SpdyFramer::ProcessControlFrameHeaderBlock(const char* data,
+                                                  size_t data_len) {
+  DCHECK_EQ(SPDY_CONTROL_FRAME_HEADER_BLOCK, state_);
+  SpdyControlFrame control_frame(current_frame_buffer_.get(), false);
+
+  bool processed_successfully = true;
+  DCHECK(control_frame.type() == SYN_STREAM ||
+         control_frame.type() == SYN_REPLY ||
+         control_frame.type() == HEADERS);
+  size_t process_bytes = std::min(data_len, remaining_control_payload_);
+  if (process_bytes > 0) {
+    if (enable_compression_) {
+      processed_successfully = IncrementallyDecompressControlFrameHeaderData(
+          &control_frame, data, process_bytes);
+    } else {
+      processed_successfully = IncrementallyDeliverControlFrameHeaderData(
+          &control_frame, data, process_bytes);
+    }
+
+    remaining_control_payload_ -= process_bytes;
+    remaining_data_ -= process_bytes;
+  }
+
+  // Handle the case that there is no futher data in this frame.
+  if (remaining_control_payload_ == 0 && processed_successfully) {
+    // The complete header block has been delivered. We send a zero-length
+    // OnControlFrameHeaderData() to indicate this.
+    visitor_->OnControlFrameHeaderData(
+        GetControlFrameStreamId(&control_frame), NULL, 0);
+
+    // If this is a FIN, tell the caller.
+    if (control_frame.flags() & CONTROL_FLAG_FIN) {
+      visitor_->OnStreamFrameData(GetControlFrameStreamId(&control_frame),
+                                  NULL, 0, DATA_FLAG_FIN);
+    }
+
+    CHANGE_STATE(SPDY_AUTO_RESET);
+  }
+
+  // Handle error.
+  if (!processed_successfully) {
+    return data_len;
+  }
+
+  // Return amount processed.
+  return process_bytes;
+}
+
+size_t SpdyFramer::ProcessSettingsFramePayload(const char* data,
+                                               size_t data_len) {
+  DCHECK_EQ(SPDY_SETTINGS_FRAME_PAYLOAD, state_);
+  SpdyControlFrame control_frame(current_frame_buffer_.get(), false);
+  DCHECK_EQ(SETTINGS, control_frame.type());
+  size_t unprocessed_bytes = std::min(data_len, remaining_control_payload_);
+  size_t processed_bytes = 0;
+
+  // Loop over our incoming data.
+  while (unprocessed_bytes > 0) {
+    // Process up to one setting at a time.
+    size_t processing = std::min(
+        unprocessed_bytes,
+        static_cast<size_t>(8 - settings_scratch_.setting_buf_len));
+
+    // Check if we have a complete setting in our input.
+    if (processing == 8) {
+      // Parse the setting directly out of the input without buffering.
+      if (!ProcessSetting(data + processed_bytes)) {
+        set_error(SPDY_INVALID_CONTROL_FRAME);
+        return processed_bytes;
+      }
+    } else {
+      // Continue updating settings_scratch_.setting_buf.
+      memcpy(settings_scratch_.setting_buf + settings_scratch_.setting_buf_len,
+             data + processed_bytes,
+             processing);
+      settings_scratch_.setting_buf_len += processing;
+
+      // Check if we have a complete setting buffered.
+      if (settings_scratch_.setting_buf_len == 8) {
+        if (!ProcessSetting(settings_scratch_.setting_buf)) {
+          set_error(SPDY_INVALID_CONTROL_FRAME);
+          return processed_bytes;
+        }
+        // Reset settings_scratch_.setting_buf for our next setting.
+        settings_scratch_.setting_buf_len = 0;
+      }
+    }
+
+    // Iterate.
+    unprocessed_bytes -= processing;
+    processed_bytes += processing;
+  }
+
+  // Check if we're done handling this SETTINGS frame.
+  remaining_control_payload_ -= processed_bytes;
+  if (remaining_control_payload_ == 0) {
+    CHANGE_STATE(SPDY_AUTO_RESET);
+  }
+
+  return processed_bytes;
+}
+
+bool SpdyFramer::ProcessSetting(const char* data) {
+  // Extract fields.
+  // Maintain behavior of old SPDY 2 bug with byte ordering of flags/id.
+  const uint32 id_and_flags_wire = *(reinterpret_cast<const uint32*>(data));
+  SettingsFlagsAndId id_and_flags =
+      SettingsFlagsAndId::FromWireFormat(spdy_version_, id_and_flags_wire);
+  uint8 flags = id_and_flags.flags();
+  uint32 value = ntohl(*(reinterpret_cast<const uint32*>(data + 4)));
+
+  // Validate id.
+  switch (id_and_flags.id()) {
+    case SETTINGS_UPLOAD_BANDWIDTH:
+    case SETTINGS_DOWNLOAD_BANDWIDTH:
+    case SETTINGS_ROUND_TRIP_TIME:
+    case SETTINGS_MAX_CONCURRENT_STREAMS:
+    case SETTINGS_CURRENT_CWND:
+    case SETTINGS_DOWNLOAD_RETRANS_RATE:
+    case SETTINGS_INITIAL_WINDOW_SIZE:
+      // Valid values.
+      break;
+    default:
+      DLOG(WARNING) << "Unknown SETTINGS ID: " << id_and_flags.id();
+      return false;
+  }
+  SpdySettingsIds id = static_cast<SpdySettingsIds>(id_and_flags.id());
+
+  // Detect duplciates.
+  if (static_cast<uint32>(id) <= settings_scratch_.last_setting_id) {
+    DLOG(WARNING) << "Duplicate entry or invalid ordering for id " << id
+                  << " in " << display_protocol_ << " SETTINGS frame "
+                  << "(last settikng id was "
+                  << settings_scratch_.last_setting_id << ").";
+    return false;
+  }
+  settings_scratch_.last_setting_id = id;
+
+  // Validate flags.
+  uint8 kFlagsMask = SETTINGS_FLAG_PLEASE_PERSIST | SETTINGS_FLAG_PERSISTED;
+  if ((flags & ~(kFlagsMask)) != 0) {
+    DLOG(WARNING) << "Unknown SETTINGS flags provided for id " << id << ": "
+                  << flags;
+    return false;
+  }
+
+  // Validation succeeded. Pass on to visitor.
+  visitor_->OnSetting(id, flags, value);
+  return true;
+}
+
+size_t SpdyFramer::ProcessControlFramePayload(const char* data, size_t len) {
+  size_t original_len = len;
+  if (remaining_control_payload_) {
+    size_t bytes_read = UpdateCurrentFrameBuffer(&data, &len,
+                                                 remaining_control_payload_);
+    remaining_control_payload_ -= bytes_read;
+    remaining_data_ -= bytes_read;
+    if (remaining_control_payload_ == 0) {
+      SpdyControlFrame control_frame(current_frame_buffer_.get(), false);
+      DCHECK(!control_frame.has_header_block());
+      // Use frame-specific handlers.
+      switch (control_frame.type()) {
+        case PING: {
+            SpdyPingControlFrame* ping_frame =
+                reinterpret_cast<SpdyPingControlFrame*>(&control_frame);
+            visitor_->OnPing(ping_frame->unique_id());
+          }
+          break;
+        case WINDOW_UPDATE: {
+            SpdyWindowUpdateControlFrame *window_update_frame =
+                reinterpret_cast<SpdyWindowUpdateControlFrame*>(&control_frame);
+            visitor_->OnWindowUpdate(window_update_frame->stream_id(),
+                                     window_update_frame->delta_window_size());
+          }
+          break;
+        case RST_STREAM: {
+            SpdyRstStreamControlFrame* rst_stream_frame =
+                reinterpret_cast<SpdyRstStreamControlFrame*>(&control_frame);
+            visitor_->OnRstStream(rst_stream_frame->stream_id(),
+                                  rst_stream_frame->status());
+          }
+          break;
+        case GOAWAY: {
+            SpdyGoAwayControlFrame* go_away_frame =
+                reinterpret_cast<SpdyGoAwayControlFrame*>(&control_frame);
+            if (spdy_version_ < 3) {
+              visitor_->OnGoAway(go_away_frame->last_accepted_stream_id(),
+                                 GOAWAY_OK);
+            } else {
+              visitor_->OnGoAway(go_away_frame->last_accepted_stream_id(),
+                                 go_away_frame->status());
+            }
+          }
+          break;
+        default:
+          // Unreachable.
+          LOG(FATAL) << "Unhandled control frame " << control_frame.type();
+      }
+
+      CHANGE_STATE(SPDY_IGNORE_REMAINING_PAYLOAD);
+    }
+  }
+  return original_len - len;
+}
+
+size_t SpdyFramer::ProcessCredentialFramePayload(const char* data, size_t len) {
+  if (len > 0) {
+    // Process only up to the end of this CREDENTIAL frame.
+    len = std::min(len, remaining_control_payload_);
+    bool processed_succesfully = visitor_->OnCredentialFrameData(data, len);
+    remaining_control_payload_ -= len;
+    remaining_data_ -= len;
+    if (!processed_succesfully) {
+      set_error(SPDY_CREDENTIAL_FRAME_CORRUPT);
+    } else if (remaining_control_payload_ == 0) {
+      visitor_->OnCredentialFrameData(NULL, 0);
+      CHANGE_STATE(SPDY_AUTO_RESET);
+    }
+  }
+  return len;
+}
+
+size_t SpdyFramer::ProcessDataFramePayload(const char* data, size_t len) {
+  size_t original_len = len;
+
+  SpdyDataFrame current_data_frame(current_frame_buffer_.get(), false);
+  if (remaining_data_ > 0) {
+    size_t amount_to_forward = std::min(remaining_data_, len);
+    if (amount_to_forward && state_ != SPDY_IGNORE_REMAINING_PAYLOAD) {
+      // Only inform the visitor if there is data.
+      if (amount_to_forward) {
+        visitor_->OnStreamFrameData(current_data_frame.stream_id(),
+                                    data, amount_to_forward, SpdyDataFlags());
+      }
+    }
+    data += amount_to_forward;
+    len -= amount_to_forward;
+    remaining_data_ -= amount_to_forward;
+
+    // If the FIN flag is set, and there is no more data in this data
+    // frame, inform the visitor of EOF via a 0-length data frame.
+    if (!remaining_data_ &&
+        current_data_frame.flags() & DATA_FLAG_FIN) {
+      visitor_->OnStreamFrameData(current_data_frame.stream_id(),
+                                  NULL, 0, DATA_FLAG_FIN);
+    }
+  }
+
+  if (remaining_data_ == 0) {
+    CHANGE_STATE(SPDY_AUTO_RESET);
+  }
+  return original_len - len;
+}
+
+bool SpdyFramer::ParseHeaderBlockInBuffer(const char* header_data,
+                                          size_t header_length,
+                                          SpdyHeaderBlock* block) const {
+  SpdyFrameReader reader(header_data, header_length);
+
+  // Read number of headers.
+  uint32 num_headers;
+  if (spdy_version_ < 3) {
+    uint16 temp;
+    if (!reader.ReadUInt16(&temp)) {
+      DLOG(INFO) << "Unable to read number of headers.";
+      return false;
+    }
+    num_headers = temp;
+  } else {
+    if (!reader.ReadUInt32(&num_headers)) {
+      DLOG(INFO) << "Unable to read number of headers.";
+      return false;
+    }
+  }
+
+  // Read each header.
+  for (uint32 index = 0; index < num_headers; ++index) {
+    base::StringPiece temp;
+
+    // Read header name.
+    if ((spdy_version_ < 3) ? !reader.ReadStringPiece16(&temp)
+                            : !reader.ReadStringPiece32(&temp)) {
+      DLOG(INFO) << "Unable to read header name (" << index + 1 << " of "
+                 << num_headers << ").";
+      return false;
+    }
+    std::string name = temp.as_string();
+
+    // Read header value.
+    if ((spdy_version_ < 3) ? !reader.ReadStringPiece16(&temp)
+                            : !reader.ReadStringPiece32(&temp)) {
+      DLOG(INFO) << "Unable to read header value (" << index + 1 << " of "
+                 << num_headers << ").";
+      return false;
+    }
+    std::string value = temp.as_string();
+
+    // Ensure no duplicates.
+    if (block->find(name) != block->end()) {
+      DLOG(INFO) << "Duplicate header '" << name << "' (" << index + 1 << " of "
+                 << num_headers << ").";
+      return false;
+    }
+
+    // Store header.
+    (*block)[name] = value;
+  }
+  return true;
+}
+
+// TODO(hkhalil): Remove, or move to test utils kit.
+/* static */
+bool SpdyFramer::ParseSettings(const SpdySettingsControlFrame* frame,
+                               SettingsMap* settings) {
+  DCHECK_EQ(frame->type(), SETTINGS);
+  DCHECK(settings);
+
+  SpdyFrameReader parser(frame->header_block(), frame->header_block_len());
+  for (size_t index = 0; index < frame->num_entries(); ++index) {
+    uint32 id_and_flags_wire;
+    uint32 value;
+    // SettingsFlagsAndId accepts off-the-wire (network byte order) data, so we
+    // use ReadBytes() instead of ReadUInt32() as the latter calls ntohl().
+    if (!parser.ReadBytes(&id_and_flags_wire, 4)) {
+      return false;
+    }
+    if (!parser.ReadUInt32(&value))
+      return false;
+    SettingsFlagsAndId flags_and_id =
+        SettingsFlagsAndId::FromWireFormat(frame->version(), id_and_flags_wire);
+    SpdySettingsIds id = static_cast<SpdySettingsIds>(flags_and_id.id());
+    SpdySettingsFlags flags =
+        static_cast<SpdySettingsFlags>(flags_and_id.flags());
+    (*settings)[id] = SettingsFlagsAndValue(flags, value);
+  }
+  return true;
+}
+
+/* static */
+bool SpdyFramer::ParseCredentialData(const char* data, size_t len,
+                                     SpdyCredential* credential) {
+  DCHECK(credential);
+
+  SpdyFrameReader parser(data, len);
+  base::StringPiece temp;
+  if (!parser.ReadUInt16(&credential->slot)) {
+    return false;
+  }
+
+  if (!parser.ReadStringPiece32(&temp)) {
+    return false;
+  }
+  credential->proof = temp.as_string();
+
+  while (!parser.IsDoneReading()) {
+    if (!parser.ReadStringPiece32(&temp)) {
+      return false;
+    }
+    credential->certs.push_back(temp.as_string());
+  }
+  return true;
+}
+
+SpdySynStreamControlFrame* SpdyFramer::CreateSynStream(
+    SpdyStreamId stream_id,
+    SpdyStreamId associated_stream_id,
+    SpdyPriority priority,
+    uint8 credential_slot,
+    SpdyControlFlags flags,
+    bool compressed,
+    const SpdyHeaderBlock* headers) {
+  DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+  DCHECK_EQ(0u, associated_stream_id & ~kStreamIdMask);
+
+  // Find our length.
+  size_t frame_size = SpdySynStreamControlFrame::size() +
+                      GetSerializedLength(headers);
+
+  SpdyFrameBuilder frame(SYN_STREAM, flags, spdy_version_, frame_size);
+  frame.WriteUInt32(stream_id);
+  frame.WriteUInt32(associated_stream_id);
+  // Cap as appropriate.
+  if (priority > GetLowestPriority()) {
+    DLOG(DFATAL) << "Priority out-of-bounds.";
+    priority = GetLowestPriority();
+  }
+  // Priority is 2 bits for <spdy3, 3 bits otherwise.
+  frame.WriteUInt8(priority << ((spdy_version_ < 3) ? 6 : 5));
+  frame.WriteUInt8((spdy_version_ < 3) ? 0 : credential_slot);
+  WriteHeaderBlock(&frame, headers);
+  DCHECK_EQ(frame.length(), frame_size);
+
+  scoped_ptr<SpdySynStreamControlFrame> syn_frame(
+      reinterpret_cast<SpdySynStreamControlFrame*>(frame.take()));
+  if (compressed) {
+    return reinterpret_cast<SpdySynStreamControlFrame*>(
+        CompressControlFrame(*syn_frame.get(), headers));
+  }
+  return syn_frame.release();
+}
+
+SpdySynReplyControlFrame* SpdyFramer::CreateSynReply(
+    SpdyStreamId stream_id,
+    SpdyControlFlags flags,
+    bool compressed,
+    const SpdyHeaderBlock* headers) {
+  DCHECK_GT(stream_id, 0u);
+  DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+
+  // Find our length.
+  size_t frame_size = SpdySynReplyControlFrame::size() +
+                      GetSerializedLength(headers);
+  // In SPDY 2, there were 2 unused bytes before payload.
+  if (spdy_version_ < 3) {
+    frame_size += 2;
+  }
+
+  SpdyFrameBuilder frame(SYN_REPLY, flags, spdy_version_, frame_size);
+  frame.WriteUInt32(stream_id);
+  if (spdy_version_ < 3) {
+    frame.WriteUInt16(0);  // Unused
+  }
+  WriteHeaderBlock(&frame, headers);
+  DCHECK_EQ(frame.length(), frame_size);
+
+  scoped_ptr<SpdySynReplyControlFrame> reply_frame(
+      reinterpret_cast<SpdySynReplyControlFrame*>(frame.take()));
+  if (compressed) {
+    return reinterpret_cast<SpdySynReplyControlFrame*>(
+        CompressControlFrame(*reply_frame.get(), headers));
+  }
+  return reply_frame.release();
+}
+
+SpdyRstStreamControlFrame* SpdyFramer::CreateRstStream(
+    SpdyStreamId stream_id,
+    SpdyStatusCodes status) const {
+  DCHECK_GT(stream_id, 0u);
+  DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+  DCHECK_NE(status, INVALID);
+  DCHECK_LT(status, NUM_STATUS_CODES);
+
+  size_t frame_size = SpdyRstStreamControlFrame::size();
+  SpdyFrameBuilder frame(RST_STREAM, CONTROL_FLAG_NONE, spdy_version_,
+                         frame_size);
+  frame.WriteUInt32(stream_id);
+  frame.WriteUInt32(status);
+  DCHECK_EQ(frame.length(), frame_size);
+  return reinterpret_cast<SpdyRstStreamControlFrame*>(frame.take());
+}
+
+SpdySettingsControlFrame* SpdyFramer::CreateSettings(
+    const SettingsMap& values) const {
+  size_t frame_size = SpdySettingsControlFrame::size() + 8 * values.size();
+  SpdyFrameBuilder frame(SETTINGS, CONTROL_FLAG_NONE, spdy_version_,
+                         frame_size);
+  frame.WriteUInt32(values.size());
+  for (SettingsMap::const_iterator it = values.begin();
+       it != values.end();
+       it++) {
+    SettingsFlagsAndId flags_and_id(it->second.first, it->first);
+    uint32 id_and_flags_wire = flags_and_id.GetWireFormat(spdy_version_);
+    frame.WriteBytes(&id_and_flags_wire, 4);
+    frame.WriteUInt32(it->second.second);
+  }
+  DCHECK_EQ(frame.length(), frame_size);
+  return reinterpret_cast<SpdySettingsControlFrame*>(frame.take());
+}
+
+SpdyPingControlFrame* SpdyFramer::CreatePingFrame(uint32 unique_id) const {
+  size_t frame_size = SpdyPingControlFrame::size();
+  SpdyFrameBuilder frame(PING, CONTROL_FLAG_NONE, spdy_version_, frame_size);
+  frame.WriteUInt32(unique_id);
+  DCHECK_EQ(frame.length(), frame_size);
+  return reinterpret_cast<SpdyPingControlFrame*>(frame.take());
+}
+
+SpdyGoAwayControlFrame* SpdyFramer::CreateGoAway(
+    SpdyStreamId last_accepted_stream_id,
+    SpdyGoAwayStatus status) const {
+  DCHECK_EQ(0u, last_accepted_stream_id & ~kStreamIdMask);
+
+  // SPDY 2 GOAWAY frames are 4 bytes smaller than in SPDY 3. We account for
+  // this difference via a separate offset variable, since
+  // SpdyGoAwayControlFrame::size() returns the SPDY 3 size.
+  const size_t goaway_offset = (protocol_version() < 3) ? 4 : 0;
+  size_t frame_size = SpdyGoAwayControlFrame::size() - goaway_offset;
+  SpdyFrameBuilder frame(GOAWAY, CONTROL_FLAG_NONE, spdy_version_, frame_size);
+  frame.WriteUInt32(last_accepted_stream_id);
+  if (protocol_version() >= 3) {
+    frame.WriteUInt32(status);
+  }
+  DCHECK_EQ(frame.length(), frame_size);
+  return reinterpret_cast<SpdyGoAwayControlFrame*>(frame.take());
+}
+
+SpdyHeadersControlFrame* SpdyFramer::CreateHeaders(
+    SpdyStreamId stream_id,
+    SpdyControlFlags flags,
+    bool compressed,
+    const SpdyHeaderBlock* headers) {
+  // Basically the same as CreateSynReply().
+  DCHECK_GT(stream_id, 0u);
+  DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+
+  // Find our length.
+  size_t frame_size = SpdyHeadersControlFrame::size() +
+                      GetSerializedLength(headers);
+  // In SPDY 2, there were 2 unused bytes before payload.
+  if (spdy_version_ < 3) {
+    frame_size += 2;
+  }
+
+  SpdyFrameBuilder frame(HEADERS, flags, spdy_version_, frame_size);
+  frame.WriteUInt32(stream_id);
+  if (spdy_version_ < 3) {
+    frame.WriteUInt16(0);  // Unused
+  }
+  WriteHeaderBlock(&frame, headers);
+  DCHECK_EQ(frame.length(), frame_size);
+
+  scoped_ptr<SpdyHeadersControlFrame> headers_frame(
+      reinterpret_cast<SpdyHeadersControlFrame*>(frame.take()));
+  if (compressed) {
+    return reinterpret_cast<SpdyHeadersControlFrame*>(
+        CompressControlFrame(*headers_frame.get(), headers));
+  }
+  return headers_frame.release();
+}
+
+SpdyWindowUpdateControlFrame* SpdyFramer::CreateWindowUpdate(
+    SpdyStreamId stream_id,
+    uint32 delta_window_size) const {
+  DCHECK_GT(stream_id, 0u);
+  DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+  DCHECK_GT(delta_window_size, 0u);
+  DCHECK_LE(delta_window_size,
+            static_cast<uint32>(kSpdyStreamMaximumWindowSize));
+
+  size_t frame_size = SpdyWindowUpdateControlFrame::size();
+  SpdyFrameBuilder frame(WINDOW_UPDATE, CONTROL_FLAG_NONE, spdy_version_,
+                         frame_size);
+  frame.WriteUInt32(stream_id);
+  frame.WriteUInt32(delta_window_size);
+  DCHECK_EQ(frame.length(), frame_size);
+  return reinterpret_cast<SpdyWindowUpdateControlFrame*>(frame.take());
+}
+
+SpdyCredentialControlFrame* SpdyFramer::CreateCredentialFrame(
+    const SpdyCredential& credential) const {
+  // Calculate the size of the frame by adding the size of the
+  // variable length data to the size of the fixed length data.
+  size_t frame_size =  SpdyCredentialControlFrame::size() +
+      credential.proof.length();
+  DCHECK_EQ(SpdyCredentialControlFrame::size(), 14u);
+  for (std::vector<std::string>::const_iterator cert = credential.certs.begin();
+       cert != credential.certs.end();
+       ++cert) {
+    frame_size += sizeof(uint32);  // size of the cert_length field
+    frame_size += cert->length();     // size of the cert_data field
+  }
+
+  SpdyFrameBuilder frame(CREDENTIAL, CONTROL_FLAG_NONE, spdy_version_,
+                         frame_size);
+  frame.WriteUInt16(credential.slot);
+  frame.WriteUInt32(credential.proof.size());
+  frame.WriteBytes(credential.proof.c_str(), credential.proof.size());
+  for (std::vector<std::string>::const_iterator cert = credential.certs.begin();
+       cert != credential.certs.end();
+       ++cert) {
+    frame.WriteUInt32(cert->length());
+    frame.WriteBytes(cert->c_str(), cert->length());
+  }
+  DCHECK_EQ(frame.length(), frame_size);
+  return reinterpret_cast<SpdyCredentialControlFrame*>(frame.take());
+}
+
+SpdyDataFrame* SpdyFramer::CreateDataFrame(
+    SpdyStreamId stream_id,
+                                           const char* data,
+    uint32 len, SpdyDataFlags flags) const {
+  DCHECK_EQ(0u, stream_id & ~kStreamIdMask);
+  size_t frame_size = SpdyDataFrame::size() + len;
+  SpdyFrameBuilder frame(stream_id, flags, frame_size);
+  frame.WriteBytes(data, len);
+  DCHECK_EQ(frame.length(), frame_size);
+  return reinterpret_cast<SpdyDataFrame*>(frame.take());
+}
+
+// The following compression setting are based on Brian Olson's analysis. See
+// https://groups.google.com/group/spdy-dev/browse_thread/thread/dfaf498542fac792
+// for more details.
+#if defined(USE_SYSTEM_ZLIB)
+// System zlib is not expected to have workaround for http://crbug.com/139744,
+// so disable compression in that case.
+// TODO(phajdan.jr): Remove the special case when it's no longer necessary.
+static const int kCompressorLevel = 0;
+#else  // !defined(USE_SYSTEM_ZLIB)
+static const int kCompressorLevel = 9;
+#endif  // !defined(USE_SYSTEM_ZLIB)
+static const int kCompressorWindowSizeInBits = 11;
+static const int kCompressorMemLevel = 1;
+
+z_stream* SpdyFramer::GetHeaderCompressor() {
+  if (header_compressor_.get())
+    return header_compressor_.get();  // Already initialized.
+
+  header_compressor_.reset(new z_stream);
+  memset(header_compressor_.get(), 0, sizeof(z_stream));
+
+  int success = deflateInit2(header_compressor_.get(),
+                             kCompressorLevel,
+                             Z_DEFLATED,
+                             kCompressorWindowSizeInBits,
+                             kCompressorMemLevel,
+                             Z_DEFAULT_STRATEGY);
+  if (success == Z_OK) {
+    const char* dictionary = (spdy_version_ < 3) ? kV2Dictionary
+                                                 : kV3Dictionary;
+    const int dictionary_size = (spdy_version_ < 3) ? kV2DictionarySize
+                                                    : kV3DictionarySize;
+    success = deflateSetDictionary(header_compressor_.get(),
+                                   reinterpret_cast<const Bytef*>(dictionary),
+                                   dictionary_size);
+  }
+  if (success != Z_OK) {
+    LOG(WARNING) << "deflateSetDictionary failure: " << success;
+    header_compressor_.reset(NULL);
+    return NULL;
+  }
+  return header_compressor_.get();
+}
+
+z_stream* SpdyFramer::GetHeaderDecompressor() {
+  if (header_decompressor_.get())
+    return header_decompressor_.get();  // Already initialized.
+
+  header_decompressor_.reset(new z_stream);
+  memset(header_decompressor_.get(), 0, sizeof(z_stream));
+
+  int success = inflateInit(header_decompressor_.get());
+  if (success != Z_OK) {
+    LOG(WARNING) << "inflateInit failure: " << success;
+    header_decompressor_.reset(NULL);
+    return NULL;
+  }
+  return header_decompressor_.get();
+}
+
+bool SpdyFramer::GetFrameBoundaries(const SpdyFrame& frame,
+                                    int* payload_length,
+                                    int* header_length,
+                                    const char** payload) const {
+  size_t frame_size;
+  if (frame.is_control_frame()) {
+    const SpdyControlFrame& control_frame =
+        reinterpret_cast<const SpdyControlFrame&>(frame);
+    switch (control_frame.type()) {
+      case SYN_STREAM:
+        {
+          const SpdySynStreamControlFrame& syn_frame =
+              reinterpret_cast<const SpdySynStreamControlFrame&>(frame);
+          frame_size = SpdySynStreamControlFrame::size();
+          *payload_length = syn_frame.header_block_len();
+          *header_length = frame_size;
+          *payload = frame.data() + *header_length;
+        }
+        break;
+      case SYN_REPLY:
+        {
+          const SpdySynReplyControlFrame& syn_frame =
+              reinterpret_cast<const SpdySynReplyControlFrame&>(frame);
+          frame_size = SpdySynReplyControlFrame::size();
+          *payload_length = syn_frame.header_block_len();
+          *header_length = frame_size;
+          *payload = frame.data() + *header_length;
+          // SPDY 2 had two bytes of unused space preceeding payload.
+          if (spdy_version_ < 3) {
+            *header_length += 2;
+            *payload += 2;
+          }
+        }
+        break;
+      case HEADERS:
+        {
+          const SpdyHeadersControlFrame& headers_frame =
+              reinterpret_cast<const SpdyHeadersControlFrame&>(frame);
+          frame_size = SpdyHeadersControlFrame::size();
+          *payload_length = headers_frame.header_block_len();
+          *header_length = frame_size;
+          *payload = frame.data() + *header_length;
+          // SPDY 2 had two bytes of unused space preceeding payload.
+          if (spdy_version_ < 3) {
+            *header_length += 2;
+            *payload += 2;
+          }
+        }
+        break;
+      default:
+        // TODO(mbelshe): set an error?
+        return false;  // We can't compress this frame!
+    }
+  } else {
+    frame_size = SpdyFrame::kHeaderSize;
+    *header_length = frame_size;
+    *payload_length = frame.length();
+    *payload = frame.data() + SpdyFrame::kHeaderSize;
+  }
+  return true;
+}
+
+SpdyControlFrame* SpdyFramer::CompressControlFrame(
+    const SpdyControlFrame& frame,
+    const SpdyHeaderBlock* headers) {
+  z_stream* compressor = GetHeaderCompressor();
+  if (!compressor)
+    return NULL;
+
+  int payload_length;
+  int header_length;
+  const char* payload;
+
+  base::StatsCounter compressed_frames("spdy.CompressedFrames");
+  base::StatsCounter pre_compress_bytes("spdy.PreCompressSize");
+  base::StatsCounter post_compress_bytes("spdy.PostCompressSize");
+
+  if (!enable_compression_)
+    return reinterpret_cast<SpdyControlFrame*>(DuplicateFrame(frame));
+
+  if (!GetFrameBoundaries(frame, &payload_length, &header_length, &payload))
+    return NULL;
+
+  // Create an output frame.
+  int compressed_max_size = deflateBound(compressor, payload_length);
+  // Since we'll be performing lots of flushes when compressing the data,
+  // zlib's lower bounds may be insufficient.
+  compressed_max_size *= 2;
+
+  size_t new_frame_size = header_length + compressed_max_size;
+  if ((frame.type() == SYN_REPLY || frame.type() == HEADERS) &&
+      spdy_version_ < 3) {
+    new_frame_size += 2;
+  }
+  DCHECK_GE(new_frame_size, frame.length() + SpdyFrame::kHeaderSize);
+  scoped_ptr<SpdyControlFrame> new_frame(new SpdyControlFrame(new_frame_size));
+  memcpy(new_frame->data(), frame.data(),
+         frame.length() + SpdyFrame::kHeaderSize);
+
+  // TODO(phajdan.jr): Clean up after we no longer need
+  // to workaround http://crbug.com/139744.
+#if defined(USE_SYSTEM_ZLIB)
+  compressor->next_in = reinterpret_cast<Bytef*>(const_cast<char*>(payload));
+  compressor->avail_in = payload_length;
+#endif  // defined(USE_SYSTEM_ZLIB)
+  compressor->next_out = reinterpret_cast<Bytef*>(new_frame->data()) +
+                          header_length;
+  compressor->avail_out = compressed_max_size;
+  // TODO(phajdan.jr): Clean up after we no longer need
+  // to workaround http://crbug.com/139744.
+#if defined(USE_SYSTEM_ZLIB)
+  int rv = deflate(compressor, Z_SYNC_FLUSH);
+  if (rv != Z_OK) {  // How can we know that it compressed everything?
+    // This shouldn't happen, right?
+    LOG(WARNING) << "deflate failure: " << rv;
+    return NULL;
+  }
+#else  // !defined(USE_SYSTEM_ZLIB)
+  WriteHeaderBlockToZ(headers, compressor);
+#endif  // !defined(USE_SYSTEM_ZLIB)
+  int compressed_size = compressed_max_size - compressor->avail_out;
+
+  // We trust zlib. Also, we can't do anything about it.
+  // See http://www.zlib.net/zlib_faq.html#faq36
+  (void)VALGRIND_MAKE_MEM_DEFINED(new_frame->data() + header_length,
+                                  compressed_size);
+
+  new_frame->set_length(
+      header_length + compressed_size - SpdyFrame::kHeaderSize);
+
+  pre_compress_bytes.Add(payload_length);
+  post_compress_bytes.Add(new_frame->length());
+
+  compressed_frames.Increment();
+
+  if (visitor_)
+    visitor_->OnControlFrameCompressed(frame, *new_frame);
+
+  return new_frame.release();
+}
+
+// Incrementally decompress the control frame's header block, feeding the
+// result to the visitor in chunks. Continue this until the visitor
+// indicates that it cannot process any more data, or (more commonly) we
+// run out of data to deliver.
+bool SpdyFramer::IncrementallyDecompressControlFrameHeaderData(
+    const SpdyControlFrame* control_frame,
+    const char* data,
+    size_t len) {
+  // Get a decompressor or set error.
+  z_stream* decomp = GetHeaderDecompressor();
+  if (decomp == NULL) {
+    LOG(DFATAL) << "Couldn't get decompressor for handling compressed headers.";
+    set_error(SPDY_DECOMPRESS_FAILURE);
+    return false;
+  }
+
+  bool processed_successfully = true;
+  char buffer[kHeaderDataChunkMaxSize];
+
+  decomp->next_in = reinterpret_cast<Bytef*>(const_cast<char*>(data));
+  decomp->avail_in = len;
+  const SpdyStreamId stream_id = GetControlFrameStreamId(control_frame);
+  DCHECK_LT(0u, stream_id);
+  while (decomp->avail_in > 0 && processed_successfully) {
+    decomp->next_out = reinterpret_cast<Bytef*>(buffer);
+    decomp->avail_out = arraysize(buffer);
+
+    int rv = inflate(decomp, Z_SYNC_FLUSH);
+    if (rv == Z_NEED_DICT) {
+      const char* dictionary = (spdy_version_ < 3) ? kV2Dictionary
+                                                   : kV3Dictionary;
+      const int dictionary_size = (spdy_version_ < 3) ? kV2DictionarySize
+                                                      : kV3DictionarySize;
+      const DictionaryIds& ids = g_dictionary_ids.Get();
+      const uLong dictionary_id = (spdy_version_ < 3) ? ids.v2_dictionary_id
+                                                      : ids.v3_dictionary_id;
+      // Need to try again with the right dictionary.
+      if (decomp->adler == dictionary_id) {
+        rv = inflateSetDictionary(decomp,
+                                  reinterpret_cast<const Bytef*>(dictionary),
+                                  dictionary_size);
+        if (rv == Z_OK)
+          rv = inflate(decomp, Z_SYNC_FLUSH);
+      }
+    }
+
+    // Inflate will generate a Z_BUF_ERROR if it runs out of input
+    // without producing any output.  The input is consumed and
+    // buffered internally by zlib so we can detect this condition by
+    // checking if avail_in is 0 after the call to inflate.
+    bool input_exhausted = ((rv == Z_BUF_ERROR) && (decomp->avail_in == 0));
+    if ((rv == Z_OK) || input_exhausted) {
+      size_t decompressed_len = arraysize(buffer) - decomp->avail_out;
+      if (decompressed_len > 0) {
+        processed_successfully = visitor_->OnControlFrameHeaderData(
+            stream_id, buffer, decompressed_len);
+      }
+      if (!processed_successfully) {
+        // Assume that the problem was the header block was too large for the
+        // visitor.
+        set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE);
+      }
+    } else {
+      DLOG(WARNING) << "inflate failure: " << rv << " " << len;
+      set_error(SPDY_DECOMPRESS_FAILURE);
+      processed_successfully = false;
+    }
+  }
+  return processed_successfully;
+}
+
+bool SpdyFramer::IncrementallyDeliverControlFrameHeaderData(
+    const SpdyControlFrame* control_frame, const char* data, size_t len) {
+  bool read_successfully = true;
+  const SpdyStreamId stream_id = GetControlFrameStreamId(control_frame);
+  while (read_successfully && len > 0) {
+    size_t bytes_to_deliver = std::min(len, kHeaderDataChunkMaxSize);
+    read_successfully = visitor_->OnControlFrameHeaderData(stream_id, data,
+                                                           bytes_to_deliver);
+    data += bytes_to_deliver;
+    len -= bytes_to_deliver;
+    if (!read_successfully) {
+      // Assume that the problem was the header block was too large for the
+      // visitor.
+      set_error(SPDY_CONTROL_PAYLOAD_TOO_LARGE);
+    }
+  }
+  return read_successfully;
+}
+
+SpdyFrame* SpdyFramer::DuplicateFrame(const SpdyFrame& frame) {
+  int size = SpdyFrame::kHeaderSize + frame.length();
+  SpdyFrame* new_frame = new SpdyFrame(size);
+  memcpy(new_frame->data(), frame.data(), size);
+  return new_frame;
+}
+
+bool SpdyFramer::IsCompressible(const SpdyFrame& frame) const {
+  // The important frames to compress are those which contain large
+  // amounts of compressible data - namely the headers in the SYN_STREAM
+  // and SYN_REPLY.
+  if (frame.is_control_frame()) {
+    const SpdyControlFrame& control_frame =
+        reinterpret_cast<const SpdyControlFrame&>(frame);
+    return control_frame.type() == SYN_STREAM ||
+        control_frame.type() == SYN_REPLY ||
+        control_frame.type() == HEADERS;
+  }
+
+  // We don't compress Data frames.
+  return false;
+}
+
+size_t SpdyFramer::GetMinimumControlFrameSize(int version,
+                                              SpdyControlType type) {
+  switch (type) {
+    case SYN_STREAM:
+      return SpdySynStreamControlFrame::size();
+    case SYN_REPLY:
+      return SpdySynReplyControlFrame::size();
+    case RST_STREAM:
+      return SpdyRstStreamControlFrame::size();
+    case SETTINGS:
+      return SpdySettingsControlFrame::size();
+    case NOOP:
+      // Even though NOOP is no longer supported, we still correctly report its
+      // size so that it can be handled correctly as incoming data if
+      // implementations so desire.
+      return SpdyFrame::kHeaderSize;
+    case PING:
+      return SpdyPingControlFrame::size();
+    case GOAWAY:
+      if (version < 3) {
+        // SPDY 2 GOAWAY is smaller by 32 bits. Since
+        // SpdyGoAwayControlFrame::size() returns the size for SPDY 3, we adjust
+        // before returning here.
+        return SpdyGoAwayControlFrame::size() - 4;
+      } else {
+        return SpdyGoAwayControlFrame::size();
+      }
+    case HEADERS:
+      return SpdyHeadersControlFrame::size();
+    case WINDOW_UPDATE:
+      return SpdyWindowUpdateControlFrame::size();
+    case CREDENTIAL:
+      return SpdyCredentialControlFrame::size();
+    case NUM_CONTROL_FRAME_TYPES:
+      break;
+  }
+  LOG(ERROR) << "Unknown control frame type " << type;
+  return std::numeric_limits<size_t>::max();
+}
+
+/* static */
+SpdyStreamId SpdyFramer::GetControlFrameStreamId(
+    const SpdyControlFrame* control_frame) {
+  SpdyStreamId stream_id = kInvalidStream;
+  if (control_frame != NULL) {
+    switch (control_frame->type()) {
+      case SYN_STREAM:
+        stream_id = reinterpret_cast<const SpdySynStreamControlFrame*>(
+            control_frame)->stream_id();
+        break;
+      case SYN_REPLY:
+        stream_id = reinterpret_cast<const SpdySynReplyControlFrame*>(
+            control_frame)->stream_id();
+        break;
+      case HEADERS:
+        stream_id = reinterpret_cast<const SpdyHeadersControlFrame*>(
+            control_frame)->stream_id();
+        break;
+      case RST_STREAM:
+        stream_id = reinterpret_cast<const SpdyRstStreamControlFrame*>(
+            control_frame)->stream_id();
+        break;
+      case WINDOW_UPDATE:
+        stream_id = reinterpret_cast<const SpdyWindowUpdateControlFrame*>(
+            control_frame)->stream_id();
+        break;
+      // All of the following types are not part of a particular stream.
+      // They all fall through to the invalid control frame type case.
+      // (The default case isn't used so that the compile will break if a new
+      // control frame type is added but not included here.)
+      case SETTINGS:
+      case NOOP:
+      case PING:
+      case GOAWAY:
+      case CREDENTIAL:
+      case NUM_CONTROL_FRAME_TYPES:  // makes compiler happy
+        break;
+    }
+  }
+  return stream_id;
+}
+
+void SpdyFramer::set_enable_compression(bool value) {
+  enable_compression_ = value;
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_framer.h b/src/net/spdy/spdy_framer.h
new file mode 100644
index 0000000..0c834fd
--- /dev/null
+++ b/src/net/spdy/spdy_framer.h
@@ -0,0 +1,609 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_SPDY_SPDY_FRAMER_H_
+#define NET_SPDY_SPDY_FRAMER_H_
+
+#include <list>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/sys_byteorder.h"
+#include "net/base/net_export.h"
+#include "net/spdy/spdy_header_block.h"
+#include "net/spdy/spdy_protocol.h"
+
+typedef struct z_stream_s z_stream;  // Forward declaration for zlib.
+
+namespace net {
+
+class HttpProxyClientSocketPoolTest;
+class HttpNetworkLayer;
+class HttpNetworkTransactionTest;
+class SpdyHttpStreamTest;
+class SpdyNetworkTransactionTest;
+class SpdyProxyClientSocketTest;
+class SpdySessionTest;
+class SpdyStreamTest;
+class SpdyWebSocketStreamTest;
+class WebSocketJobTest;
+
+class SpdyFramer;
+class SpdyFrameBuilder;
+class SpdyFramerTest;
+
+namespace test {
+
+class TestSpdyVisitor;
+
+}  // namespace test
+
+// A datastructure for holding the ID and flag fields for SETTINGS.
+// Conveniently handles converstion to/from wire format.
+class NET_EXPORT_PRIVATE SettingsFlagsAndId {
+ public:
+  static SettingsFlagsAndId FromWireFormat(int version, uint32 wire);
+
+  SettingsFlagsAndId() : flags_(0), id_(0) {}
+
+  // TODO(hkhalil): restrict to enums instead of free-form ints.
+  SettingsFlagsAndId(uint8 flags, uint32 id);
+
+  uint32 GetWireFormat(int version) const;
+
+  uint32 id() const { return id_; }
+  uint8 flags() const { return flags_; }
+
+ private:
+  static void ConvertFlagsAndIdForSpdy2(uint32* val);
+
+  uint8 flags_;
+  uint32 id_;
+};
+
+// SettingsMap has unique (flags, value) pair for given SpdySettingsIds ID.
+typedef std::pair<SpdySettingsFlags, uint32> SettingsFlagsAndValue;
+typedef std::map<SpdySettingsIds, SettingsFlagsAndValue> SettingsMap;
+
+// A datastrcture for holding the contents of a CREDENTIAL frame.
+struct NET_EXPORT_PRIVATE SpdyCredential {
+  SpdyCredential();
+  ~SpdyCredential();
+
+  uint16 slot;
+  std::vector<std::string> certs;
+  std::string proof;
+};
+
+// Scratch space necessary for processing SETTINGS frames.
+struct NET_EXPORT_PRIVATE SpdySettingsScratch {
+  SpdySettingsScratch() { Reset(); }
+
+  void Reset() {
+    setting_buf_len = 0;
+    last_setting_id = 0;
+  }
+
+  // Buffer contains up to one complete key/value pair.
+  char setting_buf[8];
+
+  // The amount of the buffer that is filled with valid data.
+  size_t setting_buf_len;
+
+  // The ID of the last setting that was processed in the current SETTINGS
+  // frame. Used for detecting out-of-order or duplicate keys within a settings
+  // frame. Set to 0 before first key/value pair is processed.
+  uint32 last_setting_id;
+};
+
+// SpdyFramerVisitorInterface is a set of callbacks for the SpdyFramer.
+// Implement this interface to receive event callbacks as frames are
+// decoded from the framer.
+//
+// Control frames that contain SPDY header blocks (SYN_STREAM, SYN_REPLY, and
+// HEADER) are processed in fashion that allows the decompressed header block
+// to be delivered in chunks to the visitor. The following steps are followed:
+//   1. OnSynStream, OnSynReply or OnHeaders is called.
+//   2. Repeated: OnControlFrameHeaderData is called with chunks of the
+//      decompressed header block. In each call the len parameter is greater
+//      than zero.
+//   3. OnControlFrameHeaderData is called with len set to zero, indicating
+//      that the full header block has been delivered for the control frame.
+// During step 2 the visitor may return false, indicating that the chunk of
+// header data could not be handled by the visitor (typically this indicates
+// resource exhaustion). If this occurs the framer will discontinue
+// delivering chunks to the visitor, set a SPDY_CONTROL_PAYLOAD_TOO_LARGE
+// error, and clean up appropriately. Note that this will cause the header
+// decompressor to lose synchronization with the sender's header compressor,
+// making the SPDY session unusable for future work. The visitor's OnError
+// function should deal with this condition by closing the SPDY connection.
+class NET_EXPORT_PRIVATE SpdyFramerVisitorInterface {
+ public:
+  virtual ~SpdyFramerVisitorInterface() {}
+
+  // Called if an error is detected in the SpdyFrame protocol.
+  virtual void OnError(SpdyFramer* framer) = 0;
+
+  // Called when a SYN_STREAM frame is received.
+  // Note that header block data is not included. See
+  // OnControlFrameHeaderData().
+  virtual void OnSynStream(SpdyStreamId stream_id,
+                           SpdyStreamId associated_stream_id,
+                           SpdyPriority priority,
+                           uint8 credential_slot,
+                           bool fin,
+                           bool unidirectional) = 0;
+
+  // Called when a SYN_REPLY frame is received.
+  // Note that header block data is not included. See
+  // OnControlFrameHeaderData().
+  virtual void OnSynReply(SpdyStreamId stream_id, bool fin) = 0;
+
+  // Called when a HEADERS frame is received.
+  // Note that header block data is not included. See
+  // OnControlFrameHeaderData().
+  virtual void OnHeaders(SpdyStreamId stream_id, bool fin) = 0;
+
+  // Called when a chunk of header data is available. This is called
+  // after OnSynStream, OnSynReply or OnHeaders().
+  // |stream_id| The stream receiving the header data.
+  // |header_data| A buffer containing the header data chunk received.
+  // |len| The length of the header data buffer. A length of zero indicates
+  //       that the header data block has been completely sent.
+  // When this function returns true the visitor indicates that it accepted
+  // all of the data. Returning false indicates that that an unrecoverable
+  // error has occurred, such as bad header data or resource exhaustion.
+  virtual bool OnControlFrameHeaderData(SpdyStreamId stream_id,
+                                        const char* header_data,
+                                        size_t len) = 0;
+
+  // Called when a chunk of payload data for a credential frame is available.
+  // |header_data| A buffer containing the header data chunk received.
+  // |len| The length of the header data buffer. A length of zero indicates
+  //       that the header data block has been completely sent.
+  // When this function returns true the visitor indicates that it accepted
+  // all of the data. Returning false indicates that that an unrecoverable
+  // error has occurred, such as bad header data or resource exhaustion.
+  virtual bool OnCredentialFrameData(const char* header_data,
+                                     size_t len) = 0;
+
+  // Called when a data frame header is received. The frame's data
+  // payload will be provided via subsequent calls to
+  // OnStreamFrameData().
+  virtual void OnDataFrameHeader(const SpdyDataFrame* frame) = 0;
+
+  // Called when data is received.
+  // |stream_id| The stream receiving data.
+  // |data| A buffer containing the data received.
+  // |len| The length of the data buffer.
+  // When the other side has finished sending data on this stream,
+  // this method will be called with a zero-length buffer.
+  virtual void OnStreamFrameData(SpdyStreamId stream_id,
+                                 const char* data,
+                                 size_t len,
+                                 SpdyDataFlags flags) = 0;
+
+  // Called when a complete setting within a SETTINGS frame has been parsed and
+  // validated.
+  virtual void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) = 0;
+
+  // Called when a PING frame has been parsed.
+  virtual void OnPing(uint32 unique_id) = 0;
+
+  // Called when a RST_STREAM frame has been parsed.
+  virtual void OnRstStream(SpdyStreamId stream_id, SpdyStatusCodes status) = 0;
+
+  // Called when a GOAWAY frame has been parsed.
+  virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+                        SpdyGoAwayStatus status) = 0;
+
+  // Called when a WINDOW_UPDATE frame has been parsed.
+  virtual void OnWindowUpdate(SpdyStreamId stream_id,
+                              int delta_window_size) = 0;
+
+  // Called after a control frame has been compressed to allow the visitor
+  // to record compression statistics.
+  virtual void OnControlFrameCompressed(
+      const SpdyControlFrame& uncompressed_frame,
+      const SpdyControlFrame& compressed_frame) = 0;
+};
+
+class NET_EXPORT_PRIVATE SpdyFramer {
+ public:
+  // SPDY states.
+  // TODO(mbelshe): Can we move these into the implementation
+  //                and avoid exposing through the header.  (Needed for test)
+  enum SpdyState {
+    SPDY_ERROR,
+    SPDY_DONE,
+    SPDY_RESET,
+    SPDY_AUTO_RESET,
+    SPDY_READING_COMMON_HEADER,
+    SPDY_CONTROL_FRAME_PAYLOAD,
+    SPDY_IGNORE_REMAINING_PAYLOAD,
+    SPDY_FORWARD_STREAM_FRAME,
+    SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK,
+    SPDY_CONTROL_FRAME_HEADER_BLOCK,
+    SPDY_CREDENTIAL_FRAME_PAYLOAD,
+    SPDY_SETTINGS_FRAME_PAYLOAD,
+  };
+
+  // SPDY error codes.
+  enum SpdyError {
+    SPDY_NO_ERROR,
+    SPDY_INVALID_CONTROL_FRAME,      // Control frame is mal-formatted.
+    SPDY_CONTROL_PAYLOAD_TOO_LARGE,  // Control frame payload was too large.
+    SPDY_ZLIB_INIT_FAILURE,          // The Zlib library could not initialize.
+    SPDY_UNSUPPORTED_VERSION,        // Control frame has unsupported version.
+    SPDY_DECOMPRESS_FAILURE,         // There was an error decompressing.
+    SPDY_COMPRESS_FAILURE,           // There was an error compressing.
+    SPDY_CREDENTIAL_FRAME_CORRUPT,   // CREDENTIAL frame could not be parsed.
+    SPDY_INVALID_DATA_FRAME_FLAGS,   // Data frame has invalid flags.
+
+    LAST_ERROR,  // Must be the last entry in the enum.
+  };
+
+  // The minimum supported SPDY version that SpdyFramer can speak.
+  static const int kMinSpdyVersion;
+
+  // The maximum supported SPDY version that SpdyFramer can speak.
+  static const int kMaxSpdyVersion;
+
+  // Constant for invalid (or unknown) stream IDs.
+  static const SpdyStreamId kInvalidStream;
+
+  // The maximum size of header data chunks delivered to the framer visitor
+  // through OnControlFrameHeaderData. (It is exposed here for unit test
+  // purposes.)
+  static const size_t kHeaderDataChunkMaxSize;
+
+  // Create a new Framer, provided a SPDY version.
+  explicit SpdyFramer(int version);
+  virtual ~SpdyFramer();
+
+  // Set callbacks to be called from the framer.  A visitor must be set, or
+  // else the framer will likely crash.  It is acceptable for the visitor
+  // to do nothing.  If this is called multiple times, only the last visitor
+  // will be used.
+  void set_visitor(SpdyFramerVisitorInterface* visitor) {
+    visitor_ = visitor;
+  }
+
+  // Pass data into the framer for parsing.
+  // Returns the number of bytes consumed. It is safe to pass more bytes in
+  // than may be consumed.
+  size_t ProcessInput(const char* data, size_t len);
+
+  // Resets the framer state after a frame has been successfully decoded.
+  // TODO(mbelshe): can we make this private?
+  void Reset();
+
+  // Check the state of the framer.
+  SpdyError error_code() const { return error_code_; }
+  SpdyState state() const { return state_; }
+
+  bool MessageFullyRead() const {
+    return state_ == SPDY_DONE || state_ == SPDY_AUTO_RESET;
+  }
+  bool HasError() const { return state_ == SPDY_ERROR; }
+
+  // Given a buffer containing a decompressed header block in SPDY
+  // serialized format, parse out a SpdyHeaderBlock, putting the results
+  // in the given header block.
+  // Returns true if successfully parsed, false otherwise.
+  bool ParseHeaderBlockInBuffer(const char* header_data,
+                                size_t header_length,
+                                SpdyHeaderBlock* block) const;
+
+  // Create a SpdySynStreamControlFrame.
+  // |stream_id| is the id for this stream.
+  // |associated_stream_id| is the associated stream id for this stream.
+  // |priority| is the priority (GetHighestPriority()-GetLowestPriority) for
+  //    this stream.
+  // |credential_slot| is the CREDENTIAL slot to be used for this request.
+  // |flags| is the flags to use with the data.
+  //    To mark this frame as the last frame, enable CONTROL_FLAG_FIN.
+  // |compressed| specifies whether the frame should be compressed.
+  // |headers| is the header block to include in the frame.
+  SpdySynStreamControlFrame* CreateSynStream(SpdyStreamId stream_id,
+                                             SpdyStreamId associated_stream_id,
+                                             SpdyPriority priority,
+                                             uint8 credential_slot,
+                                             SpdyControlFlags flags,
+                                             bool compressed,
+                                             const SpdyHeaderBlock* headers);
+
+  // Create a SpdySynReplyControlFrame.
+  // |stream_id| is the stream for this frame.
+  // |flags| is the flags to use with the data.
+  //    To mark this frame as the last frame, enable CONTROL_FLAG_FIN.
+  // |compressed| specifies whether the frame should be compressed.
+  // |headers| is the header block to include in the frame.
+  SpdySynReplyControlFrame* CreateSynReply(SpdyStreamId stream_id,
+                                           SpdyControlFlags flags,
+                                           bool compressed,
+                                           const SpdyHeaderBlock* headers);
+
+  SpdyRstStreamControlFrame* CreateRstStream(SpdyStreamId stream_id,
+                                             SpdyStatusCodes status) const;
+
+  // Creates an instance of SpdySettingsControlFrame. The SETTINGS frame is
+  // used to communicate name/value pairs relevant to the communication channel.
+  SpdySettingsControlFrame* CreateSettings(const SettingsMap& values) const;
+
+  // Creates an instance of SpdyPingControlFrame. The unique_id is used to
+  // identify the ping request/response.
+  SpdyPingControlFrame* CreatePingFrame(uint32 unique_id) const;
+
+  // Creates an instance of SpdyGoAwayControlFrame. The GOAWAY frame is used
+  // prior to the shutting down of the TCP connection, and includes the
+  // stream_id of the last stream the sender of the frame is willing to process
+  // to completion.
+  SpdyGoAwayControlFrame* CreateGoAway(SpdyStreamId last_accepted_stream_id,
+                                       SpdyGoAwayStatus status) const;
+
+  // Creates an instance of SpdyHeadersControlFrame. The HEADERS frame is used
+  // for sending additional headers outside of a SYN_STREAM/SYN_REPLY. The
+  // arguments are the same as for CreateSynReply.
+  SpdyHeadersControlFrame* CreateHeaders(SpdyStreamId stream_id,
+                                         SpdyControlFlags flags,
+                                         bool compressed,
+                                         const SpdyHeaderBlock* headers);
+
+  // Creates an instance of SpdyWindowUpdateControlFrame. The WINDOW_UPDATE
+  // frame is used to implement per stream flow control in SPDY.
+  SpdyWindowUpdateControlFrame* CreateWindowUpdate(
+      SpdyStreamId stream_id,
+      uint32 delta_window_size) const;
+
+  // Creates an instance of SpdyCredentialControlFrame.  The CREDENTIAL
+  // frame is used to send a client certificate to the server when
+  // request more than one origin are sent over the same SPDY session.
+  SpdyCredentialControlFrame* CreateCredentialFrame(
+      const SpdyCredential& credential) const;
+
+  // Given a SpdySettingsControlFrame, extract the settings.
+  // Returns true on successful parse, false otherwise.
+  static bool ParseSettings(const SpdySettingsControlFrame* frame,
+                            SettingsMap* settings);
+
+  // Given a SpdyCredentialControlFrame's payload, extract the credential.
+  // Returns true on successful parse, false otherwise.
+  // TODO(hkhalil): Implement CREDENTIAL frame parsing in SpdyFramer
+  // and eliminate this method.
+  static bool ParseCredentialData(const char* data, size_t len,
+                                  SpdyCredential* credential);
+
+  // Create a data frame.
+  // |stream_id| is the stream  for this frame
+  // |data| is the data to be included in the frame.
+  // |len| is the length of the data
+  // |flags| is the flags to use with the data.
+  //    To mark this frame as the last data frame, enable DATA_FLAG_FIN.
+  SpdyDataFrame* CreateDataFrame(SpdyStreamId stream_id, const char* data,
+                                 uint32 len, SpdyDataFlags flags) const;
+
+  // NOTES about frame compression.
+  // We want spdy to compress headers across the entire session.  As long as
+  // the session is over TCP, frames are sent serially.  The client & server
+  // can each compress frames in the same order and then compress them in that
+  // order, and the remote can do the reverse.  However, we ultimately want
+  // the creation of frames to be less sensitive to order so that they can be
+  // placed over a UDP based protocol and yet still benefit from some
+  // compression.  We don't know of any good compression protocol which does
+  // not build its state in a serial (stream based) manner....  For now, we're
+  // using zlib anyway.
+
+  // Compresses a SpdyFrame.
+  // On success, returns a new SpdyFrame with the payload compressed.
+  // Compression state is maintained as part of the SpdyFramer.
+  // Returned frame must be freed with "delete".
+  // On failure, returns NULL.
+  SpdyFrame* CompressFrame(const SpdyFrame& frame);
+
+  // Create a copy of a frame.
+  // Returned frame must be freed with "delete".
+  SpdyFrame* DuplicateFrame(const SpdyFrame& frame);
+
+  // Returns true if a frame could be compressed.
+  bool IsCompressible(const SpdyFrame& frame) const;
+
+  // Get the minimum size of the control frame for the given control frame
+  // type. This is useful for validating frame blocks.
+  static size_t GetMinimumControlFrameSize(int version, SpdyControlType type);
+
+  // Get the stream ID for the given control frame (SYN_STREAM, SYN_REPLY, and
+  // HEADERS). If the control frame is NULL or of another type, this
+  // function returns kInvalidStream.
+  static SpdyStreamId GetControlFrameStreamId(
+      const SpdyControlFrame* control_frame);
+
+  // For ease of testing and experimentation we can tweak compression on/off.
+  void set_enable_compression(bool value);
+
+  // Used only in log messages.
+  void set_display_protocol(const std::string& protocol) {
+    display_protocol_ = protocol;
+  }
+
+  // For debugging.
+  static const char* StateToString(int state);
+  static const char* ErrorCodeToString(int error_code);
+  static const char* StatusCodeToString(int status_code);
+  static const char* ControlTypeToString(SpdyControlType type);
+
+  int protocol_version() const { return spdy_version_; }
+
+  bool probable_http_response() const { return probable_http_response_; }
+
+  SpdyPriority GetLowestPriority() const {
+      return spdy_version_ < 3 ? 3U : 7U;
+  }
+  SpdyPriority GetHighestPriority() const { return 0; }
+
+ protected:
+  FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, BasicCompression);
+  FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, ControlFrameSizesAreValidated);
+  FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, HeaderCompression);
+  FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, DecompressUncompressedFrame);
+  FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, ExpandBuffer_HeapSmash);
+  FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, HugeHeaderBlock);
+  FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, UnclosedStreamDataCompressors);
+  FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest,
+                           UnclosedStreamDataCompressorsOneByteAtATime);
+  FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest,
+                           UncompressLargerThanFrameBufferInitialSize);
+  FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, ReadLargeSettingsFrame);
+  FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest,
+                           ReadLargeSettingsFrameInSmallChunks);
+  friend class net::HttpNetworkLayer;  // This is temporary for the server.
+  friend class net::HttpNetworkTransactionTest;
+  friend class net::HttpProxyClientSocketPoolTest;
+  friend class net::SpdyHttpStreamTest;
+  friend class net::SpdyNetworkTransactionTest;
+  friend class net::SpdyProxyClientSocketTest;
+  friend class net::SpdySessionTest;
+  friend class net::SpdyStreamTest;
+  friend class net::SpdyWebSocketStreamTest;
+  friend class net::WebSocketJobTest;
+  friend class test::TestSpdyVisitor;
+
+ private:
+  // Internal breakouts from ProcessInput. Each returns the number of bytes
+  // consumed from the data.
+  size_t ProcessCommonHeader(const char* data, size_t len);
+  size_t ProcessControlFramePayload(const char* data, size_t len);
+  size_t ProcessCredentialFramePayload(const char* data, size_t len);
+  size_t ProcessControlFrameBeforeHeaderBlock(const char* data, size_t len);
+  size_t ProcessControlFrameHeaderBlock(const char* data, size_t len);
+  size_t ProcessSettingsFramePayload(const char* data, size_t len);
+  size_t ProcessDataFramePayload(const char* data, size_t len);
+
+  // Helpers for above internal breakouts from ProcessInput.
+  void ProcessControlFrameHeader();
+  bool ProcessSetting(const char* data);  // Always passed exactly 8 bytes.
+
+  // Get (and lazily initialize) the ZLib state.
+  z_stream* GetHeaderCompressor();
+  z_stream* GetHeaderDecompressor();
+
+  // Deliver the given control frame's compressed headers block to the visitor
+  // in decompressed form, in chunks. Returns true if the visitor has
+  // accepted all of the chunks.
+  bool IncrementallyDecompressControlFrameHeaderData(
+      const SpdyControlFrame* frame,
+      const char* data,
+      size_t len);
+
+  // Deliver the given control frame's uncompressed headers block to the
+  // visitor in chunks. Returns true if the visitor has accepted all of the
+  // chunks.
+  bool IncrementallyDeliverControlFrameHeaderData(const SpdyControlFrame* frame,
+                                                  const char* data,
+                                                  size_t len);
+
+  // Utility to copy the given data block to the current frame buffer, up
+  // to the given maximum number of bytes, and update the buffer
+  // data (pointer and length). Returns the number of bytes
+  // read, and:
+  //   *data is advanced the number of bytes read.
+  //   *len is reduced by the number of bytes read.
+  size_t UpdateCurrentFrameBuffer(const char** data, size_t* len,
+                                  size_t max_bytes);
+
+  // Retrieve serialized length of SpdyHeaderBlock.
+  size_t GetSerializedLength(const SpdyHeaderBlock* headers) const;
+
+  // Serializes a SpdyHeaderBlock.
+  void WriteHeaderBlock(SpdyFrameBuilder* frame,
+                        const SpdyHeaderBlock* headers) const;
+
+  void WriteHeaderBlockToZ(const SpdyHeaderBlock* headers,
+                           z_stream* out) const;
+
+  // Set the error code and moves the framer into the error state.
+  void set_error(SpdyError error);
+
+  // Given a frame, breakdown the variable payload length, the static header
+  // header length, and variable payload pointer.
+  bool GetFrameBoundaries(const SpdyFrame& frame, int* payload_length,
+                          int* header_length, const char** payload) const;
+
+  // Returns a new SpdyControlFrame with the compressed payload of |frame|.
+  SpdyControlFrame* CompressControlFrame(const SpdyControlFrame& frame,
+                                         const SpdyHeaderBlock* headers);
+
+  // The size of the control frame buffer.
+  // Since this is only used for control frame headers, the maximum control
+  // frame header size (SYN_STREAM) is sufficient; all remaining control
+  // frame data is streamed to the visitor.
+  static const size_t kControlFrameBufferSize;
+
+  // The maximum size of the control frames that we support.
+  // This limit is arbitrary. We can enforce it here or at the application
+  // layer. We chose the framing layer, but this can be changed (or removed)
+  // if necessary later down the line.
+  static const size_t kMaxControlFrameSize;
+
+  SpdyState state_;
+  SpdyState previous_state_;
+  SpdyError error_code_;
+  size_t remaining_data_;
+
+  // The number of bytes remaining to read from the current control frame's
+  // payload.
+  size_t remaining_control_payload_;
+
+  // The number of bytes remaining to read from the current control frame's
+  // headers. Note that header data blocks (for control types that have them)
+  // are part of the frame's payload, and not the frame's headers.
+  size_t remaining_control_header_;
+
+  scoped_array<char> current_frame_buffer_;
+  size_t current_frame_len_;  // Number of bytes read into the current_frame_.
+
+  // Scratch space for handling SETTINGS frames.
+  // TODO(hkhalil): Unify memory for this scratch space with
+  // current_frame_buffer_.
+  SpdySettingsScratch settings_scratch_;
+
+  bool enable_compression_;  // Controls all compression
+  // SPDY header compressors.
+  scoped_ptr<z_stream> header_compressor_;
+  scoped_ptr<z_stream> header_decompressor_;
+
+  SpdyFramerVisitorInterface* visitor_;
+
+  std::string display_protocol_;
+
+  // The SPDY version to be spoken/understood by this framer. We support only
+  // integer versions here, as major version numbers indicate framer-layer
+  // incompatibility and minor version numbers indicate application-layer
+  // incompatibility.
+  const int spdy_version_;
+
+  // Tracks if we've ever gotten far enough in framing to see a control frame of
+  // type SYN_STREAM or SYN_REPLY.
+  //
+  // If we ever get something which looks like a data frame before we've had a
+  // SYN, we explicitly check to see if it looks like we got an HTTP response to
+  // a SPDY request.  This boolean lets us do that.
+  bool syn_frame_processed_;
+
+  // If we ever get a data frame before a SYN frame, we check to see if it
+  // starts with HTTP.  If it does, we likely have an HTTP response.   This
+  // isn't guaranteed though: we could have gotten a settings frame and then
+  // corrupt data that just looks like HTTP, but deterministic checking requires
+  // a lot more state.
+  bool probable_http_response_;
+};
+
+}  // namespace net
+
+#endif  // NET_SPDY_SPDY_FRAMER_H_
diff --git a/src/net/spdy/spdy_framer_test.cc b/src/net/spdy/spdy_framer_test.cc
new file mode 100644
index 0000000..e0b0d57
--- /dev/null
+++ b/src/net/spdy/spdy_framer_test.cc
@@ -0,0 +1,3218 @@
+// Copyright (c) 2012 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 <limits>
+
+#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/gmock/include/gmock/gmock.h"
+#include "testing/platform_test.h"
+
+using std::string;
+using std::max;
+using std::min;
+using std::numeric_limits;
+using testing::_;
+
+namespace net {
+
+namespace test {
+
+static const size_t kMaxDecompressedSize = 1024;
+
+class MockVisitor : public SpdyFramerVisitorInterface {
+ public:
+  MOCK_METHOD1(OnError, void(SpdyFramer* framer));
+  MOCK_METHOD6(OnSynStream, void(SpdyStreamId stream_id,
+                                 SpdyStreamId associated_stream_id,
+                                 SpdyPriority priority,
+                                 uint8 slot,
+                                 bool fin,
+                                 bool unidirectional));
+  MOCK_METHOD2(OnSynReply, void(SpdyStreamId stream_id, bool fin));
+  MOCK_METHOD2(OnHeaders, void(SpdyStreamId stream_id, bool fin));
+  MOCK_METHOD3(OnControlFrameHeaderData, bool(SpdyStreamId stream_id,
+                                              const char* header_data,
+                                              size_t len));
+  MOCK_METHOD2(OnCredentialFrameData, bool(const char* header_data,
+                                           size_t len));
+  MOCK_METHOD1(OnDataFrameHeader, void(const SpdyDataFrame* frame));
+  MOCK_METHOD4(OnStreamFrameData, void(SpdyStreamId stream_id,
+                                       const char* data,
+                                       size_t len,
+                                       SpdyDataFlags flags));
+  MOCK_METHOD3(OnSetting, void(SpdySettingsIds id, uint8 flags, uint32 value));
+  MOCK_METHOD1(OnPing, void(uint32 unique_id));
+  MOCK_METHOD2(OnRstStream, void(SpdyStreamId stream_id,
+                                 SpdyStatusCodes status));
+  MOCK_METHOD2(OnGoAway, void(SpdyStreamId last_accepted_stream_id,
+                              SpdyGoAwayStatus status));
+  MOCK_METHOD2(OnWindowUpdate, void(SpdyStreamId stream_id,
+                                    int delta_window_size));
+  MOCK_METHOD2(OnControlFrameCompressed,
+               void(const SpdyControlFrame& uncompressed_frame,
+                    const SpdyControlFrame& compressed_frame));
+};
+
+class SpdyFramerTestUtil {
+ public:
+  // Decompress a single frame using the decompression context held by
+  // the SpdyFramer.  The implemention is meant for use only in tests
+  // and will CHECK fail if the input is anything other than a single,
+  // well-formed compressed frame.
+  //
+  // Returns a new decompressed SpdyFrame.
+  template<class SpdyFrameType> static SpdyFrame* DecompressFrame(
+      SpdyFramer* framer, const SpdyFrameType& frame) {
+    DecompressionVisitor visitor(framer->protocol_version());
+    framer->set_visitor(&visitor);
+    size_t input_size = frame.length() + SpdyFrame::kHeaderSize;
+    CHECK_EQ(input_size, framer->ProcessInput(frame.data(), input_size));
+    CHECK_EQ(SpdyFramer::SPDY_RESET, framer->state());
+    framer->set_visitor(NULL);
+
+    char* buffer = visitor.ReleaseBuffer();
+    CHECK(buffer != NULL);
+    SpdyFrame* decompressed_frame = new SpdyFrame(buffer, true);
+    decompressed_frame->set_length(visitor.size() - SpdyFrame::kHeaderSize);
+    return decompressed_frame;
+  }
+
+  class DecompressionVisitor : public SpdyFramerVisitorInterface {
+   public:
+    explicit DecompressionVisitor(int version)
+        : version_(version), buffer_(NULL), size_(0), finished_(false) {
+    }
+
+    void ResetBuffer() {
+      CHECK(buffer_.get() == NULL);
+      CHECK_EQ(0u, size_);
+      CHECK(!finished_);
+      buffer_.reset(new char[kMaxDecompressedSize]);
+    }
+
+    virtual void OnSynStream(SpdyStreamId stream_id,
+                             SpdyStreamId associated_stream_id,
+                             SpdyPriority priority,
+                             uint8 slot,
+                             bool fin,
+                             bool unidirectional) {
+      SpdyFramer framer(version_);
+      const SpdyHeaderBlock null_headers;
+      int flags = CONTROL_FLAG_NONE;
+      if (fin) {
+        flags &= CONTROL_FLAG_FIN;
+      }
+      if (unidirectional) {
+        flags &= CONTROL_FLAG_UNIDIRECTIONAL;
+      }
+      scoped_ptr<SpdySynStreamControlFrame> frame(
+          framer.CreateSynStream(stream_id,
+                                 associated_stream_id,
+                                 priority,
+                                 slot,
+                                 static_cast<SpdyControlFlags>(flags),
+                                 false,
+                                 &null_headers));
+      ResetBuffer();
+      memcpy(buffer_.get(), frame->data(), SpdySynStreamControlFrame::size());
+      size_ += SpdySynStreamControlFrame::size();
+    }
+
+    virtual void OnSynReply(SpdyStreamId stream_id, bool fin) {
+      SpdyFramer framer(version_);
+      const SpdyHeaderBlock null_headers;
+      int flags = CONTROL_FLAG_NONE;
+      if (fin) {
+        flags &= CONTROL_FLAG_FIN;
+      }
+      scoped_ptr<SpdyHeadersControlFrame> frame(
+          framer.CreateHeaders(stream_id,
+                               static_cast<SpdyControlFlags>(flags),
+                               false,
+                               &null_headers));
+      ResetBuffer();
+      memcpy(buffer_.get(), frame->data(), SpdyHeadersControlFrame::size());
+      size_ += SpdySynStreamControlFrame::size();
+    }
+
+    virtual void OnHeaders(SpdyStreamId stream_id, bool fin) {
+      SpdyFramer framer(version_);
+      const SpdyHeaderBlock null_headers;
+      int flags = CONTROL_FLAG_NONE;
+      if (fin) {
+        flags &= CONTROL_FLAG_FIN;
+      }
+      scoped_ptr<SpdyHeadersControlFrame> frame(
+          framer.CreateHeaders(stream_id,
+                               static_cast<SpdyControlFlags>(flags),
+                               false,
+                               &null_headers));
+      ResetBuffer();
+      memcpy(buffer_.get(), frame->data(), SpdyHeadersControlFrame::size());
+      size_ += SpdySynStreamControlFrame::size();
+    }
+
+    virtual bool OnControlFrameHeaderData(SpdyStreamId stream_id,
+                                          const char* header_data,
+                                          size_t len) {
+      CHECK(buffer_.get() != NULL);
+      CHECK_GE(kMaxDecompressedSize, size_ + len);
+      CHECK(!finished_);
+      if (len != 0) {
+        memcpy(buffer_.get() + size_, header_data, len);
+        size_ += len;
+      } else {
+        // Done.
+        finished_ = true;
+      }
+      return true;
+    }
+
+    virtual bool OnCredentialFrameData(const char* header_data,
+                                       size_t len) {
+      LOG(FATAL) << "Unexpected CREDENTIAL Frame";
+      return false;
+    }
+
+    virtual void OnError(SpdyFramer* framer) { LOG(FATAL); }
+    virtual void OnDataFrameHeader(const SpdyDataFrame* frame) {
+      LOG(FATAL) << "Unexpected data frame header";
+    }
+    virtual void OnStreamFrameData(SpdyStreamId stream_id,
+                                   const char* data,
+                                   size_t len,
+                                   SpdyDataFlags flags) {
+      LOG(FATAL);
+    }
+    virtual void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) {
+      LOG(FATAL);
+    }
+    virtual void OnControlFrameCompressed(
+        const SpdyControlFrame& uncompressed_frame,
+        const SpdyControlFrame& compressed_frame) {
+    }
+    virtual void OnPing(uint32 unique_id) {
+      LOG(FATAL);
+    }
+    virtual void OnRstStream(SpdyStreamId stream_id, SpdyStatusCodes status) {
+      LOG(FATAL);
+    }
+    virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+                          SpdyGoAwayStatus status) {
+      LOG(FATAL);
+    }
+    virtual void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) {
+      LOG(FATAL);
+    }
+
+    char* ReleaseBuffer() {
+      CHECK(finished_);
+      return buffer_.release();
+    }
+
+    size_t size() const {
+      CHECK(finished_);
+      return size_;
+    }
+
+   private:
+    int version_;
+    scoped_array<char> buffer_;
+    size_t size_;
+    bool finished_;
+
+    DISALLOW_COPY_AND_ASSIGN(DecompressionVisitor);
+  };
+
+  DISALLOW_COPY_AND_ASSIGN(SpdyFramerTestUtil);
+};
+
+string HexDumpWithMarks(const unsigned char* data, int length,
+                             const bool* marks, int mark_length) {
+  static const char kHexChars[] = "0123456789abcdef";
+  static const int kColumns = 4;
+
+  const int kSizeLimit = 1024;
+  if (length > kSizeLimit || mark_length > kSizeLimit) {
+    LOG(ERROR) << "Only dumping first " << kSizeLimit << " bytes.";
+    length = min(length, kSizeLimit);
+    mark_length = min(mark_length, kSizeLimit);
+  }
+
+  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 string& description,
+    const unsigned char* actual,
+    const int actual_len,
+    const unsigned char* expected,
+    const int expected_len) {
+  const int min_len = min(actual_len, expected_len);
+  const int max_len = max(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);
+}
+
+class TestSpdyVisitor : public SpdyFramerVisitorInterface  {
+ public:
+  static const size_t kDefaultHeaderBufferSize = 16 * 1024;
+  static const size_t kDefaultCredentialBufferSize = 16 * 1024;
+
+  explicit TestSpdyVisitor(int version)
+    : framer_(version),
+      use_compression_(false),
+      error_count_(0),
+      syn_frame_count_(0),
+      syn_reply_frame_count_(0),
+      headers_frame_count_(0),
+      goaway_count_(0),
+      setting_count_(0),
+      data_bytes_(0),
+      fin_frame_count_(0),
+      fin_flag_count_(0),
+      zero_length_data_frame_count_(0),
+      header_blocks_count_(0),
+      control_frame_header_data_count_(0),
+      zero_length_control_frame_header_data_count_(0),
+      data_frame_count_(0),
+      header_buffer_(new char[kDefaultHeaderBufferSize]),
+      header_buffer_length_(0),
+      header_buffer_size_(kDefaultHeaderBufferSize),
+      header_stream_id_(-1),
+      header_control_type_(NUM_CONTROL_FRAME_TYPES),
+      header_buffer_valid_(false),
+      credential_buffer_(new char[kDefaultCredentialBufferSize]),
+      credential_buffer_length_(0),
+      credential_buffer_size_(kDefaultCredentialBufferSize) {
+  }
+
+  void OnError(SpdyFramer* f) {
+    LOG(INFO) << "SpdyFramer Error: "
+              << SpdyFramer::ErrorCodeToString(f->error_code());
+    error_count_++;
+  }
+
+  void OnDataFrameHeader(const SpdyDataFrame* frame) {
+    data_frame_count_++;
+    header_stream_id_ = frame->stream_id();
+  }
+
+  void OnStreamFrameData(SpdyStreamId stream_id,
+                         const char* data,
+                         size_t len,
+                         SpdyDataFlags flags) {
+    EXPECT_EQ(header_stream_id_, stream_id);
+    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";
+  }
+
+  virtual void OnSynStream(SpdyStreamId stream_id,
+                           SpdyStreamId associated_stream_id,
+                           SpdyPriority priority,
+                           uint8 credential_slot,
+                           bool fin,
+                           bool unidirectional) {
+    syn_frame_count_++;
+    InitHeaderStreaming(SYN_STREAM, stream_id);
+    if (fin) {
+      fin_flag_count_++;
+    }
+  }
+
+  virtual void OnSynReply(SpdyStreamId stream_id, bool fin) {
+    syn_reply_frame_count_++;
+    InitHeaderStreaming(HEADERS, stream_id);
+    if (fin) {
+      fin_flag_count_++;
+    }
+  }
+
+  virtual void OnHeaders(SpdyStreamId stream_id, bool fin) {
+    headers_frame_count_++;
+    InitHeaderStreaming(SYN_REPLY, stream_id);
+    if (fin) {
+      fin_flag_count_++;
+    }
+  }
+
+  virtual void OnSetting(SpdySettingsIds id, uint8 flags, uint32 value) {
+    setting_count_++;
+  }
+
+  virtual void OnControlFrameCompressed(
+      const SpdyControlFrame& uncompressed_frame,
+      const SpdyControlFrame& compressed_frame) {
+  }
+
+  virtual void OnPing(uint32 unique_id) {
+    DLOG(FATAL);
+  }
+
+  virtual void OnRstStream(SpdyStreamId stream_id, SpdyStatusCodes status) {
+    fin_frame_count_++;
+  }
+
+  virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+                        SpdyGoAwayStatus status) {
+    goaway_count_++;
+  }
+
+  virtual void OnWindowUpdate(SpdyStreamId stream_id, int delta_window_size) {
+    last_window_update_stream_ = stream_id;
+    last_window_update_delta_ = delta_window_size;
+  }
+
+  bool OnControlFrameHeaderData(SpdyStreamId stream_id,
+                                const char* header_data,
+                                size_t len) {
+    ++control_frame_header_data_count_;
+    CHECK_EQ(header_stream_id_, stream_id);
+    if (len == 0) {
+      ++zero_length_control_frame_header_data_count_;
+      // Indicates end-of-header-block.
+      CHECK(header_buffer_valid_);
+      bool parsed_headers = framer_.ParseHeaderBlockInBuffer(
+          header_buffer_.get(), header_buffer_length_, &headers_);
+      DCHECK(parsed_headers);
+      return true;
+    }
+    const size_t available = header_buffer_size_ - header_buffer_length_;
+    if (len > available) {
+      header_buffer_valid_ = false;
+      return false;
+    }
+    memcpy(header_buffer_.get() + header_buffer_length_, header_data, len);
+    header_buffer_length_ += len;
+    return true;
+  }
+
+  bool OnCredentialFrameData(const char* credential_data,
+                             size_t len) {
+    if (len == 0) {
+      if (!framer_.ParseCredentialData(credential_buffer_.get(),
+                                       credential_buffer_length_,
+                                       &credential_)) {
+        ++error_count_;
+      }
+      return true;
+    }
+    const size_t available =
+        credential_buffer_size_ - credential_buffer_length_;
+    if (len > available) {
+      return false;
+    }
+    memcpy(credential_buffer_.get() + credential_buffer_length_,
+           credential_data, len);
+    credential_buffer_length_ += len;
+    return true;
+  }
+
+  // Convenience function which runs a framer simulation with particular input.
+  void SimulateInFramer(const unsigned char* input, size_t size) {
+    framer_.set_enable_compression(use_compression_);
+    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() % 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();
+    }
+  }
+
+  void InitHeaderStreaming(SpdyControlType header_control_type,
+                           SpdyStreamId stream_id) {
+    memset(header_buffer_.get(), 0, header_buffer_size_);
+    header_buffer_length_ = 0;
+    header_stream_id_ = stream_id;
+    header_control_type_ = header_control_type;
+    header_buffer_valid_ = true;
+    DCHECK_NE(header_stream_id_, SpdyFramer::kInvalidStream);
+  }
+
+  // Override the default buffer size (16K). Call before using the framer!
+  void set_header_buffer_size(size_t header_buffer_size) {
+    header_buffer_size_ = header_buffer_size;
+    header_buffer_.reset(new char[header_buffer_size]);
+  }
+
+  static size_t control_frame_buffer_max_size() {
+    return SpdyFramer::kMaxControlFrameSize;
+  }
+
+  static size_t header_data_chunk_max_size() {
+    return SpdyFramer::kHeaderDataChunkMaxSize;
+  }
+
+  SpdyFramer framer_;
+  bool use_compression_;
+
+  // Counters from the visitor callbacks.
+  int error_count_;
+  int syn_frame_count_;
+  int syn_reply_frame_count_;
+  int headers_frame_count_;
+  int goaway_count_;
+  int setting_count_;
+  int last_window_update_stream_;
+  int last_window_update_delta_;
+  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.
+  int header_blocks_count_;
+  int control_frame_header_data_count_;  // The count of chunks received.
+  // The count of zero-length control frame header data chunks received.
+  int zero_length_control_frame_header_data_count_;
+  int data_frame_count_;
+
+  // Header block streaming state:
+  scoped_array<char> header_buffer_;
+  size_t header_buffer_length_;
+  size_t header_buffer_size_;
+  SpdyStreamId header_stream_id_;
+  SpdyControlType header_control_type_;
+  bool header_buffer_valid_;
+  SpdyHeaderBlock headers_;
+
+  scoped_array<char> credential_buffer_;
+  size_t credential_buffer_length_;
+  size_t credential_buffer_size_;
+  SpdyCredential credential_;
+};
+
+}  // namespace net
+
+using test::CompareCharArraysWithHexError;
+using test::SpdyFramerTestUtil;
+using test::TestSpdyVisitor;
+
+TEST(SpdyFrameBuilderTest, WriteLimits) {
+  SpdyFrameBuilder builder(1, DATA_FLAG_NONE, kLengthMask + 8);
+  // Data frame header is 8 bytes
+  EXPECT_EQ(8u, builder.length());
+  const string kLargeData(kLengthMask, 'A');
+  builder.WriteUInt32(kLengthMask);
+  EXPECT_EQ(12u, builder.length());
+  EXPECT_TRUE(builder.WriteBytes(kLargeData.data(), kLengthMask - 4));
+  EXPECT_EQ(kLengthMask + 8u, builder.length());
+}
+
+enum SpdyFramerTestTypes {
+  SPDY2 = 2,
+  SPDY3 = 3,
+};
+
+class SpdyFramerTest
+    : public ::testing::TestWithParam<SpdyFramerTestTypes> {
+ protected:
+  virtual void SetUp() {
+    spdy_version_ = GetParam();
+  }
+
+  void CompareFrame(const 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::kHeaderSize;
+    CompareCharArraysWithHexError(
+        description, actual, actual_len, expected, expected_len);
+  }
+
+  // Returns true if the two header blocks have equivalent content.
+  bool CompareHeaderBlocks(const SpdyHeaderBlock* expected,
+                           const SpdyHeaderBlock* actual) {
+    if (expected->size() != actual->size()) {
+      LOG(ERROR) << "Expected " << expected->size() << " headers; actually got "
+                 << actual->size() << ".";
+      return false;
+    }
+    for (SpdyHeaderBlock::const_iterator it = expected->begin();
+         it != expected->end();
+         ++it) {
+      SpdyHeaderBlock::const_iterator it2 = actual->find(it->first);
+      if (it2 == actual->end()) {
+        LOG(ERROR) << "Expected header name '" << it->first << "'.";
+        return false;
+      }
+      if (it->second.compare(it2->second) != 0) {
+        LOG(ERROR) << "Expected header named '" << it->first
+                   << "' to have a value of '" << it->second
+                   << "'. The actual value received was '" << it2->second
+                   << "'.";
+        return false;
+      }
+    }
+    return true;
+  }
+
+  void AddSpdySettingFromWireFormat(SettingsMap* settings,
+                                    uint32 key,
+                                    uint32 value) {
+    SettingsFlagsAndId flags_and_id =
+        SettingsFlagsAndId::FromWireFormat(spdy_version_, key);
+    SpdySettingsIds id = static_cast<SpdySettingsIds>(flags_and_id.id());
+    SpdySettingsFlags flags =
+        static_cast<SpdySettingsFlags>(flags_and_id.flags());
+    CHECK(settings->find(id) == settings->end());
+    settings->insert(std::make_pair(id, SettingsFlagsAndValue(flags, value)));
+  }
+
+  bool IsSpdy2() { return spdy_version_ == SPDY2; }
+
+  // Version of SPDY protocol to be used.
+  unsigned char spdy_version_;
+};
+
+// All tests are run with two different SPDY versions: SPDY/2 and SPDY/3.
+INSTANTIATE_TEST_CASE_P(SpdyFramerTests,
+                        SpdyFramerTest,
+                        ::testing::Values(SPDY2, SPDY3));
+
+TEST_P(SpdyFramerTest, IsCompressible) {
+  SpdyFramer framer(spdy_version_);
+  for (SpdyControlType type = SYN_STREAM;
+       type < NUM_CONTROL_FRAME_TYPES;
+       type = static_cast<SpdyControlType>(type + 1)) {
+    SpdyFrameBuilder frame(type, CONTROL_FLAG_NONE, spdy_version_, 1024);
+    scoped_ptr<SpdyControlFrame> control_frame(
+        reinterpret_cast<SpdyControlFrame*>(frame.take()));
+    EXPECT_EQ(control_frame->has_header_block(),
+              framer.IsCompressible(*control_frame))
+        << "Frame type: " << type;
+  }
+}
+
+// Test that we can encode and decode a SpdyHeaderBlock in serialized form.
+TEST_P(SpdyFramerTest, HeaderBlockInBuffer) {
+  SpdyHeaderBlock headers;
+  headers["alpha"] = "beta";
+  headers["gamma"] = "charlie";
+  SpdyFramer framer(spdy_version_);
+
+  // Encode the header block into a SynStream frame.
+  scoped_ptr<SpdySynStreamControlFrame> frame(
+      framer.CreateSynStream(1,  // stream id
+                             0,  // associated stream id
+                             1,  // priority
+                             0,  // credential slot
+                             CONTROL_FLAG_NONE,
+                             false,  // compress
+                             &headers));
+  EXPECT_TRUE(frame.get() != NULL);
+  string serialized_headers(frame->header_block(), frame->header_block_len());
+  SpdyHeaderBlock new_headers;
+  EXPECT_TRUE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(),
+                                              serialized_headers.size(),
+                                              &new_headers));
+
+  EXPECT_EQ(headers.size(), new_headers.size());
+  EXPECT_EQ(headers["alpha"], new_headers["alpha"]);
+  EXPECT_EQ(headers["gamma"], new_headers["gamma"]);
+}
+
+// Test that if there's not a full frame, we fail to parse it.
+TEST_P(SpdyFramerTest, UndersizedHeaderBlockInBuffer) {
+  SpdyHeaderBlock headers;
+  headers["alpha"] = "beta";
+  headers["gamma"] = "charlie";
+  SpdyFramer framer(spdy_version_);
+
+  // Encode the header block into a SynStream frame.
+  scoped_ptr<SpdySynStreamControlFrame> frame(
+      framer.CreateSynStream(1,  // stream id
+                             0,  // associated stream id
+                             1,  // priority
+                             0,  // credential slot
+                             CONTROL_FLAG_NONE,
+                             false,  // compress
+                             &headers));
+  EXPECT_TRUE(frame.get() != NULL);
+
+  string serialized_headers(frame->header_block(), frame->header_block_len());
+  SpdyHeaderBlock new_headers;
+  EXPECT_FALSE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(),
+                                               serialized_headers.size() - 2,
+                                               &new_headers));
+}
+
+TEST_P(SpdyFramerTest, OutOfOrderHeaders) {
+  // Frame builder with plentiful buffer size.
+  SpdyFrameBuilder frame(SYN_STREAM, CONTROL_FLAG_NONE, 1, 1024);
+
+  frame.WriteUInt32(3);  // stream_id
+  frame.WriteUInt32(0);  // Associated stream id
+  frame.WriteUInt16(0);  // Priority.
+
+  if (IsSpdy2()) {
+    frame.WriteUInt16(2);  // Number of headers.
+    frame.WriteString("gamma");
+    frame.WriteString("gamma");
+    frame.WriteString("alpha");
+    frame.WriteString("alpha");
+  } else {
+    frame.WriteUInt32(2);  // Number of headers.
+    frame.WriteStringPiece32("gamma");
+    frame.WriteStringPiece32("gamma");
+    frame.WriteStringPiece32("alpha");
+    frame.WriteStringPiece32("alpha");
+  }
+  // write the length
+  frame.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::kHeaderSize);
+
+  SpdyHeaderBlock new_headers;
+  scoped_ptr<SpdyFrame> control_frame(frame.take());
+  SpdySynStreamControlFrame syn_frame(control_frame->data(), false);
+  string serialized_headers(syn_frame.header_block(),
+                            syn_frame.header_block_len());
+  SpdyFramer framer(spdy_version_);
+  framer.set_enable_compression(false);
+  EXPECT_TRUE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(),
+                                              serialized_headers.size(),
+                                              &new_headers));
+}
+
+TEST_P(SpdyFramerTest, CreateCredential) {
+  SpdyFramer framer(spdy_version_);
+
+  {
+    const char kDescription[] = "CREDENTIAL frame";
+    const unsigned char kFrameData[] = {
+      0x80, spdy_version_, 0x00, 0x0A,
+      0x00, 0x00, 0x00, 0x33,
+      0x00, 0x03, 0x00, 0x00,
+      0x00, 0x05, 'p',  'r',
+      'o',  'o',  'f',  0x00,
+      0x00, 0x00, 0x06, 'a',
+      ' ',  'c',  'e',  'r',
+      't',  0x00, 0x00, 0x00,
+      0x0C, 'a',  'n',  'o',
+      't',  'h',  'e',  'r',
+      ' ',  'c',  'e',  'r',
+      't',  0x00,  0x00, 0x00,
+      0x0A, 'f',  'i',  'n',
+      'a',  'l',  ' ',  'c',
+      'e',  'r',  't',
+    };
+    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");
+    scoped_ptr<SpdyFrame> frame(framer.CreateCredentialFrame(credential));
+    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+  }
+}
+
+TEST_P(SpdyFramerTest, ParseCredentialFrameData) {
+  SpdyFramer framer(spdy_version_);
+
+  {
+    unsigned char kFrameData[] = {
+      0x80, spdy_version_, 0x00, 0x0A,
+      0x00, 0x00, 0x00, 0x33,
+      0x00, 0x03, 0x00, 0x00,
+      0x00, 0x05, 'p',  'r',
+      'o',  'o',  'f',  0x00,
+      0x00, 0x00, 0x06, 'a',
+      ' ',  'c',  'e',  'r',
+      't',  0x00, 0x00, 0x00,
+      0x0C, 'a',  'n',  'o',
+      't',  'h',  'e',  'r',
+      ' ',  'c',  'e',  'r',
+      't',  0x00,  0x00, 0x00,
+      0x0A, 'f',  'i',  'n',
+      'a',  'l',  ' ',  'c',
+      'e',  'r',  't',
+    };
+    SpdyCredentialControlFrame frame(reinterpret_cast<char*>(kFrameData),
+                                     false);
+    SpdyCredential credential;
+    EXPECT_TRUE(SpdyFramer::ParseCredentialData(frame.payload(), frame.length(),
+                                                &credential));
+    EXPECT_EQ(3u, credential.slot);
+    EXPECT_EQ("proof", credential.proof);
+    EXPECT_EQ("a cert", credential.certs.front());
+    credential.certs.erase(credential.certs.begin());
+    EXPECT_EQ("another cert", credential.certs.front());
+    credential.certs.erase(credential.certs.begin());
+    EXPECT_EQ("final cert", credential.certs.front());
+    credential.certs.erase(credential.certs.begin());
+    EXPECT_TRUE(credential.certs.empty());
+  }
+}
+
+TEST_P(SpdyFramerTest, DuplicateHeader) {
+  // Frame builder with plentiful buffer size.
+  SpdyFrameBuilder frame(SYN_STREAM, CONTROL_FLAG_NONE, 1, 1024);
+
+  frame.WriteUInt32(3);  // stream_id
+  frame.WriteUInt32(0);  // associated stream id
+  frame.WriteUInt16(0);  // Priority.
+
+  if (IsSpdy2()) {
+    frame.WriteUInt16(2);  // Number of headers.
+    frame.WriteString("name");
+    frame.WriteString("value1");
+    frame.WriteString("name");
+    frame.WriteString("value2");
+  } else {
+    frame.WriteUInt32(2);  // Number of headers.
+    frame.WriteStringPiece32("name");
+    frame.WriteStringPiece32("value1");
+    frame.WriteStringPiece32("name");
+    frame.WriteStringPiece32("value2");
+  }
+  // write the length
+  frame.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::kHeaderSize);
+
+  SpdyHeaderBlock new_headers;
+  scoped_ptr<SpdyFrame> control_frame(frame.take());
+  SpdySynStreamControlFrame syn_frame(control_frame->data(), false);
+  string serialized_headers(syn_frame.header_block(),
+                            syn_frame.header_block_len());
+  SpdyFramer framer(spdy_version_);
+  framer.set_enable_compression(false);
+  // This should fail because duplicate headers are verboten by the spec.
+  EXPECT_FALSE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(),
+                                               serialized_headers.size(),
+                                               &new_headers));
+}
+
+TEST_P(SpdyFramerTest, MultiValueHeader) {
+  // Frame builder with plentiful buffer size.
+  SpdyFrameBuilder frame(SYN_STREAM, CONTROL_FLAG_NONE, 1, 1024);
+
+  frame.WriteUInt32(3);  // stream_id
+  frame.WriteUInt32(0);  // associated stream id
+  frame.WriteUInt16(0);  // Priority.
+
+  string value("value1\0value2");
+  if (IsSpdy2()) {
+    frame.WriteUInt16(1);  // Number of headers.
+    frame.WriteString("name");
+    frame.WriteString(value);
+  } else {
+    frame.WriteUInt32(1);  // Number of headers.
+    frame.WriteStringPiece32("name");
+    frame.WriteStringPiece32(value);
+  }
+  // write the length
+  frame.WriteUInt32ToOffset(4, frame.length() - SpdyFrame::kHeaderSize);
+
+  SpdyHeaderBlock new_headers;
+  scoped_ptr<SpdyFrame> control_frame(frame.take());
+  SpdySynStreamControlFrame syn_frame(control_frame->data(), false);
+  string serialized_headers(syn_frame.header_block(),
+                            syn_frame.header_block_len());
+  SpdyFramer framer(spdy_version_);
+  framer.set_enable_compression(false);
+  EXPECT_TRUE(framer.ParseHeaderBlockInBuffer(serialized_headers.c_str(),
+                                              serialized_headers.size(),
+                                              &new_headers));
+  EXPECT_TRUE(new_headers.find("name") != new_headers.end());
+  EXPECT_EQ(value, new_headers.find("name")->second);
+}
+
+TEST_P(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(spdy_version_);
+  framer.set_enable_compression(true);
+  scoped_ptr<SpdySynStreamControlFrame> frame1(
+      framer.CreateSynStream(1,  // stream id
+                             0,  // associated stream id
+                             1,  // priority
+                             0,  // credential slot
+                             CONTROL_FLAG_NONE,
+                             true,  // compress
+                             &headers));
+  scoped_ptr<SpdySynStreamControlFrame> frame2(
+      framer.CreateSynStream(1,  // stream id
+                             0,  // associated stream id
+                             1,  // priority
+                             0,  // credential slot
+                             CONTROL_FLAG_NONE,
+                             true,  // compress
+                             &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(SpdyFramerTestUtil::DecompressFrame(
+      &framer, *frame1.get()));
+
+  // Decompress the second frame
+  scoped_ptr<SpdyFrame> frame4(SpdyFramerTestUtil::DecompressFrame(
+      &framer, *frame2.get()));
+
+  // Expect frames 3 & 4 to be the same.
+  EXPECT_EQ(0,
+      memcmp(frame3->data(), frame4->data(),
+      SpdyFrame::kHeaderSize + frame3->length()));
+
+
+  // Expect frames 3 to be the same as a uncompressed frame created
+  // from scratch.
+  scoped_ptr<SpdySynStreamControlFrame> uncompressed_frame(
+      framer.CreateSynStream(1,  // stream id
+                             0,  // associated stream id
+                             1,  // priority
+                             0,  // credential slot
+                             CONTROL_FLAG_NONE,
+                             false,  // compress
+                             &headers));
+  EXPECT_EQ(frame3->length(), uncompressed_frame->length());
+  EXPECT_EQ(0,
+      memcmp(frame3->data(), uncompressed_frame->data(),
+      SpdyFrame::kHeaderSize + uncompressed_frame->length()));
+}
+
+TEST_P(SpdyFramerTest, Basic) {
+  const unsigned char kV2Input[] = {
+    0x80, spdy_version_, 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, spdy_version_, 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, spdy_version_, 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, spdy_version_, 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, spdy_version_, 0x00, 0x03,  // RST_STREAM on Stream #3
+    0x00, 0x00, 0x00, 0x08,
+    0x00, 0x00, 0x00, 0x03,
+    0x00, 0x00, 0x00, 0x00,
+  };
+
+  const unsigned char kV3Input[] = {
+    0x80, spdy_version_, 0x00, 0x01,  // SYN Stream #1
+    0x00, 0x00, 0x00, 0x1a,
+    0x00, 0x00, 0x00, 0x01,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x01, 0x00, 0x00,
+    0x00, 0x02, 'h', 'h',
+    0x00, 0x00, 0x00, 0x02,
+    'v', 'v',
+
+    0x80, spdy_version_, 0x00, 0x08,  // HEADERS on Stream #1
+    0x00, 0x00, 0x00, 0x22,
+    0x00, 0x00, 0x00, 0x01,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x02, 0x00, 0x00,
+    0x00, 0x02, 'h', '2',
+    0x00, 0x00, 0x00, 0x02,
+    'v', '2', 0x00, 0x00,
+    0x00, 0x02, 'h', '3',
+    0x00, 0x00, 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, spdy_version_, 0x00, 0x01,  // SYN Stream #3
+    0x00, 0x00, 0x00, 0x0e,
+    0x00, 0x00, 0x00, 0x03,
+    0x00, 0x00, 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, spdy_version_, 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, spdy_version_, 0x00, 0x03,  // RST_STREAM on Stream #3
+    0x00, 0x00, 0x00, 0x08,
+    0x00, 0x00, 0x00, 0x03,
+    0x00, 0x00, 0x00, 0x00,
+  };
+
+  TestSpdyVisitor visitor(spdy_version_);
+  if (IsSpdy2()) {
+    visitor.SimulateInFramer(kV2Input, sizeof(kV2Input));
+  } else {
+    visitor.SimulateInFramer(kV3Input, sizeof(kV3Input));
+  }
+
+  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_);
+  EXPECT_EQ(4, visitor.data_frame_count_);
+}
+
+// Test that the FIN flag on a data frame signifies EOF.
+TEST_P(SpdyFramerTest, FinOnDataFrame) {
+  const unsigned char kV2Input[] = {
+    0x80, spdy_version_, 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, spdy_version_, 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,
+  };
+  const unsigned char kV3Input[] = {
+    0x80, spdy_version_, 0x00, 0x01,  // SYN Stream #1
+    0x00, 0x00, 0x00, 0x1a,
+    0x00, 0x00, 0x00, 0x01,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x01, 0x00, 0x00,
+    0x00, 0x02, 'h', 'h',
+    0x00, 0x00, 0x00, 0x02,
+    'v', 'v',
+
+    0x80, spdy_version_, 0x00, 0x02,  // SYN REPLY Stream #1
+    0x00, 0x00, 0x00, 0x16,
+    0x00, 0x00, 0x00, 0x01,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x01, 0x00, 0x00,
+    0x00, 0x02, 'a', 'a',
+    0x00, 0x00, 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(spdy_version_);
+  if (IsSpdy2()) {
+    visitor.SimulateInFramer(kV2Input, sizeof(kV2Input));
+  } else {
+    visitor.SimulateInFramer(kV3Input, sizeof(kV3Input));
+  }
+
+  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_);
+  EXPECT_EQ(2, visitor.data_frame_count_);
+}
+
+// Test that the FIN flag on a SYN reply frame signifies EOF.
+TEST_P(SpdyFramerTest, FinOnSynReplyFrame) {
+  const unsigned char kV2Input[] = {
+    0x80, spdy_version_, 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, spdy_version_, 0x00, 0x02,  // SYN REPLY Stream #1
+    0x01, 0x00, 0x00, 0x14,
+    0x00, 0x00, 0x00, 0x01,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x01,
+    0x00, 0x02, 'a', 'a',
+    0x00, 0x02, 'b', 'b',
+  };
+  const unsigned char kV3Input[] = {
+    0x80, spdy_version_, 0x00, 0x01,  // SYN Stream #1
+    0x00, 0x00, 0x00, 0x1a,
+    0x00, 0x00, 0x00, 0x01,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x01, 0x00, 0x00,
+    0x00, 0x02, 'h', 'h',
+    0x00, 0x00, 0x00, 0x02,
+    'v', 'v',
+
+    0x80, spdy_version_, 0x00, 0x02,  // SYN REPLY Stream #1
+    0x01, 0x00, 0x00, 0x1a,
+    0x00, 0x00, 0x00, 0x01,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x01, 0x00, 0x00,
+    0x00, 0x02, 'a', 'a',
+    0x00, 0x00, 0x00, 0x02,
+    'b', 'b',
+  };
+
+  TestSpdyVisitor visitor(spdy_version_);
+  if (IsSpdy2()) {
+    visitor.SimulateInFramer(kV2Input, sizeof(kV2Input));
+  } else {
+    visitor.SimulateInFramer(kV3Input, sizeof(kV3Input));
+  }
+
+  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_);
+  EXPECT_EQ(0, visitor.data_frame_count_);
+}
+
+TEST_P(SpdyFramerTest, HeaderCompression) {
+  SpdyFramer send_framer(spdy_version_);
+  SpdyFramer recv_framer(spdy_version_);
+
+  send_framer.set_enable_compression(true);
+  recv_framer.set_enable_compression(true);
+
+  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<SpdySynStreamControlFrame> syn_frame_1(
+      send_framer.CreateSynStream(1,  // stream id
+                                  0,  // associated stream id
+                                  0,  // priority
+                                  0,  // credential slot
+                                  flags,
+                                  true,  // compress
+                                  &block));
+  EXPECT_TRUE(syn_frame_1.get() != NULL);
+
+  // SYN_STREAM #2
+  block[kHeader3] = kValue3;
+  scoped_ptr<SpdySynStreamControlFrame> syn_frame_2(
+      send_framer.CreateSynStream(3,  // stream id
+                                  0,  // associated stream id
+                                  0,  // priority
+                                  0,  // credential slot
+                                  flags,
+                                  true,  // compress
+                                  &block));
+  EXPECT_TRUE(syn_frame_2.get() != NULL);
+
+  // Now start decompressing
+  scoped_ptr<SpdyFrame> decompressed;
+  scoped_ptr<SpdySynStreamControlFrame> syn_frame;
+  scoped_ptr<string> serialized_headers;
+  SpdyHeaderBlock decompressed_headers;
+
+  // Decompress SYN_STREAM #1
+  decompressed.reset(SpdyFramerTestUtil::DecompressFrame(
+      &recv_framer, *syn_frame_1.get()));
+  EXPECT_TRUE(decompressed.get() != NULL);
+  EXPECT_TRUE(decompressed->is_control_frame());
+  EXPECT_EQ(SYN_STREAM,
+            reinterpret_cast<SpdyControlFrame*>(decompressed.get())->type());
+  syn_frame.reset(new SpdySynStreamControlFrame(decompressed->data(), false));
+  serialized_headers.reset(new string(syn_frame->header_block(),
+                                      syn_frame->header_block_len()));
+  EXPECT_TRUE(recv_framer.ParseHeaderBlockInBuffer(serialized_headers->c_str(),
+                                                   serialized_headers->size(),
+                                                   &decompressed_headers));
+  EXPECT_EQ(2u, decompressed_headers.size());
+  EXPECT_EQ(kValue1, decompressed_headers[kHeader1]);
+  EXPECT_EQ(kValue2, decompressed_headers[kHeader2]);
+
+  // Decompress SYN_STREAM #2
+  decompressed.reset(SpdyFramerTestUtil::DecompressFrame(
+      &recv_framer, *syn_frame_2.get()));
+  EXPECT_TRUE(decompressed.get() != NULL);
+  EXPECT_TRUE(decompressed->is_control_frame());
+  EXPECT_EQ(SYN_STREAM,
+            reinterpret_cast<SpdyControlFrame*>(decompressed.get())->type());
+  syn_frame.reset(new SpdySynStreamControlFrame(decompressed->data(), false));
+  serialized_headers.reset(new string(syn_frame->header_block(),
+                                      syn_frame->header_block_len()));
+  decompressed_headers.clear();
+  EXPECT_TRUE(recv_framer.ParseHeaderBlockInBuffer(serialized_headers->c_str(),
+                                                   serialized_headers->size(),
+                                                   &decompressed_headers));
+  EXPECT_EQ(3u, decompressed_headers.size());
+  EXPECT_EQ(kValue1, decompressed_headers[kHeader1]);
+  EXPECT_EQ(kValue2, decompressed_headers[kHeader2]);
+  EXPECT_EQ(kValue3, decompressed_headers[kHeader3]);
+}
+
+// Verify we don't leak when we leave streams unclosed
+TEST_P(SpdyFramerTest, UnclosedStreamDataCompressors) {
+  SpdyFramer send_framer(spdy_version_);
+
+  send_framer.set_enable_compression(true);
+
+  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<SpdyFrame> syn_frame(
+      send_framer.CreateSynStream(1,  // stream id
+                                  0,  // associated stream id
+                                  0,  // priority
+                                  0,  // credential slot
+                                  flags,
+                                  true,  // compress
+                                  &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),
+          static_cast<SpdyDataFlags>(DATA_FLAG_FIN)));
+  EXPECT_TRUE(send_frame.get() != NULL);
+
+  // Run the inputs through the framer.
+  TestSpdyVisitor visitor(spdy_version_);
+  visitor.use_compression_ = true;
+  const unsigned char* data;
+  data = reinterpret_cast<const unsigned char*>(syn_frame->data());
+  visitor.SimulateInFramer(data, syn_frame->length() + SpdyFrame::kHeaderSize);
+  data = reinterpret_cast<const unsigned char*>(send_frame->data());
+  visitor.SimulateInFramer(data, send_frame->length() + SpdyFrame::kHeaderSize);
+
+  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_);
+  EXPECT_EQ(1, visitor.data_frame_count_);
+}
+
+// Verify we can decompress the stream even if handed over to the
+// framer 1 byte at a time.
+TEST_P(SpdyFramerTest, UnclosedStreamDataCompressorsOneByteAtATime) {
+  SpdyFramer send_framer(spdy_version_);
+
+  send_framer.set_enable_compression(true);
+
+  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<SpdyFrame> syn_frame(
+      send_framer.CreateSynStream(1,  // stream id
+                                  0,  // associated stream id
+                                  0,  // priority
+                                  0,  // credential slot
+                                  flags,
+                                  true,  // compress
+                                  &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),
+          static_cast<SpdyDataFlags>(DATA_FLAG_FIN)));
+  EXPECT_TRUE(send_frame.get() != NULL);
+
+  // Run the inputs through the framer.
+  TestSpdyVisitor visitor(spdy_version_);
+  visitor.use_compression_ = true;
+  const unsigned char* data;
+  data = reinterpret_cast<const unsigned char*>(syn_frame->data());
+  for (size_t idx = 0;
+       idx < syn_frame->length() + SpdyFrame::kHeaderSize;
+       ++idx) {
+    visitor.SimulateInFramer(data + idx, 1);
+    ASSERT_EQ(0, visitor.error_count_);
+  }
+  data = reinterpret_cast<const unsigned char*>(send_frame->data());
+  for (size_t idx = 0;
+       idx < send_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.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_);
+  EXPECT_EQ(1, visitor.data_frame_count_);
+}
+
+TEST_P(SpdyFramerTest, WindowUpdateFrame) {
+  SpdyFramer framer(spdy_version_);
+  scoped_ptr<SpdyWindowUpdateControlFrame> window_update_frame(
+      framer.CreateWindowUpdate(1, 0x12345678));
+
+  const unsigned char expected_data_frame[] = {
+      0x80, spdy_version_, 0x00, 0x09,
+      0x00, 0x00, 0x00, 0x08,
+      0x00, 0x00, 0x00, 0x01,
+      0x12, 0x34, 0x56, 0x78
+  };
+
+  EXPECT_EQ(16u, window_update_frame->size());
+  EXPECT_EQ(0,
+            memcmp(window_update_frame->data(), expected_data_frame, 16));
+}
+
+TEST_P(SpdyFramerTest, CreateDataFrame) {
+  SpdyFramer framer(spdy_version_);
+
+  {
+    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'
+    };
+    const char bytes[] = "hello";
+    scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
+        1, bytes, strlen(bytes), 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));
+  }
+
+  {
+    const char kDescription[] = "Large data frame";
+    const int kDataSize = 4 * 1024 * 1024;  // 4 MB
+    const string kData(kDataSize, 'A');
+    const unsigned char kFrameHeader[] = {
+      0x00, 0x00, 0x00, 0x01,
+      0x01, 0x40, 0x00, 0x00,
+    };
+
+    const int kFrameSize = arraysize(kFrameHeader) + kDataSize;
+    scoped_array<unsigned char> expected_frame_data(
+        new unsigned char[kFrameSize]);
+    memcpy(expected_frame_data.get(), kFrameHeader, arraysize(kFrameHeader));
+    memset(expected_frame_data.get() + arraysize(kFrameHeader), 'A', kDataSize);
+
+    scoped_ptr<SpdyFrame> frame(framer.CreateDataFrame(
+        1, kData.data(), kData.size(), DATA_FLAG_FIN));
+    CompareFrame(kDescription, *frame, expected_frame_data.get(), kFrameSize);
+  }
+}
+
+TEST_P(SpdyFramerTest, CreateSynStreamUncompressed) {
+  SpdyFramer framer(spdy_version_);
+  framer.set_enable_compression(false);
+
+  {
+    const char kDescription[] = "SYN_STREAM frame, lowest pri, slot 2, no FIN";
+
+    SpdyHeaderBlock headers;
+    headers["bar"] = "foo";
+    headers["foo"] = "bar";
+
+    const unsigned char kPri = IsSpdy2() ? 0xC0 : 0xE0;
+    const unsigned char kCre = IsSpdy2() ? 0 : 2;
+    const unsigned char kV2FrameData[] = {
+      0x80, spdy_version_, 0x00, 0x01,
+      0x00, 0x00, 0x00, 0x20,
+      0x00, 0x00, 0x00, 0x01,
+      0x00, 0x00, 0x00, 0x00,
+      kPri, 0x00, 0x00, 0x02,
+      0x00, 0x03, 'b',  'a',
+      'r',  0x00, 0x03, 'f',
+      'o',  'o',  0x00, 0x03,
+      'f',  'o',  'o',  0x00,
+      0x03, 'b',  'a',  'r'
+    };
+    const unsigned char kV3FrameData[] = {
+      0x80, spdy_version_, 0x00, 0x01,
+      0x00, 0x00, 0x00, 0x2a,
+      0x00, 0x00, 0x00, 0x01,
+      0x00, 0x00, 0x00, 0x00,
+      kPri, kCre, 0x00, 0x00,
+      0x00, 0x02, 0x00, 0x00,
+      0x00, 0x03, 'b',  'a',
+      'r',  0x00, 0x00, 0x00,
+      0x03, 'f',  'o',  'o',
+      0x00, 0x00, 0x00, 0x03,
+      'f',  'o',  'o',  0x00,
+      0x00, 0x00, 0x03, 'b',
+      'a',  'r'
+    };
+    scoped_ptr<SpdySynStreamControlFrame> frame(
+        framer.CreateSynStream(1,  // stream id
+                               0,  // associated stream id
+                               framer.GetLowestPriority(),
+                               kCre,  // credential slot
+                               CONTROL_FLAG_NONE,
+                               false,  // compress
+                               &headers));
+    CompareFrame(kDescription,
+                 *frame,
+                 IsSpdy2() ? kV2FrameData : kV3FrameData,
+                 IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+    EXPECT_EQ(1u, SpdyFramer::GetControlFrameStreamId(frame.get()));
+  }
+
+  {
+    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 kV2FrameData[] = {
+      0x80, spdy_version_, 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'
+    };
+    const unsigned char kV3FrameData[] = {
+      0x80, spdy_version_, 0x00, 0x01,
+      0x01, 0x00, 0x00, 0x27,
+      0x7f, 0xff, 0xff, 0xff,
+      0x7f, 0xff, 0xff, 0xff,
+      0x00, 0x00, 0x00, 0x00,
+      0x00, 0x02, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00,
+      0x00, 0x03, 'f',  'o',
+      'o',  0x00, 0x00, 0x00,
+      0x03, 'f',  'o',  'o',
+      0x00, 0x00, 0x00, 0x03,
+      'b',  'a',  'r'
+    };
+    scoped_ptr<SpdyFrame> frame(
+        framer.CreateSynStream(0x7fffffff,  // stream id
+                               0x7fffffff,  // associated stream id
+                               framer.GetHighestPriority(),
+                               0,  // credential slot
+                               CONTROL_FLAG_FIN,
+                               false,  // compress
+                               &headers));
+    CompareFrame(kDescription,
+                 *frame,
+                 IsSpdy2() ? kV2FrameData : kV3FrameData,
+                 IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+  }
+
+  {
+    const char kDescription[] =
+        "SYN_STREAM frame with a 0-length header val, high pri, FIN, "
+        "max stream ID";
+
+    SpdyHeaderBlock headers;
+    headers["bar"] = "foo";
+    headers["foo"] = "";
+
+    const unsigned char kPri = IsSpdy2() ? 0x40 : 0x20;
+    const unsigned char kV2FrameData[] = {
+      0x80, spdy_version_, 0x00, 0x01,
+      0x01, 0x00, 0x00, 0x1D,
+      0x7f, 0xff, 0xff, 0xff,
+      0x7f, 0xff, 0xff, 0xff,
+      kPri, 0x00, 0x00, 0x02,
+      0x00, 0x03, 'b',  'a',
+      'r',  0x00, 0x03, 'f',
+      'o',  'o',  0x00, 0x03,
+      'f',  'o',  'o',  0x00,
+      0x00
+    };
+    const unsigned char kV3FrameData[] = {
+      0x80, spdy_version_, 0x00, 0x01,
+      0x01, 0x00, 0x00, 0x27,
+      0x7f, 0xff, 0xff, 0xff,
+      0x7f, 0xff, 0xff, 0xff,
+      kPri, 0x00, 0x00, 0x00,
+      0x00, 0x02, 0x00, 0x00,
+      0x00, 0x03, 'b',  'a',
+      'r',  0x00, 0x00, 0x00,
+      0x03, 'f',  'o',  'o',
+      0x00, 0x00, 0x00, 0x03,
+      'f',  'o',  'o',  0x00,
+      0x00, 0x00, 0x00
+    };
+    scoped_ptr<SpdyFrame> frame(
+        framer.CreateSynStream(0x7fffffff,  // stream id
+                               0x7fffffff,  // associated stream id
+                               1,  // priority
+                               0,  // credential slot
+                               CONTROL_FLAG_FIN,
+                               false,  // compress
+                               &headers));
+    CompareFrame(kDescription,
+                 *frame,
+                 IsSpdy2() ? kV2FrameData : kV3FrameData,
+                 IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+  }
+}
+
+// TODO(phajdan.jr): Clean up after we no longer need
+// to workaround http://crbug.com/139744.
+#if !defined(USE_SYSTEM_ZLIB)
+TEST_P(SpdyFramerTest, CreateSynStreamCompressed) {
+  SpdyFramer framer(spdy_version_);
+  framer.set_enable_compression(true);
+
+  {
+    const char kDescription[] =
+        "SYN_STREAM frame, low pri, no FIN";
+
+    SpdyHeaderBlock headers;
+    headers["bar"] = "foo";
+    headers["foo"] = "bar";
+
+    const SpdyPriority priority = IsSpdy2() ? 2 : 4;
+    const unsigned char kV2FrameData[] = {
+      0x80, spdy_version_, 0x00, 0x01,
+      0x00, 0x00, 0x00, 0x36,
+      0x00, 0x00, 0x00, 0x01,
+      0x00, 0x00, 0x00, 0x00,
+      0x80, 0x00, 0x38, 0xea,
+      0xdf, 0xa2, 0x51, 0xb2,
+      0x62, 0x60, 0x62, 0x60,
+      0x4e, 0x4a, 0x2c, 0x62,
+      0x60, 0x06, 0x08, 0xa0,
+      0xb4, 0xfc, 0x7c, 0x80,
+      0x00, 0x62, 0x60, 0x4e,
+      0xcb, 0xcf, 0x67, 0x60,
+      0x06, 0x08, 0xa0, 0xa4,
+      0xc4, 0x22, 0x80, 0x00,
+      0x02, 0x00, 0x00, 0x00,
+      0xff, 0xff,
+    };
+    const unsigned char kV3FrameData[] = {
+      0x80, spdy_version_, 0x00, 0x01,
+      0x00, 0x00, 0x00, 0x37,
+      0x00, 0x00, 0x00, 0x01,
+      0x00, 0x00, 0x00, 0x00,
+      0x80, 0x00, 0x38, 0xEA,
+      0xE3, 0xC6, 0xA7, 0xC2,
+      0x02, 0xE5, 0x0E, 0x50,
+      0xC2, 0x4B, 0x4A, 0x04,
+      0xE5, 0x0B, 0x66, 0x80,
+      0x00, 0x4A, 0xCB, 0xCF,
+      0x07, 0x08, 0x20, 0x10,
+      0x95, 0x96, 0x9F, 0x0F,
+      0xA2, 0x00, 0x02, 0x28,
+      0x29, 0xB1, 0x08, 0x20,
+      0x80, 0x00, 0x00, 0x00,
+      0x00, 0xFF, 0xFF,
+    };
+    scoped_ptr<SpdyFrame> frame(
+        framer.CreateSynStream(1,  // stream id
+                               0,  // associated stream id
+                               priority,
+                               0,  // credential slot
+                               CONTROL_FLAG_NONE,
+                               true,  // compress
+                               &headers));
+    CompareFrame(kDescription,
+                 *frame,
+                 IsSpdy2() ? kV2FrameData : kV3FrameData,
+                 IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+  }
+}
+#endif  // !defined(USE_SYSTEM_ZLIB)
+
+TEST_P(SpdyFramerTest, CreateSynReplyUncompressed) {
+  SpdyFramer framer(spdy_version_);
+  framer.set_enable_compression(false);
+
+  {
+    const char kDescription[] = "SYN_REPLY frame, no FIN";
+
+    SpdyHeaderBlock headers;
+    headers["bar"] = "foo";
+    headers["foo"] = "bar";
+
+    const unsigned char kV2FrameData[] = {
+      0x80, spdy_version_, 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'
+    };
+    const unsigned char kV3FrameData[] = {
+      0x80, spdy_version_, 0x00, 0x02,
+      0x00, 0x00, 0x00, 0x24,
+      0x00, 0x00, 0x00, 0x01,
+      0x00, 0x00, 0x00, 0x02,
+      0x00, 0x00, 0x00, 0x03,
+      'b',  'a',  'r',  0x00,
+      0x00, 0x00, 0x03, 'f',
+      'o',  'o',  0x00, 0x00,
+      0x00, 0x03, 'f',  'o',
+      'o',  0x00, 0x00, 0x00,
+      0x03, 'b',  'a',  'r'
+    };
+    scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
+        1, CONTROL_FLAG_NONE, false, &headers));
+    CompareFrame(kDescription,
+                 *frame,
+                 IsSpdy2() ? kV2FrameData : kV3FrameData,
+                 IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+  }
+
+  {
+    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 kV2FrameData[] = {
+      0x80, spdy_version_, 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'
+    };
+    const unsigned char kV3FrameData[] = {
+      0x80, spdy_version_, 0x00, 0x02,
+      0x01, 0x00, 0x00, 0x21,
+      0x7f, 0xff, 0xff, 0xff,
+      0x00, 0x00, 0x00, 0x02,
+      0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x03,
+      'f',  'o',  'o',  0x00,
+      0x00, 0x00, 0x03, 'f',
+      'o',  'o',  0x00, 0x00,
+      0x00, 0x03, 'b',  'a',
+      'r'
+    };
+    scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
+        0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
+    CompareFrame(kDescription,
+                 *frame,
+                 IsSpdy2() ? kV2FrameData : kV3FrameData,
+                 IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+  }
+
+  {
+    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 kV2FrameData[] = {
+      0x80, spdy_version_, 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
+    };
+    const unsigned char kV3FrameData[] = {
+      0x80, spdy_version_, 0x00, 0x02,
+      0x01, 0x00, 0x00, 0x21,
+      0x7f, 0xff, 0xff, 0xff,
+      0x00, 0x00, 0x00, 0x02,
+      0x00, 0x00, 0x00, 0x03,
+      'b',  'a',  'r',  0x00,
+      0x00, 0x00, 0x03, 'f',
+      'o',  'o',  0x00, 0x00,
+      0x00, 0x03, 'f',  'o',
+      'o',  0x00, 0x00, 0x00,
+      0x00
+    };
+    scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
+        0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
+    CompareFrame(kDescription,
+                 *frame,
+                 IsSpdy2() ? kV2FrameData : kV3FrameData,
+                 IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+  }
+}
+
+// TODO(phajdan.jr): Clean up after we no longer need
+// to workaround http://crbug.com/139744.
+#if !defined(USE_SYSTEM_ZLIB)
+TEST_P(SpdyFramerTest, CreateSynReplyCompressed) {
+  SpdyFramer framer(spdy_version_);
+  framer.set_enable_compression(true);
+
+  {
+    const char kDescription[] = "SYN_REPLY frame, no FIN";
+
+    SpdyHeaderBlock headers;
+    headers["bar"] = "foo";
+    headers["foo"] = "bar";
+
+    const unsigned char kV2FrameData[] = {
+      0x80, spdy_version_, 0x00, 0x02,
+      0x00, 0x00, 0x00, 0x32,
+      0x00, 0x00, 0x00, 0x01,
+      0x00, 0x00, 0x38, 0xea,
+      0xdf, 0xa2, 0x51, 0xb2,
+      0x62, 0x60, 0x62, 0x60,
+      0x4e, 0x4a, 0x2c, 0x62,
+      0x60, 0x06, 0x08, 0xa0,
+      0xb4, 0xfc, 0x7c, 0x80,
+      0x00, 0x62, 0x60, 0x4e,
+      0xcb, 0xcf, 0x67, 0x60,
+      0x06, 0x08, 0xa0, 0xa4,
+      0xc4, 0x22, 0x80, 0x00,
+      0x02, 0x00, 0x00, 0x00,
+      0xff, 0xff,
+    };
+    const unsigned char kV3FrameData[] = {
+      0x80, spdy_version_, 0x00, 0x02,
+      0x00, 0x00, 0x00, 0x31,
+      0x00, 0x00, 0x00, 0x01,
+      0x38, 0xea, 0xe3, 0xc6,
+      0xa7, 0xc2, 0x02, 0xe5,
+      0x0e, 0x50, 0xc2, 0x4b,
+      0x4a, 0x04, 0xe5, 0x0b,
+      0x66, 0x80, 0x00, 0x4a,
+      0xcb, 0xcf, 0x07, 0x08,
+      0x20, 0x10, 0x95, 0x96,
+      0x9f, 0x0f, 0xa2, 0x00,
+      0x02, 0x28, 0x29, 0xb1,
+      0x08, 0x20, 0x80, 0x00,
+      0x00, 0x00, 0x00, 0xff,
+      0xff,
+    };
+    scoped_ptr<SpdyFrame> frame(framer.CreateSynReply(
+        1, CONTROL_FLAG_NONE, true, &headers));
+    CompareFrame(kDescription,
+                 *frame,
+                 IsSpdy2() ? kV2FrameData : kV3FrameData,
+                 IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+  }
+}
+#endif  // !defined(USE_SYSTEM_ZLIB)
+
+TEST_P(SpdyFramerTest, CreateRstStream) {
+  SpdyFramer framer(spdy_version_);
+
+  {
+    const char kDescription[] = "RST_STREAM frame";
+    const unsigned char kFrameData[] = {
+      0x80, spdy_version_, 0x00, 0x03,
+      0x00, 0x00, 0x00, 0x08,
+      0x00, 0x00, 0x00, 0x01,
+      0x00, 0x00, 0x00, 0x01,
+    };
+    scoped_ptr<SpdyRstStreamControlFrame> frame(
+        framer.CreateRstStream(1, PROTOCOL_ERROR));
+    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+    EXPECT_EQ(1u, SpdyFramer::GetControlFrameStreamId(frame.get()));
+  }
+
+  {
+    const char kDescription[] = "RST_STREAM frame with max stream ID";
+    const unsigned char kFrameData[] = {
+      0x80, spdy_version_, 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, spdy_version_, 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_P(SpdyFramerTest, CreateSettings) {
+  SpdyFramer framer(spdy_version_);
+
+  {
+    const char kDescription[] = "Network byte order SETTINGS frame";
+
+    uint32 kValue = 0x0a0b0c0d;
+    SpdySettingsFlags kFlags = static_cast<SpdySettingsFlags>(0x04);
+    SpdySettingsIds kId = static_cast<SpdySettingsIds>(0x030201);
+
+    SettingsMap settings;
+    settings[kId] = SettingsFlagsAndValue(kFlags, kValue);
+
+    EXPECT_EQ(kFlags, settings[kId].first);
+    EXPECT_EQ(kValue, settings[kId].second);
+
+    const unsigned char kFrameDatav2[] = {
+      0x80, spdy_version_, 0x00, 0x04,
+      0x00, 0x00, 0x00, 0x0c,
+      0x00, 0x00, 0x00, 0x01,
+      0x01, 0x02, 0x03, 0x04,
+      0x0a, 0x0b, 0x0c, 0x0d,
+    };
+
+    const unsigned char kFrameDatav3[] = {
+      0x80, spdy_version_, 0x00, 0x04,
+      0x00, 0x00, 0x00, 0x0c,
+      0x00, 0x00, 0x00, 0x01,
+      0x04, 0x03, 0x02, 0x01,
+      0x0a, 0x0b, 0x0c, 0x0d,
+    };
+
+    scoped_ptr<SpdySettingsControlFrame> frame(framer.CreateSettings(settings));
+    CompareFrame(kDescription,
+                 *frame,
+                 IsSpdy2() ? kFrameDatav2 : kFrameDatav3,
+                 arraysize(kFrameDatav3));  // Size is unchanged among versions.
+    EXPECT_EQ(SpdyFramer::kInvalidStream,
+              SpdyFramer::GetControlFrameStreamId(frame.get()));
+
+    // Make sure that ParseSettings also works as advertised.
+    SettingsMap parsed_settings;
+    EXPECT_TRUE(framer.ParseSettings(frame.get(), &parsed_settings));
+    EXPECT_EQ(settings.size(), parsed_settings.size());
+    EXPECT_EQ(kFlags, parsed_settings[kId].first);
+    EXPECT_EQ(kValue, parsed_settings[kId].second);
+  }
+
+  {
+    const char kDescription[] = "Basic SETTINGS frame";
+
+    SettingsMap settings;
+#if defined(__LB_SHELL__)
+    // second argument is expected to be in network byte order
+    AddSpdySettingFromWireFormat(
+        &settings, htonl(0x00000000), 0x00000001);  // 1st Setting
+    AddSpdySettingFromWireFormat(
+        &settings, htonl(0x02000001), 0x00000002);  // 2nd Setting
+    AddSpdySettingFromWireFormat(
+        &settings, htonl(0x03000002), 0x00000003);  // 3rd Setting
+    AddSpdySettingFromWireFormat(
+        &settings, htonl(0x04000003), 0xff000004);  // 4th Setting
+    AddSpdySettingFromWireFormat(
+        &settings, htonl(0x050000ff), 0x00000005);  // 5th Setting
+    AddSpdySettingFromWireFormat(
+        &settings, htonl(0xffffffff), 0x00000006);  // 6th Setting
+#else
+    AddSpdySettingFromWireFormat(
+        &settings, 0x00000000, 0x00000001);  // 1st Setting
+    AddSpdySettingFromWireFormat(
+        &settings, 0x01000002, 0x00000002);  // 2nd Setting
+    AddSpdySettingFromWireFormat(
+        &settings, 0x02000003, 0x00000003);  // 3rd Setting
+    AddSpdySettingFromWireFormat(
+        &settings, 0x03000004, 0xff000004);  // 4th Setting
+    AddSpdySettingFromWireFormat(
+        &settings, 0xff000005, 0x00000005);  // 5th Setting
+    AddSpdySettingFromWireFormat(
+        &settings, 0xffffffff, 0x00000006);  // 6th Setting
+#endif
+
+    const unsigned char kFrameData[] = {
+      0x80, spdy_version_, 0x00, 0x04,
+      0x00, 0x00, 0x00, 0x34,
+      0x00, 0x00, 0x00, 0x06,
+      0x00, 0x00, 0x00, 0x00,  // 1st Setting
+      0x00, 0x00, 0x00, 0x01,
+      0x02, 0x00, 0x00, 0x01,  // 2nd Setting
+      0x00, 0x00, 0x00, 0x02,
+      0x03, 0x00, 0x00, 0x02,  // 3rd Setting
+      0x00, 0x00, 0x00, 0x03,
+      0x04, 0x00, 0x00, 0x03,  // 4th Setting
+      0xff, 0x00, 0x00, 0x04,
+      0x05, 0x00, 0x00, 0xff,  // 5th Setting
+      0x00, 0x00, 0x00, 0x05,
+      0xff, 0xff, 0xff, 0xff,  // 6th Setting
+      0x00, 0x00, 0x00, 0x06,
+    };
+    scoped_ptr<SpdySettingsControlFrame> frame(framer.CreateSettings(settings));
+    CompareFrame(kDescription,
+                 *frame,
+                 kFrameData,
+                 arraysize(kFrameData));
+    EXPECT_EQ(SpdyFramer::kInvalidStream,
+              SpdyFramer::GetControlFrameStreamId(frame.get()));
+  }
+
+  {
+    const char kDescription[] = "Empty SETTINGS frame";
+
+    SettingsMap settings;
+
+    const unsigned char kFrameData[] = {
+      0x80, spdy_version_, 0x00, 0x04,
+      0x00, 0x00, 0x00, 0x04,
+      0x00, 0x00, 0x00, 0x00,
+    };
+    scoped_ptr<SpdyFrame> frame(framer.CreateSettings(settings));
+    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+  }
+}
+
+TEST_P(SpdyFramerTest, CreatePingFrame) {
+  SpdyFramer framer(spdy_version_);
+
+  {
+    const char kDescription[] = "PING frame";
+    const unsigned char kFrameData[] = {
+        0x80, spdy_version_, 0x00, 0x06,
+        0x00, 0x00, 0x00, 0x04,
+        0x12, 0x34, 0x56, 0x78,
+    };
+    scoped_ptr<SpdyPingControlFrame> frame(framer.CreatePingFrame(0x12345678u));
+    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+    EXPECT_EQ(SpdyFramer::kInvalidStream,
+              SpdyFramer::GetControlFrameStreamId(frame.get()));
+  }
+}
+
+TEST_P(SpdyFramerTest, CreateGoAway) {
+  SpdyFramer framer(spdy_version_);
+
+  {
+    const char kDescription[] = "GOAWAY frame";
+    const unsigned char kV2FrameData[] = {
+      0x80, spdy_version_, 0x00, 0x07,
+      0x00, 0x00, 0x00, 0x04,
+      0x00, 0x00, 0x00, 0x00,
+    };
+    const unsigned char kV3FrameData[] = {
+      0x80, spdy_version_, 0x00, 0x07,
+      0x00, 0x00, 0x00, 0x08,
+      0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00,
+    };
+    scoped_ptr<SpdyGoAwayControlFrame> frame(framer.CreateGoAway(0, GOAWAY_OK));
+    CompareFrame(kDescription,
+                 *frame,
+                 IsSpdy2() ? kV2FrameData : kV3FrameData,
+                 IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+    EXPECT_EQ(SpdyFramer::kInvalidStream,
+              SpdyFramer::GetControlFrameStreamId(frame.get()));
+  }
+
+  {
+    const char kDescription[] = "GOAWAY frame with max stream ID, status";
+    const unsigned char kV2FrameData[] = {
+      0x80, spdy_version_, 0x00, 0x07,
+      0x00, 0x00, 0x00, 0x04,
+      0x7f, 0xff, 0xff, 0xff,
+    };
+    const unsigned char kV3FrameData[] = {
+      0x80, spdy_version_, 0x00, 0x07,
+      0x00, 0x00, 0x00, 0x08,
+      0x7f, 0xff, 0xff, 0xff,
+      0x00, 0x00, 0x00, 0x02,
+    };
+    scoped_ptr<SpdyFrame> frame(framer.CreateGoAway(0x7FFFFFFF,
+                                                    GOAWAY_INTERNAL_ERROR));
+    CompareFrame(kDescription,
+                 *frame,
+                 IsSpdy2() ? kV2FrameData : kV3FrameData,
+                 IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+  }
+}
+
+TEST_P(SpdyFramerTest, CreateHeadersUncompressed) {
+  SpdyFramer framer(spdy_version_);
+  framer.set_enable_compression(false);
+
+  {
+    const char kDescription[] = "HEADERS frame, no FIN";
+
+    SpdyHeaderBlock headers;
+    headers["bar"] = "foo";
+    headers["foo"] = "bar";
+
+    const unsigned char kV2FrameData[] = {
+      0x80, spdy_version_, 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'
+    };
+    const unsigned char kV3FrameData[] = {
+      0x80, spdy_version_, 0x00, 0x08,
+      0x00, 0x00, 0x00, 0x24,
+      0x00, 0x00, 0x00, 0x01,
+      0x00, 0x00, 0x00, 0x02,
+      0x00, 0x00, 0x00, 0x03,
+      'b',  'a',  'r',  0x00,
+      0x00, 0x00, 0x03, 'f',
+      'o',  'o',  0x00, 0x00,
+      0x00, 0x03, 'f',  'o',
+      'o',  0x00, 0x00, 0x00,
+      0x03, 'b',  'a',  'r'
+    };
+    scoped_ptr<SpdyFrame> frame(framer.CreateHeaders(
+        1, CONTROL_FLAG_NONE, false, &headers));
+    CompareFrame(kDescription,
+                 *frame,
+                 IsSpdy2() ? kV2FrameData : kV3FrameData,
+                 IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+  }
+
+  {
+    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 kV2FrameData[] = {
+      0x80, spdy_version_, 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'
+    };
+    const unsigned char kV3FrameData[] = {
+      0x80, spdy_version_, 0x00, 0x08,
+      0x01, 0x00, 0x00, 0x21,
+      0x7f, 0xff, 0xff, 0xff,
+      0x00, 0x00, 0x00, 0x02,
+      0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x03,
+      'f',  'o',  'o',  0x00,
+      0x00, 0x00, 0x03, 'f',
+      'o',  'o',  0x00, 0x00,
+      0x00, 0x03, 'b',  'a',
+      'r'
+    };
+    scoped_ptr<SpdyFrame> frame(framer.CreateHeaders(
+        0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
+    CompareFrame(kDescription,
+                 *frame,
+                 IsSpdy2() ? kV2FrameData : kV3FrameData,
+                 IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+  }
+
+  {
+    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 kV2FrameData[] = {
+      0x80, spdy_version_, 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
+    };
+    const unsigned char kV3FrameData[] = {
+      0x80, spdy_version_, 0x00, 0x08,
+      0x01, 0x00, 0x00, 0x21,
+      0x7f, 0xff, 0xff, 0xff,
+      0x00, 0x00, 0x00, 0x02,
+      0x00, 0x00, 0x00, 0x03,
+      'b',  'a',  'r',  0x00,
+      0x00, 0x00, 0x03, 'f',
+      'o',  'o',  0x00, 0x00,
+      0x00, 0x03, 'f',  'o',
+      'o',  0x00, 0x00, 0x00,
+      0x00
+    };
+    scoped_ptr<SpdyFrame> frame(framer.CreateHeaders(
+        0x7fffffff, CONTROL_FLAG_FIN, false, &headers));
+    CompareFrame(kDescription,
+                 *frame,
+                 IsSpdy2() ? kV2FrameData : kV3FrameData,
+                 IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+  }
+}
+
+// TODO(phajdan.jr): Clean up after we no longer need
+// to workaround http://crbug.com/139744.
+#if !defined(USE_SYSTEM_ZLIB)
+TEST_P(SpdyFramerTest, CreateHeadersCompressed) {
+  SpdyFramer framer(spdy_version_);
+  framer.set_enable_compression(true);
+
+  {
+    const char kDescription[] = "HEADERS frame, no FIN";
+
+    SpdyHeaderBlock headers;
+    headers["bar"] = "foo";
+    headers["foo"] = "bar";
+
+    const unsigned char kV2FrameData[] = {
+      0x80, spdy_version_, 0x00, 0x08,
+      0x00, 0x00, 0x00, 0x32,
+      0x00, 0x00, 0x00, 0x01,
+      0x00, 0x00, 0x38, 0xea,
+      0xdf, 0xa2, 0x51, 0xb2,
+      0x62, 0x60, 0x62, 0x60,
+      0x4e, 0x4a, 0x2c, 0x62,
+      0x60, 0x06, 0x08, 0xa0,
+      0xb4, 0xfc, 0x7c, 0x80,
+      0x00, 0x62, 0x60, 0x4e,
+      0xcb, 0xcf, 0x67, 0x60,
+      0x06, 0x08, 0xa0, 0xa4,
+      0xc4, 0x22, 0x80, 0x00,
+      0x02, 0x00, 0x00, 0x00,
+      0xff, 0xff,
+    };
+    const unsigned char kV3FrameData[] = {
+      0x80, spdy_version_, 0x00, 0x08,
+      0x00, 0x00, 0x00, 0x31,
+      0x00, 0x00, 0x00, 0x01,
+      0x38, 0xea, 0xe3, 0xc6,
+      0xa7, 0xc2, 0x02, 0xe5,
+      0x0e, 0x50, 0xc2, 0x4b,
+      0x4a, 0x04, 0xe5, 0x0b,
+      0x66, 0x80, 0x00, 0x4a,
+      0xcb, 0xcf, 0x07, 0x08,
+      0x20, 0x10, 0x95, 0x96,
+      0x9f, 0x0f, 0xa2, 0x00,
+      0x02, 0x28, 0x29, 0xb1,
+      0x08, 0x20, 0x80, 0x00,
+      0x00, 0x00, 0x00, 0xff,
+      0xff,
+    };
+    scoped_ptr<SpdyFrame> frame(framer.CreateHeaders(
+        1, CONTROL_FLAG_NONE, true, &headers));
+    CompareFrame(kDescription,
+                 *frame,
+                 IsSpdy2() ? kV2FrameData : kV3FrameData,
+                 IsSpdy2() ? arraysize(kV2FrameData) : arraysize(kV3FrameData));
+  }
+}
+#endif  // !defined(USE_SYSTEM_ZLIB)
+
+TEST_P(SpdyFramerTest, CreateWindowUpdate) {
+  SpdyFramer framer(spdy_version_);
+
+  {
+    const char kDescription[] = "WINDOW_UPDATE frame";
+    const unsigned char kFrameData[] = {
+      0x80, spdy_version_, 0x00, 0x09,
+      0x00, 0x00, 0x00, 0x08,
+      0x00, 0x00, 0x00, 0x01,
+      0x00, 0x00, 0x00, 0x01,
+    };
+    scoped_ptr<SpdyWindowUpdateControlFrame> frame(
+        framer.CreateWindowUpdate(1, 1));
+    CompareFrame(kDescription, *frame, kFrameData, arraysize(kFrameData));
+    EXPECT_EQ(1u, SpdyFramer::GetControlFrameStreamId(frame.get()));
+  }
+
+  {
+    const char kDescription[] = "WINDOW_UPDATE frame with max stream ID";
+    const unsigned char kFrameData[] = {
+      0x80, spdy_version_, 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, spdy_version_, 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));
+  }
+}
+
+TEST_P(SpdyFramerTest, DuplicateFrame) {
+  SpdyFramer framer(spdy_version_);
+
+  {
+    const char kDescription[] = "PING frame";
+    const unsigned char kFrameData[] = {
+        0x80, spdy_version_, 0x00, 0x06,
+        0x00, 0x00, 0x00, 0x04,
+        0x12, 0x34, 0x56, 0x78,
+    };
+    scoped_ptr<SpdyFrame> frame1(framer.CreatePingFrame(0x12345678u));
+    CompareFrame(kDescription, *frame1, kFrameData, arraysize(kFrameData));
+
+    scoped_ptr<SpdyFrame> frame2(framer.DuplicateFrame(*frame1));
+    CompareFrame(kDescription, *frame2, kFrameData, arraysize(kFrameData));
+  }
+}
+
+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->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->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->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.
+  size_t overhead = SpdyFramer::kControlFrameBufferSize;
+  SpdyFramer framer(spdy_version_);
+  scoped_ptr<SpdyGoAwayControlFrame> goaway(framer.CreateGoAway(1, GOAWAY_OK));
+  goaway->set_length(goaway->length() + overhead);
+  string pad('A', overhead);
+  TestSpdyVisitor visitor(spdy_version_);
+
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(goaway->data()),
+      goaway->length() - overhead + SpdyControlFrame::kHeaderSize);
+  visitor.SimulateInFramer(
+      reinterpret_cast<const unsigned char*>(pad.c_str()),
+      overhead);
+
+  EXPECT_EQ(1, visitor.error_count_);  // This generated an error.
+  EXPECT_EQ(SpdyFramer::SPDY_INVALID_CONTROL_FRAME,
+            visitor.framer_.error_code());
+  EXPECT_EQ(0, visitor.goaway_count_);  // Frame not parsed.
+}
+
+TEST_P(SpdyFramerTest, ReadZeroLenSettingsFrame) {
+  SpdyFramer framer(spdy_version_);
+  SettingsMap settings;
+  scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings));
+  control_frame->set_length(0);
+  TestSpdyVisitor visitor(spdy_version_);
+  visitor.use_compression_ = false;
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame->data()),
+      control_frame->length() + SpdyControlFrame::kHeaderSize);
+  // Should generate an error, since zero-len settings frames are unsupported.
+  EXPECT_EQ(1, visitor.error_count_);
+}
+
+// Tests handling of SETTINGS frames with invalid length.
+TEST_P(SpdyFramerTest, ReadBogusLenSettingsFrame) {
+  SpdyFramer framer(spdy_version_);
+  SettingsMap settings;
+  // Add a setting to pad the frame so that we don't get a buffer overflow when
+  // calling SimulateInFramer() below.
+  settings[SETTINGS_UPLOAD_BANDWIDTH] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_PLEASE_PERSIST, 0x00000002);
+  scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings));
+  control_frame->set_length(5);
+  TestSpdyVisitor visitor(spdy_version_);
+  visitor.use_compression_ = false;
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame->data()),
+      control_frame->length() + SpdyControlFrame::kHeaderSize);
+  // Should generate an error, since zero-len settings frames are unsupported.
+  EXPECT_EQ(1, visitor.error_count_);
+}
+
+// Tests handling of SETTINGS frames larger than the frame buffer size.
+TEST_P(SpdyFramerTest, ReadLargeSettingsFrame) {
+  SpdyFramer framer(spdy_version_);
+  SettingsMap settings;
+  SpdySettingsFlags flags = SETTINGS_FLAG_PLEASE_PERSIST;
+  settings[SETTINGS_UPLOAD_BANDWIDTH] =
+      SettingsFlagsAndValue(flags, 0x00000002);
+  settings[SETTINGS_DOWNLOAD_BANDWIDTH] =
+      SettingsFlagsAndValue(flags, 0x00000003);
+  settings[SETTINGS_ROUND_TRIP_TIME] = SettingsFlagsAndValue(flags, 0x00000004);
+  scoped_ptr<SpdyFrame> control_frame(framer.CreateSettings(settings));
+  EXPECT_LT(SpdyFramer::kControlFrameBufferSize,
+            control_frame->length() + SpdyControlFrame::kHeaderSize);
+  TestSpdyVisitor visitor(spdy_version_);
+  visitor.use_compression_ = false;
+
+  // Read all at once.
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame->data()),
+      control_frame->length() + SpdyControlFrame::kHeaderSize);
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(settings.size(), static_cast<unsigned>(visitor.setting_count_));
+
+  // Read data in small chunks.
+  size_t framed_data = 0;
+  size_t unframed_data = control_frame->length() +
+                         SpdyControlFrame::kHeaderSize;
+  size_t kReadChunkSize = 5;  // Read five bytes at a time.
+  while (unframed_data > 0) {
+    size_t to_read = min(kReadChunkSize, unframed_data);
+    visitor.SimulateInFramer(
+        reinterpret_cast<unsigned char*>(control_frame->data() + framed_data),
+        to_read);
+    unframed_data -= to_read;
+    framed_data += to_read;
+  }
+  EXPECT_EQ(0, visitor.error_count_);
+  EXPECT_EQ(settings.size() * 2, static_cast<unsigned>(visitor.setting_count_));
+}
+
+// Tests handling of SETTINGS frame with duplicate entries.
+TEST_P(SpdyFramerTest, ReadDuplicateSettings) {
+  SpdyFramer framer(spdy_version_);
+
+  const unsigned char kV2FrameData[] = {
+    0x80, spdy_version_, 0x00, 0x04,
+    0x00, 0x00, 0x00, 0x1C,
+    0x00, 0x00, 0x00, 0x03,
+    0x01, 0x00, 0x00, 0x00,  // 1st Setting
+    0x00, 0x00, 0x00, 0x02,
+    0x01, 0x00, 0x00, 0x00,  // 2nd (duplicate) Setting
+    0x00, 0x00, 0x00, 0x03,
+    0x03, 0x00, 0x00, 0x00,  // 3rd (unprocessed) Setting
+    0x00, 0x00, 0x00, 0x03,
+  };
+
+  const unsigned char kV3FrameData[] = {
+    0x80, spdy_version_, 0x00, 0x04,
+    0x00, 0x00, 0x00, 0x1C,
+    0x00, 0x00, 0x00, 0x03,
+    0x00, 0x00, 0x00, 0x01,  // 1st Setting
+    0x00, 0x00, 0x00, 0x02,
+    0x00, 0x00, 0x00, 0x01,  // 2nd (duplicate) Setting
+    0x00, 0x00, 0x00, 0x03,
+    0x00, 0x00, 0x00, 0x03,  // 3rd (unprocessed) Setting
+    0x00, 0x00, 0x00, 0x03,
+  };
+
+  TestSpdyVisitor visitor(spdy_version_);
+  visitor.use_compression_ = false;
+  if (IsSpdy2()) {
+    visitor.SimulateInFramer(kV2FrameData, sizeof(kV2FrameData));
+  } else {
+    visitor.SimulateInFramer(kV3FrameData, sizeof(kV3FrameData));
+  }
+  EXPECT_EQ(1, visitor.error_count_);
+  EXPECT_EQ(1, visitor.setting_count_);
+}
+
+// Tests handling of SETTINGS frame with entries out of order.
+TEST_P(SpdyFramerTest, ReadOutOfOrderSettings) {
+  SpdyFramer framer(spdy_version_);
+
+  const unsigned char kV2FrameData[] = {
+    0x80, spdy_version_, 0x00, 0x04,
+    0x00, 0x00, 0x00, 0x1C,
+    0x00, 0x00, 0x00, 0x03,
+    0x02, 0x00, 0x00, 0x00,  // 1st Setting
+    0x00, 0x00, 0x00, 0x02,
+    0x01, 0x00, 0x00, 0x00,  // 2nd (out of order) Setting
+    0x00, 0x00, 0x00, 0x03,
+    0x03, 0x00, 0x00, 0x00,  // 3rd (unprocessed) Setting
+    0x00, 0x00, 0x00, 0x03,
+  };
+
+  const unsigned char kV3FrameData[] = {
+    0x80, spdy_version_, 0x00, 0x04,
+    0x00, 0x00, 0x00, 0x1C,
+    0x00, 0x00, 0x00, 0x03,
+    0x00, 0x00, 0x00, 0x02,  // 1st Setting
+    0x00, 0x00, 0x00, 0x02,
+    0x00, 0x00, 0x00, 0x01,  // 2nd (out of order) Setting
+    0x00, 0x00, 0x00, 0x03,
+    0x00, 0x00, 0x01, 0x03,  // 3rd (unprocessed) Setting
+    0x00, 0x00, 0x00, 0x03,
+  };
+
+  TestSpdyVisitor visitor(spdy_version_);
+  visitor.use_compression_ = false;
+  if (IsSpdy2()) {
+    visitor.SimulateInFramer(kV2FrameData, sizeof(kV2FrameData));
+  } else {
+    visitor.SimulateInFramer(kV3FrameData, sizeof(kV3FrameData));
+  }
+  EXPECT_EQ(1, visitor.error_count_);
+  EXPECT_EQ(1, visitor.setting_count_);
+}
+
+TEST_P(SpdyFramerTest, ReadWindowUpdate) {
+  SpdyFramer framer(spdy_version_);
+  scoped_ptr<SpdyWindowUpdateControlFrame> control_frame(
+      framer.CreateWindowUpdate(1, 2));
+  TestSpdyVisitor visitor(spdy_version_);
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame->data()),
+      control_frame->length() + SpdyControlFrame::kHeaderSize);
+  EXPECT_EQ(1, visitor.last_window_update_stream_);
+  EXPECT_EQ(2, visitor.last_window_update_delta_);
+}
+
+TEST_P(SpdyFramerTest, ReadCredentialFrame) {
+  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;
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame->data()),
+      control_frame->length() + SpdyControlFrame::kHeaderSize);
+  EXPECT_EQ(0, visitor.error_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, 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(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;
+  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;
+  unsigned char* data =
+      reinterpret_cast<unsigned char*>(control_frame->data());
+  size_t offset = SpdyControlFrame::kHeaderSize + 4;
+  data[offset] = 0xFF;  // Proof length is past the end of the frame
+  visitor.SimulateInFramer(
+      data, control_frame->length() + SpdyControlFrame::kHeaderSize);
+  EXPECT_EQ(1, visitor.error_count_);
+}
+
+TEST_P(SpdyFramerTest, ReadCredentialFrameWithCorruptCertificate) {
+  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;
+  unsigned char* data =
+      reinterpret_cast<unsigned char*>(control_frame->data());
+  size_t offset = SpdyControlFrame::kHeaderSize + credential.proof.length();
+  data[offset] = 0xFF;  // Certificate length is past the end of the frame
+  visitor.SimulateInFramer(
+      data, control_frame->length() + SpdyControlFrame::kHeaderSize);
+  EXPECT_EQ(1, visitor.error_count_);
+}
+
+TEST_P(SpdyFramerTest, ReadGarbage) {
+  SpdyFramer framer(spdy_version_);
+  unsigned char garbage_frame[256];
+  memset(garbage_frame, ~0, sizeof(garbage_frame));
+  TestSpdyVisitor visitor(spdy_version_);
+  visitor.use_compression_ = false;
+  visitor.SimulateInFramer(garbage_frame, sizeof(garbage_frame));
+  EXPECT_EQ(1, visitor.error_count_);
+}
+
+TEST_P(SpdyFramerTest, ReadGarbageWithValidVersion) {
+  SpdyFramer framer(spdy_version_);
+  char garbage_frame[256];
+  memset(garbage_frame, ~0, sizeof(garbage_frame));
+  SpdyControlFrame control_frame(&garbage_frame[0], false);
+  control_frame.set_version(spdy_version_);
+  TestSpdyVisitor visitor(spdy_version_);
+  visitor.use_compression_ = false;
+  visitor.SimulateInFramer(
+      reinterpret_cast<unsigned char*>(control_frame.data()),
+      sizeof(garbage_frame));
+  EXPECT_EQ(1, visitor.error_count_);
+}
+
+TEST_P(SpdyFramerTest, StateToStringTest) {
+  EXPECT_STREQ("ERROR",
+               SpdyFramer::StateToString(SpdyFramer::SPDY_ERROR));
+  EXPECT_STREQ("DONE",
+               SpdyFramer::StateToString(SpdyFramer::SPDY_DONE));
+  EXPECT_STREQ("AUTO_RESET",
+               SpdyFramer::StateToString(SpdyFramer::SPDY_AUTO_RESET));
+  EXPECT_STREQ("RESET",
+               SpdyFramer::StateToString(SpdyFramer::SPDY_RESET));
+  EXPECT_STREQ("READING_COMMON_HEADER",
+               SpdyFramer::StateToString(
+                   SpdyFramer::SPDY_READING_COMMON_HEADER));
+  EXPECT_STREQ("CONTROL_FRAME_PAYLOAD",
+               SpdyFramer::StateToString(
+                   SpdyFramer::SPDY_CONTROL_FRAME_PAYLOAD));
+  EXPECT_STREQ("IGNORE_REMAINING_PAYLOAD",
+               SpdyFramer::StateToString(
+                   SpdyFramer::SPDY_IGNORE_REMAINING_PAYLOAD));
+  EXPECT_STREQ("FORWARD_STREAM_FRAME",
+               SpdyFramer::StateToString(
+                   SpdyFramer::SPDY_FORWARD_STREAM_FRAME));
+  EXPECT_STREQ("SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK",
+               SpdyFramer::StateToString(
+                   SpdyFramer::SPDY_CONTROL_FRAME_BEFORE_HEADER_BLOCK));
+  EXPECT_STREQ("SPDY_CONTROL_FRAME_HEADER_BLOCK",
+               SpdyFramer::StateToString(
+                   SpdyFramer::SPDY_CONTROL_FRAME_HEADER_BLOCK));
+  EXPECT_STREQ("SPDY_CREDENTIAL_FRAME_PAYLOAD",
+               SpdyFramer::StateToString(
+                   SpdyFramer::SPDY_CREDENTIAL_FRAME_PAYLOAD));
+  EXPECT_STREQ("SPDY_SETTINGS_FRAME_PAYLOAD",
+               SpdyFramer::StateToString(
+                   SpdyFramer::SPDY_SETTINGS_FRAME_PAYLOAD));
+  EXPECT_STREQ("UNKNOWN_STATE",
+               SpdyFramer::StateToString(
+                   SpdyFramer::SPDY_SETTINGS_FRAME_PAYLOAD + 1));
+}
+
+TEST_P(SpdyFramerTest, ErrorCodeToStringTest) {
+  EXPECT_STREQ("NO_ERROR",
+               SpdyFramer::ErrorCodeToString(SpdyFramer::SPDY_NO_ERROR));
+  EXPECT_STREQ("INVALID_CONTROL_FRAME",
+               SpdyFramer::ErrorCodeToString(
+                   SpdyFramer::SPDY_INVALID_CONTROL_FRAME));
+  EXPECT_STREQ("CONTROL_PAYLOAD_TOO_LARGE",
+               SpdyFramer::ErrorCodeToString(
+                   SpdyFramer::SPDY_CONTROL_PAYLOAD_TOO_LARGE));
+  EXPECT_STREQ("ZLIB_INIT_FAILURE",
+               SpdyFramer::ErrorCodeToString(
+                   SpdyFramer::SPDY_ZLIB_INIT_FAILURE));
+  EXPECT_STREQ("UNSUPPORTED_VERSION",
+               SpdyFramer::ErrorCodeToString(
+                   SpdyFramer::SPDY_UNSUPPORTED_VERSION));
+  EXPECT_STREQ("DECOMPRESS_FAILURE",
+               SpdyFramer::ErrorCodeToString(
+                   SpdyFramer::SPDY_DECOMPRESS_FAILURE));
+  EXPECT_STREQ("COMPRESS_FAILURE",
+               SpdyFramer::ErrorCodeToString(
+                   SpdyFramer::SPDY_COMPRESS_FAILURE));
+  EXPECT_STREQ("SPDY_INVALID_DATA_FRAME_FLAGS",
+               SpdyFramer::ErrorCodeToString(
+                   SpdyFramer::SPDY_INVALID_DATA_FRAME_FLAGS));
+  EXPECT_STREQ("UNKNOWN_ERROR",
+               SpdyFramer::ErrorCodeToString(SpdyFramer::LAST_ERROR));
+}
+
+TEST_P(SpdyFramerTest, StatusCodeToStringTest) {
+  EXPECT_STREQ("INVALID",
+               SpdyFramer::StatusCodeToString(INVALID));
+  EXPECT_STREQ("PROTOCOL_ERROR",
+               SpdyFramer::StatusCodeToString(PROTOCOL_ERROR));
+  EXPECT_STREQ("INVALID_STREAM",
+               SpdyFramer::StatusCodeToString(INVALID_STREAM));
+  EXPECT_STREQ("REFUSED_STREAM",
+               SpdyFramer::StatusCodeToString(REFUSED_STREAM));
+  EXPECT_STREQ("UNSUPPORTED_VERSION",
+               SpdyFramer::StatusCodeToString(UNSUPPORTED_VERSION));
+  EXPECT_STREQ("CANCEL",
+               SpdyFramer::StatusCodeToString(CANCEL));
+  EXPECT_STREQ("INTERNAL_ERROR",
+               SpdyFramer::StatusCodeToString(INTERNAL_ERROR));
+  EXPECT_STREQ("FLOW_CONTROL_ERROR",
+               SpdyFramer::StatusCodeToString(FLOW_CONTROL_ERROR));
+  EXPECT_STREQ("UNKNOWN_STATUS",
+               SpdyFramer::StatusCodeToString(NUM_STATUS_CODES));
+}
+
+TEST_P(SpdyFramerTest, ControlTypeToStringTest) {
+  EXPECT_STREQ("SYN_STREAM",
+               SpdyFramer::ControlTypeToString(SYN_STREAM));
+  EXPECT_STREQ("SYN_REPLY",
+               SpdyFramer::ControlTypeToString(SYN_REPLY));
+  EXPECT_STREQ("RST_STREAM",
+               SpdyFramer::ControlTypeToString(RST_STREAM));
+  EXPECT_STREQ("SETTINGS",
+               SpdyFramer::ControlTypeToString(SETTINGS));
+  EXPECT_STREQ("NOOP",
+               SpdyFramer::ControlTypeToString(NOOP));
+  EXPECT_STREQ("PING",
+               SpdyFramer::ControlTypeToString(PING));
+  EXPECT_STREQ("GOAWAY",
+               SpdyFramer::ControlTypeToString(GOAWAY));
+  EXPECT_STREQ("HEADERS",
+               SpdyFramer::ControlTypeToString(HEADERS));
+  EXPECT_STREQ("WINDOW_UPDATE",
+               SpdyFramer::ControlTypeToString(WINDOW_UPDATE));
+  EXPECT_STREQ("CREDENTIAL",
+               SpdyFramer::ControlTypeToString(CREDENTIAL));
+  EXPECT_STREQ("UNKNOWN_CONTROL_TYPE",
+               SpdyFramer::ControlTypeToString(NUM_CONTROL_FRAME_TYPES));
+}
+
+TEST_P(SpdyFramerTest, GetMinimumControlFrameSizeTest) {
+  EXPECT_EQ(SpdySynStreamControlFrame::size(),
+            SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+                                                   SYN_STREAM));
+  EXPECT_EQ(SpdySynReplyControlFrame::size(),
+            SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+                                                   SYN_REPLY));
+  EXPECT_EQ(SpdyRstStreamControlFrame::size(),
+            SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+                                                   RST_STREAM));
+  EXPECT_EQ(SpdySettingsControlFrame::size(),
+            SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+                                                   SETTINGS));
+
+#if defined(__LB_SHELL__) || defined(COBALT)
+  // Compiler quirk: <function> declared using a type with no linkage,
+  // must be defined in this translation unit
+  unsigned int spdy_frame_header_size = SpdyFrame::kHeaderSize;
+  EXPECT_EQ(spdy_frame_header_size,
+            SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+                                                   NOOP));
+#else
+  EXPECT_EQ(SpdyFrame::kHeaderSize,
+            SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+                                                   NOOP));
+#endif
+  EXPECT_EQ(SpdyPingControlFrame::size(),
+            SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+                                                   PING));
+  size_t goaway_size = SpdyGoAwayControlFrame::size();
+  if (IsSpdy2()) {
+    // SPDY 2 GOAWAY is smaller by 32 bits.
+    goaway_size -= 4;
+  }
+  EXPECT_EQ(goaway_size,
+            SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+                                                   GOAWAY));
+  EXPECT_EQ(SpdyHeadersControlFrame::size(),
+            SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+                                                   HEADERS));
+  EXPECT_EQ(SpdyWindowUpdateControlFrame::size(),
+            SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+                                                   WINDOW_UPDATE));
+  EXPECT_EQ(SpdyCredentialControlFrame::size(),
+            SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+                                                   CREDENTIAL));
+  EXPECT_EQ(numeric_limits<size_t>::max(),
+            SpdyFramer::GetMinimumControlFrameSize(spdy_version_,
+                                                   NUM_CONTROL_FRAME_TYPES));
+}
+
+TEST_P(SpdyFramerTest, CatchProbableHttpResponse) {
+  {
+    testing::StrictMock<test::MockVisitor> visitor;
+    SpdyFramer framer(spdy_version_);
+    framer.set_visitor(&visitor);
+
+    // This won't cause an error at the framer level.  It will cause
+    // flag validation errors at the Visitor::OnDataFrameHeader level.
+    EXPECT_CALL(visitor, OnDataFrameHeader(_));
+    framer.ProcessInput("HTTP/1.1", 8);
+    EXPECT_TRUE(framer.probable_http_response());
+    EXPECT_EQ(SpdyFramer::SPDY_FORWARD_STREAM_FRAME, framer.state());
+  }
+  {
+    testing::StrictMock<test::MockVisitor> visitor;
+    SpdyFramer framer(spdy_version_);
+    framer.set_visitor(&visitor);
+
+    // This won't cause an error at the framer level.  It will cause
+    // flag validation errors at the Visitor::OnDataFrameHeader level.
+    EXPECT_CALL(visitor, OnDataFrameHeader(_));
+    framer.ProcessInput("HTTP/1.0", 8);
+    EXPECT_TRUE(framer.probable_http_response());
+    EXPECT_EQ(SpdyFramer::SPDY_FORWARD_STREAM_FRAME, framer.state());
+  }
+}
+
+TEST_P(SpdyFramerTest, DataFrameFlags) {
+  for (int flags = 0; flags < 256; ++flags) {
+    SCOPED_TRACE(testing::Message() << "Flags " << flags);
+
+    testing::StrictMock<test::MockVisitor> visitor;
+    SpdyFramer framer(spdy_version_);
+    framer.set_visitor(&visitor);
+
+    scoped_ptr<SpdyFrame> frame(
+        framer.CreateDataFrame(1, "hello", 5, DATA_FLAG_NONE));
+    frame->set_flags(flags);
+
+    // Flags are just passed along since they need to be validated at
+    // a higher protocol layer.
+    EXPECT_CALL(visitor, OnDataFrameHeader(_));
+    EXPECT_CALL(visitor, OnStreamFrameData(_, _, 5, SpdyDataFlags()));
+    if (flags & DATA_FLAG_FIN) {
+      EXPECT_CALL(visitor, OnStreamFrameData(_, _, 0, DATA_FLAG_FIN));
+    }
+
+    size_t frame_size = frame->length() + SpdyFrame::kHeaderSize;
+    framer.ProcessInput(frame->data(), frame_size);
+    EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state());
+    EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code());
+  }
+}
+
+TEST_P(SpdyFramerTest, EmptySynStream) {
+  SpdyHeaderBlock headers;
+
+  testing::StrictMock<test::MockVisitor> visitor;
+  SpdyFramer framer(spdy_version_);
+  framer.set_visitor(&visitor);
+
+  EXPECT_CALL(visitor, OnControlFrameCompressed(_, _));
+  scoped_ptr<SpdySynStreamControlFrame>
+      frame(framer.CreateSynStream(1, 0, 1, 0, CONTROL_FLAG_NONE, true,
+                                   &headers));
+  // Adjust size to remove the name/value block.
+  frame->set_length(
+      SpdySynStreamControlFrame::size() - SpdyFrame::kHeaderSize);
+
+  EXPECT_CALL(visitor, OnSynStream(1, 0, 1, 0, false, false));
+  EXPECT_CALL(visitor, OnControlFrameHeaderData(1, NULL, 0));
+
+  size_t frame_size = frame->length() + SpdyFrame::kHeaderSize;
+  framer.ProcessInput(frame->data(), frame_size);
+  EXPECT_EQ(SpdyFramer::SPDY_RESET, framer.state());
+  EXPECT_EQ(SpdyFramer::SPDY_NO_ERROR, framer.error_code());
+}
+
+TEST_P(SpdyFramerTest, SettingsFlagsAndId) {
+  const uint32 kId = 0x020304;
+  const uint32 kFlags = 0x01;
+  const uint32 kWireFormat = htonl(IsSpdy2() ? 0x04030201 : 0x01020304);
+
+  SettingsFlagsAndId id_and_flags =
+      SettingsFlagsAndId::FromWireFormat(spdy_version_, kWireFormat);
+  EXPECT_EQ(kId, id_and_flags.id());
+  EXPECT_EQ(kFlags, id_and_flags.flags());
+  EXPECT_EQ(kWireFormat, id_and_flags.GetWireFormat(spdy_version_));
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_header_block.cc b/src/net/spdy/spdy_header_block.cc
new file mode 100644
index 0000000..3680cb4
--- /dev/null
+++ b/src/net/spdy/spdy_header_block.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 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_header_block.h"
+
+#include "base/values.h"
+
+namespace net {
+
+Value* SpdyHeaderBlockNetLogCallback(
+    const SpdyHeaderBlock* headers,
+    NetLog::LogLevel /* log_level */) {
+  DictionaryValue* dict = new DictionaryValue();
+  DictionaryValue* headers_dict = new DictionaryValue();
+  for (SpdyHeaderBlock::const_iterator it = headers->begin();
+       it != headers->end(); ++it) {
+    headers_dict->SetWithoutPathExpansion(
+        it->first, new StringValue(it->second));
+  }
+  dict->Set("headers", headers_dict);
+  return dict;
+}
+
+bool SpdyHeaderBlockFromNetLogParam(
+    const base::Value* event_param,
+    SpdyHeaderBlock* headers) {
+  headers->clear();
+
+  const base::DictionaryValue* dict = NULL;
+  const base::DictionaryValue* header_dict = NULL;
+
+  if (!event_param ||
+      !event_param->GetAsDictionary(&dict) ||
+      !dict->GetDictionary("headers", &header_dict)) {
+    return false;
+  }
+
+  for (base::DictionaryValue::key_iterator it = header_dict->begin_keys();
+       it != header_dict->end_keys();
+       ++it) {
+    std::string value;
+    if (!header_dict->GetString(*it, &(*headers)[*it])) {
+      headers->clear();
+      return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_header_block.h b/src/net/spdy/spdy_header_block.h
new file mode 100644
index 0000000..8ae8fa3
--- /dev/null
+++ b/src/net/spdy/spdy_header_block.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_SPDY_SPDY_HEADER_BLOCK_H_
+#define NET_SPDY_SPDY_HEADER_BLOCK_H_
+
+#include <map>
+#include <string>
+
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+
+namespace net {
+
+// A data structure for holding a set of headers from either a
+// SYN_STREAM or SYN_REPLY frame.
+typedef std::map<std::string, std::string> SpdyHeaderBlock;
+
+// Converts a SpdyHeaderBlock into NetLog event parameters.  Caller takes
+// ownership of returned value.
+NET_EXPORT base::Value* SpdyHeaderBlockNetLogCallback(
+    const SpdyHeaderBlock* headers,
+    NetLog::LogLevel log_level);
+
+// Converts NetLog event parameters into a SPDY header block and writes them
+// to |headers|.  |event_param| must have been created by
+// SpdyHeaderBlockNetLogCallback.  On failure, returns false and clears
+// |headers|.
+NET_EXPORT bool SpdyHeaderBlockFromNetLogParam(
+    const base::Value* event_param,
+    SpdyHeaderBlock* headers);
+
+}  // namespace net
+
+#endif  // NET_SPDY_SPDY_HEADER_BLOCK_H_
diff --git a/src/net/spdy/spdy_header_block_unittest.cc b/src/net/spdy/spdy_header_block_unittest.cc
new file mode 100644
index 0000000..3cfef16
--- /dev/null
+++ b/src/net/spdy/spdy_header_block_unittest.cc
@@ -0,0 +1,31 @@
+// Copyright (c) 2012 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_header_block.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "net/base/net_log.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+TEST(SpdyHeaderBlockTest, ToNetLogParamAndBackAgain) {
+  SpdyHeaderBlock headers;
+  headers["A"] = "a";
+  headers["B"] = "b";
+
+  scoped_ptr<Value> event_param(
+      SpdyHeaderBlockNetLogCallback(&headers, NetLog::LOG_ALL_BUT_BYTES));
+
+  SpdyHeaderBlock headers2;
+  ASSERT_TRUE(SpdyHeaderBlockFromNetLogParam(event_param.get(), &headers2));
+  EXPECT_EQ(headers, headers2);
+}
+
+}  // namespace
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_http_stream.cc b/src/net/spdy/spdy_http_stream.cc
new file mode 100644
index 0000000..ab1b32f
--- /dev/null
+++ b/src/net/spdy/spdy_http_stream.cc
@@ -0,0 +1,534 @@
+// Copyright (c) 2012 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_http_stream.h"
+
+#include <algorithm>
+#include <list>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/stringprintf.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_info.h"
+#include "net/spdy/spdy_header_block.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_session.h"
+
+namespace net {
+
+SpdyHttpStream::SpdyHttpStream(SpdySession* spdy_session,
+                               bool direct)
+    : ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
+      stream_(NULL),
+      spdy_session_(spdy_session),
+      request_info_(NULL),
+      has_upload_data_(false),
+      response_info_(NULL),
+      download_finished_(false),
+      response_headers_received_(false),
+      user_buffer_len_(0),
+      buffered_read_callback_pending_(false),
+      more_read_data_pending_(false),
+      direct_(direct) { }
+
+void SpdyHttpStream::InitializeWithExistingStream(SpdyStream* spdy_stream) {
+  stream_ = spdy_stream;
+  stream_->SetDelegate(this);
+  response_headers_received_ = true;
+}
+
+SpdyHttpStream::~SpdyHttpStream() {
+  if (stream_)
+    stream_->DetachDelegate();
+}
+
+int SpdyHttpStream::InitializeStream(const HttpRequestInfo* request_info,
+                                     const BoundNetLog& stream_net_log,
+                                     const CompletionCallback& callback) {
+  DCHECK(!stream_.get());
+  if (spdy_session_->IsClosed())
+   return ERR_CONNECTION_CLOSED;
+
+  request_info_ = request_info;
+  if (request_info_->method == "GET") {
+    int error = spdy_session_->GetPushStream(request_info_->url, &stream_,
+                                             stream_net_log);
+    if (error != OK)
+      return error;
+  }
+
+  if (stream_.get())
+    return OK;
+
+  return spdy_session_->CreateStream(request_info_->url,
+                                     request_info_->priority, &stream_,
+                                     stream_net_log, callback);
+}
+
+const HttpResponseInfo* SpdyHttpStream::GetResponseInfo() const {
+  return response_info_;
+}
+
+UploadProgress SpdyHttpStream::GetUploadProgress() const {
+  if (!request_info_ || !request_info_->upload_data_stream)
+    return UploadProgress();
+
+  return UploadProgress(request_info_->upload_data_stream->position(),
+                        request_info_->upload_data_stream->size());
+}
+
+int SpdyHttpStream::ReadResponseHeaders(const CompletionCallback& callback) {
+  CHECK(!callback.is_null());
+  CHECK(!stream_->cancelled());
+
+  if (stream_->closed())
+    return stream_->response_status();
+
+  // Check if we already have the response headers. If so, return synchronously.
+  if(stream_->response_received()) {
+    CHECK(stream_->is_idle());
+    return OK;
+  }
+
+  // Still waiting for the response, return IO_PENDING.
+  CHECK(callback_.is_null());
+  callback_ = callback;
+  return ERR_IO_PENDING;
+}
+
+int SpdyHttpStream::ReadResponseBody(
+    IOBuffer* buf, int buf_len, const CompletionCallback& callback) {
+  CHECK(stream_->is_idle());
+  CHECK(buf);
+  CHECK(buf_len);
+  CHECK(!callback.is_null());
+
+  // If we have data buffered, complete the IO immediately.
+  if (!response_body_.empty()) {
+    int bytes_read = 0;
+    while (!response_body_.empty() && buf_len > 0) {
+      scoped_refptr<IOBufferWithSize> data = response_body_.front();
+      const int bytes_to_copy = std::min(buf_len, data->size());
+      memcpy(&(buf->data()[bytes_read]), data->data(), bytes_to_copy);
+      buf_len -= bytes_to_copy;
+      if (bytes_to_copy == data->size()) {
+        response_body_.pop_front();
+      } else {
+        const int bytes_remaining = data->size() - bytes_to_copy;
+        IOBufferWithSize* new_buffer = new IOBufferWithSize(bytes_remaining);
+        memcpy(new_buffer->data(), &(data->data()[bytes_to_copy]),
+               bytes_remaining);
+        response_body_.pop_front();
+        response_body_.push_front(make_scoped_refptr(new_buffer));
+      }
+      bytes_read += bytes_to_copy;
+    }
+    stream_->IncreaseRecvWindowSize(bytes_read);
+    return bytes_read;
+  } else if (stream_->closed()) {
+    return stream_->response_status();
+  }
+
+  CHECK(callback_.is_null());
+  CHECK(!user_buffer_);
+  CHECK_EQ(0, user_buffer_len_);
+
+  callback_ = callback;
+  user_buffer_ = buf;
+  user_buffer_len_ = buf_len;
+  return ERR_IO_PENDING;
+}
+
+void SpdyHttpStream::Close(bool not_reusable) {
+  // Note: the not_reusable flag has no meaning for SPDY streams.
+
+  Cancel();
+}
+
+HttpStream* SpdyHttpStream::RenewStreamForAuth() {
+  return NULL;
+}
+
+bool SpdyHttpStream::IsResponseBodyComplete() const {
+  if (!stream_)
+    return false;
+  return stream_->closed();
+}
+
+bool SpdyHttpStream::CanFindEndOfResponse() const {
+  return true;
+}
+
+bool SpdyHttpStream::IsMoreDataBuffered() const {
+  return false;
+}
+
+bool SpdyHttpStream::IsConnectionReused() const {
+  return spdy_session_->IsReused();
+}
+
+void SpdyHttpStream::SetConnectionReused() {
+  // SPDY doesn't need an indicator here.
+}
+
+bool SpdyHttpStream::IsConnectionReusable() const {
+  // SPDY streams aren't considered reusable.
+  return false;
+}
+
+int SpdyHttpStream::SendRequest(const HttpRequestHeaders& request_headers,
+                                HttpResponseInfo* response,
+                                const CompletionCallback& callback) {
+  base::Time request_time = base::Time::Now();
+  CHECK(stream_.get());
+
+  stream_->SetDelegate(this);
+
+  scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+  CreateSpdyHeadersFromHttpRequest(*request_info_, request_headers,
+                                   headers.get(), stream_->GetProtocolVersion(),
+                                   direct_);
+  stream_->net_log().AddEvent(
+      NetLog::TYPE_HTTP_TRANSACTION_SPDY_SEND_REQUEST_HEADERS,
+      base::Bind(&SpdyHeaderBlockNetLogCallback, headers.get()));
+  stream_->set_spdy_headers(headers.Pass());
+
+  stream_->SetRequestTime(request_time);
+  // This should only get called in the case of a request occurring
+  // during server push that has already begun but hasn't finished,
+  // so we set the response's request time to be the actual one
+  if (response_info_)
+    response_info_->request_time = request_time;
+
+  CHECK(!has_upload_data_);
+  has_upload_data_ = request_info_->upload_data_stream &&
+      (request_info_->upload_data_stream->size() ||
+       request_info_->upload_data_stream->is_chunked());
+  if (has_upload_data_) {
+    // Use kMaxSpdyFrameChunkSize as the buffer size, since the request
+    // body data is written with this size at a time.
+    raw_request_body_buf_ = new IOBufferWithSize(kMaxSpdyFrameChunkSize);
+    // The request body buffer is empty at first.
+    request_body_buf_ = new DrainableIOBuffer(raw_request_body_buf_, 0);
+  }
+
+  CHECK(!callback.is_null());
+  CHECK(!stream_->cancelled());
+  CHECK(response);
+
+  if (!stream_->pushed() && stream_->closed()) {
+    if (stream_->response_status() == OK)
+      return ERR_FAILED;
+    else
+      return stream_->response_status();
+  }
+
+  // SendRequest can be called in two cases.
+  //
+  // a) A client initiated request. In this case, |response_info_| should be
+  //    NULL to start with.
+  // b) A client request which matches a response that the server has already
+  //    pushed.
+  if (push_response_info_.get()) {
+    *response = *(push_response_info_.get());
+    push_response_info_.reset();
+  } else {
+    DCHECK_EQ(static_cast<HttpResponseInfo*>(NULL), response_info_);
+  }
+
+  response_info_ = response;
+
+  // Put the peer's IP address and port into the response.
+  IPEndPoint address;
+  int result = stream_->GetPeerAddress(&address);
+  if (result != OK)
+    return result;
+  response_info_->socket_address = HostPortPair::FromIPEndPoint(address);
+
+  result = stream_->SendRequest(has_upload_data_);
+  if (result == ERR_IO_PENDING) {
+    CHECK(callback_.is_null());
+    callback_ = callback;
+  }
+  return result;
+}
+
+void SpdyHttpStream::Cancel() {
+  if (spdy_session_)
+    spdy_session_->CancelPendingCreateStreams(&stream_);
+  callback_.Reset();
+  if (stream_)
+    stream_->Cancel();
+}
+
+int SpdyHttpStream::SendData() {
+  CHECK(request_info_ && request_info_->upload_data_stream);
+  CHECK_EQ(0, request_body_buf_->BytesRemaining());
+
+  // Read the data from the request body stream.
+  const int bytes_read = request_info_->upload_data_stream->Read(
+      raw_request_body_buf_, raw_request_body_buf_->size(),
+      base::Bind(
+          base::IgnoreResult(&SpdyHttpStream::OnRequestBodyReadCompleted),
+          weak_factory_.GetWeakPtr()));
+
+  if (bytes_read == ERR_IO_PENDING)
+    return ERR_IO_PENDING;
+  // ERR_IO_PENDING is the only possible error.
+  DCHECK_GE(bytes_read, 0);
+  return OnRequestBodyReadCompleted(bytes_read);
+}
+
+bool SpdyHttpStream::OnSendHeadersComplete(int status) {
+  if (!callback_.is_null())
+    DoCallback(status);
+  return !has_upload_data_;
+}
+
+int SpdyHttpStream::OnSendBody() {
+  CHECK(request_info_ && request_info_->upload_data_stream);
+  const bool eof = request_info_->upload_data_stream->IsEOF();
+  if (request_body_buf_->BytesRemaining() > 0) {
+    return stream_->WriteStreamData(
+        request_body_buf_,
+        request_body_buf_->BytesRemaining(),
+        eof ? DATA_FLAG_FIN : DATA_FLAG_NONE);
+  }
+
+  // The entire body data has been sent.
+  if (eof)
+    return OK;
+
+  return SendData();
+}
+
+int SpdyHttpStream::OnSendBodyComplete(int status, bool* eof) {
+  // |status| is the number of bytes written to the SPDY stream.
+  CHECK(request_info_ && request_info_->upload_data_stream);
+  *eof = false;
+
+  if (status > 0) {
+    request_body_buf_->DidConsume(status);
+    if (request_body_buf_->BytesRemaining()) {
+      // Go back to OnSendBody() to send the remaining data.
+      return OK;
+    }
+  }
+
+  // Check if the entire body data has been sent.
+  *eof = (request_info_->upload_data_stream->IsEOF() &&
+          !request_body_buf_->BytesRemaining());
+  return OK;
+}
+
+int SpdyHttpStream::OnResponseReceived(const SpdyHeaderBlock& response,
+                                       base::Time response_time,
+                                       int status) {
+  if (!response_info_) {
+    DCHECK(stream_->pushed());
+    push_response_info_.reset(new HttpResponseInfo);
+    response_info_ = push_response_info_.get();
+  }
+
+  // If the response is already received, these headers are too late.
+  if (response_headers_received_) {
+    LOG(WARNING) << "SpdyHttpStream headers received after response started.";
+    return OK;
+  }
+
+  // TODO(mbelshe): This is the time of all headers received, not just time
+  // to first byte.
+  response_info_->response_time = base::Time::Now();
+
+  if (!SpdyHeadersToHttpResponse(response, stream_->GetProtocolVersion(),
+                                 response_info_)) {
+    // We might not have complete headers yet.
+    return ERR_INCOMPLETE_SPDY_HEADERS;
+  }
+
+  response_headers_received_ = true;
+  // Don't store the SSLInfo in the response here, HttpNetworkTransaction
+  // will take care of that part.
+  SSLInfo ssl_info;
+  NextProto protocol_negotiated = kProtoUnknown;
+  stream_->GetSSLInfo(&ssl_info,
+                      &response_info_->was_npn_negotiated,
+                      &protocol_negotiated);
+  response_info_->npn_negotiated_protocol =
+      SSLClientSocket::NextProtoToString(protocol_negotiated);
+  response_info_->request_time = stream_->GetRequestTime();
+  response_info_->vary_data.Init(*request_info_, *response_info_->headers);
+  // TODO(ahendrickson): This is recorded after the entire SYN_STREAM control
+  // frame has been received and processed.  Move to framer?
+  response_info_->response_time = response_time;
+
+  if (!callback_.is_null())
+    DoCallback(status);
+
+  return status;
+}
+
+void SpdyHttpStream::OnHeadersSent() {
+  // For HTTP streams, no HEADERS frame is sent from the client.
+  NOTREACHED();
+}
+
+int SpdyHttpStream::OnDataReceived(const char* data, int length) {
+  // SpdyStream won't call us with data if the header block didn't contain a
+  // valid set of headers.  So we don't expect to not have headers received
+  // here.
+  if (!response_headers_received_)
+    return ERR_INCOMPLETE_SPDY_HEADERS;
+
+  // Note that data may be received for a SpdyStream prior to the user calling
+  // ReadResponseBody(), therefore user_buffer_ may be NULL.  This may often
+  // happen for server initiated streams.
+  DCHECK(!stream_->closed() || stream_->pushed());
+  if (length > 0) {
+    // Save the received data.
+    IOBufferWithSize* io_buffer = new IOBufferWithSize(length);
+    memcpy(io_buffer->data(), data, length);
+    response_body_.push_back(make_scoped_refptr(io_buffer));
+
+    if (user_buffer_) {
+      // Handing small chunks of data to the caller creates measurable overhead.
+      // We buffer data in short time-spans and send a single read notification.
+      ScheduleBufferedReadCallback();
+    }
+  }
+  return OK;
+}
+
+void SpdyHttpStream::OnDataSent(int length) {
+  // For HTTP streams, no data is sent from the client while in the OPEN state,
+  // so it is never called.
+  NOTREACHED();
+}
+
+void SpdyHttpStream::OnClose(int status) {
+  bool invoked_callback = false;
+  if (status == net::OK) {
+    // We need to complete any pending buffered read now.
+    invoked_callback = DoBufferedReadCallback();
+  }
+  if (!invoked_callback && !callback_.is_null())
+    DoCallback(status);
+}
+
+void SpdyHttpStream::ScheduleBufferedReadCallback() {
+  // If there is already a scheduled DoBufferedReadCallback, don't issue
+  // another one.  Mark that we have received more data and return.
+  if (buffered_read_callback_pending_) {
+    more_read_data_pending_ = true;
+    return;
+  }
+
+  more_read_data_pending_ = false;
+  buffered_read_callback_pending_ = true;
+  const base::TimeDelta kBufferTime = base::TimeDelta::FromMilliseconds(1);
+  MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(base::IgnoreResult(&SpdyHttpStream::DoBufferedReadCallback),
+                 weak_factory_.GetWeakPtr()),
+      kBufferTime);
+}
+
+// Checks to see if we should wait for more buffered data before notifying
+// the caller.  Returns true if we should wait, false otherwise.
+bool SpdyHttpStream::ShouldWaitForMoreBufferedData() const {
+  // If the response is complete, there is no point in waiting.
+  if (stream_->closed())
+    return false;
+
+  int bytes_buffered = 0;
+  std::list<scoped_refptr<IOBufferWithSize> >::const_iterator it;
+  for (it = response_body_.begin();
+       it != response_body_.end() && bytes_buffered < user_buffer_len_;
+       ++it)
+    bytes_buffered += (*it)->size();
+
+  return bytes_buffered < user_buffer_len_;
+}
+
+bool SpdyHttpStream::DoBufferedReadCallback() {
+  weak_factory_.InvalidateWeakPtrs();
+  buffered_read_callback_pending_ = false;
+
+  // If the transaction is cancelled or errored out, we don't need to complete
+  // the read.
+  if (!stream_ || stream_->response_status() != OK || stream_->cancelled())
+    return false;
+
+  // When more_read_data_pending_ is true, it means that more data has
+  // arrived since we started waiting.  Wait a little longer and continue
+  // to buffer.
+  if (more_read_data_pending_ && ShouldWaitForMoreBufferedData()) {
+    ScheduleBufferedReadCallback();
+    return false;
+  }
+
+  int rv = 0;
+  if (user_buffer_) {
+    rv = ReadResponseBody(user_buffer_, user_buffer_len_, callback_);
+    CHECK_NE(rv, ERR_IO_PENDING);
+    user_buffer_ = NULL;
+    user_buffer_len_ = 0;
+    DoCallback(rv);
+    return true;
+  }
+  return false;
+}
+
+void SpdyHttpStream::DoCallback(int rv) {
+  CHECK_NE(rv, ERR_IO_PENDING);
+  CHECK(!callback_.is_null());
+
+  // Since Run may result in being called back, clear user_callback_ in advance.
+  CompletionCallback c = callback_;
+  callback_.Reset();
+  c.Run(rv);
+}
+
+int SpdyHttpStream::OnRequestBodyReadCompleted(int status) {
+  DCHECK_GE(status, 0);
+
+  request_body_buf_ = new DrainableIOBuffer(raw_request_body_buf_, status);
+
+  const bool eof = request_info_->upload_data_stream->IsEOF();
+  return stream_->WriteStreamData(request_body_buf_,
+                                  request_body_buf_->BytesRemaining(),
+                                  eof ? DATA_FLAG_FIN : DATA_FLAG_NONE);
+}
+
+void SpdyHttpStream::GetSSLInfo(SSLInfo* ssl_info) {
+  DCHECK(stream_);
+  bool using_npn;
+  NextProto protocol_negotiated = kProtoUnknown;
+  stream_->GetSSLInfo(ssl_info, &using_npn, &protocol_negotiated);
+}
+
+void SpdyHttpStream::GetSSLCertRequestInfo(
+    SSLCertRequestInfo* cert_request_info) {
+  DCHECK(stream_);
+  stream_->GetSSLCertRequestInfo(cert_request_info);
+}
+
+bool SpdyHttpStream::IsSpdyHttpStream() const {
+  return true;
+}
+
+void SpdyHttpStream::Drain(HttpNetworkSession* session) {
+  Close(false);
+  delete this;
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_http_stream.h b/src/net/spdy/spdy_http_stream.h
new file mode 100644
index 0000000..ddd2ccb
--- /dev/null
+++ b/src/net/spdy/spdy_http_stream.h
@@ -0,0 +1,146 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_SPDY_SPDY_HTTP_STREAM_H_
+#define NET_SPDY_SPDY_HTTP_STREAM_H_
+
+#include <list>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_log.h"
+#include "net/http/http_stream.h"
+#include "net/spdy/spdy_stream.h"
+
+namespace net {
+
+class DrainableIOBuffer;
+struct HttpRequestInfo;
+class HttpResponseInfo;
+class IOBuffer;
+class SpdySession;
+class UploadDataStream;
+
+// The SpdyHttpStream is a HTTP-specific type of stream known to a SpdySession.
+class NET_EXPORT_PRIVATE SpdyHttpStream : public SpdyStream::Delegate,
+                                          public HttpStream {
+ public:
+  SpdyHttpStream(SpdySession* spdy_session, bool direct);
+  virtual ~SpdyHttpStream();
+
+  // Initializes this SpdyHttpStream by wrapping an existing SpdyStream.
+  void InitializeWithExistingStream(SpdyStream* spdy_stream);
+
+  SpdyStream* stream() { return stream_.get(); }
+
+  // Cancels any callbacks from being invoked and deletes the stream.
+  void Cancel();
+
+  // HttpStream implementation.
+  virtual int InitializeStream(const HttpRequestInfo* request_info,
+                               const BoundNetLog& net_log,
+                               const CompletionCallback& callback) OVERRIDE;
+  virtual int SendRequest(const HttpRequestHeaders& headers,
+                          HttpResponseInfo* response,
+                          const CompletionCallback& callback) OVERRIDE;
+  virtual UploadProgress GetUploadProgress() const OVERRIDE;
+  virtual int ReadResponseHeaders(const CompletionCallback& callback) OVERRIDE;
+  virtual const HttpResponseInfo* GetResponseInfo() const OVERRIDE;
+  virtual int ReadResponseBody(IOBuffer* buf,
+                               int buf_len,
+                               const CompletionCallback& callback) OVERRIDE;
+  virtual void Close(bool not_reusable) OVERRIDE;
+  virtual HttpStream* RenewStreamForAuth() OVERRIDE;
+  virtual bool IsResponseBodyComplete() const OVERRIDE;
+  virtual bool CanFindEndOfResponse() const OVERRIDE;
+  virtual bool IsMoreDataBuffered() const OVERRIDE;
+  virtual bool IsConnectionReused() const OVERRIDE;
+  virtual void SetConnectionReused() OVERRIDE;
+  virtual bool IsConnectionReusable() const OVERRIDE;
+  virtual void GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+  virtual void GetSSLCertRequestInfo(
+      SSLCertRequestInfo* cert_request_info) OVERRIDE;
+  virtual bool IsSpdyHttpStream() const OVERRIDE;
+  virtual void LogNumRttVsBytesMetrics() const OVERRIDE {}
+  virtual void Drain(HttpNetworkSession* session) OVERRIDE;
+
+  // SpdyStream::Delegate implementation.
+  virtual bool OnSendHeadersComplete(int status) OVERRIDE;
+  virtual int OnSendBody() OVERRIDE;
+  virtual int OnSendBodyComplete(int status, bool* eof) OVERRIDE;
+  virtual int OnResponseReceived(const SpdyHeaderBlock& response,
+                                 base::Time response_time,
+                                 int status) OVERRIDE;
+  virtual void OnHeadersSent() OVERRIDE;
+  virtual int OnDataReceived(const char* buffer, int bytes) OVERRIDE;
+  virtual void OnDataSent(int length) OVERRIDE;
+  virtual void OnClose(int status) OVERRIDE;
+
+ private:
+  // Reads the data (whether chunked or not) from the request body stream and
+  // sends the data by calling WriteStreamData on the underlying SpdyStream.
+  int SendData();
+
+  // Call the user callback.
+  void DoCallback(int rv);
+
+  int OnRequestBodyReadCompleted(int status);
+
+  void ScheduleBufferedReadCallback();
+
+  // Returns true if the callback is invoked.
+  bool DoBufferedReadCallback();
+  bool ShouldWaitForMoreBufferedData() const;
+
+  base::WeakPtrFactory<SpdyHttpStream> weak_factory_;
+  scoped_refptr<SpdyStream> stream_;
+  scoped_refptr<SpdySession> spdy_session_;
+
+  // The request to send.
+  const HttpRequestInfo* request_info_;
+
+  bool has_upload_data_;
+
+  // |response_info_| is the HTTP response data object which is filled in
+  // when a SYN_REPLY comes in for the stream.
+  // It is not owned by this stream object, or point to |push_response_info_|.
+  HttpResponseInfo* response_info_;
+
+  scoped_ptr<HttpResponseInfo> push_response_info_;
+
+  bool download_finished_;
+  bool response_headers_received_;  // Indicates waiting for more HEADERS.
+
+  // We buffer the response body as it arrives asynchronously from the stream.
+  // TODO(mbelshe):  is this infinite buffering?
+  std::list<scoped_refptr<IOBufferWithSize> > response_body_;
+
+  CompletionCallback callback_;
+
+  // User provided buffer for the ReadResponseBody() response.
+  scoped_refptr<IOBuffer> user_buffer_;
+  int user_buffer_len_;
+
+  // Temporary buffer used to read the request body from UploadDataStream.
+  scoped_refptr<IOBufferWithSize> raw_request_body_buf_;
+  // Wraps raw_request_body_buf_ to read the remaining data progressively.
+  scoped_refptr<DrainableIOBuffer> request_body_buf_;
+
+  // Is there a scheduled read callback pending.
+  bool buffered_read_callback_pending_;
+  // Has more data been received from the network during the wait for the
+  // scheduled read callback.
+  bool more_read_data_pending_;
+
+  // Is this spdy stream direct to the origin server (or to a proxy).
+  bool direct_;
+
+  DISALLOW_COPY_AND_ASSIGN(SpdyHttpStream);
+};
+
+}  // namespace net
+
+#endif  // NET_SPDY_SPDY_HTTP_STREAM_H_
diff --git a/src/net/spdy/spdy_http_stream_spdy2_unittest.cc b/src/net/spdy/spdy_http_stream_spdy2_unittest.cc
new file mode 100644
index 0000000..544f686
--- /dev/null
+++ b/src/net/spdy/spdy_http_stream_spdy2_unittest.cc
@@ -0,0 +1,375 @@
+// Copyright (c) 2012 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_http_stream.h"
+
+#include "crypto/ec_private_key.h"
+#include "crypto/ec_signature_creator.h"
+#include "crypto/signature_creator.h"
+#include "net/base/asn1_util.h"
+#include "net/base/default_server_bound_cert_store.h"
+#include "net/base/upload_data_stream.h"
+#include "net/base/upload_element_reader.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_test_util_spdy2.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using namespace net::test_spdy2;
+
+namespace net {
+
+class SpdyHttpStreamSpdy2Test : public testing::Test {
+ public:
+  OrderedSocketData* data() { return data_.get(); }
+ protected:
+  SpdyHttpStreamSpdy2Test() {}
+
+  virtual void TearDown() {
+    crypto::ECSignatureCreator::SetFactoryForTesting(NULL);
+    UploadDataStream::ResetMergeChunks();
+    MessageLoop::current()->RunUntilIdle();
+  }
+
+  void set_merge_chunks(bool merge) {
+    UploadDataStream::set_merge_chunks(merge);
+  }
+
+  int InitSession(MockRead* reads, size_t reads_count,
+                  MockWrite* writes, size_t writes_count,
+                  HostPortPair& host_port_pair) {
+    HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+    data_.reset(new OrderedSocketData(reads, reads_count,
+                                      writes, writes_count));
+    session_deps_.socket_factory->AddSocketDataProvider(data_.get());
+    http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+    session_ = http_session_->spdy_session_pool()->Get(pair, BoundNetLog());
+    transport_params_ = new TransportSocketParams(host_port_pair,
+                                                  MEDIUM, false, false,
+                                                  OnHostResolutionCallback());
+    TestCompletionCallback callback;
+    scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+    EXPECT_EQ(ERR_IO_PENDING,
+              connection->Init(host_port_pair.ToString(),
+                               transport_params_,
+                               MEDIUM,
+                               callback.callback(),
+                               http_session_->GetTransportSocketPool(
+                                   HttpNetworkSession::NORMAL_SOCKET_POOL),
+                               BoundNetLog()));
+    EXPECT_EQ(OK, callback.WaitForResult());
+    return session_->InitializeWithSocket(connection.release(), false, OK);
+  }
+
+  SpdySessionDependencies session_deps_;
+  scoped_ptr<OrderedSocketData> data_;
+  scoped_refptr<HttpNetworkSession> http_session_;
+  scoped_refptr<SpdySession> session_;
+  scoped_refptr<TransportSocketParams> transport_params_;
+};
+
+TEST_F(SpdyHttpStreamSpdy2Test, SendRequest) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = {
+    CreateMockWrite(*req.get(), 1),
+  };
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 2),
+    MockRead(SYNCHRONOUS, 0, 3)  // EOF
+  };
+
+  HostPortPair host_port_pair("www.google.com", 80);
+  HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+  EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes),
+      host_port_pair));
+
+  HttpRequestInfo request;
+  request.method = "GET";
+  request.url = GURL("http://www.google.com/");
+  TestCompletionCallback callback;
+  HttpResponseInfo response;
+  HttpRequestHeaders headers;
+  BoundNetLog net_log;
+  scoped_ptr<SpdyHttpStream> http_stream(
+      new SpdyHttpStream(session_.get(), true));
+  ASSERT_EQ(
+      OK,
+      http_stream->InitializeStream(&request, net_log, CompletionCallback()));
+
+  EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
+                                                     callback.callback()));
+  EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair));
+
+  // This triggers the MockWrite and read 2
+  callback.WaitForResult();
+
+  // This triggers read 3. The empty read causes the session to shut down.
+  data()->CompleteRead();
+
+  // Because we abandoned the stream, we don't expect to find a session in the
+  // pool anymore.
+  EXPECT_FALSE(http_session_->spdy_session_pool()->HasSession(pair));
+  EXPECT_TRUE(data()->at_read_eof());
+  EXPECT_TRUE(data()->at_write_eof());
+}
+
+TEST_F(SpdyHttpStreamSpdy2Test, SendChunkedPost) {
+  set_merge_chunks(false);
+
+  scoped_ptr<SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0));
+  scoped_ptr<SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> chunk2(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*req.get(), 0),
+    CreateMockWrite(*chunk1, 1),  // POST upload frames
+    CreateMockWrite(*chunk2, 2),
+  };
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 3),
+    CreateMockRead(*chunk1, 4),
+    CreateMockRead(*chunk2, 5),
+    MockRead(SYNCHRONOUS, 0, 6)  // EOF
+  };
+
+  HostPortPair host_port_pair("www.google.com", 80);
+  HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+  EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes),
+                            host_port_pair));
+
+  UploadDataStream upload_stream(UploadDataStream::CHUNKED, 0);
+  upload_stream.AppendChunk(kUploadData, kUploadDataSize, false);
+  upload_stream.AppendChunk(kUploadData, kUploadDataSize, true);
+
+  HttpRequestInfo request;
+  request.method = "POST";
+  request.url = GURL("http://www.google.com/");
+  request.upload_data_stream = &upload_stream;
+
+  ASSERT_EQ(OK, upload_stream.InitSync());
+
+  TestCompletionCallback callback;
+  HttpResponseInfo response;
+  HttpRequestHeaders headers;
+  BoundNetLog net_log;
+  SpdyHttpStream http_stream(session_.get(), true);
+  ASSERT_EQ(
+      OK,
+      http_stream.InitializeStream(&request, net_log, CompletionCallback()));
+
+  EXPECT_EQ(ERR_IO_PENDING, http_stream.SendRequest(
+      headers, &response, callback.callback()));
+  EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair));
+
+  // This triggers the MockWrite and read 2
+  callback.WaitForResult();
+
+  // This triggers read 3. The empty read causes the session to shut down.
+  data()->CompleteRead();
+  MessageLoop::current()->RunUntilIdle();
+
+  // Because we abandoned the stream, we don't expect to find a session in the
+  // pool anymore.
+  EXPECT_FALSE(http_session_->spdy_session_pool()->HasSession(pair));
+  EXPECT_TRUE(data()->at_read_eof());
+  EXPECT_TRUE(data()->at_write_eof());
+}
+
+// Test to ensure the SpdyStream state machine does not get confused when a
+// chunk becomes available while a write is pending.
+TEST_F(SpdyHttpStreamSpdy2Test, DelayedSendChunkedPost) {
+  set_merge_chunks(false);
+
+  const char kUploadData1[] = "12345678";
+  const int kUploadData1Size = arraysize(kUploadData1)-1;
+  scoped_ptr<SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0));
+  scoped_ptr<SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> chunk2(
+      ConstructSpdyBodyFrame(1, kUploadData1, kUploadData1Size, false));
+  scoped_ptr<SpdyFrame> chunk3(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*req.get(), 0),
+    CreateMockWrite(*chunk1, 1),  // POST upload frames
+    CreateMockWrite(*chunk2, 2),
+    CreateMockWrite(*chunk3, 3),
+  };
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 4),
+    CreateMockRead(*chunk1, 5),
+    CreateMockRead(*chunk2, 6),
+    CreateMockRead(*chunk3, 7),
+    MockRead(ASYNC, 0, 8)  // EOF
+  };
+
+  HostPortPair host_port_pair("www.google.com", 80);
+  HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+
+  DeterministicMockClientSocketFactory* socket_factory =
+      session_deps_.deterministic_socket_factory.get();
+  socket_factory->AddSocketDataProvider(&data);
+
+  http_session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic(
+      &session_deps_);
+  session_ = http_session_->spdy_session_pool()->Get(pair, BoundNetLog());
+  transport_params_ = new TransportSocketParams(host_port_pair,
+                                                MEDIUM, false, false,
+                                                OnHostResolutionCallback());
+
+  TestCompletionCallback callback;
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+
+  EXPECT_EQ(ERR_IO_PENDING,
+            connection->Init(host_port_pair.ToString(),
+                             transport_params_,
+                             MEDIUM,
+                             callback.callback(),
+                             http_session_->GetTransportSocketPool(
+                                 HttpNetworkSession::NORMAL_SOCKET_POOL),
+                             BoundNetLog()));
+
+  callback.WaitForResult();
+  EXPECT_EQ(OK,
+            session_->InitializeWithSocket(connection.release(), false, OK));
+
+  UploadDataStream upload_stream(UploadDataStream::CHUNKED, 0);
+
+  HttpRequestInfo request;
+  request.method = "POST";
+  request.url = GURL("http://www.google.com/");
+  request.upload_data_stream = &upload_stream;
+
+  ASSERT_EQ(OK, upload_stream.InitSync());
+  upload_stream.AppendChunk(kUploadData, kUploadDataSize, false);
+
+  BoundNetLog net_log;
+  scoped_ptr<SpdyHttpStream> http_stream(
+      new SpdyHttpStream(session_.get(), true));
+  ASSERT_EQ(OK, http_stream->InitializeStream(&request,
+                                              net_log,
+                                              CompletionCallback()));
+
+  HttpRequestHeaders headers;
+  HttpResponseInfo response;
+  // This will attempt to Write() the initial request and headers, which will
+  // complete asynchronously.
+  EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
+                                                     callback.callback()));
+  EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair));
+
+  // Complete the initial request write and the first chunk.
+  data.RunFor(2);
+  ASSERT_TRUE(callback.have_result());
+  EXPECT_GT(callback.WaitForResult(), 0);
+
+  // Now append the final two chunks which will enqueue two more writes.
+  upload_stream.AppendChunk(kUploadData1, kUploadData1Size, false);
+  upload_stream.AppendChunk(kUploadData, kUploadDataSize, true);
+
+  // Finish writing all the chunks.
+  data.RunFor(2);
+
+  // Read response headers.
+  data.RunFor(1);
+  ASSERT_EQ(OK, http_stream->ReadResponseHeaders(callback.callback()));
+
+  // Read and check |chunk1| response.
+  data.RunFor(1);
+  scoped_refptr<IOBuffer> buf1(new IOBuffer(kUploadDataSize));
+  ASSERT_EQ(kUploadDataSize,
+            http_stream->ReadResponseBody(buf1,
+                                          kUploadDataSize,
+                                          callback.callback()));
+  EXPECT_EQ(kUploadData, std::string(buf1->data(), kUploadDataSize));
+
+  // Read and check |chunk2| response.
+  data.RunFor(1);
+  scoped_refptr<IOBuffer> buf2(new IOBuffer(kUploadData1Size));
+  ASSERT_EQ(kUploadData1Size,
+            http_stream->ReadResponseBody(buf2,
+                                          kUploadData1Size,
+                                          callback.callback()));
+  EXPECT_EQ(kUploadData1, std::string(buf2->data(), kUploadData1Size));
+
+  // Read and check |chunk3| response.
+  data.RunFor(1);
+  scoped_refptr<IOBuffer> buf3(new IOBuffer(kUploadDataSize));
+  ASSERT_EQ(kUploadDataSize,
+            http_stream->ReadResponseBody(buf3,
+                                          kUploadDataSize,
+                                          callback.callback()));
+  EXPECT_EQ(kUploadData, std::string(buf3->data(), kUploadDataSize));
+
+  // Finish reading the |EOF|.
+  data.RunFor(1);
+  ASSERT_TRUE(response.headers.get());
+  ASSERT_EQ(200, response.headers->response_code());
+  EXPECT_TRUE(data.at_read_eof());
+  EXPECT_TRUE(data.at_write_eof());
+}
+
+// Test case for bug: http://code.google.com/p/chromium/issues/detail?id=50058
+TEST_F(SpdyHttpStreamSpdy2Test, SpdyURLTest) {
+  const char * const full_url = "http://www.google.com/foo?query=what#anchor";
+  const char * const base_url = "http://www.google.com/foo?query=what";
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(base_url, false, 1, LOWEST));
+  MockWrite writes[] = {
+    CreateMockWrite(*req.get(), 1),
+  };
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 2),
+    MockRead(SYNCHRONOUS, 0, 3)  // EOF
+  };
+
+  HostPortPair host_port_pair("www.google.com", 80);
+  HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+  EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes),
+      host_port_pair));
+
+  HttpRequestInfo request;
+  request.method = "GET";
+  request.url = GURL(full_url);
+  TestCompletionCallback callback;
+  HttpResponseInfo response;
+  HttpRequestHeaders headers;
+  BoundNetLog net_log;
+  scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true));
+  ASSERT_EQ(
+      OK,
+      http_stream->InitializeStream(&request, net_log, CompletionCallback()));
+
+  EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
+                                                     callback.callback()));
+
+  const SpdyHeaderBlock& spdy_header =
+    http_stream->stream()->spdy_headers();
+  if (spdy_header.find("url") != spdy_header.end())
+    EXPECT_EQ("/foo?query=what", spdy_header.find("url")->second);
+  else
+    FAIL() << "No url is set in spdy_header!";
+
+  // This triggers the MockWrite and read 2
+  callback.WaitForResult();
+
+  // This triggers read 3. The empty read causes the session to shut down.
+  data()->CompleteRead();
+
+  // Because we abandoned the stream, we don't expect to find a session in the
+  // pool anymore.
+  EXPECT_FALSE(http_session_->spdy_session_pool()->HasSession(pair));
+  EXPECT_TRUE(data()->at_read_eof());
+  EXPECT_TRUE(data()->at_write_eof());
+}
+
+// TODO(willchan): Write a longer test for SpdyStream that exercises all
+// methods.
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_http_stream_spdy3_unittest.cc b/src/net/spdy/spdy_http_stream_spdy3_unittest.cc
new file mode 100644
index 0000000..70c406d
--- /dev/null
+++ b/src/net/spdy/spdy_http_stream_spdy3_unittest.cc
@@ -0,0 +1,729 @@
+// Copyright (c) 2012 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_http_stream.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "crypto/ec_private_key.h"
+#include "crypto/ec_signature_creator.h"
+#include "crypto/signature_creator.h"
+#include "net/base/asn1_util.h"
+#include "net/base/default_server_bound_cert_store.h"
+#include "net/base/upload_data_stream.h"
+#include "net/base/upload_element_reader.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/spdy/spdy_credential_builder.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_test_util_spdy3.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using namespace net::test_spdy3;
+
+namespace net {
+
+class SpdyHttpStreamSpdy3Test : public testing::Test {
+ public:
+  OrderedSocketData* data() { return data_.get(); }
+
+ protected:
+  virtual void TearDown() {
+    UploadDataStream::ResetMergeChunks();
+    MessageLoop::current()->RunUntilIdle();
+  }
+
+  void set_merge_chunks(bool merge) {
+    UploadDataStream::set_merge_chunks(merge);
+  }
+
+  int InitSession(MockRead* reads, size_t reads_count,
+                  MockWrite* writes, size_t writes_count,
+                  HostPortPair& host_port_pair) {
+    HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+    data_.reset(new OrderedSocketData(reads, reads_count,
+                                      writes, writes_count));
+    session_deps_.socket_factory->AddSocketDataProvider(data_.get());
+    http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+    session_ = http_session_->spdy_session_pool()->Get(pair, BoundNetLog());
+    transport_params_ = new TransportSocketParams(host_port_pair,
+                                                  MEDIUM, false, false,
+                                                  OnHostResolutionCallback());
+    TestCompletionCallback callback;
+    scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+    EXPECT_EQ(ERR_IO_PENDING,
+              connection->Init(host_port_pair.ToString(),
+                               transport_params_,
+                               MEDIUM,
+                               callback.callback(),
+                               http_session_->GetTransportSocketPool(
+                                   HttpNetworkSession::NORMAL_SOCKET_POOL),
+                               BoundNetLog()));
+    EXPECT_EQ(OK, callback.WaitForResult());
+    return session_->InitializeWithSocket(connection.release(), false, OK);
+  }
+
+  void TestSendCredentials(
+    ServerBoundCertService* server_bound_cert_service,
+    const std::string& cert,
+    const std::string& proof);
+
+  SpdySessionDependencies session_deps_;
+  scoped_ptr<OrderedSocketData> data_;
+  scoped_refptr<HttpNetworkSession> http_session_;
+  scoped_refptr<SpdySession> session_;
+  scoped_refptr<TransportSocketParams> transport_params_;
+
+ private:
+  MockECSignatureCreatorFactory ec_signature_creator_factory_;
+};
+
+TEST_F(SpdyHttpStreamSpdy3Test, SendRequest) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = {
+    CreateMockWrite(*req.get(), 1),
+  };
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 2),
+    MockRead(SYNCHRONOUS, 0, 3)  // EOF
+  };
+
+  HostPortPair host_port_pair("www.google.com", 80);
+  HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+  EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes),
+      host_port_pair));
+
+  HttpRequestInfo request;
+  request.method = "GET";
+  request.url = GURL("http://www.google.com/");
+  TestCompletionCallback callback;
+  HttpResponseInfo response;
+  HttpRequestHeaders headers;
+  BoundNetLog net_log;
+  scoped_ptr<SpdyHttpStream> http_stream(
+      new SpdyHttpStream(session_.get(), true));
+  ASSERT_EQ(
+      OK,
+      http_stream->InitializeStream(&request, net_log, CompletionCallback()));
+
+  EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
+                                                     callback.callback()));
+  EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair));
+
+  // This triggers the MockWrite and read 2
+  callback.WaitForResult();
+
+  // This triggers read 3. The empty read causes the session to shut down.
+  data()->CompleteRead();
+
+  // Because we abandoned the stream, we don't expect to find a session in the
+  // pool anymore.
+  EXPECT_FALSE(http_session_->spdy_session_pool()->HasSession(pair));
+  EXPECT_TRUE(data()->at_read_eof());
+  EXPECT_TRUE(data()->at_write_eof());
+}
+
+TEST_F(SpdyHttpStreamSpdy3Test, SendChunkedPost) {
+  set_merge_chunks(false);
+
+  scoped_ptr<SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0));
+  scoped_ptr<SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> chunk2(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*req.get(), 0),
+    CreateMockWrite(*chunk1, 1),  // POST upload frames
+    CreateMockWrite(*chunk2, 2),
+  };
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 3),
+    CreateMockRead(*chunk1, 4),
+    CreateMockRead(*chunk2, 5),
+    MockRead(SYNCHRONOUS, 0, 6)  // EOF
+  };
+
+  HostPortPair host_port_pair("www.google.com", 80);
+  HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+  EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes),
+                            host_port_pair));
+
+  UploadDataStream upload_stream(UploadDataStream::CHUNKED, 0);
+  upload_stream.AppendChunk(kUploadData, kUploadDataSize, false);
+  upload_stream.AppendChunk(kUploadData, kUploadDataSize, true);
+
+  HttpRequestInfo request;
+  request.method = "POST";
+  request.url = GURL("http://www.google.com/");
+  request.upload_data_stream = &upload_stream;
+
+  ASSERT_EQ(OK, upload_stream.InitSync());
+
+  TestCompletionCallback callback;
+  HttpResponseInfo response;
+  HttpRequestHeaders headers;
+  BoundNetLog net_log;
+  SpdyHttpStream http_stream(session_.get(), true);
+  ASSERT_EQ(
+      OK,
+      http_stream.InitializeStream(&request, net_log, CompletionCallback()));
+
+  EXPECT_EQ(ERR_IO_PENDING, http_stream.SendRequest(
+      headers, &response, callback.callback()));
+  EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair));
+
+  // This triggers the MockWrite and read 2
+  callback.WaitForResult();
+
+  // This triggers read 3. The empty read causes the session to shut down.
+  data()->CompleteRead();
+  MessageLoop::current()->RunUntilIdle();
+
+  // Because we abandoned the stream, we don't expect to find a session in the
+  // pool anymore.
+  EXPECT_FALSE(http_session_->spdy_session_pool()->HasSession(pair));
+  EXPECT_TRUE(data()->at_read_eof());
+  EXPECT_TRUE(data()->at_write_eof());
+}
+
+// Test to ensure the SpdyStream state machine does not get confused when a
+// chunk becomes available while a write is pending.
+TEST_F(SpdyHttpStreamSpdy3Test, DelayedSendChunkedPost) {
+  set_merge_chunks(false);
+
+  const char kUploadData1[] = "12345678";
+  const int kUploadData1Size = arraysize(kUploadData1)-1;
+  scoped_ptr<SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0));
+  scoped_ptr<SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> chunk2(
+      ConstructSpdyBodyFrame(1, kUploadData1, kUploadData1Size, false));
+  scoped_ptr<SpdyFrame> chunk3(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*req.get(), 0),
+    CreateMockWrite(*chunk1, 1),  // POST upload frames
+    CreateMockWrite(*chunk2, 2),
+    CreateMockWrite(*chunk3, 3),
+  };
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 4),
+    CreateMockRead(*chunk1, 5),
+    CreateMockRead(*chunk2, 6),
+    CreateMockRead(*chunk3, 7),
+    MockRead(ASYNC, 0, 8)  // EOF
+  };
+
+  HostPortPair host_port_pair("www.google.com", 80);
+  HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+
+  DeterministicMockClientSocketFactory* socket_factory =
+      session_deps_.deterministic_socket_factory.get();
+  socket_factory->AddSocketDataProvider(&data);
+
+  http_session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic(
+      &session_deps_);
+  session_ = http_session_->spdy_session_pool()->Get(pair, BoundNetLog());
+  transport_params_ = new TransportSocketParams(host_port_pair,
+                                                MEDIUM, false, false,
+                                                OnHostResolutionCallback());
+
+  TestCompletionCallback callback;
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+
+  EXPECT_EQ(ERR_IO_PENDING,
+            connection->Init(host_port_pair.ToString(),
+                             transport_params_,
+                             MEDIUM,
+                             callback.callback(),
+                             http_session_->GetTransportSocketPool(
+                                 HttpNetworkSession::NORMAL_SOCKET_POOL),
+                             BoundNetLog()));
+
+  callback.WaitForResult();
+  EXPECT_EQ(OK,
+            session_->InitializeWithSocket(connection.release(), false, OK));
+
+  UploadDataStream upload_stream(UploadDataStream::CHUNKED, 0);
+
+  HttpRequestInfo request;
+  request.method = "POST";
+  request.url = GURL("http://www.google.com/");
+  request.upload_data_stream = &upload_stream;
+
+  ASSERT_EQ(OK, upload_stream.InitSync());
+  upload_stream.AppendChunk(kUploadData, kUploadDataSize, false);
+
+  BoundNetLog net_log;
+  scoped_ptr<SpdyHttpStream> http_stream(
+      new SpdyHttpStream(session_.get(), true));
+  ASSERT_EQ(OK, http_stream->InitializeStream(&request,
+                                              net_log,
+                                              CompletionCallback()));
+
+  HttpRequestHeaders headers;
+  HttpResponseInfo response;
+  // This will attempt to Write() the initial request and headers, which will
+  // complete asynchronously.
+  EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
+                                                     callback.callback()));
+  EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair));
+
+  // Complete the initial request write and the first chunk.
+  data.RunFor(2);
+  ASSERT_TRUE(callback.have_result());
+  EXPECT_GT(callback.WaitForResult(), 0);
+
+  // Now append the final two chunks which will enqueue two more writes.
+  upload_stream.AppendChunk(kUploadData1, kUploadData1Size, false);
+  upload_stream.AppendChunk(kUploadData, kUploadDataSize, true);
+
+  // Finish writing all the chunks.
+  data.RunFor(2);
+
+  // Read response headers.
+  data.RunFor(1);
+  ASSERT_EQ(OK, http_stream->ReadResponseHeaders(callback.callback()));
+
+  // Read and check |chunk1| response.
+  data.RunFor(1);
+  scoped_refptr<IOBuffer> buf1(new IOBuffer(kUploadDataSize));
+  ASSERT_EQ(kUploadDataSize,
+            http_stream->ReadResponseBody(buf1,
+                                          kUploadDataSize,
+                                          callback.callback()));
+  EXPECT_EQ(kUploadData, std::string(buf1->data(), kUploadDataSize));
+
+  // Read and check |chunk2| response.
+  data.RunFor(1);
+  scoped_refptr<IOBuffer> buf2(new IOBuffer(kUploadData1Size));
+  ASSERT_EQ(kUploadData1Size,
+            http_stream->ReadResponseBody(buf2,
+                                          kUploadData1Size,
+                                          callback.callback()));
+  EXPECT_EQ(kUploadData1, std::string(buf2->data(), kUploadData1Size));
+
+  // Read and check |chunk3| response.
+  data.RunFor(1);
+  scoped_refptr<IOBuffer> buf3(new IOBuffer(kUploadDataSize));
+  ASSERT_EQ(kUploadDataSize,
+            http_stream->ReadResponseBody(buf3,
+                                          kUploadDataSize,
+                                          callback.callback()));
+  EXPECT_EQ(kUploadData, std::string(buf3->data(), kUploadDataSize));
+
+  // Finish reading the |EOF|.
+  data.RunFor(1);
+  ASSERT_TRUE(response.headers.get());
+  ASSERT_EQ(200, response.headers->response_code());
+  EXPECT_TRUE(data.at_read_eof());
+  EXPECT_TRUE(data.at_write_eof());
+}
+
+// Test the receipt of a WINDOW_UPDATE frame while waiting for a chunk to be
+// made available is handled correctly.
+TEST_F(SpdyHttpStreamSpdy3Test, DelayedSendChunkedPostWithWindowUpdate) {
+  set_merge_chunks(false);
+
+  scoped_ptr<SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0));
+  scoped_ptr<SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*req.get(), 0),
+    CreateMockWrite(*chunk1, 1),
+  };
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+  scoped_ptr<SpdyFrame> window_update(
+      ConstructSpdyWindowUpdate(1, kUploadDataSize));
+  MockRead reads[] = {
+    CreateMockRead(*window_update, 2),
+    CreateMockRead(*resp, 3),
+    CreateMockRead(*chunk1, 4),
+    MockRead(ASYNC, 0, 5)  // EOF
+  };
+
+  HostPortPair host_port_pair("www.google.com", 80);
+  HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+
+  DeterministicMockClientSocketFactory* socket_factory =
+      session_deps_.deterministic_socket_factory.get();
+  socket_factory->AddSocketDataProvider(&data);
+
+  http_session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic(
+      &session_deps_);
+  session_ = http_session_->spdy_session_pool()->Get(pair, BoundNetLog());
+  transport_params_ = new TransportSocketParams(host_port_pair,
+                                                MEDIUM, false, false,
+                                                OnHostResolutionCallback());
+
+  TestCompletionCallback callback;
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+
+  EXPECT_EQ(ERR_IO_PENDING,
+            connection->Init(host_port_pair.ToString(),
+                             transport_params_,
+                             MEDIUM,
+                             callback.callback(),
+                             http_session_->GetTransportSocketPool(
+                                 HttpNetworkSession::NORMAL_SOCKET_POOL),
+                             BoundNetLog()));
+
+  callback.WaitForResult();
+  EXPECT_EQ(OK,
+            session_->InitializeWithSocket(connection.release(), false, OK));
+
+  UploadDataStream upload_stream(UploadDataStream::CHUNKED, 0);
+
+  HttpRequestInfo request;
+  request.method = "POST";
+  request.url = GURL("http://www.google.com/");
+  request.upload_data_stream = &upload_stream;
+
+  ASSERT_EQ(OK, upload_stream.InitSync());
+  upload_stream.AppendChunk(kUploadData, kUploadDataSize, true);
+
+  BoundNetLog net_log;
+  scoped_ptr<SpdyHttpStream> http_stream(
+      new SpdyHttpStream(session_.get(), true));
+  ASSERT_EQ(OK, http_stream->InitializeStream(&request, net_log,
+                                              CompletionCallback()));
+
+  HttpRequestHeaders headers;
+  HttpResponseInfo response;
+  // This will attempt to Write() the initial request and headers, which will
+  // complete asynchronously.
+  EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
+                                                     callback.callback()));
+  EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair));
+
+  // Complete the initial request write and first chunk.
+  data.RunFor(2);
+  ASSERT_TRUE(callback.have_result());
+  EXPECT_GT(callback.WaitForResult(), 0);
+
+  // Verify that the window size has decreased.
+  ASSERT_TRUE(http_stream->stream() != NULL);
+  EXPECT_NE(static_cast<int>(kSpdyStreamInitialWindowSize),
+            http_stream->stream()->send_window_size());
+
+  // Read window update.
+  data.RunFor(1);
+
+  // Verify the window update.
+  ASSERT_TRUE(http_stream->stream() != NULL);
+  EXPECT_EQ(static_cast<int>(kSpdyStreamInitialWindowSize),
+            http_stream->stream()->send_window_size());
+
+  // Read response headers.
+  data.RunFor(1);
+  ASSERT_EQ(OK, http_stream->ReadResponseHeaders(callback.callback()));
+
+  // Read and check |chunk1| response.
+  data.RunFor(1);
+  scoped_refptr<IOBuffer> buf1(new IOBuffer(kUploadDataSize));
+  ASSERT_EQ(kUploadDataSize,
+            http_stream->ReadResponseBody(buf1,
+                                          kUploadDataSize,
+                                          callback.callback()));
+  EXPECT_EQ(kUploadData, std::string(buf1->data(), kUploadDataSize));
+
+  // Finish reading the |EOF|.
+  data.RunFor(1);
+  ASSERT_TRUE(response.headers.get());
+  ASSERT_EQ(200, response.headers->response_code());
+  EXPECT_TRUE(data.at_read_eof());
+  EXPECT_TRUE(data.at_write_eof());
+}
+
+// Test case for bug: http://code.google.com/p/chromium/issues/detail?id=50058
+TEST_F(SpdyHttpStreamSpdy3Test, SpdyURLTest) {
+  const char * const full_url = "http://www.google.com/foo?query=what#anchor";
+  const char * const base_url = "http://www.google.com/foo?query=what";
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(base_url, false, 1, LOWEST));
+  MockWrite writes[] = {
+    CreateMockWrite(*req.get(), 1),
+  };
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 2),
+    MockRead(SYNCHRONOUS, 0, 3)  // EOF
+  };
+
+  HostPortPair host_port_pair("www.google.com", 80);
+  HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+  EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes),
+      host_port_pair));
+
+  HttpRequestInfo request;
+  request.method = "GET";
+  request.url = GURL(full_url);
+  TestCompletionCallback callback;
+  HttpResponseInfo response;
+  HttpRequestHeaders headers;
+  BoundNetLog net_log;
+  scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true));
+  ASSERT_EQ(
+      OK,
+      http_stream->InitializeStream(&request, net_log, CompletionCallback()));
+
+  EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
+                                                     callback.callback()));
+
+  const SpdyHeaderBlock& spdy_header =
+    http_stream->stream()->spdy_headers();
+  if (spdy_header.find(":path") != spdy_header.end())
+    EXPECT_EQ("/foo?query=what", spdy_header.find(":path")->second);
+  else
+    FAIL() << "No url is set in spdy_header!";
+
+  // This triggers the MockWrite and read 2
+  callback.WaitForResult();
+
+  // This triggers read 3. The empty read causes the session to shut down.
+  data()->CompleteRead();
+
+  // Because we abandoned the stream, we don't expect to find a session in the
+  // pool anymore.
+  EXPECT_FALSE(http_session_->spdy_session_pool()->HasSession(pair));
+  EXPECT_TRUE(data()->at_read_eof());
+  EXPECT_TRUE(data()->at_write_eof());
+}
+
+namespace {
+
+void GetECServerBoundCertAndProof(
+    const std::string& origin,
+    ServerBoundCertService* server_bound_cert_service,
+    std::string* cert,
+    std::string* proof) {
+  TestCompletionCallback callback;
+  std::vector<uint8> requested_cert_types;
+  requested_cert_types.push_back(CLIENT_CERT_ECDSA_SIGN);
+  SSLClientCertType cert_type;
+  std::string key;
+  ServerBoundCertService::RequestHandle request_handle;
+  int rv = server_bound_cert_service->GetDomainBoundCert(
+      origin, requested_cert_types, &cert_type, &key, cert, callback.callback(),
+      &request_handle);
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  EXPECT_EQ(OK, callback.WaitForResult());
+  EXPECT_EQ(CLIENT_CERT_ECDSA_SIGN, cert_type);
+
+  SpdyCredential credential;
+  SpdyCredentialBuilder::Build(MockClientSocket::kTlsUnique, cert_type, key,
+                               *cert, 2, &credential);
+
+  cert->assign(credential.certs[0]);
+  proof->assign(credential.proof);
+}
+
+}  // namespace
+
+// Constructs a standard SPDY SYN_STREAM frame for a GET request with
+// a credential set.
+SpdyFrame* ConstructCredentialRequestFrame(int slot, const GURL& url,
+                                           int stream_id) {
+  const SpdyHeaderInfo syn_headers = {
+    SYN_STREAM,
+    stream_id,
+    0,
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 3),
+    slot,
+    CONTROL_FLAG_FIN,
+    false,
+    INVALID,
+    NULL,
+    0,
+    DATA_FLAG_NONE
+  };
+
+  // TODO(rch): this is ugly.  Clean up.
+  std::string str_path = url.PathForRequest();
+  std::string str_scheme = url.scheme();
+  std::string str_host = url.host();
+  if (url.has_port()) {
+    str_host += ":";
+    str_host += url.port();
+  }
+  scoped_array<char> req(new char[str_path.size() + 1]);
+  scoped_array<char> scheme(new char[str_scheme.size() + 1]);
+  scoped_array<char> host(new char[str_host.size() + 1]);
+  memcpy(req.get(), str_path.c_str(), str_path.size());
+  memcpy(scheme.get(), str_scheme.c_str(), str_scheme.size());
+  memcpy(host.get(), str_host.c_str(), str_host.size());
+  req.get()[str_path.size()] = '\0';
+  scheme.get()[str_scheme.size()] = '\0';
+  host.get()[str_host.size()] = '\0';
+
+  const char* const headers[] = {
+    ":method",
+    "GET",
+    ":path",
+    req.get(),
+    ":host",
+    host.get(),
+    ":scheme",
+    scheme.get(),
+    ":version",
+    "HTTP/1.1"
+  };
+  return ConstructSpdyPacket(
+      syn_headers, NULL, 0, headers, arraysize(headers)/2);
+}
+
+// TODO(rch): When openssl supports server bound certifictes, this
+// guard can be removed
+#if !defined(USE_OPENSSL)
+// Test that if we request a resource for a new origin on a session that
+// used domain bound certificates, that we send a CREDENTIAL frame for
+// the new domain before we send the new request.
+void SpdyHttpStreamSpdy3Test::TestSendCredentials(
+    ServerBoundCertService* server_bound_cert_service,
+    const std::string& cert,
+    const std::string& proof) {
+  const char* kUrl1 = "https://www.google.com/";
+  const char* kUrl2 = "https://www.gmail.com/";
+
+  SpdyCredential cred;
+  cred.slot = 2;
+  cred.proof = proof;
+  cred.certs.push_back(cert);
+
+  scoped_ptr<SpdyFrame> req(ConstructCredentialRequestFrame(
+      1, GURL(kUrl1), 1));
+  scoped_ptr<SpdyFrame> credential(ConstructSpdyCredential(cred));
+  scoped_ptr<SpdyFrame> req2(ConstructCredentialRequestFrame(
+      2, GURL(kUrl2), 3));
+  MockWrite writes[] = {
+    CreateMockWrite(*req.get(), 0),
+    CreateMockWrite(*credential.get(), 2),
+    CreateMockWrite(*req2.get(), 3),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1),
+    CreateMockRead(*resp2, 4),
+    MockRead(SYNCHRONOUS, 0, 5)  // EOF
+  };
+
+  HostPortPair host_port_pair(HostPortPair::FromURL(GURL(kUrl1)));
+  HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+
+  DeterministicMockClientSocketFactory* socket_factory =
+      session_deps_.deterministic_socket_factory.get();
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+  socket_factory->AddSocketDataProvider(&data);
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  ssl.channel_id_sent = true;
+  ssl.server_bound_cert_service = server_bound_cert_service;
+  ssl.protocol_negotiated = kProtoSPDY3;
+  socket_factory->AddSSLSocketDataProvider(&ssl);
+  http_session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic(
+      &session_deps_);
+  session_ = http_session_->spdy_session_pool()->Get(pair, BoundNetLog());
+  transport_params_ = new TransportSocketParams(host_port_pair,
+                                                MEDIUM, false, false,
+                                                OnHostResolutionCallback());
+  TestCompletionCallback callback;
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  SSLConfig ssl_config;
+  scoped_refptr<SOCKSSocketParams> socks_params;
+  scoped_refptr<HttpProxySocketParams> http_proxy_params;
+  scoped_refptr<SSLSocketParams> ssl_params(
+      new SSLSocketParams(transport_params_,
+                          socks_params,
+                          http_proxy_params,
+                          ProxyServer::SCHEME_DIRECT,
+                          host_port_pair,
+                          ssl_config,
+                          0,
+                          false,
+                          false));
+  EXPECT_EQ(ERR_IO_PENDING,
+            connection->Init(host_port_pair.ToString(),
+                             ssl_params,
+                             MEDIUM,
+                             callback.callback(),
+                             http_session_->GetSSLSocketPool(
+                                 HttpNetworkSession::NORMAL_SOCKET_POOL),
+                             BoundNetLog()));
+  callback.WaitForResult();
+  EXPECT_EQ(OK,
+            session_->InitializeWithSocket(connection.release(), true, OK));
+
+  HttpRequestInfo request;
+  request.method = "GET";
+  request.url = GURL(kUrl1);
+  HttpResponseInfo response;
+  HttpRequestHeaders headers;
+  BoundNetLog net_log;
+  scoped_ptr<SpdyHttpStream> http_stream(
+      new SpdyHttpStream(session_.get(), true));
+  ASSERT_EQ(
+      OK,
+      http_stream->InitializeStream(&request, net_log, CompletionCallback()));
+
+  //  EXPECT_FALSE(session_->NeedsCredentials(request.url));
+  //  GURL new_origin(kUrl2);
+  //  EXPECT_TRUE(session_->NeedsCredentials(new_origin));
+
+  EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
+                                                     callback.callback()));
+  EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair));
+
+  data.RunFor(2);
+  callback.WaitForResult();
+
+  // Start up second request for resource on a new origin.
+  scoped_ptr<SpdyHttpStream> http_stream2(
+      new SpdyHttpStream(session_.get(), true));
+  request.url = GURL(kUrl2);
+  ASSERT_EQ(
+      OK,
+      http_stream2->InitializeStream(&request, net_log, CompletionCallback()));
+  EXPECT_EQ(ERR_IO_PENDING, http_stream2->SendRequest(headers, &response,
+                                                      callback.callback()));
+  data.RunFor(2);
+  callback.WaitForResult();
+
+  EXPECT_EQ(ERR_IO_PENDING, http_stream2->ReadResponseHeaders(
+      callback.callback()));
+  data.RunFor(1);
+  EXPECT_EQ(OK, callback.WaitForResult());
+  ASSERT_TRUE(response.headers.get() != NULL);
+  ASSERT_EQ(200, response.headers->response_code());
+}
+
+TEST_F(SpdyHttpStreamSpdy3Test, SendCredentialsEC) {
+  scoped_refptr<base::SequencedWorkerPool> sequenced_worker_pool =
+      new base::SequencedWorkerPool(1, "SpdyHttpStreamSpdy3Test");
+  scoped_ptr<ServerBoundCertService> server_bound_cert_service(
+      new ServerBoundCertService(new DefaultServerBoundCertStore(NULL),
+                                 sequenced_worker_pool));
+  std::string cert;
+  std::string proof;
+  GetECServerBoundCertAndProof("http://www.gmail.com/",
+                               server_bound_cert_service.get(),
+                               &cert, &proof);
+
+  TestSendCredentials(server_bound_cert_service.get(), cert, proof);
+
+  sequenced_worker_pool->Shutdown();
+}
+
+#endif  // !defined(USE_OPENSSL)
+
+// TODO(willchan): Write a longer test for SpdyStream that exercises all
+// methods.
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_http_utils.cc b/src/net/spdy/spdy_http_utils.cc
new file mode 100644
index 0000000..8a51685
--- /dev/null
+++ b/src/net/spdy/spdy_http_utils.cc
@@ -0,0 +1,186 @@
+// Copyright (c) 2012 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_http_utils.h"
+
+#include <string>
+
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "net/base/escape.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_util.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_util.h"
+
+namespace net {
+
+bool SpdyHeadersToHttpResponse(const SpdyHeaderBlock& headers,
+                               int protocol_version,
+                               HttpResponseInfo* response) {
+  std::string status_key = (protocol_version >= 3) ? ":status" : "status";
+  std::string version_key = (protocol_version >= 3) ? ":version" : "version";
+  std::string version;
+  std::string status;
+
+  // The "status" and "version" headers are required.
+  SpdyHeaderBlock::const_iterator it;
+  it = headers.find(status_key);
+  if (it == headers.end())
+    return false;
+  status = it->second;
+
+  it = headers.find(version_key);
+  if (it == headers.end())
+    return false;
+  version = it->second;
+
+  response->response_time = base::Time::Now();
+
+  std::string raw_headers(version);
+  raw_headers.push_back(' ');
+  raw_headers.append(status);
+  raw_headers.push_back('\0');
+  for (it = headers.begin(); it != headers.end(); ++it) {
+    // For each value, if the server sends a NUL-separated
+    // list of values, we separate that back out into
+    // individual headers for each value in the list.
+    // e.g.
+    //    Set-Cookie "foo\0bar"
+    // becomes
+    //    Set-Cookie: foo\0
+    //    Set-Cookie: bar\0
+    std::string value = it->second;
+    size_t start = 0;
+    size_t end = 0;
+    do {
+      end = value.find('\0', start);
+      std::string tval;
+      if (end != value.npos)
+        tval = value.substr(start, (end - start));
+      else
+        tval = value.substr(start);
+      if (protocol_version >= 3 && it->first[0] == ':')
+        raw_headers.append(it->first.substr(1));
+      else
+        raw_headers.append(it->first);
+      raw_headers.push_back(':');
+      raw_headers.append(tval);
+      raw_headers.push_back('\0');
+      start = end + 1;
+    } while (end != value.npos);
+  }
+
+  response->headers = new HttpResponseHeaders(raw_headers);
+  response->was_fetched_via_spdy = true;
+  return true;
+}
+
+void CreateSpdyHeadersFromHttpRequest(const HttpRequestInfo& info,
+                                      const HttpRequestHeaders& request_headers,
+                                      SpdyHeaderBlock* headers,
+                                      int protocol_version,
+                                      bool direct) {
+
+  HttpRequestHeaders::Iterator it(request_headers);
+  while (it.GetNext()) {
+    std::string name = StringToLowerASCII(it.name());
+    if (name == "connection" || name == "proxy-connection" ||
+        name == "transfer-encoding") {
+      continue;
+    }
+    if (headers->find(name) == headers->end()) {
+      (*headers)[name] = it.value();
+    } else {
+      std::string new_value = (*headers)[name];
+      new_value.append(1, '\0');  // +=() doesn't append 0's
+      new_value += it.value();
+      (*headers)[name] = new_value;
+    }
+  }
+  static const char kHttpProtocolVersion[] = "HTTP/1.1";
+
+  if (protocol_version < 3) {
+    (*headers)["version"] = kHttpProtocolVersion;
+    (*headers)["method"] = info.method;
+    (*headers)["host"] = GetHostAndOptionalPort(info.url);
+    (*headers)["scheme"] = info.url.scheme();
+    if (direct)
+      (*headers)["url"] = HttpUtil::PathForRequest(info.url);
+    else
+      (*headers)["url"] = HttpUtil::SpecForRequest(info.url);
+  } else {
+    (*headers)[":version"] = kHttpProtocolVersion;
+    (*headers)[":method"] = info.method;
+    (*headers)[":host"] = GetHostAndOptionalPort(info.url);
+    (*headers)[":scheme"] = info.url.scheme();
+    (*headers)[":path"] = HttpUtil::PathForRequest(info.url);
+    headers->erase("host"); // this is kinda insane, spdy 3 spec.
+  }
+
+}
+
+COMPILE_ASSERT(HIGHEST - LOWEST < 4 &&
+               HIGHEST - MINIMUM_PRIORITY < 5,
+               request_priority_incompatible_with_spdy);
+
+SpdyPriority ConvertRequestPriorityToSpdyPriority(
+    const RequestPriority priority,
+    int protocol_version) {
+  DCHECK_GE(priority, MINIMUM_PRIORITY);
+  DCHECK_LT(priority, NUM_PRIORITIES);
+  if (protocol_version == 2) {
+    // SPDY 2 only has 2 bits of priority, but we have 5 RequestPriorities.
+    // Map IDLE => 3, LOWEST => 2, LOW => 2, MEDIUM => 1, HIGHEST => 0.
+    if (priority > LOWEST) {
+      return static_cast<SpdyPriority>(HIGHEST - priority);
+    } else {
+      return static_cast<SpdyPriority>(HIGHEST - priority - 1);
+    }
+  } else {
+    return static_cast<SpdyPriority>(HIGHEST - priority);
+  }
+}
+
+GURL GetUrlFromHeaderBlock(const SpdyHeaderBlock& headers,
+                           int protocol_version,
+                           bool pushed) {
+  // SPDY 2 server push urls are specified in a single "url" header.
+  if (pushed && protocol_version == 2) {
+      std::string url;
+      SpdyHeaderBlock::const_iterator it;
+      it = headers.find("url");
+      if (it != headers.end())
+        url = it->second;
+      return GURL(url);
+  }
+
+  const char* scheme_header = protocol_version >= 3 ? ":scheme" : "scheme";
+  const char* host_header = protocol_version >= 3 ? ":host" : "host";
+  const char* path_header = protocol_version >= 3 ? ":path" : "url";
+
+  std::string scheme;
+  std::string host_port;
+  std::string path;
+  SpdyHeaderBlock::const_iterator it;
+  it = headers.find(scheme_header);
+  if (it != headers.end())
+    scheme = it->second;
+  it = headers.find(host_header);
+  if (it != headers.end())
+    host_port = it->second;
+  it = headers.find(path_header);
+  if (it != headers.end())
+    path = it->second;
+
+  std::string url =  (scheme.empty() || host_port.empty() || path.empty())
+      ? "" : scheme + "://" + host_port + path;
+  return GURL(url);
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_http_utils.h b/src/net/spdy/spdy_http_utils.h
new file mode 100644
index 0000000..fabf4a2
--- /dev/null
+++ b/src/net/spdy/spdy_http_utils.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_SPDY_SPDY_HTTP_UTILS_H_
+#define NET_SPDY_SPDY_HTTP_UTILS_H_
+
+#include "googleurl/src/gurl.h"
+#include "net/base/net_export.h"
+#include "net/base/request_priority.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_header_block.h"
+
+namespace net {
+
+class HttpResponseInfo;
+struct HttpRequestInfo;
+class HttpRequestHeaders;
+
+// Convert a SpdyHeaderBlock into an HttpResponseInfo.
+// |headers| input parameter with the SpdyHeaderBlock.
+// |response| output parameter for the HttpResponseInfo.
+// Returns true if successfully converted.  False if the SpdyHeaderBlock is
+// incomplete (e.g. missing 'status' or 'version').
+bool SpdyHeadersToHttpResponse(const SpdyHeaderBlock& headers,
+                               int protocol_version,
+                               HttpResponseInfo* response);
+
+// Create a SpdyHeaderBlock for a Spdy SYN_STREAM Frame from
+// HttpRequestInfo and HttpRequestHeaders.
+void CreateSpdyHeadersFromHttpRequest(const HttpRequestInfo& info,
+                                      const HttpRequestHeaders& request_headers,
+                                      SpdyHeaderBlock* headers,
+                                      int protocol_version,
+                                      bool direct);
+
+// Returns the URL associated with the |headers| by assembling the
+// scheme, host and path from the protocol specific keys.
+GURL GetUrlFromHeaderBlock(const SpdyHeaderBlock& headers,
+                           int protocol_version,
+                           bool pushed);
+
+NET_EXPORT_PRIVATE SpdyPriority ConvertRequestPriorityToSpdyPriority(
+    RequestPriority priority,
+    int protocol_version);
+
+}  // namespace net
+
+#endif  // NET_SPDY_SPDY_HTTP_UTILS_H_
diff --git a/src/net/spdy/spdy_http_utils_unittest.cc b/src/net/spdy/spdy_http_utils_unittest.cc
new file mode 100644
index 0000000..2419de1
--- /dev/null
+++ b/src/net/spdy/spdy_http_utils_unittest.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 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_http_utils.h"
+
+#include "testing/platform_test.h"
+
+namespace net {
+
+namespace test {
+
+TEST(SpdyHttpUtilsTest, ConvertRequestPriorityToSpdy2Priority) {
+  EXPECT_EQ(0, ConvertRequestPriorityToSpdyPriority(HIGHEST, 2));
+  EXPECT_EQ(1, ConvertRequestPriorityToSpdyPriority(MEDIUM, 2));
+  EXPECT_EQ(2, ConvertRequestPriorityToSpdyPriority(LOW, 2));
+  EXPECT_EQ(2, ConvertRequestPriorityToSpdyPriority(LOWEST, 2));
+  EXPECT_EQ(3, ConvertRequestPriorityToSpdyPriority(IDLE, 2));
+}
+TEST(SpdyHttpUtilsTest, ConvertRequestPriorityToSpdy3Priority) {
+  EXPECT_EQ(0, ConvertRequestPriorityToSpdyPriority(HIGHEST, 3));
+  EXPECT_EQ(1, ConvertRequestPriorityToSpdyPriority(MEDIUM, 3));
+  EXPECT_EQ(2, ConvertRequestPriorityToSpdyPriority(LOW, 3));
+  EXPECT_EQ(3, ConvertRequestPriorityToSpdyPriority(LOWEST, 3));
+  EXPECT_EQ(4, ConvertRequestPriorityToSpdyPriority(IDLE, 3));
+}
+
+}  // namespace test
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_io_buffer.cc b/src/net/spdy/spdy_io_buffer.cc
new file mode 100644
index 0000000..f0c14b1
--- /dev/null
+++ b/src/net/spdy/spdy_io_buffer.cc
@@ -0,0 +1,45 @@
+// Copyright (c) 2012 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_io_buffer.h"
+#include "net/spdy/spdy_stream.h"
+
+namespace net {
+
+// static
+uint64 SpdyIOBuffer::order_ = 0;
+
+SpdyIOBuffer::SpdyIOBuffer(
+    IOBuffer* buffer, int size, RequestPriority priority, SpdyStream* stream)
+  : buffer_(new DrainableIOBuffer(buffer, size)),
+    priority_(priority),
+    position_(++order_),
+    stream_(stream) {}
+
+SpdyIOBuffer::SpdyIOBuffer() : priority_(HIGHEST), position_(0), stream_(NULL) {
+}
+
+SpdyIOBuffer::SpdyIOBuffer(const SpdyIOBuffer& rhs) {
+  buffer_ = rhs.buffer_;
+  priority_ = rhs.priority_;
+  position_ = rhs.position_;
+  stream_ = rhs.stream_;
+}
+
+SpdyIOBuffer::~SpdyIOBuffer() {}
+
+SpdyIOBuffer& SpdyIOBuffer::operator=(const SpdyIOBuffer& rhs) {
+  buffer_ = rhs.buffer_;
+  priority_ = rhs.priority_;
+  position_ = rhs.position_;
+  stream_ = rhs.stream_;
+  return *this;
+}
+
+void SpdyIOBuffer::release() {
+  buffer_ = NULL;
+  stream_ = NULL;
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_io_buffer.h b/src/net/spdy/spdy_io_buffer.h
new file mode 100644
index 0000000..9bbee16
--- /dev/null
+++ b/src/net/spdy/spdy_io_buffer.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_SPDY_SPDY_IO_BUFFER_H_
+#define NET_SPDY_SPDY_IO_BUFFER_H_
+
+#include "base/memory/ref_counted.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_export.h"
+#include "net/base/request_priority.h"
+
+namespace net {
+
+class SpdyStream;
+
+// A class for managing SPDY IO buffers.  These buffers need to be prioritized
+// so that the SpdySession sends them in the right order.  Further, they need
+// to track the SpdyStream which they are associated with so that incremental
+// completion of the IO can notify the appropriate stream of completion.
+class NET_EXPORT_PRIVATE SpdyIOBuffer {
+ public:
+  // Constructor
+  // |buffer| is the actual data buffer.
+  // |size| is the size of the data buffer.
+  // |priority| is the priority of this buffer.
+  // |stream| is a pointer to the stream which is managing this buffer.
+  SpdyIOBuffer(IOBuffer* buffer, int size, RequestPriority priority,
+               SpdyStream* stream);
+  // Declare this instead of using the default so that we avoid needing to
+  // include spdy_stream.h.
+  SpdyIOBuffer(const SpdyIOBuffer& rhs);
+  SpdyIOBuffer();
+  ~SpdyIOBuffer();
+  // Declare this instead of using the default so that we avoid needing to
+  // include spdy_stream.h.
+  SpdyIOBuffer& operator=(const SpdyIOBuffer& rhs);
+
+  // Accessors.
+  DrainableIOBuffer* buffer() const { return buffer_; }
+  size_t size() const { return buffer_->size(); }
+  void release();
+  RequestPriority priority() const { return priority_; }
+  const scoped_refptr<SpdyStream>& stream() const { return stream_; }
+
+  // Comparison operator to support sorting.
+  bool operator<(const SpdyIOBuffer& other) const {
+    if (priority_ != other.priority_)
+      return priority_ < other.priority_;
+    return position_ > other.position_;
+  }
+
+ private:
+  scoped_refptr<DrainableIOBuffer> buffer_;
+  RequestPriority priority_;
+  uint64 position_;
+  scoped_refptr<SpdyStream> stream_;
+  static uint64 order_;  // Maintains a FIFO order for equal priorities.
+};
+
+}  // namespace net
+
+#endif  // NET_SPDY_SPDY_IO_BUFFER_H_
diff --git a/src/net/spdy/spdy_network_transaction_spdy2_unittest.cc b/src/net/spdy/spdy_network_transaction_spdy2_unittest.cc
new file mode 100644
index 0000000..07708dc
--- /dev/null
+++ b/src/net/spdy/spdy_network_transaction_spdy2_unittest.cc
@@ -0,0 +1,5780 @@
+// Copyright (c) 2012 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/http/http_network_transaction.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_vector.h"
+#include "net/base/auth.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_data_stream.h"
+#include "net/base/upload_file_element_reader.h"
+#include "net/http/http_network_session_peer.h"
+#include "net/http/http_transaction_unittest.h"
+#include "net/socket/client_socket_pool_base.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_http_stream.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/spdy/spdy_test_util_spdy2.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/platform_test.h"
+
+using namespace net::test_spdy2;
+
+//-----------------------------------------------------------------------------
+
+namespace net {
+
+enum SpdyNetworkTransactionSpdy2TestTypes {
+  SPDYNPN,
+  SPDYNOSSL,
+  SPDYSSL,
+};
+
+class SpdyNetworkTransactionSpdy2Test
+    : public ::testing::TestWithParam<SpdyNetworkTransactionSpdy2TestTypes> {
+ protected:
+
+  virtual void SetUp() {
+    google_get_request_initialized_ = false;
+    google_post_request_initialized_ = false;
+    google_chunked_post_request_initialized_ = false;
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+  }
+
+  virtual void TearDown() {
+    UploadDataStream::ResetMergeChunks();
+    // Empty the current queue.
+    MessageLoop::current()->RunUntilIdle();
+  }
+
+  void set_merge_chunks(bool merge) {
+    UploadDataStream::set_merge_chunks(merge);
+  }
+
+  struct TransactionHelperResult {
+    int rv;
+    std::string status_line;
+    std::string response_data;
+    HttpResponseInfo response_info;
+  };
+
+  // A helper class that handles all the initial npn/ssl setup.
+  class NormalSpdyTransactionHelper {
+   public:
+    NormalSpdyTransactionHelper(const HttpRequestInfo& request,
+                                const BoundNetLog& log,
+                                SpdyNetworkTransactionSpdy2TestTypes test_type,
+                                SpdySessionDependencies* session_deps)
+        : request_(request),
+          session_deps_(session_deps == NULL ?
+                        new SpdySessionDependencies() : session_deps),
+          session_(SpdySessionDependencies::SpdyCreateSession(
+                       session_deps_.get())),
+          log_(log),
+          test_type_(test_type),
+          deterministic_(false),
+          spdy_enabled_(true) {
+      switch (test_type_) {
+        case SPDYNOSSL:
+        case SPDYSSL:
+          port_ = 80;
+          break;
+        case SPDYNPN:
+          port_ = 443;
+          break;
+        default:
+          NOTREACHED();
+      }
+    }
+
+    ~NormalSpdyTransactionHelper() {
+      // Any test which doesn't close the socket by sending it an EOF will
+      // have a valid session left open, which leaks the entire session pool.
+      // This is just fine - in fact, some of our tests intentionally do this
+      // so that we can check consistency of the SpdySessionPool as the test
+      // finishes.  If we had put an EOF on the socket, the SpdySession would
+      // have closed and we wouldn't be able to check the consistency.
+
+      // Forcefully close existing sessions here.
+      session()->spdy_session_pool()->CloseAllSessions();
+    }
+
+    void SetDeterministic() {
+      session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic(
+          session_deps_.get());
+      deterministic_ = true;
+    }
+
+    void SetSpdyDisabled() {
+      spdy_enabled_ = false;
+      port_ = 80;
+    }
+
+    void RunPreTestSetup() {
+      if (!session_deps_.get())
+        session_deps_.reset(new SpdySessionDependencies());
+      if (!session_.get())
+        session_ = SpdySessionDependencies::SpdyCreateSession(
+            session_deps_.get());
+      HttpStreamFactory::set_use_alternate_protocols(false);
+      HttpStreamFactory::set_force_spdy_over_ssl(false);
+      HttpStreamFactory::set_force_spdy_always(false);
+
+      std::vector<std::string> next_protos;
+      next_protos.push_back("http/1.1");
+      next_protos.push_back("spdy/2");
+
+      switch (test_type_) {
+        case SPDYNPN:
+          session_->http_server_properties()->SetAlternateProtocol(
+              HostPortPair("www.google.com", 80), 443,
+              NPN_SPDY_2);
+          HttpStreamFactory::set_use_alternate_protocols(true);
+          HttpStreamFactory::SetNextProtos(next_protos);
+          break;
+        case SPDYNOSSL:
+          HttpStreamFactory::set_force_spdy_over_ssl(false);
+          HttpStreamFactory::set_force_spdy_always(true);
+          break;
+        case SPDYSSL:
+          HttpStreamFactory::set_force_spdy_over_ssl(true);
+          HttpStreamFactory::set_force_spdy_always(true);
+          break;
+        default:
+          NOTREACHED();
+      }
+
+      // We're now ready to use SSL-npn SPDY.
+      trans_.reset(new HttpNetworkTransaction(session_));
+    }
+
+    // Start the transaction, read some data, finish.
+    void RunDefaultTest() {
+      output_.rv = trans_->Start(&request_, callback.callback(), log_);
+
+      // We expect an IO Pending or some sort of error.
+      EXPECT_LT(output_.rv, 0);
+      if (output_.rv != ERR_IO_PENDING)
+        return;
+
+      output_.rv = callback.WaitForResult();
+      if (output_.rv != OK) {
+        session_->spdy_session_pool()->CloseCurrentSessions(net::ERR_ABORTED);
+        return;
+      }
+
+      // Verify responses.
+      const HttpResponseInfo* response = trans_->GetResponseInfo();
+      ASSERT_TRUE(response != NULL);
+      ASSERT_TRUE(response->headers != NULL);
+      EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+      EXPECT_EQ(spdy_enabled_, response->was_fetched_via_spdy);
+      if (test_type_ == SPDYNPN && spdy_enabled_) {
+        EXPECT_TRUE(response->was_npn_negotiated);
+      } else {
+        EXPECT_TRUE(!response->was_npn_negotiated);
+      }
+      // If SPDY is not enabled, a HTTP request should not be diverted
+      // over a SSL session.
+      if (!spdy_enabled_) {
+        EXPECT_EQ(request_.url.SchemeIs("https"),
+                  response->was_npn_negotiated);
+      }
+      EXPECT_EQ("127.0.0.1", response->socket_address.host());
+      EXPECT_EQ(port_, response->socket_address.port());
+      output_.status_line = response->headers->GetStatusLine();
+      output_.response_info = *response;  // Make a copy so we can verify.
+      output_.rv = ReadTransaction(trans_.get(), &output_.response_data);
+    }
+
+    // Most tests will want to call this function. In particular, the MockReads
+    // should end with an empty read, and that read needs to be processed to
+    // ensure proper deletion of the spdy_session_pool.
+    void VerifyDataConsumed() {
+      for (DataVector::iterator it = data_vector_.begin();
+          it != data_vector_.end(); ++it) {
+        EXPECT_TRUE((*it)->at_read_eof()) << "Read count: "
+                                          << (*it)->read_count()
+                                          << " Read index: "
+                                          << (*it)->read_index();
+        EXPECT_TRUE((*it)->at_write_eof()) << "Write count: "
+                                           << (*it)->write_count()
+                                           << " Write index: "
+                                           << (*it)->write_index();
+      }
+    }
+
+    // Occasionally a test will expect to error out before certain reads are
+    // processed. In that case we want to explicitly ensure that the reads were
+    // not processed.
+    void VerifyDataNotConsumed() {
+      for (DataVector::iterator it = data_vector_.begin();
+          it != data_vector_.end(); ++it) {
+        EXPECT_TRUE(!(*it)->at_read_eof()) << "Read count: "
+                                           << (*it)->read_count()
+                                           << " Read index: "
+                                           << (*it)->read_index();
+        EXPECT_TRUE(!(*it)->at_write_eof()) << "Write count: "
+                                            << (*it)->write_count()
+                                            << " Write index: "
+                                            << (*it)->write_index();
+      }
+    }
+
+    void RunToCompletion(StaticSocketDataProvider* data) {
+      RunPreTestSetup();
+      AddData(data);
+      RunDefaultTest();
+      VerifyDataConsumed();
+    }
+
+    void AddData(StaticSocketDataProvider* data) {
+      DCHECK(!deterministic_);
+      data_vector_.push_back(data);
+      SSLSocketDataProvider* ssl_provider =
+          new SSLSocketDataProvider(ASYNC, OK);
+      if (test_type_ == SPDYNPN)
+        ssl_provider->SetNextProto(kProtoSPDY2);
+
+      ssl_vector_.push_back(ssl_provider);
+      if (test_type_ == SPDYNPN || test_type_ == SPDYSSL)
+        session_deps_->socket_factory->AddSSLSocketDataProvider(ssl_provider);
+
+      session_deps_->socket_factory->AddSocketDataProvider(data);
+      if (test_type_ == SPDYNPN) {
+        MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING);
+        StaticSocketDataProvider* hanging_non_alternate_protocol_socket =
+            new StaticSocketDataProvider(NULL, 0, NULL, 0);
+        hanging_non_alternate_protocol_socket->set_connect_data(
+            never_finishing_connect);
+        session_deps_->socket_factory->AddSocketDataProvider(
+            hanging_non_alternate_protocol_socket);
+        alternate_vector_.push_back(hanging_non_alternate_protocol_socket);
+      }
+    }
+
+    void AddDeterministicData(DeterministicSocketData* data) {
+      DCHECK(deterministic_);
+      data_vector_.push_back(data);
+      SSLSocketDataProvider* ssl_provider =
+          new SSLSocketDataProvider(ASYNC, OK);
+      if (test_type_ == SPDYNPN)
+        ssl_provider->SetNextProto(kProtoSPDY2);
+
+      ssl_vector_.push_back(ssl_provider);
+      if (test_type_ == SPDYNPN || test_type_ == SPDYSSL) {
+        session_deps_->deterministic_socket_factory->
+            AddSSLSocketDataProvider(ssl_provider);
+      }
+      session_deps_->deterministic_socket_factory->AddSocketDataProvider(data);
+      if (test_type_ == SPDYNPN) {
+        MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING);
+        DeterministicSocketData* hanging_non_alternate_protocol_socket =
+            new DeterministicSocketData(NULL, 0, NULL, 0);
+        hanging_non_alternate_protocol_socket->set_connect_data(
+            never_finishing_connect);
+        session_deps_->deterministic_socket_factory->AddSocketDataProvider(
+            hanging_non_alternate_protocol_socket);
+        alternate_deterministic_vector_.push_back(
+            hanging_non_alternate_protocol_socket);
+      }
+    }
+
+    void SetSession(const scoped_refptr<HttpNetworkSession>& session) {
+      session_ = session;
+    }
+    HttpNetworkTransaction* trans() { return trans_.get(); }
+    void ResetTrans() { trans_.reset(); }
+    TransactionHelperResult& output() { return output_; }
+    const HttpRequestInfo& request() const { return request_; }
+    const scoped_refptr<HttpNetworkSession>& session() const {
+      return session_;
+    }
+    scoped_ptr<SpdySessionDependencies>& session_deps() {
+      return session_deps_;
+    }
+    int port() const { return port_; }
+    SpdyNetworkTransactionSpdy2TestTypes test_type() const {
+      return test_type_;
+    }
+
+   private:
+    typedef std::vector<StaticSocketDataProvider*> DataVector;
+    typedef ScopedVector<SSLSocketDataProvider> SSLVector;
+    typedef ScopedVector<StaticSocketDataProvider> AlternateVector;
+    typedef ScopedVector<DeterministicSocketData> AlternateDeterministicVector;
+    HttpRequestInfo request_;
+    scoped_ptr<SpdySessionDependencies> session_deps_;
+    scoped_refptr<HttpNetworkSession> session_;
+    TransactionHelperResult output_;
+    scoped_ptr<StaticSocketDataProvider> first_transaction_;
+    SSLVector ssl_vector_;
+    TestCompletionCallback callback;
+    scoped_ptr<HttpNetworkTransaction> trans_;
+    scoped_ptr<HttpNetworkTransaction> trans_http_;
+    DataVector data_vector_;
+    AlternateVector alternate_vector_;
+    AlternateDeterministicVector alternate_deterministic_vector_;
+    const BoundNetLog& log_;
+    SpdyNetworkTransactionSpdy2TestTypes test_type_;
+    int port_;
+    bool deterministic_;
+    bool spdy_enabled_;
+  };
+
+  void ConnectStatusHelperWithExpectedStatus(const MockRead& status,
+                                             int expected_status);
+
+  void ConnectStatusHelper(const MockRead& status);
+
+  const HttpRequestInfo& CreateGetPushRequest() {
+    google_get_push_request_.method = "GET";
+    google_get_push_request_.url = GURL("http://www.google.com/foo.dat");
+    google_get_push_request_.load_flags = 0;
+    return google_get_push_request_;
+  }
+
+  const HttpRequestInfo& CreateGetRequest() {
+    if (!google_get_request_initialized_) {
+      google_get_request_.method = "GET";
+      google_get_request_.url = GURL(kDefaultURL);
+      google_get_request_.load_flags = 0;
+      google_get_request_initialized_ = true;
+    }
+    return google_get_request_;
+  }
+
+  const HttpRequestInfo& CreateGetRequestWithUserAgent() {
+    if (!google_get_request_initialized_) {
+      google_get_request_.method = "GET";
+      google_get_request_.url = GURL(kDefaultURL);
+      google_get_request_.load_flags = 0;
+      google_get_request_.extra_headers.SetHeader("User-Agent", "Chrome");
+      google_get_request_initialized_ = true;
+    }
+    return google_get_request_;
+  }
+
+  const HttpRequestInfo& CreatePostRequest() {
+    if (!google_post_request_initialized_) {
+      ScopedVector<UploadElementReader> element_readers;
+      element_readers.push_back(
+          new UploadBytesElementReader(kUploadData, kUploadDataSize));
+      upload_data_stream_.reset(new UploadDataStream(&element_readers, 0));
+
+      google_post_request_.method = "POST";
+      google_post_request_.url = GURL(kDefaultURL);
+      google_post_request_.upload_data_stream = upload_data_stream_.get();
+      google_post_request_initialized_ = true;
+    }
+    return google_post_request_;
+  }
+
+  const HttpRequestInfo& CreateFilePostRequest() {
+    if (!google_post_request_initialized_) {
+      FilePath file_path;
+      CHECK(file_util::CreateTemporaryFileInDir(temp_dir_.path(), &file_path));
+      CHECK_EQ(static_cast<int>(kUploadDataSize),
+               file_util::WriteFile(file_path, kUploadData, kUploadDataSize));
+
+      ScopedVector<UploadElementReader> element_readers;
+      element_readers.push_back(new UploadFileElementReader(
+          file_path, 0, kUploadDataSize, base::Time()));
+      upload_data_stream_.reset(new UploadDataStream(&element_readers, 0));
+
+      google_post_request_.method = "POST";
+      google_post_request_.url = GURL(kDefaultURL);
+      google_post_request_.upload_data_stream = upload_data_stream_.get();
+      google_post_request_initialized_ = true;
+    }
+    return google_post_request_;
+  }
+
+  const HttpRequestInfo& CreateComplexPostRequest() {
+    if (!google_post_request_initialized_) {
+      const int kFileRangeOffset = 1;
+      const int kFileRangeLength = 3;
+      CHECK_LT(kFileRangeOffset + kFileRangeLength, kUploadDataSize);
+
+      FilePath file_path;
+      CHECK(file_util::CreateTemporaryFileInDir(temp_dir_.path(), &file_path));
+      CHECK_EQ(static_cast<int>(kUploadDataSize),
+               file_util::WriteFile(file_path, kUploadData, kUploadDataSize));
+
+      ScopedVector<UploadElementReader> element_readers;
+      element_readers.push_back(
+          new UploadBytesElementReader(kUploadData, kFileRangeOffset));
+      element_readers.push_back(new UploadFileElementReader(
+          file_path, kFileRangeOffset, kFileRangeLength, base::Time()));
+      element_readers.push_back(new UploadBytesElementReader(
+          kUploadData + kFileRangeOffset + kFileRangeLength,
+          kUploadDataSize - (kFileRangeOffset + kFileRangeLength)));
+      upload_data_stream_.reset(new UploadDataStream(&element_readers, 0));
+
+      google_post_request_.method = "POST";
+      google_post_request_.url = GURL(kDefaultURL);
+      google_post_request_.upload_data_stream = upload_data_stream_.get();
+      google_post_request_initialized_ = true;
+    }
+    return google_post_request_;
+  }
+
+  const HttpRequestInfo& CreateChunkedPostRequest() {
+    if (!google_chunked_post_request_initialized_) {
+      upload_data_stream_.reset(
+          new UploadDataStream(UploadDataStream::CHUNKED, 0));
+      upload_data_stream_->AppendChunk(kUploadData, kUploadDataSize, false);
+      upload_data_stream_->AppendChunk(kUploadData, kUploadDataSize, true);
+
+      google_chunked_post_request_.method = "POST";
+      google_chunked_post_request_.url = GURL(kDefaultURL);
+      google_chunked_post_request_.upload_data_stream =
+          upload_data_stream_.get();
+      google_chunked_post_request_initialized_ = true;
+    }
+    return google_chunked_post_request_;
+  }
+
+  // Read the result of a particular transaction, knowing that we've got
+  // multiple transactions in the read pipeline; so as we read, we may have
+  // to skip over data destined for other transactions while we consume
+  // the data for |trans|.
+  int ReadResult(HttpNetworkTransaction* trans,
+                 StaticSocketDataProvider* data,
+                 std::string* result) {
+    const int kSize = 3000;
+
+    int bytes_read = 0;
+    scoped_refptr<net::IOBufferWithSize> buf(new net::IOBufferWithSize(kSize));
+    TestCompletionCallback callback;
+    while (true) {
+      int rv = trans->Read(buf, kSize, callback.callback());
+      if (rv == ERR_IO_PENDING) {
+        // Multiple transactions may be in the data set.  Keep pulling off
+        // reads until we complete our callback.
+        while (!callback.have_result()) {
+          data->CompleteRead();
+          MessageLoop::current()->RunUntilIdle();
+        }
+        rv = callback.WaitForResult();
+      } else if (rv <= 0) {
+        break;
+      }
+      result->append(buf->data(), rv);
+      bytes_read += rv;
+    }
+    return bytes_read;
+  }
+
+  void VerifyStreamsClosed(const NormalSpdyTransactionHelper& helper) {
+    // This lengthy block is reaching into the pool to dig out the active
+    // session.  Once we have the session, we verify that the streams are
+    // all closed and not leaked at this point.
+    const GURL& url = helper.request().url;
+    int port = helper.test_type() == SPDYNPN ? 443 : 80;
+    HostPortPair host_port_pair(url.host(), port);
+    HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+    BoundNetLog log;
+    const scoped_refptr<HttpNetworkSession>& session = helper.session();
+    SpdySessionPool* pool(session->spdy_session_pool());
+    EXPECT_TRUE(pool->HasSession(pair));
+    scoped_refptr<SpdySession> spdy_session(pool->Get(pair, log));
+    ASSERT_TRUE(spdy_session.get() != NULL);
+    EXPECT_EQ(0u, spdy_session->num_active_streams());
+    EXPECT_EQ(0u, spdy_session->num_unclaimed_pushed_streams());
+  }
+
+  void RunServerPushTest(OrderedSocketData* data,
+                         HttpResponseInfo* response,
+                         HttpResponseInfo* push_response,
+                         std::string& expected) {
+    NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                       BoundNetLog(), GetParam(), NULL);
+    helper.RunPreTestSetup();
+    helper.AddData(data);
+
+    HttpNetworkTransaction* trans = helper.trans();
+
+    // Start the transaction with basic parameters.
+    TestCompletionCallback callback;
+    int rv = trans->Start(
+        &CreateGetRequest(), callback.callback(), BoundNetLog());
+    EXPECT_EQ(ERR_IO_PENDING, rv);
+    rv = callback.WaitForResult();
+
+    // Request the pushed path.
+    scoped_ptr<HttpNetworkTransaction> trans2(
+        new HttpNetworkTransaction(helper.session()));
+    rv = trans2->Start(
+        &CreateGetPushRequest(), callback.callback(), BoundNetLog());
+    EXPECT_EQ(ERR_IO_PENDING, rv);
+    MessageLoop::current()->RunUntilIdle();
+
+    // The data for the pushed path may be coming in more than 1 packet. Compile
+    // the results into a single string.
+
+    // Read the server push body.
+    std::string result2;
+    ReadResult(trans2.get(), data, &result2);
+    // Read the response body.
+    std::string result;
+    ReadResult(trans, data, &result);
+
+    // Verify that we consumed all test data.
+    EXPECT_TRUE(data->at_read_eof());
+    EXPECT_TRUE(data->at_write_eof());
+
+    // Verify that the received push data is same as the expected push data.
+    EXPECT_EQ(result2.compare(expected), 0) << "Received data: "
+                                            << result2
+                                            << "||||| Expected data: "
+                                            << expected;
+
+    // Verify the SYN_REPLY.
+    // Copy the response info, because trans goes away.
+    *response = *trans->GetResponseInfo();
+    *push_response = *trans2->GetResponseInfo();
+
+    VerifyStreamsClosed(helper);
+  }
+
+  static void DeleteSessionCallback(NormalSpdyTransactionHelper* helper,
+                                    int result) {
+    helper->ResetTrans();
+  }
+
+  static void StartTransactionCallback(
+      const scoped_refptr<HttpNetworkSession>& session,
+      int result) {
+    scoped_ptr<HttpNetworkTransaction> trans(
+        new HttpNetworkTransaction(session));
+    TestCompletionCallback callback;
+    HttpRequestInfo request;
+    request.method = "GET";
+    request.url = GURL("http://www.google.com/");
+    request.load_flags = 0;
+    int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+    EXPECT_EQ(ERR_IO_PENDING, rv);
+    callback.WaitForResult();
+  }
+
+ private:
+  scoped_ptr<UploadDataStream> upload_data_stream_;
+  bool google_get_request_initialized_;
+  bool google_post_request_initialized_;
+  bool google_chunked_post_request_initialized_;
+  HttpRequestInfo google_get_request_;
+  HttpRequestInfo google_post_request_;
+  HttpRequestInfo google_chunked_post_request_;
+  HttpRequestInfo google_get_push_request_;
+  base::ScopedTempDir temp_dir_;
+};
+
+//-----------------------------------------------------------------------------
+// All tests are run with three different connection types: SPDY after NPN
+// negotiation, SPDY without SSL, and SPDY with SSL.
+INSTANTIATE_TEST_CASE_P(Spdy,
+                        SpdyNetworkTransactionSpdy2Test,
+                        ::testing::Values(SPDYNOSSL, SPDYSSL, SPDYNPN));
+
+
+// Verify HttpNetworkTransaction constructor.
+TEST_P(SpdyNetworkTransactionSpdy2Test, Constructor) {
+  SpdySessionDependencies session_deps;
+  scoped_refptr<HttpNetworkSession> session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+  scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, Get) {
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, GetAtEachPriority) {
+  for (RequestPriority p = MINIMUM_PRIORITY; p < NUM_PRIORITIES;
+       p = RequestPriority(p + 1)) {
+    // Construct the request.
+    scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, p));
+    MockWrite writes[] = { CreateMockWrite(*req) };
+
+    const int spdy_prio = reinterpret_cast<SpdySynStreamControlFrame*>(
+        req.get())->priority();
+    // this repeats the RequestPriority-->SpdyPriority mapping from
+    // SpdyFramer::ConvertRequestPriorityToSpdyPriority to make
+    // sure it's being done right.
+    switch(p) {
+      case HIGHEST:
+        EXPECT_EQ(0, spdy_prio);
+        break;
+      case MEDIUM:
+        EXPECT_EQ(1, spdy_prio);
+        break;
+      case LOW:
+      case LOWEST:
+        EXPECT_EQ(2, spdy_prio);
+        break;
+      case IDLE:
+        EXPECT_EQ(3, spdy_prio);
+        break;
+      default:
+        FAIL();
+    }
+
+    scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+    scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+    MockRead reads[] = {
+      CreateMockRead(*resp),
+      CreateMockRead(*body),
+      MockRead(ASYNC, 0, 0)  // EOF
+    };
+
+    DelayedSocketData data(1, reads, arraysize(reads),
+                              writes, arraysize(writes));
+    HttpRequestInfo http_req = CreateGetRequest();
+    http_req.priority = p;
+
+    NormalSpdyTransactionHelper helper(http_req, BoundNetLog(),
+                                       GetParam(), NULL);
+    helper.RunToCompletion(&data);
+    TransactionHelperResult out = helper.output();
+    EXPECT_EQ(OK, out.rv);
+    EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+    EXPECT_EQ("hello!", out.response_data);
+  }
+}
+
+// Start three gets simultaniously; making sure that multiplexed
+// streams work properly.
+
+// This can't use the TransactionHelper method, since it only
+// handles a single transaction, and finishes them as soon
+// as it launches them.
+
+// TODO(gavinp): create a working generalized TransactionHelper that
+// can allow multiple streams in flight.
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ThreeGets) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+  scoped_ptr<SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+  scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, LOWEST));
+  scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
+  scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(5, false));
+  scoped_ptr<SpdyFrame> fbody3(ConstructSpdyBodyFrame(5, true));
+
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*req2),
+    CreateMockWrite(*req3),
+  };
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1),
+    CreateMockRead(*body),
+    CreateMockRead(*resp2, 4),
+    CreateMockRead(*body2),
+    CreateMockRead(*resp3, 7),
+    CreateMockRead(*body3),
+
+    CreateMockRead(*fbody),
+    CreateMockRead(*fbody2),
+    CreateMockRead(*fbody3),
+
+    MockRead(ASYNC, 0, 0),  // EOF
+  };
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  OrderedSocketData data_placeholder(NULL, 0, NULL, 0);
+
+  BoundNetLog log;
+  TransactionHelperResult out;
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  // We require placeholder data because three get requests are sent out, so
+  // there needs to be three sets of SSL connection data.
+  helper.AddData(&data_placeholder);
+  helper.AddData(&data_placeholder);
+  scoped_ptr<HttpNetworkTransaction> trans1(
+      new HttpNetworkTransaction(helper.session()));
+  scoped_ptr<HttpNetworkTransaction> trans2(
+      new HttpNetworkTransaction(helper.session()));
+  scoped_ptr<HttpNetworkTransaction> trans3(
+      new HttpNetworkTransaction(helper.session()));
+
+  TestCompletionCallback callback1;
+  TestCompletionCallback callback2;
+  TestCompletionCallback callback3;
+
+  HttpRequestInfo httpreq1 = CreateGetRequest();
+  HttpRequestInfo httpreq2 = CreateGetRequest();
+  HttpRequestInfo httpreq3 = CreateGetRequest();
+
+  out.rv = trans1->Start(&httpreq1, callback1.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+  out.rv = trans2->Start(&httpreq2, callback2.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+  out.rv = trans3->Start(&httpreq3, callback3.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+
+  out.rv = callback1.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+  out.rv = callback3.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+
+  const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+  EXPECT_TRUE(response1->headers != NULL);
+  EXPECT_TRUE(response1->was_fetched_via_spdy);
+  out.status_line = response1->headers->GetStatusLine();
+  out.response_info = *response1;
+
+  trans2->GetResponseInfo();
+
+  out.rv = ReadTransaction(trans1.get(), &out.response_data);
+  helper.VerifyDataConsumed();
+  EXPECT_EQ(OK, out.rv);
+
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, TwoGetsLateBinding) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+  scoped_ptr<SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*req2),
+  };
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1),
+    CreateMockRead(*body),
+    CreateMockRead(*resp2, 4),
+    CreateMockRead(*body2),
+    CreateMockRead(*fbody),
+    CreateMockRead(*fbody2),
+    MockRead(ASYNC, 0, 0),  // EOF
+  };
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING);
+
+  OrderedSocketData data_placeholder(NULL, 0, NULL, 0);
+  data_placeholder.set_connect_data(never_finishing_connect);
+
+  BoundNetLog log;
+  TransactionHelperResult out;
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  // We require placeholder data because two get requests are sent out, so
+  // there needs to be two sets of SSL connection data.
+  helper.AddData(&data_placeholder);
+  scoped_ptr<HttpNetworkTransaction> trans1(
+      new HttpNetworkTransaction(helper.session()));
+  scoped_ptr<HttpNetworkTransaction> trans2(
+      new HttpNetworkTransaction(helper.session()));
+
+  TestCompletionCallback callback1;
+  TestCompletionCallback callback2;
+
+  HttpRequestInfo httpreq1 = CreateGetRequest();
+  HttpRequestInfo httpreq2 = CreateGetRequest();
+
+  out.rv = trans1->Start(&httpreq1, callback1.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+  out.rv = trans2->Start(&httpreq2, callback2.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+
+  out.rv = callback1.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+  out.rv = callback2.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+
+  const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+  EXPECT_TRUE(response1->headers != NULL);
+  EXPECT_TRUE(response1->was_fetched_via_spdy);
+  out.status_line = response1->headers->GetStatusLine();
+  out.response_info = *response1;
+  out.rv = ReadTransaction(trans1.get(), &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+
+  const HttpResponseInfo* response2 = trans2->GetResponseInfo();
+  EXPECT_TRUE(response2->headers != NULL);
+  EXPECT_TRUE(response2->was_fetched_via_spdy);
+  out.status_line = response2->headers->GetStatusLine();
+  out.response_info = *response2;
+  out.rv = ReadTransaction(trans2.get(), &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+
+  helper.VerifyDataConsumed();
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, TwoGetsLateBindingFromPreconnect) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+  scoped_ptr<SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*req2),
+  };
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1),
+    CreateMockRead(*body),
+    CreateMockRead(*resp2, 4),
+    CreateMockRead(*body2),
+    CreateMockRead(*fbody),
+    CreateMockRead(*fbody2),
+    MockRead(ASYNC, 0, 0),  // EOF
+  };
+  OrderedSocketData preconnect_data(reads, arraysize(reads),
+                                    writes, arraysize(writes));
+
+  MockConnect never_finishing_connect(ASYNC, ERR_IO_PENDING);
+
+  OrderedSocketData data_placeholder(NULL, 0, NULL, 0);
+  data_placeholder.set_connect_data(never_finishing_connect);
+
+  BoundNetLog log;
+  TransactionHelperResult out;
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&preconnect_data);
+  // We require placeholder data because 3 connections are attempted (first is
+  // the preconnect, 2nd and 3rd are the never finished connections.
+  helper.AddData(&data_placeholder);
+  helper.AddData(&data_placeholder);
+
+  scoped_ptr<HttpNetworkTransaction> trans1(
+      new HttpNetworkTransaction(helper.session()));
+  scoped_ptr<HttpNetworkTransaction> trans2(
+      new HttpNetworkTransaction(helper.session()));
+
+  TestCompletionCallback callback1;
+  TestCompletionCallback callback2;
+
+  HttpRequestInfo httpreq = CreateGetRequest();
+
+  // Preconnect the first.
+  SSLConfig preconnect_ssl_config;
+  helper.session()->ssl_config_service()->GetSSLConfig(&preconnect_ssl_config);
+  HttpStreamFactory* http_stream_factory =
+      helper.session()->http_stream_factory();
+  if (http_stream_factory->has_next_protos()) {
+    preconnect_ssl_config.next_protos = http_stream_factory->next_protos();
+  }
+
+  http_stream_factory->PreconnectStreams(
+      1, httpreq, preconnect_ssl_config, preconnect_ssl_config);
+
+  out.rv = trans1->Start(&httpreq, callback1.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+  out.rv = trans2->Start(&httpreq, callback2.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+
+  out.rv = callback1.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+  out.rv = callback2.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+
+  const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+  EXPECT_TRUE(response1->headers != NULL);
+  EXPECT_TRUE(response1->was_fetched_via_spdy);
+  out.status_line = response1->headers->GetStatusLine();
+  out.response_info = *response1;
+  out.rv = ReadTransaction(trans1.get(), &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+
+  const HttpResponseInfo* response2 = trans2->GetResponseInfo();
+  EXPECT_TRUE(response2->headers != NULL);
+  EXPECT_TRUE(response2->was_fetched_via_spdy);
+  out.status_line = response2->headers->GetStatusLine();
+  out.response_info = *response2;
+  out.rv = ReadTransaction(trans2.get(), &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+
+  helper.VerifyDataConsumed();
+}
+
+// Similar to ThreeGets above, however this test adds a SETTINGS
+// frame.  The SETTINGS frame is read during the IO loop waiting on
+// the first transaction completion, and sets a maximum concurrent
+// stream limit of 1.  This means that our IO loop exists after the
+// second transaction completes, so we can assert on read_index().
+TEST_P(SpdyNetworkTransactionSpdy2Test, ThreeGetsWithMaxConcurrent) {
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+  scoped_ptr<SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+  scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, LOWEST));
+  scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
+  scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(5, false));
+  scoped_ptr<SpdyFrame> fbody3(ConstructSpdyBodyFrame(5, true));
+
+  SettingsMap settings;
+  const size_t max_concurrent_streams = 1;
+  settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+  scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*req2),
+    CreateMockWrite(*req3),
+  };
+
+  MockRead reads[] = {
+    CreateMockRead(*settings_frame, 1),
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    CreateMockRead(*fbody),
+    CreateMockRead(*resp2, 7),
+    CreateMockRead(*body2),
+    CreateMockRead(*fbody2),
+    CreateMockRead(*resp3, 12),
+    CreateMockRead(*body3),
+    CreateMockRead(*fbody3),
+
+    MockRead(ASYNC, 0, 0),  // EOF
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  OrderedSocketData data_placeholder(NULL, 0, NULL, 0);
+
+  BoundNetLog log;
+  TransactionHelperResult out;
+  {
+    NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                       BoundNetLog(), GetParam(), NULL);
+    helper.RunPreTestSetup();
+    helper.AddData(&data);
+    // We require placeholder data because three get requests are sent out, so
+    // there needs to be three sets of SSL connection data.
+    helper.AddData(&data_placeholder);
+    helper.AddData(&data_placeholder);
+    scoped_ptr<HttpNetworkTransaction> trans1(
+        new HttpNetworkTransaction(helper.session()));
+    scoped_ptr<HttpNetworkTransaction> trans2(
+        new HttpNetworkTransaction(helper.session()));
+    scoped_ptr<HttpNetworkTransaction> trans3(
+        new HttpNetworkTransaction(helper.session()));
+
+    TestCompletionCallback callback1;
+    TestCompletionCallback callback2;
+    TestCompletionCallback callback3;
+
+    HttpRequestInfo httpreq1 = CreateGetRequest();
+    HttpRequestInfo httpreq2 = CreateGetRequest();
+    HttpRequestInfo httpreq3 = CreateGetRequest();
+
+    out.rv = trans1->Start(&httpreq1, callback1.callback(), log);
+    ASSERT_EQ(out.rv, ERR_IO_PENDING);
+    // Run transaction 1 through quickly to force a read of our SETTINGS
+    // frame.
+    out.rv = callback1.WaitForResult();
+    ASSERT_EQ(OK, out.rv);
+
+    out.rv = trans2->Start(&httpreq2, callback2.callback(), log);
+    ASSERT_EQ(out.rv, ERR_IO_PENDING);
+    out.rv = trans3->Start(&httpreq3, callback3.callback(), log);
+    ASSERT_EQ(out.rv, ERR_IO_PENDING);
+    out.rv = callback2.WaitForResult();
+    ASSERT_EQ(OK, out.rv);
+    EXPECT_EQ(7U, data.read_index());  // i.e. the third trans was queued
+
+    out.rv = callback3.WaitForResult();
+    ASSERT_EQ(OK, out.rv);
+
+    const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+    ASSERT_TRUE(response1 != NULL);
+    EXPECT_TRUE(response1->headers != NULL);
+    EXPECT_TRUE(response1->was_fetched_via_spdy);
+    out.status_line = response1->headers->GetStatusLine();
+    out.response_info = *response1;
+    out.rv = ReadTransaction(trans1.get(), &out.response_data);
+    EXPECT_EQ(OK, out.rv);
+    EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+    EXPECT_EQ("hello!hello!", out.response_data);
+
+    const HttpResponseInfo* response2 = trans2->GetResponseInfo();
+    out.status_line = response2->headers->GetStatusLine();
+    out.response_info = *response2;
+    out.rv = ReadTransaction(trans2.get(), &out.response_data);
+    EXPECT_EQ(OK, out.rv);
+    EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+    EXPECT_EQ("hello!hello!", out.response_data);
+
+    const HttpResponseInfo* response3 = trans3->GetResponseInfo();
+    out.status_line = response3->headers->GetStatusLine();
+    out.response_info = *response3;
+    out.rv = ReadTransaction(trans3.get(), &out.response_data);
+    EXPECT_EQ(OK, out.rv);
+    EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+    EXPECT_EQ("hello!hello!", out.response_data);
+
+    helper.VerifyDataConsumed();
+  }
+  EXPECT_EQ(OK, out.rv);
+}
+
+// Similar to ThreeGetsWithMaxConcurrent above, however this test adds
+// a fourth transaction.  The third and fourth transactions have
+// different data ("hello!" vs "hello!hello!") and because of the
+// user specified priority, we expect to see them inverted in
+// the response from the server.
+TEST_P(SpdyNetworkTransactionSpdy2Test, FourGetsWithMaxConcurrentPriority) {
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+  scoped_ptr<SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+  scoped_ptr<SpdyFrame> req4(
+      ConstructSpdyGet(NULL, 0, false, 5, HIGHEST));
+  scoped_ptr<SpdyFrame> resp4(ConstructSpdyGetSynReply(NULL, 0, 5));
+  scoped_ptr<SpdyFrame> fbody4(ConstructSpdyBodyFrame(5, true));
+
+  scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 7, LOWEST));
+  scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 7));
+  scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(7, false));
+  scoped_ptr<SpdyFrame> fbody3(ConstructSpdyBodyFrame(7, true));
+
+  SettingsMap settings;
+  const size_t max_concurrent_streams = 1;
+  settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+  scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+
+  MockWrite writes[] = { CreateMockWrite(*req),
+    CreateMockWrite(*req2),
+    CreateMockWrite(*req4),
+    CreateMockWrite(*req3),
+  };
+  MockRead reads[] = {
+    CreateMockRead(*settings_frame, 1),
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    CreateMockRead(*fbody),
+    CreateMockRead(*resp2, 7),
+    CreateMockRead(*body2),
+    CreateMockRead(*fbody2),
+    CreateMockRead(*resp4, 13),
+    CreateMockRead(*fbody4),
+    CreateMockRead(*resp3, 16),
+    CreateMockRead(*body3),
+    CreateMockRead(*fbody3),
+
+    MockRead(ASYNC, 0, 0),  // EOF
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  OrderedSocketData data_placeholder(NULL, 0, NULL, 0);
+
+  BoundNetLog log;
+  TransactionHelperResult out;
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+      BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  // We require placeholder data because four get requests are sent out, so
+  // there needs to be four sets of SSL connection data.
+  helper.AddData(&data_placeholder);
+  helper.AddData(&data_placeholder);
+  helper.AddData(&data_placeholder);
+  scoped_ptr<HttpNetworkTransaction> trans1(
+      new HttpNetworkTransaction(helper.session()));
+  scoped_ptr<HttpNetworkTransaction> trans2(
+      new HttpNetworkTransaction(helper.session()));
+  scoped_ptr<HttpNetworkTransaction> trans3(
+      new HttpNetworkTransaction(helper.session()));
+  scoped_ptr<HttpNetworkTransaction> trans4(
+      new HttpNetworkTransaction(helper.session()));
+
+  TestCompletionCallback callback1;
+  TestCompletionCallback callback2;
+  TestCompletionCallback callback3;
+  TestCompletionCallback callback4;
+
+  HttpRequestInfo httpreq1 = CreateGetRequest();
+  HttpRequestInfo httpreq2 = CreateGetRequest();
+  HttpRequestInfo httpreq3 = CreateGetRequest();
+  HttpRequestInfo httpreq4 = CreateGetRequest();
+  httpreq4.priority = HIGHEST;
+
+  out.rv = trans1->Start(&httpreq1, callback1.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+  // Run transaction 1 through quickly to force a read of our SETTINGS frame.
+  out.rv = callback1.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+
+  out.rv = trans2->Start(&httpreq2, callback2.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+  out.rv = trans3->Start(&httpreq3, callback3.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+  out.rv = trans4->Start(&httpreq4, callback4.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+
+  out.rv = callback2.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+  EXPECT_EQ(data.read_index(), 7U);  // i.e. the third & fourth trans queued
+
+  out.rv = callback3.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+
+  const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+  EXPECT_TRUE(response1->headers != NULL);
+  EXPECT_TRUE(response1->was_fetched_via_spdy);
+  out.status_line = response1->headers->GetStatusLine();
+  out.response_info = *response1;
+  out.rv = ReadTransaction(trans1.get(), &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+
+  const HttpResponseInfo* response2 = trans2->GetResponseInfo();
+  out.status_line = response2->headers->GetStatusLine();
+  out.response_info = *response2;
+  out.rv = ReadTransaction(trans2.get(), &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+
+  // notice: response3 gets two hellos, response4 gets one
+  // hello, so we know dequeuing priority was respected.
+  const HttpResponseInfo* response3 = trans3->GetResponseInfo();
+  out.status_line = response3->headers->GetStatusLine();
+  out.response_info = *response3;
+  out.rv = ReadTransaction(trans3.get(), &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+
+  out.rv = callback4.WaitForResult();
+  EXPECT_EQ(OK, out.rv);
+  const HttpResponseInfo* response4 = trans4->GetResponseInfo();
+  out.status_line = response4->headers->GetStatusLine();
+  out.response_info = *response4;
+  out.rv = ReadTransaction(trans4.get(), &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+  helper.VerifyDataConsumed();
+  EXPECT_EQ(OK, out.rv);
+}
+
+// Similar to ThreeGetsMaxConcurrrent above, however, this test
+// deletes a session in the middle of the transaction to insure
+// that we properly remove pendingcreatestream objects from
+// the spdy_session
+TEST_P(SpdyNetworkTransactionSpdy2Test, ThreeGetsWithMaxConcurrentDelete) {
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+  scoped_ptr<SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+  SettingsMap settings;
+  const size_t max_concurrent_streams = 1;
+  settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+  scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+
+  MockWrite writes[] = { CreateMockWrite(*req),
+    CreateMockWrite(*req2),
+  };
+  MockRead reads[] = {
+    CreateMockRead(*settings_frame, 1),
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    CreateMockRead(*fbody),
+    CreateMockRead(*resp2, 7),
+    CreateMockRead(*body2),
+    CreateMockRead(*fbody2),
+    MockRead(ASYNC, 0, 0),  // EOF
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  OrderedSocketData data_placeholder(NULL, 0, NULL, 0);
+
+  BoundNetLog log;
+  TransactionHelperResult out;
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+      BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  // We require placeholder data because three get requests are sent out, so
+  // there needs to be three sets of SSL connection data.
+  helper.AddData(&data_placeholder);
+  helper.AddData(&data_placeholder);
+  scoped_ptr<HttpNetworkTransaction> trans1(
+      new HttpNetworkTransaction(helper.session()));
+  scoped_ptr<HttpNetworkTransaction> trans2(
+      new HttpNetworkTransaction(helper.session()));
+  scoped_ptr<HttpNetworkTransaction> trans3(
+      new HttpNetworkTransaction(helper.session()));
+
+  TestCompletionCallback callback1;
+  TestCompletionCallback callback2;
+  TestCompletionCallback callback3;
+
+  HttpRequestInfo httpreq1 = CreateGetRequest();
+  HttpRequestInfo httpreq2 = CreateGetRequest();
+  HttpRequestInfo httpreq3 = CreateGetRequest();
+
+  out.rv = trans1->Start(&httpreq1, callback1.callback(), log);
+  ASSERT_EQ(out.rv, ERR_IO_PENDING);
+  // Run transaction 1 through quickly to force a read of our SETTINGS frame.
+  out.rv = callback1.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+
+  out.rv = trans2->Start(&httpreq2, callback2.callback(), log);
+  ASSERT_EQ(out.rv, ERR_IO_PENDING);
+  out.rv = trans3->Start(&httpreq3, callback3.callback(), log);
+  delete trans3.release();
+  ASSERT_EQ(out.rv, ERR_IO_PENDING);
+  out.rv = callback2.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+
+  EXPECT_EQ(8U, data.read_index());
+
+  const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+  ASSERT_TRUE(response1 != NULL);
+  EXPECT_TRUE(response1->headers != NULL);
+  EXPECT_TRUE(response1->was_fetched_via_spdy);
+  out.status_line = response1->headers->GetStatusLine();
+  out.response_info = *response1;
+  out.rv = ReadTransaction(trans1.get(), &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+
+  const HttpResponseInfo* response2 = trans2->GetResponseInfo();
+  ASSERT_TRUE(response2 != NULL);
+  out.status_line = response2->headers->GetStatusLine();
+  out.response_info = *response2;
+  out.rv = ReadTransaction(trans2.get(), &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+  helper.VerifyDataConsumed();
+  EXPECT_EQ(OK, out.rv);
+}
+
+namespace {
+
+// The KillerCallback will delete the transaction on error as part of the
+// callback.
+class KillerCallback : public TestCompletionCallbackBase {
+ public:
+  explicit KillerCallback(HttpNetworkTransaction* transaction)
+      : transaction_(transaction),
+        ALLOW_THIS_IN_INITIALIZER_LIST(callback_(
+            base::Bind(&KillerCallback::OnComplete, base::Unretained(this)))) {
+  }
+
+  virtual ~KillerCallback() {}
+
+  const CompletionCallback& callback() const { return callback_; }
+
+ private:
+  void OnComplete(int result) {
+    if (result < 0)
+      delete transaction_;
+
+    SetResult(result);
+  }
+
+  HttpNetworkTransaction* transaction_;
+  CompletionCallback callback_;
+};
+
+}  // namespace
+
+// Similar to ThreeGetsMaxConcurrrentDelete above, however, this test
+// closes the socket while we have a pending transaction waiting for
+// a pending stream creation.  http://crbug.com/52901
+TEST_P(SpdyNetworkTransactionSpdy2Test, ThreeGetsWithMaxConcurrentSocketClose) {
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> fin_body(ConstructSpdyBodyFrame(1, true));
+
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+
+  SettingsMap settings;
+  const size_t max_concurrent_streams = 1;
+  settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+  scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+
+  MockWrite writes[] = { CreateMockWrite(*req),
+    CreateMockWrite(*req2),
+  };
+  MockRead reads[] = {
+    CreateMockRead(*settings_frame, 1),
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    CreateMockRead(*fin_body),
+    CreateMockRead(*resp2, 7),
+    MockRead(ASYNC, ERR_CONNECTION_RESET, 0),  // Abort!
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  OrderedSocketData data_placeholder(NULL, 0, NULL, 0);
+
+  BoundNetLog log;
+  TransactionHelperResult out;
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+      BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  // We require placeholder data because three get requests are sent out, so
+  // there needs to be three sets of SSL connection data.
+  helper.AddData(&data_placeholder);
+  helper.AddData(&data_placeholder);
+  HttpNetworkTransaction trans1(helper.session());
+  HttpNetworkTransaction trans2(helper.session());
+  HttpNetworkTransaction* trans3(new HttpNetworkTransaction(helper.session()));
+
+  TestCompletionCallback callback1;
+  TestCompletionCallback callback2;
+  KillerCallback callback3(trans3);
+
+  HttpRequestInfo httpreq1 = CreateGetRequest();
+  HttpRequestInfo httpreq2 = CreateGetRequest();
+  HttpRequestInfo httpreq3 = CreateGetRequest();
+
+  out.rv = trans1.Start(&httpreq1, callback1.callback(), log);
+  ASSERT_EQ(out.rv, ERR_IO_PENDING);
+  // Run transaction 1 through quickly to force a read of our SETTINGS frame.
+  out.rv = callback1.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+
+  out.rv = trans2.Start(&httpreq2, callback2.callback(), log);
+  ASSERT_EQ(out.rv, ERR_IO_PENDING);
+  out.rv = trans3->Start(&httpreq3, callback3.callback(), log);
+  ASSERT_EQ(out.rv, ERR_IO_PENDING);
+  out.rv = callback3.WaitForResult();
+  ASSERT_EQ(ERR_ABORTED, out.rv);
+
+  EXPECT_EQ(6U, data.read_index());
+
+  const HttpResponseInfo* response1 = trans1.GetResponseInfo();
+  ASSERT_TRUE(response1 != NULL);
+  EXPECT_TRUE(response1->headers != NULL);
+  EXPECT_TRUE(response1->was_fetched_via_spdy);
+  out.status_line = response1->headers->GetStatusLine();
+  out.response_info = *response1;
+  out.rv = ReadTransaction(&trans1, &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+
+  const HttpResponseInfo* response2 = trans2.GetResponseInfo();
+  ASSERT_TRUE(response2 != NULL);
+  out.status_line = response2->headers->GetStatusLine();
+  out.response_info = *response2;
+  out.rv = ReadTransaction(&trans2, &out.response_data);
+  EXPECT_EQ(ERR_CONNECTION_RESET, out.rv);
+
+  helper.VerifyDataConsumed();
+}
+
+// Test that a simple PUT request works.
+TEST_P(SpdyNetworkTransactionSpdy2Test, Put) {
+  // Setup the request
+  HttpRequestInfo request;
+  request.method = "PUT";
+  request.url = GURL("http://www.google.com/");
+
+  const SpdyHeaderInfo kSynStartHeader = {
+    SYN_STREAM,             // Kind = Syn
+    1,                      // Stream ID
+    0,                      // Associated stream ID
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 2),  // Priority
+    CONTROL_FLAG_FIN,       // Control Flags
+    false,                  // Compressed
+    INVALID,                // Status
+    NULL,                   // Data
+    0,                      // Length
+    DATA_FLAG_NONE          // Data Flags
+  };
+  const char* const kPutHeaders[] = {
+    "method", "PUT",
+    "url", "/",
+    "host", "www.google.com",
+    "scheme", "http",
+    "version", "HTTP/1.1",
+    "content-length", "0"
+  };
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPacket(kSynStartHeader, NULL, 0,
+    kPutHeaders, arraysize(kPutHeaders) / 2));
+  MockWrite writes[] = {
+    CreateMockWrite(*req)
+  };
+
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  const SpdyHeaderInfo kSynReplyHeader = {
+    SYN_REPLY,              // Kind = SynReply
+    1,                      // Stream ID
+    0,                      // Associated stream ID
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 2),  // Priority
+    CONTROL_FLAG_NONE,      // Control Flags
+    false,                  // Compressed
+    INVALID,                // Status
+    NULL,                   // Data
+    0,                      // Length
+    DATA_FLAG_NONE          // Data Flags
+  };
+  static const char* const kStandardGetHeaders[] = {
+    "status", "200",
+    "version", "HTTP/1.1"
+    "content-length", "1234"
+  };
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPacket(kSynReplyHeader,
+      NULL, 0, kStandardGetHeaders, arraysize(kStandardGetHeaders) / 2));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(request,
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+}
+
+// Test that a simple HEAD request works.
+TEST_P(SpdyNetworkTransactionSpdy2Test, Head) {
+  // Setup the request
+  HttpRequestInfo request;
+  request.method = "HEAD";
+  request.url = GURL("http://www.google.com/");
+
+  const SpdyHeaderInfo kSynStartHeader = {
+    SYN_STREAM,             // Kind = Syn
+    1,                      // Stream ID
+    0,                      // Associated stream ID
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 2),  // Priority
+    CONTROL_FLAG_FIN,       // Control Flags
+    false,                  // Compressed
+    INVALID,                // Status
+    NULL,                   // Data
+    0,                      // Length
+    DATA_FLAG_NONE          // Data Flags
+  };
+  const char* const kHeadHeaders[] = {
+    "method", "HEAD",
+    "url", "/",
+    "host", "www.google.com",
+    "scheme", "http",
+    "version", "HTTP/1.1",
+    "content-length", "0"
+  };
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPacket(kSynStartHeader, NULL, 0,
+    kHeadHeaders, arraysize(kHeadHeaders) / 2));
+  MockWrite writes[] = {
+    CreateMockWrite(*req)
+  };
+
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  const SpdyHeaderInfo kSynReplyHeader = {
+    SYN_REPLY,              // Kind = SynReply
+    1,                      // Stream ID
+    0,                      // Associated stream ID
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 2),  // Priority
+    CONTROL_FLAG_NONE,      // Control Flags
+    false,                  // Compressed
+    INVALID,                // Status
+    NULL,                   // Data
+    0,                      // Length
+    DATA_FLAG_NONE          // Data Flags
+  };
+  static const char* const kStandardGetHeaders[] = {
+    "status", "200",
+    "version", "HTTP/1.1"
+    "content-length", "1234"
+  };
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPacket(kSynReplyHeader,
+      NULL, 0, kStandardGetHeaders, arraysize(kStandardGetHeaders) / 2));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(request,
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+}
+
+// Test that a simple POST works.
+TEST_P(SpdyNetworkTransactionSpdy2Test, Post) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPost(kUploadDataSize, NULL, 0));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*body),  // POST upload frame
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(2, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreatePostRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+}
+
+// Test that a POST with a file works.
+TEST_P(SpdyNetworkTransactionSpdy2Test, FilePost) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPost(kUploadDataSize, NULL, 0));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*body),  // POST upload frame
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(2, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateFilePostRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+}
+
+// Test that a complex POST works.
+TEST_P(SpdyNetworkTransactionSpdy2Test, ComplexPost) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPost(kUploadDataSize, NULL, 0));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*body),  // POST upload frame
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(2, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateComplexPostRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+}
+
+// Test that a chunked POST works.
+TEST_P(SpdyNetworkTransactionSpdy2Test, ChunkedPost) {
+  set_merge_chunks(false);
+
+  scoped_ptr<SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0));
+  scoped_ptr<SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> chunk2(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*chunk1),
+    CreateMockWrite(*chunk2),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*chunk1),
+    CreateMockRead(*chunk2),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(2, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateChunkedPostRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+}
+
+// Test that a POST without any post data works.
+TEST_P(SpdyNetworkTransactionSpdy2Test, NullPost) {
+  // Setup the request
+  HttpRequestInfo request;
+  request.method = "POST";
+  request.url = GURL("http://www.google.com/");
+  // Create an empty UploadData.
+  request.upload_data_stream = NULL;
+
+  // When request.upload_data_stream is NULL for post, content-length is
+  // expected to be 0.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPost(0, NULL, 0));
+  // Set the FIN bit since there will be no body.
+  req->set_flags(CONTROL_FLAG_FIN);
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(request,
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+}
+
+// Test that a simple POST works.
+TEST_P(SpdyNetworkTransactionSpdy2Test, EmptyPost) {
+  // Create an empty UploadDataStream.
+  ScopedVector<UploadElementReader> element_readers;
+  UploadDataStream stream(&element_readers, 0);
+
+  // Setup the request
+  HttpRequestInfo request;
+  request.method = "POST";
+  request.url = GURL("http://www.google.com/");
+  request.upload_data_stream = &stream;
+
+  const uint64 kContentLength = 0;
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPost(kContentLength, NULL, 0));
+  // Set the FIN bit since there will be no body.
+  req->set_flags(CONTROL_FLAG_FIN);
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads), writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(request, BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+}
+
+// While we're doing a post, the server sends back a SYN_REPLY.
+TEST_P(SpdyNetworkTransactionSpdy2Test, PostWithEarlySynReply) {
+  static const char upload[] = { "hello!" };
+  ScopedVector<UploadElementReader> element_readers;
+  element_readers.push_back(
+      new UploadBytesElementReader(upload, sizeof(upload)));
+  UploadDataStream stream(&element_readers, 0);
+
+  // Setup the request
+  HttpRequestInfo request;
+  request.method = "POST";
+  request.url = GURL("http://www.google.com/");
+  request.upload_data_stream = &stream;
+
+  scoped_ptr<SpdyFrame> stream_reply(ConstructSpdyPostSynReply(NULL, 0));
+  scoped_ptr<SpdyFrame> stream_body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*stream_reply, 1),
+    MockRead(ASYNC, 0, 3)  // EOF
+  };
+
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPost(kUploadDataSize, NULL, 0));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*req, 0),
+    CreateMockWrite(*body, 2),
+  };
+
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreatePostRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.SetDeterministic();
+  helper.RunPreTestSetup();
+  helper.AddDeterministicData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreatePostRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  data.RunFor(2);
+  rv = callback.WaitForResult();
+  EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, rv);
+  data.RunFor(1);
+}
+
+// The client upon cancellation tries to send a RST_STREAM frame. The mock
+// socket causes the TCP write to return zero. This test checks that the client
+// tries to queue up the RST_STREAM frame again.
+TEST_P(SpdyNetworkTransactionSpdy2Test, SocketWriteReturnsZero) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> rst(
+      ConstructSpdyRstStream(1, CANCEL));
+  MockWrite writes[] = {
+    CreateMockWrite(*req.get(), 0, SYNCHRONOUS),
+    MockWrite(SYNCHRONOUS, 0, 0, 2),
+    CreateMockWrite(*rst.get(), 3, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp.get(), 1, ASYNC),
+    MockRead(ASYNC, 0, 0, 4)  // EOF
+  };
+
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.SetDeterministic();
+  helper.RunPreTestSetup();
+  helper.AddDeterministicData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  data.SetStop(2);
+  data.Run();
+  helper.ResetTrans();
+  data.SetStop(20);
+  data.Run();
+
+  helper.VerifyDataConsumed();
+}
+
+// Test that the transaction doesn't crash when we don't have a reply.
+TEST_P(SpdyNetworkTransactionSpdy2Test, ResponseWithoutSynReply) {
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads), NULL, 0);
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(ERR_SYN_REPLY_NOT_RECEIVED, out.rv);
+}
+
+// Test that the transaction doesn't crash when we get two replies on the same
+// stream ID. See http://crbug.com/45639.
+TEST_P(SpdyNetworkTransactionSpdy2Test, ResponseWithTwoSynReplies) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  rv = callback.WaitForResult();
+  EXPECT_EQ(OK, rv);
+
+  const HttpResponseInfo* response = trans->GetResponseInfo();
+  ASSERT_TRUE(response != NULL);
+  EXPECT_TRUE(response->headers != NULL);
+  EXPECT_TRUE(response->was_fetched_via_spdy);
+  std::string response_data;
+  rv = ReadTransaction(trans, &response_data);
+  EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, rv);
+
+  helper.VerifyDataConsumed();
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ResetReplyWithTransferEncoding) {
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(1, PROTOCOL_ERROR));
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*rst),
+  };
+
+  const char* const headers[] = {
+    "transfer-encoding", "chuncked"
+  };
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(headers, 1, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+
+  helper.session()->spdy_session_pool()->CloseAllSessions();
+  helper.VerifyDataConsumed();
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ResetPushWithTransferEncoding) {
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(2, PROTOCOL_ERROR));
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*rst),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  const char* const headers[] = {"url", "http://www.google.com/1",
+                                 "transfer-encoding", "chunked"};
+  scoped_ptr<SpdyFrame> push(ConstructSpdyPush(headers, arraysize(headers) / 2,
+                                               2, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*push),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+
+  helper.session()->spdy_session_pool()->CloseAllSessions();
+  helper.VerifyDataConsumed();
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, CancelledTransaction) {
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    // This following read isn't used by the test, except during the
+    // RunUntilIdle() call at the end since the SpdySession survives the
+    // HttpNetworkTransaction and still tries to continue Read()'ing.  Any
+    // MockRead will do here.
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  StaticSocketDataProvider data(reads, arraysize(reads),
+                                writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  helper.ResetTrans();  // Cancel the transaction.
+
+  // Flush the MessageLoop while the SpdySessionDependencies (in particular, the
+  // MockClientSocketFactory) are still alive.
+  MessageLoop::current()->RunUntilIdle();
+  helper.VerifyDataNotConsumed();
+}
+
+// Verify that the client sends a Rst Frame upon cancelling the stream.
+TEST_P(SpdyNetworkTransactionSpdy2Test, CancelledTransactionSendRst) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> rst(
+      ConstructSpdyRstStream(1, CANCEL));
+  MockWrite writes[] = {
+    CreateMockWrite(*req, 0, SYNCHRONOUS),
+    CreateMockWrite(*rst, 2, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 0, 3)  // EOF
+  };
+
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(),
+                                     GetParam(), NULL);
+  helper.SetDeterministic();
+  helper.RunPreTestSetup();
+  helper.AddDeterministicData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  data.SetStop(2);
+  data.Run();
+  helper.ResetTrans();
+  data.SetStop(20);
+  data.Run();
+
+  helper.VerifyDataConsumed();
+}
+
+// Verify that the client can correctly deal with the user callback attempting
+// to start another transaction on a session that is closing down. See
+// http://crbug.com/47455
+TEST_P(SpdyNetworkTransactionSpdy2Test, StartTransactionOnReadCallback) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+  MockWrite writes2[] = { CreateMockWrite(*req) };
+
+  // The indicated length of this packet is longer than its actual length. When
+  // the session receives an empty packet after this one, it shuts down the
+  // session, and calls the read callback with the incomplete data.
+  const uint8 kGetBodyFrame2[] = {
+    0x00, 0x00, 0x00, 0x01,
+    0x01, 0x00, 0x00, 0x07,
+    'h', 'e', 'l', 'l', 'o', '!',
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 2),
+    MockRead(ASYNC, ERR_IO_PENDING, 3),  // Force a pause
+    MockRead(ASYNC, reinterpret_cast<const char*>(kGetBodyFrame2),
+             arraysize(kGetBodyFrame2), 4),
+    MockRead(ASYNC, ERR_IO_PENDING, 5),  // Force a pause
+    MockRead(ASYNC, 0, 0, 6),  // EOF
+  };
+  MockRead reads2[] = {
+    CreateMockRead(*resp, 2),
+    MockRead(ASYNC, 0, 0, 3),  // EOF
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  DelayedSocketData data2(1, reads2, arraysize(reads2),
+                          writes2, arraysize(writes2));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  helper.AddData(&data2);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  // Start the transaction with basic parameters.
+  TestCompletionCallback callback;
+  int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  rv = callback.WaitForResult();
+
+  const int kSize = 3000;
+  scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize));
+  rv = trans->Read(
+      buf, kSize,
+      base::Bind(&SpdyNetworkTransactionSpdy2Test::StartTransactionCallback,
+                 helper.session()));
+  // This forces an err_IO_pending, which sets the callback.
+  data.CompleteRead();
+  // This finishes the read.
+  data.CompleteRead();
+  helper.VerifyDataConsumed();
+}
+
+// Verify that the client can correctly deal with the user callback deleting the
+// transaction. Failures will usually be valgrind errors. See
+// http://crbug.com/46925
+TEST_P(SpdyNetworkTransactionSpdy2Test, DeleteSessionOnReadCallback) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp.get(), 2),
+    MockRead(ASYNC, ERR_IO_PENDING, 3),  // Force a pause
+    CreateMockRead(*body.get(), 4),
+    MockRead(ASYNC, 0, 0, 5),  // EOF
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  // Start the transaction with basic parameters.
+  TestCompletionCallback callback;
+  int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  rv = callback.WaitForResult();
+
+  // Setup a user callback which will delete the session, and clear out the
+  // memory holding the stream object. Note that the callback deletes trans.
+  const int kSize = 3000;
+  scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize));
+  rv = trans->Read(
+      buf, kSize,
+      base::Bind(&SpdyNetworkTransactionSpdy2Test::DeleteSessionCallback,
+                 base::Unretained(&helper)));
+  ASSERT_EQ(ERR_IO_PENDING, rv);
+  data.CompleteRead();
+
+  // Finish running rest of tasks.
+  MessageLoop::current()->RunUntilIdle();
+  helper.VerifyDataConsumed();
+}
+
+// Send a spdy request to www.google.com that gets redirected to www.foo.com.
+TEST_P(SpdyNetworkTransactionSpdy2Test, RedirectGetRequest) {
+  // These are headers which the net::URLRequest tacks on.
+  const char* const kExtraHeaders[] = {
+    "accept-encoding",
+    "gzip,deflate",
+  };
+  const SpdyHeaderInfo kSynStartHeader = MakeSpdyHeader(SYN_STREAM);
+  const char* const kStandardGetHeaders[] = {
+    "host",
+    "www.google.com",
+    "method",
+    "GET",
+    "scheme",
+    "http",
+    "url",
+    "/",
+    "user-agent",
+    "",
+    "version",
+    "HTTP/1.1"
+  };
+  const char* const kStandardGetHeaders2[] = {
+    "host",
+    "www.foo.com",
+    "method",
+    "GET",
+    "scheme",
+    "http",
+    "url",
+    "/index.php",
+    "user-agent",
+    "",
+    "version",
+    "HTTP/1.1"
+  };
+
+  // Setup writes/reads to www.google.com
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPacket(
+      kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders) / 2,
+      kStandardGetHeaders, arraysize(kStandardGetHeaders) / 2));
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyPacket(
+      kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders) / 2,
+      kStandardGetHeaders2, arraysize(kStandardGetHeaders2) / 2));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReplyRedirect(1));
+  MockWrite writes[] = {
+    CreateMockWrite(*req, 1),
+  };
+  MockRead reads[] = {
+    CreateMockRead(*resp, 2),
+    MockRead(ASYNC, 0, 0, 3)  // EOF
+  };
+
+  // Setup writes/reads to www.foo.com
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes2[] = {
+    CreateMockWrite(*req2, 1),
+  };
+  MockRead reads2[] = {
+    CreateMockRead(*resp2, 2),
+    CreateMockRead(*body2, 3),
+    MockRead(ASYNC, 0, 0, 4)  // EOF
+  };
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  OrderedSocketData data2(reads2, arraysize(reads2),
+                          writes2, arraysize(writes2));
+
+  // TODO(erikchen): Make test support SPDYSSL, SPDYNPN
+  HttpStreamFactory::set_force_spdy_over_ssl(false);
+  HttpStreamFactory::set_force_spdy_always(true);
+  TestDelegate d;
+  {
+    SpdyURLRequestContext spdy_url_request_context;
+    net::URLRequest r(
+        GURL("http://www.google.com/"), &d, &spdy_url_request_context);
+    spdy_url_request_context.socket_factory().
+        AddSocketDataProvider(&data);
+    spdy_url_request_context.socket_factory().
+        AddSocketDataProvider(&data2);
+
+    d.set_quit_on_redirect(true);
+    r.Start();
+    MessageLoop::current()->Run();
+
+    EXPECT_EQ(1, d.received_redirect_count());
+
+    r.FollowDeferredRedirect();
+    MessageLoop::current()->Run();
+    EXPECT_EQ(1, d.response_started_count());
+    EXPECT_FALSE(d.received_data_before_response());
+    EXPECT_EQ(net::URLRequestStatus::SUCCESS, r.status().status());
+    std::string contents("hello!");
+    EXPECT_EQ(contents, d.data_received());
+  }
+  EXPECT_TRUE(data.at_read_eof());
+  EXPECT_TRUE(data.at_write_eof());
+  EXPECT_TRUE(data2.at_read_eof());
+  EXPECT_TRUE(data2.at_write_eof());
+}
+
+// Detect response with upper case headers and reset the stream.
+TEST_P(SpdyNetworkTransactionSpdy2Test, UpperCaseHeaders) {
+  scoped_ptr<SpdyFrame>
+      syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      rst(ConstructSpdyRstStream(1, PROTOCOL_ERROR));
+  MockWrite writes[] = {
+    CreateMockWrite(*syn, 0),
+    CreateMockWrite(*rst, 2),
+  };
+
+  const char* const kExtraHeaders[] = {"X-UpperCase", "yes"};
+  scoped_ptr<SpdyFrame>
+      reply(ConstructSpdyGetSynReply(kExtraHeaders, 1, 1));
+  MockRead reads[] = {
+    CreateMockRead(*reply, 1),
+    MockRead(ASYNC, ERR_IO_PENDING, 3),  // Force a pause
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+}
+
+// Detect response with upper case headers in a HEADERS frame and reset the
+// stream.
+TEST_P(SpdyNetworkTransactionSpdy2Test, UpperCaseHeadersInHeadersFrame) {
+  scoped_ptr<SpdyFrame>
+      syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      rst(ConstructSpdyRstStream(1, PROTOCOL_ERROR));
+  MockWrite writes[] = {
+    CreateMockWrite(*syn, 0),
+    CreateMockWrite(*rst, 2),
+  };
+
+  static const char* const kInitialHeaders[] = {
+    "status", "200 OK",
+    "version", "HTTP/1.1"
+  };
+  static const char* const kLateHeaders[] = {
+    "X-UpperCase", "yes",
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyControlFrame(kInitialHeaders,
+                                              arraysize(kInitialHeaders) / 2,
+                                              false,
+                                              1,
+                                              LOWEST,
+                                              SYN_REPLY,
+                                              CONTROL_FLAG_NONE,
+                                              NULL,
+                                              0,
+                                              0));
+  scoped_ptr<SpdyFrame>
+      stream1_headers(ConstructSpdyControlFrame(kLateHeaders,
+                                                arraysize(kLateHeaders) / 2,
+                                                false,
+                                                1,
+                                                LOWEST,
+                                                HEADERS,
+                                                CONTROL_FLAG_NONE,
+                                                NULL,
+                                                0,
+                                                0));
+  scoped_ptr<SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply),
+    CreateMockRead(*stream1_headers),
+    CreateMockRead(*stream1_body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+}
+
+// Detect push stream with upper case headers and reset the stream.
+TEST_P(SpdyNetworkTransactionSpdy2Test, UpperCaseHeadersOnPush) {
+  scoped_ptr<SpdyFrame>
+      syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      rst(ConstructSpdyRstStream(2, PROTOCOL_ERROR));
+  MockWrite writes[] = {
+    CreateMockWrite(*syn, 0),
+    CreateMockWrite(*rst, 2),
+  };
+
+  scoped_ptr<SpdyFrame>
+      reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  const char* const extra_headers[] = {"X-UpperCase", "yes"};
+  scoped_ptr<SpdyFrame>
+      push(ConstructSpdyPush(extra_headers, 1, 2, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*reply, 1),
+    CreateMockRead(*push, 1),
+    CreateMockRead(*body, 1),
+    MockRead(ASYNC, ERR_IO_PENDING, 3),  // Force a pause
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+}
+
+// Send a spdy request to www.google.com. Get a pushed stream that redirects to
+// www.foo.com.
+TEST_P(SpdyNetworkTransactionSpdy2Test, RedirectServerPush) {
+  // These are headers which the net::URLRequest tacks on.
+  const char* const kExtraHeaders[] = {
+    "accept-encoding",
+    "gzip,deflate",
+  };
+  const SpdyHeaderInfo kSynStartHeader = MakeSpdyHeader(SYN_STREAM);
+  const char* const kStandardGetHeaders[] = {
+    "host",
+    "www.google.com",
+    "method",
+    "GET",
+    "scheme",
+    "http",
+    "url",
+    "/",
+    "user-agent",
+    "",
+    "version",
+    "HTTP/1.1"
+  };
+
+  // Setup writes/reads to www.google.com
+  scoped_ptr<SpdyFrame> req(
+      ConstructSpdyPacket(kSynStartHeader,
+                          kExtraHeaders,
+                          arraysize(kExtraHeaders) / 2,
+                          kStandardGetHeaders,
+                          arraysize(kStandardGetHeaders) / 2));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> rep(
+      ConstructSpdyPush(NULL,
+                        0,
+                        2,
+                        1,
+                        "http://www.google.com/foo.dat",
+                        "301 Moved Permanently",
+                        "http://www.foo.com/index.php"));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(2, CANCEL));
+  MockWrite writes[] = {
+    CreateMockWrite(*req, 1),
+    CreateMockWrite(*rst, 6),
+  };
+  MockRead reads[] = {
+    CreateMockRead(*resp, 2),
+    CreateMockRead(*rep, 3),
+    CreateMockRead(*body, 4),
+    MockRead(ASYNC, ERR_IO_PENDING, 5),  // Force a pause
+    MockRead(ASYNC, 0, 0, 7)  // EOF
+  };
+
+  // Setup writes/reads to www.foo.com
+  const char* const kStandardGetHeaders2[] = {
+    "host",
+    "www.foo.com",
+    "method",
+    "GET",
+    "scheme",
+    "http",
+    "url",
+    "/index.php",
+    "user-agent",
+    "",
+    "version",
+    "HTTP/1.1"
+  };
+  scoped_ptr<SpdyFrame> req2(
+      ConstructSpdyPacket(kSynStartHeader,
+                          kExtraHeaders,
+                          arraysize(kExtraHeaders) / 2,
+                          kStandardGetHeaders2,
+                          arraysize(kStandardGetHeaders2) / 2));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes2[] = {
+    CreateMockWrite(*req2, 1),
+  };
+  MockRead reads2[] = {
+    CreateMockRead(*resp2, 2),
+    CreateMockRead(*body2, 3),
+    MockRead(ASYNC, 0, 0, 5)  // EOF
+  };
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  OrderedSocketData data2(reads2, arraysize(reads2),
+                          writes2, arraysize(writes2));
+
+  // TODO(erikchen): Make test support SPDYSSL, SPDYNPN
+  HttpStreamFactory::set_force_spdy_over_ssl(false);
+  HttpStreamFactory::set_force_spdy_always(true);
+  TestDelegate d;
+  TestDelegate d2;
+  SpdyURLRequestContext spdy_url_request_context;
+  {
+    net::URLRequest r(
+        GURL("http://www.google.com/"), &d, &spdy_url_request_context);
+    spdy_url_request_context.socket_factory().
+        AddSocketDataProvider(&data);
+
+    r.Start();
+    MessageLoop::current()->Run();
+
+    EXPECT_EQ(0, d.received_redirect_count());
+    std::string contents("hello!");
+    EXPECT_EQ(contents, d.data_received());
+
+    net::URLRequest r2(
+        GURL("http://www.google.com/foo.dat"), &d2, &spdy_url_request_context);
+    spdy_url_request_context.socket_factory().
+        AddSocketDataProvider(&data2);
+
+    d2.set_quit_on_redirect(true);
+    r2.Start();
+    MessageLoop::current()->Run();
+    EXPECT_EQ(1, d2.received_redirect_count());
+
+    r2.FollowDeferredRedirect();
+    MessageLoop::current()->Run();
+    EXPECT_EQ(1, d2.response_started_count());
+    EXPECT_FALSE(d2.received_data_before_response());
+    EXPECT_EQ(net::URLRequestStatus::SUCCESS, r2.status().status());
+    std::string contents2("hello!");
+    EXPECT_EQ(contents2, d2.data_received());
+  }
+  data.CompleteRead();
+  data2.CompleteRead();
+  EXPECT_TRUE(data.at_read_eof());
+  EXPECT_TRUE(data.at_write_eof());
+  EXPECT_TRUE(data2.at_read_eof());
+  EXPECT_TRUE(data2.at_write_eof());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushSingleDataFrame) {
+  static const unsigned char kPushBodyFrame[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x06,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyPush(NULL,
+                                    0,
+                                    2,
+                                    1,
+                                    "http://www.google.com/foo.dat"));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 2),
+    CreateMockRead(*stream2_syn, 3),
+    CreateMockRead(*stream1_body, 4, SYNCHRONOUS),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+             arraysize(kPushBodyFrame), 5),
+    MockRead(ASYNC, ERR_IO_PENDING, 6),  // Force a pause
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  std::string expected_push_result("pushed");
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  RunServerPushTest(&data,
+                    &response,
+                    &response2,
+                    expected_push_result);
+
+  // Verify the SYN_REPLY.
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  // Verify the pushed stream.
+  EXPECT_TRUE(response2.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushBeforeSynReply) {
+  static const unsigned char kPushBodyFrame[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x06,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyPush(NULL,
+                                    0,
+                                    2,
+                                    1,
+                                    "http://www.google.com/foo.dat"));
+  MockRead reads[] = {
+    CreateMockRead(*stream2_syn, 2),
+    CreateMockRead(*stream1_reply, 3),
+    CreateMockRead(*stream1_body, 4, SYNCHRONOUS),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+             arraysize(kPushBodyFrame), 5),
+    MockRead(ASYNC, ERR_IO_PENDING, 6),  // Force a pause
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  std::string expected_push_result("pushed");
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  RunServerPushTest(&data,
+                    &response,
+                    &response2,
+                    expected_push_result);
+
+  // Verify the SYN_REPLY.
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  // Verify the pushed stream.
+  EXPECT_TRUE(response2.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushSingleDataFrame2) {
+  static const unsigned char kPushBodyFrame[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x06,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyPush(NULL,
+                                    0,
+                                    2,
+                                    1,
+                                    "http://www.google.com/foo.dat"));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 2),
+    CreateMockRead(*stream2_syn, 3),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+             arraysize(kPushBodyFrame), 5),
+    CreateMockRead(*stream1_body, 4, SYNCHRONOUS),
+    MockRead(ASYNC, ERR_IO_PENDING, 6),  // Force a pause
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  std::string expected_push_result("pushed");
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  RunServerPushTest(&data,
+                    &response,
+                    &response2,
+                    expected_push_result);
+
+  // Verify the SYN_REPLY.
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  // Verify the pushed stream.
+  EXPECT_TRUE(response2.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushServerAborted) {
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyPush(NULL,
+                                    0,
+                                    2,
+                                    1,
+                                    "http://www.google.com/foo.dat"));
+  scoped_ptr<SpdyFrame>
+    stream2_rst(ConstructSpdyRstStream(2, PROTOCOL_ERROR));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 2),
+    CreateMockRead(*stream2_syn, 3),
+    CreateMockRead(*stream2_rst, 4),
+    CreateMockRead(*stream1_body, 5, SYNCHRONOUS),
+    MockRead(ASYNC, ERR_IO_PENDING, 6),  // Force a pause
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  // Start the transaction with basic parameters.
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  rv = callback.WaitForResult();
+  EXPECT_EQ(OK, rv);
+
+  // Verify that we consumed all test data.
+  EXPECT_TRUE(data.at_read_eof()) << "Read count: "
+                                   << data.read_count()
+                                   << " Read index: "
+                                   << data.read_index();
+  EXPECT_TRUE(data.at_write_eof()) << "Write count: "
+                                    << data.write_count()
+                                    << " Write index: "
+                                    << data.write_index();
+
+  // Verify the SYN_REPLY.
+  HttpResponseInfo response = *trans->GetResponseInfo();
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushDuplicate) {
+  // Verify that we don't leak streams and that we properly send a reset
+  // if the server pushes the same stream twice.
+  static const unsigned char kPushBodyFrame[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x06,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  scoped_ptr<SpdyFrame>
+      stream3_rst(ConstructSpdyRstStream(4, PROTOCOL_ERROR));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+    CreateMockWrite(*stream3_rst, 5),
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyPush(NULL,
+                                    0,
+                                    2,
+                                    1,
+                                    "http://www.google.com/foo.dat"));
+  scoped_ptr<SpdyFrame>
+      stream3_syn(ConstructSpdyPush(NULL,
+                                    0,
+                                    4,
+                                    1,
+                                    "http://www.google.com/foo.dat"));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 2),
+    CreateMockRead(*stream2_syn, 3),
+    CreateMockRead(*stream3_syn, 4),
+    CreateMockRead(*stream1_body, 6, SYNCHRONOUS),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+             arraysize(kPushBodyFrame), 7),
+    MockRead(ASYNC, ERR_IO_PENDING, 8),  // Force a pause
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  std::string expected_push_result("pushed");
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  RunServerPushTest(&data,
+                    &response,
+                    &response2,
+                    expected_push_result);
+
+  // Verify the SYN_REPLY.
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  // Verify the pushed stream.
+  EXPECT_TRUE(response2.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushMultipleDataFrame) {
+  static const unsigned char kPushBodyFrame1[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x1F,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+  static const char kPushBodyFrame2[] = " my darling";
+  static const char kPushBodyFrame3[] = " hello";
+  static const char kPushBodyFrame4[] = " my baby";
+
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyPush(NULL,
+                                    0,
+                                    2,
+                                    1,
+                                    "http://www.google.com/foo.dat"));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 2),
+    CreateMockRead(*stream2_syn, 3),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame1),
+             arraysize(kPushBodyFrame1), 4),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame2),
+             arraysize(kPushBodyFrame2) - 1, 5),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame3),
+             arraysize(kPushBodyFrame3) - 1, 6),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame4),
+             arraysize(kPushBodyFrame4) - 1, 7),
+    CreateMockRead(*stream1_body, 8, SYNCHRONOUS),
+    MockRead(ASYNC, ERR_IO_PENDING, 9),  // Force a pause
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  std::string expected_push_result("pushed my darling hello my baby");
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  RunServerPushTest(&data,
+                    &response,
+                    &response2,
+                    expected_push_result);
+
+  // Verify the SYN_REPLY.
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  // Verify the pushed stream.
+  EXPECT_TRUE(response2.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test,
+       ServerPushMultipleDataFrameInterrupted) {
+  static const unsigned char kPushBodyFrame1[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x1F,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+  static const char kPushBodyFrame2[] = " my darling";
+  static const char kPushBodyFrame3[] = " hello";
+  static const char kPushBodyFrame4[] = " my baby";
+
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyPush(NULL,
+                                    0,
+                                    2,
+                                    1,
+                                    "http://www.google.com/foo.dat"));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 2),
+    CreateMockRead(*stream2_syn, 3),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame1),
+             arraysize(kPushBodyFrame1), 4),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame2),
+             arraysize(kPushBodyFrame2) - 1, 5),
+    MockRead(ASYNC, ERR_IO_PENDING, 6),  // Force a pause
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame3),
+             arraysize(kPushBodyFrame3) - 1, 7),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame4),
+             arraysize(kPushBodyFrame4) - 1, 8),
+    CreateMockRead(*stream1_body.get(), 9, SYNCHRONOUS),
+    MockRead(ASYNC, ERR_IO_PENDING, 10)  // Force a pause.
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  std::string expected_push_result("pushed my darling hello my baby");
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  RunServerPushTest(&data,
+                    &response,
+                    &response2,
+                    expected_push_result);
+
+  // Verify the SYN_REPLY.
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  // Verify the pushed stream.
+  EXPECT_TRUE(response2.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushInvalidAssociatedStreamID0) {
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  scoped_ptr<SpdyFrame>
+      stream2_rst(ConstructSpdyRstStream(2, REFUSED_STREAM));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+    CreateMockWrite(*stream2_rst, 4),
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyPush(NULL,
+                                    0,
+                                    2,
+                                    0,
+                                    "http://www.google.com/foo.dat"));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 2),
+    CreateMockRead(*stream2_syn, 3),
+    CreateMockRead(*stream1_body, 4),
+    MockRead(ASYNC, ERR_IO_PENDING, 5)  // Force a pause
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  // Start the transaction with basic parameters.
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  rv = callback.WaitForResult();
+  EXPECT_EQ(OK, rv);
+
+  // Verify that we consumed all test data.
+  EXPECT_TRUE(data.at_read_eof()) << "Read count: "
+                                   << data.read_count()
+                                   << " Read index: "
+                                   << data.read_index();
+  EXPECT_TRUE(data.at_write_eof()) << "Write count: "
+                                    << data.write_count()
+                                    << " Write index: "
+                                    << data.write_index();
+
+  // Verify the SYN_REPLY.
+  HttpResponseInfo response = *trans->GetResponseInfo();
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushInvalidAssociatedStreamID9) {
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  scoped_ptr<SpdyFrame>
+      stream2_rst(ConstructSpdyRstStream(2, INVALID_STREAM));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+    CreateMockWrite(*stream2_rst, 4),
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyPush(NULL,
+                                    0,
+                                    2,
+                                    9,
+                                    "http://www.google.com/foo.dat"));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 2),
+    CreateMockRead(*stream2_syn, 3),
+    CreateMockRead(*stream1_body, 4),
+    MockRead(ASYNC, ERR_IO_PENDING, 5),  // Force a pause
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  // Start the transaction with basic parameters.
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  rv = callback.WaitForResult();
+  EXPECT_EQ(OK, rv);
+
+  // Verify that we consumed all test data.
+  EXPECT_TRUE(data.at_read_eof()) << "Read count: "
+                                   << data.read_count()
+                                   << " Read index: "
+                                   << data.read_index();
+  EXPECT_TRUE(data.at_write_eof()) << "Write count: "
+                                    << data.write_count()
+                                    << " Write index: "
+                                    << data.write_index();
+
+  // Verify the SYN_REPLY.
+  HttpResponseInfo response = *trans->GetResponseInfo();
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushNoURL) {
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  scoped_ptr<SpdyFrame>
+      stream2_rst(ConstructSpdyRstStream(2, PROTOCOL_ERROR));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+    CreateMockWrite(*stream2_rst, 4),
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyPush(NULL, 0, 2, 1));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 2),
+    CreateMockRead(*stream2_syn, 3),
+    CreateMockRead(*stream1_body, 4),
+    MockRead(ASYNC, ERR_IO_PENDING, 5)  // Force a pause
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  // Start the transaction with basic parameters.
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  rv = callback.WaitForResult();
+  EXPECT_EQ(OK, rv);
+  // Verify that we consumed all test data.
+  EXPECT_TRUE(data.at_read_eof()) << "Read count: "
+                                   << data.read_count()
+                                   << " Read index: "
+                                   << data.read_index();
+  EXPECT_TRUE(data.at_write_eof()) << "Write count: "
+                                    << data.write_count()
+                                    << " Write index: "
+                                    << data.write_index();
+
+  // Verify the SYN_REPLY.
+  HttpResponseInfo response = *trans->GetResponseInfo();
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+// Verify that various SynReply headers parse correctly through the
+// HTTP layer.
+TEST_P(SpdyNetworkTransactionSpdy2Test, SynReplyHeaders) {
+  struct SynReplyHeadersTests {
+    int num_headers;
+    const char* extra_headers[5];
+    const char* expected_headers;
+  } test_cases[] = {
+    // This uses a multi-valued cookie header.
+    { 2,
+      { "cookie", "val1",
+        "cookie", "val2",  // will get appended separated by NULL
+        NULL
+      },
+      "cookie: val1\n"
+      "cookie: val2\n"
+      "hello: bye\n"
+      "status: 200\n"
+      "version: HTTP/1.1\n"
+    },
+    // This is the minimalist set of headers.
+    { 0,
+      { NULL },
+      "hello: bye\n"
+      "status: 200\n"
+      "version: HTTP/1.1\n"
+    },
+    // Headers with a comma separated list.
+    { 1,
+      { "cookie", "val1,val2",
+        NULL
+      },
+      "cookie: val1,val2\n"
+      "hello: bye\n"
+      "status: 200\n"
+      "version: HTTP/1.1\n"
+    }
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+    scoped_ptr<SpdyFrame> req(
+        ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+    MockWrite writes[] = { CreateMockWrite(*req) };
+
+    scoped_ptr<SpdyFrame> resp(
+        ConstructSpdyGetSynReply(test_cases[i].extra_headers,
+                                 test_cases[i].num_headers,
+                                 1));
+    scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+    MockRead reads[] = {
+      CreateMockRead(*resp),
+      CreateMockRead(*body),
+      MockRead(ASYNC, 0, 0)  // EOF
+    };
+
+    DelayedSocketData data(1, reads, arraysize(reads),
+                           writes, arraysize(writes));
+    NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                       BoundNetLog(), GetParam(), NULL);
+    helper.RunToCompletion(&data);
+    TransactionHelperResult out = helper.output();
+
+    EXPECT_EQ(OK, out.rv);
+    EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+    EXPECT_EQ("hello!", out.response_data);
+
+    scoped_refptr<HttpResponseHeaders> headers = out.response_info.headers;
+    EXPECT_TRUE(headers.get() != NULL);
+    void* iter = NULL;
+    std::string name, value, lines;
+    while (headers->EnumerateHeaderLines(&iter, &name, &value)) {
+      lines.append(name);
+      lines.append(": ");
+      lines.append(value);
+      lines.append("\n");
+    }
+    EXPECT_EQ(std::string(test_cases[i].expected_headers), lines);
+  }
+}
+
+// Verify that various SynReply headers parse vary fields correctly
+// through the HTTP layer, and the response matches the request.
+TEST_P(SpdyNetworkTransactionSpdy2Test, SynReplyHeadersVary) {
+  static const SpdyHeaderInfo syn_reply_info = {
+    SYN_REPLY,                              // Syn Reply
+    1,                                      // Stream ID
+    0,                                      // Associated Stream ID
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 2),  // Priority
+    CONTROL_FLAG_NONE,                      // Control Flags
+    false,                                  // Compressed
+    INVALID,                                // Status
+    NULL,                                   // Data
+    0,                                      // Data Length
+    DATA_FLAG_NONE                          // Data Flags
+  };
+  // Modify the following data to change/add test cases:
+  struct SynReplyTests {
+    const SpdyHeaderInfo* syn_reply;
+    bool vary_matches;
+    int num_headers[2];
+    const char* extra_headers[2][16];
+  } test_cases[] = {
+    // Test the case of a multi-valued cookie.  When the value is delimited
+    // with NUL characters, it needs to be unfolded into multiple headers.
+    {
+      &syn_reply_info,
+      true,
+      { 1, 4 },
+      { { "cookie",   "val1,val2",
+          NULL
+        },
+        { "vary",     "cookie",
+          "status",   "200",
+          "url",      "/index.php",
+          "version",  "HTTP/1.1",
+          NULL
+        }
+      }
+    }, {    // Multiple vary fields.
+      &syn_reply_info,
+      true,
+      { 2, 5 },
+      { { "friend",   "barney",
+          "enemy",    "snaggletooth",
+          NULL
+        },
+        { "vary",     "friend",
+          "vary",     "enemy",
+          "status",   "200",
+          "url",      "/index.php",
+          "version",  "HTTP/1.1",
+          NULL
+        }
+      }
+    }, {    // Test a '*' vary field.
+      &syn_reply_info,
+      false,
+      { 1, 4 },
+      { { "cookie",   "val1,val2",
+          NULL
+        },
+        { "vary",     "*",
+          "status",   "200",
+          "url",      "/index.php",
+          "version",  "HTTP/1.1",
+          NULL
+        }
+      }
+    }, {    // Multiple comma-separated vary fields.
+      &syn_reply_info,
+      true,
+      { 2, 4 },
+      { { "friend",   "barney",
+          "enemy",    "snaggletooth",
+          NULL
+        },
+        { "vary",     "friend,enemy",
+          "status",   "200",
+          "url",      "/index.php",
+          "version",  "HTTP/1.1",
+          NULL
+        }
+      }
+    }
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+    // Construct the request.
+    scoped_ptr<SpdyFrame> frame_req(
+      ConstructSpdyGet(test_cases[i].extra_headers[0],
+                       test_cases[i].num_headers[0],
+                       false, 1, LOWEST));
+
+    MockWrite writes[] = {
+      CreateMockWrite(*frame_req),
+    };
+
+    // Construct the reply.
+    scoped_ptr<SpdyFrame> frame_reply(
+      ConstructSpdyPacket(*test_cases[i].syn_reply,
+                          test_cases[i].extra_headers[1],
+                          test_cases[i].num_headers[1],
+                          NULL,
+                          0));
+
+    scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+    MockRead reads[] = {
+      CreateMockRead(*frame_reply),
+      CreateMockRead(*body),
+      MockRead(ASYNC, 0, 0)  // EOF
+    };
+
+    // Attach the headers to the request.
+    int header_count = test_cases[i].num_headers[0];
+
+    HttpRequestInfo request = CreateGetRequest();
+    for (int ct = 0; ct < header_count; ct++) {
+      const char* header_key = test_cases[i].extra_headers[0][ct * 2];
+      const char* header_value = test_cases[i].extra_headers[0][ct * 2 + 1];
+      request.extra_headers.SetHeader(header_key, header_value);
+    }
+
+    DelayedSocketData data(1, reads, arraysize(reads),
+                           writes, arraysize(writes));
+    NormalSpdyTransactionHelper helper(request,
+                                       BoundNetLog(), GetParam(), NULL);
+    helper.RunToCompletion(&data);
+    TransactionHelperResult out = helper.output();
+
+    EXPECT_EQ(OK, out.rv) << i;
+    EXPECT_EQ("HTTP/1.1 200 OK", out.status_line) << i;
+    EXPECT_EQ("hello!", out.response_data) << i;
+
+    // Test the response information.
+    EXPECT_TRUE(out.response_info.response_time >
+                out.response_info.request_time) << i;
+    base::TimeDelta test_delay = out.response_info.response_time -
+                                 out.response_info.request_time;
+    base::TimeDelta min_expected_delay;
+    min_expected_delay.FromMilliseconds(10);
+    EXPECT_GT(test_delay.InMillisecondsF(),
+              min_expected_delay.InMillisecondsF()) << i;
+    EXPECT_EQ(out.response_info.vary_data.is_valid(),
+              test_cases[i].vary_matches) << i;
+
+    // Check the headers.
+    scoped_refptr<HttpResponseHeaders> headers = out.response_info.headers;
+    ASSERT_TRUE(headers.get() != NULL) << i;
+    void* iter = NULL;
+    std::string name, value, lines;
+    while (headers->EnumerateHeaderLines(&iter, &name, &value)) {
+      lines.append(name);
+      lines.append(": ");
+      lines.append(value);
+      lines.append("\n");
+    }
+
+    // Construct the expected header reply string.
+    char reply_buffer[256] = "";
+    ConstructSpdyReplyString(test_cases[i].extra_headers[1],
+                             test_cases[i].num_headers[1],
+                             reply_buffer,
+                             256);
+
+    EXPECT_EQ(std::string(reply_buffer), lines) << i;
+  }
+}
+
+// Verify that we don't crash on invalid SynReply responses.
+TEST_P(SpdyNetworkTransactionSpdy2Test, InvalidSynReply) {
+  const SpdyHeaderInfo kSynStartHeader = {
+    SYN_REPLY,              // Kind = SynReply
+    1,                      // Stream ID
+    0,                      // Associated stream ID
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 2),  // Priority
+    CONTROL_FLAG_NONE,      // Control Flags
+    false,                  // Compressed
+    INVALID,                // Status
+    NULL,                   // Data
+    0,                      // Length
+    DATA_FLAG_NONE          // Data Flags
+  };
+
+  struct InvalidSynReplyTests {
+    int num_headers;
+    const char* headers[10];
+  } test_cases[] = {
+    // SYN_REPLY missing status header
+    { 4,
+      { "cookie", "val1",
+        "cookie", "val2",
+        "url", "/index.php",
+        "version", "HTTP/1.1",
+        NULL
+      },
+    },
+    // SYN_REPLY missing version header
+    { 2,
+      { "status", "200",
+        "url", "/index.php",
+        NULL
+      },
+    },
+    // SYN_REPLY with no headers
+    { 0, { NULL }, },
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+    scoped_ptr<SpdyFrame> req(
+        ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+    MockWrite writes[] = {
+      CreateMockWrite(*req),
+    };
+
+    scoped_ptr<SpdyFrame> resp(
+        ConstructSpdyPacket(kSynStartHeader,
+                            NULL, 0,
+                            test_cases[i].headers,
+                            test_cases[i].num_headers));
+    scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+    MockRead reads[] = {
+      CreateMockRead(*resp),
+      CreateMockRead(*body),
+      MockRead(ASYNC, 0, 0)  // EOF
+    };
+
+    DelayedSocketData data(1, reads, arraysize(reads),
+                           writes, arraysize(writes));
+    NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                       BoundNetLog(), GetParam(), NULL);
+    helper.RunToCompletion(&data);
+    TransactionHelperResult out = helper.output();
+    EXPECT_EQ(ERR_INCOMPLETE_SPDY_HEADERS, out.rv);
+  }
+}
+
+// Verify that we don't crash on some corrupt frames.
+TEST_P(SpdyNetworkTransactionSpdy2Test, CorruptFrameSessionError) {
+  // This is the length field that's too short.
+  scoped_ptr<SpdyFrame> syn_reply_wrong_length(
+      ConstructSpdyGetSynReply(NULL, 0, 1));
+  syn_reply_wrong_length->set_length(syn_reply_wrong_length->length() - 4);
+
+  struct SynReplyTests {
+    const SpdyFrame* syn_reply;
+  } test_cases[] = {
+    { syn_reply_wrong_length.get(), },
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+    scoped_ptr<SpdyFrame> req(
+        ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+    MockWrite writes[] = {
+      CreateMockWrite(*req),
+      MockWrite(ASYNC, 0, 0)  // EOF
+    };
+
+    scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+    MockRead reads[] = {
+      CreateMockRead(*test_cases[i].syn_reply),
+      CreateMockRead(*body),
+      MockRead(ASYNC, 0, 0)  // EOF
+    };
+
+    DelayedSocketData data(1, reads, arraysize(reads),
+                           writes, arraysize(writes));
+    NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                       BoundNetLog(), GetParam(), NULL);
+    helper.RunToCompletion(&data);
+    TransactionHelperResult out = helper.output();
+    EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+  }
+}
+
+// Test that we shutdown correctly on write errors.
+TEST_P(SpdyNetworkTransactionSpdy2Test, WriteError) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = {
+    // We'll write 10 bytes successfully
+    MockWrite(ASYNC, req->data(), 10),
+    // Followed by ERROR!
+    MockWrite(ASYNC, ERR_FAILED),
+  };
+
+  DelayedSocketData data(2, NULL, 0,
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(ERR_FAILED, out.rv);
+  data.Reset();
+}
+
+// Test that partial writes work.
+TEST_P(SpdyNetworkTransactionSpdy2Test, PartialWrite) {
+  // Chop the SYN_STREAM frame into 5 chunks.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  const int kChunks = 5;
+  scoped_array<MockWrite> writes(ChopWriteFrame(*req.get(), kChunks));
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(kChunks, reads, arraysize(reads),
+                         writes.get(), kChunks);
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+}
+
+// In this test, we enable compression, but get a uncompressed SynReply from
+// the server.  Verify that teardown is all clean.
+TEST_P(SpdyNetworkTransactionSpdy2Test, DecompressFailureOnSynReply) {
+  scoped_ptr<SpdyFrame> compressed(
+      ConstructSpdyGet(NULL, 0, true, 1, LOWEST));
+  scoped_ptr<SpdyFrame> rst(
+      ConstructSpdyRstStream(1, PROTOCOL_ERROR));
+  MockWrite writes[] = {
+    CreateMockWrite(*compressed),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  SpdySessionDependencies* session_deps = new SpdySessionDependencies();
+  session_deps->enable_compression = true;
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), session_deps);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+  data.Reset();
+}
+
+// Test that the NetLog contains good data for a simple GET request.
+TEST_P(SpdyNetworkTransactionSpdy2Test, NetLog) {
+  static const char* const kExtraHeaders[] = {
+    "user-agent",   "Chrome",
+  };
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(kExtraHeaders, 1, false, 1,
+                                                   LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  CapturingBoundNetLog log;
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequestWithUserAgent(),
+                                     log.bound(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+
+  // Check that the NetLog was filled reasonably.
+  // This test is intentionally non-specific about the exact ordering of the
+  // log; instead we just check to make sure that certain events exist, and that
+  // they are in the right order.
+  net::CapturingNetLog::CapturedEntryList entries;
+  log.GetEntries(&entries);
+
+  EXPECT_LT(0u, entries.size());
+  int pos = 0;
+  pos = net::ExpectLogContainsSomewhere(entries, 0,
+      net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST,
+      net::NetLog::PHASE_BEGIN);
+  pos = net::ExpectLogContainsSomewhere(entries, pos + 1,
+      net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST,
+      net::NetLog::PHASE_END);
+  pos = net::ExpectLogContainsSomewhere(entries, pos + 1,
+      net::NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS,
+      net::NetLog::PHASE_BEGIN);
+  pos = net::ExpectLogContainsSomewhere(entries, pos + 1,
+      net::NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS,
+      net::NetLog::PHASE_END);
+  pos = net::ExpectLogContainsSomewhere(entries, pos + 1,
+      net::NetLog::TYPE_HTTP_TRANSACTION_READ_BODY,
+      net::NetLog::PHASE_BEGIN);
+  pos = net::ExpectLogContainsSomewhere(entries, pos + 1,
+      net::NetLog::TYPE_HTTP_TRANSACTION_READ_BODY,
+      net::NetLog::PHASE_END);
+
+  // Check that we logged all the headers correctly
+  pos = net::ExpectLogContainsSomewhere(
+      entries, 0,
+      net::NetLog::TYPE_SPDY_SESSION_SYN_STREAM,
+      net::NetLog::PHASE_NONE);
+
+  ListValue* header_list;
+  ASSERT_TRUE(entries[pos].params.get());
+  ASSERT_TRUE(entries[pos].params->GetList("headers", &header_list));
+
+  std::vector<std::string> expected;
+  expected.push_back("host: www.google.com");
+  expected.push_back("url: /");
+  expected.push_back("scheme: http");
+  expected.push_back("version: HTTP/1.1");
+  expected.push_back("method: GET");
+  expected.push_back("user-agent: Chrome");
+  EXPECT_EQ(expected.size(), header_list->GetSize());
+  for (std::vector<std::string>::const_iterator it = expected.begin();
+       it != expected.end();
+       ++it) {
+    base::StringValue header(*it);
+    EXPECT_NE(header_list->end(), header_list->Find(header)) <<
+        "Header not found: " << *it;
+  }
+}
+
+// Since we buffer the IO from the stream to the renderer, this test verifies
+// that when we read out the maximum amount of data (e.g. we received 50 bytes
+// on the network, but issued a Read for only 5 of those bytes) that the data
+// flow still works correctly.
+TEST_P(SpdyNetworkTransactionSpdy2Test, BufferFull) {
+  BufferedSpdyFramer framer(2, false);
+
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  // 2 data frames in a single read.
+  scoped_ptr<SpdyFrame> data_frame_1(
+      framer.CreateDataFrame(1, "goodby", 6, DATA_FLAG_NONE));
+  scoped_ptr<SpdyFrame> data_frame_2(
+      framer.CreateDataFrame(1, "e worl", 6, DATA_FLAG_NONE));
+  const SpdyFrame* data_frames[2] = {
+    data_frame_1.get(),
+    data_frame_2.get(),
+  };
+  char combined_data_frames[100];
+  int combined_data_frames_len =
+      CombineFrames(data_frames, arraysize(data_frames),
+                    combined_data_frames, arraysize(combined_data_frames));
+  scoped_ptr<SpdyFrame> last_frame(
+      framer.CreateDataFrame(1, "d", 1, DATA_FLAG_FIN));
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    MockRead(ASYNC, ERR_IO_PENDING),  // Force a pause
+    MockRead(ASYNC, combined_data_frames, combined_data_frames_len),
+    MockRead(ASYNC, ERR_IO_PENDING),  // Force a pause
+    CreateMockRead(*last_frame),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  TestCompletionCallback callback;
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  TransactionHelperResult out = helper.output();
+  out.rv = callback.WaitForResult();
+  EXPECT_EQ(out.rv, OK);
+
+  const HttpResponseInfo* response = trans->GetResponseInfo();
+  EXPECT_TRUE(response->headers != NULL);
+  EXPECT_TRUE(response->was_fetched_via_spdy);
+  out.status_line = response->headers->GetStatusLine();
+  out.response_info = *response;  // Make a copy so we can verify.
+
+  // Read Data
+  TestCompletionCallback read_callback;
+
+  std::string content;
+  do {
+    // Read small chunks at a time.
+    const int kSmallReadSize = 3;
+    scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize));
+    rv = trans->Read(buf, kSmallReadSize, read_callback.callback());
+    if (rv == net::ERR_IO_PENDING) {
+      data.CompleteRead();
+      rv = read_callback.WaitForResult();
+    }
+    if (rv > 0) {
+      content.append(buf->data(), rv);
+    } else if (rv < 0) {
+      NOTREACHED();
+    }
+  } while (rv > 0);
+
+  out.response_data.swap(content);
+
+  // Flush the MessageLoop while the SpdySessionDependencies (in particular, the
+  // MockClientSocketFactory) are still alive.
+  MessageLoop::current()->RunUntilIdle();
+
+  // Verify that we consumed all test data.
+  helper.VerifyDataConsumed();
+
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("goodbye world", out.response_data);
+}
+
+// Verify that basic buffering works; when multiple data frames arrive
+// at the same time, ensure that we don't notify a read completion for
+// each data frame individually.
+TEST_P(SpdyNetworkTransactionSpdy2Test, Buffering) {
+  BufferedSpdyFramer framer(2, false);
+
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  // 4 data frames in a single read.
+  scoped_ptr<SpdyFrame> data_frame(
+      framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE));
+  scoped_ptr<SpdyFrame> data_frame_fin(
+      framer.CreateDataFrame(1, "message", 7, DATA_FLAG_FIN));
+  const SpdyFrame* data_frames[4] = {
+    data_frame.get(),
+    data_frame.get(),
+    data_frame.get(),
+    data_frame_fin.get()
+  };
+  char combined_data_frames[100];
+  int combined_data_frames_len =
+      CombineFrames(data_frames, arraysize(data_frames),
+                    combined_data_frames, arraysize(combined_data_frames));
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    MockRead(ASYNC, ERR_IO_PENDING),  // Force a pause
+    MockRead(ASYNC, combined_data_frames, combined_data_frames_len),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  TransactionHelperResult out = helper.output();
+  out.rv = callback.WaitForResult();
+  EXPECT_EQ(out.rv, OK);
+
+  const HttpResponseInfo* response = trans->GetResponseInfo();
+  EXPECT_TRUE(response->headers != NULL);
+  EXPECT_TRUE(response->was_fetched_via_spdy);
+  out.status_line = response->headers->GetStatusLine();
+  out.response_info = *response;  // Make a copy so we can verify.
+
+  // Read Data
+  TestCompletionCallback read_callback;
+
+  std::string content;
+  int reads_completed = 0;
+  do {
+    // Read small chunks at a time.
+    const int kSmallReadSize = 14;
+    scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize));
+    rv = trans->Read(buf, kSmallReadSize, read_callback.callback());
+    if (rv == net::ERR_IO_PENDING) {
+      data.CompleteRead();
+      rv = read_callback.WaitForResult();
+    }
+    if (rv > 0) {
+      EXPECT_EQ(kSmallReadSize, rv);
+      content.append(buf->data(), rv);
+    } else if (rv < 0) {
+      FAIL() << "Unexpected read error: " << rv;
+    }
+    reads_completed++;
+  } while (rv > 0);
+
+  EXPECT_EQ(3, reads_completed);  // Reads are: 14 bytes, 14 bytes, 0 bytes.
+
+  out.response_data.swap(content);
+
+  // Flush the MessageLoop while the SpdySessionDependencies (in particular, the
+  // MockClientSocketFactory) are still alive.
+  MessageLoop::current()->RunUntilIdle();
+
+  // Verify that we consumed all test data.
+  helper.VerifyDataConsumed();
+
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("messagemessagemessagemessage", out.response_data);
+}
+
+// Verify the case where we buffer data but read it after it has been buffered.
+TEST_P(SpdyNetworkTransactionSpdy2Test, BufferedAll) {
+  BufferedSpdyFramer framer(2, false);
+
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  // 5 data frames in a single read.
+  scoped_ptr<SpdyFrame> syn_reply(
+      ConstructSpdyGetSynReply(NULL, 0, 1));
+  syn_reply->set_flags(CONTROL_FLAG_NONE);  // turn off FIN bit
+  scoped_ptr<SpdyFrame> data_frame(
+      framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE));
+  scoped_ptr<SpdyFrame> data_frame_fin(
+      framer.CreateDataFrame(1, "message", 7, DATA_FLAG_FIN));
+  const SpdyFrame* frames[5] = {
+    syn_reply.get(),
+    data_frame.get(),
+    data_frame.get(),
+    data_frame.get(),
+    data_frame_fin.get()
+  };
+  char combined_frames[200];
+  int combined_frames_len =
+      CombineFrames(frames, arraysize(frames),
+                    combined_frames, arraysize(combined_frames));
+
+  MockRead reads[] = {
+    MockRead(ASYNC, combined_frames, combined_frames_len),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  TransactionHelperResult out = helper.output();
+  out.rv = callback.WaitForResult();
+  EXPECT_EQ(out.rv, OK);
+
+  const HttpResponseInfo* response = trans->GetResponseInfo();
+  EXPECT_TRUE(response->headers != NULL);
+  EXPECT_TRUE(response->was_fetched_via_spdy);
+  out.status_line = response->headers->GetStatusLine();
+  out.response_info = *response;  // Make a copy so we can verify.
+
+  // Read Data
+  TestCompletionCallback read_callback;
+
+  std::string content;
+  int reads_completed = 0;
+  do {
+    // Read small chunks at a time.
+    const int kSmallReadSize = 14;
+    scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize));
+    rv = trans->Read(buf, kSmallReadSize, read_callback.callback());
+    if (rv > 0) {
+      EXPECT_EQ(kSmallReadSize, rv);
+      content.append(buf->data(), rv);
+    } else if (rv < 0) {
+      FAIL() << "Unexpected read error: " << rv;
+    }
+    reads_completed++;
+  } while (rv > 0);
+
+  EXPECT_EQ(3, reads_completed);
+
+  out.response_data.swap(content);
+
+  // Flush the MessageLoop while the SpdySessionDependencies (in particular, the
+  // MockClientSocketFactory) are still alive.
+  MessageLoop::current()->RunUntilIdle();
+
+  // Verify that we consumed all test data.
+  helper.VerifyDataConsumed();
+
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("messagemessagemessagemessage", out.response_data);
+}
+
+// Verify the case where we buffer data and close the connection.
+TEST_P(SpdyNetworkTransactionSpdy2Test, BufferedClosed) {
+  BufferedSpdyFramer framer(2, false);
+
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  // All data frames in a single read.
+  // NOTE: We don't FIN the stream.
+  scoped_ptr<SpdyFrame> data_frame(
+      framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE));
+  const SpdyFrame* data_frames[4] = {
+    data_frame.get(),
+    data_frame.get(),
+    data_frame.get(),
+    data_frame.get()
+  };
+  char combined_data_frames[100];
+  int combined_data_frames_len =
+      CombineFrames(data_frames, arraysize(data_frames),
+                    combined_data_frames, arraysize(combined_data_frames));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    MockRead(ASYNC, ERR_IO_PENDING),  // Force a wait
+    MockRead(ASYNC, combined_data_frames, combined_data_frames_len),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  TransactionHelperResult out = helper.output();
+  out.rv = callback.WaitForResult();
+  EXPECT_EQ(out.rv, OK);
+
+  const HttpResponseInfo* response = trans->GetResponseInfo();
+  EXPECT_TRUE(response->headers != NULL);
+  EXPECT_TRUE(response->was_fetched_via_spdy);
+  out.status_line = response->headers->GetStatusLine();
+  out.response_info = *response;  // Make a copy so we can verify.
+
+  // Read Data
+  TestCompletionCallback read_callback;
+
+  std::string content;
+  int reads_completed = 0;
+  do {
+    // Read small chunks at a time.
+    const int kSmallReadSize = 14;
+    scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize));
+    rv = trans->Read(buf, kSmallReadSize, read_callback.callback());
+    if (rv == net::ERR_IO_PENDING) {
+      data.CompleteRead();
+      rv = read_callback.WaitForResult();
+    }
+    if (rv > 0) {
+      content.append(buf->data(), rv);
+    } else if (rv < 0) {
+      // This test intentionally closes the connection, and will get an error.
+      EXPECT_EQ(ERR_CONNECTION_CLOSED, rv);
+      break;
+    }
+    reads_completed++;
+  } while (rv > 0);
+
+  EXPECT_EQ(0, reads_completed);
+
+  out.response_data.swap(content);
+
+  // Flush the MessageLoop while the SpdySessionDependencies (in particular, the
+  // MockClientSocketFactory) are still alive.
+  MessageLoop::current()->RunUntilIdle();
+
+  // Verify that we consumed all test data.
+  helper.VerifyDataConsumed();
+}
+
+// Verify the case where we buffer data and cancel the transaction.
+TEST_P(SpdyNetworkTransactionSpdy2Test, BufferedCancelled) {
+  BufferedSpdyFramer framer(2, false);
+
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  // NOTE: We don't FIN the stream.
+  scoped_ptr<SpdyFrame> data_frame(
+      framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE));
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    MockRead(ASYNC, ERR_IO_PENDING),  // Force a wait
+    CreateMockRead(*data_frame),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+  TestCompletionCallback callback;
+
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  TransactionHelperResult out = helper.output();
+  out.rv = callback.WaitForResult();
+  EXPECT_EQ(out.rv, OK);
+
+  const HttpResponseInfo* response = trans->GetResponseInfo();
+  EXPECT_TRUE(response->headers != NULL);
+  EXPECT_TRUE(response->was_fetched_via_spdy);
+  out.status_line = response->headers->GetStatusLine();
+  out.response_info = *response;  // Make a copy so we can verify.
+
+  // Read Data
+  TestCompletionCallback read_callback;
+
+  do {
+    const int kReadSize = 256;
+    scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kReadSize));
+    rv = trans->Read(buf, kReadSize, read_callback.callback());
+    if (rv == net::ERR_IO_PENDING) {
+      // Complete the read now, which causes buffering to start.
+      data.CompleteRead();
+      // Destroy the transaction, causing the stream to get cancelled
+      // and orphaning the buffered IO task.
+      helper.ResetTrans();
+      break;
+    }
+    // We shouldn't get here in this test.
+    FAIL() << "Unexpected read: " << rv;
+  } while (rv > 0);
+
+  // Flush the MessageLoop; this will cause the buffered IO task
+  // to run for the final time.
+  MessageLoop::current()->RunUntilIdle();
+
+  // Verify that we consumed all test data.
+  helper.VerifyDataConsumed();
+}
+
+// Test that if the server requests persistence of settings, that we save
+// the settings in the HttpServerProperties.
+TEST_P(SpdyNetworkTransactionSpdy2Test, SettingsSaved) {
+  static const SpdyHeaderInfo kSynReplyInfo = {
+    SYN_REPLY,                              // Syn Reply
+    1,                                      // Stream ID
+    0,                                      // Associated Stream ID
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 2),  // Priority
+    CONTROL_FLAG_NONE,                      // Control Flags
+    false,                                  // Compressed
+    INVALID,                                // Status
+    NULL,                                   // Data
+    0,                                      // Data Length
+    DATA_FLAG_NONE                          // Data Flags
+  };
+  static const char* const kExtraHeaders[] = {
+    "status",   "200",
+    "version",  "HTTP/1.1"
+  };
+
+  BoundNetLog net_log;
+  NormalSpdyTransactionHelper helper(CreateGetRequest(), net_log,
+                                     GetParam(), NULL);
+  helper.RunPreTestSetup();
+
+  // Verify that no settings exist initially.
+  HostPortPair host_port_pair("www.google.com", helper.port());
+  SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool();
+  EXPECT_TRUE(spdy_session_pool->http_server_properties()->GetSpdySettings(
+      host_port_pair).empty());
+
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  // Construct the reply.
+  scoped_ptr<SpdyFrame> reply(
+    ConstructSpdyPacket(kSynReplyInfo,
+                        kExtraHeaders,
+                        arraysize(kExtraHeaders) / 2,
+                        NULL,
+                        0));
+
+  const SpdySettingsIds kSampleId1 = SETTINGS_UPLOAD_BANDWIDTH;
+  unsigned int kSampleValue1 = 0x0a0a0a0a;
+  const SpdySettingsIds kSampleId2 = SETTINGS_DOWNLOAD_BANDWIDTH;
+  unsigned int kSampleValue2 = 0x0b0b0b0b;
+  const SpdySettingsIds kSampleId3 = SETTINGS_ROUND_TRIP_TIME;
+  unsigned int kSampleValue3 = 0x0c0c0c0c;
+  scoped_ptr<SpdyFrame> settings_frame;
+  {
+    // Construct the SETTINGS frame.
+    SettingsMap settings;
+    // First add a persisted setting.
+    settings[kSampleId1] =
+        SettingsFlagsAndValue(SETTINGS_FLAG_PLEASE_PERSIST, kSampleValue1);
+    // Next add a non-persisted setting.
+    settings[kSampleId2] =
+        SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kSampleValue2);
+    // Next add another persisted setting.
+    settings[kSampleId3] =
+        SettingsFlagsAndValue(SETTINGS_FLAG_PLEASE_PERSIST, kSampleValue3);
+    settings_frame.reset(ConstructSpdySettings(settings));
+  }
+
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*reply),
+    CreateMockRead(*body),
+    CreateMockRead(*settings_frame),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  helper.AddData(&data);
+  helper.RunDefaultTest();
+  helper.VerifyDataConsumed();
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+
+  {
+    // Verify we had two persisted settings.
+    const SettingsMap& settings_map =
+        spdy_session_pool->http_server_properties()->GetSpdySettings(
+            host_port_pair);
+    ASSERT_EQ(2u, settings_map.size());
+
+    // Verify the first persisted setting.
+    SettingsMap::const_iterator it1 = settings_map.find(kSampleId1);
+    EXPECT_TRUE(it1 != settings_map.end());
+    SettingsFlagsAndValue flags_and_value1 = it1->second;
+    EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value1.first);
+    EXPECT_EQ(kSampleValue1, flags_and_value1.second);
+
+    // Verify the second persisted setting.
+    SettingsMap::const_iterator it3 = settings_map.find(kSampleId3);
+    EXPECT_TRUE(it3 != settings_map.end());
+    SettingsFlagsAndValue flags_and_value3 = it3->second;
+    EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value3.first);
+    EXPECT_EQ(kSampleValue3, flags_and_value3.second);
+  }
+}
+
+// Test that when there are settings saved that they are sent back to the
+// server upon session establishment.
+TEST_P(SpdyNetworkTransactionSpdy2Test, SettingsPlayback) {
+  static const SpdyHeaderInfo kSynReplyInfo = {
+    SYN_REPLY,                              // Syn Reply
+    1,                                      // Stream ID
+    0,                                      // Associated Stream ID
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 2),  // Priority
+    CONTROL_FLAG_NONE,                      // Control Flags
+    false,                                  // Compressed
+    INVALID,                                // Status
+    NULL,                                   // Data
+    0,                                      // Data Length
+    DATA_FLAG_NONE                          // Data Flags
+  };
+  static const char* kExtraHeaders[] = {
+    "status",   "200",
+    "version",  "HTTP/1.1"
+  };
+
+  BoundNetLog net_log;
+  NormalSpdyTransactionHelper helper(CreateGetRequest(), net_log,
+                                     GetParam(), NULL);
+  helper.RunPreTestSetup();
+
+  // Verify that no settings exist initially.
+  HostPortPair host_port_pair("www.google.com", helper.port());
+  SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool();
+  EXPECT_TRUE(spdy_session_pool->http_server_properties()->GetSpdySettings(
+      host_port_pair).empty());
+
+  const SpdySettingsIds kSampleId1 = SETTINGS_UPLOAD_BANDWIDTH;
+  unsigned int kSampleValue1 = 0x0a0a0a0a;
+  const SpdySettingsIds kSampleId2 = SETTINGS_ROUND_TRIP_TIME;
+  unsigned int kSampleValue2 = 0x0c0c0c0c;
+
+  // First add a persisted setting.
+  spdy_session_pool->http_server_properties()->SetSpdySetting(
+      host_port_pair,
+      kSampleId1,
+      SETTINGS_FLAG_PLEASE_PERSIST,
+      kSampleValue1);
+
+  // Next add another persisted setting.
+  spdy_session_pool->http_server_properties()->SetSpdySetting(
+      host_port_pair,
+      kSampleId2,
+      SETTINGS_FLAG_PLEASE_PERSIST,
+      kSampleValue2);
+
+  EXPECT_EQ(2u, spdy_session_pool->http_server_properties()->GetSpdySettings(
+      host_port_pair).size());
+
+  // Construct the SETTINGS frame.
+  const SettingsMap& settings =
+      spdy_session_pool->http_server_properties()->GetSpdySettings(
+          host_port_pair);
+  scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+
+  MockWrite writes[] = {
+    CreateMockWrite(*settings_frame),
+    CreateMockWrite(*req),
+  };
+
+  // Construct the reply.
+  scoped_ptr<SpdyFrame> reply(
+    ConstructSpdyPacket(kSynReplyInfo,
+                        kExtraHeaders,
+                        arraysize(kExtraHeaders) / 2,
+                        NULL,
+                        0));
+
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*reply),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(2, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  helper.AddData(&data);
+  helper.RunDefaultTest();
+  helper.VerifyDataConsumed();
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+
+  {
+    // Verify we had two persisted settings.
+    const SettingsMap& settings_map =
+        spdy_session_pool->http_server_properties()->GetSpdySettings(
+            host_port_pair);
+    ASSERT_EQ(2u, settings_map.size());
+
+    // Verify the first persisted setting.
+    SettingsMap::const_iterator it1 = settings_map.find(kSampleId1);
+    EXPECT_TRUE(it1 != settings_map.end());
+    SettingsFlagsAndValue flags_and_value1 = it1->second;
+    EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value1.first);
+    EXPECT_EQ(kSampleValue1, flags_and_value1.second);
+
+    // Verify the second persisted setting.
+    SettingsMap::const_iterator it2 = settings_map.find(kSampleId2);
+    EXPECT_TRUE(it2 != settings_map.end());
+    SettingsFlagsAndValue flags_and_value2 = it2->second;
+    EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value2.first);
+    EXPECT_EQ(kSampleValue2, flags_and_value2.second);
+  }
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, GoAwayWithActiveStream) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  scoped_ptr<SpdyFrame> go_away(ConstructSpdyGoAway());
+  MockRead reads[] = {
+    CreateMockRead(*go_away),
+    MockRead(ASYNC, 0, 0),  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.AddData(&data);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(ERR_ABORTED, out.rv);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, CloseWithActiveStream) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    MockRead(SYNCHRONOUS, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  BoundNetLog log;
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     log, GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  TransactionHelperResult out;
+  out.rv = trans->Start(&CreateGetRequest(), callback.callback(), log);
+
+  EXPECT_EQ(out.rv, ERR_IO_PENDING);
+  out.rv = callback.WaitForResult();
+  EXPECT_EQ(out.rv, OK);
+
+  const HttpResponseInfo* response = trans->GetResponseInfo();
+  EXPECT_TRUE(response->headers != NULL);
+  EXPECT_TRUE(response->was_fetched_via_spdy);
+  out.rv = ReadTransaction(trans, &out.response_data);
+  EXPECT_EQ(ERR_CONNECTION_CLOSED, out.rv);
+
+  // Verify that we consumed all test data.
+  helper.VerifyDataConsumed();
+}
+
+// Test to make sure we can correctly connect through a proxy.
+TEST_P(SpdyNetworkTransactionSpdy2Test, ProxyConnect) {
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.session_deps().reset(new SpdySessionDependencies(
+      ProxyService::CreateFixedFromPacResult("PROXY myproxy:70")));
+  helper.SetSession(make_scoped_refptr(
+      SpdySessionDependencies::SpdyCreateSession(helper.session_deps().get())));
+  helper.RunPreTestSetup();
+  HttpNetworkTransaction* trans = helper.trans();
+
+  const char kConnect443[] = {"CONNECT www.google.com:443 HTTP/1.1\r\n"
+                           "Host: www.google.com\r\n"
+                           "Proxy-Connection: keep-alive\r\n\r\n"};
+  const char kConnect80[] = {"CONNECT www.google.com:80 HTTP/1.1\r\n"
+                           "Host: www.google.com\r\n"
+                           "Proxy-Connection: keep-alive\r\n\r\n"};
+  const char kHTTP200[] = {"HTTP/1.1 200 OK\r\n\r\n"};
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+
+  MockWrite writes_SPDYNPN[] = {
+    MockWrite(SYNCHRONOUS, kConnect443, arraysize(kConnect443) - 1, 0),
+    CreateMockWrite(*req, 2),
+  };
+  MockRead reads_SPDYNPN[] = {
+    MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1),
+    CreateMockRead(*resp, 3),
+    CreateMockRead(*body.get(), 4),
+    MockRead(ASYNC, 0, 0, 5),
+  };
+
+  MockWrite writes_SPDYSSL[] = {
+    MockWrite(SYNCHRONOUS, kConnect80, arraysize(kConnect80) - 1, 0),
+    CreateMockWrite(*req, 2),
+  };
+  MockRead reads_SPDYSSL[] = {
+    MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1),
+    CreateMockRead(*resp, 3),
+    CreateMockRead(*body.get(), 4),
+    MockRead(ASYNC, 0, 0, 5),
+  };
+
+  MockWrite writes_SPDYNOSSL[] = {
+    CreateMockWrite(*req, 0),
+  };
+
+  MockRead reads_SPDYNOSSL[] = {
+    CreateMockRead(*resp, 1),
+    CreateMockRead(*body.get(), 2),
+    MockRead(ASYNC, 0, 0, 3),
+  };
+
+  scoped_ptr<OrderedSocketData> data;
+  switch(GetParam()) {
+    case SPDYNOSSL:
+      data.reset(new OrderedSocketData(reads_SPDYNOSSL,
+                                       arraysize(reads_SPDYNOSSL),
+                                       writes_SPDYNOSSL,
+                                       arraysize(writes_SPDYNOSSL)));
+      break;
+    case SPDYSSL:
+      data.reset(new OrderedSocketData(reads_SPDYSSL,
+                                       arraysize(reads_SPDYSSL),
+                                       writes_SPDYSSL,
+                                       arraysize(writes_SPDYSSL)));
+      break;
+    case SPDYNPN:
+      data.reset(new OrderedSocketData(reads_SPDYNPN,
+                                       arraysize(reads_SPDYNPN),
+                                       writes_SPDYNPN,
+                                       arraysize(writes_SPDYNPN)));
+      break;
+    default:
+      NOTREACHED();
+  }
+
+  helper.AddData(data.get());
+  TestCompletionCallback callback;
+
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  rv = callback.WaitForResult();
+  EXPECT_EQ(0, rv);
+
+  // Verify the SYN_REPLY.
+  HttpResponseInfo response = *trans->GetResponseInfo();
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  std::string response_data;
+  ASSERT_EQ(OK, ReadTransaction(trans, &response_data));
+  EXPECT_EQ("hello!", response_data);
+  helper.VerifyDataConsumed();
+}
+
+// Test to make sure we can correctly connect through a proxy to www.google.com,
+// if there already exists a direct spdy connection to www.google.com. See
+// http://crbug.com/49874
+TEST_P(SpdyNetworkTransactionSpdy2Test, DirectConnectProxyReconnect) {
+  // When setting up the first transaction, we store the SpdySessionPool so that
+  // we can use the same pool in the second transaction.
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+
+  // Use a proxy service which returns a proxy fallback list from DIRECT to
+  // myproxy:70. For this test there will be no fallback, so it is equivalent
+  // to simply DIRECT. The reason for appending the second proxy is to verify
+  // that the session pool key used does is just "DIRECT".
+  helper.session_deps().reset(new SpdySessionDependencies(
+      ProxyService::CreateFixedFromPacResult("DIRECT; PROXY myproxy:70")));
+  helper.SetSession(make_scoped_refptr(
+      SpdySessionDependencies::SpdyCreateSession(helper.session_deps().get())));
+
+  SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool();
+  helper.RunPreTestSetup();
+
+  // Construct and send a simple GET request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = {
+    CreateMockWrite(*req, 1),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 2),
+    CreateMockRead(*body, 3),
+    MockRead(ASYNC, ERR_IO_PENDING, 4),  // Force a pause
+    MockRead(ASYNC, 0, 5)  // EOF
+  };
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  helper.AddData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  TransactionHelperResult out;
+  out.rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+
+  EXPECT_EQ(out.rv, ERR_IO_PENDING);
+  out.rv = callback.WaitForResult();
+  EXPECT_EQ(out.rv, OK);
+
+  const HttpResponseInfo* response = trans->GetResponseInfo();
+  EXPECT_TRUE(response->headers != NULL);
+  EXPECT_TRUE(response->was_fetched_via_spdy);
+  out.rv = ReadTransaction(trans, &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  out.status_line = response->headers->GetStatusLine();
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+
+  // Check that the SpdySession is still in the SpdySessionPool.
+  HostPortPair host_port_pair("www.google.com", helper.port());
+  HostPortProxyPair session_pool_key_direct(
+      host_port_pair, ProxyServer::Direct());
+  EXPECT_TRUE(spdy_session_pool->HasSession(session_pool_key_direct));
+  HostPortProxyPair session_pool_key_proxy(
+      host_port_pair,
+      ProxyServer::FromURI("www.foo.com", ProxyServer::SCHEME_HTTP));
+  EXPECT_FALSE(spdy_session_pool->HasSession(session_pool_key_proxy));
+
+  // Set up data for the proxy connection.
+  const char kConnect443[] = {"CONNECT www.google.com:443 HTTP/1.1\r\n"
+                           "Host: www.google.com\r\n"
+                           "Proxy-Connection: keep-alive\r\n\r\n"};
+  const char kConnect80[] = {"CONNECT www.google.com:80 HTTP/1.1\r\n"
+                           "Host: www.google.com\r\n"
+                           "Proxy-Connection: keep-alive\r\n\r\n"};
+  const char kHTTP200[] = {"HTTP/1.1 200 OK\r\n\r\n"};
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(
+      "http://www.google.com/foo.dat", false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(1, true));
+
+  MockWrite writes_SPDYNPN[] = {
+    MockWrite(SYNCHRONOUS, kConnect443, arraysize(kConnect443) - 1, 0),
+    CreateMockWrite(*req2, 2),
+  };
+  MockRead reads_SPDYNPN[] = {
+    MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1),
+    CreateMockRead(*resp2, 3),
+    CreateMockRead(*body2, 4),
+    MockRead(ASYNC, 0, 5)  // EOF
+  };
+
+  MockWrite writes_SPDYNOSSL[] = {
+    CreateMockWrite(*req2, 0),
+  };
+  MockRead reads_SPDYNOSSL[] = {
+    CreateMockRead(*resp2, 1),
+    CreateMockRead(*body2, 2),
+    MockRead(ASYNC, 0, 3)  // EOF
+  };
+
+  MockWrite writes_SPDYSSL[] = {
+    MockWrite(SYNCHRONOUS, kConnect80, arraysize(kConnect80) - 1, 0),
+    CreateMockWrite(*req2, 2),
+  };
+  MockRead reads_SPDYSSL[] = {
+    MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1),
+    CreateMockRead(*resp2, 3),
+    CreateMockRead(*body2, 4),
+    MockRead(ASYNC, 0, 0, 5),
+  };
+
+  scoped_ptr<OrderedSocketData> data_proxy;
+  switch(GetParam()) {
+    case SPDYNPN:
+      data_proxy.reset(new OrderedSocketData(reads_SPDYNPN,
+                                             arraysize(reads_SPDYNPN),
+                                             writes_SPDYNPN,
+                                             arraysize(writes_SPDYNPN)));
+      break;
+    case SPDYNOSSL:
+      data_proxy.reset(new OrderedSocketData(reads_SPDYNOSSL,
+                                             arraysize(reads_SPDYNOSSL),
+                                             writes_SPDYNOSSL,
+                                             arraysize(writes_SPDYNOSSL)));
+      break;
+    case SPDYSSL:
+      data_proxy.reset(new OrderedSocketData(reads_SPDYSSL,
+                                             arraysize(reads_SPDYSSL),
+                                             writes_SPDYSSL,
+                                             arraysize(writes_SPDYSSL)));
+      break;
+    default:
+      NOTREACHED();
+  }
+
+  // Create another request to www.google.com, but this time through a proxy.
+  HttpRequestInfo request_proxy;
+  request_proxy.method = "GET";
+  request_proxy.url = GURL("http://www.google.com/foo.dat");
+  request_proxy.load_flags = 0;
+  scoped_ptr<SpdySessionDependencies> ssd_proxy(new SpdySessionDependencies());
+  // Ensure that this transaction uses the same SpdySessionPool.
+  scoped_refptr<HttpNetworkSession> session_proxy(
+      SpdySessionDependencies::SpdyCreateSession(ssd_proxy.get()));
+  NormalSpdyTransactionHelper helper_proxy(request_proxy,
+                                           BoundNetLog(), GetParam(), NULL);
+  HttpNetworkSessionPeer session_peer(session_proxy);
+  scoped_ptr<net::ProxyService> proxy_service(
+          ProxyService::CreateFixedFromPacResult("PROXY myproxy:70"));
+  session_peer.SetProxyService(proxy_service.get());
+  helper_proxy.session_deps().swap(ssd_proxy);
+  helper_proxy.SetSession(session_proxy);
+  helper_proxy.RunPreTestSetup();
+  helper_proxy.AddData(data_proxy.get());
+
+  HttpNetworkTransaction* trans_proxy = helper_proxy.trans();
+  TestCompletionCallback callback_proxy;
+  int rv = trans_proxy->Start(
+      &request_proxy, callback_proxy.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  rv = callback_proxy.WaitForResult();
+  EXPECT_EQ(0, rv);
+
+  HttpResponseInfo response_proxy = *trans_proxy->GetResponseInfo();
+  EXPECT_TRUE(response_proxy.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response_proxy.headers->GetStatusLine());
+
+  std::string response_data;
+  ASSERT_EQ(OK, ReadTransaction(trans_proxy, &response_data));
+  EXPECT_EQ("hello!", response_data);
+
+  data.CompleteRead();
+  helper_proxy.VerifyDataConsumed();
+}
+
+// When we get a TCP-level RST, we need to retry a HttpNetworkTransaction
+// on a new connection, if the connection was previously known to be good.
+// This can happen when a server reboots without saying goodbye, or when
+// we're behind a NAT that masked the RST.
+TEST_P(SpdyNetworkTransactionSpdy2Test, VerifyRetryOnConnectionReset) {
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, ERR_IO_PENDING),
+    MockRead(ASYNC, ERR_CONNECTION_RESET),
+  };
+
+  MockRead reads2[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  // This test has a couple of variants.
+  enum {
+    // Induce the RST while waiting for our transaction to send.
+    VARIANT_RST_DURING_SEND_COMPLETION,
+    // Induce the RST while waiting for our transaction to read.
+    // In this case, the send completed - everything copied into the SNDBUF.
+    VARIANT_RST_DURING_READ_COMPLETION
+  };
+
+  for (int variant = VARIANT_RST_DURING_SEND_COMPLETION;
+       variant <= VARIANT_RST_DURING_READ_COMPLETION;
+       ++variant) {
+    DelayedSocketData data1(1, reads, arraysize(reads), NULL, 0);
+
+    DelayedSocketData data2(1, reads2, arraysize(reads2), NULL, 0);
+
+    NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                       BoundNetLog(), GetParam(), NULL);
+    helper.AddData(&data1);
+    helper.AddData(&data2);
+    helper.RunPreTestSetup();
+
+    for (int i = 0; i < 2; ++i) {
+      scoped_ptr<HttpNetworkTransaction> trans(
+          new HttpNetworkTransaction(helper.session()));
+
+      TestCompletionCallback callback;
+      int rv = trans->Start(
+          &helper.request(), callback.callback(), BoundNetLog());
+      EXPECT_EQ(ERR_IO_PENDING, rv);
+      // On the second transaction, we trigger the RST.
+      if (i == 1) {
+        if (variant == VARIANT_RST_DURING_READ_COMPLETION) {
+          // Writes to the socket complete asynchronously on SPDY by running
+          // through the message loop.  Complete the write here.
+          MessageLoop::current()->RunUntilIdle();
+        }
+
+        // Now schedule the ERR_CONNECTION_RESET.
+        EXPECT_EQ(3u, data1.read_index());
+        data1.CompleteRead();
+        EXPECT_EQ(4u, data1.read_index());
+      }
+      rv = callback.WaitForResult();
+      EXPECT_EQ(OK, rv);
+
+      const HttpResponseInfo* response = trans->GetResponseInfo();
+      ASSERT_TRUE(response != NULL);
+      EXPECT_TRUE(response->headers != NULL);
+      EXPECT_TRUE(response->was_fetched_via_spdy);
+      std::string response_data;
+      rv = ReadTransaction(trans.get(), &response_data);
+      EXPECT_EQ(OK, rv);
+      EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+      EXPECT_EQ("hello!", response_data);
+    }
+
+    helper.VerifyDataConsumed();
+  }
+}
+
+// Test that turning SPDY on and off works properly.
+TEST_P(SpdyNetworkTransactionSpdy2Test, SpdyOnOffToggle) {
+  net::HttpStreamFactory::set_spdy_enabled(true);
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite spdy_writes[] = { CreateMockWrite(*req) };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead spdy_reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, spdy_reads, arraysize(spdy_reads),
+                         spdy_writes, arraysize(spdy_writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+
+  net::HttpStreamFactory::set_spdy_enabled(false);
+  MockRead http_reads[] = {
+    MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+    MockRead("hello from http"),
+    MockRead(SYNCHRONOUS, OK),
+  };
+  DelayedSocketData data2(1, http_reads, arraysize(http_reads), NULL, 0);
+  NormalSpdyTransactionHelper helper2(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper2.SetSpdyDisabled();
+  helper2.RunToCompletion(&data2);
+  TransactionHelperResult out2 = helper2.output();
+  EXPECT_EQ(OK, out2.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out2.status_line);
+  EXPECT_EQ("hello from http", out2.response_data);
+
+  net::HttpStreamFactory::set_spdy_enabled(true);
+}
+
+// Tests that Basic authentication works over SPDY
+TEST_P(SpdyNetworkTransactionSpdy2Test, SpdyBasicAuth) {
+  net::HttpStreamFactory::set_spdy_enabled(true);
+
+  // The first request will be a bare GET, the second request will be a
+  // GET with an Authorization header.
+  scoped_ptr<SpdyFrame> req_get(
+      ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  const char* const kExtraAuthorizationHeaders[] = {
+    "authorization",
+    "Basic Zm9vOmJhcg==",
+  };
+  scoped_ptr<SpdyFrame> req_get_authorization(
+      ConstructSpdyGet(
+          kExtraAuthorizationHeaders,
+          arraysize(kExtraAuthorizationHeaders) / 2,
+          false, 3, LOWEST));
+  MockWrite spdy_writes[] = {
+    CreateMockWrite(*req_get, 1),
+    CreateMockWrite(*req_get_authorization, 4),
+  };
+
+  // The first response is a 401 authentication challenge, and the second
+  // response will be a 200 response since the second request includes a valid
+  // Authorization header.
+  const char* const kExtraAuthenticationHeaders[] = {
+    "www-authenticate",
+    "Basic realm=\"MyRealm\""
+  };
+  scoped_ptr<SpdyFrame> resp_authentication(
+      ConstructSpdySynReplyError(
+          "401 Authentication Required",
+          kExtraAuthenticationHeaders,
+          arraysize(kExtraAuthenticationHeaders) / 2,
+          1));
+  scoped_ptr<SpdyFrame> body_authentication(
+      ConstructSpdyBodyFrame(1, true));
+  scoped_ptr<SpdyFrame> resp_data(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body_data(ConstructSpdyBodyFrame(3, true));
+  MockRead spdy_reads[] = {
+    CreateMockRead(*resp_authentication, 2),
+    CreateMockRead(*body_authentication, 3),
+    CreateMockRead(*resp_data, 5),
+    CreateMockRead(*body_data, 6),
+    MockRead(ASYNC, 0, 7),
+  };
+
+  OrderedSocketData data(spdy_reads, arraysize(spdy_reads),
+                         spdy_writes, arraysize(spdy_writes));
+  HttpRequestInfo request(CreateGetRequest());
+  BoundNetLog net_log;
+  NormalSpdyTransactionHelper helper(request, net_log, GetParam(), NULL);
+
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+  TestCompletionCallback callback;
+  const int rv_start = trans->Start(&request, callback.callback(), net_log);
+  EXPECT_EQ(ERR_IO_PENDING, rv_start);
+  const int rv_start_complete = callback.WaitForResult();
+  EXPECT_EQ(OK, rv_start_complete);
+
+  // Make sure the response has an auth challenge.
+  const HttpResponseInfo* const response_start = trans->GetResponseInfo();
+  ASSERT_TRUE(response_start != NULL);
+  ASSERT_TRUE(response_start->headers != NULL);
+  EXPECT_EQ(401, response_start->headers->response_code());
+  EXPECT_TRUE(response_start->was_fetched_via_spdy);
+  AuthChallengeInfo* auth_challenge = response_start->auth_challenge.get();
+  ASSERT_TRUE(auth_challenge != NULL);
+  EXPECT_FALSE(auth_challenge->is_proxy);
+  EXPECT_EQ("basic", auth_challenge->scheme);
+  EXPECT_EQ("MyRealm", auth_challenge->realm);
+
+  // Restart with a username/password.
+  AuthCredentials credentials(ASCIIToUTF16("foo"), ASCIIToUTF16("bar"));
+  TestCompletionCallback callback_restart;
+  const int rv_restart = trans->RestartWithAuth(
+      credentials, callback_restart.callback());
+  EXPECT_EQ(ERR_IO_PENDING, rv_restart);
+  const int rv_restart_complete = callback_restart.WaitForResult();
+  EXPECT_EQ(OK, rv_restart_complete);
+  // TODO(cbentzel): This is actually the same response object as before, but
+  // data has changed.
+  const HttpResponseInfo* const response_restart = trans->GetResponseInfo();
+  ASSERT_TRUE(response_restart != NULL);
+  ASSERT_TRUE(response_restart->headers != NULL);
+  EXPECT_EQ(200, response_restart->headers->response_code());
+  EXPECT_TRUE(response_restart->auth_challenge.get() == NULL);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushWithHeaders) {
+  static const unsigned char kPushBodyFrame[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x06,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+  };
+
+  static const char* const kInitialHeaders[] = {
+    "url",
+    "http://www.google.com/foo.dat",
+  };
+  static const char* const kLateHeaders[] = {
+    "hello",
+    "bye",
+    "status",
+    "200",
+    "version",
+    "HTTP/1.1"
+  };
+  scoped_ptr<SpdyFrame>
+    stream2_syn(ConstructSpdyControlFrame(kInitialHeaders,
+                                          arraysize(kInitialHeaders) / 2,
+                                          false,
+                                          2,
+                                          LOWEST,
+                                          SYN_STREAM,
+                                          CONTROL_FLAG_NONE,
+                                          NULL,
+                                          0,
+                                          1));
+  scoped_ptr<SpdyFrame>
+      stream2_headers(ConstructSpdyControlFrame(kLateHeaders,
+                                                arraysize(kLateHeaders) / 2,
+                                                false,
+                                                2,
+                                                LOWEST,
+                                                HEADERS,
+                                                CONTROL_FLAG_NONE,
+                                                NULL,
+                                                0,
+                                                0));
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 2),
+    CreateMockRead(*stream2_syn, 3),
+    CreateMockRead(*stream2_headers, 4),
+    CreateMockRead(*stream1_body, 5, SYNCHRONOUS),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+             arraysize(kPushBodyFrame), 6),
+    MockRead(ASYNC, ERR_IO_PENDING, 7),  // Force a pause
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  std::string expected_push_result("pushed");
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  RunServerPushTest(&data,
+                    &response,
+                    &response2,
+                    expected_push_result);
+
+  // Verify the SYN_REPLY.
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  // Verify the pushed stream.
+  EXPECT_TRUE(response2.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushClaimBeforeHeaders) {
+  // We push a stream and attempt to claim it before the headers come down.
+  static const unsigned char kPushBodyFrame[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x06,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS),
+  };
+
+  static const char* const kInitialHeaders[] = {
+    "url",
+    "http://www.google.com/foo.dat",
+  };
+  static const char* const kLateHeaders[] = {
+    "hello",
+    "bye",
+    "status",
+    "200",
+    "version",
+    "HTTP/1.1"
+  };
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyControlFrame(kInitialHeaders,
+                                            arraysize(kInitialHeaders) / 2,
+                                            false,
+                                            2,
+                                            LOWEST,
+                                            SYN_STREAM,
+                                            CONTROL_FLAG_NONE,
+                                            NULL,
+                                            0,
+                                            1));
+  scoped_ptr<SpdyFrame>
+      stream2_headers(ConstructSpdyControlFrame(kLateHeaders,
+                                                arraysize(kLateHeaders) / 2,
+                                                false,
+                                                2,
+                                                LOWEST,
+                                                HEADERS,
+                                                CONTROL_FLAG_NONE,
+                                                NULL,
+                                                0,
+                                                0));
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 1),
+    CreateMockRead(*stream2_syn, 2),
+    CreateMockRead(*stream1_body, 3),
+    CreateMockRead(*stream2_headers, 4),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+             arraysize(kPushBodyFrame), 5),
+    MockRead(ASYNC, 0, 6),  // EOF
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  std::string expected_push_result("pushed");
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.SetDeterministic();
+  helper.AddDeterministicData(&data);
+  helper.RunPreTestSetup();
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM,
+  // and the body of the primary stream, but before we've received the HEADERS
+  // for the pushed stream.
+  data.SetStop(3);
+
+  // Start the transaction.
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  data.Run();
+  rv = callback.WaitForResult();
+  EXPECT_EQ(0, rv);
+
+  // Request the pushed path.  At this point, we've received the push, but the
+  // headers are not yet complete.
+  scoped_ptr<HttpNetworkTransaction> trans2(
+      new HttpNetworkTransaction(helper.session()));
+  rv = trans2->Start(
+      &CreateGetPushRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  data.RunFor(3);
+  MessageLoop::current()->RunUntilIdle();
+
+  // Read the server push body.
+  std::string result2;
+  ReadResult(trans2.get(), &data, &result2);
+  // Read the response body.
+  std::string result;
+  ReadResult(trans, &data, &result);
+
+  // Verify that the received push data is same as the expected push data.
+  EXPECT_EQ(result2.compare(expected_push_result), 0)
+      << "Received data: "
+      << result2
+      << "||||| Expected data: "
+      << expected_push_result;
+
+  // Verify the SYN_REPLY.
+  // Copy the response info, because trans goes away.
+  response = *trans->GetResponseInfo();
+  response2 = *trans2->GetResponseInfo();
+
+  VerifyStreamsClosed(helper);
+
+  // Verify the SYN_REPLY.
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  // Verify the pushed stream.
+  EXPECT_TRUE(response2.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+
+  // Read the final EOF (which will close the session)
+  data.RunFor(1);
+
+  // Verify that we consumed all test data.
+  EXPECT_TRUE(data.at_read_eof());
+  EXPECT_TRUE(data.at_write_eof());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushWithTwoHeaderFrames) {
+  // We push a stream and attempt to claim it before the headers come down.
+  static const unsigned char kPushBodyFrame[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x06,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS),
+  };
+
+  static const char* const kInitialHeaders[] = {
+    "url",
+    "http://www.google.com/foo.dat",
+  };
+  static const char* const kMiddleHeaders[] = {
+    "hello",
+    "bye",
+  };
+  static const char* const kLateHeaders[] = {
+    "status",
+    "200",
+    "version",
+    "HTTP/1.1"
+  };
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyControlFrame(kInitialHeaders,
+                                            arraysize(kInitialHeaders) / 2,
+                                            false,
+                                            2,
+                                            LOWEST,
+                                            SYN_STREAM,
+                                            CONTROL_FLAG_NONE,
+                                            NULL,
+                                            0,
+                                            1));
+  scoped_ptr<SpdyFrame>
+      stream2_headers1(ConstructSpdyControlFrame(kMiddleHeaders,
+                                                 arraysize(kMiddleHeaders) / 2,
+                                                 false,
+                                                 2,
+                                                 LOWEST,
+                                                 HEADERS,
+                                                 CONTROL_FLAG_NONE,
+                                                 NULL,
+                                                 0,
+                                                 0));
+  scoped_ptr<SpdyFrame>
+      stream2_headers2(ConstructSpdyControlFrame(kLateHeaders,
+                                                 arraysize(kLateHeaders) / 2,
+                                                 false,
+                                                 2,
+                                                 LOWEST,
+                                                 HEADERS,
+                                                 CONTROL_FLAG_NONE,
+                                                 NULL,
+                                                 0,
+                                                 0));
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 1),
+    CreateMockRead(*stream2_syn, 2),
+    CreateMockRead(*stream1_body, 3),
+    CreateMockRead(*stream2_headers1, 4),
+    CreateMockRead(*stream2_headers2, 5),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+             arraysize(kPushBodyFrame), 6),
+    MockRead(ASYNC, 0, 7),  // EOF
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  std::string expected_push_result("pushed");
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.SetDeterministic();
+  helper.AddDeterministicData(&data);
+  helper.RunPreTestSetup();
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM,
+  // the first HEADERS frame, and the body of the primary stream, but before
+  // we've received the final HEADERS for the pushed stream.
+  data.SetStop(4);
+
+  // Start the transaction.
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  data.Run();
+  rv = callback.WaitForResult();
+  EXPECT_EQ(0, rv);
+
+  // Request the pushed path.  At this point, we've received the push, but the
+  // headers are not yet complete.
+  scoped_ptr<HttpNetworkTransaction> trans2(
+      new HttpNetworkTransaction(helper.session()));
+  rv = trans2->Start(
+      &CreateGetPushRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  data.RunFor(3);
+  MessageLoop::current()->RunUntilIdle();
+
+  // Read the server push body.
+  std::string result2;
+  ReadResult(trans2.get(), &data, &result2);
+  // Read the response body.
+  std::string result;
+  ReadResult(trans, &data, &result);
+
+  // Verify that the received push data is same as the expected push data.
+  EXPECT_EQ(result2.compare(expected_push_result), 0)
+      << "Received data: "
+      << result2
+      << "||||| Expected data: "
+      << expected_push_result;
+
+  // Verify the SYN_REPLY.
+  // Copy the response info, because trans goes away.
+  response = *trans->GetResponseInfo();
+  response2 = *trans2->GetResponseInfo();
+
+  VerifyStreamsClosed(helper);
+
+  // Verify the SYN_REPLY.
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  // Verify the pushed stream.
+  EXPECT_TRUE(response2.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+
+  // Verify we got all the headers
+  EXPECT_TRUE(response2.headers->HasHeaderValue(
+      "url",
+      "http://www.google.com/foo.dat"));
+  EXPECT_TRUE(response2.headers->HasHeaderValue("hello", "bye"));
+  EXPECT_TRUE(response2.headers->HasHeaderValue("status", "200"));
+  EXPECT_TRUE(response2.headers->HasHeaderValue("version", "HTTP/1.1"));
+
+  // Read the final EOF (which will close the session)
+  data.RunFor(1);
+
+  // Verify that we consumed all test data.
+  EXPECT_TRUE(data.at_read_eof());
+  EXPECT_TRUE(data.at_write_eof());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushWithNoStatusHeaderFrames) {
+  // We push a stream and attempt to claim it before the headers come down.
+  static const unsigned char kPushBodyFrame[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x06,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS),
+  };
+
+  static const char* const kInitialHeaders[] = {
+    "url",
+    "http://www.google.com/foo.dat",
+  };
+  static const char* const kMiddleHeaders[] = {
+    "hello",
+    "bye",
+  };
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyControlFrame(kInitialHeaders,
+                                            arraysize(kInitialHeaders) / 2,
+                                            false,
+                                            2,
+                                            LOWEST,
+                                            SYN_STREAM,
+                                            CONTROL_FLAG_NONE,
+                                            NULL,
+                                            0,
+                                            1));
+  scoped_ptr<SpdyFrame>
+      stream2_headers1(ConstructSpdyControlFrame(kMiddleHeaders,
+                                                 arraysize(kMiddleHeaders) / 2,
+                                                 false,
+                                                 2,
+                                                 LOWEST,
+                                                 HEADERS,
+                                                 CONTROL_FLAG_NONE,
+                                                 NULL,
+                                                 0,
+                                                 0));
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 1),
+    CreateMockRead(*stream2_syn, 2),
+    CreateMockRead(*stream1_body, 3),
+    CreateMockRead(*stream2_headers1, 4),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+             arraysize(kPushBodyFrame), 5),
+    MockRead(ASYNC, 0, 6),  // EOF
+  };
+
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.SetDeterministic();
+  helper.AddDeterministicData(&data);
+  helper.RunPreTestSetup();
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM,
+  // the first HEADERS frame, and the body of the primary stream, but before
+  // we've received the final HEADERS for the pushed stream.
+  data.SetStop(4);
+
+  // Start the transaction.
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  data.Run();
+  rv = callback.WaitForResult();
+  EXPECT_EQ(0, rv);
+
+  // Request the pushed path.  At this point, we've received the push, but the
+  // headers are not yet complete.
+  scoped_ptr<HttpNetworkTransaction> trans2(
+      new HttpNetworkTransaction(helper.session()));
+  rv = trans2->Start(
+      &CreateGetPushRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  data.RunFor(2);
+  MessageLoop::current()->RunUntilIdle();
+
+  // Read the server push body.
+  std::string result2;
+  ReadResult(trans2.get(), &data, &result2);
+  // Read the response body.
+  std::string result;
+  ReadResult(trans, &data, &result);
+  EXPECT_EQ("hello!", result);
+
+  // Verify that we haven't received any push data.
+  EXPECT_EQ("", result2);
+
+  // Verify the SYN_REPLY.
+  // Copy the response info, because trans goes away.
+  HttpResponseInfo response = *trans->GetResponseInfo();
+  ASSERT_TRUE(trans2->GetResponseInfo() == NULL);
+
+  VerifyStreamsClosed(helper);
+
+  // Verify the SYN_REPLY.
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  // Read the final EOF (which will close the session).
+  data.RunFor(1);
+
+  // Verify that we consumed all test data.
+  EXPECT_TRUE(data.at_read_eof());
+  EXPECT_TRUE(data.at_write_eof());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, SynReplyWithHeaders) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  static const char* const kInitialHeaders[] = {
+    "status",
+    "200 OK",
+    "version",
+    "HTTP/1.1"
+  };
+  static const char* const kLateHeaders[] = {
+    "hello",
+    "bye",
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyControlFrame(kInitialHeaders,
+                                              arraysize(kInitialHeaders) / 2,
+                                              false,
+                                              1,
+                                              LOWEST,
+                                              SYN_REPLY,
+                                              CONTROL_FLAG_NONE,
+                                              NULL,
+                                              0,
+                                              0));
+  scoped_ptr<SpdyFrame>
+      stream1_headers(ConstructSpdyControlFrame(kLateHeaders,
+                                                arraysize(kLateHeaders) / 2,
+                                                false,
+                                                1,
+                                                LOWEST,
+                                                HEADERS,
+                                                CONTROL_FLAG_NONE,
+                                                NULL,
+                                                0,
+                                                0));
+  scoped_ptr<SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply),
+    CreateMockRead(*stream1_headers),
+    CreateMockRead(*stream1_body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, SynReplyWithLateHeaders) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  static const char* const kInitialHeaders[] = {
+    "status",
+    "200 OK",
+    "version",
+    "HTTP/1.1"
+  };
+  static const char* const kLateHeaders[] = {
+    "hello",
+    "bye",
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyControlFrame(kInitialHeaders,
+                                              arraysize(kInitialHeaders) / 2,
+                                              false,
+                                              1,
+                                              LOWEST,
+                                              SYN_REPLY,
+                                              CONTROL_FLAG_NONE,
+                                              NULL,
+                                              0,
+                                              0));
+  scoped_ptr<SpdyFrame>
+      stream1_headers(ConstructSpdyControlFrame(kLateHeaders,
+                                                arraysize(kLateHeaders) / 2,
+                                                false,
+                                                1,
+                                                LOWEST,
+                                                HEADERS,
+                                                CONTROL_FLAG_NONE,
+                                                NULL,
+                                                0,
+                                                0));
+  scoped_ptr<SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> stream1_body2(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply),
+    CreateMockRead(*stream1_body),
+    CreateMockRead(*stream1_headers),
+    CreateMockRead(*stream1_body2),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, SynReplyWithDuplicateLateHeaders) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  static const char* const kInitialHeaders[] = {
+    "status",
+    "200 OK",
+    "version",
+    "HTTP/1.1"
+  };
+  static const char* const kLateHeaders[] = {
+    "status",
+    "500 Server Error",
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyControlFrame(kInitialHeaders,
+                                              arraysize(kInitialHeaders) / 2,
+                                              false,
+                                              1,
+                                              LOWEST,
+                                              SYN_REPLY,
+                                              CONTROL_FLAG_NONE,
+                                              NULL,
+                                              0,
+                                              0));
+  scoped_ptr<SpdyFrame>
+      stream1_headers(ConstructSpdyControlFrame(kLateHeaders,
+                                                arraysize(kLateHeaders) / 2,
+                                                false,
+                                                1,
+                                                LOWEST,
+                                                HEADERS,
+                                                CONTROL_FLAG_NONE,
+                                                NULL,
+                                                0,
+                                                0));
+  scoped_ptr<SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> stream1_body2(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply),
+    CreateMockRead(*stream1_body),
+    CreateMockRead(*stream1_headers),
+    CreateMockRead(*stream1_body2),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, ServerPushCrossOriginCorrectness) {
+  // In this test we want to verify that we can't accidentally push content
+  // which can't be pushed by this content server.
+  // This test assumes that:
+  //   - if we're requesting http://www.foo.com/barbaz
+  //   - the browser has made a connection to "www.foo.com".
+
+  // A list of the URL to fetch, followed by the URL being pushed.
+  static const char* const kTestCases[] = {
+    "http://www.google.com/foo.html",
+    "http://www.google.com:81/foo.js",     // Bad port
+
+    "http://www.google.com/foo.html",
+    "https://www.google.com/foo.js",       // Bad protocol
+
+    "http://www.google.com/foo.html",
+    "ftp://www.google.com/foo.js",         // Invalid Protocol
+
+    "http://www.google.com/foo.html",
+    "http://blat.www.google.com/foo.js",   // Cross subdomain
+
+    "http://www.google.com/foo.html",
+    "http://www.foo.com/foo.js",           // Cross domain
+  };
+
+
+  static const unsigned char kPushBodyFrame[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x06,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+
+  for (size_t index = 0; index < arraysize(kTestCases); index += 2) {
+    const char* url_to_fetch = kTestCases[index];
+    const char* url_to_push = kTestCases[index + 1];
+
+    scoped_ptr<SpdyFrame>
+        stream1_syn(ConstructSpdyGet(url_to_fetch, false, 1, LOWEST));
+    scoped_ptr<SpdyFrame>
+        stream1_body(ConstructSpdyBodyFrame(1, true));
+    scoped_ptr<SpdyFrame> push_rst(
+        ConstructSpdyRstStream(2, REFUSED_STREAM));
+    MockWrite writes[] = {
+      CreateMockWrite(*stream1_syn, 1),
+      CreateMockWrite(*push_rst, 4),
+    };
+
+    scoped_ptr<SpdyFrame>
+        stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+    scoped_ptr<SpdyFrame>
+        stream2_syn(ConstructSpdyPush(NULL,
+                                      0,
+                                      2,
+                                      1,
+                                      url_to_push));
+    scoped_ptr<SpdyFrame> rst(
+        ConstructSpdyRstStream(2, CANCEL));
+
+    MockRead reads[] = {
+      CreateMockRead(*stream1_reply, 2),
+      CreateMockRead(*stream2_syn, 3),
+      CreateMockRead(*stream1_body, 5, SYNCHRONOUS),
+      MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+               arraysize(kPushBodyFrame), 6),
+      MockRead(ASYNC, ERR_IO_PENDING, 7),  // Force a pause
+    };
+
+    HttpResponseInfo response;
+    OrderedSocketData data(reads, arraysize(reads),
+                           writes, arraysize(writes));
+
+    HttpRequestInfo request;
+    request.method = "GET";
+    request.url = GURL(url_to_fetch);
+    request.load_flags = 0;
+
+    // Enable cross-origin push. Since we are not using a proxy, this should
+    // not actually enable cross-origin SPDY push.
+    scoped_ptr<SpdySessionDependencies> session_deps(
+        new SpdySessionDependencies());
+    session_deps->trusted_spdy_proxy = "123.45.67.89:8080";
+    NormalSpdyTransactionHelper helper(request,
+                                       BoundNetLog(), GetParam(),
+                                       session_deps.release());
+    helper.RunPreTestSetup();
+    helper.AddData(&data);
+
+    HttpNetworkTransaction* trans = helper.trans();
+
+    // Start the transaction with basic parameters.
+    TestCompletionCallback callback;
+
+    int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+    EXPECT_EQ(ERR_IO_PENDING, rv);
+    rv = callback.WaitForResult();
+
+    // Read the response body.
+    std::string result;
+    ReadResult(trans, &data, &result);
+
+    // Verify that we consumed all test data.
+    EXPECT_TRUE(data.at_read_eof());
+    EXPECT_TRUE(data.at_write_eof());
+
+    // Verify the SYN_REPLY.
+    // Copy the response info, because trans goes away.
+    response = *trans->GetResponseInfo();
+
+    VerifyStreamsClosed(helper);
+
+    // Verify the SYN_REPLY.
+    EXPECT_TRUE(response.headers != NULL);
+    EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+  }
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, RetryAfterRefused) {
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+  MockWrite writes[] = {
+    CreateMockWrite(*req, 1),
+    CreateMockWrite(*req2, 3),
+  };
+
+  scoped_ptr<SpdyFrame> refused(
+      ConstructSpdyRstStream(1, REFUSED_STREAM));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(3, true));
+  MockRead reads[] = {
+    CreateMockRead(*refused, 2),
+    CreateMockRead(*resp, 4),
+    CreateMockRead(*body, 5),
+    MockRead(ASYNC, 0, 6)  // EOF
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  // Start the transaction with basic parameters.
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  rv = callback.WaitForResult();
+  EXPECT_EQ(OK, rv);
+
+  // Verify that we consumed all test data.
+  EXPECT_TRUE(data.at_read_eof()) << "Read count: "
+                                   << data.read_count()
+                                   << " Read index: "
+                                   << data.read_index();
+  EXPECT_TRUE(data.at_write_eof()) << "Write count: "
+                                    << data.write_count()
+                                    << " Write index: "
+                                    << data.write_index();
+
+  // Verify the SYN_REPLY.
+  HttpResponseInfo response = *trans->GetResponseInfo();
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy2Test, OutOfOrderSynStream) {
+  // This first request will start to establish the SpdySession.
+  // Then we will start the second (MEDIUM priority) and then third
+  // (HIGHEST priority) request in such a way that the third will actually
+  // start before the second, causing the second to be numbered differently
+  // than the order they were created.
+  scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, HIGHEST));
+  scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, MEDIUM));
+  MockWrite writes[] = {
+    CreateMockWrite(*req1, 0),
+    CreateMockWrite(*req2, 3),
+    CreateMockWrite(*req3, 4),
+  };
+
+  scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body1(ConstructSpdyBodyFrame(1, true));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, true));
+  scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
+  scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(5, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp1, 1),
+    CreateMockRead(*body1, 2),
+    CreateMockRead(*resp2, 5),
+    CreateMockRead(*body2, 6),
+    CreateMockRead(*resp3, 7),
+    CreateMockRead(*body3, 8),
+    MockRead(ASYNC, 0, 9)  // EOF
+  };
+
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.SetDeterministic();
+  helper.RunPreTestSetup();
+  helper.AddDeterministicData(&data);
+
+  // Start the first transaction to set up the SpdySession
+  HttpNetworkTransaction* trans = helper.trans();
+  TestCompletionCallback callback;
+  HttpRequestInfo info1 = CreateGetRequest();
+  info1.priority = LOWEST;
+  int rv = trans->Start(&info1, callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  // Run the message loop, but do not allow the write to complete.
+  // This leaves the SpdySession with a write pending, which prevents
+  // SpdySession from attempting subsequent writes until this write completes.
+  MessageLoop::current()->RunUntilIdle();
+
+  // Now, start both new transactions
+  HttpRequestInfo info2 = CreateGetRequest();
+  info2.priority = MEDIUM;
+  TestCompletionCallback callback2;
+    scoped_ptr<HttpNetworkTransaction> trans2(
+        new HttpNetworkTransaction(helper.session()));
+  rv = trans2->Start(&info2, callback2.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  MessageLoop::current()->RunUntilIdle();
+
+  HttpRequestInfo info3 = CreateGetRequest();
+  info3.priority = HIGHEST;
+  TestCompletionCallback callback3;
+  scoped_ptr<HttpNetworkTransaction> trans3(
+      new HttpNetworkTransaction(helper.session()));
+  rv = trans3->Start(&info3, callback3.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  MessageLoop::current()->RunUntilIdle();
+
+  // We now have two SYN_STREAM frames queued up which will be
+  // dequeued only once the first write completes, which we
+  // now allow to happen.
+  data.RunFor(2);
+  EXPECT_EQ(OK, callback.WaitForResult());
+
+  // And now we can allow everything else to run to completion.
+  data.SetStop(10);
+  data.Run();
+  EXPECT_EQ(OK, callback2.WaitForResult());
+  EXPECT_EQ(OK, callback3.WaitForResult());
+
+  helper.VerifyDataConsumed();
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_network_transaction_spdy3_unittest.cc b/src/net/spdy/spdy_network_transaction_spdy3_unittest.cc
new file mode 100644
index 0000000..197ba7d
--- /dev/null
+++ b/src/net/spdy/spdy_network_transaction_spdy3_unittest.cc
@@ -0,0 +1,6364 @@
+// Copyright (c) 2012 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/http/http_network_transaction.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_vector.h"
+#include "net/base/auth.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_data_stream.h"
+#include "net/base/upload_file_element_reader.h"
+#include "net/http/http_network_session_peer.h"
+#include "net/http/http_transaction_unittest.h"
+#include "net/socket/client_socket_pool_base.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_http_stream.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/spdy/spdy_test_util_spdy3.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/platform_test.h"
+
+using namespace net::test_spdy3;
+
+//-----------------------------------------------------------------------------
+
+namespace net {
+
+enum SpdyNetworkTransactionSpdy3TestTypes {
+  SPDYNPN,
+  SPDYNOSSL,
+  SPDYSSL,
+};
+
+class SpdyNetworkTransactionSpdy3Test
+    : public ::testing::TestWithParam<SpdyNetworkTransactionSpdy3TestTypes> {
+ protected:
+
+  virtual void SetUp() {
+    google_get_request_initialized_ = false;
+    google_post_request_initialized_ = false;
+    google_chunked_post_request_initialized_ = false;
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+  }
+
+  virtual void TearDown() {
+    UploadDataStream::ResetMergeChunks();
+    // Empty the current queue.
+    MessageLoop::current()->RunUntilIdle();
+  }
+
+  void set_merge_chunks(bool merge) {
+    UploadDataStream::set_merge_chunks(merge);
+  }
+
+  struct TransactionHelperResult {
+    int rv;
+    std::string status_line;
+    std::string response_data;
+    HttpResponseInfo response_info;
+  };
+
+  // A helper class that handles all the initial npn/ssl setup.
+  class NormalSpdyTransactionHelper {
+   public:
+    NormalSpdyTransactionHelper(const HttpRequestInfo& request,
+                                const BoundNetLog& log,
+                                SpdyNetworkTransactionSpdy3TestTypes test_type,
+                                SpdySessionDependencies* session_deps)
+        : request_(request),
+          session_deps_(session_deps == NULL ?
+                        new SpdySessionDependencies() : session_deps),
+          session_(SpdySessionDependencies::SpdyCreateSession(
+                       session_deps_.get())),
+          log_(log),
+          test_type_(test_type),
+          deterministic_(false),
+          spdy_enabled_(true) {
+      switch (test_type_) {
+        case SPDYNOSSL:
+        case SPDYSSL:
+          port_ = 80;
+          break;
+        case SPDYNPN:
+          port_ = 443;
+          break;
+        default:
+          NOTREACHED();
+      }
+    }
+
+    ~NormalSpdyTransactionHelper() {
+      // Any test which doesn't close the socket by sending it an EOF will
+      // have a valid session left open, which leaks the entire session pool.
+      // This is just fine - in fact, some of our tests intentionally do this
+      // so that we can check consistency of the SpdySessionPool as the test
+      // finishes.  If we had put an EOF on the socket, the SpdySession would
+      // have closed and we wouldn't be able to check the consistency.
+
+      // Forcefully close existing sessions here.
+      session()->spdy_session_pool()->CloseAllSessions();
+    }
+
+    void SetDeterministic() {
+      session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic(
+          session_deps_.get());
+      deterministic_ = true;
+    }
+
+    void SetSpdyDisabled() {
+      spdy_enabled_ = false;
+      port_ = 80;
+    }
+
+    void RunPreTestSetup() {
+      if (!session_deps_.get())
+        session_deps_.reset(new SpdySessionDependencies());
+      if (!session_.get())
+        session_ = SpdySessionDependencies::SpdyCreateSession(
+            session_deps_.get());
+      HttpStreamFactory::set_use_alternate_protocols(false);
+      HttpStreamFactory::set_force_spdy_over_ssl(false);
+      HttpStreamFactory::set_force_spdy_always(false);
+
+      std::vector<std::string> next_protos;
+      next_protos.push_back("http/1.1");
+      next_protos.push_back("spdy/2");
+      next_protos.push_back("spdy/3");
+
+      switch (test_type_) {
+        case SPDYNPN:
+          session_->http_server_properties()->SetAlternateProtocol(
+              HostPortPair("www.google.com", 80), 443,
+              NPN_SPDY_3);
+          HttpStreamFactory::set_use_alternate_protocols(true);
+          HttpStreamFactory::SetNextProtos(next_protos);
+          break;
+        case SPDYNOSSL:
+          HttpStreamFactory::set_force_spdy_over_ssl(false);
+          HttpStreamFactory::set_force_spdy_always(true);
+          break;
+        case SPDYSSL:
+          HttpStreamFactory::set_force_spdy_over_ssl(true);
+          HttpStreamFactory::set_force_spdy_always(true);
+          break;
+        default:
+          NOTREACHED();
+      }
+
+      // We're now ready to use SSL-npn SPDY.
+      trans_.reset(new HttpNetworkTransaction(session_));
+    }
+
+    // Start the transaction, read some data, finish.
+    void RunDefaultTest() {
+      output_.rv = trans_->Start(&request_, callback.callback(), log_);
+
+      // We expect an IO Pending or some sort of error.
+      EXPECT_LT(output_.rv, 0);
+      if (output_.rv != ERR_IO_PENDING)
+        return;
+
+      output_.rv = callback.WaitForResult();
+      if (output_.rv != OK) {
+        session_->spdy_session_pool()->CloseCurrentSessions(net::ERR_ABORTED);
+        return;
+      }
+
+      // Verify responses.
+      const HttpResponseInfo* response = trans_->GetResponseInfo();
+      ASSERT_TRUE(response != NULL);
+      ASSERT_TRUE(response->headers != NULL);
+      EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+      EXPECT_EQ(spdy_enabled_, response->was_fetched_via_spdy);
+      if (test_type_ == SPDYNPN && spdy_enabled_) {
+        EXPECT_TRUE(response->was_npn_negotiated);
+      } else {
+        EXPECT_TRUE(!response->was_npn_negotiated);
+      }
+      // If SPDY is not enabled, a HTTP request should not be diverted
+      // over a SSL session.
+      if (!spdy_enabled_) {
+        EXPECT_EQ(request_.url.SchemeIs("https"),
+                  response->was_npn_negotiated);
+      }
+      EXPECT_EQ("127.0.0.1", response->socket_address.host());
+      EXPECT_EQ(port_, response->socket_address.port());
+      output_.status_line = response->headers->GetStatusLine();
+      output_.response_info = *response;  // Make a copy so we can verify.
+      output_.rv = ReadTransaction(trans_.get(), &output_.response_data);
+    }
+
+    // Most tests will want to call this function. In particular, the MockReads
+    // should end with an empty read, and that read needs to be processed to
+    // ensure proper deletion of the spdy_session_pool.
+    void VerifyDataConsumed() {
+      for (DataVector::iterator it = data_vector_.begin();
+          it != data_vector_.end(); ++it) {
+        EXPECT_TRUE((*it)->at_read_eof()) << "Read count: "
+                                          << (*it)->read_count()
+                                          << " Read index: "
+                                          << (*it)->read_index();
+        EXPECT_TRUE((*it)->at_write_eof()) << "Write count: "
+                                           << (*it)->write_count()
+                                           << " Write index: "
+                                           << (*it)->write_index();
+      }
+    }
+
+    // Occasionally a test will expect to error out before certain reads are
+    // processed. In that case we want to explicitly ensure that the reads were
+    // not processed.
+    void VerifyDataNotConsumed() {
+      for (DataVector::iterator it = data_vector_.begin();
+          it != data_vector_.end(); ++it) {
+        EXPECT_TRUE(!(*it)->at_read_eof()) << "Read count: "
+                                           << (*it)->read_count()
+                                           << " Read index: "
+                                           << (*it)->read_index();
+        EXPECT_TRUE(!(*it)->at_write_eof()) << "Write count: "
+                                            << (*it)->write_count()
+                                            << " Write index: "
+                                            << (*it)->write_index();
+      }
+    }
+
+    void RunToCompletion(StaticSocketDataProvider* data) {
+      RunPreTestSetup();
+      AddData(data);
+      RunDefaultTest();
+      VerifyDataConsumed();
+    }
+
+    void AddData(StaticSocketDataProvider* data) {
+      DCHECK(!deterministic_);
+      data_vector_.push_back(data);
+      SSLSocketDataProvider* ssl_provider =
+          new SSLSocketDataProvider(ASYNC, OK);
+      if (test_type_ == SPDYNPN)
+        ssl_provider->SetNextProto(kProtoSPDY3);
+
+      ssl_vector_.push_back(ssl_provider);
+      if (test_type_ == SPDYNPN || test_type_ == SPDYSSL)
+        session_deps_->socket_factory->AddSSLSocketDataProvider(ssl_provider);
+
+      session_deps_->socket_factory->AddSocketDataProvider(data);
+      if (test_type_ == SPDYNPN) {
+        MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING);
+        StaticSocketDataProvider* hanging_non_alternate_protocol_socket =
+                new StaticSocketDataProvider(NULL, 0, NULL, 0);
+        hanging_non_alternate_protocol_socket->set_connect_data(
+            never_finishing_connect);
+        session_deps_->socket_factory->AddSocketDataProvider(
+            hanging_non_alternate_protocol_socket);
+        alternate_vector_.push_back(hanging_non_alternate_protocol_socket);
+      }
+    }
+
+    void AddDeterministicData(DeterministicSocketData* data) {
+      DCHECK(deterministic_);
+      data_vector_.push_back(data);
+      SSLSocketDataProvider* ssl_provider =
+          new SSLSocketDataProvider(ASYNC, OK);
+      if (test_type_ == SPDYNPN)
+        ssl_provider->SetNextProto(kProtoSPDY3);
+
+      ssl_vector_.push_back(ssl_provider);
+      if (test_type_ == SPDYNPN || test_type_ == SPDYSSL) {
+        session_deps_->deterministic_socket_factory->
+            AddSSLSocketDataProvider(ssl_provider);
+      }
+      session_deps_->deterministic_socket_factory->AddSocketDataProvider(data);
+      if (test_type_ == SPDYNPN) {
+        MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING);
+        DeterministicSocketData* hanging_non_alternate_protocol_socket =
+            new DeterministicSocketData(NULL, 0, NULL, 0);
+        hanging_non_alternate_protocol_socket->set_connect_data(
+            never_finishing_connect);
+        session_deps_->deterministic_socket_factory->AddSocketDataProvider(
+            hanging_non_alternate_protocol_socket);
+        alternate_deterministic_vector_.push_back(
+            hanging_non_alternate_protocol_socket);
+      }
+    }
+
+    void SetSession(const scoped_refptr<HttpNetworkSession>& session) {
+      session_ = session;
+    }
+    HttpNetworkTransaction* trans() { return trans_.get(); }
+    void ResetTrans() { trans_.reset(); }
+    TransactionHelperResult& output() { return output_; }
+    const HttpRequestInfo& request() const { return request_; }
+    const scoped_refptr<HttpNetworkSession>& session() const {
+      return session_;
+    }
+    scoped_ptr<SpdySessionDependencies>& session_deps() {
+      return session_deps_;
+    }
+    int port() const { return port_; }
+    SpdyNetworkTransactionSpdy3TestTypes test_type() const {
+      return test_type_;
+    }
+
+   private:
+    typedef std::vector<StaticSocketDataProvider*> DataVector;
+    typedef ScopedVector<SSLSocketDataProvider> SSLVector;
+    typedef ScopedVector<StaticSocketDataProvider> AlternateVector;
+    typedef ScopedVector<DeterministicSocketData> AlternateDeterministicVector;
+    HttpRequestInfo request_;
+    scoped_ptr<SpdySessionDependencies> session_deps_;
+    scoped_refptr<HttpNetworkSession> session_;
+    TransactionHelperResult output_;
+    scoped_ptr<StaticSocketDataProvider> first_transaction_;
+    SSLVector ssl_vector_;
+    TestCompletionCallback callback;
+    scoped_ptr<HttpNetworkTransaction> trans_;
+    scoped_ptr<HttpNetworkTransaction> trans_http_;
+    DataVector data_vector_;
+    AlternateVector alternate_vector_;
+    AlternateDeterministicVector alternate_deterministic_vector_;
+    const BoundNetLog& log_;
+    SpdyNetworkTransactionSpdy3TestTypes test_type_;
+    int port_;
+    bool deterministic_;
+    bool spdy_enabled_;
+  };
+
+  void ConnectStatusHelperWithExpectedStatus(const MockRead& status,
+                                             int expected_status);
+
+  void ConnectStatusHelper(const MockRead& status);
+
+  const HttpRequestInfo& CreateGetPushRequest() {
+    google_get_push_request_.method = "GET";
+    google_get_push_request_.url = GURL("http://www.google.com/foo.dat");
+    google_get_push_request_.load_flags = 0;
+    return google_get_push_request_;
+  }
+
+  const HttpRequestInfo& CreateGetRequest() {
+    if (!google_get_request_initialized_) {
+      google_get_request_.method = "GET";
+      google_get_request_.url = GURL(kDefaultURL);
+      google_get_request_.load_flags = 0;
+      google_get_request_initialized_ = true;
+    }
+    return google_get_request_;
+  }
+
+  const HttpRequestInfo& CreateGetRequestWithUserAgent() {
+    if (!google_get_request_initialized_) {
+      google_get_request_.method = "GET";
+      google_get_request_.url = GURL(kDefaultURL);
+      google_get_request_.load_flags = 0;
+      google_get_request_.extra_headers.SetHeader("User-Agent", "Chrome");
+      google_get_request_initialized_ = true;
+    }
+    return google_get_request_;
+  }
+
+  const HttpRequestInfo& CreatePostRequest() {
+    if (!google_post_request_initialized_) {
+      ScopedVector<UploadElementReader> element_readers;
+      element_readers.push_back(
+          new UploadBytesElementReader(kUploadData, kUploadDataSize));
+      upload_data_stream_.reset(new UploadDataStream(&element_readers, 0));
+
+      google_post_request_.method = "POST";
+      google_post_request_.url = GURL(kDefaultURL);
+      google_post_request_.upload_data_stream = upload_data_stream_.get();
+      google_post_request_initialized_ = true;
+    }
+    return google_post_request_;
+  }
+
+  const HttpRequestInfo& CreateFilePostRequest() {
+    if (!google_post_request_initialized_) {
+      FilePath file_path;
+      CHECK(file_util::CreateTemporaryFileInDir(temp_dir_.path(), &file_path));
+      CHECK_EQ(static_cast<int>(kUploadDataSize),
+               file_util::WriteFile(file_path, kUploadData, kUploadDataSize));
+
+      ScopedVector<UploadElementReader> element_readers;
+      element_readers.push_back(new UploadFileElementReader(
+          file_path, 0, kUploadDataSize, base::Time()));
+      upload_data_stream_.reset(new UploadDataStream(&element_readers, 0));
+
+      google_post_request_.method = "POST";
+      google_post_request_.url = GURL(kDefaultURL);
+      google_post_request_.upload_data_stream = upload_data_stream_.get();
+      google_post_request_initialized_ = true;
+    }
+    return google_post_request_;
+  }
+
+  const HttpRequestInfo& CreateComplexPostRequest() {
+    if (!google_post_request_initialized_) {
+      const int kFileRangeOffset = 1;
+      const int kFileRangeLength = 3;
+      CHECK_LT(kFileRangeOffset + kFileRangeLength, kUploadDataSize);
+
+      FilePath file_path;
+      CHECK(file_util::CreateTemporaryFileInDir(temp_dir_.path(), &file_path));
+      CHECK_EQ(static_cast<int>(kUploadDataSize),
+               file_util::WriteFile(file_path, kUploadData, kUploadDataSize));
+
+      ScopedVector<UploadElementReader> element_readers;
+      element_readers.push_back(
+          new UploadBytesElementReader(kUploadData, kFileRangeOffset));
+      element_readers.push_back(new UploadFileElementReader(
+          file_path, kFileRangeOffset, kFileRangeLength, base::Time()));
+      element_readers.push_back(new UploadBytesElementReader(
+          kUploadData + kFileRangeOffset + kFileRangeLength,
+          kUploadDataSize - (kFileRangeOffset + kFileRangeLength)));
+      upload_data_stream_.reset(new UploadDataStream(&element_readers, 0));
+
+      google_post_request_.method = "POST";
+      google_post_request_.url = GURL(kDefaultURL);
+      google_post_request_.upload_data_stream = upload_data_stream_.get();
+      google_post_request_initialized_ = true;
+    }
+    return google_post_request_;
+  }
+
+  const HttpRequestInfo& CreateChunkedPostRequest() {
+    if (!google_chunked_post_request_initialized_) {
+      upload_data_stream_.reset(
+          new UploadDataStream(UploadDataStream::CHUNKED, 0));
+      upload_data_stream_->AppendChunk(kUploadData, kUploadDataSize, false);
+      upload_data_stream_->AppendChunk(kUploadData, kUploadDataSize, true);
+
+      google_chunked_post_request_.method = "POST";
+      google_chunked_post_request_.url = GURL(kDefaultURL);
+      google_chunked_post_request_.upload_data_stream =
+          upload_data_stream_.get();
+      google_chunked_post_request_initialized_ = true;
+    }
+    return google_chunked_post_request_;
+  }
+
+  // Read the result of a particular transaction, knowing that we've got
+  // multiple transactions in the read pipeline; so as we read, we may have
+  // to skip over data destined for other transactions while we consume
+  // the data for |trans|.
+  int ReadResult(HttpNetworkTransaction* trans,
+                 StaticSocketDataProvider* data,
+                 std::string* result) {
+    const int kSize = 3000;
+
+    int bytes_read = 0;
+    scoped_refptr<net::IOBufferWithSize> buf(new net::IOBufferWithSize(kSize));
+    TestCompletionCallback callback;
+    while (true) {
+      int rv = trans->Read(buf, kSize, callback.callback());
+      if (rv == ERR_IO_PENDING) {
+        // Multiple transactions may be in the data set.  Keep pulling off
+        // reads until we complete our callback.
+        while (!callback.have_result()) {
+          data->CompleteRead();
+          MessageLoop::current()->RunUntilIdle();
+        }
+        rv = callback.WaitForResult();
+      } else if (rv <= 0) {
+        break;
+      }
+      result->append(buf->data(), rv);
+      bytes_read += rv;
+    }
+    return bytes_read;
+  }
+
+  void VerifyStreamsClosed(const NormalSpdyTransactionHelper& helper) {
+    // This lengthy block is reaching into the pool to dig out the active
+    // session.  Once we have the session, we verify that the streams are
+    // all closed and not leaked at this point.
+    const GURL& url = helper.request().url;
+    int port = helper.test_type() == SPDYNPN ? 443 : 80;
+    HostPortPair host_port_pair(url.host(), port);
+    HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+    BoundNetLog log;
+    const scoped_refptr<HttpNetworkSession>& session = helper.session();
+    SpdySessionPool* pool(session->spdy_session_pool());
+    EXPECT_TRUE(pool->HasSession(pair));
+    scoped_refptr<SpdySession> spdy_session(pool->Get(pair, log));
+    ASSERT_TRUE(spdy_session.get() != NULL);
+    EXPECT_EQ(0u, spdy_session->num_active_streams());
+    EXPECT_EQ(0u, spdy_session->num_unclaimed_pushed_streams());
+  }
+
+  void RunServerPushTest(OrderedSocketData* data,
+                         HttpResponseInfo* response,
+                         HttpResponseInfo* push_response,
+                         std::string& expected) {
+    NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                       BoundNetLog(), GetParam(), NULL);
+    helper.RunPreTestSetup();
+    helper.AddData(data);
+
+    HttpNetworkTransaction* trans = helper.trans();
+
+    // Start the transaction with basic parameters.
+    TestCompletionCallback callback;
+    int rv = trans->Start(
+        &CreateGetRequest(), callback.callback(), BoundNetLog());
+    EXPECT_EQ(ERR_IO_PENDING, rv);
+    rv = callback.WaitForResult();
+
+    // Request the pushed path.
+    scoped_ptr<HttpNetworkTransaction> trans2(
+        new HttpNetworkTransaction(helper.session()));
+    rv = trans2->Start(
+        &CreateGetPushRequest(), callback.callback(), BoundNetLog());
+    EXPECT_EQ(ERR_IO_PENDING, rv);
+    MessageLoop::current()->RunUntilIdle();
+
+    // The data for the pushed path may be coming in more than 1 packet. Compile
+    // the results into a single string.
+
+    // Read the server push body.
+    std::string result2;
+    ReadResult(trans2.get(), data, &result2);
+    // Read the response body.
+    std::string result;
+    ReadResult(trans, data, &result);
+
+    // Verify that we consumed all test data.
+    EXPECT_TRUE(data->at_read_eof());
+    EXPECT_TRUE(data->at_write_eof());
+
+    // Verify that the received push data is same as the expected push data.
+    EXPECT_EQ(result2.compare(expected), 0) << "Received data: "
+                                            << result2
+                                            << "||||| Expected data: "
+                                            << expected;
+
+    // Verify the SYN_REPLY.
+    // Copy the response info, because trans goes away.
+    *response = *trans->GetResponseInfo();
+    *push_response = *trans2->GetResponseInfo();
+
+    VerifyStreamsClosed(helper);
+  }
+
+  static void DeleteSessionCallback(NormalSpdyTransactionHelper* helper,
+                                    int result) {
+    helper->ResetTrans();
+  }
+
+  static void StartTransactionCallback(
+      const scoped_refptr<HttpNetworkSession>& session,
+      int result) {
+    scoped_ptr<HttpNetworkTransaction> trans(
+        new HttpNetworkTransaction(session));
+    TestCompletionCallback callback;
+    HttpRequestInfo request;
+    request.method = "GET";
+    request.url = GURL("http://www.google.com/");
+    request.load_flags = 0;
+    int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+    EXPECT_EQ(ERR_IO_PENDING, rv);
+    callback.WaitForResult();
+  }
+
+ private:
+  scoped_ptr<UploadDataStream> upload_data_stream_;
+  bool google_get_request_initialized_;
+  bool google_post_request_initialized_;
+  bool google_chunked_post_request_initialized_;
+  HttpRequestInfo google_get_request_;
+  HttpRequestInfo google_post_request_;
+  HttpRequestInfo google_chunked_post_request_;
+  HttpRequestInfo google_get_push_request_;
+  base::ScopedTempDir temp_dir_;
+};
+
+//-----------------------------------------------------------------------------
+// All tests are run with three different connection types: SPDY after NPN
+// negotiation, SPDY without SSL, and SPDY with SSL.
+INSTANTIATE_TEST_CASE_P(Spdy,
+                        SpdyNetworkTransactionSpdy3Test,
+                        ::testing::Values(SPDYNOSSL, SPDYSSL, SPDYNPN));
+
+
+// Verify HttpNetworkTransaction constructor.
+TEST_P(SpdyNetworkTransactionSpdy3Test, Constructor) {
+  SpdySessionDependencies session_deps;
+  scoped_refptr<HttpNetworkSession> session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+  scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session));
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, Get) {
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, GetAtEachPriority) {
+  for (RequestPriority p = MINIMUM_PRIORITY; p < NUM_PRIORITIES;
+       p = RequestPriority(p + 1)) {
+    // Construct the request.
+    scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, p));
+    MockWrite writes[] = { CreateMockWrite(*req) };
+
+    const int spdy_prio = reinterpret_cast<SpdySynStreamControlFrame*>(
+        req.get())->priority();
+    // this repeats the RequestPriority-->SpdyPriority mapping from
+    // SpdyFramer::ConvertRequestPriorityToSpdyPriority to make
+    // sure it's being done right.
+    switch(p) {
+      case HIGHEST:
+        EXPECT_EQ(0, spdy_prio);
+        break;
+      case MEDIUM:
+        EXPECT_EQ(1, spdy_prio);
+        break;
+      case LOW:
+        EXPECT_EQ(2, spdy_prio);
+        break;
+      case LOWEST:
+        EXPECT_EQ(3, spdy_prio);
+        break;
+      case IDLE:
+        EXPECT_EQ(4, spdy_prio);
+        break;
+      default:
+        FAIL();
+    }
+
+    scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+    scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+    MockRead reads[] = {
+      CreateMockRead(*resp),
+      CreateMockRead(*body),
+      MockRead(ASYNC, 0, 0)  // EOF
+    };
+
+    DelayedSocketData data(1, reads, arraysize(reads),
+                              writes, arraysize(writes));
+    HttpRequestInfo http_req = CreateGetRequest();
+    http_req.priority = p;
+
+    NormalSpdyTransactionHelper helper(http_req, BoundNetLog(),
+                                       GetParam(), NULL);
+    helper.RunToCompletion(&data);
+    TransactionHelperResult out = helper.output();
+    EXPECT_EQ(OK, out.rv);
+    EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+    EXPECT_EQ("hello!", out.response_data);
+  }
+}
+
+// Start three gets simultaniously; making sure that multiplexed
+// streams work properly.
+
+// This can't use the TransactionHelper method, since it only
+// handles a single transaction, and finishes them as soon
+// as it launches them.
+
+// TODO(gavinp): create a working generalized TransactionHelper that
+// can allow multiple streams in flight.
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ThreeGets) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+  scoped_ptr<SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+  scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, LOWEST));
+  scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
+  scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(5, false));
+  scoped_ptr<SpdyFrame> fbody3(ConstructSpdyBodyFrame(5, true));
+
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*req2),
+    CreateMockWrite(*req3),
+  };
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1),
+    CreateMockRead(*body),
+    CreateMockRead(*resp2, 4),
+    CreateMockRead(*body2),
+    CreateMockRead(*resp3, 7),
+    CreateMockRead(*body3),
+
+    CreateMockRead(*fbody),
+    CreateMockRead(*fbody2),
+    CreateMockRead(*fbody3),
+
+    MockRead(ASYNC, 0, 0),  // EOF
+  };
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  OrderedSocketData data_placeholder(NULL, 0, NULL, 0);
+
+  BoundNetLog log;
+  TransactionHelperResult out;
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  // We require placeholder data because three get requests are sent out, so
+  // there needs to be three sets of SSL connection data.
+  helper.AddData(&data_placeholder);
+  helper.AddData(&data_placeholder);
+  scoped_ptr<HttpNetworkTransaction> trans1(
+      new HttpNetworkTransaction(helper.session()));
+  scoped_ptr<HttpNetworkTransaction> trans2(
+      new HttpNetworkTransaction(helper.session()));
+  scoped_ptr<HttpNetworkTransaction> trans3(
+      new HttpNetworkTransaction(helper.session()));
+
+  TestCompletionCallback callback1;
+  TestCompletionCallback callback2;
+  TestCompletionCallback callback3;
+
+  HttpRequestInfo httpreq1 = CreateGetRequest();
+  HttpRequestInfo httpreq2 = CreateGetRequest();
+  HttpRequestInfo httpreq3 = CreateGetRequest();
+
+  out.rv = trans1->Start(&httpreq1, callback1.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+  out.rv = trans2->Start(&httpreq2, callback2.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+  out.rv = trans3->Start(&httpreq3, callback3.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+
+  out.rv = callback1.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+  out.rv = callback3.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+
+  const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+  EXPECT_TRUE(response1->headers != NULL);
+  EXPECT_TRUE(response1->was_fetched_via_spdy);
+  out.status_line = response1->headers->GetStatusLine();
+  out.response_info = *response1;
+
+  trans2->GetResponseInfo();
+
+  out.rv = ReadTransaction(trans1.get(), &out.response_data);
+  helper.VerifyDataConsumed();
+  EXPECT_EQ(OK, out.rv);
+
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, TwoGetsLateBinding) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+  scoped_ptr<SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*req2),
+  };
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1),
+    CreateMockRead(*body),
+    CreateMockRead(*resp2, 4),
+    CreateMockRead(*body2),
+    CreateMockRead(*fbody),
+    CreateMockRead(*fbody2),
+    MockRead(ASYNC, 0, 0),  // EOF
+  };
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  MockConnect never_finishing_connect(SYNCHRONOUS, ERR_IO_PENDING);
+
+  OrderedSocketData data_placeholder(NULL, 0, NULL, 0);
+  data_placeholder.set_connect_data(never_finishing_connect);
+
+  BoundNetLog log;
+  TransactionHelperResult out;
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  // We require placeholder data because two get requests are sent out, so
+  // there needs to be two sets of SSL connection data.
+  helper.AddData(&data_placeholder);
+  scoped_ptr<HttpNetworkTransaction> trans1(
+      new HttpNetworkTransaction(helper.session()));
+  scoped_ptr<HttpNetworkTransaction> trans2(
+      new HttpNetworkTransaction(helper.session()));
+
+  TestCompletionCallback callback1;
+  TestCompletionCallback callback2;
+
+  HttpRequestInfo httpreq1 = CreateGetRequest();
+  HttpRequestInfo httpreq2 = CreateGetRequest();
+
+  out.rv = trans1->Start(&httpreq1, callback1.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+  out.rv = trans2->Start(&httpreq2, callback2.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+
+  out.rv = callback1.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+  out.rv = callback2.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+
+  const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+  EXPECT_TRUE(response1->headers != NULL);
+  EXPECT_TRUE(response1->was_fetched_via_spdy);
+  out.status_line = response1->headers->GetStatusLine();
+  out.response_info = *response1;
+  out.rv = ReadTransaction(trans1.get(), &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+
+  const HttpResponseInfo* response2 = trans2->GetResponseInfo();
+  EXPECT_TRUE(response2->headers != NULL);
+  EXPECT_TRUE(response2->was_fetched_via_spdy);
+  out.status_line = response2->headers->GetStatusLine();
+  out.response_info = *response2;
+  out.rv = ReadTransaction(trans2.get(), &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+
+  helper.VerifyDataConsumed();
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, TwoGetsLateBindingFromPreconnect) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+  scoped_ptr<SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*req2),
+  };
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1),
+    CreateMockRead(*body),
+    CreateMockRead(*resp2, 4),
+    CreateMockRead(*body2),
+    CreateMockRead(*fbody),
+    CreateMockRead(*fbody2),
+    MockRead(ASYNC, 0, 0),  // EOF
+  };
+  OrderedSocketData preconnect_data(reads, arraysize(reads),
+                                    writes, arraysize(writes));
+
+  MockConnect never_finishing_connect(ASYNC, ERR_IO_PENDING);
+
+  OrderedSocketData data_placeholder(NULL, 0, NULL, 0);
+  data_placeholder.set_connect_data(never_finishing_connect);
+
+  BoundNetLog log;
+  TransactionHelperResult out;
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&preconnect_data);
+  // We require placeholder data because 3 connections are attempted (first is
+  // the preconnect, 2nd and 3rd are the never finished connections.
+  helper.AddData(&data_placeholder);
+  helper.AddData(&data_placeholder);
+
+  scoped_ptr<HttpNetworkTransaction> trans1(
+      new HttpNetworkTransaction(helper.session()));
+  scoped_ptr<HttpNetworkTransaction> trans2(
+      new HttpNetworkTransaction(helper.session()));
+
+  TestCompletionCallback callback1;
+  TestCompletionCallback callback2;
+
+  HttpRequestInfo httpreq = CreateGetRequest();
+
+  // Preconnect the first.
+  SSLConfig preconnect_ssl_config;
+  helper.session()->ssl_config_service()->GetSSLConfig(&preconnect_ssl_config);
+  HttpStreamFactory* http_stream_factory =
+      helper.session()->http_stream_factory();
+  if (http_stream_factory->has_next_protos()) {
+    preconnect_ssl_config.next_protos = http_stream_factory->next_protos();
+  }
+
+  http_stream_factory->PreconnectStreams(
+      1, httpreq, preconnect_ssl_config, preconnect_ssl_config);
+
+  out.rv = trans1->Start(&httpreq, callback1.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+  out.rv = trans2->Start(&httpreq, callback2.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+
+  out.rv = callback1.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+  out.rv = callback2.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+
+  const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+  EXPECT_TRUE(response1->headers != NULL);
+  EXPECT_TRUE(response1->was_fetched_via_spdy);
+  out.status_line = response1->headers->GetStatusLine();
+  out.response_info = *response1;
+  out.rv = ReadTransaction(trans1.get(), &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+
+  const HttpResponseInfo* response2 = trans2->GetResponseInfo();
+  EXPECT_TRUE(response2->headers != NULL);
+  EXPECT_TRUE(response2->was_fetched_via_spdy);
+  out.status_line = response2->headers->GetStatusLine();
+  out.response_info = *response2;
+  out.rv = ReadTransaction(trans2.get(), &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+
+  helper.VerifyDataConsumed();
+}
+
+// Similar to ThreeGets above, however this test adds a SETTINGS
+// frame.  The SETTINGS frame is read during the IO loop waiting on
+// the first transaction completion, and sets a maximum concurrent
+// stream limit of 1.  This means that our IO loop exists after the
+// second transaction completes, so we can assert on read_index().
+TEST_P(SpdyNetworkTransactionSpdy3Test, ThreeGetsWithMaxConcurrent) {
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+  scoped_ptr<SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+  scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, LOWEST));
+  scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
+  scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(5, false));
+  scoped_ptr<SpdyFrame> fbody3(ConstructSpdyBodyFrame(5, true));
+
+  SettingsMap settings;
+  const size_t max_concurrent_streams = 1;
+  settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+  scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*req2),
+    CreateMockWrite(*req3),
+  };
+
+  MockRead reads[] = {
+    CreateMockRead(*settings_frame, 1),
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    CreateMockRead(*fbody),
+    CreateMockRead(*resp2, 7),
+    CreateMockRead(*body2),
+    CreateMockRead(*fbody2),
+    CreateMockRead(*resp3, 12),
+    CreateMockRead(*body3),
+    CreateMockRead(*fbody3),
+
+    MockRead(ASYNC, 0, 0),  // EOF
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  OrderedSocketData data_placeholder(NULL, 0, NULL, 0);
+
+  BoundNetLog log;
+  TransactionHelperResult out;
+  {
+    NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                       BoundNetLog(), GetParam(), NULL);
+    helper.RunPreTestSetup();
+    helper.AddData(&data);
+    // We require placeholder data because three get requests are sent out, so
+    // there needs to be three sets of SSL connection data.
+    helper.AddData(&data_placeholder);
+    helper.AddData(&data_placeholder);
+    scoped_ptr<HttpNetworkTransaction> trans1(
+        new HttpNetworkTransaction(helper.session()));
+    scoped_ptr<HttpNetworkTransaction> trans2(
+        new HttpNetworkTransaction(helper.session()));
+    scoped_ptr<HttpNetworkTransaction> trans3(
+        new HttpNetworkTransaction(helper.session()));
+
+    TestCompletionCallback callback1;
+    TestCompletionCallback callback2;
+    TestCompletionCallback callback3;
+
+    HttpRequestInfo httpreq1 = CreateGetRequest();
+    HttpRequestInfo httpreq2 = CreateGetRequest();
+    HttpRequestInfo httpreq3 = CreateGetRequest();
+
+    out.rv = trans1->Start(&httpreq1, callback1.callback(), log);
+    ASSERT_EQ(out.rv, ERR_IO_PENDING);
+    // run transaction 1 through quickly to force a read of our SETTINGS
+    // frame
+    out.rv = callback1.WaitForResult();
+    ASSERT_EQ(OK, out.rv);
+
+    out.rv = trans2->Start(&httpreq2, callback2.callback(), log);
+    ASSERT_EQ(out.rv, ERR_IO_PENDING);
+    out.rv = trans3->Start(&httpreq3, callback3.callback(), log);
+    ASSERT_EQ(out.rv, ERR_IO_PENDING);
+    out.rv = callback2.WaitForResult();
+    ASSERT_EQ(OK, out.rv);
+    EXPECT_EQ(7U, data.read_index());  // i.e. the third trans was queued
+
+    out.rv = callback3.WaitForResult();
+    ASSERT_EQ(OK, out.rv);
+
+    const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+    ASSERT_TRUE(response1 != NULL);
+    EXPECT_TRUE(response1->headers != NULL);
+    EXPECT_TRUE(response1->was_fetched_via_spdy);
+    out.status_line = response1->headers->GetStatusLine();
+    out.response_info = *response1;
+    out.rv = ReadTransaction(trans1.get(), &out.response_data);
+    EXPECT_EQ(OK, out.rv);
+    EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+    EXPECT_EQ("hello!hello!", out.response_data);
+
+    const HttpResponseInfo* response2 = trans2->GetResponseInfo();
+    out.status_line = response2->headers->GetStatusLine();
+    out.response_info = *response2;
+    out.rv = ReadTransaction(trans2.get(), &out.response_data);
+    EXPECT_EQ(OK, out.rv);
+    EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+    EXPECT_EQ("hello!hello!", out.response_data);
+
+    const HttpResponseInfo* response3 = trans3->GetResponseInfo();
+    out.status_line = response3->headers->GetStatusLine();
+    out.response_info = *response3;
+    out.rv = ReadTransaction(trans3.get(), &out.response_data);
+    EXPECT_EQ(OK, out.rv);
+    EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+    EXPECT_EQ("hello!hello!", out.response_data);
+
+    helper.VerifyDataConsumed();
+  }
+  EXPECT_EQ(OK, out.rv);
+}
+
+// Similar to ThreeGetsWithMaxConcurrent above, however this test adds
+// a fourth transaction.  The third and fourth transactions have
+// different data ("hello!" vs "hello!hello!") and because of the
+// user specified priority, we expect to see them inverted in
+// the response from the server.
+TEST_P(SpdyNetworkTransactionSpdy3Test, FourGetsWithMaxConcurrentPriority) {
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+  scoped_ptr<SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+  scoped_ptr<SpdyFrame> req4(
+      ConstructSpdyGet(NULL, 0, false, 5, HIGHEST));
+  scoped_ptr<SpdyFrame> resp4(ConstructSpdyGetSynReply(NULL, 0, 5));
+  scoped_ptr<SpdyFrame> fbody4(ConstructSpdyBodyFrame(5, true));
+
+  scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 7, LOWEST));
+  scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 7));
+  scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(7, false));
+  scoped_ptr<SpdyFrame> fbody3(ConstructSpdyBodyFrame(7, true));
+
+  SettingsMap settings;
+  const size_t max_concurrent_streams = 1;
+  settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+  scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+
+  MockWrite writes[] = { CreateMockWrite(*req),
+    CreateMockWrite(*req2),
+    CreateMockWrite(*req4),
+    CreateMockWrite(*req3),
+  };
+  MockRead reads[] = {
+    CreateMockRead(*settings_frame, 1),
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    CreateMockRead(*fbody),
+    CreateMockRead(*resp2, 7),
+    CreateMockRead(*body2),
+    CreateMockRead(*fbody2),
+    CreateMockRead(*resp4, 13),
+    CreateMockRead(*fbody4),
+    CreateMockRead(*resp3, 16),
+    CreateMockRead(*body3),
+    CreateMockRead(*fbody3),
+
+    MockRead(ASYNC, 0, 0),  // EOF
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  OrderedSocketData data_placeholder(NULL, 0, NULL, 0);
+
+  BoundNetLog log;
+  TransactionHelperResult out;
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+      BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  // We require placeholder data because four get requests are sent out, so
+  // there needs to be four sets of SSL connection data.
+  helper.AddData(&data_placeholder);
+  helper.AddData(&data_placeholder);
+  helper.AddData(&data_placeholder);
+  scoped_ptr<HttpNetworkTransaction> trans1(
+      new HttpNetworkTransaction(helper.session()));
+  scoped_ptr<HttpNetworkTransaction> trans2(
+      new HttpNetworkTransaction(helper.session()));
+  scoped_ptr<HttpNetworkTransaction> trans3(
+      new HttpNetworkTransaction(helper.session()));
+  scoped_ptr<HttpNetworkTransaction> trans4(
+      new HttpNetworkTransaction(helper.session()));
+
+  TestCompletionCallback callback1;
+  TestCompletionCallback callback2;
+  TestCompletionCallback callback3;
+  TestCompletionCallback callback4;
+
+  HttpRequestInfo httpreq1 = CreateGetRequest();
+  HttpRequestInfo httpreq2 = CreateGetRequest();
+  HttpRequestInfo httpreq3 = CreateGetRequest();
+  HttpRequestInfo httpreq4 = CreateGetRequest();
+  httpreq4.priority = HIGHEST;
+
+  out.rv = trans1->Start(&httpreq1, callback1.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+  // Run transaction 1 through quickly to force a read of our SETTINGS frame.
+  out.rv = callback1.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+
+  out.rv = trans2->Start(&httpreq2, callback2.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+  out.rv = trans3->Start(&httpreq3, callback3.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+  out.rv = trans4->Start(&httpreq4, callback4.callback(), log);
+  ASSERT_EQ(ERR_IO_PENDING, out.rv);
+
+  out.rv = callback2.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+  EXPECT_EQ(data.read_index(), 7U);  // i.e. the third & fourth trans queued
+
+  out.rv = callback3.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+
+  const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+  EXPECT_TRUE(response1->headers != NULL);
+  EXPECT_TRUE(response1->was_fetched_via_spdy);
+  out.status_line = response1->headers->GetStatusLine();
+  out.response_info = *response1;
+  out.rv = ReadTransaction(trans1.get(), &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+
+  const HttpResponseInfo* response2 = trans2->GetResponseInfo();
+  out.status_line = response2->headers->GetStatusLine();
+  out.response_info = *response2;
+  out.rv = ReadTransaction(trans2.get(), &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+
+  // notice: response3 gets two hellos, response4 gets one
+  // hello, so we know dequeuing priority was respected.
+  const HttpResponseInfo* response3 = trans3->GetResponseInfo();
+  out.status_line = response3->headers->GetStatusLine();
+  out.response_info = *response3;
+  out.rv = ReadTransaction(trans3.get(), &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+
+  out.rv = callback4.WaitForResult();
+  EXPECT_EQ(OK, out.rv);
+  const HttpResponseInfo* response4 = trans4->GetResponseInfo();
+  out.status_line = response4->headers->GetStatusLine();
+  out.response_info = *response4;
+  out.rv = ReadTransaction(trans4.get(), &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+  helper.VerifyDataConsumed();
+  EXPECT_EQ(OK, out.rv);
+}
+
+// Similar to ThreeGetsMaxConcurrrent above, however, this test
+// deletes a session in the middle of the transaction to insure
+// that we properly remove pendingcreatestream objects from
+// the spdy_session
+TEST_P(SpdyNetworkTransactionSpdy3Test, ThreeGetsWithMaxConcurrentDelete) {
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> fbody(ConstructSpdyBodyFrame(1, true));
+
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, false));
+  scoped_ptr<SpdyFrame> fbody2(ConstructSpdyBodyFrame(3, true));
+
+  SettingsMap settings;
+  const size_t max_concurrent_streams = 1;
+  settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+  scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+
+  MockWrite writes[] = { CreateMockWrite(*req),
+    CreateMockWrite(*req2),
+  };
+  MockRead reads[] = {
+    CreateMockRead(*settings_frame, 1),
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    CreateMockRead(*fbody),
+    CreateMockRead(*resp2, 7),
+    CreateMockRead(*body2),
+    CreateMockRead(*fbody2),
+    MockRead(ASYNC, 0, 0),  // EOF
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  OrderedSocketData data_placeholder(NULL, 0, NULL, 0);
+
+  BoundNetLog log;
+  TransactionHelperResult out;
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+      BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  // We require placeholder data because three get requests are sent out, so
+  // there needs to be three sets of SSL connection data.
+  helper.AddData(&data_placeholder);
+  helper.AddData(&data_placeholder);
+  scoped_ptr<HttpNetworkTransaction> trans1(
+      new HttpNetworkTransaction(helper.session()));
+  scoped_ptr<HttpNetworkTransaction> trans2(
+      new HttpNetworkTransaction(helper.session()));
+  scoped_ptr<HttpNetworkTransaction> trans3(
+      new HttpNetworkTransaction(helper.session()));
+
+  TestCompletionCallback callback1;
+  TestCompletionCallback callback2;
+  TestCompletionCallback callback3;
+
+  HttpRequestInfo httpreq1 = CreateGetRequest();
+  HttpRequestInfo httpreq2 = CreateGetRequest();
+  HttpRequestInfo httpreq3 = CreateGetRequest();
+
+  out.rv = trans1->Start(&httpreq1, callback1.callback(), log);
+  ASSERT_EQ(out.rv, ERR_IO_PENDING);
+  // Run transaction 1 through quickly to force a read of our SETTINGS frame.
+  out.rv = callback1.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+
+  out.rv = trans2->Start(&httpreq2, callback2.callback(), log);
+  ASSERT_EQ(out.rv, ERR_IO_PENDING);
+  out.rv = trans3->Start(&httpreq3, callback3.callback(), log);
+  delete trans3.release();
+  ASSERT_EQ(out.rv, ERR_IO_PENDING);
+  out.rv = callback2.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+
+  EXPECT_EQ(8U, data.read_index());
+
+  const HttpResponseInfo* response1 = trans1->GetResponseInfo();
+  ASSERT_TRUE(response1 != NULL);
+  EXPECT_TRUE(response1->headers != NULL);
+  EXPECT_TRUE(response1->was_fetched_via_spdy);
+  out.status_line = response1->headers->GetStatusLine();
+  out.response_info = *response1;
+  out.rv = ReadTransaction(trans1.get(), &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+
+  const HttpResponseInfo* response2 = trans2->GetResponseInfo();
+  ASSERT_TRUE(response2 != NULL);
+  out.status_line = response2->headers->GetStatusLine();
+  out.response_info = *response2;
+  out.rv = ReadTransaction(trans2.get(), &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+  helper.VerifyDataConsumed();
+  EXPECT_EQ(OK, out.rv);
+}
+
+namespace {
+
+// The KillerCallback will delete the transaction on error as part of the
+// callback.
+class KillerCallback : public TestCompletionCallbackBase {
+ public:
+  explicit KillerCallback(HttpNetworkTransaction* transaction)
+      : transaction_(transaction),
+        ALLOW_THIS_IN_INITIALIZER_LIST(callback_(
+            base::Bind(&KillerCallback::OnComplete, base::Unretained(this)))) {
+  }
+
+  virtual ~KillerCallback() {}
+
+  const CompletionCallback& callback() const { return callback_; }
+
+ private:
+  void OnComplete(int result) {
+    if (result < 0)
+      delete transaction_;
+
+    SetResult(result);
+  }
+
+  HttpNetworkTransaction* transaction_;
+  CompletionCallback callback_;
+};
+
+}  // namespace
+
+// Similar to ThreeGetsMaxConcurrrentDelete above, however, this test
+// closes the socket while we have a pending transaction waiting for
+// a pending stream creation.  http://crbug.com/52901
+TEST_P(SpdyNetworkTransactionSpdy3Test, ThreeGetsWithMaxConcurrentSocketClose) {
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> fin_body(ConstructSpdyBodyFrame(1, true));
+
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+
+  SettingsMap settings;
+  const size_t max_concurrent_streams = 1;
+  settings[SETTINGS_MAX_CONCURRENT_STREAMS] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+  scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+
+  MockWrite writes[] = { CreateMockWrite(*req),
+    CreateMockWrite(*req2),
+  };
+  MockRead reads[] = {
+    CreateMockRead(*settings_frame, 1),
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    CreateMockRead(*fin_body),
+    CreateMockRead(*resp2, 7),
+    MockRead(ASYNC, ERR_CONNECTION_RESET, 0),  // Abort!
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  OrderedSocketData data_placeholder(NULL, 0, NULL, 0);
+
+  BoundNetLog log;
+  TransactionHelperResult out;
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+      BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  // We require placeholder data because three get requests are sent out, so
+  // there needs to be three sets of SSL connection data.
+  helper.AddData(&data_placeholder);
+  helper.AddData(&data_placeholder);
+  HttpNetworkTransaction trans1(helper.session());
+  HttpNetworkTransaction trans2(helper.session());
+  HttpNetworkTransaction* trans3(new HttpNetworkTransaction(helper.session()));
+
+  TestCompletionCallback callback1;
+  TestCompletionCallback callback2;
+  KillerCallback callback3(trans3);
+
+  HttpRequestInfo httpreq1 = CreateGetRequest();
+  HttpRequestInfo httpreq2 = CreateGetRequest();
+  HttpRequestInfo httpreq3 = CreateGetRequest();
+
+  out.rv = trans1.Start(&httpreq1, callback1.callback(), log);
+  ASSERT_EQ(out.rv, ERR_IO_PENDING);
+  // Run transaction 1 through quickly to force a read of our SETTINGS frame.
+  out.rv = callback1.WaitForResult();
+  ASSERT_EQ(OK, out.rv);
+
+  out.rv = trans2.Start(&httpreq2, callback2.callback(), log);
+  ASSERT_EQ(out.rv, ERR_IO_PENDING);
+  out.rv = trans3->Start(&httpreq3, callback3.callback(), log);
+  ASSERT_EQ(out.rv, ERR_IO_PENDING);
+  out.rv = callback3.WaitForResult();
+  ASSERT_EQ(ERR_ABORTED, out.rv);
+
+  EXPECT_EQ(6U, data.read_index());
+
+  const HttpResponseInfo* response1 = trans1.GetResponseInfo();
+  ASSERT_TRUE(response1 != NULL);
+  EXPECT_TRUE(response1->headers != NULL);
+  EXPECT_TRUE(response1->was_fetched_via_spdy);
+  out.status_line = response1->headers->GetStatusLine();
+  out.response_info = *response1;
+  out.rv = ReadTransaction(&trans1, &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+
+  const HttpResponseInfo* response2 = trans2.GetResponseInfo();
+  ASSERT_TRUE(response2 != NULL);
+  out.status_line = response2->headers->GetStatusLine();
+  out.response_info = *response2;
+  out.rv = ReadTransaction(&trans2, &out.response_data);
+  EXPECT_EQ(ERR_CONNECTION_RESET, out.rv);
+
+  helper.VerifyDataConsumed();
+}
+
+// Test that a simple PUT request works.
+TEST_P(SpdyNetworkTransactionSpdy3Test, Put) {
+  // Setup the request
+  HttpRequestInfo request;
+  request.method = "PUT";
+  request.url = GURL("http://www.google.com/");
+
+  const SpdyHeaderInfo kSynStartHeader = {
+    SYN_STREAM,             // Kind = Syn
+    1,                      // Stream ID
+    0,                      // Associated stream ID
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 3),  // Priority
+    0,                      // Credential Slot
+    CONTROL_FLAG_FIN,       // Control Flags
+    false,                  // Compressed
+    INVALID,                // Status
+    NULL,                   // Data
+    0,                      // Length
+    DATA_FLAG_NONE          // Data Flags
+  };
+  const char* const kPutHeaders[] = {
+    ":method", "PUT",
+    ":path", "/",
+    ":host", "www.google.com",
+    ":scheme", "http",
+    ":version", "HTTP/1.1",
+    "content-length", "0"
+  };
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPacket(kSynStartHeader, NULL, 0,
+    kPutHeaders, arraysize(kPutHeaders) / 2));
+  MockWrite writes[] = {
+    CreateMockWrite(*req)
+  };
+
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  const SpdyHeaderInfo kSynReplyHeader = {
+    SYN_REPLY,              // Kind = SynReply
+    1,                      // Stream ID
+    0,                      // Associated stream ID
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 3),  // Priority
+    0,                      // Credential Slot
+    CONTROL_FLAG_NONE,      // Control Flags
+    false,                  // Compressed
+    INVALID,                // Status
+    NULL,                   // Data
+    0,                      // Length
+    DATA_FLAG_NONE          // Data Flags
+  };
+  static const char* const kStandardRespHeaders[] = {
+    ":status", "200",
+    ":version", "HTTP/1.1"
+    "content-length", "1234"
+  };
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPacket(kSynReplyHeader,
+      NULL, 0, kStandardRespHeaders, arraysize(kStandardRespHeaders) / 2));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(request,
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+}
+
+// Test that a simple HEAD request works.
+TEST_P(SpdyNetworkTransactionSpdy3Test, Head) {
+  // Setup the request
+  HttpRequestInfo request;
+  request.method = "HEAD";
+  request.url = GURL("http://www.google.com/");
+
+  const SpdyHeaderInfo kSynStartHeader = {
+    SYN_STREAM,             // Kind = Syn
+    1,                      // Stream ID
+    0,                      // Associated stream ID
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 3),  // Priority
+    0,                      // Credential Slot
+    CONTROL_FLAG_FIN,       // Control Flags
+    false,                  // Compressed
+    INVALID,                // Status
+    NULL,                   // Data
+    0,                      // Length
+    DATA_FLAG_NONE          // Data Flags
+  };
+  const char* const kHeadHeaders[] = {
+    ":method", "HEAD",
+    ":path", "/",
+    ":host", "www.google.com",
+    ":scheme", "http",
+    ":version", "HTTP/1.1",
+    "content-length", "0"
+  };
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPacket(kSynStartHeader, NULL, 0,
+    kHeadHeaders, arraysize(kHeadHeaders) / 2));
+  MockWrite writes[] = {
+    CreateMockWrite(*req)
+  };
+
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  const SpdyHeaderInfo kSynReplyHeader = {
+    SYN_REPLY,              // Kind = SynReply
+    1,                      // Stream ID
+    0,                      // Associated stream ID
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 3),  // Priority
+    0,                      // Credential Slot
+    CONTROL_FLAG_NONE,      // Control Flags
+    false,                  // Compressed
+    INVALID,                // Status
+    NULL,                   // Data
+    0,                      // Length
+    DATA_FLAG_NONE          // Data Flags
+  };
+  static const char* const kStandardRespHeaders[] = {
+    ":status", "200",
+    ":version", "HTTP/1.1"
+    "content-length", "1234"
+  };
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPacket(kSynReplyHeader,
+      NULL, 0, kStandardRespHeaders, arraysize(kStandardRespHeaders) / 2));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(request,
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+}
+
+// Test that a simple POST works.
+TEST_P(SpdyNetworkTransactionSpdy3Test, Post) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPost(kUploadDataSize, NULL, 0));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*body),  // POST upload frame
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(2, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreatePostRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+}
+
+// Test that a POST with a file works.
+TEST_P(SpdyNetworkTransactionSpdy3Test, FilePost) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPost(kUploadDataSize, NULL, 0));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*body),  // POST upload frame
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(2, reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateFilePostRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+}
+
+// Test that a complex POST works.
+TEST_P(SpdyNetworkTransactionSpdy3Test, ComplexPost) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPost(kUploadDataSize, NULL, 0));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*body),  // POST upload frame
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(2, reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateComplexPostRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+}
+
+// Test that a chunked POST works.
+TEST_P(SpdyNetworkTransactionSpdy3Test, ChunkedPost) {
+  set_merge_chunks(false);
+
+  scoped_ptr<SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0));
+  scoped_ptr<SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> chunk2(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*chunk1),
+    CreateMockWrite(*chunk2),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*chunk1),
+    CreateMockRead(*chunk2),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(2, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateChunkedPostRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+}
+
+// Test that a POST without any post data works.
+TEST_P(SpdyNetworkTransactionSpdy3Test, NullPost) {
+  // Setup the request
+  HttpRequestInfo request;
+  request.method = "POST";
+  request.url = GURL("http://www.google.com/");
+  // Create an empty UploadData.
+  request.upload_data_stream = NULL;
+
+  // When request.upload_data_stream is NULL for post, content-length is
+  // expected to be 0.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPost(0, NULL, 0));
+  // Set the FIN bit since there will be no body.
+  req->set_flags(CONTROL_FLAG_FIN);
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(request,
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+}
+
+// Test that a simple POST works.
+TEST_P(SpdyNetworkTransactionSpdy3Test, EmptyPost) {
+  // Create an empty UploadDataStream.
+  ScopedVector<UploadElementReader> element_readers;
+  UploadDataStream stream(&element_readers, 0);
+
+  // Setup the request
+  HttpRequestInfo request;
+  request.method = "POST";
+  request.url = GURL("http://www.google.com/");
+  request.upload_data_stream = &stream;
+
+  const uint64 kContentLength = 0;
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPost(kContentLength, NULL, 0));
+  // Set the FIN bit since there will be no body.
+  req->set_flags(CONTROL_FLAG_FIN);
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads), writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(request, BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+}
+
+// While we're doing a post, the server sends back a SYN_REPLY.
+TEST_P(SpdyNetworkTransactionSpdy3Test, PostWithEarlySynReply) {
+  static const char upload[] = { "hello!" };
+  ScopedVector<UploadElementReader> element_readers;
+  element_readers.push_back(
+      new UploadBytesElementReader(upload, sizeof(upload)));
+  UploadDataStream stream(&element_readers, 0);
+
+  // Setup the request
+  HttpRequestInfo request;
+  request.method = "POST";
+  request.url = GURL("http://www.google.com/");
+  request.upload_data_stream = &stream;
+
+  scoped_ptr<SpdyFrame> stream_reply(ConstructSpdyPostSynReply(NULL, 0));
+  scoped_ptr<SpdyFrame> stream_body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*stream_reply, 1),
+    MockRead(ASYNC, 0, 3)  // EOF
+  };
+
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPost(kUploadDataSize, NULL, 0));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*req, 0),
+    CreateMockWrite(*body, 2),
+  };
+
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreatePostRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.SetDeterministic();
+  helper.RunPreTestSetup();
+  helper.AddDeterministicData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreatePostRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  data.RunFor(2);
+  rv = callback.WaitForResult();
+  EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, rv);
+  data.RunFor(1);
+}
+
+// The client upon cancellation tries to send a RST_STREAM frame. The mock
+// socket causes the TCP write to return zero. This test checks that the client
+// tries to queue up the RST_STREAM frame again.
+TEST_P(SpdyNetworkTransactionSpdy3Test, SocketWriteReturnsZero) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> rst(
+      ConstructSpdyRstStream(1, CANCEL));
+  MockWrite writes[] = {
+    CreateMockWrite(*req.get(), 0, SYNCHRONOUS),
+    MockWrite(SYNCHRONOUS, 0, 0, 2),
+    CreateMockWrite(*rst.get(), 3, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp.get(), 1, ASYNC),
+    MockRead(ASYNC, 0, 0, 4)  // EOF
+  };
+
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.SetDeterministic();
+  helper.RunPreTestSetup();
+  helper.AddDeterministicData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  data.SetStop(2);
+  data.Run();
+  helper.ResetTrans();
+  data.SetStop(20);
+  data.Run();
+
+  helper.VerifyDataConsumed();
+}
+
+// Test that the transaction doesn't crash when we don't have a reply.
+TEST_P(SpdyNetworkTransactionSpdy3Test, ResponseWithoutSynReply) {
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads), NULL, 0);
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(ERR_SYN_REPLY_NOT_RECEIVED, out.rv);
+}
+
+// Test that the transaction doesn't crash when we get two replies on the same
+// stream ID. See http://crbug.com/45639.
+TEST_P(SpdyNetworkTransactionSpdy3Test, ResponseWithTwoSynReplies) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  rv = callback.WaitForResult();
+  EXPECT_EQ(OK, rv);
+
+  const HttpResponseInfo* response = trans->GetResponseInfo();
+  ASSERT_TRUE(response != NULL);
+  EXPECT_TRUE(response->headers != NULL);
+  EXPECT_TRUE(response->was_fetched_via_spdy);
+  std::string response_data;
+  rv = ReadTransaction(trans, &response_data);
+  EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, rv);
+
+  helper.VerifyDataConsumed();
+}
+
+// Test that sent data frames and received WINDOW_UPDATE frames change
+// the send_window_size_ correctly.
+
+// WINDOW_UPDATE is different than most other frames in that it can arrive
+// while the client is still sending the request body.  In order to enforce
+// this scenario, we feed a couple of dummy frames and give a delay of 0 to
+// socket data provider, so that initial read that is done as soon as the
+// stream is created, succeeds and schedules another read.  This way reads
+// and writes are interleaved; after doing a full frame write, SpdyStream
+// will break out of DoLoop and will read and process a WINDOW_UPDATE.
+// Once our WINDOW_UPDATE is read, we cannot send SYN_REPLY right away
+// since request has not been completely written, therefore we feed
+// enough number of WINDOW_UPDATEs to finish the first read and cause a
+// write, leading to a complete write of request body; after that we send
+// a reply with a body, to cause a graceful shutdown.
+
+// TODO(agayev): develop a socket data provider where both, reads and
+// writes are ordered so that writing tests like these are easy and rewrite
+// all these tests using it.  Right now we are working around the
+// limitations as described above and it's not deterministic, tests may
+// fail under specific circumstances.
+TEST_P(SpdyNetworkTransactionSpdy3Test, WindowUpdateReceived) {
+  static int kFrameCount = 2;
+  scoped_ptr<std::string> content(
+      new std::string(kMaxSpdyFrameChunkSize, 'a'));
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPost(
+      kMaxSpdyFrameChunkSize * kFrameCount, NULL, 0));
+  scoped_ptr<SpdyFrame> body(
+      ConstructSpdyBodyFrame(1, content->c_str(), content->size(), false));
+  scoped_ptr<SpdyFrame> body_end(
+      ConstructSpdyBodyFrame(1, content->c_str(), content->size(), true));
+
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*body),
+    CreateMockWrite(*body_end),
+  };
+
+  static const int32 kDeltaWindowSize = 0xff;
+  static const int kDeltaCount = 4;
+  scoped_ptr<SpdyFrame> window_update(
+      ConstructSpdyWindowUpdate(1, kDeltaWindowSize));
+  scoped_ptr<SpdyFrame> window_update_dummy(
+      ConstructSpdyWindowUpdate(2, kDeltaWindowSize));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
+  MockRead reads[] = {
+    CreateMockRead(*window_update_dummy),
+    CreateMockRead(*window_update_dummy),
+    CreateMockRead(*window_update_dummy),
+    CreateMockRead(*window_update),     // Four updates, therefore window
+    CreateMockRead(*window_update),     // size should increase by
+    CreateMockRead(*window_update),     // kDeltaWindowSize * 4
+    CreateMockRead(*window_update),
+    CreateMockRead(*resp),
+    CreateMockRead(*body_end),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(0, reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  ScopedVector<UploadElementReader> element_readers;
+  for (int i = 0; i < kFrameCount; ++i) {
+    element_readers.push_back(
+        new UploadBytesElementReader(content->c_str(), content->size()));
+  }
+  UploadDataStream upload_data_stream(&element_readers, 0);
+
+  // Setup the request
+  HttpRequestInfo request;
+  request.method = "POST";
+  request.url = GURL(kDefaultURL);
+  request.upload_data_stream = &upload_data_stream;
+
+  NormalSpdyTransactionHelper helper(request, BoundNetLog(), GetParam(), NULL);
+  helper.AddData(&data);
+  helper.RunPreTestSetup();
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog());
+
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  rv = callback.WaitForResult();
+  EXPECT_EQ(OK, rv);
+
+  SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get());
+  ASSERT_TRUE(stream != NULL);
+  ASSERT_TRUE(stream->stream() != NULL);
+  EXPECT_EQ(static_cast<int>(kSpdyStreamInitialWindowSize) +
+            kDeltaWindowSize * kDeltaCount -
+            kMaxSpdyFrameChunkSize * kFrameCount,
+            stream->stream()->send_window_size());
+  helper.VerifyDataConsumed();
+}
+
+// Test that received data frames and sent WINDOW_UPDATE frames change
+// the recv_window_size_ correctly.
+TEST_P(SpdyNetworkTransactionSpdy3Test, WindowUpdateSent) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> window_update(
+      ConstructSpdyWindowUpdate(1, kUploadDataSize));
+
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*window_update),
+  };
+
+  scoped_ptr<SpdyFrame> resp(
+      ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body_no_fin(
+      ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> body_fin(
+      ConstructSpdyBodyFrame(1, NULL, 0, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body_no_fin),
+    MockRead(ASYNC, ERR_IO_PENDING, 0),  // Force a pause
+    CreateMockRead(*body_fin),
+    MockRead(ASYNC, ERR_IO_PENDING, 0),  // Force a pause
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.AddData(&data);
+  helper.RunPreTestSetup();
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog());
+
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  rv = callback.WaitForResult();
+  EXPECT_EQ(OK, rv);
+
+  SpdyHttpStream* stream =
+      static_cast<SpdyHttpStream*>(trans->stream_.get());
+  ASSERT_TRUE(stream != NULL);
+  ASSERT_TRUE(stream->stream() != NULL);
+
+  EXPECT_EQ(
+      static_cast<int>(kSpdyStreamInitialWindowSize) - kUploadDataSize,
+      stream->stream()->recv_window_size());
+
+  const HttpResponseInfo* response = trans->GetResponseInfo();
+  ASSERT_TRUE(response != NULL);
+  ASSERT_TRUE(response->headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+  EXPECT_TRUE(response->was_fetched_via_spdy);
+
+  // Force sending of WINDOW_UPDATE by setting initial_recv_window_size to a
+  // small value.
+  stream->stream()->set_initial_recv_window_size(kUploadDataSize / 2);
+
+  // Issue a read which will cause a WINDOW_UPDATE to be sent and window
+  // size increased to default.
+  scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kUploadDataSize));
+  rv = trans->Read(buf, kUploadDataSize, CompletionCallback());
+  EXPECT_EQ(kUploadDataSize, rv);
+  std::string content(buf->data(), buf->data()+kUploadDataSize);
+  EXPECT_STREQ(kUploadData, content.c_str());
+
+  // Schedule the reading of empty data frame with FIN
+  data.CompleteRead();
+
+  // Force write of WINDOW_UPDATE which was scheduled during the above
+  // read.
+  MessageLoop::current()->RunUntilIdle();
+
+  // Read EOF.
+  data.CompleteRead();
+
+  helper.VerifyDataConsumed();
+}
+
+// Test that WINDOW_UPDATE frame causing overflow is handled correctly.
+TEST_P(SpdyNetworkTransactionSpdy3Test, WindowUpdateOverflow) {
+  // Number of full frames we hope to write (but will not, used to
+  // set content-length header correctly)
+  static int kFrameCount = 3;
+
+  scoped_ptr<std::string> content(
+      new std::string(kMaxSpdyFrameChunkSize, 'a'));
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPost(
+      kMaxSpdyFrameChunkSize * kFrameCount, NULL, 0));
+  scoped_ptr<SpdyFrame> body(
+      ConstructSpdyBodyFrame(1, content->c_str(), content->size(), false));
+  scoped_ptr<SpdyFrame> rst(
+      ConstructSpdyRstStream(1, FLOW_CONTROL_ERROR));
+
+  // We're not going to write a data frame with FIN, we'll receive a bad
+  // WINDOW_UPDATE while sending a request and will send a RST_STREAM frame.
+  MockWrite writes[] = {
+    CreateMockWrite(*req, 0),
+    CreateMockWrite(*body, 2),
+    CreateMockWrite(*rst, 3),
+  };
+
+  static const int32 kDeltaWindowSize = 0x7fffffff;  // cause an overflow
+  scoped_ptr<SpdyFrame> window_update(
+      ConstructSpdyWindowUpdate(1, kDeltaWindowSize));
+  MockRead reads[] = {
+    CreateMockRead(*window_update, 1),
+    MockRead(ASYNC, 0, 4)  // EOF
+  };
+
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+
+  ScopedVector<UploadElementReader> element_readers;
+  for (int i = 0; i < kFrameCount; ++i) {
+    element_readers.push_back(
+        new UploadBytesElementReader(content->c_str(), content->size()));
+  }
+  UploadDataStream upload_data_stream(&element_readers, 0);
+
+  // Setup the request
+  HttpRequestInfo request;
+  request.method = "POST";
+  request.url = GURL("http://www.google.com/");
+  request.upload_data_stream = &upload_data_stream;
+
+  NormalSpdyTransactionHelper helper(request,
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.SetDeterministic();
+  helper.RunPreTestSetup();
+  helper.AddDeterministicData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog());
+  ASSERT_EQ(ERR_IO_PENDING, rv);
+
+  data.RunFor(5);
+  ASSERT_TRUE(callback.have_result());
+  EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, callback.WaitForResult());
+  helper.VerifyDataConsumed();
+}
+
+// Test that after hitting a send window size of 0, the write process
+// stalls and upon receiving WINDOW_UPDATE frame write resumes.
+
+// This test constructs a POST request followed by enough data frames
+// containing 'a' that would make the window size 0, followed by another
+// data frame containing default content (which is "hello!") and this frame
+// also contains a FIN flag.  DelayedSocketData is used to enforce all
+// writes go through before a read could happen.  However, the last frame
+// ("hello!")  is not supposed to go through since by the time its turn
+// arrives, window size is 0.  At this point MessageLoop::Run() called via
+// callback would block.  Therefore we call MessageLoop::RunUntilIdle()
+// which returns after performing all possible writes.  We use DCHECKS to
+// ensure that last data frame is still there and stream has stalled.
+// After that, next read is artifically enforced, which causes a
+// WINDOW_UPDATE to be read and I/O process resumes.
+TEST_P(SpdyNetworkTransactionSpdy3Test, FlowControlStallResume) {
+  // Number of frames we need to send to zero out the window size: data
+  // frames plus SYN_STREAM plus the last data frame; also we need another
+  // data frame that we will send once the WINDOW_UPDATE is received,
+  // therefore +3.
+  size_t num_writes = kSpdyStreamInitialWindowSize / kMaxSpdyFrameChunkSize + 3;
+
+  // Calculate last frame's size; 0 size data frame is legal.
+  size_t last_frame_size =
+      kSpdyStreamInitialWindowSize % kMaxSpdyFrameChunkSize;
+
+  // Construct content for a data frame of maximum size.
+  std::string content(kMaxSpdyFrameChunkSize, 'a');
+
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPost(
+      kSpdyStreamInitialWindowSize + kUploadDataSize, NULL, 0));
+
+  // Full frames.
+  scoped_ptr<SpdyFrame> body1(
+      ConstructSpdyBodyFrame(1, content.c_str(), content.size(), false));
+
+  // Last frame to zero out the window size.
+  scoped_ptr<SpdyFrame> body2(
+      ConstructSpdyBodyFrame(1, content.c_str(), last_frame_size, false));
+
+  // Data frame to be sent once WINDOW_UPDATE frame is received.
+  scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(1, true));
+
+  // Fill in mock writes.
+  scoped_array<MockWrite> writes(new MockWrite[num_writes]);
+  size_t i = 0;
+  writes[i] = CreateMockWrite(*req);
+  for (i = 1; i < num_writes - 2; i++)
+    writes[i] = CreateMockWrite(*body1);
+  writes[i++] = CreateMockWrite(*body2);
+  writes[i] = CreateMockWrite(*body3);
+
+  // Construct read frame, give enough space to upload the rest of the
+  // data.
+  scoped_ptr<SpdyFrame> window_update(
+      ConstructSpdyWindowUpdate(1, kUploadDataSize));
+  scoped_ptr<SpdyFrame> reply(ConstructSpdyPostSynReply(NULL, 0));
+  MockRead reads[] = {
+    CreateMockRead(*window_update),
+    CreateMockRead(*window_update),
+    CreateMockRead(*reply),
+    CreateMockRead(*body2),
+    CreateMockRead(*body3),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  // Force all writes to happen before any read, last write will not
+  // actually queue a frame, due to window size being 0.
+  DelayedSocketData data(num_writes, reads, arraysize(reads),
+                         writes.get(), num_writes);
+
+  ScopedVector<UploadElementReader> element_readers;
+  std::string upload_data_string(kSpdyStreamInitialWindowSize, 'a');
+  upload_data_string.append(kUploadData, kUploadDataSize);
+  element_readers.push_back(new UploadBytesElementReader(
+      upload_data_string.c_str(), upload_data_string.size()));
+  UploadDataStream upload_data_stream(&element_readers, 0);
+
+  HttpRequestInfo request;
+  request.method = "POST";
+  request.url = GURL("http://www.google.com/");
+  request.upload_data_stream = &upload_data_stream;
+  NormalSpdyTransactionHelper helper(request, BoundNetLog(), GetParam(), NULL);
+  helper.AddData(&data);
+  helper.RunPreTestSetup();
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  MessageLoop::current()->RunUntilIdle();  // Write as much as we can.
+
+  SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get());
+  ASSERT_TRUE(stream != NULL);
+  ASSERT_TRUE(stream->stream() != NULL);
+  EXPECT_EQ(0, stream->stream()->send_window_size());
+  // All the body data should have been read.
+  // TODO(satorux): This is because of the weirdness in reading the request
+  // body in OnSendBodyComplete(). See crbug.com/113107.
+  EXPECT_TRUE(upload_data_stream.IsEOF());
+  // But the body is not yet fully sent (kUploadData is not yet sent).
+  EXPECT_FALSE(stream->stream()->body_sent());
+
+  data.ForceNextRead();   // Read in WINDOW_UPDATE frame.
+  rv = callback.WaitForResult();
+  helper.VerifyDataConsumed();
+}
+
+// Test we correctly handle the case where the SETTINGS frame results in
+// unstalling the send window.
+TEST_P(SpdyNetworkTransactionSpdy3Test, FlowControlStallResumeAfterSettings) {
+  // Number of frames we need to send to zero out the window size: data
+  // frames plus SYN_STREAM plus the last data frame; also we need another
+  // data frame that we will send once the SETTING is received, therefore +3.
+  size_t num_writes = kSpdyStreamInitialWindowSize / kMaxSpdyFrameChunkSize + 3;
+
+  // Calculate last frame's size; 0 size data frame is legal.
+  size_t last_frame_size =
+      kSpdyStreamInitialWindowSize % kMaxSpdyFrameChunkSize;
+
+  // Construct content for a data frame of maximum size.
+  std::string content(kMaxSpdyFrameChunkSize, 'a');
+
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPost(
+      kSpdyStreamInitialWindowSize + kUploadDataSize, NULL, 0));
+
+  // Full frames.
+  scoped_ptr<SpdyFrame> body1(
+      ConstructSpdyBodyFrame(1, content.c_str(), content.size(), false));
+
+  // Last frame to zero out the window size.
+  scoped_ptr<SpdyFrame> body2(
+      ConstructSpdyBodyFrame(1, content.c_str(), last_frame_size, false));
+
+  // Data frame to be sent once SETTINGS frame is received.
+  scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(1, true));
+
+  // Fill in mock writes.
+  scoped_array<MockWrite> writes(new MockWrite[num_writes]);
+  size_t i = 0;
+  writes[i] = CreateMockWrite(*req);
+  for (i = 1; i < num_writes - 2; i++)
+    writes[i] = CreateMockWrite(*body1);
+  writes[i++] = CreateMockWrite(*body2);
+  writes[i] = CreateMockWrite(*body3);
+
+  // Construct read frame for SETTINGS that gives enough space to upload the
+  // rest of the data.
+  SettingsMap settings;
+  settings[SETTINGS_INITIAL_WINDOW_SIZE] =
+      SettingsFlagsAndValue(
+          SETTINGS_FLAG_NONE, kSpdyStreamInitialWindowSize * 2);
+  scoped_ptr<SpdyFrame> settings_frame_large(ConstructSpdySettings(settings));
+  scoped_ptr<SpdyFrame> reply(ConstructSpdyPostSynReply(NULL, 0));
+  MockRead reads[] = {
+    CreateMockRead(*settings_frame_large),
+    CreateMockRead(*reply),
+    CreateMockRead(*body2),
+    CreateMockRead(*body3),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  // Force all writes to happen before any read, last write will not
+  // actually queue a frame, due to window size being 0.
+  DelayedSocketData data(num_writes, reads, arraysize(reads),
+                         writes.get(), num_writes);
+
+  ScopedVector<UploadElementReader> element_readers;
+  std::string upload_data_string(kSpdyStreamInitialWindowSize, 'a');
+  upload_data_string.append(kUploadData, kUploadDataSize);
+  element_readers.push_back(new UploadBytesElementReader(
+      upload_data_string.c_str(), upload_data_string.size()));
+  UploadDataStream upload_data_stream(&element_readers, 0);
+
+  HttpRequestInfo request;
+  request.method = "POST";
+  request.url = GURL("http://www.google.com/");
+  request.upload_data_stream = &upload_data_stream;
+  NormalSpdyTransactionHelper helper(request, BoundNetLog(), GetParam(), NULL);
+  helper.AddData(&data);
+  helper.RunPreTestSetup();
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  MessageLoop::current()->RunUntilIdle();  // Write as much as we can.
+
+  SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get());
+  ASSERT_TRUE(stream != NULL);
+  ASSERT_TRUE(stream->stream() != NULL);
+  EXPECT_EQ(0, stream->stream()->send_window_size());
+
+  // All the body data should have been read.
+  // TODO(satorux): This is because of the weirdness in reading the request
+  // body in OnSendBodyComplete(). See crbug.com/113107.
+  EXPECT_TRUE(upload_data_stream.IsEOF());
+  // But the body is not yet fully sent (kUploadData is not yet sent).
+  EXPECT_FALSE(stream->stream()->body_sent());
+  EXPECT_TRUE(stream->stream()->stalled_by_flow_control());
+
+  data.ForceNextRead();   // Read in SETTINGS frame to unstall.
+  rv = callback.WaitForResult();
+  helper.VerifyDataConsumed();
+  EXPECT_FALSE(stream->stream()->stalled_by_flow_control());
+}
+
+// Test we correctly handle the case where the SETTINGS frame results in a
+// negative send window size.
+TEST_P(SpdyNetworkTransactionSpdy3Test, FlowControlNegativeSendWindowSize) {
+  // Number of frames we need to send to zero out the window size: data
+  // frames plus SYN_STREAM plus the last data frame; also we need another
+  // data frame that we will send once the SETTING is received, therefore +3.
+  size_t num_writes = kSpdyStreamInitialWindowSize / kMaxSpdyFrameChunkSize + 3;
+
+  // Calculate last frame's size; 0 size data frame is legal.
+  size_t last_frame_size =
+      kSpdyStreamInitialWindowSize % kMaxSpdyFrameChunkSize;
+
+  // Construct content for a data frame of maximum size.
+  std::string content(kMaxSpdyFrameChunkSize, 'a');
+
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPost(
+      kSpdyStreamInitialWindowSize + kUploadDataSize, NULL, 0));
+
+  // Full frames.
+  scoped_ptr<SpdyFrame> body1(
+      ConstructSpdyBodyFrame(1, content.c_str(), content.size(), false));
+
+  // Last frame to zero out the window size.
+  scoped_ptr<SpdyFrame> body2(
+      ConstructSpdyBodyFrame(1, content.c_str(), last_frame_size, false));
+
+  // Data frame to be sent once SETTINGS frame is received.
+  scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(1, true));
+
+  // Fill in mock writes.
+  scoped_array<MockWrite> writes(new MockWrite[num_writes]);
+  size_t i = 0;
+  writes[i] = CreateMockWrite(*req);
+  for (i = 1; i < num_writes - 2; i++)
+    writes[i] = CreateMockWrite(*body1);
+  writes[i++] = CreateMockWrite(*body2);
+  writes[i] = CreateMockWrite(*body3);
+
+  // Construct read frame for SETTINGS that makes the send_window_size
+  // negative.
+  SettingsMap new_settings;
+  new_settings[SETTINGS_INITIAL_WINDOW_SIZE] =
+      SettingsFlagsAndValue(
+          SETTINGS_FLAG_NONE, kSpdyStreamInitialWindowSize / 2);
+  scoped_ptr<SpdyFrame> settings_frame_small(
+      ConstructSpdySettings(new_settings));
+  // Construct read frame for WINDOW_UPDATE that makes the send_window_size
+  // postive.
+  scoped_ptr<SpdyFrame> window_update_init_size(
+      ConstructSpdyWindowUpdate(1, kSpdyStreamInitialWindowSize));
+  scoped_ptr<SpdyFrame> reply(ConstructSpdyPostSynReply(NULL, 0));
+  MockRead reads[] = {
+    CreateMockRead(*settings_frame_small),
+    CreateMockRead(*window_update_init_size),
+    CreateMockRead(*reply),
+    CreateMockRead(*body2),
+    CreateMockRead(*body3),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  // Force all writes to happen before any read, last write will not actually
+  // queue a frame, due to window size being 0.
+  DelayedSocketData data(num_writes, reads, arraysize(reads),
+                         writes.get(), num_writes);
+
+  ScopedVector<UploadElementReader> element_readers;
+  std::string upload_data_string(kSpdyStreamInitialWindowSize, 'a');
+  upload_data_string.append(kUploadData, kUploadDataSize);
+  element_readers.push_back(new UploadBytesElementReader(
+      upload_data_string.c_str(), upload_data_string.size()));
+  UploadDataStream upload_data_stream(&element_readers, 0);
+
+  HttpRequestInfo request;
+  request.method = "POST";
+  request.url = GURL("http://www.google.com/");
+  request.upload_data_stream = &upload_data_stream;
+  NormalSpdyTransactionHelper helper(request, BoundNetLog(), GetParam(), NULL);
+  helper.AddData(&data);
+  helper.RunPreTestSetup();
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  MessageLoop::current()->RunUntilIdle();  // Write as much as we can.
+
+  SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get());
+  ASSERT_TRUE(stream != NULL);
+  ASSERT_TRUE(stream->stream() != NULL);
+  EXPECT_EQ(0, stream->stream()->send_window_size());
+
+  // All the body data should have been read.
+  // TODO(satorux): This is because of the weirdness in reading the request
+  // body in OnSendBodyComplete(). See crbug.com/113107.
+  EXPECT_TRUE(upload_data_stream.IsEOF());
+  // But the body is not yet fully sent (kUploadData is not yet sent).
+  EXPECT_FALSE(stream->stream()->body_sent());
+
+  data.ForceNextRead();   // Read in WINDOW_UPDATE or SETTINGS frame.
+  rv = callback.WaitForResult();
+  helper.VerifyDataConsumed();
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ResetReplyWithTransferEncoding) {
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(1, PROTOCOL_ERROR));
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*rst),
+  };
+
+  const char* const headers[] = {
+    "transfer-encoding", "chuncked"
+  };
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(headers, 1, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+
+  helper.session()->spdy_session_pool()->CloseAllSessions();
+  helper.VerifyDataConsumed();
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ResetPushWithTransferEncoding) {
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(2, PROTOCOL_ERROR));
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*rst),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  const char* const headers[] = {":scheme", "http",
+                                 ":host", "www.google.com",
+                                 ":path", "/1",
+                                 "transfer-encoding", "chunked"};
+  scoped_ptr<SpdyFrame> push(ConstructSpdyPush(headers, arraysize(headers) / 2,
+                                               2, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*push),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+
+  helper.session()->spdy_session_pool()->CloseAllSessions();
+  helper.VerifyDataConsumed();
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, CancelledTransaction) {
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    // This following read isn't used by the test, except during the
+    // RunUntilIdle() call at the end since the SpdySession survives the
+    // HttpNetworkTransaction and still tries to continue Read()'ing.  Any
+    // MockRead will do here.
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  StaticSocketDataProvider data(reads, arraysize(reads),
+                                writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  helper.ResetTrans();  // Cancel the transaction.
+
+  // Flush the MessageLoop while the SpdySessionDependencies (in particular, the
+  // MockClientSocketFactory) are still alive.
+  MessageLoop::current()->RunUntilIdle();
+  helper.VerifyDataNotConsumed();
+}
+
+// Verify that the client sends a Rst Frame upon cancelling the stream.
+TEST_P(SpdyNetworkTransactionSpdy3Test, CancelledTransactionSendRst) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> rst(
+      ConstructSpdyRstStream(1, CANCEL));
+  MockWrite writes[] = {
+    CreateMockWrite(*req, 0, SYNCHRONOUS),
+    CreateMockWrite(*rst, 2, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 0, 3)  // EOF
+  };
+
+  DeterministicSocketData data(reads, arraysize(reads),
+                                writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(),
+                                     GetParam(), NULL);
+  helper.SetDeterministic();
+  helper.RunPreTestSetup();
+  helper.AddDeterministicData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  data.SetStop(2);
+  data.Run();
+  helper.ResetTrans();
+  data.SetStop(20);
+  data.Run();
+
+  helper.VerifyDataConsumed();
+}
+
+// Verify that the client can correctly deal with the user callback attempting
+// to start another transaction on a session that is closing down. See
+// http://crbug.com/47455
+TEST_P(SpdyNetworkTransactionSpdy3Test, StartTransactionOnReadCallback) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+  MockWrite writes2[] = { CreateMockWrite(*req) };
+
+  // The indicated length of this packet is longer than its actual length. When
+  // the session receives an empty packet after this one, it shuts down the
+  // session, and calls the read callback with the incomplete data.
+  const uint8 kGetBodyFrame2[] = {
+    0x00, 0x00, 0x00, 0x01,
+    0x01, 0x00, 0x00, 0x07,
+    'h', 'e', 'l', 'l', 'o', '!',
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 2),
+    MockRead(ASYNC, ERR_IO_PENDING, 3),  // Force a pause
+    MockRead(ASYNC, reinterpret_cast<const char*>(kGetBodyFrame2),
+             arraysize(kGetBodyFrame2), 4),
+    MockRead(ASYNC, ERR_IO_PENDING, 5),  // Force a pause
+    MockRead(ASYNC, 0, 0, 6),  // EOF
+  };
+  MockRead reads2[] = {
+    CreateMockRead(*resp, 2),
+    MockRead(ASYNC, 0, 0, 3),  // EOF
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  DelayedSocketData data2(1, reads2, arraysize(reads2),
+                          writes2, arraysize(writes2));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  helper.AddData(&data2);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  // Start the transaction with basic parameters.
+  TestCompletionCallback callback;
+  int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  rv = callback.WaitForResult();
+
+  const int kSize = 3000;
+  scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize));
+  rv = trans->Read(
+      buf, kSize,
+      base::Bind(&SpdyNetworkTransactionSpdy3Test::StartTransactionCallback,
+                 helper.session()));
+  // This forces an err_IO_pending, which sets the callback.
+  data.CompleteRead();
+  // This finishes the read.
+  data.CompleteRead();
+  helper.VerifyDataConsumed();
+}
+
+// Verify that the client can correctly deal with the user callback deleting the
+// transaction. Failures will usually be valgrind errors. See
+// http://crbug.com/46925
+TEST_P(SpdyNetworkTransactionSpdy3Test, DeleteSessionOnReadCallback) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp.get(), 2),
+    MockRead(ASYNC, ERR_IO_PENDING, 3),  // Force a pause
+    CreateMockRead(*body.get(), 4),
+    MockRead(ASYNC, 0, 0, 5),  // EOF
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  // Start the transaction with basic parameters.
+  TestCompletionCallback callback;
+  int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  rv = callback.WaitForResult();
+
+  // Setup a user callback which will delete the session, and clear out the
+  // memory holding the stream object. Note that the callback deletes trans.
+  const int kSize = 3000;
+  scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize));
+  rv = trans->Read(
+      buf, kSize,
+      base::Bind(&SpdyNetworkTransactionSpdy3Test::DeleteSessionCallback,
+                 base::Unretained(&helper)));
+  ASSERT_EQ(ERR_IO_PENDING, rv);
+  data.CompleteRead();
+
+  // Finish running rest of tasks.
+  MessageLoop::current()->RunUntilIdle();
+  helper.VerifyDataConsumed();
+}
+
+// Send a spdy request to www.google.com that gets redirected to www.foo.com.
+TEST_P(SpdyNetworkTransactionSpdy3Test, RedirectGetRequest) {
+  // These are headers which the net::URLRequest tacks on.
+  const char* const kExtraHeaders[] = {
+    "accept-encoding",
+    "gzip,deflate",
+  };
+  const SpdyHeaderInfo kSynStartHeader = MakeSpdyHeader(SYN_STREAM);
+  const char* const kStandardGetHeaders[] = {
+    ":host",
+    "www.google.com",
+    ":method",
+    "GET",
+    ":scheme",
+    "http",
+    ":path",
+    "/",
+    "user-agent",
+    "",
+    ":version",
+    "HTTP/1.1"
+  };
+  const char* const kStandardGetHeaders2[] = {
+    ":host",
+    "www.foo.com",
+    ":method",
+    "GET",
+    ":scheme",
+    "http",
+    ":path",
+    "/index.php",
+    "user-agent",
+    "",
+    ":version",
+    "HTTP/1.1"
+  };
+
+  // Setup writes/reads to www.google.com
+  scoped_ptr<SpdyFrame> req(ConstructSpdyPacket(
+      kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders) / 2,
+      kStandardGetHeaders, arraysize(kStandardGetHeaders) / 2));
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyPacket(
+      kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders) / 2,
+      kStandardGetHeaders2, arraysize(kStandardGetHeaders2) / 2));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReplyRedirect(1));
+  MockWrite writes[] = {
+    CreateMockWrite(*req, 1),
+  };
+  MockRead reads[] = {
+    CreateMockRead(*resp, 2),
+    MockRead(ASYNC, 0, 0, 3)  // EOF
+  };
+
+  // Setup writes/reads to www.foo.com
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes2[] = {
+    CreateMockWrite(*req2, 1),
+  };
+  MockRead reads2[] = {
+    CreateMockRead(*resp2, 2),
+    CreateMockRead(*body2, 3),
+    MockRead(ASYNC, 0, 0, 4)  // EOF
+  };
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  OrderedSocketData data2(reads2, arraysize(reads2),
+                          writes2, arraysize(writes2));
+
+  // TODO(erikchen): Make test support SPDYSSL, SPDYNPN
+  HttpStreamFactory::set_force_spdy_over_ssl(false);
+  HttpStreamFactory::set_force_spdy_always(true);
+  TestDelegate d;
+  {
+    SpdyURLRequestContext spdy_url_request_context;
+    net::URLRequest r(
+        GURL("http://www.google.com/"), &d, &spdy_url_request_context);
+    spdy_url_request_context.socket_factory().
+        AddSocketDataProvider(&data);
+    spdy_url_request_context.socket_factory().
+        AddSocketDataProvider(&data2);
+
+    d.set_quit_on_redirect(true);
+    r.Start();
+    MessageLoop::current()->Run();
+    EXPECT_EQ(1, d.received_redirect_count());
+
+    r.FollowDeferredRedirect();
+    MessageLoop::current()->Run();
+    EXPECT_EQ(1, d.response_started_count());
+    EXPECT_FALSE(d.received_data_before_response());
+    EXPECT_EQ(net::URLRequestStatus::SUCCESS, r.status().status());
+    std::string contents("hello!");
+    EXPECT_EQ(contents, d.data_received());
+  }
+  EXPECT_TRUE(data.at_read_eof());
+  EXPECT_TRUE(data.at_write_eof());
+  EXPECT_TRUE(data2.at_read_eof());
+  EXPECT_TRUE(data2.at_write_eof());
+}
+
+// Detect response with upper case headers and reset the stream.
+TEST_P(SpdyNetworkTransactionSpdy3Test, UpperCaseHeaders) {
+  scoped_ptr<SpdyFrame>
+      syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      rst(ConstructSpdyRstStream(1, PROTOCOL_ERROR));
+  MockWrite writes[] = {
+    CreateMockWrite(*syn, 0),
+    CreateMockWrite(*rst, 2),
+  };
+
+  const char* const kExtraHeaders[] = {"X-UpperCase", "yes"};
+  scoped_ptr<SpdyFrame>
+      reply(ConstructSpdyGetSynReply(kExtraHeaders, 1, 1));
+  MockRead reads[] = {
+    CreateMockRead(*reply, 1),
+    MockRead(ASYNC, ERR_IO_PENDING, 3),  // Force a pause
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+}
+
+// Detect response with upper case headers in a HEADERS frame and reset the
+// stream.
+TEST_P(SpdyNetworkTransactionSpdy3Test, UpperCaseHeadersInHeadersFrame) {
+  scoped_ptr<SpdyFrame>
+      syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      rst(ConstructSpdyRstStream(1, PROTOCOL_ERROR));
+  MockWrite writes[] = {
+    CreateMockWrite(*syn, 0),
+    CreateMockWrite(*rst, 2),
+  };
+
+  static const char* const kInitialHeaders[] = {
+    ":status", "200 OK",
+    ":version", "HTTP/1.1"
+  };
+  static const char* const kLateHeaders[] = {
+    "X-UpperCase", "yes",
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyControlFrame(kInitialHeaders,
+                                              arraysize(kInitialHeaders) / 2,
+                                              false,
+                                              1,
+                                              LOWEST,
+                                              SYN_REPLY,
+                                              CONTROL_FLAG_NONE,
+                                              NULL,
+                                              0,
+                                              0));
+  scoped_ptr<SpdyFrame>
+      stream1_headers(ConstructSpdyControlFrame(kLateHeaders,
+                                                arraysize(kLateHeaders) / 2,
+                                                false,
+                                                1,
+                                                LOWEST,
+                                                HEADERS,
+                                                CONTROL_FLAG_NONE,
+                                                NULL,
+                                                0,
+                                                0));
+  scoped_ptr<SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply),
+    CreateMockRead(*stream1_headers),
+    CreateMockRead(*stream1_body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+}
+
+// Detect push stream with upper case headers and reset the stream.
+TEST_P(SpdyNetworkTransactionSpdy3Test, UpperCaseHeadersOnPush) {
+  scoped_ptr<SpdyFrame>
+      syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      rst(ConstructSpdyRstStream(2, PROTOCOL_ERROR));
+  MockWrite writes[] = {
+    CreateMockWrite(*syn, 0),
+    CreateMockWrite(*rst, 2),
+  };
+
+  scoped_ptr<SpdyFrame>
+      reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  const char* const extra_headers[] = {
+    "X-UpperCase", "yes"
+  };
+  scoped_ptr<SpdyFrame>
+      push(ConstructSpdyPush(extra_headers, arraysize(extra_headers) / 2,
+                             2, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*reply, 1),
+    CreateMockRead(*push, 1),
+    CreateMockRead(*body, 1),
+    MockRead(ASYNC, ERR_IO_PENDING, 3),  // Force a pause
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+}
+
+// Send a spdy request to www.google.com. Get a pushed stream that redirects to
+// www.foo.com.
+TEST_P(SpdyNetworkTransactionSpdy3Test, RedirectServerPush) {
+  // These are headers which the net::URLRequest tacks on.
+  const char* const kExtraHeaders[] = {
+    "accept-encoding",
+    "gzip,deflate",
+  };
+  const SpdyHeaderInfo kSynStartHeader = MakeSpdyHeader(SYN_STREAM);
+  const char* const kStandardGetHeaders[] = {
+    ":host",
+    "www.google.com",
+    ":method",
+    "GET",
+    ":scheme",
+    "http",
+    ":path",
+    "/",
+    "user-agent",
+    "",
+    ":version",
+    "HTTP/1.1"
+  };
+
+  // Setup writes/reads to www.google.com
+  scoped_ptr<SpdyFrame> req(
+      ConstructSpdyPacket(kSynStartHeader,
+                          kExtraHeaders,
+                          arraysize(kExtraHeaders) / 2,
+                          kStandardGetHeaders,
+                          arraysize(kStandardGetHeaders) / 2));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> rep(
+      ConstructSpdyPush(NULL,
+                        0,
+                        2,
+                        1,
+                        "http://www.google.com/foo.dat",
+                        "301 Moved Permanently",
+                        "http://www.foo.com/index.php"));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(2, CANCEL));
+  MockWrite writes[] = {
+    CreateMockWrite(*req, 1),
+    CreateMockWrite(*rst, 6),
+  };
+  MockRead reads[] = {
+    CreateMockRead(*resp, 2),
+    CreateMockRead(*rep, 3),
+    CreateMockRead(*body, 4),
+    MockRead(ASYNC, ERR_IO_PENDING, 5),  // Force a pause
+    MockRead(ASYNC, 0, 0, 7)  // EOF
+  };
+
+  // Setup writes/reads to www.foo.com
+  const char* const kStandardGetHeaders2[] = {
+    ":host",
+    "www.foo.com",
+    ":method",
+    "GET",
+    ":scheme",
+    "http",
+    ":path",
+    "/index.php",
+    "user-agent",
+    "",
+    ":version",
+    "HTTP/1.1"
+  };
+  scoped_ptr<SpdyFrame> req2(
+      ConstructSpdyPacket(kSynStartHeader,
+                          kExtraHeaders,
+                          arraysize(kExtraHeaders) / 2,
+                          kStandardGetHeaders2,
+                          arraysize(kStandardGetHeaders2) / 2));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes2[] = {
+    CreateMockWrite(*req2, 1),
+  };
+  MockRead reads2[] = {
+    CreateMockRead(*resp2, 2),
+    CreateMockRead(*body2, 3),
+    MockRead(ASYNC, 0, 0, 5)  // EOF
+  };
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  OrderedSocketData data2(reads2, arraysize(reads2),
+                          writes2, arraysize(writes2));
+
+  // TODO(erikchen): Make test support SPDYSSL, SPDYNPN
+  HttpStreamFactory::set_force_spdy_over_ssl(false);
+  HttpStreamFactory::set_force_spdy_always(true);
+  TestDelegate d;
+  TestDelegate d2;
+  SpdyURLRequestContext spdy_url_request_context;
+  {
+    net::URLRequest r(
+        GURL("http://www.google.com/"), &d, &spdy_url_request_context);
+    spdy_url_request_context.socket_factory().
+        AddSocketDataProvider(&data);
+
+    r.Start();
+    MessageLoop::current()->Run();
+
+    EXPECT_EQ(0, d.received_redirect_count());
+    std::string contents("hello!");
+    EXPECT_EQ(contents, d.data_received());
+
+    net::URLRequest r2(
+        GURL("http://www.google.com/foo.dat"), &d2, &spdy_url_request_context);
+    spdy_url_request_context.socket_factory().
+        AddSocketDataProvider(&data2);
+
+    d2.set_quit_on_redirect(true);
+    r2.Start();
+    MessageLoop::current()->Run();
+    EXPECT_EQ(1, d2.received_redirect_count());
+
+    r2.FollowDeferredRedirect();
+    MessageLoop::current()->Run();
+    EXPECT_EQ(1, d2.response_started_count());
+    EXPECT_FALSE(d2.received_data_before_response());
+    EXPECT_EQ(net::URLRequestStatus::SUCCESS, r2.status().status());
+    std::string contents2("hello!");
+    EXPECT_EQ(contents2, d2.data_received());
+  }
+  data.CompleteRead();
+  data2.CompleteRead();
+  EXPECT_TRUE(data.at_read_eof());
+  EXPECT_TRUE(data.at_write_eof());
+  EXPECT_TRUE(data2.at_read_eof());
+  EXPECT_TRUE(data2.at_write_eof());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushSingleDataFrame) {
+  static const unsigned char kPushBodyFrame[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x06,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyPush(NULL,
+                                    0,
+                                    2,
+                                    1,
+                                    "http://www.google.com/foo.dat"));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 2),
+    CreateMockRead(*stream2_syn, 3),
+    CreateMockRead(*stream1_body, 4, SYNCHRONOUS),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+             arraysize(kPushBodyFrame), 5),
+    MockRead(ASYNC, ERR_IO_PENDING, 6),  // Force a pause
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  std::string expected_push_result("pushed");
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  RunServerPushTest(&data,
+                    &response,
+                    &response2,
+                    expected_push_result);
+
+  // Verify the SYN_REPLY.
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  // Verify the pushed stream.
+  EXPECT_TRUE(response2.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushBeforeSynReply) {
+  static const unsigned char kPushBodyFrame[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x06,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyPush(NULL,
+                                    0,
+                                    2,
+                                    1,
+                                    "http://www.google.com/foo.dat"));
+  MockRead reads[] = {
+    CreateMockRead(*stream2_syn, 2),
+    CreateMockRead(*stream1_reply, 3),
+    CreateMockRead(*stream1_body, 4, SYNCHRONOUS),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+             arraysize(kPushBodyFrame), 5),
+    MockRead(ASYNC, ERR_IO_PENDING, 6),  // Force a pause
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  std::string expected_push_result("pushed");
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  RunServerPushTest(&data,
+                    &response,
+                    &response2,
+                    expected_push_result);
+
+  // Verify the SYN_REPLY.
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  // Verify the pushed stream.
+  EXPECT_TRUE(response2.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushSingleDataFrame2) {
+  static const unsigned char kPushBodyFrame[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x06,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyPush(NULL,
+                                    0,
+                                    2,
+                                    1,
+                                    "http://www.google.com/foo.dat"));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 2),
+    CreateMockRead(*stream2_syn, 3),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+             arraysize(kPushBodyFrame), 5),
+    CreateMockRead(*stream1_body, 4, SYNCHRONOUS),
+    MockRead(ASYNC, ERR_IO_PENDING, 6),  // Force a pause
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  std::string expected_push_result("pushed");
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  RunServerPushTest(&data,
+                    &response,
+                    &response2,
+                    expected_push_result);
+
+  // Verify the SYN_REPLY.
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  // Verify the pushed stream.
+  EXPECT_TRUE(response2.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushServerAborted) {
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyPush(NULL,
+                                    0,
+                                    2,
+                                    1,
+                                    "http://www.google.com/foo.dat"));
+  scoped_ptr<SpdyFrame>
+    stream2_rst(ConstructSpdyRstStream(2, PROTOCOL_ERROR));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 2),
+    CreateMockRead(*stream2_syn, 3),
+    CreateMockRead(*stream2_rst, 4),
+    CreateMockRead(*stream1_body, 5, SYNCHRONOUS),
+    MockRead(ASYNC, ERR_IO_PENDING, 6),  // Force a pause
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  // Start the transaction with basic parameters.
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  rv = callback.WaitForResult();
+  EXPECT_EQ(OK, rv);
+
+  // Verify that we consumed all test data.
+  EXPECT_TRUE(data.at_read_eof()) << "Read count: "
+                                   << data.read_count()
+                                   << " Read index: "
+                                   << data.read_index();
+  EXPECT_TRUE(data.at_write_eof()) << "Write count: "
+                                    << data.write_count()
+                                    << " Write index: "
+                                    << data.write_index();
+
+  // Verify the SYN_REPLY.
+  HttpResponseInfo response = *trans->GetResponseInfo();
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushDuplicate) {
+  // Verify that we don't leak streams and that we properly send a reset
+  // if the server pushes the same stream twice.
+  static const unsigned char kPushBodyFrame[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x06,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  scoped_ptr<SpdyFrame>
+      stream3_rst(ConstructSpdyRstStream(4, PROTOCOL_ERROR));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+    CreateMockWrite(*stream3_rst, 5),
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyPush(NULL,
+                                    0,
+                                    2,
+                                    1,
+                                    "http://www.google.com/foo.dat"));
+  scoped_ptr<SpdyFrame>
+      stream3_syn(ConstructSpdyPush(NULL,
+                                    0,
+                                    4,
+                                    1,
+                                    "http://www.google.com/foo.dat"));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 2),
+    CreateMockRead(*stream2_syn, 3),
+    CreateMockRead(*stream3_syn, 4),
+    CreateMockRead(*stream1_body, 6, SYNCHRONOUS),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+             arraysize(kPushBodyFrame), 7),
+    MockRead(ASYNC, ERR_IO_PENDING, 8),  // Force a pause
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  std::string expected_push_result("pushed");
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  RunServerPushTest(&data,
+                    &response,
+                    &response2,
+                    expected_push_result);
+
+  // Verify the SYN_REPLY.
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  // Verify the pushed stream.
+  EXPECT_TRUE(response2.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushMultipleDataFrame) {
+  static const unsigned char kPushBodyFrame1[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x1F,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+  static const char kPushBodyFrame2[] = " my darling";
+  static const char kPushBodyFrame3[] = " hello";
+  static const char kPushBodyFrame4[] = " my baby";
+
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyPush(NULL,
+                                    0,
+                                    2,
+                                    1,
+                                    "http://www.google.com/foo.dat"));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 2),
+    CreateMockRead(*stream2_syn, 3),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame1),
+             arraysize(kPushBodyFrame1), 4),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame2),
+             arraysize(kPushBodyFrame2) - 1, 5),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame3),
+             arraysize(kPushBodyFrame3) - 1, 6),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame4),
+             arraysize(kPushBodyFrame4) - 1, 7),
+    CreateMockRead(*stream1_body, 8, SYNCHRONOUS),
+    MockRead(ASYNC, ERR_IO_PENDING, 9),  // Force a pause
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  std::string expected_push_result("pushed my darling hello my baby");
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  RunServerPushTest(&data,
+                    &response,
+                    &response2,
+                    expected_push_result);
+
+  // Verify the SYN_REPLY.
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  // Verify the pushed stream.
+  EXPECT_TRUE(response2.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test,
+       ServerPushMultipleDataFrameInterrupted) {
+  static const unsigned char kPushBodyFrame1[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x1F,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+  static const char kPushBodyFrame2[] = " my darling";
+  static const char kPushBodyFrame3[] = " hello";
+  static const char kPushBodyFrame4[] = " my baby";
+
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyPush(NULL,
+                                    0,
+                                    2,
+                                    1,
+                                    "http://www.google.com/foo.dat"));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 2),
+    CreateMockRead(*stream2_syn, 3),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame1),
+             arraysize(kPushBodyFrame1), 4),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame2),
+             arraysize(kPushBodyFrame2) - 1, 5),
+    MockRead(ASYNC, ERR_IO_PENDING, 6),  // Force a pause
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame3),
+             arraysize(kPushBodyFrame3) - 1, 7),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame4),
+             arraysize(kPushBodyFrame4) - 1, 8),
+    CreateMockRead(*stream1_body.get(), 9, SYNCHRONOUS),
+    MockRead(ASYNC, ERR_IO_PENDING, 10)  // Force a pause.
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  std::string expected_push_result("pushed my darling hello my baby");
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  RunServerPushTest(&data,
+                    &response,
+                    &response2,
+                    expected_push_result);
+
+  // Verify the SYN_REPLY.
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  // Verify the pushed stream.
+  EXPECT_TRUE(response2.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushInvalidAssociatedStreamID0) {
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  scoped_ptr<SpdyFrame>
+      stream2_rst(ConstructSpdyRstStream(2, REFUSED_STREAM));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+    CreateMockWrite(*stream2_rst, 4),
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyPush(NULL,
+                                    0,
+                                    2,
+                                    0,
+                                    "http://www.google.com/foo.dat"));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 2),
+    CreateMockRead(*stream2_syn, 3),
+    CreateMockRead(*stream1_body, 4),
+    MockRead(ASYNC, ERR_IO_PENDING, 5)  // Force a pause
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  // Start the transaction with basic parameters.
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  rv = callback.WaitForResult();
+  EXPECT_EQ(OK, rv);
+
+  // Verify that we consumed all test data.
+  EXPECT_TRUE(data.at_read_eof()) << "Read count: "
+                                   << data.read_count()
+                                   << " Read index: "
+                                   << data.read_index();
+  EXPECT_TRUE(data.at_write_eof()) << "Write count: "
+                                    << data.write_count()
+                                    << " Write index: "
+                                    << data.write_index();
+
+  // Verify the SYN_REPLY.
+  HttpResponseInfo response = *trans->GetResponseInfo();
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushInvalidAssociatedStreamID9) {
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  scoped_ptr<SpdyFrame>
+      stream2_rst(ConstructSpdyRstStream(2, INVALID_STREAM));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+    CreateMockWrite(*stream2_rst, 4),
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyPush(NULL,
+                                    0,
+                                    2,
+                                    9,
+                                    "http://www.google.com/foo.dat"));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 2),
+    CreateMockRead(*stream2_syn, 3),
+    CreateMockRead(*stream1_body, 4),
+    MockRead(ASYNC, ERR_IO_PENDING, 5),  // Force a pause
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  // Start the transaction with basic parameters.
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  rv = callback.WaitForResult();
+  EXPECT_EQ(OK, rv);
+
+  // Verify that we consumed all test data.
+  EXPECT_TRUE(data.at_read_eof()) << "Read count: "
+                                   << data.read_count()
+                                   << " Read index: "
+                                   << data.read_index();
+  EXPECT_TRUE(data.at_write_eof()) << "Write count: "
+                                    << data.write_count()
+                                    << " Write index: "
+                                    << data.write_index();
+
+  // Verify the SYN_REPLY.
+  HttpResponseInfo response = *trans->GetResponseInfo();
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushNoURL) {
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  scoped_ptr<SpdyFrame>
+      stream2_rst(ConstructSpdyRstStream(2, PROTOCOL_ERROR));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+    CreateMockWrite(*stream2_rst, 4),
+  };
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyPush(NULL, 0, 2, 1));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 2),
+    CreateMockRead(*stream2_syn, 3),
+    CreateMockRead(*stream1_body, 4),
+    MockRead(ASYNC, ERR_IO_PENDING, 5)  // Force a pause
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  // Start the transaction with basic parameters.
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  rv = callback.WaitForResult();
+  EXPECT_EQ(OK, rv);
+  // Verify that we consumed all test data.
+  EXPECT_TRUE(data.at_read_eof()) << "Read count: "
+                                   << data.read_count()
+                                   << " Read index: "
+                                   << data.read_index();
+  EXPECT_TRUE(data.at_write_eof()) << "Write count: "
+                                    << data.write_count()
+                                    << " Write index: "
+                                    << data.write_index();
+
+  // Verify the SYN_REPLY.
+  HttpResponseInfo response = *trans->GetResponseInfo();
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+// Verify that various SynReply headers parse correctly through the
+// HTTP layer.
+TEST_P(SpdyNetworkTransactionSpdy3Test, SynReplyHeaders) {
+  struct SynReplyHeadersTests {
+    int num_headers;
+    const char* extra_headers[5];
+    const char* expected_headers;
+  } test_cases[] = {
+    // This uses a multi-valued cookie header.
+    { 2,
+      { "cookie", "val1",
+        "cookie", "val2",  // will get appended separated by NULL
+        NULL
+      },
+      "status: 200\n"
+      "version: HTTP/1.1\n"
+      "cookie: val1\n"
+      "cookie: val2\n"
+      "hello: bye\n"
+    },
+    // This is the minimalist set of headers.
+    { 0,
+      { NULL },
+      "status: 200\n"
+      "version: HTTP/1.1\n"
+      "hello: bye\n"
+    },
+    // Headers with a comma separated list.
+    { 1,
+      { "cookie", "val1,val2",
+        NULL
+      },
+      "status: 200\n"
+      "version: HTTP/1.1\n"
+      "cookie: val1,val2\n"
+      "hello: bye\n"
+    }
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+    scoped_ptr<SpdyFrame> req(
+        ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+    MockWrite writes[] = { CreateMockWrite(*req) };
+
+    scoped_ptr<SpdyFrame> resp(
+        ConstructSpdyGetSynReply(test_cases[i].extra_headers,
+                                 test_cases[i].num_headers,
+                                 1));
+    scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+    MockRead reads[] = {
+      CreateMockRead(*resp),
+      CreateMockRead(*body),
+      MockRead(ASYNC, 0, 0)  // EOF
+    };
+
+    DelayedSocketData data(1, reads, arraysize(reads),
+                           writes, arraysize(writes));
+    NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                       BoundNetLog(), GetParam(), NULL);
+    helper.RunToCompletion(&data);
+    TransactionHelperResult out = helper.output();
+
+    EXPECT_EQ(OK, out.rv);
+    EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+    EXPECT_EQ("hello!", out.response_data);
+
+    scoped_refptr<HttpResponseHeaders> headers = out.response_info.headers;
+    EXPECT_TRUE(headers.get() != NULL);
+    void* iter = NULL;
+    std::string name, value, lines;
+    while (headers->EnumerateHeaderLines(&iter, &name, &value)) {
+      lines.append(name);
+      lines.append(": ");
+      lines.append(value);
+      lines.append("\n");
+    }
+    EXPECT_EQ(std::string(test_cases[i].expected_headers), lines);
+  }
+}
+
+// Verify that various SynReply headers parse vary fields correctly
+// through the HTTP layer, and the response matches the request.
+TEST_P(SpdyNetworkTransactionSpdy3Test, SynReplyHeadersVary) {
+  static const SpdyHeaderInfo syn_reply_info = {
+    SYN_REPLY,                              // Syn Reply
+    1,                                      // Stream ID
+    0,                                      // Associated Stream ID
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 3),  // Priority
+    0,                                      // Credential Slot
+    CONTROL_FLAG_NONE,                      // Control Flags
+    false,                                  // Compressed
+    INVALID,                                // Status
+    NULL,                                   // Data
+    0,                                      // Data Length
+    DATA_FLAG_NONE                          // Data Flags
+  };
+  // Modify the following data to change/add test cases:
+  struct SynReplyTests {
+    const SpdyHeaderInfo* syn_reply;
+    bool vary_matches;
+    int num_headers[2];
+    const char* extra_headers[2][16];
+  } test_cases[] = {
+    // Test the case of a multi-valued cookie.  When the value is delimited
+    // with NUL characters, it needs to be unfolded into multiple headers.
+    {
+      &syn_reply_info,
+      true,
+      { 1, 4 },
+      { { "cookie",   "val1,val2",
+          NULL
+        },
+        { ":status",   "200",
+          ":version",  "HTTP/1.1",
+          "vary",     "cookie",
+          "url",      "/index.php",
+          NULL
+        }
+      }
+    }, {    // Multiple vary fields.
+      &syn_reply_info,
+      true,
+      { 2, 5 },
+      { { "friend",   "barney",
+          "enemy",    "snaggletooth",
+          NULL
+        },
+        { ":status",   "200",
+          ":version",  "HTTP/1.1",
+          "vary",     "friend",
+          "vary",     "enemy",
+          "url",      "/index.php",
+          NULL
+        }
+      }
+    }, {    // Test a '*' vary field.
+      &syn_reply_info,
+      false,
+      { 1, 4 },
+      { { "cookie",   "val1,val2",
+          NULL
+        },
+        { ":status",   "200",
+          ":version",  "HTTP/1.1",
+          "vary",     "*",
+          "url",      "/index.php",
+          NULL
+        }
+      }
+    }, {    // Multiple comma-separated vary fields.
+      &syn_reply_info,
+      true,
+      { 2, 4 },
+      { { "friend",   "barney",
+          "enemy",    "snaggletooth",
+          NULL
+        },
+        { ":status",   "200",
+          ":version",  "HTTP/1.1",
+          "vary",     "friend,enemy",
+          "url",      "/index.php",
+          NULL
+        }
+      }
+    }
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+    // Construct the request.
+    scoped_ptr<SpdyFrame> frame_req(
+      ConstructSpdyGet(test_cases[i].extra_headers[0],
+                       test_cases[i].num_headers[0],
+                       false, 1, LOWEST));
+
+    MockWrite writes[] = {
+      CreateMockWrite(*frame_req),
+    };
+
+    // Construct the reply.
+    scoped_ptr<SpdyFrame> frame_reply(
+      ConstructSpdyPacket(*test_cases[i].syn_reply,
+                          test_cases[i].extra_headers[1],
+                          test_cases[i].num_headers[1],
+                          NULL,
+                          0));
+
+    scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+    MockRead reads[] = {
+      CreateMockRead(*frame_reply),
+      CreateMockRead(*body),
+      MockRead(ASYNC, 0, 0)  // EOF
+    };
+
+    // Attach the headers to the request.
+    int header_count = test_cases[i].num_headers[0];
+
+    HttpRequestInfo request = CreateGetRequest();
+    for (int ct = 0; ct < header_count; ct++) {
+      const char* header_key = test_cases[i].extra_headers[0][ct * 2];
+      const char* header_value = test_cases[i].extra_headers[0][ct * 2 + 1];
+      request.extra_headers.SetHeader(header_key, header_value);
+    }
+
+    DelayedSocketData data(1, reads, arraysize(reads),
+                           writes, arraysize(writes));
+    NormalSpdyTransactionHelper helper(request,
+                                       BoundNetLog(), GetParam(), NULL);
+    helper.RunToCompletion(&data);
+    TransactionHelperResult out = helper.output();
+
+    EXPECT_EQ(OK, out.rv) << i;
+    EXPECT_EQ("HTTP/1.1 200 OK", out.status_line) << i;
+    EXPECT_EQ("hello!", out.response_data) << i;
+
+    // Test the response information.
+    EXPECT_TRUE(out.response_info.response_time >
+                out.response_info.request_time) << i;
+    base::TimeDelta test_delay = out.response_info.response_time -
+                                 out.response_info.request_time;
+    base::TimeDelta min_expected_delay;
+    min_expected_delay.FromMilliseconds(10);
+    EXPECT_GT(test_delay.InMillisecondsF(),
+              min_expected_delay.InMillisecondsF()) << i;
+    EXPECT_EQ(out.response_info.vary_data.is_valid(),
+              test_cases[i].vary_matches) << i;
+
+    // Check the headers.
+    scoped_refptr<HttpResponseHeaders> headers = out.response_info.headers;
+    ASSERT_TRUE(headers.get() != NULL) << i;
+    void* iter = NULL;
+    std::string name, value, lines;
+    while (headers->EnumerateHeaderLines(&iter, &name, &value)) {
+      lines.append(name);
+      lines.append(": ");
+      lines.append(value);
+      lines.append("\n");
+    }
+
+    // Construct the expected header reply string.
+    char reply_buffer[256] = "";
+    ConstructSpdyReplyString(test_cases[i].extra_headers[1],
+                             test_cases[i].num_headers[1],
+                             reply_buffer,
+                             256);
+
+    EXPECT_EQ(std::string(reply_buffer), lines) << i;
+  }
+}
+
+// Verify that we don't crash on invalid SynReply responses.
+TEST_P(SpdyNetworkTransactionSpdy3Test, InvalidSynReply) {
+  const SpdyHeaderInfo kSynStartHeader = {
+    SYN_REPLY,              // Kind = SynReply
+    1,                      // Stream ID
+    0,                      // Associated stream ID
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 3),  // Priority
+    0,                      // Credential Slot
+    CONTROL_FLAG_NONE,      // Control Flags
+    false,                  // Compressed
+    INVALID,                // Status
+    NULL,                   // Data
+    0,                      // Length
+    DATA_FLAG_NONE          // Data Flags
+  };
+
+  struct InvalidSynReplyTests {
+    int num_headers;
+    const char* headers[10];
+  } test_cases[] = {
+    // SYN_REPLY missing status header
+    { 4,
+      { "cookie", "val1",
+        "cookie", "val2",
+        "url", "/index.php",
+        "version", "HTTP/1.1",
+        NULL
+      },
+    },
+    // SYN_REPLY missing version header
+    { 2,
+      { ":status", "200",
+        "url", "/index.php",
+        NULL
+      },
+    },
+    // SYN_REPLY with no headers
+    { 0, { NULL }, },
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+    scoped_ptr<SpdyFrame> req(
+        ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+    MockWrite writes[] = {
+      CreateMockWrite(*req),
+    };
+
+    scoped_ptr<SpdyFrame> resp(
+        ConstructSpdyPacket(kSynStartHeader,
+                            NULL, 0,
+                            test_cases[i].headers,
+                            test_cases[i].num_headers));
+    scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+    MockRead reads[] = {
+      CreateMockRead(*resp),
+      CreateMockRead(*body),
+      MockRead(ASYNC, 0, 0)  // EOF
+    };
+
+    DelayedSocketData data(1, reads, arraysize(reads),
+                           writes, arraysize(writes));
+    NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                       BoundNetLog(), GetParam(), NULL);
+    helper.RunToCompletion(&data);
+    TransactionHelperResult out = helper.output();
+    EXPECT_EQ(ERR_INCOMPLETE_SPDY_HEADERS, out.rv);
+  }
+}
+
+// Verify that we don't crash on some corrupt frames.
+TEST_P(SpdyNetworkTransactionSpdy3Test, CorruptFrameSessionError) {
+  // This is the length field that's too short.
+  scoped_ptr<SpdyFrame> syn_reply_wrong_length(
+      ConstructSpdyGetSynReply(NULL, 0, 1));
+  syn_reply_wrong_length->set_length(syn_reply_wrong_length->length() - 4);
+
+  struct SynReplyTests {
+    const SpdyFrame* syn_reply;
+  } test_cases[] = {
+    { syn_reply_wrong_length.get(), },
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+    scoped_ptr<SpdyFrame> req(
+        ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+    MockWrite writes[] = {
+      CreateMockWrite(*req),
+      MockWrite(ASYNC, 0, 0)  // EOF
+    };
+
+    scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+    MockRead reads[] = {
+      CreateMockRead(*test_cases[i].syn_reply),
+      CreateMockRead(*body),
+      MockRead(ASYNC, 0, 0)  // EOF
+    };
+
+    DelayedSocketData data(1, reads, arraysize(reads),
+                           writes, arraysize(writes));
+    NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                       BoundNetLog(), GetParam(), NULL);
+    helper.RunToCompletion(&data);
+    TransactionHelperResult out = helper.output();
+    EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+  }
+}
+
+// Test that we shutdown correctly on write errors.
+TEST_P(SpdyNetworkTransactionSpdy3Test, WriteError) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = {
+    // We'll write 10 bytes successfully
+    MockWrite(ASYNC, req->data(), 10),
+    // Followed by ERROR!
+    MockWrite(ASYNC, ERR_FAILED),
+  };
+
+  DelayedSocketData data(2, NULL, 0,
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(ERR_FAILED, out.rv);
+  data.Reset();
+}
+
+// Test that partial writes work.
+TEST_P(SpdyNetworkTransactionSpdy3Test, PartialWrite) {
+  // Chop the SYN_STREAM frame into 5 chunks.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  const int kChunks = 5;
+  scoped_array<MockWrite> writes(ChopWriteFrame(*req.get(), kChunks));
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(kChunks, reads, arraysize(reads),
+                         writes.get(), kChunks);
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+}
+
+// In this test, we enable compression, but get a uncompressed SynReply from
+// the server.  Verify that teardown is all clean.
+TEST_P(SpdyNetworkTransactionSpdy3Test, DecompressFailureOnSynReply) {
+  scoped_ptr<SpdyFrame> compressed(
+      ConstructSpdyGet(NULL, 0, true, 1, LOWEST));
+  scoped_ptr<SpdyFrame> rst(
+      ConstructSpdyRstStream(1, PROTOCOL_ERROR));
+  MockWrite writes[] = {
+    CreateMockWrite(*compressed),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  SpdySessionDependencies* session_deps = new SpdySessionDependencies();
+  session_deps->enable_compression = true;
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), session_deps);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+  data.Reset();
+}
+
+// Test that the NetLog contains good data for a simple GET request.
+TEST_P(SpdyNetworkTransactionSpdy3Test, NetLog) {
+  static const char* const kExtraHeaders[] = {
+    "user-agent",   "Chrome",
+  };
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(kExtraHeaders, 1, false, 1,
+                                                   LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  CapturingBoundNetLog log;
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequestWithUserAgent(),
+                                     log.bound(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+
+  // Check that the NetLog was filled reasonably.
+  // This test is intentionally non-specific about the exact ordering of the
+  // log; instead we just check to make sure that certain events exist, and that
+  // they are in the right order.
+  net::CapturingNetLog::CapturedEntryList entries;
+  log.GetEntries(&entries);
+
+  EXPECT_LT(0u, entries.size());
+  int pos = 0;
+  pos = net::ExpectLogContainsSomewhere(entries, 0,
+      net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST,
+      net::NetLog::PHASE_BEGIN);
+  pos = net::ExpectLogContainsSomewhere(entries, pos + 1,
+      net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST,
+      net::NetLog::PHASE_END);
+  pos = net::ExpectLogContainsSomewhere(entries, pos + 1,
+      net::NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS,
+      net::NetLog::PHASE_BEGIN);
+  pos = net::ExpectLogContainsSomewhere(entries, pos + 1,
+      net::NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS,
+      net::NetLog::PHASE_END);
+  pos = net::ExpectLogContainsSomewhere(entries, pos + 1,
+      net::NetLog::TYPE_HTTP_TRANSACTION_READ_BODY,
+      net::NetLog::PHASE_BEGIN);
+  pos = net::ExpectLogContainsSomewhere(entries, pos + 1,
+      net::NetLog::TYPE_HTTP_TRANSACTION_READ_BODY,
+      net::NetLog::PHASE_END);
+
+  // Check that we logged all the headers correctly
+  pos = net::ExpectLogContainsSomewhere(
+      entries, 0,
+      net::NetLog::TYPE_SPDY_SESSION_SYN_STREAM,
+      net::NetLog::PHASE_NONE);
+
+  ListValue* header_list;
+  ASSERT_TRUE(entries[pos].params.get());
+  ASSERT_TRUE(entries[pos].params->GetList("headers", &header_list));
+
+  std::vector<std::string> expected;
+  expected.push_back(":host: www.google.com");
+  expected.push_back(":path: /");
+  expected.push_back(":scheme: http");
+  expected.push_back(":version: HTTP/1.1");
+  expected.push_back(":method: GET");
+  expected.push_back("user-agent: Chrome");
+  EXPECT_EQ(expected.size(), header_list->GetSize());
+  for (std::vector<std::string>::const_iterator it = expected.begin();
+       it != expected.end();
+       ++it) {
+    base::StringValue header(*it);
+    EXPECT_NE(header_list->end(), header_list->Find(header)) <<
+        "Header not found: " << *it;
+  }
+}
+
+// Since we buffer the IO from the stream to the renderer, this test verifies
+// that when we read out the maximum amount of data (e.g. we received 50 bytes
+// on the network, but issued a Read for only 5 of those bytes) that the data
+// flow still works correctly.
+TEST_P(SpdyNetworkTransactionSpdy3Test, BufferFull) {
+  BufferedSpdyFramer framer(3, false);
+
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  // 2 data frames in a single read.
+  scoped_ptr<SpdyFrame> data_frame_1(
+      framer.CreateDataFrame(1, "goodby", 6, DATA_FLAG_NONE));
+  scoped_ptr<SpdyFrame> data_frame_2(
+      framer.CreateDataFrame(1, "e worl", 6, DATA_FLAG_NONE));
+  const SpdyFrame* data_frames[2] = {
+    data_frame_1.get(),
+    data_frame_2.get(),
+  };
+  char combined_data_frames[100];
+  int combined_data_frames_len =
+      CombineFrames(data_frames, arraysize(data_frames),
+                    combined_data_frames, arraysize(combined_data_frames));
+  scoped_ptr<SpdyFrame> last_frame(
+      framer.CreateDataFrame(1, "d", 1, DATA_FLAG_FIN));
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    MockRead(ASYNC, ERR_IO_PENDING),  // Force a pause
+    MockRead(ASYNC, combined_data_frames, combined_data_frames_len),
+    MockRead(ASYNC, ERR_IO_PENDING),  // Force a pause
+    CreateMockRead(*last_frame),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  TestCompletionCallback callback;
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  TransactionHelperResult out = helper.output();
+  out.rv = callback.WaitForResult();
+  EXPECT_EQ(out.rv, OK);
+
+  const HttpResponseInfo* response = trans->GetResponseInfo();
+  EXPECT_TRUE(response->headers != NULL);
+  EXPECT_TRUE(response->was_fetched_via_spdy);
+  out.status_line = response->headers->GetStatusLine();
+  out.response_info = *response;  // Make a copy so we can verify.
+
+  // Read Data
+  TestCompletionCallback read_callback;
+
+  std::string content;
+  do {
+    // Read small chunks at a time.
+    const int kSmallReadSize = 3;
+    scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize));
+    rv = trans->Read(buf, kSmallReadSize, read_callback.callback());
+    if (rv == net::ERR_IO_PENDING) {
+      data.CompleteRead();
+      rv = read_callback.WaitForResult();
+    }
+    if (rv > 0) {
+      content.append(buf->data(), rv);
+    } else if (rv < 0) {
+      NOTREACHED();
+    }
+  } while (rv > 0);
+
+  out.response_data.swap(content);
+
+  // Flush the MessageLoop while the SpdySessionDependencies (in particular, the
+  // MockClientSocketFactory) are still alive.
+  MessageLoop::current()->RunUntilIdle();
+
+  // Verify that we consumed all test data.
+  helper.VerifyDataConsumed();
+
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("goodbye world", out.response_data);
+}
+
+// Verify that basic buffering works; when multiple data frames arrive
+// at the same time, ensure that we don't notify a read completion for
+// each data frame individually.
+TEST_P(SpdyNetworkTransactionSpdy3Test, Buffering) {
+  BufferedSpdyFramer framer(3, false);
+
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  // 4 data frames in a single read.
+  scoped_ptr<SpdyFrame> data_frame(
+      framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE));
+  scoped_ptr<SpdyFrame> data_frame_fin(
+      framer.CreateDataFrame(1, "message", 7, DATA_FLAG_FIN));
+  const SpdyFrame* data_frames[4] = {
+    data_frame.get(),
+    data_frame.get(),
+    data_frame.get(),
+    data_frame_fin.get()
+  };
+  char combined_data_frames[100];
+  int combined_data_frames_len =
+      CombineFrames(data_frames, arraysize(data_frames),
+                    combined_data_frames, arraysize(combined_data_frames));
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    MockRead(ASYNC, ERR_IO_PENDING),  // Force a pause
+    MockRead(ASYNC, combined_data_frames, combined_data_frames_len),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  TransactionHelperResult out = helper.output();
+  out.rv = callback.WaitForResult();
+  EXPECT_EQ(out.rv, OK);
+
+  const HttpResponseInfo* response = trans->GetResponseInfo();
+  EXPECT_TRUE(response->headers != NULL);
+  EXPECT_TRUE(response->was_fetched_via_spdy);
+  out.status_line = response->headers->GetStatusLine();
+  out.response_info = *response;  // Make a copy so we can verify.
+
+  // Read Data
+  TestCompletionCallback read_callback;
+
+  std::string content;
+  int reads_completed = 0;
+  do {
+    // Read small chunks at a time.
+    const int kSmallReadSize = 14;
+    scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize));
+    rv = trans->Read(buf, kSmallReadSize, read_callback.callback());
+    if (rv == net::ERR_IO_PENDING) {
+      data.CompleteRead();
+      rv = read_callback.WaitForResult();
+    }
+    if (rv > 0) {
+      EXPECT_EQ(kSmallReadSize, rv);
+      content.append(buf->data(), rv);
+    } else if (rv < 0) {
+      FAIL() << "Unexpected read error: " << rv;
+    }
+    reads_completed++;
+  } while (rv > 0);
+
+  EXPECT_EQ(3, reads_completed);  // Reads are: 14 bytes, 14 bytes, 0 bytes.
+
+  out.response_data.swap(content);
+
+  // Flush the MessageLoop while the SpdySessionDependencies (in particular, the
+  // MockClientSocketFactory) are still alive.
+  MessageLoop::current()->RunUntilIdle();
+
+  // Verify that we consumed all test data.
+  helper.VerifyDataConsumed();
+
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("messagemessagemessagemessage", out.response_data);
+}
+
+// Verify the case where we buffer data but read it after it has been buffered.
+TEST_P(SpdyNetworkTransactionSpdy3Test, BufferedAll) {
+  BufferedSpdyFramer framer(3, false);
+
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  // 5 data frames in a single read.
+  scoped_ptr<SpdyFrame> syn_reply(
+      ConstructSpdyGetSynReply(NULL, 0, 1));
+  syn_reply->set_flags(CONTROL_FLAG_NONE);  // turn off FIN bit
+  scoped_ptr<SpdyFrame> data_frame(
+      framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE));
+  scoped_ptr<SpdyFrame> data_frame_fin(
+      framer.CreateDataFrame(1, "message", 7, DATA_FLAG_FIN));
+  const SpdyFrame* frames[5] = {
+    syn_reply.get(),
+    data_frame.get(),
+    data_frame.get(),
+    data_frame.get(),
+    data_frame_fin.get()
+  };
+  char combined_frames[200];
+  int combined_frames_len =
+      CombineFrames(frames, arraysize(frames),
+                    combined_frames, arraysize(combined_frames));
+
+  MockRead reads[] = {
+    MockRead(ASYNC, combined_frames, combined_frames_len),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  TransactionHelperResult out = helper.output();
+  out.rv = callback.WaitForResult();
+  EXPECT_EQ(out.rv, OK);
+
+  const HttpResponseInfo* response = trans->GetResponseInfo();
+  EXPECT_TRUE(response->headers != NULL);
+  EXPECT_TRUE(response->was_fetched_via_spdy);
+  out.status_line = response->headers->GetStatusLine();
+  out.response_info = *response;  // Make a copy so we can verify.
+
+  // Read Data
+  TestCompletionCallback read_callback;
+
+  std::string content;
+  int reads_completed = 0;
+  do {
+    // Read small chunks at a time.
+    const int kSmallReadSize = 14;
+    scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize));
+    rv = trans->Read(buf, kSmallReadSize, read_callback.callback());
+    if (rv > 0) {
+      EXPECT_EQ(kSmallReadSize, rv);
+      content.append(buf->data(), rv);
+    } else if (rv < 0) {
+      FAIL() << "Unexpected read error: " << rv;
+    }
+    reads_completed++;
+  } while (rv > 0);
+
+  EXPECT_EQ(3, reads_completed);
+
+  out.response_data.swap(content);
+
+  // Flush the MessageLoop while the SpdySessionDependencies (in particular, the
+  // MockClientSocketFactory) are still alive.
+  MessageLoop::current()->RunUntilIdle();
+
+  // Verify that we consumed all test data.
+  helper.VerifyDataConsumed();
+
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("messagemessagemessagemessage", out.response_data);
+}
+
+// Verify the case where we buffer data and close the connection.
+TEST_P(SpdyNetworkTransactionSpdy3Test, BufferedClosed) {
+  BufferedSpdyFramer framer(3, false);
+
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  // All data frames in a single read.
+  // NOTE: We don't FIN the stream.
+  scoped_ptr<SpdyFrame> data_frame(
+      framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE));
+  const SpdyFrame* data_frames[4] = {
+    data_frame.get(),
+    data_frame.get(),
+    data_frame.get(),
+    data_frame.get()
+  };
+  char combined_data_frames[100];
+  int combined_data_frames_len =
+      CombineFrames(data_frames, arraysize(data_frames),
+                    combined_data_frames, arraysize(combined_data_frames));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    MockRead(ASYNC, ERR_IO_PENDING),  // Force a wait
+    MockRead(ASYNC, combined_data_frames, combined_data_frames_len),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  TransactionHelperResult out = helper.output();
+  out.rv = callback.WaitForResult();
+  EXPECT_EQ(out.rv, OK);
+
+  const HttpResponseInfo* response = trans->GetResponseInfo();
+  EXPECT_TRUE(response->headers != NULL);
+  EXPECT_TRUE(response->was_fetched_via_spdy);
+  out.status_line = response->headers->GetStatusLine();
+  out.response_info = *response;  // Make a copy so we can verify.
+
+  // Read Data
+  TestCompletionCallback read_callback;
+
+  std::string content;
+  int reads_completed = 0;
+  do {
+    // Read small chunks at a time.
+    const int kSmallReadSize = 14;
+    scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize));
+    rv = trans->Read(buf, kSmallReadSize, read_callback.callback());
+    if (rv == net::ERR_IO_PENDING) {
+      data.CompleteRead();
+      rv = read_callback.WaitForResult();
+    }
+    if (rv > 0) {
+      content.append(buf->data(), rv);
+    } else if (rv < 0) {
+      // This test intentionally closes the connection, and will get an error.
+      EXPECT_EQ(ERR_CONNECTION_CLOSED, rv);
+      break;
+    }
+    reads_completed++;
+  } while (rv > 0);
+
+  EXPECT_EQ(0, reads_completed);
+
+  out.response_data.swap(content);
+
+  // Flush the MessageLoop while the SpdySessionDependencies (in particular, the
+  // MockClientSocketFactory) are still alive.
+  MessageLoop::current()->RunUntilIdle();
+
+  // Verify that we consumed all test data.
+  helper.VerifyDataConsumed();
+}
+
+// Verify the case where we buffer data and cancel the transaction.
+TEST_P(SpdyNetworkTransactionSpdy3Test, BufferedCancelled) {
+  BufferedSpdyFramer framer(3, false);
+
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  // NOTE: We don't FIN the stream.
+  scoped_ptr<SpdyFrame> data_frame(
+      framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE));
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    MockRead(ASYNC, ERR_IO_PENDING),  // Force a wait
+    CreateMockRead(*data_frame),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+  TestCompletionCallback callback;
+
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  TransactionHelperResult out = helper.output();
+  out.rv = callback.WaitForResult();
+  EXPECT_EQ(out.rv, OK);
+
+  const HttpResponseInfo* response = trans->GetResponseInfo();
+  EXPECT_TRUE(response->headers != NULL);
+  EXPECT_TRUE(response->was_fetched_via_spdy);
+  out.status_line = response->headers->GetStatusLine();
+  out.response_info = *response;  // Make a copy so we can verify.
+
+  // Read Data
+  TestCompletionCallback read_callback;
+
+  do {
+    const int kReadSize = 256;
+    scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kReadSize));
+    rv = trans->Read(buf, kReadSize, read_callback.callback());
+    if (rv == net::ERR_IO_PENDING) {
+      // Complete the read now, which causes buffering to start.
+      data.CompleteRead();
+      // Destroy the transaction, causing the stream to get cancelled
+      // and orphaning the buffered IO task.
+      helper.ResetTrans();
+      break;
+    }
+    // We shouldn't get here in this test.
+    FAIL() << "Unexpected read: " << rv;
+  } while (rv > 0);
+
+  // Flush the MessageLoop; this will cause the buffered IO task
+  // to run for the final time.
+  MessageLoop::current()->RunUntilIdle();
+
+  // Verify that we consumed all test data.
+  helper.VerifyDataConsumed();
+}
+
+// Test that if the server requests persistence of settings, that we save
+// the settings in the HttpServerProperties.
+TEST_P(SpdyNetworkTransactionSpdy3Test, SettingsSaved) {
+  static const SpdyHeaderInfo kSynReplyInfo = {
+    SYN_REPLY,                              // Syn Reply
+    1,                                      // Stream ID
+    0,                                      // Associated Stream ID
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 3),  // Priority
+    0,                                      // Credential Slot
+    CONTROL_FLAG_NONE,                      // Control Flags
+    false,                                  // Compressed
+    INVALID,                                // Status
+    NULL,                                   // Data
+    0,                                      // Data Length
+    DATA_FLAG_NONE                          // Data Flags
+  };
+  static const char* const kExtraHeaders[] = {
+    ":status",   "200",
+    ":version",  "HTTP/1.1"
+  };
+
+  BoundNetLog net_log;
+  NormalSpdyTransactionHelper helper(CreateGetRequest(), net_log,
+                                     GetParam(), NULL);
+  helper.RunPreTestSetup();
+
+  // Verify that no settings exist initially.
+  HostPortPair host_port_pair("www.google.com", helper.port());
+  SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool();
+  EXPECT_TRUE(spdy_session_pool->http_server_properties()->GetSpdySettings(
+      host_port_pair).empty());
+
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  // Construct the reply.
+  scoped_ptr<SpdyFrame> reply(
+    ConstructSpdyPacket(kSynReplyInfo,
+                        kExtraHeaders,
+                        arraysize(kExtraHeaders) / 2,
+                        NULL,
+                        0));
+
+  const SpdySettingsIds kSampleId1 = SETTINGS_UPLOAD_BANDWIDTH;
+  unsigned int kSampleValue1 = 0x0a0a0a0a;
+  const SpdySettingsIds kSampleId2 = SETTINGS_DOWNLOAD_BANDWIDTH;
+  unsigned int kSampleValue2 = 0x0b0b0b0b;
+  const SpdySettingsIds kSampleId3 = SETTINGS_ROUND_TRIP_TIME;
+  unsigned int kSampleValue3 = 0x0c0c0c0c;
+  scoped_ptr<SpdyFrame> settings_frame;
+  {
+    // Construct the SETTINGS frame.
+    SettingsMap settings;
+    // First add a persisted setting.
+    settings[kSampleId1] =
+        SettingsFlagsAndValue(SETTINGS_FLAG_PLEASE_PERSIST, kSampleValue1);
+    // Next add a non-persisted setting.
+    settings[kSampleId2] =
+        SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kSampleValue2);
+    // Next add another persisted setting.
+    settings[kSampleId3] =
+        SettingsFlagsAndValue(SETTINGS_FLAG_PLEASE_PERSIST, kSampleValue3);
+    settings_frame.reset(ConstructSpdySettings(settings));
+  }
+
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*reply),
+    CreateMockRead(*body),
+    CreateMockRead(*settings_frame),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  helper.AddData(&data);
+  helper.RunDefaultTest();
+  helper.VerifyDataConsumed();
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+
+  {
+    // Verify we had two persisted settings.
+    const SettingsMap& settings_map =
+        spdy_session_pool->http_server_properties()->GetSpdySettings(
+            host_port_pair);
+    ASSERT_EQ(2u, settings_map.size());
+
+    // Verify the first persisted setting.
+    SettingsMap::const_iterator it1 = settings_map.find(kSampleId1);
+    EXPECT_TRUE(it1 != settings_map.end());
+    SettingsFlagsAndValue flags_and_value1 = it1->second;
+    EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value1.first);
+    EXPECT_EQ(kSampleValue1, flags_and_value1.second);
+
+    // Verify the second persisted setting.
+    SettingsMap::const_iterator it3 = settings_map.find(kSampleId3);
+    EXPECT_TRUE(it3 != settings_map.end());
+    SettingsFlagsAndValue flags_and_value3 = it3->second;
+    EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value3.first);
+    EXPECT_EQ(kSampleValue3, flags_and_value3.second);
+  }
+}
+
+// Test that when there are settings saved that they are sent back to the
+// server upon session establishment.
+TEST_P(SpdyNetworkTransactionSpdy3Test, SettingsPlayback) {
+  static const SpdyHeaderInfo kSynReplyInfo = {
+    SYN_REPLY,                              // Syn Reply
+    1,                                      // Stream ID
+    0,                                      // Associated Stream ID
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 3),  // Priority
+    0,                                      // Credential Slot
+    CONTROL_FLAG_NONE,                      // Control Flags
+    false,                                  // Compressed
+    INVALID,                                // Status
+    NULL,                                   // Data
+    0,                                      // Data Length
+    DATA_FLAG_NONE                          // Data Flags
+  };
+  static const char* kExtraHeaders[] = {
+    ":status",   "200",
+    ":version",  "HTTP/1.1"
+  };
+
+  BoundNetLog net_log;
+  NormalSpdyTransactionHelper helper(CreateGetRequest(), net_log,
+                                     GetParam(), NULL);
+  helper.RunPreTestSetup();
+
+  // Verify that no settings exist initially.
+  HostPortPair host_port_pair("www.google.com", helper.port());
+  SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool();
+  EXPECT_TRUE(spdy_session_pool->http_server_properties()->GetSpdySettings(
+      host_port_pair).empty());
+
+  const SpdySettingsIds kSampleId1 = SETTINGS_UPLOAD_BANDWIDTH;
+  unsigned int kSampleValue1 = 0x0a0a0a0a;
+  const SpdySettingsIds kSampleId2 = SETTINGS_ROUND_TRIP_TIME;
+  unsigned int kSampleValue2 = 0x0c0c0c0c;
+
+  // First add a persisted setting.
+  spdy_session_pool->http_server_properties()->SetSpdySetting(
+      host_port_pair,
+      kSampleId1,
+      SETTINGS_FLAG_PLEASE_PERSIST,
+      kSampleValue1);
+
+  // Next add another persisted setting.
+  spdy_session_pool->http_server_properties()->SetSpdySetting(
+      host_port_pair,
+      kSampleId2,
+      SETTINGS_FLAG_PLEASE_PERSIST,
+      kSampleValue2);
+
+  EXPECT_EQ(2u, spdy_session_pool->http_server_properties()->GetSpdySettings(
+      host_port_pair).size());
+
+  // Construct the SETTINGS frame.
+  const SettingsMap& settings =
+      spdy_session_pool->http_server_properties()->GetSpdySettings(
+          host_port_pair);
+  scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+
+  MockWrite writes[] = {
+    CreateMockWrite(*settings_frame),
+    CreateMockWrite(*req),
+  };
+
+  // Construct the reply.
+  scoped_ptr<SpdyFrame> reply(
+    ConstructSpdyPacket(kSynReplyInfo,
+                        kExtraHeaders,
+                        arraysize(kExtraHeaders) / 2,
+                        NULL,
+                        0));
+
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*reply),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(2, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  helper.AddData(&data);
+  helper.RunDefaultTest();
+  helper.VerifyDataConsumed();
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+
+  {
+    // Verify we had two persisted settings.
+    const SettingsMap& settings_map =
+        spdy_session_pool->http_server_properties()->GetSpdySettings(
+            host_port_pair);
+    ASSERT_EQ(2u, settings_map.size());
+
+    // Verify the first persisted setting.
+    SettingsMap::const_iterator it1 = settings_map.find(kSampleId1);
+    EXPECT_TRUE(it1 != settings_map.end());
+    SettingsFlagsAndValue flags_and_value1 = it1->second;
+    EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value1.first);
+    EXPECT_EQ(kSampleValue1, flags_and_value1.second);
+
+    // Verify the second persisted setting.
+    SettingsMap::const_iterator it2 = settings_map.find(kSampleId2);
+    EXPECT_TRUE(it2 != settings_map.end());
+    SettingsFlagsAndValue flags_and_value2 = it2->second;
+    EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value2.first);
+    EXPECT_EQ(kSampleValue2, flags_and_value2.second);
+  }
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, GoAwayWithActiveStream) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  scoped_ptr<SpdyFrame> go_away(ConstructSpdyGoAway());
+  MockRead reads[] = {
+    CreateMockRead(*go_away),
+    MockRead(ASYNC, 0, 0),  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.AddData(&data);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(ERR_ABORTED, out.rv);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, CloseWithActiveStream) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    MockRead(SYNCHRONOUS, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  BoundNetLog log;
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     log, GetParam(), NULL);
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  TransactionHelperResult out;
+  out.rv = trans->Start(&CreateGetRequest(), callback.callback(), log);
+
+  EXPECT_EQ(out.rv, ERR_IO_PENDING);
+  out.rv = callback.WaitForResult();
+  EXPECT_EQ(out.rv, OK);
+
+  const HttpResponseInfo* response = trans->GetResponseInfo();
+  EXPECT_TRUE(response->headers != NULL);
+  EXPECT_TRUE(response->was_fetched_via_spdy);
+  out.rv = ReadTransaction(trans, &out.response_data);
+  EXPECT_EQ(ERR_CONNECTION_CLOSED, out.rv);
+
+  // Verify that we consumed all test data.
+  helper.VerifyDataConsumed();
+}
+
+// Test to make sure we can correctly connect through a proxy.
+TEST_P(SpdyNetworkTransactionSpdy3Test, ProxyConnect) {
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.session_deps().reset(new SpdySessionDependencies(
+      ProxyService::CreateFixedFromPacResult("PROXY myproxy:70")));
+  helper.SetSession(make_scoped_refptr(
+      SpdySessionDependencies::SpdyCreateSession(helper.session_deps().get())));
+  helper.RunPreTestSetup();
+  HttpNetworkTransaction* trans = helper.trans();
+
+  const char kConnect443[] = {"CONNECT www.google.com:443 HTTP/1.1\r\n"
+                           "Host: www.google.com\r\n"
+                           "Proxy-Connection: keep-alive\r\n\r\n"};
+  const char kConnect80[] = {"CONNECT www.google.com:80 HTTP/1.1\r\n"
+                           "Host: www.google.com\r\n"
+                           "Proxy-Connection: keep-alive\r\n\r\n"};
+  const char kHTTP200[] = {"HTTP/1.1 200 OK\r\n\r\n"};
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+
+  MockWrite writes_SPDYNPN[] = {
+    MockWrite(SYNCHRONOUS, kConnect443, arraysize(kConnect443) - 1, 0),
+    CreateMockWrite(*req, 2),
+  };
+  MockRead reads_SPDYNPN[] = {
+    MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1),
+    CreateMockRead(*resp, 3),
+    CreateMockRead(*body.get(), 4),
+    MockRead(ASYNC, 0, 0, 5),
+  };
+
+  MockWrite writes_SPDYSSL[] = {
+    MockWrite(SYNCHRONOUS, kConnect80, arraysize(kConnect80) - 1, 0),
+    CreateMockWrite(*req, 2),
+  };
+  MockRead reads_SPDYSSL[] = {
+    MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1),
+    CreateMockRead(*resp, 3),
+    CreateMockRead(*body.get(), 4),
+    MockRead(ASYNC, 0, 0, 5),
+  };
+
+  MockWrite writes_SPDYNOSSL[] = {
+    CreateMockWrite(*req, 0),
+  };
+
+  MockRead reads_SPDYNOSSL[] = {
+    CreateMockRead(*resp, 1),
+    CreateMockRead(*body.get(), 2),
+    MockRead(ASYNC, 0, 0, 3),
+  };
+
+  scoped_ptr<OrderedSocketData> data;
+  switch(GetParam()) {
+    case SPDYNOSSL:
+      data.reset(new OrderedSocketData(reads_SPDYNOSSL,
+                                       arraysize(reads_SPDYNOSSL),
+                                       writes_SPDYNOSSL,
+                                       arraysize(writes_SPDYNOSSL)));
+      break;
+    case SPDYSSL:
+      data.reset(new OrderedSocketData(reads_SPDYSSL,
+                                       arraysize(reads_SPDYSSL),
+                                       writes_SPDYSSL,
+                                       arraysize(writes_SPDYSSL)));
+      break;
+    case SPDYNPN:
+      data.reset(new OrderedSocketData(reads_SPDYNPN,
+                                       arraysize(reads_SPDYNPN),
+                                       writes_SPDYNPN,
+                                       arraysize(writes_SPDYNPN)));
+      break;
+    default:
+      NOTREACHED();
+  }
+
+  helper.AddData(data.get());
+  TestCompletionCallback callback;
+
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  rv = callback.WaitForResult();
+  EXPECT_EQ(0, rv);
+
+  // Verify the SYN_REPLY.
+  HttpResponseInfo response = *trans->GetResponseInfo();
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  std::string response_data;
+  ASSERT_EQ(OK, ReadTransaction(trans, &response_data));
+  EXPECT_EQ("hello!", response_data);
+  helper.VerifyDataConsumed();
+}
+
+// Test to make sure we can correctly connect through a proxy to www.google.com,
+// if there already exists a direct spdy connection to www.google.com. See
+// http://crbug.com/49874
+TEST_P(SpdyNetworkTransactionSpdy3Test, DirectConnectProxyReconnect) {
+  // When setting up the first transaction, we store the SpdySessionPool so that
+  // we can use the same pool in the second transaction.
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+
+  // Use a proxy service which returns a proxy fallback list from DIRECT to
+  // myproxy:70. For this test there will be no fallback, so it is equivalent
+  // to simply DIRECT. The reason for appending the second proxy is to verify
+  // that the session pool key used does is just "DIRECT".
+  helper.session_deps().reset(new SpdySessionDependencies(
+      ProxyService::CreateFixedFromPacResult("DIRECT; PROXY myproxy:70")));
+  helper.SetSession(make_scoped_refptr(
+      SpdySessionDependencies::SpdyCreateSession(helper.session_deps().get())));
+
+  SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool();
+  helper.RunPreTestSetup();
+
+  // Construct and send a simple GET request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = {
+    CreateMockWrite(*req, 1),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 2),
+    CreateMockRead(*body, 3),
+    MockRead(ASYNC, ERR_IO_PENDING, 4),  // Force a pause
+    MockRead(ASYNC, 0, 5)  // EOF
+  };
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  helper.AddData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+
+  TestCompletionCallback callback;
+  TransactionHelperResult out;
+  out.rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+
+  EXPECT_EQ(out.rv, ERR_IO_PENDING);
+  out.rv = callback.WaitForResult();
+  EXPECT_EQ(out.rv, OK);
+
+  const HttpResponseInfo* response = trans->GetResponseInfo();
+  EXPECT_TRUE(response->headers != NULL);
+  EXPECT_TRUE(response->was_fetched_via_spdy);
+  out.rv = ReadTransaction(trans, &out.response_data);
+  EXPECT_EQ(OK, out.rv);
+  out.status_line = response->headers->GetStatusLine();
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+
+  // Check that the SpdySession is still in the SpdySessionPool.
+  HostPortPair host_port_pair("www.google.com", helper.port());
+  HostPortProxyPair session_pool_key_direct(
+      host_port_pair, ProxyServer::Direct());
+  EXPECT_TRUE(spdy_session_pool->HasSession(session_pool_key_direct));
+  HostPortProxyPair session_pool_key_proxy(
+      host_port_pair,
+      ProxyServer::FromURI("www.foo.com", ProxyServer::SCHEME_HTTP));
+  EXPECT_FALSE(spdy_session_pool->HasSession(session_pool_key_proxy));
+
+  // Set up data for the proxy connection.
+  const char kConnect443[] = {"CONNECT www.google.com:443 HTTP/1.1\r\n"
+                           "Host: www.google.com\r\n"
+                           "Proxy-Connection: keep-alive\r\n\r\n"};
+  const char kConnect80[] = {"CONNECT www.google.com:80 HTTP/1.1\r\n"
+                           "Host: www.google.com\r\n"
+                           "Proxy-Connection: keep-alive\r\n\r\n"};
+  const char kHTTP200[] = {"HTTP/1.1 200 OK\r\n\r\n"};
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(
+      "http://www.google.com/foo.dat", false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(1, true));
+
+  MockWrite writes_SPDYNPN[] = {
+    MockWrite(SYNCHRONOUS, kConnect443, arraysize(kConnect443) - 1, 0),
+    CreateMockWrite(*req2, 2),
+  };
+  MockRead reads_SPDYNPN[] = {
+    MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1),
+    CreateMockRead(*resp2, 3),
+    CreateMockRead(*body2, 4),
+    MockRead(ASYNC, 0, 5)  // EOF
+  };
+
+  MockWrite writes_SPDYNOSSL[] = {
+    CreateMockWrite(*req2, 0),
+  };
+  MockRead reads_SPDYNOSSL[] = {
+    CreateMockRead(*resp2, 1),
+    CreateMockRead(*body2, 2),
+    MockRead(ASYNC, 0, 3)  // EOF
+  };
+
+  MockWrite writes_SPDYSSL[] = {
+    MockWrite(SYNCHRONOUS, kConnect80, arraysize(kConnect80) - 1, 0),
+    CreateMockWrite(*req2, 2),
+  };
+  MockRead reads_SPDYSSL[] = {
+    MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1),
+    CreateMockRead(*resp2, 3),
+    CreateMockRead(*body2, 4),
+    MockRead(ASYNC, 0, 0, 5),
+  };
+
+  scoped_ptr<OrderedSocketData> data_proxy;
+  switch(GetParam()) {
+    case SPDYNPN:
+      data_proxy.reset(new OrderedSocketData(reads_SPDYNPN,
+                                             arraysize(reads_SPDYNPN),
+                                             writes_SPDYNPN,
+                                             arraysize(writes_SPDYNPN)));
+      break;
+    case SPDYNOSSL:
+      data_proxy.reset(new OrderedSocketData(reads_SPDYNOSSL,
+                                             arraysize(reads_SPDYNOSSL),
+                                             writes_SPDYNOSSL,
+                                             arraysize(writes_SPDYNOSSL)));
+      break;
+    case SPDYSSL:
+      data_proxy.reset(new OrderedSocketData(reads_SPDYSSL,
+                                             arraysize(reads_SPDYSSL),
+                                             writes_SPDYSSL,
+                                             arraysize(writes_SPDYSSL)));
+      break;
+    default:
+      NOTREACHED();
+  }
+
+  // Create another request to www.google.com, but this time through a proxy.
+  HttpRequestInfo request_proxy;
+  request_proxy.method = "GET";
+  request_proxy.url = GURL("http://www.google.com/foo.dat");
+  request_proxy.load_flags = 0;
+  scoped_ptr<SpdySessionDependencies> ssd_proxy(new SpdySessionDependencies());
+  // Ensure that this transaction uses the same SpdySessionPool.
+  scoped_refptr<HttpNetworkSession> session_proxy(
+      SpdySessionDependencies::SpdyCreateSession(ssd_proxy.get()));
+  NormalSpdyTransactionHelper helper_proxy(request_proxy,
+                                           BoundNetLog(), GetParam(), NULL);
+  HttpNetworkSessionPeer session_peer(session_proxy);
+  scoped_ptr<net::ProxyService> proxy_service(
+          ProxyService::CreateFixedFromPacResult("PROXY myproxy:70"));
+  session_peer.SetProxyService(proxy_service.get());
+  helper_proxy.session_deps().swap(ssd_proxy);
+  helper_proxy.SetSession(session_proxy);
+  helper_proxy.RunPreTestSetup();
+  helper_proxy.AddData(data_proxy.get());
+
+  HttpNetworkTransaction* trans_proxy = helper_proxy.trans();
+  TestCompletionCallback callback_proxy;
+  int rv = trans_proxy->Start(
+      &request_proxy, callback_proxy.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  rv = callback_proxy.WaitForResult();
+  EXPECT_EQ(0, rv);
+
+  HttpResponseInfo response_proxy = *trans_proxy->GetResponseInfo();
+  EXPECT_TRUE(response_proxy.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response_proxy.headers->GetStatusLine());
+
+  std::string response_data;
+  ASSERT_EQ(OK, ReadTransaction(trans_proxy, &response_data));
+  EXPECT_EQ("hello!", response_data);
+
+  data.CompleteRead();
+  helper_proxy.VerifyDataConsumed();
+}
+
+// When we get a TCP-level RST, we need to retry a HttpNetworkTransaction
+// on a new connection, if the connection was previously known to be good.
+// This can happen when a server reboots without saying goodbye, or when
+// we're behind a NAT that masked the RST.
+TEST_P(SpdyNetworkTransactionSpdy3Test, VerifyRetryOnConnectionReset) {
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, ERR_IO_PENDING),
+    MockRead(ASYNC, ERR_CONNECTION_RESET),
+  };
+
+  MockRead reads2[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  // This test has a couple of variants.
+  enum {
+    // Induce the RST while waiting for our transaction to send.
+    VARIANT_RST_DURING_SEND_COMPLETION,
+    // Induce the RST while waiting for our transaction to read.
+    // In this case, the send completed - everything copied into the SNDBUF.
+    VARIANT_RST_DURING_READ_COMPLETION
+  };
+
+  for (int variant = VARIANT_RST_DURING_SEND_COMPLETION;
+       variant <= VARIANT_RST_DURING_READ_COMPLETION;
+       ++variant) {
+    DelayedSocketData data1(1, reads, arraysize(reads), NULL, 0);
+
+    DelayedSocketData data2(1, reads2, arraysize(reads2), NULL, 0);
+
+    NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                       BoundNetLog(), GetParam(), NULL);
+    helper.AddData(&data1);
+    helper.AddData(&data2);
+    helper.RunPreTestSetup();
+
+    for (int i = 0; i < 2; ++i) {
+      scoped_ptr<HttpNetworkTransaction> trans(
+          new HttpNetworkTransaction(helper.session()));
+
+      TestCompletionCallback callback;
+      int rv = trans->Start(
+          &helper.request(), callback.callback(), BoundNetLog());
+      EXPECT_EQ(ERR_IO_PENDING, rv);
+      // On the second transaction, we trigger the RST.
+      if (i == 1) {
+        if (variant == VARIANT_RST_DURING_READ_COMPLETION) {
+          // Writes to the socket complete asynchronously on SPDY by running
+          // through the message loop.  Complete the write here.
+          MessageLoop::current()->RunUntilIdle();
+        }
+
+        // Now schedule the ERR_CONNECTION_RESET.
+        EXPECT_EQ(3u, data1.read_index());
+        data1.CompleteRead();
+        EXPECT_EQ(4u, data1.read_index());
+      }
+      rv = callback.WaitForResult();
+      EXPECT_EQ(OK, rv);
+
+      const HttpResponseInfo* response = trans->GetResponseInfo();
+      ASSERT_TRUE(response != NULL);
+      EXPECT_TRUE(response->headers != NULL);
+      EXPECT_TRUE(response->was_fetched_via_spdy);
+      std::string response_data;
+      rv = ReadTransaction(trans.get(), &response_data);
+      EXPECT_EQ(OK, rv);
+      EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine());
+      EXPECT_EQ("hello!", response_data);
+    }
+
+    helper.VerifyDataConsumed();
+  }
+}
+
+// Test that turning SPDY on and off works properly.
+TEST_P(SpdyNetworkTransactionSpdy3Test, SpdyOnOffToggle) {
+  net::HttpStreamFactory::set_spdy_enabled(true);
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite spdy_writes[] = { CreateMockWrite(*req) };
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(1, true));
+  MockRead spdy_reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, spdy_reads, arraysize(spdy_reads),
+                         spdy_writes, arraysize(spdy_writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+
+  net::HttpStreamFactory::set_spdy_enabled(false);
+  MockRead http_reads[] = {
+    MockRead("HTTP/1.1 200 OK\r\n\r\n"),
+    MockRead("hello from http"),
+    MockRead(SYNCHRONOUS, OK),
+  };
+  DelayedSocketData data2(1, http_reads, arraysize(http_reads), NULL, 0);
+  NormalSpdyTransactionHelper helper2(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper2.SetSpdyDisabled();
+  helper2.RunToCompletion(&data2);
+  TransactionHelperResult out2 = helper2.output();
+  EXPECT_EQ(OK, out2.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out2.status_line);
+  EXPECT_EQ("hello from http", out2.response_data);
+
+  net::HttpStreamFactory::set_spdy_enabled(true);
+}
+
+// Tests that Basic authentication works over SPDY
+TEST_P(SpdyNetworkTransactionSpdy3Test, SpdyBasicAuth) {
+  net::HttpStreamFactory::set_spdy_enabled(true);
+
+  // The first request will be a bare GET, the second request will be a
+  // GET with an Authorization header.
+  scoped_ptr<SpdyFrame> req_get(
+      ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  const char* const kExtraAuthorizationHeaders[] = {
+    "authorization",
+    "Basic Zm9vOmJhcg==",
+  };
+  scoped_ptr<SpdyFrame> req_get_authorization(
+      ConstructSpdyGet(
+          kExtraAuthorizationHeaders,
+          arraysize(kExtraAuthorizationHeaders) / 2,
+          false, 3, LOWEST));
+  MockWrite spdy_writes[] = {
+    CreateMockWrite(*req_get, 1),
+    CreateMockWrite(*req_get_authorization, 4),
+  };
+
+  // The first response is a 401 authentication challenge, and the second
+  // response will be a 200 response since the second request includes a valid
+  // Authorization header.
+  const char* const kExtraAuthenticationHeaders[] = {
+    "www-authenticate",
+    "Basic realm=\"MyRealm\""
+  };
+  scoped_ptr<SpdyFrame> resp_authentication(
+      ConstructSpdySynReplyError(
+          "401 Authentication Required",
+          kExtraAuthenticationHeaders,
+          arraysize(kExtraAuthenticationHeaders) / 2,
+          1));
+  scoped_ptr<SpdyFrame> body_authentication(
+      ConstructSpdyBodyFrame(1, true));
+  scoped_ptr<SpdyFrame> resp_data(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body_data(ConstructSpdyBodyFrame(3, true));
+  MockRead spdy_reads[] = {
+    CreateMockRead(*resp_authentication, 2),
+    CreateMockRead(*body_authentication, 3),
+    CreateMockRead(*resp_data, 5),
+    CreateMockRead(*body_data, 6),
+    MockRead(ASYNC, 0, 7),
+  };
+
+  OrderedSocketData data(spdy_reads, arraysize(spdy_reads),
+                         spdy_writes, arraysize(spdy_writes));
+  HttpRequestInfo request(CreateGetRequest());
+  BoundNetLog net_log;
+  NormalSpdyTransactionHelper helper(request, net_log, GetParam(), NULL);
+
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+  HttpNetworkTransaction* trans = helper.trans();
+  TestCompletionCallback callback;
+  const int rv_start = trans->Start(&request, callback.callback(), net_log);
+  EXPECT_EQ(ERR_IO_PENDING, rv_start);
+  const int rv_start_complete = callback.WaitForResult();
+  EXPECT_EQ(OK, rv_start_complete);
+
+  // Make sure the response has an auth challenge.
+  const HttpResponseInfo* const response_start = trans->GetResponseInfo();
+  ASSERT_TRUE(response_start != NULL);
+  ASSERT_TRUE(response_start->headers != NULL);
+  EXPECT_EQ(401, response_start->headers->response_code());
+  EXPECT_TRUE(response_start->was_fetched_via_spdy);
+  AuthChallengeInfo* auth_challenge = response_start->auth_challenge.get();
+  ASSERT_TRUE(auth_challenge != NULL);
+  EXPECT_FALSE(auth_challenge->is_proxy);
+  EXPECT_EQ("basic", auth_challenge->scheme);
+  EXPECT_EQ("MyRealm", auth_challenge->realm);
+
+  // Restart with a username/password.
+  AuthCredentials credentials(ASCIIToUTF16("foo"), ASCIIToUTF16("bar"));
+  TestCompletionCallback callback_restart;
+  const int rv_restart = trans->RestartWithAuth(
+      credentials, callback_restart.callback());
+  EXPECT_EQ(ERR_IO_PENDING, rv_restart);
+  const int rv_restart_complete = callback_restart.WaitForResult();
+  EXPECT_EQ(OK, rv_restart_complete);
+  // TODO(cbentzel): This is actually the same response object as before, but
+  // data has changed.
+  const HttpResponseInfo* const response_restart = trans->GetResponseInfo();
+  ASSERT_TRUE(response_restart != NULL);
+  ASSERT_TRUE(response_restart->headers != NULL);
+  EXPECT_EQ(200, response_restart->headers->response_code());
+  EXPECT_TRUE(response_restart->auth_challenge.get() == NULL);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushWithHeaders) {
+  static const unsigned char kPushBodyFrame[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x06,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 1),
+  };
+
+  static const char* const kInitialHeaders[] = {
+    ":scheme", "http",
+    ":host", "www.google.com",
+    ":path", "/foo.dat",
+  };
+  static const char* const kLateHeaders[] = {
+    "hello",
+    "bye",
+    ":status",
+    "200",
+    ":version",
+    "HTTP/1.1"
+  };
+  scoped_ptr<SpdyFrame>
+    stream2_syn(ConstructSpdyControlFrame(kInitialHeaders,
+                                          arraysize(kInitialHeaders) / 2,
+                                          false,
+                                          2,
+                                          LOWEST,
+                                          SYN_STREAM,
+                                          CONTROL_FLAG_NONE,
+                                          NULL,
+                                          0,
+                                          1));
+  scoped_ptr<SpdyFrame>
+      stream2_headers(ConstructSpdyControlFrame(kLateHeaders,
+                                                arraysize(kLateHeaders) / 2,
+                                                false,
+                                                2,
+                                                LOWEST,
+                                                HEADERS,
+                                                CONTROL_FLAG_NONE,
+                                                NULL,
+                                                0,
+                                                0));
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 2),
+    CreateMockRead(*stream2_syn, 3),
+    CreateMockRead(*stream2_headers, 4),
+    CreateMockRead(*stream1_body, 5, SYNCHRONOUS),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+             arraysize(kPushBodyFrame), 6),
+    MockRead(ASYNC, ERR_IO_PENDING, 7),  // Force a pause
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  std::string expected_push_result("pushed");
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  RunServerPushTest(&data,
+                    &response,
+                    &response2,
+                    expected_push_result);
+
+  // Verify the SYN_REPLY.
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  // Verify the pushed stream.
+  EXPECT_TRUE(response2.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushClaimBeforeHeaders) {
+  // We push a stream and attempt to claim it before the headers come down.
+  static const unsigned char kPushBodyFrame[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x06,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS),
+  };
+
+  static const char* const kInitialHeaders[] = {
+    ":scheme", "http",
+    ":host", "www.google.com",
+    ":path", "/foo.dat"
+  };
+  static const char* const kLateHeaders[] = {
+    "hello",
+    "bye",
+    ":status",
+    "200",
+    ":version",
+    "HTTP/1.1"
+  };
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyControlFrame(kInitialHeaders,
+                                            arraysize(kInitialHeaders) / 2,
+                                            false,
+                                            2,
+                                            LOWEST,
+                                            SYN_STREAM,
+                                            CONTROL_FLAG_NONE,
+                                            NULL,
+                                            0,
+                                            1));
+  scoped_ptr<SpdyFrame>
+      stream2_headers(ConstructSpdyControlFrame(kLateHeaders,
+                                                arraysize(kLateHeaders) / 2,
+                                                false,
+                                                2,
+                                                LOWEST,
+                                                HEADERS,
+                                                CONTROL_FLAG_NONE,
+                                                NULL,
+                                                0,
+                                                0));
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 1),
+    CreateMockRead(*stream2_syn, 2),
+    CreateMockRead(*stream1_body, 3),
+    CreateMockRead(*stream2_headers, 4),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+             arraysize(kPushBodyFrame), 5),
+    MockRead(ASYNC, 0, 6),  // EOF
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  std::string expected_push_result("pushed");
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.SetDeterministic();
+  helper.AddDeterministicData(&data);
+  helper.RunPreTestSetup();
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM,
+  // and the body of the primary stream, but before we've received the HEADERS
+  // for the pushed stream.
+  data.SetStop(3);
+
+  // Start the transaction.
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  data.Run();
+  rv = callback.WaitForResult();
+  EXPECT_EQ(0, rv);
+
+  // Request the pushed path.  At this point, we've received the push, but the
+  // headers are not yet complete.
+  scoped_ptr<HttpNetworkTransaction> trans2(
+      new HttpNetworkTransaction(helper.session()));
+  rv = trans2->Start(
+      &CreateGetPushRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  data.RunFor(3);
+  MessageLoop::current()->RunUntilIdle();
+
+  // Read the server push body.
+  std::string result2;
+  ReadResult(trans2.get(), &data, &result2);
+  // Read the response body.
+  std::string result;
+  ReadResult(trans, &data, &result);
+
+  // Verify that the received push data is same as the expected push data.
+  EXPECT_EQ(result2.compare(expected_push_result), 0)
+      << "Received data: "
+      << result2
+      << "||||| Expected data: "
+      << expected_push_result;
+
+  // Verify the SYN_REPLY.
+  // Copy the response info, because trans goes away.
+  response = *trans->GetResponseInfo();
+  response2 = *trans2->GetResponseInfo();
+
+  VerifyStreamsClosed(helper);
+
+  // Verify the SYN_REPLY.
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  // Verify the pushed stream.
+  EXPECT_TRUE(response2.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+
+  // Read the final EOF (which will close the session)
+  data.RunFor(1);
+
+  // Verify that we consumed all test data.
+  EXPECT_TRUE(data.at_read_eof());
+  EXPECT_TRUE(data.at_write_eof());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushWithTwoHeaderFrames) {
+  // We push a stream and attempt to claim it before the headers come down.
+  static const unsigned char kPushBodyFrame[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x06,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS),
+  };
+
+  static const char* const kInitialHeaders[] = {
+    ":scheme", "http",
+    ":host", "www.google.com",
+    ":path", "/foo.dat"
+  };
+  static const char* const kMiddleHeaders[] = {
+    "hello",
+    "bye",
+  };
+  static const char* const kLateHeaders[] = {
+    ":status",
+    "200",
+    ":version",
+    "HTTP/1.1"
+  };
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyControlFrame(kInitialHeaders,
+                                            arraysize(kInitialHeaders) / 2,
+                                            false,
+                                            2,
+                                            LOWEST,
+                                            SYN_STREAM,
+                                            CONTROL_FLAG_NONE,
+                                            NULL,
+                                            0,
+                                            1));
+  scoped_ptr<SpdyFrame>
+      stream2_headers1(ConstructSpdyControlFrame(kMiddleHeaders,
+                                                 arraysize(kMiddleHeaders) / 2,
+                                                 false,
+                                                 2,
+                                                 LOWEST,
+                                                 HEADERS,
+                                                 CONTROL_FLAG_NONE,
+                                                 NULL,
+                                                 0,
+                                                 0));
+  scoped_ptr<SpdyFrame>
+      stream2_headers2(ConstructSpdyControlFrame(kLateHeaders,
+                                                 arraysize(kLateHeaders) / 2,
+                                                 false,
+                                                 2,
+                                                 LOWEST,
+                                                 HEADERS,
+                                                 CONTROL_FLAG_NONE,
+                                                 NULL,
+                                                 0,
+                                                 0));
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 1),
+    CreateMockRead(*stream2_syn, 2),
+    CreateMockRead(*stream1_body, 3),
+    CreateMockRead(*stream2_headers1, 4),
+    CreateMockRead(*stream2_headers2, 5),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+             arraysize(kPushBodyFrame), 6),
+    MockRead(ASYNC, 0, 7),  // EOF
+  };
+
+  HttpResponseInfo response;
+  HttpResponseInfo response2;
+  std::string expected_push_result("pushed");
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.SetDeterministic();
+  helper.AddDeterministicData(&data);
+  helper.RunPreTestSetup();
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM,
+  // the first HEADERS frame, and the body of the primary stream, but before
+  // we've received the final HEADERS for the pushed stream.
+  data.SetStop(4);
+
+  // Start the transaction.
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  data.Run();
+  rv = callback.WaitForResult();
+  EXPECT_EQ(0, rv);
+
+  // Request the pushed path.  At this point, we've received the push, but the
+  // headers are not yet complete.
+  scoped_ptr<HttpNetworkTransaction> trans2(
+      new HttpNetworkTransaction(helper.session()));
+  rv = trans2->Start(
+      &CreateGetPushRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  data.RunFor(3);
+  MessageLoop::current()->RunUntilIdle();
+
+  // Read the server push body.
+  std::string result2;
+  ReadResult(trans2.get(), &data, &result2);
+  // Read the response body.
+  std::string result;
+  ReadResult(trans, &data, &result);
+
+  // Verify that the received push data is same as the expected push data.
+  EXPECT_EQ(result2.compare(expected_push_result), 0)
+      << "Received data: "
+      << result2
+      << "||||| Expected data: "
+      << expected_push_result;
+
+  // Verify the SYN_REPLY.
+  // Copy the response info, because trans goes away.
+  response = *trans->GetResponseInfo();
+  response2 = *trans2->GetResponseInfo();
+
+  VerifyStreamsClosed(helper);
+
+  // Verify the SYN_REPLY.
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  // Verify the pushed stream.
+  EXPECT_TRUE(response2.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine());
+
+  // Verify we got all the headers
+  EXPECT_TRUE(response2.headers->HasHeaderValue(
+      "scheme", "http"));
+  EXPECT_TRUE(response2.headers->HasHeaderValue(
+      "host", "www.google.com"));
+  EXPECT_TRUE(response2.headers->HasHeaderValue(
+      "path", "/foo.dat"));
+  EXPECT_TRUE(response2.headers->HasHeaderValue("hello", "bye"));
+  EXPECT_TRUE(response2.headers->HasHeaderValue("status", "200"));
+  EXPECT_TRUE(response2.headers->HasHeaderValue("version", "HTTP/1.1"));
+
+  // Read the final EOF (which will close the session)
+  data.RunFor(1);
+
+  // Verify that we consumed all test data.
+  EXPECT_TRUE(data.at_read_eof());
+  EXPECT_TRUE(data.at_write_eof());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushWithNoStatusHeaderFrames) {
+  // We push a stream and attempt to claim it before the headers come down.
+  static const unsigned char kPushBodyFrame[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x06,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_syn(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame>
+      stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockWrite writes[] = {
+    CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS),
+  };
+
+  static const char* const kInitialHeaders[] = {
+    ":scheme", "http",
+    ":host", "www.google.com",
+    ":path", "/foo.dat"
+  };
+  static const char* const kMiddleHeaders[] = {
+    "hello",
+    "bye",
+  };
+  scoped_ptr<SpdyFrame>
+      stream2_syn(ConstructSpdyControlFrame(kInitialHeaders,
+                                            arraysize(kInitialHeaders) / 2,
+                                            false,
+                                            2,
+                                            LOWEST,
+                                            SYN_STREAM,
+                                            CONTROL_FLAG_NONE,
+                                            NULL,
+                                            0,
+                                            1));
+  scoped_ptr<SpdyFrame>
+      stream2_headers1(ConstructSpdyControlFrame(kMiddleHeaders,
+                                                 arraysize(kMiddleHeaders) / 2,
+                                                 false,
+                                                 2,
+                                                 LOWEST,
+                                                 HEADERS,
+                                                 CONTROL_FLAG_NONE,
+                                                 NULL,
+                                                 0,
+                                                 0));
+
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply, 1),
+    CreateMockRead(*stream2_syn, 2),
+    CreateMockRead(*stream1_body, 3),
+    CreateMockRead(*stream2_headers1, 4),
+    MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+             arraysize(kPushBodyFrame), 5),
+    MockRead(ASYNC, 0, 6),  // EOF
+  };
+
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.SetDeterministic();
+  helper.AddDeterministicData(&data);
+  helper.RunPreTestSetup();
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM,
+  // the first HEADERS frame, and the body of the primary stream, but before
+  // we've received the final HEADERS for the pushed stream.
+  data.SetStop(4);
+
+  // Start the transaction.
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  data.Run();
+  rv = callback.WaitForResult();
+  EXPECT_EQ(0, rv);
+
+  // Request the pushed path.  At this point, we've received the push, but the
+  // headers are not yet complete.
+  scoped_ptr<HttpNetworkTransaction> trans2(
+      new HttpNetworkTransaction(helper.session()));
+  rv = trans2->Start(
+      &CreateGetPushRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  data.RunFor(2);
+  MessageLoop::current()->RunUntilIdle();
+
+  // Read the server push body.
+  std::string result2;
+  ReadResult(trans2.get(), &data, &result2);
+  // Read the response body.
+  std::string result;
+  ReadResult(trans, &data, &result);
+  EXPECT_EQ("hello!", result);
+
+  // Verify that we haven't received any push data.
+  EXPECT_EQ("", result2);
+
+  // Verify the SYN_REPLY.
+  // Copy the response info, because trans goes away.
+  HttpResponseInfo response = *trans->GetResponseInfo();
+  ASSERT_TRUE(trans2->GetResponseInfo() == NULL);
+
+  VerifyStreamsClosed(helper);
+
+  // Verify the SYN_REPLY.
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+
+  // Read the final EOF (which will close the session).
+  data.RunFor(1);
+
+  // Verify that we consumed all test data.
+  EXPECT_TRUE(data.at_read_eof());
+  EXPECT_TRUE(data.at_write_eof());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, SynReplyWithHeaders) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  static const char* const kInitialHeaders[] = {
+    ":status",
+    "200 OK",
+    ":version",
+    "HTTP/1.1"
+  };
+  static const char* const kLateHeaders[] = {
+    "hello",
+    "bye",
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyControlFrame(kInitialHeaders,
+                                              arraysize(kInitialHeaders) / 2,
+                                              false,
+                                              1,
+                                              LOWEST,
+                                              SYN_REPLY,
+                                              CONTROL_FLAG_NONE,
+                                              NULL,
+                                              0,
+                                              0));
+  scoped_ptr<SpdyFrame>
+      stream1_headers(ConstructSpdyControlFrame(kLateHeaders,
+                                                arraysize(kLateHeaders) / 2,
+                                                false,
+                                                1,
+                                                LOWEST,
+                                                HEADERS,
+                                                CONTROL_FLAG_NONE,
+                                                NULL,
+                                                0,
+                                                0));
+  scoped_ptr<SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply),
+    CreateMockRead(*stream1_headers),
+    CreateMockRead(*stream1_body),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!", out.response_data);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, SynReplyWithLateHeaders) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  static const char* const kInitialHeaders[] = {
+    ":status",
+    "200 OK",
+    ":version",
+    "HTTP/1.1"
+  };
+  static const char* const kLateHeaders[] = {
+    "hello",
+    "bye",
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyControlFrame(kInitialHeaders,
+                                              arraysize(kInitialHeaders) / 2,
+                                              false,
+                                              1,
+                                              LOWEST,
+                                              SYN_REPLY,
+                                              CONTROL_FLAG_NONE,
+                                              NULL,
+                                              0,
+                                              0));
+  scoped_ptr<SpdyFrame>
+      stream1_headers(ConstructSpdyControlFrame(kLateHeaders,
+                                                arraysize(kLateHeaders) / 2,
+                                                false,
+                                                1,
+                                                LOWEST,
+                                                HEADERS,
+                                                CONTROL_FLAG_NONE,
+                                                NULL,
+                                                0,
+                                                0));
+  scoped_ptr<SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> stream1_body2(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply),
+    CreateMockRead(*stream1_body),
+    CreateMockRead(*stream1_headers),
+    CreateMockRead(*stream1_body2),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(OK, out.rv);
+  EXPECT_EQ("HTTP/1.1 200 OK", out.status_line);
+  EXPECT_EQ("hello!hello!", out.response_data);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, SynReplyWithDuplicateLateHeaders) {
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = { CreateMockWrite(*req) };
+
+  static const char* const kInitialHeaders[] = {
+    ":status",
+    "200 OK",
+    ":version",
+    "HTTP/1.1"
+  };
+  static const char* const kLateHeaders[] = {
+    ":status",
+    "500 Server Error",
+  };
+  scoped_ptr<SpdyFrame>
+      stream1_reply(ConstructSpdyControlFrame(kInitialHeaders,
+                                              arraysize(kInitialHeaders) / 2,
+                                              false,
+                                              1,
+                                              LOWEST,
+                                              SYN_REPLY,
+                                              CONTROL_FLAG_NONE,
+                                              NULL,
+                                              0,
+                                              0));
+  scoped_ptr<SpdyFrame>
+      stream1_headers(ConstructSpdyControlFrame(kLateHeaders,
+                                                arraysize(kLateHeaders) / 2,
+                                                false,
+                                                1,
+                                                LOWEST,
+                                                HEADERS,
+                                                CONTROL_FLAG_NONE,
+                                                NULL,
+                                                0,
+                                                0));
+  scoped_ptr<SpdyFrame> stream1_body(ConstructSpdyBodyFrame(1, false));
+  scoped_ptr<SpdyFrame> stream1_body2(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*stream1_reply),
+    CreateMockRead(*stream1_body),
+    CreateMockRead(*stream1_headers),
+    CreateMockRead(*stream1_body2),
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+
+  DelayedSocketData data(1, reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.RunToCompletion(&data);
+  TransactionHelperResult out = helper.output();
+  EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, ServerPushCrossOriginCorrectness) {
+  // In this test we want to verify that we can't accidentally push content
+  // which can't be pushed by this content server.
+  // This test assumes that:
+  //   - if we're requesting http://www.foo.com/barbaz
+  //   - the browser has made a connection to "www.foo.com".
+
+  // A list of the URL to fetch, followed by the URL being pushed.
+  static const char* const kTestCases[] = {
+    "http://www.google.com/foo.html",
+    "http://www.google.com:81/foo.js",     // Bad port
+
+    "http://www.google.com/foo.html",
+    "https://www.google.com/foo.js",       // Bad protocol
+
+    "http://www.google.com/foo.html",
+    "ftp://www.google.com/foo.js",         // Invalid Protocol
+
+    "http://www.google.com/foo.html",
+    "http://blat.www.google.com/foo.js",   // Cross subdomain
+
+    "http://www.google.com/foo.html",
+    "http://www.foo.com/foo.js",           // Cross domain
+  };
+
+
+  static const unsigned char kPushBodyFrame[] = {
+    0x00, 0x00, 0x00, 0x02,                                      // header, ID
+    0x01, 0x00, 0x00, 0x06,                                      // FIN, length
+    'p', 'u', 's', 'h', 'e', 'd'                                 // "pushed"
+  };
+
+  for (size_t index = 0; index < arraysize(kTestCases); index += 2) {
+    const char* url_to_fetch = kTestCases[index];
+    const char* url_to_push = kTestCases[index + 1];
+
+    scoped_ptr<SpdyFrame>
+        stream1_syn(ConstructSpdyGet(url_to_fetch, false, 1, LOWEST));
+    scoped_ptr<SpdyFrame>
+        stream1_body(ConstructSpdyBodyFrame(1, true));
+    scoped_ptr<SpdyFrame> push_rst(
+        ConstructSpdyRstStream(2, REFUSED_STREAM));
+    MockWrite writes[] = {
+      CreateMockWrite(*stream1_syn, 1),
+      CreateMockWrite(*push_rst, 4),
+    };
+
+    scoped_ptr<SpdyFrame>
+        stream1_reply(ConstructSpdyGetSynReply(NULL, 0, 1));
+    scoped_ptr<SpdyFrame>
+        stream2_syn(ConstructSpdyPush(NULL,
+                                      0,
+                                      2,
+                                      1,
+                                      url_to_push));
+    scoped_ptr<SpdyFrame> rst(
+        ConstructSpdyRstStream(2, CANCEL));
+
+    MockRead reads[] = {
+      CreateMockRead(*stream1_reply, 2),
+      CreateMockRead(*stream2_syn, 3),
+      CreateMockRead(*stream1_body, 5, SYNCHRONOUS),
+      MockRead(ASYNC, reinterpret_cast<const char*>(kPushBodyFrame),
+               arraysize(kPushBodyFrame), 6),
+      MockRead(ASYNC, ERR_IO_PENDING, 7),  // Force a pause
+    };
+
+    HttpResponseInfo response;
+    OrderedSocketData data(reads, arraysize(reads),
+                           writes, arraysize(writes));
+
+    HttpRequestInfo request;
+    request.method = "GET";
+    request.url = GURL(url_to_fetch);
+    request.load_flags = 0;
+
+    // Enable cross-origin push. Since we are not using a proxy, this should
+    // not actually enable cross-origin SPDY push.
+    scoped_ptr<SpdySessionDependencies> session_deps(
+        new SpdySessionDependencies());
+    session_deps->trusted_spdy_proxy = "123.45.67.89:8080";
+    NormalSpdyTransactionHelper helper(request,
+                                       BoundNetLog(), GetParam(),
+                                       session_deps.release());
+    helper.RunPreTestSetup();
+    helper.AddData(&data);
+
+    HttpNetworkTransaction* trans = helper.trans();
+
+    // Start the transaction with basic parameters.
+    TestCompletionCallback callback;
+
+    int rv = trans->Start(&request, callback.callback(), BoundNetLog());
+    EXPECT_EQ(ERR_IO_PENDING, rv);
+    rv = callback.WaitForResult();
+
+    // Read the response body.
+    std::string result;
+    ReadResult(trans, &data, &result);
+
+    // Verify that we consumed all test data.
+    EXPECT_TRUE(data.at_read_eof());
+    EXPECT_TRUE(data.at_write_eof());
+
+    // Verify the SYN_REPLY.
+    // Copy the response info, because trans goes away.
+    response = *trans->GetResponseInfo();
+
+    VerifyStreamsClosed(helper);
+
+    // Verify the SYN_REPLY.
+    EXPECT_TRUE(response.headers != NULL);
+    EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+  }
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, RetryAfterRefused) {
+  // Construct the request.
+  scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+  MockWrite writes[] = {
+    CreateMockWrite(*req, 1),
+    CreateMockWrite(*req2, 3),
+  };
+
+  scoped_ptr<SpdyFrame> refused(
+      ConstructSpdyRstStream(1, REFUSED_STREAM));
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body(ConstructSpdyBodyFrame(3, true));
+  MockRead reads[] = {
+    CreateMockRead(*refused, 2),
+    CreateMockRead(*resp, 4),
+    CreateMockRead(*body, 5),
+    MockRead(ASYNC, 0, 6)  // EOF
+  };
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+
+  helper.RunPreTestSetup();
+  helper.AddData(&data);
+
+  HttpNetworkTransaction* trans = helper.trans();
+
+  // Start the transaction with basic parameters.
+  TestCompletionCallback callback;
+  int rv = trans->Start(
+      &CreateGetRequest(), callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  rv = callback.WaitForResult();
+  EXPECT_EQ(OK, rv);
+
+  // Verify that we consumed all test data.
+  EXPECT_TRUE(data.at_read_eof()) << "Read count: "
+                                   << data.read_count()
+                                   << " Read index: "
+                                   << data.read_index();
+  EXPECT_TRUE(data.at_write_eof()) << "Write count: "
+                                    << data.write_count()
+                                    << " Write index: "
+                                    << data.write_index();
+
+  // Verify the SYN_REPLY.
+  HttpResponseInfo response = *trans->GetResponseInfo();
+  EXPECT_TRUE(response.headers != NULL);
+  EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine());
+}
+
+TEST_P(SpdyNetworkTransactionSpdy3Test, OutOfOrderSynStream) {
+  // This first request will start to establish the SpdySession.
+  // Then we will start the second (MEDIUM priority) and then third
+  // (HIGHEST priority) request in such a way that the third will actually
+  // start before the second, causing the second to be numbered differently
+  // than they order they were created.
+  scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, HIGHEST));
+  scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, MEDIUM));
+  MockWrite writes[] = {
+    CreateMockWrite(*req1, 0),
+    CreateMockWrite(*req2, 3),
+    CreateMockWrite(*req3, 4),
+  };
+
+  scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body1(ConstructSpdyBodyFrame(1, true));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, true));
+  scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
+  scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(5, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp1, 1),
+    CreateMockRead(*body1, 2),
+    CreateMockRead(*resp2, 5),
+    CreateMockRead(*body2, 6),
+    CreateMockRead(*resp3, 7),
+    CreateMockRead(*body3, 8),
+    MockRead(ASYNC, 0, 9)  // EOF
+  };
+
+  DeterministicSocketData data(reads, arraysize(reads),
+                                writes, arraysize(writes));
+  NormalSpdyTransactionHelper helper(CreateGetRequest(),
+                                     BoundNetLog(), GetParam(), NULL);
+  helper.SetDeterministic();
+  helper.RunPreTestSetup();
+  helper.AddDeterministicData(&data);
+
+  // Start the first transaction to set up the SpdySession
+  HttpNetworkTransaction* trans = helper.trans();
+  TestCompletionCallback callback;
+  HttpRequestInfo info1 = CreateGetRequest();
+  info1.priority = LOWEST;
+  int rv = trans->Start(&info1, callback.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+
+  // Run the message loop, but do not allow the write to complete.
+  // This leaves the SpdySession with a write pending, which prevents
+  // SpdySession from attempting subsequent writes until this write completes.
+  MessageLoop::current()->RunUntilIdle();
+
+  // Now, start both new transactions
+  HttpRequestInfo info2 = CreateGetRequest();
+  info2.priority = MEDIUM;
+  TestCompletionCallback callback2;
+    scoped_ptr<HttpNetworkTransaction> trans2(
+        new HttpNetworkTransaction(helper.session()));
+  rv = trans2->Start(&info2, callback2.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  MessageLoop::current()->RunUntilIdle();
+
+  HttpRequestInfo info3 = CreateGetRequest();
+  info3.priority = HIGHEST;
+  TestCompletionCallback callback3;
+  scoped_ptr<HttpNetworkTransaction> trans3(
+      new HttpNetworkTransaction(helper.session()));
+  rv = trans3->Start(&info3, callback3.callback(), BoundNetLog());
+  EXPECT_EQ(ERR_IO_PENDING, rv);
+  MessageLoop::current()->RunUntilIdle();
+
+  // We now have two SYN_STREAM frames queued up which will be
+  // dequeued only once the first write completes, which we
+  // now allow to happen.
+  data.RunFor(2);
+  EXPECT_EQ(OK, callback.WaitForResult());
+
+  // And now we can allow everything else to run to completion.
+  data.SetStop(10);
+  data.Run();
+  EXPECT_EQ(OK, callback2.WaitForResult());
+  EXPECT_EQ(OK, callback3.WaitForResult());
+
+  helper.VerifyDataConsumed();
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_protocol.h b/src/net/spdy/spdy_protocol.h
new file mode 100644
index 0000000..30daee3
--- /dev/null
+++ b/src/net/spdy/spdy_protocol.h
@@ -0,0 +1,1065 @@
+// Copyright (c) 2012 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.
+
+// This file contains some protocol structures for use with Spdy.
+
+#ifndef NET_SPDY_SPDY_PROTOCOL_H_
+#define NET_SPDY_SPDY_PROTOCOL_H_
+
+#include <limits>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/sys_byteorder.h"
+#include "net/spdy/spdy_bitmasks.h"
+
+//  Data Frame Format
+//  +----------------------------------+
+//  |0|       Stream-ID (31bits)       |
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   |
+//  +----------------------------------+
+//  |               Data               |
+//  +----------------------------------+
+//
+//  Control Frame Format
+//  +----------------------------------+
+//  |1| Version(15bits) | Type(16bits) |
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   |
+//  +----------------------------------+
+//  |               Data               |
+//  +----------------------------------+
+//
+//  Control Frame: SYN_STREAM
+//  +----------------------------------+
+//  |1|000000000000001|0000000000000001|
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   |  >= 12
+//  +----------------------------------+
+//  |X|       Stream-ID(31bits)        |
+//  +----------------------------------+
+//  |X|Associated-To-Stream-ID (31bits)|
+//  +----------------------------------+
+//  |Pri| unused      | Length (16bits)|
+//  +----------------------------------+
+//
+//  Control Frame: SYN_REPLY
+//  +----------------------------------+
+//  |1|000000000000001|0000000000000010|
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   |  >= 8
+//  +----------------------------------+
+//  |X|       Stream-ID(31bits)        |
+//  +----------------------------------+
+//  | unused (16 bits)| Length (16bits)|
+//  +----------------------------------+
+//
+//  Control Frame: RST_STREAM
+//  +----------------------------------+
+//  |1|000000000000001|0000000000000011|
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   |  >= 4
+//  +----------------------------------+
+//  |X|       Stream-ID(31bits)        |
+//  +----------------------------------+
+//  |        Status code (32 bits)     |
+//  +----------------------------------+
+//
+//  Control Frame: SETTINGS
+//  +----------------------------------+
+//  |1|000000000000001|0000000000000100|
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   |
+//  +----------------------------------+
+//  |        # of entries (32)         |
+//  +----------------------------------+
+//
+//  Control Frame: NOOP
+//  +----------------------------------+
+//  |1|000000000000001|0000000000000101|
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   | = 0
+//  +----------------------------------+
+//
+//  Control Frame: PING
+//  +----------------------------------+
+//  |1|000000000000001|0000000000000110|
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   | = 4
+//  +----------------------------------+
+//  |        Unique id (32 bits)       |
+//  +----------------------------------+
+//
+//  Control Frame: GOAWAY
+//  +----------------------------------+
+//  |1|000000000000001|0000000000000111|
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   | = 4
+//  +----------------------------------+
+//  |X|  Last-accepted-stream-id       |
+//  +----------------------------------+
+//
+//  Control Frame: HEADERS
+//  +----------------------------------+
+//  |1|000000000000001|0000000000001000|
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   | >= 8
+//  +----------------------------------+
+//  |X|      Stream-ID (31 bits)       |
+//  +----------------------------------+
+//  | unused (16 bits)| Length (16bits)|
+//  +----------------------------------+
+//
+//  Control Frame: WINDOW_UPDATE
+//  +----------------------------------+
+//  |1|000000000000001|0000000000001001|
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   | = 8
+//  +----------------------------------+
+//  |X|      Stream-ID (31 bits)       |
+//  +----------------------------------+
+//  |   Delta-Window-Size (32 bits)    |
+//  +----------------------------------+
+//
+//  Control Frame: CREDENTIAL
+//  +----------------------------------+
+//  |1|000000000000001|0000000000001010|
+//  +----------------------------------+
+//  | flags (8)  |  Length (24 bits)   | >= 12
+//  +----------------------------------+
+//  |  Slot (16 bits) |                |
+//  +-----------------+                |
+//  |      Proof Length (32 bits)      |
+//  +----------------------------------+
+//  |               Proof              |
+//  +----------------------------------+ <+
+//  |   Certificate Length (32 bits)   |  |
+//  +----------------------------------+  | Repeated until end of frame
+//  |            Certificate           |  |
+//  +----------------------------------+ <+
+//
+
+namespace net {
+
+// Initial window size for a Spdy stream
+const int32 kSpdyStreamInitialWindowSize = 64 * 1024;  // 64 KBytes
+
+// Maximum window size for a Spdy stream
+const int32 kSpdyStreamMaximumWindowSize = 0x7FFFFFFF;  // Max signed 32bit int
+
+// SPDY 2 dictionary.
+// This is just a hacked dictionary to use for shrinking HTTP-like headers.
+const char kV2Dictionary[] =
+  "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-"
+  "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi"
+  "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser"
+  "-agent10010120020120220320420520630030130230330430530630740040140240340440"
+  "5406407408409410411412413414415416417500501502503504505accept-rangesageeta"
+  "glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic"
+  "ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran"
+  "sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati"
+  "oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo"
+  "ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe"
+  "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic"
+  "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1"
+  ".1statusversionurl";
+const int kV2DictionarySize = arraysize(kV2Dictionary);
+
+// SPDY 3 dictionary.
+const char kV3Dictionary[] = {
+  0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69,  // ....opti
+  0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68,  // ons....h
+  0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70,  // ead....p
+  0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70,  // ost....p
+  0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65,  // ut....de
+  0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05,  // lete....
+  0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00,  // trace...
+  0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00,  // .accept.
+  0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70,  // ...accep
+  0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65,  // t-charse
+  0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63,  // t....acc
+  0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f,  // ept-enco
+  0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f,  // ding....
+  0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c,  // accept-l
+  0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00,  // anguage.
+  0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70,  // ...accep
+  0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73,  // t-ranges
+  0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00,  // ....age.
+  0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77,  // ...allow
+  0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68,  // ....auth
+  0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f,  // orizatio
+  0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63,  // n....cac
+  0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72,  // he-contr
+  0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f,  // ol....co
+  0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,  // nnection
+  0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74,  // ....cont
+  0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65,  // ent-base
+  0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74,  // ....cont
+  0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f,  // ent-enco
+  0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10,  // ding....
+  0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d,  // content-
+  0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65,  // language
+  0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74,  // ....cont
+  0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67,  // ent-leng
+  0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f,  // th....co
+  0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f,  // ntent-lo
+  0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00,  // cation..
+  0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,  // ..conten
+  0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00,  // t-md5...
+  0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,  // .content
+  0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00,  // -range..
+  0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,  // ..conten
+  0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00,  // t-type..
+  0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00,  // ..date..
+  0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00,  // ..etag..
+  0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74,  // ..expect
+  0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69,  // ....expi
+  0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66,  // res....f
+  0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68,  // rom....h
+  0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69,  // ost....i
+  0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00,  // f-match.
+  0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f,  // ...if-mo
+  0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73,  // dified-s
+  0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d,  // ince....
+  0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d,  // if-none-
+  0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00,  // match...
+  0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67,  // .if-rang
+  0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d,  // e....if-
+  0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69,  // unmodifi
+  0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65,  // ed-since
+  0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74,  // ....last
+  0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65,  // -modifie
+  0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63,  // d....loc
+  0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00,  // ation...
+  0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72,  // .max-for
+  0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00,  // wards...
+  0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00,  // .pragma.
+  0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79,  // ...proxy
+  0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74,  // -authent
+  0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00,  // icate...
+  0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61,  // .proxy-a
+  0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61,  // uthoriza
+  0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05,  // tion....
+  0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00,  // range...
+  0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72,  // .referer
+  0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72,  // ....retr
+  0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00,  // y-after.
+  0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65,  // ...serve
+  0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00,  // r....te.
+  0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c,  // ...trail
+  0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72,  // er....tr
+  0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65,  // ansfer-e
+  0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00,  // ncoding.
+  0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61,  // ...upgra
+  0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73,  // de....us
+  0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74,  // er-agent
+  0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79,  // ....vary
+  0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00,  // ....via.
+  0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69,  // ...warni
+  0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77,  // ng....ww
+  0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e,  // w-authen
+  0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00,  // ticate..
+  0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64,  // ..method
+  0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00,  // ....get.
+  0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,  // ...statu
+  0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30,  // s....200
+  0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76,  // .OK....v
+  0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00,  // ersion..
+  0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31,  // ..HTTP.1
+  0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72,  // .1....ur
+  0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62,  // l....pub
+  0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73,  // lic....s
+  0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69,  // et-cooki
+  0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65,  // e....kee
+  0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00,  // p-alive.
+  0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69,  // ...origi
+  0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32,  // n1001012
+  0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35,  // 01202205
+  0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30,  // 20630030
+  0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33,  // 23033043
+  0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37,  // 05306307
+  0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30,  // 40240540
+  0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34,  // 64074084
+  0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31,  // 09410411
+  0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31,  // 41241341
+  0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34,  // 44154164
+  0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34,  // 17502504
+  0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e,  // 505203.N
+  0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f,  // on-Autho
+  0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65,  // ritative
+  0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61,  // .Informa
+  0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20,  // tion204.
+  0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65,  // No.Conte
+  0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f,  // nt301.Mo
+  0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d,  // ved.Perm
+  0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34,  // anently4
+  0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52,  // 00.Bad.R
+  0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30,  // equest40
+  0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68,  // 1.Unauth
+  0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30,  // orized40
+  0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64,  // 3.Forbid
+  0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e,  // den404.N
+  0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64,  // ot.Found
+  0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65,  // 500.Inte
+  0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72,  // rnal.Ser
+  0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f,  // ver.Erro
+  0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74,  // r501.Not
+  0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65,  // .Impleme
+  0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20,  // nted503.
+  0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20,  // Service.
+  0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61,  // Unavaila
+  0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46,  // bleJan.F
+  0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41,  // eb.Mar.A
+  0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a,  // pr.May.J
+  0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41,  // un.Jul.A
+  0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20,  // ug.Sept.
+  0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20,  // Oct.Nov.
+  0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30,  // Dec.00.0
+  0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e,  // 0.00.Mon
+  0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57,  // ..Tue..W
+  0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c,  // ed..Thu.
+  0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61,  // .Fri..Sa
+  0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20,  // t..Sun..
+  0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b,  // GMTchunk
+  0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f,  // ed.text.
+  0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61,  // html.ima
+  0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69,  // ge.png.i
+  0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67,  // mage.jpg
+  0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67,  // .image.g
+  0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69,  // if.appli
+  0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78,  // cation.x
+  0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69,  // ml.appli
+  0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78,  // cation.x
+  0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c,  // html.xml
+  0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c,  // .text.pl
+  0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74,  // ain.text
+  0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72,  // .javascr
+  0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c,  // ipt.publ
+  0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74,  // icprivat
+  0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65,  // emax-age
+  0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65,  // .gzip.de
+  0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64,  // flate.sd
+  0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65,  // chcharse
+  0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63,  // t.utf-8c
+  0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69,  // harset.i
+  0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d,  // so-8859-
+  0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a,  // 1.utf-..
+  0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e         // .enq.0.
+};
+const int kV3DictionarySize = arraysize(kV3Dictionary);
+
+// 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.
+
+// Types of Spdy Control Frames.
+enum SpdyControlType {
+  SYN_STREAM = 1,
+  SYN_REPLY,
+  RST_STREAM,
+  SETTINGS,
+  NOOP,  // Because it is valid in SPDY/2, kept for identifiability/enum order.
+  PING,
+  GOAWAY,
+  HEADERS,
+  WINDOW_UPDATE,
+  CREDENTIAL,
+  NUM_CONTROL_FRAME_TYPES
+};
+
+// Flags on data packets.
+enum SpdyDataFlags {
+  DATA_FLAG_NONE = 0,
+  DATA_FLAG_FIN = 1,
+};
+
+// Flags on control packets
+enum SpdyControlFlags {
+  CONTROL_FLAG_NONE = 0,
+  CONTROL_FLAG_FIN = 1,
+  CONTROL_FLAG_UNIDIRECTIONAL = 2
+};
+
+// Flags on the SETTINGS control frame.
+enum SpdySettingsControlFlags {
+  SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS = 0x1
+};
+
+// Flags for settings within a SETTINGS frame.
+enum SpdySettingsFlags {
+  SETTINGS_FLAG_NONE = 0x0,
+  SETTINGS_FLAG_PLEASE_PERSIST = 0x1,
+  SETTINGS_FLAG_PERSISTED = 0x2
+};
+
+// List of known settings.
+enum SpdySettingsIds {
+  SETTINGS_UPLOAD_BANDWIDTH = 0x1,
+  SETTINGS_DOWNLOAD_BANDWIDTH = 0x2,
+  // Network round trip time in milliseconds.
+  SETTINGS_ROUND_TRIP_TIME = 0x3,
+  SETTINGS_MAX_CONCURRENT_STREAMS = 0x4,
+  // TCP congestion window in packets.
+  SETTINGS_CURRENT_CWND = 0x5,
+  // Downstream byte retransmission rate in percentage.
+  SETTINGS_DOWNLOAD_RETRANS_RATE = 0x6,
+  // Initial window size in bytes
+  SETTINGS_INITIAL_WINDOW_SIZE = 0x7
+};
+
+// Status codes, as used in control frames (primarily RST_STREAM).
+// TODO(hkhalil): Rename to SpdyRstStreamStatus
+enum SpdyStatusCodes {
+  INVALID = 0,
+  PROTOCOL_ERROR = 1,
+  INVALID_STREAM = 2,
+  REFUSED_STREAM = 3,
+  UNSUPPORTED_VERSION = 4,
+  CANCEL = 5,
+  INTERNAL_ERROR = 6,
+  FLOW_CONTROL_ERROR = 7,
+  STREAM_IN_USE = 8,
+  STREAM_ALREADY_CLOSED = 9,
+  INVALID_CREDENTIALS = 10,
+  FRAME_TOO_LARGE = 11,
+  NUM_STATUS_CODES = 12
+};
+
+enum SpdyGoAwayStatus {
+  GOAWAY_INVALID = -1,
+  GOAWAY_OK = 0,
+  GOAWAY_PROTOCOL_ERROR = 1,
+  GOAWAY_INTERNAL_ERROR = 2,
+  GOAWAY_NUM_STATUS_CODES = 3
+};
+
+// A SPDY stream id is a 31 bit entity.
+typedef uint32 SpdyStreamId;
+
+// A SPDY priority is a number between 0 and 7 (inclusive).
+// SPDY priority range is version-dependant. For SPDY 2 and below, priority is a
+// number between 0 and 3.
+typedef uint8 SpdyPriority;
+
+// -------------------------------------------------------------------------
+// These structures mirror the protocol structure definitions.
+
+// For the control data structures, we pack so that sizes match the
+// protocol over-the-wire sizes.
+#pragma pack(push)
+#pragma pack(1)
+
+// A special structure for the 8 bit flags and 24 bit length fields.
+union FlagsAndLength {
+  uint8 flags_[4];  // 8 bits
+  uint32 length_;   // 24 bits
+};
+
+// The basic SPDY Frame structure.
+struct SpdyFrameBlock {
+  union {
+    struct {
+      uint16 version_;
+      uint16 type_;
+    } control_;
+    struct {
+      SpdyStreamId stream_id_;
+    } data_;
+  };
+  FlagsAndLength flags_length_;
+};
+
+// A SYN_STREAM Control Frame structure.
+struct SpdySynStreamControlFrameBlock : SpdyFrameBlock {
+  SpdyStreamId stream_id_;
+  SpdyStreamId associated_stream_id_;
+  SpdyPriority priority_;
+  uint8 credential_slot_;
+};
+
+// A SYN_REPLY Control Frame structure.
+struct SpdySynReplyControlFrameBlock : SpdyFrameBlock {
+  SpdyStreamId stream_id_;
+};
+
+// A RST_STREAM Control Frame structure.
+struct SpdyRstStreamControlFrameBlock : SpdyFrameBlock {
+  SpdyStreamId stream_id_;
+  uint32 status_;
+};
+
+// A SETTINGS Control Frame structure.
+struct SpdySettingsControlFrameBlock : SpdyFrameBlock {
+  uint32 num_entries_;
+  // Variable data here.
+};
+
+// A PING Control Frame structure.
+struct SpdyPingControlFrameBlock : SpdyFrameBlock {
+  uint32 unique_id_;
+};
+
+// TODO(avd): remove this struct
+// A CREDENTIAL Control Frame structure.
+struct SpdyCredentialControlFrameBlock : SpdyFrameBlock {
+  uint16 slot_;
+  uint32 proof_len_;
+  // Variable data here.
+  // proof data
+  // for each certificate: unit32 certificate_len + certificate_data[i]
+};
+
+// A GOAWAY Control Frame structure.
+struct SpdyGoAwayControlFrameBlock : SpdyFrameBlock {
+  SpdyStreamId last_accepted_stream_id_;
+  SpdyGoAwayStatus status_;
+};
+
+// A HEADERS Control Frame structure.
+struct SpdyHeadersControlFrameBlock : SpdyFrameBlock {
+  SpdyStreamId stream_id_;
+};
+
+// A WINDOW_UPDATE Control Frame structure
+struct SpdyWindowUpdateControlFrameBlock : SpdyFrameBlock {
+  SpdyStreamId stream_id_;
+  uint32 delta_window_size_;
+};
+
+#pragma pack(pop)
+
+// -------------------------------------------------------------------------
+// Wrapper classes for various SPDY frames.
+
+// All Spdy Frame types derive from this SpdyFrame class.
+class SpdyFrame {
+ public:
+  // Create a SpdyFrame for a given sized buffer.
+  explicit SpdyFrame(size_t size) : frame_(NULL), owns_buffer_(true) {
+    DCHECK_GE(size, sizeof(struct SpdyFrameBlock));
+    char* buffer = new char[size];
+    memset(buffer, 0, size);
+    frame_ = reinterpret_cast<struct SpdyFrameBlock*>(buffer);
+  }
+
+  // Create a SpdyFrame using a pre-created buffer.
+  // If |owns_buffer| is true, this class takes ownership of the buffer
+  // and will delete it on cleanup.  The buffer must have been created using
+  // new char[].
+  // If |owns_buffer| is false, the caller retains ownership of the buffer and
+  // is responsible for making sure the buffer outlives this frame.  In other
+  // words, this class does NOT create a copy of the buffer.
+  SpdyFrame(char* data, bool owns_buffer)
+      : frame_(reinterpret_cast<struct SpdyFrameBlock*>(data)),
+        owns_buffer_(owns_buffer) {
+    DCHECK(frame_);
+  }
+
+  ~SpdyFrame() {
+    if (owns_buffer_) {
+      char* buffer = reinterpret_cast<char*>(frame_);
+      delete [] buffer;
+    }
+    frame_ = NULL;
+  }
+
+  // Provides access to the frame bytes, which is a buffer containing
+  // the frame packed as expected for sending over the wire.
+  char* data() const { return reinterpret_cast<char*>(frame_); }
+
+  uint8 flags() const { return frame_->flags_length_.flags_[0]; }
+  void set_flags(uint8 flags) { frame_->flags_length_.flags_[0] = flags; }
+
+  uint32 length() const {
+    return ntohl(frame_->flags_length_.length_) & kLengthMask;
+  }
+
+  void set_length(uint32 length) {
+    DCHECK_EQ(0u, (length & ~kLengthMask));
+    /*
+    +----------------------------------+
+    | Flags (8)  |  Length (24 bits)   |
+    +----------------------------------+
+    */
+    frame_->flags_length_.length_ = htonl((length & kLengthMask)
+                                          | (flags() << 24));
+  }
+
+  bool is_control_frame() const {
+    return (ntohs(frame_->control_.version_) & kControlFlagMask) ==
+        kControlFlagMask;
+  }
+
+  // The size of the SpdyFrameBlock structure.
+  // Every SpdyFrame* class has a static size() method for accessing
+  // the size of the data structure which will be sent over the wire.
+  // Note:  this is not the same as sizeof(SpdyFrame).
+  enum { kHeaderSize = sizeof(struct SpdyFrameBlock) };
+
+ protected:
+  SpdyFrameBlock* frame_;
+
+ private:
+  bool owns_buffer_;
+  DISALLOW_COPY_AND_ASSIGN(SpdyFrame);
+};
+
+// A Data Frame.
+class SpdyDataFrame : public SpdyFrame {
+ public:
+  SpdyDataFrame() : SpdyFrame(size()) {}
+  SpdyDataFrame(char* data, bool owns_buffer)
+      : SpdyFrame(data, owns_buffer) {}
+
+  SpdyStreamId stream_id() const {
+    return ntohl(frame_->data_.stream_id_) & kStreamIdMask;
+  }
+
+  // Note that setting the stream id sets the control bit to false.
+  // As stream id should always be set, this means the control bit
+  // should always be set correctly.
+  void set_stream_id(SpdyStreamId id) {
+    DCHECK_EQ(0u, (id & ~kStreamIdMask));
+    frame_->data_.stream_id_ = htonl(id & kStreamIdMask);
+  }
+
+  // Returns the size of the SpdyFrameBlock structure.
+  // Note: this is not the size of the SpdyDataFrame class.
+  static size_t size() { return SpdyFrame::kHeaderSize; }
+
+  const char* payload() const {
+    return reinterpret_cast<const char*>(frame_) + size();
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SpdyDataFrame);
+};
+
+// A Control Frame.
+class SpdyControlFrame : public SpdyFrame {
+ public:
+  explicit SpdyControlFrame(size_t size) : SpdyFrame(size) {}
+  SpdyControlFrame(char* data, bool owns_buffer)
+      : SpdyFrame(data, owns_buffer) {}
+
+  // Callers can use this method to check if the frame appears to be a valid
+  // frame.  Does not guarantee that there are no errors.
+  bool AppearsToBeAValidControlFrame() const {
+    // Right now we only check if the frame has an out-of-bounds type.
+    uint16 type = ntohs(block()->control_.type_);
+    // NOOP is not a 'valid' control frame in SPDY/3 and beyond.
+    return type >= SYN_STREAM &&
+        type < NUM_CONTROL_FRAME_TYPES &&
+        (version() == 2 || type != NOOP);
+  }
+
+  uint16 version() const {
+    const int kVersionMask = 0x7fff;
+    return static_cast<uint16>(
+        ntohs((block()->control_.version_)) & kVersionMask);
+  }
+
+  void set_version(uint16 version) {
+    /*
+    +----------------------------------+
+    |C| Version(15bits) | Type(16bits) |
+    +----------------------------------+
+    */
+    DCHECK_EQ(0U, version & kControlFlagMask);
+    mutable_block()->control_.version_ = htons(kControlFlagMask | version);
+  }
+
+  SpdyControlType type() const {
+    uint16 type = ntohs(block()->control_.type_);
+    LOG_IF(DFATAL, type < SYN_STREAM || type >= NUM_CONTROL_FRAME_TYPES)
+        << "Invalid control frame type " << type;
+    return static_cast<SpdyControlType>(type);
+  }
+
+  void set_type(SpdyControlType type) {
+    DCHECK(type >= SYN_STREAM && type < NUM_CONTROL_FRAME_TYPES);
+    mutable_block()->control_.type_ = htons(static_cast<uint16>(type));
+  }
+
+  // Returns true if this control frame is of a type that has a header block,
+  // otherwise it returns false.
+  bool has_header_block() const {
+    return type() == SYN_STREAM || type() == SYN_REPLY || type() == HEADERS;
+  }
+
+ private:
+  const struct SpdyFrameBlock* block() const {
+    return frame_;
+  }
+  struct SpdyFrameBlock* mutable_block() {
+    return frame_;
+  }
+  DISALLOW_COPY_AND_ASSIGN(SpdyControlFrame);
+};
+
+// A SYN_STREAM frame.
+class SpdySynStreamControlFrame : public SpdyControlFrame {
+ public:
+  SpdySynStreamControlFrame() : SpdyControlFrame(size()) {}
+  SpdySynStreamControlFrame(char* data, bool owns_buffer)
+      : SpdyControlFrame(data, owns_buffer) {}
+
+  SpdyStreamId stream_id() const {
+    return ntohl(block()->stream_id_) & kStreamIdMask;
+  }
+
+  void set_stream_id(SpdyStreamId id) {
+    mutable_block()->stream_id_ = htonl(id & kStreamIdMask);
+  }
+
+  SpdyStreamId associated_stream_id() const {
+    return ntohl(block()->associated_stream_id_) & kStreamIdMask;
+  }
+
+  void set_associated_stream_id(SpdyStreamId id) {
+    mutable_block()->associated_stream_id_ = htonl(id & kStreamIdMask);
+  }
+
+  SpdyPriority priority() const {
+    if (version() < 3) {
+      return (block()->priority_ & kSpdy2PriorityMask) >> 6;
+    } else {
+      return (block()->priority_ & kSpdy3PriorityMask) >> 5;
+    }
+  }
+
+  uint8 credential_slot() const {
+    if (version() < 3) {
+      return 0;
+    } else {
+      return block()->credential_slot_;
+    }
+  }
+
+  void set_credential_slot(uint8 credential_slot) {
+    DCHECK(version() >= 3);
+    mutable_block()->credential_slot_ = credential_slot;
+  }
+
+  // The number of bytes in the header block beyond the frame header length.
+  int header_block_len() const {
+    return static_cast<int>(length() - (size() - SpdyFrame::kHeaderSize));
+  }
+
+  const char* header_block() const {
+    return reinterpret_cast<const char*>(block()) + size();
+  }
+
+  // Returns the size of the SpdySynStreamControlFrameBlock structure.
+  // Note: this is not the size of the SpdySynStreamControlFrame class.
+  static size_t size() { return sizeof(SpdySynStreamControlFrameBlock); }
+
+ private:
+  const struct SpdySynStreamControlFrameBlock* block() const {
+    return static_cast<SpdySynStreamControlFrameBlock*>(frame_);
+  }
+  struct SpdySynStreamControlFrameBlock* mutable_block() {
+    return static_cast<SpdySynStreamControlFrameBlock*>(frame_);
+  }
+  DISALLOW_COPY_AND_ASSIGN(SpdySynStreamControlFrame);
+};
+
+// A SYN_REPLY frame.
+class SpdySynReplyControlFrame : public SpdyControlFrame {
+ public:
+  SpdySynReplyControlFrame() : SpdyControlFrame(size()) {}
+  SpdySynReplyControlFrame(char* data, bool owns_buffer)
+      : SpdyControlFrame(data, owns_buffer) {}
+
+  SpdyStreamId stream_id() const {
+    return ntohl(block()->stream_id_) & kStreamIdMask;
+  }
+
+  void set_stream_id(SpdyStreamId id) {
+    mutable_block()->stream_id_ = htonl(id & kStreamIdMask);
+  }
+
+  int header_block_len() const {
+    size_t header_block_len = length() - (size() - SpdyFrame::kHeaderSize);
+    // SPDY 2 had 2 bytes of unused space preceeding the header block.
+    if (version() < 3) {
+      header_block_len -= 2;
+    }
+    return static_cast<int>(header_block_len);
+  }
+
+  const char* header_block() const {
+    const char* header_block = reinterpret_cast<const char*>(block()) + size();
+    // SPDY 2 had 2 bytes of unused space preceeding the header block.
+    if (version() < 3) {
+      header_block += 2;
+    }
+    return header_block;
+  }
+
+  // Returns the size of the SpdySynReplyControlFrameBlock structure.
+  // Note: this is not the size of the SpdySynReplyControlFrame class.
+  static size_t size() { return sizeof(SpdySynReplyControlFrameBlock); }
+
+ private:
+  const struct SpdySynReplyControlFrameBlock* block() const {
+    return static_cast<SpdySynReplyControlFrameBlock*>(frame_);
+  }
+  struct SpdySynReplyControlFrameBlock* mutable_block() {
+    return static_cast<SpdySynReplyControlFrameBlock*>(frame_);
+  }
+  DISALLOW_COPY_AND_ASSIGN(SpdySynReplyControlFrame);
+};
+
+// A RST_STREAM frame.
+class SpdyRstStreamControlFrame : public SpdyControlFrame {
+ public:
+  SpdyRstStreamControlFrame() : SpdyControlFrame(size()) {}
+  SpdyRstStreamControlFrame(char* data, bool owns_buffer)
+      : SpdyControlFrame(data, owns_buffer) {}
+
+  SpdyStreamId stream_id() const {
+    return ntohl(block()->stream_id_) & kStreamIdMask;
+  }
+
+  void set_stream_id(SpdyStreamId id) {
+    mutable_block()->stream_id_ = htonl(id & kStreamIdMask);
+  }
+
+  SpdyStatusCodes status() const {
+    SpdyStatusCodes status =
+        static_cast<SpdyStatusCodes>(ntohl(block()->status_));
+    if (status < INVALID || status >= NUM_STATUS_CODES) {
+      status = INVALID;
+    }
+    return status;
+  }
+  void set_status(SpdyStatusCodes status) {
+    mutable_block()->status_ = htonl(static_cast<uint32>(status));
+  }
+
+  // Returns the size of the SpdyRstStreamControlFrameBlock structure.
+  // Note: this is not the size of the SpdyRstStreamControlFrame class.
+  static size_t size() { return sizeof(SpdyRstStreamControlFrameBlock); }
+
+ private:
+  const struct SpdyRstStreamControlFrameBlock* block() const {
+    return static_cast<SpdyRstStreamControlFrameBlock*>(frame_);
+  }
+  struct SpdyRstStreamControlFrameBlock* mutable_block() {
+    return static_cast<SpdyRstStreamControlFrameBlock*>(frame_);
+  }
+  DISALLOW_COPY_AND_ASSIGN(SpdyRstStreamControlFrame);
+};
+
+class SpdySettingsControlFrame : public SpdyControlFrame {
+ public:
+  SpdySettingsControlFrame() : SpdyControlFrame(size()) {}
+  SpdySettingsControlFrame(char* data, bool owns_buffer)
+      : SpdyControlFrame(data, owns_buffer) {}
+
+  uint32 num_entries() const {
+    return ntohl(block()->num_entries_);
+  }
+
+  void set_num_entries(int val) {
+    mutable_block()->num_entries_ = htonl(static_cast<uint32>(val));
+  }
+
+  int header_block_len() const {
+    return static_cast<int>(length() - (size() - SpdyFrame::kHeaderSize));
+  }
+
+  const char* header_block() const {
+    return reinterpret_cast<const char*>(block()) + size();
+  }
+
+  // Returns the size of the SpdySettingsControlFrameBlock structure.
+  // Note: this is not the size of the SpdySettingsControlFrameBlock class.
+  static size_t size() { return sizeof(SpdySettingsControlFrameBlock); }
+
+ private:
+  const struct SpdySettingsControlFrameBlock* block() const {
+    return static_cast<SpdySettingsControlFrameBlock*>(frame_);
+  }
+  struct SpdySettingsControlFrameBlock* mutable_block() {
+    return static_cast<SpdySettingsControlFrameBlock*>(frame_);
+  }
+  DISALLOW_COPY_AND_ASSIGN(SpdySettingsControlFrame);
+};
+
+class SpdyPingControlFrame : public SpdyControlFrame {
+ public:
+  SpdyPingControlFrame() : SpdyControlFrame(size()) {}
+  SpdyPingControlFrame(char* data, bool owns_buffer)
+      : SpdyControlFrame(data, owns_buffer) {}
+
+  uint32 unique_id() const {
+    return ntohl(block()->unique_id_);
+  }
+
+  void set_unique_id(uint32 unique_id) {
+    mutable_block()->unique_id_ = htonl(unique_id);
+  }
+
+  static size_t size() { return sizeof(SpdyPingControlFrameBlock); }
+
+ private:
+  const struct SpdyPingControlFrameBlock* block() const {
+    return static_cast<SpdyPingControlFrameBlock*>(frame_);
+  }
+  struct SpdyPingControlFrameBlock* mutable_block() {
+    return static_cast<SpdyPingControlFrameBlock*>(frame_);
+  }
+};
+
+class SpdyCredentialControlFrame : public SpdyControlFrame {
+ public:
+  SpdyCredentialControlFrame() : SpdyControlFrame(size()) {}
+  SpdyCredentialControlFrame(char* data, bool owns_buffer)
+      : SpdyControlFrame(data, owns_buffer) {}
+
+  const char* payload() const {
+    return reinterpret_cast<const char*>(block()) + SpdyFrame::kHeaderSize;
+  }
+
+  static size_t size() { return sizeof(SpdyCredentialControlFrameBlock); }
+
+ private:
+  const struct SpdyCredentialControlFrameBlock* block() const {
+    return static_cast<SpdyCredentialControlFrameBlock*>(frame_);
+  }
+  DISALLOW_COPY_AND_ASSIGN(SpdyCredentialControlFrame);
+};
+
+class SpdyGoAwayControlFrame : public SpdyControlFrame {
+ public:
+  SpdyGoAwayControlFrame() : SpdyControlFrame(size()) {}
+  SpdyGoAwayControlFrame(char* data, bool owns_buffer)
+      : SpdyControlFrame(data, owns_buffer) {}
+
+  SpdyStreamId last_accepted_stream_id() const {
+    return ntohl(block()->last_accepted_stream_id_) & kStreamIdMask;
+  }
+
+  SpdyGoAwayStatus status() const {
+    if (version() < 3) {
+      LOG(DFATAL) << "Attempted to access status of SPDY 2 GOAWAY.";
+      return GOAWAY_INVALID;
+    } else {
+      uint32 status = ntohl(block()->status_);
+      if (status >= GOAWAY_NUM_STATUS_CODES) {
+        return GOAWAY_INVALID;
+      } else {
+        return static_cast<SpdyGoAwayStatus>(status);
+      }
+    }
+  }
+
+  void set_last_accepted_stream_id(SpdyStreamId id) {
+    mutable_block()->last_accepted_stream_id_ = htonl(id & kStreamIdMask);
+  }
+
+  static size_t size() { return sizeof(SpdyGoAwayControlFrameBlock); }
+
+ private:
+  const struct SpdyGoAwayControlFrameBlock* block() const {
+    return static_cast<SpdyGoAwayControlFrameBlock*>(frame_);
+  }
+  struct SpdyGoAwayControlFrameBlock* mutable_block() {
+    return static_cast<SpdyGoAwayControlFrameBlock*>(frame_);
+  }
+  DISALLOW_COPY_AND_ASSIGN(SpdyGoAwayControlFrame);
+};
+
+// A HEADERS frame.
+class SpdyHeadersControlFrame : public SpdyControlFrame {
+ public:
+  SpdyHeadersControlFrame() : SpdyControlFrame(size()) {}
+  SpdyHeadersControlFrame(char* data, bool owns_buffer)
+      : SpdyControlFrame(data, owns_buffer) {}
+
+  SpdyStreamId stream_id() const {
+    return ntohl(block()->stream_id_) & kStreamIdMask;
+  }
+
+  void set_stream_id(SpdyStreamId id) {
+    mutable_block()->stream_id_ = htonl(id & kStreamIdMask);
+  }
+
+  // The number of bytes in the header block beyond the frame header length.
+  int header_block_len() const {
+    size_t header_block_len = length() - (size() - SpdyFrame::kHeaderSize);
+    // SPDY 2 had 2 bytes of unused space preceeding the header block.
+    if (version() < 3) {
+      header_block_len -= 2;
+    }
+    return static_cast<int>(header_block_len);
+  }
+
+  const char* header_block() const {
+    const char* header_block = reinterpret_cast<const char*>(block()) + size();
+    // SPDY 2 had 2 bytes of unused space preceeding the header block.
+    if (version() < 3) {
+      header_block += 2;
+    }
+    return header_block;
+  }
+
+  // Returns the size of the SpdyHeadersControlFrameBlock structure.
+  // Note: this is not the size of the SpdyHeadersControlFrame class.
+  static size_t size() { return sizeof(SpdyHeadersControlFrameBlock); }
+
+ private:
+  const struct SpdyHeadersControlFrameBlock* block() const {
+    return static_cast<SpdyHeadersControlFrameBlock*>(frame_);
+  }
+  struct SpdyHeadersControlFrameBlock* mutable_block() {
+    return static_cast<SpdyHeadersControlFrameBlock*>(frame_);
+  }
+  DISALLOW_COPY_AND_ASSIGN(SpdyHeadersControlFrame);
+};
+
+// A WINDOW_UPDATE frame.
+class SpdyWindowUpdateControlFrame : public SpdyControlFrame {
+ public:
+  SpdyWindowUpdateControlFrame() : SpdyControlFrame(size()) {}
+  SpdyWindowUpdateControlFrame(char* data, bool owns_buffer)
+      : SpdyControlFrame(data, owns_buffer) {}
+
+  SpdyStreamId stream_id() const {
+    return ntohl(block()->stream_id_) & kStreamIdMask;
+  }
+
+  void set_stream_id(SpdyStreamId id) {
+    mutable_block()->stream_id_ = htonl(id & kStreamIdMask);
+  }
+
+  uint32 delta_window_size() const {
+    return ntohl(block()->delta_window_size_);
+  }
+
+  void set_delta_window_size(uint32 delta_window_size) {
+    mutable_block()->delta_window_size_ = htonl(delta_window_size);
+  }
+
+  // Returns the size of the SpdyWindowUpdateControlFrameBlock structure.
+  // Note: this is not the size of the SpdyWindowUpdateControlFrame class.
+  static size_t size() { return sizeof(SpdyWindowUpdateControlFrameBlock); }
+
+ private:
+  const struct SpdyWindowUpdateControlFrameBlock* block() const {
+    return static_cast<SpdyWindowUpdateControlFrameBlock*>(frame_);
+  }
+  struct SpdyWindowUpdateControlFrameBlock* mutable_block() {
+    return static_cast<SpdyWindowUpdateControlFrameBlock*>(frame_);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(SpdyWindowUpdateControlFrame);
+};
+
+}  // namespace net
+
+#endif  // NET_SPDY_SPDY_PROTOCOL_H_
diff --git a/src/net/spdy/spdy_protocol_test.cc b/src/net/spdy/spdy_protocol_test.cc
new file mode 100644
index 0000000..fba50d7
--- /dev/null
+++ b/src/net/spdy/spdy_protocol_test.cc
@@ -0,0 +1,403 @@
+// Copyright (c) 2012 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_protocol.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "net/spdy/spdy_bitmasks.h"
+#include "net/spdy/spdy_framer.h"
+#include "testing/platform_test.h"
+
+namespace {
+
+enum SpdyProtocolTestTypes {
+  SPDY2 = 2,
+  SPDY3 = 3,
+};
+
+} // namespace
+
+namespace net {
+
+class SpdyProtocolTest
+    : public ::testing::TestWithParam<SpdyProtocolTestTypes> {
+ protected:
+  virtual void SetUp() {
+    spdy_version_ = GetParam();
+  }
+
+  bool IsSpdy2() { return spdy_version_ == SPDY2; }
+
+  // Version of SPDY protocol to be used.
+  int spdy_version_;
+};
+
+// All tests are run with two different SPDY versions: SPDY/2 and SPDY/3.
+INSTANTIATE_TEST_CASE_P(SpdyProtocolTests,
+                        SpdyProtocolTest,
+                        ::testing::Values(SPDY2, SPDY3));
+
+// Test our protocol constants
+TEST_P(SpdyProtocolTest, ProtocolConstants) {
+#if defined(__LB_SHELL__) || defined(COBALT)
+  // Compiler quirk: <function> declared using a type with no linkage,
+  // must be defined in this translation unit
+  unsigned int spdy_frame_header_size = SpdyFrame::kHeaderSize;
+  EXPECT_EQ(8u, spdy_frame_header_size);
+#else
+  EXPECT_EQ(8u, SpdyFrame::kHeaderSize);
+#endif
+  EXPECT_EQ(8u, SpdyDataFrame::size());
+#if defined(__LB_SHELL__) || defined(COBALT)
+  // Compiler quirk: <function> declared using a type with no linkage,
+  // must be defined in this translation unit
+  unsigned int spdy_control_frame_header_size = SpdyFrame::kHeaderSize;
+  EXPECT_EQ(8u, spdy_control_frame_header_size);
+#else
+  EXPECT_EQ(8u, SpdyControlFrame::kHeaderSize);
+#endif
+  EXPECT_EQ(18u, SpdySynStreamControlFrame::size());
+  EXPECT_EQ(12u, SpdySynReplyControlFrame::size());
+  EXPECT_EQ(16u, SpdyRstStreamControlFrame::size());
+  EXPECT_EQ(12u, SpdySettingsControlFrame::size());
+  EXPECT_EQ(12u, SpdyPingControlFrame::size());
+  EXPECT_EQ(16u, SpdyGoAwayControlFrame::size());
+  EXPECT_EQ(12u, SpdyHeadersControlFrame::size());
+  EXPECT_EQ(16u, SpdyWindowUpdateControlFrame::size());
+  EXPECT_EQ(4u, sizeof(FlagsAndLength));
+  EXPECT_EQ(1, SYN_STREAM);
+  EXPECT_EQ(2, SYN_REPLY);
+  EXPECT_EQ(3, RST_STREAM);
+  EXPECT_EQ(4, SETTINGS);
+  EXPECT_EQ(5, NOOP);
+  EXPECT_EQ(6, PING);
+  EXPECT_EQ(7, GOAWAY);
+  EXPECT_EQ(8, HEADERS);
+  EXPECT_EQ(9, WINDOW_UPDATE);
+}
+
+// Test some of the protocol helper functions
+TEST_P(SpdyProtocolTest, FrameStructs) {
+  SpdyFrame frame(SpdyFrame::kHeaderSize);
+  frame.set_length(12345);
+  frame.set_flags(10);
+  EXPECT_EQ(12345u, frame.length());
+  EXPECT_EQ(10u, frame.flags());
+  EXPECT_FALSE(frame.is_control_frame());
+
+  frame.set_length(0);
+  frame.set_flags(10);
+  EXPECT_EQ(0u, frame.length());
+  EXPECT_EQ(10u, frame.flags());
+  EXPECT_FALSE(frame.is_control_frame());
+}
+
+TEST_P(SpdyProtocolTest, DataFrameStructs) {
+  SpdyDataFrame data_frame;
+  data_frame.set_stream_id(12345);
+  EXPECT_EQ(12345u, data_frame.stream_id());
+}
+
+TEST_P(SpdyProtocolTest, ControlFrameStructs) {
+  SpdyFramer framer(spdy_version_);
+  SpdyHeaderBlock headers;
+
+  const uint8 credential_slot = IsSpdy2() ? 0 : 5;
+
+  scoped_ptr<SpdySynStreamControlFrame> syn_frame(framer.CreateSynStream(
+      123, 456, 2, credential_slot, CONTROL_FLAG_FIN, false, &headers));
+  EXPECT_EQ(framer.protocol_version(), syn_frame->version());
+  EXPECT_TRUE(syn_frame->is_control_frame());
+  EXPECT_EQ(SYN_STREAM, syn_frame->type());
+  EXPECT_EQ(123u, syn_frame->stream_id());
+  EXPECT_EQ(456u, syn_frame->associated_stream_id());
+  EXPECT_EQ(2u, syn_frame->priority());
+  EXPECT_EQ(credential_slot, syn_frame->credential_slot());
+  EXPECT_EQ(IsSpdy2() ? 2 : 4, syn_frame->header_block_len());
+  EXPECT_EQ(1u, syn_frame->flags());
+  syn_frame->set_associated_stream_id(999u);
+  EXPECT_EQ(123u, syn_frame->stream_id());
+  EXPECT_EQ(999u, syn_frame->associated_stream_id());
+
+  scoped_ptr<SpdySynReplyControlFrame> syn_reply(
+      framer.CreateSynReply(123, CONTROL_FLAG_NONE, false, &headers));
+  EXPECT_EQ(framer.protocol_version(), syn_reply->version());
+  EXPECT_TRUE(syn_reply->is_control_frame());
+  EXPECT_EQ(SYN_REPLY, syn_reply->type());
+  EXPECT_EQ(123u, syn_reply->stream_id());
+  EXPECT_EQ(IsSpdy2() ? 2 : 4, syn_reply->header_block_len());
+  EXPECT_EQ(0, syn_reply->flags());
+
+  scoped_ptr<SpdyRstStreamControlFrame> rst_frame(
+      framer.CreateRstStream(123, PROTOCOL_ERROR));
+  EXPECT_EQ(framer.protocol_version(), rst_frame->version());
+  EXPECT_TRUE(rst_frame->is_control_frame());
+  EXPECT_EQ(RST_STREAM, rst_frame->type());
+  EXPECT_EQ(123u, rst_frame->stream_id());
+  EXPECT_EQ(PROTOCOL_ERROR, rst_frame->status());
+  rst_frame->set_status(INVALID_STREAM);
+  EXPECT_EQ(INVALID_STREAM, rst_frame->status());
+  EXPECT_EQ(0, rst_frame->flags());
+
+  const uint32 kUniqueId = 1234567u;
+  const uint32 kUniqueId2 = 31415926u;
+  scoped_ptr<SpdyPingControlFrame> ping_frame(
+      framer.CreatePingFrame(kUniqueId));
+  EXPECT_EQ(framer.protocol_version(), ping_frame->version());
+  EXPECT_TRUE(ping_frame->is_control_frame());
+  EXPECT_EQ(PING, ping_frame->type());
+  EXPECT_EQ(kUniqueId, ping_frame->unique_id());
+  ping_frame->set_unique_id(kUniqueId2);
+  EXPECT_EQ(kUniqueId2, ping_frame->unique_id());
+
+  scoped_ptr<SpdyGoAwayControlFrame> goaway_frame(
+      framer.CreateGoAway(123, GOAWAY_INTERNAL_ERROR));
+  EXPECT_EQ(framer.protocol_version(), goaway_frame->version());
+  EXPECT_TRUE(goaway_frame->is_control_frame());
+  EXPECT_EQ(GOAWAY, goaway_frame->type());
+  EXPECT_EQ(123u, goaway_frame->last_accepted_stream_id());
+  if (!IsSpdy2()) {
+    EXPECT_EQ(GOAWAY_INTERNAL_ERROR, goaway_frame->status());
+  }
+
+  scoped_ptr<SpdyHeadersControlFrame> headers_frame(
+      framer.CreateHeaders(123, CONTROL_FLAG_NONE, false, &headers));
+  EXPECT_EQ(framer.protocol_version(), headers_frame->version());
+  EXPECT_TRUE(headers_frame->is_control_frame());
+  EXPECT_EQ(HEADERS, headers_frame->type());
+  EXPECT_EQ(123u, headers_frame->stream_id());
+  EXPECT_EQ(IsSpdy2() ? 2 : 4, headers_frame->header_block_len());
+  EXPECT_EQ(0, headers_frame->flags());
+
+  scoped_ptr<SpdyWindowUpdateControlFrame> window_update_frame(
+      framer.CreateWindowUpdate(123, 456));
+  EXPECT_EQ(framer.protocol_version(), window_update_frame->version());
+  EXPECT_TRUE(window_update_frame->is_control_frame());
+  EXPECT_EQ(WINDOW_UPDATE, window_update_frame->type());
+  EXPECT_EQ(123u, window_update_frame->stream_id());
+  EXPECT_EQ(456u, window_update_frame->delta_window_size());
+}
+
+TEST_P(SpdyProtocolTest, TestDataFrame) {
+  SpdyDataFrame frame;
+
+  // Set the stream ID to various values.
+  frame.set_stream_id(0);
+  EXPECT_EQ(0u, frame.stream_id());
+  EXPECT_FALSE(frame.is_control_frame());
+  frame.set_stream_id(~0 & kStreamIdMask);
+  EXPECT_EQ(~0 & kStreamIdMask, frame.stream_id());
+  EXPECT_FALSE(frame.is_control_frame());
+
+  // Set length to various values.  Make sure that when you set_length(x),
+  // length() == x.  Also make sure the flags are unaltered.
+  memset(frame.data(), '1', SpdyDataFrame::size());
+  int8 flags = frame.flags();
+  frame.set_length(0);
+  EXPECT_EQ(0u, frame.length());
+  EXPECT_EQ(flags, frame.flags());
+  frame.set_length(kLengthMask);
+  EXPECT_EQ(kLengthMask, frame.length());
+  EXPECT_EQ(flags, frame.flags());
+  frame.set_length(5u);
+  EXPECT_EQ(5u, frame.length());
+  EXPECT_EQ(flags, frame.flags());
+
+  // Set flags to various values.  Make sure that when you set_flags(x),
+  // flags() == x.  Also make sure the length is unaltered.
+  memset(frame.data(), '1', SpdyDataFrame::size());
+  uint32 length = frame.length();
+  frame.set_flags(0u);
+  EXPECT_EQ(0u, frame.flags());
+  EXPECT_EQ(length, frame.length());
+  int8 all_flags = ~0;
+  frame.set_flags(all_flags);
+  flags = frame.flags();
+  EXPECT_EQ(all_flags, flags);
+  EXPECT_EQ(length, frame.length());
+  frame.set_flags(5u);
+  EXPECT_EQ(5u, frame.flags());
+  EXPECT_EQ(length, frame.length());
+}
+
+// Test various types of SETTINGS frames.
+TEST_P(SpdyProtocolTest, TestSpdySettingsFrame) {
+  SpdyFramer framer(spdy_version_);
+
+  // Create a settings frame with no settings.
+  SettingsMap settings;
+  scoped_ptr<SpdySettingsControlFrame> settings_frame(
+      framer.CreateSettings(settings));
+  EXPECT_EQ(framer.protocol_version(), settings_frame->version());
+  EXPECT_TRUE(settings_frame->is_control_frame());
+  EXPECT_EQ(SETTINGS, settings_frame->type());
+  EXPECT_EQ(0u, settings_frame->num_entries());
+
+  // We'll add several different ID/Flag combinations and then verify
+  // that they encode and decode properly.
+  SettingsFlagsAndId ids[] = {
+    SettingsFlagsAndId::FromWireFormat(spdy_version_, 0x00000000),
+    SettingsFlagsAndId::FromWireFormat(spdy_version_, 0xffffffff),
+    SettingsFlagsAndId::FromWireFormat(spdy_version_, 0xff000001),
+    SettingsFlagsAndId::FromWireFormat(spdy_version_, 0x01000002),
+    SettingsFlagsAndId(6, 9)
+  };
+
+  for (size_t index = 0; index < arraysize(ids); ++index) {
+    SettingsFlagsAndId flags_and_id = ids[index];
+    SpdySettingsIds id = static_cast<SpdySettingsIds>(flags_and_id.id());
+    SpdySettingsFlags flags =
+        static_cast<SpdySettingsFlags>(flags_and_id.flags());
+    settings[id] = SettingsFlagsAndValue(flags, index);
+    settings_frame.reset(framer.CreateSettings(settings));
+    EXPECT_EQ(framer.protocol_version(), settings_frame->version());
+    EXPECT_TRUE(settings_frame->is_control_frame());
+    EXPECT_EQ(SETTINGS, settings_frame->type());
+    EXPECT_EQ(index + 1, settings_frame->num_entries());
+
+    SettingsMap parsed_settings;
+    EXPECT_TRUE(framer.ParseSettings(settings_frame.get(), &parsed_settings));
+    EXPECT_EQ(settings.size(), parsed_settings.size());
+    for (SettingsMap::const_iterator it = parsed_settings.begin();
+         it != parsed_settings.end();
+         it++) {
+      SettingsMap::const_iterator it2 = settings.find(it->first);
+      EXPECT_EQ(it->first, it2->first);
+      SettingsFlagsAndValue parsed = it->second;
+      SettingsFlagsAndValue created = it2->second;
+      EXPECT_EQ(created.first, parsed.first);
+      EXPECT_EQ(created.second, parsed.second);
+    }
+  }
+}
+
+TEST_P(SpdyProtocolTest, HasHeaderBlock) {
+  SpdyControlFrame frame(SpdyControlFrame::kHeaderSize);
+  for (SpdyControlType type = SYN_STREAM;
+      type < NUM_CONTROL_FRAME_TYPES;
+      type = static_cast<SpdyControlType>(type + 1)) {
+    frame.set_type(type);
+    if (type == SYN_STREAM || type == SYN_REPLY || type == HEADERS) {
+      EXPECT_TRUE(frame.has_header_block());
+    } else {
+      EXPECT_FALSE(frame.has_header_block());
+    }
+  }
+}
+
+class SpdyProtocolDeathTest : public SpdyProtocolTest {};
+
+// All tests are run with two different SPDY versions: SPDY/2 and SPDY/3.
+INSTANTIATE_TEST_CASE_P(SpdyProtocolDeathTests,
+                        SpdyProtocolDeathTest,
+                        ::testing::Values(SPDY2, SPDY3));
+
+// Make sure that overflows both die in debug mode, and do not cause problems
+// in opt mode.  Note:  The EXPECT_DEBUG_DEATH call does not work on Win32 yet,
+// so we comment it out.
+TEST_P(SpdyProtocolDeathTest, TestDataFrame) {
+  SpdyDataFrame frame;
+
+  frame.set_stream_id(0);
+  // TODO(mbelshe):  implement EXPECT_DEBUG_DEATH on windows.
+#if !defined(WIN32) && defined(GTEST_HAS_DEATH_TEST)
+#if !defined(DCHECK_ALWAYS_ON)
+  EXPECT_DEBUG_DEATH(frame.set_stream_id(~0), "");
+#else
+  EXPECT_DEATH(frame.set_stream_id(~0), "");
+#endif
+#endif
+  EXPECT_FALSE(frame.is_control_frame());
+
+  frame.set_flags(0);
+#if !defined(WIN32) && defined(GTEST_HAS_DEATH_TEST)
+#if !defined(DCHECK_ALWAYS_ON)
+  EXPECT_DEBUG_DEATH(frame.set_length(~0), "");
+#else
+  EXPECT_DEATH(frame.set_length(~0), "");
+#endif
+#endif
+  EXPECT_EQ(0, frame.flags());
+}
+
+TEST_P(SpdyProtocolDeathTest, TestSpdyControlFrameStreamId) {
+  SpdyControlFrame frame_store(SpdySynStreamControlFrame::size());
+  memset(frame_store.data(), '1', SpdyControlFrame::kHeaderSize);
+  SpdySynStreamControlFrame* frame =
+      reinterpret_cast<SpdySynStreamControlFrame*>(&frame_store);
+
+  // Set the stream ID to various values.
+  frame->set_stream_id(0);
+  EXPECT_EQ(0u, frame->stream_id());
+  EXPECT_FALSE(frame->is_control_frame());
+  frame->set_stream_id(kStreamIdMask);
+  EXPECT_EQ(kStreamIdMask, frame->stream_id());
+  EXPECT_FALSE(frame->is_control_frame());
+}
+
+TEST_P(SpdyProtocolDeathTest, TestSpdyControlFrameVersion) {
+  const unsigned int kVersionMask = 0x7fff;
+  SpdyControlFrame frame(SpdySynStreamControlFrame::size());
+  memset(frame.data(), '1', SpdyControlFrame::kHeaderSize);
+
+  // Set the version to various values, and make sure it does not affect the
+  // type.
+  frame.set_type(SYN_STREAM);
+  frame.set_version(0);
+  EXPECT_EQ(0, frame.version());
+  EXPECT_TRUE(frame.is_control_frame());
+  EXPECT_EQ(SYN_STREAM, frame.type());
+
+  SpdySynStreamControlFrame* syn_stream =
+      reinterpret_cast<SpdySynStreamControlFrame*>(&frame);
+  syn_stream->set_stream_id(~0 & kVersionMask);
+  EXPECT_EQ(~0 & kVersionMask, syn_stream->stream_id());
+  EXPECT_TRUE(frame.is_control_frame());
+  EXPECT_EQ(SYN_STREAM, frame.type());
+}
+
+TEST_P(SpdyProtocolDeathTest, TestSpdyControlFrameType) {
+  SpdyControlFrame frame(SpdyControlFrame::kHeaderSize);
+  memset(frame.data(), 255, SpdyControlFrame::kHeaderSize);
+
+  // type() should be out of bounds.
+  EXPECT_FALSE(frame.AppearsToBeAValidControlFrame());
+
+  frame.set_version(spdy_version_);
+  uint16 version = frame.version();
+
+  for (int i = SYN_STREAM; i <= WINDOW_UPDATE; ++i) {
+    frame.set_type(static_cast<SpdyControlType>(i));
+    EXPECT_EQ(i, static_cast<int>(frame.type()));
+    if (!IsSpdy2() && i == NOOP) {
+      // NOOP frames aren't 'valid'.
+      EXPECT_FALSE(frame.AppearsToBeAValidControlFrame());
+    } else {
+      EXPECT_TRUE(frame.AppearsToBeAValidControlFrame());
+    }
+    // Make sure setting type does not alter the version block.
+    EXPECT_EQ(version, frame.version());
+    EXPECT_TRUE(frame.is_control_frame());
+  }
+}
+
+TEST_P(SpdyProtocolDeathTest, TestRstStreamStatusBounds) {
+  SpdyFramer framer(spdy_version_);
+  scoped_ptr<SpdyRstStreamControlFrame> rst_frame;
+
+  rst_frame.reset(framer.CreateRstStream(123, PROTOCOL_ERROR));
+  EXPECT_EQ(PROTOCOL_ERROR, rst_frame->status());
+
+  rst_frame->set_status(INVALID);
+  EXPECT_EQ(INVALID, rst_frame->status());
+
+  rst_frame->set_status(
+      static_cast<SpdyStatusCodes>(INVALID - 1));
+  EXPECT_EQ(INVALID, rst_frame->status());
+
+  rst_frame->set_status(NUM_STATUS_CODES);
+  EXPECT_EQ(INVALID, rst_frame->status());
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_proxy_client_socket.cc b/src/net/spdy/spdy_proxy_client_socket.cc
new file mode 100644
index 0000000..0d62ce7
--- /dev/null
+++ b/src/net/spdy/spdy_proxy_client_socket.cc
@@ -0,0 +1,591 @@
+// Copyright (c) 2012 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_proxy_client_socket.h"
+
+#include <algorithm>  // min
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/auth.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_util.h"
+#include "net/http/http_auth_cache.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_response_headers.h"
+#include "net/spdy/spdy_http_utils.h"
+
+namespace net {
+
+SpdyProxyClientSocket::SpdyProxyClientSocket(
+    SpdyStream* spdy_stream,
+    const std::string& user_agent,
+    const HostPortPair& endpoint,
+    const GURL& url,
+    const HostPortPair& proxy_server,
+    HttpAuthCache* auth_cache,
+    HttpAuthHandlerFactory* auth_handler_factory)
+    : next_state_(STATE_DISCONNECTED),
+      spdy_stream_(spdy_stream),
+      endpoint_(endpoint),
+      auth_(
+          new HttpAuthController(HttpAuth::AUTH_PROXY,
+                                 GURL("https://" + proxy_server.ToString()),
+                                 auth_cache,
+                                 auth_handler_factory)),
+      user_buffer_(NULL),
+      write_buffer_len_(0),
+      write_bytes_outstanding_(0),
+      ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
+      net_log_(spdy_stream->net_log()) {
+  request_.method = "CONNECT";
+  request_.url = url;
+  if (!user_agent.empty())
+    request_.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent,
+                                     user_agent);
+  spdy_stream_->SetDelegate(this);
+  was_ever_used_ = spdy_stream_->WasEverUsed();
+}
+
+SpdyProxyClientSocket::~SpdyProxyClientSocket() {
+  Disconnect();
+}
+
+const HttpResponseInfo* SpdyProxyClientSocket::GetConnectResponseInfo() const {
+  return response_.headers ? &response_ : NULL;
+}
+
+const scoped_refptr<HttpAuthController>&
+SpdyProxyClientSocket::GetAuthController() const {
+  return auth_;
+}
+
+int SpdyProxyClientSocket::RestartWithAuth(const CompletionCallback& callback) {
+  // A SPDY Stream can only handle a single request, so the underlying
+  // stream may not be reused and a new SpdyProxyClientSocket must be
+  // created (possibly on top of the same SPDY Session).
+  next_state_ = STATE_DISCONNECTED;
+  return OK;
+}
+
+bool SpdyProxyClientSocket::IsUsingSpdy() const {
+  return true;
+}
+
+NextProto SpdyProxyClientSocket::GetProtocolNegotiated() const {
+  // Save the negotiated protocol
+  SSLInfo ssl_info;
+  bool was_npn_negotiated;
+  NextProto protocol_negotiated;
+  spdy_stream_->GetSSLInfo(&ssl_info, &was_npn_negotiated,
+                           &protocol_negotiated);
+  return protocol_negotiated;
+}
+
+HttpStream* SpdyProxyClientSocket::CreateConnectResponseStream() {
+  DCHECK(response_stream_.get());
+  return response_stream_.release();
+}
+
+// Sends a SYN_STREAM frame to the proxy with a CONNECT request
+// for the specified endpoint.  Waits for the server to send back
+// a SYN_REPLY frame.  OK will be returned if the status is 200.
+// ERR_TUNNEL_CONNECTION_FAILED will be returned for any other status.
+// In any of these cases, Read() may be called to retrieve the HTTP
+// response body.  Any other return values should be considered fatal.
+// TODO(rch): handle 407 proxy auth requested correctly, perhaps
+// by creating a new stream for the subsequent request.
+// TODO(rch): create a more appropriate error code to disambiguate
+// the HTTPS Proxy tunnel failure from an HTTP Proxy tunnel failure.
+int SpdyProxyClientSocket::Connect(const CompletionCallback& callback) {
+  DCHECK(read_callback_.is_null());
+  if (next_state_ == STATE_OPEN)
+    return OK;
+
+  DCHECK_EQ(STATE_DISCONNECTED, next_state_);
+  next_state_ = STATE_GENERATE_AUTH_TOKEN;
+
+  int rv = DoLoop(OK);
+  if (rv == ERR_IO_PENDING)
+    read_callback_ = callback;
+  return rv;
+}
+
+void SpdyProxyClientSocket::Disconnect() {
+  read_buffer_.clear();
+  user_buffer_ = NULL;
+  read_callback_.Reset();
+
+  write_buffer_len_ = 0;
+  write_bytes_outstanding_ = 0;
+  write_callback_.Reset();
+
+  next_state_ = STATE_DISCONNECTED;
+
+  if (spdy_stream_)
+    // This will cause OnClose to be invoked, which takes care of
+    // cleaning up all the internal state.
+    spdy_stream_->Cancel();
+}
+
+bool SpdyProxyClientSocket::IsConnected() const {
+  return next_state_ == STATE_OPEN;
+}
+
+bool SpdyProxyClientSocket::IsConnectedAndIdle() const {
+  return IsConnected() && read_buffer_.empty() && spdy_stream_->is_idle();
+}
+
+const BoundNetLog& SpdyProxyClientSocket::NetLog() const {
+  return net_log_;
+}
+
+void SpdyProxyClientSocket::SetSubresourceSpeculation() {
+  // TODO(rch): what should this implementation be?
+}
+
+void SpdyProxyClientSocket::SetOmniboxSpeculation() {
+  // TODO(rch): what should this implementation be?
+}
+
+bool SpdyProxyClientSocket::WasEverUsed() const {
+  return was_ever_used_ || (spdy_stream_ && spdy_stream_->WasEverUsed());
+}
+
+bool SpdyProxyClientSocket::UsingTCPFastOpen() const {
+  return false;
+}
+
+int64 SpdyProxyClientSocket::NumBytesRead() const {
+  return -1;
+}
+
+base::TimeDelta SpdyProxyClientSocket::GetConnectTimeMicros() const {
+  return base::TimeDelta::FromMicroseconds(-1);
+}
+
+bool SpdyProxyClientSocket::WasNpnNegotiated() const {
+  return false;
+}
+
+NextProto SpdyProxyClientSocket::GetNegotiatedProtocol() const {
+  return kProtoUnknown;
+}
+
+bool SpdyProxyClientSocket::GetSSLInfo(SSLInfo* ssl_info) {
+  bool was_npn_negotiated;
+  NextProto protocol_negotiated;
+  return spdy_stream_->GetSSLInfo(ssl_info, &was_npn_negotiated,
+                                  &protocol_negotiated);
+}
+
+int SpdyProxyClientSocket::Read(IOBuffer* buf, int buf_len,
+                                const CompletionCallback& callback) {
+  DCHECK(read_callback_.is_null());
+  DCHECK(!user_buffer_);
+
+  if (next_state_ == STATE_DISCONNECTED)
+    return ERR_SOCKET_NOT_CONNECTED;
+
+  if (next_state_ == STATE_CLOSED && read_buffer_.empty()) {
+    return 0;
+  }
+
+  DCHECK(next_state_ == STATE_OPEN || next_state_ == STATE_CLOSED);
+  DCHECK(buf);
+  user_buffer_ = new DrainableIOBuffer(buf, buf_len);
+  int result = PopulateUserReadBuffer();
+  if (result == 0) {
+    DCHECK(!callback.is_null());
+    read_callback_ = callback;
+    return ERR_IO_PENDING;
+  }
+  user_buffer_ = NULL;
+  return result;
+}
+
+int SpdyProxyClientSocket::PopulateUserReadBuffer() {
+  if (!user_buffer_)
+    return ERR_IO_PENDING;
+
+  int bytes_read = 0;
+  while (!read_buffer_.empty() && user_buffer_->BytesRemaining() > 0) {
+    scoped_refptr<DrainableIOBuffer> data = read_buffer_.front();
+    const int bytes_to_copy = std::min(user_buffer_->BytesRemaining(),
+                                       data->BytesRemaining());
+    memcpy(user_buffer_->data(), data->data(), bytes_to_copy);
+    user_buffer_->DidConsume(bytes_to_copy);
+    bytes_read += bytes_to_copy;
+    if (data->BytesRemaining() == bytes_to_copy) {
+      // Consumed all data from this buffer
+      read_buffer_.pop_front();
+    } else {
+      data->DidConsume(bytes_to_copy);
+    }
+  }
+
+  if (bytes_read > 0 && spdy_stream_)
+    spdy_stream_->IncreaseRecvWindowSize(bytes_read);
+
+  return user_buffer_->BytesConsumed();
+}
+
+int SpdyProxyClientSocket::Write(IOBuffer* buf, int buf_len,
+                                 const CompletionCallback& callback) {
+  DCHECK(write_callback_.is_null());
+  if (next_state_ != STATE_OPEN)
+    return ERR_SOCKET_NOT_CONNECTED;
+
+  DCHECK(spdy_stream_);
+  write_bytes_outstanding_= buf_len;
+  if (buf_len <= kMaxSpdyFrameChunkSize) {
+    int rv = spdy_stream_->WriteStreamData(buf, buf_len, DATA_FLAG_NONE);
+    if (rv == ERR_IO_PENDING) {
+      write_callback_ = callback;
+      write_buffer_len_ = buf_len;
+    }
+    return rv;
+  }
+
+  // Since a SPDY Data frame can only include kMaxSpdyFrameChunkSize bytes
+  // we need to send multiple data frames
+  for (int i = 0; i < buf_len; i += kMaxSpdyFrameChunkSize) {
+    int len = std::min(kMaxSpdyFrameChunkSize, buf_len - i);
+    scoped_refptr<DrainableIOBuffer> iobuf(new DrainableIOBuffer(buf, i + len));
+    iobuf->SetOffset(i);
+    int rv = spdy_stream_->WriteStreamData(iobuf, len, DATA_FLAG_NONE);
+    if (rv > 0) {
+      write_bytes_outstanding_ -= rv;
+    } else if (rv != ERR_IO_PENDING) {
+      return rv;
+    }
+  }
+  if (write_bytes_outstanding_ > 0) {
+    write_callback_ = callback;
+    write_buffer_len_ = buf_len;
+    return ERR_IO_PENDING;
+  } else {
+    return buf_len;
+  }
+}
+
+bool SpdyProxyClientSocket::SetReceiveBufferSize(int32 size) {
+  // Since this StreamSocket sits on top of a shared SpdySession, it
+  // is not safe for callers to set change this underlying socket.
+  return false;
+}
+
+bool SpdyProxyClientSocket::SetSendBufferSize(int32 size) {
+  // Since this StreamSocket sits on top of a shared SpdySession, it
+  // is not safe for callers to set change this underlying socket.
+  return false;
+}
+
+int SpdyProxyClientSocket::GetPeerAddress(IPEndPoint* address) const {
+  if (!IsConnected())
+    return ERR_SOCKET_NOT_CONNECTED;
+  return spdy_stream_->GetPeerAddress(address);
+}
+
+int SpdyProxyClientSocket::GetLocalAddress(IPEndPoint* address) const {
+  if (!IsConnected())
+    return ERR_SOCKET_NOT_CONNECTED;
+  return spdy_stream_->GetLocalAddress(address);
+}
+
+void SpdyProxyClientSocket::LogBlockedTunnelResponse() const {
+  ProxyClientSocket::LogBlockedTunnelResponse(
+      response_.headers->response_code(),
+      request_.url,
+      /* is_https_proxy = */ true);
+}
+
+void SpdyProxyClientSocket::OnIOComplete(int result) {
+  DCHECK_NE(STATE_DISCONNECTED, next_state_);
+  int rv = DoLoop(result);
+  if (rv != ERR_IO_PENDING) {
+    CompletionCallback c = read_callback_;
+    read_callback_.Reset();
+    c.Run(rv);
+  }
+}
+
+int SpdyProxyClientSocket::DoLoop(int last_io_result) {
+  DCHECK_NE(next_state_, STATE_DISCONNECTED);
+  int rv = last_io_result;
+  do {
+    State state = next_state_;
+    next_state_ = STATE_DISCONNECTED;
+    switch (state) {
+      case STATE_GENERATE_AUTH_TOKEN:
+        DCHECK_EQ(OK, rv);
+        rv = DoGenerateAuthToken();
+        break;
+      case STATE_GENERATE_AUTH_TOKEN_COMPLETE:
+        rv = DoGenerateAuthTokenComplete(rv);
+        break;
+      case STATE_SEND_REQUEST:
+        DCHECK_EQ(OK, rv);
+        net_log_.BeginEvent(
+            NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST);
+        rv = DoSendRequest();
+        break;
+      case STATE_SEND_REQUEST_COMPLETE:
+        net_log_.EndEventWithNetErrorCode(
+            NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST, rv);
+        rv = DoSendRequestComplete(rv);
+        break;
+      case STATE_READ_REPLY_COMPLETE:
+        rv = DoReadReplyComplete(rv);
+        net_log_.EndEventWithNetErrorCode(
+            NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS, rv);
+        break;
+      default:
+        NOTREACHED() << "bad state";
+        rv = ERR_UNEXPECTED;
+        break;
+    }
+  } while (rv != ERR_IO_PENDING && next_state_ != STATE_DISCONNECTED &&
+           next_state_ != STATE_OPEN);
+  return rv;
+}
+
+int SpdyProxyClientSocket::DoGenerateAuthToken() {
+  next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE;
+  return auth_->MaybeGenerateAuthToken(
+      &request_,
+      base::Bind(&SpdyProxyClientSocket::OnIOComplete, base::Unretained(this)),
+      net_log_);
+}
+
+int SpdyProxyClientSocket::DoGenerateAuthTokenComplete(int result) {
+  DCHECK_NE(ERR_IO_PENDING, result);
+  if (result == OK)
+    next_state_ = STATE_SEND_REQUEST;
+  return result;
+}
+
+int SpdyProxyClientSocket::DoSendRequest() {
+  next_state_ = STATE_SEND_REQUEST_COMPLETE;
+
+  // Add Proxy-Authentication header if necessary.
+  HttpRequestHeaders authorization_headers;
+  if (auth_->HaveAuth()) {
+    auth_->AddAuthorizationHeader(&authorization_headers);
+  }
+
+  std::string request_line;
+  HttpRequestHeaders request_headers;
+  BuildTunnelRequest(request_, authorization_headers, endpoint_, &request_line,
+                     &request_headers);
+
+  net_log_.AddEvent(
+      NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS,
+      base::Bind(&HttpRequestHeaders::NetLogCallback,
+                 base::Unretained(&request_headers),
+                 &request_line));
+
+  request_.extra_headers.MergeFrom(request_headers);
+  scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock());
+  CreateSpdyHeadersFromHttpRequest(request_, request_headers, headers.get(),
+                                   spdy_stream_->GetProtocolVersion(), true);
+  // Reset the URL to be the endpoint of the connection
+  if (spdy_stream_->GetProtocolVersion() > 2) {
+    (*headers)[":path"] = endpoint_.ToString();
+    headers->erase(":scheme");
+  } else {
+    (*headers)["url"] = endpoint_.ToString();
+    headers->erase("scheme");
+  }
+  spdy_stream_->set_spdy_headers(headers.Pass());
+
+  return spdy_stream_->SendRequest(true);
+}
+
+int SpdyProxyClientSocket::DoSendRequestComplete(int result) {
+  if (result < 0)
+    return result;
+
+  // Wait for SYN_REPLY frame from the server
+  next_state_ = STATE_READ_REPLY_COMPLETE;
+  return ERR_IO_PENDING;
+}
+
+int SpdyProxyClientSocket::DoReadReplyComplete(int result) {
+  // We enter this method directly from DoSendRequestComplete, since
+  // we are notified by a callback when the SYN_REPLY frame arrives
+
+  if (result < 0)
+    return result;
+
+  // Require the "HTTP/1.x" status line for SSL CONNECT.
+  if (response_.headers->GetParsedHttpVersion() < HttpVersion(1, 0))
+    return ERR_TUNNEL_CONNECTION_FAILED;
+
+  net_log_.AddEvent(
+      NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
+      base::Bind(&HttpResponseHeaders::NetLogCallback, response_.headers));
+
+  switch (response_.headers->response_code()) {
+    case 200:  // OK
+      next_state_ = STATE_OPEN;
+      return OK;
+
+    case 302:  // Found / Moved Temporarily
+      // Try to return a sanitized response so we can follow auth redirects.
+      // If we can't, fail the tunnel connection.
+      if (SanitizeProxyRedirect(&response_, request_.url)) {
+        // Immediately hand off our SpdyStream to a newly created
+        // SpdyHttpStream so that any subsequent SpdyFrames are processed in
+        // the context of the HttpStream, not the socket.
+        DCHECK(spdy_stream_);
+        SpdyStream* stream = spdy_stream_;
+        spdy_stream_ = NULL;
+        response_stream_.reset(new SpdyHttpStream(NULL, false));
+        response_stream_->InitializeWithExistingStream(stream);
+        next_state_ = STATE_DISCONNECTED;
+        return ERR_HTTPS_PROXY_TUNNEL_RESPONSE;
+      } else {
+        LogBlockedTunnelResponse();
+        return ERR_TUNNEL_CONNECTION_FAILED;
+      }
+
+    case 407:  // Proxy Authentication Required
+      next_state_ = STATE_OPEN;
+      return HandleProxyAuthChallenge(auth_, &response_, net_log_);
+
+    default:
+      // Ignore response to avoid letting the proxy impersonate the target
+      // server.  (See http://crbug.com/137891.)
+      LogBlockedTunnelResponse();
+      return ERR_TUNNEL_CONNECTION_FAILED;
+  }
+}
+
+// SpdyStream::Delegate methods:
+// Called when SYN frame has been sent.
+// Returns true if no more data to be sent after SYN frame.
+bool SpdyProxyClientSocket::OnSendHeadersComplete(int status) {
+  DCHECK_EQ(next_state_, STATE_SEND_REQUEST_COMPLETE);
+
+  OnIOComplete(status);
+
+  // We return true here so that we send |spdy_stream_| into
+  // STATE_OPEN (ala WebSockets).
+  return true;
+}
+
+int SpdyProxyClientSocket::OnSendBody() {
+  // Because we use |spdy_stream_| via STATE_OPEN (ala WebSockets)
+  // OnSendBody() should never be called.
+  NOTREACHED();
+  return ERR_UNEXPECTED;
+}
+
+int SpdyProxyClientSocket::OnSendBodyComplete(int /*status*/, bool* /*eof*/) {
+  // Because we use |spdy_stream_| via STATE_OPEN (ala WebSockets)
+  // OnSendBodyComplete() should never be called.
+  NOTREACHED();
+  return ERR_UNEXPECTED;
+}
+
+int SpdyProxyClientSocket::OnResponseReceived(
+    const SpdyHeaderBlock& response,
+    base::Time response_time,
+    int status) {
+  // If we've already received the reply, existing headers are too late.
+  // TODO(mbelshe): figure out a way to make HEADERS frames useful after the
+  //                initial response.
+  if (next_state_ != STATE_READ_REPLY_COMPLETE)
+    return OK;
+
+  // Save the response
+  if (!SpdyHeadersToHttpResponse(
+          response, spdy_stream_->GetProtocolVersion(), &response_))
+      return ERR_INCOMPLETE_SPDY_HEADERS;
+
+  OnIOComplete(status);
+  return OK;
+}
+
+void SpdyProxyClientSocket::OnHeadersSent() {
+  // Proxy client sockets don't send any HEADERS frame.
+  NOTREACHED();
+}
+
+// Called when data is received.
+int SpdyProxyClientSocket::OnDataReceived(const char* data, int length) {
+  if (length > 0) {
+    // Save the received data.
+    scoped_refptr<IOBuffer> io_buffer(new IOBuffer(length));
+    memcpy(io_buffer->data(), data, length);
+    read_buffer_.push_back(
+        make_scoped_refptr(new DrainableIOBuffer(io_buffer, length)));
+  }
+
+  if (!read_callback_.is_null()) {
+    int rv = PopulateUserReadBuffer();
+    CompletionCallback c = read_callback_;
+    read_callback_.Reset();
+    user_buffer_ = NULL;
+    c.Run(rv);
+  }
+  return OK;
+}
+
+void SpdyProxyClientSocket::OnDataSent(int length)  {
+  DCHECK(!write_callback_.is_null());
+
+  write_bytes_outstanding_ -= length;
+
+  DCHECK_GE(write_bytes_outstanding_, 0);
+
+  if (write_bytes_outstanding_ == 0) {
+    int rv = write_buffer_len_;
+    write_buffer_len_ = 0;
+    write_bytes_outstanding_ = 0;
+    CompletionCallback c = write_callback_;
+    write_callback_.Reset();
+    c.Run(rv);
+  }
+}
+
+void SpdyProxyClientSocket::OnClose(int status)  {
+  DCHECK(spdy_stream_);
+  was_ever_used_ = spdy_stream_->WasEverUsed();
+  spdy_stream_ = NULL;
+
+  bool connecting = next_state_ != STATE_DISCONNECTED &&
+      next_state_ < STATE_OPEN;
+  if (next_state_ == STATE_OPEN)
+    next_state_ = STATE_CLOSED;
+  else
+    next_state_ = STATE_DISCONNECTED;
+
+  base::WeakPtr<SpdyProxyClientSocket> weak_ptr = weak_factory_.GetWeakPtr();
+  CompletionCallback write_callback = write_callback_;
+  write_callback_.Reset();
+  write_buffer_len_ = 0;
+  write_bytes_outstanding_ = 0;
+
+  // If we're in the middle of connecting, we need to make sure
+  // we invoke the connect callback.
+  if (connecting) {
+    DCHECK(!read_callback_.is_null());
+    CompletionCallback read_callback = read_callback_;
+    read_callback_.Reset();
+    read_callback.Run(status);
+  } else if (!read_callback_.is_null()) {
+    // If we have a read_callback_, the we need to make sure we call it back.
+    OnDataReceived(NULL, 0);
+  }
+  // This may have been deleted by read_callback_, so check first.
+  if (weak_ptr && !write_callback.is_null())
+    write_callback.Run(ERR_CONNECTION_CLOSED);
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_proxy_client_socket.h b/src/net/spdy/spdy_proxy_client_socket.h
new file mode 100644
index 0000000..a5da281
--- /dev/null
+++ b/src/net/spdy/spdy_proxy_client_socket.h
@@ -0,0 +1,177 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_SPDY_SPDY_PROXY_CLIENT_SOCKET_H_
+#define NET_SPDY_SPDY_PROXY_CLIENT_SOCKET_H_
+
+#include <string>
+#include <list>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_log.h"
+#include "net/http/http_auth_controller.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_info.h"
+#include "net/http/proxy_client_socket.h"
+#include "net/spdy/spdy_http_stream.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_stream.h"
+
+
+class GURL;
+
+namespace net {
+
+class AddressList;
+class HttpStream;
+class IOBuffer;
+class SpdyStream;
+
+class NET_EXPORT_PRIVATE SpdyProxyClientSocket : public ProxyClientSocket,
+                                                 public SpdyStream::Delegate {
+ public:
+  // Create a socket on top of the |spdy_stream| by sending a SYN_STREAM
+  // CONNECT frame for |endpoint|.  After the SYN_REPLY is received,
+  // any data read/written to the socket will be transferred in data
+  // frames.
+  SpdyProxyClientSocket(SpdyStream* spdy_stream,
+                        const std::string& user_agent,
+                        const HostPortPair& endpoint,
+                        const GURL& url,
+                        const HostPortPair& proxy_server,
+                        HttpAuthCache* auth_cache,
+                        HttpAuthHandlerFactory* auth_handler_factory);
+
+
+  // On destruction Disconnect() is called.
+  virtual ~SpdyProxyClientSocket();
+
+  // ProxyClientSocket methods:
+  virtual const HttpResponseInfo* GetConnectResponseInfo() const OVERRIDE;
+  virtual HttpStream* CreateConnectResponseStream() OVERRIDE;
+  virtual const scoped_refptr<HttpAuthController>& GetAuthController() const
+      OVERRIDE;
+  virtual int RestartWithAuth(const CompletionCallback& callback) OVERRIDE;
+  virtual bool IsUsingSpdy() const OVERRIDE;
+  virtual NextProto GetProtocolNegotiated() const OVERRIDE;
+
+  // StreamSocket implementation.
+  virtual int Connect(const CompletionCallback& callback) OVERRIDE;
+  virtual void Disconnect() OVERRIDE;
+  virtual bool IsConnected() const OVERRIDE;
+  virtual bool IsConnectedAndIdle() const OVERRIDE;
+  virtual const BoundNetLog& NetLog() const OVERRIDE;
+  virtual void SetSubresourceSpeculation() OVERRIDE;
+  virtual void SetOmniboxSpeculation() OVERRIDE;
+  virtual bool WasEverUsed() const OVERRIDE;
+  virtual bool UsingTCPFastOpen() const OVERRIDE;
+  virtual int64 NumBytesRead() const OVERRIDE;
+  virtual base::TimeDelta GetConnectTimeMicros() const OVERRIDE;
+  virtual bool WasNpnNegotiated() const OVERRIDE;
+  virtual NextProto GetNegotiatedProtocol() const OVERRIDE;
+  virtual bool GetSSLInfo(SSLInfo* ssl_info) OVERRIDE;
+
+  // Socket implementation.
+  virtual int Read(IOBuffer* buf,
+                   int buf_len,
+                   const CompletionCallback& callback) OVERRIDE;
+  virtual int Write(IOBuffer* buf,
+                    int buf_len,
+                    const CompletionCallback& callback) OVERRIDE;
+  virtual bool SetReceiveBufferSize(int32 size) OVERRIDE;
+  virtual bool SetSendBufferSize(int32 size) OVERRIDE;
+  virtual int GetPeerAddress(IPEndPoint* address) const OVERRIDE;
+  virtual int GetLocalAddress(IPEndPoint* address) const OVERRIDE;
+
+  // SpdyStream::Delegate implementation.
+  virtual bool OnSendHeadersComplete(int status) OVERRIDE;
+  virtual int OnSendBody() OVERRIDE;
+  virtual int OnSendBodyComplete(int status, bool* eof) OVERRIDE;
+  virtual int OnResponseReceived(const SpdyHeaderBlock& response,
+                                 base::Time response_time,
+                                 int status) OVERRIDE;
+  virtual void OnHeadersSent() OVERRIDE;
+  virtual int OnDataReceived(const char* data, int length) OVERRIDE;
+  virtual void OnDataSent(int length) OVERRIDE;
+  virtual void OnClose(int status) OVERRIDE;
+
+ private:
+  enum State {
+    STATE_DISCONNECTED,
+    STATE_GENERATE_AUTH_TOKEN,
+    STATE_GENERATE_AUTH_TOKEN_COMPLETE,
+    STATE_SEND_REQUEST,
+    STATE_SEND_REQUEST_COMPLETE,
+    STATE_READ_REPLY_COMPLETE,
+    STATE_OPEN,
+    STATE_CLOSED
+  };
+
+  void LogBlockedTunnelResponse() const;
+
+  void OnIOComplete(int result);
+
+  int DoLoop(int last_io_result);
+  int DoGenerateAuthToken();
+  int DoGenerateAuthTokenComplete(int result);
+  int DoSendRequest();
+  int DoSendRequestComplete(int result);
+  int DoReadReplyComplete(int result);
+
+  // Populates |user_buffer_| with as much read data as possible
+  // and returns the number of bytes read.
+  int PopulateUserReadBuffer();
+
+  State next_state_;
+
+  // Pointer to the SPDY Stream that this sits on top of.
+  scoped_refptr<SpdyStream> spdy_stream_;
+
+  // Stores the callback to the layer above, called on completing Read() or
+  // Connect().
+  CompletionCallback read_callback_;
+  // Stores the callback to the layer above, called on completing Write().
+  CompletionCallback write_callback_;
+
+  // CONNECT request and response.
+  HttpRequestInfo request_;
+  HttpResponseInfo response_;
+
+  // The hostname and port of the endpoint.  This is not necessarily the one
+  // specified by the URL, due to Alternate-Protocol or fixed testing ports.
+  const HostPortPair endpoint_;
+  scoped_refptr<HttpAuthController> auth_;
+
+  // We buffer the response body as it arrives asynchronously from the stream.
+  std::list<scoped_refptr<DrainableIOBuffer> > read_buffer_;
+
+  // User provided buffer for the Read() response.
+  scoped_refptr<DrainableIOBuffer> user_buffer_;
+
+  // User specified number of bytes to be written.
+  int write_buffer_len_;
+  // Number of bytes written which have not been confirmed
+  int write_bytes_outstanding_;
+
+  // True if the transport socket has ever sent data.
+  bool was_ever_used_;
+
+  scoped_ptr<SpdyHttpStream> response_stream_;
+
+  base::WeakPtrFactory<SpdyProxyClientSocket> weak_factory_;
+
+  const BoundNetLog net_log_;
+
+  DISALLOW_COPY_AND_ASSIGN(SpdyProxyClientSocket);
+};
+
+}  // namespace net
+
+#endif  // NET_SPDY_SPDY_PROXY_CLIENT_SOCKET_H_
diff --git a/src/net/spdy/spdy_proxy_client_socket_spdy2_unittest.cc b/src/net/spdy/spdy_proxy_client_socket_spdy2_unittest.cc
new file mode 100644
index 0000000..5216e4a
--- /dev/null
+++ b/src/net/spdy/spdy_proxy_client_socket_spdy2_unittest.cc
@@ -0,0 +1,1357 @@
+// Copyright (c) 2012 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_proxy_client_socket.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/utf_string_conversions.h"
+#include "net/base/address_list.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/mock_host_resolver.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/winsock_init.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/tcp_client_socket.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/spdy/spdy_test_util_spdy2.h"
+#include "testing/platform_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using namespace net::test_spdy2;
+
+//-----------------------------------------------------------------------------
+
+namespace {
+
+static const char kRequestUrl[] = "https://www.google.com/";
+static const char kOriginHost[] = "www.google.com";
+static const int kOriginPort = 443;
+static const char kOriginHostPort[] = "www.google.com:443";
+static const char kProxyUrl[] = "https://myproxy:6121/";
+static const char kProxyHost[] = "myproxy";
+static const int kProxyPort = 6121;
+static const char kUserAgent[] = "Mozilla/1.0";
+
+static const int kStreamId = 1;
+
+static const char kMsg1[] = "\0hello!\xff";
+static const int kLen1 = 8;
+static const char kMsg2[] = "\00012345678\0";
+static const int kLen2 = 10;
+static const char kMsg3[] = "bye!";
+static const int kLen3 = 4;
+static const char kMsg33[] = "bye!bye!";
+static const int kLen33 = kLen3 + kLen3;
+static const char kMsg333[] = "bye!bye!bye!";
+static const int kLen333 = kLen3 + kLen3 + kLen3;
+
+static const char kRedirectUrl[] = "https://example.com/";
+
+}  // anonymous namespace
+
+namespace net {
+
+class SpdyProxyClientSocketSpdy2Test : public PlatformTest {
+ public:
+  SpdyProxyClientSocketSpdy2Test();
+
+  virtual void TearDown();
+
+ protected:
+  void Initialize(MockRead* reads, size_t reads_count, MockWrite* writes,
+                  size_t writes_count);
+  SpdyFrame* ConstructConnectRequestFrame();
+  SpdyFrame* ConstructConnectAuthRequestFrame();
+  SpdyFrame* ConstructConnectReplyFrame();
+  SpdyFrame* ConstructConnectAuthReplyFrame();
+  SpdyFrame* ConstructConnectRedirectReplyFrame();
+  SpdyFrame* ConstructConnectErrorReplyFrame();
+  SpdyFrame* ConstructBodyFrame(const char* data, int length);
+  scoped_refptr<IOBufferWithSize> CreateBuffer(const char* data, int size);
+  void AssertConnectSucceeds();
+  void AssertConnectFails(int result);
+  void AssertConnectionEstablished();
+  void AssertSyncReadEquals(const char* data, int len);
+  void AssertAsyncReadEquals(const char* data, int len);
+  void AssertReadStarts(const char* data, int len);
+  void AssertReadReturns(const char* data, int len);
+  void AssertAsyncWriteSucceeds(const char* data, int len);
+  void AssertWriteReturns(const char* data, int len, int rv);
+  void AssertWriteLength(int len);
+  void AssertAsyncWriteWithReadsSucceeds(const char* data, int len,
+                                        int num_reads);
+
+  void AddAuthToCache() {
+    const string16 kFoo(ASCIIToUTF16("foo"));
+    const string16 kBar(ASCIIToUTF16("bar"));
+    session_->http_auth_cache()->Add(GURL(kProxyUrl),
+                                     "MyRealm1",
+                                     HttpAuth::AUTH_SCHEME_BASIC,
+                                     "Basic realm=MyRealm1",
+                                     AuthCredentials(kFoo, kBar),
+                                     "/");
+  }
+
+  void Run(int steps) {
+    data_->StopAfter(steps);
+    data_->Run();
+  }
+
+  scoped_ptr<SpdyProxyClientSocket> sock_;
+  TestCompletionCallback read_callback_;
+  TestCompletionCallback write_callback_;
+  scoped_ptr<DeterministicSocketData> data_;
+
+ private:
+  scoped_refptr<HttpNetworkSession> session_;
+  scoped_refptr<IOBuffer> read_buf_;
+  SpdySessionDependencies session_deps_;
+  MockConnect connect_data_;
+  scoped_refptr<SpdySession> spdy_session_;
+  scoped_refptr<SpdyStream> spdy_stream_;
+  BufferedSpdyFramer framer_;
+
+  std::string user_agent_;
+  GURL url_;
+  HostPortPair proxy_host_port_;
+  HostPortPair endpoint_host_port_pair_;
+  ProxyServer proxy_;
+  HostPortProxyPair endpoint_host_port_proxy_pair_;
+  scoped_refptr<TransportSocketParams> transport_params_;
+
+  DISALLOW_COPY_AND_ASSIGN(SpdyProxyClientSocketSpdy2Test);
+};
+
+SpdyProxyClientSocketSpdy2Test::SpdyProxyClientSocketSpdy2Test()
+    : sock_(NULL),
+      data_(NULL),
+      session_(NULL),
+      read_buf_(NULL),
+      session_deps_(),
+      connect_data_(SYNCHRONOUS, OK),
+      spdy_session_(NULL),
+      spdy_stream_(NULL),
+      framer_(2, false),
+      user_agent_(kUserAgent),
+      url_(kRequestUrl),
+      proxy_host_port_(kProxyHost, kProxyPort),
+      endpoint_host_port_pair_(kOriginHost, kOriginPort),
+      proxy_(ProxyServer::SCHEME_HTTPS, proxy_host_port_),
+      endpoint_host_port_proxy_pair_(endpoint_host_port_pair_, proxy_),
+      transport_params_(new TransportSocketParams(proxy_host_port_,
+                                                  LOWEST,
+                                                  false,
+                                                  false,
+                                                  OnHostResolutionCallback())) {
+}
+
+void SpdyProxyClientSocketSpdy2Test::TearDown() {
+  sock_.reset(NULL);
+  if (session_ != NULL)
+    session_->spdy_session_pool()->CloseAllSessions();
+
+  // Empty the current queue.
+  MessageLoop::current()->RunUntilIdle();
+  PlatformTest::TearDown();
+}
+
+void SpdyProxyClientSocketSpdy2Test::Initialize(MockRead* reads,
+                                           size_t reads_count,
+                                           MockWrite* writes,
+                                           size_t writes_count) {
+  data_.reset(new DeterministicSocketData(reads, reads_count,
+                                          writes, writes_count));
+  data_->set_connect_data(connect_data_);
+  data_->SetStop(2);
+
+  session_deps_.deterministic_socket_factory->AddSocketDataProvider(
+      data_.get());
+  session_deps_.host_resolver->set_synchronous_mode(true);
+
+  session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic(
+      &session_deps_);
+
+  // Creates a new spdy session
+  spdy_session_ =
+      session_->spdy_session_pool()->Get(endpoint_host_port_proxy_pair_,
+                                         BoundNetLog());
+
+  // Perform the TCP connect
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK,
+            connection->Init(endpoint_host_port_pair_.ToString(),
+                             transport_params_, LOWEST, CompletionCallback(),
+                             session_->GetTransportSocketPool(
+                                 HttpNetworkSession::NORMAL_SOCKET_POOL),
+                             BoundNetLog()));
+  spdy_session_->InitializeWithSocket(connection.release(), false, OK);
+
+  // Create the SPDY Stream
+  ASSERT_EQ(
+      OK,
+      spdy_session_->CreateStream(url_, LOWEST, &spdy_stream_, BoundNetLog(),
+                                  CompletionCallback()));
+
+  // Create the SpdyProxyClientSocket
+  sock_.reset(
+      new SpdyProxyClientSocket(spdy_stream_, user_agent_,
+                                endpoint_host_port_pair_, url_,
+                                proxy_host_port_, session_->http_auth_cache(),
+                                session_->http_auth_handler_factory()));
+}
+
+scoped_refptr<IOBufferWithSize> SpdyProxyClientSocketSpdy2Test::CreateBuffer(
+    const char* data, int size) {
+  scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(size));
+  memcpy(buf->data(), data, size);
+  return buf;
+}
+
+void SpdyProxyClientSocketSpdy2Test::AssertConnectSucceeds() {
+  ASSERT_EQ(ERR_IO_PENDING, sock_->Connect(read_callback_.callback()));
+  data_->Run();
+  ASSERT_EQ(OK, read_callback_.WaitForResult());
+}
+
+void SpdyProxyClientSocketSpdy2Test::AssertConnectFails(int result) {
+  ASSERT_EQ(ERR_IO_PENDING, sock_->Connect(read_callback_.callback()));
+  data_->Run();
+  ASSERT_EQ(result, read_callback_.WaitForResult());
+}
+
+void SpdyProxyClientSocketSpdy2Test::AssertConnectionEstablished() {
+  const HttpResponseInfo* response = sock_->GetConnectResponseInfo();
+  ASSERT_TRUE(response != NULL);
+  ASSERT_EQ(200, response->headers->response_code());
+  ASSERT_EQ("Connection Established", response->headers->GetStatusText());
+}
+
+void SpdyProxyClientSocketSpdy2Test::AssertSyncReadEquals(const char* data,
+                                                     int len) {
+  scoped_refptr<IOBuffer> buf(new IOBuffer(len));
+  ASSERT_EQ(len, sock_->Read(buf, len, CompletionCallback()));
+  ASSERT_EQ(std::string(data, len), std::string(buf->data(), len));
+  ASSERT_TRUE(sock_->IsConnected());
+}
+
+void SpdyProxyClientSocketSpdy2Test::AssertAsyncReadEquals(const char* data,
+                                                           int len) {
+  data_->StopAfter(1);
+  // Issue the read, which will be completed asynchronously
+  scoped_refptr<IOBuffer> buf(new IOBuffer(len));
+  ASSERT_EQ(ERR_IO_PENDING, sock_->Read(buf, len, read_callback_.callback()));
+  EXPECT_TRUE(sock_->IsConnected());
+  data_->Run();
+
+  EXPECT_TRUE(sock_->IsConnected());
+
+  // Now the read will return
+  EXPECT_EQ(len, read_callback_.WaitForResult());
+  ASSERT_EQ(std::string(data, len), std::string(buf->data(), len));
+}
+
+void SpdyProxyClientSocketSpdy2Test::AssertReadStarts(const char* data,
+                                                      int len) {
+  data_->StopAfter(1);
+  // Issue the read, which will be completed asynchronously
+  read_buf_ = new IOBuffer(len);
+  ASSERT_EQ(ERR_IO_PENDING,
+            sock_->Read(read_buf_, len, read_callback_.callback()));
+  EXPECT_TRUE(sock_->IsConnected());
+}
+
+void SpdyProxyClientSocketSpdy2Test::AssertReadReturns(const char* data,
+                                                       int len) {
+  EXPECT_TRUE(sock_->IsConnected());
+
+  // Now the read will return
+  EXPECT_EQ(len, read_callback_.WaitForResult());
+  ASSERT_EQ(std::string(data, len), std::string(read_buf_->data(), len));
+}
+
+void SpdyProxyClientSocketSpdy2Test::AssertAsyncWriteSucceeds(const char* data,
+                                                              int len) {
+  AssertWriteReturns(data, len, ERR_IO_PENDING);
+  data_->RunFor(1);
+  AssertWriteLength(len);
+}
+
+void SpdyProxyClientSocketSpdy2Test::AssertWriteReturns(const char* data,
+                                                        int len,
+                                                        int rv) {
+  scoped_refptr<IOBufferWithSize> buf(CreateBuffer(data, len));
+  EXPECT_EQ(rv, sock_->Write(buf, buf->size(), write_callback_.callback()));
+}
+
+void SpdyProxyClientSocketSpdy2Test::AssertWriteLength(int len) {
+  EXPECT_EQ(len, write_callback_.WaitForResult());
+}
+
+void SpdyProxyClientSocketSpdy2Test::AssertAsyncWriteWithReadsSucceeds(
+    const char* data, int len, int num_reads) {
+  scoped_refptr<IOBufferWithSize> buf(CreateBuffer(data, len));
+
+  EXPECT_EQ(ERR_IO_PENDING, sock_->Write(buf, buf->size(),
+                                         write_callback_.callback()));
+
+  for (int i = 0; i < num_reads; i++) {
+    Run(1);
+    AssertSyncReadEquals(kMsg2, kLen2);
+  }
+
+  write_callback_.WaitForResult();
+}
+
+// Constructs a standard SPDY SYN_STREAM frame for a CONNECT request.
+SpdyFrame*
+SpdyProxyClientSocketSpdy2Test::ConstructConnectRequestFrame() {
+  const SpdyHeaderInfo kSynStartHeader = {
+    SYN_STREAM,
+    kStreamId,
+    0,
+    net::ConvertRequestPriorityToSpdyPriority(LOWEST, 2),
+    CONTROL_FLAG_NONE,
+    false,
+    INVALID,
+    NULL,
+    0,
+    DATA_FLAG_NONE
+  };
+  const char* const kConnectHeaders[] = {
+    "method", "CONNECT",
+    "url", kOriginHostPort,
+    "host", kOriginHost,
+    "user-agent", kUserAgent,
+    "version", "HTTP/1.1",
+  };
+  return ConstructSpdyPacket(
+      kSynStartHeader, NULL, 0, kConnectHeaders, arraysize(kConnectHeaders)/2);
+}
+
+// Constructs a SPDY SYN_STREAM frame for a CONNECT request which includes
+// Proxy-Authorization headers.
+SpdyFrame*
+SpdyProxyClientSocketSpdy2Test::ConstructConnectAuthRequestFrame() {
+  const SpdyHeaderInfo kSynStartHeader = {
+    SYN_STREAM,
+    kStreamId,
+    0,
+    net::ConvertRequestPriorityToSpdyPriority(LOWEST, 2),
+    CONTROL_FLAG_NONE,
+    false,
+    INVALID,
+    NULL,
+    0,
+    DATA_FLAG_NONE
+  };
+  const char* const kConnectHeaders[] = {
+    "method", "CONNECT",
+    "url", kOriginHostPort,
+    "host", kOriginHost,
+    "user-agent", kUserAgent,
+    "version", "HTTP/1.1",
+    "proxy-authorization", "Basic Zm9vOmJhcg==",
+  };
+  return ConstructSpdyPacket(
+      kSynStartHeader, NULL, 0, kConnectHeaders, arraysize(kConnectHeaders)/2);
+}
+
+// Constructs a standard SPDY SYN_REPLY frame to match the SPDY CONNECT.
+SpdyFrame* SpdyProxyClientSocketSpdy2Test::ConstructConnectReplyFrame() {
+  const char* const kStandardReplyHeaders[] = {
+      "status", "200 Connection Established",
+      "version", "HTTP/1.1"
+  };
+  return ConstructSpdyControlFrame(NULL,
+                                   0,
+                                   false,
+                                   kStreamId,
+                                   LOWEST,
+                                   SYN_REPLY,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardReplyHeaders,
+                                   arraysize(kStandardReplyHeaders));
+}
+
+// Constructs a standard SPDY SYN_REPLY frame to match the SPDY CONNECT.
+SpdyFrame*
+SpdyProxyClientSocketSpdy2Test::ConstructConnectAuthReplyFrame() {
+  const char* const kStandardReplyHeaders[] = {
+      "status", "407 Proxy Authentication Required",
+      "version", "HTTP/1.1",
+      "proxy-authenticate", "Basic realm=\"MyRealm1\"",
+  };
+
+  return ConstructSpdyControlFrame(NULL,
+                                   0,
+                                   false,
+                                   kStreamId,
+                                   LOWEST,
+                                   SYN_REPLY,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardReplyHeaders,
+                                   arraysize(kStandardReplyHeaders));
+}
+
+// Constructs a SPDY SYN_REPLY frame with an HTTP 302 redirect.
+SpdyFrame*
+SpdyProxyClientSocketSpdy2Test::ConstructConnectRedirectReplyFrame() {
+  const char* const kStandardReplyHeaders[] = {
+      "status", "302 Found",
+      "version", "HTTP/1.1",
+      "location", kRedirectUrl,
+      "set-cookie", "foo=bar"
+  };
+
+  return ConstructSpdyControlFrame(NULL,
+                                   0,
+                                   false,
+                                   kStreamId,
+                                   LOWEST,
+                                   SYN_REPLY,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardReplyHeaders,
+                                   arraysize(kStandardReplyHeaders));
+}
+
+// Constructs a SPDY SYN_REPLY frame with an HTTP 500 error.
+SpdyFrame*
+SpdyProxyClientSocketSpdy2Test::ConstructConnectErrorReplyFrame() {
+  const char* const kStandardReplyHeaders[] = {
+      "status", "500 Internal Server Error",
+      "version", "HTTP/1.1",
+  };
+
+  return ConstructSpdyControlFrame(NULL,
+                                   0,
+                                   false,
+                                   kStreamId,
+                                   LOWEST,
+                                   SYN_REPLY,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardReplyHeaders,
+                                   arraysize(kStandardReplyHeaders));
+}
+
+SpdyFrame* SpdyProxyClientSocketSpdy2Test::ConstructBodyFrame(
+    const char* data,  int length) {
+  return framer_.CreateDataFrame(kStreamId, data, length, DATA_FLAG_NONE);
+}
+
+// ----------- Connect
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, ConnectSendsCorrectRequest) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  ASSERT_FALSE(sock_->IsConnected());
+
+  AssertConnectSucceeds();
+
+  AssertConnectionEstablished();
+}
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, ConnectWithAuthRequested) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectAuthReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectFails(ERR_PROXY_AUTH_REQUESTED);
+
+  const HttpResponseInfo* response = sock_->GetConnectResponseInfo();
+  ASSERT_TRUE(response != NULL);
+  ASSERT_EQ(407, response->headers->response_code());
+  ASSERT_EQ("Proxy Authentication Required",
+            response->headers->GetStatusText());
+}
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, ConnectWithAuthCredentials) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectAuthRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+  AddAuthToCache();
+
+  AssertConnectSucceeds();
+
+  AssertConnectionEstablished();
+}
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, ConnectRedirects) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectRedirectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectFails(ERR_HTTPS_PROXY_TUNNEL_RESPONSE);
+
+  const HttpResponseInfo* response = sock_->GetConnectResponseInfo();
+  ASSERT_TRUE(response != NULL);
+
+  const HttpResponseHeaders* headers = response->headers;
+  ASSERT_EQ(302, headers->response_code());
+  ASSERT_FALSE(headers->HasHeader("set-cookie"));
+  ASSERT_TRUE(headers->HasHeaderValue("content-length", "0"));
+
+  std::string location;
+  ASSERT_TRUE(headers->IsRedirect(&location));
+  ASSERT_EQ(location, kRedirectUrl);
+}
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, ConnectFails) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    MockRead(ASYNC, 0, 1),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  ASSERT_FALSE(sock_->IsConnected());
+
+  AssertConnectFails(ERR_CONNECTION_CLOSED);
+
+  ASSERT_FALSE(sock_->IsConnected());
+}
+
+// ----------- WasEverUsed
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, WasEverUsedReturnsCorrectValues) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  EXPECT_FALSE(sock_->WasEverUsed());
+  AssertConnectSucceeds();
+  EXPECT_TRUE(sock_->WasEverUsed());
+  sock_->Disconnect();
+  EXPECT_TRUE(sock_->WasEverUsed());
+}
+
+// ----------- GetPeerAddress
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, GetPeerAddressReturnsCorrectValues) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  net::IPEndPoint addr;
+  EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, sock_->GetPeerAddress(&addr));
+
+  AssertConnectSucceeds();
+  EXPECT_TRUE(sock_->IsConnected());
+  EXPECT_EQ(OK, sock_->GetPeerAddress(&addr));
+
+  Run(1);
+
+  EXPECT_FALSE(sock_->IsConnected());
+  EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, sock_->GetPeerAddress(&addr));
+
+  sock_->Disconnect();
+
+  EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, sock_->GetPeerAddress(&addr));
+}
+
+// ----------- Write
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, WriteSendsDataInDataFrame) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+    CreateMockWrite(*msg1, 2, SYNCHRONOUS),
+    CreateMockWrite(*msg2, 3, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 4),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  AssertAsyncWriteSucceeds(kMsg1, kLen1);
+  AssertAsyncWriteSucceeds(kMsg2, kLen2);
+}
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, WriteSplitsLargeDataIntoMultipleFrames) {
+  std::string chunk_data(kMaxSpdyFrameChunkSize, 'x');
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  scoped_ptr<SpdyFrame> chunk(ConstructBodyFrame(chunk_data.data(),
+                                                       chunk_data.length()));
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+    CreateMockWrite(*chunk, 2, SYNCHRONOUS),
+    CreateMockWrite(*chunk, 3, SYNCHRONOUS),
+    CreateMockWrite(*chunk, 4, SYNCHRONOUS)
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 5),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  std::string big_data(kMaxSpdyFrameChunkSize * 3, 'x');
+  scoped_refptr<IOBufferWithSize> buf(CreateBuffer(big_data.data(),
+                                                   big_data.length()));
+
+  EXPECT_EQ(ERR_IO_PENDING, sock_->Write(buf, buf->size(),
+                                         write_callback_.callback()));
+  data_->RunFor(3);
+
+  EXPECT_EQ(buf->size(), write_callback_.WaitForResult());
+}
+
+// ----------- Read
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, ReadReadsDataInDataFrame) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg1, 2, ASYNC),
+    MockRead(ASYNC, 0, 3),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(1);  // SpdySession consumes the next read and sends it to
+           // sock_ to be buffered.
+  AssertSyncReadEquals(kMsg1, kLen1);
+}
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, ReadDataFromBufferedFrames) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg1, 2, ASYNC),
+    CreateMockRead(*msg2, 3, ASYNC),
+    MockRead(ASYNC, 0, 4),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(1);  // SpdySession consumes the next read and sends it to
+           // sock_ to be buffered.
+  AssertSyncReadEquals(kMsg1, kLen1);
+  Run(1);  // SpdySession consumes the next read and sends it to
+           // sock_ to be buffered.
+  AssertSyncReadEquals(kMsg2, kLen2);
+}
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, ReadDataMultipleBufferedFrames) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg1, 2, ASYNC),
+    CreateMockRead(*msg2, 3, ASYNC),
+    MockRead(ASYNC, 0, 4),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(2);  // SpdySession consumes the next two reads and sends then to
+           // sock_ to be buffered.
+  AssertSyncReadEquals(kMsg1, kLen1);
+  AssertSyncReadEquals(kMsg2, kLen2);
+}
+
+TEST_F(SpdyProxyClientSocketSpdy2Test,
+       LargeReadWillMergeDataFromDifferentFrames) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  scoped_ptr<SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg3, 2, ASYNC),
+    CreateMockRead(*msg3, 3, ASYNC),
+    MockRead(ASYNC, 0, 4),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(2);  // SpdySession consumes the next two reads and sends then to
+           // sock_ to be buffered.
+  // The payload from two data frames, each with kMsg3 will be combined
+  // together into a single read().
+  AssertSyncReadEquals(kMsg33, kLen33);
+}
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, MultipleShortReadsThenMoreRead) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  scoped_ptr<SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3));
+  scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg1, 2, ASYNC),
+    CreateMockRead(*msg3, 3, ASYNC),
+    CreateMockRead(*msg3, 4, ASYNC),
+    CreateMockRead(*msg2, 5, ASYNC),
+    MockRead(ASYNC, 0, 6),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(4);  // SpdySession consumes the next four reads and sends then to
+           // sock_ to be buffered.
+  AssertSyncReadEquals(kMsg1, kLen1);
+  // The payload from two data frames, each with kMsg3 will be combined
+  // together into a single read().
+  AssertSyncReadEquals(kMsg33, kLen33);
+  AssertSyncReadEquals(kMsg2, kLen2);
+}
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, ReadWillSplitDataFromLargeFrame) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  scoped_ptr<SpdyFrame> msg33(ConstructBodyFrame(kMsg33, kLen33));
+  scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg1, 2, ASYNC),
+    CreateMockRead(*msg33, 3, ASYNC),
+    MockRead(ASYNC, 0, 4),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(2);  // SpdySession consumes the next two reads and sends then to
+           // sock_ to be buffered.
+  AssertSyncReadEquals(kMsg1, kLen1);
+  // The payload from the single large data frame will be read across
+  // two different reads.
+  AssertSyncReadEquals(kMsg3, kLen3);
+  AssertSyncReadEquals(kMsg3, kLen3);
+}
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, MultipleReadsFromSameLargeFrame) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg333(ConstructBodyFrame(kMsg333, kLen333));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg333, 2, ASYNC),
+    MockRead(ASYNC, 0, 3),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(1);  // SpdySession consumes the next read and sends it to
+           // sock_ to be buffered.
+  // The payload from the single large data frame will be read across
+  // two different reads.
+  AssertSyncReadEquals(kMsg33, kLen33);
+
+  // Now attempt to do a read of more data than remains buffered
+  scoped_refptr<IOBuffer> buf(new IOBuffer(kLen33));
+  ASSERT_EQ(kLen3, sock_->Read(buf, kLen33, read_callback_.callback()));
+  ASSERT_EQ(std::string(kMsg3, kLen3), std::string(buf->data(), kLen3));
+  ASSERT_TRUE(sock_->IsConnected());
+}
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, ReadAuthResponseBody) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectAuthReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg1, 2, ASYNC),
+    CreateMockRead(*msg2, 3, ASYNC),
+    MockRead(ASYNC, 0, 4),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectFails(ERR_PROXY_AUTH_REQUESTED);
+
+  Run(2);  // SpdySession consumes the next two reads and sends then to
+           // sock_ to be buffered.
+  AssertSyncReadEquals(kMsg1, kLen1);
+  AssertSyncReadEquals(kMsg2, kLen2);
+}
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, ReadErrorResponseBody) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectErrorReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg1, 2, ASYNC),
+    CreateMockRead(*msg2, 3, ASYNC),
+    MockRead(ASYNC, 0, 4),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectFails(ERR_TUNNEL_CONNECTION_FAILED);
+}
+
+// ----------- Reads and Writes
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, AsyncReadAroundWrite) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+    CreateMockWrite(*msg2, 3, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  scoped_ptr<SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg1, 2, ASYNC),  // sync read
+    CreateMockRead(*msg3, 4, ASYNC),  // async read
+    MockRead(ASYNC, 0, 5),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(1);
+  AssertSyncReadEquals(kMsg1, kLen1);
+
+  AssertReadStarts(kMsg3, kLen3);
+  // Read should block until after the write succeeds
+
+  AssertAsyncWriteSucceeds(kMsg2, kLen2);  // Runs 1 step
+
+  ASSERT_FALSE(read_callback_.have_result());
+  Run(1);
+  // Now the read will return
+  AssertReadReturns(kMsg3, kLen3);
+}
+
+TEST_F(SpdyProxyClientSocketSpdy2Test, AsyncWriteAroundReads) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+    CreateMockWrite(*msg2, 4, ASYNC),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  scoped_ptr<SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg1, 2, ASYNC),
+    CreateMockRead(*msg3, 3, ASYNC),
+    MockRead(ASYNC, 0, 5),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(1);
+  AssertSyncReadEquals(kMsg1, kLen1);
+  // Write should block until the read completes
+  AssertWriteReturns(kMsg2, kLen2, ERR_IO_PENDING);
+
+  AssertAsyncReadEquals(kMsg3, kLen3);
+
+  ASSERT_FALSE(write_callback_.have_result());
+
+  // Now the write will complete
+  Run(1);
+  AssertWriteLength(kLen2);
+}
+
+// ----------- Reading/Writing on Closed socket
+
+// Reading from an already closed socket should return 0
+TEST_F(SpdyProxyClientSocketSpdy2Test, ReadOnClosedSocketReturnsZero) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(1);
+
+  ASSERT_FALSE(sock_->IsConnected());
+  ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback()));
+  ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback()));
+  ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback()));
+  ASSERT_FALSE(sock_->IsConnectedAndIdle());
+}
+
+// Read pending when socket is closed should return 0
+TEST_F(SpdyProxyClientSocketSpdy2Test, PendingReadOnCloseReturnsZero) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  AssertReadStarts(kMsg1, kLen1);
+
+  Run(1);
+
+  ASSERT_EQ(0, read_callback_.WaitForResult());
+}
+
+// Reading from a disconnected socket is an error
+TEST_F(SpdyProxyClientSocketSpdy2Test,
+       ReadOnDisconnectSocketReturnsNotConnected) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  sock_->Disconnect();
+
+  ASSERT_EQ(ERR_SOCKET_NOT_CONNECTED,
+            sock_->Read(NULL, 1, CompletionCallback()));
+}
+
+// Reading buffered data from an already closed socket should return
+// buffered data, then 0.
+TEST_F(SpdyProxyClientSocketSpdy2Test, ReadOnClosedSocketReturnsBufferedData) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg1, 2, ASYNC),
+    MockRead(ASYNC, 0, 3),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(2);
+
+  ASSERT_FALSE(sock_->IsConnected());
+  scoped_refptr<IOBuffer> buf(new IOBuffer(kLen1));
+  ASSERT_EQ(kLen1, sock_->Read(buf, kLen1, CompletionCallback()));
+  ASSERT_EQ(std::string(kMsg1, kLen1), std::string(buf->data(), kLen1));
+
+  ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback()));
+  ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback()));
+  sock_->Disconnect();
+  ASSERT_EQ(ERR_SOCKET_NOT_CONNECTED,
+            sock_->Read(NULL, 1, CompletionCallback()));
+}
+
+// Calling Write() on a closed socket is an error
+TEST_F(SpdyProxyClientSocketSpdy2Test, WriteOnClosedStream) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(1);  // Read EOF which will close the stream
+  scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1));
+  EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED,
+            sock_->Write(buf, buf->size(), CompletionCallback()));
+}
+
+// Calling Write() on a disconnected socket is an error
+TEST_F(SpdyProxyClientSocketSpdy2Test, WriteOnDisconnectedSocket) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  sock_->Disconnect();
+
+  scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1));
+  EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED,
+            sock_->Write(buf, buf->size(), CompletionCallback()));
+}
+
+// If the socket is closed with a pending Write(), the callback
+// should be called with ERR_CONNECTION_CLOSED.
+TEST_F(SpdyProxyClientSocketSpdy2Test, WritePendingOnClose) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+    MockWrite(ASYNC, ERR_IO_PENDING, 2),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 3),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  EXPECT_TRUE(sock_->IsConnected());
+
+  scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1));
+  EXPECT_EQ(ERR_IO_PENDING,
+            sock_->Write(buf, buf->size(), write_callback_.callback()));
+
+  Run(1);
+
+  EXPECT_EQ(ERR_CONNECTION_CLOSED, write_callback_.WaitForResult());
+}
+
+// If the socket is Disconnected with a pending Write(), the callback
+// should not be called.
+TEST_F(SpdyProxyClientSocketSpdy2Test, DisconnectWithWritePending) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+    MockWrite(SYNCHRONOUS, 0, 2),  // EOF
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 3),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  EXPECT_TRUE(sock_->IsConnected());
+
+  scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1));
+  EXPECT_EQ(ERR_IO_PENDING,
+            sock_->Write(buf, buf->size(), write_callback_.callback()));
+
+  sock_->Disconnect();
+
+  EXPECT_FALSE(sock_->IsConnected());
+  EXPECT_FALSE(write_callback_.have_result());
+}
+
+// If the socket is Disconnected with a pending Read(), the callback
+// should not be called.
+TEST_F(SpdyProxyClientSocketSpdy2Test, DisconnectWithReadPending) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  EXPECT_TRUE(sock_->IsConnected());
+
+  scoped_refptr<IOBuffer> buf(new IOBuffer(kLen1));
+  ASSERT_EQ(ERR_IO_PENDING,
+            sock_->Read(buf, kLen1, read_callback_.callback()));
+
+  sock_->Disconnect();
+
+  EXPECT_FALSE(sock_->IsConnected());
+  EXPECT_FALSE(read_callback_.have_result());
+}
+
+// If the socket is Reset when both a read and write are pending,
+// both should be called back.
+TEST_F(SpdyProxyClientSocketSpdy2Test, RstWithReadAndWritePending) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+    MockWrite(ASYNC, ERR_IO_PENDING, 2),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(1, CANCEL));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*rst, 3, ASYNC),
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  EXPECT_TRUE(sock_->IsConnected());
+
+  scoped_refptr<IOBuffer> read_buf(new IOBuffer(kLen1));
+  ASSERT_EQ(ERR_IO_PENDING,
+            sock_->Read(read_buf, kLen1, read_callback_.callback()));
+
+  scoped_refptr<IOBufferWithSize> write_buf(CreateBuffer(kMsg1, kLen1));
+  EXPECT_EQ(ERR_IO_PENDING,
+            sock_->Write(write_buf, write_buf->size(),
+                         write_callback_.callback()));
+
+  Run(2);
+
+  EXPECT_TRUE(sock_.get());
+  EXPECT_TRUE(read_callback_.have_result());
+  EXPECT_TRUE(write_callback_.have_result());
+}
+
+// CompletionCallback that causes the SpdyProxyClientSocket to be
+// deleted when Run is invoked.
+class DeleteSockCallback : public TestCompletionCallbackBase {
+ public:
+  explicit DeleteSockCallback(scoped_ptr<SpdyProxyClientSocket>* sock)
+    : sock_(sock),
+      ALLOW_THIS_IN_INITIALIZER_LIST(callback_(
+          base::Bind(&DeleteSockCallback::OnComplete,
+                     base::Unretained(this)))) {
+  }
+
+  virtual ~DeleteSockCallback() {
+  }
+
+  const CompletionCallback& callback() const { return callback_; }
+
+ private:
+  void OnComplete(int result) {
+    sock_->reset(NULL);
+    SetResult(result);
+  }
+
+  scoped_ptr<SpdyProxyClientSocket>* sock_;
+  CompletionCallback callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(DeleteSockCallback);
+};
+
+// If the socket is Reset when both a read and write are pending, and the
+// read callback causes the socket to be deleted, the write callback should
+// not be called.
+TEST_F(SpdyProxyClientSocketSpdy2Test, RstWithReadAndWritePendingDelete) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+    MockWrite(ASYNC, ERR_IO_PENDING, 2),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(1, CANCEL));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*rst, 3, ASYNC),
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  EXPECT_TRUE(sock_->IsConnected());
+
+  DeleteSockCallback read_callback(&sock_);
+
+  scoped_refptr<IOBuffer> read_buf(new IOBuffer(kLen1));
+  ASSERT_EQ(ERR_IO_PENDING,
+            sock_->Read(read_buf, kLen1, read_callback.callback()));
+
+  scoped_refptr<IOBufferWithSize> write_buf(CreateBuffer(kMsg1, kLen1));
+  EXPECT_EQ(ERR_IO_PENDING, sock_->Write(write_buf, write_buf->size(),
+                                         write_callback_.callback()));
+
+  Run(2);
+
+  EXPECT_FALSE(sock_.get());
+  EXPECT_TRUE(read_callback.have_result());
+  EXPECT_FALSE(write_callback_.have_result());
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_proxy_client_socket_spdy3_unittest.cc b/src/net/spdy/spdy_proxy_client_socket_spdy3_unittest.cc
new file mode 100644
index 0000000..627568f
--- /dev/null
+++ b/src/net/spdy/spdy_proxy_client_socket_spdy3_unittest.cc
@@ -0,0 +1,1360 @@
+// Copyright (c) 2012 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_proxy_client_socket.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/utf_string_conversions.h"
+#include "net/base/address_list.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/mock_host_resolver.h"
+#include "net/base/test_completion_callback.h"
+#include "net/base/winsock_init.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/tcp_client_socket.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/spdy/spdy_test_util_spdy3.h"
+#include "testing/platform_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using namespace net::test_spdy3;
+
+//-----------------------------------------------------------------------------
+
+namespace {
+
+static const char kRequestUrl[] = "https://www.google.com/";
+static const char kOriginHost[] = "www.google.com";
+static const int kOriginPort = 443;
+static const char kOriginHostPort[] = "www.google.com:443";
+static const char kProxyUrl[] = "https://myproxy:6121/";
+static const char kProxyHost[] = "myproxy";
+static const int kProxyPort = 6121;
+static const char kUserAgent[] = "Mozilla/1.0";
+
+static const int kStreamId = 1;
+
+static const char kMsg1[] = "\0hello!\xff";
+static const int kLen1 = 8;
+static const char kMsg2[] = "\00012345678\0";
+static const int kLen2 = 10;
+static const char kMsg3[] = "bye!";
+static const int kLen3 = 4;
+static const char kMsg33[] = "bye!bye!";
+static const int kLen33 = kLen3 + kLen3;
+static const char kMsg333[] = "bye!bye!bye!";
+static const int kLen333 = kLen3 + kLen3 + kLen3;
+
+static const char kRedirectUrl[] = "https://example.com/";
+
+}  // anonymous namespace
+
+namespace net {
+
+class SpdyProxyClientSocketSpdy3Test : public PlatformTest {
+ public:
+  SpdyProxyClientSocketSpdy3Test();
+
+  virtual void TearDown();
+
+ protected:
+  void Initialize(MockRead* reads, size_t reads_count, MockWrite* writes,
+                  size_t writes_count);
+  SpdyFrame* ConstructConnectRequestFrame();
+  SpdyFrame* ConstructConnectAuthRequestFrame();
+  SpdyFrame* ConstructConnectReplyFrame();
+  SpdyFrame* ConstructConnectAuthReplyFrame();
+  SpdyFrame* ConstructConnectRedirectReplyFrame();
+  SpdyFrame* ConstructConnectErrorReplyFrame();
+  SpdyFrame* ConstructBodyFrame(const char* data, int length);
+  scoped_refptr<IOBufferWithSize> CreateBuffer(const char* data, int size);
+  void AssertConnectSucceeds();
+  void AssertConnectFails(int result);
+  void AssertConnectionEstablished();
+  void AssertSyncReadEquals(const char* data, int len);
+  void AssertAsyncReadEquals(const char* data, int len);
+  void AssertReadStarts(const char* data, int len);
+  void AssertReadReturns(const char* data, int len);
+  void AssertAsyncWriteSucceeds(const char* data, int len);
+  void AssertWriteReturns(const char* data, int len, int rv);
+  void AssertWriteLength(int len);
+  void AssertAsyncWriteWithReadsSucceeds(const char* data, int len,
+                                        int num_reads);
+
+  void AddAuthToCache() {
+    const string16 kFoo(ASCIIToUTF16("foo"));
+    const string16 kBar(ASCIIToUTF16("bar"));
+    session_->http_auth_cache()->Add(GURL(kProxyUrl),
+                                     "MyRealm1",
+                                     HttpAuth::AUTH_SCHEME_BASIC,
+                                     "Basic realm=MyRealm1",
+                                     AuthCredentials(kFoo, kBar),
+                                     "/");
+  }
+
+  void Run(int steps) {
+    data_->StopAfter(steps);
+    data_->Run();
+  }
+
+  scoped_ptr<SpdyProxyClientSocket> sock_;
+  TestCompletionCallback read_callback_;
+  TestCompletionCallback write_callback_;
+  scoped_ptr<DeterministicSocketData> data_;
+
+ private:
+  scoped_refptr<HttpNetworkSession> session_;
+  scoped_refptr<IOBuffer> read_buf_;
+  SpdySessionDependencies session_deps_;
+  MockConnect connect_data_;
+  scoped_refptr<SpdySession> spdy_session_;
+  scoped_refptr<SpdyStream> spdy_stream_;
+  BufferedSpdyFramer framer_;
+
+  std::string user_agent_;
+  GURL url_;
+  HostPortPair proxy_host_port_;
+  HostPortPair endpoint_host_port_pair_;
+  ProxyServer proxy_;
+  HostPortProxyPair endpoint_host_port_proxy_pair_;
+  scoped_refptr<TransportSocketParams> transport_params_;
+
+  DISALLOW_COPY_AND_ASSIGN(SpdyProxyClientSocketSpdy3Test);
+};
+
+SpdyProxyClientSocketSpdy3Test::SpdyProxyClientSocketSpdy3Test()
+    : sock_(NULL),
+      data_(NULL),
+      session_(NULL),
+      read_buf_(NULL),
+      session_deps_(),
+      connect_data_(SYNCHRONOUS, OK),
+      spdy_session_(NULL),
+      spdy_stream_(NULL),
+      framer_(3, false),
+      user_agent_(kUserAgent),
+      url_(kRequestUrl),
+      proxy_host_port_(kProxyHost, kProxyPort),
+      endpoint_host_port_pair_(kOriginHost, kOriginPort),
+      proxy_(ProxyServer::SCHEME_HTTPS, proxy_host_port_),
+      endpoint_host_port_proxy_pair_(endpoint_host_port_pair_, proxy_),
+      transport_params_(new TransportSocketParams(proxy_host_port_,
+                                                  LOWEST,
+                                                  false,
+                                                  false,
+                                                  OnHostResolutionCallback())) {
+}
+
+void SpdyProxyClientSocketSpdy3Test::TearDown() {
+  sock_.reset(NULL);
+  if (session_ != NULL)
+    session_->spdy_session_pool()->CloseAllSessions();
+
+  // Empty the current queue.
+  MessageLoop::current()->RunUntilIdle();
+  PlatformTest::TearDown();
+}
+
+void SpdyProxyClientSocketSpdy3Test::Initialize(MockRead* reads,
+                                           size_t reads_count,
+                                           MockWrite* writes,
+                                           size_t writes_count) {
+  data_.reset(new DeterministicSocketData(reads, reads_count,
+                                          writes, writes_count));
+  data_->set_connect_data(connect_data_);
+  data_->SetStop(2);
+
+  session_deps_.deterministic_socket_factory->AddSocketDataProvider(
+      data_.get());
+  session_deps_.host_resolver->set_synchronous_mode(true);
+
+  session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic(
+      &session_deps_);
+
+  // Creates a new spdy session
+  spdy_session_ =
+      session_->spdy_session_pool()->Get(endpoint_host_port_proxy_pair_,
+                                         BoundNetLog());
+
+  // Perform the TCP connect
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK,
+            connection->Init(endpoint_host_port_pair_.ToString(),
+                             transport_params_, LOWEST, CompletionCallback(),
+                             session_->GetTransportSocketPool(
+                                 HttpNetworkSession::NORMAL_SOCKET_POOL),
+                             BoundNetLog()));
+  spdy_session_->InitializeWithSocket(connection.release(), false, OK);
+
+  // Create the SPDY Stream
+  ASSERT_EQ(
+      OK,
+      spdy_session_->CreateStream(url_, LOWEST, &spdy_stream_, BoundNetLog(),
+                                  CompletionCallback()));
+
+  // Create the SpdyProxyClientSocket
+  sock_.reset(
+      new SpdyProxyClientSocket(spdy_stream_, user_agent_,
+                                endpoint_host_port_pair_, url_,
+                                proxy_host_port_, session_->http_auth_cache(),
+                                session_->http_auth_handler_factory()));
+}
+
+scoped_refptr<IOBufferWithSize> SpdyProxyClientSocketSpdy3Test::CreateBuffer(
+    const char* data, int size) {
+  scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(size));
+  memcpy(buf->data(), data, size);
+  return buf;
+}
+
+void SpdyProxyClientSocketSpdy3Test::AssertConnectSucceeds() {
+  ASSERT_EQ(ERR_IO_PENDING, sock_->Connect(read_callback_.callback()));
+  data_->Run();
+  ASSERT_EQ(OK, read_callback_.WaitForResult());
+}
+
+void SpdyProxyClientSocketSpdy3Test::AssertConnectFails(int result) {
+  ASSERT_EQ(ERR_IO_PENDING, sock_->Connect(read_callback_.callback()));
+  data_->Run();
+  ASSERT_EQ(result, read_callback_.WaitForResult());
+}
+
+void SpdyProxyClientSocketSpdy3Test::AssertConnectionEstablished() {
+  const HttpResponseInfo* response = sock_->GetConnectResponseInfo();
+  ASSERT_TRUE(response != NULL);
+  ASSERT_EQ(200, response->headers->response_code());
+  ASSERT_EQ("Connection Established", response->headers->GetStatusText());
+}
+
+void SpdyProxyClientSocketSpdy3Test::AssertSyncReadEquals(const char* data,
+                                                          int len) {
+  scoped_refptr<IOBuffer> buf(new IOBuffer(len));
+  ASSERT_EQ(len, sock_->Read(buf, len, CompletionCallback()));
+  ASSERT_EQ(std::string(data, len), std::string(buf->data(), len));
+  ASSERT_TRUE(sock_->IsConnected());
+}
+
+void SpdyProxyClientSocketSpdy3Test::AssertAsyncReadEquals(const char* data,
+                                                           int len) {
+  data_->StopAfter(1);
+  // Issue the read, which will be completed asynchronously
+  scoped_refptr<IOBuffer> buf(new IOBuffer(len));
+  ASSERT_EQ(ERR_IO_PENDING, sock_->Read(buf, len, read_callback_.callback()));
+  EXPECT_TRUE(sock_->IsConnected());
+  data_->Run();
+
+  EXPECT_TRUE(sock_->IsConnected());
+
+  // Now the read will return
+  EXPECT_EQ(len, read_callback_.WaitForResult());
+  ASSERT_EQ(std::string(data, len), std::string(buf->data(), len));
+}
+
+void SpdyProxyClientSocketSpdy3Test::AssertReadStarts(const char* data,
+                                                      int len) {
+  data_->StopAfter(1);
+  // Issue the read, which will be completed asynchronously
+  read_buf_ = new IOBuffer(len);
+  ASSERT_EQ(ERR_IO_PENDING,
+            sock_->Read(read_buf_, len, read_callback_.callback()));
+  EXPECT_TRUE(sock_->IsConnected());
+}
+
+void SpdyProxyClientSocketSpdy3Test::AssertReadReturns(const char* data,
+                                                       int len) {
+  EXPECT_TRUE(sock_->IsConnected());
+
+  // Now the read will return
+  EXPECT_EQ(len, read_callback_.WaitForResult());
+  ASSERT_EQ(std::string(data, len), std::string(read_buf_->data(), len));
+}
+
+void SpdyProxyClientSocketSpdy3Test::AssertAsyncWriteSucceeds(const char* data,
+                                                              int len) {
+  AssertWriteReturns(data, len, ERR_IO_PENDING);
+  data_->RunFor(1);
+  AssertWriteLength(len);
+}
+
+void SpdyProxyClientSocketSpdy3Test::AssertWriteReturns(const char* data,
+                                                        int len,
+                                                        int rv) {
+  scoped_refptr<IOBufferWithSize> buf(CreateBuffer(data, len));
+  EXPECT_EQ(rv, sock_->Write(buf, buf->size(), write_callback_.callback()));
+}
+
+void SpdyProxyClientSocketSpdy3Test::AssertWriteLength(int len) {
+  EXPECT_EQ(len, write_callback_.WaitForResult());
+}
+
+void SpdyProxyClientSocketSpdy3Test::AssertAsyncWriteWithReadsSucceeds(
+    const char* data, int len, int num_reads) {
+  scoped_refptr<IOBufferWithSize> buf(CreateBuffer(data, len));
+
+  EXPECT_EQ(ERR_IO_PENDING, sock_->Write(buf, buf->size(),
+                                         write_callback_.callback()));
+
+  for (int i = 0; i < num_reads; i++) {
+    Run(1);
+    AssertSyncReadEquals(kMsg2, kLen2);
+  }
+
+  write_callback_.WaitForResult();
+}
+
+// Constructs a standard SPDY SYN_STREAM frame for a CONNECT request.
+SpdyFrame*
+SpdyProxyClientSocketSpdy3Test::ConstructConnectRequestFrame() {
+  const SpdyHeaderInfo kSynStartHeader = {
+    SYN_STREAM,
+    kStreamId,
+    0,
+    net::ConvertRequestPriorityToSpdyPriority(LOWEST, 3),
+    0,
+    CONTROL_FLAG_NONE,
+    false,
+    INVALID,
+    NULL,
+    0,
+    DATA_FLAG_NONE
+  };
+  const char* const kConnectHeaders[] = {
+    ":method", "CONNECT",
+    ":path", kOriginHostPort,
+    ":host", kOriginHost,
+    "user-agent", kUserAgent,
+    ":version", "HTTP/1.1",
+  };
+  return ConstructSpdyPacket(
+      kSynStartHeader, NULL, 0, kConnectHeaders, arraysize(kConnectHeaders)/2);
+}
+
+// Constructs a SPDY SYN_STREAM frame for a CONNECT request which includes
+// Proxy-Authorization headers.
+SpdyFrame*
+SpdyProxyClientSocketSpdy3Test::ConstructConnectAuthRequestFrame() {
+  const SpdyHeaderInfo kSynStartHeader = {
+    SYN_STREAM,
+    kStreamId,
+    0,
+    net::ConvertRequestPriorityToSpdyPriority(LOWEST, 3),
+    0,
+    CONTROL_FLAG_NONE,
+    false,
+    INVALID,
+    NULL,
+    0,
+    DATA_FLAG_NONE
+  };
+  const char* const kConnectHeaders[] = {
+    ":method", "CONNECT",
+    ":path", kOriginHostPort,
+    ":host", kOriginHost,
+    "user-agent", kUserAgent,
+    ":version", "HTTP/1.1",
+    "proxy-authorization", "Basic Zm9vOmJhcg==",
+  };
+  return ConstructSpdyPacket(
+      kSynStartHeader, NULL, 0, kConnectHeaders, arraysize(kConnectHeaders)/2);
+}
+
+// Constructs a standard SPDY SYN_REPLY frame to match the SPDY CONNECT.
+SpdyFrame* SpdyProxyClientSocketSpdy3Test::ConstructConnectReplyFrame() {
+  const char* const kStandardReplyHeaders[] = {
+      ":status", "200 Connection Established",
+      ":version", "HTTP/1.1"
+  };
+  return ConstructSpdyControlFrame(NULL,
+                                   0,
+                                   false,
+                                   kStreamId,
+                                   LOWEST,
+                                   SYN_REPLY,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardReplyHeaders,
+                                   arraysize(kStandardReplyHeaders));
+}
+
+// Constructs a standard SPDY SYN_REPLY frame to match the SPDY CONNECT.
+SpdyFrame*
+SpdyProxyClientSocketSpdy3Test::ConstructConnectAuthReplyFrame() {
+  const char* const kStandardReplyHeaders[] = {
+      ":status", "407 Proxy Authentication Required",
+      ":version", "HTTP/1.1",
+      "proxy-authenticate", "Basic realm=\"MyRealm1\"",
+  };
+
+  return ConstructSpdyControlFrame(NULL,
+                                   0,
+                                   false,
+                                   kStreamId,
+                                   LOWEST,
+                                   SYN_REPLY,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardReplyHeaders,
+                                   arraysize(kStandardReplyHeaders));
+}
+
+// Constructs a SPDY SYN_REPLY frame with an HTTP 302 redirect.
+SpdyFrame*
+SpdyProxyClientSocketSpdy3Test::ConstructConnectRedirectReplyFrame() {
+  const char* const kStandardReplyHeaders[] = {
+      ":status", "302 Found",
+      ":version", "HTTP/1.1",
+      "location", kRedirectUrl,
+      "set-cookie", "foo=bar"
+  };
+
+  return ConstructSpdyControlFrame(NULL,
+                                   0,
+                                   false,
+                                   kStreamId,
+                                   LOWEST,
+                                   SYN_REPLY,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardReplyHeaders,
+                                   arraysize(kStandardReplyHeaders));
+}
+
+// Constructs a SPDY SYN_REPLY frame with an HTTP 500 error.
+SpdyFrame*
+SpdyProxyClientSocketSpdy3Test::ConstructConnectErrorReplyFrame() {
+  const char* const kStandardReplyHeaders[] = {
+      ":status", "500 Internal Server Error",
+      ":version", "HTTP/1.1",
+  };
+
+  return ConstructSpdyControlFrame(NULL,
+                                   0,
+                                   false,
+                                   kStreamId,
+                                   LOWEST,
+                                   SYN_REPLY,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardReplyHeaders,
+                                   arraysize(kStandardReplyHeaders));
+}
+
+SpdyFrame* SpdyProxyClientSocketSpdy3Test::ConstructBodyFrame(
+    const char* data,
+    int length) {
+  return framer_.CreateDataFrame(kStreamId, data, length, DATA_FLAG_NONE);
+}
+
+// ----------- Connect
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, ConnectSendsCorrectRequest) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  ASSERT_FALSE(sock_->IsConnected());
+
+  AssertConnectSucceeds();
+
+  AssertConnectionEstablished();
+}
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, ConnectWithAuthRequested) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectAuthReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectFails(ERR_PROXY_AUTH_REQUESTED);
+
+  const HttpResponseInfo* response = sock_->GetConnectResponseInfo();
+  ASSERT_TRUE(response != NULL);
+  ASSERT_EQ(407, response->headers->response_code());
+  ASSERT_EQ("Proxy Authentication Required",
+            response->headers->GetStatusText());
+}
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, ConnectWithAuthCredentials) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectAuthRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+  AddAuthToCache();
+
+  AssertConnectSucceeds();
+
+  AssertConnectionEstablished();
+}
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, ConnectRedirects) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectRedirectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectFails(ERR_HTTPS_PROXY_TUNNEL_RESPONSE);
+
+  const HttpResponseInfo* response = sock_->GetConnectResponseInfo();
+  ASSERT_TRUE(response != NULL);
+
+  const HttpResponseHeaders* headers = response->headers;
+  ASSERT_EQ(302, headers->response_code());
+  ASSERT_FALSE(headers->HasHeader("set-cookie"));
+  ASSERT_TRUE(headers->HasHeaderValue("content-length", "0"));
+
+  std::string location;
+  ASSERT_TRUE(headers->IsRedirect(&location));
+  ASSERT_EQ(location, kRedirectUrl);
+}
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, ConnectFails) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    MockRead(ASYNC, 0, 1),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  ASSERT_FALSE(sock_->IsConnected());
+
+  AssertConnectFails(ERR_CONNECTION_CLOSED);
+
+  ASSERT_FALSE(sock_->IsConnected());
+}
+
+// ----------- WasEverUsed
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, WasEverUsedReturnsCorrectValues) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  EXPECT_FALSE(sock_->WasEverUsed());
+  AssertConnectSucceeds();
+  EXPECT_TRUE(sock_->WasEverUsed());
+  sock_->Disconnect();
+  EXPECT_TRUE(sock_->WasEverUsed());
+}
+
+// ----------- GetPeerAddress
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, GetPeerAddressReturnsCorrectValues) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  net::IPEndPoint addr;
+  EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, sock_->GetPeerAddress(&addr));
+
+  AssertConnectSucceeds();
+  EXPECT_TRUE(sock_->IsConnected());
+  EXPECT_EQ(OK, sock_->GetPeerAddress(&addr));
+
+  Run(1);
+
+  EXPECT_FALSE(sock_->IsConnected());
+  EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, sock_->GetPeerAddress(&addr));
+
+  sock_->Disconnect();
+
+  EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED, sock_->GetPeerAddress(&addr));
+}
+
+// ----------- Write
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, WriteSendsDataInDataFrame) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+    CreateMockWrite(*msg1, 2, SYNCHRONOUS),
+    CreateMockWrite(*msg2, 3, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 4),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  AssertAsyncWriteSucceeds(kMsg1, kLen1);
+  AssertAsyncWriteSucceeds(kMsg2, kLen2);
+}
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, WriteSplitsLargeDataIntoMultipleFrames) {
+  std::string chunk_data(kMaxSpdyFrameChunkSize, 'x');
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  scoped_ptr<SpdyFrame> chunk(ConstructBodyFrame(chunk_data.data(),
+                                                       chunk_data.length()));
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+    CreateMockWrite(*chunk, 2, SYNCHRONOUS),
+    CreateMockWrite(*chunk, 3, SYNCHRONOUS),
+    CreateMockWrite(*chunk, 4, SYNCHRONOUS)
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 5),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  std::string big_data(kMaxSpdyFrameChunkSize * 3, 'x');
+  scoped_refptr<IOBufferWithSize> buf(CreateBuffer(big_data.data(),
+                                                   big_data.length()));
+
+  EXPECT_EQ(ERR_IO_PENDING, sock_->Write(buf, buf->size(),
+                                         write_callback_.callback()));
+  data_->RunFor(3);
+
+  EXPECT_EQ(buf->size(), write_callback_.WaitForResult());
+}
+
+// ----------- Read
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, ReadReadsDataInDataFrame) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg1, 2, ASYNC),
+    MockRead(ASYNC, 0, 3),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(1);  // SpdySession consumes the next read and sends it to
+           // sock_ to be buffered.
+  AssertSyncReadEquals(kMsg1, kLen1);
+}
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, ReadDataFromBufferedFrames) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg1, 2, ASYNC),
+    CreateMockRead(*msg2, 3, ASYNC),
+    MockRead(ASYNC, 0, 4),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(1);  // SpdySession consumes the next read and sends it to
+           // sock_ to be buffered.
+  AssertSyncReadEquals(kMsg1, kLen1);
+  Run(1);  // SpdySession consumes the next read and sends it to
+           // sock_ to be buffered.
+  AssertSyncReadEquals(kMsg2, kLen2);
+}
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, ReadDataMultipleBufferedFrames) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg1, 2, ASYNC),
+    CreateMockRead(*msg2, 3, ASYNC),
+    MockRead(ASYNC, 0, 4),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(2);  // SpdySession consumes the next two reads and sends then to
+           // sock_ to be buffered.
+  AssertSyncReadEquals(kMsg1, kLen1);
+  AssertSyncReadEquals(kMsg2, kLen2);
+}
+
+TEST_F(SpdyProxyClientSocketSpdy3Test,
+       LargeReadWillMergeDataFromDifferentFrames) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  scoped_ptr<SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg3, 2, ASYNC),
+    CreateMockRead(*msg3, 3, ASYNC),
+    MockRead(ASYNC, 0, 4),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(2);  // SpdySession consumes the next two reads and sends then to
+           // sock_ to be buffered.
+  // The payload from two data frames, each with kMsg3 will be combined
+  // together into a single read().
+  AssertSyncReadEquals(kMsg33, kLen33);
+}
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, MultipleShortReadsThenMoreRead) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  scoped_ptr<SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3));
+  scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg1, 2, ASYNC),
+    CreateMockRead(*msg3, 3, ASYNC),
+    CreateMockRead(*msg3, 4, ASYNC),
+    CreateMockRead(*msg2, 5, ASYNC),
+    MockRead(ASYNC, 0, 6),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(4);  // SpdySession consumes the next four reads and sends then to
+           // sock_ to be buffered.
+  AssertSyncReadEquals(kMsg1, kLen1);
+  // The payload from two data frames, each with kMsg3 will be combined
+  // together into a single read().
+  AssertSyncReadEquals(kMsg33, kLen33);
+  AssertSyncReadEquals(kMsg2, kLen2);
+}
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, ReadWillSplitDataFromLargeFrame) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  scoped_ptr<SpdyFrame> msg33(ConstructBodyFrame(kMsg33, kLen33));
+  scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg1, 2, ASYNC),
+    CreateMockRead(*msg33, 3, ASYNC),
+    MockRead(ASYNC, 0, 4),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(2);  // SpdySession consumes the next two reads and sends then to
+           // sock_ to be buffered.
+  AssertSyncReadEquals(kMsg1, kLen1);
+  // The payload from the single large data frame will be read across
+  // two different reads.
+  AssertSyncReadEquals(kMsg3, kLen3);
+  AssertSyncReadEquals(kMsg3, kLen3);
+}
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, MultipleReadsFromSameLargeFrame) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg333(ConstructBodyFrame(kMsg333, kLen333));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg333, 2, ASYNC),
+    MockRead(ASYNC, 0, 3),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(1);  // SpdySession consumes the next read and sends it to
+           // sock_ to be buffered.
+  // The payload from the single large data frame will be read across
+  // two different reads.
+  AssertSyncReadEquals(kMsg33, kLen33);
+
+  // Now attempt to do a read of more data than remains buffered
+  scoped_refptr<IOBuffer> buf(new IOBuffer(kLen33));
+  ASSERT_EQ(kLen3, sock_->Read(buf, kLen33, read_callback_.callback()));
+  ASSERT_EQ(std::string(kMsg3, kLen3), std::string(buf->data(), kLen3));
+  ASSERT_TRUE(sock_->IsConnected());
+}
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, ReadAuthResponseBody) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectAuthReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg1, 2, ASYNC),
+    CreateMockRead(*msg2, 3, ASYNC),
+    MockRead(ASYNC, 0, 4),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectFails(ERR_PROXY_AUTH_REQUESTED);
+
+  Run(2);  // SpdySession consumes the next two reads and sends then to
+           // sock_ to be buffered.
+  AssertSyncReadEquals(kMsg1, kLen1);
+  AssertSyncReadEquals(kMsg2, kLen2);
+}
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, ReadErrorResponseBody) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectErrorReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg1, 2, ASYNC),
+    CreateMockRead(*msg2, 3, ASYNC),
+    MockRead(ASYNC, 0, 4),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectFails(ERR_TUNNEL_CONNECTION_FAILED);
+}
+
+// ----------- Reads and Writes
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, AsyncReadAroundWrite) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+    CreateMockWrite(*msg2, 3, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  scoped_ptr<SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg1, 2, ASYNC),  // sync read
+    CreateMockRead(*msg3, 4, ASYNC),  // async read
+    MockRead(ASYNC, 0, 5),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(1);
+  AssertSyncReadEquals(kMsg1, kLen1);
+
+  AssertReadStarts(kMsg3, kLen3);
+  // Read should block until after the write succeeds
+
+  AssertAsyncWriteSucceeds(kMsg2, kLen2);  // Runs 1 step
+
+  ASSERT_FALSE(read_callback_.have_result());
+  Run(1);
+  // Now the read will return
+  AssertReadReturns(kMsg3, kLen3);
+}
+
+TEST_F(SpdyProxyClientSocketSpdy3Test, AsyncWriteAroundReads) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  scoped_ptr<SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2));
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+    CreateMockWrite(*msg2, 4, ASYNC),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  scoped_ptr<SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg1, 2, ASYNC),
+    CreateMockRead(*msg3, 3, ASYNC),
+    MockRead(ASYNC, 0, 5),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(1);
+  AssertSyncReadEquals(kMsg1, kLen1);
+  // Write should block until the read completes
+  AssertWriteReturns(kMsg2, kLen2, ERR_IO_PENDING);
+
+  AssertAsyncReadEquals(kMsg3, kLen3);
+
+  ASSERT_FALSE(write_callback_.have_result());
+
+  // Now the write will complete
+  Run(1);
+  AssertWriteLength(kLen2);
+}
+
+// ----------- Reading/Writing on Closed socket
+
+// Reading from an already closed socket should return 0
+TEST_F(SpdyProxyClientSocketSpdy3Test, ReadOnClosedSocketReturnsZero) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(1);
+
+  ASSERT_FALSE(sock_->IsConnected());
+  ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback()));
+  ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback()));
+  ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback()));
+  ASSERT_FALSE(sock_->IsConnectedAndIdle());
+}
+
+// Read pending when socket is closed should return 0
+TEST_F(SpdyProxyClientSocketSpdy3Test, PendingReadOnCloseReturnsZero) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  AssertReadStarts(kMsg1, kLen1);
+
+  Run(1);
+
+  ASSERT_EQ(0, read_callback_.WaitForResult());
+}
+
+// Reading from a disconnected socket is an error
+TEST_F(SpdyProxyClientSocketSpdy3Test,
+       ReadOnDisconnectSocketReturnsNotConnected) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  sock_->Disconnect();
+
+  ASSERT_EQ(ERR_SOCKET_NOT_CONNECTED,
+            sock_->Read(NULL, 1, CompletionCallback()));
+}
+
+// Reading buffered data from an already closed socket should return
+// buffered data, then 0.
+TEST_F(SpdyProxyClientSocketSpdy3Test, ReadOnClosedSocketReturnsBufferedData) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*msg1, 2, ASYNC),
+    MockRead(ASYNC, 0, 3),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(2);
+
+  ASSERT_FALSE(sock_->IsConnected());
+  scoped_refptr<IOBuffer> buf(new IOBuffer(kLen1));
+  ASSERT_EQ(kLen1, sock_->Read(buf, kLen1, CompletionCallback()));
+  ASSERT_EQ(std::string(kMsg1, kLen1), std::string(buf->data(), kLen1));
+
+  ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback()));
+  ASSERT_EQ(0, sock_->Read(NULL, 1, CompletionCallback()));
+  sock_->Disconnect();
+  ASSERT_EQ(ERR_SOCKET_NOT_CONNECTED,
+            sock_->Read(NULL, 1, CompletionCallback()));
+}
+
+// Calling Write() on a closed socket is an error
+TEST_F(SpdyProxyClientSocketSpdy3Test, WriteOnClosedStream) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  Run(1);  // Read EOF which will close the stream
+  scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1));
+  EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED,
+            sock_->Write(buf, buf->size(), CompletionCallback()));
+}
+
+// Calling Write() on a disconnected socket is an error
+TEST_F(SpdyProxyClientSocketSpdy3Test, WriteOnDisconnectedSocket) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  sock_->Disconnect();
+
+  scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1));
+  EXPECT_EQ(ERR_SOCKET_NOT_CONNECTED,
+            sock_->Write(buf, buf->size(), CompletionCallback()));
+}
+
+// If the socket is closed with a pending Write(), the callback
+// should be called with ERR_CONNECTION_CLOSED.
+TEST_F(SpdyProxyClientSocketSpdy3Test, WritePendingOnClose) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+    MockWrite(ASYNC, ERR_IO_PENDING, 2),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 3),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  EXPECT_TRUE(sock_->IsConnected());
+
+  scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1));
+  EXPECT_EQ(ERR_IO_PENDING,
+            sock_->Write(buf, buf->size(), write_callback_.callback()));
+
+  Run(1);
+
+  EXPECT_EQ(ERR_CONNECTION_CLOSED, write_callback_.WaitForResult());
+}
+
+// If the socket is Disconnected with a pending Write(), the callback
+// should not be called.
+TEST_F(SpdyProxyClientSocketSpdy3Test, DisconnectWithWritePending) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+    MockWrite(SYNCHRONOUS, 0, 2),  // EOF
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 3),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  EXPECT_TRUE(sock_->IsConnected());
+
+  scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1));
+  EXPECT_EQ(ERR_IO_PENDING,
+            sock_->Write(buf, buf->size(), write_callback_.callback()));
+
+  sock_->Disconnect();
+
+  EXPECT_FALSE(sock_->IsConnected());
+  EXPECT_FALSE(write_callback_.have_result());
+}
+
+// If the socket is Disconnected with a pending Read(), the callback
+// should not be called.
+TEST_F(SpdyProxyClientSocketSpdy3Test, DisconnectWithReadPending) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    MockRead(ASYNC, 0, 2),  // EOF
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  EXPECT_TRUE(sock_->IsConnected());
+
+  scoped_refptr<IOBuffer> buf(new IOBuffer(kLen1));
+  ASSERT_EQ(ERR_IO_PENDING,
+            sock_->Read(buf, kLen1, read_callback_.callback()));
+
+  sock_->Disconnect();
+
+  EXPECT_FALSE(sock_->IsConnected());
+  EXPECT_FALSE(read_callback_.have_result());
+}
+
+// If the socket is Reset when both a read and write are pending,
+// both should be called back.
+TEST_F(SpdyProxyClientSocketSpdy3Test, RstWithReadAndWritePending) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+    MockWrite(ASYNC, ERR_IO_PENDING, 2),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(1, CANCEL));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*rst, 3, ASYNC),
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  EXPECT_TRUE(sock_->IsConnected());
+
+  scoped_refptr<IOBuffer> read_buf(new IOBuffer(kLen1));
+  ASSERT_EQ(ERR_IO_PENDING,
+            sock_->Read(read_buf, kLen1, read_callback_.callback()));
+
+  scoped_refptr<IOBufferWithSize> write_buf(CreateBuffer(kMsg1, kLen1));
+  EXPECT_EQ(ERR_IO_PENDING,
+            sock_->Write(write_buf, write_buf->size(),
+                         write_callback_.callback()));
+
+  Run(2);
+
+  EXPECT_TRUE(sock_.get());
+  EXPECT_TRUE(read_callback_.have_result());
+  EXPECT_TRUE(write_callback_.have_result());
+}
+
+// CompletionCallback that causes the SpdyProxyClientSocket to be
+// deleted when Run is invoked.
+class DeleteSockCallback : public TestCompletionCallbackBase {
+ public:
+  explicit DeleteSockCallback(scoped_ptr<SpdyProxyClientSocket>* sock)
+    : sock_(sock),
+      ALLOW_THIS_IN_INITIALIZER_LIST(callback_(
+          base::Bind(&DeleteSockCallback::OnComplete,
+                     base::Unretained(this)))) {
+  }
+
+  virtual ~DeleteSockCallback() {
+  }
+
+  const CompletionCallback& callback() const { return callback_; }
+
+ private:
+  void OnComplete(int result) {
+    sock_->reset(NULL);
+    SetResult(result);
+  }
+
+  scoped_ptr<SpdyProxyClientSocket>* sock_;
+  CompletionCallback callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(DeleteSockCallback);
+};
+
+// If the socket is Reset when both a read and write are pending, and the
+// read callback causes the socket to be deleted, the write callback should
+// not be called.
+TEST_F(SpdyProxyClientSocketSpdy3Test, RstWithReadAndWritePendingDelete) {
+  scoped_ptr<SpdyFrame> conn(ConstructConnectRequestFrame());
+  MockWrite writes[] = {
+    CreateMockWrite(*conn, 0, SYNCHRONOUS),
+    MockWrite(ASYNC, ERR_IO_PENDING, 2),
+  };
+
+  scoped_ptr<SpdyFrame> resp(ConstructConnectReplyFrame());
+  scoped_ptr<SpdyFrame> rst(ConstructSpdyRstStream(1, CANCEL));
+  MockRead reads[] = {
+    CreateMockRead(*resp, 1, ASYNC),
+    CreateMockRead(*rst, 3, ASYNC),
+  };
+
+  Initialize(reads, arraysize(reads), writes, arraysize(writes));
+
+  AssertConnectSucceeds();
+
+  EXPECT_TRUE(sock_->IsConnected());
+
+  DeleteSockCallback read_callback(&sock_);
+
+  scoped_refptr<IOBuffer> read_buf(new IOBuffer(kLen1));
+  ASSERT_EQ(ERR_IO_PENDING,
+            sock_->Read(read_buf, kLen1, read_callback.callback()));
+
+  scoped_refptr<IOBufferWithSize> write_buf(CreateBuffer(kMsg1, kLen1));
+  EXPECT_EQ(ERR_IO_PENDING, sock_->Write(write_buf, write_buf->size(),
+                                         write_callback_.callback()));
+
+  Run(2);
+
+  EXPECT_FALSE(sock_.get());
+  EXPECT_TRUE(read_callback.have_result());
+  EXPECT_FALSE(write_callback_.have_result());
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_session.cc b/src/net/spdy/spdy_session.cc
new file mode 100644
index 0000000..9982a59
--- /dev/null
+++ b/src/net/spdy/spdy_session.cc
@@ -0,0 +1,1976 @@
+// Copyright (c) 2012 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_session.h"
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/stats_counters.h"
+#include "base/stl_util.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+#include "base/time.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "crypto/ec_private_key.h"
+#include "crypto/ec_signature_creator.h"
+#include "net/base/asn1_util.h"
+#include "net/base/connection_type_histograms.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/base/server_bound_cert_service.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_server_properties.h"
+#include "net/spdy/spdy_credential_builder.h"
+#include "net/spdy/spdy_frame_builder.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/spdy/spdy_stream.h"
+
+namespace net {
+
+namespace {
+
+const int kReadBufferSize = 8 * 1024;
+const int kDefaultConnectionAtRiskOfLossSeconds = 10;
+const int kHungIntervalSeconds = 10;
+
+// Minimum seconds that unclaimed pushed streams will be kept in memory.
+const int kMinPushedStreamLifetimeSeconds = 300;
+
+Value* NetLogSpdySynCallback(const SpdyHeaderBlock* headers,
+                             bool fin,
+                             bool unidirectional,
+                             SpdyStreamId stream_id,
+                             SpdyStreamId associated_stream,
+                             NetLog::LogLevel /* log_level */) {
+  DictionaryValue* dict = new DictionaryValue();
+  ListValue* headers_list = new ListValue();
+  for (SpdyHeaderBlock::const_iterator it = headers->begin();
+       it != headers->end(); ++it) {
+    headers_list->Append(new StringValue(base::StringPrintf(
+        "%s: %s", it->first.c_str(), it->second.c_str())));
+  }
+  dict->SetBoolean("fin", fin);
+  dict->SetBoolean("unidirectional", unidirectional);
+  dict->Set("headers", headers_list);
+  dict->SetInteger("stream_id", stream_id);
+  if (associated_stream)
+    dict->SetInteger("associated_stream", associated_stream);
+  return dict;
+}
+
+Value* NetLogSpdyCredentialCallback(size_t slot,
+                                    const std::string* origin,
+                                    NetLog::LogLevel /* log_level */) {
+  DictionaryValue* dict = new DictionaryValue();
+  dict->SetInteger("slot", slot);
+  dict->SetString("origin", *origin);
+  return dict;
+}
+
+Value* NetLogSpdySessionCloseCallback(int net_error,
+                                      const std::string* description,
+                                      NetLog::LogLevel /* log_level */) {
+  DictionaryValue* dict = new DictionaryValue();
+  dict->SetInteger("net_error", net_error);
+  dict->SetString("description", *description);
+  return dict;
+}
+
+Value* NetLogSpdySessionCallback(const HostPortProxyPair* host_pair,
+                                 NetLog::LogLevel /* log_level */) {
+  DictionaryValue* dict = new DictionaryValue();
+  dict->SetString("host", host_pair->first.ToString());
+  dict->SetString("proxy", host_pair->second.ToPacString());
+  return dict;
+}
+
+Value* NetLogSpdySettingCallback(SpdySettingsIds id,
+                                 SpdySettingsFlags flags,
+                                 uint32 value,
+                                 NetLog::LogLevel /* log_level */) {
+  DictionaryValue* dict = new DictionaryValue();
+  dict->SetInteger("id", id);
+  dict->SetInteger("flags", flags);
+  dict->SetInteger("value", value);
+  return dict;
+}
+
+Value* NetLogSpdySettingsCallback(const SettingsMap* settings,
+                                  NetLog::LogLevel /* log_level */) {
+  DictionaryValue* dict = new DictionaryValue();
+  ListValue* settings_list = new ListValue();
+  for (SettingsMap::const_iterator it = settings->begin();
+       it != settings->end(); ++it) {
+    const SpdySettingsIds id = it->first;
+    const SpdySettingsFlags flags = it->second.first;
+    const uint32 value = it->second.second;
+    settings_list->Append(new StringValue(
+        base::StringPrintf("[id:%u flags:%u value:%u]", id, flags, value)));
+  }
+  dict->Set("settings", settings_list);
+  return dict;
+}
+
+Value* NetLogSpdyWindowUpdateCallback(SpdyStreamId stream_id,
+                                      int32 delta,
+                                      NetLog::LogLevel /* log_level */) {
+  DictionaryValue* dict = new DictionaryValue();
+  dict->SetInteger("stream_id", static_cast<int>(stream_id));
+  dict->SetInteger("delta", delta);
+  return dict;
+}
+
+Value* NetLogSpdyDataCallback(SpdyStreamId stream_id,
+                              int size,
+                              SpdyDataFlags flags,
+                              NetLog::LogLevel /* log_level */) {
+  DictionaryValue* dict = new DictionaryValue();
+  dict->SetInteger("stream_id", static_cast<int>(stream_id));
+  dict->SetInteger("size", size);
+  dict->SetInteger("flags", static_cast<int>(flags));
+  return dict;
+}
+
+Value* NetLogSpdyRstCallback(SpdyStreamId stream_id,
+                             int status,
+                             const std::string* description,
+                             NetLog::LogLevel /* log_level */) {
+  DictionaryValue* dict = new DictionaryValue();
+  dict->SetInteger("stream_id", static_cast<int>(stream_id));
+  dict->SetInteger("status", status);
+  dict->SetString("description", *description);
+  return dict;
+}
+
+Value* NetLogSpdyPingCallback(uint32 unique_id,
+                              const char* type,
+                              NetLog::LogLevel /* log_level */) {
+  DictionaryValue* dict = new DictionaryValue();
+  dict->SetInteger("unique_id", unique_id);
+  dict->SetString("type", type);
+  return dict;
+}
+
+Value* NetLogSpdyGoAwayCallback(SpdyStreamId last_stream_id,
+                                int active_streams,
+                                int unclaimed_streams,
+                                SpdyGoAwayStatus status,
+                                NetLog::LogLevel /* log_level */) {
+  DictionaryValue* dict = new DictionaryValue();
+  dict->SetInteger("last_accepted_stream_id",
+                   static_cast<int>(last_stream_id));
+  dict->SetInteger("active_streams", active_streams);
+  dict->SetInteger("unclaimed_streams", unclaimed_streams);
+  dict->SetInteger("status", static_cast<int>(status));
+  return dict;
+}
+
+// Maximum number of concurrent streams we will create, unless the server
+// sends a SETTINGS frame with a different value.
+const size_t kInitialMaxConcurrentStreams = 100;
+// The maximum number of concurrent streams we will ever create.  Even if
+// the server permits more, we will never exceed this limit.
+const size_t kMaxConcurrentStreamLimit = 256;
+const size_t kDefaultInitialRecvWindowSize = 10 * 1024 * 1024;  // 10MB
+
+}  // namespace
+
+// static
+void SpdySession::SpdyIOBufferProducer::ActivateStream(
+    SpdySession* spdy_session,
+    SpdyStream* spdy_stream) {
+  spdy_session->ActivateStream(spdy_stream);
+}
+
+// static
+SpdyIOBuffer* SpdySession::SpdyIOBufferProducer::CreateIOBuffer(
+    SpdyFrame* frame,
+    RequestPriority priority,
+    SpdyStream* stream) {
+  size_t size = frame->length() + SpdyFrame::kHeaderSize;
+  DCHECK_GT(size, 0u);
+
+  // TODO(mbelshe): We have too much copying of data here.
+  IOBufferWithSize* buffer = new IOBufferWithSize(size);
+  memcpy(buffer->data(), frame->data(), size);
+
+  return new SpdyIOBuffer(buffer, size, priority, stream);
+}
+
+SpdySession::SpdySession(const HostPortProxyPair& host_port_proxy_pair,
+                         SpdySessionPool* spdy_session_pool,
+                         HttpServerProperties* http_server_properties,
+                         bool verify_domain_authentication,
+                         bool enable_sending_initial_settings,
+                         bool enable_credential_frames,
+                         bool enable_compression,
+                         bool enable_ping_based_connection_checking,
+                         NextProto default_protocol,
+                         size_t initial_recv_window_size,
+                         size_t initial_max_concurrent_streams,
+                         size_t max_concurrent_streams_limit,
+                         TimeFunc time_func,
+                         const HostPortPair& trusted_spdy_proxy,
+                         NetLog* net_log)
+    : ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
+      host_port_proxy_pair_(host_port_proxy_pair),
+      spdy_session_pool_(spdy_session_pool),
+      http_server_properties_(http_server_properties),
+      connection_(new ClientSocketHandle),
+      read_buffer_(new IOBuffer(kReadBufferSize)),
+      read_pending_(false),
+      stream_hi_water_mark_(1),  // Always start at 1 for the first stream id.
+      write_pending_(false),
+      delayed_write_pending_(false),
+      is_secure_(false),
+      certificate_error_code_(OK),
+      error_(OK),
+      state_(IDLE),
+      max_concurrent_streams_(initial_max_concurrent_streams == 0 ?
+                              kInitialMaxConcurrentStreams :
+                              initial_max_concurrent_streams),
+      max_concurrent_streams_limit_(max_concurrent_streams_limit == 0 ?
+                                    kMaxConcurrentStreamLimit :
+                                    max_concurrent_streams_limit),
+      streams_initiated_count_(0),
+      streams_pushed_count_(0),
+      streams_pushed_and_claimed_count_(0),
+      streams_abandoned_count_(0),
+      bytes_received_(0),
+      sent_settings_(false),
+      received_settings_(false),
+      stalled_streams_(0),
+      pings_in_flight_(0),
+      next_ping_id_(1),
+      last_activity_time_(base::TimeTicks::Now()),
+      check_ping_status_pending_(false),
+      flow_control_(false),
+      initial_send_window_size_(kSpdyStreamInitialWindowSize),
+      initial_recv_window_size_(initial_recv_window_size == 0 ?
+                                kDefaultInitialRecvWindowSize :
+                                initial_recv_window_size),
+      net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_SPDY_SESSION)),
+      verify_domain_authentication_(verify_domain_authentication),
+      enable_sending_initial_settings_(enable_sending_initial_settings),
+      enable_credential_frames_(enable_credential_frames),
+      enable_compression_(enable_compression),
+      enable_ping_based_connection_checking_(
+          enable_ping_based_connection_checking),
+      default_protocol_(default_protocol),
+      credential_state_(SpdyCredentialState::kDefaultNumSlots),
+      connection_at_risk_of_loss_time_(
+          base::TimeDelta::FromSeconds(kDefaultConnectionAtRiskOfLossSeconds)),
+      hung_interval_(
+          base::TimeDelta::FromSeconds(kHungIntervalSeconds)),
+      trusted_spdy_proxy_(trusted_spdy_proxy),
+      time_func_(time_func) {
+  DCHECK(HttpStreamFactory::spdy_enabled());
+  net_log_.BeginEvent(
+      NetLog::TYPE_SPDY_SESSION,
+      base::Bind(&NetLogSpdySessionCallback, &host_port_proxy_pair_));
+  next_unclaimed_push_stream_sweep_time_ = time_func_() +
+      base::TimeDelta::FromSeconds(kMinPushedStreamLifetimeSeconds);
+  // TODO(mbelshe): consider randomization of the stream_hi_water_mark.
+}
+
+SpdySession::PendingCreateStream::PendingCreateStream(
+    const GURL& url, RequestPriority priority,
+    scoped_refptr<SpdyStream>* spdy_stream,
+    const BoundNetLog& stream_net_log,
+    const CompletionCallback& callback)
+    : url(&url),
+      priority(priority),
+      spdy_stream(spdy_stream),
+      stream_net_log(&stream_net_log),
+      callback(callback) {
+}
+
+SpdySession::PendingCreateStream::~PendingCreateStream() {}
+
+SpdySession::CallbackResultPair::CallbackResultPair(
+    const CompletionCallback& callback_in, int result_in)
+    : callback(callback_in),
+      result(result_in) {
+}
+
+SpdySession::CallbackResultPair::~CallbackResultPair() {}
+
+SpdySession::~SpdySession() {
+  if (state_ != CLOSED) {
+    state_ = CLOSED;
+
+    // Cleanup all the streams.
+    CloseAllStreams(net::ERR_ABORTED);
+  }
+
+  if (connection_->is_initialized()) {
+    // With SPDY we can't recycle sockets.
+    connection_->socket()->Disconnect();
+  }
+
+  // Streams should all be gone now.
+  DCHECK_EQ(0u, num_active_streams());
+  DCHECK_EQ(0u, num_unclaimed_pushed_streams());
+
+  DCHECK(pending_callback_map_.empty());
+
+  RecordHistograms();
+
+  net_log_.EndEvent(NetLog::TYPE_SPDY_SESSION);
+}
+
+net::Error SpdySession::InitializeWithSocket(
+    ClientSocketHandle* connection,
+    bool is_secure,
+    int certificate_error_code) {
+  base::StatsCounter spdy_sessions("spdy.sessions");
+  spdy_sessions.Increment();
+
+  state_ = CONNECTED;
+  connection_.reset(connection);
+  is_secure_ = is_secure;
+  certificate_error_code_ = certificate_error_code;
+
+  NextProto protocol = default_protocol_;
+  NextProto protocol_negotiated = connection->socket()->GetNegotiatedProtocol();
+  if (protocol_negotiated != kProtoUnknown) {
+    protocol = protocol_negotiated;
+  }
+
+  SSLClientSocket* ssl_socket = GetSSLClientSocket();
+  if (ssl_socket && ssl_socket->WasChannelIDSent()) {
+    // According to the SPDY spec, the credential associated with the TLS
+    // connection is stored in slot[1].
+    credential_state_.SetHasCredential(GURL("https://" +
+                                            host_port_pair().ToString()));
+  }
+
+  DCHECK(protocol >= kProtoSPDY2);
+  DCHECK(protocol <= kProtoSPDY3);
+  int version = (protocol == kProtoSPDY3) ? 3 : 2;
+  flow_control_ = (protocol >= kProtoSPDY3);
+
+  buffered_spdy_framer_.reset(new BufferedSpdyFramer(version,
+                                                     enable_compression_));
+  buffered_spdy_framer_->set_visitor(this);
+  SendInitialSettings();
+  UMA_HISTOGRAM_ENUMERATION("Net.SpdyVersion", protocol, kProtoMaximumVersion);
+
+  // Write out any data that we might have to send, such as the settings frame.
+  WriteSocketLater();
+  net::Error error = ReadSocket();
+  if (error == ERR_IO_PENDING)
+    return OK;
+  return error;
+}
+
+bool SpdySession::VerifyDomainAuthentication(const std::string& domain) {
+  if (!verify_domain_authentication_)
+    return true;
+
+  if (state_ != CONNECTED)
+    return false;
+
+  SSLInfo ssl_info;
+  bool was_npn_negotiated;
+  NextProto protocol_negotiated = kProtoUnknown;
+  if (!GetSSLInfo(&ssl_info, &was_npn_negotiated, &protocol_negotiated))
+    return true;   // This is not a secure session, so all domains are okay.
+
+  return !ssl_info.client_cert_sent &&
+      (enable_credential_frames_ || !ssl_info.channel_id_sent ||
+       ServerBoundCertService::GetDomainForHost(domain) ==
+       ServerBoundCertService::GetDomainForHost(
+           host_port_proxy_pair_.first.host())) &&
+      ssl_info.cert->VerifyNameMatch(domain);
+}
+
+void SpdySession::SetStreamHasWriteAvailable(SpdyStream* stream,
+                                             SpdyIOBufferProducer* producer) {
+  write_queue_.push(producer);
+  stream_producers_[producer] = stream;
+  WriteSocketLater();
+}
+
+int SpdySession::GetPushStream(
+    const GURL& url,
+    scoped_refptr<SpdyStream>* stream,
+    const BoundNetLog& stream_net_log) {
+  CHECK_NE(state_, CLOSED);
+
+  *stream = NULL;
+
+  // Don't allow access to secure push streams over an unauthenticated, but
+  // encrypted SSL socket.
+  if (is_secure_ && certificate_error_code_ != OK &&
+      (url.SchemeIs("https") || url.SchemeIs("wss"))) {
+    RecordProtocolErrorHistogram(
+        PROTOCOL_ERROR_REQUEST_FOR_SECURE_CONTENT_OVER_INSECURE_SESSION);
+    CloseSessionOnError(
+        static_cast<net::Error>(certificate_error_code_),
+        true,
+        "Tried to get SPDY stream for secure content over an unauthenticated "
+        "session.");
+    return ERR_SPDY_PROTOCOL_ERROR;
+  }
+
+  *stream = GetActivePushStream(url.spec());
+  if (stream->get()) {
+    DCHECK(streams_pushed_and_claimed_count_ < streams_pushed_count_);
+    streams_pushed_and_claimed_count_++;
+    return OK;
+  }
+  return 0;
+}
+
+int SpdySession::CreateStream(
+    const GURL& url,
+    RequestPriority priority,
+    scoped_refptr<SpdyStream>* spdy_stream,
+    const BoundNetLog& stream_net_log,
+    const CompletionCallback& callback) {
+  if (!max_concurrent_streams_ ||
+      (active_streams_.size() + created_streams_.size() <
+       max_concurrent_streams_)) {
+    return CreateStreamImpl(url, priority, spdy_stream, stream_net_log);
+  }
+
+  stalled_streams_++;
+  net_log().AddEvent(NetLog::TYPE_SPDY_SESSION_STALLED_MAX_STREAMS);
+  pending_create_stream_queues_[priority].push(
+      PendingCreateStream(url, priority, spdy_stream,
+                          stream_net_log, callback));
+  return ERR_IO_PENDING;
+}
+
+void SpdySession::ProcessPendingCreateStreams() {
+  while (!max_concurrent_streams_ ||
+         (active_streams_.size() + created_streams_.size() <
+          max_concurrent_streams_)) {
+    bool no_pending_create_streams = true;
+    for (int i = NUM_PRIORITIES - 1; i >= MINIMUM_PRIORITY; --i) {
+      if (!pending_create_stream_queues_[i].empty()) {
+        PendingCreateStream pending_create =
+            pending_create_stream_queues_[i].front();
+        pending_create_stream_queues_[i].pop();
+        no_pending_create_streams = false;
+        int error = CreateStreamImpl(*pending_create.url,
+                                     pending_create.priority,
+                                     pending_create.spdy_stream,
+                                     *pending_create.stream_net_log);
+        scoped_refptr<SpdyStream>* stream = pending_create.spdy_stream;
+        DCHECK(!ContainsKey(pending_callback_map_, stream));
+        pending_callback_map_.insert(std::make_pair(stream,
+            CallbackResultPair(pending_create.callback, error)));
+        MessageLoop::current()->PostTask(
+            FROM_HERE,
+            base::Bind(&SpdySession::InvokeUserStreamCreationCallback,
+                       weak_factory_.GetWeakPtr(), stream));
+        break;
+      }
+    }
+    if (no_pending_create_streams)
+      return;  // there were no streams in any queue
+  }
+}
+
+void SpdySession::CancelPendingCreateStreams(
+    const scoped_refptr<SpdyStream>* spdy_stream) {
+  PendingCallbackMap::iterator it = pending_callback_map_.find(spdy_stream);
+  if (it != pending_callback_map_.end()) {
+    pending_callback_map_.erase(it);
+    return;
+  }
+
+  for (int i = 0; i < NUM_PRIORITIES; ++i) {
+    PendingCreateStreamQueue tmp;
+    // Make a copy removing this trans
+    while (!pending_create_stream_queues_[i].empty()) {
+      PendingCreateStream pending_create =
+          pending_create_stream_queues_[i].front();
+      pending_create_stream_queues_[i].pop();
+      if (pending_create.spdy_stream != spdy_stream)
+        tmp.push(pending_create);
+    }
+    // Now copy it back
+    while (!tmp.empty()) {
+      pending_create_stream_queues_[i].push(tmp.front());
+      tmp.pop();
+    }
+  }
+}
+
+int SpdySession::CreateStreamImpl(
+    const GURL& url,
+    RequestPriority priority,
+    scoped_refptr<SpdyStream>* spdy_stream,
+    const BoundNetLog& stream_net_log) {
+  DCHECK_GE(priority, MINIMUM_PRIORITY);
+  DCHECK_LT(priority, NUM_PRIORITIES);
+
+  // Make sure that we don't try to send https/wss over an unauthenticated, but
+  // encrypted SSL socket.
+  if (is_secure_ && certificate_error_code_ != OK &&
+      (url.SchemeIs("https") || url.SchemeIs("wss"))) {
+    RecordProtocolErrorHistogram(
+        PROTOCOL_ERROR_REQUEST_FOR_SECURE_CONTENT_OVER_INSECURE_SESSION);
+    CloseSessionOnError(
+        static_cast<net::Error>(certificate_error_code_),
+        true,
+        "Tried to create SPDY stream for secure content over an "
+        "unauthenticated session.");
+    return ERR_SPDY_PROTOCOL_ERROR;
+  }
+
+  const std::string& path = url.PathForRequest();
+
+  *spdy_stream = new SpdyStream(this,
+                                false,
+                                stream_net_log);
+  const scoped_refptr<SpdyStream>& stream = *spdy_stream;
+
+  stream->set_priority(priority);
+  stream->set_path(path);
+  stream->set_send_window_size(initial_send_window_size_);
+  stream->set_recv_window_size(initial_recv_window_size_);
+  created_streams_.insert(stream);
+
+  UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyPriorityCount",
+      static_cast<int>(priority), 0, 10, 11);
+
+  // TODO(mbelshe): Optimize memory allocations
+
+  return OK;
+}
+
+bool SpdySession::NeedsCredentials() const {
+  if (!is_secure_)
+    return false;
+  SSLClientSocket* ssl_socket = GetSSLClientSocket();
+  if (ssl_socket->GetNegotiatedProtocol() < kProtoSPDY3)
+    return false;
+  return ssl_socket->WasChannelIDSent();
+}
+
+void SpdySession::AddPooledAlias(const HostPortProxyPair& alias) {
+  pooled_aliases_.insert(alias);
+}
+
+int SpdySession::GetProtocolVersion() const {
+  DCHECK(buffered_spdy_framer_.get());
+  return buffered_spdy_framer_->protocol_version();
+}
+
+SpdySynStreamControlFrame* SpdySession::CreateSynStream(
+    SpdyStreamId stream_id,
+    RequestPriority priority,
+    uint8 credential_slot,
+    SpdyControlFlags flags,
+    const SpdyHeaderBlock& headers) {
+  CHECK(IsStreamActive(stream_id));
+  const scoped_refptr<SpdyStream>& stream = active_streams_[stream_id];
+  CHECK_EQ(stream->stream_id(), stream_id);
+
+  SendPrefacePingIfNoneInFlight();
+
+  DCHECK(buffered_spdy_framer_.get());
+  scoped_ptr<SpdySynStreamControlFrame> syn_frame(
+      buffered_spdy_framer_->CreateSynStream(
+          stream_id, 0,
+          ConvertRequestPriorityToSpdyPriority(priority, GetProtocolVersion()),
+          credential_slot, flags, true, &headers));
+
+  base::StatsCounter spdy_requests("spdy.requests");
+  spdy_requests.Increment();
+  streams_initiated_count_++;
+
+  if (net_log().IsLoggingAllEvents()) {
+    net_log().AddEvent(
+        NetLog::TYPE_SPDY_SESSION_SYN_STREAM,
+        base::Bind(&NetLogSpdySynCallback, &headers,
+                   (flags & CONTROL_FLAG_FIN) != 0,
+                   (flags & CONTROL_FLAG_UNIDIRECTIONAL) != 0,
+                   stream_id, 0));
+  }
+
+  return syn_frame.release();
+}
+
+SpdyCredentialControlFrame* SpdySession::CreateCredentialFrame(
+    const std::string& origin,
+    SSLClientCertType type,
+    const std::string& key,
+    const std::string& cert,
+    RequestPriority priority) {
+  DCHECK(is_secure_);
+  SSLClientSocket* ssl_socket = GetSSLClientSocket();
+  DCHECK(ssl_socket);
+  DCHECK(ssl_socket->WasChannelIDSent());
+
+  SpdyCredential credential;
+  std::string tls_unique;
+  ssl_socket->GetTLSUniqueChannelBinding(&tls_unique);
+  size_t slot = credential_state_.SetHasCredential(GURL(origin));
+  int rv = SpdyCredentialBuilder::Build(tls_unique, type, key, cert, slot,
+                                        &credential);
+  DCHECK_EQ(OK, rv);
+  if (rv != OK)
+    return NULL;
+
+  DCHECK(buffered_spdy_framer_.get());
+  scoped_ptr<SpdyCredentialControlFrame> credential_frame(
+      buffered_spdy_framer_->CreateCredentialFrame(credential));
+
+  if (net_log().IsLoggingAllEvents()) {
+    net_log().AddEvent(
+        NetLog::TYPE_SPDY_SESSION_SEND_CREDENTIAL,
+        base::Bind(&NetLogSpdyCredentialCallback, credential.slot, &origin));
+  }
+  return credential_frame.release();
+}
+
+SpdyHeadersControlFrame* SpdySession::CreateHeadersFrame(
+    SpdyStreamId stream_id,
+    const SpdyHeaderBlock& headers,
+    SpdyControlFlags flags) {
+  // Find our stream
+  CHECK(IsStreamActive(stream_id));
+  scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+  CHECK_EQ(stream->stream_id(), stream_id);
+
+  // Create a HEADER frame.
+  scoped_ptr<SpdyHeadersControlFrame> frame(
+      buffered_spdy_framer_->CreateHeaders(stream_id, flags, true, &headers));
+
+  if (net_log().IsLoggingAllEvents()) {
+    bool fin = flags & CONTROL_FLAG_FIN;
+    net_log().AddEvent(
+        NetLog::TYPE_SPDY_SESSION_SEND_HEADERS,
+        base::Bind(&NetLogSpdySynCallback,
+                   &headers, fin, /*unidirectional=*/false,
+                   stream_id, 0));
+  }
+  return frame.release();
+}
+
+SpdyDataFrame* SpdySession::CreateDataFrame(SpdyStreamId stream_id,
+                                            net::IOBuffer* data, int len,
+                                            SpdyDataFlags flags) {
+  // Find our stream
+  CHECK(IsStreamActive(stream_id));
+  scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+  CHECK_EQ(stream->stream_id(), stream_id);
+
+  if (len > kMaxSpdyFrameChunkSize) {
+    len = kMaxSpdyFrameChunkSize;
+    flags = static_cast<SpdyDataFlags>(flags & ~DATA_FLAG_FIN);
+  }
+
+  // Obey send window size of the stream if flow control is enabled.
+  if (flow_control_) {
+    if (stream->send_window_size() <= 0) {
+      // Because we queue frames onto the session, it is possible that
+      // a stream was not flow controlled at the time it attempted the
+      // write, but when we go to fulfill the write, it is now flow
+      // controlled.  This is why we need the session to mark the stream
+      // as stalled - because only the session knows for sure when the
+      // stall occurs.
+      stream->set_stalled_by_flow_control(true);
+      net_log().AddEvent(
+          NetLog::TYPE_SPDY_SESSION_STALLED_ON_SEND_WINDOW,
+          NetLog::IntegerCallback("stream_id", stream_id));
+      return NULL;
+    }
+    int new_len = std::min(len, stream->send_window_size());
+    if (new_len < len) {
+      len = new_len;
+      flags = static_cast<SpdyDataFlags>(flags & ~DATA_FLAG_FIN);
+    }
+    stream->DecreaseSendWindowSize(len);
+  }
+
+  if (net_log().IsLoggingAllEvents()) {
+    net_log().AddEvent(
+        NetLog::TYPE_SPDY_SESSION_SEND_DATA,
+        base::Bind(&NetLogSpdyDataCallback, stream_id, len, flags));
+  }
+
+  // Send PrefacePing for DATA_FRAMEs with nonzero payload size.
+  if (len > 0)
+    SendPrefacePingIfNoneInFlight();
+
+  // TODO(mbelshe): reduce memory copies here.
+  DCHECK(buffered_spdy_framer_.get());
+  scoped_ptr<SpdyDataFrame> frame(
+      buffered_spdy_framer_->CreateDataFrame(
+          stream_id, data->data(), len, flags));
+
+  return frame.release();
+}
+
+void SpdySession::CloseStream(SpdyStreamId stream_id, int status) {
+  DCHECK_NE(0u, stream_id);
+  // TODO(mbelshe): We should send a RST_STREAM control frame here
+  //                so that the server can cancel a large send.
+
+  DeleteStream(stream_id, status);
+}
+
+void SpdySession::CloseCreatedStream(SpdyStream* stream, int status) {
+  DCHECK_EQ(0u, stream->stream_id());
+  created_streams_.erase(scoped_refptr<SpdyStream>(stream));
+  ProcessPendingCreateStreams();
+}
+
+void SpdySession::ResetStream(SpdyStreamId stream_id,
+                              SpdyStatusCodes status,
+                              const std::string& description) {
+  net_log().AddEvent(
+      NetLog::TYPE_SPDY_SESSION_SEND_RST_STREAM,
+      base::Bind(&NetLogSpdyRstCallback, stream_id, status, &description));
+
+  DCHECK(buffered_spdy_framer_.get());
+  scoped_ptr<SpdyRstStreamControlFrame> rst_frame(
+      buffered_spdy_framer_->CreateRstStream(stream_id, status));
+
+  // Default to lowest priority unless we know otherwise.
+  RequestPriority priority = net::IDLE;
+  if(IsStreamActive(stream_id)) {
+    scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+    priority = stream->priority();
+  }
+  QueueFrame(rst_frame.release(), priority);
+  RecordProtocolErrorHistogram(
+      static_cast<SpdyProtocolErrorDetails>(status + STATUS_CODE_INVALID));
+  DeleteStream(stream_id, ERR_SPDY_PROTOCOL_ERROR);
+}
+
+bool SpdySession::IsStreamActive(SpdyStreamId stream_id) const {
+  return ContainsKey(active_streams_, stream_id);
+}
+
+LoadState SpdySession::GetLoadState() const {
+  // NOTE: The application only queries the LoadState via the
+  //       SpdyNetworkTransaction, and details are only needed when
+  //       we're in the process of connecting.
+
+  // If we're connecting, defer to the connection to give us the actual
+  // LoadState.
+  if (state_ == CONNECTING)
+    return connection_->GetLoadState();
+
+  // Just report that we're idle since the session could be doing
+  // many things concurrently.
+  return LOAD_STATE_IDLE;
+}
+
+void SpdySession::OnReadComplete(int bytes_read) {
+  // Parse a frame.  For now this code requires that the frame fit into our
+  // buffer (32KB).
+  // TODO(mbelshe): support arbitrarily large frames!
+
+  read_pending_ = false;
+
+  if (bytes_read <= 0) {
+    // Session is tearing down.
+    net::Error error = static_cast<net::Error>(bytes_read);
+    if (bytes_read == 0)
+      error = ERR_CONNECTION_CLOSED;
+    CloseSessionOnError(error, true, "bytes_read is <= 0.");
+    return;
+  }
+
+  bytes_received_ += bytes_read;
+
+  last_activity_time_ = base::TimeTicks::Now();
+
+  // The SpdyFramer will use callbacks onto |this| as it parses frames.
+  // When errors occur, those callbacks can lead to teardown of all references
+  // to |this|, so maintain a reference to self during this call for safe
+  // cleanup.
+  scoped_refptr<SpdySession> self(this);
+
+  DCHECK(buffered_spdy_framer_.get());
+  char *data = read_buffer_->data();
+  while (bytes_read &&
+         buffered_spdy_framer_->error_code() ==
+             SpdyFramer::SPDY_NO_ERROR) {
+    uint32 bytes_processed =
+        buffered_spdy_framer_->ProcessInput(data, bytes_read);
+    bytes_read -= bytes_processed;
+    data += bytes_processed;
+    if (buffered_spdy_framer_->state() == SpdyFramer::SPDY_DONE)
+      buffered_spdy_framer_->Reset();
+  }
+
+  if (state_ != CLOSED)
+    ReadSocket();
+}
+
+void SpdySession::OnWriteComplete(int result) {
+  DCHECK(write_pending_);
+  DCHECK(in_flight_write_.size());
+
+  last_activity_time_ = base::TimeTicks::Now();
+  write_pending_ = false;
+
+  scoped_refptr<SpdyStream> stream = in_flight_write_.stream();
+
+  if (result >= 0) {
+    // It should not be possible to have written more bytes than our
+    // in_flight_write_.
+    DCHECK_LE(result, in_flight_write_.buffer()->BytesRemaining());
+
+    in_flight_write_.buffer()->DidConsume(result);
+
+    // We only notify the stream when we've fully written the pending frame.
+    if (!in_flight_write_.buffer()->BytesRemaining()) {
+      if (stream) {
+        // Report the number of bytes written to the caller, but exclude the
+        // frame size overhead.  NOTE: if this frame was compressed the
+        // reported bytes written is the compressed size, not the original
+        // size.
+        if (result > 0) {
+          result = in_flight_write_.buffer()->size();
+          DCHECK_GE(result, static_cast<int>(SpdyFrame::kHeaderSize));
+          result -= static_cast<int>(SpdyFrame::kHeaderSize);
+        }
+
+        // It is possible that the stream was cancelled while we were writing
+        // to the socket.
+        if (!stream->cancelled())
+          stream->OnWriteComplete(result);
+      }
+
+      // Cleanup the write which just completed.
+      in_flight_write_.release();
+    }
+
+    // Write more data.  We're already in a continuation, so we can
+    // go ahead and write it immediately (without going back to the
+    // message loop).
+    WriteSocketLater();
+  } else {
+    in_flight_write_.release();
+
+    // The stream is now errored.  Close it down.
+    CloseSessionOnError(
+        static_cast<net::Error>(result), true, "The stream has errored.");
+  }
+}
+
+net::Error SpdySession::ReadSocket() {
+  if (read_pending_)
+    return OK;
+
+  if (state_ == CLOSED) {
+    NOTREACHED();
+    return ERR_UNEXPECTED;
+  }
+
+  CHECK(connection_.get());
+  CHECK(connection_->socket());
+  int bytes_read = connection_->socket()->Read(
+      read_buffer_.get(),
+      kReadBufferSize,
+      base::Bind(&SpdySession::OnReadComplete, base::Unretained(this)));
+  switch (bytes_read) {
+    case 0:
+      // Socket is closed!
+      CloseSessionOnError(ERR_CONNECTION_CLOSED, true, "bytes_read is 0.");
+      return ERR_CONNECTION_CLOSED;
+    case net::ERR_IO_PENDING:
+      // Waiting for data.  Nothing to do now.
+      read_pending_ = true;
+      return ERR_IO_PENDING;
+    default:
+      // Data was read, process it.
+      // Schedule the work through the message loop to avoid recursive
+      // callbacks.
+      read_pending_ = true;
+      MessageLoop::current()->PostTask(
+          FROM_HERE,
+          base::Bind(&SpdySession::OnReadComplete,
+                     weak_factory_.GetWeakPtr(), bytes_read));
+      break;
+  }
+  return OK;
+}
+
+void SpdySession::WriteSocketLater() {
+  if (delayed_write_pending_)
+    return;
+
+  if (state_ < CONNECTED)
+    return;
+
+  delayed_write_pending_ = true;
+  MessageLoop::current()->PostTask(
+      FROM_HERE,
+      base::Bind(&SpdySession::WriteSocket, weak_factory_.GetWeakPtr()));
+}
+
+void SpdySession::WriteSocket() {
+  // This function should only be called via WriteSocketLater.
+  DCHECK(delayed_write_pending_);
+  delayed_write_pending_ = false;
+
+  // If the socket isn't connected yet, just wait; we'll get called
+  // again when the socket connection completes.  If the socket is
+  // closed, just return.
+  if (state_ < CONNECTED || state_ == CLOSED)
+    return;
+
+  if (write_pending_)   // Another write is in progress still.
+    return;
+
+  // Loop sending frames until we've sent everything or until the write
+  // returns error (or ERR_IO_PENDING).
+  DCHECK(buffered_spdy_framer_.get());
+  while (in_flight_write_.buffer() || !write_queue_.empty()) {
+    if (!in_flight_write_.buffer()) {
+      // Grab the next SpdyBuffer to send.
+      scoped_ptr<SpdyIOBufferProducer> producer(write_queue_.top());
+      write_queue_.pop();
+      scoped_ptr<SpdyIOBuffer> buffer(producer->ProduceNextBuffer(this));
+      stream_producers_.erase(producer.get());
+      // It is possible that a stream had data to write, but a
+      // WINDOW_UPDATE frame has been received which made that
+      // stream no longer writable.
+      // TODO(rch): consider handling that case by removing the
+      // stream from the writable queue?
+      if (buffer == NULL)
+        continue;
+
+      in_flight_write_ = *buffer;
+    } else {
+      DCHECK(in_flight_write_.buffer()->BytesRemaining());
+    }
+
+    write_pending_ = true;
+    int rv = connection_->socket()->Write(
+        in_flight_write_.buffer(),
+        in_flight_write_.buffer()->BytesRemaining(),
+        base::Bind(&SpdySession::OnWriteComplete, base::Unretained(this)));
+    if (rv == net::ERR_IO_PENDING)
+      break;
+
+    // We sent the frame successfully.
+    OnWriteComplete(rv);
+
+    // TODO(mbelshe):  Test this error case.  Maybe we should mark the socket
+    //                 as in an error state.
+    if (rv < 0)
+      break;
+  }
+}
+
+void SpdySession::CloseAllStreams(net::Error status) {
+  base::StatsCounter abandoned_streams("spdy.abandoned_streams");
+  base::StatsCounter abandoned_push_streams(
+      "spdy.abandoned_push_streams");
+
+  if (!active_streams_.empty())
+    abandoned_streams.Add(active_streams_.size());
+  if (!unclaimed_pushed_streams_.empty()) {
+    streams_abandoned_count_ += unclaimed_pushed_streams_.size();
+    abandoned_push_streams.Add(unclaimed_pushed_streams_.size());
+    unclaimed_pushed_streams_.clear();
+  }
+
+  for (int i = 0; i < NUM_PRIORITIES; ++i) {
+    while (!pending_create_stream_queues_[i].empty()) {
+      PendingCreateStream pending_create =
+          pending_create_stream_queues_[i].front();
+      pending_create_stream_queues_[i].pop();
+      pending_create.callback.Run(ERR_ABORTED);
+    }
+  }
+
+  while (!active_streams_.empty()) {
+    ActiveStreamMap::iterator it = active_streams_.begin();
+    const scoped_refptr<SpdyStream>& stream = it->second;
+    LogAbandonedStream(stream, status);
+    DeleteStream(stream->stream_id(), status);
+  }
+
+  while (!created_streams_.empty()) {
+    CreatedStreamSet::iterator it = created_streams_.begin();
+    const scoped_refptr<SpdyStream> stream = *it;
+    created_streams_.erase(it);
+    LogAbandonedStream(stream, status);
+    stream->OnClose(status);
+  }
+
+  // We also need to drain the queue.
+  while (!write_queue_.empty()) {
+    scoped_ptr<SpdyIOBufferProducer> producer(write_queue_.top());
+    write_queue_.pop();
+    stream_producers_.erase(producer.get());
+  }
+}
+
+void SpdySession::LogAbandonedStream(const scoped_refptr<SpdyStream>& stream,
+                                     net::Error status) {
+  DCHECK(stream);
+  std::string description = base::StringPrintf(
+      "ABANDONED (stream_id=%d): ", stream->stream_id()) + stream->path();
+  stream->LogStreamError(status, description);
+}
+
+int SpdySession::GetNewStreamId() {
+  int id = stream_hi_water_mark_;
+  stream_hi_water_mark_ += 2;
+  if (stream_hi_water_mark_ > 0x7fff)
+    stream_hi_water_mark_ = 1;
+  return id;
+}
+
+void SpdySession::CloseSessionOnError(net::Error err,
+                                      bool remove_from_pool,
+                                      const std::string& description) {
+  // Closing all streams can have a side-effect of dropping the last reference
+  // to |this|.  Hold a reference through this function.
+  scoped_refptr<SpdySession> self(this);
+
+  DCHECK_LT(err, OK);
+  net_log_.AddEvent(
+      NetLog::TYPE_SPDY_SESSION_CLOSE,
+      base::Bind(&NetLogSpdySessionCloseCallback, err, &description));
+
+  // Don't close twice.  This can occur because we can have both
+  // a read and a write outstanding, and each can complete with
+  // an error.
+  if (state_ != CLOSED) {
+    state_ = CLOSED;
+    error_ = err;
+    if (remove_from_pool)
+      RemoveFromPool();
+    CloseAllStreams(err);
+  }
+}
+
+Value* SpdySession::GetInfoAsValue() const {
+  DictionaryValue* dict = new DictionaryValue();
+
+  dict->SetInteger("source_id", net_log_.source().id);
+
+  dict->SetString("host_port_pair", host_port_proxy_pair_.first.ToString());
+  if (!pooled_aliases_.empty()) {
+    ListValue* alias_list = new ListValue();
+    for (std::set<HostPortProxyPair>::const_iterator it =
+             pooled_aliases_.begin();
+         it != pooled_aliases_.end(); it++) {
+      alias_list->Append(Value::CreateStringValue(it->first.ToString()));
+    }
+    dict->Set("aliases", alias_list);
+  }
+  dict->SetString("proxy", host_port_proxy_pair_.second.ToURI());
+
+  dict->SetInteger("active_streams", active_streams_.size());
+
+  dict->SetInteger("unclaimed_pushed_streams",
+      unclaimed_pushed_streams_.size());
+
+  dict->SetBoolean("is_secure", is_secure_);
+
+  dict->SetString("protocol_negotiated",
+                  SSLClientSocket::NextProtoToString(
+                      connection_->socket()->GetNegotiatedProtocol()));
+
+  dict->SetInteger("error", error_);
+  dict->SetInteger("max_concurrent_streams", max_concurrent_streams_);
+
+  dict->SetInteger("streams_initiated_count", streams_initiated_count_);
+  dict->SetInteger("streams_pushed_count", streams_pushed_count_);
+  dict->SetInteger("streams_pushed_and_claimed_count",
+      streams_pushed_and_claimed_count_);
+  dict->SetInteger("streams_abandoned_count", streams_abandoned_count_);
+  DCHECK(buffered_spdy_framer_.get());
+  dict->SetInteger("frames_received", buffered_spdy_framer_->frames_received());
+
+  dict->SetBoolean("sent_settings", sent_settings_);
+  dict->SetBoolean("received_settings", received_settings_);
+  return dict;
+}
+
+bool SpdySession::IsReused() const {
+  return buffered_spdy_framer_->frames_received() > 0;
+}
+
+int SpdySession::GetPeerAddress(IPEndPoint* address) const {
+  if (!connection_->socket())
+    return ERR_SOCKET_NOT_CONNECTED;
+
+  return connection_->socket()->GetPeerAddress(address);
+}
+
+int SpdySession::GetLocalAddress(IPEndPoint* address) const {
+  if (!connection_->socket())
+    return ERR_SOCKET_NOT_CONNECTED;
+
+  return connection_->socket()->GetLocalAddress(address);
+}
+
+class SimpleSpdyIOBufferProducer : public SpdySession::SpdyIOBufferProducer {
+ public:
+  SimpleSpdyIOBufferProducer(SpdyFrame* frame,
+                             RequestPriority priority)
+      : frame_(frame),
+        priority_(priority) {
+  }
+
+  virtual RequestPriority GetPriority() const OVERRIDE {
+    return priority_;
+  }
+
+  virtual SpdyIOBuffer* ProduceNextBuffer(SpdySession* session) OVERRIDE {
+    return SpdySession::SpdyIOBufferProducer::CreateIOBuffer(
+        frame_.get(), priority_, NULL);
+  }
+
+ private:
+  scoped_ptr<SpdyFrame> frame_;
+  RequestPriority priority_;
+};
+
+void SpdySession::QueueFrame(SpdyFrame* frame,
+                             RequestPriority priority) {
+  SimpleSpdyIOBufferProducer* producer =
+      new SimpleSpdyIOBufferProducer(frame, priority);
+  write_queue_.push(producer);
+  WriteSocketLater();
+}
+
+void SpdySession::ActivateStream(SpdyStream* stream) {
+  if (stream->stream_id() == 0) {
+    stream->set_stream_id(GetNewStreamId());
+    created_streams_.erase(scoped_refptr<SpdyStream>(stream));
+  }
+  const SpdyStreamId id = stream->stream_id();
+  DCHECK(!IsStreamActive(id));
+
+  active_streams_[id] = stream;
+}
+
+void SpdySession::DeleteStream(SpdyStreamId id, int status) {
+  // For push streams, if they are being deleted normally, we leave
+  // the stream in the unclaimed_pushed_streams_ list.  However, if
+  // the stream is errored out, clean it up entirely.
+  if (status != OK) {
+    PushedStreamMap::iterator it;
+    for (it = unclaimed_pushed_streams_.begin();
+         it != unclaimed_pushed_streams_.end(); ++it) {
+      scoped_refptr<SpdyStream> curr = it->second.first;
+      if (id == curr->stream_id()) {
+        unclaimed_pushed_streams_.erase(it);
+        break;
+      }
+    }
+  }
+
+  // The stream might have been deleted.
+  ActiveStreamMap::iterator it2 = active_streams_.find(id);
+  if (it2 == active_streams_.end())
+    return;
+
+  // Possibly remove from the write queue.
+  WriteQueue old = write_queue_;
+  write_queue_ = WriteQueue();
+  while (!old.empty()) {
+    scoped_ptr<SpdyIOBufferProducer> producer(old.top());
+    old.pop();
+    StreamProducerMap::iterator it = stream_producers_.find(producer.get());
+    if (it == stream_producers_.end() || it->second->stream_id() != id) {
+      write_queue_.push(producer.release());
+    } else {
+      stream_producers_.erase(producer.get());
+      producer.reset(NULL);
+    }
+  }
+
+  // If this is an active stream, call the callback.
+  const scoped_refptr<SpdyStream> stream(it2->second);
+  active_streams_.erase(it2);
+  if (stream)
+    stream->OnClose(status);
+  ProcessPendingCreateStreams();
+}
+
+void SpdySession::RemoveFromPool() {
+  if (spdy_session_pool_) {
+    SpdySessionPool* pool = spdy_session_pool_;
+    spdy_session_pool_ = NULL;
+    pool->Remove(make_scoped_refptr(this));
+  }
+}
+
+scoped_refptr<SpdyStream> SpdySession::GetActivePushStream(
+    const std::string& path) {
+  base::StatsCounter used_push_streams("spdy.claimed_push_streams");
+
+  PushedStreamMap::iterator it = unclaimed_pushed_streams_.find(path);
+  if (it != unclaimed_pushed_streams_.end()) {
+    net_log_.AddEvent(NetLog::TYPE_SPDY_STREAM_ADOPTED_PUSH_STREAM);
+    scoped_refptr<SpdyStream> stream = it->second.first;
+    unclaimed_pushed_streams_.erase(it);
+    used_push_streams.Increment();
+    return stream;
+  }
+  return NULL;
+}
+
+bool SpdySession::GetSSLInfo(SSLInfo* ssl_info,
+                             bool* was_npn_negotiated,
+                             NextProto* protocol_negotiated) {
+
+  *was_npn_negotiated = connection_->socket()->WasNpnNegotiated();
+  *protocol_negotiated = connection_->socket()->GetNegotiatedProtocol();
+  return connection_->socket()->GetSSLInfo(ssl_info);
+}
+
+bool SpdySession::GetSSLCertRequestInfo(
+    SSLCertRequestInfo* cert_request_info) {
+  if (!is_secure_)
+    return false;
+  GetSSLClientSocket()->GetSSLCertRequestInfo(cert_request_info);
+  return true;
+}
+
+ServerBoundCertService* SpdySession::GetServerBoundCertService() const {
+  if (!is_secure_)
+    return NULL;
+  return GetSSLClientSocket()->GetServerBoundCertService();
+}
+
+void SpdySession::OnError(SpdyFramer::SpdyError error_code) {
+  RecordProtocolErrorHistogram(
+      static_cast<SpdyProtocolErrorDetails>(error_code));
+  std::string description = base::StringPrintf(
+      "SPDY_ERROR error_code: %d.", error_code);
+  CloseSessionOnError(net::ERR_SPDY_PROTOCOL_ERROR, true, description);
+}
+
+void SpdySession::OnStreamError(SpdyStreamId stream_id,
+                                const std::string& description) {
+  if (IsStreamActive(stream_id))
+    ResetStream(stream_id, PROTOCOL_ERROR, description);
+}
+
+void SpdySession::OnStreamFrameData(SpdyStreamId stream_id,
+                                    const char* data,
+                                    size_t len,
+                                    SpdyDataFlags flags) {
+  if (net_log().IsLoggingAllEvents()) {
+    net_log().AddEvent(
+        NetLog::TYPE_SPDY_SESSION_RECV_DATA,
+        base::Bind(&NetLogSpdyDataCallback, stream_id, len, flags));
+  }
+
+  if (!IsStreamActive(stream_id)) {
+    // NOTE:  it may just be that the stream was cancelled.
+    return;
+  }
+
+  scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+  stream->OnDataReceived(data, len);
+}
+
+void SpdySession::OnSetting(SpdySettingsIds id,
+                            uint8 flags,
+                            uint32 value) {
+  HandleSetting(id, value);
+  http_server_properties_->SetSpdySetting(
+      host_port_pair(),
+      id,
+      static_cast<SpdySettingsFlags>(flags),
+      value);
+  received_settings_ = true;
+
+  // Log the setting.
+  net_log_.AddEvent(
+      NetLog::TYPE_SPDY_SESSION_RECV_SETTING,
+      base::Bind(&NetLogSpdySettingCallback,
+                 id, static_cast<SpdySettingsFlags>(flags), value));
+}
+
+void SpdySession::OnControlFrameCompressed(
+    const SpdyControlFrame& uncompressed_frame,
+    const SpdyControlFrame& compressed_frame) {
+  if (uncompressed_frame.type() == SYN_STREAM) {
+    int uncompressed_size = uncompressed_frame.length();
+    int compressed_size = compressed_frame.length();
+    // Make sure we avoid early decimal truncation.
+    int compression_pct = 100 - (100 * compressed_size) / uncompressed_size;
+    UMA_HISTOGRAM_PERCENTAGE("Net.SpdySynStreamCompressionPercentage",
+                             compression_pct);
+  }
+}
+
+
+bool SpdySession::Respond(const SpdyHeaderBlock& headers,
+                          const scoped_refptr<SpdyStream> stream) {
+  int rv = OK;
+  rv = stream->OnResponseReceived(headers);
+  if (rv < 0) {
+    DCHECK_NE(rv, ERR_IO_PENDING);
+    const SpdyStreamId stream_id = stream->stream_id();
+    DeleteStream(stream_id, rv);
+    return false;
+  }
+  return true;
+}
+
+void SpdySession::OnSynStream(SpdyStreamId stream_id,
+                              SpdyStreamId associated_stream_id,
+                              SpdyPriority priority,
+                              uint8 credential_slot,
+                              bool fin,
+                              bool unidirectional,
+                              const SpdyHeaderBlock& headers) {
+  if (net_log_.IsLoggingAllEvents()) {
+    net_log_.AddEvent(
+        NetLog::TYPE_SPDY_SESSION_PUSHED_SYN_STREAM,
+        base::Bind(&NetLogSpdySynCallback,
+                   &headers, fin, unidirectional,
+                   stream_id, associated_stream_id));
+  }
+
+  // Server-initiated streams should have even sequence numbers.
+  if ((stream_id & 0x1) != 0) {
+    LOG(WARNING) << "Received invalid OnSyn stream id " << stream_id;
+    return;
+  }
+
+  if (IsStreamActive(stream_id)) {
+    LOG(WARNING) << "Received OnSyn for active stream " << stream_id;
+    return;
+  }
+
+  if (associated_stream_id == 0) {
+    std::string description = base::StringPrintf(
+        "Received invalid OnSyn associated stream id %d for stream %d",
+        associated_stream_id, stream_id);
+    ResetStream(stream_id, REFUSED_STREAM, description);
+    return;
+  }
+
+  streams_pushed_count_++;
+
+  // TODO(mbelshe): DCHECK that this is a GET method?
+
+  // Verify that the response had a URL for us.
+  GURL gurl = GetUrlFromHeaderBlock(headers, GetProtocolVersion(), true);
+  if (!gurl.is_valid()) {
+    ResetStream(stream_id, PROTOCOL_ERROR,
+                "Pushed stream url was invalid: " + gurl.spec());
+    return;
+  }
+  const std::string& url = gurl.spec();
+
+  // Verify we have a valid stream association.
+  if (!IsStreamActive(associated_stream_id)) {
+    ResetStream(stream_id, INVALID_STREAM,
+                base::StringPrintf(
+                    "Received OnSyn with inactive associated stream %d",
+                    associated_stream_id));
+    return;
+  }
+
+  // Check that the SYN advertises the same origin as its associated stream.
+  // Bypass this check if and only if this session is with a SPDY proxy that
+  // is trusted explicitly via the --trusted-spdy-proxy switch.
+  if (trusted_spdy_proxy_.Equals(host_port_pair())) {
+    // Disallow pushing of HTTPS content.
+    if (gurl.SchemeIs("https")) {
+      ResetStream(stream_id, REFUSED_STREAM,
+                  base::StringPrintf(
+                      "Rejected push of Cross Origin HTTPS content %d",
+                      associated_stream_id));
+    }
+  } else {
+    scoped_refptr<SpdyStream> associated_stream =
+        active_streams_[associated_stream_id];
+    GURL associated_url(associated_stream->GetUrl());
+    if (associated_url.GetOrigin() != gurl.GetOrigin()) {
+      ResetStream(stream_id, REFUSED_STREAM,
+                  base::StringPrintf(
+                      "Rejected Cross Origin Push Stream %d",
+                      associated_stream_id));
+      return;
+    }
+  }
+
+  // There should not be an existing pushed stream with the same path.
+  PushedStreamMap::iterator it = unclaimed_pushed_streams_.find(url);
+  if (it != unclaimed_pushed_streams_.end()) {
+    ResetStream(stream_id, PROTOCOL_ERROR,
+                "Received duplicate pushed stream with url: " + url);
+    return;
+  }
+
+  scoped_refptr<SpdyStream> stream(new SpdyStream(this, true, net_log_));
+  stream->set_stream_id(stream_id);
+
+  stream->set_path(gurl.PathForRequest());
+  stream->set_send_window_size(initial_send_window_size_);
+  stream->set_recv_window_size(initial_recv_window_size_);
+
+  DeleteExpiredPushedStreams();
+  unclaimed_pushed_streams_[url] =
+      std::pair<scoped_refptr<SpdyStream>, base::TimeTicks> (
+          stream, time_func_());
+
+
+  ActivateStream(stream);
+  stream->set_response_received();
+
+  // Parse the headers.
+  if (!Respond(headers, stream))
+    return;
+
+  base::StatsCounter push_requests("spdy.pushed_streams");
+  push_requests.Increment();
+}
+
+void SpdySession::DeleteExpiredPushedStreams() {
+  if (unclaimed_pushed_streams_.empty())
+    return;
+
+  // Check that adequate time has elapsed since the last sweep.
+  if (time_func_() < next_unclaimed_push_stream_sweep_time_)
+    return;
+
+  // Delete old streams.
+  base::TimeTicks minimum_freshness = time_func_() -
+      base::TimeDelta::FromSeconds(kMinPushedStreamLifetimeSeconds);
+  PushedStreamMap::iterator it;
+  for (it = unclaimed_pushed_streams_.begin();
+      it != unclaimed_pushed_streams_.end(); ) {
+    const scoped_refptr<SpdyStream>& stream = it->second.first;
+    base::TimeTicks creation_time = it->second.second;
+    // DeleteStream() will invalidate the current iterator, so move to next.
+    ++it;
+    if (minimum_freshness > creation_time) {
+      DeleteStream(stream->stream_id(), ERR_INVALID_SPDY_STREAM);
+      base::StatsCounter abandoned_push_streams(
+          "spdy.abandoned_push_streams");
+      base::StatsCounter abandoned_streams("spdy.abandoned_streams");
+      abandoned_push_streams.Increment();
+      abandoned_streams.Increment();
+      streams_abandoned_count_++;
+    }
+  }
+  next_unclaimed_push_stream_sweep_time_ = time_func_() +
+      base::TimeDelta::FromSeconds(kMinPushedStreamLifetimeSeconds);
+}
+
+void SpdySession::OnSynReply(SpdyStreamId stream_id,
+                             bool fin,
+                             const SpdyHeaderBlock& headers) {
+  if (net_log().IsLoggingAllEvents()) {
+    net_log().AddEvent(
+        NetLog::TYPE_SPDY_SESSION_SYN_REPLY,
+        base::Bind(&NetLogSpdySynCallback,
+                   &headers, fin, false,// not unidirectional
+                   stream_id, 0));
+  }
+
+  if (!IsStreamActive(stream_id)) {
+    // NOTE:  it may just be that the stream was cancelled.
+    LOG(WARNING) << "Received SYN_REPLY for invalid stream " << stream_id;
+    return;
+  }
+
+  scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+  CHECK_EQ(stream->stream_id(), stream_id);
+  CHECK(!stream->cancelled());
+
+  if (stream->response_received()) {
+    stream->LogStreamError(ERR_SYN_REPLY_NOT_RECEIVED,
+                           "Received duplicate SYN_REPLY for stream.");
+    RecordProtocolErrorHistogram(PROTOCOL_ERROR_SYN_REPLY_NOT_RECEIVED);
+    CloseStream(stream->stream_id(), ERR_SPDY_PROTOCOL_ERROR);
+    return;
+  }
+  stream->set_response_received();
+
+  Respond(headers, stream);
+}
+
+void SpdySession::OnHeaders(SpdyStreamId stream_id,
+                            bool fin,
+                            const SpdyHeaderBlock& headers) {
+  if (net_log().IsLoggingAllEvents()) {
+    net_log().AddEvent(
+        NetLog::TYPE_SPDY_SESSION_RECV_HEADERS,
+        base::Bind(&NetLogSpdySynCallback,
+                   &headers, fin, /*unidirectional=*/false,
+                   stream_id, 0));
+  }
+
+  if (!IsStreamActive(stream_id)) {
+    // NOTE:  it may just be that the stream was cancelled.
+    LOG(WARNING) << "Received HEADERS for invalid stream " << stream_id;
+    return;
+  }
+
+  scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+  CHECK_EQ(stream->stream_id(), stream_id);
+  CHECK(!stream->cancelled());
+
+  int rv = stream->OnHeaders(headers);
+  if (rv < 0) {
+    DCHECK_NE(rv, ERR_IO_PENDING);
+    const SpdyStreamId stream_id = stream->stream_id();
+    DeleteStream(stream_id, rv);
+  }
+}
+
+void SpdySession::OnRstStream(SpdyStreamId stream_id, SpdyStatusCodes status) {
+  std::string description;
+  net_log().AddEvent(
+      NetLog::TYPE_SPDY_SESSION_RST_STREAM,
+      base::Bind(&NetLogSpdyRstCallback,
+                 stream_id, status, &description));
+
+  if (!IsStreamActive(stream_id)) {
+    // NOTE:  it may just be that the stream was cancelled.
+    LOG(WARNING) << "Received RST for invalid stream" << stream_id;
+    return;
+  }
+  scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+  CHECK_EQ(stream->stream_id(), stream_id);
+  CHECK(!stream->cancelled());
+
+  if (status == 0) {
+    stream->OnDataReceived(NULL, 0);
+  } else if (status == REFUSED_STREAM) {
+    DeleteStream(stream_id, ERR_SPDY_SERVER_REFUSED_STREAM);
+  } else {
+    RecordProtocolErrorHistogram(
+        PROTOCOL_ERROR_RST_STREAM_FOR_NON_ACTIVE_STREAM);
+    stream->LogStreamError(ERR_SPDY_PROTOCOL_ERROR,
+                           base::StringPrintf("SPDY stream closed: %d",
+                                              status));
+    // TODO(mbelshe): Map from Spdy-protocol errors to something sensical.
+    //                For now, it doesn't matter much - it is a protocol error.
+    DeleteStream(stream_id, ERR_SPDY_PROTOCOL_ERROR);
+  }
+}
+
+void SpdySession::OnGoAway(SpdyStreamId last_accepted_stream_id,
+                           SpdyGoAwayStatus status) {
+  net_log_.AddEvent(NetLog::TYPE_SPDY_SESSION_GOAWAY,
+      base::Bind(&NetLogSpdyGoAwayCallback,
+                 last_accepted_stream_id,
+                 active_streams_.size(),
+                 unclaimed_pushed_streams_.size(),
+                 status));
+  RemoveFromPool();
+  CloseAllStreams(net::ERR_ABORTED);
+
+  // TODO(willchan): Cancel any streams that are past the GoAway frame's
+  // |last_accepted_stream_id|.
+
+  // Don't bother killing any streams that are still reading.  They'll either
+  // complete successfully or get an ERR_CONNECTION_CLOSED when the socket is
+  // closed.
+}
+
+void SpdySession::OnPing(uint32 unique_id) {
+  net_log_.AddEvent(
+      NetLog::TYPE_SPDY_SESSION_PING,
+      base::Bind(&NetLogSpdyPingCallback, unique_id, "received"));
+
+  // Send response to a PING from server.
+  if (unique_id % 2 == 0) {
+    WritePingFrame(unique_id);
+    return;
+  }
+
+  --pings_in_flight_;
+  if (pings_in_flight_ < 0) {
+    RecordProtocolErrorHistogram(PROTOCOL_ERROR_UNEXPECTED_PING);
+    CloseSessionOnError(
+        net::ERR_SPDY_PROTOCOL_ERROR, true, "pings_in_flight_ is < 0.");
+    pings_in_flight_ = 0;
+    return;
+  }
+
+  if (pings_in_flight_ > 0)
+    return;
+
+  // We will record RTT in histogram when there are no more client sent
+  // pings_in_flight_.
+  RecordPingRTTHistogram(base::TimeTicks::Now() - last_ping_sent_time_);
+}
+
+void SpdySession::OnWindowUpdate(SpdyStreamId stream_id,
+                                 int delta_window_size) {
+  net_log_.AddEvent(
+      NetLog::TYPE_SPDY_SESSION_RECEIVED_WINDOW_UPDATE,
+      base::Bind(&NetLogSpdyWindowUpdateCallback,
+                 stream_id, delta_window_size));
+
+  if (!IsStreamActive(stream_id)) {
+    LOG(WARNING) << "Received WINDOW_UPDATE for invalid stream " << stream_id;
+    return;
+  }
+
+  if (delta_window_size < 1) {
+    ResetStream(stream_id, FLOW_CONTROL_ERROR,
+                base::StringPrintf(
+                    "Received WINDOW_UPDATE with an invalid "
+                    "delta_window_size %d", delta_window_size));
+    return;
+  }
+
+  scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+  CHECK_EQ(stream->stream_id(), stream_id);
+  CHECK(!stream->cancelled());
+
+  if (flow_control_)
+    stream->IncreaseSendWindowSize(delta_window_size);
+}
+
+void SpdySession::SendWindowUpdate(SpdyStreamId stream_id,
+                                   int32 delta_window_size) {
+  CHECK(IsStreamActive(stream_id));
+  scoped_refptr<SpdyStream> stream = active_streams_[stream_id];
+  CHECK_EQ(stream->stream_id(), stream_id);
+
+  net_log_.AddEvent(
+      NetLog::TYPE_SPDY_SESSION_SENT_WINDOW_UPDATE,
+      base::Bind(&NetLogSpdyWindowUpdateCallback,
+                 stream_id, delta_window_size));
+
+  DCHECK(buffered_spdy_framer_.get());
+  scoped_ptr<SpdyWindowUpdateControlFrame> window_update_frame(
+      buffered_spdy_framer_->CreateWindowUpdate(stream_id, delta_window_size));
+  QueueFrame(window_update_frame.release(), stream->priority());
+}
+
+// Given a cwnd that we would have sent to the server, modify it based on the
+// field trial policy.
+uint32 ApplyCwndFieldTrialPolicy(int cwnd) {
+#if defined(COBALT)
+  // Cobalt doesn't support field trials.
+  return cwnd;
+#else
+  base::FieldTrial* trial = base::FieldTrialList::Find("SpdyCwnd");
+  if (!trial) {
+      LOG(WARNING) << "Could not find \"SpdyCwnd\" in FieldTrialList";
+      return cwnd;
+  }
+  if (trial->group_name() == "cwnd10")
+    return 10;
+  else if (trial->group_name() == "cwnd16")
+    return 16;
+  else if (trial->group_name() == "cwndMin16")
+    return std::max(cwnd, 16);
+  else if (trial->group_name() == "cwndMin10")
+    return std::max(cwnd, 10);
+  else if (trial->group_name() == "cwndDynamic")
+    return cwnd;
+  NOTREACHED();
+  return cwnd;
+#endif
+}
+
+void SpdySession::SendInitialSettings() {
+  // First notify the server about the settings they should use when
+  // communicating with us.
+  if (GetProtocolVersion() >= 2 && enable_sending_initial_settings_) {
+    SettingsMap settings_map;
+    // Create a new settings frame notifying the sever of our
+    // max_concurrent_streams_ and initial window size.
+    settings_map[SETTINGS_MAX_CONCURRENT_STREAMS] =
+        SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kMaxConcurrentPushedStreams);
+    if (GetProtocolVersion() > 2 &&
+        initial_recv_window_size_ != kSpdyStreamInitialWindowSize) {
+      settings_map[SETTINGS_INITIAL_WINDOW_SIZE] =
+          SettingsFlagsAndValue(SETTINGS_FLAG_NONE, initial_recv_window_size_);
+    }
+    SendSettings(settings_map);
+  }
+
+  // Next notify the server about the settings they have previously
+  // told us to use when communicating with them.
+  const SettingsMap& settings_map =
+      http_server_properties_->GetSpdySettings(host_port_pair());
+  if (settings_map.empty())
+    return;
+
+  // Record Histogram Data and Apply the SpdyCwnd FieldTrial if applicable.
+  const SpdySettingsIds id = SETTINGS_CURRENT_CWND;
+  SettingsMap::const_iterator it = settings_map.find(id);
+  uint32 value = 0;
+  if (it != settings_map.end())
+    value = it->second.second;
+  uint32 cwnd = ApplyCwndFieldTrialPolicy(value);
+  UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwndSent", cwnd, 1, 200, 100);
+  if (cwnd != value) {
+    http_server_properties_->SetSpdySetting(
+        host_port_pair(), id, SETTINGS_FLAG_PLEASE_PERSIST, cwnd);
+  }
+
+  const SettingsMap& settings_map_new =
+      http_server_properties_->GetSpdySettings(host_port_pair());
+  for (SettingsMap::const_iterator i = settings_map_new.begin(),
+           end = settings_map_new.end(); i != end; ++i) {
+    const SpdySettingsIds new_id = i->first;
+    const uint32 new_val = i->second.second;
+    HandleSetting(new_id, new_val);
+  }
+
+  SendSettings(settings_map_new);
+}
+
+
+void SpdySession::SendSettings(const SettingsMap& settings) {
+  net_log_.AddEvent(
+      NetLog::TYPE_SPDY_SESSION_SEND_SETTINGS,
+      base::Bind(&NetLogSpdySettingsCallback, &settings));
+
+  // Create the SETTINGS frame and send it.
+  DCHECK(buffered_spdy_framer_.get());
+  scoped_ptr<SpdySettingsControlFrame> settings_frame(
+      buffered_spdy_framer_->CreateSettings(settings));
+  sent_settings_ = true;
+  QueueFrame(settings_frame.release(), HIGHEST);
+}
+
+void SpdySession::HandleSetting(uint32 id, uint32 value) {
+  switch (id) {
+    case SETTINGS_MAX_CONCURRENT_STREAMS:
+      max_concurrent_streams_ = std::min(static_cast<size_t>(value),
+                                         kMaxConcurrentStreamLimit);
+      ProcessPendingCreateStreams();
+      break;
+    case SETTINGS_INITIAL_WINDOW_SIZE:
+      if (static_cast<int32>(value) < 0) {
+        net_log().AddEvent(
+            NetLog::TYPE_SPDY_SESSION_NEGATIVE_INITIAL_WINDOW_SIZE,
+            NetLog::IntegerCallback("initial_window_size", value));
+      } else {
+        // SETTINGS_INITIAL_WINDOW_SIZE updates initial_send_window_size_ only.
+        int32 delta_window_size = value - initial_send_window_size_;
+        initial_send_window_size_ = value;
+        UpdateStreamsSendWindowSize(delta_window_size);
+        net_log().AddEvent(
+            NetLog::TYPE_SPDY_SESSION_UPDATE_STREAMS_SEND_WINDOW_SIZE,
+            NetLog::IntegerCallback("delta_window_size", delta_window_size));
+      }
+      break;
+  }
+}
+
+void SpdySession::UpdateStreamsSendWindowSize(int32 delta_window_size) {
+  ActiveStreamMap::iterator it;
+  for (it = active_streams_.begin(); it != active_streams_.end(); ++it) {
+    const scoped_refptr<SpdyStream>& stream = it->second;
+    DCHECK(stream);
+    stream->AdjustSendWindowSize(delta_window_size);
+  }
+
+  CreatedStreamSet::iterator i;
+  for (i = created_streams_.begin(); i != created_streams_.end(); i++) {
+    const scoped_refptr<SpdyStream>& stream = *i;
+    stream->AdjustSendWindowSize(delta_window_size);
+  }
+}
+
+void SpdySession::SendPrefacePingIfNoneInFlight() {
+  if (pings_in_flight_ || !enable_ping_based_connection_checking_)
+    return;
+
+  base::TimeTicks now = base::TimeTicks::Now();
+  // If there is no activity in the session, then send a preface-PING.
+  if ((now - last_activity_time_) > connection_at_risk_of_loss_time_)
+    SendPrefacePing();
+}
+
+void SpdySession::SendPrefacePing() {
+  WritePingFrame(next_ping_id_);
+}
+
+void SpdySession::WritePingFrame(uint32 unique_id) {
+  DCHECK(buffered_spdy_framer_.get());
+  scoped_ptr<SpdyPingControlFrame> ping_frame(
+      buffered_spdy_framer_->CreatePingFrame(unique_id));
+  QueueFrame(ping_frame.release(), HIGHEST);
+
+  if (net_log().IsLoggingAllEvents()) {
+    net_log().AddEvent(
+        NetLog::TYPE_SPDY_SESSION_PING,
+        base::Bind(&NetLogSpdyPingCallback, unique_id, "sent"));
+  }
+  if (unique_id % 2 != 0) {
+    next_ping_id_ += 2;
+    ++pings_in_flight_;
+    PlanToCheckPingStatus();
+    last_ping_sent_time_ = base::TimeTicks::Now();
+  }
+}
+
+void SpdySession::PlanToCheckPingStatus() {
+  if (check_ping_status_pending_)
+    return;
+
+  check_ping_status_pending_ = true;
+  MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&SpdySession::CheckPingStatus, weak_factory_.GetWeakPtr(),
+                 base::TimeTicks::Now()), hung_interval_);
+}
+
+void SpdySession::CheckPingStatus(base::TimeTicks last_check_time) {
+  // Check if we got a response back for all PINGs we had sent.
+  if (pings_in_flight_ == 0) {
+    check_ping_status_pending_ = false;
+    return;
+  }
+
+  DCHECK(check_ping_status_pending_);
+
+  base::TimeTicks now = base::TimeTicks::Now();
+  base::TimeDelta delay = hung_interval_ - (now - last_activity_time_);
+
+  if (delay.InMilliseconds() < 0 || last_activity_time_ < last_check_time) {
+    CloseSessionOnError(net::ERR_SPDY_PING_FAILED, true, "Failed ping.");
+    // Track all failed PING messages in a separate bucket.
+    const base::TimeDelta kFailedPing =
+        base::TimeDelta::FromInternalValue(INT_MAX);
+    RecordPingRTTHistogram(kFailedPing);
+    return;
+  }
+
+  // Check the status of connection after a delay.
+  MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&SpdySession::CheckPingStatus, weak_factory_.GetWeakPtr(),
+                 now),
+      delay);
+}
+
+void SpdySession::RecordPingRTTHistogram(base::TimeDelta duration) {
+  UMA_HISTOGRAM_TIMES("Net.SpdyPing.RTT", duration);
+}
+
+void SpdySession::RecordProtocolErrorHistogram(
+    SpdyProtocolErrorDetails details) {
+  UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionErrorDetails", details,
+                            NUM_SPDY_PROTOCOL_ERROR_DETAILS);
+  if (EndsWith(host_port_pair().host(), "google.com", false)) {
+    UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionErrorDetails_Google", details,
+                              NUM_SPDY_PROTOCOL_ERROR_DETAILS);
+  }
+}
+
+void SpdySession::RecordHistograms() {
+  UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsPerSession",
+                              streams_initiated_count_,
+                              0, 300, 50);
+  UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsPushedPerSession",
+                              streams_pushed_count_,
+                              0, 300, 50);
+  UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsPushedAndClaimedPerSession",
+                              streams_pushed_and_claimed_count_,
+                              0, 300, 50);
+  UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamsAbandonedPerSession",
+                              streams_abandoned_count_,
+                              0, 300, 50);
+  UMA_HISTOGRAM_ENUMERATION("Net.SpdySettingsSent",
+                            sent_settings_ ? 1 : 0, 2);
+  UMA_HISTOGRAM_ENUMERATION("Net.SpdySettingsReceived",
+                            received_settings_ ? 1 : 0, 2);
+  UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyStreamStallsPerSession",
+                              stalled_streams_,
+                              0, 300, 50);
+  UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionsWithStalls",
+                            stalled_streams_ > 0 ? 1 : 0, 2);
+
+  if (received_settings_) {
+    // Enumerate the saved settings, and set histograms for it.
+    const SettingsMap& settings_map =
+        http_server_properties_->GetSpdySettings(host_port_pair());
+
+    SettingsMap::const_iterator it;
+    for (it = settings_map.begin(); it != settings_map.end(); ++it) {
+      const SpdySettingsIds id = it->first;
+      const uint32 val = it->second.second;
+      switch (id) {
+        case SETTINGS_CURRENT_CWND:
+          // Record several different histograms to see if cwnd converges
+          // for larger volumes of data being sent.
+          UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd",
+                                      val, 1, 200, 100);
+          if (bytes_received_ > 10 * 1024) {
+            UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd10K",
+                                        val, 1, 200, 100);
+            if (bytes_received_ > 25 * 1024) {
+              UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd25K",
+                                          val, 1, 200, 100);
+              if (bytes_received_ > 50 * 1024) {
+                UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd50K",
+                                            val, 1, 200, 100);
+                if (bytes_received_ > 100 * 1024) {
+                  UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwnd100K",
+                                              val, 1, 200, 100);
+                }
+              }
+            }
+          }
+          break;
+        case SETTINGS_ROUND_TRIP_TIME:
+          UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsRTT",
+                                      val, 1, 1200, 100);
+          break;
+        case SETTINGS_DOWNLOAD_RETRANS_RATE:
+          UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsRetransRate",
+                                      val, 1, 100, 50);
+          break;
+        default:
+          break;
+      }
+    }
+  }
+}
+
+void SpdySession::InvokeUserStreamCreationCallback(
+    scoped_refptr<SpdyStream>* stream) {
+  PendingCallbackMap::iterator it = pending_callback_map_.find(stream);
+
+  // Exit if the request has already been cancelled.
+  if (it == pending_callback_map_.end())
+    return;
+
+  CompletionCallback callback = it->second.callback;
+  int result = it->second.result;
+  pending_callback_map_.erase(it);
+  callback.Run(result);
+}
+
+SSLClientSocket* SpdySession::GetSSLClientSocket() const {
+  if (!is_secure_)
+    return NULL;
+  SSLClientSocket* ssl_socket =
+      reinterpret_cast<SSLClientSocket*>(connection_->socket());
+  DCHECK(ssl_socket);
+  return ssl_socket;
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_session.h b/src/net/spdy/spdy_session.h
new file mode 100644
index 0000000..abb5e92
--- /dev/null
+++ b/src/net/spdy/spdy_session.h
@@ -0,0 +1,746 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_SPDY_SPDY_SESSION_H_
+#define NET_SPDY_SPDY_SESSION_H_
+
+#include <algorithm>
+#include <list>
+#include <map>
+#include <queue>
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_states.h"
+#include "net/base/net_errors.h"
+#include "net/base/request_priority.h"
+#include "net/base/ssl_client_cert_type.h"
+#include "net/base/ssl_config_service.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/stream_socket.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_credential_state.h"
+#include "net/spdy/spdy_header_block.h"
+#include "net/spdy/spdy_io_buffer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_session_pool.h"
+
+namespace net {
+
+// This is somewhat arbitrary and not really fixed, but it will always work
+// reasonably with ethernet. Chop the world into 2-packet chunks.  This is
+// somewhat arbitrary, but is reasonably small and ensures that we elicit
+// ACKs quickly from TCP (because TCP tries to only ACK every other packet).
+const int kMss = 1430;
+const int kMaxSpdyFrameChunkSize = (2 * kMss) - SpdyFrame::kHeaderSize;
+
+// Specifies the maxiumum concurrent streams server could send (via push).
+const int kMaxConcurrentPushedStreams = 1000;
+
+class BoundNetLog;
+class SpdyStream;
+class SSLInfo;
+
+enum SpdyProtocolErrorDetails {
+  // SpdyFramer::SpdyErrors
+  SPDY_ERROR_NO_ERROR,
+  SPDY_ERROR_INVALID_CONTROL_FRAME,
+  SPDY_ERROR_CONTROL_PAYLOAD_TOO_LARGE,
+  SPDY_ERROR_ZLIB_INIT_FAILURE,
+  SPDY_ERROR_UNSUPPORTED_VERSION,
+  SPDY_ERROR_DECOMPRESS_FAILURE,
+  SPDY_ERROR_COMPRESS_FAILURE,
+  SPDY_ERROR_CREDENTIAL_FRAME_CORRUPT,
+  SPDY_ERROR_INVALID_DATA_FRAME_FLAGS,
+
+  // SpdyStatusCodes
+  STATUS_CODE_INVALID,
+  STATUS_CODE_PROTOCOL_ERROR,
+  STATUS_CODE_INVALID_STREAM,
+  STATUS_CODE_REFUSED_STREAM,
+  STATUS_CODE_UNSUPPORTED_VERSION,
+  STATUS_CODE_CANCEL,
+  STATUS_CODE_INTERNAL_ERROR,
+  STATUS_CODE_FLOW_CONTROL_ERROR,
+  STATUS_CODE_STREAM_IN_USE,
+  STATUS_CODE_STREAM_ALREADY_CLOSED,
+  STATUS_CODE_INVALID_CREDENTIALS,
+  STATUS_CODE_FRAME_TOO_LARGE,
+
+  // SpdySession errors
+  PROTOCOL_ERROR_UNEXPECTED_PING,
+  PROTOCOL_ERROR_RST_STREAM_FOR_NON_ACTIVE_STREAM,
+  PROTOCOL_ERROR_SPDY_COMPRESSION_FAILURE,
+  PROTOCOL_ERROR_REQUEST_FOR_SECURE_CONTENT_OVER_INSECURE_SESSION,
+  PROTOCOL_ERROR_SYN_REPLY_NOT_RECEIVED,
+  NUM_SPDY_PROTOCOL_ERROR_DETAILS
+};
+
+COMPILE_ASSERT(STATUS_CODE_INVALID ==
+               static_cast<SpdyProtocolErrorDetails>(SpdyFramer::LAST_ERROR),
+               SpdyProtocolErrorDetails_SpdyErrors_mismatch);
+
+COMPILE_ASSERT(PROTOCOL_ERROR_UNEXPECTED_PING ==
+               static_cast<SpdyProtocolErrorDetails>(NUM_STATUS_CODES +
+                                                     STATUS_CODE_INVALID),
+               SpdyProtocolErrorDetails_SpdyErrors_mismatch);
+
+class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>,
+                               public BufferedSpdyFramerVisitorInterface {
+ public:
+  typedef base::TimeTicks (*TimeFunc)(void);
+
+  // Defines an interface for producing SpdyIOBuffers.
+  class NET_EXPORT_PRIVATE SpdyIOBufferProducer {
+   public:
+    SpdyIOBufferProducer() {}
+
+    // Returns a newly created SpdyIOBuffer, owned by the caller, or NULL
+    // if not buffer is ready to be produced.
+    virtual SpdyIOBuffer* ProduceNextBuffer(SpdySession* session) = 0;
+
+    virtual RequestPriority GetPriority() const = 0;
+
+    virtual ~SpdyIOBufferProducer() {}
+
+   protected:
+    // Activates |spdy_stream| in |spdy_session|.
+    static void ActivateStream(SpdySession* spdy_session,
+                               SpdyStream* spdy_stream);
+
+    static SpdyIOBuffer* CreateIOBuffer(SpdyFrame* frame,
+                                        RequestPriority priority,
+                                        SpdyStream* spdy_stream);
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(SpdyIOBufferProducer);
+  };
+
+  // Create a new SpdySession.
+  // |host_port_proxy_pair| is the host/port that this session connects to, and
+  // the proxy configuration settings that it's using.
+  // |spdy_session_pool| is the SpdySessionPool that owns us.  Its lifetime must
+  // strictly be greater than |this|.
+  // |session| is the HttpNetworkSession.  |net_log| is the NetLog that we log
+  // network events to.
+  SpdySession(const HostPortProxyPair& host_port_proxy_pair,
+              SpdySessionPool* spdy_session_pool,
+              HttpServerProperties* http_server_properties,
+              bool verify_domain_authentication,
+              bool enable_sending_initial_settings,
+              bool enable_credential_frames,
+              bool enable_compression,
+              bool enable_ping_based_connection_checking,
+              NextProto default_protocol_,
+              size_t initial_recv_window_size,
+              size_t initial_max_concurrent_streams,
+              size_t max_concurrent_streams_limit,
+              TimeFunc time_func,
+              const HostPortPair& trusted_spdy_proxy,
+              NetLog* net_log);
+
+  const HostPortPair& host_port_pair() const {
+    return host_port_proxy_pair_.first;
+  }
+  const HostPortProxyPair& host_port_proxy_pair() const {
+    return host_port_proxy_pair_;
+  }
+
+  // Get a pushed stream for a given |url|.
+  // If the server initiates a stream, it might already exist for a given path.
+  // The server might also not have initiated the stream yet, but indicated it
+  // will via X-Associated-Content.  Writes the stream out to |spdy_stream|.
+  // Returns a net error code.
+  int GetPushStream(
+      const GURL& url,
+      scoped_refptr<SpdyStream>* spdy_stream,
+      const BoundNetLog& stream_net_log);
+
+  // Create a new stream for a given |url|.  Writes it out to |spdy_stream|.
+  // Returns a net error code, possibly ERR_IO_PENDING.
+  int CreateStream(
+      const GURL& url,
+      RequestPriority priority,
+      scoped_refptr<SpdyStream>* spdy_stream,
+      const BoundNetLog& stream_net_log,
+      const CompletionCallback& callback);
+
+  // Remove PendingCreateStream objects on transaction deletion
+  void CancelPendingCreateStreams(const scoped_refptr<SpdyStream>* spdy_stream);
+
+  // Used by SpdySessionPool to initialize with a pre-existing SSL socket. For
+  // testing, setting is_secure to false allows initialization with a
+  // pre-existing TCP socket.
+  // Returns OK on success, or an error on failure.
+  net::Error InitializeWithSocket(ClientSocketHandle* connection,
+                                  bool is_secure,
+                                  int certificate_error_code);
+
+  // Check to see if this SPDY session can support an additional domain.
+  // If the session is un-authenticated, then this call always returns true.
+  // For SSL-based sessions, verifies that the server certificate in use by
+  // this session provides authentication for the domain and no client
+  // certificate or channel ID was sent to the original server during the SSL
+  // handshake.  NOTE:  This function can have false negatives on some
+  // platforms.
+  // TODO(wtc): rename this function and the Net.SpdyIPPoolDomainMatch
+  // histogram because this function does more than verifying domain
+  // authentication now.
+  bool VerifyDomainAuthentication(const std::string& domain);
+
+  // Records that |stream| has a write available from |producer|.
+  // |producer| will be owned by this SpdySession.
+  void SetStreamHasWriteAvailable(SpdyStream* stream,
+                                  SpdyIOBufferProducer* producer);
+
+  // Send the SYN frame for |stream_id|. This also sends PING message to check
+  // the status of the connection.
+  SpdySynStreamControlFrame* CreateSynStream(
+      SpdyStreamId stream_id,
+      RequestPriority priority,
+      uint8 credential_slot,
+      SpdyControlFlags flags,
+      const SpdyHeaderBlock& headers);
+
+  // Write a CREDENTIAL frame to the session.
+  SpdyCredentialControlFrame* CreateCredentialFrame(const std::string& origin,
+                                                    SSLClientCertType type,
+                                                    const std::string& key,
+                                                    const std::string& cert,
+                                                    RequestPriority priority);
+
+  // Write a HEADERS frame to the stream.
+  SpdyHeadersControlFrame* CreateHeadersFrame(SpdyStreamId stream_id,
+                                              const SpdyHeaderBlock& headers,
+                                              SpdyControlFlags flags);
+
+  // Write a data frame to the stream.
+  // Used to create and queue a data frame for the given stream.
+  SpdyDataFrame* CreateDataFrame(SpdyStreamId stream_id,
+                                 net::IOBuffer* data, int len,
+                                 SpdyDataFlags flags);
+
+  // Close a stream.
+  void CloseStream(SpdyStreamId stream_id, int status);
+
+  // Close a stream that has been created but is not yet active.
+  void CloseCreatedStream(SpdyStream* stream, 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(SpdyStreamId stream_id,
+                   SpdyStatusCodes status,
+                   const std::string& description);
+
+  // Check if a stream is active.
+  bool IsStreamActive(SpdyStreamId stream_id) const;
+
+  // The LoadState is used for informing the user of the current network
+  // status, such as "resolving host", "connecting", etc.
+  LoadState GetLoadState() const;
+
+  // Fills SSL info in |ssl_info| and returns true when SSL is in use.
+  bool GetSSLInfo(SSLInfo* ssl_info,
+                  bool* was_npn_negotiated,
+                  NextProto* protocol_negotiated);
+
+  // Fills SSL Certificate Request info |cert_request_info| and returns
+  // true when SSL is in use.
+  bool GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info);
+
+  // Returns the ServerBoundCertService used by this Socket, or NULL
+  // if server bound certs are not supported in this session.
+  ServerBoundCertService* GetServerBoundCertService() const;
+
+  // Send WINDOW_UPDATE frame, called by a stream whenever receive window
+  // size is increased.
+  void SendWindowUpdate(SpdyStreamId stream_id, int32 delta_window_size);
+
+  // If session is closed, no new streams/transactions should be created.
+  bool IsClosed() const { return state_ == CLOSED; }
+
+  // Closes this session.  This will close all active streams and mark
+  // the session as permanently closed.
+  // |err| should not be OK; this function is intended to be called on
+  // error.
+  // |remove_from_pool| indicates whether to also remove the session from the
+  // session pool.
+  // |description| indicates the reason for the error.
+  void CloseSessionOnError(net::Error err,
+                           bool remove_from_pool,
+                           const std::string& description);
+
+  // Retrieves information on the current state of the SPDY session as a
+  // Value.  Caller takes possession of the returned value.
+  base::Value* GetInfoAsValue() const;
+
+  // Indicates whether the session is being reused after having successfully
+  // used to send/receive data in the past.
+  bool IsReused() const;
+
+  // Returns true if the underlying transport socket ever had any reads or
+  // writes.
+  bool WasEverUsed() const {
+    return connection_->socket()->WasEverUsed();
+  }
+
+  void set_spdy_session_pool(SpdySessionPool* pool) {
+    spdy_session_pool_ = NULL;
+  }
+
+  // Returns true if session is not currently active
+  bool is_active() const {
+    return !active_streams_.empty() || !created_streams_.empty();
+  }
+
+  // Access to the number of active and pending streams.  These are primarily
+  // available for testing and diagnostics.
+  size_t num_active_streams() const { return active_streams_.size(); }
+  size_t num_unclaimed_pushed_streams() const {
+      return unclaimed_pushed_streams_.size();
+  }
+  size_t num_created_streams() const { return created_streams_.size(); }
+
+  size_t pending_create_stream_queues(int priority) {
+    DCHECK_LT(priority, NUM_PRIORITIES);
+    return pending_create_stream_queues_[priority].size();
+  }
+
+  // Returns true if flow control is enabled for the session.
+  bool is_flow_control_enabled() const {
+    return flow_control_;
+  }
+
+  // Returns the current |initial_send_window_size_|.
+  int32 initial_send_window_size() const {
+    return initial_send_window_size_;
+  }
+
+  // Returns the current |initial_recv_window_size_|.
+  int32 initial_recv_window_size() const { return initial_recv_window_size_; }
+
+  // Sets |initial_recv_window_size_| used by unittests.
+  void set_initial_recv_window_size(int32 window_size) {
+    initial_recv_window_size_ = window_size;
+  }
+
+  const BoundNetLog& net_log() const { return net_log_; }
+
+  int GetPeerAddress(IPEndPoint* address) const;
+  int GetLocalAddress(IPEndPoint* address) const;
+
+  // Returns true if requests on this session require credentials.
+  bool NeedsCredentials() const;
+
+  SpdyCredentialState* credential_state() { return &credential_state_; }
+
+  // Adds |alias| to set of aliases associated with this session.
+  void AddPooledAlias(const HostPortProxyPair& alias);
+
+  // Returns the set of aliases associated with this session.
+  const std::set<HostPortProxyPair>& pooled_aliases() const {
+    return pooled_aliases_;
+  }
+
+  int GetProtocolVersion() const;
+
+ private:
+  friend class base::RefCounted<SpdySession>;
+
+  // Allow tests to access our innards for testing purposes.
+  FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy2Test, ClientPing);
+  FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy2Test, FailedPing);
+  FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy2Test, GetActivePushStream);
+  FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy2Test, DeleteExpiredPushStreams);
+  FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy3Test, ClientPing);
+  FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy3Test, FailedPing);
+  FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy3Test, GetActivePushStream);
+  FRIEND_TEST_ALL_PREFIXES(SpdySessionSpdy3Test, DeleteExpiredPushStreams);
+
+  struct PendingCreateStream {
+    PendingCreateStream(const GURL& url, RequestPriority priority,
+                        scoped_refptr<SpdyStream>* spdy_stream,
+                        const BoundNetLog& stream_net_log,
+                        const CompletionCallback& callback);
+
+    ~PendingCreateStream();
+
+    const GURL* url;
+    RequestPriority priority;
+    scoped_refptr<SpdyStream>* spdy_stream;
+    const BoundNetLog* stream_net_log;
+    CompletionCallback callback;
+  };
+  typedef std::queue<PendingCreateStream, std::list<PendingCreateStream> >
+      PendingCreateStreamQueue;
+  typedef std::map<int, scoped_refptr<SpdyStream> > ActiveStreamMap;
+  typedef std::map<std::string,
+      std::pair<scoped_refptr<SpdyStream>, base::TimeTicks> > PushedStreamMap;
+  typedef std::priority_queue<SpdyIOBuffer> OutputQueue;
+
+  typedef std::set<scoped_refptr<SpdyStream> > CreatedStreamSet;
+  typedef std::map<SpdyIOBufferProducer*, SpdyStream*> StreamProducerMap;
+  class SpdyIOBufferProducerCompare {
+   public:
+    bool operator() (const SpdyIOBufferProducer* lhs,
+                     const SpdyIOBufferProducer* rhs) const {
+      return lhs->GetPriority() < rhs->GetPriority();
+    }
+  };
+  typedef std::priority_queue<SpdyIOBufferProducer*,
+                              std::vector<SpdyIOBufferProducer*>,
+                              SpdyIOBufferProducerCompare> WriteQueue;
+
+  struct CallbackResultPair {
+    CallbackResultPair(const CompletionCallback& callback_in, int result_in);
+    ~CallbackResultPair();
+
+    CompletionCallback callback;
+    int result;
+  };
+
+  typedef std::map<const scoped_refptr<SpdyStream>*, CallbackResultPair>
+      PendingCallbackMap;
+
+  enum State {
+    IDLE,
+    CONNECTING,
+    CONNECTED,
+    CLOSED
+  };
+
+  virtual ~SpdySession();
+
+  void ProcessPendingCreateStreams();
+  int CreateStreamImpl(
+      const GURL& url,
+      RequestPriority priority,
+      scoped_refptr<SpdyStream>* spdy_stream,
+      const BoundNetLog& stream_net_log);
+
+  // IO Callbacks
+  void OnReadComplete(int result);
+  void OnWriteComplete(int result);
+
+  // Send relevant SETTINGS.  This is generally called on connection setup.
+  void SendInitialSettings();
+
+  // Helper method to send SETTINGS a frame.
+  void SendSettings(const SettingsMap& settings);
+
+  // Handle SETTING.  Either when we send settings, or when we receive a
+  // SETTINGS control frame, update our SpdySession accordingly.
+  void HandleSetting(uint32 id, uint32 value);
+
+  // Adjust the send window size of all ActiveStreams and PendingCreateStreams.
+  void UpdateStreamsSendWindowSize(int32 delta_window_size);
+
+  // Send the PING (preface-PING) frame.
+  void SendPrefacePingIfNoneInFlight();
+
+  // Send PING if there are no PINGs in flight and we haven't heard from server.
+  void SendPrefacePing();
+
+  // Send the PING frame.
+  void WritePingFrame(uint32 unique_id);
+
+  // Post a CheckPingStatus call after delay. Don't post if there is already
+  // CheckPingStatus running.
+  void PlanToCheckPingStatus();
+
+  // Check the status of the connection. It calls |CloseSessionOnError| if we
+  // haven't received any data in |kHungInterval| time period.
+  void CheckPingStatus(base::TimeTicks last_check_time);
+
+  // Start reading from the socket.
+  // Returns OK on success, or an error on failure.
+  net::Error ReadSocket();
+
+  // Write current data to the socket.
+  void WriteSocketLater();
+  void WriteSocket();
+
+  // Get a new stream id.
+  int GetNewStreamId();
+
+  // Queue a frame for sending.
+  // |frame| is the frame to send.
+  // |priority| is the priority for insertion into the queue.
+  void QueueFrame(SpdyFrame* frame, RequestPriority priority);
+
+  // Track active streams in the active stream list.
+  void ActivateStream(SpdyStream* stream);
+  void DeleteStream(SpdyStreamId id, int status);
+
+  // Removes this session from the session pool.
+  void RemoveFromPool();
+
+  // Check if we have a pending pushed-stream for this url
+  // Returns the stream if found (and returns it from the pending
+  // list), returns NULL otherwise.
+  scoped_refptr<SpdyStream> GetActivePushStream(const std::string& url);
+
+  // Calls OnResponseReceived().
+  // Returns true if successful.
+  bool Respond(const SpdyHeaderBlock& headers,
+               const scoped_refptr<SpdyStream> stream);
+
+  void RecordPingRTTHistogram(base::TimeDelta duration);
+  void RecordHistograms();
+  void RecordProtocolErrorHistogram(SpdyProtocolErrorDetails details);
+
+  // Closes all streams.  Used as part of shutdown.
+  void CloseAllStreams(net::Error status);
+
+  void LogAbandonedStream(const scoped_refptr<SpdyStream>& stream,
+                          net::Error status);
+
+  // Invokes a user callback for stream creation.  We provide this method so it
+  // can be deferred to the MessageLoop, so we avoid re-entrancy problems.
+  void InvokeUserStreamCreationCallback(scoped_refptr<SpdyStream>* stream);
+
+  // Remove old unclaimed pushed streams.
+  void DeleteExpiredPushedStreams();
+
+  // BufferedSpdyFramerVisitorInterface:
+  virtual void OnError(SpdyFramer::SpdyError error_code) OVERRIDE;
+  virtual void OnStreamError(SpdyStreamId stream_id,
+                             const std::string& description) OVERRIDE;
+  virtual void OnPing(uint32 unique_id) OVERRIDE;
+  virtual void OnRstStream(SpdyStreamId stream_id,
+                           SpdyStatusCodes status) OVERRIDE;
+  virtual void OnGoAway(SpdyStreamId last_accepted_stream_id,
+                        SpdyGoAwayStatus status) OVERRIDE;
+  virtual void OnStreamFrameData(SpdyStreamId stream_id,
+                                 const char* data,
+                                 size_t len,
+                                 SpdyDataFlags flags) OVERRIDE;
+  virtual void OnSetting(
+      SpdySettingsIds id, uint8 flags, uint32 value) OVERRIDE;
+  virtual void OnWindowUpdate(SpdyStreamId stream_id,
+                              int delta_window_size) OVERRIDE;
+  virtual void OnControlFrameCompressed(
+      const SpdyControlFrame& uncompressed_frame,
+      const SpdyControlFrame& compressed_frame) OVERRIDE;
+  virtual void OnSynStream(SpdyStreamId stream_id,
+                           SpdyStreamId associated_stream_id,
+                           SpdyPriority priority,
+                           uint8 credential_slot,
+                           bool fin,
+                           bool unidirectional,
+                           const SpdyHeaderBlock& headers) OVERRIDE;
+  virtual void OnSynReply(
+      SpdyStreamId stream_id,
+      bool fin,
+      const SpdyHeaderBlock& headers) OVERRIDE;
+  virtual void OnHeaders(
+      SpdyStreamId stream_id,
+      bool fin,
+      const SpdyHeaderBlock& headers) OVERRIDE;
+
+  // --------------------------
+  // Helper methods for testing
+  // --------------------------
+
+  void set_connection_at_risk_of_loss_time(base::TimeDelta duration) {
+    connection_at_risk_of_loss_time_ = duration;
+  }
+
+  void set_hung_interval(base::TimeDelta duration) {
+    hung_interval_ = duration;
+  }
+
+  int64 pings_in_flight() const { return pings_in_flight_; }
+
+  uint32 next_ping_id() const { return next_ping_id_; }
+
+  base::TimeTicks last_activity_time() const { return last_activity_time_; }
+
+  bool check_ping_status_pending() const { return check_ping_status_pending_; }
+
+  // Returns the SSLClientSocket that this SPDY session sits on top of,
+  // or NULL, if the transport is not SSL.
+  SSLClientSocket* GetSSLClientSocket() const;
+
+  // Used for posting asynchronous IO tasks.  We use this even though
+  // SpdySession is refcounted because we don't need to keep the SpdySession
+  // alive if the last reference is within a RunnableMethod.  Just revoke the
+  // method.
+  base::WeakPtrFactory<SpdySession> weak_factory_;
+
+  // Map of the SpdyStreams for which we have a pending Task to invoke a
+  // callback.  This is necessary since, before we invoke said callback, it's
+  // possible that the request is cancelled.
+  PendingCallbackMap pending_callback_map_;
+
+  // The domain this session is connected to.
+  const HostPortProxyPair host_port_proxy_pair_;
+
+  // Set set of HostPortProxyPairs for which this session has serviced
+  // requests.
+  std::set<HostPortProxyPair> pooled_aliases_;
+
+  // |spdy_session_pool_| owns us, therefore its lifetime must exceed ours.  We
+  // set this to NULL after we are removed from the pool.
+  SpdySessionPool* spdy_session_pool_;
+  HttpServerProperties* const http_server_properties_;
+
+  // The socket handle for this session.
+  scoped_ptr<ClientSocketHandle> connection_;
+
+  // The read buffer used to read data from the socket.
+  scoped_refptr<IOBuffer> read_buffer_;
+  bool read_pending_;
+
+  int stream_hi_water_mark_;  // The next stream id to use.
+
+  // Queue, for each priority, of pending Create Streams that have not
+  // yet been satisfied
+  PendingCreateStreamQueue pending_create_stream_queues_[NUM_PRIORITIES];
+
+  // Map from stream id to all active streams.  Streams are active in the sense
+  // that they have a consumer (typically SpdyNetworkTransaction and regardless
+  // of whether or not there is currently any ongoing IO [might be waiting for
+  // the server to start pushing the stream]) or there are still network events
+  // incoming even though the consumer has already gone away (cancellation).
+  // TODO(willchan): Perhaps we should separate out cancelled streams and move
+  // them into a separate ActiveStreamMap, and not deliver network events to
+  // them?
+  ActiveStreamMap active_streams_;
+  // Map of all the streams that have already started to be pushed by the
+  // server, but do not have consumers yet.
+  PushedStreamMap unclaimed_pushed_streams_;
+
+  // Set of all created streams but that have not yet sent any frames.
+  CreatedStreamSet created_streams_;
+
+  // As streams have data to be sent, we put them into the write queue.
+  WriteQueue write_queue_;
+
+  // Mapping from SpdyIOBufferProducers to their corresponding SpdyStream
+  // so that when a stream is destroyed, we can remove the corresponding
+  // producer from |write_queue_|.
+  StreamProducerMap stream_producers_;
+
+  // The packet we are currently sending.
+  bool write_pending_;            // Will be true when a write is in progress.
+  SpdyIOBuffer in_flight_write_;  // This is the write buffer in progress.
+
+  // Flag if we have a pending message scheduled for WriteSocket.
+  bool delayed_write_pending_;
+
+  // Flag if we're using an SSL connection for this SpdySession.
+  bool is_secure_;
+
+  // Certificate error code when using a secure connection.
+  int certificate_error_code_;
+
+  // Spdy Frame state.
+  scoped_ptr<BufferedSpdyFramer> buffered_spdy_framer_;
+
+  // If an error has occurred on the session, the session is effectively
+  // dead.  Record this error here.  When no error has occurred, |error_| will
+  // be OK.
+  net::Error error_;
+  State state_;
+
+  // Limits
+  size_t max_concurrent_streams_;  // 0 if no limit
+  size_t max_concurrent_streams_limit_;
+
+  // Some statistics counters for the session.
+  int streams_initiated_count_;
+  int streams_pushed_count_;
+  int streams_pushed_and_claimed_count_;
+  int streams_abandoned_count_;
+  int bytes_received_;
+  bool sent_settings_;      // Did this session send settings when it started.
+  bool received_settings_;  // Did this session receive at least one settings
+                            // frame.
+  int stalled_streams_;     // Count of streams that were ever stalled.
+
+  // Count of all pings on the wire, for which we have not gotten a response.
+  int64 pings_in_flight_;
+
+  // This is the next ping_id (unique_id) to be sent in PING frame.
+  uint32 next_ping_id_;
+
+  // This is the last time we have sent a PING.
+  base::TimeTicks last_ping_sent_time_;
+
+  // This is the last time we had activity in the session.
+  base::TimeTicks last_activity_time_;
+
+  // This is the next time that unclaimed push streams should be checked for
+  // expirations.
+  base::TimeTicks next_unclaimed_push_stream_sweep_time_;
+
+  // Indicate if we have already scheduled a delayed task to check the ping
+  // status.
+  bool check_ping_status_pending_;
+
+  // Indicate if flow control is enabled or not.
+  bool flow_control_;
+
+  // Initial send window size for the session; can be changed by an
+  // arriving SETTINGS frame; newly created streams use this value for the
+  // initial send window size.
+  int32 initial_send_window_size_;
+
+  // Initial receive window size for the session; there are plans to add a
+  // command line switch that would cause a SETTINGS frame with window size
+  // announcement to be sent on startup; newly created streams will use
+  // this value for the initial receive window size.
+  int32 initial_recv_window_size_;
+
+  BoundNetLog net_log_;
+
+  // Outside of tests, these should always be true.
+  bool verify_domain_authentication_;
+  bool enable_sending_initial_settings_;
+  bool enable_credential_frames_;
+  bool enable_compression_;
+  bool enable_ping_based_connection_checking_;
+  NextProto default_protocol_;
+
+  SpdyCredentialState credential_state_;
+
+  // |connection_at_risk_of_loss_time_| is an optimization to avoid sending
+  // wasteful preface pings (when we just got some data).
+  //
+  // If it is zero (the most conservative figure), then we always send the
+  // preface ping (when none are in flight).
+  //
+  // It is common for TCP/IP sessions to time out in about 3-5 minutes.
+  // Certainly if it has been more than 3 minutes, we do want to send a preface
+  // ping.
+  //
+  // We don't think any connection will time out in under about 10 seconds. So
+  // this might as well be set to something conservative like 10 seconds. Later,
+  // we could adjust it to send fewer pings perhaps.
+  base::TimeDelta connection_at_risk_of_loss_time_;
+
+  // The amount of time that we are willing to tolerate with no activity (of any
+  // form), while there is a ping in flight, before we declare the connection to
+  // be hung. TODO(rtenneti): When hung, instead of resetting connection, race
+  // to build a new connection, and see if that completes before we (finally)
+  // get a PING response (http://crbug.com/127812).
+  base::TimeDelta hung_interval_;
+
+  // This SPDY proxy is allowed to push resources from origins that are
+  // different from those of their associated streams.
+  HostPortPair trusted_spdy_proxy_;
+
+  TimeFunc time_func_;
+};
+
+}  // namespace net
+
+#endif  // NET_SPDY_SPDY_SESSION_H_
diff --git a/src/net/spdy/spdy_session_pool.cc b/src/net/spdy/spdy_session_pool.cc
new file mode 100644
index 0000000..b967a46
--- /dev/null
+++ b/src/net/spdy/spdy_session_pool.cc
@@ -0,0 +1,497 @@
+// Copyright (c) 2012 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_session_pool.h"
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/values.h"
+#include "net/base/address_list.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_server_properties.h"
+#include "net/spdy/spdy_session.h"
+
+
+namespace net {
+
+namespace {
+
+enum SpdySessionGetTypes {
+  CREATED_NEW                 = 0,
+  FOUND_EXISTING              = 1,
+  FOUND_EXISTING_FROM_IP_POOL = 2,
+  IMPORTED_FROM_SOCKET        = 3,
+  SPDY_SESSION_GET_MAX        = 4
+};
+
+bool HostPortProxyPairsAreEqual(const HostPortProxyPair& a,
+                                const HostPortProxyPair& b) {
+  return a.first.Equals(b.first) && a.second == b.second;
+}
+
+}
+
+// The maximum number of sessions to open to a single domain.
+static const size_t kMaxSessionsPerDomain = 1;
+
+SpdySessionPool::SpdySessionPool(
+    HostResolver* resolver,
+    SSLConfigService* ssl_config_service,
+    HttpServerProperties* http_server_properties,
+    size_t max_sessions_per_domain,
+    bool force_single_domain,
+    bool enable_ip_pooling,
+    bool enable_credential_frames,
+    bool enable_compression,
+    bool enable_ping_based_connection_checking,
+    NextProto default_protocol,
+    size_t initial_recv_window_size,
+    size_t initial_max_concurrent_streams,
+    size_t max_concurrent_streams_limit,
+    SpdySessionPool::TimeFunc time_func,
+    const std::string& trusted_spdy_proxy)
+    : http_server_properties_(http_server_properties),
+      ssl_config_service_(ssl_config_service),
+      resolver_(resolver),
+      verify_domain_authentication_(true),
+      enable_sending_initial_settings_(true),
+      max_sessions_per_domain_(max_sessions_per_domain == 0 ?
+                               kMaxSessionsPerDomain :
+                               max_sessions_per_domain),
+      force_single_domain_(force_single_domain),
+      enable_ip_pooling_(enable_ip_pooling),
+      enable_credential_frames_(enable_credential_frames),
+      enable_compression_(enable_compression),
+      enable_ping_based_connection_checking_(
+          enable_ping_based_connection_checking),
+      default_protocol_(default_protocol),
+      initial_recv_window_size_(initial_recv_window_size),
+      initial_max_concurrent_streams_(initial_max_concurrent_streams),
+      max_concurrent_streams_limit_(max_concurrent_streams_limit),
+      time_func_(time_func),
+      trusted_spdy_proxy_(
+          HostPortPair::FromString(trusted_spdy_proxy)) {
+  NetworkChangeNotifier::AddIPAddressObserver(this);
+  if (ssl_config_service_)
+    ssl_config_service_->AddObserver(this);
+  CertDatabase::GetInstance()->AddObserver(this);
+}
+
+SpdySessionPool::~SpdySessionPool() {
+  CloseAllSessions();
+
+  if (ssl_config_service_)
+    ssl_config_service_->RemoveObserver(this);
+  NetworkChangeNotifier::RemoveIPAddressObserver(this);
+  CertDatabase::GetInstance()->RemoveObserver(this);
+}
+
+scoped_refptr<SpdySession> SpdySessionPool::Get(
+    const HostPortProxyPair& host_port_proxy_pair,
+    const BoundNetLog& net_log) {
+  return GetInternal(host_port_proxy_pair, net_log, false);
+}
+
+scoped_refptr<SpdySession> SpdySessionPool::GetIfExists(
+    const HostPortProxyPair& host_port_proxy_pair,
+    const BoundNetLog& net_log) {
+  return GetInternal(host_port_proxy_pair, net_log, true);
+}
+
+scoped_refptr<SpdySession> SpdySessionPool::GetInternal(
+    const HostPortProxyPair& host_port_proxy_pair,
+    const BoundNetLog& net_log,
+    bool only_use_existing_sessions) {
+  scoped_refptr<SpdySession> spdy_session;
+  SpdySessionList* list = GetSessionList(host_port_proxy_pair);
+  if (!list) {
+    // Check if we have a Session through a domain alias.
+    spdy_session = GetFromAlias(host_port_proxy_pair, net_log, true);
+    if (spdy_session) {
+      UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet",
+                                FOUND_EXISTING_FROM_IP_POOL,
+                                SPDY_SESSION_GET_MAX);
+      net_log.AddEvent(
+          NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION_FROM_IP_POOL,
+          spdy_session->net_log().source().ToEventParametersCallback());
+      // Add this session to the map so that we can find it next time.
+      list = AddSessionList(host_port_proxy_pair);
+      list->push_back(spdy_session);
+      spdy_session->AddPooledAlias(host_port_proxy_pair);
+      return spdy_session;
+    } else if (only_use_existing_sessions) {
+      return NULL;
+    }
+    list = AddSessionList(host_port_proxy_pair);
+  }
+
+  DCHECK(list);
+  if (list->size() && list->size() == max_sessions_per_domain_) {
+    UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet",
+                              FOUND_EXISTING,
+                              SPDY_SESSION_GET_MAX);
+    spdy_session = GetExistingSession(list, net_log);
+    net_log.AddEvent(
+      NetLog::TYPE_SPDY_SESSION_POOL_FOUND_EXISTING_SESSION,
+      spdy_session->net_log().source().ToEventParametersCallback());
+    return spdy_session;
+  }
+
+  DCHECK(!only_use_existing_sessions);
+
+  spdy_session = new SpdySession(host_port_proxy_pair, this,
+                                 http_server_properties_,
+                                 verify_domain_authentication_,
+                                 enable_sending_initial_settings_,
+                                 enable_credential_frames_,
+                                 enable_compression_,
+                                 enable_ping_based_connection_checking_,
+                                 default_protocol_,
+                                 initial_recv_window_size_,
+                                 initial_max_concurrent_streams_,
+                                 max_concurrent_streams_limit_,
+                                 time_func_,
+                                 trusted_spdy_proxy_,
+                                 net_log.net_log());
+  UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet",
+                            CREATED_NEW,
+                            SPDY_SESSION_GET_MAX);
+  list->push_back(spdy_session);
+  net_log.AddEvent(
+      NetLog::TYPE_SPDY_SESSION_POOL_CREATED_NEW_SESSION,
+      spdy_session->net_log().source().ToEventParametersCallback());
+  DCHECK_LE(list->size(), max_sessions_per_domain_);
+  return spdy_session;
+}
+
+net::Error SpdySessionPool::GetSpdySessionFromSocket(
+    const HostPortProxyPair& host_port_proxy_pair,
+    ClientSocketHandle* connection,
+    const BoundNetLog& net_log,
+    int certificate_error_code,
+    scoped_refptr<SpdySession>* spdy_session,
+    bool is_secure) {
+  UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet",
+                            IMPORTED_FROM_SOCKET,
+                            SPDY_SESSION_GET_MAX);
+  // Create the SPDY session and add it to the pool.
+  *spdy_session = new SpdySession(host_port_proxy_pair, this,
+                                  http_server_properties_,
+                                  verify_domain_authentication_,
+                                  enable_sending_initial_settings_,
+                                  enable_credential_frames_,
+                                  enable_compression_,
+                                  enable_ping_based_connection_checking_,
+                                  default_protocol_,
+                                  initial_recv_window_size_,
+                                  initial_max_concurrent_streams_,
+                                  max_concurrent_streams_limit_,
+                                  time_func_,
+                                  trusted_spdy_proxy_,
+                                  net_log.net_log());
+  SpdySessionList* list = GetSessionList(host_port_proxy_pair);
+  if (!list)
+    list = AddSessionList(host_port_proxy_pair);
+  DCHECK(list->empty());
+  list->push_back(*spdy_session);
+
+  net_log.AddEvent(
+      NetLog::TYPE_SPDY_SESSION_POOL_IMPORTED_SESSION_FROM_SOCKET,
+      (*spdy_session)->net_log().source().ToEventParametersCallback());
+
+  // We have a new session.  Lookup the IP address for this session so that we
+  // can match future Sessions (potentially to different domains) which can
+  // potentially be pooled with this one. Because GetPeerAddress() reports the
+  // proxy's address instead of the origin server, check to see if this is a
+  // direct connection.
+  if (enable_ip_pooling_  && host_port_proxy_pair.second.is_direct()) {
+    IPEndPoint address;
+    if (connection->socket()->GetPeerAddress(&address) == OK)
+      AddAlias(address, host_port_proxy_pair);
+  }
+
+  // Now we can initialize the session with the SSL socket.
+  return (*spdy_session)->InitializeWithSocket(connection, is_secure,
+                                               certificate_error_code);
+}
+
+bool SpdySessionPool::HasSession(
+    const HostPortProxyPair& host_port_proxy_pair) const {
+  if (GetSessionList(host_port_proxy_pair))
+    return true;
+
+  // Check if we have a session via an alias.
+  scoped_refptr<SpdySession> spdy_session =
+      GetFromAlias(host_port_proxy_pair, BoundNetLog(), false);
+  return spdy_session.get() != NULL;
+}
+
+void SpdySessionPool::Remove(const scoped_refptr<SpdySession>& session) {
+  bool ok = RemoveFromSessionList(session, session->host_port_proxy_pair());
+  DCHECK(ok);
+  session->net_log().AddEvent(
+      NetLog::TYPE_SPDY_SESSION_POOL_REMOVE_SESSION,
+      session->net_log().source().ToEventParametersCallback());
+
+  const std::set<HostPortProxyPair>& aliases = session->pooled_aliases();
+  for (std::set<HostPortProxyPair>::const_iterator it = aliases.begin();
+       it != aliases.end(); ++it) {
+    ok = RemoveFromSessionList(session, *it);
+    DCHECK(ok);
+  }
+}
+
+bool SpdySessionPool::RemoveFromSessionList(
+    const scoped_refptr<SpdySession>& session,
+    const HostPortProxyPair& pair) {
+  SpdySessionList* list = GetSessionList(pair);
+  if (!list)
+    return false;
+  list->remove(session);
+  if (list->empty())
+    RemoveSessionList(pair);
+  return true;
+}
+
+Value* SpdySessionPool::SpdySessionPoolInfoToValue() const {
+  ListValue* list = new ListValue();
+
+  for (SpdySessionsMap::const_iterator it = sessions_.begin();
+       it != sessions_.end(); ++it) {
+    SpdySessionList* sessions = it->second;
+    for (SpdySessionList::const_iterator session = sessions->begin();
+         session != sessions->end(); ++session) {
+      // Only add the session if the key in the map matches the main
+      // host_port_proxy_pair (not an alias).
+      const HostPortProxyPair& key = it->first;
+      const HostPortProxyPair& pair = session->get()->host_port_proxy_pair();
+      if (key.first.Equals(pair.first) && key.second == pair.second)
+        list->Append(session->get()->GetInfoAsValue());
+    }
+  }
+  return list;
+}
+
+void SpdySessionPool::OnIPAddressChanged() {
+  CloseCurrentSessions(ERR_NETWORK_CHANGED);
+  http_server_properties_->ClearSpdySettings();
+}
+
+void SpdySessionPool::OnSSLConfigChanged() {
+  CloseCurrentSessions(ERR_NETWORK_CHANGED);
+}
+
+scoped_refptr<SpdySession> SpdySessionPool::GetExistingSession(
+    SpdySessionList* list,
+    const BoundNetLog& net_log) const {
+  DCHECK(list);
+  DCHECK_LT(0u, list->size());
+  scoped_refptr<SpdySession> spdy_session = list->front();
+  if (list->size() > 1) {
+    list->pop_front();  // Rotate the list.
+    list->push_back(spdy_session);
+  }
+
+  return spdy_session;
+}
+
+scoped_refptr<SpdySession> SpdySessionPool::GetFromAlias(
+      const HostPortProxyPair& host_port_proxy_pair,
+      const BoundNetLog& net_log,
+      bool record_histograms) const {
+  // We should only be checking aliases when there is no direct session.
+  DCHECK(!GetSessionList(host_port_proxy_pair));
+
+  if (!enable_ip_pooling_)
+    return NULL;
+
+  AddressList addresses;
+  if (!LookupAddresses(host_port_proxy_pair, net_log, &addresses))
+    return NULL;
+  for (AddressList::const_iterator iter = addresses.begin();
+       iter != addresses.end();
+       ++iter) {
+    SpdyAliasMap::const_iterator alias_iter = aliases_.find(*iter);
+    if (alias_iter == aliases_.end())
+      continue;
+
+    // We found an alias.
+    const HostPortProxyPair& alias_pair = alias_iter->second;
+
+    // If the proxy settings match, we can reuse this session.
+    if (!(alias_pair.second == host_port_proxy_pair.second))
+      continue;
+
+    SpdySessionList* list = GetSessionList(alias_pair);
+    if (!list) {
+      NOTREACHED();  // It shouldn't be in the aliases table if we can't get it!
+      continue;
+    }
+
+    scoped_refptr<SpdySession> spdy_session = GetExistingSession(list, net_log);
+    // If the SPDY session is a secure one, we need to verify that the server
+    // is authenticated to serve traffic for |host_port_proxy_pair| too.
+    if (!spdy_session->VerifyDomainAuthentication(
+            host_port_proxy_pair.first.host())) {
+      if (record_histograms)
+        UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 0, 2);
+      continue;
+    }
+    if (record_histograms)
+      UMA_HISTOGRAM_ENUMERATION("Net.SpdyIPPoolDomainMatch", 1, 2);
+    return spdy_session;
+  }
+  return NULL;
+}
+
+void SpdySessionPool::OnCertAdded(const X509Certificate* cert) {
+  CloseCurrentSessions(ERR_NETWORK_CHANGED);
+}
+
+void SpdySessionPool::OnCertTrustChanged(const X509Certificate* cert) {
+  // Per wtc, we actually only need to CloseCurrentSessions when trust is
+  // reduced. CloseCurrentSessions now because OnCertTrustChanged does not
+  // tell us this.
+  // See comments in ClientSocketPoolManager::OnCertTrustChanged.
+  CloseCurrentSessions(ERR_NETWORK_CHANGED);
+}
+
+const HostPortProxyPair& SpdySessionPool::NormalizeListPair(
+    const HostPortProxyPair& host_port_proxy_pair) const {
+  if (!force_single_domain_)
+    return host_port_proxy_pair;
+
+  static HostPortProxyPair* single_domain_pair = NULL;
+  if (!single_domain_pair) {
+    HostPortPair single_domain = HostPortPair("singledomain.com", 80);
+    single_domain_pair = new HostPortProxyPair(single_domain,
+                                               ProxyServer::Direct());
+  }
+  return *single_domain_pair;
+}
+
+SpdySessionPool::SpdySessionList*
+    SpdySessionPool::AddSessionList(
+        const HostPortProxyPair& host_port_proxy_pair) {
+  const HostPortProxyPair& pair = NormalizeListPair(host_port_proxy_pair);
+  DCHECK(sessions_.find(pair) == sessions_.end());
+  SpdySessionPool::SpdySessionList* list = new SpdySessionList();
+  sessions_[pair] = list;
+  return list;
+}
+
+SpdySessionPool::SpdySessionList*
+    SpdySessionPool::GetSessionList(
+        const HostPortProxyPair& host_port_proxy_pair) const {
+  const HostPortProxyPair& pair = NormalizeListPair(host_port_proxy_pair);
+  SpdySessionsMap::const_iterator it = sessions_.find(pair);
+  if (it != sessions_.end())
+    return it->second;
+  return NULL;
+}
+
+void SpdySessionPool::RemoveSessionList(
+    const HostPortProxyPair& host_port_proxy_pair) {
+  const HostPortProxyPair& pair = NormalizeListPair(host_port_proxy_pair);
+  SpdySessionList* list = GetSessionList(pair);
+  if (list) {
+    delete list;
+    sessions_.erase(pair);
+  } else {
+    DCHECK(false) << "removing orphaned session list";
+  }
+  RemoveAliases(host_port_proxy_pair);
+}
+
+bool SpdySessionPool::LookupAddresses(const HostPortProxyPair& pair,
+                                      const BoundNetLog& net_log,
+                                      AddressList* addresses) const {
+  net::HostResolver::RequestInfo resolve_info(pair.first);
+  int rv = resolver_->ResolveFromCache(resolve_info, addresses, net_log);
+  DCHECK_NE(ERR_IO_PENDING, rv);
+  return rv == OK;
+}
+
+void SpdySessionPool::AddAlias(const IPEndPoint& endpoint,
+                               const HostPortProxyPair& pair) {
+  DCHECK(enable_ip_pooling_);
+  aliases_[endpoint] = pair;
+}
+
+void SpdySessionPool::RemoveAliases(const HostPortProxyPair& pair) {
+  // Walk the aliases map, find references to this pair.
+  // TODO(mbelshe):  Figure out if this is too expensive.
+  SpdyAliasMap::iterator alias_it = aliases_.begin();
+  while (alias_it != aliases_.end()) {
+    if (HostPortProxyPairsAreEqual(alias_it->second, pair)) {
+      aliases_.erase(alias_it);
+      alias_it = aliases_.begin();  // Iterator was invalidated.
+      continue;
+    }
+    ++alias_it;
+  }
+}
+
+void SpdySessionPool::CloseAllSessions() {
+  while (!sessions_.empty()) {
+    SpdySessionList* list = sessions_.begin()->second;
+    CHECK(list);
+    const scoped_refptr<SpdySession>& session = list->front();
+    CHECK(session);
+    // This call takes care of removing the session from the pool, as well as
+    // removing the session list if the list is empty.
+    session->CloseSessionOnError(
+        net::ERR_ABORTED, true, "Closing all sessions.");
+  }
+}
+
+void SpdySessionPool::CloseCurrentSessions(net::Error error) {
+  SpdySessionsMap old_map;
+  old_map.swap(sessions_);
+  for (SpdySessionsMap::const_iterator it = old_map.begin();
+       it != old_map.end(); ++it) {
+    SpdySessionList* list = it->second;
+    CHECK(list);
+    const scoped_refptr<SpdySession>& session = list->front();
+    CHECK(session);
+    session->set_spdy_session_pool(NULL);
+  }
+
+  while (!old_map.empty()) {
+    SpdySessionList* list = old_map.begin()->second;
+    CHECK(list);
+    const scoped_refptr<SpdySession>& session = list->front();
+    CHECK(session);
+    session->CloseSessionOnError(error, false, "Closing current sessions.");
+    list->pop_front();
+    if (list->empty()) {
+      delete list;
+      RemoveAliases(old_map.begin()->first);
+      old_map.erase(old_map.begin()->first);
+    }
+  }
+  DCHECK(sessions_.empty());
+  DCHECK(aliases_.empty());
+}
+
+void SpdySessionPool::CloseIdleSessions() {
+  SpdySessionsMap::const_iterator map_it = sessions_.begin();
+  while (map_it != sessions_.end()) {
+    SpdySessionList* list = map_it->second;
+    ++map_it;
+    CHECK(list);
+
+    // Assumes there is only 1 element in the list
+    SpdySessionList::iterator session_it = list->begin();
+    const scoped_refptr<SpdySession>& session = *session_it;
+    CHECK(session);
+    if (!session->is_active()) {
+      session->CloseSessionOnError(
+          net::ERR_ABORTED, true, "Closing idle sessions.");
+    }
+  }
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_session_pool.h b/src/net/spdy/spdy_session_pool.h
new file mode 100644
index 0000000..0467583
--- /dev/null
+++ b/src/net/spdy/spdy_session_pool.h
@@ -0,0 +1,230 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_SPDY_SPDY_SESSION_POOL_H_
+#define NET_SPDY_SPDY_SESSION_POOL_H_
+
+#include <map>
+#include <list>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "net/base/cert_database.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_export.h"
+#include "net/base/network_change_notifier.h"
+#include "net/base/ssl_config_service.h"
+#include "net/proxy/proxy_config.h"
+#include "net/proxy/proxy_server.h"
+#include "net/socket/next_proto.h"
+
+namespace net {
+
+class AddressList;
+class BoundNetLog;
+class ClientSocketHandle;
+class HostResolver;
+class HttpServerProperties;
+class SpdySession;
+
+namespace test_spdy2 {
+class SpdySessionPoolPeer;
+}  // namespace test_spdy
+
+namespace test_spdy3 {
+class SpdySessionPoolPeer;
+}  // namespace test_spdy
+
+// This is a very simple pool for open SpdySessions.
+class NET_EXPORT SpdySessionPool
+    : public NetworkChangeNotifier::IPAddressObserver,
+      public SSLConfigService::Observer,
+      public CertDatabase::Observer {
+ public:
+  typedef base::TimeTicks (*TimeFunc)(void);
+
+  SpdySessionPool(HostResolver* host_resolver,
+                  SSLConfigService* ssl_config_service,
+                  HttpServerProperties* http_server_properties,
+                  size_t max_sessions_per_domain,
+                  bool force_single_domain,
+                  bool enable_ip_pooling,
+                  bool enable_credential_frames,
+                  bool enable_compression,
+                  bool enable_ping_based_connection_checking,
+                  NextProto default_protocol,
+                  size_t default_initial_recv_window_size,
+                  size_t initial_max_concurrent_streams,
+                  size_t max_concurrent_streams_limit,
+                  SpdySessionPool::TimeFunc time_func,
+                  const std::string& trusted_spdy_proxy);
+  virtual ~SpdySessionPool();
+
+  // Either returns an existing SpdySession or creates a new SpdySession for
+  // use.
+  scoped_refptr<SpdySession> Get(
+      const HostPortProxyPair& host_port_proxy_pair,
+      const BoundNetLog& net_log);
+
+  // Only returns a SpdySession if it already exists.
+  scoped_refptr<SpdySession> GetIfExists(
+      const HostPortProxyPair& host_port_proxy_pair,
+      const BoundNetLog& net_log);
+
+  // Builds a SpdySession from an existing SSL socket.  Users should try
+  // calling Get() first to use an existing SpdySession so we don't get
+  // multiple SpdySessions per domain.  Note that ownership of |connection| is
+  // transferred from the caller to the SpdySession.
+  // |certificate_error_code| is used to indicate the certificate error
+  // encountered when connecting the SSL socket.  OK means there was no error.
+  // For testing, setting is_secure to false allows Spdy to connect with a
+  // pre-existing TCP socket.
+  // Returns OK on success, and the |spdy_session| will be provided.
+  // Returns an error on failure, and |spdy_session| will be NULL.
+  net::Error GetSpdySessionFromSocket(
+      const HostPortProxyPair& host_port_proxy_pair,
+      ClientSocketHandle* connection,
+      const BoundNetLog& net_log,
+      int certificate_error_code,
+      scoped_refptr<SpdySession>* spdy_session,
+      bool is_secure);
+
+  // TODO(willchan): Consider renaming to HasReusableSession, since perhaps we
+  // should be creating a new session. WARNING: Because of IP connection pooling
+  // using the HostCache, if HasSession() returns true at one point, it does not
+  // imply the SpdySessionPool will still have a matching session in the near
+  // future, since the HostCache's entry may have expired.
+  bool HasSession(const HostPortProxyPair& host_port_proxy_pair) const;
+
+  // Close all SpdySessions, including any new ones created in the process of
+  // closing the current ones.
+  void CloseAllSessions();
+  // Close only the currently existing SpdySessions with |error|.
+  // Let any new ones created continue to live.
+  void CloseCurrentSessions(net::Error error);
+  // Close only the idle SpdySessions.
+  void CloseIdleSessions();
+
+  // Removes a SpdySession from the SpdySessionPool. This should only be called
+  // by SpdySession, because otherwise session->state_ is not set to CLOSED.
+  void Remove(const scoped_refptr<SpdySession>& session);
+
+  // Creates a Value summary of the state of the spdy session pool. The caller
+  // responsible for deleting the returned value.
+  base::Value* SpdySessionPoolInfoToValue() const;
+
+  HttpServerProperties* http_server_properties() {
+    return http_server_properties_;
+  }
+
+  // NetworkChangeNotifier::IPAddressObserver methods:
+
+  // We flush all idle sessions and release references to the active ones so
+  // they won't get re-used.  The active ones will either complete successfully
+  // or error out due to the IP address change.
+  virtual void OnIPAddressChanged() OVERRIDE;
+
+  // SSLConfigService::Observer methods:
+
+  // We perform the same flushing as described above when SSL settings change.
+  virtual void OnSSLConfigChanged() OVERRIDE;
+
+  // CertDatabase::Observer methods:
+  virtual void OnCertAdded(const X509Certificate* cert) OVERRIDE;
+  virtual void OnCertTrustChanged(const X509Certificate* cert) OVERRIDE;
+
+ private:
+  friend class test_spdy2::SpdySessionPoolPeer;  // For testing.
+  friend class test_spdy3::SpdySessionPoolPeer;  // For testing.
+  friend class SpdyNetworkTransactionSpdy2Test;  // For testing.
+  friend class SpdyNetworkTransactionSpdy3Test;  // For testing.
+  FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy2Test,
+                           WindowUpdateOverflow);
+  FRIEND_TEST_ALL_PREFIXES(SpdyNetworkTransactionSpdy3Test,
+                           WindowUpdateOverflow);
+
+  typedef std::list<scoped_refptr<SpdySession> > SpdySessionList;
+  typedef std::map<HostPortProxyPair, SpdySessionList*> SpdySessionsMap;
+  typedef std::map<IPEndPoint, HostPortProxyPair> SpdyAliasMap;
+
+
+  scoped_refptr<SpdySession> GetInternal(
+      const HostPortProxyPair& host_port_proxy_pair,
+      const BoundNetLog& net_log,
+      bool only_use_existing_sessions);
+  scoped_refptr<SpdySession> GetExistingSession(
+      SpdySessionList* list,
+      const BoundNetLog& net_log) const;
+  scoped_refptr<SpdySession> GetFromAlias(
+      const HostPortProxyPair& host_port_proxy_pair,
+      const BoundNetLog& net_log,
+      bool record_histograms) const;
+
+  // Helper functions for manipulating the lists.
+  const HostPortProxyPair& NormalizeListPair(
+      const HostPortProxyPair& host_port_proxy_pair) const;
+  SpdySessionList* AddSessionList(
+      const HostPortProxyPair& host_port_proxy_pair);
+  SpdySessionList* GetSessionList(
+      const HostPortProxyPair& host_port_proxy_pair) const;
+  void RemoveSessionList(const HostPortProxyPair& host_port_proxy_pair);
+
+  // Does a DNS cache lookup for |pair|, and returns the |addresses| found.
+  // Returns true if addresses found, false otherwise.
+  bool LookupAddresses(const HostPortProxyPair& pair,
+                       const BoundNetLog& net_log,
+                       AddressList* addresses) const;
+
+  // Add |address| as an IP-equivalent address for |pair|.
+  void AddAlias(const IPEndPoint& address, const HostPortProxyPair& pair);
+
+  // Remove all aliases for |pair| from the aliases table.
+  void RemoveAliases(const HostPortProxyPair& pair);
+
+  // Removes |session| from the session list associated with |pair|.
+  // Returns true if the session was removed, false otherwise.
+  bool RemoveFromSessionList(const scoped_refptr<SpdySession>& session,
+                             const HostPortProxyPair& pair);
+
+  HttpServerProperties* const http_server_properties_;
+
+  // This is our weak session pool - one session per domain.
+  SpdySessionsMap sessions_;
+  // A map of IPEndPoint aliases for sessions.
+  SpdyAliasMap aliases_;
+
+  static bool g_force_single_domain;
+
+  const scoped_refptr<SSLConfigService> ssl_config_service_;
+  HostResolver* const resolver_;
+
+  // Defaults to true. May be controlled via SpdySessionPoolPeer for tests.
+  bool verify_domain_authentication_;
+  bool enable_sending_initial_settings_;
+  size_t max_sessions_per_domain_;
+  bool force_single_domain_;
+  bool enable_ip_pooling_;
+  bool enable_credential_frames_;
+  bool enable_compression_;
+  bool enable_ping_based_connection_checking_;
+  NextProto default_protocol_;
+  size_t initial_recv_window_size_;
+  size_t initial_max_concurrent_streams_;
+  size_t max_concurrent_streams_limit_;
+  TimeFunc time_func_;
+
+  // This SPDY proxy is allowed to push resources from origins that are
+  // different from those of their associated streams.
+  HostPortPair trusted_spdy_proxy_;
+
+  DISALLOW_COPY_AND_ASSIGN(SpdySessionPool);
+};
+
+}  // namespace net
+
+#endif  // NET_SPDY_SPDY_SESSION_POOL_H_
diff --git a/src/net/spdy/spdy_session_spdy2_unittest.cc b/src/net/spdy/spdy_session_spdy2_unittest.cc
new file mode 100644
index 0000000..655676d
--- /dev/null
+++ b/src/net/spdy/spdy_session_spdy2_unittest.cc
@@ -0,0 +1,1871 @@
+// Copyright (c) 2012 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_session.h"
+
+#include "net/base/host_cache.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_log_unittest.h"
+#include "net/spdy/spdy_io_buffer.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/spdy/spdy_stream.h"
+#include "net/spdy/spdy_test_util_spdy2.h"
+#include "testing/platform_test.h"
+
+using namespace net::test_spdy2;
+
+namespace net {
+
+namespace {
+
+static int g_delta_seconds = 0;
+base::TimeTicks TheNearFuture() {
+  return base::TimeTicks::Now() + base::TimeDelta::FromSeconds(g_delta_seconds);
+}
+
+class ClosingDelegate : public SpdyStream::Delegate {
+ public:
+  ClosingDelegate(SpdyStream* stream) : stream_(stream) {}
+
+  // SpdyStream::Delegate implementation:
+  virtual bool OnSendHeadersComplete(int status) OVERRIDE {
+    return true;
+  }
+  virtual int OnSendBody() OVERRIDE {
+    return OK;
+  }
+  virtual int OnSendBodyComplete(int status, bool* eof) OVERRIDE {
+    return OK;
+  }
+  virtual int OnResponseReceived(const SpdyHeaderBlock& response,
+                                 base::Time response_time,
+                                 int status) OVERRIDE {
+    return OK;
+  }
+  virtual void OnHeadersSent() OVERRIDE {}
+  virtual int OnDataReceived(const char* data, int length) OVERRIDE {
+    return OK;
+  }
+  virtual void OnDataSent(int length) OVERRIDE {}
+  virtual void OnClose(int status) OVERRIDE {
+    stream_->Close();
+  }
+ private:
+  SpdyStream* stream_;
+};
+
+} // namespace
+
+class SpdySessionSpdy2Test : public PlatformTest {
+ protected:
+  void SetUp() {
+    g_delta_seconds = 0;
+  }
+};
+
+class TestSpdyStreamDelegate : public net::SpdyStream::Delegate {
+ public:
+  explicit TestSpdyStreamDelegate(const CompletionCallback& callback)
+      : callback_(callback) {}
+  virtual ~TestSpdyStreamDelegate() {}
+
+  virtual bool OnSendHeadersComplete(int status) OVERRIDE { return true; }
+
+  virtual int OnSendBody() OVERRIDE {
+    return ERR_UNEXPECTED;
+  }
+
+  virtual int OnSendBodyComplete(int /*status*/, bool* /*eof*/) OVERRIDE {
+    return ERR_UNEXPECTED;
+  }
+
+  virtual int OnResponseReceived(const SpdyHeaderBlock& response,
+                                 base::Time response_time,
+                                 int status) OVERRIDE {
+    return status;
+  }
+
+  virtual void OnHeadersSent() OVERRIDE {}
+
+  virtual int OnDataReceived(const char* buffer, int bytes) OVERRIDE {
+    return OK;
+  }
+
+  virtual void OnDataSent(int length) OVERRIDE {}
+
+  virtual void OnClose(int status) OVERRIDE {
+    CompletionCallback callback = callback_;
+    callback_.Reset();
+    callback.Run(OK);
+  }
+
+ private:
+  CompletionCallback callback_;
+};
+
+// Test the SpdyIOBuffer class.
+TEST_F(SpdySessionSpdy2Test, SpdyIOBuffer) {
+  std::priority_queue<SpdyIOBuffer> queue_;
+  const size_t kQueueSize = 100;
+
+  // Insert items with random priority and increasing buffer size.
+  for (size_t index = 0; index < kQueueSize; ++index) {
+    queue_.push(SpdyIOBuffer(
+        new IOBufferWithSize(index + 1),
+        index + 1,
+        static_cast<RequestPriority>(rand() % NUM_PRIORITIES),
+        NULL));
+  }
+
+  EXPECT_EQ(kQueueSize, queue_.size());
+
+  // Verify items come out with decreasing priority or FIFO order.
+  RequestPriority last_priority = NUM_PRIORITIES;
+  size_t last_size = 0;
+  for (size_t index = 0; index < kQueueSize; ++index) {
+    SpdyIOBuffer buffer = queue_.top();
+    EXPECT_LE(buffer.priority(), last_priority);
+    if (buffer.priority() < last_priority)
+      last_size = 0;
+    EXPECT_LT(last_size, buffer.size());
+    last_priority = buffer.priority();
+    last_size = buffer.size();
+    queue_.pop();
+  }
+
+  EXPECT_EQ(0u, queue_.size());
+}
+
+TEST_F(SpdySessionSpdy2Test, GoAway) {
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  scoped_ptr<SpdyFrame> goaway(ConstructSpdyGoAway());
+  MockRead reads[] = {
+    CreateMockRead(*goaway),
+    MockRead(SYNCHRONOUS, 0, 0)  // EOF
+  };
+  StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  ssl.SetNextProto(kProtoSPDY2);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+  EXPECT_EQ(2, session->GetProtocolVersion());
+
+  // Flush the SpdySession::OnReadComplete() task.
+  MessageLoop::current()->RunUntilIdle();
+
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<SpdySession> session2 =
+      spdy_session_pool->Get(pair, BoundNetLog());
+
+  // Delete the first session.
+  session = NULL;
+
+  // Delete the second session.
+  spdy_session_pool->Remove(session2);
+  session2 = NULL;
+}
+
+TEST_F(SpdySessionSpdy2Test, ClientPing) {
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  scoped_ptr<SpdyFrame> read_ping(ConstructSpdyPing(1));
+  MockRead reads[] = {
+    CreateMockRead(*read_ping),
+    MockRead(SYNCHRONOUS, 0, 0)  // EOF
+  };
+  scoped_ptr<SpdyFrame> write_ping(ConstructSpdyPing(1));
+  MockWrite writes[] = {
+    CreateMockWrite(*write_ping),
+  };
+  StaticSocketDataProvider data(
+      reads, arraysize(reads), writes, arraysize(writes));
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  static const char kStreamUrl[] = "http://www.google.com/";
+  GURL url(kStreamUrl);
+
+  const std::string kTestHost("www.google.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  scoped_refptr<SpdyStream> spdy_stream1;
+  TestCompletionCallback callback1;
+  EXPECT_EQ(OK, session->CreateStream(url,
+                                      MEDIUM,
+                                      &spdy_stream1,
+                                      BoundNetLog(),
+                                      callback1.callback()));
+  scoped_ptr<TestSpdyStreamDelegate> delegate(
+      new TestSpdyStreamDelegate(callback1.callback()));
+  spdy_stream1->SetDelegate(delegate.get());
+
+  base::TimeTicks before_ping_time = base::TimeTicks::Now();
+
+  session->set_connection_at_risk_of_loss_time(base::TimeDelta::FromSeconds(0));
+  session->set_hung_interval(base::TimeDelta::FromMilliseconds(50));
+
+  session->SendPrefacePingIfNoneInFlight();
+
+  EXPECT_EQ(OK, callback1.WaitForResult());
+
+  session->CheckPingStatus(before_ping_time);
+
+  EXPECT_EQ(0, session->pings_in_flight());
+  EXPECT_GE(session->next_ping_id(), static_cast<uint32>(1));
+  EXPECT_FALSE(session->check_ping_status_pending());
+  EXPECT_GE(session->last_activity_time(), before_ping_time);
+
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+
+  // Delete the first session.
+  session = NULL;
+}
+
+TEST_F(SpdySessionSpdy2Test, ServerPing) {
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  scoped_ptr<SpdyFrame> read_ping(ConstructSpdyPing(2));
+  MockRead reads[] = {
+    CreateMockRead(*read_ping),
+    MockRead(SYNCHRONOUS, 0, 0)  // EOF
+  };
+  scoped_ptr<SpdyFrame> write_ping(ConstructSpdyPing(2));
+  MockWrite writes[] = {
+    CreateMockWrite(*write_ping),
+  };
+  StaticSocketDataProvider data(
+      reads, arraysize(reads), writes, arraysize(writes));
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  static const char kStreamUrl[] = "http://www.google.com/";
+  GURL url(kStreamUrl);
+
+  const std::string kTestHost("www.google.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  scoped_refptr<SpdyStream> spdy_stream1;
+  TestCompletionCallback callback1;
+  EXPECT_EQ(OK, session->CreateStream(url,
+                                      MEDIUM,
+                                      &spdy_stream1,
+                                      BoundNetLog(),
+                                      callback1.callback()));
+  scoped_ptr<TestSpdyStreamDelegate> delegate(
+      new TestSpdyStreamDelegate(callback1.callback()));
+  spdy_stream1->SetDelegate(delegate.get());
+
+  // Flush the SpdySession::OnReadComplete() task.
+  MessageLoop::current()->RunUntilIdle();
+
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+
+  // Delete the session.
+  session = NULL;
+}
+
+TEST_F(SpdySessionSpdy2Test, DeleteExpiredPushStreams) {
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+  session_deps.time_func = TheNearFuture;
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  const std::string kTestHost("www.google.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+  // Give the session a SPDY2 framer.
+  session->buffered_spdy_framer_.reset(new BufferedSpdyFramer(2, false));
+
+  // Create the associated stream and add to active streams.
+  scoped_ptr<SpdyHeaderBlock> request_headers(new SpdyHeaderBlock);
+  (*request_headers)["scheme"] = "http";
+  (*request_headers)["host"] = "www.google.com";
+  (*request_headers)["url"] = "/";
+
+  scoped_refptr<SpdyStream> stream(
+      new SpdyStream(session, false, session->net_log_));
+  stream->set_spdy_headers(request_headers.Pass());
+  session->ActivateStream(stream);
+
+  SpdyHeaderBlock headers;
+  headers["url"] = "http://www.google.com/a.dat";
+  session->OnSynStream(2, 1, 0, 0, true, false, headers);
+
+  // Verify that there is one unclaimed push stream.
+  EXPECT_EQ(1u, session->num_unclaimed_pushed_streams());
+  SpdySession::PushedStreamMap::iterator  iter  =
+      session->unclaimed_pushed_streams_.find("http://www.google.com/a.dat");
+  EXPECT_TRUE(session->unclaimed_pushed_streams_.end() != iter);
+
+  // Shift time.
+  g_delta_seconds = 301;
+
+  headers["url"] = "http://www.google.com/b.dat";
+  session->OnSynStream(4, 1, 0, 0, true, false, headers);
+
+  // Verify that the second pushed stream evicted the first pushed stream.
+  EXPECT_EQ(1u, session->num_unclaimed_pushed_streams());
+  iter = session->unclaimed_pushed_streams_.find("http://www.google.com/b.dat");
+  EXPECT_TRUE(session->unclaimed_pushed_streams_.end() != iter);
+
+  // Delete the session.
+  session = NULL;
+}
+
+TEST_F(SpdySessionSpdy2Test, FailedPing) {
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  scoped_ptr<SpdyFrame> read_ping(ConstructSpdyPing(1));
+  MockRead reads[] = {
+    CreateMockRead(*read_ping),
+    MockRead(SYNCHRONOUS, 0, 0)  // EOF
+  };
+  scoped_ptr<SpdyFrame> write_ping(ConstructSpdyPing(1));
+  MockWrite writes[] = {
+    CreateMockWrite(*write_ping),
+  };
+  StaticSocketDataProvider data(
+      reads, arraysize(reads), writes, arraysize(writes));
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  static const char kStreamUrl[] = "http://www.gmail.com/";
+  GURL url(kStreamUrl);
+
+  const std::string kTestHost("www.gmail.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  scoped_refptr<SpdyStream> spdy_stream1;
+  TestCompletionCallback callback1;
+  EXPECT_EQ(OK, session->CreateStream(url,
+                                      MEDIUM,
+                                      &spdy_stream1,
+                                      BoundNetLog(),
+                                      callback1.callback()));
+  scoped_ptr<TestSpdyStreamDelegate> delegate(
+      new TestSpdyStreamDelegate(callback1.callback()));
+  spdy_stream1->SetDelegate(delegate.get());
+
+  session->set_connection_at_risk_of_loss_time(base::TimeDelta::FromSeconds(0));
+  session->set_hung_interval(base::TimeDelta::FromSeconds(0));
+
+  // Send a PING frame.
+  session->WritePingFrame(1);
+  EXPECT_LT(0, session->pings_in_flight());
+  EXPECT_GE(session->next_ping_id(), static_cast<uint32>(1));
+  EXPECT_TRUE(session->check_ping_status_pending());
+
+  // Assert session is not closed.
+  EXPECT_FALSE(session->IsClosed());
+  EXPECT_LT(0u, session->num_active_streams() + session->num_created_streams());
+  EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+  // We set last time we have received any data in 1 sec less than now.
+  // CheckPingStatus will trigger timeout because hung interval is zero.
+  base::TimeTicks now = base::TimeTicks::Now();
+  session->last_activity_time_ = now - base::TimeDelta::FromSeconds(1);
+  session->CheckPingStatus(now);
+
+  EXPECT_TRUE(session->IsClosed());
+  EXPECT_EQ(0u, session->num_active_streams());
+  EXPECT_EQ(0u, session->num_unclaimed_pushed_streams());
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+
+  // Delete the first session.
+  session = NULL;
+}
+
+class StreamReleaserCallback : public TestCompletionCallbackBase {
+ public:
+  StreamReleaserCallback(SpdySession* session,
+                         SpdyStream* first_stream)
+      : session_(session),
+        first_stream_(first_stream),
+        ALLOW_THIS_IN_INITIALIZER_LIST(callback_(
+            base::Bind(&StreamReleaserCallback::OnComplete,
+                       base::Unretained(this)))) {
+  }
+
+  virtual ~StreamReleaserCallback() {}
+
+  scoped_refptr<SpdyStream>* stream() { return &stream_; }
+
+  const CompletionCallback& callback() const { return callback_; }
+
+ private:
+  void OnComplete(int result) {
+    session_->CloseSessionOnError(ERR_FAILED, false, "On complete.");
+    session_ = NULL;
+    first_stream_->Cancel();
+    first_stream_ = NULL;
+    stream_->Cancel();
+    stream_ = NULL;
+    SetResult(result);
+  }
+
+  scoped_refptr<SpdySession> session_;
+  scoped_refptr<SpdyStream> first_stream_;
+  scoped_refptr<SpdyStream> stream_;
+  CompletionCallback callback_;
+};
+
+// TODO(kristianm): Could also test with more sessions where some are idle,
+// and more than one session to a HostPortPair.
+TEST_F(SpdySessionSpdy2Test, CloseIdleSessions) {
+  SpdySessionDependencies session_deps;
+  scoped_refptr<HttpNetworkSession> http_session(
+  SpdySessionDependencies::SpdyCreateSession(&session_deps));
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+  // Set up session 1
+  const std::string kTestHost1("http://www.a.com");
+  HostPortPair test_host_port_pair1(kTestHost1, 80);
+  HostPortProxyPair pair1(test_host_port_pair1, ProxyServer::Direct());
+  scoped_refptr<SpdySession> session1 =
+      spdy_session_pool->Get(pair1, BoundNetLog());
+  scoped_refptr<SpdyStream> spdy_stream1;
+  TestCompletionCallback callback1;
+  GURL url1(kTestHost1);
+  EXPECT_EQ(OK, session1->CreateStream(url1,
+                                      MEDIUM, /* priority, not important */
+                                      &spdy_stream1,
+                                      BoundNetLog(),
+                                      callback1.callback()));
+
+  // Set up session 2
+  const std::string kTestHost2("http://www.b.com");
+  HostPortPair test_host_port_pair2(kTestHost2, 80);
+  HostPortProxyPair pair2(test_host_port_pair2, ProxyServer::Direct());
+  scoped_refptr<SpdySession> session2 =
+      spdy_session_pool->Get(pair2, BoundNetLog());
+  scoped_refptr<SpdyStream> spdy_stream2;
+  TestCompletionCallback callback2;
+  GURL url2(kTestHost2);
+  EXPECT_EQ(OK, session2->CreateStream(
+      url2, MEDIUM, /* priority, not important */
+      &spdy_stream2, BoundNetLog(), callback2.callback()));
+
+  // Set up session 3
+  const std::string kTestHost3("http://www.c.com");
+  HostPortPair test_host_port_pair3(kTestHost3, 80);
+  HostPortProxyPair pair3(test_host_port_pair3, ProxyServer::Direct());
+  scoped_refptr<SpdySession> session3 =
+      spdy_session_pool->Get(pair3, BoundNetLog());
+  scoped_refptr<SpdyStream> spdy_stream3;
+  TestCompletionCallback callback3;
+  GURL url3(kTestHost3);
+  EXPECT_EQ(OK, session3->CreateStream(
+      url3, MEDIUM, /* priority, not important */
+      &spdy_stream3, BoundNetLog(), callback3.callback()));
+
+  // All sessions are active and not closed
+  EXPECT_TRUE(session1->is_active());
+  EXPECT_FALSE(session1->IsClosed());
+  EXPECT_TRUE(session2->is_active());
+  EXPECT_FALSE(session2->IsClosed());
+  EXPECT_TRUE(session3->is_active());
+  EXPECT_FALSE(session3->IsClosed());
+
+  // Should not do anything, all are active
+  spdy_session_pool->CloseIdleSessions();
+  EXPECT_TRUE(session1->is_active());
+  EXPECT_FALSE(session1->IsClosed());
+  EXPECT_TRUE(session2->is_active());
+  EXPECT_FALSE(session2->IsClosed());
+  EXPECT_TRUE(session3->is_active());
+  EXPECT_FALSE(session3->IsClosed());
+
+  // Make sessions 1 and 3 inactive, but keep them open.
+  // Session 2 still open and active
+  session1->CloseCreatedStream(spdy_stream1, OK);
+  session3->CloseCreatedStream(spdy_stream3, OK);
+  EXPECT_FALSE(session1->is_active());
+  EXPECT_FALSE(session1->IsClosed());
+  EXPECT_TRUE(session2->is_active());
+  EXPECT_FALSE(session2->IsClosed());
+  EXPECT_FALSE(session3->is_active());
+  EXPECT_FALSE(session3->IsClosed());
+
+  // Should close session 1 and 3, 2 should be left open
+  spdy_session_pool->CloseIdleSessions();
+  EXPECT_FALSE(session1->is_active());
+  EXPECT_TRUE(session1->IsClosed());
+  EXPECT_TRUE(session2->is_active());
+  EXPECT_FALSE(session2->IsClosed());
+  EXPECT_FALSE(session3->is_active());
+  EXPECT_TRUE(session3->IsClosed());
+
+  // Should not do anything
+  spdy_session_pool->CloseIdleSessions();
+  EXPECT_TRUE(session2->is_active());
+  EXPECT_FALSE(session2->IsClosed());
+
+  // Make 2 not active
+  session2->CloseCreatedStream(spdy_stream2, OK);
+  EXPECT_FALSE(session2->is_active());
+  EXPECT_FALSE(session2->IsClosed());
+
+  // This should close session 2
+  spdy_session_pool->CloseIdleSessions();
+  EXPECT_FALSE(session2->is_active());
+  EXPECT_TRUE(session2->IsClosed());
+}
+
+// Start with max concurrent streams set to 1.  Request two streams.  Receive a
+// settings frame setting max concurrent streams to 2.  Have the callback
+// release the stream, which releases its reference (the last) to the session.
+// Make sure nothing blows up.
+// http://crbug.com/57331
+TEST_F(SpdySessionSpdy2Test, OnSettings) {
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  SettingsMap new_settings;
+  const SpdySettingsIds kSpdySettingsIds1 = SETTINGS_MAX_CONCURRENT_STREAMS;
+  const size_t max_concurrent_streams = 2;
+  new_settings[kSpdySettingsIds1] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+
+  // Set up the socket so we read a SETTINGS frame that raises max concurrent
+  // streams to 2.
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(new_settings));
+  MockRead reads[] = {
+    CreateMockRead(*settings_frame),
+    MockRead(SYNCHRONOUS, 0, 0)  // EOF
+  };
+
+  StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  // Initialize the SpdySetting with 1 max concurrent streams.
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  spdy_session_pool->http_server_properties()->SetSpdySetting(
+      test_host_port_pair,
+      kSpdySettingsIds1,
+      SETTINGS_FLAG_PLEASE_PERSIST,
+      1);
+
+  // Create a session.
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  // Create 2 streams.  First will succeed.  Second will be pending.
+  scoped_refptr<SpdyStream> spdy_stream1;
+  TestCompletionCallback callback1;
+  GURL url("http://www.google.com");
+  EXPECT_EQ(OK,
+            session->CreateStream(url,
+                                  MEDIUM, /* priority, not important */
+                                  &spdy_stream1,
+                                  BoundNetLog(),
+                                  callback1.callback()));
+
+  StreamReleaserCallback stream_releaser(session, spdy_stream1);
+
+  ASSERT_EQ(ERR_IO_PENDING,
+            session->CreateStream(url,
+                                  MEDIUM, /* priority, not important */
+                                  stream_releaser.stream(),
+                                  BoundNetLog(),
+                                  stream_releaser.callback()));
+
+  // Make sure |stream_releaser| holds the last refs.
+  session = NULL;
+  spdy_stream1 = NULL;
+
+  EXPECT_EQ(OK, stream_releaser.WaitForResult());
+}
+
+// Start with max concurrent streams set to 1.  Request two streams.  When the
+// first completes, have the callback close itself, which should trigger the
+// second stream creation.  Then cancel that one immediately.  Don't crash.
+// http://crbug.com/63532
+TEST_F(SpdySessionSpdy2Test, CancelPendingCreateStream) {
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  MockRead reads[] = {
+    MockRead(SYNCHRONOUS, ERR_IO_PENDING)  // Stall forever.
+  };
+
+  StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+  MockConnect connect_data(SYNCHRONOUS, OK);
+
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  // Initialize the SpdySetting with 1 max concurrent streams.
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  spdy_session_pool->http_server_properties()->SetSpdySetting(
+      test_host_port_pair,
+      SETTINGS_MAX_CONCURRENT_STREAMS,
+      SETTINGS_FLAG_PLEASE_PERSIST,
+      1);
+
+  // Create a session.
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  // Use scoped_ptr to let us invalidate the memory when we want to, to trigger
+  // a valgrind error if the callback is invoked when it's not supposed to be.
+  scoped_ptr<TestCompletionCallback> callback(new TestCompletionCallback);
+
+  // Create 2 streams.  First will succeed.  Second will be pending.
+  scoped_refptr<SpdyStream> spdy_stream1;
+  GURL url("http://www.google.com");
+  ASSERT_EQ(OK,
+            session->CreateStream(url,
+                                  MEDIUM, /* priority, not important */
+                                  &spdy_stream1,
+                                  BoundNetLog(),
+                                  callback->callback()));
+
+  scoped_refptr<SpdyStream> spdy_stream2;
+  ASSERT_EQ(ERR_IO_PENDING,
+            session->CreateStream(url,
+                                  MEDIUM, /* priority, not important */
+                                  &spdy_stream2,
+                                  BoundNetLog(),
+                                  callback->callback()));
+
+  // Release the first one, this will allow the second to be created.
+  spdy_stream1->Cancel();
+  spdy_stream1 = NULL;
+
+  session->CancelPendingCreateStreams(&spdy_stream2);
+  callback.reset();
+
+  // Should not crash when running the pending callback.
+  MessageLoop::current()->RunUntilIdle();
+}
+
+TEST_F(SpdySessionSpdy2Test, SendInitialSettingsOnNewSession) {
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  MockRead reads[] = {
+    MockRead(SYNCHRONOUS, ERR_IO_PENDING)  // Stall forever.
+  };
+
+  SettingsMap settings;
+  const SpdySettingsIds kSpdySettingsIds1 = SETTINGS_MAX_CONCURRENT_STREAMS;
+  settings[kSpdySettingsIds1] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kMaxConcurrentPushedStreams);
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+  MockWrite writes[] = {
+    CreateMockWrite(*settings_frame),
+  };
+
+  StaticSocketDataProvider data(
+      reads, arraysize(reads), writes, arraysize(writes));
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  static const char kStreamUrl[] = "http://www.google.com/";
+  GURL url(kStreamUrl);
+
+  const std::string kTestHost("www.google.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  SpdySessionPoolPeer pool_peer(spdy_session_pool);
+  pool_peer.EnableSendingInitialSettings(true);
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+  MessageLoop::current()->RunUntilIdle();
+  EXPECT_TRUE(data.at_write_eof());
+}
+
+TEST_F(SpdySessionSpdy2Test, SendSettingsOnNewSession) {
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  MockRead reads[] = {
+    MockRead(SYNCHRONOUS, ERR_IO_PENDING)  // Stall forever.
+  };
+
+  // Create the bogus setting that we want to verify is sent out.
+  // Note that it will be marked as SETTINGS_FLAG_PERSISTED when sent out. But
+  // to persist it into the HttpServerProperties, we need to mark as
+  // SETTINGS_FLAG_PLEASE_PERSIST.
+  SettingsMap settings;
+  const SpdySettingsIds kSpdySettingsIds1 = SETTINGS_UPLOAD_BANDWIDTH;
+  const uint32 kBogusSettingValue = 0xCDCD;
+  settings[kSpdySettingsIds1] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_PERSISTED, kBogusSettingValue);
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+  MockWrite writes[] = {
+    CreateMockWrite(*settings_frame),
+  };
+
+  StaticSocketDataProvider data(
+      reads, arraysize(reads), writes, arraysize(writes));
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  spdy_session_pool->http_server_properties()->SetSpdySetting(
+      test_host_port_pair,
+      kSpdySettingsIds1,
+      SETTINGS_FLAG_PLEASE_PERSIST,
+      kBogusSettingValue);
+
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+  MessageLoop::current()->RunUntilIdle();
+  EXPECT_TRUE(data.at_write_eof());
+}
+
+namespace {
+// This test has two variants, one for each style of closing the connection.
+// If |clean_via_close_current_sessions| is false, the sessions are closed
+// manually, calling SpdySessionPool::Remove() directly.  If it is true,
+// sessions are closed with SpdySessionPool::CloseCurrentSessions().
+void IPPoolingTest(bool clean_via_close_current_sessions) {
+  const int kTestPort = 80;
+  struct TestHosts {
+    std::string name;
+    std::string iplist;
+    HostPortProxyPair pair;
+    AddressList addresses;
+  } test_hosts[] = {
+    { "www.foo.com",    "192.0.2.33,192.168.0.1,192.168.0.5" },
+    { "images.foo.com", "192.168.0.2,192.168.0.3,192.168.0.5,192.0.2.33" },
+    { "js.foo.com",     "192.168.0.4,192.168.0.3" },
+  };
+
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_hosts); i++) {
+    session_deps.host_resolver->rules()->AddIPLiteralRule(test_hosts[i].name,
+        test_hosts[i].iplist, "");
+
+    // This test requires that the HostResolver cache be populated.  Normal
+    // code would have done this already, but we do it manually.
+    HostResolver::RequestInfo info(HostPortPair(test_hosts[i].name, kTestPort));
+    session_deps.host_resolver->Resolve(
+        info, &test_hosts[i].addresses, CompletionCallback(), NULL,
+        BoundNetLog());
+
+    // Setup a HostPortProxyPair
+    test_hosts[i].pair = HostPortProxyPair(
+        HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct());
+  }
+
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  MockRead reads[] = {
+    MockRead(SYNCHRONOUS, ERR_IO_PENDING)  // Stall forever.
+  };
+
+  StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  // Setup the first session to the first host.
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[0].pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(test_hosts[0].pair, BoundNetLog());
+  EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[0].pair));
+
+  HostPortPair test_host_port_pair(test_hosts[0].name, kTestPort);
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  // TODO(rtenneti): MockClientSocket::GetPeerAddress return's 0 as the port
+  // number. Fix it to return port 80 and then use GetPeerAddress to AddAlias.
+  SpdySessionPoolPeer pool_peer(spdy_session_pool);
+  pool_peer.AddAlias(test_hosts[0].addresses.front(), test_hosts[0].pair);
+
+  // Flush the SpdySession::OnReadComplete() task.
+  MessageLoop::current()->RunUntilIdle();
+
+  // The third host has no overlap with the first, so it can't pool IPs.
+  EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair));
+
+  // The second host overlaps with the first, and should IP pool.
+  EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair));
+
+  // Verify that the second host, through a proxy, won't share the IP.
+  HostPortProxyPair proxy_pair(test_hosts[1].pair.first,
+      ProxyServer::FromPacString("HTTP http://proxy.foo.com/"));
+  EXPECT_FALSE(spdy_session_pool->HasSession(proxy_pair));
+
+  // Overlap between 2 and 3 does is not transitive to 1.
+  EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair));
+
+  // Create a new session to host 2.
+  scoped_refptr<SpdySession> session2 =
+      spdy_session_pool->Get(test_hosts[2].pair, BoundNetLog());
+
+  // Verify that we have sessions for everything.
+  EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[0].pair));
+  EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair));
+  EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[2].pair));
+
+  // Grab the session to host 1 and verify that it is the same session
+  // we got with host 0, and that is a different than host 2's session.
+  scoped_refptr<SpdySession> session1 =
+      spdy_session_pool->Get(test_hosts[1].pair, BoundNetLog());
+  EXPECT_EQ(session.get(), session1.get());
+  EXPECT_NE(session2.get(), session1.get());
+
+  // Remove the aliases and observe that we still have a session for host1.
+  pool_peer.RemoveAliases(test_hosts[0].pair);
+  pool_peer.RemoveAliases(test_hosts[1].pair);
+  EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair));
+
+  // Expire the host cache
+  session_deps.host_resolver->GetHostCache()->clear();
+  EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair));
+
+  // Cleanup the sessions.
+  if (!clean_via_close_current_sessions) {
+    spdy_session_pool->Remove(session);
+    session = NULL;
+    spdy_session_pool->Remove(session2);
+    session2 = NULL;
+  } else {
+    spdy_session_pool->CloseCurrentSessions(net::ERR_ABORTED);
+  }
+
+  // Verify that the map is all cleaned up.
+  EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[0].pair));
+  EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[1].pair));
+  EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair));
+}
+
+}  // namespace
+
+TEST_F(SpdySessionSpdy2Test, IPPooling) {
+  IPPoolingTest(false);
+}
+
+TEST_F(SpdySessionSpdy2Test, IPPoolingCloseCurrentSessions) {
+  IPPoolingTest(true);
+}
+
+TEST_F(SpdySessionSpdy2Test, ClearSettingsStorageOnIPAddressChanged) {
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+
+  SpdySessionDependencies session_deps;
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+  HttpServerProperties* test_http_server_properties =
+      spdy_session_pool->http_server_properties();
+  SettingsFlagsAndValue flags_and_value1(SETTINGS_FLAG_PLEASE_PERSIST, 2);
+  test_http_server_properties->SetSpdySetting(
+      test_host_port_pair,
+      SETTINGS_MAX_CONCURRENT_STREAMS,
+      SETTINGS_FLAG_PLEASE_PERSIST,
+      2);
+  EXPECT_NE(0u, test_http_server_properties->GetSpdySettings(
+      test_host_port_pair).size());
+  spdy_session_pool->OnIPAddressChanged();
+  EXPECT_EQ(0u, test_http_server_properties->GetSpdySettings(
+      test_host_port_pair).size());
+}
+
+TEST_F(SpdySessionSpdy2Test, NeedsCredentials) {
+  SpdySessionDependencies session_deps;
+
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  MockRead reads[] = {
+    MockRead(SYNCHRONOUS, ERR_IO_PENDING)  // Stall forever.
+  };
+  StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  ssl.channel_id_sent = true;
+  ssl.protocol_negotiated = kProtoSPDY2;
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+  SSLConfig ssl_config;
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_refptr<SOCKSSocketParams> socks_params;
+  scoped_refptr<HttpProxySocketParams> http_proxy_params;
+  scoped_refptr<SSLSocketParams> ssl_params(
+      new SSLSocketParams(transport_params,
+                          socks_params,
+                          http_proxy_params,
+                          ProxyServer::SCHEME_DIRECT,
+                          test_host_port_pair,
+                          ssl_config,
+                          0,
+                          false,
+                          false));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 ssl_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetSSLSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), true, OK));
+
+  EXPECT_FALSE(session->NeedsCredentials());
+
+  // Flush the SpdySession::OnReadComplete() task.
+  MessageLoop::current()->RunUntilIdle();
+
+  spdy_session_pool->Remove(session);
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+}
+
+TEST_F(SpdySessionSpdy2Test, CloseSessionOnError) {
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  scoped_ptr<SpdyFrame> goaway(ConstructSpdyGoAway());
+  MockRead reads[] = {
+    CreateMockRead(*goaway),
+    MockRead(SYNCHRONOUS, 0, 0)  // EOF
+  };
+
+  CapturingBoundNetLog log;
+
+  StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, log.bound());
+  EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 log.bound()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  // Flush the SpdySession::OnReadComplete() task.
+  MessageLoop::current()->RunUntilIdle();
+
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+
+  // Check that the NetLog was filled reasonably.
+  net::CapturingNetLog::CapturedEntryList entries;
+  log.GetEntries(&entries);
+  EXPECT_LT(0u, entries.size());
+
+  // Check that we logged SPDY_SESSION_CLOSE correctly.
+  int pos = net::ExpectLogContainsSomewhere(
+      entries, 0,
+      net::NetLog::TYPE_SPDY_SESSION_CLOSE,
+      net::NetLog::PHASE_NONE);
+
+  CapturingNetLog::CapturedEntry entry = entries[pos];
+  int error_code = 0;
+  ASSERT_TRUE(entry.GetNetErrorCode(&error_code));
+  EXPECT_EQ(ERR_CONNECTION_CLOSED, error_code);
+}
+
+TEST_F(SpdySessionSpdy2Test, OutOfOrderSynStreams) {
+  // Construct the request.
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, HIGHEST));
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+  MockWrite writes[] = {
+    CreateMockWrite(*req1, 2),
+    CreateMockWrite(*req2, 1),
+  };
+
+  scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body1(ConstructSpdyBodyFrame(1, true));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp1, 3),
+    CreateMockRead(*body1, 4),
+    CreateMockRead(*resp2, 5),
+    CreateMockRead(*body2, 6),
+    MockRead(ASYNC, 0, 7)  // EOF
+  };
+
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  StaticSocketDataProvider data(reads, arraysize(reads),
+                                writes, arraysize(writes));
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+  // Create a session.
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  scoped_refptr<SpdyStream> spdy_stream1;
+  TestCompletionCallback callback1;
+  GURL url1("http://www.google.com");
+  EXPECT_EQ(OK, session->CreateStream(url1, LOWEST, &spdy_stream1,
+                                      BoundNetLog(), callback1.callback()));
+  EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+  scoped_refptr<SpdyStream> spdy_stream2;
+  TestCompletionCallback callback2;
+  GURL url2("http://www.google.com");
+  EXPECT_EQ(OK, session->CreateStream(url2, HIGHEST, &spdy_stream2,
+                                      BoundNetLog(), callback2.callback()));
+  EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+  scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+  (*headers)["method"] = "GET";
+  (*headers)["scheme"] = url1.scheme();
+  (*headers)["host"] = url1.host();
+  (*headers)["url"] = url1.path();
+  (*headers)["version"] = "HTTP/1.1";
+  scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock);
+  *headers2 = *headers;
+
+  spdy_stream1->set_spdy_headers(headers.Pass());
+  EXPECT_TRUE(spdy_stream1->HasUrl());
+
+  spdy_stream2->set_spdy_headers(headers2.Pass());
+  EXPECT_TRUE(spdy_stream2->HasUrl());
+
+  spdy_stream1->SendRequest(false);
+  spdy_stream2->SendRequest(false);
+  MessageLoop::current()->RunUntilIdle();
+
+  EXPECT_EQ(3u, spdy_stream1->stream_id());
+  EXPECT_EQ(1u, spdy_stream2->stream_id());
+
+  spdy_stream1->Cancel();
+  spdy_stream1 = NULL;
+
+  spdy_stream2->Cancel();
+  spdy_stream2 = NULL;
+}
+
+TEST_F(SpdySessionSpdy2Test, CancelStream) {
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  // Request 1, at HIGHEST priority, will be cancelled before it writes data.
+  // Request 2, at LOWEST priority, will be a full request and will be id 1.
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = {
+    CreateMockWrite(*req2, 0),
+  };
+
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp2, 1),
+    CreateMockRead(*body2, 2),
+    MockRead(ASYNC, 0, 3)  // EOF
+  };
+
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+  data.set_connect_data(connect_data);
+  session_deps.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+  // Create a session.
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  scoped_refptr<SpdyStream> spdy_stream1;
+  TestCompletionCallback callback1;
+  GURL url1("http://www.google.com");
+  EXPECT_EQ(OK, session->CreateStream(url1, HIGHEST, &spdy_stream1,
+                                      BoundNetLog(), callback1.callback()));
+  EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+  scoped_refptr<SpdyStream> spdy_stream2;
+  TestCompletionCallback callback2;
+  GURL url2("http://www.google.com");
+  EXPECT_EQ(OK, session->CreateStream(url2, LOWEST, &spdy_stream2,
+                                      BoundNetLog(), callback2.callback()));
+  EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+  scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+  (*headers)["method"] = "GET";
+  (*headers)["scheme"] = url1.scheme();
+  (*headers)["host"] = url1.host();
+  (*headers)["url"] = url1.path();
+  (*headers)["version"] = "HTTP/1.1";
+  scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock);
+  *headers2 = *headers;
+
+  spdy_stream1->set_spdy_headers(headers.Pass());
+  EXPECT_TRUE(spdy_stream1->HasUrl());
+
+  spdy_stream2->set_spdy_headers(headers2.Pass());
+  EXPECT_TRUE(spdy_stream2->HasUrl());
+
+  spdy_stream1->SendRequest(false);
+  spdy_stream2->SendRequest(false);
+
+  EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+  spdy_stream1->Cancel();
+
+  EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+  data.RunFor(1);
+
+  EXPECT_EQ(0u, spdy_stream1->stream_id());
+  EXPECT_EQ(1u, spdy_stream2->stream_id());
+
+  spdy_stream1 = NULL;
+  spdy_stream2->Cancel();
+  spdy_stream2 = NULL;
+}
+
+TEST_F(SpdySessionSpdy2Test, CloseSessionWithTwoCreatedStreams) {
+  // Test that if a sesion is closed with two created streams pending,
+  // it does not crash.  http://crbug.com/139518
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  // No actual data will be sent.
+  MockWrite writes[] = {
+    MockWrite(ASYNC, 0, 1)  // EOF
+  };
+
+  MockRead reads[] = {
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+  data.set_connect_data(connect_data);
+  session_deps.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+  // Create a session.
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  scoped_refptr<SpdyStream> spdy_stream1;
+  TestCompletionCallback callback1;
+  GURL url1("http://www.google.com");
+  EXPECT_EQ(OK, session->CreateStream(url1, HIGHEST, &spdy_stream1,
+                                      BoundNetLog(), callback1.callback()));
+  EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+  scoped_refptr<SpdyStream> spdy_stream2;
+  TestCompletionCallback callback2;
+  GURL url2("http://www.google.com");
+  EXPECT_EQ(OK, session->CreateStream(url2, LOWEST, &spdy_stream2,
+                                      BoundNetLog(), callback2.callback()));
+  EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+  scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+  (*headers)["method"] = "GET";
+  (*headers)["scheme"] = url1.scheme();
+  (*headers)["host"] = url1.host();
+  (*headers)["url"] = url1.path();
+  (*headers)["version"] = "HTTP/1.1";
+  scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock);
+  *headers2 = *headers;
+
+  spdy_stream1->set_spdy_headers(headers.Pass());
+  EXPECT_TRUE(spdy_stream1->HasUrl());
+  ClosingDelegate delegate1(spdy_stream1.get());
+  spdy_stream1->SetDelegate(&delegate1);
+
+  spdy_stream2->set_spdy_headers(headers2.Pass());
+  EXPECT_TRUE(spdy_stream2->HasUrl());
+  ClosingDelegate delegate2(spdy_stream2.get());
+  spdy_stream2->SetDelegate(&delegate2);
+
+  spdy_stream1->SendRequest(false);
+  spdy_stream2->SendRequest(false);
+
+  // Ensure that the streams have not yet been activated and assigned an id.
+  EXPECT_EQ(0u, spdy_stream1->stream_id());
+  EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+  // Ensure we don't crash while closing the session.
+  session->CloseSessionOnError(ERR_ABORTED, true, "");
+
+  EXPECT_TRUE(spdy_stream1->closed());
+  EXPECT_TRUE(spdy_stream2->closed());
+
+  spdy_stream1 = NULL;
+  spdy_stream2 = NULL;
+}
+
+TEST_F(SpdySessionSpdy2Test, CloseTwoStalledCreateStream) {
+  // TODO(rtenneti): Define a helper class/methods and move the common code in
+  // this file.
+  MockConnect connect_data(SYNCHRONOUS, OK);
+
+  SettingsMap new_settings;
+  const SpdySettingsIds kSpdySettingsIds1 = SETTINGS_MAX_CONCURRENT_STREAMS;
+  const uint32 max_concurrent_streams = 1;
+  new_settings[kSpdySettingsIds1] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+
+  scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+  scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, LOWEST));
+  MockWrite writes[] = {
+    CreateMockWrite(*req1, 1),
+    CreateMockWrite(*req2, 4),
+    CreateMockWrite(*req3, 7),
+  };
+
+  // Set up the socket so we read a SETTINGS frame that sets max concurrent
+  // streams to 1.
+  scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(new_settings));
+
+  scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body1(ConstructSpdyBodyFrame(1, true));
+
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, true));
+
+  scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
+  scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(5, true));
+
+  MockRead reads[] = {
+    CreateMockRead(*settings_frame),
+    CreateMockRead(*resp1, 2),
+    CreateMockRead(*body1, 3),
+    CreateMockRead(*resp2, 5),
+    CreateMockRead(*body2, 6),
+    CreateMockRead(*resp3, 8),
+    CreateMockRead(*body3, 9),
+    MockRead(ASYNC, 0, 10)  // EOF
+  };
+
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+  data.set_connect_data(connect_data);
+  session_deps.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+  // Create a session.
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                LOWEST,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, LOWEST, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  // Read the settings frame.
+  data.RunFor(1);
+
+  scoped_refptr<SpdyStream> spdy_stream1;
+  TestCompletionCallback callback1;
+  GURL url1("http://www.google.com");
+  EXPECT_EQ(OK, session->CreateStream(url1,
+                                      LOWEST,
+                                      &spdy_stream1,
+                                      BoundNetLog(),
+                                      callback1.callback()));
+  EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+  scoped_refptr<SpdyStream> spdy_stream2;
+  TestCompletionCallback callback2;
+  GURL url2("http://www.google.com");
+  EXPECT_EQ(ERR_IO_PENDING,
+            session->CreateStream(url2,
+                                  LOWEST,
+                                  &spdy_stream2,
+                                  BoundNetLog(),
+                                  callback2.callback()));
+
+  scoped_refptr<SpdyStream> spdy_stream3;
+  TestCompletionCallback callback3;
+  GURL url3("http://www.google.com");
+  EXPECT_EQ(ERR_IO_PENDING,
+            session->CreateStream(url3,
+                                  LOWEST,
+                                  &spdy_stream3,
+                                  BoundNetLog(),
+                                  callback3.callback()));
+
+  EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+  EXPECT_EQ(2u, session->pending_create_stream_queues(LOWEST));
+
+  scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+  (*headers)["method"] = "GET";
+  (*headers)["scheme"] = url1.scheme();
+  (*headers)["host"] = url1.host();
+  (*headers)["url"] = url1.path();
+  (*headers)["version"] = "HTTP/1.1";
+  scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock);
+  *headers2 = *headers;
+  scoped_ptr<SpdyHeaderBlock> headers3(new SpdyHeaderBlock);
+  *headers3 = *headers;
+
+  spdy_stream1->set_spdy_headers(headers.Pass());
+  EXPECT_TRUE(spdy_stream1->HasUrl());
+  spdy_stream1->SendRequest(false);
+
+  // Run until 1st stream is closed.
+  EXPECT_EQ(0u, spdy_stream1->stream_id());
+  data.RunFor(3);
+  EXPECT_EQ(1u, spdy_stream1->stream_id());
+  EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+  EXPECT_EQ(1u, session->pending_create_stream_queues(LOWEST));
+
+  EXPECT_TRUE(spdy_stream2.get() != NULL);
+  spdy_stream2->set_spdy_headers(headers2.Pass());
+  EXPECT_TRUE(spdy_stream2->HasUrl());
+  spdy_stream2->SendRequest(false);
+
+  // Run until 2nd stream is closed.
+  EXPECT_EQ(0u, spdy_stream2->stream_id());
+  data.RunFor(3);
+  EXPECT_EQ(3u, spdy_stream2->stream_id());
+  EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+  EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
+
+  EXPECT_TRUE(spdy_stream3.get() != NULL);
+  spdy_stream3->set_spdy_headers(headers3.Pass());
+  EXPECT_TRUE(spdy_stream3->HasUrl());
+  spdy_stream3->SendRequest(false);
+
+  EXPECT_EQ(0u, spdy_stream3->stream_id());
+  data.RunFor(4);
+  EXPECT_EQ(5u, spdy_stream3->stream_id());
+  EXPECT_EQ(0u, session->num_active_streams() + session->num_created_streams());
+  EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
+}
+
+TEST_F(SpdySessionSpdy2Test, CancelTwoStalledCreateStream) {
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  MockRead reads[] = {
+    MockRead(SYNCHRONOUS, ERR_IO_PENDING)  // Stall forever.
+  };
+
+  StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+  MockConnect connect_data(SYNCHRONOUS, OK);
+
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+  // Initialize the SpdySetting with 1 max concurrent streams.
+  spdy_session_pool->http_server_properties()->SetSpdySetting(
+      test_host_port_pair,
+      SETTINGS_MAX_CONCURRENT_STREAMS,
+      SETTINGS_FLAG_PLEASE_PERSIST,
+      1);
+
+  // Create a session.
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                LOWEST,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params,
+                                 LOWEST,
+                                 CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  scoped_refptr<SpdyStream> spdy_stream1;
+  TestCompletionCallback callback1;
+  GURL url1("http://www.google.com");
+  ASSERT_EQ(OK,
+            session->CreateStream(url1,
+                                  LOWEST,
+                                  &spdy_stream1,
+                                  BoundNetLog(),
+                                  callback1.callback()));
+  EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+  scoped_refptr<SpdyStream> spdy_stream2;
+  TestCompletionCallback callback2;
+  GURL url2("http://www.google.com");
+  ASSERT_EQ(ERR_IO_PENDING,
+            session->CreateStream(url2,
+                                  LOWEST,
+                                  &spdy_stream2,
+                                  BoundNetLog(),
+                                  callback2.callback()));
+
+  scoped_refptr<SpdyStream> spdy_stream3;
+  TestCompletionCallback callback3;
+  GURL url3("http://www.google.com");
+  ASSERT_EQ(ERR_IO_PENDING,
+            session->CreateStream(url3,
+                                  LOWEST,
+                                  &spdy_stream3,
+                                  BoundNetLog(),
+                                  callback3.callback()));
+
+  EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+  EXPECT_EQ(2u, session->pending_create_stream_queues(LOWEST));
+
+  // Cancel the first stream, this will allow the second stream to be created.
+  EXPECT_TRUE(spdy_stream1.get() != NULL);
+  spdy_stream1->Cancel();
+  spdy_stream1 = NULL;
+  session->CancelPendingCreateStreams(&spdy_stream1);
+  EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+  EXPECT_EQ(1u, session->pending_create_stream_queues(LOWEST));
+
+  // Cancel the second stream, this will allow the third stream to be created.
+  EXPECT_TRUE(spdy_stream2.get() != NULL);
+  spdy_stream2->Cancel();
+  spdy_stream2 = NULL;
+  session->CancelPendingCreateStreams(&spdy_stream2);
+  EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+  EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
+
+  // Cancel the third stream.
+  EXPECT_TRUE(spdy_stream3.get() != NULL);
+  spdy_stream3->Cancel();
+  spdy_stream3 = NULL;
+  session->CancelPendingCreateStreams(&spdy_stream3);
+  EXPECT_EQ(0u, session->num_active_streams() + session->num_created_streams());
+  EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_session_spdy3_unittest.cc b/src/net/spdy/spdy_session_spdy3_unittest.cc
new file mode 100644
index 0000000..3065e64
--- /dev/null
+++ b/src/net/spdy/spdy_session_spdy3_unittest.cc
@@ -0,0 +1,2205 @@
+// Copyright (c) 2012 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_session.h"
+
+#include "net/base/cert_test_util.h"
+#include "net/base/host_cache.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/test_data_directory.h"
+#include "net/spdy/spdy_io_buffer.h"
+#include "net/spdy/spdy_session_pool.h"
+#include "net/spdy/spdy_stream.h"
+#include "net/spdy/spdy_test_util_spdy3.h"
+#include "testing/platform_test.h"
+
+using namespace net::test_spdy3;
+
+namespace net {
+
+namespace {
+
+static int g_delta_seconds = 0;
+base::TimeTicks TheNearFuture() {
+  return base::TimeTicks::Now() + base::TimeDelta::FromSeconds(g_delta_seconds);
+}
+
+class ClosingDelegate : public SpdyStream::Delegate {
+ public:
+  ClosingDelegate(SpdyStream* stream) : stream_(stream) {}
+
+  // SpdyStream::Delegate implementation:
+  virtual bool OnSendHeadersComplete(int status) OVERRIDE {
+    return true;
+  }
+  virtual int OnSendBody() OVERRIDE {
+    return OK;
+  }
+  virtual int OnSendBodyComplete(int status, bool* eof) OVERRIDE {
+    return OK;
+  }
+  virtual int OnResponseReceived(const SpdyHeaderBlock& response,
+                                 base::Time response_time,
+                                 int status) OVERRIDE {
+    return OK;
+  }
+  virtual void OnHeadersSent() OVERRIDE {}
+  virtual int OnDataReceived(const char* data, int length) OVERRIDE {
+    return OK;
+  }
+  virtual void OnDataSent(int length) OVERRIDE {}
+  virtual void OnClose(int status) OVERRIDE {
+    stream_->Close();
+  }
+ private:
+  SpdyStream* stream_;
+};
+
+
+class TestSpdyStreamDelegate : public SpdyStream::Delegate {
+ public:
+  explicit TestSpdyStreamDelegate(const CompletionCallback& callback)
+      : callback_(callback) {}
+  virtual ~TestSpdyStreamDelegate() {}
+
+  virtual bool OnSendHeadersComplete(int status) OVERRIDE { return true; }
+
+  virtual int OnSendBody() OVERRIDE {
+    return ERR_UNEXPECTED;
+  }
+
+  virtual int OnSendBodyComplete(int /*status*/, bool* /*eof*/) OVERRIDE {
+    return ERR_UNEXPECTED;
+  }
+
+  virtual int OnResponseReceived(const SpdyHeaderBlock& response,
+                                 base::Time response_time,
+                                 int status) OVERRIDE {
+    return status;
+  }
+
+  virtual void OnHeadersSent() OVERRIDE {}
+  virtual int OnDataReceived(const char* buffer, int bytes) OVERRIDE {
+    return OK;
+  }
+
+  virtual void OnDataSent(int length) OVERRIDE {}
+
+  virtual void OnClose(int status) OVERRIDE {
+    CompletionCallback callback = callback_;
+    callback_.Reset();
+    callback.Run(OK);
+  }
+
+ private:
+  CompletionCallback callback_;
+};
+
+} // namespace
+
+class SpdySessionSpdy3Test : public PlatformTest {
+ protected:
+  void SetUp() {
+    g_delta_seconds = 0;
+  }
+};
+
+// Test the SpdyIOBuffer class.
+TEST_F(SpdySessionSpdy3Test, SpdyIOBuffer) {
+  std::priority_queue<SpdyIOBuffer> queue_;
+  const size_t kQueueSize = 100;
+
+  // Insert items with random priority and increasing buffer size
+  for (size_t index = 0; index < kQueueSize; ++index) {
+    queue_.push(SpdyIOBuffer(
+        new IOBufferWithSize(index + 1),
+        index + 1,
+        static_cast<RequestPriority>(rand() % NUM_PRIORITIES),
+        NULL));
+  }
+
+  EXPECT_EQ(kQueueSize, queue_.size());
+
+  // Verify items come out with decreasing priority or FIFO order.
+  RequestPriority last_priority = NUM_PRIORITIES;
+  size_t last_size = 0;
+  for (size_t index = 0; index < kQueueSize; ++index) {
+    SpdyIOBuffer buffer = queue_.top();
+    EXPECT_LE(buffer.priority(), last_priority);
+    if (buffer.priority() < last_priority)
+      last_size = 0;
+    EXPECT_LT(last_size, buffer.size());
+    last_priority = buffer.priority();
+    last_size = buffer.size();
+    queue_.pop();
+  }
+
+  EXPECT_EQ(0u, queue_.size());
+}
+
+TEST_F(SpdySessionSpdy3Test, GoAway) {
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  scoped_ptr<SpdyFrame> goaway(ConstructSpdyGoAway());
+  MockRead reads[] = {
+    CreateMockRead(*goaway),
+    MockRead(SYNCHRONOUS, 0, 0)  // EOF
+  };
+  StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  ssl.SetNextProto(kProtoSPDY3);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+  EXPECT_EQ(3, session->GetProtocolVersion());
+
+  // Flush the SpdySession::OnReadComplete() task.
+  MessageLoop::current()->RunUntilIdle();
+
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<SpdySession> session2 =
+      spdy_session_pool->Get(pair, BoundNetLog());
+
+  // Delete the first session.
+  session = NULL;
+
+  // Delete the second session.
+  spdy_session_pool->Remove(session2);
+  session2 = NULL;
+}
+
+TEST_F(SpdySessionSpdy3Test, ClientPing) {
+  SpdySessionDependencies session_deps;
+  session_deps.enable_ping = true;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  scoped_ptr<SpdyFrame> read_ping(ConstructSpdyPing(1));
+  MockRead reads[] = {
+    CreateMockRead(*read_ping),
+    MockRead(SYNCHRONOUS, 0, 0)  // EOF
+  };
+  scoped_ptr<SpdyFrame> write_ping(ConstructSpdyPing(1));
+  MockWrite writes[] = {
+    CreateMockWrite(*write_ping),
+  };
+  StaticSocketDataProvider data(
+      reads, arraysize(reads), writes, arraysize(writes));
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  static const char kStreamUrl[] = "http://www.google.com/";
+  GURL url(kStreamUrl);
+
+  const std::string kTestHost("www.google.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  scoped_refptr<SpdyStream> spdy_stream1;
+  TestCompletionCallback callback1;
+  EXPECT_EQ(OK, session->CreateStream(url,
+                                      MEDIUM,
+                                      &spdy_stream1,
+                                      BoundNetLog(),
+                                      callback1.callback()));
+  scoped_ptr<TestSpdyStreamDelegate> delegate(
+      new TestSpdyStreamDelegate(callback1.callback()));
+  spdy_stream1->SetDelegate(delegate.get());
+
+  base::TimeTicks before_ping_time = base::TimeTicks::Now();
+
+  session->set_connection_at_risk_of_loss_time(base::TimeDelta::FromSeconds(0));
+  session->set_hung_interval(base::TimeDelta::FromMilliseconds(50));
+
+  session->SendPrefacePingIfNoneInFlight();
+
+  EXPECT_EQ(OK, callback1.WaitForResult());
+
+  session->CheckPingStatus(before_ping_time);
+
+  EXPECT_EQ(0, session->pings_in_flight());
+  EXPECT_GE(session->next_ping_id(), static_cast<uint32>(1));
+  EXPECT_FALSE(session->check_ping_status_pending());
+  EXPECT_GE(session->last_activity_time(), before_ping_time);
+
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+
+  // Delete the first session.
+  session = NULL;
+}
+
+TEST_F(SpdySessionSpdy3Test, ServerPing) {
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  scoped_ptr<SpdyFrame> read_ping(ConstructSpdyPing(2));
+  MockRead reads[] = {
+    CreateMockRead(*read_ping),
+    MockRead(SYNCHRONOUS, 0, 0)  // EOF
+  };
+  scoped_ptr<SpdyFrame> write_ping(ConstructSpdyPing(2));
+  MockWrite writes[] = {
+    CreateMockWrite(*write_ping),
+  };
+  StaticSocketDataProvider data(
+      reads, arraysize(reads), writes, arraysize(writes));
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  static const char kStreamUrl[] = "http://www.google.com/";
+  GURL url(kStreamUrl);
+
+  const std::string kTestHost("www.google.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  scoped_refptr<SpdyStream> spdy_stream1;
+  TestCompletionCallback callback1;
+  EXPECT_EQ(OK, session->CreateStream(url,
+                                      MEDIUM,
+                                      &spdy_stream1,
+                                      BoundNetLog(),
+                                      callback1.callback()));
+  scoped_ptr<TestSpdyStreamDelegate> delegate(
+      new TestSpdyStreamDelegate(callback1.callback()));
+  spdy_stream1->SetDelegate(delegate.get());
+
+  // Flush the SpdySession::OnReadComplete() task.
+  MessageLoop::current()->RunUntilIdle();
+
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+
+  // Delete the session.
+  session = NULL;
+}
+
+TEST_F(SpdySessionSpdy3Test, DeleteExpiredPushStreams) {
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+  session_deps.time_func = TheNearFuture;
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  const std::string kTestHost("www.google.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+  // Give the session a SPDY3 framer.
+  session->buffered_spdy_framer_.reset(new BufferedSpdyFramer(3, false));
+
+  // Create the associated stream and add to active streams.
+  scoped_ptr<SpdyHeaderBlock> request_headers(new SpdyHeaderBlock);
+  (*request_headers)[":scheme"] = "http";
+  (*request_headers)[":host"] = "www.google.com";
+  (*request_headers)[":path"] = "/";
+
+  scoped_refptr<SpdyStream> stream(
+      new SpdyStream(session, false, session->net_log_));
+  stream->set_spdy_headers(request_headers.Pass());
+  session->ActivateStream(stream);
+
+  SpdyHeaderBlock headers;
+  headers[":scheme"] = "http";
+  headers[":host"] = "www.google.com";
+  headers[":path"] = "/a.dat";
+  session->OnSynStream(2, 1, 0, 0, true, false, headers);
+
+  // Verify that there is one unclaimed push stream.
+  EXPECT_EQ(1u, session->num_unclaimed_pushed_streams());
+  SpdySession::PushedStreamMap::iterator  iter  =
+      session->unclaimed_pushed_streams_.find("http://www.google.com/a.dat");
+  EXPECT_TRUE(session->unclaimed_pushed_streams_.end() != iter);
+
+  // Shift time.
+  g_delta_seconds = 301;
+
+  headers[":scheme"] = "http";
+  headers[":host"] = "www.google.com";
+  headers[":path"] = "/b.dat";
+  session->OnSynStream(4, 1, 0, 0, true, false, headers);
+
+  // Verify that the second pushed stream evicted the first pushed stream.
+  EXPECT_EQ(1u, session->num_unclaimed_pushed_streams());
+  iter = session->unclaimed_pushed_streams_.find("http://www.google.com/b.dat");
+  EXPECT_TRUE(session->unclaimed_pushed_streams_.end() != iter);
+
+  // Delete the session.
+  session = NULL;
+}
+
+TEST_F(SpdySessionSpdy3Test, FailedPing) {
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  scoped_ptr<SpdyFrame> read_ping(ConstructSpdyPing(1));
+  MockRead reads[] = {
+    CreateMockRead(*read_ping),
+    MockRead(SYNCHRONOUS, 0, 0)  // EOF
+  };
+  scoped_ptr<SpdyFrame> write_ping(ConstructSpdyPing(1));
+  MockWrite writes[] = {
+    CreateMockWrite(*write_ping),
+  };
+  StaticSocketDataProvider data(
+      reads, arraysize(reads), writes, arraysize(writes));
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  static const char kStreamUrl[] = "http://www.gmail.com/";
+  GURL url(kStreamUrl);
+
+  const std::string kTestHost("www.gmail.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  scoped_refptr<SpdyStream> spdy_stream1;
+  TestCompletionCallback callback1;
+  EXPECT_EQ(OK, session->CreateStream(url,
+                                      MEDIUM,
+                                      &spdy_stream1,
+                                      BoundNetLog(),
+                                      callback1.callback()));
+  scoped_ptr<TestSpdyStreamDelegate> delegate(
+      new TestSpdyStreamDelegate(callback1.callback()));
+  spdy_stream1->SetDelegate(delegate.get());
+
+  session->set_connection_at_risk_of_loss_time(base::TimeDelta::FromSeconds(0));
+  session->set_hung_interval(base::TimeDelta::FromSeconds(0));
+
+  // Send a PING frame.
+  session->WritePingFrame(1);
+  EXPECT_LT(0, session->pings_in_flight());
+  EXPECT_GE(session->next_ping_id(), static_cast<uint32>(1));
+  EXPECT_TRUE(session->check_ping_status_pending());
+
+  // Assert session is not closed.
+  EXPECT_FALSE(session->IsClosed());
+  EXPECT_LT(0u, session->num_active_streams() + session->num_created_streams());
+  EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+  // We set last time we have received any data in 1 sec less than now.
+  // CheckPingStatus will trigger timeout because hung interval is zero.
+  base::TimeTicks now = base::TimeTicks::Now();
+  session->last_activity_time_ = now - base::TimeDelta::FromSeconds(1);
+  session->CheckPingStatus(now);
+
+  EXPECT_TRUE(session->IsClosed());
+  EXPECT_EQ(0u, session->num_active_streams());
+  EXPECT_EQ(0u, session->num_unclaimed_pushed_streams());
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+
+  // Delete the first session.
+  session = NULL;
+}
+
+class StreamReleaserCallback : public TestCompletionCallbackBase {
+ public:
+  StreamReleaserCallback(SpdySession* session,
+                         SpdyStream* first_stream)
+      : session_(session),
+        first_stream_(first_stream),
+        ALLOW_THIS_IN_INITIALIZER_LIST(callback_(
+            base::Bind(&StreamReleaserCallback::OnComplete,
+                       base::Unretained(this)))) {
+  }
+
+  virtual ~StreamReleaserCallback() {}
+
+  scoped_refptr<SpdyStream>* stream() { return &stream_; }
+
+  const CompletionCallback& callback() const { return callback_; }
+
+ private:
+  void OnComplete(int result) {
+    session_->CloseSessionOnError(ERR_FAILED, false, "On complete.");
+    session_ = NULL;
+    first_stream_->Cancel();
+    first_stream_ = NULL;
+    stream_->Cancel();
+    stream_ = NULL;
+    SetResult(result);
+  }
+
+  scoped_refptr<SpdySession> session_;
+  scoped_refptr<SpdyStream> first_stream_;
+  scoped_refptr<SpdyStream> stream_;
+  CompletionCallback callback_;
+};
+
+// TODO(kristianm): Could also test with more sessions where some are idle,
+// and more than one session to a HostPortPair.
+TEST_F(SpdySessionSpdy3Test, CloseIdleSessions) {
+  SpdySessionDependencies session_deps;
+  scoped_refptr<HttpNetworkSession> http_session(
+  SpdySessionDependencies::SpdyCreateSession(&session_deps));
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+  // Set up session 1
+  const std::string kTestHost1("http://www.a.com");
+  HostPortPair test_host_port_pair1(kTestHost1, 80);
+  HostPortProxyPair pair1(test_host_port_pair1, ProxyServer::Direct());
+  scoped_refptr<SpdySession> session1 =
+      spdy_session_pool->Get(pair1, BoundNetLog());
+  scoped_refptr<SpdyStream> spdy_stream1;
+  TestCompletionCallback callback1;
+  GURL url1(kTestHost1);
+  EXPECT_EQ(OK, session1->CreateStream(url1,
+                                      MEDIUM, /* priority, not important */
+                                      &spdy_stream1,
+                                      BoundNetLog(),
+                                      callback1.callback()));
+
+  // Set up session 2
+  const std::string kTestHost2("http://www.b.com");
+  HostPortPair test_host_port_pair2(kTestHost2, 80);
+  HostPortProxyPair pair2(test_host_port_pair2, ProxyServer::Direct());
+  scoped_refptr<SpdySession> session2 =
+      spdy_session_pool->Get(pair2, BoundNetLog());
+  scoped_refptr<SpdyStream> spdy_stream2;
+  TestCompletionCallback callback2;
+  GURL url2(kTestHost2);
+  EXPECT_EQ(OK, session2->CreateStream(
+      url2, MEDIUM, /* priority, not important */
+      &spdy_stream2, BoundNetLog(), callback2.callback()));
+
+  // Set up session 3
+  const std::string kTestHost3("http://www.c.com");
+  HostPortPair test_host_port_pair3(kTestHost3, 80);
+  HostPortProxyPair pair3(test_host_port_pair3, ProxyServer::Direct());
+  scoped_refptr<SpdySession> session3 =
+      spdy_session_pool->Get(pair3, BoundNetLog());
+  scoped_refptr<SpdyStream> spdy_stream3;
+  TestCompletionCallback callback3;
+  GURL url3(kTestHost3);
+  EXPECT_EQ(OK, session3->CreateStream(
+      url3, MEDIUM, /* priority, not important */
+      &spdy_stream3, BoundNetLog(), callback3.callback()));
+
+  // All sessions are active and not closed
+  EXPECT_TRUE(session1->is_active());
+  EXPECT_FALSE(session1->IsClosed());
+  EXPECT_TRUE(session2->is_active());
+  EXPECT_FALSE(session2->IsClosed());
+  EXPECT_TRUE(session3->is_active());
+  EXPECT_FALSE(session3->IsClosed());
+
+  // Should not do anything, all are active
+  spdy_session_pool->CloseIdleSessions();
+  EXPECT_TRUE(session1->is_active());
+  EXPECT_FALSE(session1->IsClosed());
+  EXPECT_TRUE(session2->is_active());
+  EXPECT_FALSE(session2->IsClosed());
+  EXPECT_TRUE(session3->is_active());
+  EXPECT_FALSE(session3->IsClosed());
+
+  // Make sessions 1 and 3 inactive, but keep them open.
+  // Session 2 still open and active
+  session1->CloseCreatedStream(spdy_stream1, OK);
+  session3->CloseCreatedStream(spdy_stream3, OK);
+  EXPECT_FALSE(session1->is_active());
+  EXPECT_FALSE(session1->IsClosed());
+  EXPECT_TRUE(session2->is_active());
+  EXPECT_FALSE(session2->IsClosed());
+  EXPECT_FALSE(session3->is_active());
+  EXPECT_FALSE(session3->IsClosed());
+
+  // Should close session 1 and 3, 2 should be left open
+  spdy_session_pool->CloseIdleSessions();
+  EXPECT_FALSE(session1->is_active());
+  EXPECT_TRUE(session1->IsClosed());
+  EXPECT_TRUE(session2->is_active());
+  EXPECT_FALSE(session2->IsClosed());
+  EXPECT_FALSE(session3->is_active());
+  EXPECT_TRUE(session3->IsClosed());
+
+  // Should not do anything
+  spdy_session_pool->CloseIdleSessions();
+  EXPECT_TRUE(session2->is_active());
+  EXPECT_FALSE(session2->IsClosed());
+
+  // Make 2 not active
+  session2->CloseCreatedStream(spdy_stream2, OK);
+  EXPECT_FALSE(session2->is_active());
+  EXPECT_FALSE(session2->IsClosed());
+
+  // This should close session 2
+  spdy_session_pool->CloseIdleSessions();
+  EXPECT_FALSE(session2->is_active());
+  EXPECT_TRUE(session2->IsClosed());
+}
+
+// Start with max concurrent streams set to 1.  Request two streams.  Receive a
+// settings frame setting max concurrent streams to 2.  Have the callback
+// release the stream, which releases its reference (the last) to the session.
+// Make sure nothing blows up.
+// http://crbug.com/57331
+TEST_F(SpdySessionSpdy3Test, OnSettings) {
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  SettingsMap new_settings;
+  const SpdySettingsIds kSpdySettingsIds1 = SETTINGS_MAX_CONCURRENT_STREAMS;
+  const size_t max_concurrent_streams = 2;
+  new_settings[kSpdySettingsIds1] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+
+  // Set up the socket so we read a SETTINGS frame that raises max concurrent
+  // streams to 2.
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(new_settings));
+  MockRead reads[] = {
+    CreateMockRead(*settings_frame),
+    MockRead(SYNCHRONOUS, 0, 0)  // EOF
+  };
+
+  StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  // Initialize the SpdySetting with 1 max concurrent streams.
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  spdy_session_pool->http_server_properties()->SetSpdySetting(
+      test_host_port_pair,
+      kSpdySettingsIds1,
+      SETTINGS_FLAG_PLEASE_PERSIST,
+      1);
+
+  // Create a session.
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  // Create 2 streams.  First will succeed.  Second will be pending.
+  scoped_refptr<SpdyStream> spdy_stream1;
+  TestCompletionCallback callback1;
+  GURL url("http://www.google.com");
+  EXPECT_EQ(OK,
+            session->CreateStream(url,
+                                  MEDIUM, /* priority, not important */
+                                  &spdy_stream1,
+                                  BoundNetLog(),
+                                  callback1.callback()));
+
+  StreamReleaserCallback stream_releaser(session, spdy_stream1);
+
+  ASSERT_EQ(ERR_IO_PENDING,
+            session->CreateStream(url,
+                                  MEDIUM, /* priority, not important */
+                                  stream_releaser.stream(),
+                                  BoundNetLog(),
+                                  stream_releaser.callback()));
+
+  // Make sure |stream_releaser| holds the last refs.
+  session = NULL;
+  spdy_stream1 = NULL;
+
+  EXPECT_EQ(OK, stream_releaser.WaitForResult());
+}
+
+// Start with max concurrent streams set to 1.  Request two streams.  When the
+// first completes, have the callback close itself, which should trigger the
+// second stream creation.  Then cancel that one immediately.  Don't crash.
+// http://crbug.com/63532
+TEST_F(SpdySessionSpdy3Test, CancelPendingCreateStream) {
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  MockRead reads[] = {
+    MockRead(SYNCHRONOUS, ERR_IO_PENDING)  // Stall forever.
+  };
+
+  StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+  MockConnect connect_data(SYNCHRONOUS, OK);
+
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  // Initialize the SpdySetting with 1 max concurrent streams.
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  spdy_session_pool->http_server_properties()->SetSpdySetting(
+      test_host_port_pair,
+      SETTINGS_MAX_CONCURRENT_STREAMS,
+      SETTINGS_FLAG_PLEASE_PERSIST,
+      1);
+
+  // Create a session.
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  // Use scoped_ptr to let us invalidate the memory when we want to, to trigger
+  // a valgrind error if the callback is invoked when it's not supposed to be.
+  scoped_ptr<TestCompletionCallback> callback(new TestCompletionCallback);
+
+  // Create 2 streams.  First will succeed.  Second will be pending.
+  scoped_refptr<SpdyStream> spdy_stream1;
+  GURL url("http://www.google.com");
+  ASSERT_EQ(OK,
+            session->CreateStream(url,
+                                  MEDIUM, /* priority, not important */
+                                  &spdy_stream1,
+                                  BoundNetLog(),
+                                  callback->callback()));
+
+  scoped_refptr<SpdyStream> spdy_stream2;
+  ASSERT_EQ(ERR_IO_PENDING,
+            session->CreateStream(url,
+                                  MEDIUM, /* priority, not important */
+                                  &spdy_stream2,
+                                  BoundNetLog(),
+                                  callback->callback()));
+
+  // Release the first one, this will allow the second to be created.
+  spdy_stream1->Cancel();
+  spdy_stream1 = NULL;
+
+  session->CancelPendingCreateStreams(&spdy_stream2);
+  callback.reset();
+
+  // Should not crash when running the pending callback.
+  MessageLoop::current()->RunUntilIdle();
+}
+
+TEST_F(SpdySessionSpdy3Test, SendInitialSettingsOnNewSession) {
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  MockRead reads[] = {
+    MockRead(SYNCHRONOUS, ERR_IO_PENDING)  // Stall forever.
+  };
+
+  SettingsMap settings;
+  const SpdySettingsIds kSpdySettingsIds1 = SETTINGS_MAX_CONCURRENT_STREAMS;
+  const SpdySettingsIds kSpdySettingsIds2 = SETTINGS_INITIAL_WINDOW_SIZE;
+  const uint32 kInitialRecvWindowSize = 10 * 1024 * 1024;
+  settings[kSpdySettingsIds1] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kMaxConcurrentPushedStreams);
+  settings[kSpdySettingsIds2] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kInitialRecvWindowSize);
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+  MockWrite writes[] = {
+    CreateMockWrite(*settings_frame),
+  };
+  session_deps.initial_recv_window_size = kInitialRecvWindowSize;
+
+  StaticSocketDataProvider data(
+      reads, arraysize(reads), writes, arraysize(writes));
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  static const char kStreamUrl[] = "http://www.google.com/";
+  GURL url(kStreamUrl);
+
+  const std::string kTestHost("www.google.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  SpdySessionPoolPeer pool_peer(spdy_session_pool);
+  pool_peer.EnableSendingInitialSettings(true);
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+  MessageLoop::current()->RunUntilIdle();
+  EXPECT_TRUE(data.at_write_eof());
+}
+
+TEST_F(SpdySessionSpdy3Test, SendSettingsOnNewSession) {
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  MockRead reads[] = {
+    MockRead(SYNCHRONOUS, ERR_IO_PENDING)  // Stall forever.
+  };
+
+  // Create the bogus setting that we want to verify is sent out.
+  // Note that it will be marked as SETTINGS_FLAG_PERSISTED when sent out. But
+  // to persist it into the HttpServerProperties, we need to mark as
+  // SETTINGS_FLAG_PLEASE_PERSIST.
+  SettingsMap settings;
+  const SpdySettingsIds kSpdySettingsIds1 = SETTINGS_UPLOAD_BANDWIDTH;
+  const uint32 kBogusSettingValue = 0xCDCD;
+  settings[kSpdySettingsIds1] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_PERSISTED, kBogusSettingValue);
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+  MockWrite writes[] = {
+    CreateMockWrite(*settings_frame),
+  };
+
+  StaticSocketDataProvider data(
+      reads, arraysize(reads), writes, arraysize(writes));
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  spdy_session_pool->http_server_properties()->SetSpdySetting(
+      test_host_port_pair,
+      kSpdySettingsIds1,
+      SETTINGS_FLAG_PLEASE_PERSIST,
+      kBogusSettingValue);
+
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+  MessageLoop::current()->RunUntilIdle();
+  EXPECT_TRUE(data.at_write_eof());
+}
+
+namespace {
+// This test has two variants, one for each style of closing the connection.
+// If |clean_via_close_current_sessions| is false, the sessions are closed
+// manually, calling SpdySessionPool::Remove() directly.  If it is true,
+// sessions are closed with SpdySessionPool::CloseCurrentSessions().
+void IPPoolingTest(bool clean_via_close_current_sessions) {
+  const int kTestPort = 80;
+  struct TestHosts {
+    std::string name;
+    std::string iplist;
+    HostPortProxyPair pair;
+    AddressList addresses;
+  } test_hosts[] = {
+    { "www.foo.com",    "192.0.2.33,192.168.0.1,192.168.0.5" },
+    { "images.foo.com", "192.168.0.2,192.168.0.3,192.168.0.5,192.0.2.33" },
+    { "js.foo.com",     "192.168.0.4,192.168.0.3" },
+  };
+
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_hosts); i++) {
+    session_deps.host_resolver->rules()->AddIPLiteralRule(test_hosts[i].name,
+        test_hosts[i].iplist, "");
+
+    // This test requires that the HostResolver cache be populated.  Normal
+    // code would have done this already, but we do it manually.
+    HostResolver::RequestInfo info(HostPortPair(test_hosts[i].name, kTestPort));
+    session_deps.host_resolver->Resolve(
+        info, &test_hosts[i].addresses, CompletionCallback(), NULL,
+        BoundNetLog());
+
+    // Setup a HostPortProxyPair
+    test_hosts[i].pair = HostPortProxyPair(
+        HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct());
+  }
+
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  MockRead reads[] = {
+    MockRead(SYNCHRONOUS, ERR_IO_PENDING)  // Stall forever.
+  };
+
+  StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  // Setup the first session to the first host.
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[0].pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(test_hosts[0].pair, BoundNetLog());
+  EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[0].pair));
+
+  HostPortPair test_host_port_pair(test_hosts[0].name, kTestPort);
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  // TODO(rtenneti): MockClientSocket::GetPeerAddress return's 0 as the port
+  // number. Fix it to return port 80 and then use GetPeerAddress to AddAlias.
+  SpdySessionPoolPeer pool_peer(spdy_session_pool);
+  pool_peer.AddAlias(test_hosts[0].addresses.front(), test_hosts[0].pair);
+
+  // Flush the SpdySession::OnReadComplete() task.
+  MessageLoop::current()->RunUntilIdle();
+
+  // The third host has no overlap with the first, so it can't pool IPs.
+  EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair));
+
+  // The second host overlaps with the first, and should IP pool.
+  EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair));
+
+  // Verify that the second host, through a proxy, won't share the IP.
+  HostPortProxyPair proxy_pair(test_hosts[1].pair.first,
+      ProxyServer::FromPacString("HTTP http://proxy.foo.com/"));
+  EXPECT_FALSE(spdy_session_pool->HasSession(proxy_pair));
+
+  // Overlap between 2 and 3 does is not transitive to 1.
+  EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair));
+
+  // Create a new session to host 2.
+  scoped_refptr<SpdySession> session2 =
+      spdy_session_pool->Get(test_hosts[2].pair, BoundNetLog());
+
+  // Verify that we have sessions for everything.
+  EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[0].pair));
+  EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair));
+  EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[2].pair));
+
+  // Grab the session to host 1 and verify that it is the same session
+  // we got with host 0, and that is a different than host 2's session.
+  scoped_refptr<SpdySession> session1 =
+      spdy_session_pool->Get(test_hosts[1].pair, BoundNetLog());
+  EXPECT_EQ(session.get(), session1.get());
+  EXPECT_NE(session2.get(), session1.get());
+
+  // Remove the aliases and observe that we still have a session for host1.
+  pool_peer.RemoveAliases(test_hosts[0].pair);
+  pool_peer.RemoveAliases(test_hosts[1].pair);
+  EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair));
+
+  // Expire the host cache
+  session_deps.host_resolver->GetHostCache()->clear();
+  EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair));
+
+  // Cleanup the sessions.
+  if (!clean_via_close_current_sessions) {
+    spdy_session_pool->Remove(session);
+    session = NULL;
+    spdy_session_pool->Remove(session2);
+    session2 = NULL;
+  } else {
+    spdy_session_pool->CloseCurrentSessions(net::ERR_ABORTED);
+  }
+
+  // Verify that the map is all cleaned up.
+  EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[0].pair));
+  EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[1].pair));
+  EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair));
+}
+
+}  // namespace
+
+TEST_F(SpdySessionSpdy3Test, IPPooling) {
+  IPPoolingTest(false);
+}
+
+TEST_F(SpdySessionSpdy3Test, IPPoolingCloseCurrentSessions) {
+  IPPoolingTest(true);
+}
+
+TEST_F(SpdySessionSpdy3Test, ClearSettingsStorageOnIPAddressChanged) {
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+
+  SpdySessionDependencies session_deps;
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+  HttpServerProperties* test_http_server_properties =
+      spdy_session_pool->http_server_properties();
+  SettingsFlagsAndValue flags_and_value1(SETTINGS_FLAG_PLEASE_PERSIST, 2);
+  test_http_server_properties->SetSpdySetting(
+      test_host_port_pair,
+      SETTINGS_MAX_CONCURRENT_STREAMS,
+      SETTINGS_FLAG_PLEASE_PERSIST,
+      2);
+  EXPECT_NE(0u, test_http_server_properties->GetSpdySettings(
+      test_host_port_pair).size());
+  spdy_session_pool->OnIPAddressChanged();
+  EXPECT_EQ(0u, test_http_server_properties->GetSpdySettings(
+      test_host_port_pair).size());
+}
+
+TEST_F(SpdySessionSpdy3Test, NeedsCredentials) {
+  SpdySessionDependencies session_deps;
+
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  MockRead reads[] = {
+    MockRead(SYNCHRONOUS, ERR_IO_PENDING)  // Stall forever.
+  };
+  StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  ssl.channel_id_sent = true;
+  ssl.protocol_negotiated = kProtoSPDY3;
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  const GURL url("https:/www.foo.com");
+  HostPortPair test_host_port_pair(url.host(), 443);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+  SSLConfig ssl_config;
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_refptr<SOCKSSocketParams> socks_params;
+  scoped_refptr<HttpProxySocketParams> http_proxy_params;
+  scoped_refptr<SSLSocketParams> ssl_params(
+      new SSLSocketParams(transport_params,
+                          socks_params,
+                          http_proxy_params,
+                          ProxyServer::SCHEME_DIRECT,
+                          test_host_port_pair,
+                          ssl_config,
+                          0,
+                          false,
+                          false));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 ssl_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetSSLSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), true, OK));
+
+  EXPECT_TRUE(session->NeedsCredentials());
+
+  // Flush the SpdySession::OnReadComplete() task.
+  MessageLoop::current()->RunUntilIdle();
+
+  spdy_session_pool->Remove(session);
+}
+
+TEST_F(SpdySessionSpdy3Test, SendCredentials) {
+  SpdySessionDependencies session_deps;
+
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  MockRead reads[] = {
+    MockRead(SYNCHRONOUS, ERR_IO_PENDING)  // Stall forever.
+  };
+  SettingsMap settings;
+  scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(settings));
+  MockWrite writes[] = {
+    CreateMockWrite(*settings_frame),
+  };
+  StaticSocketDataProvider data(reads, arraysize(reads),
+                                writes, arraysize(writes));
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  ssl.channel_id_sent = true;
+  ssl.protocol_negotiated = kProtoSPDY3;
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  const GURL kTestUrl("https://www.foo.com");
+  HostPortPair test_host_port_pair(kTestUrl.host(), 443);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+  SSLConfig ssl_config;
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_refptr<SOCKSSocketParams> socks_params;
+  scoped_refptr<HttpProxySocketParams> http_proxy_params;
+  scoped_refptr<SSLSocketParams> ssl_params(
+      new SSLSocketParams(transport_params,
+                          socks_params,
+                          http_proxy_params,
+                          ProxyServer::SCHEME_DIRECT,
+                          test_host_port_pair,
+                          ssl_config,
+                          0,
+                          false,
+                          false));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 ssl_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetSSLSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), true, OK));
+  EXPECT_TRUE(session->NeedsCredentials());
+
+  // Flush the SpdySession::OnReadComplete() task.
+  MessageLoop::current()->RunUntilIdle();
+
+  spdy_session_pool->Remove(session);
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+}
+
+TEST_F(SpdySessionSpdy3Test, CloseSessionOnError) {
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  scoped_ptr<SpdyFrame> goaway(ConstructSpdyGoAway());
+  MockRead reads[] = {
+    CreateMockRead(*goaway),
+    MockRead(SYNCHRONOUS, 0, 0)  // EOF
+  };
+
+  CapturingBoundNetLog log;
+
+  StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, log.bound());
+  EXPECT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 log.bound()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  // Flush the SpdySession::OnReadComplete() task.
+  MessageLoop::current()->RunUntilIdle();
+
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+
+  // Check that the NetLog was filled reasonably.
+  net::CapturingNetLog::CapturedEntryList entries;
+  log.GetEntries(&entries);
+  EXPECT_LT(0u, entries.size());
+
+  // Check that we logged SPDY_SESSION_CLOSE correctly.
+  int pos = net::ExpectLogContainsSomewhere(
+      entries, 0,
+      net::NetLog::TYPE_SPDY_SESSION_CLOSE,
+      net::NetLog::PHASE_NONE);
+
+  CapturingNetLog::CapturedEntry entry = entries[pos];
+  int error_code = 0;
+  ASSERT_TRUE(entry.GetNetErrorCode(&error_code));
+  EXPECT_EQ(ERR_CONNECTION_CLOSED, error_code);
+}
+
+TEST_F(SpdySessionSpdy3Test, UpdateStreamsSendWindowSize) {
+  // Set SETTINGS_INITIAL_WINDOW_SIZE to a small number so that WINDOW_UPDATE
+  // gets sent.
+  SettingsMap new_settings;
+  int32 window_size = 1;
+  new_settings[SETTINGS_INITIAL_WINDOW_SIZE] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_NONE, window_size);
+
+  // Set up the socket so we read a SETTINGS frame that sets
+  // INITIAL_WINDOW_SIZE.
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(new_settings));
+  MockRead reads[] = {
+    CreateMockRead(*settings_frame, 0),
+    MockRead(ASYNC, 0, 1)  // EOF
+  };
+
+  SpdySessionDependencies session_deps;
+
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  scoped_ptr<DeterministicSocketData> data(
+      new DeterministicSocketData(reads, arraysize(reads), NULL, 0));
+  data->set_connect_data(connect_data);
+  session_deps.deterministic_socket_factory->AddSocketDataProvider(data.get());
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+  // Create a session.
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  scoped_refptr<SpdyStream> spdy_stream1;
+  TestCompletionCallback callback1;
+  GURL url("http://www.google.com");
+  EXPECT_EQ(OK,
+            session->CreateStream(url,
+                                  MEDIUM,  /* priority, not important */
+                                  &spdy_stream1,
+                                  BoundNetLog(),
+                                  callback1.callback()));
+  EXPECT_NE(spdy_stream1->send_window_size(), window_size);
+
+  data->RunFor(1);  // Process the SETTINGS frame, but not the EOF
+  MessageLoop::current()->RunUntilIdle();
+  EXPECT_EQ(session->initial_send_window_size(), window_size);
+  EXPECT_EQ(spdy_stream1->send_window_size(), window_size);
+
+  // Release the first one, this will allow the second to be created.
+  spdy_stream1->Cancel();
+  spdy_stream1 = NULL;
+
+  scoped_refptr<SpdyStream> spdy_stream2;
+  EXPECT_EQ(OK,
+            session->CreateStream(url,
+                                  MEDIUM,  /* priority, not important */
+                                  &spdy_stream2,
+                                  BoundNetLog(),
+                                  callback1.callback()));
+
+  EXPECT_EQ(spdy_stream2->send_window_size(), window_size);
+  spdy_stream2->Cancel();
+  spdy_stream2 = NULL;
+}
+
+TEST_F(SpdySessionSpdy3Test, OutOfOrderSynStreams) {
+  // Construct the request.
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, HIGHEST));
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+  MockWrite writes[] = {
+    CreateMockWrite(*req1, 2),
+    CreateMockWrite(*req2, 1),
+  };
+
+  scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body1(ConstructSpdyBodyFrame(3, true));
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 5));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(5, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp1, 3),
+    CreateMockRead(*body1, 4),
+    CreateMockRead(*resp2, 5),
+    CreateMockRead(*body2, 6),
+    MockRead(ASYNC, 0, 7)  // EOF
+  };
+
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  StaticSocketDataProvider data(reads, arraysize(reads),
+                                writes, arraysize(writes));
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+  // Create a session.
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  scoped_refptr<SpdyStream> spdy_stream1;
+  TestCompletionCallback callback1;
+  GURL url1("http://www.google.com");
+  EXPECT_EQ(OK, session->CreateStream(url1, LOWEST, &spdy_stream1,
+                                      BoundNetLog(), callback1.callback()));
+  EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+  scoped_refptr<SpdyStream> spdy_stream2;
+  TestCompletionCallback callback2;
+  GURL url2("http://www.google.com");
+  EXPECT_EQ(OK, session->CreateStream(url2, HIGHEST, &spdy_stream2,
+                                      BoundNetLog(), callback2.callback()));
+  EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+  scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+  (*headers)[":method"] = "GET";
+  (*headers)[":scheme"] = url1.scheme();
+  (*headers)[":host"] = url1.host();
+  (*headers)[":path"] = url1.path();
+  (*headers)[":version"] = "HTTP/1.1";
+  scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock);
+  *headers2 = *headers;
+
+  spdy_stream1->set_spdy_headers(headers.Pass());
+  EXPECT_TRUE(spdy_stream1->HasUrl());
+
+  spdy_stream2->set_spdy_headers(headers2.Pass());
+  EXPECT_TRUE(spdy_stream2->HasUrl());
+
+  spdy_stream1->SendRequest(false);
+  spdy_stream2->SendRequest(false);
+  MessageLoop::current()->RunUntilIdle();
+
+  EXPECT_EQ(3u, spdy_stream1->stream_id());
+  EXPECT_EQ(1u, spdy_stream2->stream_id());
+
+  spdy_stream1->Cancel();
+  spdy_stream1 = NULL;
+
+  spdy_stream2->Cancel();
+  spdy_stream2 = NULL;
+}
+
+TEST_F(SpdySessionSpdy3Test, CancelStream) {
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  // Request 1, at HIGHEST priority, will be cancelled before it writes data.
+  // Request 2, at LOWEST priority, will be a full request and will be id 1.
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  MockWrite writes[] = {
+    CreateMockWrite(*req2, 0),
+  };
+
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(1, true));
+  MockRead reads[] = {
+    CreateMockRead(*resp2, 1),
+    CreateMockRead(*body2, 2),
+    MockRead(ASYNC, 0, 3)  // EOF
+  };
+
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+  data.set_connect_data(connect_data);
+  session_deps.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+  // Create a session.
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  scoped_refptr<SpdyStream> spdy_stream1;
+  TestCompletionCallback callback1;
+  GURL url1("http://www.google.com");
+  EXPECT_EQ(OK, session->CreateStream(url1, HIGHEST, &spdy_stream1,
+                                      BoundNetLog(), callback1.callback()));
+  EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+  scoped_refptr<SpdyStream> spdy_stream2;
+  TestCompletionCallback callback2;
+  GURL url2("http://www.google.com");
+  EXPECT_EQ(OK, session->CreateStream(url2, LOWEST, &spdy_stream2,
+                                      BoundNetLog(), callback2.callback()));
+  EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+  scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+  (*headers)[":method"] = "GET";
+  (*headers)[":scheme"] = url1.scheme();
+  (*headers)[":host"] = url1.host();
+  (*headers)[":path"] = url1.path();
+  (*headers)[":version"] = "HTTP/1.1";
+  scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock);
+  *headers2 = *headers;
+
+  spdy_stream1->set_spdy_headers(headers.Pass());
+  EXPECT_TRUE(spdy_stream1->HasUrl());
+
+  spdy_stream2->set_spdy_headers(headers2.Pass());
+  EXPECT_TRUE(spdy_stream2->HasUrl());
+
+  spdy_stream1->SendRequest(false);
+  spdy_stream2->SendRequest(false);
+
+  EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+  spdy_stream1->Cancel();
+
+  EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+  data.RunFor(1);
+
+  EXPECT_EQ(0u, spdy_stream1->stream_id());
+  EXPECT_EQ(1u, spdy_stream2->stream_id());
+
+  spdy_stream1 = NULL;
+  spdy_stream2->Cancel();
+  spdy_stream2 = NULL;
+}
+
+TEST_F(SpdySessionSpdy3Test, CloseSessionWithTwoCreatedStreams) {
+  // Test that if a sesion is closed with two created streams pending,
+  // it does not crash.  http://crbug.com/139518
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  // No actual data will be sent.
+  MockWrite writes[] = {
+    MockWrite(ASYNC, 0, 1)  // EOF
+  };
+
+  MockRead reads[] = {
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+  data.set_connect_data(connect_data);
+  session_deps.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+  // Create a session.
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  scoped_refptr<SpdyStream> spdy_stream1;
+  TestCompletionCallback callback1;
+  GURL url1("http://www.google.com");
+  EXPECT_EQ(OK, session->CreateStream(url1, HIGHEST, &spdy_stream1,
+                                      BoundNetLog(), callback1.callback()));
+  EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+  scoped_refptr<SpdyStream> spdy_stream2;
+  TestCompletionCallback callback2;
+  GURL url2("http://www.google.com");
+  EXPECT_EQ(OK, session->CreateStream(url2, LOWEST, &spdy_stream2,
+                                      BoundNetLog(), callback2.callback()));
+  EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+  scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+  (*headers)["method"] = "GET";
+  (*headers)["scheme"] = url1.scheme();
+  (*headers)["host"] = url1.host();
+  (*headers)["url"] = url1.path();
+  (*headers)["version"] = "HTTP/1.1";
+  scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock);
+  *headers2 = *headers;
+
+  spdy_stream1->set_spdy_headers(headers.Pass());
+  EXPECT_TRUE(spdy_stream1->HasUrl());
+  ClosingDelegate delegate1(spdy_stream1.get());
+  spdy_stream1->SetDelegate(&delegate1);
+
+  spdy_stream2->set_spdy_headers(headers2.Pass());
+  EXPECT_TRUE(spdy_stream2->HasUrl());
+  ClosingDelegate delegate2(spdy_stream2.get());
+  spdy_stream2->SetDelegate(&delegate2);
+
+  spdy_stream1->SendRequest(false);
+  spdy_stream2->SendRequest(false);
+
+  // Ensure that the streams have not yet been activated and assigned an id.
+  EXPECT_EQ(0u, spdy_stream1->stream_id());
+  EXPECT_EQ(0u, spdy_stream2->stream_id());
+
+  // Ensure we don't crash while closing the session.
+  session->CloseSessionOnError(ERR_ABORTED, true, "");
+
+  EXPECT_TRUE(spdy_stream1->closed());
+  EXPECT_TRUE(spdy_stream2->closed());
+
+  spdy_stream1 = NULL;
+  spdy_stream2 = NULL;
+}
+
+TEST_F(SpdySessionSpdy3Test, VerifyDomainAuthentication) {
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  // No actual data will be sent.
+  MockWrite writes[] = {
+    MockWrite(ASYNC, 0, 1)  // EOF
+  };
+
+  MockRead reads[] = {
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+  data.set_connect_data(connect_data);
+  session_deps.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+  // Load a cert that is valid for:
+  //   www.example.org
+  //   mail.example.org
+  //   www.example.com
+  FilePath certs_dir = GetTestCertsDirectory();
+  scoped_refptr<X509Certificate> test_cert(
+      ImportCertFromFile(certs_dir, "spdy_pooling.pem"));
+  ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  ssl.cert = test_cert;
+  session_deps.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps));
+
+  const std::string kTestHost("www.example.org");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+  // Create a session.
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+  SSLConfig ssl_config;
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_refptr<SOCKSSocketParams> socks_params;
+  scoped_refptr<HttpProxySocketParams> http_proxy_params;
+  scoped_refptr<SSLSocketParams> ssl_params(
+      new SSLSocketParams(transport_params,
+                          socks_params,
+                          http_proxy_params,
+                          ProxyServer::SCHEME_DIRECT,
+                          test_host_port_pair,
+                          ssl_config,
+                          0,
+                          false,
+                          false));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 ssl_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetSSLSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+  EXPECT_TRUE(session->VerifyDomainAuthentication("www.example.org"));
+  EXPECT_TRUE(session->VerifyDomainAuthentication("mail.example.org"));
+  EXPECT_TRUE(session->VerifyDomainAuthentication("mail.example.com"));
+  EXPECT_FALSE(session->VerifyDomainAuthentication("mail.google.com"));
+}
+
+TEST_F(SpdySessionSpdy3Test, ConnectionPooledWithTlsChannelId) {
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  // No actual data will be sent.
+  MockWrite writes[] = {
+    MockWrite(ASYNC, 0, 1)  // EOF
+  };
+
+  MockRead reads[] = {
+    MockRead(ASYNC, 0, 0)  // EOF
+  };
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+  data.set_connect_data(connect_data);
+  session_deps.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+  // Load a cert that is valid for:
+  //   www.example.org
+  //   mail.example.org
+  //   www.example.com
+  FilePath certs_dir = GetTestCertsDirectory();
+  scoped_refptr<X509Certificate> test_cert(
+      ImportCertFromFile(certs_dir, "spdy_pooling.pem"));
+  ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  ssl.channel_id_sent = true;
+  ssl.cert = test_cert;
+  session_deps.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps));
+
+  const std::string kTestHost("www.example.org");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+  // Create a session.
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+  SSLConfig ssl_config;
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                MEDIUM,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_refptr<SOCKSSocketParams> socks_params;
+  scoped_refptr<HttpProxySocketParams> http_proxy_params;
+  scoped_refptr<SSLSocketParams> ssl_params(
+      new SSLSocketParams(transport_params,
+                          socks_params,
+                          http_proxy_params,
+                          ProxyServer::SCHEME_DIRECT,
+                          test_host_port_pair,
+                          ssl_config,
+                          0,
+                          false,
+                          false));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 ssl_params, MEDIUM, CompletionCallback(),
+                                 http_session->GetSSLSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+  EXPECT_TRUE(session->VerifyDomainAuthentication("www.example.org"));
+  EXPECT_TRUE(session->VerifyDomainAuthentication("mail.example.org"));
+  EXPECT_FALSE(session->VerifyDomainAuthentication("mail.example.com"));
+  EXPECT_FALSE(session->VerifyDomainAuthentication("mail.google.com"));
+}
+
+TEST_F(SpdySessionSpdy3Test, CloseTwoStalledCreateStream) {
+  // TODO(rtenneti): Define a helper class/methods and move the common code in
+  // this file.
+  MockConnect connect_data(SYNCHRONOUS, OK);
+
+  SettingsMap new_settings;
+  const SpdySettingsIds kSpdySettingsIds1 = SETTINGS_MAX_CONCURRENT_STREAMS;
+  const uint32 max_concurrent_streams = 1;
+  new_settings[kSpdySettingsIds1] =
+      SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
+
+  scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
+  scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
+  scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, LOWEST));
+  MockWrite writes[] = {
+    CreateMockWrite(*req1, 1),
+    CreateMockWrite(*req2, 4),
+    CreateMockWrite(*req3, 7),
+  };
+
+  // Set up the socket so we read a SETTINGS frame that sets max concurrent
+  // streams to 1.
+  scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(new_settings));
+
+  scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> body1(ConstructSpdyBodyFrame(1, true));
+
+  scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
+  scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, true));
+
+  scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
+  scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(5, true));
+
+  MockRead reads[] = {
+    CreateMockRead(*settings_frame),
+    CreateMockRead(*resp1, 2),
+    CreateMockRead(*body1, 3),
+    CreateMockRead(*resp2, 5),
+    CreateMockRead(*body2, 6),
+    CreateMockRead(*resp3, 8),
+    CreateMockRead(*body3, 9),
+    MockRead(ASYNC, 0, 10)  // EOF
+  };
+
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  DeterministicSocketData data(reads, arraysize(reads),
+                               writes, arraysize(writes));
+  data.set_connect_data(connect_data);
+  session_deps.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+  // Create a session.
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                LOWEST,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params, LOWEST, CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  // Read the settings frame.
+  data.RunFor(1);
+
+  scoped_refptr<SpdyStream> spdy_stream1;
+  TestCompletionCallback callback1;
+  GURL url1("http://www.google.com");
+  EXPECT_EQ(OK, session->CreateStream(url1,
+                                      LOWEST,
+                                      &spdy_stream1,
+                                      BoundNetLog(),
+                                      callback1.callback()));
+  EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+  scoped_refptr<SpdyStream> spdy_stream2;
+  TestCompletionCallback callback2;
+  GURL url2("http://www.google.com");
+  EXPECT_EQ(ERR_IO_PENDING,
+            session->CreateStream(url2,
+                                  LOWEST,
+                                  &spdy_stream2,
+                                  BoundNetLog(),
+                                  callback2.callback()));
+
+  scoped_refptr<SpdyStream> spdy_stream3;
+  TestCompletionCallback callback3;
+  GURL url3("http://www.google.com");
+  EXPECT_EQ(ERR_IO_PENDING,
+            session->CreateStream(url3,
+                                  LOWEST,
+                                  &spdy_stream3,
+                                  BoundNetLog(),
+                                  callback3.callback()));
+
+  EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+  EXPECT_EQ(2u, session->pending_create_stream_queues(LOWEST));
+
+  scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+  (*headers)[":method"] = "GET";
+  (*headers)[":scheme"] = url1.scheme();
+  (*headers)[":host"] = url1.host();
+  (*headers)[":path"] = url1.path();
+  (*headers)[":version"] = "HTTP/1.1";
+  scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock);
+  *headers2 = *headers;
+  scoped_ptr<SpdyHeaderBlock> headers3(new SpdyHeaderBlock);
+  *headers3 = *headers;
+
+  spdy_stream1->set_spdy_headers(headers.Pass());
+  EXPECT_TRUE(spdy_stream1->HasUrl());
+  spdy_stream1->SendRequest(false);
+
+  // Run until 1st stream is closed.
+  EXPECT_EQ(0u, spdy_stream1->stream_id());
+  data.RunFor(3);
+  EXPECT_EQ(1u, spdy_stream1->stream_id());
+  EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+  EXPECT_EQ(1u, session->pending_create_stream_queues(LOWEST));
+
+  EXPECT_TRUE(spdy_stream2.get() != NULL);
+  spdy_stream2->set_spdy_headers(headers2.Pass());
+  EXPECT_TRUE(spdy_stream2->HasUrl());
+  spdy_stream2->SendRequest(false);
+
+  // Run until 2nd stream is closed.
+  EXPECT_EQ(0u, spdy_stream2->stream_id());
+  data.RunFor(3);
+  EXPECT_EQ(3u, spdy_stream2->stream_id());
+  EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+  EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
+
+  EXPECT_TRUE(spdy_stream3.get() != NULL);
+  spdy_stream3->set_spdy_headers(headers3.Pass());
+  EXPECT_TRUE(spdy_stream3->HasUrl());
+  spdy_stream3->SendRequest(false);
+
+  EXPECT_EQ(0u, spdy_stream3->stream_id());
+  data.RunFor(4);
+  EXPECT_EQ(5u, spdy_stream3->stream_id());
+  EXPECT_EQ(0u, session->num_active_streams() + session->num_created_streams());
+  EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
+}
+
+TEST_F(SpdySessionSpdy3Test, CancelTwoStalledCreateStream) {
+  SpdySessionDependencies session_deps;
+  session_deps.host_resolver->set_synchronous_mode(true);
+
+  MockRead reads[] = {
+    MockRead(SYNCHRONOUS, ERR_IO_PENDING)  // Stall forever.
+  };
+
+  StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
+  MockConnect connect_data(SYNCHRONOUS, OK);
+
+  data.set_connect_data(connect_data);
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
+  session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
+
+  scoped_refptr<HttpNetworkSession> http_session(
+      SpdySessionDependencies::SpdyCreateSession(&session_deps));
+
+  const std::string kTestHost("www.foo.com");
+  const int kTestPort = 80;
+  HostPortPair test_host_port_pair(kTestHost, kTestPort);
+  HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
+
+  SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
+
+  // Initialize the SpdySetting with 1 max concurrent streams.
+  spdy_session_pool->http_server_properties()->SetSpdySetting(
+      test_host_port_pair,
+      SETTINGS_MAX_CONCURRENT_STREAMS,
+      SETTINGS_FLAG_PLEASE_PERSIST,
+      1);
+
+  // Create a session.
+  EXPECT_FALSE(spdy_session_pool->HasSession(pair));
+  scoped_refptr<SpdySession> session =
+      spdy_session_pool->Get(pair, BoundNetLog());
+  ASSERT_TRUE(spdy_session_pool->HasSession(pair));
+
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(test_host_port_pair,
+                                LOWEST,
+                                false,
+                                false,
+                                OnHostResolutionCallback()));
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
+                                 transport_params,
+                                 LOWEST,
+                                 CompletionCallback(),
+                                 http_session->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
+
+  scoped_refptr<SpdyStream> spdy_stream1;
+  TestCompletionCallback callback1;
+  GURL url1("http://www.google.com");
+  ASSERT_EQ(OK,
+            session->CreateStream(url1,
+                                  LOWEST,
+                                  &spdy_stream1,
+                                  BoundNetLog(),
+                                  callback1.callback()));
+  EXPECT_EQ(0u, spdy_stream1->stream_id());
+
+  scoped_refptr<SpdyStream> spdy_stream2;
+  TestCompletionCallback callback2;
+  GURL url2("http://www.google.com");
+  ASSERT_EQ(ERR_IO_PENDING,
+            session->CreateStream(url2,
+                                  LOWEST,
+                                  &spdy_stream2,
+                                  BoundNetLog(),
+                                  callback2.callback()));
+
+  scoped_refptr<SpdyStream> spdy_stream3;
+  TestCompletionCallback callback3;
+  GURL url3("http://www.google.com");
+  ASSERT_EQ(ERR_IO_PENDING,
+            session->CreateStream(url3,
+                                  LOWEST,
+                                  &spdy_stream3,
+                                  BoundNetLog(),
+                                  callback3.callback()));
+
+  EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+  EXPECT_EQ(2u, session->pending_create_stream_queues(LOWEST));
+
+  // Cancel the first stream, this will allow the second stream to be created.
+  EXPECT_TRUE(spdy_stream1.get() != NULL);
+  spdy_stream1->Cancel();
+  spdy_stream1 = NULL;
+  session->CancelPendingCreateStreams(&spdy_stream1);
+  EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+  EXPECT_EQ(1u, session->pending_create_stream_queues(LOWEST));
+
+  // Cancel the second stream, this will allow the third stream to be created.
+  EXPECT_TRUE(spdy_stream2.get() != NULL);
+  spdy_stream2->Cancel();
+  spdy_stream2 = NULL;
+  session->CancelPendingCreateStreams(&spdy_stream2);
+  EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
+  EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
+
+  // Cancel the third stream.
+  EXPECT_TRUE(spdy_stream3.get() != NULL);
+  spdy_stream3->Cancel();
+  spdy_stream3 = NULL;
+  session->CancelPendingCreateStreams(&spdy_stream3);
+  EXPECT_EQ(0u, session->num_active_streams() + session->num_created_streams());
+  EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_stream.cc b/src/net/spdy/spdy_stream.cc
new file mode 100644
index 0000000..aeb370e
--- /dev/null
+++ b/src/net/spdy/spdy_stream.cc
@@ -0,0 +1,867 @@
+// Copyright (c) 2012 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_stream.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/stringprintf.h"
+#include "base/values.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_session.h"
+
+namespace net {
+
+namespace {
+
+Value* NetLogSpdyStreamErrorCallback(SpdyStreamId stream_id,
+                                     int status,
+                                     const std::string* description,
+                                     NetLog::LogLevel /* log_level */) {
+  DictionaryValue* dict = new DictionaryValue();
+  dict->SetInteger("stream_id", static_cast<int>(stream_id));
+  dict->SetInteger("status", status);
+  dict->SetString("description", *description);
+  return dict;
+}
+
+Value* NetLogSpdyStreamWindowUpdateCallback(SpdyStreamId stream_id,
+                                            int32 delta,
+                                            int32 window_size,
+                                            NetLog::LogLevel /* log_level */) {
+  DictionaryValue* dict = new DictionaryValue();
+  dict->SetInteger("stream_id", stream_id);
+  dict->SetInteger("delta", delta);
+  dict->SetInteger("window_size", window_size);
+  return dict;
+}
+
+bool ContainsUpperAscii(const std::string& str) {
+  for (std::string::const_iterator i(str.begin()); i != str.end(); ++i) {
+    if (*i >= 'A' && *i <= 'Z') {
+      return true;
+    }
+  }
+  return false;
+}
+
+}  // namespace
+
+SpdyStream::SpdyStream(SpdySession* session,
+                       bool pushed,
+                       const BoundNetLog& net_log)
+    : continue_buffering_data_(true),
+      stream_id_(0),
+      priority_(HIGHEST),
+      slot_(0),
+      stalled_by_flow_control_(false),
+      send_window_size_(kSpdyStreamInitialWindowSize),
+      recv_window_size_(kSpdyStreamInitialWindowSize),
+      unacked_recv_window_bytes_(0),
+      pushed_(pushed),
+      response_received_(false),
+      session_(session),
+      delegate_(NULL),
+      request_time_(base::Time::Now()),
+      response_(new SpdyHeaderBlock),
+      io_state_(STATE_NONE),
+      response_status_(OK),
+      cancelled_(false),
+      has_upload_data_(false),
+      net_log_(net_log),
+      send_bytes_(0),
+      recv_bytes_(0),
+      domain_bound_cert_type_(CLIENT_CERT_INVALID_TYPE),
+      domain_bound_cert_request_handle_(NULL) {
+}
+
+class SpdyStream::SpdyStreamIOBufferProducer
+    : public SpdySession::SpdyIOBufferProducer {
+ public:
+  SpdyStreamIOBufferProducer(SpdyStream* stream) : stream_(stream) {}
+
+  // SpdyFrameProducer
+  virtual RequestPriority GetPriority() const OVERRIDE {
+    return stream_->priority();
+  }
+
+  virtual SpdyIOBuffer* ProduceNextBuffer(SpdySession* session) OVERRIDE {
+    if (stream_->cancelled())
+      return NULL;
+    if (stream_->stream_id() == 0)
+      SpdySession::SpdyIOBufferProducer::ActivateStream(session, stream_);
+    frame_.reset(stream_->ProduceNextFrame());
+    return frame_ == NULL ? NULL :
+        SpdySession::SpdyIOBufferProducer::CreateIOBuffer(
+            frame_.get(), GetPriority(), stream_);
+  }
+
+ private:
+  scoped_refptr<SpdyStream> stream_;
+  scoped_ptr<SpdyFrame> frame_;
+};
+
+void SpdyStream::SetHasWriteAvailable() {
+  session_->SetStreamHasWriteAvailable(this,
+                                       new SpdyStreamIOBufferProducer(this));
+}
+
+SpdyFrame* SpdyStream::ProduceNextFrame() {
+  if (io_state_ == STATE_SEND_DOMAIN_BOUND_CERT_COMPLETE) {
+    CHECK(request_.get());
+    CHECK_GT(stream_id_, 0u);
+
+    std::string origin = GetUrl().GetOrigin().spec();
+    DCHECK(origin[origin.length() - 1] == '/');
+    origin.erase(origin.length() - 1);  // Trim trailing slash.
+    SpdyCredentialControlFrame* frame = session_->CreateCredentialFrame(
+        origin, domain_bound_cert_type_, domain_bound_private_key_,
+        domain_bound_cert_, priority_);
+    return frame;
+  } else if (io_state_ == STATE_SEND_HEADERS_COMPLETE) {
+    CHECK(request_.get());
+    CHECK_GT(stream_id_, 0u);
+
+    SpdyControlFlags flags =
+        has_upload_data_ ? CONTROL_FLAG_NONE : CONTROL_FLAG_FIN;
+    SpdySynStreamControlFrame* frame = session_->CreateSynStream(
+        stream_id_, priority_, slot_, flags, *request_);
+    send_time_ = base::TimeTicks::Now();
+    return frame;
+  } else {
+    CHECK(!cancelled());
+    // We must need to write stream data.
+    // Until the headers have been completely sent, we can not be sure
+    // that our stream_id is correct.
+    DCHECK_GT(io_state_, STATE_SEND_HEADERS_COMPLETE);
+    DCHECK_GT(stream_id_, 0u);
+    DCHECK(!pending_frames_.empty());
+
+    PendingFrame frame = pending_frames_.front();
+    pending_frames_.pop_front();
+
+    waiting_completions_.push_back(frame.type);
+
+    if (frame.type == TYPE_DATA) {
+      // Send queued data frame.
+      return frame.data_frame;
+    } else {
+      DCHECK(frame.type == TYPE_HEADERS);
+      // Create actual HEADERS frame just in time because it depends on
+      // compression context and should not be reordered after the creation.
+      SpdyFrame* header_frame = session_->CreateHeadersFrame(
+          stream_id_, *frame.header_block, SpdyControlFlags());
+      delete frame.header_block;
+      return header_frame;
+    }
+  }
+  NOTREACHED();
+}
+
+SpdyStream::~SpdyStream() {
+  UpdateHistograms();
+  while (!pending_frames_.empty()) {
+    PendingFrame frame = pending_frames_.back();
+    pending_frames_.pop_back();
+    if (frame.type == TYPE_DATA)
+      delete frame.data_frame;
+    else
+      delete frame.header_block;
+  }
+}
+
+void SpdyStream::SetDelegate(Delegate* delegate) {
+  CHECK(delegate);
+  delegate_ = delegate;
+
+  if (pushed_) {
+    CHECK(response_received());
+    MessageLoop::current()->PostTask(
+        FROM_HERE, base::Bind(&SpdyStream::PushedStreamReplayData, this));
+  } else {
+    continue_buffering_data_ = false;
+  }
+}
+
+void SpdyStream::PushedStreamReplayData() {
+  if (cancelled_ || !delegate_)
+    return;
+
+  continue_buffering_data_ = false;
+
+  int rv = delegate_->OnResponseReceived(*response_, response_time_, OK);
+  if (rv == ERR_INCOMPLETE_SPDY_HEADERS) {
+    // We don't have complete headers.  Assume we're waiting for another
+    // HEADERS frame.  Since we don't have headers, we had better not have
+    // any pending data frames.
+    if (pending_buffers_.size() != 0U) {
+      LogStreamError(ERR_SPDY_PROTOCOL_ERROR,
+                     "HEADERS incomplete headers, but pending data frames.");
+      session_->CloseStream(stream_id_, ERR_SPDY_PROTOCOL_ERROR);
+    }
+    return;
+  }
+
+  std::vector<scoped_refptr<IOBufferWithSize> > buffers;
+  buffers.swap(pending_buffers_);
+  for (size_t i = 0; i < buffers.size(); ++i) {
+    // It is always possible that a callback to the delegate results in
+    // the delegate no longer being available.
+    if (!delegate_)
+      break;
+    if (buffers[i]) {
+      delegate_->OnDataReceived(buffers[i]->data(), buffers[i]->size());
+    } else {
+      delegate_->OnDataReceived(NULL, 0);
+      session_->CloseStream(stream_id_, net::OK);
+      // Note: |this| may be deleted after calling CloseStream.
+      DCHECK_EQ(buffers.size() - 1, i);
+    }
+  }
+}
+
+void SpdyStream::DetachDelegate() {
+  delegate_ = NULL;
+  if (!closed())
+    Cancel();
+}
+
+const SpdyHeaderBlock& SpdyStream::spdy_headers() const {
+  DCHECK(request_ != NULL);
+  return *request_.get();
+}
+
+void SpdyStream::set_spdy_headers(scoped_ptr<SpdyHeaderBlock> headers) {
+  request_.reset(headers.release());
+}
+
+void SpdyStream::set_initial_recv_window_size(int32 window_size) {
+  session_->set_initial_recv_window_size(window_size);
+}
+
+void SpdyStream::PossiblyResumeIfStalled() {
+  if (send_window_size_ > 0 && stalled_by_flow_control_) {
+    stalled_by_flow_control_ = false;
+    io_state_ = STATE_SEND_BODY;
+    DoLoop(OK);
+  }
+}
+
+void SpdyStream::AdjustSendWindowSize(int32 delta_window_size) {
+  send_window_size_ += delta_window_size;
+  PossiblyResumeIfStalled();
+}
+
+void SpdyStream::IncreaseSendWindowSize(int32 delta_window_size) {
+  DCHECK(session_->is_flow_control_enabled());
+  DCHECK_GE(delta_window_size, 1);
+
+  // Ignore late WINDOW_UPDATEs.
+  if (closed())
+    return;
+
+  int32 new_window_size = send_window_size_ + delta_window_size;
+
+  // It's valid for send_window_size_ to become negative (via an incoming
+  // SETTINGS), in which case incoming WINDOW_UPDATEs will eventually make
+  // it positive; however, if send_window_size_ is positive and incoming
+  // WINDOW_UPDATE makes it negative, we have an overflow.
+  if (send_window_size_ > 0 && new_window_size < 0) {
+    std::string desc = base::StringPrintf(
+        "Received WINDOW_UPDATE [delta: %d] for stream %d overflows "
+        "send_window_size_ [current: %d]", delta_window_size, stream_id_,
+        send_window_size_);
+    session_->ResetStream(stream_id_, FLOW_CONTROL_ERROR, desc);
+    return;
+  }
+
+  send_window_size_ = new_window_size;
+
+  net_log_.AddEvent(
+      NetLog::TYPE_SPDY_STREAM_UPDATE_SEND_WINDOW,
+      base::Bind(&NetLogSpdyStreamWindowUpdateCallback,
+                 stream_id_, delta_window_size, send_window_size_));
+
+  PossiblyResumeIfStalled();
+}
+
+void SpdyStream::DecreaseSendWindowSize(int32 delta_window_size) {
+  // we only call this method when sending a frame, therefore
+  // |delta_window_size| should be within the valid frame size range.
+  DCHECK(session_->is_flow_control_enabled());
+  DCHECK_GE(delta_window_size, 1);
+  DCHECK_LE(delta_window_size, kMaxSpdyFrameChunkSize);
+
+  // |send_window_size_| should have been at least |delta_window_size| for
+  // this call to happen.
+  DCHECK_GE(send_window_size_, delta_window_size);
+
+  send_window_size_ -= delta_window_size;
+
+  net_log_.AddEvent(
+      NetLog::TYPE_SPDY_STREAM_UPDATE_SEND_WINDOW,
+      base::Bind(&NetLogSpdyStreamWindowUpdateCallback,
+                 stream_id_, -delta_window_size, send_window_size_));
+}
+
+void SpdyStream::IncreaseRecvWindowSize(int32 delta_window_size) {
+  DCHECK_GE(delta_window_size, 1);
+  // By the time a read is isued, stream may become inactive.
+  if (!session_->IsStreamActive(stream_id_))
+    return;
+
+  if (!session_->is_flow_control_enabled())
+    return;
+
+  int32 new_window_size = recv_window_size_ + delta_window_size;
+  if (recv_window_size_ > 0)
+    DCHECK(new_window_size > 0);
+
+  recv_window_size_ = new_window_size;
+  net_log_.AddEvent(
+      NetLog::TYPE_SPDY_STREAM_UPDATE_RECV_WINDOW,
+      base::Bind(&NetLogSpdyStreamWindowUpdateCallback,
+                 stream_id_, delta_window_size, recv_window_size_));
+
+  unacked_recv_window_bytes_ += delta_window_size;
+  if (unacked_recv_window_bytes_ > session_->initial_recv_window_size() / 2) {
+    session_->SendWindowUpdate(stream_id_, unacked_recv_window_bytes_);
+    unacked_recv_window_bytes_ = 0;
+  }
+}
+
+void SpdyStream::DecreaseRecvWindowSize(int32 delta_window_size) {
+  DCHECK_GE(delta_window_size, 1);
+
+  if (!session_->is_flow_control_enabled())
+    return;
+
+  recv_window_size_ -= delta_window_size;
+  net_log_.AddEvent(
+      NetLog::TYPE_SPDY_STREAM_UPDATE_RECV_WINDOW,
+      base::Bind(&NetLogSpdyStreamWindowUpdateCallback,
+                 stream_id_, -delta_window_size, recv_window_size_));
+
+  // Since we never decrease the initial window size, we should never hit
+  // a negative |recv_window_size_|, if we do, it's a client side bug, so we use
+  // PROTOCOL_ERROR for lack of a better error code.
+  if (recv_window_size_ < 0) {
+    session_->ResetStream(stream_id_, PROTOCOL_ERROR,
+                          "Negative recv window size");
+    NOTREACHED();
+  }
+}
+
+int SpdyStream::GetPeerAddress(IPEndPoint* address) const {
+  return session_->GetPeerAddress(address);
+}
+
+int SpdyStream::GetLocalAddress(IPEndPoint* address) const {
+  return session_->GetLocalAddress(address);
+}
+
+bool SpdyStream::WasEverUsed() const {
+  return session_->WasEverUsed();
+}
+
+base::Time SpdyStream::GetRequestTime() const {
+  return request_time_;
+}
+
+void SpdyStream::SetRequestTime(base::Time t) {
+  request_time_ = t;
+}
+
+int SpdyStream::OnResponseReceived(const SpdyHeaderBlock& response) {
+  int rv = OK;
+
+  metrics_.StartStream();
+
+  DCHECK(response_->empty());
+  *response_ = response;  // TODO(ukai): avoid copy.
+
+  recv_first_byte_time_ = base::TimeTicks::Now();
+  response_time_ = base::Time::Now();
+
+  // If we receive a response before we are in STATE_WAITING_FOR_RESPONSE, then
+  // the server has sent the SYN_REPLY too early.
+  if (!pushed_ && io_state_ != STATE_WAITING_FOR_RESPONSE)
+    return ERR_SPDY_PROTOCOL_ERROR;
+  if (pushed_)
+    CHECK(io_state_ == STATE_NONE);
+  io_state_ = STATE_OPEN;
+
+  // Append all the headers into the response header block.
+  for (SpdyHeaderBlock::const_iterator it = response.begin();
+       it != response.end(); ++it) {
+    // Disallow uppercase headers.
+    if (ContainsUpperAscii(it->first)) {
+      session_->ResetStream(stream_id_, PROTOCOL_ERROR,
+                            "Upper case characters in header: " + it->first);
+      response_status_ = ERR_SPDY_PROTOCOL_ERROR;
+      return ERR_SPDY_PROTOCOL_ERROR;
+    }
+  }
+
+  if ((*response_).find("transfer-encoding") != (*response_).end()) {
+    session_->ResetStream(stream_id_, PROTOCOL_ERROR,
+                         "Received transfer-encoding header");
+    return ERR_SPDY_PROTOCOL_ERROR;
+  }
+
+  if (delegate_)
+    rv = delegate_->OnResponseReceived(*response_, response_time_, rv);
+  // If delegate_ is not yet attached, we'll call OnResponseReceived after the
+  // delegate gets attached to the stream.
+
+  return rv;
+}
+
+int SpdyStream::OnHeaders(const SpdyHeaderBlock& headers) {
+  DCHECK(!response_->empty());
+
+  // Append all the headers into the response header block.
+  for (SpdyHeaderBlock::const_iterator it = headers.begin();
+      it != headers.end(); ++it) {
+    // Disallow duplicate headers.  This is just to be conservative.
+    if ((*response_).find(it->first) != (*response_).end()) {
+      LogStreamError(ERR_SPDY_PROTOCOL_ERROR, "HEADERS duplicate header");
+      response_status_ = ERR_SPDY_PROTOCOL_ERROR;
+      return ERR_SPDY_PROTOCOL_ERROR;
+    }
+
+    // Disallow uppercase headers.
+    if (ContainsUpperAscii(it->first)) {
+      session_->ResetStream(stream_id_, PROTOCOL_ERROR,
+                            "Upper case characters in header: " + it->first);
+      response_status_ = ERR_SPDY_PROTOCOL_ERROR;
+      return ERR_SPDY_PROTOCOL_ERROR;
+    }
+
+    (*response_)[it->first] = it->second;
+  }
+
+  if ((*response_).find("transfer-encoding") != (*response_).end()) {
+    session_->ResetStream(stream_id_, PROTOCOL_ERROR,
+                         "Received transfer-encoding header");
+    return ERR_SPDY_PROTOCOL_ERROR;
+  }
+
+  int rv = OK;
+  if (delegate_) {
+    rv = delegate_->OnResponseReceived(*response_, response_time_, rv);
+    // ERR_INCOMPLETE_SPDY_HEADERS means that we are waiting for more
+    // headers before the response header block is complete.
+    if (rv == ERR_INCOMPLETE_SPDY_HEADERS)
+      rv = OK;
+  }
+  return rv;
+}
+
+void SpdyStream::OnDataReceived(const char* data, int length) {
+  DCHECK_GE(length, 0);
+
+  // If we don't have a response, then the SYN_REPLY did not come through.
+  // We cannot pass data up to the caller unless the reply headers have been
+  // received.
+  if (!response_received()) {
+    LogStreamError(ERR_SYN_REPLY_NOT_RECEIVED, "Didn't receive a response.");
+    session_->CloseStream(stream_id_, ERR_SYN_REPLY_NOT_RECEIVED);
+    return;
+  }
+
+  if (!delegate_ || continue_buffering_data_) {
+    // It should be valid for this to happen in the server push case.
+    // We'll return received data when delegate gets attached to the stream.
+    if (length > 0) {
+      IOBufferWithSize* buf = new IOBufferWithSize(length);
+      memcpy(buf->data(), data, length);
+      pending_buffers_.push_back(make_scoped_refptr(buf));
+    } else {
+      pending_buffers_.push_back(NULL);
+      metrics_.StopStream();
+      // Note: we leave the stream open in the session until the stream
+      //       is claimed.
+    }
+    return;
+  }
+
+  CHECK(!closed());
+
+  // A zero-length read means that the stream is being closed.
+  if (!length) {
+    metrics_.StopStream();
+    session_->CloseStream(stream_id_, net::OK);
+    // Note: |this| may be deleted after calling CloseStream.
+    return;
+  }
+
+  DecreaseRecvWindowSize(length);
+
+  // Track our bandwidth.
+  metrics_.RecordBytes(length);
+  recv_bytes_ += length;
+  recv_last_byte_time_ = base::TimeTicks::Now();
+
+  if (!delegate_) {
+    // It should be valid for this to happen in the server push case.
+    // We'll return received data when delegate gets attached to the stream.
+    IOBufferWithSize* buf = new IOBufferWithSize(length);
+    memcpy(buf->data(), data, length);
+    pending_buffers_.push_back(make_scoped_refptr(buf));
+    return;
+  }
+
+  if (delegate_->OnDataReceived(data, length) != net::OK) {
+    // |delegate_| rejected the data.
+    LogStreamError(ERR_SPDY_PROTOCOL_ERROR, "Delegate rejected the data");
+    session_->CloseStream(stream_id_, ERR_SPDY_PROTOCOL_ERROR);
+    return;
+  }
+}
+
+// This function is only called when an entire frame is written.
+void SpdyStream::OnWriteComplete(int bytes) {
+  DCHECK_LE(0, bytes);
+  send_bytes_ += bytes;
+  if (cancelled() || closed())
+    return;
+  DoLoop(bytes);
+}
+
+int SpdyStream::GetProtocolVersion() const {
+  return session_->GetProtocolVersion();
+}
+
+void SpdyStream::LogStreamError(int status, const std::string& description) {
+  net_log_.AddEvent(NetLog::TYPE_SPDY_STREAM_ERROR,
+                    base::Bind(&NetLogSpdyStreamErrorCallback,
+                               stream_id_, status, &description));
+}
+
+void SpdyStream::OnClose(int status) {
+  io_state_ = STATE_DONE;
+  response_status_ = status;
+  Delegate* delegate = delegate_;
+  delegate_ = NULL;
+  if (delegate)
+    delegate->OnClose(status);
+}
+
+void SpdyStream::Cancel() {
+  if (cancelled())
+    return;
+
+  cancelled_ = true;
+  if (session_->IsStreamActive(stream_id_))
+    session_->ResetStream(stream_id_, CANCEL, "");
+  else if (stream_id_ == 0)
+    session_->CloseCreatedStream(this, CANCEL);
+}
+
+void SpdyStream::Close() {
+  if (stream_id_ != 0)
+    session_->CloseStream(stream_id_, net::OK);
+  else
+    session_->CloseCreatedStream(this, OK);
+}
+
+int SpdyStream::SendRequest(bool has_upload_data) {
+  // Pushed streams do not send any data, and should always be in STATE_OPEN or
+  // STATE_DONE. However, we still want to return IO_PENDING to mimic non-push
+  // behavior.
+  has_upload_data_ = has_upload_data;
+  if (pushed_) {
+    send_time_ = base::TimeTicks::Now();
+    DCHECK(!has_upload_data_);
+    DCHECK(response_received());
+    return ERR_IO_PENDING;
+  }
+  CHECK_EQ(STATE_NONE, io_state_);
+  io_state_ = STATE_GET_DOMAIN_BOUND_CERT;
+  return DoLoop(OK);
+}
+
+int SpdyStream::WriteHeaders(SpdyHeaderBlock* headers) {
+  // Until the first headers by SYN_STREAM have been completely sent, we can
+  // not be sure that our stream_id is correct.
+  DCHECK_GT(io_state_, STATE_SEND_HEADERS_COMPLETE);
+  CHECK_GT(stream_id_, 0u);
+
+  PendingFrame frame;
+  frame.type = TYPE_HEADERS;
+  frame.header_block = headers;
+  pending_frames_.push_back(frame);
+
+  SetHasWriteAvailable();
+  return ERR_IO_PENDING;
+}
+
+int SpdyStream::WriteStreamData(IOBuffer* data,
+                                int length,
+                                SpdyDataFlags flags) {
+  // Until the headers have been completely sent, we can not be sure
+  // that our stream_id is correct.
+  DCHECK_GT(io_state_, STATE_SEND_HEADERS_COMPLETE);
+  CHECK_GT(stream_id_, 0u);
+
+  SpdyDataFrame* data_frame = session_->CreateDataFrame(
+      stream_id_, data, length, flags);
+  if (!data_frame)
+    return ERR_IO_PENDING;
+
+  PendingFrame frame;
+  frame.type = TYPE_DATA;
+  frame.data_frame = data_frame;
+  pending_frames_.push_back(frame);
+
+  SetHasWriteAvailable();
+  return ERR_IO_PENDING;
+}
+
+bool SpdyStream::GetSSLInfo(SSLInfo* ssl_info,
+                            bool* was_npn_negotiated,
+                            NextProto* protocol_negotiated) {
+  return session_->GetSSLInfo(
+      ssl_info, was_npn_negotiated, protocol_negotiated);
+}
+
+bool SpdyStream::GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) {
+  return session_->GetSSLCertRequestInfo(cert_request_info);
+}
+
+bool SpdyStream::HasUrl() const {
+  if (pushed_)
+    return response_received();
+  return request_.get() != NULL;
+}
+
+GURL SpdyStream::GetUrl() const {
+  DCHECK(HasUrl());
+
+  const SpdyHeaderBlock& headers = (pushed_) ? *response_ : *request_;
+  return GetUrlFromHeaderBlock(headers, GetProtocolVersion(), pushed_);
+}
+
+void SpdyStream::OnGetDomainBoundCertComplete(int result) {
+  DCHECK_EQ(STATE_GET_DOMAIN_BOUND_CERT_COMPLETE, io_state_);
+  DoLoop(result);
+}
+
+int SpdyStream::DoLoop(int result) {
+  do {
+    State state = io_state_;
+    io_state_ = STATE_NONE;
+    switch (state) {
+      // State machine 1: Send headers and body.
+      case STATE_GET_DOMAIN_BOUND_CERT:
+        CHECK_EQ(OK, result);
+        result = DoGetDomainBoundCert();
+        break;
+      case STATE_GET_DOMAIN_BOUND_CERT_COMPLETE:
+        result = DoGetDomainBoundCertComplete(result);
+        break;
+      case STATE_SEND_DOMAIN_BOUND_CERT:
+        CHECK_EQ(OK, result);
+        result = DoSendDomainBoundCert();
+        break;
+      case STATE_SEND_DOMAIN_BOUND_CERT_COMPLETE:
+        result = DoSendDomainBoundCertComplete(result);
+        break;
+      case STATE_SEND_HEADERS:
+        CHECK_EQ(OK, result);
+        result = DoSendHeaders();
+        break;
+      case STATE_SEND_HEADERS_COMPLETE:
+        result = DoSendHeadersComplete(result);
+        break;
+      case STATE_SEND_BODY:
+        CHECK_EQ(OK, result);
+        result = DoSendBody();
+        break;
+      case STATE_SEND_BODY_COMPLETE:
+        result = DoSendBodyComplete(result);
+        break;
+      // This is an intermediary waiting state. This state is reached when all
+      // data has been sent, but no data has been received.
+      case STATE_WAITING_FOR_RESPONSE:
+        io_state_ = STATE_WAITING_FOR_RESPONSE;
+        result = ERR_IO_PENDING;
+        break;
+      // State machine 2: connection is established.
+      // In STATE_OPEN, OnResponseReceived has already been called.
+      // OnDataReceived, OnClose and OnWriteCompelte can be called.
+      // Only OnWriteComplete calls DoLoop(().
+      //
+      // For HTTP streams, no data is sent from the client while in the OPEN
+      // state, so OnWriteComplete is never called here.  The HTTP body is
+      // handled in the OnDataReceived callback, which does not call into
+      // DoLoop.
+      //
+      // For WebSocket streams, which are bi-directional, we'll send and
+      // receive data once the connection is established.  Received data is
+      // handled in OnDataReceived.  Sent data is handled in OnWriteComplete,
+      // which calls DoOpen().
+      case STATE_OPEN:
+        result = DoOpen(result);
+        break;
+
+      case STATE_DONE:
+        DCHECK(result != ERR_IO_PENDING);
+        break;
+      default:
+        NOTREACHED() << io_state_;
+        break;
+    }
+  } while (result != ERR_IO_PENDING && io_state_ != STATE_NONE &&
+           io_state_ != STATE_OPEN);
+
+  return result;
+}
+
+int SpdyStream::DoGetDomainBoundCert() {
+  CHECK(request_.get());
+  if (!session_->NeedsCredentials()) {
+    // Proceed directly to sending headers
+    io_state_ = STATE_SEND_HEADERS;
+    return OK;
+  }
+
+  slot_ = session_->credential_state()->FindCredentialSlot(GetUrl());
+  if (slot_ != SpdyCredentialState::kNoEntry) {
+    // Proceed directly to sending headers
+    io_state_ = STATE_SEND_HEADERS;
+    return OK;
+  }
+
+  io_state_ = STATE_GET_DOMAIN_BOUND_CERT_COMPLETE;
+  ServerBoundCertService* sbc_service = session_->GetServerBoundCertService();
+  DCHECK(sbc_service != NULL);
+  std::vector<uint8> requested_cert_types;
+  requested_cert_types.push_back(CLIENT_CERT_ECDSA_SIGN);
+  int rv = sbc_service->GetDomainBoundCert(
+      GetUrl().GetOrigin().spec(), requested_cert_types,
+      &domain_bound_cert_type_, &domain_bound_private_key_, &domain_bound_cert_,
+      base::Bind(&SpdyStream::OnGetDomainBoundCertComplete,
+                 base::Unretained(this)),
+      &domain_bound_cert_request_handle_);
+  return rv;
+}
+
+int SpdyStream::DoGetDomainBoundCertComplete(int result) {
+  if (result != OK)
+    return result;
+
+  io_state_ = STATE_SEND_DOMAIN_BOUND_CERT;
+  slot_ =  session_->credential_state()->SetHasCredential(GetUrl());
+  return OK;
+}
+
+int SpdyStream::DoSendDomainBoundCert() {
+  io_state_ = STATE_SEND_DOMAIN_BOUND_CERT_COMPLETE;
+  CHECK(request_.get());
+  SetHasWriteAvailable();
+  return ERR_IO_PENDING;
+}
+
+int SpdyStream::DoSendDomainBoundCertComplete(int result) {
+  if (result < 0)
+    return result;
+
+  io_state_ = STATE_SEND_HEADERS;
+  return OK;
+}
+
+int SpdyStream::DoSendHeaders() {
+  CHECK(!cancelled_);
+
+  SetHasWriteAvailable();
+  io_state_ = STATE_SEND_HEADERS_COMPLETE;
+  return ERR_IO_PENDING;
+}
+
+int SpdyStream::DoSendHeadersComplete(int result) {
+  if (result < 0)
+    return result;
+
+  CHECK_GT(result, 0);
+
+  if (!delegate_)
+    return ERR_UNEXPECTED;
+
+  // There is no body, skip that state.
+  if (delegate_->OnSendHeadersComplete(result)) {
+    io_state_ = STATE_WAITING_FOR_RESPONSE;
+    return OK;
+  }
+
+  io_state_ = STATE_SEND_BODY;
+  return OK;
+}
+
+// DoSendBody is called to send the optional body for the request.  This call
+// will also be called as each write of a chunk of the body completes.
+int SpdyStream::DoSendBody() {
+  // If we're already in the STATE_SEND_BODY state, then we've already
+  // sent a portion of the body.  In that case, we need to first consume
+  // the bytes written in the body stream.  Note that the bytes written is
+  // the number of bytes in the frame that were written, only consume the
+  // data portion, of course.
+  io_state_ = STATE_SEND_BODY_COMPLETE;
+  if (!delegate_)
+    return ERR_UNEXPECTED;
+  return delegate_->OnSendBody();
+}
+
+int SpdyStream::DoSendBodyComplete(int result) {
+  if (result < 0)
+    return result;
+
+  if (!delegate_)
+    return ERR_UNEXPECTED;
+
+  bool eof = false;
+  result = delegate_->OnSendBodyComplete(result, &eof);
+  if (!eof)
+    io_state_ = STATE_SEND_BODY;
+  else
+    io_state_ = STATE_WAITING_FOR_RESPONSE;
+
+  return result;
+}
+
+int SpdyStream::DoOpen(int result) {
+  if (delegate_) {
+    FrameType type = waiting_completions_.front();
+    waiting_completions_.pop_front();
+    if (type == TYPE_DATA) {
+      delegate_->OnDataSent(result);
+    } else {
+      DCHECK(type == TYPE_HEADERS);
+      delegate_->OnHeadersSent();
+    }
+  }
+  io_state_ = STATE_OPEN;
+  return result;
+}
+
+void SpdyStream::UpdateHistograms() {
+  // We need all timers to be filled in, otherwise metrics can be bogus.
+  if (send_time_.is_null() || recv_first_byte_time_.is_null() ||
+      recv_last_byte_time_.is_null())
+    return;
+
+  UMA_HISTOGRAM_TIMES("Net.SpdyStreamTimeToFirstByte",
+      recv_first_byte_time_ - send_time_);
+  UMA_HISTOGRAM_TIMES("Net.SpdyStreamDownloadTime",
+      recv_last_byte_time_ - recv_first_byte_time_);
+  UMA_HISTOGRAM_TIMES("Net.SpdyStreamTime",
+      recv_last_byte_time_ - send_time_);
+
+  UMA_HISTOGRAM_COUNTS("Net.SpdySendBytes", send_bytes_);
+  UMA_HISTOGRAM_COUNTS("Net.SpdyRecvBytes", recv_bytes_);
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_stream.h b/src/net/spdy/spdy_stream.h
new file mode 100644
index 0000000..5621564
--- /dev/null
+++ b/src/net/spdy/spdy_stream.h
@@ -0,0 +1,410 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_SPDY_SPDY_STREAM_H_
+#define NET_SPDY_SPDY_STREAM_H_
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/bandwidth_metrics.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/base/request_priority.h"
+#include "net/base/server_bound_cert_service.h"
+#include "net/base/ssl_client_cert_type.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_header_block.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_session.h"
+
+namespace net {
+
+class AddressList;
+class IPEndPoint;
+class SSLCertRequestInfo;
+class SSLInfo;
+
+// The SpdyStream is used by the SpdySession to represent each stream known
+// on the SpdySession.  This class provides interfaces for SpdySession to use.
+// Streams can be created either by the client or by the server.  When they
+// are initiated by the client, both the SpdySession and client object (such as
+// a SpdyNetworkTransaction) will maintain a reference to the stream.  When
+// initiated by the server, only the SpdySession will maintain any reference,
+// until such a time as a client object requests a stream for the path.
+class NET_EXPORT_PRIVATE SpdyStream
+    : public base::RefCounted<SpdyStream> {
+ public:
+  // Delegate handles protocol specific behavior of spdy stream.
+  class NET_EXPORT_PRIVATE Delegate {
+   public:
+    Delegate() {}
+
+    // Called when SYN frame has been sent.
+    // Returns true if no more data to be sent after SYN frame.
+    virtual bool OnSendHeadersComplete(int status) = 0;
+
+    // Called when stream is ready to send data.
+    // Returns network error code. OK when it successfully sent data.
+    // ERR_IO_PENDING when performing operation asynchronously.
+    virtual int OnSendBody() = 0;
+
+    // Called when data has been sent. |status| indicates network error
+    // or number of bytes that has been sent. On return, |eof| is set to true
+    // if no more data is available to send in the request body.
+    // Returns network error code. OK when it successfully sent data.
+    virtual int OnSendBodyComplete(int status, bool* eof) = 0;
+
+    // Called when the SYN_STREAM, SYN_REPLY, or HEADERS frames are received.
+    // Normal streams will receive a SYN_REPLY and optional HEADERS frames.
+    // Pushed streams will receive a SYN_STREAM and optional HEADERS frames.
+    // Because a stream may have a SYN_* frame and multiple HEADERS frames,
+    // this callback may be called multiple times.
+    // |status| indicates network error. Returns network error code.
+    virtual int OnResponseReceived(const SpdyHeaderBlock& response,
+                                   base::Time response_time,
+                                   int status) = 0;
+
+    // Called when a HEADERS frame is sent.
+    virtual void OnHeadersSent() = 0;
+
+    // Called when data is received.
+    // Returns network error code. OK when it successfully receives data.
+    virtual int OnDataReceived(const char* data, int length) = 0;
+
+    // Called when data is sent.
+    virtual void OnDataSent(int length) = 0;
+
+    // Called when SpdyStream is closed.
+    virtual void OnClose(int status) = 0;
+
+   protected:
+    virtual ~Delegate() {}
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(Delegate);
+  };
+
+  // Indicates pending frame type.
+  enum FrameType {
+    TYPE_HEADERS,
+    TYPE_DATA
+  };
+
+  // Structure to contains pending frame information.
+  typedef struct {
+    FrameType type;
+    union {
+      SpdyHeaderBlock* header_block;
+      SpdyDataFrame* data_frame;
+    };
+  } PendingFrame;
+
+  // SpdyStream constructor
+  SpdyStream(SpdySession* session,
+             bool pushed,
+             const BoundNetLog& net_log);
+
+  // Set new |delegate|. |delegate| must not be NULL.
+  // If it already received SYN_REPLY or data, OnResponseReceived() or
+  // OnDataReceived() will be called.
+  void SetDelegate(Delegate* delegate);
+  Delegate* GetDelegate() { return delegate_; }
+
+  // Detach delegate from the stream. It will cancel the stream if it was not
+  // cancelled yet.  It is safe to call multiple times.
+  void DetachDelegate();
+
+  // Is this stream a pushed stream from the server.
+  bool pushed() const { return pushed_; }
+
+  SpdyStreamId stream_id() const { return stream_id_; }
+  void set_stream_id(SpdyStreamId stream_id) { stream_id_ = stream_id; }
+
+  bool response_received() const { return response_received_; }
+  void set_response_received() { response_received_ = true; }
+
+  // For pushed streams, we track a path to identify them.
+  const std::string& path() const { return path_; }
+  void set_path(const std::string& path) { path_ = path; }
+
+  RequestPriority priority() const { return priority_; }
+  void set_priority(RequestPriority priority) { priority_ = priority; }
+
+  int32 send_window_size() const { return send_window_size_; }
+  void set_send_window_size(int32 window_size) {
+    send_window_size_ = window_size;
+  }
+
+  int32 recv_window_size() const { return recv_window_size_; }
+  void set_recv_window_size(int32 window_size) {
+    recv_window_size_ = window_size;
+  }
+
+  // Set session_'s initial_recv_window_size. Used by unittests.
+  void set_initial_recv_window_size(int32 window_size);
+
+  bool stalled_by_flow_control() { return stalled_by_flow_control_; }
+
+  void set_stalled_by_flow_control(bool stalled) {
+    stalled_by_flow_control_ = stalled;
+  }
+
+  // Adjusts the |send_window_size_| by |delta_window_size|. |delta_window_size|
+  // is the difference between the SETTINGS_INITIAL_WINDOW_SIZE in SETTINGS
+  // frame and the previous initial_send_window_size.
+  void AdjustSendWindowSize(int32 delta_window_size);
+
+  // Increases |send_window_size_| with delta extracted from a WINDOW_UPDATE
+  // frame; sends a RST_STREAM if delta overflows |send_window_size_| and
+  // removes the stream from the session.
+  void IncreaseSendWindowSize(int32 delta_window_size);
+
+  // Decreases |send_window_size_| by the given number of bytes.
+  void DecreaseSendWindowSize(int32 delta_window_size);
+
+  int GetPeerAddress(IPEndPoint* address) const;
+  int GetLocalAddress(IPEndPoint* address) const;
+
+  // Returns true if the underlying transport socket ever had any reads or
+  // writes.
+  bool WasEverUsed() const;
+
+  // Increases |recv_window_size_| by the given number of bytes, also sends
+  // a WINDOW_UPDATE frame.
+  void IncreaseRecvWindowSize(int32 delta_window_size);
+
+  // Decreases |recv_window_size_| by the given number of bytes, called
+  // whenever data is read.  May also send a RST_STREAM and remove the
+  // stream from the session if the resultant |recv_window_size_| is
+  // negative, since that would be a flow control violation.
+  void DecreaseRecvWindowSize(int32 delta_window_size);
+
+  const BoundNetLog& net_log() const { return net_log_; }
+
+  const SpdyHeaderBlock& spdy_headers() const;
+  void set_spdy_headers(scoped_ptr<SpdyHeaderBlock> headers);
+  base::Time GetRequestTime() const;
+  void SetRequestTime(base::Time t);
+
+  // Called by the SpdySession when a response (e.g. a SYN_STREAM or SYN_REPLY)
+  // has been received for this stream. Returns a status code.
+  int OnResponseReceived(const SpdyHeaderBlock& response);
+
+  // Called by the SpdySession when late-bound headers are received for a
+  // stream. Returns a status code.
+  int OnHeaders(const SpdyHeaderBlock& headers);
+
+  // Called by the SpdySession when response data has been received for this
+  // stream.  This callback may be called multiple times as data arrives
+  // from the network, and will never be called prior to OnResponseReceived.
+  // |buffer| contains the data received.  The stream must copy any data
+  //          from this buffer before returning from this callback.
+  // |length| is the number of bytes received or an error.
+  //         A zero-length count does not indicate end-of-stream.
+  void OnDataReceived(const char* buffer, int bytes);
+
+  // Called by the SpdySession when a write has completed.  This callback
+  // will be called multiple times for each write which completes.  Writes
+  // include the SYN_STREAM write and also DATA frame writes.
+  // |result| is the number of bytes written or a net error code.
+  void OnWriteComplete(int bytes);
+
+  // Called by the SpdySession when the request is finished.  This callback
+  // will always be called at the end of the request and signals to the
+  // stream that the stream has no more network events.  No further callbacks
+  // to the stream will be made after this call.
+  // |status| is an error code or OK.
+  void OnClose(int status);
+
+  // Called by the SpdySession to log stream related errors.
+  void LogStreamError(int status, const std::string& description);
+
+  void Cancel();
+  void Close();
+  bool cancelled() const { return cancelled_; }
+  bool closed() const { return io_state_ == STATE_DONE; }
+  // TODO(satorux): This is only for testing. We should be able to remove
+  // this once crbug.com/113107 is addressed.
+  bool body_sent() const { return io_state_ > STATE_SEND_BODY_COMPLETE; }
+
+  // Interface for Spdy[Http|WebSocket]Stream to use.
+
+  // Sends the request.
+  // For non push stream, it will send SYN_STREAM frame.
+  int SendRequest(bool has_upload_data);
+
+  // Sends a HEADERS frame. SpdyStream owns |headers| and will release it after
+  // the HEADERS frame is actually sent.
+  int WriteHeaders(SpdyHeaderBlock* headers);
+
+  // Sends DATA frame.
+  int WriteStreamData(IOBuffer* data, int length,
+                      SpdyDataFlags flags);
+
+  // Fills SSL info in |ssl_info| and returns true when SSL is in use.
+  bool GetSSLInfo(SSLInfo* ssl_info,
+                  bool* was_npn_negotiated,
+                  NextProto* protocol_negotiated);
+
+  // Fills SSL Certificate Request info |cert_request_info| and returns
+  // true when SSL is in use.
+  bool GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info);
+
+  bool is_idle() const {
+    return io_state_ == STATE_OPEN || io_state_ == STATE_DONE;
+  }
+
+  int response_status() const { return response_status_; }
+
+  // Returns true if the URL for this stream is known.
+  bool HasUrl() const;
+
+  // Get the URL associated with this stream.  Only valid when has_url() is
+  // true.
+  GURL GetUrl() const;
+
+  int GetProtocolVersion() const;
+
+ private:
+  class SpdyStreamIOBufferProducer;
+
+  enum State {
+    STATE_NONE,
+    STATE_GET_DOMAIN_BOUND_CERT,
+    STATE_GET_DOMAIN_BOUND_CERT_COMPLETE,
+    STATE_SEND_DOMAIN_BOUND_CERT,
+    STATE_SEND_DOMAIN_BOUND_CERT_COMPLETE,
+    STATE_SEND_HEADERS,
+    STATE_SEND_HEADERS_COMPLETE,
+    STATE_SEND_BODY,
+    STATE_SEND_BODY_COMPLETE,
+    STATE_WAITING_FOR_RESPONSE,
+    STATE_OPEN,
+    STATE_DONE
+  };
+
+  friend class base::RefCounted<SpdyStream>;
+
+  virtual ~SpdyStream();
+
+  // If the stream is stalled and if |send_window_size_| is positive, then set
+  // |stalled_by_flow_control_| to false and unstall the stream.
+  void PossiblyResumeIfStalled();
+
+  void OnGetDomainBoundCertComplete(int result);
+
+  // Try to make progress sending/receiving the request/response.
+  int DoLoop(int result);
+
+  // The implementations of each state of the state machine.
+  int DoGetDomainBoundCert();
+  int DoGetDomainBoundCertComplete(int result);
+  int DoSendDomainBoundCert();
+  int DoSendDomainBoundCertComplete(int result);
+  int DoSendHeaders();
+  int DoSendHeadersComplete(int result);
+  int DoSendBody();
+  int DoSendBodyComplete(int result);
+  int DoReadHeaders();
+  int DoReadHeadersComplete(int result);
+  int DoOpen(int result);
+
+  // Update the histograms.  Can safely be called repeatedly, but should only
+  // be called after the stream has completed.
+  void UpdateHistograms();
+
+  // When a server pushed stream is first created, this function is posted on
+  // the MessageLoop to replay all the data that the server has already sent.
+  void PushedStreamReplayData();
+
+  // Informs the SpdySession that this stream has a write available.
+  void SetHasWriteAvailable();
+
+  // Returns a newly created SPDY frame owned by the called that contains
+  // the next frame to be sent by this frame.  May return NULL if this
+  // stream has become stalled on flow control.
+  SpdyFrame* ProduceNextFrame();
+
+  // There is a small period of time between when a server pushed stream is
+  // first created, and the pushed data is replayed. Any data received during
+  // this time should continue to be buffered.
+  bool continue_buffering_data_;
+
+  SpdyStreamId stream_id_;
+  std::string path_;
+  RequestPriority priority_;
+  size_t slot_;
+
+  // Flow control variables.
+  bool stalled_by_flow_control_;
+  int32 send_window_size_;
+  int32 recv_window_size_;
+  int32 unacked_recv_window_bytes_;
+
+  const bool pushed_;
+  ScopedBandwidthMetrics metrics_;
+  bool response_received_;
+
+  scoped_refptr<SpdySession> session_;
+
+  // The transaction should own the delegate.
+  SpdyStream::Delegate* delegate_;
+
+  // The request to send.
+  scoped_ptr<SpdyHeaderBlock> request_;
+
+  // The time at which the request was made that resulted in this response.
+  // For cached responses, this time could be "far" in the past.
+  base::Time request_time_;
+
+  scoped_ptr<SpdyHeaderBlock> response_;
+  base::Time response_time_;
+
+  // An in order list of pending frame data that are going to be sent. HEADERS
+  // frames are queued as SpdyHeaderBlock structures because these must be
+  // compressed just before sending. Data frames are queued as SpdyDataFrame.
+  std::list<PendingFrame> pending_frames_;
+
+  // An in order list of sending frame types. It will be used to know which type
+  // of frame is sent and which callback should be invoked in OnOpen().
+  std::list<FrameType> waiting_completions_;
+
+  State io_state_;
+
+  // Since we buffer the response, we also buffer the response status.
+  // Not valid until the stream is closed.
+  int response_status_;
+
+  bool cancelled_;
+  bool has_upload_data_;
+
+  BoundNetLog net_log_;
+
+  base::TimeTicks send_time_;
+  base::TimeTicks recv_first_byte_time_;
+  base::TimeTicks recv_last_byte_time_;
+  int send_bytes_;
+  int recv_bytes_;
+  // Data received before delegate is attached.
+  std::vector<scoped_refptr<IOBufferWithSize> > pending_buffers_;
+
+  SSLClientCertType domain_bound_cert_type_;
+  std::string domain_bound_private_key_;
+  std::string domain_bound_cert_;
+  ServerBoundCertService::RequestHandle domain_bound_cert_request_handle_;
+
+  DISALLOW_COPY_AND_ASSIGN(SpdyStream);
+};
+
+}  // namespace net
+
+#endif  // NET_SPDY_SPDY_STREAM_H_
diff --git a/src/net/spdy/spdy_stream_spdy2_unittest.cc b/src/net/spdy/spdy_stream_spdy2_unittest.cc
new file mode 100644
index 0000000..602ff31
--- /dev/null
+++ b/src/net/spdy/spdy_stream_spdy2_unittest.cc
@@ -0,0 +1,462 @@
+// Copyright (c) 2012 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 "base/memory/ref_counted.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_log_unittest.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_stream.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_stream_test_util.h"
+#include "net/spdy/spdy_test_util_spdy2.h"
+#include "net/spdy/spdy_websocket_test_util_spdy2.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using namespace net::test_spdy2;
+
+// TODO(ukai): factor out common part with spdy_http_stream_unittest.cc
+//
+namespace net {
+
+namespace {
+
+SpdyFrame* ConstructSpdyBodyFrame(const char* data, int length) {
+  BufferedSpdyFramer framer(2, false);
+  return framer.CreateDataFrame(1, data, length, DATA_FLAG_NONE);
+}
+
+}  // anonymous namespace
+
+namespace test {
+
+class SpdyStreamSpdy2Test : public testing::Test {
+ protected:
+  SpdyStreamSpdy2Test() {
+  }
+
+  scoped_refptr<SpdySession> CreateSpdySession() {
+    HostPortPair host_port_pair("www.google.com", 80);
+    HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+    scoped_refptr<SpdySession> session(
+        session_->spdy_session_pool()->Get(pair, BoundNetLog()));
+    return session;
+  }
+
+  virtual void TearDown() {
+    MessageLoop::current()->RunUntilIdle();
+  }
+
+  scoped_refptr<HttpNetworkSession> session_;
+};
+
+TEST_F(SpdyStreamSpdy2Test, SendDataAfterOpen) {
+  SpdySessionDependencies session_deps;
+
+  session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps);
+  SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool());
+
+  const SpdyHeaderInfo kSynStartHeader = {
+    SYN_STREAM,
+    1,
+    0,
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 2),
+    CONTROL_FLAG_NONE,
+    false,
+    INVALID,
+    NULL,
+    0,
+    DATA_FLAG_NONE
+  };
+  static const char* const kGetHeaders[] = {
+    "method",
+    "GET",
+    "scheme",
+    "http",
+    "host",
+    "www.google.com",
+    "url",
+    "/",
+    "version",
+    "HTTP/1.1",
+  };
+  scoped_ptr<SpdyFrame> req(
+      ConstructSpdyPacket(
+          kSynStartHeader, NULL, 0, kGetHeaders, arraysize(kGetHeaders) / 2));
+  scoped_ptr<SpdyFrame> msg(
+      ConstructSpdyBodyFrame("\0hello!\xff", 8));
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*msg),
+  };
+  writes[0].sequence_number = 0;
+  writes[1].sequence_number = 2;
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> echo(
+      ConstructSpdyBodyFrame("\0hello!\xff", 8));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*echo),
+    MockRead(ASYNC, 0, 0), // EOF
+  };
+  reads[0].sequence_number = 1;
+  reads[1].sequence_number = 3;
+  reads[2].sequence_number = 4;
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  data.set_connect_data(connect_data);
+
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  scoped_refptr<SpdySession> session(CreateSpdySession());
+  const char* kStreamUrl = "http://www.google.com/";
+  GURL url(kStreamUrl);
+
+  HostPortPair host_port_pair("www.google.com", 80);
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(host_port_pair, LOWEST, false, false,
+                                OnHostResolutionCallback()));
+
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params,
+                                 LOWEST, CompletionCallback(),
+                                 session_->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  session->InitializeWithSocket(connection.release(), false, OK);
+
+  scoped_refptr<SpdyStream> stream;
+  ASSERT_EQ(
+      OK,
+      session->CreateStream(url, LOWEST, &stream, BoundNetLog(),
+                            CompletionCallback()));
+  scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(8));
+  memcpy(buf->data(), "\0hello!\xff", 8);
+  TestCompletionCallback callback;
+
+  scoped_ptr<TestSpdyStreamDelegate> delegate(
+      new TestSpdyStreamDelegate(
+          stream.get(), NULL, buf.get(), callback.callback()));
+  stream->SetDelegate(delegate.get());
+
+  EXPECT_FALSE(stream->HasUrl());
+
+  scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+  (*headers)["method"] = "GET";
+  (*headers)["scheme"] = url.scheme();
+  (*headers)["host"] = url.host();
+  (*headers)["url"] = url.path();
+  (*headers)["version"] = "HTTP/1.1";
+  stream->set_spdy_headers(headers.Pass());
+  EXPECT_TRUE(stream->HasUrl());
+  EXPECT_EQ(kStreamUrl, stream->GetUrl().spec());
+
+  EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(true));
+
+  EXPECT_EQ(OK, callback.WaitForResult());
+
+  EXPECT_TRUE(delegate->send_headers_completed());
+  EXPECT_EQ("200", (*delegate->response())["status"]);
+  EXPECT_EQ("HTTP/1.1", (*delegate->response())["version"]);
+  EXPECT_EQ(std::string("\0hello!\xff", 8), delegate->received_data());
+  EXPECT_EQ(8, delegate->data_sent());
+  EXPECT_TRUE(delegate->closed());
+}
+
+TEST_F(SpdyStreamSpdy2Test, SendHeaderAndDataAfterOpen) {
+  SpdySessionDependencies session_deps;
+
+  session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps);
+  SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool());
+
+  scoped_ptr<SpdyFrame> expected_request(ConstructSpdyWebSocketSynStream(
+      1,
+      "/chat",
+      "server.example.com",
+      "http://example.com"));
+  scoped_ptr<SpdyFrame> expected_headers(ConstructSpdyWebSocketHeadersFrame(
+      1, "6", true));
+  scoped_ptr<SpdyFrame> expected_message(ConstructSpdyBodyFrame("hello!", 6));
+  MockWrite writes[] = {
+    CreateMockWrite(*expected_request),
+    CreateMockWrite(*expected_headers),
+    CreateMockWrite(*expected_message)
+  };
+  writes[0].sequence_number = 0;
+  writes[1].sequence_number = 2;
+  writes[1].sequence_number = 3;
+
+  scoped_ptr<SpdyFrame> response(
+      ConstructSpdyWebSocketSynReply(1));
+  MockRead reads[] = {
+    CreateMockRead(*response),
+    MockRead(ASYNC, 0, 0), // EOF
+  };
+  reads[0].sequence_number = 1;
+  reads[1].sequence_number = 4;
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  data.set_connect_data(connect_data);
+
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  scoped_refptr<SpdySession> session(CreateSpdySession());
+  const char* kStreamUrl = "ws://server.example.com/chat";
+  GURL url(kStreamUrl);
+
+  HostPortPair host_port_pair("server.example.com", 80);
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(host_port_pair, LOWEST, false, false,
+                                OnHostResolutionCallback()));
+
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params,
+                                 LOWEST, CompletionCallback(),
+                                 session_->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  session->InitializeWithSocket(connection.release(), false, OK);
+
+  scoped_refptr<SpdyStream> stream;
+  ASSERT_EQ(
+      OK,
+      session->CreateStream(url, HIGHEST, &stream, BoundNetLog(),
+                            CompletionCallback()));
+  scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(6));
+  memcpy(buf->data(), "hello!", 6);
+  TestCompletionCallback callback;
+  scoped_ptr<SpdyHeaderBlock> message_headers(new SpdyHeaderBlock);
+  (*message_headers)["opcode"] = "1";
+  (*message_headers)["length"] = "6";
+  (*message_headers)["fin"] = "1";
+
+  scoped_ptr<TestSpdyStreamDelegate> delegate(
+      new TestSpdyStreamDelegate(stream.get(),
+                                 message_headers.release(),
+                                 buf.get(),
+                                 callback.callback()));
+  stream->SetDelegate(delegate.get());
+
+  EXPECT_FALSE(stream->HasUrl());
+
+  scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+  (*headers)["path"] = url.path();
+  (*headers)["host"] = url.host();
+  (*headers)["version"] = "WebSocket/13";
+  (*headers)["scheme"] = url.scheme();
+  (*headers)["origin"] = "http://example.com";
+  stream->set_spdy_headers(headers.Pass());
+  EXPECT_TRUE(stream->HasUrl());
+
+  EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(true));
+
+  EXPECT_EQ(OK, callback.WaitForResult());
+
+  EXPECT_TRUE(delegate->send_headers_completed());
+  EXPECT_EQ("101", (*delegate->response())["status"]);
+  EXPECT_EQ(1, delegate->headers_sent());
+  EXPECT_EQ(std::string(), delegate->received_data());
+  EXPECT_EQ(6, delegate->data_sent());
+}
+
+TEST_F(SpdyStreamSpdy2Test, PushedStream) {
+  const char kStreamUrl[] = "http://www.google.com/";
+
+  SpdySessionDependencies session_deps;
+  session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps);
+  SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool());
+  scoped_refptr<SpdySession> spdy_session(CreateSpdySession());
+
+  MockRead reads[] = {
+    MockRead(ASYNC, 0, 0), // EOF
+  };
+
+  OrderedSocketData data(reads, arraysize(reads), NULL, 0);
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  data.set_connect_data(connect_data);
+
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  HostPortPair host_port_pair("www.google.com", 80);
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(host_port_pair, LOWEST, false, false,
+                                OnHostResolutionCallback()));
+
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params,
+                                 LOWEST, CompletionCallback(),
+                                 session_->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  spdy_session->InitializeWithSocket(connection.release(), false, OK);
+  BoundNetLog net_log;
+
+  // Conjure up a stream.
+  scoped_refptr<SpdyStream> stream = new SpdyStream(spdy_session,
+                                                    true,
+                                                    net_log);
+  stream->set_stream_id(2);
+  EXPECT_FALSE(stream->response_received());
+  EXPECT_FALSE(stream->HasUrl());
+
+  // Set a couple of headers.
+  SpdyHeaderBlock response;
+  response["url"] = kStreamUrl;
+  stream->OnResponseReceived(response);
+
+  // Send some basic headers.
+  SpdyHeaderBlock headers;
+  response["status"] = "200";
+  response["version"] = "OK";
+  stream->OnHeaders(headers);
+
+  stream->set_response_received();
+  EXPECT_TRUE(stream->response_received());
+  EXPECT_TRUE(stream->HasUrl());
+  EXPECT_EQ(kStreamUrl, stream->GetUrl().spec());
+}
+
+TEST_F(SpdyStreamSpdy2Test, StreamError) {
+  SpdySessionDependencies session_deps;
+
+  session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps);
+  SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool());
+
+  const SpdyHeaderInfo kSynStartHeader = {
+    SYN_STREAM,
+    1,
+    0,
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 2),
+    CONTROL_FLAG_NONE,
+    false,
+    INVALID,
+    NULL,
+    0,
+    DATA_FLAG_NONE
+  };
+  static const char* const kGetHeaders[] = {
+    "method",
+    "GET",
+    "scheme",
+    "http",
+    "host",
+    "www.google.com",
+    "url",
+    "/",
+    "version",
+    "HTTP/1.1",
+  };
+  scoped_ptr<SpdyFrame> req(
+      ConstructSpdyPacket(
+          kSynStartHeader, NULL, 0, kGetHeaders, arraysize(kGetHeaders) / 2));
+  scoped_ptr<SpdyFrame> msg(
+      ConstructSpdyBodyFrame("\0hello!\xff", 8));
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*msg),
+  };
+  writes[0].sequence_number = 0;
+  writes[1].sequence_number = 2;
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> echo(
+      ConstructSpdyBodyFrame("\0hello!\xff", 8));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*echo),
+    MockRead(ASYNC, 0, 0),  // EOF
+  };
+  reads[0].sequence_number = 1;
+  reads[1].sequence_number = 3;
+  reads[2].sequence_number = 4;
+
+  CapturingBoundNetLog log;
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  data.set_connect_data(connect_data);
+
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  scoped_refptr<SpdySession> session(CreateSpdySession());
+  const char* kStreamUrl = "http://www.google.com/";
+  GURL url(kStreamUrl);
+
+  HostPortPair host_port_pair("www.google.com", 80);
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(host_port_pair, LOWEST, false, false,
+                                OnHostResolutionCallback()));
+
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params,
+                                 LOWEST, CompletionCallback(),
+                                 session_->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 log.bound()));
+  session->InitializeWithSocket(connection.release(), false, OK);
+
+  scoped_refptr<SpdyStream> stream;
+  ASSERT_EQ(
+      OK,
+      session->CreateStream(url, LOWEST, &stream, log.bound(),
+                            CompletionCallback()));
+  scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(8));
+  memcpy(buf->data(), "\0hello!\xff", 8);
+  TestCompletionCallback callback;
+
+  scoped_ptr<TestSpdyStreamDelegate> delegate(
+      new TestSpdyStreamDelegate(
+          stream.get(), NULL, buf.get(), callback.callback()));
+  stream->SetDelegate(delegate.get());
+
+  EXPECT_FALSE(stream->HasUrl());
+
+  scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+  (*headers)["method"] = "GET";
+  (*headers)["scheme"] = url.scheme();
+  (*headers)["host"] = url.host();
+  (*headers)["url"] = url.path();
+  (*headers)["version"] = "HTTP/1.1";
+  stream->set_spdy_headers(headers.Pass());
+  EXPECT_TRUE(stream->HasUrl());
+  EXPECT_EQ(kStreamUrl, stream->GetUrl().spec());
+
+  EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(true));
+
+  EXPECT_EQ(OK, callback.WaitForResult());
+
+  const SpdyStreamId stream_id = stream->stream_id();
+
+  EXPECT_TRUE(delegate->send_headers_completed());
+  EXPECT_EQ("200", (*delegate->response())["status"]);
+  EXPECT_EQ("HTTP/1.1", (*delegate->response())["version"]);
+  EXPECT_EQ(std::string("\0hello!\xff", 8), delegate->received_data());
+  EXPECT_EQ(8, delegate->data_sent());
+  EXPECT_TRUE(delegate->closed());
+
+  // Check that the NetLog was filled reasonably.
+  net::CapturingNetLog::CapturedEntryList entries;
+  log.GetEntries(&entries);
+  EXPECT_LT(0u, entries.size());
+
+  // Check that we logged SPDY_STREAM_ERROR correctly.
+  int pos = net::ExpectLogContainsSomewhere(
+      entries, 0,
+      net::NetLog::TYPE_SPDY_STREAM_ERROR,
+      net::NetLog::PHASE_NONE);
+
+  int stream_id2;
+  ASSERT_TRUE(entries[pos].GetIntegerValue("stream_id", &stream_id2));
+  EXPECT_EQ(static_cast<int>(stream_id), stream_id2);
+}
+
+} //namespace test
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_stream_spdy3_unittest.cc b/src/net/spdy/spdy_stream_spdy3_unittest.cc
new file mode 100644
index 0000000..3b07ea4
--- /dev/null
+++ b/src/net/spdy/spdy_stream_spdy3_unittest.cc
@@ -0,0 +1,467 @@
+// Copyright (c) 2012 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 "base/memory/ref_counted.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_log_unittest.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_stream.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_stream_test_util.h"
+#include "net/spdy/spdy_test_util_spdy3.h"
+#include "net/spdy/spdy_websocket_test_util_spdy3.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using namespace net::test_spdy3;
+
+// TODO(ukai): factor out common part with spdy_http_stream_unittest.cc
+//
+namespace net {
+
+namespace {
+
+SpdyFrame* ConstructSpdyBodyFrame(const char* data, int length) {
+  BufferedSpdyFramer framer(3, false);
+  return framer.CreateDataFrame(1, data, length, DATA_FLAG_NONE);
+}
+
+}  // anonymous namespace
+
+namespace test {
+
+class SpdyStreamSpdy3Test : public testing::Test {
+ protected:
+  SpdyStreamSpdy3Test() {
+  }
+
+  scoped_refptr<SpdySession> CreateSpdySession() {
+    HostPortPair host_port_pair("www.google.com", 80);
+    HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
+    scoped_refptr<SpdySession> session(
+        session_->spdy_session_pool()->Get(pair, BoundNetLog()));
+    return session;
+  }
+
+  virtual void TearDown() {
+    MessageLoop::current()->RunUntilIdle();
+  }
+
+  scoped_refptr<HttpNetworkSession> session_;
+};
+
+TEST_F(SpdyStreamSpdy3Test, SendDataAfterOpen) {
+  SpdySessionDependencies session_deps;
+
+  session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps);
+  SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool());
+
+  const SpdyHeaderInfo kSynStartHeader = {
+    SYN_STREAM,
+    1,
+    0,
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 3),
+    0,
+    CONTROL_FLAG_NONE,
+    false,
+    INVALID,
+    NULL,
+    0,
+    DATA_FLAG_NONE
+  };
+  static const char* const kGetHeaders[] = {
+    ":method",
+    "GET",
+    ":scheme",
+    "http",
+    ":host",
+    "www.google.com",
+    ":path",
+    "/",
+    ":version",
+    "HTTP/1.1",
+  };
+  scoped_ptr<SpdyFrame> req(
+      ConstructSpdyPacket(
+          kSynStartHeader, NULL, 0, kGetHeaders, arraysize(kGetHeaders) / 2));
+  scoped_ptr<SpdyFrame> msg(
+      ConstructSpdyBodyFrame("\0hello!\xff", 8));
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*msg),
+  };
+  writes[0].sequence_number = 0;
+  writes[1].sequence_number = 2;
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> echo(
+      ConstructSpdyBodyFrame("\0hello!\xff", 8));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*echo),
+    MockRead(ASYNC, 0, 0), // EOF
+  };
+  reads[0].sequence_number = 1;
+  reads[1].sequence_number = 3;
+  reads[2].sequence_number = 4;
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  data.set_connect_data(connect_data);
+
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  scoped_refptr<SpdySession> session(CreateSpdySession());
+  const char* kStreamUrl = "http://www.google.com/";
+  GURL url(kStreamUrl);
+
+  HostPortPair host_port_pair("www.google.com", 80);
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(host_port_pair, LOWEST, false, false,
+                                OnHostResolutionCallback()));
+
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params,
+                                 LOWEST, CompletionCallback(),
+                                 session_->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  session->InitializeWithSocket(connection.release(), false, OK);
+
+  scoped_refptr<SpdyStream> stream;
+  ASSERT_EQ(
+      OK,
+      session->CreateStream(url, LOWEST, &stream, BoundNetLog(),
+                            CompletionCallback()));
+  scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(8));
+  memcpy(buf->data(), "\0hello!\xff", 8);
+  TestCompletionCallback callback;
+
+  scoped_ptr<TestSpdyStreamDelegate> delegate(
+      new TestSpdyStreamDelegate(
+          stream.get(), NULL, buf.get(), callback.callback()));
+  stream->SetDelegate(delegate.get());
+
+  EXPECT_FALSE(stream->HasUrl());
+
+  scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+  (*headers)[":method"] = "GET";
+  (*headers)[":scheme"] = url.scheme();
+  (*headers)[":host"] = url.host();
+  (*headers)[":path"] = url.path();
+  (*headers)[":version"] = "HTTP/1.1";
+  stream->set_spdy_headers(headers.Pass());
+  EXPECT_TRUE(stream->HasUrl());
+  EXPECT_EQ(kStreamUrl, stream->GetUrl().spec());
+
+  EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(true));
+
+  EXPECT_EQ(OK, callback.WaitForResult());
+
+  EXPECT_TRUE(delegate->send_headers_completed());
+  EXPECT_EQ("200", (*delegate->response())[":status"]);
+  EXPECT_EQ("HTTP/1.1", (*delegate->response())[":version"]);
+  EXPECT_EQ(std::string("\0hello!\xff", 8), delegate->received_data());
+  EXPECT_EQ(8, delegate->data_sent());
+  EXPECT_TRUE(delegate->closed());
+}
+
+TEST_F(SpdyStreamSpdy3Test, SendHeaderAndDataAfterOpen) {
+  SpdySessionDependencies session_deps;
+
+  session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps);
+  SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool());
+
+  scoped_ptr<SpdyFrame> expected_request(ConstructSpdyWebSocketSynStream(
+      1,
+      "/chat",
+      "server.example.com",
+      "http://example.com"));
+  scoped_ptr<SpdyFrame> expected_headers(ConstructSpdyWebSocketHeadersFrame(
+      1, "6", true));
+  scoped_ptr<SpdyFrame> expected_message(ConstructSpdyBodyFrame("hello!", 6));
+  MockWrite writes[] = {
+    CreateMockWrite(*expected_request),
+    CreateMockWrite(*expected_headers),
+    CreateMockWrite(*expected_message)
+  };
+  writes[0].sequence_number = 0;
+  writes[1].sequence_number = 2;
+  writes[1].sequence_number = 3;
+
+  scoped_ptr<SpdyFrame> response(
+      ConstructSpdyWebSocketSynReply(1));
+  MockRead reads[] = {
+    CreateMockRead(*response),
+    MockRead(ASYNC, 0, 0), // EOF
+  };
+  reads[0].sequence_number = 1;
+  reads[1].sequence_number = 4;
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  data.set_connect_data(connect_data);
+
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  scoped_refptr<SpdySession> session(CreateSpdySession());
+  const char* kStreamUrl = "ws://server.example.com/chat";
+  GURL url(kStreamUrl);
+
+  HostPortPair host_port_pair("server.example.com", 80);
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(host_port_pair, LOWEST, false, false,
+                                OnHostResolutionCallback()));
+
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params,
+                                 LOWEST, CompletionCallback(),
+                                 session_->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  session->InitializeWithSocket(connection.release(), false, OK);
+
+  scoped_refptr<SpdyStream> stream;
+  ASSERT_EQ(
+      OK,
+      session->CreateStream(url, HIGHEST, &stream, BoundNetLog(),
+                            CompletionCallback()));
+  scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(6));
+  memcpy(buf->data(), "hello!", 6);
+  TestCompletionCallback callback;
+  scoped_ptr<SpdyHeaderBlock> message_headers(new SpdyHeaderBlock);
+  (*message_headers)[":opcode"] = "1";
+  (*message_headers)[":length"] = "6";
+  (*message_headers)[":fin"] = "1";
+
+  scoped_ptr<TestSpdyStreamDelegate> delegate(
+      new TestSpdyStreamDelegate(stream.get(),
+                                 message_headers.release(),
+                                 buf.get(),
+                                 callback.callback()));
+  stream->SetDelegate(delegate.get());
+
+  EXPECT_FALSE(stream->HasUrl());
+
+  scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+  (*headers)[":path"] = url.path();
+  (*headers)[":host"] = url.host();
+  (*headers)[":version"] = "WebSocket/13";
+  (*headers)[":scheme"] = url.scheme();
+  (*headers)[":origin"] = "http://example.com";
+  stream->set_spdy_headers(headers.Pass());
+  EXPECT_TRUE(stream->HasUrl());
+
+  EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(true));
+
+  EXPECT_EQ(OK, callback.WaitForResult());
+
+  EXPECT_TRUE(delegate->send_headers_completed());
+  EXPECT_EQ("101", (*delegate->response())[":status"]);
+  EXPECT_EQ(1, delegate->headers_sent());
+  EXPECT_EQ(std::string(), delegate->received_data());
+  EXPECT_EQ(6, delegate->data_sent());
+}
+
+TEST_F(SpdyStreamSpdy3Test, PushedStream) {
+  const char kStreamUrl[] = "http://www.google.com/";
+
+  SpdySessionDependencies session_deps;
+  session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps);
+  SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool());
+  scoped_refptr<SpdySession> spdy_session(CreateSpdySession());
+
+  MockRead reads[] = {
+    MockRead(ASYNC, 0, 0), // EOF
+  };
+
+  OrderedSocketData data(reads, arraysize(reads), NULL, 0);
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  data.set_connect_data(connect_data);
+
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  HostPortPair host_port_pair("www.google.com", 80);
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(host_port_pair, LOWEST, false, false,
+                                OnHostResolutionCallback()));
+
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params,
+                                 LOWEST, CompletionCallback(),
+                                 session_->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 BoundNetLog()));
+  spdy_session->InitializeWithSocket(connection.release(), false, OK);
+  BoundNetLog net_log;
+
+  // Conjure up a stream.
+  scoped_refptr<SpdyStream> stream = new SpdyStream(spdy_session,
+                                                    true,
+                                                    net_log);
+  stream->set_stream_id(2);
+  EXPECT_FALSE(stream->response_received());
+  EXPECT_FALSE(stream->HasUrl());
+
+  // Set a couple of headers.
+  SpdyHeaderBlock response;
+  GURL url(kStreamUrl);
+  response[":host"] = url.host();
+  response[":scheme"] = url.scheme();
+  response[":path"] = url.path();
+  stream->OnResponseReceived(response);
+
+  // Send some basic headers.
+  SpdyHeaderBlock headers;
+  response[":status"] = "200";
+  response[":version"] = "OK";
+  stream->OnHeaders(headers);
+
+  stream->set_response_received();
+  EXPECT_TRUE(stream->response_received());
+  EXPECT_TRUE(stream->HasUrl());
+  EXPECT_EQ(kStreamUrl, stream->GetUrl().spec());
+}
+
+TEST_F(SpdyStreamSpdy3Test, StreamError) {
+  SpdySessionDependencies session_deps;
+
+  session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps);
+  SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool());
+
+  const SpdyHeaderInfo kSynStartHeader = {
+    SYN_STREAM,
+    1,
+    0,
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 3),
+    0,
+    CONTROL_FLAG_NONE,
+    false,
+    INVALID,
+    NULL,
+    0,
+    DATA_FLAG_NONE
+  };
+  static const char* const kGetHeaders[] = {
+    ":method",
+    "GET",
+    ":scheme",
+    "http",
+    ":host",
+    "www.google.com",
+    ":path",
+    "/",
+    ":version",
+    "HTTP/1.1",
+  };
+  scoped_ptr<SpdyFrame> req(
+      ConstructSpdyPacket(
+          kSynStartHeader, NULL, 0, kGetHeaders, arraysize(kGetHeaders) / 2));
+  scoped_ptr<SpdyFrame> msg(
+      ConstructSpdyBodyFrame("\0hello!\xff", 8));
+  MockWrite writes[] = {
+    CreateMockWrite(*req),
+    CreateMockWrite(*msg),
+  };
+  writes[0].sequence_number = 0;
+  writes[1].sequence_number = 2;
+
+  scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
+  scoped_ptr<SpdyFrame> echo(
+      ConstructSpdyBodyFrame("\0hello!\xff", 8));
+  MockRead reads[] = {
+    CreateMockRead(*resp),
+    CreateMockRead(*echo),
+    MockRead(ASYNC, 0, 0),  // EOF
+  };
+  reads[0].sequence_number = 1;
+  reads[1].sequence_number = 3;
+  reads[2].sequence_number = 4;
+
+  CapturingBoundNetLog log;
+
+  OrderedSocketData data(reads, arraysize(reads),
+                         writes, arraysize(writes));
+  MockConnect connect_data(SYNCHRONOUS, OK);
+  data.set_connect_data(connect_data);
+
+  session_deps.socket_factory->AddSocketDataProvider(&data);
+
+  scoped_refptr<SpdySession> session(CreateSpdySession());
+  const char* kStreamUrl = "http://www.google.com/";
+  GURL url(kStreamUrl);
+
+  HostPortPair host_port_pair("www.google.com", 80);
+  scoped_refptr<TransportSocketParams> transport_params(
+      new TransportSocketParams(host_port_pair, LOWEST, false, false,
+                                OnHostResolutionCallback()));
+
+  scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+  EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params,
+                                 LOWEST, CompletionCallback(),
+                                 session_->GetTransportSocketPool(
+                                     HttpNetworkSession::NORMAL_SOCKET_POOL),
+                                 log.bound()));
+  session->InitializeWithSocket(connection.release(), false, OK);
+
+  scoped_refptr<SpdyStream> stream;
+  ASSERT_EQ(
+      OK,
+      session->CreateStream(url, LOWEST, &stream, log.bound(),
+                            CompletionCallback()));
+  scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(8));
+  memcpy(buf->data(), "\0hello!\xff", 8);
+  TestCompletionCallback callback;
+
+  scoped_ptr<TestSpdyStreamDelegate> delegate(
+      new TestSpdyStreamDelegate(
+          stream.get(), NULL, buf.get(), callback.callback()));
+  stream->SetDelegate(delegate.get());
+
+  EXPECT_FALSE(stream->HasUrl());
+
+  scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+  (*headers)[":method"] = "GET";
+  (*headers)[":scheme"] = url.scheme();
+  (*headers)[":host"] = url.host();
+  (*headers)[":path"] = url.path();
+  (*headers)[":version"] = "HTTP/1.1";
+  stream->set_spdy_headers(headers.Pass());
+  EXPECT_TRUE(stream->HasUrl());
+  EXPECT_EQ(kStreamUrl, stream->GetUrl().spec());
+
+  EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(true));
+
+  EXPECT_EQ(OK, callback.WaitForResult());
+
+  const SpdyStreamId stream_id = stream->stream_id();
+
+  EXPECT_TRUE(delegate->send_headers_completed());
+  EXPECT_EQ("200", (*delegate->response())[":status"]);
+  EXPECT_EQ("HTTP/1.1", (*delegate->response())[":version"]);
+  EXPECT_EQ(std::string("\0hello!\xff", 8), delegate->received_data());
+  EXPECT_EQ(8, delegate->data_sent());
+  EXPECT_TRUE(delegate->closed());
+
+  // Check that the NetLog was filled reasonably.
+  net::CapturingNetLog::CapturedEntryList entries;
+  log.GetEntries(&entries);
+  EXPECT_LT(0u, entries.size());
+
+  // Check that we logged SPDY_STREAM_ERROR correctly.
+  int pos = net::ExpectLogContainsSomewhere(
+      entries, 0,
+      net::NetLog::TYPE_SPDY_STREAM_ERROR,
+      net::NetLog::PHASE_NONE);
+
+  int stream_id2;
+  ASSERT_TRUE(entries[pos].GetIntegerValue("stream_id", &stream_id2));
+  EXPECT_EQ(static_cast<int>(stream_id), stream_id2);
+}
+
+}  // namespace test
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_stream_test_util.cc b/src/net/spdy/spdy_stream_test_util.cc
new file mode 100644
index 0000000..e2144ae
--- /dev/null
+++ b/src/net/spdy/spdy_stream_test_util.cc
@@ -0,0 +1,87 @@
+// Copyright (c) 2012 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_stream_test_util.h"
+
+#include "net/base/completion_callback.h"
+#include "net/spdy/spdy_stream.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace test {
+
+TestSpdyStreamDelegate::TestSpdyStreamDelegate(
+    SpdyStream* stream,
+    SpdyHeaderBlock* headers,
+    IOBufferWithSize* buf,
+    const CompletionCallback& callback)
+    : stream_(stream),
+      headers_(headers),
+      buf_(buf),
+      callback_(callback),
+      send_headers_completed_(false),
+      response_(new SpdyHeaderBlock),
+      headers_sent_(0),
+      data_sent_(0),
+      closed_(false) {
+}
+
+TestSpdyStreamDelegate::~TestSpdyStreamDelegate() {
+}
+
+bool TestSpdyStreamDelegate::OnSendHeadersComplete(int status) {
+  send_headers_completed_ = true;
+  return true;
+}
+
+int TestSpdyStreamDelegate::OnSendBody() {
+  ADD_FAILURE() << "OnSendBody should not be called";
+  return ERR_UNEXPECTED;
+}
+int TestSpdyStreamDelegate::OnSendBodyComplete(int /*status*/, bool* /*eof*/) {
+  ADD_FAILURE() << "OnSendBodyComplete should not be called";
+  return ERR_UNEXPECTED;
+}
+
+int TestSpdyStreamDelegate::OnResponseReceived(const SpdyHeaderBlock& response,
+                                               base::Time response_time,
+                                               int status) {
+  EXPECT_TRUE(send_headers_completed_);
+  *response_ = response;
+  if (headers_.get()) {
+    EXPECT_EQ(ERR_IO_PENDING,
+              stream_->WriteHeaders(headers_.release()));
+  }
+  if (buf_) {
+    EXPECT_EQ(ERR_IO_PENDING,
+              stream_->WriteStreamData(buf_.get(), buf_->size(),
+                                       DATA_FLAG_NONE));
+  }
+  return status;
+}
+
+void TestSpdyStreamDelegate::OnHeadersSent() {
+  headers_sent_++;
+}
+
+int TestSpdyStreamDelegate::OnDataReceived(const char* buffer, int bytes) {
+  received_data_ += std::string(buffer, bytes);
+  return OK;
+}
+
+void TestSpdyStreamDelegate::OnDataSent(int length) {
+  data_sent_ += length;
+}
+
+void TestSpdyStreamDelegate::OnClose(int status) {
+  closed_ = true;
+  CompletionCallback callback = callback_;
+  callback_.Reset();
+  callback.Run(OK);
+}
+
+} // namespace test
+
+} // namespace net
diff --git a/src/net/spdy/spdy_stream_test_util.h b/src/net/spdy/spdy_stream_test_util.h
new file mode 100644
index 0000000..6059014
--- /dev/null
+++ b/src/net/spdy/spdy_stream_test_util.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_SPDY_SPDY_STREAM_TEST_UTIL_H_
+#define NET_SPDY_SPDY_STREAM_TEST_UTIL_H_
+
+#include "base/memory/linked_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "net/base/completion_callback.h"
+#include "net/spdy/spdy_stream.h"
+
+namespace net {
+
+namespace test {
+
+class TestSpdyStreamDelegate : public SpdyStream::Delegate {
+ public:
+  TestSpdyStreamDelegate(SpdyStream* stream,
+                         SpdyHeaderBlock* headers,
+                         IOBufferWithSize* buf,
+                         const CompletionCallback& callback);
+  virtual ~TestSpdyStreamDelegate();
+
+  virtual bool OnSendHeadersComplete(int status) OVERRIDE;
+  virtual int OnSendBody() OVERRIDE;
+  virtual int OnSendBodyComplete(int status, bool* eof) OVERRIDE;
+  virtual int OnResponseReceived(const SpdyHeaderBlock& response,
+                                 base::Time response_time,
+                                 int status) OVERRIDE;
+  virtual void OnHeadersSent() OVERRIDE;
+  virtual int OnDataReceived(const char* buffer, int bytes) OVERRIDE;
+  virtual void OnDataSent(int length) OVERRIDE;
+  virtual void OnClose(int status) OVERRIDE;
+
+  bool send_headers_completed() const { return send_headers_completed_; }
+  const linked_ptr<SpdyHeaderBlock>& response() const {
+    return response_;
+  }
+  const std::string& received_data() const { return received_data_; }
+  int headers_sent() const { return headers_sent_; }
+  int data_sent() const { return data_sent_; }
+  bool closed() const {  return closed_; }
+
+ private:
+  SpdyStream* stream_;
+  scoped_ptr<SpdyHeaderBlock> headers_;
+  scoped_refptr<IOBufferWithSize> buf_;
+  CompletionCallback callback_;
+  bool send_headers_completed_;
+  linked_ptr<SpdyHeaderBlock> response_;
+  std::string received_data_;
+  int headers_sent_;
+  int data_sent_;
+  bool closed_;
+
+};
+
+} // namespace test
+
+} // namespace net
+
+#endif // NET_SPDY_SPDY_STREAM_TEST_UTIL_H_
diff --git a/src/net/spdy/spdy_test_util_spdy2.cc b/src/net/spdy/spdy_test_util_spdy2.cc
new file mode 100644
index 0000000..5f5e017
--- /dev/null
+++ b/src/net/spdy/spdy_test_util_spdy2.cc
@@ -0,0 +1,987 @@
+// Copyright (c) 2012 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_test_util_spdy2.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "net/base/mock_cert_verifier.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_network_transaction.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_session.h"
+
+namespace net {
+namespace test_spdy2 {
+
+namespace {
+
+// Parses a URL into the scheme, host, and path components required for a
+// SPDY request.
+void ParseUrl(const char* const url, std::string* scheme, std::string* host,
+              std::string* path) {
+  GURL gurl(url);
+  path->assign(gurl.PathForRequest());
+  scheme->assign(gurl.scheme());
+  host->assign(gurl.host());
+  if (gurl.has_port()) {
+    host->append(":");
+    host->append(gurl.port());
+  }
+}
+
+}  // namespace
+
+// Chop a frame into an array of MockWrites.
+// |data| is the frame to chop.
+// |length| is the length of the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockWrite* ChopWriteFrame(const char* data, int length, int num_chunks) {
+  MockWrite* chunks = new MockWrite[num_chunks];
+  int chunk_size = length / num_chunks;
+  for (int index = 0; index < num_chunks; index++) {
+    const char* ptr = data + (index * chunk_size);
+    if (index == num_chunks - 1)
+      chunk_size += length % chunk_size;  // The last chunk takes the remainder.
+    chunks[index] = MockWrite(ASYNC, ptr, chunk_size);
+  }
+  return chunks;
+}
+
+// Chop a SpdyFrame into an array of MockWrites.
+// |frame| is the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockWrite* ChopWriteFrame(const SpdyFrame& frame, int num_chunks) {
+  return ChopWriteFrame(frame.data(),
+                        frame.length() + SpdyFrame::kHeaderSize,
+                        num_chunks);
+}
+
+// Chop a frame into an array of MockReads.
+// |data| is the frame to chop.
+// |length| is the length of the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockRead* ChopReadFrame(const char* data, int length, int num_chunks) {
+  MockRead* chunks = new MockRead[num_chunks];
+  int chunk_size = length / num_chunks;
+  for (int index = 0; index < num_chunks; index++) {
+    const char* ptr = data + (index * chunk_size);
+    if (index == num_chunks - 1)
+      chunk_size += length % chunk_size;  // The last chunk takes the remainder.
+    chunks[index] = MockRead(ASYNC, ptr, chunk_size);
+  }
+  return chunks;
+}
+
+// Chop a SpdyFrame into an array of MockReads.
+// |frame| is the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockRead* ChopReadFrame(const SpdyFrame& frame, int num_chunks) {
+  return ChopReadFrame(frame.data(),
+                       frame.length() + SpdyFrame::kHeaderSize,
+                       num_chunks);
+}
+
+// Adds headers and values to a map.
+// |extra_headers| is an array of { name, value } pairs, arranged as strings
+// where the even entries are the header names, and the odd entries are the
+// header values.
+// |headers| gets filled in from |extra_headers|.
+void AppendHeadersToSpdyFrame(const char* const extra_headers[],
+                              int extra_header_count,
+                              SpdyHeaderBlock* headers) {
+  std::string this_header;
+  std::string this_value;
+
+  if (!extra_header_count)
+    return;
+
+  // Sanity check: Non-NULL header list.
+  DCHECK(NULL != extra_headers) << "NULL header value pair list";
+  // Sanity check: Non-NULL header map.
+  DCHECK(NULL != headers) << "NULL header map";
+  // Copy in the headers.
+  for (int i = 0; i < extra_header_count; i++) {
+    // Sanity check: Non-empty header.
+    DCHECK_NE('\0', *extra_headers[i * 2]) << "Empty header value pair";
+    this_header = extra_headers[i * 2];
+    std::string::size_type header_len = this_header.length();
+    if (!header_len)
+      continue;
+    this_value = extra_headers[1 + (i * 2)];
+    std::string new_value;
+    if (headers->find(this_header) != headers->end()) {
+      // More than one entry in the header.
+      // Don't add the header again, just the append to the value,
+      // separated by a NULL character.
+
+      // Adjust the value.
+      new_value = (*headers)[this_header];
+      // Put in a NULL separator.
+      new_value.append(1, '\0');
+      // Append the new value.
+      new_value += this_value;
+    } else {
+      // Not a duplicate, just write the value.
+      new_value = this_value;
+    }
+    (*headers)[this_header] = new_value;
+  }
+}
+
+// Writes |val| to a location of size |len|, in big-endian format.
+// in the buffer pointed to by |buffer_handle|.
+// Updates the |*buffer_handle| pointer by |len|
+// Returns the number of bytes written
+int AppendToBuffer(int val,
+                   int len,
+                   unsigned char** buffer_handle,
+                   int* buffer_len_remaining) {
+  if (len <= 0)
+    return 0;
+  DCHECK((size_t) len <= sizeof(len)) << "Data length too long for data type";
+  DCHECK(NULL != buffer_handle) << "NULL buffer handle";
+  DCHECK(NULL != *buffer_handle) << "NULL pointer";
+  DCHECK(NULL != buffer_len_remaining)
+      << "NULL buffer remainder length pointer";
+  DCHECK_GE(*buffer_len_remaining, len) << "Insufficient buffer size";
+  for (int i = 0; i < len; i++) {
+    int shift = (8 * (len - (i + 1)));
+    unsigned char val_chunk = (val >> shift) & 0x0FF;
+    *(*buffer_handle)++ = val_chunk;
+    *buffer_len_remaining += 1;
+  }
+  return len;
+}
+
+// Construct a SPDY packet.
+// |head| is the start of the packet, up to but not including
+// the header value pairs.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// |tail| is any (relatively constant) header-value pairs to add.
+// |buffer| is the buffer we're filling in.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPacket(const SpdyHeaderInfo& header_info,
+                               const char* const extra_headers[],
+                               int extra_header_count,
+                               const char* const tail[],
+                               int tail_header_count) {
+  BufferedSpdyFramer framer(2, header_info.compressed);
+  SpdyHeaderBlock headers;
+  // Copy in the extra headers to our map.
+  AppendHeadersToSpdyFrame(extra_headers, extra_header_count, &headers);
+  // Copy in the tail headers to our map.
+  if (tail && tail_header_count)
+    AppendHeadersToSpdyFrame(tail, tail_header_count, &headers);
+  SpdyFrame* frame = NULL;
+  switch (header_info.kind) {
+    case SYN_STREAM:
+      frame = framer.CreateSynStream(header_info.id, header_info.assoc_id,
+                                     header_info.priority, 0,
+                                     header_info.control_flags,
+                                     header_info.compressed, &headers);
+      break;
+    case SYN_REPLY:
+      frame = framer.CreateSynReply(header_info.id, header_info.control_flags,
+                                    header_info.compressed, &headers);
+      break;
+    case RST_STREAM:
+      frame = framer.CreateRstStream(header_info.id, header_info.status);
+      break;
+    case HEADERS:
+      frame = framer.CreateHeaders(header_info.id, header_info.control_flags,
+                                   header_info.compressed, &headers);
+      break;
+    default:
+      frame = framer.CreateDataFrame(header_info.id, header_info.data,
+                                     header_info.data_length,
+                                     header_info.data_flags);
+      break;
+  }
+  return frame;
+}
+
+// Construct an expected SPDY SETTINGS frame.
+// |settings| are the settings to set.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdySettings(const SettingsMap& settings) {
+  BufferedSpdyFramer framer(2, false);
+  return framer.CreateSettings(settings);
+}
+
+// Construct an expected SPDY CREDENTIAL frame.
+// |credential| is the credential to sen.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyCredential(
+    const SpdyCredential& credential) {
+  BufferedSpdyFramer framer(2, false);
+  return framer.CreateCredentialFrame(credential);
+}
+
+// Construct a SPDY PING frame.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyPing(uint32 ping_id) {
+  BufferedSpdyFramer framer(2, false);
+  return framer.CreatePingFrame(ping_id);
+}
+
+// Construct a SPDY GOAWAY frame.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyGoAway() {
+  BufferedSpdyFramer framer(2, false);
+  return framer.CreateGoAway(0, GOAWAY_OK);
+}
+
+// Construct a SPDY WINDOW_UPDATE frame.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyWindowUpdate(
+    const SpdyStreamId stream_id, uint32 delta_window_size) {
+  BufferedSpdyFramer framer(2, false);
+  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.
+SpdyFrame* ConstructSpdyRstStream(SpdyStreamId stream_id,
+                                  SpdyStatusCodes status) {
+  BufferedSpdyFramer framer(2, false);
+  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.
+// |index| is the index of the header we want.
+// Returns the number of bytes written into |buffer|.
+int ConstructSpdyHeader(const char* const extra_headers[],
+                        int extra_header_count,
+                        char* buffer,
+                        int buffer_length,
+                        int index) {
+  const char* this_header = NULL;
+  const char* this_value = NULL;
+  if (!buffer || !buffer_length)
+    return 0;
+  *buffer = '\0';
+  // Sanity check: Non-empty header list.
+  DCHECK(NULL != extra_headers) << "NULL extra headers pointer";
+  // Sanity check: Index out of range.
+  DCHECK((index >= 0) && (index < extra_header_count))
+      << "Index " << index
+      << " out of range [0, " << extra_header_count << ")";
+  this_header = extra_headers[index * 2];
+  // Sanity check: Non-empty header.
+  if (!*this_header)
+    return 0;
+  std::string::size_type header_len = strlen(this_header);
+  if (!header_len)
+    return 0;
+  this_value = extra_headers[1 + (index * 2)];
+  // Sanity check: Non-empty value.
+  if (!*this_value)
+    this_value = "";
+  int n = base::snprintf(buffer,
+                         buffer_length,
+                         "%s: %s\r\n",
+                         this_header,
+                         this_value);
+  return n;
+}
+
+SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[],
+                                     int extra_header_count,
+                                     bool compressed,
+                                     int stream_id,
+                                     RequestPriority request_priority,
+                                     SpdyControlType type,
+                                     SpdyControlFlags flags,
+                                     const char* const* kHeaders,
+                                     int kHeadersSize) {
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   compressed,
+                                   stream_id,
+                                   request_priority,
+                                   type,
+                                   flags,
+                                   kHeaders,
+                                   kHeadersSize,
+                                   0);
+}
+
+SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[],
+                                     int extra_header_count,
+                                     bool compressed,
+                                     int stream_id,
+                                     RequestPriority request_priority,
+                                     SpdyControlType type,
+                                     SpdyControlFlags flags,
+                                     const char* const* kHeaders,
+                                     int kHeadersSize,
+                                     int associated_stream_id) {
+  const SpdyHeaderInfo kSynStartHeader = {
+    type,                         // Kind = Syn
+    stream_id,                    // Stream ID
+    associated_stream_id,         // Associated stream ID
+    ConvertRequestPriorityToSpdyPriority(request_priority, 2),
+                                  // Priority
+    flags,                        // Control Flags
+    compressed,                   // Compressed
+    INVALID,                      // Status
+    NULL,                         // Data
+    0,                            // Length
+    DATA_FLAG_NONE                // Data Flags
+  };
+  return ConstructSpdyPacket(kSynStartHeader,
+                             extra_headers,
+                             extra_header_count,
+                             kHeaders,
+                             kHeadersSize / 2);
+}
+
+// Constructs a standard SPDY GET SYN packet, optionally compressed
+// for the url |url|.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGet(const char* const url,
+                            bool compressed,
+                            int stream_id,
+                            RequestPriority request_priority) {
+  const SpdyHeaderInfo kSynStartHeader = {
+    SYN_STREAM,             // Kind = Syn
+    stream_id,              // Stream ID
+    0,                      // Associated stream ID
+    ConvertRequestPriorityToSpdyPriority(request_priority, 2),
+                            // Priority
+    CONTROL_FLAG_FIN,       // Control Flags
+    compressed,             // Compressed
+    INVALID,                // Status
+    NULL,                   // Data
+    0,                      // Length
+    DATA_FLAG_NONE          // Data Flags
+  };
+
+  std::string scheme, host, path;
+  ParseUrl(url, &scheme, &host, &path);
+  const char* const headers[] = {
+    "method",  "GET",
+    "url",     path.c_str(),
+    "host",    host.c_str(),
+    "scheme",  scheme.c_str(),
+    "version", "HTTP/1.1"
+  };
+  return ConstructSpdyPacket(
+      kSynStartHeader,
+      NULL,
+      0,
+      headers,
+      arraysize(headers) / 2);
+}
+
+// Constructs a standard SPDY GET SYN packet, optionally compressed.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGet(const char* const extra_headers[],
+                            int extra_header_count,
+                            bool compressed,
+                            int stream_id,
+                            RequestPriority request_priority) {
+  return ConstructSpdyGet(extra_headers, extra_header_count, compressed,
+                          stream_id, request_priority, true);
+}
+
+// Constructs a standard SPDY GET SYN packet, optionally compressed.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGet(const char* const extra_headers[],
+                            int extra_header_count,
+                            bool compressed,
+                            int stream_id,
+                            RequestPriority request_priority,
+                            bool direct) {
+  const char* const kStandardGetHeaders[] = {
+    "method", "GET",
+    "url", (direct ? "/" : "http://www.google.com/"),
+    "host", "www.google.com",
+    "scheme", "http",
+    "version", "HTTP/1.1"
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   compressed,
+                                   stream_id,
+                                   request_priority,
+                                   SYN_STREAM,
+                                   CONTROL_FLAG_FIN,
+                                   kStandardGetHeaders,
+                                   arraysize(kStandardGetHeaders));
+}
+
+// Constructs a standard SPDY SYN_STREAM frame for a CONNECT request.
+SpdyFrame* ConstructSpdyConnect(const char* const extra_headers[],
+                                int extra_header_count,
+                                int stream_id) {
+  const char* const kConnectHeaders[] = {
+    "method", "CONNECT",
+    "url", "www.google.com:443",
+    "host", "www.google.com",
+    "version", "HTTP/1.1",
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   /*compressed*/ false,
+                                   stream_id,
+                                   LOWEST,
+                                   SYN_STREAM,
+                                   CONTROL_FLAG_NONE,
+                                   kConnectHeaders,
+                                   arraysize(kConnectHeaders));
+}
+
+// Constructs a standard SPDY push SYN packet.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPush(const char* const extra_headers[],
+                             int extra_header_count,
+                             int stream_id,
+                             int associated_stream_id) {
+  const char* const kStandardGetHeaders[] = {
+    "hello", "bye",
+    "status", "200",
+    "version", "HTTP/1.1"
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   false,
+                                   stream_id,
+                                   LOWEST,
+                                   SYN_STREAM,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardGetHeaders,
+                                   arraysize(kStandardGetHeaders),
+                                   associated_stream_id);
+}
+
+SpdyFrame* ConstructSpdyPush(const char* const extra_headers[],
+                             int extra_header_count,
+                             int stream_id,
+                             int associated_stream_id,
+                             const char* url) {
+  const char* const kStandardGetHeaders[] = {
+    "hello", "bye",
+    "status", "200 OK",
+    "url", url,
+    "version", "HTTP/1.1"
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   false,
+                                   stream_id,
+                                   LOWEST,
+                                   SYN_STREAM,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardGetHeaders,
+                                   arraysize(kStandardGetHeaders),
+                                   associated_stream_id);
+
+}
+SpdyFrame* ConstructSpdyPush(const char* const extra_headers[],
+                             int extra_header_count,
+                             int stream_id,
+                             int associated_stream_id,
+                             const char* url,
+                             const char* status,
+                             const char* location) {
+  const char* const kStandardGetHeaders[] = {
+    "hello", "bye",
+    "status",  status,
+    "location", location,
+    "url", url,
+    "version", "HTTP/1.1"
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   false,
+                                   stream_id,
+                                   LOWEST,
+                                   SYN_STREAM,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardGetHeaders,
+                                   arraysize(kStandardGetHeaders),
+                                   associated_stream_id);
+}
+
+SpdyFrame* ConstructSpdyPush(int stream_id,
+                             int associated_stream_id,
+                             const char* url) {
+  const char* const kStandardGetHeaders[] = {
+    "url", url
+  };
+  return ConstructSpdyControlFrame(0,
+                                   0,
+                                   false,
+                                   stream_id,
+                                   LOWEST,
+                                   SYN_STREAM,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardGetHeaders,
+                                   arraysize(kStandardGetHeaders),
+                                   associated_stream_id);
+}
+
+SpdyFrame* ConstructSpdyPushHeaders(int stream_id,
+                                    const char* const extra_headers[],
+                                    int extra_header_count) {
+  const char* const kStandardGetHeaders[] = {
+    "status", "200 OK",
+    "version", "HTTP/1.1"
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   false,
+                                   stream_id,
+                                   LOWEST,
+                                   HEADERS,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardGetHeaders,
+                                   arraysize(kStandardGetHeaders));
+}
+
+// Constructs a standard SPDY SYN_REPLY packet with the specified status code.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdySynReplyError(const char* const status,
+                                      const char* const* const extra_headers,
+                                      int extra_header_count,
+                                      int stream_id) {
+  const char* const kStandardGetHeaders[] = {
+    "hello", "bye",
+    "status", status,
+    "version", "HTTP/1.1"
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   false,
+                                   stream_id,
+                                   LOWEST,
+                                   SYN_REPLY,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardGetHeaders,
+                                   arraysize(kStandardGetHeaders));
+}
+
+// Constructs a standard SPDY SYN_REPLY packet to match the SPDY GET.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGetSynReplyRedirect(int stream_id) {
+  static const char* const kExtraHeaders[] = {
+    "location", "http://www.foo.com/index.php",
+  };
+  return ConstructSpdySynReplyError("301 Moved Permanently", kExtraHeaders,
+                                    arraysize(kExtraHeaders)/2, stream_id);
+}
+
+// Constructs a standard SPDY SYN_REPLY packet with an Internal Server
+// Error status code.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdySynReplyError(int stream_id) {
+  return ConstructSpdySynReplyError("500 Internal Server Error", NULL, 0, 1);
+}
+
+// Constructs a standard SPDY SYN_REPLY packet to match the SPDY GET.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGetSynReply(const char* const extra_headers[],
+                                    int extra_header_count,
+                                    int stream_id) {
+  static const char* const kStandardGetHeaders[] = {
+    "hello", "bye",
+    "status", "200",
+    "version", "HTTP/1.1"
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   false,
+                                   stream_id,
+                                   LOWEST,
+                                   SYN_REPLY,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardGetHeaders,
+                                   arraysize(kStandardGetHeaders));
+}
+
+// Constructs a standard SPDY POST SYN packet.
+// |content_length| is the size of post data.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPost(int64 content_length,
+                             const char* const extra_headers[],
+                             int extra_header_count) {
+  std::string length_str = base::Int64ToString(content_length);
+  const char* post_headers[] = {
+    "method", "POST",
+    "url", "/",
+    "host", "www.google.com",
+    "scheme", "http",
+    "version", "HTTP/1.1",
+    "content-length", length_str.c_str()
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   false,
+                                   1,
+                                   LOWEST,
+                                   SYN_STREAM,
+                                   CONTROL_FLAG_NONE,
+                                   post_headers,
+                                   arraysize(post_headers));
+}
+
+// Constructs a chunked transfer SPDY POST SYN packet.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructChunkedSpdyPost(const char* const extra_headers[],
+                                    int extra_header_count) {
+  const char* post_headers[] = {
+    "method", "POST",
+    "url", "/",
+    "host", "www.google.com",
+    "scheme", "http",
+    "version", "HTTP/1.1"
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   false,
+                                   1,
+                                   LOWEST,
+                                   SYN_STREAM,
+                                   CONTROL_FLAG_NONE,
+                                   post_headers,
+                                   arraysize(post_headers));
+}
+
+// Constructs a standard SPDY SYN_REPLY packet to match the SPDY POST.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPostSynReply(const char* const extra_headers[],
+                                     int extra_header_count) {
+  static const char* const kStandardGetHeaders[] = {
+    "hello", "bye",
+    "status", "200",
+    "url", "/index.php",
+    "version", "HTTP/1.1"
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   false,
+                                   1,
+                                   LOWEST,
+                                   SYN_REPLY,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardGetHeaders,
+                                   arraysize(kStandardGetHeaders));
+}
+
+// Constructs a single SPDY data frame with the default contents.
+SpdyFrame* ConstructSpdyBodyFrame(int stream_id, bool fin) {
+  BufferedSpdyFramer framer(2, false);
+  return framer.CreateDataFrame(
+      stream_id, kUploadData, kUploadDataSize,
+      fin ? DATA_FLAG_FIN : DATA_FLAG_NONE);
+}
+
+// Constructs a single SPDY data frame with the given content.
+SpdyFrame* ConstructSpdyBodyFrame(int stream_id, const char* data,
+                                  uint32 len, bool fin) {
+  BufferedSpdyFramer framer(2, false);
+  return framer.CreateDataFrame(
+      stream_id, data, len, fin ? DATA_FLAG_FIN : DATA_FLAG_NONE);
+}
+
+// Wraps |frame| in the payload of a data frame in stream |stream_id|.
+SpdyFrame* ConstructWrappedSpdyFrame(const scoped_ptr<SpdyFrame>& frame,
+                                     int stream_id) {
+  return ConstructSpdyBodyFrame(stream_id, frame->data(),
+                                frame->length() + SpdyFrame::kHeaderSize,
+                                false);
+}
+
+// Construct an expected SPDY reply string.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// |buffer| is the buffer we're filling in.
+// Returns the number of bytes written into |buffer|.
+int ConstructSpdyReplyString(const char* const extra_headers[],
+                             int extra_header_count,
+                             char* buffer,
+                             int buffer_length) {
+  int packet_size = 0;
+  char* buffer_write = buffer;
+  int buffer_left = buffer_length;
+  SpdyHeaderBlock headers;
+  if (!buffer || !buffer_length)
+    return 0;
+  // Copy in the extra headers.
+  AppendHeadersToSpdyFrame(extra_headers, extra_header_count, &headers);
+  // The iterator gets us the list of header/value pairs in sorted order.
+  SpdyHeaderBlock::iterator next = headers.begin();
+  SpdyHeaderBlock::iterator last = headers.end();
+  for ( ; next != last; ++next) {
+    // Write the header.
+    int value_len, current_len, offset;
+    const char* header_string = next->first.c_str();
+    packet_size += AppendToBuffer(header_string,
+                                  next->first.length(),
+                                  &buffer_write,
+                                  &buffer_left);
+    packet_size += AppendToBuffer(": ",
+                                  strlen(": "),
+                                  &buffer_write,
+                                  &buffer_left);
+    // Write the value(s).
+    const char* value_string = next->second.c_str();
+    // Check if it's split among two or more values.
+    value_len = next->second.length();
+    current_len = strlen(value_string);
+    offset = 0;
+    // Handle the first N-1 values.
+    while (current_len < value_len) {
+      // Finish this line -- write the current value.
+      packet_size += AppendToBuffer(value_string + offset,
+                                    current_len - offset,
+                                    &buffer_write,
+                                    &buffer_left);
+      packet_size += AppendToBuffer("\n",
+                                    strlen("\n"),
+                                    &buffer_write,
+                                    &buffer_left);
+      // Advance to next value.
+      offset = current_len + 1;
+      current_len += 1 + strlen(value_string + offset);
+      // Start another line -- add the header again.
+      packet_size += AppendToBuffer(header_string,
+                                    next->first.length(),
+                                    &buffer_write,
+                                    &buffer_left);
+      packet_size += AppendToBuffer(": ",
+                                    strlen(": "),
+                                    &buffer_write,
+                                    &buffer_left);
+    }
+    EXPECT_EQ(value_len, current_len);
+    // Copy the last (or only) value.
+    packet_size += AppendToBuffer(value_string + offset,
+                                  value_len - offset,
+                                  &buffer_write,
+                                  &buffer_left);
+    packet_size += AppendToBuffer("\n",
+                                  strlen("\n"),
+                                  &buffer_write,
+                                  &buffer_left);
+  }
+  return packet_size;
+}
+
+// Create a MockWrite from the given SpdyFrame.
+MockWrite CreateMockWrite(const SpdyFrame& req) {
+  return MockWrite(
+      ASYNC, req.data(), req.length() + SpdyFrame::kHeaderSize);
+}
+
+// Create a MockWrite from the given SpdyFrame and sequence number.
+MockWrite CreateMockWrite(const SpdyFrame& req, int seq) {
+  return CreateMockWrite(req, seq, ASYNC);
+}
+
+// Create a MockWrite from the given SpdyFrame and sequence number.
+MockWrite CreateMockWrite(const SpdyFrame& req, int seq, IoMode mode) {
+  return MockWrite(
+      mode, req.data(), req.length() + SpdyFrame::kHeaderSize, seq);
+}
+
+// Create a MockRead from the given SpdyFrame.
+MockRead CreateMockRead(const SpdyFrame& resp) {
+  return MockRead(
+      ASYNC, resp.data(), resp.length() + SpdyFrame::kHeaderSize);
+}
+
+// Create a MockRead from the given SpdyFrame and sequence number.
+MockRead CreateMockRead(const SpdyFrame& resp, int seq) {
+  return CreateMockRead(resp, seq, ASYNC);
+}
+
+// Create a MockRead from the given SpdyFrame and sequence number.
+MockRead CreateMockRead(const SpdyFrame& resp, int seq, IoMode mode) {
+  return MockRead(
+      mode, resp.data(), resp.length() + SpdyFrame::kHeaderSize, seq);
+}
+
+// Combines the given SpdyFrames into the given char array and returns
+// the total length.
+int CombineFrames(const SpdyFrame** frames, int num_frames,
+                  char* buff, int buff_len) {
+  int total_len = 0;
+  for (int i = 0; i < num_frames; ++i) {
+    total_len += frames[i]->length() + SpdyFrame::kHeaderSize;
+  }
+  DCHECK_LE(total_len, buff_len);
+  char* ptr = buff;
+  for (int i = 0; i < num_frames; ++i) {
+    int len = frames[i]->length() + SpdyFrame::kHeaderSize;
+    memcpy(ptr, frames[i]->data(), len);
+    ptr += len;
+  }
+  return total_len;
+}
+
+SpdySessionDependencies::SpdySessionDependencies()
+    : host_resolver(new MockCachingHostResolver),
+      cert_verifier(new MockCertVerifier),
+      proxy_service(ProxyService::CreateDirect()),
+      ssl_config_service(new SSLConfigServiceDefaults),
+      socket_factory(new MockClientSocketFactory),
+      deterministic_socket_factory(new DeterministicMockClientSocketFactory),
+      http_auth_handler_factory(
+          HttpAuthHandlerFactory::CreateDefault(host_resolver.get())),
+      enable_ip_pooling(true),
+      enable_compression(false),
+      enable_ping(false),
+      time_func(&base::TimeTicks::Now),
+      net_log(NULL) {
+  // Note: The CancelledTransaction test does cleanup by running all
+  // tasks in the message loop (RunAllPending).  Unfortunately, that
+  // doesn't clean up tasks on the host resolver thread; and
+  // TCPConnectJob is currently not cancellable.  Using synchronous
+  // lookups allows the test to shutdown cleanly.  Until we have
+  // cancellable TCPConnectJobs, use synchronous lookups.
+  host_resolver->set_synchronous_mode(true);
+}
+
+SpdySessionDependencies::SpdySessionDependencies(ProxyService* proxy_service)
+    : host_resolver(new MockHostResolver),
+      cert_verifier(new MockCertVerifier),
+      proxy_service(proxy_service),
+      ssl_config_service(new SSLConfigServiceDefaults),
+      socket_factory(new MockClientSocketFactory),
+      deterministic_socket_factory(new DeterministicMockClientSocketFactory),
+      http_auth_handler_factory(
+          HttpAuthHandlerFactory::CreateDefault(host_resolver.get())),
+      enable_ip_pooling(true),
+      enable_compression(false),
+      enable_ping(false),
+      time_func(&base::TimeTicks::Now),
+      net_log(NULL) {}
+
+SpdySessionDependencies::~SpdySessionDependencies() {}
+
+// static
+HttpNetworkSession* SpdySessionDependencies::SpdyCreateSession(
+    SpdySessionDependencies* session_deps) {
+  net::HttpNetworkSession::Params params = CreateSessionParams(session_deps);
+  params.client_socket_factory = session_deps->socket_factory.get();
+  HttpNetworkSession* http_session = new HttpNetworkSession(params);
+  SpdySessionPoolPeer pool_peer(http_session->spdy_session_pool());
+  pool_peer.EnableSendingInitialSettings(false);
+  return http_session;
+}
+
+// static
+HttpNetworkSession* SpdySessionDependencies::SpdyCreateSessionDeterministic(
+    SpdySessionDependencies* session_deps) {
+  net::HttpNetworkSession::Params params = CreateSessionParams(session_deps);
+  params.client_socket_factory =
+      session_deps->deterministic_socket_factory.get();
+  HttpNetworkSession* http_session = new HttpNetworkSession(params);
+  SpdySessionPoolPeer pool_peer(http_session->spdy_session_pool());
+  pool_peer.EnableSendingInitialSettings(false);
+  return http_session;
+}
+
+// static
+net::HttpNetworkSession::Params SpdySessionDependencies::CreateSessionParams(
+    SpdySessionDependencies* session_deps) {
+  net::HttpNetworkSession::Params params;
+  params.host_resolver = session_deps->host_resolver.get();
+  params.cert_verifier = session_deps->cert_verifier.get();
+  params.proxy_service = session_deps->proxy_service.get();
+  params.ssl_config_service = session_deps->ssl_config_service;
+  params.http_auth_handler_factory =
+      session_deps->http_auth_handler_factory.get();
+  params.http_server_properties = &session_deps->http_server_properties;
+  params.enable_spdy_ip_pooling = session_deps->enable_ip_pooling;
+  params.enable_spdy_compression = session_deps->enable_compression;
+  params.enable_spdy_ping_based_connection_checking = session_deps->enable_ping;
+  params.spdy_default_protocol = kProtoSPDY2;
+  params.time_func = session_deps->time_func;
+  params.trusted_spdy_proxy = session_deps->trusted_spdy_proxy;
+  params.net_log = session_deps->net_log;
+  return params;
+}
+
+SpdyURLRequestContext::SpdyURLRequestContext()
+    : ALLOW_THIS_IN_INITIALIZER_LIST(storage_(this)) {
+  storage_.set_host_resolver(scoped_ptr<HostResolver>(new MockHostResolver));
+  storage_.set_cert_verifier(new MockCertVerifier);
+  storage_.set_proxy_service(ProxyService::CreateDirect());
+  storage_.set_ssl_config_service(new SSLConfigServiceDefaults);
+  storage_.set_http_auth_handler_factory(HttpAuthHandlerFactory::CreateDefault(
+      host_resolver()));
+  storage_.set_http_server_properties(new HttpServerPropertiesImpl);
+  net::HttpNetworkSession::Params params;
+  params.client_socket_factory = &socket_factory_;
+  params.host_resolver = host_resolver();
+  params.cert_verifier = cert_verifier();
+  params.proxy_service = proxy_service();
+  params.ssl_config_service = ssl_config_service();
+  params.http_auth_handler_factory = http_auth_handler_factory();
+  params.network_delegate = network_delegate();
+  params.enable_spdy_compression = false;
+  params.enable_spdy_ping_based_connection_checking = false;
+  params.spdy_default_protocol = kProtoSPDY2;
+  params.http_server_properties = http_server_properties();
+  scoped_refptr<HttpNetworkSession> network_session(
+      new HttpNetworkSession(params));
+  SpdySessionPoolPeer pool_peer(network_session->spdy_session_pool());
+  pool_peer.EnableSendingInitialSettings(false);
+  storage_.set_http_transaction_factory(new HttpCache(
+      network_session,
+      HttpCache::DefaultBackend::InMemory(0)));
+}
+
+SpdyURLRequestContext::~SpdyURLRequestContext() {
+}
+
+const SpdyHeaderInfo MakeSpdyHeader(SpdyControlType type) {
+  const SpdyHeaderInfo kHeader = {
+    type,                         // Kind = Syn
+    1,                            // Stream ID
+    0,                            // Associated stream ID
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 2),  // Priority
+    CONTROL_FLAG_FIN,       // Control Flags
+    false,                        // Compressed
+    INVALID,                // Status
+    NULL,                         // Data
+    0,                            // Length
+    DATA_FLAG_NONE          // Data Flags
+  };
+  return kHeader;
+}
+
+}  // namespace test_spdy2
+}  // namespace net
diff --git a/src/net/spdy/spdy_test_util_spdy2.h b/src/net/spdy/spdy_test_util_spdy2.h
new file mode 100644
index 0000000..1a9be94
--- /dev/null
+++ b/src/net/spdy/spdy_test_util_spdy2.h
@@ -0,0 +1,425 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_SPDY_SPDY_TEST_UTIL_H_
+#define NET_SPDY_SPDY_TEST_UTIL_H_
+
+#include "base/basictypes.h"
+#include "net/base/cert_verifier.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/mock_host_resolver.h"
+#include "net/base/request_priority.h"
+#include "net/base/ssl_config_service_defaults.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_network_layer.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/proxy/proxy_service.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/spdy_session.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_storage.h"
+
+namespace net {
+
+namespace test_spdy2 {
+
+// Default upload data used by both, mock objects and framer when creating
+// data frames.
+const char kDefaultURL[] = "http://www.google.com";
+const char kUploadData[] = "hello!";
+const int kUploadDataSize = arraysize(kUploadData)-1;
+
+// NOTE: In GCC, on a Mac, this can't be in an anonymous namespace!
+// This struct holds information used to construct spdy control and data frames.
+struct SpdyHeaderInfo {
+  SpdyControlType kind;
+  SpdyStreamId id;
+  SpdyStreamId assoc_id;
+  SpdyPriority priority;
+  SpdyControlFlags control_flags;
+  bool compressed;
+  SpdyStatusCodes status;
+  const char* data;
+  uint32 data_length;
+  SpdyDataFlags data_flags;
+};
+
+// Chop a frame into an array of MockWrites.
+// |data| is the frame to chop.
+// |length| is the length of the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockWrite* ChopWriteFrame(const char* data, int length, int num_chunks);
+
+// Chop a SpdyFrame into an array of MockWrites.
+// |frame| is the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockWrite* ChopWriteFrame(const SpdyFrame& frame, int num_chunks);
+
+// Chop a frame into an array of MockReads.
+// |data| is the frame to chop.
+// |length| is the length of the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockRead* ChopReadFrame(const char* data, int length, int num_chunks);
+
+// Chop a SpdyFrame into an array of MockReads.
+// |frame| is the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockRead* ChopReadFrame(const SpdyFrame& frame, int num_chunks);
+
+// Adds headers and values to a map.
+// |extra_headers| is an array of { name, value } pairs, arranged as strings
+// where the even entries are the header names, and the odd entries are the
+// header values.
+// |headers| gets filled in from |extra_headers|.
+void AppendHeadersToSpdyFrame(const char* const extra_headers[],
+                              int extra_header_count,
+                              SpdyHeaderBlock* headers);
+
+// Writes |str| of the given |len| to the buffer pointed to by |buffer_handle|.
+// Uses a template so buffer_handle can be a char* or an unsigned char*.
+// Updates the |*buffer_handle| pointer by |len|
+// Returns the number of bytes written into *|buffer_handle|
+template<class T>
+int AppendToBuffer(const char* str,
+                   int len,
+                   T** buffer_handle,
+                   int* buffer_len_remaining) {
+  DCHECK_GT(len, 0);
+  DCHECK(NULL != buffer_handle) << "NULL buffer handle";
+  DCHECK(NULL != *buffer_handle) << "NULL pointer";
+  DCHECK(NULL != buffer_len_remaining)
+      << "NULL buffer remainder length pointer";
+  DCHECK_GE(*buffer_len_remaining, len) << "Insufficient buffer size";
+  memcpy(*buffer_handle, str, len);
+  *buffer_handle += len;
+  *buffer_len_remaining -= len;
+  return len;
+}
+
+// Writes |val| to a location of size |len|, in big-endian format.
+// in the buffer pointed to by |buffer_handle|.
+// Updates the |*buffer_handle| pointer by |len|
+// Returns the number of bytes written
+int AppendToBuffer(int val,
+                   int len,
+                   unsigned char** buffer_handle,
+                   int* buffer_len_remaining);
+
+// Construct a SPDY packet.
+// |head| is the start of the packet, up to but not including
+// the header value pairs.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// |tail| is any (relatively constant) header-value pairs to add.
+// |buffer| is the buffer we're filling in.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPacket(const SpdyHeaderInfo& header_info,
+                               const char* const extra_headers[],
+                               int extra_header_count,
+                               const char* const tail[],
+                               int tail_header_count);
+
+// Construct a generic SpdyControlFrame.
+SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[],
+                                     int extra_header_count,
+                                     bool compressed,
+                                     int stream_id,
+                                     RequestPriority request_priority,
+                                     SpdyControlType type,
+                                     SpdyControlFlags flags,
+                                     const char* const* kHeaders,
+                                     int kHeadersSize);
+SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[],
+                                     int extra_header_count,
+                                     bool compressed,
+                                     int stream_id,
+                                     RequestPriority request_priority,
+                                     SpdyControlType type,
+                                     SpdyControlFlags flags,
+                                     const char* const* kHeaders,
+                                     int kHeadersSize,
+                                     int associated_stream_id);
+
+// Construct an expected SPDY reply string.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// |buffer| is the buffer we're filling in.
+// Returns the number of bytes written into |buffer|.
+int ConstructSpdyReplyString(const char* const extra_headers[],
+                             int extra_header_count,
+                             char* buffer,
+                             int buffer_length);
+
+// Construct an expected SPDY SETTINGS frame.
+// |settings| are the settings to set.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdySettings(const SettingsMap& settings);
+
+// Construct an expected SPDY CREDENTIAL frame.
+// |credential| is the credential to send.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyCredential(const SpdyCredential& credential);
+
+// Construct a SPDY PING frame.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyPing(uint32 ping_id);
+
+// Construct a SPDY GOAWAY frame.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyGoAway();
+
+// Construct a SPDY WINDOW_UPDATE frame.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyWindowUpdate(SpdyStreamId, uint32 delta_window_size);
+
+// Construct a SPDY RST_STREAM frame.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyRstStream(SpdyStreamId stream_id,
+                                  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.
+// |index| is the index of the header we want.
+// Returns the number of bytes written into |buffer|.
+int ConstructSpdyHeader(const char* const extra_headers[],
+                        int extra_header_count,
+                        char* buffer,
+                        int buffer_length,
+                        int index);
+
+// Constructs a standard SPDY GET SYN packet, optionally compressed
+// for the url |url|.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGet(const char* const url,
+                            bool compressed,
+                            int stream_id,
+                            RequestPriority request_priority);
+
+// Constructs a standard SPDY GET SYN packet, optionally compressed.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGet(const char* const extra_headers[],
+                            int extra_header_count,
+                            bool compressed,
+                            int stream_id,
+                            RequestPriority request_priority);
+
+// Constructs a standard SPDY GET SYN packet, optionally compressed.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.  If |direct| is false, the
+// the full url will be used instead of simply the path.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGet(const char* const extra_headers[],
+                            int extra_header_count,
+                            bool compressed,
+                            int stream_id,
+                            RequestPriority request_priority,
+                            bool direct);
+
+// Constructs a standard SPDY SYN_STREAM frame for a CONNECT request.
+SpdyFrame* ConstructSpdyConnect(const char* const extra_headers[],
+                                int extra_header_count,
+                                int stream_id);
+
+// Constructs a standard SPDY push SYN packet.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPush(const char* const extra_headers[],
+                             int extra_header_count,
+                             int stream_id,
+                             int associated_stream_id);
+SpdyFrame* ConstructSpdyPush(const char* const extra_headers[],
+                             int extra_header_count,
+                             int stream_id,
+                             int associated_stream_id,
+                             const char* url);
+SpdyFrame* ConstructSpdyPush(const char* const extra_headers[],
+                             int extra_header_count,
+                             int stream_id,
+                             int associated_stream_id,
+                             const char* url,
+                             const char* status,
+                             const char* location);
+SpdyFrame* ConstructSpdyPush(int stream_id,
+                             int associated_stream_id,
+                             const char* url);
+
+SpdyFrame* ConstructSpdyPushHeaders(int stream_id,
+                                    const char* const extra_headers[],
+                                    int extra_header_count);
+
+// Constructs a standard SPDY SYN_REPLY packet to match the SPDY GET.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGetSynReply(const char* const extra_headers[],
+                                    int extra_header_count,
+                                    int stream_id);
+
+// Constructs a standard SPDY SYN_REPLY packet to match the SPDY GET.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGetSynReplyRedirect(int stream_id);
+
+// Constructs a standard SPDY SYN_REPLY packet with an Internal Server
+// Error status code.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdySynReplyError(int stream_id);
+
+// Constructs a standard SPDY SYN_REPLY packet with the specified status code.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdySynReplyError(const char* const status,
+                                      const char* const* const extra_headers,
+                                      int extra_header_count,
+                                      int stream_id);
+
+// Constructs a standard SPDY POST SYN packet.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPost(int64 content_length,
+                             const char* const extra_headers[],
+                             int extra_header_count);
+
+// Constructs a chunked transfer SPDY POST SYN packet.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructChunkedSpdyPost(const char* const extra_headers[],
+                                    int extra_header_count);
+
+// Constructs a standard SPDY SYN_REPLY packet to match the SPDY POST.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPostSynReply(const char* const extra_headers[],
+                                     int extra_header_count);
+
+// Constructs a single SPDY data frame with the contents "hello!"
+SpdyFrame* ConstructSpdyBodyFrame(int stream_id,
+                                  bool fin);
+
+// Constructs a single SPDY data frame with the given content.
+SpdyFrame* ConstructSpdyBodyFrame(int stream_id, const char* data,
+                                  uint32 len, bool fin);
+
+// Wraps |frame| in the payload of a data frame in stream |stream_id|.
+SpdyFrame* ConstructWrappedSpdyFrame(const scoped_ptr<SpdyFrame>& frame,
+                                     int stream_id);
+
+// Create an async MockWrite from the given SpdyFrame.
+MockWrite CreateMockWrite(const SpdyFrame& req);
+
+// Create an async MockWrite from the given SpdyFrame and sequence number.
+MockWrite CreateMockWrite(const SpdyFrame& req, int seq);
+
+MockWrite CreateMockWrite(const SpdyFrame& req, int seq, IoMode mode);
+
+// Create a MockRead from the given SpdyFrame.
+MockRead CreateMockRead(const SpdyFrame& resp);
+
+// Create a MockRead from the given SpdyFrame and sequence number.
+MockRead CreateMockRead(const SpdyFrame& resp, int seq);
+
+MockRead CreateMockRead(const SpdyFrame& resp, int seq, IoMode mode);
+
+// Combines the given SpdyFrames into the given char array and returns
+// the total length.
+int CombineFrames(const SpdyFrame** frames, int num_frames,
+                  char* buff, int buff_len);
+
+// Helper to manage the lifetimes of the dependencies for a
+// HttpNetworkTransaction.
+struct SpdySessionDependencies {
+  // Default set of dependencies -- "null" proxy service.
+  SpdySessionDependencies();
+
+  // Custom proxy service dependency.
+  explicit SpdySessionDependencies(ProxyService* proxy_service);
+
+  ~SpdySessionDependencies();
+
+  static HttpNetworkSession* SpdyCreateSession(
+      SpdySessionDependencies* session_deps);
+  static HttpNetworkSession* SpdyCreateSessionDeterministic(
+      SpdySessionDependencies* session_deps);
+  static HttpNetworkSession::Params CreateSessionParams(
+      SpdySessionDependencies* session_deps);
+
+  // NOTE: host_resolver must be ordered before http_auth_handler_factory.
+  scoped_ptr<MockHostResolverBase> host_resolver;
+  scoped_ptr<CertVerifier> cert_verifier;
+  scoped_ptr<ProxyService> proxy_service;
+  scoped_refptr<SSLConfigService> ssl_config_service;
+  scoped_ptr<MockClientSocketFactory> socket_factory;
+  scoped_ptr<DeterministicMockClientSocketFactory> deterministic_socket_factory;
+  scoped_ptr<HttpAuthHandlerFactory> http_auth_handler_factory;
+  HttpServerPropertiesImpl http_server_properties;
+  bool enable_ip_pooling;
+  bool enable_compression;
+  bool enable_ping;
+  SpdySession::TimeFunc time_func;
+  std::string trusted_spdy_proxy;
+  NetLog* net_log;
+};
+
+class SpdyURLRequestContext : public URLRequestContext {
+ public:
+  SpdyURLRequestContext();
+  virtual ~SpdyURLRequestContext();
+
+  MockClientSocketFactory& socket_factory() { return socket_factory_; }
+
+ private:
+  MockClientSocketFactory socket_factory_;
+  net::URLRequestContextStorage storage_;
+};
+
+const SpdyHeaderInfo MakeSpdyHeader(SpdyControlType type);
+
+class SpdySessionPoolPeer {
+ public:
+  explicit SpdySessionPoolPeer(SpdySessionPool* pool)
+      : pool_(pool) {}
+
+  void AddAlias(const IPEndPoint& address, const HostPortProxyPair& pair) {
+    pool_->AddAlias(address, pair);
+  }
+
+  void RemoveAliases(const HostPortProxyPair& pair) {
+    pool_->RemoveAliases(pair);
+  }
+
+  void RemoveSpdySession(const scoped_refptr<SpdySession>& session) {
+    pool_->Remove(session);
+  }
+
+  void DisableDomainAuthenticationVerification() {
+    pool_->verify_domain_authentication_ = false;
+  }
+
+  void EnableSendingInitialSettings(bool enabled) {
+    pool_->enable_sending_initial_settings_ = enabled;
+  }
+
+ private:
+  SpdySessionPool* const pool_;
+
+  DISALLOW_COPY_AND_ASSIGN(SpdySessionPoolPeer);
+};
+
+}  // namespace test_spdy2
+
+}  // namespace net
+
+#endif  // NET_SPDY_SPDY_TEST_UTIL_H_
diff --git a/src/net/spdy/spdy_test_util_spdy3.cc b/src/net/spdy/spdy_test_util_spdy3.cc
new file mode 100644
index 0000000..79d8100
--- /dev/null
+++ b/src/net/spdy/spdy_test_util_spdy3.cc
@@ -0,0 +1,1033 @@
+// Copyright (c) 2012 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_test_util_spdy3.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "net/base/mock_cert_verifier.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_network_transaction.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_session.h"
+
+namespace net {
+
+namespace test_spdy3 {
+
+namespace {
+
+// Parses a URL into the scheme, host, and path components required for a
+// SPDY request.
+void ParseUrl(const char* const url, std::string* scheme, std::string* host,
+              std::string* path) {
+  GURL gurl(url);
+  path->assign(gurl.PathForRequest());
+  scheme->assign(gurl.scheme());
+  host->assign(gurl.host());
+  if (gurl.has_port()) {
+    host->append(":");
+    host->append(gurl.port());
+  }
+}
+
+}  // namespace
+
+
+MockECSignatureCreator::MockECSignatureCreator(crypto::ECPrivateKey* key)
+    : key_(key) {
+}
+
+bool MockECSignatureCreator::Sign(const uint8* data,
+                                  int data_len,
+                                  std::vector<uint8>* signature) {
+  std::vector<uint8> private_key_value;
+  key_->ExportValue(&private_key_value);
+  std::string head = "fakesignature";
+  std::string tail = "/fakesignature";
+
+  signature->clear();
+  signature->insert(signature->end(), head.begin(), head.end());
+  signature->insert(signature->end(), private_key_value.begin(),
+                    private_key_value.end());
+  signature->insert(signature->end(), '-');
+  signature->insert(signature->end(), data, data + data_len);
+  signature->insert(signature->end(), tail.begin(), tail.end());
+  return true;
+}
+
+bool MockECSignatureCreator::DecodeSignature(
+    const std::vector<uint8>& signature,
+    std::vector<uint8>* out_raw_sig) {
+  *out_raw_sig = signature;
+  return true;
+}
+
+MockECSignatureCreatorFactory::MockECSignatureCreatorFactory() {
+  crypto::ECSignatureCreator::SetFactoryForTesting(this);
+}
+
+MockECSignatureCreatorFactory::~MockECSignatureCreatorFactory() {
+  crypto::ECSignatureCreator::SetFactoryForTesting(NULL);
+}
+
+crypto::ECSignatureCreator* MockECSignatureCreatorFactory::Create(
+    crypto::ECPrivateKey* key) {
+  return new MockECSignatureCreator(key);
+}
+
+// Chop a frame into an array of MockWrites.
+// |data| is the frame to chop.
+// |length| is the length of the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockWrite* ChopWriteFrame(const char* data, int length, int num_chunks) {
+  MockWrite* chunks = new MockWrite[num_chunks];
+  int chunk_size = length / num_chunks;
+  for (int index = 0; index < num_chunks; index++) {
+    const char* ptr = data + (index * chunk_size);
+    if (index == num_chunks - 1)
+      chunk_size += length % chunk_size;  // The last chunk takes the remainder.
+    chunks[index] = MockWrite(ASYNC, ptr, chunk_size);
+  }
+  return chunks;
+}
+
+// Chop a SpdyFrame into an array of MockWrites.
+// |frame| is the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockWrite* ChopWriteFrame(const SpdyFrame& frame, int num_chunks) {
+  return ChopWriteFrame(frame.data(),
+                        frame.length() + SpdyFrame::kHeaderSize,
+                        num_chunks);
+}
+
+// Chop a frame into an array of MockReads.
+// |data| is the frame to chop.
+// |length| is the length of the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockRead* ChopReadFrame(const char* data, int length, int num_chunks) {
+  MockRead* chunks = new MockRead[num_chunks];
+  int chunk_size = length / num_chunks;
+  for (int index = 0; index < num_chunks; index++) {
+    const char* ptr = data + (index * chunk_size);
+    if (index == num_chunks - 1)
+      chunk_size += length % chunk_size;  // The last chunk takes the remainder.
+    chunks[index] = MockRead(ASYNC, ptr, chunk_size);
+  }
+  return chunks;
+}
+
+// Chop a SpdyFrame into an array of MockReads.
+// |frame| is the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockRead* ChopReadFrame(const SpdyFrame& frame, int num_chunks) {
+  return ChopReadFrame(frame.data(),
+                       frame.length() + SpdyFrame::kHeaderSize,
+                       num_chunks);
+}
+
+// Adds headers and values to a map.
+// |extra_headers| is an array of { name, value } pairs, arranged as strings
+// where the even entries are the header names, and the odd entries are the
+// header values.
+// |headers| gets filled in from |extra_headers|.
+void AppendHeadersToSpdyFrame(const char* const extra_headers[],
+                              int extra_header_count,
+                              SpdyHeaderBlock* headers) {
+  std::string this_header;
+  std::string this_value;
+
+  if (!extra_header_count)
+    return;
+
+  // Sanity check: Non-NULL header list.
+  DCHECK(NULL != extra_headers) << "NULL header value pair list";
+  // Sanity check: Non-NULL header map.
+  DCHECK(NULL != headers) << "NULL header map";
+  // Copy in the headers.
+  for (int i = 0; i < extra_header_count; i++) {
+    // Sanity check: Non-empty header.
+    DCHECK_NE('\0', *extra_headers[i * 2]) << "Empty header value pair";
+    this_header = extra_headers[i * 2];
+    std::string::size_type header_len = this_header.length();
+    if (!header_len)
+      continue;
+    this_value = extra_headers[1 + (i * 2)];
+    std::string new_value;
+    if (headers->find(this_header) != headers->end()) {
+      // More than one entry in the header.
+      // Don't add the header again, just the append to the value,
+      // separated by a NULL character.
+
+      // Adjust the value.
+      new_value = (*headers)[this_header];
+      // Put in a NULL separator.
+      new_value.append(1, '\0');
+      // Append the new value.
+      new_value += this_value;
+    } else {
+      // Not a duplicate, just write the value.
+      new_value = this_value;
+    }
+    (*headers)[this_header] = new_value;
+  }
+}
+
+// Writes |val| to a location of size |len|, in big-endian format.
+// in the buffer pointed to by |buffer_handle|.
+// Updates the |*buffer_handle| pointer by |len|
+// Returns the number of bytes written
+int AppendToBuffer(int val,
+                   int len,
+                   unsigned char** buffer_handle,
+                   int* buffer_len_remaining) {
+  if (len <= 0)
+    return 0;
+  DCHECK((size_t) len <= sizeof(len)) << "Data length too long for data type";
+  DCHECK(NULL != buffer_handle) << "NULL buffer handle";
+  DCHECK(NULL != *buffer_handle) << "NULL pointer";
+  DCHECK(NULL != buffer_len_remaining)
+      << "NULL buffer remainder length pointer";
+  DCHECK_GE(*buffer_len_remaining, len) << "Insufficient buffer size";
+  for (int i = 0; i < len; i++) {
+    int shift = (8 * (len - (i + 1)));
+    unsigned char val_chunk = (val >> shift) & 0x0FF;
+    *(*buffer_handle)++ = val_chunk;
+    *buffer_len_remaining += 1;
+  }
+  return len;
+}
+
+// Construct a SPDY packet.
+// |head| is the start of the packet, up to but not including
+// the header value pairs.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// |tail| is any (relatively constant) header-value pairs to add.
+// |buffer| is the buffer we're filling in.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPacket(const SpdyHeaderInfo& header_info,
+                               const char* const extra_headers[],
+                               int extra_header_count,
+                               const char* const tail[],
+                               int tail_header_count) {
+  BufferedSpdyFramer framer(3, header_info.compressed);
+  SpdyHeaderBlock headers;
+  // Copy in the extra headers to our map.
+  AppendHeadersToSpdyFrame(extra_headers, extra_header_count, &headers);
+  // Copy in the tail headers to our map.
+  if (tail && tail_header_count)
+    AppendHeadersToSpdyFrame(tail, tail_header_count, &headers);
+  SpdyFrame* frame = NULL;
+  switch (header_info.kind) {
+    case SYN_STREAM:
+      frame = framer.CreateSynStream(header_info.id, header_info.assoc_id,
+                                     header_info.priority,
+                                     header_info.credential_slot,
+                                     header_info.control_flags,
+                                     header_info.compressed, &headers);
+      break;
+    case SYN_REPLY:
+      frame = framer.CreateSynReply(header_info.id, header_info.control_flags,
+                                    header_info.compressed, &headers);
+      break;
+    case RST_STREAM:
+      frame = framer.CreateRstStream(header_info.id, header_info.status);
+      break;
+    case HEADERS:
+      frame = framer.CreateHeaders(header_info.id, header_info.control_flags,
+                                   header_info.compressed, &headers);
+      break;
+    default:
+      frame = framer.CreateDataFrame(header_info.id, header_info.data,
+                                     header_info.data_length,
+                                     header_info.data_flags);
+      break;
+  }
+  return frame;
+}
+
+// Construct an expected SPDY SETTINGS frame.
+// |settings| are the settings to set.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdySettings(const SettingsMap& settings) {
+  BufferedSpdyFramer framer(3, false);
+  return framer.CreateSettings(settings);
+}
+
+// Construct an expected SPDY CREDENTIAL frame.
+// |credential| is the credential to sen.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyCredential(
+    const SpdyCredential& credential) {
+  BufferedSpdyFramer framer(3, false);
+  return framer.CreateCredentialFrame(credential);
+}
+
+// Construct a SPDY PING frame.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyPing(uint32 ping_id) {
+  BufferedSpdyFramer framer(3, false);
+  return framer.CreatePingFrame(ping_id);
+}
+
+// Construct a SPDY GOAWAY frame.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyGoAway() {
+  BufferedSpdyFramer framer(3, false);
+  return framer.CreateGoAway(0, GOAWAY_OK);
+}
+
+// Construct a SPDY WINDOW_UPDATE frame.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyWindowUpdate(
+    const SpdyStreamId stream_id, uint32 delta_window_size) {
+  BufferedSpdyFramer framer(3, false);
+  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.
+SpdyFrame* ConstructSpdyRstStream(SpdyStreamId stream_id,
+                                  SpdyStatusCodes status) {
+  BufferedSpdyFramer framer(3, false);
+  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.
+// |index| is the index of the header we want.
+// Returns the number of bytes written into |buffer|.
+int ConstructSpdyHeader(const char* const extra_headers[],
+                        int extra_header_count,
+                        char* buffer,
+                        int buffer_length,
+                        int index) {
+  const char* this_header = NULL;
+  const char* this_value = NULL;
+  if (!buffer || !buffer_length)
+    return 0;
+  *buffer = '\0';
+  // Sanity check: Non-empty header list.
+  DCHECK(NULL != extra_headers) << "NULL extra headers pointer";
+  // Sanity check: Index out of range.
+  DCHECK((index >= 0) && (index < extra_header_count))
+      << "Index " << index
+      << " out of range [0, " << extra_header_count << ")";
+  this_header = extra_headers[index * 2];
+  // Sanity check: Non-empty header.
+  if (!*this_header)
+    return 0;
+  std::string::size_type header_len = strlen(this_header);
+  if (!header_len)
+    return 0;
+  this_value = extra_headers[1 + (index * 2)];
+  // Sanity check: Non-empty value.
+  if (!*this_value)
+    this_value = "";
+  int n = base::snprintf(buffer,
+                         buffer_length,
+                         "%s: %s\r\n",
+                         this_header,
+                         this_value);
+  return n;
+}
+
+SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[],
+                                     int extra_header_count,
+                                     bool compressed,
+                                     int stream_id,
+                                     RequestPriority request_priority,
+                                     SpdyControlType type,
+                                     SpdyControlFlags flags,
+                                     const char* const* kHeaders,
+                                     int kHeadersSize) {
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   compressed,
+                                   stream_id,
+                                   request_priority,
+                                   type,
+                                   flags,
+                                   kHeaders,
+                                   kHeadersSize,
+                                   0);
+}
+
+SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[],
+                                     int extra_header_count,
+                                     bool compressed,
+                                     int stream_id,
+                                     RequestPriority request_priority,
+                                     SpdyControlType type,
+                                     SpdyControlFlags flags,
+                                     const char* const* kHeaders,
+                                     int kHeadersSize,
+                                     int associated_stream_id) {
+  const SpdyHeaderInfo kSynStartHeader = {
+    type,                         // Kind = Syn
+    stream_id,                    // Stream ID
+    associated_stream_id,         // Associated stream ID
+    ConvertRequestPriorityToSpdyPriority(request_priority, 3),
+                                  // Priority
+    0,                            // Credential Slot
+    flags,                        // Control Flags
+    compressed,                   // Compressed
+    INVALID,                      // Status
+    NULL,                         // Data
+    0,                            // Length
+    DATA_FLAG_NONE                // Data Flags
+  };
+  return ConstructSpdyPacket(kSynStartHeader,
+                             extra_headers,
+                             extra_header_count,
+                             kHeaders,
+                             kHeadersSize / 2);
+}
+
+// Constructs a standard SPDY GET SYN packet, optionally compressed
+// for the url |url|.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGet(const char* const url,
+                            bool compressed,
+                            int stream_id,
+                            RequestPriority request_priority) {
+  const SpdyHeaderInfo kSynStartHeader = {
+    SYN_STREAM,                   // Kind = Syn
+    stream_id,                    // Stream ID
+    0,                            // Associated stream ID
+    ConvertRequestPriorityToSpdyPriority(request_priority, 3),
+                                  // Priority
+    0,                            // Credential Slot
+    CONTROL_FLAG_FIN,             // Control Flags
+    compressed,                   // Compressed
+    INVALID,                      // Status
+    NULL,                         // Data
+    0,                            // Length
+    DATA_FLAG_NONE                // Data Flags
+  };
+
+  std::string scheme, host, path;
+  ParseUrl(url, &scheme, &host, &path);
+  const char* const headers[] = {
+    ":method", "GET",
+    ":path", path.c_str(),
+    ":host", host.c_str(),
+    ":scheme", scheme.c_str(),
+    ":version", "HTTP/1.1"
+  };
+  return ConstructSpdyPacket(
+      kSynStartHeader,
+      NULL,
+      0,
+      headers,
+      arraysize(headers) / 2);
+}
+
+// Constructs a standard SPDY GET SYN packet, optionally compressed.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGet(const char* const extra_headers[],
+                            int extra_header_count,
+                            bool compressed,
+                            int stream_id,
+                            RequestPriority request_priority) {
+  return ConstructSpdyGet(extra_headers, extra_header_count, compressed,
+                          stream_id, request_priority, true);
+}
+
+// Constructs a standard SPDY GET SYN packet, optionally compressed.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGet(const char* const extra_headers[],
+                            int extra_header_count,
+                            bool compressed,
+                            int stream_id,
+                            RequestPriority request_priority,
+                            bool direct) {
+  const char* const kStandardGetHeaders[] = {
+    ":method", "GET",
+    ":host", "www.google.com",
+    ":scheme", "http",
+    ":version", "HTTP/1.1",
+    ":path", (direct ? "/" : "/")
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   compressed,
+                                   stream_id,
+                                   request_priority,
+                                   SYN_STREAM,
+                                   CONTROL_FLAG_FIN,
+                                   kStandardGetHeaders,
+                                   arraysize(kStandardGetHeaders));
+}
+
+// Constructs a standard SPDY SYN_STREAM frame for a CONNECT request.
+SpdyFrame* ConstructSpdyConnect(const char* const extra_headers[],
+                                int extra_header_count,
+                                int stream_id) {
+  const char* const kConnectHeaders[] = {
+    ":method", "CONNECT",
+    ":path", "www.google.com:443",
+    ":host", "www.google.com",
+    ":version", "HTTP/1.1",
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   /*compressed*/ false,
+                                   stream_id,
+                                   LOWEST,
+                                   SYN_STREAM,
+                                   CONTROL_FLAG_NONE,
+                                   kConnectHeaders,
+                                   arraysize(kConnectHeaders));
+}
+
+// Constructs a standard SPDY push SYN packet.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPush(const char* const extra_headers[],
+                             int extra_header_count,
+                             int stream_id,
+                             int associated_stream_id) {
+  const char* const kStandardPushHeaders[] = {
+    "hello", "bye",
+    ":status",  "200",
+    ":version", "HTTP/1.1"
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   false,
+                                   stream_id,
+                                   LOWEST,
+                                   SYN_STREAM,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardPushHeaders,
+                                   arraysize(kStandardPushHeaders),
+                                   associated_stream_id);
+}
+
+SpdyFrame* ConstructSpdyPush(const char* const extra_headers[],
+                             int extra_header_count,
+                             int stream_id,
+                             int associated_stream_id,
+                             const char* url) {
+  std::string scheme, host, path;
+  ParseUrl(url, &scheme, &host, &path);
+  const char* const headers[] = {
+    "hello",    "bye",
+    ":status",  "200 OK",
+    ":version", "HTTP/1.1",
+    ":path",    path.c_str(),
+    ":host",    host.c_str(),
+    ":scheme",  scheme.c_str(),
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   false,
+                                   stream_id,
+                                   LOWEST,
+                                   SYN_STREAM,
+                                   CONTROL_FLAG_NONE,
+                                   headers,
+                                   arraysize(headers),
+                                   associated_stream_id);
+
+}
+SpdyFrame* ConstructSpdyPush(const char* const extra_headers[],
+                             int extra_header_count,
+                             int stream_id,
+                             int associated_stream_id,
+                             const char* url,
+                             const char* status,
+                             const char* location) {
+  std::string scheme, host, path;
+  ParseUrl(url, &scheme, &host, &path);
+  const char* const headers[] = {
+    "hello",    "bye",
+    ":status",  status,
+    "location", location,
+    ":path",    path.c_str(),
+    ":host",    host.c_str(),
+    ":scheme",  scheme.c_str(),
+    ":version", "HTTP/1.1"
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   false,
+                                   stream_id,
+                                   LOWEST,
+                                   SYN_STREAM,
+                                   CONTROL_FLAG_NONE,
+                                   headers,
+                                   arraysize(headers),
+                                   associated_stream_id);
+}
+
+SpdyFrame* ConstructSpdyPushHeaders(int stream_id,
+                                    const char* const extra_headers[],
+                                    int extra_header_count) {
+  const char* const kStandardGetHeaders[] = {
+    ":status", "200 OK",
+    ":version", "HTTP/1.1"
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   false,
+                                   stream_id,
+                                   LOWEST,
+                                   HEADERS,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardGetHeaders,
+                                   arraysize(kStandardGetHeaders));
+}
+
+// Constructs a standard SPDY SYN_REPLY packet with the specified status code.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdySynReplyError(const char* const status,
+                                      const char* const* const extra_headers,
+                                      int extra_header_count,
+                                      int stream_id) {
+  const char* const kStandardGetHeaders[] = {
+    "hello", "bye",
+    ":status",  status,
+    ":version", "HTTP/1.1"
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   false,
+                                   stream_id,
+                                   LOWEST,
+                                   SYN_REPLY,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardGetHeaders,
+                                   arraysize(kStandardGetHeaders));
+}
+
+// Constructs a standard SPDY SYN_REPLY packet to match the SPDY GET.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGetSynReplyRedirect(int stream_id) {
+  static const char* const kExtraHeaders[] = {
+    "location", "http://www.foo.com/index.php",
+  };
+  return ConstructSpdySynReplyError("301 Moved Permanently", kExtraHeaders,
+                                    arraysize(kExtraHeaders)/2, stream_id);
+}
+
+// Constructs a standard SPDY SYN_REPLY packet with an Internal Server
+// Error status code.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdySynReplyError(int stream_id) {
+  return ConstructSpdySynReplyError("500 Internal Server Error", NULL, 0, 1);
+}
+
+
+
+
+// Constructs a standard SPDY SYN_REPLY packet to match the SPDY GET.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGetSynReply(const char* const extra_headers[],
+                                    int extra_header_count,
+                                    int stream_id) {
+  static const char* const kStandardGetHeaders[] = {
+    "hello", "bye",
+    ":status", "200",
+    ":version", "HTTP/1.1"
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   false,
+                                   stream_id,
+                                   LOWEST,
+                                   SYN_REPLY,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardGetHeaders,
+                                   arraysize(kStandardGetHeaders));
+}
+
+// Constructs a standard SPDY POST SYN packet.
+// |content_length| is the size of post data.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPost(int64 content_length,
+                             const char* const extra_headers[],
+                             int extra_header_count) {
+  std::string length_str = base::Int64ToString(content_length);
+  const char* post_headers[] = {
+    ":method", "POST",
+    ":path", "/",
+    ":host", "www.google.com",
+    ":scheme", "http",
+    ":version", "HTTP/1.1",
+    "content-length",  length_str.c_str()
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   false,
+                                   1,
+                                   LOWEST,
+                                   SYN_STREAM,
+                                   CONTROL_FLAG_NONE,
+                                   post_headers,
+                                   arraysize(post_headers));
+}
+
+// Constructs a chunked transfer SPDY POST SYN packet.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructChunkedSpdyPost(const char* const extra_headers[],
+                                    int extra_header_count) {
+  const char* post_headers[] = {
+    ":method", "POST",
+    ":path", "/",
+    ":host", "www.google.com",
+    ":scheme", "http",
+    ":version", "HTTP/1.1"
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   false,
+                                   1,
+                                   LOWEST,
+                                   SYN_STREAM,
+                                   CONTROL_FLAG_NONE,
+                                   post_headers,
+                                   arraysize(post_headers));
+}
+
+// Constructs a standard SPDY SYN_REPLY packet to match the SPDY POST.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPostSynReply(const char* const extra_headers[],
+                                     int extra_header_count) {
+  static const char* const kStandardGetHeaders[] = {
+    "hello", "bye",
+    ":status", "200",
+    "url", "/index.php",
+    ":version", "HTTP/1.1"
+  };
+  return ConstructSpdyControlFrame(extra_headers,
+                                   extra_header_count,
+                                   false,
+                                   1,
+                                   LOWEST,
+                                   SYN_REPLY,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardGetHeaders,
+                                   arraysize(kStandardGetHeaders));
+}
+
+// Constructs a single SPDY data frame with the default contents.
+SpdyFrame* ConstructSpdyBodyFrame(int stream_id, bool fin) {
+  BufferedSpdyFramer framer(3, false);
+  return framer.CreateDataFrame(
+      stream_id, kUploadData, kUploadDataSize,
+      fin ? DATA_FLAG_FIN : DATA_FLAG_NONE);
+}
+
+// Constructs a single SPDY data frame with the given content.
+SpdyFrame* ConstructSpdyBodyFrame(int stream_id, const char* data,
+                                  uint32 len, bool fin) {
+  BufferedSpdyFramer framer(3, false);
+  return framer.CreateDataFrame(
+      stream_id, data, len, fin ? DATA_FLAG_FIN : DATA_FLAG_NONE);
+}
+
+// Wraps |frame| in the payload of a data frame in stream |stream_id|.
+SpdyFrame* ConstructWrappedSpdyFrame(const scoped_ptr<SpdyFrame>& frame,
+                                     int stream_id) {
+  return ConstructSpdyBodyFrame(stream_id, frame->data(),
+                                frame->length() + SpdyFrame::kHeaderSize,
+                                false);
+}
+
+// Construct an expected SPDY reply string.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// |buffer| is the buffer we're filling in.
+// Returns the number of bytes written into |buffer|.
+int ConstructSpdyReplyString(const char* const extra_headers[],
+                             int extra_header_count,
+                             char* buffer,
+                             int buffer_length) {
+  int packet_size = 0;
+  char* buffer_write = buffer;
+  int buffer_left = buffer_length;
+  SpdyHeaderBlock headers;
+  if (!buffer || !buffer_length)
+    return 0;
+  // Copy in the extra headers.
+  AppendHeadersToSpdyFrame(extra_headers, extra_header_count, &headers);
+  // The iterator gets us the list of header/value pairs in sorted order.
+  SpdyHeaderBlock::iterator next = headers.begin();
+  SpdyHeaderBlock::iterator last = headers.end();
+  for ( ; next != last; ++next) {
+    // Write the header.
+    int value_len, current_len, offset;
+    const char* header_string = next->first.c_str();
+    if (header_string && header_string[0] == ':')
+      header_string++;
+    packet_size += AppendToBuffer(header_string,
+                                  strlen(header_string),
+                                  &buffer_write,
+                                  &buffer_left);
+    packet_size += AppendToBuffer(": ",
+                                  strlen(": "),
+                                  &buffer_write,
+                                  &buffer_left);
+    // Write the value(s).
+    const char* value_string = next->second.c_str();
+    // Check if it's split among two or more values.
+    value_len = next->second.length();
+    current_len = strlen(value_string);
+    offset = 0;
+    // Handle the first N-1 values.
+    while (current_len < value_len) {
+      // Finish this line -- write the current value.
+      packet_size += AppendToBuffer(value_string + offset,
+                                    current_len - offset,
+                                    &buffer_write,
+                                    &buffer_left);
+      packet_size += AppendToBuffer("\n",
+                                    strlen("\n"),
+                                    &buffer_write,
+                                    &buffer_left);
+      // Advance to next value.
+      offset = current_len + 1;
+      current_len += 1 + strlen(value_string + offset);
+      // Start another line -- add the header again.
+      packet_size += AppendToBuffer(header_string,
+                                    next->first.length(),
+                                    &buffer_write,
+                                    &buffer_left);
+      packet_size += AppendToBuffer(": ",
+                                    strlen(": "),
+                                    &buffer_write,
+                                    &buffer_left);
+    }
+    EXPECT_EQ(value_len, current_len);
+    // Copy the last (or only) value.
+    packet_size += AppendToBuffer(value_string + offset,
+                                  value_len - offset,
+                                  &buffer_write,
+                                  &buffer_left);
+    packet_size += AppendToBuffer("\n",
+                                  strlen("\n"),
+                                  &buffer_write,
+                                  &buffer_left);
+  }
+  return packet_size;
+}
+
+// Create a MockWrite from the given SpdyFrame.
+MockWrite CreateMockWrite(const SpdyFrame& req) {
+  return MockWrite(
+      ASYNC, req.data(), req.length() + SpdyFrame::kHeaderSize);
+}
+
+// Create a MockWrite from the given SpdyFrame and sequence number.
+MockWrite CreateMockWrite(const SpdyFrame& req, int seq) {
+  return CreateMockWrite(req, seq, ASYNC);
+}
+
+// Create a MockWrite from the given SpdyFrame and sequence number.
+MockWrite CreateMockWrite(const SpdyFrame& req, int seq, IoMode mode) {
+  return MockWrite(
+      mode, req.data(), req.length() + SpdyFrame::kHeaderSize, seq);
+}
+
+// Create a MockRead from the given SpdyFrame.
+MockRead CreateMockRead(const SpdyFrame& resp) {
+  return MockRead(
+      ASYNC, resp.data(), resp.length() + SpdyFrame::kHeaderSize);
+}
+
+// Create a MockRead from the given SpdyFrame and sequence number.
+MockRead CreateMockRead(const SpdyFrame& resp, int seq) {
+  return CreateMockRead(resp, seq, ASYNC);
+}
+
+// Create a MockRead from the given SpdyFrame and sequence number.
+MockRead CreateMockRead(const SpdyFrame& resp, int seq, IoMode mode) {
+  return MockRead(
+      mode, resp.data(), resp.length() + SpdyFrame::kHeaderSize, seq);
+}
+
+// Combines the given SpdyFrames into the given char array and returns
+// the total length.
+int CombineFrames(const SpdyFrame** frames, int num_frames,
+                  char* buff, int buff_len) {
+  int total_len = 0;
+  for (int i = 0; i < num_frames; ++i) {
+    total_len += frames[i]->length() + SpdyFrame::kHeaderSize;
+  }
+  DCHECK_LE(total_len, buff_len);
+  char* ptr = buff;
+  for (int i = 0; i < num_frames; ++i) {
+    int len = frames[i]->length() + SpdyFrame::kHeaderSize;
+    memcpy(ptr, frames[i]->data(), len);
+    ptr += len;
+  }
+  return total_len;
+}
+
+SpdySessionDependencies::SpdySessionDependencies()
+    : host_resolver(new MockCachingHostResolver),
+      cert_verifier(new MockCertVerifier),
+      proxy_service(ProxyService::CreateDirect()),
+      ssl_config_service(new SSLConfigServiceDefaults),
+      socket_factory(new MockClientSocketFactory),
+      deterministic_socket_factory(new DeterministicMockClientSocketFactory),
+      http_auth_handler_factory(
+          HttpAuthHandlerFactory::CreateDefault(host_resolver.get())),
+      enable_ip_pooling(true),
+      enable_compression(false),
+      enable_ping(false),
+      initial_recv_window_size(kSpdyStreamInitialWindowSize),
+      time_func(&base::TimeTicks::Now),
+      net_log(NULL) {
+  // Note: The CancelledTransaction test does cleanup by running all
+  // tasks in the message loop (RunAllPending).  Unfortunately, that
+  // doesn't clean up tasks on the host resolver thread; and
+  // TCPConnectJob is currently not cancellable.  Using synchronous
+  // lookups allows the test to shutdown cleanly.  Until we have
+  // cancellable TCPConnectJobs, use synchronous lookups.
+  host_resolver->set_synchronous_mode(true);
+}
+
+SpdySessionDependencies::SpdySessionDependencies(ProxyService* proxy_service)
+    : host_resolver(new MockHostResolver),
+      cert_verifier(new MockCertVerifier),
+      proxy_service(proxy_service),
+      ssl_config_service(new SSLConfigServiceDefaults),
+      socket_factory(new MockClientSocketFactory),
+      deterministic_socket_factory(new DeterministicMockClientSocketFactory),
+      http_auth_handler_factory(
+          HttpAuthHandlerFactory::CreateDefault(host_resolver.get())),
+      enable_ip_pooling(true),
+      enable_compression(false),
+      enable_ping(false),
+      initial_recv_window_size(kSpdyStreamInitialWindowSize),
+      time_func(&base::TimeTicks::Now),
+      net_log(NULL) {}
+
+SpdySessionDependencies::~SpdySessionDependencies() {}
+
+// static
+HttpNetworkSession* SpdySessionDependencies::SpdyCreateSession(
+    SpdySessionDependencies* session_deps) {
+  net::HttpNetworkSession::Params params = CreateSessionParams(session_deps);
+  params.client_socket_factory = session_deps->socket_factory.get();
+  HttpNetworkSession* http_session = new HttpNetworkSession(params);
+  SpdySessionPoolPeer pool_peer(http_session->spdy_session_pool());
+  pool_peer.EnableSendingInitialSettings(false);
+  return http_session;
+}
+
+// static
+HttpNetworkSession* SpdySessionDependencies::SpdyCreateSessionDeterministic(
+    SpdySessionDependencies* session_deps) {
+  net::HttpNetworkSession::Params params = CreateSessionParams(session_deps);
+  params.client_socket_factory =
+      session_deps->deterministic_socket_factory.get();
+  HttpNetworkSession* http_session = new HttpNetworkSession(params);
+  SpdySessionPoolPeer pool_peer(http_session->spdy_session_pool());
+  pool_peer.EnableSendingInitialSettings(false);
+  return http_session;
+}
+
+// static
+net::HttpNetworkSession::Params SpdySessionDependencies::CreateSessionParams(
+    SpdySessionDependencies* session_deps) {
+  net::HttpNetworkSession::Params params;
+  params.host_resolver = session_deps->host_resolver.get();
+  params.cert_verifier = session_deps->cert_verifier.get();
+  params.proxy_service = session_deps->proxy_service.get();
+  params.ssl_config_service = session_deps->ssl_config_service;
+  params.http_auth_handler_factory =
+      session_deps->http_auth_handler_factory.get();
+  params.http_server_properties = &session_deps->http_server_properties;
+  params.enable_spdy_compression = session_deps->enable_compression;
+  params.enable_spdy_ping_based_connection_checking = session_deps->enable_ping;
+  params.spdy_default_protocol = kProtoSPDY3;
+  params.spdy_initial_recv_window_size = session_deps->initial_recv_window_size;
+  params.time_func = session_deps->time_func;
+  params.trusted_spdy_proxy = session_deps->trusted_spdy_proxy;
+  params.net_log = session_deps->net_log;
+  return params;
+}
+
+SpdyURLRequestContext::SpdyURLRequestContext()
+    : ALLOW_THIS_IN_INITIALIZER_LIST(storage_(this)) {
+  storage_.set_host_resolver(scoped_ptr<HostResolver>(new MockHostResolver));
+  storage_.set_cert_verifier(new MockCertVerifier);
+  storage_.set_proxy_service(ProxyService::CreateDirect());
+  storage_.set_ssl_config_service(new SSLConfigServiceDefaults);
+  storage_.set_http_auth_handler_factory(HttpAuthHandlerFactory::CreateDefault(
+      host_resolver()));
+  storage_.set_http_server_properties(new HttpServerPropertiesImpl);
+  net::HttpNetworkSession::Params params;
+  params.client_socket_factory = &socket_factory_;
+  params.host_resolver = host_resolver();
+  params.cert_verifier = cert_verifier();
+  params.proxy_service = proxy_service();
+  params.ssl_config_service = ssl_config_service();
+  params.http_auth_handler_factory = http_auth_handler_factory();
+  params.network_delegate = network_delegate();
+  params.enable_spdy_compression = false;
+  params.enable_spdy_ping_based_connection_checking = false;
+  params.spdy_default_protocol = kProtoSPDY3;
+  params.http_server_properties = http_server_properties();
+  scoped_refptr<HttpNetworkSession> network_session(
+      new HttpNetworkSession(params));
+  SpdySessionPoolPeer pool_peer(network_session->spdy_session_pool());
+  pool_peer.EnableSendingInitialSettings(false);
+  storage_.set_http_transaction_factory(new HttpCache(
+      network_session,
+      HttpCache::DefaultBackend::InMemory(0)));
+}
+
+SpdyURLRequestContext::~SpdyURLRequestContext() {
+}
+
+const SpdyHeaderInfo MakeSpdyHeader(SpdyControlType type) {
+  const SpdyHeaderInfo kHeader = {
+    type,                         // Kind = Syn
+    1,                            // Stream ID
+    0,                            // Associated stream ID
+    ConvertRequestPriorityToSpdyPriority(LOWEST, 3),  // Priority
+    0,                            // Credential Slot
+    CONTROL_FLAG_FIN,       // Control Flags
+    false,                        // Compressed
+    INVALID,                // Status
+    NULL,                         // Data
+    0,                            // Length
+    DATA_FLAG_NONE          // Data Flags
+  };
+  return kHeader;
+}
+
+}  // namespace test_spdy3
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_test_util_spdy3.h b/src/net/spdy/spdy_test_util_spdy3.h
new file mode 100644
index 0000000..5103c89
--- /dev/null
+++ b/src/net/spdy/spdy_test_util_spdy3.h
@@ -0,0 +1,466 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_SPDY_SPDY_TEST_UTIL_H_
+#define NET_SPDY_SPDY_TEST_UTIL_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "crypto/ec_private_key.h"
+#include "crypto/ec_signature_creator.h"
+#include "net/base/cert_verifier.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/mock_host_resolver.h"
+#include "net/base/request_priority.h"
+#include "net/base/ssl_config_service_defaults.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_network_layer.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/proxy/proxy_service.h"
+#include "net/socket/socket_test_util.h"
+#include "net/spdy/spdy_session.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_storage.h"
+
+namespace crypto {
+class ECSignatureCreatorFactory;
+}
+
+namespace net {
+
+namespace test_spdy3 {
+
+// Default upload data used by both, mock objects and framer when creating
+// data frames.
+const char kDefaultURL[] = "http://www.google.com";
+const char kUploadData[] = "hello!";
+const int kUploadDataSize = arraysize(kUploadData)-1;
+
+// NOTE: In GCC, on a Mac, this can't be in an anonymous namespace!
+// This struct holds information used to construct spdy control and data frames.
+struct SpdyHeaderInfo {
+  SpdyControlType kind;
+  SpdyStreamId id;
+  SpdyStreamId assoc_id;
+  SpdyPriority priority;
+  size_t credential_slot;
+  SpdyControlFlags control_flags;
+  bool compressed;
+  SpdyStatusCodes status;
+  const char* data;
+  uint32 data_length;
+  SpdyDataFlags data_flags;
+};
+
+// An ECSignatureCreator that returns deterministic signatures.
+class MockECSignatureCreator : public crypto::ECSignatureCreator {
+ public:
+  explicit MockECSignatureCreator(crypto::ECPrivateKey* key);
+
+  // crypto::ECSignatureCreator
+  virtual bool Sign(const uint8* data,
+                    int data_len,
+                    std::vector<uint8>* signature) OVERRIDE;
+  virtual bool DecodeSignature(const std::vector<uint8>& signature,
+                               std::vector<uint8>* out_raw_sig) OVERRIDE;
+
+ private:
+  crypto::ECPrivateKey* key_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockECSignatureCreator);
+};
+
+// An ECSignatureCreatorFactory creates MockECSignatureCreator.
+class MockECSignatureCreatorFactory : public crypto::ECSignatureCreatorFactory {
+ public:
+  MockECSignatureCreatorFactory();
+  virtual ~MockECSignatureCreatorFactory();
+
+  // crypto::ECSignatureCreatorFactory
+  virtual crypto::ECSignatureCreator* Create(
+      crypto::ECPrivateKey* key) OVERRIDE;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockECSignatureCreatorFactory);
+};
+
+// Chop a frame into an array of MockWrites.
+// |data| is the frame to chop.
+// |length| is the length of the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockWrite* ChopWriteFrame(const char* data, int length, int num_chunks);
+
+// Chop a SpdyFrame into an array of MockWrites.
+// |frame| is the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockWrite* ChopWriteFrame(const SpdyFrame& frame, int num_chunks);
+
+// Chop a frame into an array of MockReads.
+// |data| is the frame to chop.
+// |length| is the length of the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockRead* ChopReadFrame(const char* data, int length, int num_chunks);
+
+// Chop a SpdyFrame into an array of MockReads.
+// |frame| is the frame to chop.
+// |num_chunks| is the number of chunks to create.
+MockRead* ChopReadFrame(const SpdyFrame& frame, int num_chunks);
+
+// Adds headers and values to a map.
+// |extra_headers| is an array of { name, value } pairs, arranged as strings
+// where the even entries are the header names, and the odd entries are the
+// header values.
+// |headers| gets filled in from |extra_headers|.
+void AppendHeadersToSpdyFrame(const char* const extra_headers[],
+                              int extra_header_count,
+                              SpdyHeaderBlock* headers);
+
+// Writes |str| of the given |len| to the buffer pointed to by |buffer_handle|.
+// Uses a template so buffer_handle can be a char* or an unsigned char*.
+// Updates the |*buffer_handle| pointer by |len|
+// Returns the number of bytes written into *|buffer_handle|
+template<class T>
+int AppendToBuffer(const char* str,
+                   int len,
+                   T** buffer_handle,
+                   int* buffer_len_remaining) {
+  DCHECK_GT(len, 0);
+  DCHECK(NULL != buffer_handle) << "NULL buffer handle";
+  DCHECK(NULL != *buffer_handle) << "NULL pointer";
+  DCHECK(NULL != buffer_len_remaining)
+      << "NULL buffer remainder length pointer";
+  DCHECK_GE(*buffer_len_remaining, len) << "Insufficient buffer size";
+  memcpy(*buffer_handle, str, len);
+  *buffer_handle += len;
+  *buffer_len_remaining -= len;
+  return len;
+}
+
+// Writes |val| to a location of size |len|, in big-endian format.
+// in the buffer pointed to by |buffer_handle|.
+// Updates the |*buffer_handle| pointer by |len|
+// Returns the number of bytes written
+int AppendToBuffer(int val,
+                   int len,
+                   unsigned char** buffer_handle,
+                   int* buffer_len_remaining);
+
+// Construct a SPDY packet.
+// |head| is the start of the packet, up to but not including
+// the header value pairs.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// |tail| is any (relatively constant) header-value pairs to add.
+// |buffer| is the buffer we're filling in.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPacket(const SpdyHeaderInfo& header_info,
+                               const char* const extra_headers[],
+                               int extra_header_count,
+                               const char* const tail[],
+                               int tail_header_count);
+
+// Construct a generic SpdyControlFrame.
+SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[],
+                                     int extra_header_count,
+                                     bool compressed,
+                                     int stream_id,
+                                     RequestPriority request_priority,
+                                     SpdyControlType type,
+                                     SpdyControlFlags flags,
+                                     const char* const* kHeaders,
+                                     int kHeadersSize);
+SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[],
+                                     int extra_header_count,
+                                     bool compressed,
+                                     int stream_id,
+                                     RequestPriority request_priority,
+                                     SpdyControlType type,
+                                     SpdyControlFlags flags,
+                                     const char* const* kHeaders,
+                                     int kHeadersSize,
+                                     int associated_stream_id);
+
+// Construct an expected SPDY reply string.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// |buffer| is the buffer we're filling in.
+// Returns the number of bytes written into |buffer|.
+int ConstructSpdyReplyString(const char* const extra_headers[],
+                             int extra_header_count,
+                             char* buffer,
+                             int buffer_length);
+
+// Construct an expected SPDY SETTINGS frame.
+// |settings| are the settings to set.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdySettings(const SettingsMap& settings);
+
+// Construct an expected SPDY CREDENTIAL frame.
+// |credential| is the credential to send.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyCredential(const SpdyCredential& credential);
+
+// Construct a SPDY PING frame.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyPing(uint32 ping_id);
+
+// Construct a SPDY GOAWAY frame.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyGoAway();
+
+// Construct a SPDY WINDOW_UPDATE frame.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyWindowUpdate(SpdyStreamId, uint32 delta_window_size);
+
+// Construct a SPDY RST_STREAM frame.
+// Returns the constructed frame.  The caller takes ownership of the frame.
+SpdyFrame* ConstructSpdyRstStream(SpdyStreamId stream_id,
+                                  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.
+// |index| is the index of the header we want.
+// Returns the number of bytes written into |buffer|.
+int ConstructSpdyHeader(const char* const extra_headers[],
+                        int extra_header_count,
+                        char* buffer,
+                        int buffer_length,
+                        int index);
+
+// Constructs a standard SPDY GET SYN packet, optionally compressed
+// for the url |url|.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGet(const char* const url,
+                            bool compressed,
+                            int stream_id,
+                            RequestPriority request_priority);
+
+// Constructs a standard SPDY GET SYN packet, optionally compressed.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGet(const char* const extra_headers[],
+                            int extra_header_count,
+                            bool compressed,
+                            int stream_id,
+                            RequestPriority request_priority);
+
+// Constructs a standard SPDY GET SYN packet, optionally compressed.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.  If |direct| is false, the
+// the full url will be used instead of simply the path.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGet(const char* const extra_headers[],
+                            int extra_header_count,
+                            bool compressed,
+                            int stream_id,
+                            RequestPriority request_priority,
+                            bool direct);
+
+// Constructs a standard SPDY SYN_STREAM frame for a CONNECT request.
+SpdyFrame* ConstructSpdyConnect(const char* const extra_headers[],
+                                int extra_header_count,
+                                int stream_id);
+
+// Constructs a standard SPDY push SYN packet.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPush(const char* const extra_headers[],
+                             int extra_header_count,
+                             int stream_id,
+                             int associated_stream_id);
+SpdyFrame* ConstructSpdyPush(const char* const extra_headers[],
+                             int extra_header_count,
+                             int stream_id,
+                             int associated_stream_id,
+                             const char* url);
+SpdyFrame* ConstructSpdyPush(const char* const extra_headers[],
+                             int extra_header_count,
+                             int stream_id,
+                             int associated_stream_id,
+                             const char* url,
+                             const char* status,
+                             const char* location);
+SpdyFrame* ConstructSpdyPush(int stream_id,
+                             int associated_stream_id,
+                             const char* url);
+
+SpdyFrame* ConstructSpdyPushHeaders(int stream_id,
+                                    const char* const extra_headers[],
+                                    int extra_header_count);
+
+// Constructs a standard SPDY SYN_REPLY packet to match the SPDY GET.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGetSynReply(const char* const extra_headers[],
+                                    int extra_header_count,
+                                    int stream_id);
+
+// Constructs a standard SPDY SYN_REPLY packet to match the SPDY GET.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyGetSynReplyRedirect(int stream_id);
+
+// Constructs a standard SPDY SYN_REPLY packet with an Internal Server
+// Error status code.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdySynReplyError(int stream_id);
+
+// Constructs a standard SPDY SYN_REPLY packet with the specified status code.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdySynReplyError(const char* const status,
+                                      const char* const* const extra_headers,
+                                      int extra_header_count,
+                                      int stream_id);
+
+// Constructs a standard SPDY POST SYN packet.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPost(int64 content_length,
+                             const char* const extra_headers[],
+                             int extra_header_count);
+
+// Constructs a chunked transfer SPDY POST SYN packet.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructChunkedSpdyPost(const char* const extra_headers[],
+                                    int extra_header_count);
+
+// Constructs a standard SPDY SYN_REPLY packet to match the SPDY POST.
+// |extra_headers| are the extra header-value pairs, which typically
+// will vary the most between calls.
+// Returns a SpdyFrame.
+SpdyFrame* ConstructSpdyPostSynReply(const char* const extra_headers[],
+                                     int extra_header_count);
+
+// Constructs a single SPDY data frame with the contents "hello!"
+SpdyFrame* ConstructSpdyBodyFrame(int stream_id,
+                                  bool fin);
+
+// Constructs a single SPDY data frame with the given content.
+SpdyFrame* ConstructSpdyBodyFrame(int stream_id, const char* data,
+                                  uint32 len, bool fin);
+
+// Wraps |frame| in the payload of a data frame in stream |stream_id|.
+SpdyFrame* ConstructWrappedSpdyFrame(const scoped_ptr<SpdyFrame>& frame,
+                                     int stream_id);
+
+// Create an async MockWrite from the given SpdyFrame.
+MockWrite CreateMockWrite(const SpdyFrame& req);
+
+// Create an async MockWrite from the given SpdyFrame and sequence number.
+MockWrite CreateMockWrite(const SpdyFrame& req, int seq);
+
+MockWrite CreateMockWrite(const SpdyFrame& req, int seq, IoMode mode);
+
+// Create a MockRead from the given SpdyFrame.
+MockRead CreateMockRead(const SpdyFrame& resp);
+
+// Create a MockRead from the given SpdyFrame and sequence number.
+MockRead CreateMockRead(const SpdyFrame& resp, int seq);
+
+MockRead CreateMockRead(const SpdyFrame& resp, int seq, IoMode mode);
+
+// Combines the given SpdyFrames into the given char array and returns
+// the total length.
+int CombineFrames(const SpdyFrame** frames, int num_frames,
+                  char* buff, int buff_len);
+
+// Helper to manage the lifetimes of the dependencies for a
+// HttpNetworkTransaction.
+struct SpdySessionDependencies {
+  // Default set of dependencies -- "null" proxy service.
+  SpdySessionDependencies();
+
+  // Custom proxy service dependency.
+  explicit SpdySessionDependencies(ProxyService* proxy_service);
+
+  ~SpdySessionDependencies();
+
+  static HttpNetworkSession* SpdyCreateSession(
+      SpdySessionDependencies* session_deps);
+  static HttpNetworkSession* SpdyCreateSessionDeterministic(
+      SpdySessionDependencies* session_deps);
+  static HttpNetworkSession::Params CreateSessionParams(
+      SpdySessionDependencies* session_deps);
+
+  // NOTE: host_resolver must be ordered before http_auth_handler_factory.
+  scoped_ptr<MockHostResolverBase> host_resolver;
+  scoped_ptr<CertVerifier> cert_verifier;
+  scoped_ptr<ProxyService> proxy_service;
+  scoped_refptr<SSLConfigService> ssl_config_service;
+  scoped_ptr<MockClientSocketFactory> socket_factory;
+  scoped_ptr<DeterministicMockClientSocketFactory> deterministic_socket_factory;
+  scoped_ptr<HttpAuthHandlerFactory> http_auth_handler_factory;
+  HttpServerPropertiesImpl http_server_properties;
+  bool enable_ip_pooling;
+  bool enable_compression;
+  bool enable_ping;
+  size_t initial_recv_window_size;
+  SpdySession::TimeFunc time_func;
+  std::string trusted_spdy_proxy;
+  NetLog* net_log;
+};
+
+class SpdyURLRequestContext : public URLRequestContext {
+ public:
+  SpdyURLRequestContext();
+  virtual ~SpdyURLRequestContext();
+
+  MockClientSocketFactory& socket_factory() { return socket_factory_; }
+
+ private:
+  MockClientSocketFactory socket_factory_;
+  net::URLRequestContextStorage storage_;
+};
+
+const SpdyHeaderInfo MakeSpdyHeader(SpdyControlType type);
+
+class SpdySessionPoolPeer {
+ public:
+  explicit SpdySessionPoolPeer(SpdySessionPool* pool)
+      : pool_(pool) {}
+
+  void AddAlias(const IPEndPoint& address, const HostPortProxyPair& pair) {
+    pool_->AddAlias(address, pair);
+  }
+
+  void RemoveAliases(const HostPortProxyPair& pair) {
+    pool_->RemoveAliases(pair);
+  }
+
+  void RemoveSpdySession(const scoped_refptr<SpdySession>& session) {
+    pool_->Remove(session);
+  }
+
+  void DisableDomainAuthenticationVerification() {
+    pool_->verify_domain_authentication_ = false;
+  }
+
+  void EnableSendingInitialSettings(bool enabled) {
+    pool_->enable_sending_initial_settings_ = enabled;
+  }
+
+ private:
+  SpdySessionPool* const pool_;
+
+  DISALLOW_COPY_AND_ASSIGN(SpdySessionPoolPeer);
+};
+
+}  // namespace test_spdy3
+
+}  // namespace net
+
+#endif  // NET_SPDY_SPDY_TEST_UTIL_H_
diff --git a/src/net/spdy/spdy_websocket_stream.cc b/src/net/spdy/spdy_websocket_stream.cc
new file mode 100644
index 0000000..6323b71
--- /dev/null
+++ b/src/net/spdy/spdy_websocket_stream.cc
@@ -0,0 +1,147 @@
+// Copyright (c) 2012 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_websocket_stream.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/net_errors.h"
+#include "net/base/io_buffer.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_stream.h"
+
+namespace net {
+
+SpdyWebSocketStream::SpdyWebSocketStream(
+    SpdySession* spdy_session, Delegate* delegate)
+    : stream_(NULL),
+      spdy_session_(spdy_session),
+      delegate_(delegate) {
+  DCHECK(spdy_session_);
+  DCHECK(delegate_);
+}
+
+SpdyWebSocketStream::~SpdyWebSocketStream() {
+  if (stream_) {
+    // If Close() has not already been called, DetachDelegate() will send a
+    // SPDY RST_STREAM. Deleting SpdyWebSocketStream is good enough to initiate
+    // graceful shutdown, so we call Close() to avoid sending a RST_STREAM.
+    // For safe, we should eliminate |delegate_| for OnClose() calback.
+    delegate_ = NULL;
+    stream_->Close();
+  }
+}
+
+int SpdyWebSocketStream::InitializeStream(const GURL& url,
+                                          RequestPriority request_priority,
+                                          const BoundNetLog& net_log) {
+  if (spdy_session_->IsClosed())
+    return ERR_SOCKET_NOT_CONNECTED;
+
+  int result = spdy_session_->CreateStream(
+      url, request_priority, &stream_, net_log,
+      base::Bind(&SpdyWebSocketStream::OnSpdyStreamCreated,
+                 base::Unretained(this)));
+
+  if (result == OK) {
+    DCHECK(stream_);
+    stream_->SetDelegate(this);
+  }
+  return result;
+}
+
+int SpdyWebSocketStream::SendRequest(scoped_ptr<SpdyHeaderBlock> headers) {
+  if (!stream_) {
+    NOTREACHED();
+    return ERR_UNEXPECTED;
+  }
+  stream_->set_spdy_headers(headers.Pass());
+  int result = stream_->SendRequest(true);
+  if (result < OK && result != ERR_IO_PENDING)
+    Close();
+  return result;
+}
+
+int SpdyWebSocketStream::SendData(const char* data, int length) {
+  if (!stream_) {
+    NOTREACHED();
+    return ERR_UNEXPECTED;
+  }
+  scoped_refptr<IOBuffer> buf(new IOBuffer(length));
+  memcpy(buf->data(), data, length);
+  return stream_->WriteStreamData(buf.get(), length, DATA_FLAG_NONE);
+}
+
+void SpdyWebSocketStream::Close() {
+  if (spdy_session_)
+    spdy_session_->CancelPendingCreateStreams(&stream_);
+  if (stream_)
+    stream_->Close();
+}
+
+bool SpdyWebSocketStream::OnSendHeadersComplete(int status) {
+  DCHECK(delegate_);
+  delegate_->OnSentSpdyHeaders(status);
+  return true;
+}
+
+int SpdyWebSocketStream::OnSendBody() {
+  NOTREACHED();
+  return ERR_UNEXPECTED;
+}
+
+int SpdyWebSocketStream::OnSendBodyComplete(int status, bool* eof) {
+  NOTREACHED();
+  *eof = true;
+  return ERR_UNEXPECTED;
+}
+
+int SpdyWebSocketStream::OnResponseReceived(
+    const SpdyHeaderBlock& response,
+    base::Time response_time, int status) {
+  DCHECK(delegate_);
+  return delegate_->OnReceivedSpdyResponseHeader(response, status);
+}
+
+void SpdyWebSocketStream::OnHeadersSent() {
+  // This will be called when WebSocket over SPDY supports new framing.
+  NOTREACHED();
+}
+
+int SpdyWebSocketStream::OnDataReceived(const char* data, int length) {
+  DCHECK(delegate_);
+  delegate_->OnReceivedSpdyData(data, length);
+  return OK;
+}
+
+void SpdyWebSocketStream::OnDataSent(int length) {
+  DCHECK(delegate_);
+  delegate_->OnSentSpdyData(length);
+}
+
+void SpdyWebSocketStream::OnClose(int status) {
+  stream_ = NULL;
+
+  // Destruction without Close() call OnClose() with delegate_ being NULL.
+  if (!delegate_)
+    return;
+  Delegate* delegate = delegate_;
+  delegate_ = NULL;
+  delegate->OnCloseSpdyStream();
+}
+
+void SpdyWebSocketStream::OnSpdyStreamCreated(int result) {
+  DCHECK_NE(ERR_IO_PENDING, result);
+  if (result == OK) {
+    DCHECK(stream_);
+    stream_->SetDelegate(this);
+  }
+  DCHECK(delegate_);
+  delegate_->OnCreatedSpdyStream(result);
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_websocket_stream.h b/src/net/spdy/spdy_websocket_stream.h
new file mode 100644
index 0000000..5ab4c48
--- /dev/null
+++ b/src/net/spdy/spdy_websocket_stream.h
@@ -0,0 +1,103 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_SPDY_SPDY_WEBSOCKET_STREAM_H_
+#define NET_SPDY_SPDY_WEBSOCKET_STREAM_H_
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time.h"
+#include "net/base/completion_callback.h"
+#include "net/base/request_priority.h"
+#include "net/spdy/spdy_framer.h"
+#include "net/spdy/spdy_header_block.h"
+#include "net/spdy/spdy_stream.h"
+
+namespace net {
+
+// The SpdyWebSocketStream is a WebSocket-specific type of stream known to a
+// SpdySession. WebSocket's opening handshake is converted to SPDY's
+// SYN_STREAM/SYN_REPLY. WebSocket frames are encapsulated as SPDY data frames.
+class NET_EXPORT_PRIVATE SpdyWebSocketStream
+    : public SpdyStream::Delegate {
+ public:
+  // Delegate handles asynchronous events.
+  class NET_EXPORT_PRIVATE Delegate {
+   public:
+    // Called when InitializeStream() finishes asynchronously. This delegate is
+    // called if InitializeStream() returns ERR_IO_PENDING. |status| indicates
+    // network error.
+    virtual void OnCreatedSpdyStream(int status) = 0;
+
+    // Called on corresponding to OnSendHeadersComplete() or SPDY's SYN frame
+    // has been sent.
+    virtual void OnSentSpdyHeaders(int status) = 0;
+
+    // Called on corresponding to OnResponseReceived() or SPDY's SYN_STREAM,
+    // SYN_REPLY, or HEADERS frames are received. This callback may be called
+    // multiple times as SPDY's delegate does.
+    virtual int OnReceivedSpdyResponseHeader(
+        const SpdyHeaderBlock& headers,
+        int status) = 0;
+
+    // Called when data is sent.
+    virtual void OnSentSpdyData(int amount_sent) = 0;
+
+    // Called when data is received.
+    virtual void OnReceivedSpdyData(const char* data, int length) = 0;
+
+    // Called when SpdyStream is closed.
+    virtual void OnCloseSpdyStream() = 0;
+
+   protected:
+    virtual ~Delegate() {}
+  };
+
+  SpdyWebSocketStream(SpdySession* spdy_session, Delegate* delegate);
+  virtual ~SpdyWebSocketStream();
+
+  // Initializes SPDY stream for the WebSocket.
+  // It might create SPDY stream asynchronously.  In this case, this method
+  // returns ERR_IO_PENDING and call OnCreatedSpdyStream delegate with result
+  // after completion. In other cases, delegate does not be called.
+  int InitializeStream(const GURL& url,
+                       RequestPriority request_priority,
+                       const BoundNetLog& stream_net_log);
+
+  int SendRequest(scoped_ptr<SpdyHeaderBlock> headers);
+  int SendData(const char* data, int length);
+  void Close();
+
+  // SpdyStream::Delegate
+  virtual bool OnSendHeadersComplete(int status) OVERRIDE;
+  virtual int OnSendBody() OVERRIDE;
+  virtual int OnSendBodyComplete(int status, bool* eof) OVERRIDE;
+  virtual int OnResponseReceived(const SpdyHeaderBlock& response,
+                                 base::Time response_time,
+                                 int status) OVERRIDE;
+  virtual void OnHeadersSent() OVERRIDE;
+  virtual int OnDataReceived(const char* data, int length) OVERRIDE;
+  virtual void OnDataSent(int length) OVERRIDE;
+  virtual void OnClose(int status) OVERRIDE;
+
+ private:
+  friend class SpdyWebSocketStreamSpdy2Test;
+  friend class SpdyWebSocketStreamSpdy3Test;
+  FRIEND_TEST_ALL_PREFIXES(SpdyWebSocketStreamSpdy2Test, Basic);
+  FRIEND_TEST_ALL_PREFIXES(SpdyWebSocketStreamSpdy3Test, Basic);
+
+  void OnSpdyStreamCreated(int status);
+
+  scoped_refptr<SpdyStream> stream_;
+  scoped_refptr<SpdySession> spdy_session_;
+  Delegate* delegate_;
+
+  DISALLOW_COPY_AND_ASSIGN(SpdyWebSocketStream);
+};
+
+}  // namespace net
+
+#endif  // NET_SPDY_SPDY_WEBSOCKET_STREAM_H_
diff --git a/src/net/spdy/spdy_websocket_stream_spdy2_unittest.cc b/src/net/spdy/spdy_websocket_stream_spdy2_unittest.cc
new file mode 100644
index 0000000..20b7217
--- /dev/null
+++ b/src/net/spdy/spdy_websocket_stream_spdy2_unittest.cc
@@ -0,0 +1,615 @@
+// Copyright (c) 2012 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_websocket_stream.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "net/base/completion_callback.h"
+#include "net/proxy/proxy_server.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_test_util_spdy2.h"
+#include "net/spdy/spdy_websocket_test_util_spdy2.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using namespace net::test_spdy2;
+
+namespace net {
+
+namespace {
+
+struct SpdyWebSocketStreamEvent {
+  enum EventType {
+    EVENT_CREATED,
+    EVENT_SENT_HEADERS,
+    EVENT_RECEIVED_HEADER,
+    EVENT_SENT_DATA,
+    EVENT_RECEIVED_DATA,
+    EVENT_CLOSE,
+  };
+  SpdyWebSocketStreamEvent(EventType type,
+                           const SpdyHeaderBlock& headers,
+                           int result,
+                           const std::string& data)
+      : event_type(type),
+        headers(headers),
+        result(result),
+        data(data) {}
+
+  EventType event_type;
+  SpdyHeaderBlock headers;
+  int result;
+  std::string data;
+};
+
+class SpdyWebSocketStreamEventRecorder : public SpdyWebSocketStream::Delegate {
+ public:
+  explicit SpdyWebSocketStreamEventRecorder(const CompletionCallback& callback)
+      : callback_(callback) {}
+  virtual ~SpdyWebSocketStreamEventRecorder() {}
+
+  typedef base::Callback<void(SpdyWebSocketStreamEvent*)> StreamEventCallback;
+
+  void SetOnCreated(const StreamEventCallback& callback) {
+    on_created_ = callback;
+  }
+  void SetOnSentHeaders(const StreamEventCallback& callback) {
+    on_sent_headers_ = callback;
+  }
+  void SetOnReceivedHeader(const StreamEventCallback& callback) {
+    on_received_header_ = callback;
+  }
+  void SetOnSentData(const StreamEventCallback& callback) {
+    on_sent_data_ = callback;
+  }
+  void SetOnReceivedData(const StreamEventCallback& callback) {
+    on_received_data_ = callback;
+  }
+  void SetOnClose(const StreamEventCallback& callback) {
+    on_close_ = callback;
+  }
+
+  virtual void OnCreatedSpdyStream(int result) {
+    events_.push_back(
+        SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_CREATED,
+                                 SpdyHeaderBlock(),
+                                 result,
+                                 std::string()));
+    if (!on_created_.is_null())
+      on_created_.Run(&events_.back());
+  }
+  virtual void OnSentSpdyHeaders(int result) {
+    events_.push_back(
+        SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS,
+                                 SpdyHeaderBlock(),
+                                 result,
+                                 std::string()));
+    if (!on_sent_data_.is_null())
+      on_sent_data_.Run(&events_.back());
+  }
+  virtual int OnReceivedSpdyResponseHeader(
+      const SpdyHeaderBlock& headers, int status) {
+    events_.push_back(
+        SpdyWebSocketStreamEvent(
+            SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER,
+            headers,
+            status,
+            std::string()));
+    if (!on_received_header_.is_null())
+      on_received_header_.Run(&events_.back());
+    return status;
+  }
+  virtual void OnSentSpdyData(int amount_sent) {
+    events_.push_back(
+        SpdyWebSocketStreamEvent(
+            SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+            SpdyHeaderBlock(),
+            amount_sent,
+            std::string()));
+    if (!on_sent_data_.is_null())
+      on_sent_data_.Run(&events_.back());
+  }
+  virtual void OnReceivedSpdyData(const char* data, int length) {
+    events_.push_back(
+        SpdyWebSocketStreamEvent(
+            SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+            SpdyHeaderBlock(),
+            length,
+            std::string(data, length)));
+    if (!on_received_data_.is_null())
+      on_received_data_.Run(&events_.back());
+  }
+  virtual void OnCloseSpdyStream() {
+    events_.push_back(
+        SpdyWebSocketStreamEvent(
+            SpdyWebSocketStreamEvent::EVENT_CLOSE,
+            SpdyHeaderBlock(),
+            OK,
+            std::string()));
+    if (!on_close_.is_null())
+      on_close_.Run(&events_.back());
+    if (!callback_.is_null())
+      callback_.Run(OK);
+  }
+
+  const std::vector<SpdyWebSocketStreamEvent>& GetSeenEvents() const {
+    return events_;
+  }
+
+ private:
+  std::vector<SpdyWebSocketStreamEvent> events_;
+  StreamEventCallback on_created_;
+  StreamEventCallback on_sent_headers_;
+  StreamEventCallback on_received_header_;
+  StreamEventCallback on_sent_data_;
+  StreamEventCallback on_received_data_;
+  StreamEventCallback on_close_;
+  CompletionCallback callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(SpdyWebSocketStreamEventRecorder);
+};
+
+}  // namespace
+
+class SpdyWebSocketStreamSpdy2Test : public testing::Test {
+ public:
+  OrderedSocketData* data() { return data_.get(); }
+
+  void DoSendHelloFrame(SpdyWebSocketStreamEvent* event) {
+    // Record the actual stream_id.
+    created_stream_id_ = websocket_stream_->stream_->stream_id();
+    websocket_stream_->SendData(kMessageFrame, kMessageFrameLength);
+  }
+
+  void DoSendClosingFrame(SpdyWebSocketStreamEvent* event) {
+    websocket_stream_->SendData(kClosingFrame, kClosingFrameLength);
+  }
+
+  void DoClose(SpdyWebSocketStreamEvent* event) {
+    websocket_stream_->Close();
+  }
+
+  void DoSync(SpdyWebSocketStreamEvent* event) {
+    sync_callback_.callback().Run(OK);
+  }
+
+ protected:
+  SpdyWebSocketStreamSpdy2Test() {}
+  virtual ~SpdyWebSocketStreamSpdy2Test() {}
+
+  virtual void SetUp() {
+    host_port_pair_.set_host("example.com");
+    host_port_pair_.set_port(80);
+    host_port_proxy_pair_.first = host_port_pair_;
+    host_port_proxy_pair_.second = ProxyServer::Direct();
+
+    spdy_settings_id_to_set_ = SETTINGS_MAX_CONCURRENT_STREAMS;
+    spdy_settings_flags_to_set_ = SETTINGS_FLAG_PLEASE_PERSIST;
+    spdy_settings_value_to_set_ = 1;
+
+    spdy_settings_to_send_[spdy_settings_id_to_set_] =
+        SettingsFlagsAndValue(
+            SETTINGS_FLAG_PERSISTED, spdy_settings_value_to_set_);
+  }
+
+  virtual void TearDown() {
+    MessageLoop::current()->RunUntilIdle();
+  }
+
+  void Prepare(SpdyStreamId stream_id) {
+    stream_id_ = stream_id;
+
+    request_frame_.reset(ConstructSpdyWebSocketSynStream(
+        stream_id_,
+        "/echo",
+        "example.com",
+        "http://example.com/wsdemo"));
+
+    response_frame_.reset(ConstructSpdyWebSocketSynReply(stream_id_));
+
+    message_frame_.reset(ConstructSpdyWebSocketDataFrame(
+        kMessageFrame,
+        kMessageFrameLength,
+        stream_id_,
+        false));
+
+    closing_frame_.reset(ConstructSpdyWebSocketDataFrame(
+        kClosingFrame,
+        kClosingFrameLength,
+        stream_id_,
+        false));
+  }
+
+  int InitSession(MockRead* reads, size_t reads_count,
+                  MockWrite* writes, size_t writes_count,
+                  bool throttling) {
+    data_.reset(new OrderedSocketData(reads, reads_count,
+                                      writes, writes_count));
+    session_deps_.socket_factory->AddSocketDataProvider(data_.get());
+    http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+    SpdySessionPool* spdy_session_pool(http_session_->spdy_session_pool());
+
+    if (throttling) {
+      // Set max concurrent streams to 1.
+      spdy_session_pool->http_server_properties()->SetSpdySetting(
+          host_port_pair_,
+          spdy_settings_id_to_set_,
+          spdy_settings_flags_to_set_,
+          spdy_settings_value_to_set_);
+    }
+
+    EXPECT_FALSE(spdy_session_pool->HasSession(host_port_proxy_pair_));
+    session_ = spdy_session_pool->Get(host_port_proxy_pair_, BoundNetLog());
+    EXPECT_TRUE(spdy_session_pool->HasSession(host_port_proxy_pair_));
+    transport_params_ = new TransportSocketParams(host_port_pair_, MEDIUM,
+                                                  false, false,
+                                                  OnHostResolutionCallback());
+    TestCompletionCallback callback;
+    scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+    EXPECT_EQ(ERR_IO_PENDING,
+              connection->Init(host_port_pair_.ToString(), transport_params_,
+                               MEDIUM, callback.callback(),
+                               http_session_->GetTransportSocketPool(
+                                   HttpNetworkSession::NORMAL_SOCKET_POOL),
+                               BoundNetLog()));
+    EXPECT_EQ(OK, callback.WaitForResult());
+    return session_->InitializeWithSocket(connection.release(), false, OK);
+  }
+
+  void SendRequest() {
+    scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+    (*headers)["path"] = "/echo";
+    (*headers)["host"] = "example.com";
+    (*headers)["version"] = "WebSocket/13";
+    (*headers)["scheme"] = "ws";
+    (*headers)["origin"] = "http://example.com/wsdemo";
+
+    websocket_stream_->SendRequest(headers.Pass());
+  }
+
+  SpdySettingsIds spdy_settings_id_to_set_;
+  SpdySettingsFlags spdy_settings_flags_to_set_;
+  uint32 spdy_settings_value_to_set_;
+  SettingsMap spdy_settings_to_send_;
+  SpdySessionDependencies session_deps_;
+  scoped_ptr<OrderedSocketData> data_;
+  scoped_refptr<HttpNetworkSession> http_session_;
+  scoped_refptr<SpdySession> session_;
+  scoped_refptr<TransportSocketParams> transport_params_;
+  scoped_ptr<SpdyWebSocketStream> websocket_stream_;
+  SpdyStreamId stream_id_;
+  SpdyStreamId created_stream_id_;
+  scoped_ptr<SpdyFrame> request_frame_;
+  scoped_ptr<SpdyFrame> response_frame_;
+  scoped_ptr<SpdyFrame> message_frame_;
+  scoped_ptr<SpdyFrame> closing_frame_;
+  HostPortPair host_port_pair_;
+  HostPortProxyPair host_port_proxy_pair_;
+  TestCompletionCallback completion_callback_;
+  TestCompletionCallback sync_callback_;
+
+  static const char kMessageFrame[];
+  static const char kClosingFrame[];
+  static const size_t kMessageFrameLength;
+  static const size_t kClosingFrameLength;
+};
+
+// TODO(toyoshim): Replace old framing data to new one, then use HEADERS and
+// data frames.
+const char SpdyWebSocketStreamSpdy2Test::kMessageFrame[] = "\x81\x05hello";
+const char SpdyWebSocketStreamSpdy2Test::kClosingFrame[] = "\x88\0";
+const size_t SpdyWebSocketStreamSpdy2Test::kMessageFrameLength =
+    arraysize(SpdyWebSocketStreamSpdy2Test::kMessageFrame) - 1;
+const size_t SpdyWebSocketStreamSpdy2Test::kClosingFrameLength =
+    arraysize(SpdyWebSocketStreamSpdy2Test::kClosingFrame) - 1;
+
+TEST_F(SpdyWebSocketStreamSpdy2Test, Basic) {
+  Prepare(1);
+  MockWrite writes[] = {
+    CreateMockWrite(*request_frame_.get(), 1),
+    CreateMockWrite(*message_frame_.get(), 3),
+    CreateMockWrite(*closing_frame_.get(), 5)
+  };
+
+  MockRead reads[] = {
+    CreateMockRead(*response_frame_.get(), 2),
+    CreateMockRead(*message_frame_.get(), 4),
+    // Skip sequence 6 to notify closing has been sent.
+    CreateMockRead(*closing_frame_.get(), 7),
+    MockRead(SYNCHRONOUS, 0, 8)  // EOF cause OnCloseSpdyStream event.
+  };
+
+  EXPECT_EQ(OK, InitSession(reads, arraysize(reads),
+                            writes, arraysize(writes), false));
+
+  SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback());
+  delegate.SetOnReceivedHeader(
+      base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendHelloFrame,
+                 base::Unretained(this)));
+  delegate.SetOnReceivedData(
+      base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendClosingFrame,
+                 base::Unretained(this)));
+
+  websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate));
+
+  BoundNetLog net_log;
+  GURL url("ws://example.com/echo");
+  ASSERT_EQ(OK, websocket_stream_->InitializeStream(url, HIGHEST, net_log));
+
+  ASSERT_TRUE(websocket_stream_->stream_);
+
+  SendRequest();
+
+  completion_callback_.WaitForResult();
+
+  EXPECT_EQ(stream_id_, created_stream_id_);
+
+  websocket_stream_.reset();
+
+  const std::vector<SpdyWebSocketStreamEvent>& events =
+      delegate.GetSeenEvents();
+  ASSERT_EQ(7U, events.size());
+
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS,
+            events[0].event_type);
+  EXPECT_LT(0, events[0].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER,
+            events[1].event_type);
+  EXPECT_EQ(OK, events[1].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+            events[2].event_type);
+  EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[2].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+            events[3].event_type);
+  EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+            events[4].event_type);
+  EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[4].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+            events[5].event_type);
+  EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[5].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CLOSE,
+            events[6].event_type);
+  EXPECT_EQ(OK, events[6].result);
+
+  // EOF close SPDY session.
+  EXPECT_TRUE(!http_session_->spdy_session_pool()->HasSession(
+      host_port_proxy_pair_));
+  EXPECT_TRUE(data()->at_read_eof());
+  EXPECT_TRUE(data()->at_write_eof());
+}
+
+TEST_F(SpdyWebSocketStreamSpdy2Test, DestructionBeforeClose) {
+  Prepare(1);
+  MockWrite writes[] = {
+    CreateMockWrite(*request_frame_.get(), 1),
+    CreateMockWrite(*message_frame_.get(), 3)
+  };
+
+  MockRead reads[] = {
+    CreateMockRead(*response_frame_.get(), 2),
+    CreateMockRead(*message_frame_.get(), 4),
+    MockRead(ASYNC, ERR_IO_PENDING, 5)
+  };
+
+  EXPECT_EQ(OK, InitSession(reads, arraysize(reads),
+                            writes, arraysize(writes), false));
+
+  SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback());
+  delegate.SetOnReceivedHeader(
+      base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendHelloFrame,
+                 base::Unretained(this)));
+  delegate.SetOnReceivedData(
+      base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSync,
+                 base::Unretained(this)));
+
+  websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate));
+
+  BoundNetLog net_log;
+  GURL url("ws://example.com/echo");
+  ASSERT_EQ(OK, websocket_stream_->InitializeStream(url, HIGHEST, net_log));
+
+  SendRequest();
+
+  sync_callback_.WaitForResult();
+
+  // WebSocketStream destruction remove its SPDY stream from the session.
+  EXPECT_TRUE(session_->IsStreamActive(stream_id_));
+  websocket_stream_.reset();
+  EXPECT_FALSE(session_->IsStreamActive(stream_id_));
+
+  const std::vector<SpdyWebSocketStreamEvent>& events =
+      delegate.GetSeenEvents();
+  ASSERT_GE(4U, events.size());
+
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS,
+            events[0].event_type);
+  EXPECT_LT(0, events[0].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER,
+            events[1].event_type);
+  EXPECT_EQ(OK, events[1].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+            events[2].event_type);
+  EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[2].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+            events[3].event_type);
+  EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result);
+
+  EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(
+      host_port_proxy_pair_));
+  EXPECT_TRUE(data()->at_read_eof());
+  EXPECT_TRUE(data()->at_write_eof());
+}
+
+TEST_F(SpdyWebSocketStreamSpdy2Test, DestructionAfterExplicitClose) {
+  Prepare(1);
+  MockWrite writes[] = {
+    CreateMockWrite(*request_frame_.get(), 1),
+    CreateMockWrite(*message_frame_.get(), 3),
+    CreateMockWrite(*closing_frame_.get(), 5)
+  };
+
+  MockRead reads[] = {
+    CreateMockRead(*response_frame_.get(), 2),
+    CreateMockRead(*message_frame_.get(), 4),
+    MockRead(ASYNC, ERR_IO_PENDING, 6)
+  };
+
+  EXPECT_EQ(OK, InitSession(reads, arraysize(reads),
+                            writes, arraysize(writes), false));
+
+  SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback());
+  delegate.SetOnReceivedHeader(
+      base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendHelloFrame,
+                 base::Unretained(this)));
+  delegate.SetOnReceivedData(
+      base::Bind(&SpdyWebSocketStreamSpdy2Test::DoClose,
+                 base::Unretained(this)));
+
+  websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate));
+
+  BoundNetLog net_log;
+  GURL url("ws://example.com/echo");
+  ASSERT_EQ(OK, websocket_stream_->InitializeStream(url, HIGHEST, net_log));
+
+  SendRequest();
+
+  completion_callback_.WaitForResult();
+
+  // SPDY stream has already been removed from the session by Close().
+  EXPECT_FALSE(session_->IsStreamActive(stream_id_));
+  websocket_stream_.reset();
+
+  const std::vector<SpdyWebSocketStreamEvent>& events =
+      delegate.GetSeenEvents();
+  ASSERT_EQ(5U, events.size());
+
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS,
+            events[0].event_type);
+  EXPECT_LT(0, events[0].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER,
+            events[1].event_type);
+  EXPECT_EQ(OK, events[1].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+            events[2].event_type);
+  EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[2].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+            events[3].event_type);
+  EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CLOSE, events[4].event_type);
+
+  EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(
+      host_port_proxy_pair_));
+}
+
+TEST_F(SpdyWebSocketStreamSpdy2Test, IOPending) {
+  Prepare(1);
+  scoped_ptr<SpdyFrame> settings_frame(
+      ConstructSpdySettings(spdy_settings_to_send_));
+  MockWrite writes[] = {
+    // Setting throttling make SpdySession send settings frame automatically.
+    CreateMockWrite(*settings_frame.get(), 1),
+    CreateMockWrite(*request_frame_.get(), 3),
+    CreateMockWrite(*message_frame_.get(), 6),
+    CreateMockWrite(*closing_frame_.get(), 9)
+  };
+
+  MockRead reads[] = {
+    CreateMockRead(*settings_frame.get(), 2),
+    CreateMockRead(*response_frame_.get(), 4),
+    // Skip sequence 5 (I/O Pending)
+    CreateMockRead(*message_frame_.get(), 7),
+    // Skip sequence 8 (I/O Pending)
+    CreateMockRead(*closing_frame_.get(), 10),
+    MockRead(SYNCHRONOUS, 0, 11)  // EOF cause OnCloseSpdyStream event.
+  };
+
+  EXPECT_EQ(OK, InitSession(reads, arraysize(reads),
+                            writes, arraysize(writes), true));
+
+  // Create a dummy WebSocketStream which cause ERR_IO_PENDING to another
+  // WebSocketStream under test.
+  SpdyWebSocketStreamEventRecorder block_delegate((CompletionCallback()));
+
+  scoped_ptr<SpdyWebSocketStream> block_stream(
+      new SpdyWebSocketStream(session_, &block_delegate));
+  BoundNetLog block_net_log;
+  GURL block_url("ws://example.com/block");
+  ASSERT_EQ(OK,
+            block_stream->InitializeStream(block_url, HIGHEST, block_net_log));
+
+  // Create a WebSocketStream under test.
+  SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback());
+  delegate.SetOnCreated(
+      base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSync,
+                 base::Unretained(this)));
+  delegate.SetOnReceivedHeader(
+      base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendHelloFrame,
+                 base::Unretained(this)));
+  delegate.SetOnReceivedData(
+      base::Bind(&SpdyWebSocketStreamSpdy2Test::DoSendClosingFrame,
+                 base::Unretained(this)));
+
+  websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate));
+  BoundNetLog net_log;
+  GURL url("ws://example.com/echo");
+  ASSERT_EQ(ERR_IO_PENDING, websocket_stream_->InitializeStream(
+      url, HIGHEST, net_log));
+
+  // Delete the fist stream to allow create the second stream.
+  block_stream.reset();
+  ASSERT_EQ(OK, sync_callback_.WaitForResult());
+
+  SendRequest();
+
+  completion_callback_.WaitForResult();
+
+  websocket_stream_.reset();
+
+  const std::vector<SpdyWebSocketStreamEvent>& block_events =
+      block_delegate.GetSeenEvents();
+  ASSERT_EQ(0U, block_events.size());
+
+  const std::vector<SpdyWebSocketStreamEvent>& events =
+      delegate.GetSeenEvents();
+  ASSERT_EQ(8U, events.size());
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CREATED,
+            events[0].event_type);
+  EXPECT_EQ(0, events[0].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS,
+            events[1].event_type);
+  EXPECT_LT(0, events[1].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER,
+            events[2].event_type);
+  EXPECT_EQ(OK, events[2].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+            events[3].event_type);
+  EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+            events[4].event_type);
+  EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[4].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+            events[5].event_type);
+  EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[5].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+            events[6].event_type);
+  EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[6].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CLOSE,
+            events[7].event_type);
+  EXPECT_EQ(OK, events[7].result);
+
+  // EOF close SPDY session.
+  EXPECT_TRUE(!http_session_->spdy_session_pool()->HasSession(
+      host_port_proxy_pair_));
+  EXPECT_TRUE(data()->at_read_eof());
+  EXPECT_TRUE(data()->at_write_eof());
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_websocket_stream_spdy3_unittest.cc b/src/net/spdy/spdy_websocket_stream_spdy3_unittest.cc
new file mode 100644
index 0000000..932d0a0
--- /dev/null
+++ b/src/net/spdy/spdy_websocket_stream_spdy3_unittest.cc
@@ -0,0 +1,615 @@
+// Copyright (c) 2012 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_websocket_stream.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "net/base/completion_callback.h"
+#include "net/proxy/proxy_server.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_protocol.h"
+#include "net/spdy/spdy_session.h"
+#include "net/spdy/spdy_test_util_spdy3.h"
+#include "net/spdy/spdy_websocket_test_util_spdy3.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using namespace net::test_spdy3;
+
+namespace net {
+
+namespace {
+
+struct SpdyWebSocketStreamEvent {
+  enum EventType {
+    EVENT_CREATED,
+    EVENT_SENT_HEADERS,
+    EVENT_RECEIVED_HEADER,
+    EVENT_SENT_DATA,
+    EVENT_RECEIVED_DATA,
+    EVENT_CLOSE,
+  };
+  SpdyWebSocketStreamEvent(EventType type,
+                           const SpdyHeaderBlock& headers,
+                           int result,
+                           const std::string& data)
+      : event_type(type),
+        headers(headers),
+        result(result),
+        data(data) {}
+
+  EventType event_type;
+  SpdyHeaderBlock headers;
+  int result;
+  std::string data;
+};
+
+class SpdyWebSocketStreamEventRecorder : public SpdyWebSocketStream::Delegate {
+ public:
+  explicit SpdyWebSocketStreamEventRecorder(const CompletionCallback& callback)
+      : callback_(callback) {}
+  virtual ~SpdyWebSocketStreamEventRecorder() {}
+
+  typedef base::Callback<void(SpdyWebSocketStreamEvent*)> StreamEventCallback;
+
+  void SetOnCreated(const StreamEventCallback& callback) {
+    on_created_ = callback;
+  }
+  void SetOnSentHeaders(const StreamEventCallback& callback) {
+    on_sent_headers_ = callback;
+  }
+  void SetOnReceivedHeader(const StreamEventCallback& callback) {
+    on_received_header_ = callback;
+  }
+  void SetOnSentData(const StreamEventCallback& callback) {
+    on_sent_data_ = callback;
+  }
+  void SetOnReceivedData(const StreamEventCallback& callback) {
+    on_received_data_ = callback;
+  }
+  void SetOnClose(const StreamEventCallback& callback) {
+    on_close_ = callback;
+  }
+
+  virtual void OnCreatedSpdyStream(int result) {
+    events_.push_back(
+        SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_CREATED,
+                                 SpdyHeaderBlock(),
+                                 result,
+                                 std::string()));
+    if (!on_created_.is_null())
+      on_created_.Run(&events_.back());
+  }
+  virtual void OnSentSpdyHeaders(int result) {
+    events_.push_back(
+        SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS,
+                                 SpdyHeaderBlock(),
+                                 result,
+                                 std::string()));
+    if (!on_sent_data_.is_null())
+      on_sent_data_.Run(&events_.back());
+  }
+  virtual int OnReceivedSpdyResponseHeader(
+      const SpdyHeaderBlock& headers, int status) {
+    events_.push_back(
+        SpdyWebSocketStreamEvent(
+            SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER,
+            headers,
+            status,
+            std::string()));
+    if (!on_received_header_.is_null())
+      on_received_header_.Run(&events_.back());
+    return status;
+  }
+  virtual void OnSentSpdyData(int amount_sent) {
+    events_.push_back(
+        SpdyWebSocketStreamEvent(
+            SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+            SpdyHeaderBlock(),
+            amount_sent,
+            std::string()));
+    if (!on_sent_data_.is_null())
+      on_sent_data_.Run(&events_.back());
+  }
+  virtual void OnReceivedSpdyData(const char* data, int length) {
+    events_.push_back(
+        SpdyWebSocketStreamEvent(
+            SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+            SpdyHeaderBlock(),
+            length,
+            std::string(data, length)));
+    if (!on_received_data_.is_null())
+      on_received_data_.Run(&events_.back());
+  }
+  virtual void OnCloseSpdyStream() {
+    events_.push_back(
+        SpdyWebSocketStreamEvent(
+            SpdyWebSocketStreamEvent::EVENT_CLOSE,
+            SpdyHeaderBlock(),
+            OK,
+            std::string()));
+    if (!on_close_.is_null())
+      on_close_.Run(&events_.back());
+    if (!callback_.is_null())
+      callback_.Run(OK);
+  }
+
+  const std::vector<SpdyWebSocketStreamEvent>& GetSeenEvents() const {
+    return events_;
+  }
+
+ private:
+  std::vector<SpdyWebSocketStreamEvent> events_;
+  StreamEventCallback on_created_;
+  StreamEventCallback on_sent_headers_;
+  StreamEventCallback on_received_header_;
+  StreamEventCallback on_sent_data_;
+  StreamEventCallback on_received_data_;
+  StreamEventCallback on_close_;
+  CompletionCallback callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(SpdyWebSocketStreamEventRecorder);
+};
+
+}  // namespace
+
+class SpdyWebSocketStreamSpdy3Test : public testing::Test {
+ public:
+  OrderedSocketData* data() { return data_.get(); }
+
+  void DoSendHelloFrame(SpdyWebSocketStreamEvent* event) {
+    // Record the actual stream_id.
+    created_stream_id_ = websocket_stream_->stream_->stream_id();
+    websocket_stream_->SendData(kMessageFrame, kMessageFrameLength);
+  }
+
+  void DoSendClosingFrame(SpdyWebSocketStreamEvent* event) {
+    websocket_stream_->SendData(kClosingFrame, kClosingFrameLength);
+  }
+
+  void DoClose(SpdyWebSocketStreamEvent* event) {
+    websocket_stream_->Close();
+  }
+
+  void DoSync(SpdyWebSocketStreamEvent* event) {
+    sync_callback_.callback().Run(OK);
+  }
+
+ protected:
+  SpdyWebSocketStreamSpdy3Test() {}
+  virtual ~SpdyWebSocketStreamSpdy3Test() {}
+
+  virtual void SetUp() {
+    host_port_pair_.set_host("example.com");
+    host_port_pair_.set_port(80);
+    host_port_proxy_pair_.first = host_port_pair_;
+    host_port_proxy_pair_.second = ProxyServer::Direct();
+
+    spdy_settings_id_to_set_ = SETTINGS_MAX_CONCURRENT_STREAMS;
+    spdy_settings_flags_to_set_ = SETTINGS_FLAG_PLEASE_PERSIST;
+    spdy_settings_value_to_set_ = 1;
+
+    spdy_settings_to_send_[spdy_settings_id_to_set_] =
+        SettingsFlagsAndValue(
+            SETTINGS_FLAG_PERSISTED, spdy_settings_value_to_set_);
+  }
+
+  virtual void TearDown() {
+    MessageLoop::current()->RunUntilIdle();
+  }
+
+  void Prepare(SpdyStreamId stream_id) {
+    stream_id_ = stream_id;
+
+    request_frame_.reset(ConstructSpdyWebSocketSynStream(
+        stream_id_,
+        "/echo",
+        "example.com",
+        "http://example.com/wsdemo"));
+
+    response_frame_.reset(ConstructSpdyWebSocketSynReply(stream_id_));
+
+    message_frame_.reset(ConstructSpdyWebSocketDataFrame(
+        kMessageFrame,
+        kMessageFrameLength,
+        stream_id_,
+        false));
+
+    closing_frame_.reset(ConstructSpdyWebSocketDataFrame(
+        kClosingFrame,
+        kClosingFrameLength,
+        stream_id_,
+        false));
+  }
+
+  int InitSession(MockRead* reads, size_t reads_count,
+                  MockWrite* writes, size_t writes_count,
+                  bool throttling) {
+    data_.reset(new OrderedSocketData(reads, reads_count,
+                                      writes, writes_count));
+    session_deps_.socket_factory->AddSocketDataProvider(data_.get());
+    http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
+    SpdySessionPool* spdy_session_pool(http_session_->spdy_session_pool());
+
+    if (throttling) {
+      // Set max concurrent streams to 1.
+      spdy_session_pool->http_server_properties()->SetSpdySetting(
+          host_port_pair_,
+          spdy_settings_id_to_set_,
+          spdy_settings_flags_to_set_,
+          spdy_settings_value_to_set_);
+    }
+
+    EXPECT_FALSE(spdy_session_pool->HasSession(host_port_proxy_pair_));
+    session_ = spdy_session_pool->Get(host_port_proxy_pair_, BoundNetLog());
+    EXPECT_TRUE(spdy_session_pool->HasSession(host_port_proxy_pair_));
+    transport_params_ = new TransportSocketParams(host_port_pair_, MEDIUM,
+                                                  false, false,
+                                                  OnHostResolutionCallback());
+    TestCompletionCallback callback;
+    scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
+    EXPECT_EQ(ERR_IO_PENDING,
+              connection->Init(host_port_pair_.ToString(), transport_params_,
+                               MEDIUM, callback.callback(),
+                               http_session_->GetTransportSocketPool(
+                                   HttpNetworkSession::NORMAL_SOCKET_POOL),
+                               BoundNetLog()));
+    EXPECT_EQ(OK, callback.WaitForResult());
+    return session_->InitializeWithSocket(connection.release(), false, OK);
+  }
+
+  void SendRequest() {
+    scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
+    (*headers)[":path"] = "/echo";
+    (*headers)[":host"] = "example.com";
+    (*headers)[":version"] = "WebSocket/13";
+    (*headers)[":scheme"] = "ws";
+    (*headers)[":origin"] = "http://example.com/wsdemo";
+
+    websocket_stream_->SendRequest(headers.Pass());
+  }
+
+  SpdySettingsIds spdy_settings_id_to_set_;
+  SpdySettingsFlags spdy_settings_flags_to_set_;
+  uint32 spdy_settings_value_to_set_;
+  SettingsMap spdy_settings_to_send_;
+  SpdySessionDependencies session_deps_;
+  scoped_ptr<OrderedSocketData> data_;
+  scoped_refptr<HttpNetworkSession> http_session_;
+  scoped_refptr<SpdySession> session_;
+  scoped_refptr<TransportSocketParams> transport_params_;
+  scoped_ptr<SpdyWebSocketStream> websocket_stream_;
+  SpdyStreamId stream_id_;
+  SpdyStreamId created_stream_id_;
+  scoped_ptr<SpdyFrame> request_frame_;
+  scoped_ptr<SpdyFrame> response_frame_;
+  scoped_ptr<SpdyFrame> message_frame_;
+  scoped_ptr<SpdyFrame> closing_frame_;
+  HostPortPair host_port_pair_;
+  HostPortProxyPair host_port_proxy_pair_;
+  TestCompletionCallback completion_callback_;
+  TestCompletionCallback sync_callback_;
+
+  static const char kMessageFrame[];
+  static const char kClosingFrame[];
+  static const size_t kMessageFrameLength;
+  static const size_t kClosingFrameLength;
+};
+
+// TODO(toyoshim): Replace old framing data to new one, then use HEADERS and
+// data frames.
+const char SpdyWebSocketStreamSpdy3Test::kMessageFrame[] = "\x81\x05hello";
+const char SpdyWebSocketStreamSpdy3Test::kClosingFrame[] = "\x88\0";
+const size_t SpdyWebSocketStreamSpdy3Test::kMessageFrameLength =
+    arraysize(SpdyWebSocketStreamSpdy3Test::kMessageFrame) - 1;
+const size_t SpdyWebSocketStreamSpdy3Test::kClosingFrameLength =
+    arraysize(SpdyWebSocketStreamSpdy3Test::kClosingFrame) - 1;
+
+TEST_F(SpdyWebSocketStreamSpdy3Test, Basic) {
+  Prepare(1);
+  MockWrite writes[] = {
+    CreateMockWrite(*request_frame_.get(), 1),
+    CreateMockWrite(*message_frame_.get(), 3),
+    CreateMockWrite(*closing_frame_.get(), 5)
+  };
+
+  MockRead reads[] = {
+    CreateMockRead(*response_frame_.get(), 2),
+    CreateMockRead(*message_frame_.get(), 4),
+    // Skip sequence 6 to notify closing has been sent.
+    CreateMockRead(*closing_frame_.get(), 7),
+    MockRead(SYNCHRONOUS, 0, 8)  // EOF cause OnCloseSpdyStream event.
+  };
+
+  EXPECT_EQ(OK, InitSession(reads, arraysize(reads),
+                            writes, arraysize(writes), false));
+
+  SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback());
+  delegate.SetOnReceivedHeader(
+      base::Bind(&SpdyWebSocketStreamSpdy3Test::DoSendHelloFrame,
+                 base::Unretained(this)));
+  delegate.SetOnReceivedData(
+      base::Bind(&SpdyWebSocketStreamSpdy3Test::DoSendClosingFrame,
+                 base::Unretained(this)));
+
+  websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate));
+
+  BoundNetLog net_log;
+  GURL url("ws://example.com/echo");
+  ASSERT_EQ(OK, websocket_stream_->InitializeStream(url, HIGHEST, net_log));
+
+  ASSERT_TRUE(websocket_stream_->stream_);
+
+  SendRequest();
+
+  completion_callback_.WaitForResult();
+
+  EXPECT_EQ(stream_id_, created_stream_id_);
+
+  websocket_stream_.reset();
+
+  const std::vector<SpdyWebSocketStreamEvent>& events =
+      delegate.GetSeenEvents();
+  ASSERT_EQ(7U, events.size());
+
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS,
+            events[0].event_type);
+  EXPECT_LT(0, events[0].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER,
+            events[1].event_type);
+  EXPECT_EQ(OK, events[1].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+            events[2].event_type);
+  EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[2].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+            events[3].event_type);
+  EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+            events[4].event_type);
+  EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[4].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+            events[5].event_type);
+  EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[5].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CLOSE,
+            events[6].event_type);
+  EXPECT_EQ(OK, events[6].result);
+
+  // EOF close SPDY session.
+  EXPECT_TRUE(!http_session_->spdy_session_pool()->HasSession(
+      host_port_proxy_pair_));
+  EXPECT_TRUE(data()->at_read_eof());
+  EXPECT_TRUE(data()->at_write_eof());
+}
+
+TEST_F(SpdyWebSocketStreamSpdy3Test, DestructionBeforeClose) {
+  Prepare(1);
+  MockWrite writes[] = {
+    CreateMockWrite(*request_frame_.get(), 1),
+    CreateMockWrite(*message_frame_.get(), 3)
+  };
+
+  MockRead reads[] = {
+    CreateMockRead(*response_frame_.get(), 2),
+    CreateMockRead(*message_frame_.get(), 4),
+    MockRead(ASYNC, ERR_IO_PENDING, 5)
+  };
+
+  EXPECT_EQ(OK, InitSession(reads, arraysize(reads),
+                            writes, arraysize(writes), false));
+
+  SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback());
+  delegate.SetOnReceivedHeader(
+      base::Bind(&SpdyWebSocketStreamSpdy3Test::DoSendHelloFrame,
+                 base::Unretained(this)));
+  delegate.SetOnReceivedData(
+      base::Bind(&SpdyWebSocketStreamSpdy3Test::DoSync,
+                 base::Unretained(this)));
+
+  websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate));
+
+  BoundNetLog net_log;
+  GURL url("ws://example.com/echo");
+  ASSERT_EQ(OK, websocket_stream_->InitializeStream(url, HIGHEST, net_log));
+
+  SendRequest();
+
+  sync_callback_.WaitForResult();
+
+  // WebSocketStream destruction remove its SPDY stream from the session.
+  EXPECT_TRUE(session_->IsStreamActive(stream_id_));
+  websocket_stream_.reset();
+  EXPECT_FALSE(session_->IsStreamActive(stream_id_));
+
+  const std::vector<SpdyWebSocketStreamEvent>& events =
+      delegate.GetSeenEvents();
+  ASSERT_GE(4U, events.size());
+
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS,
+            events[0].event_type);
+  EXPECT_LT(0, events[0].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER,
+            events[1].event_type);
+  EXPECT_EQ(OK, events[1].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+            events[2].event_type);
+  EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[2].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+            events[3].event_type);
+  EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result);
+
+  EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(
+      host_port_proxy_pair_));
+  EXPECT_TRUE(data()->at_read_eof());
+  EXPECT_TRUE(data()->at_write_eof());
+}
+
+TEST_F(SpdyWebSocketStreamSpdy3Test, DestructionAfterExplicitClose) {
+  Prepare(1);
+  MockWrite writes[] = {
+    CreateMockWrite(*request_frame_.get(), 1),
+    CreateMockWrite(*message_frame_.get(), 3),
+    CreateMockWrite(*closing_frame_.get(), 5)
+  };
+
+  MockRead reads[] = {
+    CreateMockRead(*response_frame_.get(), 2),
+    CreateMockRead(*message_frame_.get(), 4),
+    MockRead(ASYNC, ERR_IO_PENDING, 6)
+  };
+
+  EXPECT_EQ(OK, InitSession(reads, arraysize(reads),
+                            writes, arraysize(writes), false));
+
+  SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback());
+  delegate.SetOnReceivedHeader(
+      base::Bind(&SpdyWebSocketStreamSpdy3Test::DoSendHelloFrame,
+                 base::Unretained(this)));
+  delegate.SetOnReceivedData(
+      base::Bind(&SpdyWebSocketStreamSpdy3Test::DoClose,
+                 base::Unretained(this)));
+
+  websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate));
+
+  BoundNetLog net_log;
+  GURL url("ws://example.com/echo");
+  ASSERT_EQ(OK, websocket_stream_->InitializeStream(url, HIGHEST, net_log));
+
+  SendRequest();
+
+  completion_callback_.WaitForResult();
+
+  // SPDY stream has already been removed from the session by Close().
+  EXPECT_FALSE(session_->IsStreamActive(stream_id_));
+  websocket_stream_.reset();
+
+  const std::vector<SpdyWebSocketStreamEvent>& events =
+      delegate.GetSeenEvents();
+  ASSERT_EQ(5U, events.size());
+
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS,
+            events[0].event_type);
+  EXPECT_LT(0, events[0].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER,
+            events[1].event_type);
+  EXPECT_EQ(OK, events[1].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+            events[2].event_type);
+  EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[2].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+            events[3].event_type);
+  EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CLOSE, events[4].event_type);
+
+  EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(
+      host_port_proxy_pair_));
+}
+
+TEST_F(SpdyWebSocketStreamSpdy3Test, IOPending) {
+  Prepare(1);
+  scoped_ptr<SpdyFrame> settings_frame(
+      ConstructSpdySettings(spdy_settings_to_send_));
+  MockWrite writes[] = {
+    // Setting throttling make SpdySession send settings frame automatically.
+    CreateMockWrite(*settings_frame.get(), 1),
+    CreateMockWrite(*request_frame_.get(), 3),
+    CreateMockWrite(*message_frame_.get(), 6),
+    CreateMockWrite(*closing_frame_.get(), 9)
+  };
+
+  MockRead reads[] = {
+    CreateMockRead(*settings_frame.get(), 2),
+    CreateMockRead(*response_frame_.get(), 4),
+    // Skip sequence 5 (I/O Pending)
+    CreateMockRead(*message_frame_.get(), 7),
+    // Skip sequence 8 (I/O Pending)
+    CreateMockRead(*closing_frame_.get(), 10),
+    MockRead(SYNCHRONOUS, 0, 11)  // EOF cause OnCloseSpdyStream event.
+  };
+
+  EXPECT_EQ(OK, InitSession(reads, arraysize(reads),
+                            writes, arraysize(writes), true));
+
+  // Create a dummy WebSocketStream which cause ERR_IO_PENDING to another
+  // WebSocketStream under test.
+  SpdyWebSocketStreamEventRecorder block_delegate((CompletionCallback()));
+
+  scoped_ptr<SpdyWebSocketStream> block_stream(
+      new SpdyWebSocketStream(session_, &block_delegate));
+  BoundNetLog block_net_log;
+  GURL block_url("ws://example.com/block");
+  ASSERT_EQ(OK,
+            block_stream->InitializeStream(block_url, HIGHEST, block_net_log));
+
+  // Create a WebSocketStream under test.
+  SpdyWebSocketStreamEventRecorder delegate(completion_callback_.callback());
+  delegate.SetOnCreated(
+      base::Bind(&SpdyWebSocketStreamSpdy3Test::DoSync,
+                 base::Unretained(this)));
+  delegate.SetOnReceivedHeader(
+      base::Bind(&SpdyWebSocketStreamSpdy3Test::DoSendHelloFrame,
+                 base::Unretained(this)));
+  delegate.SetOnReceivedData(
+      base::Bind(&SpdyWebSocketStreamSpdy3Test::DoSendClosingFrame,
+                 base::Unretained(this)));
+
+  websocket_stream_.reset(new SpdyWebSocketStream(session_, &delegate));
+  BoundNetLog net_log;
+  GURL url("ws://example.com/echo");
+  ASSERT_EQ(ERR_IO_PENDING, websocket_stream_->InitializeStream(
+      url, HIGHEST, net_log));
+
+  // Delete the first stream to allow create the second stream.
+  block_stream.reset();
+  ASSERT_EQ(OK, sync_callback_.WaitForResult());
+
+  SendRequest();
+
+  completion_callback_.WaitForResult();
+
+  websocket_stream_.reset();
+
+  const std::vector<SpdyWebSocketStreamEvent>& block_events =
+      block_delegate.GetSeenEvents();
+  ASSERT_EQ(0U, block_events.size());
+
+  const std::vector<SpdyWebSocketStreamEvent>& events =
+      delegate.GetSeenEvents();
+  ASSERT_EQ(8U, events.size());
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CREATED,
+            events[0].event_type);
+  EXPECT_EQ(0, events[0].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS,
+            events[1].event_type);
+  EXPECT_LT(0, events[1].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER,
+            events[2].event_type);
+  EXPECT_EQ(OK, events[2].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+            events[3].event_type);
+  EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[3].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+            events[4].event_type);
+  EXPECT_EQ(static_cast<int>(kMessageFrameLength), events[4].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
+            events[5].event_type);
+  EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[5].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
+            events[6].event_type);
+  EXPECT_EQ(static_cast<int>(kClosingFrameLength), events[6].result);
+  EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CLOSE,
+            events[7].event_type);
+  EXPECT_EQ(OK, events[7].result);
+
+  // EOF close SPDY session.
+  EXPECT_TRUE(!http_session_->spdy_session_pool()->HasSession(
+      host_port_proxy_pair_));
+  EXPECT_TRUE(data()->at_read_eof());
+  EXPECT_TRUE(data()->at_write_eof());
+}
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_websocket_test_util_spdy2.cc b/src/net/spdy/spdy_websocket_test_util_spdy2.cc
new file mode 100644
index 0000000..1a45949
--- /dev/null
+++ b/src/net/spdy/spdy_websocket_test_util_spdy2.cc
@@ -0,0 +1,162 @@
+// Copyright (c) 2012 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_websocket_test_util_spdy2.h"
+
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_test_util_spdy2.h"
+
+static const int kDefaultAssociatedStreamId = 0;
+static const bool kDefaultCompressed = false;
+static const char* const kDefaultDataPointer = NULL;
+static const uint32 kDefaultDataLength = 0;
+static const char** const kDefaultExtraHeaders = NULL;
+static const int kDefaultExtraHeaderCount = 0;
+
+namespace net {
+
+namespace test_spdy2 {
+
+SpdyFrame* ConstructSpdyWebSocketSynStream(int stream_id,
+                                           const char* path,
+                                           const char* host,
+                                           const char* origin) {
+  const char* const kWebSocketHeaders[] = {
+    "path",
+    path,
+    "host",
+    host,
+    "version",
+    "WebSocket/13",
+    "scheme",
+    "ws",
+    "origin",
+    origin
+  };
+  return ConstructSpdyControlFrame(/*extra_headers*/ NULL,
+                                   /*extra_header_count*/ 0,
+                                   /*compressed*/ false,
+                                   stream_id,
+                                   HIGHEST,
+                                   SYN_STREAM,
+                                   CONTROL_FLAG_NONE,
+                                   kWebSocketHeaders,
+                                   arraysize(kWebSocketHeaders));
+}
+
+SpdyFrame* ConstructSpdyWebSocketSynReply(int stream_id) {
+  static const char* const kStandardWebSocketHeaders[] = {
+    "status",
+    "101"
+  };
+  return ConstructSpdyControlFrame(NULL,
+                                   0,
+                                   false,
+                                   stream_id,
+                                   LOWEST,
+                                   SYN_REPLY,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardWebSocketHeaders,
+                                   arraysize(kStandardWebSocketHeaders));
+}
+
+SpdyFrame* ConstructSpdyWebSocketHandshakeRequestFrame(
+    const char* const headers[],
+    int header_count,
+    SpdyStreamId stream_id,
+    RequestPriority request_priority) {
+
+  // SPDY SYN_STREAM control frame header.
+  const SpdyHeaderInfo kSynStreamHeader = {
+    SYN_STREAM,
+    stream_id,
+    kDefaultAssociatedStreamId,
+    ConvertRequestPriorityToSpdyPriority(request_priority, 2),
+    CONTROL_FLAG_NONE,
+    kDefaultCompressed,
+    INVALID,
+    kDefaultDataPointer,
+    kDefaultDataLength,
+    DATA_FLAG_NONE
+  };
+
+  // Construct SPDY SYN_STREAM control frame.
+  return ConstructSpdyPacket(
+      kSynStreamHeader,
+      kDefaultExtraHeaders,
+      kDefaultExtraHeaderCount,
+      headers,
+      header_count);
+}
+
+SpdyFrame* ConstructSpdyWebSocketHandshakeResponseFrame(
+    const char* const headers[],
+    int header_count,
+    SpdyStreamId stream_id,
+    RequestPriority request_priority) {
+
+  // SPDY SYN_REPLY control frame header.
+  const SpdyHeaderInfo kSynReplyHeader = {
+    SYN_REPLY,
+    stream_id,
+    kDefaultAssociatedStreamId,
+    ConvertRequestPriorityToSpdyPriority(request_priority, 2),
+    CONTROL_FLAG_NONE,
+    kDefaultCompressed,
+    INVALID,
+    kDefaultDataPointer,
+    kDefaultDataLength,
+    DATA_FLAG_NONE
+  };
+
+  // Construct SPDY SYN_REPLY control frame.
+  return ConstructSpdyPacket(
+      kSynReplyHeader,
+      kDefaultExtraHeaders,
+      kDefaultExtraHeaderCount,
+      headers,
+      header_count);
+}
+
+SpdyFrame* ConstructSpdyWebSocketHeadersFrame(int stream_id,
+                                              const char* length,
+                                              bool fin) {
+  static const char* const kHeaders[] = {
+    "opcode",
+    "1",  // text frame
+    "length",
+    length,
+    "fin",
+    fin ? "1" : "0"
+  };
+  return ConstructSpdyControlFrame(/*extra_headers*/ NULL,
+                                   /*extra_header_count*/ 0,
+                                   /*compression*/ false,
+                                   stream_id,
+                                   LOWEST,
+                                   HEADERS,
+                                   CONTROL_FLAG_NONE,
+                                   kHeaders,
+                                   arraysize(kHeaders));
+}
+
+SpdyFrame* ConstructSpdyWebSocketDataFrame(
+    const char* data,
+    int len,
+    SpdyStreamId stream_id,
+    bool fin) {
+
+  // Construct SPDY data frame.
+  BufferedSpdyFramer framer(2, false);
+  return framer.CreateDataFrame(
+      stream_id,
+      data,
+      len,
+      fin ? DATA_FLAG_FIN : DATA_FLAG_NONE);
+}
+
+}  // namespace test_spdy2
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_websocket_test_util_spdy2.h b/src/net/spdy/spdy_websocket_test_util_spdy2.h
new file mode 100644
index 0000000..3856bb6
--- /dev/null
+++ b/src/net/spdy/spdy_websocket_test_util_spdy2.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_SPDY_SPDY_WEBSOCKET_TEST_UTIL_H_
+#define NET_SPDY_SPDY_WEBSOCKET_TEST_UTIL_H_
+
+#include "net/base/request_priority.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+
+namespace test_spdy2 {
+
+// Constructs a standard SPDY SYN_STREAM frame for WebSocket over SPDY opening
+// handshake.
+SpdyFrame* ConstructSpdyWebSocketSynStream(int stream_id,
+                                           const char* path,
+                                           const char* host,
+                                           const char* origin);
+
+// Constructs a standard SPDY SYN_REPLY packet to match the WebSocket over SPDY
+// opening handshake.
+SpdyFrame* ConstructSpdyWebSocketSynReply(int stream_id);
+
+// Constructs a WebSocket over SPDY handshake request packet.
+SpdyFrame* ConstructSpdyWebSocketHandshakeRequestFrame(
+    const char* const headers[],
+    int header_count,
+    SpdyStreamId stream_id,
+    RequestPriority request_priority);
+
+// Constructs a WebSocket over SPDY handshake response packet.
+SpdyFrame* ConstructSpdyWebSocketHandshakeResponseFrame(
+    const char* const headers[],
+    int header_count,
+    SpdyStreamId stream_id,
+    RequestPriority request_priority);
+
+// Constructs a SPDY HEADERS frame for a WebSocket frame over SPDY.
+SpdyFrame* ConstructSpdyWebSocketHeadersFrame(int stream_id,
+                                              const char* length,
+                                              bool fin);
+
+// Constructs a WebSocket over SPDY data packet.
+SpdyFrame* ConstructSpdyWebSocketDataFrame(const char* data,
+                                           int len,
+                                           SpdyStreamId stream_id,
+                                           bool fin);
+
+}  // namespace test_spdy2
+
+}  // namespace net
+
+#endif  // NET_SPDY_SPDY_WEBSOCKET_TEST_UTIL_H_
diff --git a/src/net/spdy/spdy_websocket_test_util_spdy3.cc b/src/net/spdy/spdy_websocket_test_util_spdy3.cc
new file mode 100644
index 0000000..4e2eec4
--- /dev/null
+++ b/src/net/spdy/spdy_websocket_test_util_spdy3.cc
@@ -0,0 +1,165 @@
+// Copyright (c) 2012 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_websocket_test_util_spdy3.h"
+
+#include "net/spdy/buffered_spdy_framer.h"
+#include "net/spdy/spdy_http_utils.h"
+#include "net/spdy/spdy_test_util_spdy3.h"
+
+static const int kDefaultAssociatedStreamId = 0;
+static const bool kDefaultCompressed = false;
+static const char* const kDefaultDataPointer = NULL;
+static const uint32 kDefaultDataLength = 0;
+static const char** const kDefaultExtraHeaders = NULL;
+static const int kDefaultExtraHeaderCount = 0;
+static const int kDefaultCredentialSlot = 0;
+
+namespace net {
+
+namespace test_spdy3 {
+
+SpdyFrame* ConstructSpdyWebSocketSynStream(int stream_id,
+                                           const char* path,
+                                           const char* host,
+                                           const char* origin) {
+  const char* const kWebSocketHeaders[] = {
+    ":path",
+    path,
+    ":host",
+    host,
+    ":version",
+    "WebSocket/13",
+    ":scheme",
+    "ws",
+    ":origin",
+    origin
+  };
+  return ConstructSpdyControlFrame(/*extra_headers*/ NULL,
+                                   /*extra_header_count*/ 0,
+                                   /*compressed*/ false,
+                                   stream_id,
+                                   HIGHEST,
+                                   SYN_STREAM,
+                                   CONTROL_FLAG_NONE,
+                                   kWebSocketHeaders,
+                                   arraysize(kWebSocketHeaders));
+}
+
+SpdyFrame* ConstructSpdyWebSocketSynReply(int stream_id) {
+  static const char* const kStandardWebSocketHeaders[] = {
+    ":status",
+    "101"
+  };
+  return ConstructSpdyControlFrame(NULL,
+                                   0,
+                                   false,
+                                   stream_id,
+                                   LOWEST,
+                                   SYN_REPLY,
+                                   CONTROL_FLAG_NONE,
+                                   kStandardWebSocketHeaders,
+                                   arraysize(kStandardWebSocketHeaders));
+}
+
+SpdyFrame* ConstructSpdyWebSocketHandshakeRequestFrame(
+    const char* const headers[],
+    int header_count,
+    SpdyStreamId stream_id,
+    RequestPriority request_priority) {
+
+  // SPDY SYN_STREAM control frame header.
+  const SpdyHeaderInfo kSynStreamHeader = {
+    SYN_STREAM,
+    stream_id,
+    kDefaultAssociatedStreamId,
+    ConvertRequestPriorityToSpdyPriority(request_priority, 3),
+    kDefaultCredentialSlot,
+    CONTROL_FLAG_NONE,
+    kDefaultCompressed,
+    INVALID,
+    kDefaultDataPointer,
+    kDefaultDataLength,
+    DATA_FLAG_NONE
+  };
+
+  // Construct SPDY SYN_STREAM control frame.
+  return ConstructSpdyPacket(
+      kSynStreamHeader,
+      kDefaultExtraHeaders,
+      kDefaultExtraHeaderCount,
+      headers,
+      header_count);
+}
+
+SpdyFrame* ConstructSpdyWebSocketHandshakeResponseFrame(
+    const char* const headers[],
+    int header_count,
+    SpdyStreamId stream_id,
+    RequestPriority request_priority) {
+
+  // SPDY SYN_REPLY control frame header.
+  const SpdyHeaderInfo kSynReplyHeader = {
+    SYN_REPLY,
+    stream_id,
+    kDefaultAssociatedStreamId,
+    ConvertRequestPriorityToSpdyPriority(request_priority, 3),
+    kDefaultCredentialSlot,
+    CONTROL_FLAG_NONE,
+    kDefaultCompressed,
+    INVALID,
+    kDefaultDataPointer,
+    kDefaultDataLength,
+    DATA_FLAG_NONE
+  };
+
+  // Construct SPDY SYN_REPLY control frame.
+  return ConstructSpdyPacket(
+      kSynReplyHeader,
+      kDefaultExtraHeaders,
+      kDefaultExtraHeaderCount,
+      headers,
+      header_count);
+}
+
+SpdyFrame* ConstructSpdyWebSocketHeadersFrame(int stream_id,
+                                              const char* length,
+                                              bool fin) {
+  static const char* const kHeaders[] = {
+    ":opcode",
+    "1",  // text frame
+    ":length",
+    length,
+    ":fin",
+    fin ? "1" : "0"
+  };
+  return ConstructSpdyControlFrame(/*extra_headers*/ NULL,
+                                   /*extra_header_count*/ 0,
+                                   /*compression*/ false,
+                                   stream_id,
+                                   LOWEST,
+                                   HEADERS,
+                                   CONTROL_FLAG_NONE,
+                                   kHeaders,
+                                   arraysize(kHeaders));
+}
+
+SpdyFrame* ConstructSpdyWebSocketDataFrame(
+    const char* data,
+    int len,
+    SpdyStreamId stream_id,
+    bool fin) {
+
+  // Construct SPDY data frame.
+  BufferedSpdyFramer framer(3, false);
+  return framer.CreateDataFrame(
+      stream_id,
+      data,
+      len,
+      fin ? DATA_FLAG_FIN : DATA_FLAG_NONE);
+}
+
+}  // namespace test_spdy3
+
+}  // namespace net
diff --git a/src/net/spdy/spdy_websocket_test_util_spdy3.h b/src/net/spdy/spdy_websocket_test_util_spdy3.h
new file mode 100644
index 0000000..3446738
--- /dev/null
+++ b/src/net/spdy/spdy_websocket_test_util_spdy3.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_SPDY_SPDY_WEBSOCKET_TEST_UTIL_H_
+#define NET_SPDY_SPDY_WEBSOCKET_TEST_UTIL_H_
+
+#include "net/base/request_priority.h"
+#include "net/spdy/spdy_protocol.h"
+
+namespace net {
+
+namespace test_spdy3 {
+
+// Constructs a standard SPDY SYN_STREAM frame for WebSocket over SPDY opening
+// handshake.
+SpdyFrame* ConstructSpdyWebSocketSynStream(int stream_id,
+                                           const char* path,
+                                           const char* host,
+                                           const char* origin);
+
+// Constructs a standard SPDY SYN_REPLY packet to match the WebSocket over SPDY
+// opening handshake.
+SpdyFrame* ConstructSpdyWebSocketSynReply(int stream_id);
+
+// Constructs a WebSocket over SPDY handshake request packet.
+SpdyFrame* ConstructSpdyWebSocketHandshakeRequestFrame(
+    const char* const headers[],
+    int header_count,
+    SpdyStreamId stream_id,
+    RequestPriority request_priority);
+
+// Constructs a WebSocket over SPDY handshake response packet.
+SpdyFrame* ConstructSpdyWebSocketHandshakeResponseFrame(
+    const char* const headers[],
+    int header_count,
+    SpdyStreamId stream_id,
+    RequestPriority request_priority);
+
+// Constructs a SPDY HEADERS frame for a WebSocket frame over SPDY.
+SpdyFrame* ConstructSpdyWebSocketHeadersFrame(int stream_id,
+                                              const char* length,
+                                              bool fin);
+
+// Constructs a WebSocket over SPDY data packet.
+SpdyFrame* ConstructSpdyWebSocketDataFrame(const char* data,
+                                           int len,
+                                           SpdyStreamId stream_id,
+                                           bool fin);
+
+}  // namespace test_spdy3
+
+}  // namespace net
+
+#endif  // NET_SPDY_SPDY_WEBSOCKET_TEST_UTIL_H_