bug 912550 - backout spdy/2 removal from ff27 r=mcmanus a=backout
authorPatrick McManus <mcmanus@ducksong.com>
Mon, 11 Nov 2013 08:59:32 -0500
changeset 167408 856ca571aa89dab408220de4213f745a0e6c5245
parent 167407 7ee062b4c13aee81d36ad43fb216c55f86032d6a
child 167409 0939430ea588ba0b6ddb00b4249ad0986a8934c7
push id428
push userbbajaj@mozilla.com
push dateTue, 28 Jan 2014 00:16:25 +0000
treeherdermozilla-release@cd72a7ff3a75 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcmanus, backout
bugs912550
milestone27.0a2
bug 912550 - backout spdy/2 removal from ff27 r=mcmanus a=backout
modules/libpref/src/init/all.js
netwerk/base/src/Dashboard.cpp
netwerk/protocol/http/ASpdySession.cpp
netwerk/protocol/http/ASpdySession.h
netwerk/protocol/http/ConnectionDiagnostics.cpp
netwerk/protocol/http/SpdySession2.cpp
netwerk/protocol/http/SpdySession2.h
netwerk/protocol/http/SpdyStream2.cpp
netwerk/protocol/http/SpdyStream2.h
netwerk/protocol/http/moz.build
netwerk/protocol/http/nsHttp.h
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/protocol/http/nsHttpHandler.h
netwerk/test/unit/test_spdy.js
netwerk/test/unit/test_spdy2.js
netwerk/test/unit/xpcshell.ini
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -1116,16 +1116,17 @@ pref("network.http.fast-fallback-to-IPv4
 #ifdef RELEASE_BUILD
 pref("network.http.bypass-cachelock-threshold", 200000);
 #else
 pref("network.http.bypass-cachelock-threshold", 250);
 #endif
 
 // Try and use SPDY when using SSL
 pref("network.http.spdy.enabled", true);
+pref("network.http.spdy.enabled.v2", true);
 pref("network.http.spdy.enabled.v3", true);
 pref("network.http.spdy.enabled.v3-1", true);
 pref("network.http.spdy.chunk-size", 4096);
 pref("network.http.spdy.timeout", 180);
 pref("network.http.spdy.coalesce-hostnames", true);
 pref("network.http.spdy.persistent-settings", false);
 pref("network.http.spdy.ping-threshold", 58);
 pref("network.http.spdy.ping-timeout", 8);
--- a/netwerk/base/src/Dashboard.cpp
+++ b/netwerk/base/src/Dashboard.cpp
@@ -567,17 +567,19 @@ HttpConnInfo::SetHTTP1ProtocolVersion(ui
     default:
         protocolVersion.Assign(NS_LITERAL_STRING("unknown protocol version"));
     }
 }
 
 void
 HttpConnInfo::SetHTTP2ProtocolVersion(uint8_t pv)
 {
-    if (pv == SPDY_VERSION_3)
+    if (pv == SPDY_VERSION_2)
+        protocolVersion.Assign(NS_LITERAL_STRING("spdy/2"));
+    else if (pv == SPDY_VERSION_3)
         protocolVersion.Assign(NS_LITERAL_STRING("spdy/3"));
     else {
         MOZ_ASSERT (pv == SPDY_VERSION_31);
         protocolVersion.Assign(NS_LITERAL_STRING("spdy/3.1"));
     }
 }
 
 NS_IMETHODIMP
--- a/netwerk/protocol/http/ASpdySession.cpp
+++ b/netwerk/protocol/http/ASpdySession.cpp
@@ -3,78 +3,88 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // HttpLog.h should generally be included first
 #include "HttpLog.h"
 
 /*
-  Currently supported are spdy/3.1 and spdy/3
+  Currently supported are spdy/3.1 and spdy/3 and spdy/2
 
 */
 
 #include "nsHttp.h"
 #include "nsHttpHandler.h"
 
 #include "ASpdySession.h"
 #include "PSpdyPush.h"
 #include "SpdyPush3.h"
 #include "SpdyPush31.h"
+#include "SpdySession2.h"
 #include "SpdySession3.h"
 #include "SpdySession31.h"
 
 #include "mozilla/Telemetry.h"
 
 namespace mozilla {
 namespace net {
 
 ASpdySession *
 ASpdySession::NewSpdySession(uint32_t version,
                              nsAHttpTransaction *aTransaction,
                              nsISocketTransport *aTransport,
                              int32_t aPriority)
 {
   // This is a necko only interface, so we can enforce version
   // requests as a precondition
-  MOZ_ASSERT(version == SPDY_VERSION_3 ||
+  MOZ_ASSERT(version == SPDY_VERSION_2 ||
+             version == SPDY_VERSION_3 ||
              version == SPDY_VERSION_31 ,
              "Unsupported spdy version");
 
   // Don't do a runtime check of IsSpdyV?Enabled() here because pref value
   // may have changed since starting negotiation. The selected protocol comes
   // from a list provided in the SERVER HELLO filtered by our acceptable
   // versions, so there is no risk of the server ignoring our prefs.
 
   Telemetry::Accumulate(Telemetry::SPDY_VERSION2, version);
 
   if (version == SPDY_VERSION_3)
     return new SpdySession3(aTransaction, aTransport, aPriority);
 
+  if (version == SPDY_VERSION_2)
+    return new SpdySession2(aTransaction, aTransport, aPriority);
+
   return new SpdySession31(aTransaction, aTransport, aPriority);
 }
 
 SpdyInformation::SpdyInformation()
 {
-  Version[0] = SPDY_VERSION_3;
-  VersionString[0] = NS_LITERAL_CSTRING("spdy/3");
+  Version[0] = SPDY_VERSION_2;
+  VersionString[0] = NS_LITERAL_CSTRING("spdy/2");
 
-  Version[1] = SPDY_VERSION_31;
-  VersionString[1] = NS_LITERAL_CSTRING("spdy/3.1");
+  Version[1] = SPDY_VERSION_3;
+  VersionString[1] = NS_LITERAL_CSTRING("spdy/3");
+
+  Version[2] = SPDY_VERSION_31;
+  VersionString[2] = NS_LITERAL_CSTRING("spdy/3.1");
 }
 
 bool
 SpdyInformation::ProtocolEnabled(uint32_t index)
 {
   MOZ_ASSERT(index < kCount, "index out of range");
 
   switch (index) {
   case 0:
+    return gHttpHandler->IsSpdyV2Enabled();
+  case 1:
     return gHttpHandler->IsSpdyV3Enabled();
-  case 1:
+  case 2:
     return gHttpHandler->IsSpdyV31Enabled();
   }
   return false;
 }
 
 nsresult
 SpdyInformation::GetNPNVersionIndex(const nsACString &npnString,
                                     uint8_t *result)
--- a/netwerk/protocol/http/ASpdySession.h
+++ b/netwerk/protocol/http/ASpdySession.h
@@ -46,17 +46,17 @@ public:
 // It could be all static except using static ctors of XPCOM objects is a
 // bad idea.
 class SpdyInformation
 {
 public:
   SpdyInformation();
   ~SpdyInformation() {}
 
-  static const uint32_t kCount = 2;
+  static const uint32_t kCount = 3;
 
   // determine if a version of the protocol is enabled for index <= kCount
   bool ProtocolEnabled(uint32_t index);
 
   // lookup a version enum based on an npn string. returns NS_OK if
   // string was known.
   nsresult GetNPNVersionIndex(const nsACString &npnString, uint8_t *result);
 
--- a/netwerk/protocol/http/ConnectionDiagnostics.cpp
+++ b/netwerk/protocol/http/ConnectionDiagnostics.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // HttpLog.h should generally be included first
 #include "HttpLog.h"
 
 #include "nsHttpConnectionMgr.h"
 #include "nsHttpConnection.h"
+#include "SpdySession2.h"
 #include "SpdySession3.h"
 #include "SpdySession31.h"
 #include "nsHttpHandler.h"
 #include "nsIConsoleService.h"
 #include "nsHttpRequestHead.h"
 
 using namespace mozilla;
 using namespace mozilla::net;
@@ -154,16 +155,52 @@ nsHttpConnection::PrintDiagnostics(nsCSt
 
   log.AppendPrintf("    supports pipeline = %d classification = 0x%x\n",
                    mSupportsPipelining, mClassification);
 
   if (mSpdySession)
     mSpdySession->PrintDiagnostics(log);
 }
 
+void
+SpdySession2::PrintDiagnostics(nsCString &log)
+{
+  log.AppendPrintf("     ::: SPDY VERSION 2\n");
+  log.AppendPrintf("     shouldgoaway = %d mClosed = %d CanReuse = %d nextID=0x%X\n",
+                   mShouldGoAway, mClosed, CanReuse(), mNextStreamID);
+
+  log.AppendPrintf("     concurrent = %d maxconcurrent = %d\n",
+                   mConcurrent, mMaxConcurrent);
+
+  log.AppendPrintf("     roomformorestreams = %d roomformoreconcurrent = %d\n",
+                   RoomForMoreStreams(), RoomForMoreConcurrent());
+
+  log.AppendPrintf("     transactionHashCount = %d streamIDHashCount = %d\n",
+                   mStreamTransactionHash.Count(),
+                   mStreamIDHash.Count());
+
+  log.AppendPrintf("     Queued Stream Size = %d\n", mQueuedStreams.GetSize());
+
+  PRIntervalTime now = PR_IntervalNow();
+  log.AppendPrintf("     Ping Threshold = %ums next ping id = 0x%X\n",
+                   PR_IntervalToMilliseconds(mPingThreshold),
+                   mNextPingID);
+  log.AppendPrintf("     Ping Timeout = %ums\n",
+                   PR_IntervalToMilliseconds(gHttpHandler->SpdyPingTimeout()));
+  log.AppendPrintf("     Idle for Any Activity (ping) = %ums\n",
+                   PR_IntervalToMilliseconds(now - mLastReadEpoch));
+  log.AppendPrintf("     Idle for Data Activity = %ums\n",
+                   PR_IntervalToMilliseconds(now - mLastDataReadEpoch));
+  if (mPingSentEpoch)
+    log.AppendPrintf("     Ping Outstanding (ping) = %ums, expired = %d\n",
+                     PR_IntervalToMilliseconds(now - mPingSentEpoch),
+                     now - mPingSentEpoch >= gHttpHandler->SpdyPingTimeout());
+  else
+    log.AppendPrintf("     No Ping Outstanding\n");
+}
 
 void
 SpdySession3::PrintDiagnostics(nsCString &log)
 {
   log.AppendPrintf("     ::: SPDY VERSION 3\n");
   log.AppendPrintf("     shouldgoaway = %d mClosed = %d CanReuse = %d nextID=0x%X\n",
                    mShouldGoAway, mClosed, CanReuse(), mNextStreamID);
 
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/SpdySession2.cpp
@@ -0,0 +1,2335 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttp.h"
+#include "SpdySession2.h"
+#include "SpdyStream2.h"
+#include "nsHttpHandler.h"
+#include "prnetdb.h"
+#include "mozilla/Telemetry.h"
+#include "prprf.h"
+#include <algorithm>
+
+#ifdef DEBUG
+// defined by the socket transport service while active
+extern PRThread *gSocketThread;
+#endif
+
+namespace mozilla {
+namespace net {
+
+// SpdySession2 has multiple inheritance of things that implement
+// nsISupports, so this magic is taken from nsHttpPipeline that
+// implements some of the same abstract classes.
+NS_IMPL_ADDREF(SpdySession2)
+NS_IMPL_RELEASE(SpdySession2)
+NS_INTERFACE_MAP_BEGIN(SpdySession2)
+    NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection)
+NS_INTERFACE_MAP_END
+
+SpdySession2::SpdySession2(nsAHttpTransaction *aHttpTransaction,
+                         nsISocketTransport *aSocketTransport,
+                         int32_t firstPriority)
+  : mSocketTransport(aSocketTransport),
+    mSegmentReader(nullptr),
+    mSegmentWriter(nullptr),
+    mSendingChunkSize(ASpdySession::kSendingChunkSize),
+    mNextStreamID(1),
+    mConcurrentHighWater(0),
+    mDownstreamState(BUFFERING_FRAME_HEADER),
+    mInputFrameBufferSize(kDefaultBufferSize),
+    mInputFrameBufferUsed(0),
+    mInputFrameDataLast(false),
+    mInputFrameDataStream(nullptr),
+    mNeedsCleanup(nullptr),
+    mDecompressBufferSize(kDefaultBufferSize),
+    mDecompressBufferUsed(0),
+    mShouldGoAway(false),
+    mClosed(false),
+    mCleanShutdown(false),
+    mGoAwayID(0),
+    mMaxConcurrent(kDefaultMaxConcurrent),
+    mConcurrent(0),
+    mServerPushedResources(0),
+    mOutputQueueSize(kDefaultQueueSize),
+    mOutputQueueUsed(0),
+    mOutputQueueSent(0),
+    mLastReadEpoch(PR_IntervalNow()),
+    mPingSentEpoch(0),
+    mNextPingID(1)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  LOG3(("SpdySession2::SpdySession2 %p transaction 1 = %p",
+        this, aHttpTransaction));
+
+  mConnection = aHttpTransaction->Connection();
+  mInputFrameBuffer = new char[mInputFrameBufferSize];
+  mDecompressBuffer = new char[mDecompressBufferSize];
+  mOutputQueueBuffer = new char[mOutputQueueSize];
+  zlibInit();
+
+  mSendingChunkSize = gHttpHandler->SpdySendingChunkSize();
+  if (!aHttpTransaction->IsNullTransaction())
+    AddStream(aHttpTransaction, firstPriority);
+  mLastDataReadEpoch = mLastReadEpoch;
+
+  mPingThreshold = gHttpHandler->SpdyPingThreshold();
+}
+
+PLDHashOperator
+SpdySession2::ShutdownEnumerator(nsAHttpTransaction *key,
+                                nsAutoPtr<SpdyStream2> &stream,
+                                void *closure)
+{
+  SpdySession2 *self = static_cast<SpdySession2 *>(closure);
+
+  // On a clean server hangup the server sets the GoAwayID to be the ID of
+  // the last transaction it processed. If the ID of stream in the
+  // local stream is greater than that it can safely be restarted because the
+  // server guarantees it was not partially processed. Streams that have not
+  // registered an ID haven't actually been sent yet so they can always be
+  // restarted.
+  if (self->mCleanShutdown &&
+      (stream->StreamID() > self->mGoAwayID || !stream->HasRegisteredID()))
+    self->CloseStream(stream, NS_ERROR_NET_RESET); // can be restarted
+  else
+    self->CloseStream(stream, NS_ERROR_ABORT);
+
+  return PL_DHASH_NEXT;
+}
+
+PLDHashOperator
+SpdySession2::GoAwayEnumerator(nsAHttpTransaction *key,
+                               nsAutoPtr<SpdyStream2> &stream,
+                               void *closure)
+{
+  SpdySession2 *self = static_cast<SpdySession2 *>(closure);
+
+  // these streams were not processed by the server and can be restarted.
+  // Do that after the enumerator completes to avoid the risk of
+  // a restart event re-entrantly modifying this hash.
+  if (stream->StreamID() > self->mGoAwayID || !stream->HasRegisteredID())
+    self->mGoAwayStreamsToRestart.Push(stream);
+
+  return PL_DHASH_NEXT;
+}
+
+SpdySession2::~SpdySession2()
+{
+  LOG3(("SpdySession2::~SpdySession2 %p mDownstreamState=%X",
+        this, mDownstreamState));
+
+  inflateEnd(&mDownstreamZlib);
+  deflateEnd(&mUpstreamZlib);
+
+  mStreamTransactionHash.Enumerate(ShutdownEnumerator, this);
+  Telemetry::Accumulate(Telemetry::SPDY_PARALLEL_STREAMS, mConcurrentHighWater);
+  Telemetry::Accumulate(Telemetry::SPDY_REQUEST_PER_CONN, (mNextStreamID - 1) / 2);
+  Telemetry::Accumulate(Telemetry::SPDY_SERVER_INITIATED_STREAMS,
+                        mServerPushedResources);
+}
+
+void
+SpdySession2::LogIO(SpdySession2 *self, SpdyStream2 *stream, const char *label,
+                   const char *data, uint32_t datalen)
+{
+  if (!LOG4_ENABLED())
+    return;
+
+  LOG4(("SpdySession2::LogIO %p stream=%p id=0x%X [%s]",
+        self, stream, stream ? stream->StreamID() : 0, label));
+
+  // Max line is (16 * 3) + 10(prefix) + newline + null
+  char linebuf[128];
+  uint32_t index;
+  char *line = linebuf;
+
+  linebuf[127] = 0;
+
+  for (index = 0; index < datalen; ++index) {
+    if (!(index % 16)) {
+      if (index) {
+        *line = 0;
+        LOG4(("%s", linebuf));
+      }
+      line = linebuf;
+      PR_snprintf(line, 128, "%08X: ", index);
+      line += 10;
+    }
+    PR_snprintf(line, 128 - (line - linebuf), "%02X ",
+                ((unsigned char *)data)[index]);
+    line += 3;
+  }
+  if (index) {
+    *line = 0;
+    LOG4(("%s", linebuf));
+  }
+}
+
+typedef nsresult  (*Control_FX) (SpdySession2 *self);
+static Control_FX sControlFunctions[] =
+{
+  nullptr,
+  SpdySession2::HandleSynStream,
+  SpdySession2::HandleSynReply,
+  SpdySession2::HandleRstStream,
+  SpdySession2::HandleSettings,
+  SpdySession2::HandleNoop,
+  SpdySession2::HandlePing,
+  SpdySession2::HandleGoAway,
+  SpdySession2::HandleHeaders,
+  SpdySession2::HandleWindowUpdate
+};
+
+bool
+SpdySession2::RoomForMoreConcurrent()
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  return (mConcurrent < mMaxConcurrent);
+}
+
+bool
+SpdySession2::RoomForMoreStreams()
+{
+  if (mNextStreamID + mStreamTransactionHash.Count() * 2 > kMaxStreamID)
+    return false;
+
+  return !mShouldGoAway;
+}
+
+PRIntervalTime
+SpdySession2::IdleTime()
+{
+  return PR_IntervalNow() - mLastDataReadEpoch;
+}
+
+void
+SpdySession2::ReadTimeoutTick(PRIntervalTime now)
+{
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+    MOZ_ASSERT(mNextPingID & 1, "Ping Counter Not Odd");
+
+    if (!mPingThreshold)
+      return;
+
+    LOG(("SpdySession2::ReadTimeoutTick %p delta since last read %ds\n",
+         this, PR_IntervalToSeconds(now - mLastReadEpoch)));
+
+    if ((now - mLastReadEpoch) < mPingThreshold) {
+      // recent activity means ping is not an issue
+      if (mPingSentEpoch)
+        mPingSentEpoch = 0;
+      return;
+    }
+
+    if (mPingSentEpoch) {
+      LOG(("SpdySession2::ReadTimeoutTick %p handle outstanding ping\n"));
+      if ((now - mPingSentEpoch) >= gHttpHandler->SpdyPingTimeout()) {
+        LOG(("SpdySession2::ReadTimeoutTick %p Ping Timer Exhaustion\n",
+             this));
+        mPingSentEpoch = 0;
+        Close(NS_ERROR_NET_TIMEOUT);
+      }
+      return;
+    }
+
+    LOG(("SpdySession2::ReadTimeoutTick %p generating ping 0x%x\n",
+         this, mNextPingID));
+
+    if (mNextPingID == 0xffffffff) {
+      LOG(("SpdySession2::ReadTimeoutTick %p cannot form ping - ids exhausted\n",
+           this));
+      return;
+    }
+
+    mPingSentEpoch = PR_IntervalNow();
+    if (!mPingSentEpoch)
+      mPingSentEpoch = 1; // avoid the 0 sentinel value
+    GeneratePing(mNextPingID);
+    mNextPingID += 2;
+    ResumeRecv(); // read the ping reply
+
+    if (mNextPingID == 0xffffffff) {
+      LOG(("SpdySession2::ReadTimeoutTick %p "
+           "ping ids exhausted marking goaway\n", this));
+      mShouldGoAway = true;
+    }
+}
+
+uint32_t
+SpdySession2::RegisterStreamID(SpdyStream2 *stream)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  LOG3(("SpdySession2::RegisterStreamID session=%p stream=%p id=0x%X "
+        "concurrent=%d",this, stream, mNextStreamID, mConcurrent));
+
+  MOZ_ASSERT(mNextStreamID < 0xfffffff0, "should have stopped admitting streams");
+
+  uint32_t result = mNextStreamID;
+  mNextStreamID += 2;
+
+  // We've used up plenty of ID's on this session. Start
+  // moving to a new one before there is a crunch involving
+  // server push streams or concurrent non-registered submits
+  if (mNextStreamID >= kMaxStreamID)
+    mShouldGoAway = true;
+
+  // integrity check
+  if (mStreamIDHash.Get(result)) {
+    LOG3(("   New ID already present\n"));
+    MOZ_ASSERT(false, "New ID already present in mStreamIDHash");
+    mShouldGoAway = true;
+    return kDeadStreamID;
+  }
+
+  mStreamIDHash.Put(result, stream);
+  return result;
+}
+
+bool
+SpdySession2::AddStream(nsAHttpTransaction *aHttpTransaction,
+                       int32_t aPriority)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  // integrity check
+  if (mStreamTransactionHash.Get(aHttpTransaction)) {
+    LOG3(("   New transaction already present\n"));
+    MOZ_ASSERT(false, "AddStream duplicate transaction pointer");
+    return false;
+  }
+
+  aHttpTransaction->SetConnection(this);
+  SpdyStream2 *stream = new SpdyStream2(aHttpTransaction,
+                                      this,
+                                      mSocketTransport,
+                                      mSendingChunkSize,
+                                      &mUpstreamZlib,
+                                      aPriority);
+
+
+  LOG3(("SpdySession2::AddStream session=%p stream=%p NextID=0x%X (tentative)",
+        this, stream, mNextStreamID));
+
+  mStreamTransactionHash.Put(aHttpTransaction, stream);
+
+  if (RoomForMoreConcurrent()) {
+    LOG3(("SpdySession2::AddStream %p stream %p activated immediately.",
+          this, stream));
+    ActivateStream(stream);
+  }
+  else {
+    LOG3(("SpdySession2::AddStream %p stream %p queued.",
+          this, stream));
+    mQueuedStreams.Push(stream);
+  }
+
+  if (!(aHttpTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE)) {
+    LOG3(("SpdySession2::AddStream %p transaction %p forces keep-alive off.\n",
+          this, aHttpTransaction));
+    DontReuse();
+  }
+
+  return true;
+}
+
+void
+SpdySession2::ActivateStream(SpdyStream2 *stream)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  mConcurrent++;
+  if (mConcurrent > mConcurrentHighWater)
+    mConcurrentHighWater = mConcurrent;
+  LOG3(("SpdySession2::AddStream %p activating stream %p Currently %d "
+        "streams in session, high water mark is %d",
+        this, stream, mConcurrent, mConcurrentHighWater));
+
+  mReadyForWrite.Push(stream);
+  SetWriteCallbacks();
+
+  // Kick off the SYN transmit without waiting for the poll loop
+  // This won't work for stream id=1 because there is no segment reader
+  // yet.
+  if (mSegmentReader) {
+    uint32_t countRead;
+    ReadSegments(nullptr, kDefaultBufferSize, &countRead);
+  }
+}
+
+void
+SpdySession2::ProcessPending()
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  while (RoomForMoreConcurrent()) {
+    SpdyStream2 *stream = static_cast<SpdyStream2 *>(mQueuedStreams.PopFront());
+    if (!stream)
+      return;
+    LOG3(("SpdySession2::ProcessPending %p stream %p activated from queue.",
+          this, stream));
+    ActivateStream(stream);
+  }
+}
+
+nsresult
+SpdySession2::NetworkRead(nsAHttpSegmentWriter *writer, char *buf,
+                         uint32_t count, uint32_t *countWritten)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  if (!count) {
+    *countWritten = 0;
+    return NS_OK;
+  }
+
+  nsresult rv = writer->OnWriteSegment(buf, count, countWritten);
+  if (NS_SUCCEEDED(rv) && *countWritten > 0)
+    mLastReadEpoch = PR_IntervalNow();
+  return rv;
+}
+
+void
+SpdySession2::SetWriteCallbacks()
+{
+  if (mConnection && (GetWriteQueueSize() || mOutputQueueUsed))
+      mConnection->ResumeSend();
+}
+
+void
+SpdySession2::RealignOutputQueue()
+{
+  mOutputQueueUsed -= mOutputQueueSent;
+  memmove(mOutputQueueBuffer.get(),
+          mOutputQueueBuffer.get() + mOutputQueueSent,
+          mOutputQueueUsed);
+  mOutputQueueSent = 0;
+}
+
+void
+SpdySession2::FlushOutputQueue()
+{
+  if (!mSegmentReader || !mOutputQueueUsed)
+    return;
+
+  nsresult rv;
+  uint32_t countRead;
+  uint32_t avail = mOutputQueueUsed - mOutputQueueSent;
+
+  rv = mSegmentReader->
+    OnReadSegment(mOutputQueueBuffer.get() + mOutputQueueSent, avail,
+                                     &countRead);
+  LOG3(("SpdySession2::FlushOutputQueue %p sz=%d rv=%x actual=%d",
+        this, avail, rv, countRead));
+
+  // Dont worry about errors on write, we will pick this up as a read error too
+  if (NS_FAILED(rv))
+    return;
+
+  if (countRead == avail) {
+    mOutputQueueUsed = 0;
+    mOutputQueueSent = 0;
+    return;
+  }
+
+  mOutputQueueSent += countRead;
+
+  // If the output queue is close to filling up and we have sent out a good
+  // chunk of data from the beginning then realign it.
+
+  if ((mOutputQueueSent >= kQueueMinimumCleanup) &&
+      ((mOutputQueueSize - mOutputQueueUsed) < kQueueTailRoom)) {
+    RealignOutputQueue();
+  }
+}
+
+void
+SpdySession2::DontReuse()
+{
+  mShouldGoAway = true;
+  if (!mStreamTransactionHash.Count())
+    Close(NS_OK);
+}
+
+uint32_t
+SpdySession2::GetWriteQueueSize()
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  return mUrgentForWrite.GetSize() + mReadyForWrite.GetSize();
+}
+
+void
+SpdySession2::ChangeDownstreamState(enum stateType newState)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  LOG3(("SpdyStream2::ChangeDownstreamState() %p from %X to %X",
+        this, mDownstreamState, newState));
+  mDownstreamState = newState;
+}
+
+void
+SpdySession2::ResetDownstreamState()
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  LOG3(("SpdyStream2::ResetDownstreamState() %p", this));
+  ChangeDownstreamState(BUFFERING_FRAME_HEADER);
+
+  if (mInputFrameDataLast && mInputFrameDataStream) {
+    mInputFrameDataLast = false;
+    if (!mInputFrameDataStream->RecvdFin()) {
+      mInputFrameDataStream->SetRecvdFin(true);
+      --mConcurrent;
+      ProcessPending();
+    }
+  }
+  mInputFrameBufferUsed = 0;
+  mInputFrameDataStream = nullptr;
+}
+
+void
+SpdySession2::EnsureBuffer(nsAutoArrayPtr<char> &buf,
+                          uint32_t newSize,
+                          uint32_t preserve,
+                          uint32_t &objSize)
+{
+  if (objSize >= newSize)
+      return;
+
+  // Leave a little slop on the new allocation - add 2KB to
+  // what we need and then round the result up to a 4KB (page)
+  // boundary.
+
+  objSize = (newSize + 2048 + 4095) & ~4095;
+
+  nsAutoArrayPtr<char> tmp(new char[objSize]);
+  memcpy(tmp, buf, preserve);
+  buf = tmp;
+}
+
+void
+SpdySession2::zlibInit()
+{
+  mDownstreamZlib.zalloc = SpdyStream2::zlib_allocator;
+  mDownstreamZlib.zfree = SpdyStream2::zlib_destructor;
+  mDownstreamZlib.opaque = Z_NULL;
+
+  inflateInit(&mDownstreamZlib);
+
+  mUpstreamZlib.zalloc = SpdyStream2::zlib_allocator;
+  mUpstreamZlib.zfree = SpdyStream2::zlib_destructor;
+  mUpstreamZlib.opaque = Z_NULL;
+
+  // mixing carte blanche compression with tls subjects us to traffic
+  // analysis attacks
+  deflateInit(&mUpstreamZlib, Z_NO_COMPRESSION);
+  deflateSetDictionary(&mUpstreamZlib,
+                       reinterpret_cast<const unsigned char *>
+                       (SpdyStream2::kDictionary),
+                       strlen(SpdyStream2::kDictionary) + 1);
+
+}
+
+nsresult
+SpdySession2::DownstreamUncompress(char *blockStart, uint32_t blockLen)
+{
+  mDecompressBufferUsed = 0;
+
+  mDownstreamZlib.avail_in = blockLen;
+  mDownstreamZlib.next_in = reinterpret_cast<unsigned char *>(blockStart);
+  bool triedDictionary = false;
+
+  do {
+    mDownstreamZlib.next_out =
+      reinterpret_cast<unsigned char *>(mDecompressBuffer.get()) +
+      mDecompressBufferUsed;
+    mDownstreamZlib.avail_out = mDecompressBufferSize - mDecompressBufferUsed;
+    int zlib_rv = inflate(&mDownstreamZlib, Z_NO_FLUSH);
+
+    if (zlib_rv == Z_NEED_DICT) {
+      if (triedDictionary) {
+        LOG3(("SpdySession2::DownstreamUncompress %p Dictionary Error\n", this));
+        return NS_ERROR_FAILURE;
+      }
+
+      triedDictionary = true;
+      inflateSetDictionary(&mDownstreamZlib,
+                           reinterpret_cast<const unsigned char *>
+                           (SpdyStream2::kDictionary),
+                           strlen(SpdyStream2::kDictionary) + 1);
+    }
+
+    if (zlib_rv == Z_DATA_ERROR || zlib_rv == Z_MEM_ERROR)
+      return NS_ERROR_FAILURE;
+
+    mDecompressBufferUsed += mDecompressBufferSize - mDecompressBufferUsed -
+      mDownstreamZlib.avail_out;
+
+    // When there is no more output room, but input still available then
+    // increase the output space
+    if (zlib_rv == Z_OK &&
+        !mDownstreamZlib.avail_out && mDownstreamZlib.avail_in) {
+      LOG3(("SpdySession2::DownstreamUncompress %p Large Headers - so far %d",
+            this, mDecompressBufferSize));
+      EnsureBuffer(mDecompressBuffer,
+                   mDecompressBufferSize + 4096,
+                   mDecompressBufferUsed,
+                   mDecompressBufferSize);
+    }
+  }
+  while (mDownstreamZlib.avail_in);
+  return NS_OK;
+}
+
+nsresult
+SpdySession2::FindHeader(nsCString name,
+                        nsDependentCSubstring &value)
+{
+  const unsigned char *nvpair = reinterpret_cast<unsigned char *>
+    (mDecompressBuffer.get()) + 2;
+  const unsigned char *lastHeaderByte = reinterpret_cast<unsigned char *>
+    (mDecompressBuffer.get()) + mDecompressBufferUsed;
+  if (lastHeaderByte < nvpair)
+    return NS_ERROR_ILLEGAL_VALUE;
+  uint16_t numPairs =
+    PR_ntohs(reinterpret_cast<uint16_t *>(mDecompressBuffer.get())[0]);
+  for (uint16_t index = 0; index < numPairs; ++index) {
+    if (lastHeaderByte < nvpair + 2)
+      return NS_ERROR_ILLEGAL_VALUE;
+    uint32_t nameLen = (nvpair[0] << 8) + nvpair[1];
+    if (lastHeaderByte < nvpair + 2 + nameLen)
+      return NS_ERROR_ILLEGAL_VALUE;
+    nsDependentCSubstring nameString =
+      Substring(reinterpret_cast<const char *>(nvpair) + 2,
+                reinterpret_cast<const char *>(nvpair) + 2 + nameLen);
+    if (lastHeaderByte < nvpair + 4 + nameLen)
+      return NS_ERROR_ILLEGAL_VALUE;
+    uint16_t valueLen = (nvpair[2 + nameLen] << 8) + nvpair[3 + nameLen];
+    if (lastHeaderByte < nvpair + 4 + nameLen + valueLen)
+      return NS_ERROR_ILLEGAL_VALUE;
+    if (nameString.Equals(name)) {
+      value.Assign(((char *)nvpair) + 4 + nameLen, valueLen);
+      return NS_OK;
+    }
+    nvpair += 4 + nameLen + valueLen;
+  }
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult
+SpdySession2::ConvertHeaders(nsDependentCSubstring &status,
+                            nsDependentCSubstring &version)
+{
+  mFlatHTTPResponseHeaders.Truncate();
+  mFlatHTTPResponseHeadersOut = 0;
+  mFlatHTTPResponseHeaders.SetCapacity(mDecompressBufferUsed + 64);
+
+  // Connection, Keep-Alive and chunked transfer encodings are to be
+  // removed.
+
+  // Content-Length is 'advisory'.. we will not strip it because it can
+  // create UI feedback.
+
+  mFlatHTTPResponseHeaders.Append(version);
+  mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING(" "));
+  mFlatHTTPResponseHeaders.Append(status);
+  mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING("\r\n"));
+
+  const unsigned char *nvpair = reinterpret_cast<unsigned char *>
+    (mDecompressBuffer.get()) + 2;
+  const unsigned char *lastHeaderByte = reinterpret_cast<unsigned char *>
+    (mDecompressBuffer.get()) + mDecompressBufferUsed;
+
+  if (lastHeaderByte < nvpair)
+    return NS_ERROR_ILLEGAL_VALUE;
+
+  uint16_t numPairs =
+    PR_ntohs(reinterpret_cast<uint16_t *>(mDecompressBuffer.get())[0]);
+
+  for (uint16_t index = 0; index < numPairs; ++index) {
+    if (lastHeaderByte < nvpair + 2)
+      return NS_ERROR_ILLEGAL_VALUE;
+
+    uint32_t nameLen = (nvpair[0] << 8) + nvpair[1];
+    if (lastHeaderByte < nvpair + 2 + nameLen)
+      return NS_ERROR_ILLEGAL_VALUE;
+
+    nsDependentCSubstring nameString =
+      Substring(reinterpret_cast<const char *>(nvpair) + 2,
+                reinterpret_cast<const char *>(nvpair) + 2 + nameLen);
+
+    if (lastHeaderByte < nvpair + 4 + nameLen)
+      return NS_ERROR_ILLEGAL_VALUE;
+
+    // Look for illegal characters in the nameString.
+    // This includes upper case characters and nulls (as they will
+    // break the fixup-nulls-in-value-string algorithm)
+    // Look for upper case characters in the name. They are illegal.
+    for (char *cPtr = nameString.BeginWriting();
+         cPtr && cPtr < nameString.EndWriting();
+         ++cPtr) {
+      if (*cPtr <= 'Z' && *cPtr >= 'A') {
+        nsCString toLog(nameString);
+
+        LOG3(("SpdySession2::ConvertHeaders session=%p stream=%p "
+              "upper case response header found. [%s]\n",
+              this, mInputFrameDataStream, toLog.get()));
+
+        return NS_ERROR_ILLEGAL_VALUE;
+      }
+
+      // check for null characters
+      if (*cPtr == '\0')
+        return NS_ERROR_ILLEGAL_VALUE;
+    }
+
+    // HTTP Chunked responses are not legal over spdy. We do not need
+    // to look for chunked specifically because it is the only HTTP
+    // allowed default encoding and we did not negotiate further encodings
+    // via TE
+    if (nameString.Equals(NS_LITERAL_CSTRING("transfer-encoding"))) {
+      LOG3(("SpdySession2::ConvertHeaders session=%p stream=%p "
+            "transfer-encoding found. Chunked is invalid and no TE sent.",
+            this, mInputFrameDataStream));
+
+      return NS_ERROR_ILLEGAL_VALUE;
+    }
+
+    uint16_t valueLen = (nvpair[2 + nameLen] << 8) + nvpair[3 + nameLen];
+    if (lastHeaderByte < nvpair + 4 + nameLen + valueLen)
+      return NS_ERROR_ILLEGAL_VALUE;
+
+    if (!nameString.Equals(NS_LITERAL_CSTRING("version")) &&
+        !nameString.Equals(NS_LITERAL_CSTRING("status")) &&
+        !nameString.Equals(NS_LITERAL_CSTRING("connection")) &&
+        !nameString.Equals(NS_LITERAL_CSTRING("keep-alive"))) {
+      nsDependentCSubstring valueString =
+        Substring(reinterpret_cast<const char *>(nvpair) + 4 + nameLen,
+                  reinterpret_cast<const char *>(nvpair) + 4 + nameLen +
+                  valueLen);
+
+      mFlatHTTPResponseHeaders.Append(nameString);
+      mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING(": "));
+
+      // expand nullptr bytes in the value string
+      for (char *cPtr = valueString.BeginWriting();
+           cPtr && cPtr < valueString.EndWriting();
+           ++cPtr) {
+        if (*cPtr != 0) {
+          mFlatHTTPResponseHeaders.Append(*cPtr);
+          continue;
+        }
+
+        // NULLs are really "\r\nhdr: "
+        mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING("\r\n"));
+        mFlatHTTPResponseHeaders.Append(nameString);
+        mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING(": "));
+      }
+
+      mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING("\r\n"));
+    }
+    nvpair += 4 + nameLen + valueLen;
+  }
+
+  mFlatHTTPResponseHeaders.Append(
+    NS_LITERAL_CSTRING("X-Firefox-Spdy: 2\r\n\r\n"));
+  LOG (("decoded response headers are:\n%s",
+        mFlatHTTPResponseHeaders.get()));
+
+  return NS_OK;
+}
+
+void
+SpdySession2::GeneratePing(uint32_t aID)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  LOG3(("SpdySession2::GeneratePing %p 0x%X\n", this, aID));
+
+  EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 12,
+               mOutputQueueUsed, mOutputQueueSize);
+  char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
+  mOutputQueueUsed += 12;
+
+  packet[0] = kFlag_Control;
+  packet[1] = 2;                                  /* version 2 */
+  packet[2] = 0;
+  packet[3] = CONTROL_TYPE_PING;
+  packet[4] = 0;                                  /* flags */
+  packet[5] = 0;
+  packet[6] = 0;
+  packet[7] = 4;                                  /* length */
+
+  aID = PR_htonl(aID);
+  memcpy(packet + 8, &aID, 4);
+
+  FlushOutputQueue();
+}
+
+void
+SpdySession2::GenerateRstStream(uint32_t aStatusCode, uint32_t aID)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  LOG3(("SpdySession2::GenerateRst %p 0x%X %d\n", this, aID, aStatusCode));
+
+  EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 16,
+               mOutputQueueUsed, mOutputQueueSize);
+  char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
+  mOutputQueueUsed += 16;
+
+  packet[0] = kFlag_Control;
+  packet[1] = 2;                                  /* version 2 */
+  packet[2] = 0;
+  packet[3] = CONTROL_TYPE_RST_STREAM;
+  packet[4] = 0;                                  /* flags */
+  packet[5] = 0;
+  packet[6] = 0;
+  packet[7] = 8;                                  /* length */
+
+  aID = PR_htonl(aID);
+  memcpy(packet + 8, &aID, 4);
+  aStatusCode = PR_htonl(aStatusCode);
+  memcpy(packet + 12, &aStatusCode, 4);
+
+  FlushOutputQueue();
+}
+
+void
+SpdySession2::GenerateGoAway()
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  LOG3(("SpdySession2::GenerateGoAway %p\n", this));
+
+  EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 12,
+               mOutputQueueUsed, mOutputQueueSize);
+  char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed;
+  mOutputQueueUsed += 12;
+
+  memset(packet, 0, 12);
+  packet[0] = kFlag_Control;
+  packet[1] = 2;                                  /* version 2 */
+  packet[3] = CONTROL_TYPE_GOAWAY;
+  packet[7] = 4;                                  /* data length */
+
+  // last-good-stream-id are bytes 8-11, when we accept server push this will
+  // need to be set non zero
+
+  FlushOutputQueue();
+}
+
+// perform a bunch of integrity checks on the stream.
+// returns true if passed, false (plus LOG and ABORT) if failed.
+bool
+SpdySession2::VerifyStream(SpdyStream2 *aStream, uint32_t aOptionalID = 0)
+{
+  // This is annoying, but at least it is O(1)
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+#ifndef DEBUG
+  // Only do the real verification in debug builds
+  return true;
+#endif
+
+  if (!aStream)
+    return true;
+
+  uint32_t test = 0;
+
+  do {
+    if (aStream->StreamID() == kDeadStreamID)
+      break;
+
+    nsAHttpTransaction *trans = aStream->Transaction();
+
+    test++;
+    if (!trans)
+      break;
+
+    test++;
+    if (mStreamTransactionHash.Get(trans) != aStream)
+      break;
+
+    if (aStream->StreamID()) {
+      SpdyStream2 *idStream = mStreamIDHash.Get(aStream->StreamID());
+
+      test++;
+      if (idStream != aStream)
+        break;
+
+      if (aOptionalID) {
+        test++;
+        if (idStream->StreamID() != aOptionalID)
+          break;
+      }
+    }
+
+    // tests passed
+    return true;
+  } while (0);
+
+  LOG(("SpdySession %p VerifyStream Failure %p stream->id=0x%x "
+       "optionalID=0x%x trans=%p test=%d\n",
+       this, aStream, aStream->StreamID(),
+       aOptionalID, aStream->Transaction(), test));
+  MOZ_ASSERT(false, "VerifyStream");
+  return false;
+}
+
+void
+SpdySession2::CleanupStream(SpdyStream2 *aStream, nsresult aResult,
+                           rstReason aResetCode)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  LOG3(("SpdySession2::CleanupStream %p %p 0x%x %X\n",
+        this, aStream, aStream->StreamID(), aResult));
+
+  if (!VerifyStream(aStream)) {
+    LOG(("SpdySession2::CleanupStream failed to verify stream\n"));
+    return;
+  }
+
+  if (!aStream->RecvdFin() && aStream->StreamID()) {
+    LOG3(("Stream had not processed recv FIN, sending RST code %X\n",
+          aResetCode));
+    GenerateRstStream(aResetCode, aStream->StreamID());
+    --mConcurrent;
+    ProcessPending();
+  }
+
+  CloseStream(aStream, aResult);
+
+  // Remove the stream from the ID hash table. (this one isn't short, which is
+  // why it is hashed.)
+  mStreamIDHash.Remove(aStream->StreamID());
+
+  // removing from the stream transaction hash will
+  // delete the SpdyStream2 and drop the reference to
+  // its transaction
+  mStreamTransactionHash.Remove(aStream->Transaction());
+
+  if (mShouldGoAway && !mStreamTransactionHash.Count())
+    Close(NS_OK);
+}
+
+void
+SpdySession2::CloseStream(SpdyStream2 *aStream, nsresult aResult)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  LOG3(("SpdySession2::CloseStream %p %p 0x%x %X\n",
+        this, aStream, aStream->StreamID(), aResult));
+
+  // Check if partial frame reader
+  if (aStream == mInputFrameDataStream) {
+    LOG3(("Stream had active partial read frame on close"));
+    ChangeDownstreamState(DISCARDING_DATA_FRAME);
+    mInputFrameDataStream = nullptr;
+  }
+
+  // check the streams blocked on write, this is linear but the list
+  // should be pretty short.
+  uint32_t size = mReadyForWrite.GetSize();
+  for (uint32_t count = 0; count < size; ++count) {
+    SpdyStream2 *stream = static_cast<SpdyStream2 *>(mReadyForWrite.PopFront());
+    if (stream != aStream)
+      mReadyForWrite.Push(stream);
+  }
+
+  // Check the streams blocked on urgent (i.e. window update) writing.
+  // This should also be short.
+  size = mUrgentForWrite.GetSize();
+  for (uint32_t count = 0; count < size; ++count) {
+    SpdyStream2 *stream = static_cast<SpdyStream2 *>(mUrgentForWrite.PopFront());
+    if (stream != aStream)
+      mUrgentForWrite.Push(stream);
+  }
+
+  // Check the streams queued for activation. Because we normally accept a high
+  // level of parallelization this should also be short.
+  size = mQueuedStreams.GetSize();
+  for (uint32_t count = 0; count < size; ++count) {
+    SpdyStream2 *stream = static_cast<SpdyStream2 *>(mQueuedStreams.PopFront());
+    if (stream != aStream)
+      mQueuedStreams.Push(stream);
+  }
+
+  // Send the stream the close() indication
+  aStream->Close(aResult);
+}
+
+nsresult
+SpdySession2::HandleSynStream(SpdySession2 *self)
+{
+  MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_SYN_STREAM);
+
+  if (self->mInputFrameDataSize < 18) {
+    LOG3(("SpdySession2::HandleSynStream %p SYN_STREAM too short data=%d",
+          self, self->mInputFrameDataSize));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  uint32_t streamID =
+    PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]);
+  uint32_t associatedID =
+    PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[3]);
+
+  LOG3(("SpdySession2::HandleSynStream %p recv SYN_STREAM (push) "
+        "for ID 0x%X associated with 0x%X.",
+        self, streamID, associatedID));
+
+  if (streamID & 0x01) {                   // test for odd stream ID
+    LOG3(("SpdySession2::HandleSynStream %p recvd SYN_STREAM id must be even.",
+          self));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  ++(self->mServerPushedResources);
+
+  // Anytime we start using the high bit of stream ID (either client or server)
+  // begin to migrate to a new session.
+  if (streamID >= kMaxStreamID)
+    self->mShouldGoAway = true;
+
+  // Need to decompress the headers even though we aren't using them yet in
+  // order to keep the compression context consistent for other syn_reply frames
+  nsresult rv = self->DownstreamUncompress(self->mInputFrameBuffer + 18,
+                                           self->mInputFrameDataSize - 10);
+  if (NS_FAILED(rv)) {
+    LOG(("SpdySession2::HandleSynStream uncompress failed\n"));
+    return rv;
+  }
+
+  // todo populate cache. For now, just reject server push p3
+  self->GenerateRstStream(RST_REFUSED_STREAM, streamID);
+  self->ResetDownstreamState();
+  return NS_OK;
+}
+
+nsresult
+SpdySession2::SetInputFrameDataStream(uint32_t streamID)
+{
+  mInputFrameDataStream = mStreamIDHash.Get(streamID);
+  if (VerifyStream(mInputFrameDataStream, streamID))
+    return NS_OK;
+
+  LOG(("SpdySession2::SetInputFrameDataStream failed to verify 0x%X\n",
+       streamID));
+  mInputFrameDataStream = nullptr;
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+SpdySession2::HandleSynReply(SpdySession2 *self)
+{
+  MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_SYN_REPLY);
+
+  if (self->mInputFrameDataSize < 8) {
+    LOG3(("SpdySession2::HandleSynReply %p SYN REPLY too short data=%d",
+          self, self->mInputFrameDataSize));
+    // A framing error is a session wide error that cannot be recovered
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  // Uncompress the headers into mDecompressBuffer, leaving them in
+  // spdy format for the time being. Make certain to do this
+  // step before any error handling that might abort the stream but not
+  // the session becuase the session compression context will become
+  // inconsistent if all of the compressed data is not processed.
+  if (NS_FAILED(self->DownstreamUncompress(self->mInputFrameBuffer + 14,
+                                           self->mInputFrameDataSize - 6))) {
+    LOG(("SpdySession2::HandleSynReply uncompress failed\n"));
+    return NS_ERROR_FAILURE;
+  }
+
+  LOG3(("SpdySession2::HandleSynReply %p lookup via streamID in syn_reply.\n",
+        self));
+  uint32_t streamID =
+    PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]);
+  nsresult rv = self->SetInputFrameDataStream(streamID);
+  if (NS_FAILED(rv))
+    return rv;
+
+  if (!self->mInputFrameDataStream) {
+    LOG3(("SpdySession2::HandleSynReply %p lookup streamID in syn_reply "
+          "0x%X failed. NextStreamID = 0x%x", self, streamID,
+          self->mNextStreamID));
+    if (streamID >= self->mNextStreamID)
+      self->GenerateRstStream(RST_INVALID_STREAM, streamID);
+
+    self->ResetDownstreamState();
+    return NS_OK;
+  }
+
+  rv = self->HandleSynReplyForValidStream();
+  if (rv == NS_ERROR_ILLEGAL_VALUE) {
+    LOG3(("SpdySession2::HandleSynReply %p PROTOCOL_ERROR detected 0x%X\n",
+          self, streamID));
+    self->CleanupStream(self->mInputFrameDataStream, rv, RST_PROTOCOL_ERROR);
+    self->ResetDownstreamState();
+    rv = NS_OK;
+  }
+
+  return rv;
+}
+
+// HandleSynReplyForValidStream() returns NS_ERROR_ILLEGAL_VALUE when the stream
+// should be reset with a PROTOCOL_ERROR, NS_OK when the SYN_REPLY was
+// fine, and any other error is fatal to the session.
+nsresult
+SpdySession2::HandleSynReplyForValidStream()
+{
+  if (mInputFrameDataStream->GetFullyOpen()) {
+    // "If an endpoint receives multiple SYN_REPLY frames for the same active
+    // stream ID, it must drop the stream, and send a RST_STREAM for the
+    // stream with the error PROTOCOL_ERROR."
+    //
+    // If the stream is open then just RST_STREAM and move on, otherwise
+    // abort the session
+    return mInputFrameDataStream->RecvdFin() ?
+      NS_ERROR_ALREADY_OPENED : NS_ERROR_ILLEGAL_VALUE;
+  }
+  mInputFrameDataStream->SetFullyOpen();
+
+  mInputFrameDataLast = mInputFrameBuffer[4] & kFlag_Data_FIN;
+
+  if (mInputFrameBuffer[4] & kFlag_Data_UNI) {
+    LOG3(("SynReply had unidirectional flag set on it - nonsensical"));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  LOG3(("SpdySession2::HandleSynReplyForValidStream %p SYN_REPLY for 0x%X "
+        "fin=%d",
+        this, mInputFrameDataStream->StreamID(), mInputFrameDataLast));
+
+  Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE,
+                        mInputFrameDataSize - 6);
+  if (mDecompressBufferUsed) {
+    uint32_t ratio =
+      (mInputFrameDataSize - 6) * 100 / mDecompressBufferUsed;
+    Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio);
+  }
+
+  // status and version are required.
+  nsDependentCSubstring status, version;
+  nsresult rv = FindHeader(NS_LITERAL_CSTRING("status"), status);
+  if (NS_FAILED(rv))
+    return (rv == NS_ERROR_NOT_AVAILABLE) ? NS_ERROR_ILLEGAL_VALUE : rv;
+
+  rv = FindHeader(NS_LITERAL_CSTRING("version"), version);
+  if (NS_FAILED(rv))
+    return (rv == NS_ERROR_NOT_AVAILABLE) ? NS_ERROR_ILLEGAL_VALUE : rv;
+
+  // The spdystream needs to see flattened http headers
+  // Uncompressed spdy format headers currently live in
+  // mDeccompressBuffer - convert that to HTTP format in
+  // mFlatHTTPResponseHeaders in ConvertHeaders()
+
+  rv = ConvertHeaders(status, version);
+  if (NS_FAILED(rv))
+    return rv;
+
+  mInputFrameDataStream->UpdateTransportReadEvents(mInputFrameDataSize);
+  mLastDataReadEpoch = mLastReadEpoch;
+  ChangeDownstreamState(PROCESSING_CONTROL_SYN_REPLY);
+  return NS_OK;
+}
+
+nsresult
+SpdySession2::HandleRstStream(SpdySession2 *self)
+{
+  MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_RST_STREAM);
+
+  if (self->mInputFrameDataSize != 8) {
+    LOG3(("SpdySession2::HandleRstStream %p RST_STREAM wrong length data=%d",
+          self, self->mInputFrameDataSize));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  uint8_t flags = reinterpret_cast<uint8_t *>(self->mInputFrameBuffer.get())[4];
+
+  uint32_t streamID =
+    PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]);
+
+  self->mDownstreamRstReason =
+    PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[3]);
+
+  LOG3(("SpdySession2::HandleRstStream %p RST_STREAM Reason Code %u ID %x "
+        "flags %x", self, self->mDownstreamRstReason, streamID, flags));
+
+  if (flags != 0) {
+    LOG3(("SpdySession2::HandleRstStream %p RST_STREAM with flags is illegal",
+          self));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  if (self->mDownstreamRstReason == RST_INVALID_STREAM ||
+      self->mDownstreamRstReason == RST_FLOW_CONTROL_ERROR) {
+    // basically just ignore this
+    self->ResetDownstreamState();
+    return NS_OK;
+  }
+
+  nsresult rv = self->SetInputFrameDataStream(streamID);
+
+  if (!self->mInputFrameDataStream) {
+    if (NS_FAILED(rv))
+      LOG(("SpdySession2::HandleRstStream %p lookup streamID for RST Frame "
+           "0x%X failed reason = %d :: VerifyStream Failed\n", self, streamID,
+           self->mDownstreamRstReason));
+
+    LOG3(("SpdySession2::HandleRstStream %p lookup streamID for RST Frame "
+          "0x%X failed reason = %d", self, streamID,
+          self->mDownstreamRstReason));
+
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  self->ChangeDownstreamState(PROCESSING_CONTROL_RST_STREAM);
+  return NS_OK;
+}
+
+nsresult
+SpdySession2::HandleSettings(SpdySession2 *self)
+{
+  MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_SETTINGS);
+
+  if (self->mInputFrameDataSize < 4) {
+    LOG3(("SpdySession2::HandleSettings %p SETTINGS wrong length data=%d",
+          self, self->mInputFrameDataSize));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  uint32_t numEntries =
+    PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]);
+
+  // Ensure frame is large enough for supplied number of entries
+  // Each entry is 8 bytes, frame data is reduced by 4 to account for
+  // the NumEntries value.
+  if ((self->mInputFrameDataSize - 4) < (numEntries * 8)) {
+    LOG3(("SpdySession2::HandleSettings %p SETTINGS wrong length data=%d",
+          self, self->mInputFrameDataSize));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  LOG3(("SpdySession2::HandleSettings %p SETTINGS Control Frame with %d entries",
+        self, numEntries));
+
+  for (uint32_t index = 0; index < numEntries; ++index) {
+    // To clarify the v2 spec:
+    // Each entry is a 24 bits of a little endian id
+    // followed by 8 bits of flags
+    // followed by a 32 bit big endian value
+
+    unsigned char *setting = reinterpret_cast<unsigned char *>
+      (self->mInputFrameBuffer.get()) + 12 + index * 8;
+
+    uint32_t id = (setting[2] << 16) + (setting[1] << 8) + setting[0];
+    uint32_t flags = setting[3];
+    uint32_t value =  PR_ntohl(reinterpret_cast<uint32_t *>(setting)[1]);
+
+    LOG3(("Settings ID %d, Flags %X, Value %d", id, flags, value));
+
+    switch (id)
+    {
+    case SETTINGS_TYPE_UPLOAD_BW:
+      Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_UL_BW, value);
+      break;
+
+    case SETTINGS_TYPE_DOWNLOAD_BW:
+      Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_DL_BW, value);
+      break;
+
+    case SETTINGS_TYPE_RTT:
+      Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_RTT, value);
+      break;
+
+    case SETTINGS_TYPE_MAX_CONCURRENT:
+      self->mMaxConcurrent = value;
+      Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_MAX_STREAMS, value);
+      break;
+
+    case SETTINGS_TYPE_CWND:
+      Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_CWND, value);
+      break;
+
+    case SETTINGS_TYPE_DOWNLOAD_RETRANS_RATE:
+      Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_RETRANS, value);
+      break;
+
+    case SETTINGS_TYPE_INITIAL_WINDOW:
+      Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_IW, value >> 10);
+      break;
+
+    default:
+      break;
+    }
+
+  }
+
+  self->ResetDownstreamState();
+  return NS_OK;
+}
+
+nsresult
+SpdySession2::HandleNoop(SpdySession2 *self)
+{
+  MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_NOOP);
+
+  if (self->mInputFrameDataSize != 0) {
+    LOG3(("SpdySession2::HandleNoop %p NOP had data %d",
+          self, self->mInputFrameDataSize));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  LOG3(("SpdySession2::HandleNoop %p NOP.", self));
+
+  self->ResetDownstreamState();
+  return NS_OK;
+}
+
+nsresult
+SpdySession2::HandlePing(SpdySession2 *self)
+{
+  MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_PING);
+
+  if (self->mInputFrameDataSize != 4) {
+    LOG3(("SpdySession2::HandlePing %p PING had wrong amount of data %d",
+          self, self->mInputFrameDataSize));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  uint32_t pingID =
+    PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]);
+
+  LOG3(("SpdySession2::HandlePing %p PING ID 0x%X.", self, pingID));
+
+  if (pingID & 0x01) {
+    // presumably a reply to our timeout ping
+    self->mPingSentEpoch = 0;
+  }
+  else {
+    // Servers initiate even numbered pings, go ahead and echo it back
+    self->GeneratePing(pingID);
+  }
+
+  self->ResetDownstreamState();
+  return NS_OK;
+}
+
+nsresult
+SpdySession2::HandleGoAway(SpdySession2 *self)
+{
+  MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_GOAWAY);
+
+  if (self->mInputFrameDataSize != 4) {
+    LOG3(("SpdySession2::HandleGoAway %p GOAWAY had wrong amount of data %d",
+          self, self->mInputFrameDataSize));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  self->mShouldGoAway = true;
+  self->mGoAwayID =
+    PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]);
+  self->mCleanShutdown = true;
+
+  // Find streams greater than the last-good ID and mark them for deletion
+  // in the mGoAwayStreamsToRestart queue with the GoAwayEnumerator. They can
+  // be restarted.
+  self->mStreamTransactionHash.Enumerate(GoAwayEnumerator, self);
+
+  // Process the streams marked for deletion and restart.
+  uint32_t size = self->mGoAwayStreamsToRestart.GetSize();
+  for (uint32_t count = 0; count < size; ++count) {
+    SpdyStream2 *stream =
+      static_cast<SpdyStream2 *>(self->mGoAwayStreamsToRestart.PopFront());
+
+    self->CloseStream(stream, NS_ERROR_NET_RESET);
+    if (stream->HasRegisteredID())
+      self->mStreamIDHash.Remove(stream->StreamID());
+    self->mStreamTransactionHash.Remove(stream->Transaction());
+  }
+
+  // Queued streams can also be deleted from this session and restarted
+  // in another one. (they were never sent on the network so they implicitly
+  // are not covered by the last-good id.
+  size = self->mQueuedStreams.GetSize();
+  for (uint32_t count = 0; count < size; ++count) {
+    SpdyStream2 *stream =
+      static_cast<SpdyStream2 *>(self->mQueuedStreams.PopFront());
+    self->CloseStream(stream, NS_ERROR_NET_RESET);
+    self->mStreamTransactionHash.Remove(stream->Transaction());
+  }
+
+  LOG3(("SpdySession2::HandleGoAway %p GOAWAY Last-Good-ID 0x%X."
+        "live streams=%d\n", self, self->mGoAwayID,
+        self->mStreamTransactionHash.Count()));
+  self->ResumeRecv();
+  self->ResetDownstreamState();
+  return NS_OK;
+}
+
+nsresult
+SpdySession2::HandleHeaders(SpdySession2 *self)
+{
+  MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_HEADERS);
+
+  if (self->mInputFrameDataSize < 10) {
+    LOG3(("SpdySession2::HandleHeaders %p HEADERS had wrong amount of data %d",
+          self, self->mInputFrameDataSize));
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  uint32_t streamID =
+    PR_ntohl(reinterpret_cast<uint32_t *>(self->mInputFrameBuffer.get())[2]);
+
+  // this is actually not legal in the HTTP mapping of SPDY. All
+  // headers are in the syn or syn reply. Log and ignore it.
+
+  // in v3 this will be legal and we must remember to note
+  // NS_NET_STATUS_RECEIVING_FROM from it
+
+  LOG3(("SpdySession2::HandleHeaders %p HEADERS for Stream 0x%X. "
+        "They are ignored in the HTTP/SPDY mapping.",
+        self, streamID));
+  self->mLastDataReadEpoch = self->mLastReadEpoch;
+  self->ResetDownstreamState();
+  return NS_OK;
+}
+
+nsresult
+SpdySession2::HandleWindowUpdate(SpdySession2 *self)
+{
+  MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_WINDOW_UPDATE);
+
+  LOG3(("SpdySession2::HandleWindowUpdate %p WINDOW UPDATE was "
+        "received. WINDOW UPDATE is no longer defined in v2. Ignoring.",
+        self));
+
+  self->ResetDownstreamState();
+  return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpTransaction. It is expected that nsHttpConnection is the caller
+// of these methods
+//-----------------------------------------------------------------------------
+
+void
+SpdySession2::OnTransportStatus(nsITransport* aTransport,
+                               nsresult aStatus,
+                               uint64_t aProgress)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  switch (aStatus) {
+    // These should appear only once, deliver to the first
+    // transaction on the session.
+  case NS_NET_STATUS_RESOLVING_HOST:
+  case NS_NET_STATUS_RESOLVED_HOST:
+  case NS_NET_STATUS_CONNECTING_TO:
+  case NS_NET_STATUS_CONNECTED_TO:
+  {
+    SpdyStream2 *target = mStreamIDHash.Get(1);
+    if (target)
+      target->Transaction()->OnTransportStatus(aTransport, aStatus, aProgress);
+    break;
+  }
+
+  default:
+    // The other transport events are ignored here because there is no good
+    // way to map them to the right transaction in spdy. Instead, the events
+    // are generated again from the spdy code and passed directly to the
+    // correct transaction.
+
+    // NS_NET_STATUS_SENDING_TO:
+    // This is generated by the socket transport when (part) of
+    // a transaction is written out
+    //
+    // There is no good way to map it to the right transaction in spdy,
+    // so it is ignored here and generated separately when the SYN_STREAM
+    // is sent from SpdyStream2::TransmitFrame
+
+    // NS_NET_STATUS_WAITING_FOR:
+    // Created by nsHttpConnection when the request has been totally sent.
+    // There is no good way to map it to the right transaction in spdy,
+    // so it is ignored here and generated separately when the same
+    // condition is complete in SpdyStream2 when there is no more
+    // request body left to be transmitted.
+
+    // NS_NET_STATUS_RECEIVING_FROM
+    // Generated in spdysession whenever we read a data frame or a syn_reply
+    // that can be attributed to a particular stream/transaction
+
+    break;
+  }
+}
+
+// ReadSegments() is used to write data to the network. Generally, HTTP
+// request data is pulled from the approriate transaction and
+// converted to SPDY data. Sometimes control data like window-update are
+// generated instead.
+
+nsresult
+SpdySession2::ReadSegments(nsAHttpSegmentReader *reader,
+                          uint32_t count,
+                          uint32_t *countRead)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  MOZ_ASSERT(!mSegmentReader || !reader || (mSegmentReader == reader),
+             "Inconsistent Write Function Callback");
+
+  if (reader)
+    mSegmentReader = reader;
+
+  nsresult rv;
+  *countRead = 0;
+
+  // First priority goes to frames that were writing to the network but were
+  // blocked part way through. Then to frames that have no streams (e.g ping
+  // reply) and then third to streams marked urgent (generally they have
+  // window updates), and finally to streams generally
+  // ready to send data frames (http requests).
+
+  LOG3(("SpdySession2::ReadSegments %p", this));
+
+  SpdyStream2 *stream;
+
+  stream = static_cast<SpdyStream2 *>(mUrgentForWrite.PopFront());
+  if (!stream)
+    stream = static_cast<SpdyStream2 *>(mReadyForWrite.PopFront());
+  if (!stream) {
+    LOG3(("SpdySession2 %p could not identify a stream to write; suspending.",
+          this));
+    FlushOutputQueue();
+    SetWriteCallbacks();
+    return NS_BASE_STREAM_WOULD_BLOCK;
+  }
+
+  LOG3(("SpdySession2 %p will write from SpdyStream2 %p", this, stream));
+
+  rv = stream->ReadSegments(this, count, countRead);
+
+  // Not every permutation of stream->ReadSegents produces data (and therefore
+  // tries to flush the output queue) - SENDING_FIN_STREAM can be an example
+  // of that. But we might still have old data buffered that would be good
+  // to flush.
+  FlushOutputQueue();
+
+  if (stream->RequestBlockedOnRead()) {
+
+    // We are blocked waiting for input - either more http headers or
+    // any request body data. When more data from the request stream
+    // becomes available the httptransaction will call conn->ResumeSend().
+
+    LOG3(("SpdySession2::ReadSegments %p dealing with block on read", this));
+
+    // call readsegments again if there are other streams ready
+    // to run in this session
+    if (GetWriteQueueSize())
+      rv = NS_OK;
+    else
+      rv = NS_BASE_STREAM_WOULD_BLOCK;
+    SetWriteCallbacks();
+    return rv;
+  }
+
+  if (NS_FAILED(rv)) {
+    LOG3(("SpdySession2::ReadSegments %p returning FAIL code %X",
+          this, rv));
+    if (rv != NS_BASE_STREAM_WOULD_BLOCK)
+      CleanupStream(stream, rv, RST_CANCEL);
+    return rv;
+  }
+
+  if (*countRead > 0) {
+    LOG3(("SpdySession2::ReadSegments %p stream=%p generated end of frame %d",
+          this, stream, *countRead));
+    mReadyForWrite.Push(stream);
+    SetWriteCallbacks();
+    return rv;
+  }
+
+  LOG3(("SpdySession2::ReadSegments %p stream=%p stream send complete",
+        this, stream));
+
+  /* we now want to recv data */
+  ResumeRecv();
+
+  // call readsegments again if there are other streams ready
+  // to go in this session
+  SetWriteCallbacks();
+
+  return rv;
+}
+
+// WriteSegments() is used to read data off the socket. Generally this is
+// just the SPDY frame header and from there the appropriate SPDYStream
+// is identified from the Stream-ID. The http transaction associated with
+// that read then pulls in the data directly, which it will feed to
+// OnWriteSegment(). That function will gateway it into http and feed
+// it to the appropriate transaction.
+
+// we call writer->OnWriteSegment via NetworkRead() to get a spdy header..
+// and decide if it is data or control.. if it is control, just deal with it.
+// if it is data, identify the spdy stream
+// call stream->WriteSegemnts which can call this::OnWriteSegment to get the
+// data. It always gets full frames if they are part of the stream
+
+nsresult
+SpdySession2::WriteSegments(nsAHttpSegmentWriter *writer,
+                           uint32_t count,
+                           uint32_t *countWritten)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  nsresult rv;
+  *countWritten = 0;
+
+  if (mClosed)
+    return NS_ERROR_FAILURE;
+
+  SetWriteCallbacks();
+
+  // We buffer all control frames and act on them in this layer.
+  // We buffer the first 8 bytes of data frames (the header) but
+  // the actual data is passed through unprocessed.
+
+  if (mDownstreamState == BUFFERING_FRAME_HEADER) {
+    // The first 8 bytes of every frame is header information that
+    // we are going to want to strip before passing to http. That is
+    // true of both control and data packets.
+
+    MOZ_ASSERT(mInputFrameBufferUsed < 8,
+               "Frame Buffer Used Too Large for State");
+
+    rv = NetworkRead(writer, mInputFrameBuffer + mInputFrameBufferUsed,
+                     8 - mInputFrameBufferUsed, countWritten);
+
+    if (NS_FAILED(rv)) {
+      LOG3(("SpdySession2 %p buffering frame header read failure %x\n",
+            this, rv));
+      // maybe just blocked reading from network
+      if (rv == NS_BASE_STREAM_WOULD_BLOCK)
+        ResumeRecv();
+      return rv;
+    }
+
+    LogIO(this, nullptr, "Reading Frame Header",
+          mInputFrameBuffer + mInputFrameBufferUsed, *countWritten);
+
+    mInputFrameBufferUsed += *countWritten;
+
+    if (mInputFrameBufferUsed < 8)
+    {
+      LOG3(("SpdySession2::WriteSegments %p "
+            "BUFFERING FRAME HEADER incomplete size=%d",
+            this, mInputFrameBufferUsed));
+      return rv;
+    }
+
+    // For both control and data frames the second 32 bit word of the header
+    // is 8-flags, 24-length. (network byte order)
+    mInputFrameDataSize =
+      PR_ntohl(reinterpret_cast<uint32_t *>(mInputFrameBuffer.get())[1]);
+    mInputFrameDataSize &= 0x00ffffff;
+    mInputFrameDataRead = 0;
+
+    if (mInputFrameBuffer[0] & kFlag_Control) {
+      EnsureBuffer(mInputFrameBuffer, mInputFrameDataSize + 8, 8,
+                   mInputFrameBufferSize);
+      ChangeDownstreamState(BUFFERING_CONTROL_FRAME);
+
+      // The first 32 bit word of the header is
+      // 1 ctrl - 15 version - 16 type
+      uint16_t version =
+        PR_ntohs(reinterpret_cast<uint16_t *>(mInputFrameBuffer.get())[0]);
+      version &= 0x7fff;
+
+      mFrameControlType =
+        PR_ntohs(reinterpret_cast<uint16_t *>(mInputFrameBuffer.get())[1]);
+
+      LOG3(("SpdySession2::WriteSegments %p - Control Frame Identified "
+            "type %d version %d data len %d",
+            this, mFrameControlType, version, mInputFrameDataSize));
+
+      if (mFrameControlType >= CONTROL_TYPE_LAST ||
+          mFrameControlType <= CONTROL_TYPE_FIRST)
+        return NS_ERROR_ILLEGAL_VALUE;
+
+      // The protocol document says this value must be 1 even though this
+      // is known as version 2.. Testing interop indicates that is a typo
+      // in the protocol document
+      if (version != 2) {
+        return NS_ERROR_ILLEGAL_VALUE;
+      }
+    }
+    else {
+      ChangeDownstreamState(PROCESSING_DATA_FRAME);
+
+      uint32_t streamID =
+        PR_ntohl(reinterpret_cast<uint32_t *>(mInputFrameBuffer.get())[0]);
+      rv = SetInputFrameDataStream(streamID);
+      if (NS_FAILED(rv)) {
+        LOG(("SpdySession2::WriteSegments %p lookup streamID 0x%X failed. "
+              "probably due to verification.\n", this, streamID));
+        return rv;
+      }
+      if (!mInputFrameDataStream) {
+        LOG3(("SpdySession2::WriteSegments %p lookup streamID 0x%X failed. "
+              "Next = 0x%x", this, streamID, mNextStreamID));
+        if (streamID >= mNextStreamID)
+          GenerateRstStream(RST_INVALID_STREAM, streamID);
+        ChangeDownstreamState(DISCARDING_DATA_FRAME);
+      }
+      mInputFrameDataLast = (mInputFrameBuffer[4] & kFlag_Data_FIN);
+      Telemetry::Accumulate(Telemetry::SPDY_CHUNK_RECVD,
+                            mInputFrameDataSize >> 10);
+      LOG3(("Start Processing Data Frame. "
+            "Session=%p Stream ID 0x%x Stream Ptr %p Fin=%d Len=%d",
+            this, streamID, mInputFrameDataStream, mInputFrameDataLast,
+            mInputFrameDataSize));
+      mLastDataReadEpoch = mLastReadEpoch;
+
+      if (mInputFrameBuffer[4] & kFlag_Data_ZLIB) {
+        LOG3(("Data flag has ZLIB flag set which is not valid >=2 spdy"));
+        return NS_ERROR_ILLEGAL_VALUE;
+      }
+    }
+  }
+
+  if (mDownstreamState == PROCESSING_CONTROL_RST_STREAM) {
+    if (mDownstreamRstReason == RST_REFUSED_STREAM)
+      rv = NS_ERROR_NET_RESET;            //we can retry this 100% safely
+    else if (mDownstreamRstReason == RST_CANCEL ||
+             mDownstreamRstReason == RST_PROTOCOL_ERROR ||
+             mDownstreamRstReason == RST_INTERNAL_ERROR ||
+             mDownstreamRstReason == RST_UNSUPPORTED_VERSION)
+      rv = NS_ERROR_NET_INTERRUPT;
+    else
+      rv = NS_ERROR_ILLEGAL_VALUE;
+
+    if (mDownstreamRstReason != RST_REFUSED_STREAM &&
+        mDownstreamRstReason != RST_CANCEL)
+      mShouldGoAway = true;
+
+    // mInputFrameDataStream is reset by ChangeDownstreamState
+    SpdyStream2 *stream = mInputFrameDataStream;
+    ResetDownstreamState();
+    LOG3(("SpdySession2::WriteSegments cleanup stream on recv of rst "
+          "session=%p stream=%p 0x%X\n", this, stream,
+          stream ? stream->StreamID() : 0));
+    CleanupStream(stream, rv, RST_CANCEL);
+    return NS_OK;
+  }
+
+  if (mDownstreamState == PROCESSING_DATA_FRAME ||
+      mDownstreamState == PROCESSING_CONTROL_SYN_REPLY) {
+
+    // The cleanup stream should only be set while stream->WriteSegments is
+    // on the stack and then cleaned up in this code block afterwards.
+    MOZ_ASSERT(!mNeedsCleanup, "cleanup stream set unexpectedly");
+    mNeedsCleanup = nullptr;                     /* just in case */
+
+    mSegmentWriter = writer;
+    rv = mInputFrameDataStream->WriteSegments(this, count, countWritten);
+    mSegmentWriter = nullptr;
+
+    mLastDataReadEpoch = mLastReadEpoch;
+
+    if (rv == NS_BASE_STREAM_CLOSED) {
+      // This will happen when the transaction figures out it is EOF, generally
+      // due to a content-length match being made
+      SpdyStream2 *stream = mInputFrameDataStream;
+      if (mInputFrameDataRead == mInputFrameDataSize)
+        ResetDownstreamState();
+      LOG3(("SpdySession2::WriteSegments session=%p stream=%p 0x%X "
+            "needscleanup=%p. cleanup stream based on "
+            "stream->writeSegments returning BASE_STREAM_CLOSED\n",
+            this, stream, stream ? stream->StreamID() : 0,
+            mNeedsCleanup));
+      CleanupStream(stream, NS_OK, RST_CANCEL);
+      MOZ_ASSERT(!mNeedsCleanup, "double cleanup out of data frame");
+      mNeedsCleanup = nullptr;                     /* just in case */
+      return NS_OK;
+    }
+
+    if (mNeedsCleanup) {
+      LOG3(("SpdySession2::WriteSegments session=%p stream=%p 0x%X "
+            "cleanup stream based on mNeedsCleanup.\n",
+            this, mNeedsCleanup, mNeedsCleanup ? mNeedsCleanup->StreamID() : 0));
+      CleanupStream(mNeedsCleanup, NS_OK, RST_CANCEL);
+      mNeedsCleanup = nullptr;
+    }
+
+    // In v3 this is where we would generate a window update
+
+    return rv;
+  }
+
+  if (mDownstreamState == DISCARDING_DATA_FRAME) {
+    char trash[4096];
+    uint32_t count = std::min(4096U, mInputFrameDataSize - mInputFrameDataRead);
+
+    if (!count) {
+      ResetDownstreamState();
+      ResumeRecv();
+      return NS_BASE_STREAM_WOULD_BLOCK;
+    }
+
+    rv = NetworkRead(writer, trash, count, countWritten);
+
+    if (NS_FAILED(rv)) {
+      LOG3(("SpdySession2 %p discard frame read failure %x\n", this, rv));
+      // maybe just blocked reading from network
+      if (rv == NS_BASE_STREAM_WOULD_BLOCK)
+        ResumeRecv();
+      return rv;
+    }
+
+    LogIO(this, nullptr, "Discarding Frame", trash, *countWritten);
+
+    mInputFrameDataRead += *countWritten;
+
+    if (mInputFrameDataRead == mInputFrameDataSize)
+      ResetDownstreamState();
+    return rv;
+  }
+
+  if (mDownstreamState != BUFFERING_CONTROL_FRAME) {
+    // this cannot happen
+    MOZ_ASSERT(false, "Not in Bufering Control Frame State");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  MOZ_ASSERT(mInputFrameBufferUsed == 8,
+             "Frame Buffer Header Not Present");
+
+  rv = NetworkRead(writer, mInputFrameBuffer + 8 + mInputFrameDataRead,
+                   mInputFrameDataSize - mInputFrameDataRead, countWritten);
+
+  if (NS_FAILED(rv)) {
+    LOG3(("SpdySession2 %p buffering control frame read failure %x\n",
+          this, rv));
+    // maybe just blocked reading from network
+    if (rv == NS_BASE_STREAM_WOULD_BLOCK)
+      ResumeRecv();
+    return rv;
+  }
+
+  LogIO(this, nullptr, "Reading Control Frame",
+        mInputFrameBuffer + 8 + mInputFrameDataRead, *countWritten);
+
+  mInputFrameDataRead += *countWritten;
+
+  if (mInputFrameDataRead != mInputFrameDataSize)
+    return NS_OK;
+
+  // This check is actually redundant, the control type was previously
+  // checked to make sure it was in range, but we will check it again
+  // at time of use to make sure a regression doesn't creep in.
+  if (mFrameControlType >= CONTROL_TYPE_LAST ||
+      mFrameControlType <= CONTROL_TYPE_FIRST)
+  {
+    MOZ_ASSERT(false, "control type out of range");
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+  rv = sControlFunctions[mFrameControlType](this);
+
+  MOZ_ASSERT(NS_FAILED(rv) ||
+             mDownstreamState != BUFFERING_CONTROL_FRAME,
+             "Control Handler returned OK but did not change state");
+
+  if (mShouldGoAway && !mStreamTransactionHash.Count())
+    Close(NS_OK);
+  return rv;
+}
+
+void
+SpdySession2::Close(nsresult aReason)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  if (mClosed)
+    return;
+
+  LOG3(("SpdySession2::Close %p %X", this, aReason));
+
+  mClosed = true;
+
+  mStreamTransactionHash.Enumerate(ShutdownEnumerator, this);
+  mStreamIDHash.Clear();
+  mStreamTransactionHash.Clear();
+
+  if (NS_SUCCEEDED(aReason))
+    GenerateGoAway();
+  mConnection = nullptr;
+  mSegmentReader = nullptr;
+  mSegmentWriter = nullptr;
+}
+
+void
+SpdySession2::CloseTransaction(nsAHttpTransaction *aTransaction,
+                              nsresult aResult)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  LOG3(("SpdySession2::CloseTransaction %p %p %x", this, aTransaction, aResult));
+
+  // Generally this arrives as a cancel event from the connection manager.
+
+  // need to find the stream and call CleanupStream() on it.
+  SpdyStream2 *stream = mStreamTransactionHash.Get(aTransaction);
+  if (!stream) {
+    LOG3(("SpdySession2::CloseTransaction %p %p %x - not found.",
+          this, aTransaction, aResult));
+    return;
+  }
+  LOG3(("SpdySession2::CloseTranscation probably a cancel. "
+        "this=%p, trans=%p, result=%x, streamID=0x%X stream=%p",
+        this, aTransaction, aResult, stream->StreamID(), stream));
+  CleanupStream(stream, aResult, RST_CANCEL);
+  ResumeRecv();
+}
+
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentReader
+//-----------------------------------------------------------------------------
+
+nsresult
+SpdySession2::OnReadSegment(const char *buf,
+                           uint32_t count,
+                           uint32_t *countRead)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  nsresult rv;
+
+  // If we can release old queued data then we can try and write the new
+  // data directly to the network without using the output queue at all
+  if (mOutputQueueUsed)
+    FlushOutputQueue();
+
+  if (!mOutputQueueUsed && mSegmentReader) {
+    // try and write directly without output queue
+    rv = mSegmentReader->OnReadSegment(buf, count, countRead);
+
+    if (rv == NS_BASE_STREAM_WOULD_BLOCK)
+      *countRead = 0;
+    else if (NS_FAILED(rv))
+      return rv;
+
+    if (*countRead < count) {
+      uint32_t required = count - *countRead;
+      // assuming a commitment() happened, this ensurebuffer is a nop
+      // but just in case the queuesize is too small for the required data
+      // call ensurebuffer().
+      EnsureBuffer(mOutputQueueBuffer, required, 0, mOutputQueueSize);
+      memcpy(mOutputQueueBuffer.get(), buf + *countRead, required);
+      mOutputQueueUsed = required;
+    }
+
+    *countRead = count;
+    return NS_OK;
+  }
+
+  // At this point we are going to buffer the new data in the output
+  // queue if it fits. By coalescing multiple small submissions into one larger
+  // buffer we can get larger writes out to the network later on.
+
+  // This routine should not be allowed to fill up the output queue
+  // all on its own - at least kQueueReserved bytes are always left
+  // for other routines to use - but this is an all-or-nothing function,
+  // so if it will not all fit just return WOULD_BLOCK
+
+  if ((mOutputQueueUsed + count) > (mOutputQueueSize - kQueueReserved))
+    return NS_BASE_STREAM_WOULD_BLOCK;
+
+  memcpy(mOutputQueueBuffer.get() + mOutputQueueUsed, buf, count);
+  mOutputQueueUsed += count;
+  *countRead = count;
+
+  FlushOutputQueue();
+
+  return NS_OK;
+}
+
+nsresult
+SpdySession2::CommitToSegmentSize(uint32_t count, bool forceCommitment)
+{
+  if (mOutputQueueUsed)
+    FlushOutputQueue();
+
+  // would there be enough room to buffer this if needed?
+  if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
+    return NS_OK;
+
+  // if we are using part of our buffers already, try again later unless
+  // forceCommitment is set.
+  if (mOutputQueueUsed && !forceCommitment)
+    return NS_BASE_STREAM_WOULD_BLOCK;
+
+  if (mOutputQueueUsed) {
+    // normally we avoid the memmove of RealignOutputQueue, but we'll try
+    // it if forceCommitment is set before growing the buffer.
+    RealignOutputQueue();
+
+    // is there enough room now?
+    if ((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved))
+      return NS_OK;
+  }
+
+  // resize the buffers as needed
+  EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + count + kQueueReserved,
+               mOutputQueueUsed, mOutputQueueSize);
+
+  MOZ_ASSERT((mOutputQueueUsed + count) <= (mOutputQueueSize - kQueueReserved),
+             "buffer not as large as expected");
+
+  return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentWriter
+//-----------------------------------------------------------------------------
+
+nsresult
+SpdySession2::OnWriteSegment(char *buf,
+                            uint32_t count,
+                            uint32_t *countWritten)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  nsresult rv;
+
+  if (!mSegmentWriter) {
+    // the only way this could happen would be if Close() were called on the
+    // stack with WriteSegments()
+    return NS_ERROR_FAILURE;
+  }
+
+  if (mDownstreamState == PROCESSING_DATA_FRAME) {
+
+    if (mInputFrameDataLast &&
+        mInputFrameDataRead == mInputFrameDataSize) {
+      *countWritten = 0;
+      SetNeedsCleanup();
+      return NS_BASE_STREAM_CLOSED;
+    }
+
+    count = std::min(count, mInputFrameDataSize - mInputFrameDataRead);
+    rv = NetworkRead(mSegmentWriter, buf, count, countWritten);
+    if (NS_FAILED(rv))
+      return rv;
+
+    LogIO(this, mInputFrameDataStream, "Reading Data Frame",
+          buf, *countWritten);
+
+    mInputFrameDataRead += *countWritten;
+
+    mInputFrameDataStream->UpdateTransportReadEvents(*countWritten);
+    if ((mInputFrameDataRead == mInputFrameDataSize) && !mInputFrameDataLast)
+      ResetDownstreamState();
+
+    return rv;
+  }
+
+  if (mDownstreamState == PROCESSING_CONTROL_SYN_REPLY) {
+
+    if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut &&
+        mInputFrameDataLast) {
+      *countWritten = 0;
+      SetNeedsCleanup();
+      return NS_BASE_STREAM_CLOSED;
+    }
+
+    count = std::min(count,
+                   mFlatHTTPResponseHeaders.Length() -
+                   mFlatHTTPResponseHeadersOut);
+    memcpy(buf,
+           mFlatHTTPResponseHeaders.get() + mFlatHTTPResponseHeadersOut,
+           count);
+    mFlatHTTPResponseHeadersOut += count;
+    *countWritten = count;
+
+    if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut &&
+        !mInputFrameDataLast)
+      ResetDownstreamState();
+    return NS_OK;
+  }
+
+  return NS_ERROR_UNEXPECTED;
+}
+
+void
+SpdySession2::SetNeedsCleanup()
+{
+  LOG3(("SpdySession2::SetNeedsCleanup %p - recorded downstream fin of "
+        "stream %p 0x%X", this, mInputFrameDataStream,
+        mInputFrameDataStream->StreamID()));
+
+  // This will result in Close() being called
+  MOZ_ASSERT(!mNeedsCleanup, "mNeedsCleanup unexpectedly set");
+  mNeedsCleanup = mInputFrameDataStream;
+  ResetDownstreamState();
+}
+
+//-----------------------------------------------------------------------------
+// Modified methods of nsAHttpConnection
+//-----------------------------------------------------------------------------
+
+void
+SpdySession2::TransactionHasDataToWrite(nsAHttpTransaction *caller)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  LOG3(("SpdySession2::TransactionHasDataToWrite %p trans=%p", this, caller));
+
+  // a trapped signal from the http transaction to the connection that
+  // it is no longer blocked on read.
+
+  SpdyStream2 *stream = mStreamTransactionHash.Get(caller);
+  if (!stream || !VerifyStream(stream)) {
+    LOG3(("SpdySession2::TransactionHasDataToWrite %p caller %p not found",
+          this, caller));
+    return;
+  }
+
+  LOG3(("SpdySession2::TransactionHasDataToWrite %p ID is %x",
+        this, stream->StreamID()));
+
+  mReadyForWrite.Push(stream);
+}
+
+void
+SpdySession2::TransactionHasDataToWrite(SpdyStream2 *stream)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  LOG3(("SpdySession2::TransactionHasDataToWrite %p stream=%p ID=%x",
+        this, stream, stream->StreamID()));
+
+  mReadyForWrite.Push(stream);
+  SetWriteCallbacks();
+}
+
+bool
+SpdySession2::IsPersistent()
+{
+  return true;
+}
+
+nsresult
+SpdySession2::TakeTransport(nsISocketTransport **,
+                           nsIAsyncInputStream **,
+                           nsIAsyncOutputStream **)
+{
+  MOZ_ASSERT(false, "TakeTransport of SpdySession2");
+  return NS_ERROR_UNEXPECTED;
+}
+
+nsHttpConnection *
+SpdySession2::TakeHttpConnection()
+{
+  MOZ_ASSERT(false, "TakeHttpConnection of SpdySession2");
+  return nullptr;
+}
+
+uint32_t
+SpdySession2::CancelPipeline(nsresult reason)
+{
+  // we don't pipeline inside spdy, so this isn't an issue
+  return 0;
+}
+
+nsAHttpTransaction::Classifier
+SpdySession2::Classification()
+{
+  if (!mConnection)
+    return nsAHttpTransaction::CLASS_GENERAL;
+  return mConnection->Classification();
+}
+
+//-----------------------------------------------------------------------------
+// unused methods of nsAHttpTransaction
+// We can be sure of this because SpdySession2 is only constructed in
+// nsHttpConnection and is never passed out of that object
+//-----------------------------------------------------------------------------
+
+void
+SpdySession2::SetConnection(nsAHttpConnection *)
+{
+  // This is unexpected
+  MOZ_ASSERT(false, "SpdySession2::SetConnection()");
+}
+
+void
+SpdySession2::GetSecurityCallbacks(nsIInterfaceRequestor **)
+{
+  // This is unexpected
+  MOZ_ASSERT(false, "SpdySession2::GetSecurityCallbacks()");
+}
+
+void
+SpdySession2::SetProxyConnectFailed()
+{
+  MOZ_ASSERT(false, "SpdySession2::SetProxyConnectFailed()");
+}
+
+bool
+SpdySession2::IsDone()
+{
+  return !mStreamTransactionHash.Count();
+}
+
+nsresult
+SpdySession2::Status()
+{
+  MOZ_ASSERT(false, "SpdySession2::Status()");
+  return NS_ERROR_UNEXPECTED;
+}
+
+uint32_t
+SpdySession2::Caps()
+{
+  MOZ_ASSERT(false, "SpdySession2::Caps()");
+  return 0;
+}
+
+uint64_t
+SpdySession2::Available()
+{
+  MOZ_ASSERT(false, "SpdySession2::Available()");
+  return 0;
+}
+
+nsHttpRequestHead *
+SpdySession2::RequestHead()
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  MOZ_ASSERT(false,
+             "SpdySession2::RequestHead() "
+             "should not be called after SPDY is setup");
+  return nullptr;
+}
+
+uint32_t
+SpdySession2::Http1xTransactionCount()
+{
+  return 0;
+}
+
+// used as an enumerator by TakeSubTransactions()
+static PLDHashOperator
+TakeStream(nsAHttpTransaction *key,
+           nsAutoPtr<SpdyStream2> &stream,
+           void *closure)
+{
+  nsTArray<nsRefPtr<nsAHttpTransaction> > *list =
+    static_cast<nsTArray<nsRefPtr<nsAHttpTransaction> > *>(closure);
+
+  list->AppendElement(key);
+
+  // removing the stream from the hash will delete the stream
+  // and drop the transaction reference the hash held
+  return PL_DHASH_REMOVE;
+}
+
+nsresult
+SpdySession2::TakeSubTransactions(
+    nsTArray<nsRefPtr<nsAHttpTransaction> > &outTransactions)
+{
+  // Generally this cannot be done with spdy as transactions are
+  // started right away.
+
+  LOG3(("SpdySession2::TakeSubTransactions %p\n", this));
+
+  if (mConcurrentHighWater > 0)
+    return NS_ERROR_ALREADY_OPENED;
+
+  LOG3(("   taking %d\n", mStreamTransactionHash.Count()));
+
+  mStreamTransactionHash.Enumerate(TakeStream, &outTransactions);
+  return NS_OK;
+}
+
+nsresult
+SpdySession2::AddTransaction(nsAHttpTransaction *)
+{
+  // This API is meant for pipelining, SpdySession2's should be
+  // extended with AddStream()
+
+  MOZ_ASSERT(false,
+             "SpdySession2::AddTransaction() should not be called");
+
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+uint32_t
+SpdySession2::PipelineDepth()
+{
+  return IsDone() ? 0 : 1;
+}
+
+nsresult
+SpdySession2::SetPipelinePosition(int32_t position)
+{
+  // This API is meant for pipelining, SpdySession2's should be
+  // extended with AddStream()
+
+  MOZ_ASSERT(false,
+             "SpdySession2::SetPipelinePosition() should not be called");
+
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+int32_t
+SpdySession2::PipelinePosition()
+{
+    return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Pass through methods of nsAHttpConnection
+//-----------------------------------------------------------------------------
+
+nsAHttpConnection *
+SpdySession2::Connection()
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  return mConnection;
+}
+
+nsresult
+SpdySession2::OnHeadersAvailable(nsAHttpTransaction *transaction,
+                                nsHttpRequestHead *requestHead,
+                                nsHttpResponseHead *responseHead,
+                                bool *reset)
+{
+  return mConnection->OnHeadersAvailable(transaction,
+                                         requestHead,
+                                         responseHead,
+                                         reset);
+}
+
+bool
+SpdySession2::IsReused()
+{
+  return mConnection->IsReused();
+}
+
+nsresult
+SpdySession2::PushBack(const char *buf, uint32_t len)
+{
+  return mConnection->PushBack(buf, len);
+}
+
+} // namespace mozilla::net
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/SpdySession2.h
@@ -0,0 +1,349 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_SpdySession2_h
+#define mozilla_net_SpdySession2_h
+
+// SPDY as defined by
+// http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2
+
+#include "ASpdySession.h"
+#include "nsAHttpConnection.h"
+#include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
+#include "nsDeque.h"
+#include "nsHashKeys.h"
+#include "zlib.h"
+#include "mozilla/Attributes.h"
+
+class nsISocketTransport;
+
+namespace mozilla { namespace net {
+
+class SpdyStream2;
+
+class SpdySession2 MOZ_FINAL : public ASpdySession
+                             , public nsAHttpConnection
+                             , public nsAHttpSegmentReader
+                             , public nsAHttpSegmentWriter
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSAHTTPTRANSACTION
+  NS_DECL_NSAHTTPCONNECTION(mConnection)
+  NS_DECL_NSAHTTPSEGMENTREADER
+  NS_DECL_NSAHTTPSEGMENTWRITER
+
+  SpdySession2(nsAHttpTransaction *, nsISocketTransport *, int32_t);
+  ~SpdySession2();
+
+  bool AddStream(nsAHttpTransaction *, int32_t);
+  bool CanReuse() { return !mShouldGoAway && !mClosed; }
+  bool RoomForMoreStreams();
+
+  // When the connection is active this is called every 1 second
+  void ReadTimeoutTick(PRIntervalTime now);
+
+  // Idle time represents time since "goodput".. e.g. a data or header frame
+  PRIntervalTime IdleTime();
+
+  uint32_t RegisterStreamID(SpdyStream2 *);
+
+  const static uint8_t kFlag_Control   = 0x80;
+
+  const static uint8_t kFlag_Data_FIN  = 0x01;
+  const static uint8_t kFlag_Data_UNI  = 0x02;
+  const static uint8_t kFlag_Data_ZLIB = 0x02;
+
+  // The protocol document for v2 specifies that the
+  // highest value (3) is the highest priority, but in
+  // reality 0 is the highest priority.
+  //
+  // Draft 3 notes here https://sites.google.com/a/chromium.org/dev/spdy/spdy-protocol/
+  // are the best guide to the mistake. Also see
+  // GetLowestPriority() and GetHighestPriority() in spdy_framer.h of
+  // chromium source.
+
+  const static uint8_t kPri00   = 0 << 6; // highest
+  const static uint8_t kPri01   = 1 << 6;
+  const static uint8_t kPri02   = 2 << 6;
+  const static uint8_t kPri03   = 3 << 6; // lowest
+
+  enum
+  {
+    CONTROL_TYPE_FIRST = 0,
+    CONTROL_TYPE_SYN_STREAM = 1,
+    CONTROL_TYPE_SYN_REPLY = 2,
+    CONTROL_TYPE_RST_STREAM = 3,
+    CONTROL_TYPE_SETTINGS = 4,
+    CONTROL_TYPE_NOOP = 5,
+    CONTROL_TYPE_PING = 6,
+    CONTROL_TYPE_GOAWAY = 7,
+    CONTROL_TYPE_HEADERS = 8,
+    CONTROL_TYPE_WINDOW_UPDATE = 9,               /* no longer in v2 */
+    CONTROL_TYPE_LAST = 10
+  };
+
+  enum rstReason
+  {
+    RST_PROTOCOL_ERROR = 1,
+    RST_INVALID_STREAM = 2,
+    RST_REFUSED_STREAM = 3,
+    RST_UNSUPPORTED_VERSION = 4,
+    RST_CANCEL = 5,
+    RST_INTERNAL_ERROR = 6,
+    RST_FLOW_CONTROL_ERROR = 7,
+    RST_BAD_ASSOC_STREAM = 8
+  };
+
+  enum
+  {
+    SETTINGS_TYPE_UPLOAD_BW = 1, // kb/s
+    SETTINGS_TYPE_DOWNLOAD_BW = 2, // kb/s
+    SETTINGS_TYPE_RTT = 3, // ms
+    SETTINGS_TYPE_MAX_CONCURRENT = 4, // streams
+    SETTINGS_TYPE_CWND = 5, // packets
+    SETTINGS_TYPE_DOWNLOAD_RETRANS_RATE = 6, // percentage
+    SETTINGS_TYPE_INITIAL_WINDOW = 7  // bytes. Not used in v2.
+  };
+
+  // This should be big enough to hold all of your control packets,
+  // but if it needs to grow for huge headers it can do so dynamically.
+  // About 1% of responses from SPDY google services seem to be > 1000
+  // with all less than 2000 when compression is enabled.
+  const static uint32_t kDefaultBufferSize = 2048;
+
+  // kDefaultQueueSize must be >= other queue size constants
+  const static uint32_t kDefaultQueueSize =  32768;
+  const static uint32_t kQueueMinimumCleanup = 24576;
+  const static uint32_t kQueueTailRoom    =  4096;
+  const static uint32_t kQueueReserved    =  1024;
+
+  const static uint32_t kDefaultMaxConcurrent = 100;
+  const static uint32_t kMaxStreamID = 0x7800000;
+
+  // This is a sentinel for a deleted stream. It is not a valid
+  // 31 bit stream ID.
+  const static uint32_t kDeadStreamID = 0xffffdead;
+
+  static nsresult HandleSynStream(SpdySession2 *);
+  static nsresult HandleSynReply(SpdySession2 *);
+  static nsresult HandleRstStream(SpdySession2 *);
+  static nsresult HandleSettings(SpdySession2 *);
+  static nsresult HandleNoop(SpdySession2 *);
+  static nsresult HandlePing(SpdySession2 *);
+  static nsresult HandleGoAway(SpdySession2 *);
+  static nsresult HandleHeaders(SpdySession2 *);
+  static nsresult HandleWindowUpdate(SpdySession2 *);
+
+  static void EnsureBuffer(nsAutoArrayPtr<char> &,
+                           uint32_t, uint32_t, uint32_t &);
+
+  // For writing the SPDY data stream to LOG4
+  static void LogIO(SpdySession2 *, SpdyStream2 *, const char *,
+                    const char *, uint32_t);
+
+  // an overload of nsAHttpConnection
+  void TransactionHasDataToWrite(nsAHttpTransaction *);
+
+  // a similar version for SpdyStream2
+  void TransactionHasDataToWrite(SpdyStream2 *);
+
+  // an overload of nsAHttpSegementReader
+  virtual nsresult CommitToSegmentSize(uint32_t size, bool forceCommitment);
+
+  void     PrintDiagnostics (nsCString &log);
+
+private:
+
+  enum stateType {
+    BUFFERING_FRAME_HEADER,
+    BUFFERING_CONTROL_FRAME,
+    PROCESSING_DATA_FRAME,
+    DISCARDING_DATA_FRAME,
+    PROCESSING_CONTROL_SYN_REPLY,
+    PROCESSING_CONTROL_RST_STREAM
+  };
+
+  nsresult    HandleSynReplyForValidStream();
+  uint32_t    GetWriteQueueSize();
+  void        ChangeDownstreamState(enum stateType);
+  void        ResetDownstreamState();
+  nsresult    DownstreamUncompress(char *, uint32_t);
+  void        zlibInit();
+  nsresult    FindHeader(nsCString, nsDependentCSubstring &);
+  nsresult    ConvertHeaders(nsDependentCSubstring &,
+                             nsDependentCSubstring &);
+  void        GeneratePing(uint32_t);
+  void        GenerateRstStream(uint32_t, uint32_t);
+  void        GenerateGoAway();
+  void        CleanupStream(SpdyStream2 *, nsresult, rstReason);
+  void        CloseStream(SpdyStream2 *, nsresult);
+
+  void        SetWriteCallbacks();
+  void        FlushOutputQueue();
+  void        RealignOutputQueue();
+
+  bool        RoomForMoreConcurrent();
+  void        ActivateStream(SpdyStream2 *);
+  void        ProcessPending();
+  nsresult    SetInputFrameDataStream(uint32_t);
+  bool        VerifyStream(SpdyStream2 *, uint32_t);
+  void        SetNeedsCleanup();
+
+  // a wrapper for all calls to the nshttpconnection level segment writer. Used
+  // to track network I/O for timeout purposes
+  nsresult   NetworkRead(nsAHttpSegmentWriter *, char *, uint32_t, uint32_t *);
+
+  static PLDHashOperator ShutdownEnumerator(nsAHttpTransaction *,
+                                            nsAutoPtr<SpdyStream2> &,
+                                            void *);
+
+  static PLDHashOperator GoAwayEnumerator(nsAHttpTransaction *,
+                                          nsAutoPtr<SpdyStream2> &,
+                                          void *);
+
+  // This is intended to be nsHttpConnectionMgr:nsHttpConnectionHandle taken
+  // from the first transaction on this session. That object contains the
+  // pointer to the real network-level nsHttpConnection object.
+  nsRefPtr<nsAHttpConnection> mConnection;
+
+  // The underlying socket transport object is needed to propogate some events
+  nsISocketTransport         *mSocketTransport;
+
+  // These are temporary state variables to hold the argument to
+  // Read/WriteSegments so it can be accessed by On(read/write)segment
+  // further up the stack.
+  nsAHttpSegmentReader       *mSegmentReader;
+  nsAHttpSegmentWriter       *mSegmentWriter;
+
+  uint32_t          mSendingChunkSize;        /* the transmission chunk size */
+  uint32_t          mNextStreamID;            /* 24 bits */
+  uint32_t          mConcurrentHighWater;     /* max parallelism on session */
+
+  stateType         mDownstreamState; /* in frame, between frames, etc..  */
+
+  // Maintain 5 indexes - one by stream ID, one by transaction ptr,
+  // one list of streams ready to write, one list of streams that are queued
+  // due to max parallelism settings, and one list of streams
+  // that must be given priority to write for window updates. The objects
+  // are not ref counted - they get destroyed
+  // by the nsClassHashtable implementation when they are removed from
+  // there.
+  nsDataHashtable<nsUint32HashKey, SpdyStream2 *>     mStreamIDHash;
+  nsClassHashtable<nsPtrHashKey<nsAHttpTransaction>,
+                   SpdyStream2>                       mStreamTransactionHash;
+  nsDeque                                             mReadyForWrite;
+  nsDeque                                             mQueuedStreams;
+
+  // UrgentForWrite is meant to carry window updates. They were defined in
+  // the v2 spec but apparently never implemented so are now scheduled to
+  // be removed. But they will be reintroduced for v3, so we will leave
+  // this queue in place to ease that transition.
+  nsDeque           mUrgentForWrite;
+
+  // Compression contexts for header transport using deflate.
+  // SPDY compresses only HTTP headers and does not reset zlib in between
+  // frames.
+  z_stream            mDownstreamZlib;
+  z_stream            mUpstreamZlib;
+
+  // mInputFrameBuffer is used to store received control packets and the 8 bytes
+  // of header on data packets
+  uint32_t             mInputFrameBufferSize;
+  uint32_t             mInputFrameBufferUsed;
+  nsAutoArrayPtr<char> mInputFrameBuffer;
+
+  // mInputFrameDataSize/Read are used for tracking the amount of data consumed
+  // in a data frame. the data itself is not buffered in spdy
+  // The frame size is mInputFrameDataSize + the constant 8 byte header
+  uint32_t             mInputFrameDataSize;
+  uint32_t             mInputFrameDataRead;
+  bool                 mInputFrameDataLast; // This frame was marked FIN
+
+  // When a frame has been received that is addressed to a particular stream
+  // (e.g. a data frame after the stream-id has been decoded), this points
+  // to the stream.
+  SpdyStream2          *mInputFrameDataStream;
+
+  // mNeedsCleanup is a state variable to defer cleanup of a closed stream
+  // If needed, It is set in session::OnWriteSegments() and acted on and
+  // cleared when the stack returns to session::WriteSegments(). The stream
+  // cannot be destroyed directly out of OnWriteSegments because
+  // stream::writeSegments() is on the stack at that time.
+  SpdyStream2          *mNeedsCleanup;
+
+  // The CONTROL_TYPE value for a control frame
+  uint32_t             mFrameControlType;
+
+  // This reason code in the last processed RESET frame
+  uint32_t             mDownstreamRstReason;
+
+  // These are used for decompressing downstream spdy response headers
+  // This is done at the session level because sometimes the stream
+  // has already been canceled but the decompression still must happen
+  // to keep the zlib state correct for the next state of headers.
+  uint32_t             mDecompressBufferSize;
+  uint32_t             mDecompressBufferUsed;
+  nsAutoArrayPtr<char> mDecompressBuffer;
+
+  // for the conversion of downstream http headers into spdy formatted headers
+  nsCString            mFlatHTTPResponseHeaders;
+  uint32_t             mFlatHTTPResponseHeadersOut;
+
+  // when set, the session will go away when it reaches 0 streams. This flag
+  // is set when: the stream IDs are running out (at either the client or the
+  // server), when DontReuse() is called, a RST that is not specific to a
+  // particular stream is received, a GOAWAY frame has been received from
+  // the server.
+  bool                 mShouldGoAway;
+
+  // the session has received a nsAHttpTransaction::Close()  call
+  bool                 mClosed;
+
+  // the session received a GoAway frame with a valid GoAwayID
+  bool                 mCleanShutdown;
+
+  // If a GoAway message was received this is the ID of the last valid
+  // stream. 0 otherwise. (0 is never a valid stream id.)
+  uint32_t             mGoAwayID;
+
+  // The limit on number of concurrent streams for this session. Normally it
+  // is basically unlimited, but the SETTINGS control message from the
+  // server might bring it down.
+  uint32_t             mMaxConcurrent;
+
+  // The actual number of concurrent streams at this moment. Generally below
+  // mMaxConcurrent, but the max can be lowered in real time to a value
+  // below the current value
+  uint32_t             mConcurrent;
+
+  // The number of server initiated SYN-STREAMS, tracked for telemetry
+  uint32_t             mServerPushedResources;
+
+  // This is a output queue of bytes ready to be written to the SSL stream.
+  // When that streams returns WOULD_BLOCK on direct write the bytes get
+  // coalesced together here. This results in larger writes to the SSL layer.
+  // The buffer is not dynamically grown to accomodate stream writes, but
+  // does expand to accept infallible session wide frames like GoAway and RST.
+  uint32_t             mOutputQueueSize;
+  uint32_t             mOutputQueueUsed;
+  uint32_t             mOutputQueueSent;
+  nsAutoArrayPtr<char> mOutputQueueBuffer;
+
+  PRIntervalTime       mPingThreshold;
+  PRIntervalTime       mLastReadEpoch;     // used for ping timeouts
+  PRIntervalTime       mLastDataReadEpoch; // used for IdleTime()
+  PRIntervalTime       mPingSentEpoch;
+  uint32_t             mNextPingID;
+
+  // used as a temporary buffer while enumerating the stream hash during GoAway
+  nsDeque  mGoAwayStreamsToRestart;
+};
+
+}} // namespace mozilla::net
+
+#endif // mozilla_net_SpdySession2_h
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/SpdyStream2.cpp
@@ -0,0 +1,873 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// HttpLog.h should generally be included first
+#include "HttpLog.h"
+
+#include "nsHttp.h"
+#include "SpdySession2.h"
+#include "SpdyStream2.h"
+#include "prnetdb.h"
+#include "nsHttpRequestHead.h"
+#include "mozilla/Telemetry.h"
+#include "nsISocketTransport.h"
+#include "nsISupportsPriority.h"
+#include "nsHttpHandler.h"
+#include <algorithm>
+
+#ifdef DEBUG
+// defined by the socket transport service while active
+extern PRThread *gSocketThread;
+#endif
+
+namespace mozilla {
+namespace net {
+
+SpdyStream2::SpdyStream2(nsAHttpTransaction *httpTransaction,
+                       SpdySession2 *spdySession,
+                       nsISocketTransport *socketTransport,
+                       uint32_t chunkSize,
+                       z_stream *compressionContext,
+                       int32_t priority)
+  : mUpstreamState(GENERATING_SYN_STREAM),
+    mTransaction(httpTransaction),
+    mSession(spdySession),
+    mSocketTransport(socketTransport),
+    mSegmentReader(nullptr),
+    mSegmentWriter(nullptr),
+    mStreamID(0),
+    mChunkSize(chunkSize),
+    mSynFrameComplete(0),
+    mRequestBlockedOnRead(0),
+    mSentFinOnData(0),
+    mRecvdFin(0),
+    mFullyOpen(0),
+    mSentWaitingFor(0),
+    mSetTCPSocketBuffer(0),
+    mTxInlineFrameSize(SpdySession2::kDefaultBufferSize),
+    mTxInlineFrameUsed(0),
+    mTxStreamFrameSize(0),
+    mZlib(compressionContext),
+    mRequestBodyLenRemaining(0),
+    mPriority(priority),
+    mTotalSent(0),
+    mTotalRead(0)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  LOG3(("SpdyStream2::SpdyStream2 %p", this));
+
+  mTxInlineFrame = new char[mTxInlineFrameSize];
+}
+
+SpdyStream2::~SpdyStream2()
+{
+  mStreamID = SpdySession2::kDeadStreamID;
+}
+
+// ReadSegments() is used to write data down the socket. Generally, HTTP
+// request data is pulled from the approriate transaction and
+// converted to SPDY data. Sometimes control data like a window-update is
+// generated instead.
+
+nsresult
+SpdyStream2::ReadSegments(nsAHttpSegmentReader *reader,
+                         uint32_t count,
+                         uint32_t *countRead)
+{
+  LOG3(("SpdyStream2 %p ReadSegments reader=%p count=%d state=%x",
+        this, reader, count, mUpstreamState));
+
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  nsresult rv = NS_ERROR_UNEXPECTED;
+  mRequestBlockedOnRead = 0;
+
+  switch (mUpstreamState) {
+  case GENERATING_SYN_STREAM:
+  case GENERATING_REQUEST_BODY:
+  case SENDING_REQUEST_BODY:
+    // Call into the HTTP Transaction to generate the HTTP request
+    // stream. That stream will show up in OnReadSegment().
+    mSegmentReader = reader;
+    rv = mTransaction->ReadSegments(this, count, countRead);
+    mSegmentReader = nullptr;
+
+    // Check to see if the transaction's request could be written out now.
+    // If not, mark the stream for callback when writing can proceed.
+    if (NS_SUCCEEDED(rv) &&
+        mUpstreamState == GENERATING_SYN_STREAM &&
+        !mSynFrameComplete)
+      mSession->TransactionHasDataToWrite(this);
+
+    // mTxinlineFrameUsed represents any queued un-sent frame. It might
+    // be 0 if there is no such frame, which is not a gurantee that we
+    // don't have more request body to send - just that any data that was
+    // sent comprised a complete SPDY frame. Likewise, a non 0 value is
+    // a queued, but complete, spdy frame length.
+
+    // Mark that we are blocked on read if the http transaction needs to
+    // provide more of the request message body and there is nothing queued
+    // for writing
+    if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mTxInlineFrameUsed)
+      mRequestBlockedOnRead = 1;
+
+    if (!mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) {
+      LOG3(("ReadSegments %p: Sending request data complete, mUpstreamState=%x",
+            this, mUpstreamState));
+      if (mSentFinOnData) {
+        ChangeState(UPSTREAM_COMPLETE);
+      }
+      else {
+        GenerateDataFrameHeader(0, true);
+        ChangeState(SENDING_FIN_STREAM);
+        mSession->TransactionHasDataToWrite(this);
+        rv = NS_BASE_STREAM_WOULD_BLOCK;
+      }
+    }
+
+    break;
+
+  case SENDING_FIN_STREAM:
+    // We were trying to send the FIN-STREAM but were blocked from
+    // sending it out - try again.
+    if (!mSentFinOnData) {
+      mSegmentReader = reader;
+      rv = TransmitFrame(nullptr, nullptr, false);
+      mSegmentReader = nullptr;
+      MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
+                 "Transmit Frame should be all or nothing");
+      if (NS_SUCCEEDED(rv))
+        ChangeState(UPSTREAM_COMPLETE);
+    }
+    else {
+      rv = NS_OK;
+      mTxInlineFrameUsed = 0;         // cancel fin data packet
+      ChangeState(UPSTREAM_COMPLETE);
+    }
+
+    *countRead = 0;
+
+    // don't change OK to WOULD BLOCK. we are really done sending if OK
+    break;
+
+  case UPSTREAM_COMPLETE:
+    *countRead = 0;
+    rv = NS_OK;
+    break;
+
+  default:
+    MOZ_ASSERT(false, "SpdyStream2::ReadSegments unknown state");
+    break;
+  }
+
+  return rv;
+}
+
+// WriteSegments() is used to read data off the socket. Generally this is
+// just the SPDY frame header and from there the appropriate SPDYStream
+// is identified from the Stream-ID. The http transaction associated with
+// that read then pulls in the data directly.
+
+nsresult
+SpdyStream2::WriteSegments(nsAHttpSegmentWriter *writer,
+                          uint32_t count,
+                          uint32_t *countWritten)
+{
+  LOG3(("SpdyStream2::WriteSegments %p count=%d state=%x",
+        this, count, mUpstreamState));
+
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  MOZ_ASSERT(!mSegmentWriter, "segment writer in progress");
+
+  mSegmentWriter = writer;
+  nsresult rv = mTransaction->WriteSegments(writer, count, countWritten);
+  mSegmentWriter = nullptr;
+  return rv;
+}
+
+PLDHashOperator
+SpdyStream2::hdrHashEnumerate(const nsACString &key,
+                             nsAutoPtr<nsCString> &value,
+                             void *closure)
+{
+  SpdyStream2 *self = static_cast<SpdyStream2 *>(closure);
+
+  self->CompressToFrame(key);
+  self->CompressToFrame(value.get());
+  return PL_DHASH_NEXT;
+}
+
+nsresult
+SpdyStream2::ParseHttpRequestHeaders(const char *buf,
+                                    uint32_t avail,
+                                    uint32_t *countUsed)
+{
+  // Returns NS_OK even if the headers are incomplete
+  // set mSynFrameComplete flag if they are complete
+
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  MOZ_ASSERT(mUpstreamState == GENERATING_SYN_STREAM);
+
+  LOG3(("SpdyStream2::ParseHttpRequestHeaders %p avail=%d state=%x",
+        this, avail, mUpstreamState));
+
+  mFlatHttpRequestHeaders.Append(buf, avail);
+
+  // We can use the simple double crlf because firefox is the
+  // only client we are parsing
+  int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n");
+
+  if (endHeader == kNotFound) {
+    // We don't have all the headers yet
+    LOG3(("SpdyStream2::ParseHttpRequestHeaders %p "
+          "Need more header bytes. Len = %d",
+          this, mFlatHttpRequestHeaders.Length()));
+    *countUsed = avail;
+    return NS_OK;
+  }
+
+  // We have recvd all the headers, trim the local
+  // buffer of the final empty line, and set countUsed to reflect
+  // the whole header has been consumed.
+  uint32_t oldLen = mFlatHttpRequestHeaders.Length();
+  mFlatHttpRequestHeaders.SetLength(endHeader + 2);
+  *countUsed = avail - (oldLen - endHeader) + 4;
+  mSynFrameComplete = 1;
+
+  // It is now OK to assign a streamID that we are assured will
+  // be monotonically increasing amongst syn-streams on this
+  // session
+  mStreamID = mSession->RegisterStreamID(this);
+  MOZ_ASSERT(mStreamID & 1,
+             "Spdy Stream Channel ID must be odd");
+
+  if (mStreamID >= 0x80000000) {
+    // streamID must fit in 31 bits. This is theoretically possible
+    // because stream ID assignment is asynchronous to stream creation
+    // because of the protocol requirement that the ID in syn-stream
+    // be monotonically increasing. In reality this is really not possible
+    // because new streams stop being added to a session with 0x10000000 / 2
+    // IDs still available and no race condition is going to bridge that gap,
+    // so we can be comfortable on just erroring out for correctness in that
+    // case.
+    LOG3(("Stream assigned out of range ID: 0x%X", mStreamID));
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  // Now we need to convert the flat http headers into a set
+  // of SPDY headers..  writing to mTxInlineFrame{sz}
+
+  mTxInlineFrame[0] = SpdySession2::kFlag_Control;
+  mTxInlineFrame[1] = 2;                          /* version */
+  mTxInlineFrame[2] = 0;
+  mTxInlineFrame[3] = SpdySession2::CONTROL_TYPE_SYN_STREAM;
+  // 4 to 7 are length and flags, we'll fill that in later
+
+  uint32_t networkOrderID = PR_htonl(mStreamID);
+  memcpy(mTxInlineFrame + 8, &networkOrderID, 4);
+
+  // this is the associated-to field, which is not used sending
+  // from the client in the http binding
+  memset (mTxInlineFrame + 12, 0, 4);
+
+  // Priority flags are the C0 mask of byte 16.
+  //
+  // The other 6 bits of 16 are unused. Spdy/3 will expand
+  // priority to 3 bits.
+  //
+  // When Spdy/3 implements WINDOW_UPDATE the lowest priority
+  // streams over a threshold (32?) should be given tiny
+  // receive windows, separate from their spdy priority
+  //
+  if (mPriority >= nsISupportsPriority::PRIORITY_LOW)
+    mTxInlineFrame[16] = SpdySession2::kPri03;
+  else if (mPriority >= nsISupportsPriority::PRIORITY_NORMAL)
+    mTxInlineFrame[16] = SpdySession2::kPri02;
+  else if (mPriority >= nsISupportsPriority::PRIORITY_HIGH)
+    mTxInlineFrame[16] = SpdySession2::kPri01;
+  else
+    mTxInlineFrame[16] = SpdySession2::kPri00;
+
+  mTxInlineFrame[17] = 0;                         /* unused */
+
+  const char *methodHeader = mTransaction->RequestHead()->Method().get();
+
+  nsCString hostHeader;
+  mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader);
+
+  nsCString versionHeader;
+  if (mTransaction->RequestHead()->Version() == NS_HTTP_VERSION_1_1)
+    versionHeader = NS_LITERAL_CSTRING("HTTP/1.1");
+  else
+    versionHeader = NS_LITERAL_CSTRING("HTTP/1.0");
+
+  // use mRequestHead() to get a sense of how big to make the hash,
+  // even though we are parsing the actual text stream because
+  // it is legit to append headers.
+  nsClassHashtable<nsCStringHashKey, nsCString>
+    hdrHash(1 + (mTransaction->RequestHead()->Headers().Count() * 2));
+
+  const char *beginBuffer = mFlatHttpRequestHeaders.BeginReading();
+
+  // need to hash all the headers together to remove duplicates, special
+  // headers, etc..
+
+  int32_t crlfIndex = mFlatHttpRequestHeaders.Find("\r\n");
+  while (true) {
+    int32_t startIndex = crlfIndex + 2;
+
+    crlfIndex = mFlatHttpRequestHeaders.Find("\r\n", false, startIndex);
+    if (crlfIndex == -1)
+      break;
+
+    int32_t colonIndex = mFlatHttpRequestHeaders.Find(":", false, startIndex,
+                                                      crlfIndex - startIndex);
+    if (colonIndex == -1)
+      break;
+
+    nsDependentCSubstring name = Substring(beginBuffer + startIndex,
+                                           beginBuffer + colonIndex);
+    // all header names are lower case in spdy
+    ToLowerCase(name);
+
+    if (name.Equals("method") ||
+        name.Equals("version") ||
+        name.Equals("scheme") ||
+        name.Equals("keep-alive") ||
+        name.Equals("accept-encoding") ||
+        name.Equals("te") ||
+        name.Equals("connection") ||
+        name.Equals("url"))
+      continue;
+
+    nsCString *val = hdrHash.Get(name);
+    if (!val) {
+      val = new nsCString();
+      hdrHash.Put(name, val);
+    }
+
+    int32_t valueIndex = colonIndex + 1;
+    while (valueIndex < crlfIndex && beginBuffer[valueIndex] == ' ')
+      ++valueIndex;
+
+    nsDependentCSubstring v = Substring(beginBuffer + valueIndex,
+                                        beginBuffer + crlfIndex);
+    if (!val->IsEmpty())
+      val->Append(static_cast<char>(0));
+    val->Append(v);
+
+    if (name.Equals("content-length")) {
+      int64_t len;
+      if (nsHttp::ParseInt64(val->get(), nullptr, &len))
+        mRequestBodyLenRemaining = len;
+    }
+  }
+
+  mTxInlineFrameUsed = 18;
+
+  // Do not naively log the request headers here beacuse they might
+  // contain auth. The http transaction already logs the sanitized request
+  // headers at this same level so it is not necessary to do so here.
+
+  // The header block length
+  uint16_t count = hdrHash.Count() + 4; /* method, scheme, url, version */
+  CompressToFrame(count);
+
+  // method, scheme, url, and version headers for request line
+
+  CompressToFrame(NS_LITERAL_CSTRING("method"));
+  CompressToFrame(methodHeader, strlen(methodHeader));
+  CompressToFrame(NS_LITERAL_CSTRING("scheme"));
+  CompressToFrame(NS_LITERAL_CSTRING("https"));
+  CompressToFrame(NS_LITERAL_CSTRING("url"));
+  CompressToFrame(mTransaction->RequestHead()->RequestURI());
+  CompressToFrame(NS_LITERAL_CSTRING("version"));
+  CompressToFrame(versionHeader);
+
+  hdrHash.Enumerate(hdrHashEnumerate, this);
+  CompressFlushFrame();
+
+  // 4 to 7 are length and flags, which we can now fill in
+  (reinterpret_cast<uint32_t *>(mTxInlineFrame.get()))[1] =
+    PR_htonl(mTxInlineFrameUsed - 8);
+
+  MOZ_ASSERT(!mTxInlineFrame[4],
+             "Size greater than 24 bits");
+
+  // Determine whether to put the fin bit on the syn stream frame or whether
+  // to wait for a data packet to put it on.
+
+  if (mTransaction->RequestHead()->Method() == nsHttp::Get ||
+      mTransaction->RequestHead()->Method() == nsHttp::Connect ||
+      mTransaction->RequestHead()->Method() == nsHttp::Head) {
+    // for GET, CONNECT, and HEAD place the fin bit right on the
+    // syn stream packet
+
+    mSentFinOnData = 1;
+    mTxInlineFrame[4] = SpdySession2::kFlag_Data_FIN;
+  }
+  else if (mTransaction->RequestHead()->Method() == nsHttp::Post ||
+           mTransaction->RequestHead()->Method() == nsHttp::Put ||
+           mTransaction->RequestHead()->Method() == nsHttp::Options) {
+    // place fin in a data frame even for 0 length messages, I've seen
+    // the google gateway be unhappy with fin-on-syn for 0 length POST
+  }
+  else if (!mRequestBodyLenRemaining) {
+    // for other HTTP extension methods, rely on the content-length
+    // to determine whether or not to put fin on syn
+    mSentFinOnData = 1;
+    mTxInlineFrame[4] = SpdySession2::kFlag_Data_FIN;
+  }
+
+  Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, mTxInlineFrameUsed - 18);
+
+  // The size of the input headers is approximate
+  uint32_t ratio =
+    (mTxInlineFrameUsed - 18) * 100 /
+    (11 + mTransaction->RequestHead()->RequestURI().Length() +
+     mFlatHttpRequestHeaders.Length());
+
+  Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio);
+  return NS_OK;
+}
+
+void
+SpdyStream2::UpdateTransportReadEvents(uint32_t count)
+{
+  mTotalRead += count;
+
+  mTransaction->OnTransportStatus(mSocketTransport,
+                                  NS_NET_STATUS_RECEIVING_FROM,
+                                  mTotalRead);
+}
+
+void
+SpdyStream2::UpdateTransportSendEvents(uint32_t count)
+{
+  mTotalSent += count;
+
+  // normally on non-windows platform we use TCP autotuning for
+  // the socket buffers, and this works well (managing enough
+  // buffers for BDP while conserving memory) for HTTP even when
+  // it creates really deep queues. However this 'buffer bloat' is
+  // a problem for spdy because it ruins the low latency properties
+  // necessary for PING and cancel to work meaningfully.
+  //
+  // If this stream represents a large upload, disable autotuning for
+  // the session and cap the send buffers by default at 128KB.
+  // (10Mbit/sec @ 100ms)
+  //
+  uint32_t bufferSize = gHttpHandler->SpdySendBufferSize();
+  if ((mTotalSent > bufferSize) && !mSetTCPSocketBuffer) {
+    mSetTCPSocketBuffer = 1;
+    mSocketTransport->SetSendBufferSize(bufferSize);
+  }
+
+  if (mUpstreamState != SENDING_FIN_STREAM)
+    mTransaction->OnTransportStatus(mSocketTransport,
+                                    NS_NET_STATUS_SENDING_TO,
+                                    mTotalSent);
+
+  if (!mSentWaitingFor && !mRequestBodyLenRemaining) {
+    mSentWaitingFor = 1;
+    mTransaction->OnTransportStatus(mSocketTransport,
+                                    NS_NET_STATUS_WAITING_FOR,
+                                    0);
+  }
+}
+
+nsresult
+SpdyStream2::TransmitFrame(const char *buf,
+                           uint32_t *countUsed,
+                           bool forceCommitment)
+{
+  // If TransmitFrame returns SUCCESS than all the data is sent (or at least
+  // buffered at the session level), if it returns WOULD_BLOCK then none of
+  // the data is sent.
+
+  // You can call this function with no data and no out parameter in order to
+  // flush internal buffers that were previously blocked on writing. You can
+  // of course feed new data to it as well.
+
+  MOZ_ASSERT(mTxInlineFrameUsed, "empty stream frame in transmit");
+  MOZ_ASSERT(mSegmentReader, "TransmitFrame with null mSegmentReader");
+  MOZ_ASSERT((buf && countUsed) || (!buf && !countUsed),
+             "TransmitFrame arguments inconsistent");
+
+  uint32_t transmittedCount;
+  nsresult rv;
+
+  LOG3(("SpdyStream2::TransmitFrame %p inline=%d stream=%d",
+        this, mTxInlineFrameUsed, mTxStreamFrameSize));
+  if (countUsed)
+    *countUsed = 0;
+
+  // In the (relatively common) event that we have a small amount of data
+  // split between the inlineframe and the streamframe, then move the stream
+  // data into the inlineframe via copy in order to coalesce into one write.
+  // Given the interaction with ssl this is worth the small copy cost.
+  if (mTxStreamFrameSize && mTxInlineFrameUsed &&
+      mTxStreamFrameSize < SpdySession2::kDefaultBufferSize &&
+      mTxInlineFrameUsed + mTxStreamFrameSize < mTxInlineFrameSize) {
+    LOG3(("Coalesce Transmit"));
+    memcpy (mTxInlineFrame + mTxInlineFrameUsed,
+            buf, mTxStreamFrameSize);
+    if (countUsed)
+      *countUsed += mTxStreamFrameSize;
+    mTxInlineFrameUsed += mTxStreamFrameSize;
+    mTxStreamFrameSize = 0;
+  }
+
+  rv =
+    mSegmentReader->CommitToSegmentSize(mTxStreamFrameSize + mTxInlineFrameUsed,
+                                        forceCommitment);
+
+  if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+    MOZ_ASSERT(!forceCommitment, "forceCommitment with WOULD_BLOCK");
+    mSession->TransactionHasDataToWrite(this);
+  }
+  if (NS_FAILED(rv))     // this will include WOULD_BLOCK
+    return rv;
+
+  // This function calls mSegmentReader->OnReadSegment to report the actual SPDY
+  // bytes through to the SpdySession2 and then the HttpConnection which calls
+  // the socket write function. It will accept all of the inline and stream
+  // data because of the above 'commitment' even if it has to buffer
+
+  rv = mSegmentReader->OnReadSegment(mTxInlineFrame, mTxInlineFrameUsed,
+                                     &transmittedCount);
+  LOG3(("SpdyStream2::TransmitFrame for inline session=%p "
+        "stream=%p result %x len=%d",
+        mSession, this, rv, transmittedCount));
+
+  MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
+             "inconsistent inline commitment result");
+
+  if (NS_FAILED(rv))
+    return rv;
+
+  MOZ_ASSERT(transmittedCount == mTxInlineFrameUsed,
+             "inconsistent inline commitment count");
+
+  SpdySession2::LogIO(mSession, this, "Writing from Inline Buffer",
+                     mTxInlineFrame, transmittedCount);
+
+  if (mTxStreamFrameSize) {
+    if (!buf) {
+      // this cannot happen
+      MOZ_ASSERT(false, "Stream transmit with null buf argument to "
+                 "TransmitFrame()");
+      LOG(("Stream transmit with null buf argument to TransmitFrame()\n"));
+      return NS_ERROR_UNEXPECTED;
+    }
+
+    rv = mSegmentReader->OnReadSegment(buf, mTxStreamFrameSize,
+                                       &transmittedCount);
+
+    LOG3(("SpdyStream2::TransmitFrame for regular session=%p "
+          "stream=%p result %x len=%d",
+          mSession, this, rv, transmittedCount));
+
+    MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK,
+               "inconsistent stream commitment result");
+
+    if (NS_FAILED(rv))
+      return rv;
+
+    MOZ_ASSERT(transmittedCount == mTxStreamFrameSize,
+               "inconsistent stream commitment count");
+
+    SpdySession2::LogIO(mSession, this, "Writing from Transaction Buffer",
+                       buf, transmittedCount);
+
+    *countUsed += mTxStreamFrameSize;
+  }
+
+  // calling this will trigger waiting_for if mRequestBodyLenRemaining is 0
+  UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize);
+
+  mTxInlineFrameUsed = 0;
+  mTxStreamFrameSize = 0;
+
+  return NS_OK;
+}
+
+void
+SpdyStream2::ChangeState(enum stateType newState)
+{
+  LOG3(("SpdyStream2::ChangeState() %p from %X to %X",
+        this, mUpstreamState, newState));
+  mUpstreamState = newState;
+  return;
+}
+
+void
+SpdyStream2::GenerateDataFrameHeader(uint32_t dataLength, bool lastFrame)
+{
+  LOG3(("SpdyStream2::GenerateDataFrameHeader %p len=%d last=%d",
+        this, dataLength, lastFrame));
+
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  MOZ_ASSERT(!mTxInlineFrameUsed, "inline frame not empty");
+  MOZ_ASSERT(!mTxStreamFrameSize, "stream frame not empty");
+  MOZ_ASSERT(!(dataLength & 0xff000000), "datalength > 24 bits");
+
+  (reinterpret_cast<uint32_t *>(mTxInlineFrame.get()))[0] = PR_htonl(mStreamID);
+  (reinterpret_cast<uint32_t *>(mTxInlineFrame.get()))[1] =
+    PR_htonl(dataLength);
+
+  MOZ_ASSERT(!(mTxInlineFrame[0] & 0x80), "control bit set unexpectedly");
+  MOZ_ASSERT(!mTxInlineFrame[4], "flag bits set unexpectedly");
+
+  mTxInlineFrameUsed = 8;
+  mTxStreamFrameSize = dataLength;
+
+  if (lastFrame) {
+    mTxInlineFrame[4] |= SpdySession2::kFlag_Data_FIN;
+    if (dataLength)
+      mSentFinOnData = 1;
+  }
+}
+
+void
+SpdyStream2::CompressToFrame(const nsACString &str)
+{
+  CompressToFrame(str.BeginReading(), str.Length());
+}
+
+void
+SpdyStream2::CompressToFrame(const nsACString *str)
+{
+  CompressToFrame(str->BeginReading(), str->Length());
+}
+
+// Dictionary taken from
+// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2
+// Name/Value Header Block Format
+// spec indicates that the compression dictionary is not null terminated
+// but in reality it is. see:
+// https://groups.google.com/forum/#!topic/spdy-dev/2pWxxOZEIcs
+
+const char *SpdyStream2::kDictionary =
+  "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";
+
+// use for zlib data types
+void *
+SpdyStream2::zlib_allocator(void *opaque, uInt items, uInt size)
+{
+  return moz_xmalloc(items * size);
+}
+
+// use for zlib data types
+void
+SpdyStream2::zlib_destructor(void *opaque, void *addr)
+{
+  moz_free(addr);
+}
+
+void
+SpdyStream2::ExecuteCompress(uint32_t flushMode)
+{
+  // Expect mZlib->avail_in and mZlib->next_in to be set.
+  // Append the compressed version of next_in to mTxInlineFrame
+
+  do
+  {
+    uint32_t avail = mTxInlineFrameSize - mTxInlineFrameUsed;
+    if (avail < 1) {
+      SpdySession2::EnsureBuffer(mTxInlineFrame,
+                                mTxInlineFrameSize + 2000,
+                                mTxInlineFrameUsed,
+                                mTxInlineFrameSize);
+      avail = mTxInlineFrameSize - mTxInlineFrameUsed;
+    }
+
+    mZlib->next_out = reinterpret_cast<unsigned char *> (mTxInlineFrame.get()) +
+      mTxInlineFrameUsed;
+    mZlib->avail_out = avail;
+    deflate(mZlib, flushMode);
+    mTxInlineFrameUsed += avail - mZlib->avail_out;
+  } while (mZlib->avail_in > 0 || !mZlib->avail_out);
+}
+
+void
+SpdyStream2::CompressToFrame(uint16_t data)
+{
+  // convert the data to network byte order and write that
+  // to the compressed stream
+
+  data = PR_htons(data);
+
+  mZlib->next_in = reinterpret_cast<unsigned char *> (&data);
+  mZlib->avail_in = 2;
+  ExecuteCompress(Z_NO_FLUSH);
+}
+
+
+void
+SpdyStream2::CompressToFrame(const char *data, uint32_t len)
+{
+  // Format calls for a network ordered 16 bit length
+  // followed by the utf8 string
+
+  // for now, silently truncate headers greater than 64KB. Spdy/3 will
+  // fix this by making the len a 32 bit quantity
+  if (len > 0xffff)
+    len = 0xffff;
+
+  uint16_t networkLen = PR_htons(len);
+
+  // write out the length
+  mZlib->next_in = reinterpret_cast<unsigned char *> (&networkLen);
+  mZlib->avail_in = 2;
+  ExecuteCompress(Z_NO_FLUSH);
+
+  // write out the data
+  mZlib->next_in = (unsigned char *)data;
+  mZlib->avail_in = len;
+  ExecuteCompress(Z_NO_FLUSH);
+}
+
+void
+SpdyStream2::CompressFlushFrame()
+{
+  mZlib->next_in = (unsigned char *) "";
+  mZlib->avail_in = 0;
+  ExecuteCompress(Z_SYNC_FLUSH);
+}
+
+void
+SpdyStream2::Close(nsresult reason)
+{
+  mTransaction->Close(reason);
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentReader
+//-----------------------------------------------------------------------------
+
+nsresult
+SpdyStream2::OnReadSegment(const char *buf,
+                          uint32_t count,
+                          uint32_t *countRead)
+{
+  LOG3(("SpdyStream2::OnReadSegment %p count=%d state=%x",
+        this, count, mUpstreamState));
+
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  MOZ_ASSERT(mSegmentReader);
+
+  nsresult rv = NS_ERROR_UNEXPECTED;
+  uint32_t dataLength;
+
+  switch (mUpstreamState) {
+  case GENERATING_SYN_STREAM:
+    // The buffer is the HTTP request stream, including at least part of the
+    // HTTP request header. This state's job is to build a SYN_STREAM frame
+    // from the header information. count is the number of http bytes available
+    // (which may include more than the header), and in countRead we return
+    // the number of those bytes that we consume (i.e. the portion that are
+    // header bytes)
+
+    rv = ParseHttpRequestHeaders(buf, count, countRead);
+    if (NS_FAILED(rv))
+      return rv;
+    LOG3(("ParseHttpRequestHeaders %p used %d of %d. complete = %d",
+          this, *countRead, count, mSynFrameComplete));
+    if (mSynFrameComplete) {
+      MOZ_ASSERT(mTxInlineFrameUsed, "OnReadSegment SynFrameComplete 0b");
+      rv = TransmitFrame(nullptr, nullptr, true);
+      if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+        // this can't happen
+        MOZ_ASSERT(false, "Transmit Frame SYN_FRAME must at least buffer data");
+        rv = NS_ERROR_UNEXPECTED;
+      }
+
+      ChangeState(GENERATING_REQUEST_BODY);
+      break;
+    }
+    MOZ_ASSERT(*countRead == count, "Header parsing not complete but unused data");
+    break;
+
+  case GENERATING_REQUEST_BODY:
+    dataLength = std::min(count, mChunkSize);
+    LOG3(("SpdyStream2 %p id %x request len remaining %d, "
+          "count avail %d, chunk used %d",
+          this, mStreamID, mRequestBodyLenRemaining, count, dataLength));
+    if (dataLength > mRequestBodyLenRemaining)
+      return NS_ERROR_UNEXPECTED;
+    mRequestBodyLenRemaining -= dataLength;
+    GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining);
+    ChangeState(SENDING_REQUEST_BODY);
+    // NO BREAK
+
+  case SENDING_REQUEST_BODY:
+    MOZ_ASSERT(mTxInlineFrameUsed, "OnReadSegment Send Data Header 0b");
+    rv = TransmitFrame(buf, countRead, false);
+    MOZ_ASSERT(NS_FAILED(rv) || !mTxInlineFrameUsed,
+               "Transmit Frame should be all or nothing");
+
+    LOG3(("TransmitFrame() rv=%x returning %d data bytes. "
+          "Header is %d Body is %d.",
+          rv, *countRead, mTxInlineFrameUsed, mTxStreamFrameSize));
+
+    // normalize a partial write with a WOULD_BLOCK into just a partial write
+    // as some code will take WOULD_BLOCK to mean an error with nothing
+    // written (e.g. nsHttpTransaction::ReadRequestSegment()
+    if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead)
+      rv = NS_OK;
+
+    // If that frame was all sent, look for another one
+    if (!mTxInlineFrameUsed)
+        ChangeState(GENERATING_REQUEST_BODY);
+    break;
+
+  case SENDING_FIN_STREAM:
+    MOZ_ASSERT(false, "resuming partial fin stream out of OnReadSegment");
+    break;
+
+  default:
+    MOZ_ASSERT(false, "SpdyStream2::OnReadSegment non-write state");
+    break;
+  }
+
+  return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentWriter
+//-----------------------------------------------------------------------------
+
+nsresult
+SpdyStream2::OnWriteSegment(char *buf,
+                           uint32_t count,
+                           uint32_t *countWritten)
+{
+  LOG3(("SpdyStream2::OnWriteSegment %p count=%d state=%x",
+        this, count, mUpstreamState));
+
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  MOZ_ASSERT(mSegmentWriter, "OnWriteSegment with null mSegmentWriter");
+
+  return mSegmentWriter->OnWriteSegment(buf, count, countWritten);
+}
+
+} // namespace mozilla::net
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/SpdyStream2.h
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_SpdyStream2_h
+#define mozilla_net_SpdyStream2_h
+
+#include "nsAHttpTransaction.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla { namespace net {
+
+class SpdyStream2 MOZ_FINAL : public nsAHttpSegmentReader
+                            , public nsAHttpSegmentWriter
+{
+public:
+  NS_DECL_NSAHTTPSEGMENTREADER
+  NS_DECL_NSAHTTPSEGMENTWRITER
+
+  SpdyStream2(nsAHttpTransaction *,
+             SpdySession2 *, nsISocketTransport *,
+             uint32_t, z_stream *, int32_t);
+
+  uint32_t StreamID() { return mStreamID; }
+
+  nsresult ReadSegments(nsAHttpSegmentReader *,  uint32_t, uint32_t *);
+  nsresult WriteSegments(nsAHttpSegmentWriter *, uint32_t, uint32_t *);
+
+  bool RequestBlockedOnRead()
+  {
+    return static_cast<bool>(mRequestBlockedOnRead);
+  }
+
+  // returns false if called more than once
+  bool GetFullyOpen() {return mFullyOpen;}
+  void SetFullyOpen()
+  {
+    MOZ_ASSERT(!mFullyOpen);
+    mFullyOpen = 1;
+  }
+
+  bool HasRegisteredID() { return mStreamID != 0; }
+
+  nsAHttpTransaction *Transaction()
+  {
+    return mTransaction;
+  }
+
+  void Close(nsresult reason);
+
+  void SetRecvdFin(bool aStatus) { mRecvdFin = aStatus ? 1 : 0; }
+  bool RecvdFin() { return mRecvdFin; }
+
+  void UpdateTransportSendEvents(uint32_t count);
+  void UpdateTransportReadEvents(uint32_t count);
+
+  // The zlib header compression dictionary defined by SPDY,
+  // and hooks to the mozilla allocator for zlib to use.
+  static const char *kDictionary;
+  static void *zlib_allocator(void *, uInt, uInt);
+  static void zlib_destructor(void *, void *);
+
+private:
+
+  // a SpdyStream2 object is only destroyed by being removed from the
+  // SpdySession2 mStreamTransactionHash - make the dtor private to
+  // just the AutoPtr implementation needed for that hash.
+  friend class nsAutoPtr<SpdyStream2>;
+  ~SpdyStream2();
+
+  enum stateType {
+    GENERATING_SYN_STREAM,
+    GENERATING_REQUEST_BODY,
+    SENDING_REQUEST_BODY,
+    SENDING_FIN_STREAM,
+    UPSTREAM_COMPLETE
+  };
+
+  static PLDHashOperator hdrHashEnumerate(const nsACString &,
+                                          nsAutoPtr<nsCString> &,
+                                          void *);
+
+  void     ChangeState(enum stateType);
+  nsresult ParseHttpRequestHeaders(const char *, uint32_t, uint32_t *);
+  nsresult TransmitFrame(const char *, uint32_t *, bool forceCommitment);
+  void     GenerateDataFrameHeader(uint32_t, bool);
+
+  void     CompressToFrame(const nsACString &);
+  void     CompressToFrame(const nsACString *);
+  void     CompressToFrame(const char *, uint32_t);
+  void     CompressToFrame(uint16_t);
+  void     CompressFlushFrame();
+  void     ExecuteCompress(uint32_t);
+
+  // Each stream goes from syn_stream to upstream_complete, perhaps
+  // looping on multiple instances of generating_request_body and
+  // sending_request_body for each SPDY chunk in the upload.
+  enum stateType mUpstreamState;
+
+  // The underlying HTTP transaction. This pointer is used as the key
+  // in the SpdySession2 mStreamTransactionHash so it is important to
+  // keep a reference to it as long as this stream is a member of that hash.
+  // (i.e. don't change it or release it after it is set in the ctor).
+  nsRefPtr<nsAHttpTransaction> mTransaction;
+
+  // The session that this stream is a subset of
+  SpdySession2                *mSession;
+
+  // The underlying socket transport object is needed to propogate some events
+  nsISocketTransport         *mSocketTransport;
+
+  // These are temporary state variables to hold the argument to
+  // Read/WriteSegments so it can be accessed by On(read/write)segment
+  // further up the stack.
+  nsAHttpSegmentReader        *mSegmentReader;
+  nsAHttpSegmentWriter        *mSegmentWriter;
+
+  // The 24 bit SPDY stream ID
+  uint32_t                    mStreamID;
+
+  // The quanta upstream data frames are chopped into
+  uint32_t                    mChunkSize;
+
+  // Flag is set when all http request headers have been read and ID is stable
+  uint32_t                     mSynFrameComplete     : 1;
+
+  // Flag is set when the HTTP processor has more data to send
+  // but has blocked in doing so.
+  uint32_t                     mRequestBlockedOnRead : 1;
+
+  // Flag is set when a FIN has been placed on a data or syn packet
+  // (i.e after the client has closed)
+  uint32_t                     mSentFinOnData        : 1;
+
+  // Flag is set after the response frame bearing the fin bit has
+  // been processed. (i.e. after the server has closed).
+  uint32_t                     mRecvdFin             : 1;
+
+  // Flag is set after syn reply received
+  uint32_t                     mFullyOpen            : 1;
+
+  // Flag is set after the WAITING_FOR Transport event has been generated
+  uint32_t                     mSentWaitingFor       : 1;
+
+  // Flag is set after TCP send autotuning has been disabled
+  uint32_t                     mSetTCPSocketBuffer   : 1;
+
+  // The InlineFrame and associated data is used for composing control
+  // frames and data frame headers.
+  nsAutoArrayPtr<char>         mTxInlineFrame;
+  uint32_t                     mTxInlineFrameSize;
+  uint32_t                     mTxInlineFrameUsed;
+
+  // mTxStreamFrameSize tracks the progress of
+  // transmitting a request body data frame. The data frame itself
+  // is never copied into the spdy layer.
+  uint32_t                     mTxStreamFrameSize;
+
+  // Compression context and buffer for request header compression.
+  // This is a copy of SpdySession2::mUpstreamZlib because it needs
+  //  to remain the same in all streams of a session.
+  z_stream                     *mZlib;
+  nsCString                    mFlatHttpRequestHeaders;
+
+  // Track the content-length of a request body so that we can
+  // place the fin flag on the last data packet instead of waiting
+  // for a stream closed indication. Relying on stream close results
+  // in an extra 0-length runt packet and seems to have some interop
+  // problems with the google servers.
+  int64_t                      mRequestBodyLenRemaining;
+
+  // based on nsISupportsPriority definitions
+  int32_t                      mPriority;
+
+  // For Progress Events
+  uint64_t                     mTotalSent;
+  uint64_t                     mTotalRead;
+};
+
+}} // namespace mozilla::net
+
+#endif // mozilla_net_SpdyStream2_h
--- a/netwerk/protocol/http/moz.build
+++ b/netwerk/protocol/http/moz.build
@@ -64,18 +64,20 @@ SOURCES += [
     'nsHttpNTLMAuth.cpp',
     'nsHttpPipeline.cpp',
     'nsHttpRequestHead.cpp',
     'nsHttpResponseHead.cpp',
     'nsHttpTransaction.cpp',
     'NullHttpTransaction.cpp',
     'SpdyPush3.cpp',
     'SpdyPush31.cpp',
+    'SpdySession2.cpp',
     'SpdySession3.cpp',
     'SpdySession31.cpp',
+    'SpdyStream2.cpp',
     'SpdyStream3.cpp',
     'SpdyStream31.cpp',
 ]
 
 IPDL_SOURCES += [
     'PHttpChannel.ipdl',
 ]
 EXTRA_JS_MODULES += [
--- a/netwerk/protocol/http/nsHttp.h
+++ b/netwerk/protocol/http/nsHttp.h
@@ -19,17 +19,17 @@
 #define NS_HTTP_VERSION_1_1     11
 
 namespace mozilla {
 
 class Mutex;
 
 namespace net {
     enum {
-        SPDY_VERSION_2_REMOVED = 2,
+        SPDY_VERSION_2 = 2,
         SPDY_VERSION_3 = 3,
         SPDY_VERSION_31 = 4
     };
 } // namespace mozilla::net
 } // namespace mozilla
 
 typedef uint8_t nsHttpVersion;
 
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -179,16 +179,17 @@ nsHttpHandler::nsHttpHandler()
     , mSendSecureXSiteReferrer(true)
     , mEnablePersistentHttpsCaching(false)
     , mDoNotTrackEnabled(false)
     , mDoNotTrackValue(1)
     , mTelemetryEnabled(false)
     , mAllowExperiments(true)
     , mHandlerActive(false)
     , mEnableSpdy(false)
+    , mSpdyV2(true)
     , mSpdyV3(true)
     , mSpdyV31(true)
     , mCoalesceSpdy(true)
     , mSpdyPersistentSettings(false)
     , mAllowSpdyPush(true)
     , mSpdySendingChunkSize(ASpdySession::kSendingChunkSize)
     , mSpdySendBufferSize(ASpdySession::kTCPSendBufferSize)
     , mSpdyPushAllowance(32768)
@@ -1122,16 +1123,22 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
     }
 
     if (PREF_CHANGED(HTTP_PREF("spdy.enabled"))) {
         rv = prefs->GetBoolPref(HTTP_PREF("spdy.enabled"), &cVar);
         if (NS_SUCCEEDED(rv))
             mEnableSpdy = cVar;
     }
 
+    if (PREF_CHANGED(HTTP_PREF("spdy.enabled.v2"))) {
+        rv = prefs->GetBoolPref(HTTP_PREF("spdy.enabled.v2"), &cVar);
+        if (NS_SUCCEEDED(rv))
+            mSpdyV2 = cVar;
+    }
+
     if (PREF_CHANGED(HTTP_PREF("spdy.enabled.v3"))) {
         rv = prefs->GetBoolPref(HTTP_PREF("spdy.enabled.v3"), &cVar);
         if (NS_SUCCEEDED(rv))
             mSpdyV3 = cVar;
     }
 
     if (PREF_CHANGED(HTTP_PREF("spdy.enabled.v3-1"))) {
         rv = prefs->GetBoolPref(HTTP_PREF("spdy.enabled.v3-1"), &cVar);
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -90,16 +90,17 @@ public:
     uint32_t       MaxSocketCount();
     bool           EnforceAssocReq()         { return mEnforceAssocReq; }
 
     bool           IsPersistentHttpsCachingEnabled() { return mEnablePersistentHttpsCaching; }
     bool           IsTelemetryEnabled() { return mTelemetryEnabled; }
     bool           AllowExperiments() { return mTelemetryEnabled && mAllowExperiments; }
 
     bool           IsSpdyEnabled() { return mEnableSpdy; }
+    bool           IsSpdyV2Enabled() { return mSpdyV2; }
     bool           IsSpdyV3Enabled() { return mSpdyV3; }
     bool           IsSpdyV31Enabled() { return mSpdyV31; }
     bool           CoalesceSpdy() { return mCoalesceSpdy; }
     bool           UseSpdyPersistentSettings() { return mSpdyPersistentSettings; }
     uint32_t       SpdySendingChunkSize() { return mSpdySendingChunkSize; }
     uint32_t       SpdySendBufferSize()      { return mSpdySendBufferSize; }
     uint32_t       SpdyPushAllowance()       { return mSpdyPushAllowance; }
     PRIntervalTime SpdyPingThreshold() { return mSpdyPingThreshold; }
@@ -410,16 +411,17 @@ private:
     bool           mAllowExperiments;
 
     // true in between init and shutdown states
     bool           mHandlerActive;
 
     // Try to use SPDY features instead of HTTP/1.1 over SSL
     mozilla::net::SpdyInformation mSpdyInfo;
     bool           mEnableSpdy;
+    bool           mSpdyV2;
     bool           mSpdyV3;
     bool           mSpdyV31;
     bool           mCoalesceSpdy;
     bool           mSpdyPersistentSettings;
     bool           mAllowSpdyPush;
     uint32_t       mSpdySendingChunkSize;
     uint32_t       mSpdySendBufferSize;
     uint32_t       mSpdyPushAllowance;
--- a/netwerk/test/unit/test_spdy.js
+++ b/netwerk/test/unit/test_spdy.js
@@ -380,23 +380,25 @@ function addCertOverride(host, port, bit
     req.send(null);
   } catch (e) {
     // This will fail since the server is not trusted yet
   }
 }
 
 var prefs;
 var spdypref;
+var spdy2pref;
 var spdy3pref;
 var spdypush;
 
 var loadGroup;
 
 function resetPrefs() {
   prefs.setBoolPref("network.http.spdy.enabled", spdypref);
+  prefs.setBoolPref("network.http.spdy.enabled.v2", spdy2pref);
   prefs.setBoolPref("network.http.spdy.enabled.v3", spdy3pref);
   prefs.setBoolPref("network.http.spdy.allow-push", spdypush);
 }
 
 function run_test() {
   // Set to allow the cert presented by our SPDY server
   do_get_profile();
   var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
@@ -407,19 +409,21 @@ function run_test() {
                   Ci.nsICertOverrideService.ERROR_UNTRUSTED |
                   Ci.nsICertOverrideService.ERROR_MISMATCH |
                   Ci.nsICertOverrideService.ERROR_TIME);
 
   prefs.setIntPref("network.http.speculative-parallel-limit", oldPref);
 
   // Enable all versions of spdy to see that we auto negotiate spdy/3
   spdypref = prefs.getBoolPref("network.http.spdy.enabled");
+  spdy2pref = prefs.getBoolPref("network.http.spdy.enabled.v2");
   spdy3pref = prefs.getBoolPref("network.http.spdy.enabled.v3");
   spdypush = prefs.getBoolPref("network.http.spdy.allow-push");
   prefs.setBoolPref("network.http.spdy.enabled", true);
+  prefs.setBoolPref("network.http.spdy.enabled.v2", true);
   prefs.setBoolPref("network.http.spdy.enabled.v3", true);
   prefs.setBoolPref("network.http.spdy.allow-push", true);
 
   loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(Ci.nsILoadGroup);
 
   // And make go!
   run_next_test();
 }
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_spdy2.js
@@ -0,0 +1,372 @@
+// test spdy/2
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+// Generate a small and a large post with known pre-calculated md5 sums
+function generateContent(size) {
+  var content = "";
+  for (var i = 0; i < size; i++) {
+    content += "0";
+  }
+  return content;
+}
+
+var posts = [];
+posts.push(generateContent(10));
+posts.push(generateContent(128 * 1024));
+
+// pre-calculated md5sums (in hex) of the above posts
+var md5s = ['f1b708bba17f1ce948dc979f4d7092bc',
+            '8f607cfdd2c87d6a7eedb657dafbd836'];
+
+function checkIsSpdy(request) {
+  try {
+    if (request.getResponseHeader("X-Firefox-Spdy") == "2") {
+      if (request.getResponseHeader("X-Connection-Spdy") == "yes") {
+        return true;
+      }
+      return false; // Weird case, but the server disagrees with us
+    }
+  } catch (e) {
+    // Nothing to do here
+  }
+  return false;
+}
+
+var SpdyCheckListener = function() {};
+
+SpdyCheckListener.prototype = {
+  onStartRequestFired: false,
+  onDataAvailableFired: false,
+  isSpdyConnection: false,
+
+  onStartRequest: function testOnStartRequest(request, ctx) {
+    this.onStartRequestFired = true;
+
+    if (!Components.isSuccessCode(request.status))
+      do_throw("Channel should have a success code! (" + request.status + ")");
+    if (!(request instanceof Components.interfaces.nsIHttpChannel))
+      do_throw("Expecting an HTTP channel");
+
+    do_check_eq(request.responseStatus, 200);
+    do_check_eq(request.requestSucceeded, true);
+  },
+
+  onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
+    this.onDataAvailableFired = true;
+    this.isSpdyConnection = checkIsSpdy(request);
+
+    read_stream(stream, cnt);
+  },
+
+  onStopRequest: function testOnStopRequest(request, ctx, status) {
+    do_check_true(this.onStartRequestFired);
+    do_check_true(this.onDataAvailableFired);
+    do_check_true(this.isSpdyConnection);
+
+    run_next_test();
+    do_test_finished();
+  }
+};
+
+/*
+ * Support for testing valid multiplexing of streams
+ */
+
+var multiplexContent = generateContent(30*1024);
+var completed_channels = [];
+function register_completed_channel(listener) {
+  completed_channels.push(listener);
+  if (completed_channels.length == 2) {
+    do_check_neq(completed_channels[0].streamID, completed_channels[1].streamID);
+    run_next_test();
+    do_test_finished();
+  }
+}
+
+/* Listener class to control the testing of multiplexing */
+var SpdyMultiplexListener = function() {};
+
+SpdyMultiplexListener.prototype = new SpdyCheckListener();
+
+SpdyMultiplexListener.prototype.streamID = 0;
+SpdyMultiplexListener.prototype.buffer = "";
+
+SpdyMultiplexListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
+  this.onDataAvailableFired = true;
+  this.isSpdyConnection = checkIsSpdy(request);
+  this.streamID = parseInt(request.getResponseHeader("X-Spdy-StreamID"));
+  var data = read_stream(stream, cnt);
+  this.buffer = this.buffer.concat(data);
+};
+
+SpdyMultiplexListener.prototype.onStopRequest = function(request, ctx, status) {
+  do_check_true(this.onStartRequestFired);
+  do_check_true(this.onDataAvailableFired);
+  do_check_true(this.isSpdyConnection);
+  do_check_true(this.buffer == multiplexContent);
+  
+  // This is what does most of the hard work for us
+  register_completed_channel(this);
+};
+
+// Does the appropriate checks for header gatewaying
+var SpdyHeaderListener = function(value) {
+  this.value = value
+};
+
+SpdyHeaderListener.prototype = new SpdyCheckListener();
+SpdyHeaderListener.prototype.value = "";
+
+SpdyHeaderListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
+  this.onDataAvailableFired = true;
+  this.isSpdyConnection = checkIsSpdy(request);
+  do_check_eq(request.getResponseHeader("X-Received-Test-Header"), this.value);
+  read_stream(stream, cnt);
+};
+
+// Does the appropriate checks for a large GET response
+var SpdyBigListener = function() {};
+
+SpdyBigListener.prototype = new SpdyCheckListener();
+SpdyBigListener.prototype.buffer = "";
+
+SpdyBigListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
+  this.onDataAvailableFired = true;
+  this.isSpdyConnection = checkIsSpdy(request);
+  this.buffer = this.buffer.concat(read_stream(stream, cnt));
+  // We know the server should send us the same data as our big post will be,
+  // so the md5 should be the same
+  do_check_eq(md5s[1], request.getResponseHeader("X-Expected-MD5"));
+};
+
+SpdyBigListener.prototype.onStopRequest = function(request, ctx, status) {
+  do_check_true(this.onStartRequestFired);
+  do_check_true(this.onDataAvailableFired);
+  do_check_true(this.isSpdyConnection);
+
+  // Don't want to flood output, so don't use do_check_eq
+  do_check_true(this.buffer == posts[1]);
+
+  run_next_test();
+  do_test_finished();
+};
+
+// Does the appropriate checks for POSTs
+var SpdyPostListener = function(expected_md5) {
+  this.expected_md5 = expected_md5;
+};
+
+SpdyPostListener.prototype = new SpdyCheckListener();
+SpdyPostListener.prototype.expected_md5 = "";
+
+SpdyPostListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
+  this.onDataAvailableFired = true;
+  this.isSpdyConnection = checkIsSpdy(request);
+  read_stream(stream, cnt);
+  do_check_eq(this.expected_md5, request.getResponseHeader("X-Calculated-MD5"));
+};
+
+function makeChan(url) {
+  var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+  var chan = ios.newChannel(url, null, null).QueryInterface(Ci.nsIHttpChannel);
+
+  return chan;
+}
+
+// Make sure we make a spdy connection and both us and the server mark it as such
+function test_spdy_basic() {
+  var chan = makeChan("https://localhost:4443/");
+  var listener = new SpdyCheckListener();
+  chan.asyncOpen(listener, null);
+}
+
+// Support for making sure XHR works over SPDY
+function checkXhr(xhr) {
+  if (xhr.readyState != 4) {
+    return;
+  }
+
+  do_check_eq(xhr.status, 200);
+  do_check_eq(checkIsSpdy(xhr), true);
+  run_next_test();
+  do_test_finished();
+}
+
+// Fires off an XHR request over SPDY
+function test_spdy_xhr() {
+  var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+            .createInstance(Ci.nsIXMLHttpRequest);
+  req.open("GET", "https://localhost:4443/", true);
+  req.addEventListener("readystatechange", function (evt) { checkXhr(req); },
+                       false);
+  req.send(null);
+}
+
+// Test to make sure we get multiplexing right
+function test_spdy_multiplex() {
+  var chan1 = makeChan("https://localhost:4443/multiplex1");
+  var chan2 = makeChan("https://localhost:4443/multiplex2");
+  var listener1 = new SpdyMultiplexListener();
+  var listener2 = new SpdyMultiplexListener();
+  chan1.asyncOpen(listener1, null);
+  chan2.asyncOpen(listener2, null);
+}
+
+// Test to make sure we gateway non-standard headers properly
+function test_spdy_header() {
+  var chan = makeChan("https://localhost:4443/header");
+  var hvalue = "Headers are fun";
+  var listener = new SpdyHeaderListener(hvalue);
+  chan.setRequestHeader("X-Test-Header", hvalue, false);
+  chan.asyncOpen(listener, null);
+}
+
+// Make sure we handle GETs that cover more than 2 frames properly
+function test_spdy_big() {
+  var chan = makeChan("https://localhost:4443/big");
+  var listener = new SpdyBigListener();
+  chan.asyncOpen(listener, null);
+}
+
+// Support for doing a POST
+function do_post(content, chan, listener) {
+  var stream = Cc["@mozilla.org/io/string-input-stream;1"]
+               .createInstance(Ci.nsIStringInputStream);
+  stream.data = content;
+
+  var uchan = chan.QueryInterface(Ci.nsIUploadChannel);
+  uchan.setUploadStream(stream, "text/plain", stream.available());
+
+  chan.requestMethod = "POST";
+
+  chan.asyncOpen(listener, null);
+}
+
+// Make sure we can do a simple POST
+function test_spdy_post() {
+  var chan = makeChan("https://localhost:4443/post");
+  var listener = new SpdyPostListener(md5s[0]);
+  do_post(posts[0], chan, listener);
+}
+
+// Make sure we can do a POST that covers more than 2 frames
+function test_spdy_post_big() {
+  var chan = makeChan("https://localhost:4443/post");
+  var listener = new SpdyPostListener(md5s[1]);
+  do_post(posts[1], chan, listener);
+}
+
+// hack - the header test resets the multiplex object on the server,
+// so make sure header is always run before the multiplex test.
+
+var tests = [ test_spdy_basic
+            , test_spdy_xhr
+            , test_spdy_header
+            , test_spdy_multiplex
+            , test_spdy_big
+            , test_spdy_post
+            , test_spdy_post_big
+            ];
+var current_test = 0;
+
+function run_next_test() {
+  if (current_test < tests.length) {
+    tests[current_test]();
+    current_test++;
+    do_test_pending();
+  }
+}
+
+// Support for making sure we can talk to the invalid cert the server presents
+var CertOverrideListener = function(host, port, bits) {
+  this.host = host;
+  if (port) {
+    this.port = port;
+  }
+  this.bits = bits;
+};
+
+CertOverrideListener.prototype = {
+  host: null,
+  port: -1,
+  bits: null,
+
+  getInterface: function(aIID) {
+    return this.QueryInterface(aIID);
+  },
+
+  QueryInterface: function(aIID) {
+    if (aIID.equals(Ci.nsIBadCertListener2) ||
+        aIID.equals(Ci.nsIInterfaceRequestor) ||
+        aIID.equals(Ci.nsISupports))
+      return this;
+    throw Components.results.NS_ERROR_NO_INTERFACE;
+  },
+
+  notifyCertProblem: function(socketInfo, sslStatus, targetHost) {
+    var cert = sslStatus.QueryInterface(Ci.nsISSLStatus).serverCert;
+    var cos = Cc["@mozilla.org/security/certoverride;1"].
+              getService(Ci.nsICertOverrideService);
+    cos.rememberValidityOverride(this.host, this.port, cert, this.bits, false);
+    return true;
+  },
+};
+
+function addCertOverride(host, port, bits) {
+  var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+            .createInstance(Ci.nsIXMLHttpRequest);
+  try {
+    var url;
+    if (port) {
+      url = "https://" + host + ":" + port + "/";
+    } else {
+      url = "https://" + host + "/";
+    }
+    req.open("GET", url, false);
+    req.channel.notificationCallbacks = new CertOverrideListener(host, port, bits);
+    req.send(null);
+  } catch (e) {
+    // This will fail since the server is not trusted yet
+  }
+}
+
+var prefs;
+var spdypref;
+var spdy2pref;
+var spdy3pref;
+
+function resetPrefs() {
+  prefs.setBoolPref("network.http.spdy.enabled", spdypref);
+  prefs.setBoolPref("network.http.spdy.enabled.v2", spdy2pref);
+  prefs.setBoolPref("network.http.spdy.enabled.v3", spdy3pref);
+}
+
+function run_test() {
+  // Set to allow the cert presented by our SPDY server
+  do_get_profile();
+  prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+  var oldPref = prefs.getIntPref("network.http.speculative-parallel-limit");
+  prefs.setIntPref("network.http.speculative-parallel-limit", 0);
+
+  addCertOverride("localhost", 4443,
+                  Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+                  Ci.nsICertOverrideService.ERROR_MISMATCH |
+                  Ci.nsICertOverrideService.ERROR_TIME);
+
+  prefs.setIntPref("network.http.speculative-parallel-limit", oldPref);
+
+  // Make sure just spdy/2 is enabled
+  spdypref = prefs.getBoolPref("network.http.spdy.enabled");
+  spdy2pref = prefs.getBoolPref("network.http.spdy.enabled.v2");
+  spdy3pref = prefs.getBoolPref("network.http.spdy.enabled.v3");
+  prefs.setBoolPref("network.http.spdy.enabled", true);
+  prefs.setBoolPref("network.http.spdy.enabled.v2", true);
+  prefs.setBoolPref("network.http.spdy.enabled.v3", false);
+  
+  do_register_cleanup(resetPrefs);
+
+  // And make go!
+  run_next_test();
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -261,16 +261,18 @@ fail-if = os == "android"
 [test_resumable_truncate.js]
 [test_safeoutputstream.js]
 [test_simple.js]
 [test_sockettransportsvc_available.js]
 [test_socks.js]
 # Bug 675039: test fails consistently on Android
 fail-if = os == "android"
 # spdy unit tests require us to have node available to run the spdy server
+[test_spdy2.js]
+run-if = hasNode
 [test_spdy.js]
 run-if = hasNode
 [test_speculative_connect.js]
 [test_standardurl.js]
 [test_standardurl_port.js]
 [test_streamcopier.js]
 [test_traceable_channel.js]
 [test_unescapestring.js]