diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index 430f9f240249..3aaf91d81ef3 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -1094,6 +1094,7 @@ pref("network.http.bypass-cachelock-threshold", 250); 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); diff --git a/netwerk/base/public/nsILoadGroup.idl b/netwerk/base/public/nsILoadGroup.idl index 5e219975f4a1..72439ebf55a0 100644 --- a/netwerk/base/public/nsILoadGroup.idl +++ b/netwerk/base/public/nsILoadGroup.idl @@ -96,22 +96,22 @@ interface nsILoadGroup : nsIRequest }; %{C++ -// Forward-declare mozilla::net::SpdyPushCache3 +// Forward-declare mozilla::net::SpdyPushCache namespace mozilla { namespace net { -class SpdyPushCache3; +class SpdyPushCache; } } %} -[ptr] native SpdyPushCache3Ptr(mozilla::net::SpdyPushCache3); +[ptr] native SpdyPushCachePtr(mozilla::net::SpdyPushCache); /** * Used to maintain state about the connections of a load group and * how they interact with blocking items like HEAD css/js loads. */ -[uuid(5361f30e-f968-437c-8f41-69d2756a6022)] +[uuid(fdc9659c-b597-4ac0-9c9e-14b04dbb682f)] interface nsILoadGroupConnectionInfo : nsISupports { /** @@ -137,5 +137,5 @@ interface nsILoadGroupConnectionInfo : nsISupports * and will destroy it when overwritten or when the load group * ends. */ - [noscript] attribute SpdyPushCache3Ptr spdyPushCache3; + [noscript] attribute SpdyPushCachePtr spdyPushCache; }; diff --git a/netwerk/base/src/Dashboard.cpp b/netwerk/base/src/Dashboard.cpp index 473db12c1470..5a500d04af93 100644 --- a/netwerk/base/src/Dashboard.cpp +++ b/netwerk/base/src/Dashboard.cpp @@ -574,8 +574,12 @@ HttpConnInfo::SetHTTP2ProtocolVersion(uint8_t pv) { if (pv == SPDY_VERSION_2) protocolVersion.Assign(NS_LITERAL_STRING("spdy/2")); - else + 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 diff --git a/netwerk/base/src/nsLoadGroup.cpp b/netwerk/base/src/nsLoadGroup.cpp index b3a63e6182aa..cad2585829d1 100644 --- a/netwerk/base/src/nsLoadGroup.cpp +++ b/netwerk/base/src/nsLoadGroup.cpp @@ -17,7 +17,7 @@ #include "mozilla/Atomics.h" #include "mozilla/Telemetry.h" #include "nsAutoPtr.h" -#include "mozilla/net/PSpdyPush3.h" +#include "mozilla/net/PSpdyPush.h" #include "nsITimedChannel.h" #include "nsIInterfaceRequestor.h" #include "nsIRequestObserver.h" @@ -1101,7 +1101,7 @@ public: nsLoadGroupConnectionInfo(); private: Atomic mBlockingTransactionCount; - nsAutoPtr mSpdyCache3; + nsAutoPtr mSpdyCache; }; NS_IMPL_ISUPPORTS1(nsLoadGroupConnectionInfo, nsILoadGroupConnectionInfo) @@ -1135,17 +1135,18 @@ nsLoadGroupConnectionInfo::RemoveBlockingTransaction(uint32_t *_retval) return NS_OK; } -/* [noscript] attribute SpdyPushCache3Ptr spdyPushCache3; */ +/* [noscript] attribute SpdyPushCachePtr spdyPushCache; */ NS_IMETHODIMP -nsLoadGroupConnectionInfo::GetSpdyPushCache3(mozilla::net::SpdyPushCache3 **aSpdyPushCache3) +nsLoadGroupConnectionInfo::GetSpdyPushCache(mozilla::net::SpdyPushCache **aSpdyPushCache) { - *aSpdyPushCache3 = mSpdyCache3.get(); + *aSpdyPushCache = mSpdyCache.get(); return NS_OK; } + NS_IMETHODIMP -nsLoadGroupConnectionInfo::SetSpdyPushCache3(mozilla::net::SpdyPushCache3 *aSpdyPushCache3) +nsLoadGroupConnectionInfo::SetSpdyPushCache(mozilla::net::SpdyPushCache *aSpdyPushCache) { - mSpdyCache3 = aSpdyPushCache3; + mSpdyCache = aSpdyPushCache; return NS_OK; } diff --git a/netwerk/protocol/http/ASpdySession.cpp b/netwerk/protocol/http/ASpdySession.cpp index cbf1256e4d81..6cb1d87a0803 100644 --- a/netwerk/protocol/http/ASpdySession.cpp +++ b/netwerk/protocol/http/ASpdySession.cpp @@ -7,12 +7,21 @@ // HttpLog.h should generally be included first #include "HttpLog.h" +/* + 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" @@ -28,7 +37,8 @@ ASpdySession::NewSpdySession(uint32_t version, // This is a necko only interface, so we can enforce version // requests as a precondition MOZ_ASSERT(version == SPDY_VERSION_2 || - version == SPDY_VERSION_3, + version == SPDY_VERSION_3 || + version == SPDY_VERSION_31 , "Unsupported spdy version"); // Don't do a runtime check of IsSpdyV?Enabled() here because pref value @@ -38,32 +48,40 @@ ASpdySession::NewSpdySession(uint32_t version, 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 SpdySession3(aTransaction, aTransport, aPriority); + return new SpdySession31(aTransaction, aTransport, aPriority); } SpdyInformation::SpdyInformation() { - // list the preferred version first - 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_2; - VersionString[1] = NS_LITERAL_CSTRING("spdy/2"); + 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) { - if (index == 0) - return gHttpHandler->IsSpdyV3Enabled(); + MOZ_ASSERT(index < kCount, "index out of range"); - if (index == 1) + switch (index) { + case 0: return gHttpHandler->IsSpdyV2Enabled(); - - MOZ_ASSERT(false, "index out of range"); + case 1: + return gHttpHandler->IsSpdyV3Enabled(); + case 2: + return gHttpHandler->IsSpdyV31Enabled(); + } return false; } @@ -74,14 +92,74 @@ SpdyInformation::GetNPNVersionIndex(const nsACString &npnString, if (npnString.IsEmpty()) return NS_ERROR_FAILURE; - if (npnString.Equals(VersionString[0])) - *result = Version[0]; - else if (npnString.Equals(VersionString[1])) - *result = Version[1]; - else - return NS_ERROR_FAILURE; + for (uint32_t index = 0; index < kCount; ++index) { + if (npnString.Equals(VersionString[index])) { + *result = Version[index]; + return NS_OK; + } + } - return NS_OK; + return NS_ERROR_FAILURE; +} + +////////////////////////////////////////// +// SpdyPushCache +////////////////////////////////////////// + +SpdyPushCache::SpdyPushCache() +{ +} + +SpdyPushCache::~SpdyPushCache() +{ + mHashSpdy3.Clear(); + mHashSpdy31.Clear(); +} + +bool +SpdyPushCache::RegisterPushedStreamSpdy3(nsCString key, + SpdyPushedStream3 *stream) +{ + LOG3(("SpdyPushCache::RegisterPushedStreamSpdy3 %s 0x%X\n", + key.get(), stream->StreamID())); + if(mHashSpdy3.Get(key)) + return false; + mHashSpdy3.Put(key, stream); + return true; +} + +SpdyPushedStream3 * +SpdyPushCache::RemovePushedStreamSpdy3(nsCString key) +{ + SpdyPushedStream3 *rv = mHashSpdy3.Get(key); + LOG3(("SpdyPushCache::RemovePushedStream %s 0x%X\n", + key.get(), rv ? rv->StreamID() : 0)); + if (rv) + mHashSpdy3.Remove(key); + return rv; +} + +bool +SpdyPushCache::RegisterPushedStreamSpdy31(nsCString key, + SpdyPushedStream31 *stream) +{ + LOG3(("SpdyPushCache::RegisterPushedStreamSpdy31 %s 0x%X\n", + key.get(), stream->StreamID())); + if(mHashSpdy31.Get(key)) + return false; + mHashSpdy31.Put(key, stream); + return true; +} + +SpdyPushedStream31 * +SpdyPushCache::RemovePushedStreamSpdy31(nsCString key) +{ + SpdyPushedStream31 *rv = mHashSpdy31.Get(key); + LOG3(("SpdyPushCache::RemovePushedStream %s 0x%X\n", + key.get(), rv ? rv->StreamID() : 0)); + if (rv) + mHashSpdy31.Remove(key); + return rv; } } // namespace mozilla::net diff --git a/netwerk/protocol/http/ASpdySession.h b/netwerk/protocol/http/ASpdySession.h index 77121b0df280..da0dd3964f6d 100644 --- a/netwerk/protocol/http/ASpdySession.h +++ b/netwerk/protocol/http/ASpdySession.h @@ -15,13 +15,6 @@ class nsISocketTransport; namespace mozilla { namespace net { -// This is designed to handle up to 2 concrete protocol levels -// simultaneously -// -// Currently supported are v3 (preferred), and v2 -// network.protocol.http.spdy.enabled.v2 (and v3) prefs can enable/disable -// them. - class ASpdySession : public nsAHttpTransaction { public: @@ -58,16 +51,17 @@ public: SpdyInformation(); ~SpdyInformation() {} - // determine if a version of the protocol is enabled. The primary - // version is index 0, the secondary version is index 1. + 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); - uint8_t Version[2]; - nsCString VersionString[2]; + uint8_t Version[kCount]; + nsCString VersionString[kCount]; }; }} // namespace mozilla::net diff --git a/netwerk/protocol/http/ConnectionDiagnostics.cpp b/netwerk/protocol/http/ConnectionDiagnostics.cpp index 70cb0505a789..a3c21219005f 100644 --- a/netwerk/protocol/http/ConnectionDiagnostics.cpp +++ b/netwerk/protocol/http/ConnectionDiagnostics.cpp @@ -11,6 +11,7 @@ #include "nsHttpConnection.h" #include "SpdySession2.h" #include "SpdySession3.h" +#include "SpdySession31.h" #include "nsHttpHandler.h" #include "nsIConsoleService.h" #include "nsHttpRequestHead.h" @@ -159,6 +160,43 @@ nsHttpConnection::PrintDiagnostics(nsCString &log) 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) { @@ -197,9 +235,9 @@ SpdySession3::PrintDiagnostics(nsCString &log) } void -SpdySession2::PrintDiagnostics(nsCString &log) +SpdySession31::PrintDiagnostics(nsCString &log) { - log.AppendPrintf(" ::: SPDY VERSION 2\n"); + log.AppendPrintf(" ::: SPDY VERSION 3.1\n"); log.AppendPrintf(" shouldgoaway = %d mClosed = %d CanReuse = %d nextID=0x%X\n", mShouldGoAway, mClosed, CanReuse(), mNextStreamID); diff --git a/netwerk/protocol/http/PSpdyPush3.h b/netwerk/protocol/http/PSpdyPush.h similarity index 62% rename from netwerk/protocol/http/PSpdyPush3.h rename to netwerk/protocol/http/PSpdyPush.h index 1a914131ad60..b003501822aa 100644 --- a/netwerk/protocol/http/PSpdyPush3.h +++ b/netwerk/protocol/http/PSpdyPush.h @@ -3,8 +3,7 @@ * 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/. */ -// SPDY Server Push as defined by -// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3 +// SPDY Server Push /* A pushed stream is put into a memory buffer (The SpdyPushTransactionBuffer) @@ -23,8 +22,8 @@ client pull behavior. */ -#ifndef mozilla_net_SpdyPush3_Public_h -#define mozilla_net_SpdyPush3_Public_h +#ifndef mozilla_net_SpdyPush_Public_h +#define mozilla_net_SpdyPush_Public_h #include "nsAutoPtr.h" #include "nsDataHashtable.h" @@ -36,25 +35,35 @@ namespace mozilla { namespace net { class SpdyPushedStream3; +class SpdyPushedStream31; -// One Cache per load group -class SpdyPushCache3 +// One cache per load group +class SpdyPushCache { public: - SpdyPushCache3(); - virtual ~SpdyPushCache3(); - // The cache holds only weak pointers - no references - bool RegisterPushedStream(nsCString key, - SpdyPushedStream3 *stream); - SpdyPushedStream3 *RemovePushedStream(nsCString key); - SpdyPushedStream3 *GetPushedStream(nsCString key); + SpdyPushCache(); + virtual ~SpdyPushCache(); + +// for spdy/3 +public: + bool RegisterPushedStreamSpdy3(nsCString key, + SpdyPushedStream3 *stream); + SpdyPushedStream3 *RemovePushedStreamSpdy3(nsCString key); private: - nsDataHashtable mHash; + nsDataHashtable mHashSpdy3; + +// for spdy/3.1 +public: + bool RegisterPushedStreamSpdy31(nsCString key, + SpdyPushedStream31 *stream); + SpdyPushedStream31 *RemovePushedStreamSpdy31(nsCString key); +private: + nsDataHashtable mHashSpdy31; }; } // namespace mozilla::net } // namespace mozilla -#endif // mozilla_net_SpdyPush3_Public_h +#endif // mozilla_net_SpdyPush_Public_h diff --git a/netwerk/protocol/http/SpdyPush3.cpp b/netwerk/protocol/http/SpdyPush3.cpp index d2b0a840227f..bae80a910bfc 100644 --- a/netwerk/protocol/http/SpdyPush3.cpp +++ b/netwerk/protocol/http/SpdyPush3.cpp @@ -10,7 +10,7 @@ #include #include "SpdyPush3.h" -#include "PSpdyPush3.h" +#include "PSpdyPush.h" #include "SpdySession3.h" #include "nsHttpRequestHead.h" @@ -137,7 +137,7 @@ SpdyPushedStream3::IsOrphaned(TimeStamp now) bool rv = ((now - mLastRead).ToSeconds() > 30.0); if (rv) { - LOG3(("SpdyPushCache3::IsOrphaned 0x%X IsOrphaned %3.2f\n", + LOG3(("SpdyPushCache::IsOrphaned 0x%X IsOrphaned %3.2f\n", mStreamID, (now - mLastRead).ToSeconds())); } return rv; @@ -161,47 +161,6 @@ SpdyPushedStream3::GetBufferedData(char *buf, return rv; } -////////////////////////////////////////// -// SpdyPushCache3 -////////////////////////////////////////// - -SpdyPushCache3::SpdyPushCache3() -{ -} - -SpdyPushCache3::~SpdyPushCache3() -{ -} - -SpdyPushedStream3 * -SpdyPushCache3::GetPushedStream(nsCString key) -{ - return mHash.Get(key); -} - -bool -SpdyPushCache3::RegisterPushedStream(nsCString key, - SpdyPushedStream3 *stream) -{ - LOG3(("SpdyPushCache3::RegisterPushedStream %s 0x%X\n", - key.get(), stream->StreamID())); - if(mHash.Get(key)) - return false; - mHash.Put(key, stream); - return true; -} - -SpdyPushedStream3 * -SpdyPushCache3::RemovePushedStream(nsCString key) -{ - SpdyPushedStream3 *rv = mHash.Get(key); - LOG3(("SpdyPushCache3::RemovePushedStream %s 0x%X\n", - key.get(), rv ? rv->StreamID() : 0)); - if (rv) - mHash.Remove(key); - return rv; -} - ////////////////////////////////////////// // SpdyPush3TransactionBuffer // This is the nsAHttpTransction owned by the stream when the pushed diff --git a/netwerk/protocol/http/SpdyPush31.cpp b/netwerk/protocol/http/SpdyPush31.cpp new file mode 100644 index 000000000000..c8cf704e3ce1 --- /dev/null +++ b/netwerk/protocol/http/SpdyPush31.cpp @@ -0,0 +1,355 @@ +/* -*- 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 + +#include "nsDependentString.h" +#include "SpdyPush31.h" + +namespace mozilla { +namespace net { + +////////////////////////////////////////// +// SpdyPushedStream31 +////////////////////////////////////////// + +SpdyPushedStream31::SpdyPushedStream31(SpdyPush31TransactionBuffer *aTransaction, + SpdySession31 *aSession, + SpdyStream31 *aAssociatedStream, + uint32_t aID) + :SpdyStream31(aTransaction, aSession, + 0 /* priority is only for sending, so ignore it on push */) + , mConsumerStream(nullptr) + , mBufferedPush(aTransaction) + , mStatus(NS_OK) + , mPushCompleted(false) + , mDeferCleanupOnSuccess(true) +{ + LOG3(("SpdyPushedStream31 ctor this=%p id=0x%X\n", this, aID)); + mStreamID = aID; + mBufferedPush->SetPushStream(this); + mLoadGroupCI = aAssociatedStream->LoadGroupConnectionInfo(); + mLastRead = TimeStamp::Now(); +} + +bool +SpdyPushedStream31::GetPushComplete() +{ + return mPushCompleted; +} + +nsresult +SpdyPushedStream31::WriteSegments(nsAHttpSegmentWriter *writer, + uint32_t count, + uint32_t *countWritten) +{ + nsresult rv = SpdyStream31::WriteSegments(writer, count, countWritten); + if (NS_SUCCEEDED(rv) && *countWritten) { + mLastRead = TimeStamp::Now(); + } + + if (rv == NS_BASE_STREAM_CLOSED) { + mPushCompleted = true; + rv = NS_OK; // this is what a normal HTTP transaction would do + } + if (rv != NS_BASE_STREAM_WOULD_BLOCK && NS_FAILED(rv)) + mStatus = rv; + return rv; +} + +nsresult +SpdyPushedStream31::ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *count) +{ + // The SYN_STREAM for this has been processed, so we need to verify + // that :host, :scheme, and :path MUST be present + nsDependentCSubstring host, scheme, path; + nsresult rv; + + rv = SpdyStream31::FindHeader(NS_LITERAL_CSTRING(":host"), host); + if (NS_FAILED(rv)) { + LOG3(("SpdyPushedStream31::ReadSegments session=%p ID 0x%X " + "push without required :host\n", mSession, mStreamID)); + return rv; + } + + rv = SpdyStream31::FindHeader(NS_LITERAL_CSTRING(":scheme"), scheme); + if (NS_FAILED(rv)) { + LOG3(("SpdyPushedStream31::ReadSegments session=%p ID 0x%X " + "push without required :scheme\n", mSession, mStreamID)); + return rv; + } + + rv = SpdyStream31::FindHeader(NS_LITERAL_CSTRING(":path"), path); + if (NS_FAILED(rv)) { + LOG3(("SpdyPushedStream31::ReadSegments session=%p ID 0x%X " + "push without required :host\n", mSession, mStreamID)); + return rv; + } + + CreatePushHashKey(nsCString(scheme), nsCString(host), + mSession->Serial(), path, + mOrigin, mHashKey); + + LOG3(("SpdyPushStream31 0x%X hash key %s\n", mStreamID, mHashKey.get())); + + // the write side of a pushed transaction just involves manipulating a little state + SpdyStream31::mSentFinOnData = 1; + SpdyStream31::mSynFrameComplete = 1; + SpdyStream31::ChangeState(UPSTREAM_COMPLETE); + *count = 0; + return NS_OK; +} + +bool +SpdyPushedStream31::GetHashKey(nsCString &key) +{ + if (mHashKey.IsEmpty()) + return false; + + key = mHashKey; + return true; +} + +void +SpdyPushedStream31::ConnectPushedStream(SpdyStream31 *stream) +{ + mSession->ConnectPushedStream(stream); +} + +bool +SpdyPushedStream31::IsOrphaned(TimeStamp now) +{ + MOZ_ASSERT(!now.IsNull()); + + // if spdy is not transmitting, and is also not connected to a consumer + // stream, and its been like that for too long then it is oprhaned + + if (mConsumerStream) + return false; + + bool rv = ((now - mLastRead).ToSeconds() > 30.0); + if (rv) { + LOG3(("SpdyPushedStream31::IsOrphaned 0x%X IsOrphaned %3.2f\n", + mStreamID, (now - mLastRead).ToSeconds())); + } + return rv; +} + +nsresult +SpdyPushedStream31::GetBufferedData(char *buf, + uint32_t count, + uint32_t *countWritten) +{ + if (NS_FAILED(mStatus)) + return mStatus; + + nsresult rv = mBufferedPush->GetBufferedData(buf, count, countWritten); + if (NS_FAILED(rv)) + return rv; + + if (!*countWritten) + rv = GetPushComplete() ? NS_BASE_STREAM_CLOSED : NS_BASE_STREAM_WOULD_BLOCK; + + return rv; +} + +////////////////////////////////////////// +// SpdyPush31TransactionBuffer +// This is the nsAHttpTransction owned by the stream when the pushed +// stream has not yet been matched with a pull request +////////////////////////////////////////// + +NS_IMPL_ISUPPORTS0(SpdyPush31TransactionBuffer) + +SpdyPush31TransactionBuffer::SpdyPush31TransactionBuffer() + : mStatus(NS_OK) + , mRequestHead(nullptr) + , mPushStream(nullptr) + , mIsDone(false) + , mBufferedHTTP1Size(kDefaultBufferSize) + , mBufferedHTTP1Used(0) + , mBufferedHTTP1Consumed(0) +{ + mBufferedHTTP1 = new char[mBufferedHTTP1Size]; +} + +SpdyPush31TransactionBuffer::~SpdyPush31TransactionBuffer() +{ + delete mRequestHead; +} + +void +SpdyPush31TransactionBuffer::SetConnection(nsAHttpConnection *conn) +{ +} + +nsAHttpConnection * +SpdyPush31TransactionBuffer::Connection() +{ + return nullptr; +} + +void +SpdyPush31TransactionBuffer::GetSecurityCallbacks(nsIInterfaceRequestor **outCB) +{ + *outCB = nullptr; +} + +void +SpdyPush31TransactionBuffer::OnTransportStatus(nsITransport* transport, + nsresult status, uint64_t progress) +{ +} + +bool +SpdyPush31TransactionBuffer::IsDone() +{ + return mIsDone; +} + +nsresult +SpdyPush31TransactionBuffer::Status() +{ + return mStatus; +} + +uint32_t +SpdyPush31TransactionBuffer::Caps() +{ + return 0; +} + +uint64_t +SpdyPush31TransactionBuffer::Available() +{ + return mBufferedHTTP1Used - mBufferedHTTP1Consumed; +} + +nsresult +SpdyPush31TransactionBuffer::ReadSegments(nsAHttpSegmentReader *reader, + uint32_t count, uint32_t *countRead) +{ + *countRead = 0; + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +SpdyPush31TransactionBuffer::WriteSegments(nsAHttpSegmentWriter *writer, + uint32_t count, uint32_t *countWritten) +{ + if ((mBufferedHTTP1Size - mBufferedHTTP1Used) < 20480) { + SpdySession31::EnsureBuffer(mBufferedHTTP1, + mBufferedHTTP1Size + kDefaultBufferSize, + mBufferedHTTP1Used, + mBufferedHTTP1Size); + } + + count = std::min(count, mBufferedHTTP1Size - mBufferedHTTP1Used); + nsresult rv = writer->OnWriteSegment(mBufferedHTTP1 + mBufferedHTTP1Used, + count, countWritten); + if (NS_SUCCEEDED(rv)) { + mBufferedHTTP1Used += *countWritten; + } + else if (rv == NS_BASE_STREAM_CLOSED) { + mIsDone = true; + } + + if (Available()) { + SpdyStream31 *consumer = mPushStream->GetConsumerStream(); + + if (consumer) { + LOG3(("SpdyPush31TransactionBuffer::WriteSegments notifying connection " + "consumer data available 0x%X [%u]\n", + mPushStream->StreamID(), Available())); + mPushStream->ConnectPushedStream(consumer); + } + } + + return rv; +} + +uint32_t +SpdyPush31TransactionBuffer::Http1xTransactionCount() +{ + return 0; +} + +nsHttpRequestHead * +SpdyPush31TransactionBuffer::RequestHead() +{ + if (!mRequestHead) + mRequestHead = new nsHttpRequestHead(); + return mRequestHead; +} + +nsresult +SpdyPush31TransactionBuffer::TakeSubTransactions( + nsTArray > &outTransactions) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +void +SpdyPush31TransactionBuffer::SetProxyConnectFailed() +{ +} + +void +SpdyPush31TransactionBuffer::Close(nsresult reason) +{ + mStatus = reason; + mIsDone = true; +} + +nsresult +SpdyPush31TransactionBuffer::AddTransaction(nsAHttpTransaction *trans) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +uint32_t +SpdyPush31TransactionBuffer::PipelineDepth() +{ + return 0; +} + +nsresult +SpdyPush31TransactionBuffer::SetPipelinePosition(int32_t position) +{ + return NS_OK; +} + +int32_t +SpdyPush31TransactionBuffer::PipelinePosition() +{ + return 1; +} + +nsresult +SpdyPush31TransactionBuffer::GetBufferedData(char *buf, + uint32_t count, + uint32_t *countWritten) +{ + *countWritten = std::min(count, static_cast(Available())); + if (*countWritten) { + memcpy(buf, mBufferedHTTP1 + mBufferedHTTP1Consumed, *countWritten); + mBufferedHTTP1Consumed += *countWritten; + } + + // If all the data has been consumed then reset the buffer + if (mBufferedHTTP1Consumed == mBufferedHTTP1Used) { + mBufferedHTTP1Consumed = 0; + mBufferedHTTP1Used = 0; + } + + return NS_OK; +} + +} // namespace mozilla::net +} // namespace mozilla diff --git a/netwerk/protocol/http/SpdyPush31.h b/netwerk/protocol/http/SpdyPush31.h new file mode 100644 index 000000000000..02bdb99948a3 --- /dev/null +++ b/netwerk/protocol/http/SpdyPush31.h @@ -0,0 +1,101 @@ +/* -*- 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/. */ + +// spdy/3.1 + +#ifndef mozilla_net_SpdyPush31_Internal_h +#define mozilla_net_SpdyPush3_Internal_h + +#include "mozilla/Attributes.h" +#include "mozilla/TimeStamp.h" +#include "nsHttpRequestHead.h" +#include "nsILoadGroup.h" +#include "nsString.h" +#include "PSpdyPush.h" +#include "SpdySession31.h" +#include "SpdyStream31.h" + +namespace mozilla { +namespace net { + +class SpdyPush31TransactionBuffer; + +class SpdyPushedStream31 MOZ_FINAL : public SpdyStream31 +{ +public: + SpdyPushedStream31(SpdyPush31TransactionBuffer *aTransaction, + SpdySession31 *aSession, + SpdyStream31 *aAssociatedStream, + uint32_t aID); + virtual ~SpdyPushedStream31() {} + + bool GetPushComplete(); + SpdyStream31 *GetConsumerStream() { return mConsumerStream; }; + void SetConsumerStream(SpdyStream31 *aStream) { mConsumerStream = aStream; } + bool GetHashKey(nsCString &key); + + // override of SpdyStream31 + nsresult ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *); + nsresult WriteSegments(nsAHttpSegmentWriter *, uint32_t, uint32_t *); + + nsILoadGroupConnectionInfo *LoadGroupConnectionInfo() { return mLoadGroupCI; }; + void ConnectPushedStream(SpdyStream31 *consumer); + + bool DeferCleanupOnSuccess() { return mDeferCleanupOnSuccess; } + void SetDeferCleanupOnSuccess(bool val) { mDeferCleanupOnSuccess = val; } + + bool IsOrphaned(TimeStamp now); + + nsresult GetBufferedData(char *buf, uint32_t count, uint32_t *countWritten); + + // overload of SpdyStream31 + virtual bool HasSink() { return !!mConsumerStream; } + +private: + + SpdyStream31 *mConsumerStream; // paired request stream that consumes from + // real spdy one.. null until a match is made. + + nsCOMPtr mLoadGroupCI; + + SpdyPush31TransactionBuffer *mBufferedPush; + mozilla::TimeStamp mLastRead; + + nsCString mHashKey; + nsresult mStatus; + bool mPushCompleted; // server push FIN received + bool mDeferCleanupOnSuccess; +}; + +class SpdyPush31TransactionBuffer MOZ_FINAL : public nsAHttpTransaction +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSAHTTPTRANSACTION + + SpdyPush31TransactionBuffer(); + virtual ~SpdyPush31TransactionBuffer(); + + nsresult GetBufferedData(char *buf, uint32_t count, uint32_t *countWritten); + void SetPushStream(SpdyPushedStream31 *stream) { mPushStream = stream; } + +private: + const static uint32_t kDefaultBufferSize = 4096; + + nsresult mStatus; + nsHttpRequestHead *mRequestHead; + SpdyPushedStream31 *mPushStream; + bool mIsDone; + + nsAutoArrayPtr mBufferedHTTP1; + uint32_t mBufferedHTTP1Size; + uint32_t mBufferedHTTP1Used; + uint32_t mBufferedHTTP1Consumed; +}; + +} // namespace mozilla::net +} // namespace mozilla + +#endif // mozilla_net_SpdyPush3_Internal_h diff --git a/netwerk/protocol/http/SpdySession3.cpp b/netwerk/protocol/http/SpdySession3.cpp index 0390e84445b6..2e1475a8630a 100644 --- a/netwerk/protocol/http/SpdySession3.cpp +++ b/netwerk/protocol/http/SpdySession3.cpp @@ -16,7 +16,7 @@ #include "SpdyPush3.h" #include "SpdySession3.h" #include "SpdyStream3.h" -#include "PSpdyPush3.h" +#include "PSpdyPush.h" #include @@ -988,7 +988,7 @@ SpdySession3::HandleSynStream(SpdySession3 *self) self->mShouldGoAway = true; bool resetStream = true; - SpdyPushCache3 *cache = nullptr; + SpdyPushCache *cache = nullptr; if (!(flags & kFlag_Data_UNI)) { // pushed streams require UNIDIRECTIONAL flag @@ -1017,10 +1017,10 @@ SpdySession3::HandleSynStream(SpdySession3 *self) } else { nsILoadGroupConnectionInfo *loadGroupCI = associatedStream->LoadGroupConnectionInfo(); if (loadGroupCI) { - loadGroupCI->GetSpdyPushCache3(&cache); + loadGroupCI->GetSpdyPushCache(&cache); if (!cache) { - cache = new SpdyPushCache3(); - if (!cache || NS_FAILED(loadGroupCI->SetSpdyPushCache3(cache))) { + cache = new SpdyPushCache(); + if (!cache || NS_FAILED(loadGroupCI->SetSpdyPushCache(cache))) { delete cache; cache = nullptr; } @@ -1101,7 +1101,7 @@ SpdySession3::HandleSynStream(SpdySession3 *self) return NS_OK; } - if (!cache->RegisterPushedStream(key, pushedStream)) { + if (!cache->RegisterPushedStreamSpdy3(key, pushedStream)) { LOG(("SpdySession3::HandleSynStream registerPushedStream Failed\n")); self->CleanupStream(pushedStream, NS_ERROR_FAILURE, RST_INVALID_STREAM); self->ResetDownstreamState(); diff --git a/netwerk/protocol/http/SpdySession31.cpp b/netwerk/protocol/http/SpdySession31.cpp new file mode 100644 index 000000000000..d9adfffe1dd8 --- /dev/null +++ b/netwerk/protocol/http/SpdySession31.cpp @@ -0,0 +1,2857 @@ +/* -*- 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 "mozilla/Telemetry.h" +#include "mozilla/Preferences.h" +#include "nsHttp.h" +#include "nsHttpHandler.h" +#include "nsHttpConnection.h" +#include "nsILoadGroup.h" +#include "prprf.h" +#include "prnetdb.h" +#include "SpdyPush31.h" +#include "SpdySession31.h" +#include "SpdyStream31.h" + +#include + +#ifdef DEBUG +// defined by the socket transport service while active +extern PRThread *gSocketThread; +#endif + +namespace mozilla { +namespace net { + +// SpdySession31 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(SpdySession31) +NS_IMPL_RELEASE(SpdySession31) +NS_INTERFACE_MAP_BEGIN(SpdySession31) +NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection) +NS_INTERFACE_MAP_END + +SpdySession31::SpdySession31(nsAHttpTransaction *aHttpTransaction, + nsISocketTransport *aSocketTransport, + int32_t firstPriority) + : mSocketTransport(aSocketTransport), + mSegmentReader(nullptr), + mSegmentWriter(nullptr), + mNextStreamID(1), + mConcurrentHighWater(0), + mDownstreamState(BUFFERING_FRAME_HEADER), + mInputFrameBufferSize(kDefaultBufferSize), + mInputFrameBufferUsed(0), + mInputFrameDataLast(false), + mInputFrameDataStream(nullptr), + mNeedsCleanup(nullptr), + mShouldGoAway(false), + mClosed(false), + mCleanShutdown(false), + mDataPending(false), + mGoAwayID(0), + mMaxConcurrent(kDefaultMaxConcurrent), + mConcurrent(0), + mServerPushedResources(0), + mServerInitialStreamWindow(kDefaultRwin), + mLocalSessionWindow(kDefaultRwin), + mRemoteSessionWindow(kDefaultRwin), + mOutputQueueSize(kDefaultQueueSize), + mOutputQueueUsed(0), + mOutputQueueSent(0), + mLastReadEpoch(PR_IntervalNow()), + mPingSentEpoch(0), + mNextPingID(1) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + static uint64_t sSerial; + mSerial = ++sSerial; + + LOG3(("SpdySession31::SpdySession31 %p transaction 1 = %p serial=0x%X\n", + this, aHttpTransaction, mSerial)); + + mConnection = aHttpTransaction->Connection(); + mInputFrameBuffer = new char[mInputFrameBufferSize]; + mOutputQueueBuffer = new char[mOutputQueueSize]; + zlibInit(); + + mPushAllowance = gHttpHandler->SpdyPushAllowance(); + + mSendingChunkSize = gHttpHandler->SpdySendingChunkSize(); + GenerateSettings(); + + if (!aHttpTransaction->IsNullTransaction()) + AddStream(aHttpTransaction, firstPriority); + mLastDataReadEpoch = mLastReadEpoch; + + mPingThreshold = gHttpHandler->SpdyPingThreshold(); +} + +PLDHashOperator + SpdySession31::ShutdownEnumerator(nsAHttpTransaction *key, + nsAutoPtr &stream, + void *closure) +{ + SpdySession31 *self = static_cast(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 +SpdySession31::GoAwayEnumerator(nsAHttpTransaction *key, + nsAutoPtr &stream, + void *closure) +{ + SpdySession31 *self = static_cast(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. Be sure not to restart + // a pushed (even numbered) stream + if ((stream->StreamID() > self->mGoAwayID && (stream->StreamID() & 1)) || + !stream->HasRegisteredID()) { + self->mGoAwayStreamsToRestart.Push(stream); + } + + return PL_DHASH_NEXT; +} + +SpdySession31::~SpdySession31() +{ + LOG3(("SpdySession31::~SpdySession31 %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 +SpdySession31::LogIO(SpdySession31 *self, SpdyStream31 *stream, const char *label, + const char *data, uint32_t datalen) +{ + if (!LOG4_ENABLED()) + return; + + LOG4(("SpdySession31::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) (SpdySession31 *self); +static Control_FX sControlFunctions[] = +{ + nullptr, + SpdySession31::HandleSynStream, + SpdySession31::HandleSynReply, + SpdySession31::HandleRstStream, + SpdySession31::HandleSettings, + SpdySession31::HandleNoop, + SpdySession31::HandlePing, + SpdySession31::HandleGoAway, + SpdySession31::HandleHeaders, + SpdySession31::HandleWindowUpdate, + SpdySession31::HandleCredential +}; + +bool +SpdySession31::RoomForMoreConcurrent() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + return (mConcurrent < mMaxConcurrent); +} + +bool +SpdySession31::RoomForMoreStreams() +{ + if (mNextStreamID + mStreamTransactionHash.Count() * 2 > kMaxStreamID) + return false; + + return !mShouldGoAway; +} + +PRIntervalTime +SpdySession31::IdleTime() +{ + return PR_IntervalNow() - mLastDataReadEpoch; +} + +void +SpdySession31::ReadTimeoutTick(PRIntervalTime now) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + MOZ_ASSERT(mNextPingID & 1, "Ping Counter Not Odd"); + + LOG(("SpdySession31::ReadTimeoutTick %p delta since last read %ds\n", + this, PR_IntervalToSeconds(now - mLastReadEpoch))); + + if (!mPingThreshold) + return; + + if ((now - mLastReadEpoch) < mPingThreshold) { + // recent activity means ping is not an issue + if (mPingSentEpoch) + mPingSentEpoch = 0; + return; + } + + if (mPingSentEpoch) { + LOG(("SpdySession31::ReadTimeoutTick %p handle outstanding ping\n", this)); + if ((now - mPingSentEpoch) >= gHttpHandler->SpdyPingTimeout()) { + LOG(("SpdySession31::ReadTimeoutTick %p Ping Timer Exhaustion\n", + this)); + mPingSentEpoch = 0; + Close(NS_ERROR_NET_TIMEOUT); + } + return; + } + + LOG(("SpdySession31::ReadTimeoutTick %p generating ping 0x%X\n", + this, mNextPingID)); + + if (mNextPingID == 0xffffffff) { + LOG(("SpdySession31::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 + + // Check for orphaned push streams. This looks expensive, but generally the + // list is empty. + SpdyPushedStream31 *deleteMe; + TimeStamp timestampNow; + do { + deleteMe = nullptr; + + for (uint32_t index = mPushedStreams.Length(); + index > 0 ; --index) { + SpdyPushedStream31 *pushedStream = mPushedStreams[index - 1]; + + if (timestampNow.IsNull()) + timestampNow = TimeStamp::Now(); // lazy initializer + + // if spdy finished, but not connected, and its been like that for too long.. + // cleanup the stream.. + if (pushedStream->IsOrphaned(timestampNow)) + { + LOG3(("SpdySession31 Timeout Pushed Stream %p 0x%X\n", + this, pushedStream->StreamID())); + deleteMe = pushedStream; + break; // don't CleanupStream() while iterating this vector + } + } + if (deleteMe) + CleanupStream(deleteMe, NS_ERROR_ABORT, RST_CANCEL); + + } while (deleteMe); + + if (mNextPingID == 0xffffffff) { + LOG(("SpdySession31::ReadTimeoutTick %p " + "ping ids exhausted marking goaway\n", this)); + mShouldGoAway = true; + } +} + +uint32_t +SpdySession31::RegisterStreamID(SpdyStream31 *stream, uint32_t aNewID) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + MOZ_ASSERT(mNextStreamID < 0xfffffff0, + "should have stopped admitting streams"); + + MOZ_ASSERT(!(aNewID & 1), + "0 for autoassign pull, otherwise explicit even push assignment"); + if (!aNewID) { + // auto generate a new pull stream ID + aNewID = mNextStreamID; + MOZ_ASSERT(aNewID & 1, "pull ID must be odd."); + mNextStreamID += 2; + } + + LOG3(("SpdySession31::RegisterStreamID session=%p stream=%p id=0x%X " + "concurrent=%d",this, stream, aNewID, mConcurrent)); + + // 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 (aNewID >= kMaxStreamID) + mShouldGoAway = true; + + // integrity check + if (mStreamIDHash.Get(aNewID)) { + LOG3((" New ID already present\n")); + MOZ_ASSERT(false, "New ID already present in mStreamIDHash"); + mShouldGoAway = true; + return kDeadStreamID; + } + + mStreamIDHash.Put(aNewID, stream); + return aNewID; +} + +bool +SpdySession31::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); + SpdyStream31 *stream = new SpdyStream31(aHttpTransaction, this, aPriority); + + LOG3(("SpdySession31::AddStream session=%p stream=%p NextID=0x%X (tentative)", + this, stream, mNextStreamID)); + + mStreamTransactionHash.Put(aHttpTransaction, stream); + + if (RoomForMoreConcurrent()) { + LOG3(("SpdySession31::AddStream %p stream %p activated immediately.", + this, stream)); + ActivateStream(stream); + } + else { + LOG3(("SpdySession31::AddStream %p stream %p queued.", this, stream)); + mQueuedStreams.Push(stream); + } + + if (!(aHttpTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE)) { + LOG3(("SpdySession31::AddStream %p transaction %p forces keep-alive off.\n", + this, aHttpTransaction)); + DontReuse(); + } + + return true; +} + +void +SpdySession31::ActivateStream(SpdyStream31 *stream) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + MOZ_ASSERT(!stream->StreamID() || (stream->StreamID() & 1), + "Do not activate pushed streams"); + + ++mConcurrent; + if (mConcurrent > mConcurrentHighWater) + mConcurrentHighWater = mConcurrent; + LOG3(("SpdySession31::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 +SpdySession31::ProcessPending() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + while (RoomForMoreConcurrent()) { + SpdyStream31 *stream = static_cast(mQueuedStreams.PopFront()); + if (!stream) + return; + LOG3(("SpdySession31::ProcessPending %p stream %p activated from queue.", + this, stream)); + ActivateStream(stream); + } +} + +nsresult +SpdySession31::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 +SpdySession31::SetWriteCallbacks() +{ + if (mConnection && (GetWriteQueueSize() || mOutputQueueUsed)) + mConnection->ResumeSend(); +} + +void +SpdySession31::RealignOutputQueue() +{ + mOutputQueueUsed -= mOutputQueueSent; + memmove(mOutputQueueBuffer.get(), + mOutputQueueBuffer.get() + mOutputQueueSent, + mOutputQueueUsed); + mOutputQueueSent = 0; +} + +void +SpdySession31::FlushOutputQueue() +{ + if (!mSegmentReader || !mOutputQueueUsed) + return; + + nsresult rv; + uint32_t countRead; + uint32_t avail = mOutputQueueUsed - mOutputQueueSent; + + rv = mSegmentReader-> + OnReadSegment(mOutputQueueBuffer.get() + mOutputQueueSent, avail, + &countRead); + LOG3(("SpdySession31::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 +SpdySession31::DontReuse() +{ + mShouldGoAway = true; + if (!mStreamTransactionHash.Count()) + Close(NS_OK); +} + +uint32_t +SpdySession31::GetWriteQueueSize() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + return mReadyForWrite.GetSize(); +} + +void +SpdySession31::ChangeDownstreamState(enum stateType newState) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + LOG3(("SpdyStream31::ChangeDownstreamState() %p from %X to %X", + this, mDownstreamState, newState)); + mDownstreamState = newState; +} + +void +SpdySession31::ResetDownstreamState() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + LOG3(("SpdyStream31::ResetDownstreamState() %p", this)); + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + + if (mInputFrameDataLast && mInputFrameDataStream) { + mInputFrameDataLast = false; + if (!mInputFrameDataStream->RecvdFin()) { + LOG3((" SetRecvdFin id=0x%x\n", mInputFrameDataStream->StreamID())); + mInputFrameDataStream->SetRecvdFin(true); + DecrementConcurrent(mInputFrameDataStream); + } + } + mInputFrameBufferUsed = 0; + mInputFrameDataStream = nullptr; +} + +template void +SpdySession31::EnsureBuffer(nsAutoArrayPtr &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; + + static_assert(sizeof(T) == 1, "sizeof(T) must be 1"); + nsAutoArrayPtr tmp(new T[objSize]); + memcpy(tmp, buf, preserve); + buf = tmp; +} + +// Instantiate supported templates explicitly. +template void +SpdySession31::EnsureBuffer(nsAutoArrayPtr &buf, + uint32_t newSize, + uint32_t preserve, + uint32_t &objSize); + +template void +SpdySession31::EnsureBuffer(nsAutoArrayPtr &buf, + uint32_t newSize, + uint32_t preserve, + uint32_t &objSize); + +void +SpdySession31::DecrementConcurrent(SpdyStream31 *aStream) +{ + uint32_t id = aStream->StreamID(); + + if (id && !(id & 0x1)) + return; // pushed streams aren't counted in concurrent limit + + MOZ_ASSERT(mConcurrent); + --mConcurrent; + LOG3(("DecrementConcurrent %p id=0x%X concurrent=%d\n", + this, id, mConcurrent)); + ProcessPending(); +} + +void +SpdySession31::zlibInit() +{ + mDownstreamZlib.zalloc = SpdyStream31::zlib_allocator; + mDownstreamZlib.zfree = SpdyStream31::zlib_destructor; + mDownstreamZlib.opaque = Z_NULL; + + inflateInit(&mDownstreamZlib); + + mUpstreamZlib.zalloc = SpdyStream31::zlib_allocator; + mUpstreamZlib.zfree = SpdyStream31::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, + SpdyStream31::kDictionary, + sizeof(SpdyStream31::kDictionary)); +} + +// Need to decompress some data in order to keep the compression +// context correct, but we really don't care what the result is +nsresult +SpdySession31::UncompressAndDiscard(uint32_t offset, + uint32_t blockLen) +{ + char *blockStart = mInputFrameBuffer + offset; + unsigned char trash[2048]; + mDownstreamZlib.avail_in = blockLen; + mDownstreamZlib.next_in = reinterpret_cast(blockStart); + bool triedDictionary = false; + + do { + mDownstreamZlib.next_out = trash; + mDownstreamZlib.avail_out = sizeof(trash); + int zlib_rv = inflate(&mDownstreamZlib, Z_NO_FLUSH); + + if (zlib_rv == Z_NEED_DICT) { + if (triedDictionary) { + LOG3(("SpdySession31::UncompressAndDiscard %p Dictionary Error\n", this)); + return NS_ERROR_ILLEGAL_VALUE; + } + + triedDictionary = true; + inflateSetDictionary(&mDownstreamZlib, SpdyStream31::kDictionary, + sizeof(SpdyStream31::kDictionary)); + } + + if (zlib_rv == Z_DATA_ERROR) + return NS_ERROR_ILLEGAL_VALUE; + + if (zlib_rv == Z_MEM_ERROR) + return NS_ERROR_FAILURE; + } + while (mDownstreamZlib.avail_in); + return NS_OK; +} + +void +SpdySession31::GeneratePing(uint32_t aID) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + LOG3(("SpdySession31::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] = kVersion; + 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); + + LogIO(this, nullptr, "Generate Ping", packet, 12); + FlushOutputQueue(); +} + +void +SpdySession31::GenerateRstStream(uint32_t aStatusCode, uint32_t aID) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + LOG3(("SpdySession31::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] = kVersion; + 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); + + LogIO(this, nullptr, "Generate Reset", packet, 16); + FlushOutputQueue(); +} + +void +SpdySession31::GenerateGoAway(uint32_t aStatusCode) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + LOG3(("SpdySession31::GenerateGoAway %p code=%X\n", this, aStatusCode)); + + EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 16, + mOutputQueueUsed, mOutputQueueSize); + char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed; + mOutputQueueUsed += 16; + + memset(packet, 0, 16); + packet[0] = kFlag_Control; + packet[1] = kVersion; + packet[3] = CONTROL_TYPE_GOAWAY; + packet[7] = 8; /* data length */ + + // last-good-stream-id are bytes 8-11, when we accept server push this will + // need to be set non zero + + // bytes 12-15 are the status code. + aStatusCode = PR_htonl(aStatusCode); + memcpy(packet + 12, &aStatusCode, 4); + + LogIO(this, nullptr, "Generate GoAway", packet, 16); + FlushOutputQueue(); +} + +void +SpdySession31::GenerateSettings() +{ + uint32_t sessionWindowBump = ASpdySession::kInitialRwin - kDefaultRwin; + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + LOG3(("SpdySession31::GenerateSettings %p\n", this)); + +// sized for 3 settings and a session window update to follow + static const uint32_t maxDataLen = 4 + 3 * 8 + 16; + EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + 8 + maxDataLen, + mOutputQueueUsed, mOutputQueueSize); + char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed; + + memset(packet, 0, 8 + maxDataLen); + packet[0] = kFlag_Control; + packet[1] = kVersion; + packet[3] = CONTROL_TYPE_SETTINGS; + + uint8_t numberOfEntries = 0; + + // entries need to be listed in order by ID + // 1st entry is bytes 12 to 19 + // 2nd entry is bytes 20 to 27 + // 3rd entry is bytes 28 to 35 + + if (!gHttpHandler->AllowSpdyPush()) { + // announcing that we accept 0 incoming streams is done to + // disable server push + packet[15 + 8 * numberOfEntries] = SETTINGS_TYPE_MAX_CONCURRENT; + // The value portion of the setting pair is already initialized to 0 + numberOfEntries++; + } + + nsRefPtr ci; + uint32_t cwnd = 0; + GetConnectionInfo(getter_AddRefs(ci)); + if (ci) + cwnd = gHttpHandler->ConnMgr()->GetSpdyCWNDSetting(ci); + if (cwnd) { + packet[12 + 8 * numberOfEntries] = PERSISTED_VALUE; + packet[15 + 8 * numberOfEntries] = SETTINGS_TYPE_CWND; + LOG(("SpdySession31::GenerateSettings %p sending CWND %u\n", this, cwnd)); + cwnd = PR_htonl(cwnd); + memcpy(packet + 16 + 8 * numberOfEntries, &cwnd, 4); + numberOfEntries++; + } + + // Advertise the Push RWIN and on each client SYN_STREAM pipeline + // a window update with it in order to use larger initial windows with pulled + // streams. + packet[15 + 8 * numberOfEntries] = SETTINGS_TYPE_INITIAL_WINDOW; + uint32_t rwin = PR_htonl(mPushAllowance); + memcpy(packet + 16 + 8 * numberOfEntries, &rwin, 4); + numberOfEntries++; + + uint32_t dataLen = 4 + 8 * numberOfEntries; + mOutputQueueUsed += 8 + dataLen; + packet[7] = dataLen; + packet[11] = numberOfEntries; + + LogIO(this, nullptr, "Generate Settings", packet, 8 + dataLen); + + if (kDefaultRwin >= ASpdySession::kInitialRwin) + goto generateSettings_complete; + + // send a window update for the session (Stream 0) for something large + sessionWindowBump = PR_htonl(sessionWindowBump); + mLocalSessionWindow = ASpdySession::kInitialRwin; + + packet = mOutputQueueBuffer.get() + mOutputQueueUsed; + mOutputQueueUsed += 16; + + packet[0] = kFlag_Control; + packet[1] = kVersion; + packet[3] = CONTROL_TYPE_WINDOW_UPDATE; + packet[7] = 8; // 8 data bytes after 8 byte header + + // 8 to 11 stay 0 bytes for id = 0 + memcpy(packet + 12, &sessionWindowBump, 4); + + LOG3(("Session Window increase at start of session %p %u\n", + this, PR_ntohl(sessionWindowBump))); + LogIO(this, nullptr, "Session Window Bump ", packet, 12); + +generateSettings_complete: + FlushOutputQueue(); +} + +// perform a bunch of integrity checks on the stream. +// returns true if passed, false (plus LOG and ABORT) if failed. +bool +SpdySession31::VerifyStream(SpdyStream31 *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()) { + SpdyStream31 *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(("SpdySession31 %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 +SpdySession31::CleanupStream(SpdyStream31 *aStream, nsresult aResult, + rstReason aResetCode) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + LOG3(("SpdySession31::CleanupStream %p %p 0x%X %X\n", + this, aStream, aStream->StreamID(), aResult)); + + SpdyPushedStream31 *pushSource = nullptr; + + if (NS_SUCCEEDED(aResult) && aStream->DeferCleanupOnSuccess()) { + LOG(("SpdySession31::CleanupStream 0x%X deferred\n", aStream->StreamID())); + return; + } + + if (!VerifyStream(aStream)) { + LOG(("SpdySession31::CleanupStream failed to verify stream\n")); + return; + } + + pushSource = aStream->PushSource(); + + if (!aStream->RecvdFin() && aStream->StreamID()) { + LOG3(("Stream had not processed recv FIN, sending RST code %X\n", + aResetCode)); + GenerateRstStream(aResetCode, aStream->StreamID()); + DecrementConcurrent(aStream); + } + + CloseStream(aStream, aResult); + + // Remove the stream from the ID hash table and, if an even id, the pushed + // table too. + uint32_t id = aStream->StreamID(); + if (id > 0) { + mStreamIDHash.Remove(id); + if (!(id & 1)) + mPushedStreams.RemoveElement(aStream); + } + + RemoveStreamFromQueues(aStream); + + // removing from the stream transaction hash will + // delete the SpdyStream31 and drop the reference to + // its transaction + mStreamTransactionHash.Remove(aStream->Transaction()); + + if (mShouldGoAway && !mStreamTransactionHash.Count()) + Close(NS_OK); + + if (pushSource) { + pushSource->SetDeferCleanupOnSuccess(false); + CleanupStream(pushSource, aResult, aResetCode); + } +} + +static void RemoveStreamFromQueue(SpdyStream31 *aStream, nsDeque &queue) +{ + uint32_t size = queue.GetSize(); + for (uint32_t count = 0; count < size; ++count) { + SpdyStream31 *stream = static_cast(queue.PopFront()); + if (stream != aStream) + queue.Push(stream); + } +} + +void +SpdySession31::RemoveStreamFromQueues(SpdyStream31 *aStream) +{ + RemoveStreamFromQueue(aStream, mReadyForWrite); + RemoveStreamFromQueue(aStream, mQueuedStreams); + RemoveStreamFromQueue(aStream, mReadyForRead); +} + +void +SpdySession31::CloseStream(SpdyStream31 *aStream, nsresult aResult) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + LOG3(("SpdySession31::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; + } + + RemoveStreamFromQueues(aStream); + + // Send the stream the close() indication + aStream->Close(aResult); +} + +nsresult +SpdySession31::HandleSynStream(SpdySession31 *self) +{ + MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_SYN_STREAM); + + if (self->mInputFrameDataSize < 18) { + LOG3(("SpdySession31::HandleSynStream %p SYN_STREAM too short data=%d", + self, self->mInputFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + uint32_t streamID = + PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[2]); + uint32_t associatedID = + PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[3]); + uint8_t flags = reinterpret_cast(self->mInputFrameBuffer.get())[4]; + + LOG3(("SpdySession31::HandleSynStream %p recv SYN_STREAM (push) " + "for ID 0x%X associated with 0x%X.\n", + self, streamID, associatedID)); + + if (streamID & 0x01) { // test for odd stream ID + LOG3(("SpdySession31::HandleSynStream %p recvd SYN_STREAM id must be even.", + self)); + return NS_ERROR_ILLEGAL_VALUE; + } + + // confirm associated-to + nsresult rv = self->SetInputFrameDataStream(associatedID); + if (NS_FAILED(rv)) + return rv; + SpdyStream31 *associatedStream = self->mInputFrameDataStream; + + ++(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; + + bool resetStream = true; + SpdyPushCache *cache = nullptr; + + if (!(flags & kFlag_Data_UNI)) { + // pushed streams require UNIDIRECTIONAL flag + LOG3(("SpdySession31::HandleSynStream %p ID %0x%X associated ID 0x%X failed.\n", + self, streamID, associatedID)); + self->GenerateRstStream(RST_PROTOCOL_ERROR, streamID); + + } else if (!associatedID) { + // associated stream 0 will never find a match, but the spec requires a + // PROTOCOL_ERROR in this specific case + LOG3(("SpdySession31::HandleSynStream %p associated ID of 0 failed.\n", self)); + self->GenerateRstStream(RST_PROTOCOL_ERROR, streamID); + + } else if (!gHttpHandler->AllowSpdyPush()) { + // MAX_CONCURRENT_STREAMS of 0 in settings should have disabled push, + // but some servers are buggy about that.. or the config could have + // been updated after the settings frame was sent. In both cases just + // reject the pushed stream as refused + LOG3(("SpdySession31::HandleSynStream Push Recevied when Disabled\n")); + self->GenerateRstStream(RST_REFUSED_STREAM, streamID); + + } else if (!associatedStream) { + LOG3(("SpdySession31::HandleSynStream %p lookup associated ID failed.\n", self)); + self->GenerateRstStream(RST_INVALID_STREAM, streamID); + + } else { + nsILoadGroupConnectionInfo *loadGroupCI = associatedStream->LoadGroupConnectionInfo(); + if (loadGroupCI) { + loadGroupCI->GetSpdyPushCache(&cache); + if (!cache) { + cache = new SpdyPushCache(); + if (!cache || NS_FAILED(loadGroupCI->SetSpdyPushCache(cache))) { + delete cache; + cache = nullptr; + } + } + } + if (!cache) { + // this is unexpected, but we can handle it just be refusing the push + LOG3(("SpdySession31::HandleSynStream Push Recevied without loadgroup cache\n")); + self->GenerateRstStream(RST_REFUSED_STREAM, streamID); + } + else { + resetStream = false; + } + } + + if (resetStream) { + // 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 + rv = self->UncompressAndDiscard(18, self->mInputFrameDataSize - 10); + if (NS_FAILED(rv)) { + LOG(("SpdySession31::HandleSynStream uncompress failed\n")); + return rv; + } + self->ResetDownstreamState(); + return NS_OK; + } + + // Create the buffering transaction and push stream + nsRefPtr transactionBuffer = + new SpdyPush31TransactionBuffer(); + transactionBuffer->SetConnection(self); + SpdyPushedStream31 *pushedStream = + new SpdyPushedStream31(transactionBuffer, self, + associatedStream, streamID); + + // ownership of the pushed stream is by the transaction hash, just as it + // is for a client initiated stream. Errors that aren't fatal to the + // whole session must call cleanupStream() after this point in order + // to remove the stream from that hash. + self->mStreamTransactionHash.Put(transactionBuffer, pushedStream); + self->mPushedStreams.AppendElement(pushedStream); + + // The pushed stream is unidirectional so it is fully open immediately + pushedStream->SetFullyOpen(); + + // Uncompress the response headers into a stream specific buffer, leaving them + // in spdy format for the time being. + rv = pushedStream->Uncompress(&self->mDownstreamZlib, + self->mInputFrameBuffer + 18, + self->mInputFrameDataSize - 10); + if (NS_FAILED(rv)) { + LOG(("SpdySession31::HandleSynStream uncompress failed\n")); + return rv; + } + + if (self->RegisterStreamID(pushedStream, streamID) == kDeadStreamID) { + LOG(("SpdySession31::HandleSynStream registerstreamid failed\n")); + return NS_ERROR_FAILURE; + } + + // Fake the request side of the pushed HTTP transaction. Sets up hash + // key and origin + uint32_t notUsed; + pushedStream->ReadSegments(nullptr, 1, ¬Used); + + nsAutoCString key; + if (!pushedStream->GetHashKey(key)) { + LOG(("SpdySession31::HandleSynStream one of :host :scheme :path missing from push\n")); + self->CleanupStream(pushedStream, NS_ERROR_FAILURE, RST_INVALID_STREAM); + self->ResetDownstreamState(); + return NS_OK; + } + + if (!associatedStream->Origin().Equals(pushedStream->Origin())) { + LOG(("SpdySession31::HandleSynStream pushed stream mismatched origin\n")); + self->CleanupStream(pushedStream, NS_ERROR_FAILURE, RST_INVALID_STREAM); + self->ResetDownstreamState(); + return NS_OK; + } + + if (!cache->RegisterPushedStreamSpdy31(key, pushedStream)) { + LOG(("SpdySession31::HandleSynStream registerPushedStream Failed\n")); + self->CleanupStream(pushedStream, NS_ERROR_FAILURE, RST_INVALID_STREAM); + self->ResetDownstreamState(); + return NS_OK; + } + + self->ResetDownstreamState(); + return NS_OK; +} + +nsresult +SpdySession31::SetInputFrameDataStream(uint32_t streamID) +{ + mInputFrameDataStream = mStreamIDHash.Get(streamID); + if (VerifyStream(mInputFrameDataStream, streamID)) + return NS_OK; + + LOG(("SpdySession31::SetInputFrameDataStream failed to verify 0x%X\n", + streamID)); + mInputFrameDataStream = nullptr; + return NS_ERROR_UNEXPECTED; +} + +nsresult +SpdySession31::HandleSynReply(SpdySession31 *self) +{ + MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_SYN_REPLY); + + if (self->mInputFrameDataSize < 4) { + LOG3(("SpdySession31::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; + } + + LOG3(("SpdySession31::HandleSynReply %p lookup via streamID in syn_reply.\n", + self)); + uint32_t streamID = + PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[2]); + nsresult rv = self->SetInputFrameDataStream(streamID); + if (NS_FAILED(rv)) + return rv; + + if (!self->mInputFrameDataStream) { + // Cannot find stream. We can continue the SPDY session, but we need to + // uncompress the header block to maintain the correct compression context + + LOG3(("SpdySession31::HandleSynReply %p lookup streamID in syn_reply " + "0x%X failed. NextStreamID = 0x%X\n", + self, streamID, self->mNextStreamID)); + + if (streamID >= self->mNextStreamID) + self->GenerateRstStream(RST_INVALID_STREAM, streamID); + + rv = self->UncompressAndDiscard(12, self->mInputFrameDataSize - 4); + if (NS_FAILED(rv)) { + LOG(("SpdySession31::HandleSynReply uncompress failed\n")); + // this is fatal to the session + return rv; + } + + self->ResetDownstreamState(); + return NS_OK; + } + + // Uncompress the headers into a stream specific buffer, 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. + rv = self->mInputFrameDataStream->Uncompress(&self->mDownstreamZlib, + self->mInputFrameBuffer + 12, + self->mInputFrameDataSize - 4); + + if (NS_FAILED(rv)) { + LOG(("SpdySession31::HandleSynReply uncompress failed\n")); + return rv; + } + + if (self->mInputFrameDataStream->GetFullyOpen()) { + // "If an endpoint receives multiple SYN_REPLY frames for the same active + // stream ID, it MUST issue a stream error (Section 2.4.2) with the error + // code STREAM_IN_USE." + // + // "STREAM_ALREADY_CLOSED. The endpoint received a data or SYN_REPLY + // frame for a stream which is half closed." + // + // If the stream is open then just RST_STREAM with STREAM_IN_USE + // If the stream is half closed then RST_STREAM with STREAM_ALREADY_CLOSED + // abort the session + // + LOG3(("SpdySession31::HandleSynReply %p dup SYN_REPLY for 0x%X" + " recvdfin=%d", self, self->mInputFrameDataStream->StreamID(), + self->mInputFrameDataStream->RecvdFin())); + + self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ALREADY_OPENED, + self->mInputFrameDataStream->RecvdFin() ? + RST_STREAM_ALREADY_CLOSED : RST_STREAM_IN_USE); + self->ResetDownstreamState(); + return NS_OK; + } + self->mInputFrameDataStream->SetFullyOpen(); + + self->mInputFrameDataLast = self->mInputFrameBuffer[4] & kFlag_Data_FIN; + self->mInputFrameDataStream->UpdateTransportReadEvents(self->mInputFrameDataSize); + self->mLastDataReadEpoch = self->mLastReadEpoch; + + if (self->mInputFrameBuffer[4] & ~kFlag_Data_FIN) { + LOG3(("SynReply %p had undefined flag set 0x%X\n", self, streamID)); + self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE, + RST_PROTOCOL_ERROR); + self->ResetDownstreamState(); + return NS_OK; + } + + if (!self->mInputFrameDataLast) { + // don't process the headers yet as there could be more coming from HEADERS + // frames + self->ResetDownstreamState(); + return NS_OK; + } + + rv = self->ResponseHeadersComplete(); + if (rv == NS_ERROR_ILLEGAL_VALUE) { + LOG3(("SpdySession31::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; +} + +// ResponseHeadersComplete() 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 +SpdySession31::ResponseHeadersComplete() +{ + LOG3(("SpdySession31::ResponseHeadersComplete %p for 0x%X fin=%d", + this, mInputFrameDataStream->StreamID(), mInputFrameDataLast)); + + // The spdystream needs to see flattened http headers + // Uncompressed spdy format headers currently live in + // SpdyStream31::mDecompressBuffer - convert that to HTTP format in + // mFlatHTTPResponseHeaders via ConvertHeaders() + + mFlatHTTPResponseHeadersOut = 0; + nsresult rv = mInputFrameDataStream->ConvertHeaders(mFlatHTTPResponseHeaders); + if (NS_FAILED(rv)) + return rv; + + ChangeDownstreamState(PROCESSING_COMPLETE_HEADERS); + return NS_OK; +} + +nsresult +SpdySession31::HandleRstStream(SpdySession31 *self) +{ + MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_RST_STREAM); + + if (self->mInputFrameDataSize != 8) { + LOG3(("SpdySession31::HandleRstStream %p RST_STREAM wrong length data=%d", + self, self->mInputFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + uint8_t flags = reinterpret_cast(self->mInputFrameBuffer.get())[4]; + + uint32_t streamID = + PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[2]); + + self->mDownstreamRstReason = + PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[3]); + + LOG3(("SpdySession31::HandleRstStream %p RST_STREAM Reason Code %u ID %x " + "flags %x", self, self->mDownstreamRstReason, streamID, flags)); + + if (flags != 0) { + LOG3(("SpdySession31::HandleRstStream %p RST_STREAM with flags is illegal", + self)); + return NS_ERROR_ILLEGAL_VALUE; + } + + if (self->mDownstreamRstReason == RST_INVALID_STREAM || + self->mDownstreamRstReason == RST_STREAM_IN_USE || + self->mDownstreamRstReason == RST_FLOW_CONTROL_ERROR) { + // basically just ignore this + LOG3(("SpdySession31::HandleRstStream %p No Reset Processing Needed.\n")); + self->ResetDownstreamState(); + return NS_OK; + } + + nsresult rv = self->SetInputFrameDataStream(streamID); + + if (!self->mInputFrameDataStream) { + if (NS_FAILED(rv)) + LOG(("SpdySession31::HandleRstStream %p lookup streamID for RST Frame " + "0x%X failed reason = %d :: VerifyStream Failed\n", self, streamID, + self->mDownstreamRstReason)); + + LOG3(("SpdySession31::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; +} + +PLDHashOperator +SpdySession31::UpdateServerRwinEnumerator(nsAHttpTransaction *key, + nsAutoPtr &stream, + void *closure) +{ + int32_t delta = *(static_cast(closure)); + stream->UpdateRemoteWindow(delta); + return PL_DHASH_NEXT; +} + +nsresult +SpdySession31::HandleSettings(SpdySession31 *self) +{ + MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_SETTINGS); + + if (self->mInputFrameDataSize < 4) { + LOG3(("SpdySession31::HandleSettings %p SETTINGS wrong length data=%d", + self, self->mInputFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + uint32_t numEntries = + PR_ntohl(reinterpret_cast(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(("SpdySession31::HandleSettings %p SETTINGS wrong length data=%d", + self, self->mInputFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + LOG3(("SpdySession31::HandleSettings %p SETTINGS Control Frame with %d entries", + self, numEntries)); + + for (uint32_t index = 0; index < numEntries; ++index) { + unsigned char *setting = reinterpret_cast + (self->mInputFrameBuffer.get()) + 12 + index * 8; + + uint32_t flags = setting[0]; + uint32_t id = PR_ntohl(reinterpret_cast(setting)[0]) & 0xffffff; + uint32_t value = PR_ntohl(reinterpret_cast(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: + if (flags & PERSIST_VALUE) + { + nsRefPtr ci; + self->GetConnectionInfo(getter_AddRefs(ci)); + if (ci) + gHttpHandler->ConnMgr()->ReportSpdyCWNDSetting(ci, value); + } + 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); + { + int32_t delta = value - self->mServerInitialStreamWindow; + self->mServerInitialStreamWindow = value; + + // do not use SETTINGS to adjust the session window. + + // we need to add the delta to all open streams (delta can be negative) + self->mStreamTransactionHash.Enumerate(UpdateServerRwinEnumerator, + &delta); + } + break; + + default: + break; + } + + } + + self->ResetDownstreamState(); + return NS_OK; +} + +nsresult +SpdySession31::HandleNoop(SpdySession31 *self) +{ + MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_NOOP); + + // Should not be receiving noop frames in spdy/3, so we'll just + // make a log and ignore it + + LOG3(("SpdySession31::HandleNoop %p NOP.", self)); + + self->ResetDownstreamState(); + return NS_OK; +} + +nsresult +SpdySession31::HandlePing(SpdySession31 *self) +{ + MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_PING); + + if (self->mInputFrameDataSize != 4) { + LOG3(("SpdySession31::HandlePing %p PING had wrong amount of data %d", + self, self->mInputFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + uint32_t pingID = + PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[2]); + + LOG3(("SpdySession31::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 +SpdySession31::HandleGoAway(SpdySession31 *self) +{ + MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_GOAWAY); + + if (self->mInputFrameDataSize != 8) { + LOG3(("SpdySession31::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(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. The + // underlying transaction 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) { + SpdyStream31 *stream = + static_cast(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) { + SpdyStream31 *stream = + static_cast(self->mQueuedStreams.PopFront()); + self->CloseStream(stream, NS_ERROR_NET_RESET); + self->mStreamTransactionHash.Remove(stream->Transaction()); + } + + LOG3(("SpdySession31::HandleGoAway %p GOAWAY Last-Good-ID 0x%X status 0x%X " + "live streams=%d\n", self, self->mGoAwayID, + PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[3]), + self->mStreamTransactionHash.Count())); + + self->ResetDownstreamState(); + return NS_OK; +} + +nsresult +SpdySession31::HandleHeaders(SpdySession31 *self) +{ + MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_HEADERS); + + if (self->mInputFrameDataSize < 4) { + LOG3(("SpdySession31::HandleHeaders %p HEADERS had wrong amount of data %d", + self, self->mInputFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + uint32_t streamID = + PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[2]); + LOG3(("SpdySession31::HandleHeaders %p HEADERS for Stream 0x%X.\n", + self, streamID)); + nsresult rv = self->SetInputFrameDataStream(streamID); + if (NS_FAILED(rv)) + return rv; + + if (!self->mInputFrameDataStream) { + LOG3(("SpdySession31::HandleHeaders %p lookup streamID 0x%X failed.\n", + self, streamID)); + if (streamID >= self->mNextStreamID) + self->GenerateRstStream(RST_INVALID_STREAM, streamID); + + rv = self->UncompressAndDiscard(12, self->mInputFrameDataSize - 4); + if (NS_FAILED(rv)) { + LOG(("SpdySession31::HandleHeaders uncompress failed\n")); + // this is fatal to the session + return rv; + } + self->ResetDownstreamState(); + return NS_OK; + } + + // Uncompress the headers into local buffers in the SpdyStream, 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. + rv = self->mInputFrameDataStream->Uncompress(&self->mDownstreamZlib, + self->mInputFrameBuffer + 12, + self->mInputFrameDataSize - 4); + if (NS_FAILED(rv)) { + LOG(("SpdySession31::HandleHeaders uncompress failed\n")); + return rv; + } + + self->mInputFrameDataLast = self->mInputFrameBuffer[4] & kFlag_Data_FIN; + self->mInputFrameDataStream-> + UpdateTransportReadEvents(self->mInputFrameDataSize); + self->mLastDataReadEpoch = self->mLastReadEpoch; + + if (self->mInputFrameBuffer[4] & ~kFlag_Data_FIN) { + LOG3(("Headers %p had undefined flag set 0x%X\n", self, streamID)); + self->CleanupStream(self->mInputFrameDataStream, NS_ERROR_ILLEGAL_VALUE, + RST_PROTOCOL_ERROR); + self->ResetDownstreamState(); + return NS_OK; + } + + if (!self->mInputFrameDataLast) { + // don't process the headers yet as there could be more HEADERS frames + self->ResetDownstreamState(); + return NS_OK; + } + + rv = self->ResponseHeadersComplete(); + if (rv == NS_ERROR_ILLEGAL_VALUE) { + LOG3(("SpdySession31::HanndleHeaders %p PROTOCOL_ERROR detected 0x%X\n", + self, streamID)); + self->CleanupStream(self->mInputFrameDataStream, rv, RST_PROTOCOL_ERROR); + self->ResetDownstreamState(); + rv = NS_OK; + } + return rv; +} + +PLDHashOperator +SpdySession31::RestartBlockedOnRwinEnumerator(nsAHttpTransaction *key, + nsAutoPtr &stream, + void *closure) +{ + SpdySession31 *self = static_cast(closure); + MOZ_ASSERT(self->mRemoteSessionWindow > 0); + + if (!stream->BlockedOnRwin() || stream->RemoteWindow() <= 0) + return PL_DHASH_NEXT; + + self->mReadyForWrite.Push(stream); + self->SetWriteCallbacks(); + return PL_DHASH_NEXT; +} + +nsresult +SpdySession31::HandleWindowUpdate(SpdySession31 *self) +{ + MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_WINDOW_UPDATE); + + if (self->mInputFrameDataSize < 8) { + LOG3(("SpdySession31::HandleWindowUpdate %p Window Update wrong length %d\n", + self, self->mInputFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + uint32_t delta = + PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[3]); + delta &= 0x7fffffff; + uint32_t streamID = + PR_ntohl(reinterpret_cast(self->mInputFrameBuffer.get())[2]); + streamID &= 0x7fffffff; + + LOG3(("SpdySession31::HandleWindowUpdate %p len=%d for Stream 0x%X.\n", + self, delta, streamID)); + + int64_t oldRemoteWindow; + + // ID of 0 is a session window update + if (streamID) { + nsresult rv = self->SetInputFrameDataStream(streamID); + if (NS_FAILED(rv)) + return rv; + + if (!self->mInputFrameDataStream) { + LOG3(("SpdySession31::HandleWindowUpdate %p lookup streamID 0x%X failed.\n", + self, streamID)); + if (streamID >= self->mNextStreamID) + self->GenerateRstStream(RST_INVALID_STREAM, streamID); + self->ResetDownstreamState(); + return NS_OK; + } + oldRemoteWindow = self->mInputFrameDataStream->RemoteWindow(); + self->mInputFrameDataStream->UpdateRemoteWindow(delta); + + // If the stream had a <=0 window, that has now opened + // schedule it for writing again + if (self->mInputFrameDataStream->BlockedOnRwin() && + self->mRemoteSessionWindow > 0 && + self->mInputFrameDataStream->RemoteWindow() > 0) { + self->mReadyForWrite.Push(self->mInputFrameDataStream); + self->SetWriteCallbacks(); + } + } + else { + oldRemoteWindow = self->mRemoteSessionWindow; + self->mRemoteSessionWindow += delta; + if ((oldRemoteWindow <= 0) && (self->mRemoteSessionWindow > 0)) { + LOG3(("SpdySession31::HandleWindowUpdate %p restart session window\n", + self)); + self->mStreamTransactionHash.Enumerate(RestartBlockedOnRwinEnumerator, self); + } + } + + LOG3(("SpdySession31::HandleWindowUpdate %p stream 0x%X window " + "%d increased by %d.\n", self, streamID, oldRemoteWindow, delta)); + + // If the stream had a <=0 window, that has now opened + // schedule it for writing again + if (oldRemoteWindow <= 0 && + self->mInputFrameDataStream->RemoteWindow() > 0) { + self->mReadyForWrite.Push(self->mInputFrameDataStream); + self->SetWriteCallbacks(); + } + + self->ResetDownstreamState(); + return NS_OK; +} + +nsresult +SpdySession31::HandleCredential(SpdySession31 *self) +{ + MOZ_ASSERT(self->mFrameControlType == CONTROL_TYPE_CREDENTIAL); + + // These aren't used yet. Just ignore the frame. + + LOG3(("SpdySession31::HandleCredential %p NOP.", self)); + + self->ResetDownstreamState(); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsAHttpTransaction. It is expected that nsHttpConnection is the caller +// of these methods +//----------------------------------------------------------------------------- + +void +SpdySession31::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: + { + SpdyStream31 *target = mStreamIDHash.Get(1); + nsAHttpTransaction *transaction = target ? target->Transaction() : nullptr; + if (transaction) + 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 SpdyStream31::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 SpdyStream31 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 +SpdySession31::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; + + LOG3(("SpdySession31::ReadSegments %p", this)); + + SpdyStream31 *stream = static_cast(mReadyForWrite.PopFront()); + if (!stream) { + LOG3(("SpdySession31 %p could not identify a stream to write; suspending.", + this)); + FlushOutputQueue(); + SetWriteCallbacks(); + return NS_BASE_STREAM_WOULD_BLOCK; + } + + LOG3(("SpdySession31 %p will write from SpdyStream31 %p 0x%X " + "block-input=%d block-output=%d\n", this, stream, stream->StreamID(), + stream->RequestBlockedOnRead(), stream->BlockedOnRwin())); + + 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(); + + // Allow new server reads - that might be data or control information + // (e.g. window updates or http replies) that are responses to these writes + ResumeRecv(); + + 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(("SpdySession31::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(("SpdySession31::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(("SpdySession31::ReadSegments %p stream=%p countread=%d", + this, stream, *countRead)); + mReadyForWrite.Push(stream); + SetWriteCallbacks(); + return rv; + } + + if (stream->BlockedOnRwin()) { + LOG3(("SpdySession31 %p will stream %p 0x%X suspended for flow control\n", + this, stream, stream->StreamID())); + return NS_BASE_STREAM_WOULD_BLOCK; + } + + LOG3(("SpdySession31::ReadSegments %p stream=%p stream send complete", + this, stream)); + + // 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->WriteSegments which can call this::OnWriteSegment to get the +// data. It always gets full frames if they are part of the stream + +nsresult +SpdySession31::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(); + + // If there are http transactions attached to a push stream with filled buffers + // trigger that data pump here. This only reads from buffers (not the network) + // so mDownstreamState doesn't matter. + SpdyStream31 *pushConnectedStream = + static_cast(mReadyForRead.PopFront()); + if (pushConnectedStream) { + LOG3(("SpdySession31::WriteSegments %p processing pushed stream 0x%X\n", + this, pushConnectedStream->StreamID())); + mSegmentWriter = writer; + rv = pushConnectedStream->WriteSegments(this, count, countWritten); + mSegmentWriter = nullptr; + + // The pipe in nsHttpTransaction rewrites CLOSED error codes into OK + // so we need this check to determine the truth. + if (NS_SUCCEEDED(rv) && !*countWritten && + pushConnectedStream->PushSource() && + pushConnectedStream->PushSource()->GetPushComplete()) { + rv = NS_BASE_STREAM_CLOSED; + } + + if (rv == NS_BASE_STREAM_CLOSED) { + CleanupStream(pushConnectedStream, NS_OK, RST_CANCEL); + rv = NS_OK; + } + + // if we return OK to nsHttpConnection it will use mSocketInCondition + // to determine whether to schedule more reads, incorrectly + // assuming that nsHttpConnection::OnSocketWrite() was called. + if (NS_SUCCEEDED(rv) || rv == NS_BASE_STREAM_WOULD_BLOCK) { + rv = NS_BASE_STREAM_WOULD_BLOCK; + ResumeRecv(); + } + + return rv; + } + + // 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(("SpdySession31 %p buffering frame header read failure %x\n", + this, rv)); + // maybe just blocked reading from network + if (rv == NS_BASE_STREAM_WOULD_BLOCK) + rv = NS_OK; + return rv; + } + + LogIO(this, nullptr, "Reading Frame Header", + mInputFrameBuffer + mInputFrameBufferUsed, *countWritten); + + mInputFrameBufferUsed += *countWritten; + + if (mInputFrameBufferUsed < 8) + { + LOG3(("SpdySession31::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(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(mInputFrameBuffer.get())[0]); + version &= 0x7fff; + + mFrameControlType = + PR_ntohs(reinterpret_cast(mInputFrameBuffer.get())[1]); + + LOG3(("SpdySession31::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; + + if (version != kVersion) + return NS_ERROR_ILLEGAL_VALUE; + } + else { + ChangeDownstreamState(PROCESSING_DATA_FRAME); + + Telemetry::Accumulate(Telemetry::SPDY_CHUNK_RECVD, + mInputFrameDataSize >> 10); + mLastDataReadEpoch = mLastReadEpoch; + + uint32_t streamID = + PR_ntohl(reinterpret_cast(mInputFrameBuffer.get())[0]); + rv = SetInputFrameDataStream(streamID); + if (NS_FAILED(rv)) { + LOG(("SpdySession31::WriteSegments %p lookup streamID 0x%X failed. " + "probably due to verification.\n", this, streamID)); + return rv; + } + if (!mInputFrameDataStream) { + LOG3(("SpdySession31::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); + } + else if (mInputFrameDataStream->RecvdFin()) { + LOG3(("SpdySession31::WriteSegments %p streamID 0x%X " + "Data arrived for already server closed stream.\n", + this, streamID)); + GenerateRstStream(RST_STREAM_ALREADY_CLOSED, streamID); + ChangeDownstreamState(DISCARDING_DATA_FRAME); + } + else if (!mInputFrameDataStream->RecvdData()) { + LOG3(("SpdySession31 %p First Data Frame Flushes Headers stream 0x%X\n", + this, streamID)); + + mInputFrameDataStream->SetRecvdData(true); + rv = ResponseHeadersComplete(); + if (rv == NS_ERROR_ILLEGAL_VALUE) { + LOG3(("SpdySession31 %p PROTOCOL_ERROR detected 0x%X\n", + this, streamID)); + CleanupStream(mInputFrameDataStream, rv, RST_PROTOCOL_ERROR); + ChangeDownstreamState(DISCARDING_DATA_FRAME); + } + else { + mDataPending = true; + } + } + + mInputFrameDataLast = (mInputFrameBuffer[4] & kFlag_Data_FIN); + LOG3(("Start Processing Data Frame. " + "Session=%p Stream ID 0x%X Stream Ptr %p Fin=%d Len=%d", + this, streamID, mInputFrameDataStream, mInputFrameDataLast, + mInputFrameDataSize)); + UpdateLocalRwin(mInputFrameDataStream, mInputFrameDataSize); + } + } + + 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 if (mDownstreamRstReason == RST_FRAME_TOO_LARGE) + rv = NS_ERROR_FILE_TOO_BIG; + else + rv = NS_ERROR_ILLEGAL_VALUE; + + if (mDownstreamRstReason != RST_REFUSED_STREAM && + mDownstreamRstReason != RST_CANCEL) + mShouldGoAway = true; + + // mInputFrameDataStream is reset by ChangeDownstreamState + SpdyStream31 *stream = mInputFrameDataStream; + ResetDownstreamState(); + LOG3(("SpdySession31::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_COMPLETE_HEADERS) { + + // 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 + SpdyStream31 *stream = mInputFrameDataStream; + + // if we were doing PROCESSING_COMPLETE_HEADERS need to pop the state + // back to PROCESSING_DATA_FRAME where we came from + mDownstreamState = PROCESSING_DATA_FRAME; + + if (mInputFrameDataRead == mInputFrameDataSize) + ResetDownstreamState(); + LOG3(("SpdySession31::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(("SpdySession31::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; + } + + if (NS_FAILED(rv)) { + LOG3(("SpdySession31 %p data frame read failure %x\n", this, rv)); + // maybe just blocked reading from network + if (rv == NS_BASE_STREAM_WOULD_BLOCK) + rv = NS_OK; + } + + 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(("SpdySession31 %p discard frame read failure %x\n", this, rv)); + // maybe just blocked reading from network + if (rv == NS_BASE_STREAM_WOULD_BLOCK) + rv = NS_OK; + return rv; + } + + LogIO(this, nullptr, "Discarding Frame", trash, *countWritten); + + mInputFrameDataRead += *countWritten; + + if (mInputFrameDataRead == mInputFrameDataSize) + ResetDownstreamState(); + return rv; + } + + MOZ_ASSERT(mDownstreamState == BUFFERING_CONTROL_FRAME); + if (mDownstreamState != BUFFERING_CONTROL_FRAME) { + // this cannot happen + 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(("SpdySession31 %p buffering control frame read failure %x\n", + this, rv)); + // maybe just blocked reading from network + if (rv == NS_BASE_STREAM_WOULD_BLOCK) + rv = NS_OK; + 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 +SpdySession31::UpdateLocalStreamWindow(SpdyStream31 *stream, + uint32_t bytes) +{ + if (!stream) // this is ok - it means there was a data frame for a rst stream + return; + + stream->DecrementLocalWindow(bytes); + + // If this data packet was not for a valid or live stream then there + // is no reason to mess with the flow control + if (stream->RecvdFin()) + return; + + // Don't necessarily ack every data packet. Only do it + // after a significant amount of data. + uint64_t unacked = stream->LocalUnAcked(); + int64_t localWindow = stream->LocalWindow(); + + LOG3(("SpdySession31::UpdateLocalStreamWindow this=%p id=0x%X newbytes=%u " + "unacked=%llu localWindow=%lld\n", + this, stream->StreamID(), bytes, unacked, localWindow)); + + if (!unacked) + return; + + if ((unacked < kMinimumToAck) && (localWindow > kEmergencyWindowThreshold)) + return; + + if (!stream->HasSink()) { + LOG3(("SpdySession31::UpdateLocalStreamWindow %p 0x%X Pushed Stream Has No Sink\n", + this, stream->StreamID())); + return; + } + + // Generate window updates directly out of spdysession instead of the stream + // in order to avoid queue delays in getting the 'ACK' out. + uint32_t toack = (unacked <= 0x7fffffffU) ? unacked : 0x7fffffffU; + + LOG3(("SpdySession31::UpdateLocalStreamWindow Ack this=%p id=0x%X acksize=%d\n", + this, stream->StreamID(), toack)); + stream->IncrementLocalWindow(toack); + + // room for this packet needs to be ensured before calling this function + static const uint32_t dataLen = 8; + char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed; + mOutputQueueUsed += 8 + dataLen; + MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize); + + memset(packet, 0, 8 + dataLen); + packet[0] = kFlag_Control; + packet[1] = kVersion; + packet[3] = CONTROL_TYPE_WINDOW_UPDATE; + packet[7] = dataLen; + + uint32_t id = PR_htonl(stream->StreamID()); + memcpy(packet + 8, &id, 4); + toack = PR_htonl(toack); + memcpy(packet + 12, &toack, 4); + + LogIO(this, stream, "Stream Window Update", packet, 8 + dataLen); + // dont flush here, this write can commonly be coalesced with a + // session window update to immediately follow. +} + +void +SpdySession31::UpdateLocalSessionWindow(uint32_t bytes) +{ + if (!bytes) + return; + + mLocalSessionWindow -= bytes; + + LOG3(("SpdySession31::UpdateLocalSessionWindow this=%p newbytes=%u " + "localWindow=%lld\n", this, bytes, mLocalSessionWindow)); + + // Don't necessarily ack every data packet. Only do it + // after a significant amount of data. + if ((mLocalSessionWindow > (ASpdySession::kInitialRwin - kMinimumToAck)) && + (mLocalSessionWindow > kEmergencyWindowThreshold)) + return; + + // Only send max 31 bits of window updates at a time. + uint64_t toack64 = ASpdySession::kInitialRwin - mLocalSessionWindow; + uint32_t toack = (toack64 <= 0x7fffffffU) ? toack64 : 0x7fffffffU; + + LOG3(("SpdySession31::UpdateLocalSessionWindow Ack this=%p acksize=%u\n", + this, toack)); + mLocalSessionWindow += toack; + + // room for this packet needs to be ensured before calling this function + static const uint32_t dataLen = 8; + char *packet = mOutputQueueBuffer.get() + mOutputQueueUsed; + mOutputQueueUsed += 8 + dataLen; + MOZ_ASSERT(mOutputQueueUsed <= mOutputQueueSize); + + memset(packet, 0, 8 + dataLen); + packet[0] = kFlag_Control; + packet[1] = kVersion; + packet[3] = CONTROL_TYPE_WINDOW_UPDATE; + packet[7] = dataLen; + + // packet 8-11 is ID and left at 0 for session ID + toack = PR_htonl(toack); + memcpy(packet + 12, &toack, 4); + + LogIO(this, nullptr, "Session Window Update", packet, 8 + dataLen); + // dont flush here, this write can commonly be coalesced with others +} + +void +SpdySession31::UpdateLocalRwin(SpdyStream31 *stream, + uint32_t bytes) +{ + // make sure there is room for 2 window updates even though + // we may not generate any. + EnsureBuffer(mOutputQueueBuffer, mOutputQueueUsed + (16 *2), + mOutputQueueUsed, mOutputQueueSize); + + UpdateLocalStreamWindow(stream, bytes); + UpdateLocalSessionWindow(bytes); + FlushOutputQueue(); +} + +void +SpdySession31::Close(nsresult aReason) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + if (mClosed) + return; + + LOG3(("SpdySession31::Close %p %X", this, aReason)); + + mClosed = true; + + mStreamTransactionHash.Enumerate(ShutdownEnumerator, this); + mStreamIDHash.Clear(); + mStreamTransactionHash.Clear(); + + uint32_t goAwayReason; + if (NS_SUCCEEDED(aReason)) { + goAwayReason = OK; + } else if (aReason == NS_ERROR_ILLEGAL_VALUE) { + goAwayReason = PROTOCOL_ERROR; + } else { + goAwayReason = INTERNAL_ERROR; + } + GenerateGoAway(goAwayReason); + mConnection = nullptr; + mSegmentReader = nullptr; + mSegmentWriter = nullptr; +} + +void +SpdySession31::CloseTransaction(nsAHttpTransaction *aTransaction, + nsresult aResult) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + LOG3(("SpdySession31::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. + SpdyStream31 *stream = mStreamTransactionHash.Get(aTransaction); + if (!stream) { + LOG3(("SpdySession31::CloseTransaction %p %p %x - not found.", + this, aTransaction, aResult)); + return; + } + LOG3(("SpdySession31::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 +SpdySession31::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 +SpdySession31::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 +SpdySession31::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_COMPLETE_HEADERS) { + + 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) { + if (mDataPending) { + // Now ready to process data frames - pop PROCESING_DATA_FRAME back onto + // the stack because receipt of that first data frame triggered the + // response header processing + mDataPending = false; + ChangeDownstreamState(PROCESSING_DATA_FRAME); + } + else if (!mInputFrameDataLast) { + // If more frames are expected in this stream, then reset the state so they can be + // handled. Otherwise (e.g. a 0 length response with the fin on the SYN_REPLY) + // stay in PROCESSING_COMPLETE_HEADERS state so the SetNeedsCleanup() code above can + // cleanup the stream. + ResetDownstreamState(); + } + } + + return NS_OK; + } + + return NS_ERROR_UNEXPECTED; +} + +void +SpdySession31::SetNeedsCleanup() +{ + LOG3(("SpdySession31::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(); +} + +void +SpdySession31::ConnectPushedStream(SpdyStream31 *stream) +{ + mReadyForRead.Push(stream); + ForceRecv(); +} + +//----------------------------------------------------------------------------- +// Modified methods of nsAHttpConnection +//----------------------------------------------------------------------------- + +void +SpdySession31::TransactionHasDataToWrite(nsAHttpTransaction *caller) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + LOG3(("SpdySession31::TransactionHasDataToWrite %p trans=%p", this, caller)); + + // a trapped signal from the http transaction to the connection that + // it is no longer blocked on read. + + SpdyStream31 *stream = mStreamTransactionHash.Get(caller); + if (!stream || !VerifyStream(stream)) { + LOG3(("SpdySession31::TransactionHasDataToWrite %p caller %p not found", + this, caller)); + return; + } + + LOG3(("SpdySession31::TransactionHasDataToWrite %p ID is 0x%X\n", + this, stream->StreamID())); + + mReadyForWrite.Push(stream); +} + +void +SpdySession31::TransactionHasDataToWrite(SpdyStream31 *stream) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + LOG3(("SpdySession31::TransactionHasDataToWrite %p stream=%p ID=%x", + this, stream, stream->StreamID())); + + mReadyForWrite.Push(stream); + SetWriteCallbacks(); +} + +bool +SpdySession31::IsPersistent() +{ + return true; +} + +nsresult +SpdySession31::TakeTransport(nsISocketTransport **, + nsIAsyncInputStream **, + nsIAsyncOutputStream **) +{ + MOZ_ASSERT(false, "TakeTransport of SpdySession31"); + return NS_ERROR_UNEXPECTED; +} + +nsHttpConnection * +SpdySession31::TakeHttpConnection() +{ + MOZ_ASSERT(false, "TakeHttpConnection of SpdySession31"); + return nullptr; +} + +uint32_t +SpdySession31::CancelPipeline(nsresult reason) +{ + // we don't pipeline inside spdy, so this isn't an issue + return 0; +} + +nsAHttpTransaction::Classifier +SpdySession31::Classification() +{ + if (!mConnection) + return nsAHttpTransaction::CLASS_GENERAL; + return mConnection->Classification(); +} + +//----------------------------------------------------------------------------- +// unused methods of nsAHttpTransaction +// We can be sure of this because SpdySession31 is only constructed in +// nsHttpConnection and is never passed out of that object +//----------------------------------------------------------------------------- + +void +SpdySession31::SetConnection(nsAHttpConnection *) +{ + // This is unexpected + MOZ_ASSERT(false, "SpdySession31::SetConnection()"); +} + +void +SpdySession31::GetSecurityCallbacks(nsIInterfaceRequestor **) +{ + // This is unexpected + MOZ_ASSERT(false, "SpdySession31::GetSecurityCallbacks()"); +} + +void +SpdySession31::SetProxyConnectFailed() +{ + MOZ_ASSERT(false, "SpdySession31::SetProxyConnectFailed()"); +} + +bool +SpdySession31::IsDone() +{ + return !mStreamTransactionHash.Count(); +} + +nsresult +SpdySession31::Status() +{ + MOZ_ASSERT(false, "SpdySession31::Status()"); + return NS_ERROR_UNEXPECTED; +} + +uint32_t +SpdySession31::Caps() +{ + MOZ_ASSERT(false, "SpdySession31::Caps()"); + return 0; +} + +uint64_t +SpdySession31::Available() +{ + MOZ_ASSERT(false, "SpdySession31::Available()"); + return 0; +} + +nsHttpRequestHead * +SpdySession31::RequestHead() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + MOZ_ASSERT(false, + "SpdySession31::RequestHead() " + "should not be called after SPDY is setup"); + return NULL; +} + +uint32_t +SpdySession31::Http1xTransactionCount() +{ + return 0; +} + +// used as an enumerator by TakeSubTransactions() +static PLDHashOperator + TakeStream(nsAHttpTransaction *key, + nsAutoPtr &stream, + void *closure) +{ + nsTArray > *list = + static_cast > *>(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 +SpdySession31::TakeSubTransactions( + nsTArray > &outTransactions) +{ + // Generally this cannot be done with spdy as transactions are + // started right away. + + LOG3(("SpdySession31::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 +SpdySession31::AddTransaction(nsAHttpTransaction *) +{ + // This API is meant for pipelining, SpdySession31's should be + // extended with AddStream() + + MOZ_ASSERT(false, + "SpdySession31::AddTransaction() should not be called"); + + return NS_ERROR_NOT_IMPLEMENTED; +} + +uint32_t +SpdySession31::PipelineDepth() +{ + return IsDone() ? 0 : 1; +} + +nsresult +SpdySession31::SetPipelinePosition(int32_t position) +{ + // This API is meant for pipelining, SpdySession31's should be + // extended with AddStream() + + MOZ_ASSERT(false, + "SpdySession31::SetPipelinePosition() should not be called"); + + return NS_ERROR_NOT_IMPLEMENTED; +} + +int32_t +SpdySession31::PipelinePosition() +{ + return 0; +} + +//----------------------------------------------------------------------------- +// Pass through methods of nsAHttpConnection +//----------------------------------------------------------------------------- + +nsAHttpConnection * +SpdySession31::Connection() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + return mConnection; +} + +nsresult +SpdySession31::OnHeadersAvailable(nsAHttpTransaction *transaction, + nsHttpRequestHead *requestHead, + nsHttpResponseHead *responseHead, + bool *reset) +{ + return mConnection->OnHeadersAvailable(transaction, + requestHead, + responseHead, + reset); +} + +bool +SpdySession31::IsReused() +{ + return mConnection->IsReused(); +} + +nsresult +SpdySession31::PushBack(const char *buf, uint32_t len) +{ + return mConnection->PushBack(buf, len); +} + +} // namespace mozilla::net +} // namespace mozilla diff --git a/netwerk/protocol/http/SpdySession31.h b/netwerk/protocol/http/SpdySession31.h new file mode 100644 index 000000000000..62124653c3a8 --- /dev/null +++ b/netwerk/protocol/http/SpdySession31.h @@ -0,0 +1,407 @@ +/* -*- 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_SpdySession31_h +#define mozilla_net_SpdySession31_h + +// spdy/3.1 + +#include "ASpdySession.h" +#include "mozilla/Attributes.h" +#include "nsAHttpConnection.h" +#include "nsClassHashtable.h" +#include "nsDataHashtable.h" +#include "nsDeque.h" +#include "nsHashKeys.h" +#include "zlib.h" + +class nsISocketTransport; + +namespace mozilla { namespace net { + +class SpdyPushedStream31; +class SpdyStream31; + +class SpdySession31 MOZ_FINAL : public ASpdySession + , public nsAHttpConnection + , public nsAHttpSegmentReader + , public nsAHttpSegmentWriter +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSAHTTPTRANSACTION + NS_DECL_NSAHTTPCONNECTION(mConnection) + NS_DECL_NSAHTTPSEGMENTREADER + NS_DECL_NSAHTTPSEGMENTWRITER + + SpdySession31(nsAHttpTransaction *, nsISocketTransport *, int32_t); + ~SpdySession31(); + + 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(); + + // Registering with a newID of 0 means pick the next available odd ID + uint32_t RegisterStreamID(SpdyStream31 *, uint32_t aNewID = 0); + + const static uint8_t kVersion = 3; + + const static uint8_t kFlag_Control = 0x80; + + const static uint8_t kFlag_Data_FIN = 0x01; + const static uint8_t kFlag_Data_UNI = 0x02; + + 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, /* deprecated */ + CONTROL_TYPE_PING = 6, + CONTROL_TYPE_GOAWAY = 7, + CONTROL_TYPE_HEADERS = 8, + CONTROL_TYPE_WINDOW_UPDATE = 9, + CONTROL_TYPE_CREDENTIAL = 10, + CONTROL_TYPE_LAST = 11 + }; + + 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_STREAM_IN_USE = 8, + RST_STREAM_ALREADY_CLOSED = 9, + RST_INVALID_CREDENTIALS = 10, + RST_FRAME_TOO_LARGE = 11 + }; + + enum goawayReason + { + OK = 0, + PROTOCOL_ERROR = 1, + INTERNAL_ERROR = 2, // sometimes misdocumented as 11 + NUM_STATUS_CODES = 3 // reserved by chromium but undocumented + }; + + enum settingsFlags + { + PERSIST_VALUE = 1, + PERSISTED_VALUE = 2 + }; + + 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 for flow control + SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE = 8 + }; + + // 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; + + // below the emergency threshold of local window we ack every received + // byte. Above that we coalesce bytes into the MinimumToAck size. + const static int32_t kEmergencyWindowThreshold = 1024 * 1024; + const static uint32_t kMinimumToAck = 64 * 1024; + + // The default rwin is 64KB unless updated by a settings frame + const static uint32_t kDefaultRwin = 64 * 1024; + + static nsresult HandleSynStream(SpdySession31 *); + static nsresult HandleSynReply(SpdySession31 *); + static nsresult HandleRstStream(SpdySession31 *); + static nsresult HandleSettings(SpdySession31 *); + static nsresult HandleNoop(SpdySession31 *); + static nsresult HandlePing(SpdySession31 *); + static nsresult HandleGoAway(SpdySession31 *); + static nsresult HandleHeaders(SpdySession31 *); + static nsresult HandleWindowUpdate(SpdySession31 *); + static nsresult HandleCredential(SpdySession31 *); + + template + static void EnsureBuffer(nsAutoArrayPtr &, + uint32_t, uint32_t, uint32_t &); + + // For writing the SPDY data stream to LOG4 + static void LogIO(SpdySession31 *, SpdyStream31 *, const char *, + const char *, uint32_t); + + // an overload of nsAHttpConnection + void TransactionHasDataToWrite(nsAHttpTransaction *); + + // a similar version for SpdyStream31 + void TransactionHasDataToWrite(SpdyStream31 *); + + // an overload of nsAHttpSegementReader + virtual nsresult CommitToSegmentSize(uint32_t size, bool forceCommitment); + + uint32_t GetServerInitialStreamWindow() { return mServerInitialStreamWindow; } + + void ConnectPushedStream(SpdyStream31 *stream); + + uint64_t Serial() { return mSerial; } + + void PrintDiagnostics (nsCString &log); + + // Streams need access to these + uint32_t SendingChunkSize() { return mSendingChunkSize; } + uint32_t PushAllowance() { return mPushAllowance; } + z_stream *UpstreamZlib() { return &mUpstreamZlib; } + nsISocketTransport *SocketTransport() { return mSocketTransport; } + int64_t RemoteSessionWindow() { return mRemoteSessionWindow; } + void DecrementRemoteSessionWindow (uint32_t bytes) { mRemoteSessionWindow -= bytes; } + +private: + + enum stateType { + BUFFERING_FRAME_HEADER, + BUFFERING_CONTROL_FRAME, + PROCESSING_DATA_FRAME, + DISCARDING_DATA_FRAME, + PROCESSING_COMPLETE_HEADERS, + PROCESSING_CONTROL_RST_STREAM + }; + + nsresult ResponseHeadersComplete(); + uint32_t GetWriteQueueSize(); + void ChangeDownstreamState(enum stateType); + void ResetDownstreamState(); + nsresult UncompressAndDiscard(uint32_t, uint32_t); + void DecrementConcurrent(SpdyStream31 *); + void zlibInit(); + void GeneratePing(uint32_t); + void GenerateRstStream(uint32_t, uint32_t); + void GenerateGoAway(uint32_t); + void CleanupStream(SpdyStream31 *, nsresult, rstReason); + void CloseStream(SpdyStream31 *, nsresult); + void GenerateSettings(); + void RemoveStreamFromQueues(SpdyStream31 *); + + void SetWriteCallbacks(); + void FlushOutputQueue(); + void RealignOutputQueue(); + + bool RoomForMoreConcurrent(); + void ActivateStream(SpdyStream31 *); + void ProcessPending(); + nsresult SetInputFrameDataStream(uint32_t); + bool VerifyStream(SpdyStream31 *, uint32_t); + void SetNeedsCleanup(); + + void UpdateLocalRwin(SpdyStream31 *stream, uint32_t bytes); + void UpdateLocalStreamWindow(SpdyStream31 *stream, uint32_t bytes); + void UpdateLocalSessionWindow(uint32_t bytes); + + // 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 &, + void *); + + static PLDHashOperator GoAwayEnumerator(nsAHttpTransaction *, + nsAutoPtr &, + void *); + + static PLDHashOperator UpdateServerRwinEnumerator(nsAHttpTransaction *, + nsAutoPtr &, + void *); + + static PLDHashOperator RestartBlockedOnRwinEnumerator(nsAHttpTransaction *, + nsAutoPtr &, + void *); + + // This is intended to be nsHttpConnectionMgr:nsConnectionHandle taken + // from the first transaction on this session. That object contains the + // pointer to the real network-level nsHttpConnection object. + nsRefPtr 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 */ + uint32_t mPushAllowance; /* rwin for unmatched pushes */ + + stateType mDownstreamState; /* in frame, between frames, etc.. */ + + // Maintain 2 indexes - one by stream ID, one by transaction pointer. + // There are also several lists of streams: ready to write, queued due to + // max parallelism, streams that need to force a read for push, and the full + // set of pushed streams. + // The objects are not ref counted - they get destroyed + // by the nsClassHashtable implementation when they are removed from + // the transaction hash. + nsDataHashtable mStreamIDHash; + nsClassHashtable, + SpdyStream31> mStreamTransactionHash; + + nsDeque mReadyForWrite; + nsDeque mQueuedStreams; + nsDeque mReadyForRead; + nsTArray mPushedStreams; + + // Compression contexts for header transport using deflate. + // SPDY compresses only HTTP headers and does not reset zlib in between + // frames. Even data that is not associated with a stream (e.g invalid + // stream ID) is passed through these contexts to keep the compression + // context correct. + 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 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. + SpdyStream31 *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. + SpdyStream31 *mNeedsCleanup; + + // The CONTROL_TYPE value for a control frame + uint32_t mFrameControlType; + + // This reason code in the last processed RESET frame + uint32_t mDownstreamRstReason; + + // for the conversion of downstream http headers into spdy formatted headers + // The data here does not persist between frames + 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; + + // indicates PROCESSING_COMPLETE_HEADERS state was pushed onto the stack + // over an active PROCESSING_DATA_FRAME, which should be restored when + // the processed headers are written to the stream + bool mDataPending; + + // 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; + + // The server rwin for new streams as determined from a SETTINGS frame + uint32_t mServerInitialStreamWindow; + + // The Local Session window is how much data the server is allowed to send + // (across all streams) without getting a window update to stream 0. It is + // signed because asynchronous changes via SETTINGS can drive it negative. + int64_t mLocalSessionWindow; + + // The Remote Session Window is how much data the client is allowed to send + // (across all streams) without receiving a window update to stream 0. It is + // signed because asynchronous changes via SETTINGS can drive it negative. + int64_t mRemoteSessionWindow; + + // 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 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; + + // Each session gets a unique serial number because the push cache is correlated + // by the load group and the serial number can be used as part of the cache key + // to make sure streams aren't shared across sessions. + uint64_t mSerial; +}; + +}} // namespace mozilla::net + +#endif // mozilla_net_SpdySession31_h diff --git a/netwerk/protocol/http/SpdyStream3.cpp b/netwerk/protocol/http/SpdyStream3.cpp index ed26afe71c65..ea0f90a13bef 100644 --- a/netwerk/protocol/http/SpdyStream3.cpp +++ b/netwerk/protocol/http/SpdyStream3.cpp @@ -17,7 +17,7 @@ #include "SpdyPush3.h" #include "SpdySession3.h" #include "SpdyStream3.h" -#include "PSpdyPush3.h" +#include "PSpdyPush.h" #include @@ -285,16 +285,16 @@ SpdyStream3::ParseHttpRequestHeaders(const char *buf, if (mTransaction->RequestHead()->Method() == nsHttp::Get) { // from :scheme, :host, :path nsILoadGroupConnectionInfo *loadGroupCI = mTransaction->LoadGroupConnectionInfo(); - SpdyPushCache3 *cache = nullptr; + SpdyPushCache *cache = nullptr; if (loadGroupCI) - loadGroupCI->GetSpdyPushCache3(&cache); + loadGroupCI->GetSpdyPushCache(&cache); SpdyPushedStream3 *pushedStream = nullptr; // we remove the pushedstream from the push cache so that // it will not be used for another GET. This does not destroy the // stream itself - that is done when the transactionhash is done with it. if (cache) - pushedStream = cache->RemovePushedStream(hashkey); + pushedStream = cache->RemovePushedStreamSpdy3(hashkey); if (pushedStream) { LOG3(("Pushed Stream Match located id=0x%X key=%s\n", diff --git a/netwerk/protocol/http/SpdyStream31.cpp b/netwerk/protocol/http/SpdyStream31.cpp new file mode 100644 index 000000000000..59e49d95b61d --- /dev/null +++ b/netwerk/protocol/http/SpdyStream31.cpp @@ -0,0 +1,1474 @@ +/* -*- 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 "mozilla/Telemetry.h" +#include "nsAlgorithm.h" +#include "nsHttp.h" +#include "nsHttpHandler.h" +#include "nsHttpRequestHead.h" +#include "nsISocketTransport.h" +#include "nsISupportsPriority.h" +#include "prnetdb.h" +#include "SpdyPush31.h" +#include "SpdySession31.h" +#include "SpdyStream31.h" + +#include + +#ifdef DEBUG +// defined by the socket transport service while active +extern PRThread *gSocketThread; +#endif + +namespace mozilla { +namespace net { + +SpdyStream31::SpdyStream31(nsAHttpTransaction *httpTransaction, + SpdySession31 *spdySession, + int32_t priority) + : mStreamID(0), + mSession(spdySession), + mUpstreamState(GENERATING_SYN_STREAM), + mSynFrameComplete(0), + mSentFinOnData(0), + mTransaction(httpTransaction), + mSocketTransport(spdySession->SocketTransport()), + mSegmentReader(nullptr), + mSegmentWriter(nullptr), + mChunkSize(spdySession->SendingChunkSize()), + mRequestBlockedOnRead(0), + mRecvdFin(0), + mFullyOpen(0), + mSentWaitingFor(0), + mReceivedData(0), + mSetTCPSocketBuffer(0), + mTxInlineFrameSize(SpdySession31::kDefaultBufferSize), + mTxInlineFrameUsed(0), + mTxStreamFrameSize(0), + mZlib(spdySession->UpstreamZlib()), + mDecompressBufferSize(SpdySession31::kDefaultBufferSize), + mDecompressBufferUsed(0), + mDecompressedBytes(0), + mRequestBodyLenRemaining(0), + mPriority(priority), + mLocalUnacked(0), + mBlockedOnRwin(false), + mTotalSent(0), + mTotalRead(0), + mPushSource(nullptr) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + LOG3(("SpdyStream31::SpdyStream31 %p", this)); + + mRemoteWindow = spdySession->GetServerInitialStreamWindow(); + mLocalWindow = spdySession->PushAllowance(); + + mTxInlineFrame = new uint8_t[mTxInlineFrameSize]; + mDecompressBuffer = new char[mDecompressBufferSize]; +} + +SpdyStream31::~SpdyStream31() +{ + mStreamID = SpdySession31::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 +SpdyStream31::ReadSegments(nsAHttpSegmentReader *reader, + uint32_t count, + uint32_t *countRead) +{ + LOG3(("SpdyStream31 %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 the sending flow control window is open (!mBlockedOnRwin) then + // continue sending the request + if (!mBlockedOnRwin && + !mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) { + LOG3(("SpdyStream31::ReadSegments %p 0x%X: Sending request data complete, " + "mUpstreamState=%x",this, mStreamID, 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, "SpdyStream31::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 +SpdyStream31::WriteSegments(nsAHttpSegmentWriter *writer, + uint32_t count, + uint32_t *countWritten) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + MOZ_ASSERT(!mSegmentWriter, "segment writer in progress"); + + LOG3(("SpdyStream31::WriteSegments %p count=%d state=%x", + this, count, mUpstreamState)); + + mSegmentWriter = writer; + nsresult rv = mTransaction->WriteSegments(this, count, countWritten); + mSegmentWriter = nullptr; + + return rv; +} + +PLDHashOperator +SpdyStream31::hdrHashEnumerate(const nsACString &key, + nsAutoPtr &value, + void *closure) +{ + SpdyStream31 *self = static_cast(closure); + + self->CompressToFrame(key); + self->CompressToFrame(value.get()); + return PL_DHASH_NEXT; +} + +void +SpdyStream31::CreatePushHashKey(const nsCString &scheme, + const nsCString &hostHeader, + uint64_t serial, + const nsCSubstring &pathInfo, + nsCString &outOrigin, + nsCString &outKey) +{ + outOrigin = scheme; + outOrigin.Append(NS_LITERAL_CSTRING("://")); + outOrigin.Append(hostHeader); + + outKey = outOrigin; + outKey.Append(NS_LITERAL_CSTRING("/[spdy3_1.")); + outKey.AppendInt(serial); + outKey.Append(NS_LITERAL_CSTRING("]")); + outKey.Append(pathInfo); +} + + +nsresult +SpdyStream31::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(("SpdyStream31::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(("SpdyStream31::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; + + nsCString hostHeader; + nsCString hashkey; + mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader); + + CreatePushHashKey(NS_LITERAL_CSTRING("https"), + hostHeader, mSession->Serial(), + mTransaction->RequestHead()->RequestURI(), + mOrigin, hashkey); + + // check the push cache for GET + if (mTransaction->RequestHead()->Method() == nsHttp::Get) { + // from :scheme, :host, :path + nsILoadGroupConnectionInfo *loadGroupCI = mTransaction->LoadGroupConnectionInfo(); + SpdyPushCache *cache = nullptr; + if (loadGroupCI) + loadGroupCI->GetSpdyPushCache(&cache); + + SpdyPushedStream31 *pushedStream = nullptr; + // we remove the pushedstream from the push cache so that + // it will not be used for another GET. This does not destroy the + // stream itself - that is done when the transactionhash is done with it. + if (cache) + pushedStream = cache->RemovePushedStreamSpdy31(hashkey); + + if (pushedStream) { + LOG3(("Pushed Stream Match located id=0x%X key=%s\n", + pushedStream->StreamID(), hashkey.get())); + pushedStream->SetConsumerStream(this); + mPushSource = pushedStream; + mSentFinOnData = 1; + + // There is probably pushed data buffered so trigger a read manually + // as we can't rely on future network events to do it + mSession->ConnectPushedStream(this); + return NS_OK; + } + } + + // 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] = SpdySession31::kFlag_Control; + mTxInlineFrame[1] = SpdySession31::kVersion; + mTxInlineFrame[2] = 0; + mTxInlineFrame[3] = SpdySession31::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 E0 mask of byte 16. + // 0 is highest priority, 7 is lowest. + // The other 5 bits of byte 16 are unused. + + if (mPriority >= nsISupportsPriority::PRIORITY_LOWEST) + mTxInlineFrame[16] = 7 << 5; + else if (mPriority <= nsISupportsPriority::PRIORITY_HIGHEST) + mTxInlineFrame[16] = 0 << 5; + else { + // The priority mapping relies on the unfiltered ranged to be + // between -20 .. +20 + PR_STATIC_ASSERT(nsISupportsPriority::PRIORITY_LOWEST == 20); + PR_STATIC_ASSERT(nsISupportsPriority::PRIORITY_HIGHEST == -20); + + // Add one to the priority so that values such as -10 and -11 + // get different spdy priorities - this appears to be an important + // breaking line in the priorities content assigns to + // transactions. + uint8_t calculatedPriority = 3 + ((mPriority + 1) / 5); + MOZ_ASSERT (!(calculatedPriority & 0xf8), + "Calculated Priority Out Of Range"); + mTxInlineFrame[16] = calculatedPriority << 5; + } + + // The client cert "slot". Right now we don't send client certs + mTxInlineFrame[17] = 0; + + 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 + 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); + + // exclusions.. mostly from 3.2.1 + if (name.Equals("connection") || + name.Equals("keep-alive") || + name.Equals("host") || + name.Equals("accept-encoding") || + name.Equals("te") || + name.Equals("transfer-encoding")) + 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(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. + + const char *methodHeader = mTransaction->RequestHead()->Method().get(); + + // The header block length + uint16_t count = hdrHash.Count() + 5; /* method, path, version, host, scheme */ + CompressToFrame(count); + + // :method, :path, :version comprise a HTTP/1 request line, so send those first + // to make life easy for any gateways + CompressToFrame(NS_LITERAL_CSTRING(":method")); + CompressToFrame(methodHeader, strlen(methodHeader)); + CompressToFrame(NS_LITERAL_CSTRING(":path")); + CompressToFrame(mTransaction->RequestHead()->RequestURI()); + CompressToFrame(NS_LITERAL_CSTRING(":version")); + CompressToFrame(versionHeader); + + CompressToFrame(NS_LITERAL_CSTRING(":host")); + CompressToFrame(hostHeader); + CompressToFrame(NS_LITERAL_CSTRING(":scheme")); + CompressToFrame(NS_LITERAL_CSTRING("https")); + + hdrHash.Enumerate(hdrHashEnumerate, this); + CompressFlushFrame(); + + // 4 to 7 are length and flags, which we can now fill in + (reinterpret_cast(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] = SpdySession31::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] = SpdySession31::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 +SpdyStream31::AdjustInitialWindow() +{ + MOZ_ASSERT(mSession->PushAllowance() <= ASpdySession::kInitialRwin); + + // The session initial_window is sized for serverpushed streams. When we + // generate a client pulled stream we want to adjust the initial window + // to a huge value in a pipeline with that SYN_STREAM. + + // >0 even numbered IDs are pushed streams. + // odd numbered IDs are pulled streams. + // 0 is the sink for a pushed stream. + SpdyStream31 *stream = this; + if (!mStreamID) { + MOZ_ASSERT(mPushSource); + if (!mPushSource) + return; + stream = mPushSource; + MOZ_ASSERT(stream->mStreamID); + MOZ_ASSERT(!(stream->mStreamID & 1)); // is a push stream + + // If the pushed stream has sent a FIN, there is no reason to update + // the window + if (stream->RecvdFin()) + return; + } + + // For server pushes we also want to include in the ack any data that has been + // buffered but unacknowledged. + + // mLocalUnacked is technically 64 bits, but because it can never grow larger than + // our window size (which is closer to 29bits max) we know it fits comfortably in 32. + // However we don't really enforce that, and track it as a 64 so that broken senders + // can still interoperate. That means we have to be careful with this calculation. + uint64_t toack64 = (ASpdySession::kInitialRwin - mSession->PushAllowance()) + + stream->mLocalUnacked; + stream->mLocalUnacked = 0; + if (toack64 > 0x7fffffff) { + stream->mLocalUnacked = toack64 - 0x7fffffff; + toack64 = 0x7fffffff; + } + uint32_t toack = static_cast(toack64); + if (!toack) + return; + toack = PR_htonl(toack); + + SpdySession31::EnsureBuffer(mTxInlineFrame, + mTxInlineFrameUsed + 16, + mTxInlineFrameUsed, + mTxInlineFrameSize); + + unsigned char *packet = mTxInlineFrame.get() + mTxInlineFrameUsed; + mTxInlineFrameUsed += 16; + + memset(packet, 0, 8); + packet[0] = SpdySession31::kFlag_Control; + packet[1] = SpdySession31::kVersion; + packet[3] = SpdySession31::CONTROL_TYPE_WINDOW_UPDATE; + packet[7] = 8; // 8 data bytes after 8 byte header + + uint32_t id = PR_htonl(stream->mStreamID); + memcpy(packet + 8, &id, 4); + memcpy(packet + 12, &toack, 4); + + stream->mLocalWindow += PR_ntohl(toack); + LOG3(("AdjustInitialwindow %p 0x%X %u\n", + this, stream->mStreamID, PR_ntohl(toack))); +} + +void +SpdyStream31::UpdateTransportReadEvents(uint32_t count) +{ + mTotalRead += count; + + mTransaction->OnTransportStatus(mSocketTransport, + NS_NET_STATUS_RECEIVING_FROM, + mTotalRead); +} + +void +SpdyStream31::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 +SpdyStream31::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. + + LOG3(("SpdyStream31::TransmitFrame %p inline=%d stream=%d", + this, mTxInlineFrameUsed, mTxStreamFrameSize)); + if (countUsed) + *countUsed = 0; + + if (!mTxInlineFrameUsed) { + MOZ_ASSERT(!buf); + return NS_OK; + } + + 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; + + // 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 < SpdySession31::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 SpdySession31 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(reinterpret_cast(mTxInlineFrame.get()), + mTxInlineFrameUsed, + &transmittedCount); + LOG3(("SpdyStream31::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"); + + SpdySession31::LogIO(mSession, this, "Writing from Inline Buffer", + reinterpret_cast(mTxInlineFrame.get()), + 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(("SpdyStream31::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"); + + SpdySession31::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 +SpdyStream31::ChangeState(enum stateType newState) +{ + LOG3(("SpdyStream31::ChangeState() %p from %X to %X", + this, mUpstreamState, newState)); + mUpstreamState = newState; + return; +} + +void +SpdyStream31::GenerateDataFrameHeader(uint32_t dataLength, bool lastFrame) +{ + LOG3(("SpdyStream31::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(mTxInlineFrame.get()))[0] = PR_htonl(mStreamID); + (reinterpret_cast(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] |= SpdySession31::kFlag_Data_FIN; + if (dataLength) + mSentFinOnData = 1; + } +} + +void +SpdyStream31::CompressToFrame(const nsACString &str) +{ + CompressToFrame(str.BeginReading(), str.Length()); +} + +void +SpdyStream31::CompressToFrame(const nsACString *str) +{ + CompressToFrame(str->BeginReading(), str->Length()); +} + +// Dictionary taken from +// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3 + +const unsigned char SpdyStream31::kDictionary[] = { + 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, // - - - - o p t i + 0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, // o n s - - - - h + 0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, // e a d - - - - p + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, // o s t - - - - p + 0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, // u t - - - - d e + 0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, // l e t e - - - - + 0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, // t r a c e - - - + 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, // - a c c e p t - + 0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, // - - - a c c e p + 0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // t - c h a r s e + 0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, // t - - - - a c c + 0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // e p t - e n c o + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, // d i n g - - - - + 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, // a c c e p t - l + 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, // a n g u a g e - + 0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, // - - - a c c e p + 0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, // t - r a n g e s + 0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, // - - - - a g e - + 0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, // - - - a l l o w + 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, // - - - - a u t h + 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, // o r i z a t i o + 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, // n - - - - c a c + 0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, // h e - c o n t r + 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, // o l - - - - c o + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, // n n e c t i o n + 0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t + 0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, // e n t - b a s e + 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t + 0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // e n t - e n c o + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, // d i n g - - - - + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, // c o n t e n t - + 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, // l a n g u a g e + 0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, // - - - - c o n t + 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, // e n t - l e n g + 0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, // t h - - - - c o + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, // n t e n t - l o + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, // c a t i o n - - + 0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // - - c o n t e n + 0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, // t - m d 5 - - - + 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, // - c o n t e n t + 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, // - r a n g e - - + 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // - - c o n t e n + 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, // t - t y p e - - + 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, // - - d a t e - - + 0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, // - - e t a g - - + 0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, // - - e x p e c t + 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, // - - - - e x p i + 0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, // r e s - - - - f + 0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, // r o m - - - - h + 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, // o s t - - - - i + 0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, // f - m a t c h - + 0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, // - - - i f - m o + 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, // d i f i e d - s + 0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, // i n c e - - - - + 0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, // i f - n o n e - + 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, // m a t c h - - - + 0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, // - i f - r a n g + 0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, // e - - - - i f - + 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, // u n m o d i f i + 0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, // e d - s i n c e + 0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, // - - - - l a s t + 0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, // - m o d i f i e + 0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, // d - - - - l o c + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, // a t i o n - - - + 0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, // - m a x - f o r + 0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, // w a r d s - - - + 0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, // - p r a g m a - + 0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, // - - - p r o x y + 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, // - a u t h e n t + 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, // i c a t e - - - + 0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, // - p r o x y - a + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, // u t h o r i z a + 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, // t i o n - - - - + 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, // r a n g e - - - + 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, // - r e f e r e r + 0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, // - - - - r e t r + 0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, // y - a f t e r - + 0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, // - - - s e r v e + 0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, // r - - - - t e - + 0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, // - - - t r a i l + 0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, // e r - - - - t r + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, // a n s f e r - e + 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, // n c o d i n g - + 0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, // - - - u p g r a + 0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, // d e - - - - u s + 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, // e r - a g e n t + 0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, // - - - - v a r y + 0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, // - - - - v i a - + 0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, // - - - w a r n i + 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, // n g - - - - w w + 0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, // w - a u t h e n + 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, // t i c a t e - - + 0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, // - - m e t h o d + 0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, // - - - - g e t - + 0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, // - - - s t a t u + 0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, // s - - - - 2 0 0 + 0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, // - O K - - - - v + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, // e r s i o n - - + 0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, // - - H T T P - 1 + 0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, // - 1 - - - - u r + 0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, // l - - - - p u b + 0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, // l i c - - - - s + 0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, // e t - c o o k i + 0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, // e - - - - k e e + 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, // p - a l i v e - + 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, // - - - o r i g i + 0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, // n 1 0 0 1 0 1 2 + 0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, // 0 1 2 0 2 2 0 5 + 0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, // 2 0 6 3 0 0 3 0 + 0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, // 2 3 0 3 3 0 4 3 + 0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, // 0 5 3 0 6 3 0 7 + 0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, // 4 0 2 4 0 5 4 0 + 0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, // 6 4 0 7 4 0 8 4 + 0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, // 0 9 4 1 0 4 1 1 + 0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, // 4 1 2 4 1 3 4 1 + 0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, // 4 4 1 5 4 1 6 4 + 0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, // 1 7 5 0 2 5 0 4 + 0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, // 5 0 5 2 0 3 - N + 0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, // o n - A u t h o + 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, // r i t a t i v e + 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, // - I n f o r m a + 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, // t i o n 2 0 4 - + 0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, // N o - C o n t e + 0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, // n t 3 0 1 - M o + 0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, // v e d - P e r m + 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, // a n e n t l y 4 + 0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, // 0 0 - B a d - R + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, // e q u e s t 4 0 + 0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, // 1 - U n a u t h + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, // o r i z e d 4 0 + 0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, // 3 - F o r b i d + 0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, // d e n 4 0 4 - N + 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, // o t - F o u n d + 0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, // 5 0 0 - I n t e + 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, // r n a l - S e r + 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, // v e r - E r r o + 0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, // r 5 0 1 - N o t + 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, // - I m p l e m e + 0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, // n t e d 5 0 3 - + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, // S e r v i c e - + 0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, // U n a v a i l a + 0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, // b l e J a n - F + 0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, // e b - M a r - A + 0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, // p r - M a y - J + 0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, // u n - J u l - A + 0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, // u g - S e p t - + 0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, // O c t - N o v - + 0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, // D e c - 0 0 - 0 + 0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, // 0 - 0 0 - M o n + 0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, // - - T u e - - W + 0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, // e d - - T h u - + 0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, // - F r i - - S a + 0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, // t - - S u n - - + 0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, // G M T c h u n k + 0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, // e d - t e x t - + 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, // h t m l - i m a + 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, // g e - p n g - i + 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, // m a g e - j p g + 0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, // - i m a g e - g + 0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // i f - a p p l i + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // c a t i o n - x + 0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // m l - a p p l i + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // c a t i o n - x + 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, // h t m l - x m l + 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, // - t e x t - p l + 0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, // a i n - t e x t + 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, // - j a v a s c r + 0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, // i p t - p u b l + 0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, // i c p r i v a t + 0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, // e m a x - a g e + 0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, // - g z i p - d e + 0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, // f l a t e - s d + 0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // c h c h a r s e + 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, // t - u t f - 8 c + 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, // h a r s e t - i + 0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, // s o - 8 8 5 9 - + 0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, // 1 - u t f - - - + 0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e // - e n q - 0 - +}; + +// use for zlib data types +void * +SpdyStream31::zlib_allocator(void *opaque, uInt items, uInt size) +{ + return moz_xmalloc(items * size); +} + +// use for zlib data types +void +SpdyStream31::zlib_destructor(void *opaque, void *addr) +{ + moz_free(addr); +} + +// This can be called N times.. 1 for syn_reply and 0->N for headers +nsresult +SpdyStream31::Uncompress(z_stream *context, + char *blockStart, + uint32_t blockLen) +{ + mDecompressedBytes += blockLen; + + context->avail_in = blockLen; + context->next_in = reinterpret_cast(blockStart); + bool triedDictionary = false; + + do { + context->next_out = + reinterpret_cast(mDecompressBuffer.get()) + + mDecompressBufferUsed; + context->avail_out = mDecompressBufferSize - mDecompressBufferUsed; + int zlib_rv = inflate(context, Z_NO_FLUSH); + + if (zlib_rv == Z_NEED_DICT) { + if (triedDictionary) { + LOG3(("SpdySession31::Uncompress %p Dictionary Error\n", this)); + return NS_ERROR_ILLEGAL_VALUE; + } + + triedDictionary = true; + inflateSetDictionary(context, kDictionary, sizeof(kDictionary)); + } + + if (zlib_rv == Z_DATA_ERROR) + return NS_ERROR_ILLEGAL_VALUE; + + if (zlib_rv == Z_MEM_ERROR) + return NS_ERROR_FAILURE; + + // zlib's inflate() decreases context->avail_out by the amount it places + // in the output buffer + + mDecompressBufferUsed += mDecompressBufferSize - mDecompressBufferUsed - + context->avail_out; + + // When there is no more output room, but input still available then + // increase the output space + if (zlib_rv == Z_OK && + !context->avail_out && context->avail_in) { + LOG3(("SpdyStream31::Uncompress %p Large Headers - so far %d", + this, mDecompressBufferSize)); + SpdySession31::EnsureBuffer(mDecompressBuffer, + mDecompressBufferSize + 4096, + mDecompressBufferUsed, + mDecompressBufferSize); + } + } + while (context->avail_in); + return NS_OK; +} + +// mDecompressBuffer contains 0 to N uncompressed Name/Value Header blocks +nsresult +SpdyStream31::FindHeader(nsCString name, + nsDependentCSubstring &value) +{ + const unsigned char *nvpair = reinterpret_cast + (mDecompressBuffer.get()) + 4; + const unsigned char *lastHeaderByte = reinterpret_cast + (mDecompressBuffer.get()) + mDecompressBufferUsed; + if (lastHeaderByte < nvpair) + return NS_ERROR_ILLEGAL_VALUE; + + do { + uint32_t numPairs = PR_ntohl(reinterpret_cast(nvpair)[-1]); + + for (uint32_t index = 0; index < numPairs; ++index) { + if (lastHeaderByte < nvpair + 4) + return NS_ERROR_ILLEGAL_VALUE; + uint32_t nameLen = (nvpair[0] << 24) + (nvpair[1] << 16) + + (nvpair[2] << 8) + nvpair[3]; + if (lastHeaderByte < nvpair + 4 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; + nsDependentCSubstring nameString = + Substring(reinterpret_cast(nvpair) + 4, + reinterpret_cast(nvpair) + 4 + nameLen); + if (lastHeaderByte < nvpair + 8 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; + uint32_t valueLen = (nvpair[4 + nameLen] << 24) + (nvpair[5 + nameLen] << 16) + + (nvpair[6 + nameLen] << 8) + nvpair[7 + nameLen]; + if (lastHeaderByte < nvpair + 8 + nameLen + valueLen) + return NS_ERROR_ILLEGAL_VALUE; + if (nameString.Equals(name)) { + value.Assign(((char *)nvpair) + 8 + nameLen, valueLen); + return NS_OK; + } + + // that pair didn't match - try the next one in this block + nvpair += 8 + nameLen + valueLen; + } + + // move to the next name/value header block (if there is one) - the + // first pair is offset 4 bytes into it + nvpair += 4; + } while (lastHeaderByte >= nvpair); + + return NS_ERROR_NOT_AVAILABLE; +} + +// ConvertHeaders is used to convert the response headers +// in a syn_reply or in 0..N headers frames that follow it into +// HTTP/1 format +nsresult +SpdyStream31::ConvertHeaders(nsACString &aHeadersOut) +{ + // :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; + + if (mDecompressedBytes && mDecompressBufferUsed) { + Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, mDecompressedBytes); + uint32_t ratio = + mDecompressedBytes * 100 / mDecompressBufferUsed; + Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio); + } + + aHeadersOut.Truncate(); + aHeadersOut.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. + + aHeadersOut.Append(version); + aHeadersOut.Append(NS_LITERAL_CSTRING(" ")); + aHeadersOut.Append(status); + aHeadersOut.Append(NS_LITERAL_CSTRING("\r\n")); + + const unsigned char *nvpair = reinterpret_cast + (mDecompressBuffer.get()) + 4; + const unsigned char *lastHeaderByte = reinterpret_cast + (mDecompressBuffer.get()) + mDecompressBufferUsed; + if (lastHeaderByte < nvpair) + return NS_ERROR_ILLEGAL_VALUE; + + do { + uint32_t numPairs = PR_ntohl(reinterpret_cast(nvpair)[-1]); + + for (uint32_t index = 0; index < numPairs; ++index) { + if (lastHeaderByte < nvpair + 4) + return NS_ERROR_ILLEGAL_VALUE; + + uint32_t nameLen = (nvpair[0] << 24) + (nvpair[1] << 16) + + (nvpair[2] << 8) + nvpair[3]; + if (lastHeaderByte < nvpair + 4 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; + + nsDependentCSubstring nameString = + Substring(reinterpret_cast(nvpair) + 4, + reinterpret_cast(nvpair) + 4 + nameLen); + + if (lastHeaderByte < nvpair + 8 + 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(("SpdyStream31::ConvertHeaders session=%p stream=%p " + "upper case response header found. [%s]\n", + mSession, this, 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(("SpdyStream31::ConvertHeaders session=%p stream=%p " + "transfer-encoding found. Chunked is invalid and no TE sent.", + mSession, this)); + + return NS_ERROR_ILLEGAL_VALUE; + } + + uint32_t valueLen = + (nvpair[4 + nameLen] << 24) + (nvpair[5 + nameLen] << 16) + + (nvpair[6 + nameLen] << 8) + nvpair[7 + nameLen]; + + if (lastHeaderByte < nvpair + 8 + nameLen + valueLen) + return NS_ERROR_ILLEGAL_VALUE; + + // spdy transport level headers shouldn't be gatewayed into http/1 + if (!nameString.IsEmpty() && nameString[0] != ':' && + !nameString.Equals(NS_LITERAL_CSTRING("connection")) && + !nameString.Equals(NS_LITERAL_CSTRING("keep-alive"))) { + nsDependentCSubstring valueString = + Substring(reinterpret_cast(nvpair) + 8 + nameLen, + reinterpret_cast(nvpair) + 8 + nameLen + + valueLen); + + aHeadersOut.Append(nameString); + aHeadersOut.Append(NS_LITERAL_CSTRING(": ")); + + // expand NULL bytes in the value string + for (char *cPtr = valueString.BeginWriting(); + cPtr && cPtr < valueString.EndWriting(); + ++cPtr) { + if (*cPtr != 0) { + aHeadersOut.Append(*cPtr); + continue; + } + + // NULLs are really "\r\nhdr: " + aHeadersOut.Append(NS_LITERAL_CSTRING("\r\n")); + aHeadersOut.Append(nameString); + aHeadersOut.Append(NS_LITERAL_CSTRING(": ")); + } + + aHeadersOut.Append(NS_LITERAL_CSTRING("\r\n")); + } + // move to the next name/value pair in this block + nvpair += 8 + nameLen + valueLen; + } + + // move to the next name/value header block (if there is one) - the + // first pair is offset 4 bytes into it + nvpair += 4; + } while (lastHeaderByte >= nvpair); + + aHeadersOut.Append(NS_LITERAL_CSTRING("X-Firefox-Spdy: 3.1\r\n\r\n")); + LOG (("decoded response headers are:\n%s", + aHeadersOut.BeginReading())); + + // The spdy formatted buffer isnt needed anymore - free it up + mDecompressBuffer = nullptr; + mDecompressBufferSize = 0; + mDecompressBufferUsed = 0; + + return NS_OK; +} + +void +SpdyStream31::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) { + SpdySession31::EnsureBuffer(mTxInlineFrame, + mTxInlineFrameSize + 2000, + mTxInlineFrameUsed, + mTxInlineFrameSize); + avail = mTxInlineFrameSize - mTxInlineFrameUsed; + } + + mZlib->next_out = mTxInlineFrame + mTxInlineFrameUsed; + mZlib->avail_out = avail; + deflate(mZlib, flushMode); + mTxInlineFrameUsed += avail - mZlib->avail_out; + } while (mZlib->avail_in > 0 || !mZlib->avail_out); +} + +void +SpdyStream31::CompressToFrame(uint32_t data) +{ + // convert the data to 4 byte network byte order and write that + // to the compressed stream + data = PR_htonl(data); + + mZlib->next_in = reinterpret_cast (&data); + mZlib->avail_in = 4; + ExecuteCompress(Z_NO_FLUSH); +} + + +void +SpdyStream31::CompressToFrame(const char *data, uint32_t len) +{ + // Format calls for a network ordered 32 bit length + // followed by the utf8 string + + uint32_t networkLen = PR_htonl(len); + + // write out the length + mZlib->next_in = reinterpret_cast (&networkLen); + mZlib->avail_in = 4; + ExecuteCompress(Z_NO_FLUSH); + + // write out the data + mZlib->next_in = (unsigned char *)data; + mZlib->avail_in = len; + ExecuteCompress(Z_NO_FLUSH); +} + +void +SpdyStream31::CompressFlushFrame() +{ + mZlib->next_in = (unsigned char *) ""; + mZlib->avail_in = 0; + ExecuteCompress(Z_SYNC_FLUSH); +} + +void +SpdyStream31::Close(nsresult reason) +{ + mTransaction->Close(reason); +} + +//----------------------------------------------------------------------------- +// nsAHttpSegmentReader +//----------------------------------------------------------------------------- + +nsresult +SpdyStream31::OnReadSegment(const char *buf, + uint32_t count, + uint32_t *countRead) +{ + LOG3(("SpdyStream31::OnReadSegment %p count=%d state=%x", + this, count, mUpstreamState)); + + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + MOZ_ASSERT(mSegmentReader, "OnReadSegment with null 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) { + AdjustInitialWindow(); + 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: + if ((mRemoteWindow <= 0) || (mSession->RemoteSessionWindow() <= 0)) { + *countRead = 0; + LOG3(("SpdyStream31 this=%p, id 0x%X request body suspended because " + "remote window is stream=%ld session=%ld.\n", this, mStreamID, + mRemoteWindow, mSession->RemoteSessionWindow())); + mBlockedOnRwin = true; + return NS_BASE_STREAM_WOULD_BLOCK; + } + mBlockedOnRwin = false; + + dataLength = std::min(count, mChunkSize); + + if (dataLength > mRemoteWindow) + dataLength = static_cast(mRemoteWindow); + + if (dataLength > mSession->RemoteSessionWindow()) + dataLength = static_cast(mSession->RemoteSessionWindow()); + + LOG3(("SpdyStream31 this=%p id 0x%X remote window is stream %ld and " + "session %ld. Chunk is %d\n", + this, mStreamID, mRemoteWindow, mSession->RemoteSessionWindow(), + dataLength)); + mRemoteWindow -= dataLength; + mSession->DecrementRemoteSessionWindow(dataLength); + + LOG3(("SpdyStream31 %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, "SpdyStream31::OnReadSegment non-write state"); + break; + } + + return rv; +} + +//----------------------------------------------------------------------------- +// nsAHttpSegmentWriter +//----------------------------------------------------------------------------- + +nsresult +SpdyStream31::OnWriteSegment(char *buf, + uint32_t count, + uint32_t *countWritten) +{ + LOG3(("SpdyStream31::OnWriteSegment %p count=%d state=%x 0x%X\n", + this, count, mUpstreamState, mStreamID)); + + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + MOZ_ASSERT(mSegmentWriter); + + if (!mPushSource) + return mSegmentWriter->OnWriteSegment(buf, count, countWritten); + + nsresult rv; + rv = mPushSource->GetBufferedData(buf, count, countWritten); + if (NS_FAILED(rv)) + return rv; + + mSession->ConnectPushedStream(this); + return NS_OK; +} + +} // namespace mozilla::net +} // namespace mozilla + diff --git a/netwerk/protocol/http/SpdyStream31.h b/netwerk/protocol/http/SpdyStream31.h new file mode 100644 index 000000000000..99095dec4439 --- /dev/null +++ b/netwerk/protocol/http/SpdyStream31.h @@ -0,0 +1,258 @@ +/* -*- Mode: C++; tab-width: 8; 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_SpdyStream31_h +#define mozilla_net_SpdyStream31_h + +#include "mozilla/Attributes.h" +#include "nsAHttpTransaction.h" + +namespace mozilla { namespace net { + +class SpdyStream31 : public nsAHttpSegmentReader + , public nsAHttpSegmentWriter +{ +public: + NS_DECL_NSAHTTPSEGMENTREADER + NS_DECL_NSAHTTPSEGMENTWRITER + + SpdyStream31(nsAHttpTransaction *, SpdySession31 *, int32_t); + + uint32_t StreamID() { return mStreamID; } + SpdyPushedStream31 *PushSource() { return mPushSource; } + + virtual nsresult ReadSegments(nsAHttpSegmentReader *, uint32_t, uint32_t *); + virtual nsresult WriteSegments(nsAHttpSegmentWriter *, uint32_t, uint32_t *); + virtual bool DeferCleanupOnSuccess() { return false; } + + const nsAFlatCString &Origin() const { return mOrigin; } + + bool RequestBlockedOnRead() + { + return static_cast(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; } + virtual nsILoadGroupConnectionInfo *LoadGroupConnectionInfo() + { + return mTransaction ? mTransaction->LoadGroupConnectionInfo() : nullptr; + } + + void Close(nsresult reason); + + void SetRecvdFin(bool aStatus) { mRecvdFin = aStatus ? 1 : 0; } + bool RecvdFin() { return mRecvdFin; } + + void SetRecvdData(bool aStatus) { mReceivedData = aStatus ? 1 : 0; } + bool RecvdData() { return mReceivedData; } + + 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 unsigned char kDictionary[1423]; + static void *zlib_allocator(void *, uInt, uInt); + static void zlib_destructor(void *, void *); + + nsresult Uncompress(z_stream *, char *, uint32_t); + nsresult ConvertHeaders(nsACString &); + + void UpdateRemoteWindow(int32_t delta) { mRemoteWindow += delta; } + int64_t RemoteWindow() { return mRemoteWindow; } + + void DecrementLocalWindow(uint32_t delta) { + mLocalWindow -= delta; + mLocalUnacked += delta; + } + + void IncrementLocalWindow(uint32_t delta) { + mLocalWindow += delta; + mLocalUnacked -= delta; + } + + uint64_t LocalUnAcked() { return mLocalUnacked; } + int64_t LocalWindow() { return mLocalWindow; } + + bool BlockedOnRwin() { return mBlockedOnRwin; } + + // A pull stream has an implicit sink, a pushed stream has a sink + // once it is matched to a pull stream. + virtual bool HasSink() { return true; } + + virtual ~SpdyStream31(); + +protected: + nsresult FindHeader(nsCString, nsDependentCSubstring &); + + static void CreatePushHashKey(const nsCString &scheme, + const nsCString &hostHeader, + uint64_t serial, + const nsCSubstring &pathInfo, + nsCString &outOrigin, + nsCString &outKey); + + enum stateType { + GENERATING_SYN_STREAM, + GENERATING_REQUEST_BODY, + SENDING_REQUEST_BODY, + SENDING_FIN_STREAM, + UPSTREAM_COMPLETE + }; + + uint32_t mStreamID; + + // The session that this stream is a subset of + SpdySession31 *mSession; + + nsCString mOrigin; + + // 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; + + // Flag is set when all http request headers have been read and ID is stable + uint32_t mSynFrameComplete : 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; + + void ChangeState(enum stateType); + +private: + friend class nsAutoPtr; + + static PLDHashOperator hdrHashEnumerate(const nsACString &, + nsAutoPtr &, + void *); + + nsresult ParseHttpRequestHeaders(const char *, uint32_t, uint32_t *); + void AdjustInitialWindow(); + 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(uint32_t); + void CompressFlushFrame(); + void ExecuteCompress(uint32_t); + + // The underlying HTTP transaction. This pointer is used as the key + // in the SpdySession31 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 mTransaction; + + // 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 quanta upstream data frames are chopped into + uint32_t mChunkSize; + + // 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 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 1st DATA frame has been passed to stream, after + // which additional HEADERS data is invalid + uint32_t mReceivedData : 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 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 SpdySession31::mUpstreamZlib because it needs + // to remain the same in all streams of a session. + z_stream *mZlib; + nsCString mFlatHttpRequestHeaders; + + // These are used for decompressing downstream spdy response headers + uint32_t mDecompressBufferSize; + uint32_t mDecompressBufferUsed; + uint32_t mDecompressedBytes; + nsAutoArrayPtr mDecompressBuffer; + + // 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; + + // mLocalWindow, mRemoteWindow, and mLocalUnacked are for flow control. + // *window are signed because the race conditions in asynchronous SETTINGS + // messages can force them temporarily negative. + + // LocalWindow is how much data the server will send without getting a + // window update + int64_t mLocalWindow; + + // RemoteWindow is how much data the client is allowed to send without + // getting a window update + int64_t mRemoteWindow; + + // LocalUnacked is the number of bytes received by the client but not + // yet reflected in a window update. Sending that update will increment + // LocalWindow + uint64_t mLocalUnacked; + + // True when sending is suspended becuase the remote flow control window is + // <= 0 + bool mBlockedOnRwin; + + // For Progress Events + uint64_t mTotalSent; + uint64_t mTotalRead; + + // For SpdyPush + SpdyPushedStream31 *mPushSource; +}; + +}} // namespace mozilla::net + +#endif // mozilla_net_SpdyStream31_h diff --git a/netwerk/protocol/http/moz.build b/netwerk/protocol/http/moz.build index d6a587a8b4d0..f42326ddf7ad 100644 --- a/netwerk/protocol/http/moz.build +++ b/netwerk/protocol/http/moz.build @@ -36,7 +36,7 @@ EXPORTS.mozilla.net += [ 'HttpChannelParent.h', 'HttpInfo.h', 'PHttpChannelParams.h', - 'PSpdyPush3.h', + 'PSpdyPush.h', ] CPP_SOURCES += [ @@ -49,10 +49,13 @@ CPP_SOURCES += [ 'HttpInfo.cpp', 'NullHttpTransaction.cpp', 'SpdyPush3.cpp', + 'SpdyPush31.cpp', 'SpdySession2.cpp', 'SpdySession3.cpp', + 'SpdySession31.cpp', 'SpdyStream2.cpp', 'SpdyStream3.cpp', + 'SpdyStream31.cpp', 'nsHttp.cpp', 'nsHttpActivityDistributor.cpp', 'nsHttpAuthCache.cpp', diff --git a/netwerk/protocol/http/nsHttp.h b/netwerk/protocol/http/nsHttp.h index 3a5e1bdb3fcb..93dae55bf95f 100644 --- a/netwerk/protocol/http/nsHttp.h +++ b/netwerk/protocol/http/nsHttp.h @@ -25,7 +25,8 @@ class Mutex; namespace net { enum { SPDY_VERSION_2 = 2, - SPDY_VERSION_3 = 3 + SPDY_VERSION_3 = 3, + SPDY_VERSION_31 = 4 }; } // namespace mozilla::net } // namespace mozilla diff --git a/netwerk/protocol/http/nsHttpConnection.cpp b/netwerk/protocol/http/nsHttpConnection.cpp index 9e6e55f06122..301388017f01 100644 --- a/netwerk/protocol/http/nsHttpConnection.cpp +++ b/netwerk/protocol/http/nsHttpConnection.cpp @@ -272,7 +272,7 @@ nsHttpConnection::EnsureNPNComplete() if (NS_FAILED(rv)) goto npnComplete; - LOG(("nsHttpConnection::EnsureNPNComplete %p negotiated to '%s'", + LOG(("nsHttpConnection::EnsureNPNComplete %p negotiated to '%s'\n", this, negotiatedNPN.get())); uint8_t spdyVersion; @@ -408,12 +408,11 @@ nsHttpConnection::SetupSSL(uint32_t caps) if (gHttpHandler->IsSpdyEnabled() && !(caps & NS_HTTP_DISALLOW_SPDY)) { LOG(("nsHttpConnection::SetupSSL Allow SPDY NPN selection")); - if (gHttpHandler->SpdyInfo()->ProtocolEnabled(0)) - protocolArray.AppendElement( - gHttpHandler->SpdyInfo()->VersionString[0]); - if (gHttpHandler->SpdyInfo()->ProtocolEnabled(1)) - protocolArray.AppendElement( - gHttpHandler->SpdyInfo()->VersionString[1]); + for (uint32_t index = 0; index < SpdyInformation::kCount; ++index) { + if (gHttpHandler->SpdyInfo()->ProtocolEnabled(index)) + protocolArray.AppendElement( + gHttpHandler->SpdyInfo()->VersionString[index]); + } } if (NS_SUCCEEDED(ssl->SetNPNList(protocolArray))) { diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp index dc1dad8367a7..4f260dd870ab 100644 --- a/netwerk/protocol/http/nsHttpHandler.cpp +++ b/netwerk/protocol/http/nsHttpHandler.cpp @@ -186,6 +186,7 @@ nsHttpHandler::nsHttpHandler() , mEnableSpdy(false) , mSpdyV2(true) , mSpdyV3(true) + , mSpdyV31(true) , mCoalesceSpdy(true) , mSpdyPersistentSettings(false) , mAllowSpdyPush(true) @@ -1136,6 +1137,12 @@ nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref) mSpdyV3 = cVar; } + if (PREF_CHANGED(HTTP_PREF("spdy.enabled.v3-1"))) { + rv = prefs->GetBoolPref(HTTP_PREF("spdy.enabled.v3-1"), &cVar); + if (NS_SUCCEEDED(rv)) + mSpdyV31 = cVar; + } + if (PREF_CHANGED(HTTP_PREF("spdy.coalesce-hostnames"))) { rv = prefs->GetBoolPref(HTTP_PREF("spdy.coalesce-hostnames"), &cVar); if (NS_SUCCEEDED(rv)) diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h index 1daa12844d76..424b036357bd 100644 --- a/netwerk/protocol/http/nsHttpHandler.h +++ b/netwerk/protocol/http/nsHttpHandler.h @@ -97,6 +97,7 @@ public: 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; } @@ -417,6 +418,7 @@ private: bool mEnableSpdy; bool mSpdyV2; bool mSpdyV3; + bool mSpdyV31; bool mCoalesceSpdy; bool mSpdyPersistentSettings; bool mAllowSpdyPush;