# HG changeset patch # Parent 41b84b87c816403e1b74963d8094cff0406c989e # User Patrick McManus try: -b o -p linux,linux64,macosx64,win32 -u none -t none diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -773,16 +773,21 @@ pref("network.http.prompt-temp-redirect" // Section 4.8 "High-Throughput Data Service Class" pref("network.http.qos", 0); // The number of milliseconds after sending a SYN for an HTTP connection, // to wait before trying a different connection. 0 means do not use a second // connection. pref("network.http.connection-retry-timeout", 250); +// Try and use SPDY when using SSL +pref("network.http.spdy.enabled", true); +pref("network.http.spdy.chunk-size", 4096); +pref("network.http.spdy.timeout", 180); + // default values for FTP // in a DSCP environment this should be 40 (0x28, or AF11), per RFC-4594, // Section 4.8 "High-Throughput Data Service Class", and 80 (0x50, or AF22) // per Section 4.7 "Low-Latency Data Service Class". pref("network.ftp.data.qos", 0); pref("network.ftp.control.qos", 0); // diff --git a/netwerk/base/src/nsSocketTransportService2.cpp b/netwerk/base/src/nsSocketTransportService2.cpp --- a/netwerk/base/src/nsSocketTransportService2.cpp +++ b/netwerk/base/src/nsSocketTransportService2.cpp @@ -388,17 +388,22 @@ nsSocketTransportService::Poll(PRBool wa PRPollDesc *pollList; PRUint32 pollCount; PRIntervalTime pollTimeout; if (mPollList[0].fd) { mPollList[0].out_flags = 0; pollList = mPollList; pollCount = mActiveCount + 1; - pollTimeout = PollTimeout(); +// pollTimeout = PollTimeout(); + // This truly awful hack is because the sslthread force handshake + // implementation is broken. + // This won't matter much for speed, but its a non-starter for battery + // consumption + pollTimeout = PR_MillisecondsToInterval(100); } else { // no pollable event, so busy wait... pollCount = mActiveCount; if (pollCount) pollList = &mPollList[1]; else pollList = nsnull; diff --git a/netwerk/protocol/http/Makefile.in b/netwerk/protocol/http/Makefile.in --- a/netwerk/protocol/http/Makefile.in +++ b/netwerk/protocol/http/Makefile.in @@ -105,16 +105,18 @@ CPPSRCS = \ HttpBaseChannel.cpp \ nsHttpChannel.cpp \ nsHttpPipeline.cpp \ nsHttpActivityDistributor.cpp \ nsHttpChannelAuthProvider.cpp \ HttpChannelParent.cpp \ HttpChannelChild.cpp \ HttpChannelParentListener.cpp \ + SpdySession.cpp \ + SpdyStream.cpp \ $(NULL) LOCAL_INCLUDES = \ -I$(srcdir)/../../base/src \ -I$(topsrcdir)/xpcom/ds \ -I$(topsrcdir)/content/base/src \ -I$(topsrcdir)/content/events/src \ $(NULL) diff --git a/netwerk/protocol/http/SpdySession.cpp b/netwerk/protocol/http/SpdySession.cpp new file mode 100644 --- /dev/null +++ b/netwerk/protocol/http/SpdySession.cpp @@ -0,0 +1,1668 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick McManus + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsHttp.h" +#include "SpdySession.h" +#include "SpdyStream.h" +#include "nsHttpConnection.h" +#include "prnetdb.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Preferences.h" + +#ifdef DEBUG +// defined by the socket transport service while active +extern PRThread *gSocketThread; +#endif + +namespace mozilla { +namespace net { + +// SpdySession 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_THREADSAFE_ADDREF(SpdySession) +NS_IMPL_THREADSAFE_RELEASE(SpdySession) +NS_INTERFACE_MAP_BEGIN(SpdySession) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection) +NS_INTERFACE_MAP_END + +SpdySession::SpdySession(nsAHttpTransaction *aHttpTransaction, + nsISocketTransport *aSocketTransport, + PRInt32 firstPriority) + : mSocketTransport(aSocketTransport), + mSegmentReader(nsnull), + mSegmentWriter(nsnull), + mSendingChunkSize(4096), + mNextStreamID(1), + mParallelStreamCount(0), + mForceScheduleCounter(0), + mDownstreamState(BUFFERING_FRAME_HEADER), + mPartialFrame(nsnull), + mFrameBufferSize(kDefaultBufferSize), + mFrameBufferUsed(0), + mFrameDataLast(false), + mFrameDataStream(nsnull), + mNeedsCleanup(nsnull), + mDecompressBufferSize(kDefaultBufferSize), + mDecompressBufferUsed(0), + mShouldGoAway(false), + mClosed(false), + mCleanShutdown(false), + mGoAwayID(0), + mMaxConcurrent(0x00ffffff), + mServerPushedResources(0) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + LOG(("SpdySession::SpdySession %p transaction 1 = %p", + this, aHttpTransaction)); + + mStreamIDHash.Init(); + mStreamTransactionHash.Init(); + mConnection = aHttpTransaction->Connection(); + mFrameBuffer = new char[mFrameBufferSize]; + mDecompressBuffer = new char[mDecompressBufferSize]; + zlibInit(); + + mSendingChunkSize = + Preferences::GetBool("network.http.spdy.chunk-size", 4096); + AddStream(aHttpTransaction, firstPriority); +} + +PLDHashOperator +SpdySession::Shutdown(nsAHttpTransaction *key, + nsAutoPtr &stream, + void *closure) +{ + SpdySession *self = static_cast(closure); + + if (self->mCleanShutdown && + self->mGoAwayID < stream->StreamID()) + stream->Close(NS_ERROR_NET_RESET); // can be restarted + else + stream->Close(NS_ERROR_ABORT); + + return PL_DHASH_NEXT; +} + +SpdySession::~SpdySession() +{ + LOG(("SpdySession::~SpdySession %p", this)); + + inflateEnd(&mDownstreamZlib); + deflateEnd(&mUpstreamZlib); + + nsCString *localFrame; + while ((localFrame = static_cast(mLocalForWrite.PopFront()))) + delete localFrame; + + mStreamTransactionHash.Enumerate(Shutdown, this); + Telemetry::Accumulate(Telemetry::SPDY_PARALLEL_STREAMS, mParallelStreamCount); + Telemetry::Accumulate(Telemetry::SPDY_TOTAL_STREAMS, (mNextStreamID - 1) / 2); + Telemetry::Accumulate(Telemetry::SPDY_SERVER_INITIATED_STREAMS, mServerPushedResources); +} + +typedef nsresult (*Control_FX) (SpdySession *self); +static Control_FX sControlFunctions[] = +{ + nsnull, + SpdySession::HandleSynStream, + SpdySession::HandleSynReply, + SpdySession::HandleRstStream, + SpdySession::HandleSettings, + SpdySession::HandleNoop, + SpdySession::HandlePing, + SpdySession::HandleGoAway, + SpdySession::HandleHeaders, + SpdySession::HandleWindowUpdate +}; + +bool +SpdySession::RoomForMore() +{ + return ((mNextStreamID <= 0x70000002) && + (mStreamTransactionHash.Count() <= mMaxConcurrent)); +} + +PRUint32 +SpdySession::RegisterStreamID(SpdyStream *stream) +{ + LOG(("SpdySession::RegisterStreamID session=%p stream=%p id=0x%X", + this, stream, mNextStreamID)); + + PRUint32 result = mNextStreamID; + mNextStreamID += 2; + + mStreamIDHash.Put(result, stream); + return result; +} + +bool +SpdySession::AddStream(nsAHttpTransaction *aHttpTransaction, + PRInt32 aPriority) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG(("SpdySession::AddStream %p", this)); + + if (!RoomForMore()) + return false; + + // 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. + if (mNextStreamID >= 0x70000000) + mShouldGoAway = true; + + aHttpTransaction->SetConnection(this); + SpdyStream *stream = new SpdyStream(aHttpTransaction, + this, + mSocketTransport, + mSendingChunkSize, + &mUpstreamZlib, + aPriority); + + + LOG(("SpdySession::AddStream %p stream %p NextID=0x%X (tentative)", + this, stream, mNextStreamID)); + + mStreamTransactionHash.Put(aHttpTransaction, stream); + mReadyForWrite.Push(stream); + PRUint32 newCount = mStreamTransactionHash.Count(); + if (newCount > mParallelStreamCount) + mParallelStreamCount = newCount; + LOG(("Currently %d streams in session, high water mark is %d", + newCount, mParallelStreamCount)); + if (mConnection) + mConnection->ResumeSend(aHttpTransaction); + + return true; +} + +void +SpdySession::DontReuse() +{ + mShouldGoAway = true; + if(!mStreamTransactionHash.Count()) + Close(NS_OK); +} + +PRUint32 +SpdySession::WriteQueueSize() +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + PRUint32 count = mUrgentForWrite.GetSize() + + mReadyForWrite.GetSize() + mLocalForWrite.GetSize(); + + if (mPartialFrame) + ++count; + return count; +} + +void +SpdySession::ChangeDownstreamState(enum stateType newState) +{ + LOG(("SpdyStream::ChangeDownstreamState() %p from %X to %X", + this, mDownstreamState, newState)); + mDownstreamState = newState; + + if (mDownstreamState == BUFFERING_FRAME_HEADER) { + if (mFrameDataLast && mFrameDataStream) { + mFrameDataLast = 0; + mFrameDataStream->SetRecvdFin(true); + } + mFrameBufferUsed = 0; + mFrameDataStream = nsnull; + } + + return; +} + +void +SpdySession::EnsureBuffer(nsAutoArrayPtr &buf, + PRUint32 newSize, + PRUint32 preserve, + PRUint32 &objSize) +{ + if (objSize >= newSize) + return; + + objSize = newSize; + nsAutoArrayPtr tmp(new char[objSize]); + memcpy (tmp, buf, preserve); + buf = tmp; +} + +void +SpdySession::zlibInit() +{ + mDownstreamZlib.zalloc = SpdyStream::zlib_allocator; + mDownstreamZlib.zfree = SpdyStream::zlib_destructor; + mDownstreamZlib.opaque = Z_NULL; + + inflateInit(&mDownstreamZlib); + + mUpstreamZlib.zalloc = SpdyStream::zlib_allocator; + mUpstreamZlib.zfree = SpdyStream::zlib_destructor; + mUpstreamZlib.opaque = Z_NULL; + + deflateInit(&mUpstreamZlib, Z_DEFAULT_COMPRESSION); + deflateSetDictionary(&mUpstreamZlib, + reinterpret_cast + (SpdyStream::kDictionary), + strlen(SpdyStream::kDictionary) + 1); + +} + +nsresult +SpdySession::DownstreamUncompress(char *blockStart, PRUint32 blockLen) +{ + mDecompressBufferUsed = 0; + + mDownstreamZlib.avail_in = blockLen; + mDownstreamZlib.next_in = reinterpret_cast(blockStart); + + do { + mDownstreamZlib.next_out = + reinterpret_cast(mDecompressBuffer.get()) + + mDecompressBufferUsed; + mDownstreamZlib.avail_out = mDecompressBufferSize - mDecompressBufferUsed; + int zlib_rv = inflate(&mDownstreamZlib, Z_NO_FLUSH); + + if (zlib_rv == Z_NEED_DICT) + inflateSetDictionary(&mDownstreamZlib, + reinterpret_cast + (SpdyStream::kDictionary), + strlen(SpdyStream::kDictionary) + 1); + + if (zlib_rv == Z_DATA_ERROR || zlib_rv == Z_MEM_ERROR) + return NS_ERROR_FAILURE; + + mDecompressBufferUsed += mDecompressBufferSize -mDecompressBufferUsed - + mDownstreamZlib.avail_out; + + if (mDownstreamZlib.avail_out) { + LOG(("SpdySession::DownstreamUncompress %p Large Headers - so far %d", + this, mDecompressBufferSize)); + EnsureBuffer(mDecompressBuffer, + mDecompressBufferSize + 4096, + mDecompressBufferUsed, + mDecompressBufferSize); + } + } + while (mDownstreamZlib.avail_in); + return NS_OK; +} + +nsresult +SpdySession::FindHeader(nsCString name, + nsDependentCSubstring &value) +{ + const unsigned char *nvpair = reinterpret_cast + (mDecompressBuffer.get()) + 2; + const unsigned char *lastHeaderByte = reinterpret_cast + (mDecompressBuffer.get()) + mDecompressBufferUsed; + if (lastHeaderByte < nvpair) + return NS_ERROR_ILLEGAL_VALUE; + PRUint16 numPairs = + PR_ntohs(reinterpret_cast(mDecompressBuffer.get())[0]); + for (PRUint16 index = 0; index < numPairs; ++index) { + if (lastHeaderByte < nvpair + 2) + return NS_ERROR_ILLEGAL_VALUE; + PRUint32 nameLen = (nvpair[0] << 8) + nvpair[1]; + if (lastHeaderByte < nvpair + 2 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; + nsDependentCSubstring nameString = + Substring (reinterpret_cast(nvpair) + 2, + reinterpret_cast(nvpair) + 2 + nameLen); + if (lastHeaderByte < nvpair + 4 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; + PRUint16 valueLen = (nvpair[2 + nameLen] << 8) + nvpair[3 + nameLen]; + if (lastHeaderByte < nvpair + 4 + nameLen + valueLen) + return NS_ERROR_ILLEGAL_VALUE; + if (nameString.Equals(name)) { + value.Assign(((char *)nvpair) + 4 + nameLen, valueLen); + return NS_OK; + } + nvpair += 4 + nameLen + valueLen; + } + return NS_ERROR_NOT_AVAILABLE; +} + +nsresult +SpdySession::ConvertHeaders(nsDependentCSubstring &status, + nsDependentCSubstring &version) +{ + + mFlatHTTPResponseHeaders.Truncate(); + mFlatHTTPResponseHeadersOut = 0; + mFlatHTTPResponseHeaders.SetCapacity(mDecompressBufferUsed + 64); + + // Connection, Keep-Alive and chunked transfer encodings are to be + // removed. + + // Content-Length is 'advisory'.. we will not strip it because it can + // create UI feedback. + + mFlatHTTPResponseHeaders.Append(version); + mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING(" ")); + mFlatHTTPResponseHeaders.Append(status); + mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING("\r\n")); + + const unsigned char *nvpair = reinterpret_cast + (mDecompressBuffer.get()) + 2; + const unsigned char *lastHeaderByte = reinterpret_cast + (mDecompressBuffer.get()) + mDecompressBufferUsed; + + if (lastHeaderByte < nvpair) + return NS_ERROR_ILLEGAL_VALUE; + + PRUint16 numPairs = + PR_ntohs(reinterpret_cast(mDecompressBuffer.get())[0]); + + for (PRUint16 index = 0; index < numPairs; ++index) { + if (lastHeaderByte < nvpair + 2) + return NS_ERROR_ILLEGAL_VALUE; + + PRUint32 nameLen = (nvpair[0] << 8) + nvpair[1]; + if (lastHeaderByte < nvpair + 2 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; + + nsDependentCSubstring nameString = + Substring (reinterpret_cast(nvpair) + 2, + reinterpret_cast(nvpair) + 2 + nameLen); + + // a null in the name string is particularly wrong because it will + // break the fix-up-nulls-in-value-string algorithm. + if (nameString.FindChar(0) != -1) + return NS_ERROR_ILLEGAL_VALUE; + + if (lastHeaderByte < nvpair + 4 + nameLen) + return NS_ERROR_ILLEGAL_VALUE; + PRUint16 valueLen = (nvpair[2 + nameLen] << 8) + nvpair[3 + nameLen]; + if (lastHeaderByte < nvpair + 4 + nameLen + valueLen) + return NS_ERROR_ILLEGAL_VALUE; + + if (!nameString.Equals(NS_LITERAL_CSTRING("version")) && + !nameString.Equals(NS_LITERAL_CSTRING("status")) && + !nameString.Equals(NS_LITERAL_CSTRING("connection")) && + !nameString.Equals(NS_LITERAL_CSTRING("transfer-encoding")) && + !nameString.Equals(NS_LITERAL_CSTRING("keep-alive"))) { + nsDependentCSubstring valueString = + Substring (reinterpret_cast(nvpair) + 4 + nameLen, + reinterpret_cast(nvpair) + 4 + nameLen + valueLen); + + mFlatHTTPResponseHeaders.Append(nameString); + mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING(": ")); + + PRInt32 valueIndex; + // NULLs are really "\r\nhdr: " + while ((valueIndex = valueString.FindChar(0)) != -1) { + nsCString replacement = NS_LITERAL_CSTRING("\r\n"); + replacement.Append(nameString); + replacement.Append(NS_LITERAL_CSTRING(": ")); + valueString.Replace(valueIndex, 1, replacement); + } + + mFlatHTTPResponseHeaders.Append(valueString); + mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING("\r\n")); + } + nvpair += 4 + nameLen + valueLen; + } + + mFlatHTTPResponseHeaders.Append(NS_LITERAL_CSTRING("X-Firefox-Spdy: 1\r\n\r\n")); + LOG (("decoded response headers are:\n%s", + mFlatHTTPResponseHeaders.get())); + + return NS_OK; +} + +void +SpdySession::GeneratePing(PRUint32 aID) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG(("SpdySession::GeneratePing %p 0x%X\n", this, aID)); + + nsCString *reply = new nsCString(); + reply->SetCapacity(12); + char *packet = reply->BeginWriting(); + packet[0] = kFlag_Control; + packet[1] = 2; /* version 2 */ + packet[2] = 0; + packet[3] = CONTROL_TYPE_PING; + packet[4] = 0; /* flags */ + packet[5] = 0; + packet[6] = 0; + packet[7] = 4; /* length */ + + aID = PR_htonl(aID); + memcpy (packet + 8, &aID, 4); + reply->SetLength(12); + mLocalForWrite.Push(reply); +} + +void +SpdySession::GenerateRstStream(PRUint32 aStatusCode, PRUint32 aID) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG(("SpdySession::GenerateRst %p 0x%X %d\n", this, aID, aStatusCode)); + + nsCString *reply = new nsCString(); + reply->SetCapacity(16); + char *packet = reply->BeginWriting(); + packet[0] = kFlag_Control; + packet[1] = 2; /* version 2 */ + packet[2] = 0; + packet[3] = CONTROL_TYPE_RST_STREAM; + packet[4] = 0; /* flags */ + packet[5] = 0; + packet[6] = 0; + packet[7] = 8; /* length */ + + aID = PR_htonl(aID); + memcpy (packet + 8, &aID, 4); + aStatusCode = PR_htonl(aStatusCode); + memcpy (packet + 12, &aStatusCode, 4); + reply->SetLength(16); + mLocalForWrite.Push(reply); +} + +void +SpdySession::GenerateGoAway() +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG(("SpdySession::GenerateGoAway %p\n", this)); + + nsCString *goaway = new nsCString(); + goaway->SetCapacity(12); + char *packet = goaway->BeginWriting(); + memset (packet, 0, 12); + packet[0] = kFlag_Control; + packet[1] = 2; /* version 2 */ + packet[3] = CONTROL_TYPE_GOAWAY; + packet[7] = 4; /* data length */ + goaway->SetLength(12); + + // last-good-stream-id are bytes 8-11, when we accept server push this will + // need to be set. + + mLocalForWrite.Push(goaway); +} + +void +SpdySession::CleanupStream(SpdyStream *aStream, nsresult aResult) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG(("SpdySession::CleanupStream %p %p 0x%x %X\n", + this, aStream, aStream->StreamID(), aResult)); + + nsresult abortCode = NS_OK; + + if (!aStream->RecvdFin() && aStream->StreamID()) { + LOG(("Stream had not processed recv FIN, sending RST")); + GenerateRstStream(RST_CANCEL, aStream->StreamID()); + } + + // Check if partial frame writer + if (mPartialFrame == aStream) { + LOG(("Stream had active partial write frame - need to abort session")); + abortCode = aResult; + if (NS_SUCCEEDED(abortCode)) + abortCode = NS_ERROR_ABORT; + + mPartialFrame = nsnull; + } + + // Check if partial frame reader + if (aStream == mFrameDataStream) { + LOG(("Stream had active partial read frame on close - need to abort")); + abortCode = NS_ERROR_ABORT; + } + + // check the streams blocked on write, this is linear but the list + // should be pretty short. + PRUint32 size = mReadyForWrite.GetSize(); + for (PRUint32 count = 0; count < size; ++count) { + SpdyStream *stream = static_cast(mReadyForWrite.PopFront()); + if (stream != aStream) + mReadyForWrite.Push(stream); + } + + // Check the streams blocked on urgent (i.e. window update) writing. + // This should also be short. + size = mUrgentForWrite.GetSize(); + for (PRUint32 count = 0; count < size; ++count) { + SpdyStream *stream = static_cast(mUrgentForWrite.PopFront()); + if (stream != aStream) + mUrgentForWrite.Push(stream); + } + + // Remove the stream from the ID hash table. (this one isn't short, which is + // why it is hashed.) + mStreamIDHash.Remove(aStream->StreamID()); + + // Send the stream the close() indication + aStream->Close(aResult); + + // removing from the stream transaction hash will + // delete the SpdyStream and drop the reference to + // its transaction + mStreamTransactionHash.Remove(aStream->Transaction()); + + if (NS_FAILED(abortCode)) + Close(abortCode); + else if (mShouldGoAway && !mStreamTransactionHash.Count()) + Close(NS_OK); +} + +nsresult +SpdySession::HandleSynStream(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_SYN_STREAM, + "wrong control type"); + + if (self->mFrameDataSize < 12) { + LOG(("SpdySession::HandleSynStream %p SYN_STREAM too short data=%d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + PRUint32 streamID = + PR_ntohl(reinterpret_cast(self->mFrameBuffer.get())[2]); + + LOG(("SpdySession::HandleSynStream %p recv SYN_STREAM (push) for ID 0x%X.", + self, streamID)); + + if (streamID & 0x01) { // test for odd stream ID + LOG(("SpdySession::HandleSynStream %p recvd SYN_STREAM id must be even.", + self)); + return NS_ERROR_ILLEGAL_VALUE; + } + + ++(self->mServerPushedResources); + + // Anytime we start using the high bit of stream ID (either client or server) + // begin to migrate to a new session. + if (streamID >= 0x70000000) + self->mShouldGoAway = true; + + // todo populate cache. For now, just reject server push p3 + self->GenerateRstStream(RST_REFUSED_STREAM, streamID); + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; +} + +nsresult +SpdySession::HandleSynReply(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_SYN_REPLY, + "wrong control type"); + + if (self->mFrameDataSize < 8) { + LOG(("SpdySession::HandleSynReply %p SYN REPLY too short data=%d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + PRUint32 streamID = + PR_ntohl(reinterpret_cast(self->mFrameBuffer.get())[2]); + self->mFrameDataStream = self->mStreamIDHash.Get(streamID); + if (!self->mFrameDataStream) { + LOG(("SpdySession::HandleSynReply %p lookup streamID in syn_reply " + "0x%X failed. NextStreamID = 0x%x", self, streamID, + self->mNextStreamID)); + if (streamID >= self->mNextStreamID) + self->GenerateRstStream(RST_INVALID_STREAM, streamID); + + // It is likely that this is a reply to a stream ID that has been canceled. + // For the most part we would like to ignore it, but the header needs to be + // be parsed to keep the compression context synchronized + self->DownstreamUncompress(self->mFrameBuffer + 14, + self->mFrameDataSize - 6); + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; + } + + if (!self->mFrameDataStream->SetFullyOpen()) { + // "If an endpoint receives multiple SYN_REPLY frames for the same active + // stream ID, it must drop the stream, and send a RST_STREAM for the + // stream with the error PROTOCOL_ERROR." + // + // In addition to that we abort the session - this is a serious protocol + // violation. + + self->GenerateRstStream(RST_PROTOCOL_ERROR, streamID); + return NS_ERROR_ILLEGAL_VALUE; + } + + self->mFrameDataLast = self->mFrameBuffer[4] & kFlag_Data_FIN; + + if (self->mFrameBuffer[4] & kFlag_Data_UNI) { + LOG(("SynReply had unidirectional flag set on it - nonsensical")); + return NS_ERROR_ILLEGAL_VALUE; + } + + LOG(("SpdySession::HandleSynReply %p SYN_REPLY for 0x%X fin=%d", + self, streamID, self->mFrameDataLast)); + + // The spdystream needs to see flattened http headers + // The Frame Buffer currently holds the complete SYN_REPLY + // frame. The interesting data is at offset 14, where the + // compressed name/value header block lives. + // We unpack that into the mDecompressBuffer - we can't do + // it streamed because the version and status information + // is not guaranteed to be first. This is then finally + // converted to HTTP format in mFlatHTTPResponseHeaders + + nsresult rv = self->DownstreamUncompress(self->mFrameBuffer + 14, + self->mFrameDataSize - 6); + if (NS_FAILED(rv)) + return rv; + + Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, + self->mFrameDataSize - 6); + PRUint32 ratio = + (self->mFrameDataSize - 6) * 100 / self->mDecompressBufferUsed; + Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio); + + // status and version are required. + nsDependentCSubstring status, version; + rv = self->FindHeader(NS_LITERAL_CSTRING("status"), status); + if (NS_FAILED(rv)) + return rv; + + rv = self->FindHeader(NS_LITERAL_CSTRING("version"), version); + if (NS_FAILED(rv)) + return rv; + + rv = self->ConvertHeaders(status, version); + if (NS_FAILED(rv)) + return rv; + + self->ChangeDownstreamState(PROCESSING_CONTROL_SYN_REPLY); + return NS_OK; +} + +nsresult +SpdySession::HandleRstStream(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_RST_STREAM, + "wrong control type"); + + if (self->mFrameDataSize != 8) { + LOG(("SpdySession::HandleRstStream %p RST_STREAM wrong length data=%d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + PRUint32 streamID = + PR_ntohl(reinterpret_cast(self->mFrameBuffer.get())[2]); + + self->mDownstreamRstReason = + PR_ntohl(reinterpret_cast(self->mFrameBuffer.get())[3]); + + LOG(("SpdySession::HandleRstStream %p RST_STREAM Reason Code %u ID %x", + self, self->mDownstreamRstReason, streamID)); + + if (self->mDownstreamRstReason == RST_INVALID_STREAM || + self->mDownstreamRstReason == RST_FLOW_CONTROL_ERROR) { + // basically just ignore this + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; + } + + self->mFrameDataStream = self->mStreamIDHash.Get(streamID); + if (!self->mFrameDataStream) { + LOG(("SpdySession::HandleRstStream %p lookup streamID for RST Frame " + "0x%X failed", self, streamID)); + return NS_ERROR_ILLEGAL_VALUE; + } + + self->ChangeDownstreamState(PROCESSING_CONTROL_RST_STREAM); + return NS_OK; +} + +nsresult +SpdySession::HandleSettings(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_SETTINGS, + "wrong control type"); + + if (self->mFrameDataSize < 4) { + LOG(("SpdySession::HandleSettings %p SETTINGS wrong length data=%d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + PRUint32 numEntries = + PR_ntohl(reinterpret_cast(self->mFrameBuffer.get())[2]); + + LOG(("SpdySession::HandleSettings %p SETTINGS Control Frame with %d entries", + self, numEntries)); + + for (PRUint32 index = 0; index < numEntries; ++index) { + // To clarify the v2 spec: + // Each entry is a 24 bits of a little endian id, + // followed by 8 bits of flags, + // followed by a 32 bit big endian value + + unsigned char *setting = + reinterpret_cast(self->mFrameBuffer.get()) + 12 + index * 8; + + PRUint32 id = (setting[2] << 16) + (setting[1] << 8) + setting[0]; + PRUint32 flags = setting[3]; + PRUint32 value = PR_ntohl(reinterpret_cast(setting)[1]); + + LOG(("Settings ID %d, Flags %X, Value %d",id, flags, value)); + + switch (id) + { + case SETTINGS_TYPE_UPLOAD_BW: + Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_UL_BW, value); + break; + + case SETTINGS_TYPE_DOWNLOAD_BW: + Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_DL_BW, value); + break; + + case SETTINGS_TYPE_RTT: + Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_RTT, value); + break; + + case SETTINGS_TYPE_MAX_CONCURRENT: + self->mMaxConcurrent = value; + Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_MAX_STREAMS, value); + break; + + case SETTINGS_TYPE_CWND: + Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_CWND, value); + break; + + case SETTINGS_TYPE_DOWNLOAD_RETRANS_RATE: + Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_RETRANS, value); + break; + + case SETTINGS_TYPE_INITIAL_WINDOW: + Telemetry::Accumulate(Telemetry::SPDY_SETTINGS_IW, value >> 10); + break; + + default: + break; + } + + } + + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; +} + +nsresult +SpdySession::HandleNoop(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_NOOP, + "wrong control type"); + + if (self->mFrameDataSize != 0) { + LOG(("SpdySession::HandleNoop %p NOP had data %d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + LOG(("SpdySession::HandleNoop %p NOP.", self)); + + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; +} + +nsresult +SpdySession::HandlePing(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_PING, + "wrong control type"); + + if (self->mFrameDataSize != 4) { + LOG(("SpdySession::HandlePing %p PING had wrong amount of data %d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + PRUint32 pingID = + PR_ntohl(reinterpret_cast(self->mFrameBuffer.get())[2]); + + LOG(("SpdySession::HandlePing %p PING ID 0x%X.", self, pingID)); + + if (pingID & 0x01) { + // We never expect to see an odd PING beacuse we never generate PING. + // The spec mandates ignoring this + LOG(("SpdySession::HandlePing %p PING ID from server was odd.", + self)); + } + else { + self->GeneratePing(pingID); + } + + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; +} + +nsresult +SpdySession::HandleGoAway(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_GOAWAY, + "wrong control type"); + + if (self->mFrameDataSize != 4) { + LOG(("SpdySession::HandleGoAway %p GOAWAY had wrong amount of data %d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + self->mShouldGoAway = true; + self->mGoAwayID = + PR_ntohl(reinterpret_cast(self->mFrameBuffer.get())[2]); + self->mCleanShutdown = true; + + LOG(("SpdySession::HandleGoAway %p GOAWAY Last-Good-ID 0x%X.", + self, self->mGoAwayID)); + self->ResumeRecv(self); + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; +} + +nsresult +SpdySession::HandleHeaders(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_HEADERS, + "wrong control type"); + + if (self->mFrameDataSize < 10) { + LOG(("SpdySession::HandleHeaders %p HEADERS had wrong amount of data %d", + self, self->mFrameDataSize)); + return NS_ERROR_ILLEGAL_VALUE; + } + + PRUint32 streamID = + PR_ntohl(reinterpret_cast(self->mFrameBuffer.get())[2]); + + // this is actually not legal in the HTTP mapping of SPDY. All + // headers are in the syn or syn reply. Log and ignore it. + + LOG(("SpdySession::HandleHeaders %p HEADERS for Stream 0x%X. " + "They are ignored in the HTTP/SPDY mapping.", + self, streamID)); + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; +} + +nsresult +SpdySession::HandleWindowUpdate(SpdySession *self) +{ + NS_ABORT_IF_FALSE(self->mFrameControlType == CONTROL_TYPE_WINDOW_UPDATE, + "wrong control type"); + LOG(("SpdySession::HandleWindowUpdate %p WINDOW UPDATE was " + "received. WINDOW UPDATE is no longer defined in v2. Ignoring.", + self)); + + self->ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; +} + +// Used for the hashtable enumeration to propogate OnTransportStatus events +struct transportStatus +{ + nsITransport *transport; + nsresult status; + PRUint64 progress; +}; + +static PLDHashOperator +StreamTransportStatus(nsAHttpTransaction *key, + nsAutoPtr &stream, + void *closure) +{ + struct transportStatus *status = + static_cast(closure); + + stream->Transaction()->OnTransportStatus(status->transport, + status->status, + status->progress); + return PL_DHASH_NEXT; +} + + +//----------------------------------------------------------------------------- +// nsAHttpTransaction. It is expected that nsHttpConnection is the caller +// of these methods +//----------------------------------------------------------------------------- + +void +SpdySession::OnTransportStatus(nsITransport* aTransport, + nsresult aStatus, + PRUint64 aProgress) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + // nsHttpChannel synthesizes progress events in OnDataAvailable + if (aStatus == nsISocketTransport::STATUS_RECEIVING_FROM) + return; + + // STATUS_SENDING_TO is handled by SpdyStream + if (aStatus == nsISocketTransport::STATUS_SENDING_TO) + return; + + struct transportStatus status; + + status.transport = aTransport; + status.status = aStatus; + status.progress = aProgress; + + mStreamTransactionHash.Enumerate(StreamTransportStatus, &status); +} + +// 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 +SpdySession::ReadSegments(nsAHttpSegmentReader *reader, + PRUint32 count, + PRUint32 *countRead) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + nsresult rv; + *countRead = 0; + + // First priority goes to frames that were writing to the network but were + // blocked part way through. Then to frames that have no streams (e.g ping + // reply) and then third to streams marked urgent (generally they have + // window updates), and finally to streams generally + // ready to send data frames (http requests). + + LOG(("SpdySession::ReadSegments %p parial frame stream=%p", + this, mPartialFrame)); + + SpdyStream *stream = mPartialFrame; + mPartialFrame = nsnull; + + if (!stream) { + nsCString *localFrame = + static_cast(mLocalForWrite.PopFront()); + + if (localFrame){ + LOG(("SpdySession::ReadSegments %p using local frame %p", + this, localFrame)); + + rv = reader->OnReadSegment(localFrame->BeginReading(), + localFrame->Length(), + countRead); + if (NS_FAILED(rv)) { + mLocalForWrite.PushFront(localFrame); + if (mConnection) + mConnection->ResumeSend(nsnull); + return rv; + } + + if (*countRead != localFrame->Length()) { + // need to cut some bytes off and put it back on the front + // of the queue + localFrame->Cut(0, *countRead); + mLocalForWrite.PushFront(localFrame); + } else { + delete localFrame; + } + + if (WriteQueueSize() && mConnection) + mConnection->ResumeSend(nsnull); + return rv; + } + } + + if (!stream) + stream = static_cast(mUrgentForWrite.PopFront()); + if (!stream) + stream = static_cast(mReadyForWrite.PopFront()); + if (!stream) { + LOG(("SpdySession %p could not identify a stream to write; suspending.", + this)); + return NS_BASE_STREAM_WOULD_BLOCK; + } + + LOG(("SpdySession %p will write from SpdyStream %p ctr=%d", + this, stream, mForceScheduleCounter)); + + // We don't want nsHttpConnection::OnSocketWritable to loop over + // us until we run dry - that doesn't leave room for new transactions + // to easily get into the multiplex mix. The counter only has to be + // updated when we would get called in a tight loop (i.e. returning + // NS_OK and > 0 countRead + + if (mForceScheduleCounter >= 4) { + ResumeSend(stream->Transaction()); + mForceScheduleCounter = 0; + return NS_BASE_STREAM_WOULD_BLOCK; + } + PRUint32 oldForceScheduleCounter = mForceScheduleCounter; + mForceScheduleCounter = 0; + + mSegmentReader = reader; + rv = stream->ReadSegments(this, count, countRead); + mSegmentReader = nsnull; + + if (stream->BlockedOnWrite()) { + + // We are writing a frame out, but it is blocked on the output stream. + // Make sure to service that stream next write because we can only + // multiplex between complete frames. + + LOG(("SpdySession::ReadSegments %p dealing with block on write", this)); + + NS_ABORT_IF_FALSE(!mPartialFrame, "partial frame should be empty"); + + mPartialFrame = stream; + mConnection->ResumeSend(stream->Transaction()); + return rv; + } + + 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(). + + LOG(("SpdySession::ReadSegments %p dealing with block on read", this)); + + // call readsegments again if there are other streams ready + // to run in this session + if (WriteQueueSize()) + mConnection->ResumeSend(stream->Transaction()); + return NS_BASE_STREAM_WOULD_BLOCK; + } + + NS_ABORT_IF_FALSE(rv != NS_BASE_STREAM_WOULD_BLOCK, + "Stream Would Block inconsistency"); + + if (NS_FAILED(rv)) { + LOG(("SpdySession::ReadSegments %p returning FAIL code %X", + this, rv)); + return rv; + } + + if (*countRead > 0) { + LOG(("SpdySession::ReadSegments %p stream=%p generated end of frame %d", + this, *countRead)); + mReadyForWrite.Push(stream); + mForceScheduleCounter = oldForceScheduleCounter + 1; + return rv; + } + + LOG(("SpdySession::ReadSegments %p stream=%p stream send complete", this)); + + // in normal http this is done by nshttpconnection, but that class does not + // know which http transaction has made this state transition. + stream->Transaction()->OnTransportStatus(mSocketTransport, + nsISocketTransport::STATUS_WAITING_FOR, + LL_ZERO); + /* we now want to recv data */ + mConnection->ResumeRecv(stream->Transaction()); + + // call readsegments again if there are other streams ready + // to go in this session + if (WriteQueueSize()) + mConnection->ResumeSend(stream->Transaction()); + 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 + +// we call writer->OnWriteSegment to get a spdy header.. and decide if it is +// data or control.. if it is control, just deal with it. +// if it is data, identify the spdy stream +// call stream->WriteSegemnts which can call this::OnWriteSegment to get its data.. it +// always gets full frames, if they are part of the stream + +nsresult +SpdySession::WriteSegments(nsAHttpSegmentWriter *writer, + PRUint32 count, + PRUint32 *countWritten) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + nsresult rv; + *countWritten = 0; + + if (mClosed) + return NS_ERROR_FAILURE; + + // 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. + + NS_ABORT_IF_FALSE(mFrameBufferUsed < 8, + "Frame Buffer Used Too Large for State"); + + rv = writer->OnWriteSegment(mFrameBuffer + mFrameBufferUsed, + 8 - mFrameBufferUsed, + countWritten); + if (NS_FAILED(rv)) { + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + ResumeRecv(nsnull); + } + return rv; + } + + mFrameBufferUsed += *countWritten; + + if (mFrameBufferUsed < 8) + { + LOG(("SpdySession::WriteSegments %p " + "BUFFERING FRAME HEADER incomplete size=%d", + this, mFrameBufferUsed)); + return rv; + } + + // For both control and data frames the second 32 bit word of the header + // is 8-flags, 24-length. (network byte order) + mFrameDataSize = + PR_ntohl(reinterpret_cast(mFrameBuffer.get())[1]); + mFrameDataSize &= 0x00ffffff; + mFrameDataRead = 0; + + if (mFrameBuffer[0] & kFlag_Control) { + EnsureBuffer(mFrameBuffer, mFrameDataSize + 8, 8, mFrameBufferSize); + ChangeDownstreamState(BUFFERING_CONTROL_FRAME); + + // The first 32 bit word of the header is + // 1 ctrl - 15 version - 16 type + PRUint16 version = + PR_ntohs(reinterpret_cast(mFrameBuffer.get())[0]); + version &= 0x7fff; + + mFrameControlType = + PR_ntohs(reinterpret_cast(mFrameBuffer.get())[1]); + + LOG(("SpdySession::WriteSegments %p - Control Frame Identified " + "type %d version %d data len %d", + this, mFrameControlType, version, mFrameDataSize)); + + if (mFrameControlType >= CONTROL_TYPE_LAST || + mFrameControlType <= CONTROL_TYPE_FIRST) + return NS_ERROR_ILLEGAL_VALUE; + + // The protocol document says this value must be 1 even though this + // is known as version 2.. Testing interop indicates that is a typo + // in the protocol document + if (version != 2) + return NS_ERROR_ILLEGAL_VALUE; + } + else { + ChangeDownstreamState(PROCESSING_DATA_FRAME); + + PRUint32 streamID = + PR_ntohl(reinterpret_cast(mFrameBuffer.get())[0]); + mFrameDataStream = mStreamIDHash.Get(streamID); + if (!mFrameDataStream) { + LOG(("SpdySession::WriteSegments %p lookup streamID 0x%X failed. " + "Next = 0x%x", this, streamID, mNextStreamID)); + if (streamID >= mNextStreamID) + GenerateRstStream(RST_INVALID_STREAM, streamID); + ChangeDownstreamState(DISCARD_DATA_FRAME); + } + mFrameDataLast = (mFrameBuffer[4] & kFlag_Data_FIN); + Telemetry::Accumulate(Telemetry::SPDY_CHUNK_RECVD, mFrameDataSize >> 10); + LOG(("Start Processing Data Frame. Session=%p Stream 0x%x Fin=%d Len=%d", + this, streamID, mFrameDataLast, mFrameDataSize)); + + if (mFrameBuffer[4] & kFlag_Data_ZLIB) { + LOG(("Data flag has ZLIB flag set which is not valid >=2 spdy")); + return NS_ERROR_ILLEGAL_VALUE; + } + } + } + + if (mDownstreamState == PROCESSING_CONTROL_RST_STREAM) { + if (mDownstreamRstReason == RST_REFUSED_STREAM) + rv = NS_ERROR_NET_RESET; + else if (mDownstreamRstReason == RST_CANCEL || + mDownstreamRstReason == RST_PROTOCOL_ERROR || + mDownstreamRstReason == RST_INTERNAL_ERROR || + mDownstreamRstReason == RST_UNSUPPORTED_VERSION) + rv = NS_ERROR_NET_INTERRUPT; + else + rv = NS_ERROR_ILLEGAL_VALUE; + + mShouldGoAway = true; + SpdyStream *stream = mFrameDataStream; + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + CleanupStream(stream, rv); + return NS_OK; + } + + if (mDownstreamState == PROCESSING_DATA_FRAME || + mDownstreamState == PROCESSING_CONTROL_SYN_REPLY) { + + mSegmentWriter = writer; + rv = mFrameDataStream->WriteSegments(this, count, countWritten); + mSegmentWriter = nsnull; + + 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 + SpdyStream *stream = mFrameDataStream; + if (mFrameDataRead == mFrameDataSize) + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + CleanupStream(stream, NS_OK); + NS_ABORT_IF_FALSE(!mNeedsCleanup, "double cleanup out of data frame"); + return NS_OK; + } + + if (mNeedsCleanup) { + CleanupStream(mNeedsCleanup, NS_OK); + mNeedsCleanup = nsnull; + } + + // In v3 this is where we would generate a window update + + return rv; + } + + if (mDownstreamState == DISCARD_DATA_FRAME) { + char trash[4096]; + PRUint32 count = NS_MIN(4096U, mFrameDataSize - mFrameDataRead); + + if (!count) { + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + *countWritten = 1; + return NS_OK; + } + + rv = writer->OnWriteSegment(trash, count, countWritten); + + if (NS_FAILED(rv)) { + // maybe just blocked reading from network + ResumeRecv(nsnull); + return rv; + } + + mFrameDataRead += *countWritten; + + if (mFrameDataRead == mFrameDataSize) + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return rv; + } + + NS_ABORT_IF_FALSE(mDownstreamState == BUFFERING_CONTROL_FRAME, + "Not in Bufering Control Frame State"); + NS_ABORT_IF_FALSE(mFrameBufferUsed == 8, + "Frame Buffer Header Not Present"); + + rv = writer->OnWriteSegment(mFrameBuffer + 8 + mFrameDataRead, + mFrameDataSize - mFrameDataRead, + countWritten); + if (NS_FAILED(rv)) { + // maybe just blocked reading from network + ResumeRecv(nsnull); + return rv; + } + + mFrameDataRead += *countWritten; + + if (mFrameDataRead != mFrameDataSize) + return NS_OK; + + rv = sControlFunctions[mFrameControlType](this); + + NS_ABORT_IF_FALSE(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 +SpdySession::Close(nsresult aReason) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + if (mClosed) + return; + + LOG(("SpdySession::Close %p %X", this, aReason)); + mClosed = true; + mStreamTransactionHash.Enumerate(Shutdown, this); + GenerateGoAway(); + mConnection = nsnull; +} + +void +SpdySession::CloseTransaction(nsAHttpTransaction *aTransaction, + nsresult aResult) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG(("spdysession::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. + SpdyStream *stream = mStreamTransactionHash.Get(aTransaction); + if (!stream) { + LOG(("spdysession::CloseTransaction %p %p %x - not found.", + this, aTransaction, aResult)); + return; + } + LOG(("SpdySession::CloseTranscation probably a cancel. " + "this=%p, trans=%p, result=%x, streamID=0x%X stream=%p", + this, aTransaction, aResult, stream->StreamID(), stream)); + CleanupStream(stream, aResult); + ResumeRecv(this); +} + + +//----------------------------------------------------------------------------- +// nsAHttpSegmentReader +//----------------------------------------------------------------------------- + +nsresult +SpdySession::OnReadSegment(const char *buf, + PRUint32 count, + PRUint32 *countRead) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(mSegmentReader, "OnReadSegment with null mSegmentReader"); + + return mSegmentReader->OnReadSegment(buf, count, countRead); +} + +//----------------------------------------------------------------------------- +// nsAHttpSegmentWriter +//----------------------------------------------------------------------------- + +nsresult +SpdySession::OnWriteSegment(char *buf, + PRUint32 count, + PRUint32 *countWritten) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(mSegmentWriter, "OnWriteSegment with null mSegmentWriter"); + nsresult rv; + + if (mDownstreamState == PROCESSING_DATA_FRAME) { + + if (mFrameDataLast && + mFrameDataRead == mFrameDataSize) { + // This will result in Close() being called + mNeedsCleanup = mFrameDataStream; + + LOG(("SpdySession::OnWriteSegment %p - recorded downstream fin of " + "stream %p 0x%X", this, mFrameDataStream, + mFrameDataStream->StreamID())); + *countWritten = 0; + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_BASE_STREAM_CLOSED; + } + + count = NS_MIN(count, mFrameDataSize - mFrameDataRead); + rv = mSegmentWriter->OnWriteSegment(buf, count, countWritten); + if (NS_FAILED(rv)) + return rv; + + mFrameDataRead += *countWritten; + + if ((mFrameDataRead == mFrameDataSize) && !mFrameDataLast) + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + + return rv; + } + + if (mDownstreamState == PROCESSING_CONTROL_SYN_REPLY) { + + if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut && + mFrameDataLast) { + *countWritten = 0; + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_BASE_STREAM_CLOSED; + } + + count = NS_MIN(count, + mFlatHTTPResponseHeaders.Length() - + mFlatHTTPResponseHeadersOut); + memcpy(buf, + mFlatHTTPResponseHeaders.get() + mFlatHTTPResponseHeadersOut, + count); + mFlatHTTPResponseHeadersOut += count; + *countWritten = count; + + if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut && + !mFrameDataLast) + ChangeDownstreamState(BUFFERING_FRAME_HEADER); + return NS_OK; + } + + return NS_ERROR_UNEXPECTED; +} + +//----------------------------------------------------------------------------- +// Modified methods of nsAHttpConnection +//----------------------------------------------------------------------------- + +nsresult +SpdySession::ResumeSend(nsAHttpTransaction *caller) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG(("SpdySession::ResumeSend %p caller=%p", this, caller)); + + // a trapped signal from the http transaction to the connection that + // it is no longer blocked on read. + + if (!mConnection) + return NS_ERROR_FAILURE; + + SpdyStream *stream = mStreamTransactionHash.Get(caller); + if (stream) + mReadyForWrite.Push(stream); + else + LOG(("SpdySession::ResumeSend %p caller %p not found", this, caller)); + + return mConnection->ResumeSend(caller); +} + +nsresult +SpdySession::ResumeRecv(nsAHttpTransaction *caller) +{ + if (!mConnection) + return NS_ERROR_FAILURE; + + return mConnection->ResumeRecv(caller); +} + +PRBool +SpdySession::IsPersistent() +{ + return PR_TRUE; +} + +nsresult +SpdySession::TakeTransport(nsISocketTransport **, + nsIAsyncInputStream **, + nsIAsyncOutputStream **) +{ + NS_ABORT_IF_FALSE(false, "TakeTransport of SpdySession"); + return NS_ERROR_UNEXPECTED; +} + +nsHttpConnection * +SpdySession::TakeHttpConnection() +{ + NS_ABORT_IF_FALSE(false, "TakeHttpConnection of SpdySession"); + return nsnull; +} + +//----------------------------------------------------------------------------- +// unused methods of nsAHttpTransaction +// We can be sure of this because SpdySession is only constructed in +// nsHttpConnection and is never passed out of that object +//----------------------------------------------------------------------------- + +void +SpdySession::SetConnection(nsAHttpConnection *) +{ + // This is unexpected + NS_ABORT_IF_FALSE(false, "SpdySession::SetConnection()"); +} + +void +SpdySession::GetSecurityCallbacks(nsIInterfaceRequestor **, + nsIEventTarget **) +{ + // This is unexpected + NS_ABORT_IF_FALSE(false, "SpdySession::GetSecurityCallbacks()"); +} + +void +SpdySession::SetSSLConnectFailed() +{ + NS_ABORT_IF_FALSE(false, "SpdySession::SetSSLConnectFailed()"); +} + +PRBool +SpdySession::IsDone() +{ + NS_ABORT_IF_FALSE(false, "SpdySession::IsDone()"); + return PR_FALSE; +} + +nsresult +SpdySession::Status() +{ + NS_ABORT_IF_FALSE(false, "SpdySession::Status()"); + return NS_ERROR_UNEXPECTED; +} + +PRUint32 +SpdySession::Available() +{ + NS_ABORT_IF_FALSE(false, "SpdySession::Available()"); + return 0; +} + +nsHttpRequestHead * +SpdySession::RequestHead() +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(false, + "SpdySession::RequestHead() " + "should not be called after SPDY is setup"); + return NULL; +} + +//----------------------------------------------------------------------------- +// Pass through methods of nsAHttpConnection +//----------------------------------------------------------------------------- + +nsAHttpConnection * +SpdySession::Connection() +{ + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + return mConnection; +} + +nsresult +SpdySession::OnHeadersAvailable(nsAHttpTransaction *transaction, + nsHttpRequestHead *requestHead, + nsHttpResponseHead *responseHead, + PRBool *reset) +{ + return mConnection->OnHeadersAvailable(transaction, + requestHead, + responseHead, + reset); +} + +void +SpdySession::GetConnectionInfo(nsHttpConnectionInfo **connInfo) +{ + mConnection->GetConnectionInfo(connInfo); +} + +void +SpdySession::GetSecurityInfo(nsISupports **supports) +{ + mConnection->GetSecurityInfo(supports); +} + +PRBool +SpdySession::IsReused() +{ + return mConnection->IsReused(); +} + +nsresult +SpdySession::PushBack(const char *buf, PRUint32 len) +{ + return mConnection->PushBack(buf, len); +} + +PRBool +SpdySession::LastTransactionExpectedNoContent() +{ + return mConnection->LastTransactionExpectedNoContent(); +} + +void +SpdySession::SetLastTransactionExpectedNoContent(PRBool val) +{ + mConnection->SetLastTransactionExpectedNoContent(val); +} + +} // namespace mozilla::net +} // namespace mozilla + diff --git a/netwerk/protocol/http/SpdySession.h b/netwerk/protocol/http/SpdySession.h new file mode 100644 --- /dev/null +++ b/netwerk/protocol/http/SpdySession.h @@ -0,0 +1,294 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick McManus + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef mozilla_net_SpdySession_h +#define mozilla_net_SpdySession_h + +// SPDY as defined by +// http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2 + +#include "nsAHttpTransaction.h" +#include "nsAHttpConnection.h" +#include "nsClassHashtable.h" +#include "nsDataHashtable.h" +#include "nsDeque.h" +#include "nsHashKeys.h" +#include "zlib.h" + +class nsHttpConnection; +class nsISocketTransport; + +namespace mozilla { namespace net { + +class SpdyStream; + +class SpdySession : public nsAHttpTransaction + , public nsAHttpConnection + , public nsAHttpSegmentReader + , public nsAHttpSegmentWriter +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSAHTTPTRANSACTION + NS_DECL_NSAHTTPCONNECTION + NS_DECL_NSAHTTPSEGMENTREADER + NS_DECL_NSAHTTPSEGMENTWRITER + + SpdySession(nsAHttpTransaction *, nsISocketTransport *, PRInt32); + ~SpdySession(); + + bool AddStream(nsAHttpTransaction *, PRInt32); + bool CanReuse() { return !mShouldGoAway && !mClosed; } + void DontReuse(); + bool RoomForMore(); + PRUint32 RegisterStreamID(SpdyStream *); + + const static PRUint8 kFlag_Control = 0x80; + + const static PRUint8 kFlag_Data_FIN = 0x01; + const static PRUint8 kFlag_Data_UNI = 0x02; + const static PRUint8 kFlag_Data_ZLIB = 0x02; + + const static PRUint8 kPri00 = 0x00; + const static PRUint8 kPri01 = 0x40; + const static PRUint8 kPri02 = 0x80; + const static PRUint8 kPri03 = 0xC0; + + enum + { + CONTROL_TYPE_FIRST = 0, + CONTROL_TYPE_SYN_STREAM = 1, + CONTROL_TYPE_SYN_REPLY = 2, + CONTROL_TYPE_RST_STREAM = 3, + CONTROL_TYPE_SETTINGS = 4, + CONTROL_TYPE_NOOP = 5, + CONTROL_TYPE_PING = 6, + CONTROL_TYPE_GOAWAY = 7, + CONTROL_TYPE_HEADERS = 8, + CONTROL_TYPE_WINDOW_UPDATE = 9, /* no longer in v2 */ + CONTROL_TYPE_LAST = 10 + }; + + enum + { + RST_PROTOCOL_ERROR = 1, + RST_INVALID_STREAM = 2, + RST_REFUSED_STREAM = 3, + RST_UNSUPPORTED_VERSION = 4, + RST_CANCEL = 5, + RST_INTERNAL_ERROR = 6, + RST_FLOW_CONTROL_ERROR = 7, + RST_BAD_ASSOC_STREAM = 8 + }; + + enum + { + SETTINGS_TYPE_UPLOAD_BW = 1, // kb/s + SETTINGS_TYPE_DOWNLOAD_BW = 2, // kb/s + SETTINGS_TYPE_RTT = 3, // ms + SETTINGS_TYPE_MAX_CONCURRENT = 4, // streams + SETTINGS_TYPE_CWND = 5, // packets + SETTINGS_TYPE_DOWNLOAD_RETRANS_RATE = 6, // percentage + SETTINGS_TYPE_INITIAL_WINDOW = 7 // bytes. Not used in v2. + }; + + // This should be big enough to hold all of your control packets, + // but if it needs to grow for huge headers it can do so dynamically. + // About 1% of requests to SPDY google services seem to be > 1000 + // with all less than 2000. + const static PRUint32 kDefaultBufferSize = 2000; + + static nsresult HandleSynStream(SpdySession *); + static nsresult HandleSynReply(SpdySession *); + static nsresult HandleRstStream(SpdySession *); + static nsresult HandleSettings(SpdySession *); + static nsresult HandleNoop(SpdySession *); + static nsresult HandlePing(SpdySession *); + static nsresult HandleGoAway(SpdySession *); + static nsresult HandleHeaders(SpdySession *); + static nsresult HandleWindowUpdate(SpdySession *); + + static void EnsureBuffer(nsAutoArrayPtr &, + PRUint32, PRUint32, PRUint32 &); +private: + + enum stateType { + BUFFERING_FRAME_HEADER, + BUFFERING_CONTROL_FRAME, + PROCESSING_DATA_FRAME, + DISCARD_DATA_FRAME, + PROCESSING_CONTROL_SYN_REPLY, + PROCESSING_CONTROL_RST_STREAM + }; + + PRUint32 WriteQueueSize(); + void ChangeDownstreamState(enum stateType); + nsresult DownstreamUncompress(char *, PRUint32); + void zlibInit(); + nsresult FindHeader(nsCString, nsDependentCSubstring &); + nsresult ConvertHeaders(nsDependentCSubstring &, + nsDependentCSubstring &); + void GeneratePing(PRUint32); + void GenerateRstStream(PRUint32, PRUint32); + void GenerateGoAway(); + void CleanupStream(SpdyStream *, nsresult); + + static PLDHashOperator Shutdown(nsAHttpTransaction *, + nsAutoPtr &, + void *); + + + // This is intended to be nsHttpConnectionMgr:nsHttpConnectionHandle taken + // from the first transcation 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; + + PRUint32 mSendingChunkSize; /* the transmisison chunk size */ + PRUint32 mNextStreamID; /* 24 bits */ + PRUint32 mParallelStreamCount; /* max parallelism on session */ + + // This counter is used to make sure one session, multiplexing lots of streams + // still lets other nshttpconnection level objects run + PRUint32 mForceScheduleCounter; + + stateType mDownstreamState; /* in frame, between frames, etc.. */ + + // Maintain 4 indexes - one by stream ID, one by transaction ptr, + // one list of streams ready to write, and one list of streams + // that must be given priority to write for window updates. The objects + // are not ref counted - they get destryoed + // by the nsClassHashtable implementation when they are removed from + // there. + nsDataHashtable mStreamIDHash; + nsClassHashtable, + SpdyStream> mStreamTransactionHash; + nsDeque mReadyForWrite; + + // UrgentForWrite is meant to carry window updates. They were defined in + // the v2 spec but apparently never implemented so are now scheduled to + // be removed. But they will be reintroduced for v3, so we will leave + // this queue in place to ease that transition. + nsDeque mUrgentForWrite; + + // LocalForWrite is a q of nsCString frames without a stream (e.g. ping) + nsDeque mLocalForWrite; + + // If we block while wrting out a frame then this points to the stream + // that was blocked. When writing again that stream must be the first + // one to write. It is null if there is not a partial frame. + SpdyStream *mPartialFrame; + + // Compression contexts for header transport using deflate. + // SPDY compresses only HTTP headers and does not reset zlib in between + // frames. + z_stream mDownstreamZlib; + z_stream mUpstreamZlib; + + // mFrameBuffer is used to store received control packets and the 8 bytes + // of header on data packets + PRUint32 mFrameBufferSize; + PRUint32 mFrameBufferUsed; + nsAutoArrayPtr mFrameBuffer; + + // mFrameDataSize/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 mFrameDataSize + the constant 8 byte header + PRUint32 mFrameDataSize; + PRUint32 mFrameDataRead; + bool mFrameDataLast; // 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. + SpdyStream *mFrameDataStream; + + // A state variable to cleanup a closed stream after the stack has unwound. + SpdyStream *mNeedsCleanup; + + // The CONTROL_TYPE value for a control frame + PRUint32 mFrameControlType; + + // This reason code in the last processed RESET frame + PRUint32 mDownstreamRstReason; + + // These are used for decompressing downstream spdy response headers + // This is done at the session level because sometimes the stream + // has already been canceled but the decompression still must happen + // to keep the zlib state correct for the next state of headers. + PRUint32 mDecompressBufferSize; + PRUint32 mDecompressBufferUsed; + nsAutoArrayPtr mDecompressBuffer; + + // for the conversion of downstream http headers into spdy formatted headers + nsCString mFlatHTTPResponseHeaders; + PRUint32 mFlatHTTPResponseHeadersOut; + + // when set, the session will go away when it reaches 0 streams + bool mShouldGoAway; + + // the session has received a nsAHttpTransaction::Close() call + bool mClosed; + + // the session received a GoAway frame with a valid GoAwayID + bool mCleanShutdown; + + // If a GoAway message was received this is the ID of the last valid + // stream. 0 otherwise. (0 is never a valid stream id.) + PRUint32 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. + PRUint32 mMaxConcurrent; + + // The number of server initiated SYN-STREAMS, tracked for telemetry + PRUint32 mServerPushedResources; +}; + +}} // namespace mozilla::net + +#endif // mozilla_net_SpdySession_h diff --git a/netwerk/protocol/http/SpdyStream.cpp b/netwerk/protocol/http/SpdyStream.cpp new file mode 100644 --- /dev/null +++ b/netwerk/protocol/http/SpdyStream.cpp @@ -0,0 +1,816 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick McManus + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsHttp.h" +#include "SpdySession.h" +#include "SpdyStream.h" +#include "nsAlgorithm.h" +#include "prnetdb.h" +#include "nsHttpRequestHead.h" +#include "mozilla/Telemetry.h" +#include "nsISocketTransport.h" +#include "nsISupportsPriority.h" + +#ifdef DEBUG +// defined by the socket transport service while active +extern PRThread *gSocketThread; +#endif + +namespace mozilla { +namespace net { + +SpdyStream::SpdyStream(nsAHttpTransaction *httpTransaction, + SpdySession *spdySession, + nsISocketTransport *socketTransport, + PRUint32 chunkSize, + z_stream *compressionContext, + PRInt32 priority) + : mUpstreamState(GENERATING_SYN_STREAM), + mTransaction(httpTransaction), + mSession(spdySession), + mSocketTransport(socketTransport), + mSegmentReader(nsnull), + mSegmentWriter(nsnull), + mStreamID(0), + mChunkSize(chunkSize), + mSynFrameComplete(0), + mBlockedOnWrite(0), + mRequestBlockedOnRead(0), + mSentFinOnData(0), + mRecvdFin(0), + mFullyOpen(0), + mTxInlineFrameAllocation(SpdySession::kDefaultBufferSize), + mTxInlineFrameSize(0), + mTxInlineFrameSent(0), + mTxStreamFrameSize(0), + mTxStreamFrameSent(0), + mZlib(compressionContext), + mRequestBodyLen(0), + mPriority(priority) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + LOG(("SpdyStream::SpdyStream %p", this)); + + mTxInlineFrame = new char[mTxInlineFrameAllocation]; +} + +SpdyStream::~SpdyStream() +{ +} + +// 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 +SpdyStream::ReadSegments(nsAHttpSegmentReader *reader, + PRUint32 count, + PRUint32 *countRead) +{ + LOG(("SpdyStream::ReadSegments %p count=%d state=%x", + this, count, mUpstreamState)); + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG(("SpdyStream %p ReadSegments reader=%p count=%d", + this, reader, count)); + + nsresult rv = NS_ERROR_UNEXPECTED; + mBlockedOnWrite = 0; + 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 = nsnull; + + // Mark that we are blocked on read if we couldn't read + // the http headers or we recvd would_block from the http transaction. + if ((mUpstreamState == GENERATING_SYN_STREAM && !mSynFrameComplete) || + (rv == NS_BASE_STREAM_WOULD_BLOCK && !mBlockedOnWrite)) + mRequestBlockedOnRead = 1; + + if (!mBlockedOnWrite && NS_SUCCEEDED(rv) && (!*countRead)) { + LOG(("ReadSegments %p Send Request data complete from %x", + this, mUpstreamState)); + GenerateDataFrameHeader(0, true); + ChangeState(SENDING_FIN_STREAM); + mBlockedOnWrite = 1; + rv = NS_BASE_STREAM_WOULD_BLOCK; + } + + break; + + case SENDING_SYN_STREAM: + // We were trying to send the SYN-STREAM but only got part of it out + // before being blocked. Try and send more. + mSegmentReader = reader; + rv = TransmitFrame(nsnull, nsnull); + mSegmentReader = nsnull; + if (!mTxInlineFrameSize) { + ChangeState(GENERATING_REQUEST_BODY); + mBlockedOnWrite = 1; + } + *countRead = 0; + if (NS_SUCCEEDED(rv)) + rv = NS_BASE_STREAM_WOULD_BLOCK; + break; + + case SENDING_FIN_STREAM: + // We were trying to send the SYN-STREAM but only got part of it out + // before being blocked. Try and send more. + if (!mSentFinOnData) { + mSegmentReader = reader; + rv = TransmitFrame(nsnull, nsnull); + mSegmentReader = nsnull; + if (!mTxInlineFrameSize) + ChangeState(UPSTREAM_COMPLETE); + } + else { + rv = NS_OK; + mTxInlineFrameSize = 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; + + default: + NS_ABORT_IF_FALSE(false, "SpdyStream::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 +SpdyStream::WriteSegments(nsAHttpSegmentWriter *writer, + PRUint32 count, + PRUint32 *countWritten) +{ + LOG(("SpdyStream::WriteSegments %p count=%d state=%x", + this, count, mUpstreamState)); + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(!mSegmentWriter, "segment writer in progress"); + + mSegmentWriter = writer; + nsresult rv = mTransaction->WriteSegments(writer, count, countWritten); + mSegmentWriter = nsnull; + return rv; +} + +PLDHashOperator +SpdyStream::hdrHashEnumerate(const nsACString &key, + nsAutoPtr &value, + void *closure) +{ + SpdyStream *self = static_cast(closure); + + self->CompressToFrame(key); + self->CompressToFrame(value.get()); + return PL_DHASH_NEXT; +} + +nsresult +SpdyStream::ParseHttpRequestHeaders(const char *buf, + PRUint32 avail, + PRUint32 *countUsed) +{ + // Returns NS_OK even if the headers are incomplete + // set mSynFrameComplete flag if they are complete + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(mUpstreamState == GENERATING_SYN_STREAM, "wrong state"); + + LOG(("SpdyStream::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 + PRInt32 endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n"); + + if (endHeader == -1) { + // We don't have all the headers yet + LOG(("SpdyStream::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. + PRUint32 oldLen = mFlatHttpRequestHeaders.Length(); + mFlatHttpRequestHeaders.SetLength(endHeader + 2); + *countUsed = avail - (oldLen - endHeader) + 4; + mSynFrameComplete = 1; + + // It is now OK to assign a streamID that we are assured will + // be monotonically increasing amongst syn-streams on this + // session + mStreamID = mSession->RegisterStreamID(this); + NS_ABORT_IF_FALSE(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. + LOG(("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] = SpdySession::kFlag_Control; + mTxInlineFrame[1] = 2; /* version */ + mTxInlineFrame[2] = 0; + mTxInlineFrame[3] = SpdySession::CONTROL_TYPE_SYN_STREAM; + // 4 to 7 are length and flags, we'll fill that in later + + PRUint32 networkOrderID = PR_htonl(mStreamID); + memcpy(mTxInlineFrame + 8, &networkOrderID, 4); + + // this is the associated-to field, which is not used sending + // from the client in the http binding + memset (mTxInlineFrame + 12, 0, 4); + + // Priority flags are the C0 mask of byte 16. + // From low to high: 00 40 80 C0 + // higher raw priority values are actually less important + // + // The other 6 bits of 16 are unused. Spdy/3 will expand + // priority to 4 bits. + // + // When Spdy/3 implements WINDOW_UPDATE the lowest priority + // streams over a threshold (32?) should be given tiny + // receive windows, separate from their spdy priority + // + if (mPriority >= nsISupportsPriority::PRIORITY_LOW) + mTxInlineFrame[16] = SpdySession::kPri00; + else if (mPriority >= nsISupportsPriority::PRIORITY_NORMAL) + mTxInlineFrame[16] = SpdySession::kPri01; + else if (mPriority >= nsISupportsPriority::PRIORITY_HIGH) + mTxInlineFrame[16] = SpdySession::kPri02; + else + mTxInlineFrame[16] = SpdySession::kPri03; + + mTxInlineFrame[17] = 0; /* unused */ + + // need to parse the request line + // METHOD URI VERSION into method, url, version headers + const char *method = mTransaction->RequestHead()->Method().get(); + + const char *version; + if (mTransaction->RequestHead()->Version() == NS_HTTP_VERSION_1_1) + version = "HTTP/1.1"; + else + version = "HTTP/1.0"; + + nsCString hostHeader; + mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader); + + nsClassHashtable hdrHash; + + // 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. + hdrHash.Init(1 + (mTransaction->RequestHead()->Headers().Count() * 2)); + + const char *beginBuffer = mFlatHttpRequestHeaders.BeginReading(); + + // need to hash all the headers together to remove duplicates, special + // headers, etc.. + + PRInt32 crlfIndex = mFlatHttpRequestHeaders.Find("\r\n"); + while (true) { + PRInt32 startIndex = crlfIndex + 2; + + crlfIndex = mFlatHttpRequestHeaders.Find("\r\n", false, startIndex); + if (crlfIndex == -1) + break; + + PRInt32 colonIndex = mFlatHttpRequestHeaders.Find(":", false, startIndex, + crlfIndex - startIndex); + if (colonIndex == -1) + break; + + nsDependentCSubstring name = Substring(beginBuffer + startIndex, + beginBuffer + colonIndex); + // all header names are lower case in spdy + ToLowerCase(name); + + if (name.Equals("method") || + name.Equals("version") || + name.Equals("scheme") || + name.Equals("keep-alive") || + name.Equals("accept-encoding") || + name.Equals("TE") || + name.Equals("connection") || + name.Equals("proxy-connection") || + name.Equals("url")) + continue; + + nsCString *val = hdrHash.Get(name); + if (!val) { + val = new nsCString(); + hdrHash.Put(name, val); + } + + PRInt32 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")) { + PRInt64 len; + if (nsHttp::ParseInt64(val->get(), nsnull, &len)) + mRequestBodyLen = len; + } + } + + mTxInlineFrameSize = 18; + + LOG(("http request headers to encode are: \n%s", + mFlatHttpRequestHeaders.get())); + + // The header block length + PRUint16 count = hdrHash.Count() + 4; /* method, scheme, url, version */ + CompressToFrame(count); + + // method, url, version headers + + CompressToFrame(NS_LITERAL_CSTRING("method")); + CompressToFrame(method, PL_strlen(method)); + CompressToFrame(NS_LITERAL_CSTRING("scheme")); + CompressToFrame(NS_LITERAL_CSTRING("https")); + CompressToFrame(NS_LITERAL_CSTRING("url")); + CompressToFrame(mTransaction->RequestHead()->RequestURI()); + CompressToFrame(NS_LITERAL_CSTRING("version")); + CompressToFrame(version, PL_strlen(version)); + + 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(mTxInlineFrameSize - 8); + + NS_ABORT_IF_FALSE(!mTxInlineFrame[4], + "Size greater than 24 bits"); + + // For methods other than POST and PUT, we will set the fin bit + // right on the syn stream packet. + + if (mTransaction->RequestHead()->Method() != nsHttp::Post && + mTransaction->RequestHead()->Method() != nsHttp::Put) { + mSentFinOnData = 1; + mTxInlineFrame[4] = SpdySession::kFlag_Data_FIN; + } + + Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, mTxInlineFrameSize - 18); + + // The size of the input headers is approximate + PRUint32 ratio = + (mTxInlineFrameSize - 18) * 100 / + (11 + mTransaction->RequestHead()->RequestURI().Length() + + mFlatHttpRequestHeaders.Length()); + + Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio); + return NS_OK; +} + +nsresult +SpdyStream::TransmitFrame(const char *buf, + PRUint32 *countUsed) +{ + NS_ABORT_IF_FALSE(mTxInlineFrameSize, "empty stream frame in transmit"); + NS_ABORT_IF_FALSE(mSegmentReader, "TransmitFrame with null mSegmentReader"); + NS_ABORT_IF_FALSE(!mBlockedOnWrite, "TransmitFrame with block-w preset"); + + PRUint32 transmittedCount; + nsresult rv; + + LOG(("SpdyStream::TransmitFrame %p inline=%d of %d stream=%d of %d", + this, mTxInlineFrameSent, mTxInlineFrameSize, + mTxStreamFrameSent, mTxStreamFrameSize)); + if (countUsed) + *countUsed = 0; + + // In the (relatively common) event that we have a small amount of data + // split between the inlineframe and the streamframe, then move the stream + // data into the inlineframe via copy in order to coalesce into one write. Given + // the interaction with ssl this is worth the small copy cost. + if (mTxStreamFrameSize && mTxInlineFrameSize && + !mTxInlineFrameSent && !mTxStreamFrameSent && + mTxStreamFrameSize < SpdySession::kDefaultBufferSize && + mTxInlineFrameSize + mTxStreamFrameSize < mTxInlineFrameAllocation) { + LOG(("Coalesce Transmit")); + memcpy (mTxInlineFrame + mTxInlineFrameSize, + buf, mTxStreamFrameSize); + mTxInlineFrameSize += mTxStreamFrameSize; + mTxStreamFrameSent = 0; + mTxStreamFrameSize = 0; + } + + // This function calls mSegmentReader->OnReadSegment to report the actual SPDY + // bytes through to the SpdySession and then the HttpConnection which calls + // the socket write function. + + while (mTxInlineFrameSent < mTxInlineFrameSize) { + rv = mSegmentReader->OnReadSegment(mTxInlineFrame + mTxInlineFrameSent, + mTxInlineFrameSize - mTxInlineFrameSent, + &transmittedCount); + LOG(("SpdyStream::TransmitFrame for inline %p result %x len=%d", + this, rv, transmittedCount)); + + if (rv == NS_BASE_STREAM_WOULD_BLOCK) + mBlockedOnWrite = 1; + + if (NS_FAILED(rv)) // this will include WOULD_BLOCK + return rv; + + mTxInlineFrameSent += transmittedCount; + } + + PRUint32 offset = 0; + NS_ABORT_IF_FALSE(mTxStreamFrameSize >= mTxStreamFrameSent, "negative unsent"); + PRUint32 avail = mTxStreamFrameSize - mTxStreamFrameSent; + + while (avail) { + NS_ABORT_IF_FALSE(countUsed, "null countused pointer in a stream context"); + rv = mSegmentReader->OnReadSegment(buf + offset, avail, &transmittedCount); + + LOG(("SpdyStream::TransmitFrame for stream %p result %x len=%d", + this, rv, transmittedCount)); + + if (rv == NS_BASE_STREAM_WOULD_BLOCK) + mBlockedOnWrite = 1; + + if (NS_FAILED(rv)) // this will include WOULD_BLOCK + return rv; + + if (mUpstreamState == SENDING_REQUEST_BODY) { + mTransaction->OnTransportStatus(mSocketTransport, + nsISocketTransport::STATUS_SENDING_TO, + transmittedCount); + } + + *countUsed += transmittedCount; + avail -= transmittedCount; + offset += transmittedCount; + mTxStreamFrameSent += transmittedCount; + } + + if (!avail) { + mTxInlineFrameSent = 0; + mTxInlineFrameSize = 0; + mTxStreamFrameSent = 0; + mTxStreamFrameSize = 0; + } + + return NS_OK; +} + +void +SpdyStream::ChangeState(enum stateType newState) +{ + LOG(("SpdyStream::ChangeState() %p from %X to %X", mUpstreamState, newState)); + mUpstreamState = newState; + return; +} + +void +SpdyStream::GenerateDataFrameHeader(PRUint32 dataLength, bool lastFrame) +{ + LOG(("SpdyStream::GenerateDataFrameHeader %p len=%d last=%d", + this, dataLength, lastFrame)); + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(!mTxInlineFrameSize, "inline frame not empty"); + NS_ABORT_IF_FALSE(!mTxInlineFrameSent, "inline partial send not 0"); + NS_ABORT_IF_FALSE(!mTxStreamFrameSize, "stream frame not empty"); + NS_ABORT_IF_FALSE(!mTxStreamFrameSent, "stream partial send not 0"); + NS_ABORT_IF_FALSE(!(dataLength & 0xff000000), "datalength > 24 bits"); + + (reinterpret_cast(mTxInlineFrame.get()))[0] = PR_htonl(mStreamID); + (reinterpret_cast(mTxInlineFrame.get()))[1] = + PR_htonl(dataLength); + + NS_ABORT_IF_FALSE(!(mTxInlineFrame[0] & 0x80), + "control bit set unexpectedly"); + NS_ABORT_IF_FALSE(!mTxInlineFrame[4], "flag bits set unexpectedly"); + + mTxInlineFrameSize = 8; + mTxStreamFrameSize = dataLength; + + if (lastFrame) { + mTxInlineFrame[4] |= SpdySession::kFlag_Data_FIN; + if (dataLength) + mSentFinOnData = 1; + } +} + +void +SpdyStream::CompressToFrame(const nsACString &str) +{ + CompressToFrame(str.BeginReading(), str.Length()); +} + +void +SpdyStream::CompressToFrame(const nsACString *str) +{ + CompressToFrame(str->BeginReading(), str->Length()); +} + +// Dictionary taken from http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2 +// Name/Value Header Block Format +// spec indicates that the compression dictionary is not null terminated +// but in reality it is. see: +// https://groups.google.com/forum/#!topic/spdy-dev/2pWxxOZEIcs + +const char *SpdyStream::kDictionary = + "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-" + "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi" + "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser" + "-agent10010120020120220320420520630030130230330430530630740040140240340440" + "5406407408409410411412413414415416417500501502503504505accept-rangesageeta" + "glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic" + "ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran" + "sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati" + "oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo" + "ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe" + "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic" + "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1" + ".1statusversionurl"; + +// use for zlib data types +void * +SpdyStream::zlib_allocator(void *opaque, uInt items, uInt size) +{ + return moz_xmalloc(items * size); +} + +// use for zlib data types +void +SpdyStream::zlib_destructor(void *opaque, void *addr) +{ + moz_free(addr); +} + +void +SpdyStream::ExecuteCompress(PRUint32 flushMode) +{ + // Expect mZlib->avail_in and mZlib->next_in to be set. + // Append the compressed version of next_in to mTxInlineFrame + + do + { + PRUint32 avail = mTxInlineFrameAllocation - mTxInlineFrameSize; + if (avail < 1) { + SpdySession::EnsureBuffer(mTxInlineFrame, + mTxInlineFrameAllocation + 2000, + mTxInlineFrameSize, + mTxInlineFrameAllocation); + avail = mTxInlineFrameAllocation - mTxInlineFrameSize; + } + + mZlib->next_out = reinterpret_cast (mTxInlineFrame.get()) + + mTxInlineFrameSize; + mZlib->avail_out = avail; + deflate(mZlib, flushMode); + mTxInlineFrameSize += avail - mZlib->avail_out; + } while (mZlib->avail_in > 0 || !mZlib->avail_out); +} + +void +SpdyStream::CompressToFrame(PRUint16 data) +{ + // convert the data to network byte order and write that + // to the compressed stream + + data = PR_htons(data); + + mZlib->next_in = reinterpret_cast (&data); + mZlib->avail_in = 2; + ExecuteCompress(Z_NO_FLUSH); +} + + +void +SpdyStream::CompressToFrame(const char *data, PRUint32 len) +{ + // Format calls for a network ordered 16 bit length + // followed by the utf8 string + + // for now, silently truncate headers greater than 64KB. Spdy/3 will + // fix this by making the len a 32 bit quantity + if (len > 0xffff) + len = 0xffff; + + PRUint16 networkLen = len; + networkLen = PR_htons(len); + + // write out the length + mZlib->next_in = reinterpret_cast (&networkLen); + mZlib->avail_in = 2; + ExecuteCompress(Z_NO_FLUSH); + + // write out the data + mZlib->next_in = (unsigned char *)data; + mZlib->avail_in = len; + ExecuteCompress(Z_NO_FLUSH); +} + +void +SpdyStream::CompressFlushFrame() +{ + mZlib->next_in = (unsigned char *) ""; + mZlib->avail_in = 0; + ExecuteCompress(Z_SYNC_FLUSH); +} + +void +SpdyStream::Close(nsresult reason) +{ + mTransaction->Close(reason); +} + +//----------------------------------------------------------------------------- +// nsAHttpSegmentReader +//----------------------------------------------------------------------------- + +nsresult +SpdyStream::OnReadSegment(const char *buf, + PRUint32 count, + PRUint32 *countRead) +{ + LOG(("SpdyStream::OnReadSegment %p count=%d state=%x", + this, count, mUpstreamState)); + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(mSegmentReader, "OnReadSegment with null mSegmentReader"); + + nsresult rv = NS_ERROR_UNEXPECTED; + PRUint32 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; + LOG(("ParseHttpRequestHeaders %p used %d of %d.", + this, *countRead, count)); + if (mSynFrameComplete) { + NS_ABORT_IF_FALSE(mTxInlineFrameSize, "OnReadSegment SynFrameComplete 0b"); + rv = TransmitFrame(nsnull, nsnull); + if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead) + rv = NS_OK; + if (mTxInlineFrameSize) + ChangeState(SENDING_SYN_STREAM); + else + ChangeState(GENERATING_REQUEST_BODY); + break; + } + NS_ABORT_IF_FALSE(*countRead == count, + "Header parsing not complete but unused data"); + break; + + case GENERATING_REQUEST_BODY: + NS_ABORT_IF_FALSE(!mTxInlineFrameSent, + "OnReadSegment in generating_request_body with " + "frame in progress"); + dataLength = NS_MIN(count, mChunkSize); + if (dataLength > mRequestBodyLen) + return NS_ERROR_UNEXPECTED; + mRequestBodyLen -= dataLength; + GenerateDataFrameHeader(dataLength, !mRequestBodyLen); + ChangeState(SENDING_REQUEST_BODY); + // NO BREAK + + case SENDING_REQUEST_BODY: + NS_ABORT_IF_FALSE(mTxInlineFrameSize, "OnReadSegment Send Data Header 0b"); + rv = TransmitFrame(buf, countRead); + LOG(("TransmitFrame() rv=%x returning %d data bytes. " + "Header is %d/%d Body is %d/%d.", + rv, *countRead, + mTxInlineFrameSent, mTxInlineFrameSize, + mTxStreamFrameSent, mTxStreamFrameSize)); + + if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead) + rv = NS_OK; + + // If that frame was all sent, look for another one + if (!mTxInlineFrameSize) + ChangeState(GENERATING_REQUEST_BODY); + break; + + case SENDING_SYN_STREAM: + rv = NS_BASE_STREAM_WOULD_BLOCK; + break; + + case SENDING_FIN_STREAM: + NS_ABORT_IF_FALSE(false, + "resuming partial fin stream out of OnReadSegment"); + break; + + default: + NS_ABORT_IF_FALSE(false, "SpdyStream::OnReadSegment non-write state"); + break; + } + + return rv; +} + +//----------------------------------------------------------------------------- +// nsAHttpSegmentWriter +//----------------------------------------------------------------------------- + +nsresult +SpdyStream::OnWriteSegment(char *buf, + PRUint32 count, + PRUint32 *countWritten) +{ + LOG(("SpdyStream::OnWriteSegment %p count=%d state=%x", + this, count, mUpstreamState)); + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(mSegmentWriter, "OnWriteSegment with null mSegmentWriter"); + + return mSegmentWriter->OnWriteSegment(buf, count, countWritten); +} + +} // namespace mozilla::net +} // namespace mozilla + diff --git a/netwerk/protocol/http/SpdyStream.h b/netwerk/protocol/http/SpdyStream.h new file mode 100644 --- /dev/null +++ b/netwerk/protocol/http/SpdyStream.h @@ -0,0 +1,206 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick McManus + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef mozilla_net_SpdyStream_h +#define mozilla_net_SpdyStream_h + +#include "nsAHttpTransaction.h" + +namespace mozilla { namespace net { + +class SpdyStream : public nsAHttpSegmentReader + , public nsAHttpSegmentWriter +{ +public: + NS_DECL_NSAHTTPSEGMENTREADER + NS_DECL_NSAHTTPSEGMENTWRITER + + SpdyStream(nsAHttpTransaction *, + SpdySession *, nsISocketTransport *, + PRUint32, z_stream *, PRInt32); + ~SpdyStream(); + + PRUint32 StreamID() { return mStreamID; } + + nsresult ReadSegments(nsAHttpSegmentReader *, PRUint32, PRUint32 *); + nsresult WriteSegments(nsAHttpSegmentWriter *, PRUint32, PRUint32 *); + + bool BlockedOnWrite() + { + return static_cast(mBlockedOnWrite); + } + + bool RequestBlockedOnRead() + { + return static_cast(mRequestBlockedOnRead); + } + + // returns false if called more than once + bool SetFullyOpen() + { + bool result = !mFullyOpen; + mFullyOpen = 1; + return result; + } + + nsAHttpTransaction *Transaction() + { + return mTransaction; + } + + void Close(nsresult reason); + + void SetRecvdFin(bool aStatus) { mRecvdFin = aStatus ? 1 : 0; } + bool RecvdFin() { return mRecvdFin; } + + // The zlib header compression dictionary defined by SPDY, + // and hooks to the mozilla allocator for zlib to use. + static const char *kDictionary; + static void *zlib_allocator(void *, uInt, uInt); + static void zlib_destructor(void *, void *); + +private: + + enum stateType { + GENERATING_SYN_STREAM, + SENDING_SYN_STREAM, + GENERATING_REQUEST_BODY, + SENDING_REQUEST_BODY, + SENDING_FIN_STREAM, + UPSTREAM_COMPLETE + }; + + static PLDHashOperator hdrHashEnumerate(const nsACString &, + nsAutoPtr &, + void *); + + void ChangeState(enum stateType ); + nsresult ParseHttpRequestHeaders(const char *, PRUint32, PRUint32 *); + nsresult TransmitFrame(const char *, PRUint32 *); + void GenerateDataFrameHeader(PRUint32, bool); + + void CompressToFrame(const nsACString &); + void CompressToFrame(const nsACString *); + void CompressToFrame(const char *, PRUint32); + void CompressToFrame(PRUint16); + void CompressFlushFrame(); + void ExecuteCompress(PRUint32); + + // Each stream goes from syn_stream to upstream_complete, perhaps + // looping on multiple instances of generating_request_body and + // sending_request_body for each SPDY chunk in the upload. + enum stateType mUpstreamState; + + // The underlying HTTP transaction + nsRefPtr mTransaction; + + // The session that this stream is a subset of + SpdySession *mSession; + + // The underlying socket transport object is needed to propogate some events + nsISocketTransport *mSocketTransport; + + // These are temporary state variables to hold the argument to Read/WriteSegments + // so it can be accessed by On(read/write)segment further up the stack. + nsAHttpSegmentReader *mSegmentReader; + nsAHttpSegmentWriter *mSegmentWriter; + + // The 24 bit SPDY stream ID + PRUint32 mStreamID; + + // The quanta upstream data frames are chopped into (4kb default) + PRUint32 mChunkSize; + + // Flag is set when all http request headers have been read + PRUint32 mSynFrameComplete : 1; + + // Flag is set when there is more request data to send and the + // stream needs to be rescheduled for writing. Sometimes this + // is done as a matter of fairness, not really due to blocking + PRUint32 mBlockedOnWrite : 1; + + // Flag is set when the HTTP processor has more data to send + // but has blocked in doing so. + PRUint32 mRequestBlockedOnRead : 1; + + // Flag is set when a FIN has been placed on a data or syn packet + // (i.e after the client has closed) + PRUint32 mSentFinOnData : 1; + + // Flag is set after the response frame bearing the fin bit has + // been processed. (i.e. after the server has closed). + PRUint32 mRecvdFin : 1; + + // Flag is set after syn reply received + PRUint32 mFullyOpen : 1; + + // The InlineFrame and associated data is used for composing control + // frames and data frame headers. + nsAutoArrayPtr mTxInlineFrame; + PRUint32 mTxInlineFrameAllocation; + PRUint32 mTxInlineFrameSize; + PRUint32 mTxInlineFrameSent; + + // mTxStreamFrameSize and mTxStreamFrameSent track the progress of + // transmitting a request body data frame. The data frame itself + // is never copied into the spdy layer. + PRUint32 mTxStreamFrameSize; + PRUint32 mTxStreamFrameSent; + + // Compression context and buffer for request header compression. + // This is a copy of SpdySession::mUpstreamZlib because it needs + // to remain the same in all streams of a session. + z_stream *mZlib; + nsCString mFlatHttpRequestHeaders; + + // Track the content-length of a request body so that we can + // place the fin flag on the last data packet instead of waiting + // for a stream closed indication. Relying on stream close results + // in an extra 0-length runt packet and seems to have some interop + // problems with the google servers. + PRInt64 mRequestBodyLen; + + // based on nsISupportsPriority definitions + PRInt32 mPriority; + +}; + +}} // namespace mozilla::net + +#endif // mozilla_net_SpdyStream_h diff --git a/netwerk/protocol/http/nsAHttpConnection.h b/netwerk/protocol/http/nsAHttpConnection.h --- a/netwerk/protocol/http/nsAHttpConnection.h +++ b/netwerk/protocol/http/nsAHttpConnection.h @@ -72,18 +72,18 @@ public: nsHttpResponseHead *, PRBool *reset) = 0; // // called by a transaction to resume either sending or receiving data // after a transaction returned NS_BASE_STREAM_WOULD_BLOCK from its // ReadSegments/WriteSegments methods. // - virtual nsresult ResumeSend() = 0; - virtual nsresult ResumeRecv() = 0; + virtual nsresult ResumeSend(nsAHttpTransaction *caller) = 0; + virtual nsresult ResumeRecv(nsAHttpTransaction *caller) = 0; // // called by the connection manager to close a transaction being processed // by this connection. // // @param transaction // the transaction being closed. // @param reason @@ -123,18 +123,18 @@ public: // Transfer the base http connection object along with a // reference to it to the caller. virtual nsHttpConnection *TakeHttpConnection() = 0; }; #define NS_DECL_NSAHTTPCONNECTION \ nsresult OnHeadersAvailable(nsAHttpTransaction *, nsHttpRequestHead *, nsHttpResponseHead *, PRBool *reset); \ - nsresult ResumeSend(); \ - nsresult ResumeRecv(); \ + nsresult ResumeSend(nsAHttpTransaction *); \ + nsresult ResumeRecv(nsAHttpTransaction *); \ void CloseTransaction(nsAHttpTransaction *, nsresult); \ void GetConnectionInfo(nsHttpConnectionInfo **); \ nsresult TakeTransport(nsISocketTransport **, \ nsIAsyncInputStream **, \ nsIAsyncOutputStream **); \ void GetSecurityInfo(nsISupports **); \ PRBool IsPersistent(); \ PRBool IsReused(); \ diff --git a/netwerk/protocol/http/nsAHttpTransaction.h b/netwerk/protocol/http/nsAHttpTransaction.h --- a/netwerk/protocol/http/nsAHttpTransaction.h +++ b/netwerk/protocol/http/nsAHttpTransaction.h @@ -57,16 +57,17 @@ class nsHttpRequestHead; // write function returns NS_BASE_STREAM_WOULD_BLOCK in this case). //---------------------------------------------------------------------------- class nsAHttpTransaction : public nsISupports { public: // called by the connection when it takes ownership of the transaction. virtual void SetConnection(nsAHttpConnection *) = 0; + virtual nsAHttpConnection *Connection() = 0; // called by the connection to get security callbacks to set on the // socket transport. virtual void GetSecurityCallbacks(nsIInterfaceRequestor **, nsIEventTarget **) = 0; // called to report socket status (see nsITransportEventSink) virtual void OnTransportStatus(nsITransport* transport, @@ -94,16 +95,17 @@ public: virtual void SetSSLConnectFailed() = 0; // called to retrieve the request headers of the transaction virtual nsHttpRequestHead *RequestHead() = 0; }; #define NS_DECL_NSAHTTPTRANSACTION \ void SetConnection(nsAHttpConnection *); \ + nsAHttpConnection *Connection(); \ void GetSecurityCallbacks(nsIInterfaceRequestor **, \ nsIEventTarget **); \ void OnTransportStatus(nsITransport* transport, \ nsresult status, PRUint64 progress); \ PRBool IsDone(); \ nsresult Status(); \ PRUint32 Available(); \ nsresult ReadSegments(nsAHttpSegmentReader *, PRUint32, PRUint32 *); \ diff --git a/netwerk/protocol/http/nsHttp.h b/netwerk/protocol/http/nsHttp.h --- a/netwerk/protocol/http/nsHttp.h +++ b/netwerk/protocol/http/nsHttp.h @@ -126,16 +126,21 @@ typedef PRUint8 nsHttpVersion; // a transaction with this caps flag keeps timing information #define NS_HTTP_TIMING_ENABLED (1<<5) // a transaction with this caps flag will not only not use an existing // persistent connection but it will close outstanding ones to the same // host. Used by a forced reload to reset the connection states. #define NS_HTTP_CLEAR_KEEPALIVES (1<<6) +// Disallow the use of the SPDY protocol. This is meant for the contexts +// such as HTTP upgrade which are nonsensical for SPDY, it is not the +// SPDY configuration variable. +#define NS_HTTP_DISALLOW_SPDY (1<<7) + //----------------------------------------------------------------------------- // some default values //----------------------------------------------------------------------------- // hard upper limit on the number of requests that can be pipelined #define NS_HTTP_MAX_PIPELINED_REQUESTS 8 #define NS_HTTP_DEFAULT_PORT 80 diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -599,16 +599,17 @@ nsHttpChannel::SetupTransaction() if (mUpgradeProtocolCallback) { mRequestHead.SetHeader(nsHttp::Upgrade, mUpgradeProtocol, PR_FALSE); mRequestHead.SetHeader(nsHttp::Connection, nsDependentCString(nsHttp::Upgrade.get()), PR_TRUE); mCaps |= NS_HTTP_STICKY_CONNECTION; mCaps &= ~NS_HTTP_ALLOW_PIPELINING; mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE; + mCaps |= NS_HTTP_DISALLOW_SPDY; } nsCOMPtr responseStream; rv = mTransaction->Init(mCaps, mConnectionInfo, &mRequestHead, mUploadStream, mUploadStreamHasHeaders, NS_GetCurrentThread(), callbacks, this, getter_AddRefs(responseStream)); if (NS_FAILED(rv)) { diff --git a/netwerk/protocol/http/nsHttpConnection.cpp b/netwerk/protocol/http/nsHttpConnection.cpp --- a/netwerk/protocol/http/nsHttpConnection.cpp +++ b/netwerk/protocol/http/nsHttpConnection.cpp @@ -42,22 +42,27 @@ #include "nsHttpRequestHead.h" #include "nsHttpResponseHead.h" #include "nsHttpHandler.h" #include "nsIOService.h" #include "nsISocketTransportService.h" #include "nsISocketTransport.h" #include "nsIServiceManager.h" #include "nsISSLSocketControl.h" +#include "nsISSLStatusProvider.h" +#include "nsISSLStatus.h" #include "nsStringStream.h" #include "netCore.h" #include "nsNetCID.h" #include "nsProxyRelease.h" #include "prmem.h" #include "nsPreloadedStream.h" +#include "SpdySession.h" +#include "mozilla/Telemetry.h" +#include "nsISupportsPriority.h" #ifdef DEBUG // defined by the socket transport service while active extern PRThread *gSocketThread; #endif static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID); @@ -77,16 +82,21 @@ nsHttpConnection::nsHttpConnection() , mMaxBytesRead(0) , mKeepAlive(PR_TRUE) // assume to keep-alive by default , mKeepAliveMask(PR_TRUE) , mSupportsPipelining(PR_FALSE) // assume low-grade server , mIsReused(PR_FALSE) , mCompletedProxyConnect(PR_FALSE) , mLastTransactionExpectedNoContent(PR_FALSE) , mIdleMonitoring(PR_FALSE) + , mSegmentSize(nsIOService::gDefaultSegmentSize) + , mNPNComplete(false) + , mUsingSpdy(false) + , mPriority(nsISupportsPriority::PRIORITY_NORMAL) + , mReportedSpdy(false) { LOG(("Creating nsHttpConnection @%x\n", this)); // grab a reference to the handler to ensure that it doesn't go away. nsHttpHandler *handler = gHttpHandler; NS_ADDREF(handler); } @@ -136,41 +146,157 @@ nsHttpConnection::Init(nsHttpConnectionI mCallbacks = callbacks; mCallbackTarget = callbackTarget; rv = mSocketTransport->SetSecurityCallbacks(this); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } +bool +nsHttpConnection::EnsureNPNComplete() +{ + // If for some reason the components to check on NPN aren't available, + // this function will just return true to continue on and disable SPDY + + NS_ABORT_IF_FALSE(mSocketTransport, "EnsureNPNComplete " + "socket transport precondition"); + + if (mNPNComplete) + return true; + + nsresult rv; + + nsCOMPtr securityInfo; + nsCOMPtr sslStatusProvider; + nsCOMPtr sslstatus; + nsCOMPtr sslstat; + nsCAutoString negotiatedNPN; + + rv = mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo)); + if (NS_FAILED(rv)) + goto npnComplete; + + sslStatusProvider = do_QueryInterface(securityInfo, &rv); + if (NS_FAILED(rv)) + goto npnComplete; + + sslStatusProvider->GetSSLStatus(getter_AddRefs(sslstatus)); + if (!sslstatus) { + // In this case we will return false and give time for the status + // object to become available, which is a sign the handshake is complete + + nsCOMPtr ssl = do_QueryInterface(securityInfo, &rv); + if (NS_FAILED(rv)) + goto npnComplete; + rv = ssl->ForceHandshake(); + if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) + goto npnComplete; + return false; + } + + sslstat = do_QueryInterface(sslstatus); + if (!sslstat) + goto npnComplete; + + rv = sslstat->GetNegotiatedNPN(negotiatedNPN); + if (rv == NS_ERROR_NOT_CONNECTED) { + + nsCOMPtr ssl = do_QueryInterface(securityInfo, &rv); + if (NS_FAILED(rv)) + goto npnComplete; + rv = ssl->ForceHandshake(); + if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) + goto npnComplete; + return false; + } + + if (NS_FAILED(rv)) + goto npnComplete; + + LOG(("nsHttpConnection::EnsureNPNComplete negotiated to '%s'", + negotiatedNPN.get())); + + if(negotiatedNPN.Equals(NS_LITERAL_CSTRING("spdy/2"))) { + + mUsingSpdy = true; + mIsReused = PR_TRUE; /* all spdy streams are reused */ + + // Wrap the old http transaction into the new spdy session + // as the first stream + mSpdySession = new SpdySession(mTransaction, + mSocketTransport, + mPriority); + mTransaction = mSpdySession; + mIdleTimeout = gHttpHandler->SpdyTimeout(); + } + + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SPDY_NPN_CONNECT, mUsingSpdy); + +npnComplete: + LOG(("nsHttpConnection::EnsureNPNComplete setting complete to true")); + mNPNComplete = true; + return true; +} + // called on the socket thread nsresult -nsHttpConnection::Activate(nsAHttpTransaction *trans, PRUint8 caps) +nsHttpConnection::Activate(nsAHttpTransaction *trans, PRUint8 caps, PRInt32 pri) { nsresult rv; NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnection::Activate [this=%x trans=%x caps=%x]\n", this, trans, caps)); + if (mTransaction && mUsingSpdy) + return AddTransaction(trans, pri); + mPriority = pri; + NS_ENSURE_ARG_POINTER(trans); NS_ENSURE_TRUE(!mTransaction, NS_ERROR_IN_PROGRESS); // Update security callbacks nsCOMPtr callbacks; nsCOMPtr callbackTarget; trans->GetSecurityCallbacks(getter_AddRefs(callbacks), getter_AddRefs(callbackTarget)); if (callbacks != mCallbacks) { mCallbacks.swap(callbacks); if (callbacks) NS_ProxyRelease(mCallbackTarget, callbacks); mCallbackTarget = callbackTarget; } + // If SPDY is enabled setup the NPN negotiation for it + if (!mNPNComplete) { + + mNPNComplete = true; + + if (mConnInfo->UsingSSL() && + !(caps & NS_HTTP_DISALLOW_SPDY) && + gHttpHandler->IsSPDYEnabled()) { + LOG(("nsHttpConnection::Init Setting up SPDY Negotiation")); + nsCOMPtr securityInfo; + nsresult rv = mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr ssl = do_QueryInterface(securityInfo, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray protocolArray; + protocolArray.AppendElement(NS_LITERAL_CSTRING("spdy/2")); + protocolArray.AppendElement(NS_LITERAL_CSTRING("http/1.1")); + if (NS_SUCCEEDED(ssl->SetNPNList(protocolArray))) { + LOG(("nsHttpConnection::Init Setting up SPDY Negotiation OK")); + mNPNComplete = false; + } + } + } + // take ownership of the transaction mTransaction = trans; NS_ABORT_IF_FALSE(!mIdleMonitoring, "Activating a connection with an Idle Monitor"); mIdleMonitoring = PR_FALSE; // set mKeepAlive according to what will be requested @@ -193,16 +319,39 @@ nsHttpConnection::Activate(nsAHttpTransa failed_activation: if (NS_FAILED(rv)) { mTransaction = nsnull; } return rv; } +nsresult +nsHttpConnection::AddTransaction(nsAHttpTransaction *httpTransaction, + PRInt32 priority) +{ + LOG(("nsHttpConnection::AddTransaction for SPDY")); + + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(mSpdySession && mUsingSpdy, + "AddTransaction to live http connection without spdy"); + NS_ABORT_IF_FALSE(mTransaction, + "AddTransaction to idle http connection"); + + if (!mSpdySession->AddStream(httpTransaction, priority)) { + NS_ABORT_IF_FALSE(0, "AddStream should never fail due to" + "RoomForMore() admission check"); + return NS_ERROR_FAILURE; + } + + ResumeSend(httpTransaction); + + return NS_OK; +} + void nsHttpConnection::Close(nsresult reason) { LOG(("nsHttpConnection::Close [this=%x reason=%x]\n", this, reason)); NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); if (NS_FAILED(reason)) { @@ -232,40 +381,67 @@ nsHttpConnection::ProxyStartSSL() if (NS_FAILED(rv)) return rv; nsCOMPtr ssl = do_QueryInterface(securityInfo, &rv); if (NS_FAILED(rv)) return rv; return ssl->ProxyStartSSL(); } +void +nsHttpConnection::DontReuse() +{ + mKeepAliveMask = PR_FALSE; + mKeepAlive = PR_FALSE; + mIdleTimeout = 0; + if (mUsingSpdy) + mSpdySession->DontReuse(); +} + PRBool nsHttpConnection::CanReuse() { - PRBool canReuse = IsKeepAlive() && + PRBool canReuse; + + if (mUsingSpdy) + canReuse = mSpdySession->CanReuse(); + else + canReuse = IsKeepAlive(); + + canReuse = canReuse && (NowInSeconds() - mLastReadTime < mIdleTimeout) && IsAlive(); - + // An idle persistent connection should not have data waiting to be read // before a request is sent. Data here is likely a 408 timeout response // which we would deal with later on through the restart logic, but that // path is more expensive than just closing the socket now. SSL check can // be removed with fixing of 631801 PRUint32 dataSize; - if (canReuse && mSocketIn && !mConnInfo->UsingSSL() && + if (canReuse && mSocketIn && !mConnInfo->UsingSSL() && !mUsingSpdy && NS_SUCCEEDED(mSocketIn->Available(&dataSize)) && dataSize) { LOG(("nsHttpConnection::CanReuse %p %s" "Socket not reusable because read data pending (%d) on it.\n", this, mConnInfo->Host(), dataSize)); canReuse = PR_FALSE; } return canReuse; } +bool +nsHttpConnection::CanDirectlyActivate() +{ + // return true if a new transaction can be addded to ths connection at any + // time through Activate(). In practice this means this is a healthy SPDY + // connection with room for more concurrent streams. + + return UsingSpdy() && CanReuse() && mSpdySession->RoomForMore(); +} + PRUint32 nsHttpConnection::TimeToLive() { PRInt32 tmp = mIdleTimeout - (NowInSeconds() - mLastReadTime); if (0 > tmp) tmp = 0; return tmp; } @@ -290,16 +466,20 @@ nsHttpConnection::IsAlive() #endif return alive; } PRBool nsHttpConnection::SupportsPipelining(nsHttpResponseHead *responseHead) { + // SPDY supports infinite parallelism, so no need to pipeline. + if (mUsingSpdy) + return PR_FALSE; + // XXX there should be a strict mode available that disables this // blacklisting. // assuming connection is HTTP/1.1 with keep-alive enabled if (mConnInfo->UsingHttpProxy() && !mConnInfo->UsingSSL()) { // XXX check for bad proxy servers... return PR_TRUE; } @@ -418,26 +598,29 @@ nsHttpConnection::OnHeadersAvailable(nsA // specified then we use our advertized timeout value. if (mKeepAlive) { val = responseHead->PeekHeader(nsHttp::Keep_Alive); const char *cp = PL_strcasestr(val, "timeout="); if (cp) mIdleTimeout = (PRUint32) atoi(cp + 8); else - mIdleTimeout = gHttpHandler->IdleTimeout(); + mIdleTimeout = mUsingSpdy ? + gHttpHandler->SpdyTimeout() : gHttpHandler->IdleTimeout(); LOG(("Connection can be reused [this=%x idle-timeout=%u]\n", this, mIdleTimeout)); } // if we're doing an SSL proxy connect, then we need to check whether or not // the connect was successful. if so, then we have to reset the transaction // and step-up the socket connection to SSL. finally, we have to wake up the // socket write request. if (mProxyConnectStream) { + NS_ABORT_IF_FALSE(!mUsingSpdy, + "SPDY NPN Complete while using proxy connect stream"); mProxyConnectStream = 0; if (responseHead->Status() == 200) { LOG(("proxy CONNECT succeeded! ssl=%s\n", mConnInfo->UsingSSL() ? "true" :"false")); *reset = PR_TRUE; nsresult rv; if (mConnInfo->UsingSSL()) { rv = ProxyStartSSL(); @@ -500,16 +683,18 @@ nsHttpConnection::SetIsReusedAfter(PRUin mConsiderReusedAfterInterval = PR_MillisecondsToInterval(afterMilliseconds); } nsresult nsHttpConnection::TakeTransport(nsISocketTransport **aTransport, nsIAsyncInputStream **aInputStream, nsIAsyncOutputStream **aOutputStream) { + if (mUsingSpdy) + return NS_ERROR_FAILURE; if (mTransaction && !mTransaction->IsDone()) return NS_ERROR_IN_PROGRESS; if (!(mSocketTransport && mSocketIn && mSocketOut)) return NS_ERROR_NOT_INITIALIZED; if (mInputOverflow) mSocketIn = mInputOverflow.forget(); @@ -547,31 +732,31 @@ nsHttpConnection::PushBack(const char *d return NS_ERROR_UNEXPECTED; } mInputOverflow = new nsPreloadedStream(mSocketIn, data, length); return NS_OK; } nsresult -nsHttpConnection::ResumeSend() +nsHttpConnection::ResumeSend(nsAHttpTransaction *) { LOG(("nsHttpConnection::ResumeSend [this=%p]\n", this)); NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); if (mSocketOut) return mSocketOut->AsyncWait(this, 0, 0, nsnull); NS_NOTREACHED("no socket output stream"); return NS_ERROR_UNEXPECTED; } nsresult -nsHttpConnection::ResumeRecv() +nsHttpConnection::ResumeRecv(nsAHttpTransaction *) { LOG(("nsHttpConnection::ResumeRecv [this=%p]\n", this)); NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); if (mSocketIn) return mSocketIn->AsyncWait(this, 0, 0, nsnull); @@ -580,17 +765,18 @@ nsHttpConnection::ResumeRecv() } void nsHttpConnection::BeginIdleMonitoring() { LOG(("nsHttpConnection::BeginIdleMonitoring [this=%p]\n", this)); NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ABORT_IF_FALSE(!mTransaction, "BeginIdleMonitoring() while active"); - + NS_ABORT_IF_FALSE(!mUsingSpdy, "Idle monitoring of spdy not allowed"); + LOG(("Entering Idle Monitoring Mode [this=%p]", this)); mIdleMonitoring = PR_TRUE; if (mSocketIn) mSocketIn->AsyncWait(this, 0, 0, nsnull); } void nsHttpConnection::EndIdleMonitoring() @@ -611,30 +797,36 @@ nsHttpConnection::EndIdleMonitoring() // nsHttpConnection //----------------------------------------------------------------------------- void nsHttpConnection::CloseTransaction(nsAHttpTransaction *trans, nsresult reason) { LOG(("nsHttpConnection::CloseTransaction[this=%x trans=%x reason=%x]\n", this, trans, reason)); - + NS_ASSERTION(trans == mTransaction, "wrong transaction"); NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); if (mCurrentBytesRead > mMaxBytesRead) mMaxBytesRead = mCurrentBytesRead; // mask this error code because its not a real error. if (reason == NS_BASE_STREAM_CLOSED) reason = NS_OK; mTransaction->Close(reason); mTransaction = nsnull; + if (mUsingSpdy) { + DontReuse(); + mUsingSpdy = false; + mSpdySession = nsnull; + } + if (mCallbacks) { nsIInterfaceRequestor *cbs = nsnull; mCallbacks.swap(cbs); NS_ProxyRelease(mCallbackTarget, cbs); } if (NS_FAILED(reason)) Close(reason); @@ -686,32 +878,49 @@ nsHttpConnection::OnSocketWritable() { LOG(("nsHttpConnection::OnSocketWritable [this=%x]\n", this)); nsresult rv; PRUint32 n; PRBool again = PR_TRUE; do { + mSocketOutCondition = NS_OK; + // if we're doing an SSL proxy connect, then we need to bypass calling // into the transaction. // // NOTE: this code path can't be shared since the transaction doesn't // implement nsIInputStream. doing so is not worth the added cost of // extra indirections during normal reading. // if (mProxyConnectStream) { LOG((" writing CONNECT request stream\n")); rv = mProxyConnectStream->ReadSegments(ReadFromStream, this, - nsIOService::gDefaultSegmentSize, + mSegmentSize, &n); } + else if (!EnsureNPNComplete()) { + // We are ready to proceed with SSL but the handshake is not done. + // When using NPN to negotiate between HTTPS and SPDY, we need to + // see the results of the handshake to know what bytes to send, so + // we cannot proceed with the request headers. + + mSocketIn->AsyncWait(this, 0, 0, nsnull); + rv = NS_OK; + mSocketOutCondition = NS_BASE_STREAM_WOULD_BLOCK; + n = 0; + } else { + if (!mReportedSpdy) + gHttpHandler->ConnMgr()-> + ReportSpdyConnection(this, mUsingSpdy); + LOG((" writing transaction request stream\n")); - rv = mTransaction->ReadSegments(this, nsIOService::gDefaultSegmentSize, &n); + rv = mTransaction->ReadSegments(this, mSegmentSize, &n); } LOG((" ReadSegments returned [rv=%x read=%u sock-cond=%x]\n", rv, n, mSocketOutCondition)); // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF. if (rv == NS_BASE_STREAM_CLOSED) { rv = NS_OK; @@ -763,46 +972,55 @@ nsHttpConnection::OnWriteSegment(char *b // or else we'd end up closing the socket prematurely. NS_ERROR("bad WriteSegments implementation"); return NS_ERROR_FAILURE; // stop iterating } nsresult rv = mSocketIn->Read(buf, count, countWritten); if (NS_FAILED(rv)) mSocketInCondition = rv; - else if (*countWritten == 0) + else if (*countWritten == 0) { mSocketInCondition = NS_BASE_STREAM_CLOSED; + } else mSocketInCondition = NS_OK; // reset condition return mSocketInCondition; } nsresult nsHttpConnection::OnSocketReadable() { LOG(("nsHttpConnection::OnSocketReadable [this=%x]\n", this)); + if (!mNPNComplete) { + if (!EnsureNPNComplete()) + mSocketIn->AsyncWait(this, 0, 0, nsnull); + else + return OnSocketWritable(); + return NS_OK; + } + PRUint32 now = NowInSeconds(); if (mKeepAliveMask && (now - mLastReadTime >= PRUint32(mMaxHangTime))) { LOG(("max hang time exceeded!\n")); // give the handler a chance to create a new persistent connection to // this host if we've been busy for too long. mKeepAliveMask = PR_FALSE; gHttpHandler->ProcessPendingQ(mConnInfo); } mLastReadTime = now; nsresult rv; PRUint32 n; PRBool again = PR_TRUE; do { - rv = mTransaction->WriteSegments(this, nsIOService::gDefaultSegmentSize, &n); + rv = mTransaction->WriteSegments(this, mSegmentSize, &n); if (NS_FAILED(rv)) { // if the transaction didn't want to take any more data, then // wait for the transaction to call ResumeRecv. if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK; again = PR_FALSE; } else { @@ -825,16 +1043,18 @@ nsHttpConnection::OnSocketReadable() nsresult nsHttpConnection::SetupProxyConnect() { const char *val; LOG(("nsHttpConnection::SetupProxyConnect [this=%x]\n", this)); NS_ENSURE_TRUE(!mProxyConnectStream, NS_ERROR_ALREADY_INITIALIZED); + NS_ABORT_IF_FALSE(!mUsingSpdy, + "SPDY NPN Complete while using proxy connect stream"); nsCAutoString buf; nsresult rv = nsHttpHandler::GenerateHostPort( nsDependentCString(mConnInfo->Host()), mConnInfo->Port(), buf); if (NS_FAILED(rv)) return rv; // CONNECT host:port HTTP/1.1 diff --git a/netwerk/protocol/http/nsHttpConnection.h b/netwerk/protocol/http/nsHttpConnection.h --- a/netwerk/protocol/http/nsHttpConnection.h +++ b/netwerk/protocol/http/nsHttpConnection.h @@ -42,16 +42,17 @@ #include "nsHttp.h" #include "nsHttpConnectionInfo.h" #include "nsAHttpConnection.h" #include "nsAHttpTransaction.h" #include "nsXPIDLString.h" #include "nsCOMPtr.h" #include "nsAutoPtr.h" #include "prinrval.h" +#include "SpdySession.h" #include "nsIStreamListener.h" #include "nsISocketTransport.h" #include "nsIAsyncInputStream.h" #include "nsIAsyncOutputStream.h" #include "nsIInterfaceRequestor.h" #include "nsIEventTarget.h" @@ -87,35 +88,36 @@ public: // single transaction before it should no longer be kept // alive. a value of 0xffff indicates no limit. nsresult Init(nsHttpConnectionInfo *info, PRUint16 maxHangTime, nsISocketTransport *, nsIAsyncInputStream *, nsIAsyncOutputStream *, nsIInterfaceRequestor *, nsIEventTarget *); // Activate causes the given transaction to be processed on this - // connection. It fails if there is already an existing transaction. - nsresult Activate(nsAHttpTransaction *, PRUint8 caps); + // connection. It fails if there is already an existing transaction unless + // a multiplexing protocol such as SPDY is being used + nsresult Activate(nsAHttpTransaction *, PRUint8 caps, PRInt32 pri); // Close the underlying socket transport. void Close(nsresult reason); //------------------------------------------------------------------------- // XXX document when these are ok to call PRBool SupportsPipelining() { return mSupportsPipelining; } - PRBool IsKeepAlive() { return mKeepAliveMask && mKeepAlive; } + PRBool IsKeepAlive() { return mUsingSpdy || + (mKeepAliveMask && mKeepAlive); } PRBool CanReuse(); // can this connection be reused? + bool CanDirectlyActivate(); // Returns time in seconds for how long connection can be reused. PRUint32 TimeToLive(); - void DontReuse() { mKeepAliveMask = PR_FALSE; - mKeepAlive = PR_FALSE; - mIdleTimeout = 0; } + void DontReuse(); void DropTransport() { DontReuse(); mSocketTransport = 0; } PRBool LastTransactionExpectedNoContent() { return mLastTransactionExpectedNoContent; } void SetLastTransactionExpectedNoContent(PRBool val) @@ -134,43 +136,51 @@ public: nsIAsyncInputStream **, nsIAsyncOutputStream **); void GetSecurityInfo(nsISupports **); PRBool IsPersistent() { return IsKeepAlive(); } PRBool IsReused(); void SetIsReusedAfter(PRUint32 afterMilliseconds); void SetIdleTimeout(PRUint16 val) {mIdleTimeout = val;} nsresult PushBack(const char *data, PRUint32 length); - nsresult ResumeSend(); - nsresult ResumeRecv(); + nsresult ResumeSend(nsAHttpTransaction *caller); + nsresult ResumeRecv(nsAHttpTransaction *caller); PRInt64 MaxBytesRead() {return mMaxBytesRead;} static NS_METHOD ReadFromStream(nsIInputStream *, void *, const char *, PRUint32, PRUint32, PRUint32 *); // When a persistent connection is in the connection manager idle // connection pool, the nsHttpConnection still reads errors and hangups // on the socket so that it can be proactively released if the server // initiates a termination. Only call on socket thread. void BeginIdleMonitoring(); void EndIdleMonitoring(); + bool UsingSpdy() { return mUsingSpdy; } + void SetSegmentSize(PRUint32 aSegmentSize) { mSegmentSize = aSegmentSize; } + private: // called to cause the underlying socket to start speaking SSL nsresult ProxyStartSSL(); nsresult OnTransactionDone(nsresult reason); nsresult OnSocketWritable(); nsresult OnSocketReadable(); nsresult SetupProxyConnect(); PRBool IsAlive(); PRBool SupportsPipelining(nsHttpResponseHead *); + // Makes certain the SSL handshake is complete and NPN negotiation + // has had a chance to happen + bool EnsureNPNComplete(); + nsresult AddTransaction(nsAHttpTransaction *, PRInt32); + private: nsCOMPtr mSocketTransport; nsCOMPtr mSocketIn; nsCOMPtr mSocketOut; nsresult mSocketInCondition; nsresult mSocketOutCondition; @@ -198,11 +208,19 @@ private: PRPackedBool mKeepAlive; PRPackedBool mKeepAliveMask; PRPackedBool mSupportsPipelining; PRPackedBool mIsReused; PRPackedBool mCompletedProxyConnect; PRPackedBool mLastTransactionExpectedNoContent; PRPackedBool mIdleMonitoring; + + PRUint32 mSegmentSize; + + bool mNPNComplete; + bool mUsingSpdy; + nsRefPtr mSpdySession; + PRInt32 mPriority; + bool mReportedSpdy; }; #endif // nsHttpConnection_h__ diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.cpp b/netwerk/protocol/http/nsHttpConnectionMgr.cpp --- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp +++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp @@ -224,18 +224,23 @@ nsHttpConnectionMgr::PruneDeadConnection mTimeOfNextWakeUp = timeInSeconds + NowInSeconds(); mTimer->Init(this, timeInSeconds*1000, nsITimer::TYPE_ONE_SHOT); } else { NS_WARNING("failed to create: timer for pruning the dead connections!"); } } void -nsHttpConnectionMgr::StopPruneDeadConnectionsTimer() +nsHttpConnectionMgr::ConditionallyStopPruneDeadConnectionsTimer() { + // Leave the timer in place if there are connections that potentially + // need management + if (mNumIdleConns || mNumActiveConns) + return; + LOG(("nsHttpConnectionMgr::StopPruneDeadConnectionsTimer\n")); // Reset mTimeOfNextWakeUp so that we can find a new shortest value. mTimeOfNextWakeUp = LL_MAXUINT; if (mTimer) { mTimer->Cancel(); mTimer = NULL; } @@ -403,21 +408,42 @@ nsHttpConnectionMgr::CloseIdleConnection nsConnectionEntry *ent = mCT.Get(ci->HashKey()); if (!ent || !ent->mIdleConns.RemoveElement(conn)) return NS_ERROR_UNEXPECTED; conn->Close(NS_ERROR_ABORT); NS_RELEASE(conn); mNumIdleConns--; - if (0 == mNumIdleConns) - StopPruneDeadConnectionsTimer(); + ConditionallyStopPruneDeadConnectionsTimer(); return NS_OK; } +void +nsHttpConnectionMgr::ReportSpdyConnection(nsHttpConnection *conn, bool usingSpdy) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + nsConnectionEntry *ent = mCT.Get(conn->ConnectionInfo()->HashKey()); + NS_ABORT_IF_FALSE(ent, "no connection entry"); + if (!ent) + return; + + ent->mTestedSpdy = true; + ent->mUsingSpdy = ent->mUsingSpdy || usingSpdy; + + if (usingSpdy) { + PRUint32 ttl = conn->TimeToLive(); + PRUint64 timeOfExpire = NowInSeconds() + ttl; + if (!mTimer || timeOfExpire < mTimeOfNextWakeUp) + PruneDeadConnectionsAfter(ttl); + } + ProcessPendingQForEntry(ent); +} + //----------------------------------------------------------------------------- // enumeration callbacks PLDHashOperator nsHttpConnectionMgr::ProcessOneTransactionCB(const nsACString &key, nsAutoPtr &ent, void *closure) { @@ -444,79 +470,100 @@ nsHttpConnectionMgr::PurgeExcessIdleConn // There are no idle conns left in this connection entry return PL_DHASH_NEXT; } nsHttpConnection *conn = ent->mIdleConns[0]; ent->mIdleConns.RemoveElementAt(0); conn->Close(NS_ERROR_ABORT); NS_RELEASE(conn); self->mNumIdleConns--; - if (0 == self->mNumIdleConns) - self->StopPruneDeadConnectionsTimer(); + self->ConditionallyStopPruneDeadConnectionsTimer(); } return PL_DHASH_STOP; } PLDHashOperator nsHttpConnectionMgr::PruneDeadConnectionsCB(const nsACString &key, nsAutoPtr &ent, void *closure) { nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure; LOG((" pruning [ci=%s]\n", ent->mConnInfo->HashKey().get())); // Find out how long it will take for next idle connection to not be reusable // anymore. + bool liveConnections = false; PRUint32 timeToNextExpire = PR_UINT32_MAX; PRInt32 count = ent->mIdleConns.Length(); if (count > 0) { for (PRInt32 i=count-1; i>=0; --i) { nsHttpConnection *conn = ent->mIdleConns[i]; if (!conn->CanReuse()) { ent->mIdleConns.RemoveElementAt(i); conn->Close(NS_ERROR_ABORT); NS_RELEASE(conn); self->mNumIdleConns--; } else { timeToNextExpire = NS_MIN(timeToNextExpire, conn->TimeToLive()); + liveConnections = true; } } } + if (ent->mUsingSpdy) { + for (PRUint32 index = 0; index < ent->mActiveConns.Length(); ++index) { + nsHttpConnection *conn = ent->mActiveConns[index]; + if (conn->UsingSpdy()) { + if (!conn->CanReuse()) { + // marking it dont reuse will create an active tear down if + // the spdy session is idle. + conn->DontReuse(); + } + else { + timeToNextExpire = NS_MIN(timeToNextExpire, conn->TimeToLive()); + liveConnections = true; + } + } + } + } + // If time to next expire found is shorter than time to next wake-up, we need to // change the time for next wake-up. - PRUint32 now = NowInSeconds(); - if (0 < ent->mIdleConns.Length()) { + if (liveConnections) { + PRUint32 now = NowInSeconds(); PRUint64 timeOfNextExpire = now + timeToNextExpire; // If pruning of dead connections is not already scheduled to happen // or time found for next connection to expire is is before // mTimeOfNextWakeUp, we need to schedule the pruning to happen // after timeToNextExpire. if (!self->mTimer || timeOfNextExpire < self->mTimeOfNextWakeUp) { self->PruneDeadConnectionsAfter(timeToNextExpire); } - } else if (0 == self->mNumIdleConns) { - self->StopPruneDeadConnectionsTimer(); + } else { + self->ConditionallyStopPruneDeadConnectionsTimer(); } #ifdef DEBUG count = ent->mActiveConns.Length(); if (count > 0) { for (PRInt32 i=count-1; i>=0; --i) { nsHttpConnection *conn = ent->mActiveConns[i]; LOG((" active conn [%x] with trans [%x]\n", conn, conn->Transaction())); } } #endif // if this entry is empty, then we can remove it. if (ent->mIdleConns.Length() == 0 && ent->mActiveConns.Length() == 0 && ent->mHalfOpens.Length() == 0 && - ent->mPendingQ.Length() == 0) { + ent->mPendingQ.Length() == 0 && + (!ent->mTestedSpdy || + !gHttpHandler->IsSPDYEnabled() || + self->mCT.Count() > 250)) { LOG((" removing empty connection entry\n")); return PL_DHASH_REMOVE; } // else, use this opportunity to compact our arrays... ent->mIdleConns.Compact(); ent->mActiveConns.Compact(); ent->mPendingQ.Compact(); @@ -552,18 +599,17 @@ nsHttpConnectionMgr::ShutdownPassCB(cons ent->mIdleConns.RemoveElementAt(0); self->mNumIdleConns--; conn->Close(NS_ERROR_ABORT); NS_RELEASE(conn); } // If all idle connections are removed, // we can stop pruning dead connections. - if (0 == self->mNumIdleConns) - self->StopPruneDeadConnectionsTimer(); + self->ConditionallyStopPruneDeadConnectionsTimer(); // close all pending transactions while (ent->mPendingQ.Length()) { trans = ent->mPendingQ[0]; ent->mPendingQ.RemoveElementAt(0); trans->Close(NS_ERROR_ABORT); @@ -721,26 +767,57 @@ void nsHttpConnectionMgr::GetConnection(nsConnectionEntry *ent, nsHttpTransaction *trans, PRBool onlyReusedConnection, nsHttpConnection **result) { LOG(("nsHttpConnectionMgr::GetConnection [ci=%s caps=%x]\n", ent->mConnInfo->HashKey().get(), PRUint32(trans->Caps()))); - // First, see if an idle persistent connection may be reused instead of + // First, see if an existing connection can be used - either an idle + // persistent connection or an active spdy session may be reused instead of // establishing a new socket. We do not need to check the connection limits // yet as they govern the maximum number of open connections and reusing // an old connection never increases that. *result = nsnull; nsHttpConnection *conn = nsnull; + bool addConnToActiveList = true; + // If this is SSL and we haven't tried NPN with this host yet and there is a + // connection already in progress, then lets wait for it to complete to see + // if NPN yields any SPDYness. It would be good to make that information + // persistent. + + if (gHttpHandler->IsSPDYEnabled() && + ent->mConnInfo->UsingSSL() && !ent->mTestedSpdy && + (ent->mHalfOpens.Length() || ent->mActiveConns.Length())) + return; + if (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) { + + // look at the live connection list in order to find a SPDY session + // that this can be added to. + if (!(trans->Caps() & NS_HTTP_DISALLOW_SPDY) && ent->mUsingSpdy) { + for (PRUint32 index = 0; + index < ent->mActiveConns.Length(); + ++index) { + conn = ent->mActiveConns[index]; + if (conn->CanDirectlyActivate()) { + LOG(("using active conn %p for spdy dispatch")); + addConnToActiveList = false; + break; + } + else { + conn = nsnull; + } + } + } + // search the idle connection list. Each element in the list // has a reference, so if we remove it from the list into a local // ptr, that ptr now owns the reference while (!conn && (ent->mIdleConns.Length() > 0)) { conn = ent->mIdleConns[0]; // we check if the connection can be reused before even checking if // it is a "matching" connection. if (!conn->CanReuse()) { @@ -753,18 +830,17 @@ nsHttpConnectionMgr::GetConnection(nsCon conn->EndIdleMonitoring(); } ent->mIdleConns.RemoveElementAt(0); mNumIdleConns--; // If there are no idle connections left at all, we need to make // sure that we are not pruning dead connections anymore. - if (0 == mNumIdleConns) - StopPruneDeadConnectionsTimer(); + ConditionallyStopPruneDeadConnectionsTimer(); } } if (!conn) { // If the onlyReusedConnection parameter is TRUE, then GetConnection() // does not create new transports under any circumstances. if (onlyReusedConnection) @@ -790,21 +866,23 @@ nsHttpConnectionMgr::GetConnection(nsCon } nsresult rv = CreateTransport(ent, trans); if (NS_FAILED(rv)) trans->Close(rv); return; } - // hold an owning ref to this connection - ent->mActiveConns.AppendElement(conn); - mNumActiveConns++; + if (addConnToActiveList) { + // hold an owning ref to this connection + ent->mActiveConns.AppendElement(conn); + mNumActiveConns++; + } + NS_ADDREF(conn); - *result = conn; } void nsHttpConnectionMgr::AddActiveConn(nsHttpConnection *conn, nsConnectionEntry *ent) { NS_ADDREF(conn); @@ -836,39 +914,46 @@ nsHttpConnectionMgr::CreateTransport(nsC sock->SetupBackupTimer(); ent->mHalfOpens.AppendElement(sock); return NS_OK; } nsresult nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent, - nsAHttpTransaction *trans, + nsHttpTransaction *aTrans, PRUint8 caps, nsHttpConnection *conn) { LOG(("nsHttpConnectionMgr::DispatchTransaction [ci=%s trans=%x caps=%x conn=%x]\n", - ent->mConnInfo->HashKey().get(), trans, caps, conn)); + ent->mConnInfo->HashKey().get(), aTrans, caps, conn)); + + PRInt32 priority = aTrans->Priority(); + + if (conn->UsingSpdy()) + return conn->Activate(aTrans, caps, priority); nsConnectionHandle *handle = new nsConnectionHandle(conn); if (!handle) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(handle); nsHttpPipeline *pipeline = nsnull; + nsAHttpTransaction *trans = aTrans; + if (conn->SupportsPipelining() && (caps & NS_HTTP_ALLOW_PIPELINING)) { LOG((" looking to build pipeline...\n")); if (BuildPipeline(ent, trans, &pipeline)) trans = pipeline; } // give the transaction the indirect reference to the connection. trans->SetConnection(handle); - nsresult rv = conn->Activate(trans, caps); + nsresult rv = conn->Activate(trans, caps, priority); if (NS_FAILED(rv)) { LOG((" conn->Activate failed [rv=%x]\n", rv)); ent->mActiveConns.RemoveElement(conn); mNumActiveConns--; // sever back references to connection, and do so without triggering // a call to ReclaimConnection ;-) trans->SetConnection(nsnull); @@ -1095,18 +1180,17 @@ nsHttpConnectionMgr::OnMsgProcessPending void nsHttpConnectionMgr::OnMsgPruneDeadConnections(PRInt32, void *) { LOG(("nsHttpConnectionMgr::OnMsgPruneDeadConnections\n")); // Reset mTimeOfNextWakeUp so that we can find a new shortest value. mTimeOfNextWakeUp = LL_MAXUINT; - if (mNumIdleConns > 0) - mCT.Enumerate(PruneDeadConnectionsCB, this); + mCT.Enumerate(PruneDeadConnectionsCB, this); } void nsHttpConnectionMgr::OnMsgClosePersistentConnections(PRInt32, void *) { LOG(("nsHttpConnectionMgr::OnMsgClosePersistentConnections\n")); mCT.Enumerate(ClosePersistentConnectionsCB, this); @@ -1159,17 +1243,17 @@ nsHttpConnectionMgr::OnMsgReclaimConnect } NS_ADDREF(conn); ent->mIdleConns.InsertElementAt(idx, conn); mNumIdleConns++; conn->BeginIdleMonitoring(); // If the added connection was first idle connection or has shortest - // time to live among the idle connections, pruning dead + // time to live among the watched connections, pruning dead // connections needs to be done when it can't be reused anymore. PRUint32 timeToLive = conn->TimeToLive(); if(!mTimer || NowInSeconds() + timeToLive < mTimeOfNextWakeUp) PruneDeadConnectionsAfter(timeToLive); } else { LOG((" connection cannot be reused; closing connection\n")); // make sure the connection is closed and release our reference. @@ -1232,25 +1316,25 @@ nsHttpConnectionMgr::nsConnectionHandle: nsHttpRequestHead *req, nsHttpResponseHead *resp, PRBool *reset) { return mConn->OnHeadersAvailable(trans, req, resp, reset); } nsresult -nsHttpConnectionMgr::nsConnectionHandle::ResumeSend() +nsHttpConnectionMgr::nsConnectionHandle::ResumeSend(nsAHttpTransaction *caller) { - return mConn->ResumeSend(); + return mConn->ResumeSend(caller); } nsresult -nsHttpConnectionMgr::nsConnectionHandle::ResumeRecv() +nsHttpConnectionMgr::nsConnectionHandle::ResumeRecv(nsAHttpTransaction *caller) { - return mConn->ResumeRecv(); + return mConn->ResumeRecv(caller); } void nsHttpConnectionMgr::nsConnectionHandle::CloseTransaction(nsAHttpTransaction *trans, nsresult reason) { mConn->CloseTransaction(trans, reason); } @@ -1442,16 +1526,17 @@ nsHttpConnectionMgr::nsHalfOpenSocket::S return rv; } void nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupTimer() { PRUint16 timeout = gHttpHandler->GetIdleSynTimeout(); NS_ABORT_IF_FALSE(!mSynTimer, "timer already initd"); + if (timeout) { // Setup the timer that will establish a backup socket // if we do not get a writable event on the main one. // We do this because a lost SYN takes a very long time // to repair at the TCP level. // // Failure to setup the timer is something we can live with, // so don't return an error in that case. diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.h b/netwerk/protocol/http/nsHttpConnectionMgr.h --- a/netwerk/protocol/http/nsHttpConnectionMgr.h +++ b/netwerk/protocol/http/nsHttpConnectionMgr.h @@ -91,18 +91,19 @@ public: //------------------------------------------------------------------------- // NOTE: functions below may be called on any thread. //------------------------------------------------------------------------- // Schedules next pruning of dead connection to happen after // given time. void PruneDeadConnectionsAfter(PRUint32 time); - // Stops timer scheduled for next pruning of dead connections. - void StopPruneDeadConnectionsTimer(); + // Stops timer scheduled for next pruning of dead connections if + // there are no more idle connections or active spdy ones + void ConditionallyStopPruneDeadConnectionsTimer(); // adds a transaction to the list of managed transactions. nsresult AddTransaction(nsHttpTransaction *, PRInt32 priority); // called to reschedule the given transaction. it must already have been // added to the connection manager via AddTransaction. nsresult RescheduleTransaction(nsHttpTransaction *, PRInt32 priority); @@ -142,40 +143,50 @@ public: // preference to the specified connection. nsresult ProcessPendingQ(nsHttpConnectionInfo *); // This is used to force an idle connection to be closed and removed from // the idle connection list. It is called when the idle connection detects // that the network peer has closed the transport. nsresult CloseIdleConnection(nsHttpConnection *); + // The connection manager needs to know when a normal HTTP connection has been + // upgraded to SPDY because the dispatch and idle semantics are a little + // bit different. + void ReportSpdyConnection(nsHttpConnection *, bool usingSpdy); + private: virtual ~nsHttpConnectionMgr(); class nsHalfOpenSocket; // nsConnectionEntry // // mCT maps connection info hash key to nsConnectionEntry object, which // contains list of active and idle connections as well as the list of // pending transactions. // struct nsConnectionEntry { nsConnectionEntry(nsHttpConnectionInfo *ci) - : mConnInfo(ci) + : mConnInfo(ci), + mUsingSpdy(false), + mTestedSpdy(false) { NS_ADDREF(mConnInfo); } ~nsConnectionEntry() { NS_RELEASE(mConnInfo); } nsHttpConnectionInfo *mConnInfo; nsTArray mPendingQ; // pending transaction queue nsTArray mActiveConns; // active connections nsTArray mIdleConns; // idle persistent connections nsTArray mHalfOpens; + + bool mUsingSpdy; + bool mTestedSpdy; }; // nsConnectionHandle // // thin wrapper around a real connection, used to keep track of references // to the connection to determine when the connection may be reused. the // transaction (or pipeline) owns a reference to this handle. this extra // layer of indirection greatly simplifies consumer code, avoiding the @@ -267,17 +278,17 @@ private: static PLDHashOperator PruneDeadConnectionsCB(const nsACString &, nsAutoPtr &, void *); static PLDHashOperator ShutdownPassCB(const nsACString &, nsAutoPtr &, void *); static PLDHashOperator PurgeExcessIdleConnectionsCB(const nsACString &, nsAutoPtr &, void *); static PLDHashOperator ClosePersistentConnectionsCB(const nsACString &, nsAutoPtr &, void *); PRBool ProcessPendingQForEntry(nsConnectionEntry *); PRBool AtActiveConnectionLimit(nsConnectionEntry *, PRUint8 caps); void GetConnection(nsConnectionEntry *, nsHttpTransaction *, PRBool, nsHttpConnection **); - nsresult DispatchTransaction(nsConnectionEntry *, nsAHttpTransaction *, + nsresult DispatchTransaction(nsConnectionEntry *, nsHttpTransaction *, PRUint8 caps, nsHttpConnection *); PRBool BuildPipeline(nsConnectionEntry *, nsAHttpTransaction *, nsHttpPipeline **); nsresult ProcessNewTransaction(nsHttpTransaction *); nsresult EnsureSocketThreadTargetIfOnline(); void ClosePersistentConnections(nsConnectionEntry *ent); nsresult CreateTransport(nsConnectionEntry *, nsHttpTransaction *); void AddActiveConn(nsHttpConnection *, nsConnectionEntry *); void StartedConnect(); diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp --- a/netwerk/protocol/http/nsHttpHandler.cpp +++ b/netwerk/protocol/http/nsHttpHandler.cpp @@ -166,16 +166,17 @@ nsHttpHandler *gHttpHandler = nsnull; nsHttpHandler::nsHttpHandler() : mConnMgr(nsnull) , mHttpVersion(NS_HTTP_VERSION_1_1) , mProxyHttpVersion(NS_HTTP_VERSION_1_1) , mCapabilities(NS_HTTP_ALLOW_KEEPALIVE) , mProxyCapabilities(NS_HTTP_ALLOW_KEEPALIVE) , mReferrerLevel(0xff) // by default we always send a referrer , mIdleTimeout(10) + , mSpdyTimeout(180) , mMaxRequestAttempts(10) , mMaxRequestDelay(10) , mIdleSynTimeout(250) , mMaxConnections(24) , mMaxConnectionsPerServer(8) , mMaxPersistentConnectionsPerServer(2) , mMaxPersistentConnectionsPerProxy(4) , mMaxPipelinedRequests(2) @@ -190,16 +191,17 @@ nsHttpHandler::nsHttpHandler() , mLegacyAppVersion("5.0") , mProduct("Gecko") , mUserAgentIsDirty(PR_TRUE) , mUseCache(PR_TRUE) , mPromptTempRedirect(PR_TRUE) , mSendSecureXSiteReferrer(PR_TRUE) , mEnablePersistentHttpsCaching(PR_FALSE) , mDoNotTrackEnabled(PR_FALSE) + , mEnableSPDY(PR_TRUE) { #if defined(PR_LOGGING) gHttpLog = PR_NewLogModule("nsHttp"); #endif LOG(("Creating nsHttpHandler [this=%x].\n", this)); NS_ASSERTION(!gHttpHandler, "HTTP handler already created!"); @@ -1053,16 +1055,28 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc } if (PREF_CHANGED(HTTP_PREF("phishy-userpass-length"))) { rv = prefs->GetIntPref(HTTP_PREF("phishy-userpass-length"), &val); if (NS_SUCCEEDED(rv)) mPhishyUserPassLength = (PRUint8) NS_CLAMP(val, 0, 0xff); } + if (PREF_CHANGED(HTTP_PREF("spdy.enabled"))) { + rv = prefs->GetBoolPref(HTTP_PREF("spdy.enabled"), &cVar); + if (NS_SUCCEEDED(rv)) + mEnableSPDY = cVar; + } + + if (PREF_CHANGED(HTTP_PREF("spdy.timeout"))) { + rv = prefs->GetIntPref(HTTP_PREF("spdy.timeout"), &val); + if (NS_SUCCEEDED(rv)) + mSpdyTimeout = (PRUint16) NS_CLAMP(val, 1, 0xffff); + } + // // INTL options // if (PREF_CHANGED(INTL_ACCEPT_LANGUAGES)) { nsCOMPtr pls; prefs->GetComplexValue(INTL_ACCEPT_LANGUAGES, NS_GET_IID(nsIPrefLocalizedString), diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h --- a/netwerk/protocol/http/nsHttpHandler.h +++ b/netwerk/protocol/http/nsHttpHandler.h @@ -97,25 +97,28 @@ public: const nsAFlatCString &UserAgent(); nsHttpVersion HttpVersion() { return mHttpVersion; } nsHttpVersion ProxyHttpVersion() { return mProxyHttpVersion; } PRUint8 ReferrerLevel() { return mReferrerLevel; } PRBool SendSecureXSiteReferrer() { return mSendSecureXSiteReferrer; } PRUint8 RedirectionLimit() { return mRedirectionLimit; } PRUint16 IdleTimeout() { return mIdleTimeout; } + PRUint16 SpdyTimeout() { return mSpdyTimeout; } PRUint16 MaxRequestAttempts() { return mMaxRequestAttempts; } const char *DefaultSocketType() { return mDefaultSocketType.get(); /* ok to return null */ } nsIIDNService *IDNConverter() { return mIDNConverter; } PRUint32 PhishyUserPassLength() { return mPhishyUserPassLength; } PRUint8 GetQoSBits() { return mQoSBits; } PRUint16 GetIdleSynTimeout() { return mIdleSynTimeout; } PRBool IsPersistentHttpsCachingEnabled() { return mEnablePersistentHttpsCaching; } + PRBool IsSPDYEnabled() {return mEnableSPDY; } + PRBool PromptTempRedirect() { return mPromptTempRedirect; } nsHttpAuthCache *AuthCache() { return &mAuthCache; } nsHttpConnectionMgr *ConnMgr() { return mConnMgr; } // cache support nsresult GetCacheSession(nsCacheStoragePolicy, nsICacheSession **); PRUint32 GenerateUniqueID() { return ++mLastUniqueID; } @@ -256,16 +259,17 @@ private: PRUint8 mHttpVersion; PRUint8 mProxyHttpVersion; PRUint8 mCapabilities; PRUint8 mProxyCapabilities; PRUint8 mReferrerLevel; PRUint16 mIdleTimeout; + PRUint16 mSpdyTimeout; PRUint16 mMaxRequestAttempts; PRUint16 mMaxRequestDelay; PRUint16 mIdleSynTimeout; PRUint16 mMaxConnections; PRUint8 mMaxConnectionsPerServer; PRUint8 mMaxPersistentConnectionsPerServer; PRUint8 mMaxPersistentConnectionsPerProxy; @@ -323,16 +327,19 @@ private: // if true allow referrer headers between secure non-matching hosts PRPackedBool mSendSecureXSiteReferrer; // Persistent HTTPS caching flag PRPackedBool mEnablePersistentHttpsCaching; // For broadcasting the preference to not be tracked PRPackedBool mDoNotTrackEnabled; + + // Try to use SPDY instead of HTTP/1.1 over SSL + PRPackedBool mEnableSPDY; }; //----------------------------------------------------------------------------- extern nsHttpHandler *gHttpHandler; //----------------------------------------------------------------------------- // nsHttpsHandler - thin wrapper to distinguish the HTTP handler from the diff --git a/netwerk/protocol/http/nsHttpPipeline.cpp b/netwerk/protocol/http/nsHttpPipeline.cpp --- a/netwerk/protocol/http/nsHttpPipeline.cpp +++ b/netwerk/protocol/http/nsHttpPipeline.cpp @@ -120,17 +120,17 @@ nsHttpPipeline::AddTransaction(nsAHttpTr NS_ADDREF(trans); mRequestQ.AppendElement(trans); if (mConnection) { trans->SetConnection(this); if (mRequestQ.Length() == 1) - mConnection->ResumeSend(); + mConnection->ResumeSend(trans); } return NS_OK; } //----------------------------------------------------------------------------- // nsHttpPipeline::nsISupports //----------------------------------------------------------------------------- @@ -159,28 +159,28 @@ nsHttpPipeline::OnHeadersAvailable(nsAHt NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ASSERTION(mConnection, "no connection"); // trans has now received its response headers; forward to the real connection return mConnection->OnHeadersAvailable(trans, requestHead, responseHead, reset); } nsresult -nsHttpPipeline::ResumeSend() +nsHttpPipeline::ResumeSend(nsAHttpTransaction *) { NS_NOTREACHED("nsHttpPipeline::ResumeSend"); return NS_ERROR_UNEXPECTED; } nsresult -nsHttpPipeline::ResumeRecv() +nsHttpPipeline::ResumeRecv(nsAHttpTransaction *trans) { NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ASSERTION(mConnection, "no connection"); - return mConnection->ResumeRecv(); + return mConnection->ResumeRecv(trans); } void nsHttpPipeline::CloseTransaction(nsAHttpTransaction *trans, nsresult reason) { LOG(("nsHttpPipeline::CloseTransaction [this=%x trans=%x reason=%x]\n", this, trans, reason)); @@ -357,16 +357,25 @@ nsHttpPipeline::SetConnection(nsAHttpCon NS_IF_ADDREF(mConnection = conn); PRInt32 i, count = mRequestQ.Length(); for (i=0; iSetConnection(this); } +nsAHttpConnection * +nsHttpPipeline::Connection() +{ + LOG(("nsHttpPipeline::Connection [this=%x conn=%x]\n", this, mConnection)); + + NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + return mConnection; +} + void nsHttpPipeline::GetSecurityCallbacks(nsIInterfaceRequestor **result, nsIEventTarget **target) { NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); // return security callbacks from first request nsAHttpTransaction *trans = Request(0); diff --git a/netwerk/protocol/http/nsHttpTransaction.cpp b/netwerk/protocol/http/nsHttpTransaction.cpp --- a/netwerk/protocol/http/nsHttpTransaction.cpp +++ b/netwerk/protocol/http/nsHttpTransaction.cpp @@ -136,17 +136,17 @@ nsHttpTransaction::nsHttpTransaction() , mPreserveStream(PR_FALSE) { LOG(("Creating nsHttpTransaction @%x\n", this)); } nsHttpTransaction::~nsHttpTransaction() { LOG(("Destroying nsHttpTransaction @%x\n", this)); - + NS_IF_RELEASE(mConnection); NS_IF_RELEASE(mConnInfo); delete mResponseHead; delete mChunkedDecoder; } nsresult @@ -300,16 +300,22 @@ nsHttpTransaction::Init(PRUint8 caps, nsIOService::gDefaultSegmentCount, nsIOService::gBufferCache); if (NS_FAILED(rv)) return rv; NS_ADDREF(*responseBody = mPipeIn); return NS_OK; } +nsAHttpConnection * +nsHttpTransaction::Connection() +{ + return mConnection; +} + nsHttpResponseHead * nsHttpTransaction::TakeResponseHead() { if (!mHaveAllHeaders) { NS_WARNING("response headers not available or incomplete"); return nsnull; } @@ -707,18 +713,19 @@ nsHttpTransaction::Restart() seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); // clear old connection state... mSecurityInfo = 0; NS_IF_RELEASE(mConnection); // disable pipelining for the next attempt in case pipelining caused the // reset. this is being overly cautious since we don't know if pipelining - // was the problem here. + // was the problem here. Same thing with SPDY. mCaps &= ~NS_HTTP_ALLOW_PIPELINING; + mCaps |= NS_HTTP_DISALLOW_SPDY; return gHttpHandler->InitiateTransaction(this, mPriority); } char * nsHttpTransaction::LocateHttpStart(char *buf, PRUint32 len, PRBool aAllowPartialMatch) { @@ -1277,30 +1284,30 @@ NS_IMPL_THREADSAFE_QUERY_INTERFACE2(nsHt // nsHttpTransaction::nsIInputStreamCallback //----------------------------------------------------------------------------- // called on the socket thread NS_IMETHODIMP nsHttpTransaction::OnInputStreamReady(nsIAsyncInputStream *out) { if (mConnection) { - nsresult rv = mConnection->ResumeSend(); + nsresult rv = mConnection->ResumeSend(this); if (NS_FAILED(rv)) NS_ERROR("ResumeSend failed"); } return NS_OK; } //----------------------------------------------------------------------------- // nsHttpTransaction::nsIOutputStreamCallback //----------------------------------------------------------------------------- // called on the socket thread NS_IMETHODIMP nsHttpTransaction::OnOutputStreamReady(nsIAsyncOutputStream *out) { if (mConnection) { - nsresult rv = mConnection->ResumeRecv(); + nsresult rv = mConnection->ResumeRecv(this); if (NS_FAILED(rv)) NS_ERROR("ResumeRecv failed"); } return NS_OK; } diff --git a/netwerk/protocol/http/nsHttpTransaction.h b/netwerk/protocol/http/nsHttpTransaction.h --- a/netwerk/protocol/http/nsHttpTransaction.h +++ b/netwerk/protocol/http/nsHttpTransaction.h @@ -115,28 +115,27 @@ public: // attributes PRUint8 Caps() { return mCaps; } nsHttpConnectionInfo *ConnectionInfo() { return mConnInfo; } nsHttpResponseHead *ResponseHead() { return mHaveAllHeaders ? mResponseHead : nsnull; } nsISupports *SecurityInfo() { return mSecurityInfo; } nsIInterfaceRequestor *Callbacks() { return mCallbacks; } nsIEventTarget *ConsumerTarget() { return mConsumerTarget; } - nsAHttpConnection *Connection() { return mConnection; } // Called to take ownership of the response headers; the transaction // will drop any reference to the response headers after this call. nsHttpResponseHead *TakeResponseHead(); // Called to find out if the transaction generated a complete response. PRBool ResponseIsComplete() { return mResponseIsComplete; } PRBool SSLConnectFailed() { return mSSLConnectFailed; } - // These methods may only be used by the connection manager. + // SetPriority() may only be used by the connection manager. void SetPriority(PRInt32 priority) { mPriority = priority; } PRInt32 Priority() { return mPriority; } const TimingStruct& Timings() const { return mTimings; } private: nsresult Restart(); char *LocateHttpStart(char *buf, PRUint32 len, diff --git a/netwerk/socket/nsISSLSocketControl.idl b/netwerk/socket/nsISSLSocketControl.idl --- a/netwerk/socket/nsISSLSocketControl.idl +++ b/netwerk/socket/nsISSLSocketControl.idl @@ -37,16 +37,24 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsISupports.idl" interface nsIInterfaceRequestor; -[scriptable, uuid(a092097c-8386-4f1b-97b1-90eb70008c2d)] +%{C++ +#include "nsTArray.h" +class nsCString; +%} +[ref] native nsCStringTArrayRef(nsTArray); + +[scriptable, uuid(ad53b03a-119a-4174-9a9a-fbb44dc6b3c8)] interface nsISSLSocketControl : nsISupports { attribute nsIInterfaceRequestor notificationCallbacks; void proxyStartSSL(); void StartTLS(); + [noscript] void setNPNList(in nsCStringTArrayRef aNPNList); + [noscript] void forceHandshake(); }; diff --git a/security/manager/ssl/public/nsISSLStatus.idl b/security/manager/ssl/public/nsISSLStatus.idl --- a/security/manager/ssl/public/nsISSLStatus.idl +++ b/security/manager/ssl/public/nsISSLStatus.idl @@ -36,17 +36,17 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsISupports.idl" interface nsIX509Cert; -[scriptable, uuid(cfede939-def1-49be-81ed-d401b3a07d1c)] +[scriptable, uuid(151c2aa2-193f-45a9-8f6e-19fdf2342b83)] interface nsISSLStatus : nsISupports { readonly attribute nsIX509Cert serverCert; readonly attribute string cipherName; readonly attribute unsigned long keyLength; readonly attribute unsigned long secretKeyLength; readonly attribute boolean isDomainMismatch; @@ -54,9 +54,11 @@ interface nsISSLStatus : nsISupports { /* Note: To distinguish between * "unstrusted because missing or untrusted issuer" * and * "untrusted because self signed" * query nsIX509Cert3::isSelfSigned */ readonly attribute boolean isUntrusted; + + readonly attribute ACString negotiatedNPN; }; diff --git a/security/manager/ssl/src/nsNSSCallbacks.cpp b/security/manager/ssl/src/nsNSSCallbacks.cpp --- a/security/manager/ssl/src/nsNSSCallbacks.cpp +++ b/security/manager/ssl/src/nsNSSCallbacks.cpp @@ -951,16 +951,29 @@ void PR_CALLBACK HandshakeCallback(PRFil } } } status->mHaveKeyLengthAndCipher = PR_TRUE; status->mKeyLength = keyLength; status->mSecretKeyLength = encryptBits; status->mCipherName.Assign(cipherName); + + // Get the NPN value. Do this on the stack and copy it into + // a string rather than preallocating the string because right + // now we expect NPN to fail more often than it succeeds. + int state; + unsigned char npnbuf[256]; + unsigned int npnlen; + + if (SSL_GetNextProto(fd, &state, npnbuf, &npnlen, 256) == SECSuccess && + state == SSL_NEXT_PROTO_NEGOTIATED) { + status->mNegotiatedNPN.Assign(reinterpret_cast(npnbuf), npnlen); + } + status->mHandshakeFinishedNormally = true; } PORT_Free(cipherName); PR_FREEIF(certOrgName); PR_Free(signer); } SECStatus diff --git a/security/manager/ssl/src/nsNSSIOLayer.cpp b/security/manager/ssl/src/nsNSSIOLayer.cpp --- a/security/manager/ssl/src/nsNSSIOLayer.cpp +++ b/security/manager/ssl/src/nsNSSIOLayer.cpp @@ -603,16 +603,36 @@ nsNSSSocketInfo::ProxyStartSSL() } NS_IMETHODIMP nsNSSSocketInfo::StartTLS() { return ActivateSSL(); } +NS_IMETHODIMP +nsNSSSocketInfo::SetNPNList(nsTArray &protocolArray) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + return nsSSLThread::requestSetNextProtoNego(this, protocolArray); +} + +NS_IMETHODIMP +nsNSSSocketInfo::ForceHandshake() +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + return nsSSLThread::requestForceHandshake(this); +} + static NS_DEFINE_CID(kNSSCertificateCID, NS_X509CERT_CID); #define NSSSOCKETINFOMAGIC { 0xa9863a23, 0x26b8, 0x4a9c, \ { 0x83, 0xf1, 0xe9, 0xda, 0xdb, 0x36, 0xb8, 0x30 } } static NS_DEFINE_CID(kNSSSocketInfoMagic, NSSSOCKETINFOMAGIC); NS_IMETHODIMP nsNSSSocketInfo::Write(nsIObjectOutputStream* stream) { stream->WriteID(kNSSSocketInfoMagic); diff --git a/security/manager/ssl/src/nsNSSIOLayer.h b/security/manager/ssl/src/nsNSSIOLayer.h --- a/security/manager/ssl/src/nsNSSIOLayer.h +++ b/security/manager/ssl/src/nsNSSIOLayer.h @@ -78,17 +78,18 @@ public: PRBool ensure_buffer_size(PRInt32 amount); enum ssl_state { ssl_invalid, // used for initializating, should never occur ssl_idle, // not in use by SSL thread, no activity pending ssl_pending_write, // waiting for SSL thread to complete writing ssl_pending_read, // waiting for SSL thread to complete reading ssl_writing_done, // SSL write completed, results are ready - ssl_reading_done // SSL read completed, results are ready + ssl_reading_done, // SSL read completed, results are ready + ssl_force_handshake// SSL handshake required }; ssl_state mSSLState; // Used to transport I/O error codes between SSL thread // and initial caller thread. PRErrorCode mPRErrorCode; diff --git a/security/manager/ssl/src/nsSSLStatus.cpp b/security/manager/ssl/src/nsSSLStatus.cpp --- a/security/manager/ssl/src/nsSSLStatus.cpp +++ b/security/manager/ssl/src/nsSSLStatus.cpp @@ -38,16 +38,17 @@ * ***** END LICENSE BLOCK ***** */ #include "nsSSLStatus.h" #include "plstr.h" #include "nsIClassInfoImpl.h" #include "nsIProgrammingLanguage.h" #include "nsIObjectOutputStream.h" #include "nsIObjectInputStream.h" +#include "nsNetError.h" NS_IMETHODIMP nsSSLStatus::GetServerCert(nsIX509Cert** _result) { NS_ASSERTION(_result, "non-NULL destination required"); *_result = mServerCert; NS_IF_ADDREF(*_result); @@ -117,16 +118,26 @@ nsSSLStatus::GetIsUntrusted(PRBool* _res NS_ASSERTION(_result, "non-NULL destination required"); *_result = mHaveCertErrorBits && mIsUntrusted; return NS_OK; } NS_IMETHODIMP +nsSSLStatus::GetNegotiatedNPN(nsACString & aNegotiatedNPN) +{ + if (!mHandshakeFinishedNormally) + return NS_ERROR_NOT_CONNECTED; + + aNegotiatedNPN = mNegotiatedNPN; + return NS_OK; +} + +NS_IMETHODIMP nsSSLStatus::Read(nsIObjectInputStream* stream) { nsCOMPtr cert; nsresult rv = stream->ReadObject(PR_TRUE, getter_AddRefs(cert)); NS_ENSURE_SUCCESS(rv, rv); mServerCert = do_QueryInterface(cert); if (!mServerCert) @@ -249,16 +260,17 @@ nsSSLStatus::GetClassIDNoAlloc(nsCID *aC nsSSLStatus::nsSSLStatus() : mKeyLength(0), mSecretKeyLength(0) , mIsDomainMismatch(PR_FALSE) , mIsNotValidAtThisTime(PR_FALSE) , mIsUntrusted(PR_FALSE) , mHaveKeyLengthAndCipher(PR_FALSE) , mHaveCertErrorBits(PR_FALSE) +, mHandshakeFinishedNormally(false) { mCipherName = ""; } NS_IMPL_THREADSAFE_ISUPPORTS3(nsSSLStatus, nsISSLStatus, nsISerializable, nsIClassInfo) nsSSLStatus::~nsSSLStatus() { diff --git a/security/manager/ssl/src/nsSSLStatus.h b/security/manager/ssl/src/nsSSLStatus.h --- a/security/manager/ssl/src/nsSSLStatus.h +++ b/security/manager/ssl/src/nsSSLStatus.h @@ -70,16 +70,19 @@ public: nsXPIDLCString mCipherName; PRBool mIsDomainMismatch; PRBool mIsNotValidAtThisTime; PRBool mIsUntrusted; PRBool mHaveKeyLengthAndCipher; PRBool mHaveCertErrorBits; + + nsCString mNegotiatedNPN; + bool mHandshakeFinishedNormally; }; // 2c3837af-8b85-4a68-b0d8-0aed88985b32 #define NS_SSLSTATUS_CID \ { 0x2c3837af, 0x8b85, 0x4a68, \ { 0xb0, 0xd8, 0x0a, 0xed, 0x88, 0x98, 0x5b, 0x32 } } #endif diff --git a/security/manager/ssl/src/nsSSLThread.cpp b/security/manager/ssl/src/nsSSLThread.cpp --- a/security/manager/ssl/src/nsSSLThread.cpp +++ b/security/manager/ssl/src/nsSSLThread.cpp @@ -179,16 +179,17 @@ PRInt32 nsSSLThread::requestRecvMsgPeek( memcpy(buf, si->mThreadData->mSSLRemainingReadResultData, return_amount); return return_amount; } case nsSSLSocketThreadData::ssl_writing_done: case nsSSLSocketThreadData::ssl_pending_write: case nsSSLSocketThreadData::ssl_pending_read: + case nsSSLSocketThreadData::ssl_force_handshake: // for safety reasons, also return would_block on any other state, // although this switch statement should be complete and list // the appropriate behaviour for each state. default: { PORT_SetError(PR_WOULD_BLOCK_ERROR); return -1; @@ -218,16 +219,132 @@ nsresult nsSSLThread::requestActivateSSL return NS_ERROR_FAILURE; if (SECSuccess != SSL_ResetHandshake(fd, PR_FALSE)) return NS_ERROR_FAILURE; return NS_OK; } +nsresult nsSSLThread::requestForceHandshake(nsNSSSocketInfo *si) +{ + if (!ssl_thread_singleton || !si || !ssl_thread_singleton->mThreadHandle) + { + PR_SetError(PR_UNKNOWN_ERROR, 0); + return NS_ERROR_FAILURE; + } + + PRBool some_socket_is_busy = PR_FALSE; + nsSSLSocketThreadData::ssl_state my_ssl_state = nsSSLSocketThreadData::ssl_invalid; + PRFileDesc *blockingFD = nsnull; + + { + MutexAutoLock threadLock(ssl_thread_singleton->mMutex); + + if (ssl_thread_singleton->exitRequested(threadLock)) { + PR_SetError(PR_UNKNOWN_ERROR, 0); + return NS_ERROR_FAILURE; + } + + if (getRealFDIfBlockingSocket_locked(si, blockingFD) == PR_FAILURE) { + return NS_ERROR_FAILURE; + } + + if (blockingFD) + return NS_ERROR_FAILURE; + + my_ssl_state = si->mThreadData->mSSLState; + + if (ssl_thread_singleton->mBusySocket && + ssl_thread_singleton->mBusySocket != si) + { + some_socket_is_busy = PR_TRUE; + } + } + + if (my_ssl_state != nsSSLSocketThreadData::ssl_idle) + { + return NS_BASE_STREAM_WOULD_BLOCK; + } + + if (some_socket_is_busy) + { + return NS_BASE_STREAM_WOULD_BLOCK; + } + + if (si->isPK11LoggedOut() || si->isAlreadyShutDown()) { + PR_SetError(PR_SOCKET_SHUTDOWN_ERROR, 0); + return NS_ERROR_FAILURE; + } + + if (si->GetCanceled()) { + return NS_ERROR_FAILURE; + } + + if (si->mThreadData->mPRErrorCode != PR_SUCCESS && + si->mThreadData->mPRErrorCode != PR_WOULD_BLOCK_ERROR) { + return NS_ERROR_NET_INTERRUPT; + } + + si->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_force_handshake; + + { + MutexAutoLock threadLock(ssl_thread_singleton->mMutex); + + if (nsSSLIOLayerHelpers::mSharedPollableEvent) + { + NS_ASSERTION(!nsSSLIOLayerHelpers::mSocketOwningPollableEvent, + "oops, some other socket still owns our shared pollable event"); + + NS_ASSERTION(!si->mThreadData->mReplacedSSLFileDesc, "oops"); + + si->mThreadData->mReplacedSSLFileDesc = si->mFd->lower; + si->mFd->lower = nsSSLIOLayerHelpers::mSharedPollableEvent; + } + + nsSSLIOLayerHelpers::mSocketOwningPollableEvent = si; + ssl_thread_singleton->mBusySocket = si; + + // notify the thread + ssl_thread_singleton->mCond.NotifyAll(); + } + + return NS_OK; +} + +nsresult nsSSLThread::requestSetNextProtoNego(nsNSSSocketInfo *si, + nsTArray &protocolArray) +{ + PRFileDesc *fd = getRealSSLFD(si); + if (!fd) + return NS_ERROR_FAILURE; + + nsCString npnList; + + // the nss list is a concattenated list of 8 bit byte strings. + + for (PRUint32 index = 0; index < protocolArray.Length(); ++index) { + if (protocolArray[index].IsEmpty() || + protocolArray[index].Length() > 255) + return NS_ERROR_ILLEGAL_VALUE; + + npnList.Append(protocolArray[index].Length()); + npnList.Append(protocolArray[index]); + } + + if (SSL_SetNextProtoNego( + fd, + reinterpret_cast(npnList.get()), + npnList.Length()) != SECSuccess) + return NS_ERROR_FAILURE; + + return NS_OK; + +} + PRInt16 nsSSLThread::requestPoll(nsNSSSocketInfo *si, PRInt16 in_flags, PRInt16 *out_flags) { if (!ssl_thread_singleton || !si || !ssl_thread_singleton->mThreadHandle) return 0; *out_flags = 0; // Socket is unusable - set EXCEPT-flag and return. See bug #480619. @@ -919,16 +1036,18 @@ PRInt32 nsSSLThread::requestWrite(nsNSSS void nsSSLThread::Run(void) { // Helper variable, we don't want to call destroy // while holding the mutex. nsNSSSocketInfo *socketToDestroy = nsnull; while (PR_TRUE) { + bool forceHandshakeDone = false; + if (socketToDestroy) { socketToDestroy->CloseSocketAndDestroy(); socketToDestroy = nsnull; } // remember whether we'll write or read nsSSLSocketThreadData::ssl_state busy_socket_ssl_state; @@ -964,16 +1083,18 @@ void nsSSLThread::Run(void) PRBool pending_work = PR_FALSE; do { if (mBusySocket && (mBusySocket->mThreadData->mSSLState == nsSSLSocketThreadData::ssl_pending_read || + mBusySocket->mThreadData->mSSLState == nsSSLSocketThreadData::ssl_force_handshake + || mBusySocket->mThreadData->mSSLState == nsSSLSocketThreadData::ssl_pending_write)) { pending_work = PR_TRUE; } if (!pending_work) { // no work to do ? let's wait a moment @@ -1086,31 +1207,51 @@ void nsSSLThread::Run(void) // give the error back to caller bstd.mPRErrorCode = PR_GetError(); } bstd.mSSLResultRemainingBytes = bytesRead; bstd.mSSLRemainingReadResultData = bstd.mSSLDataBuffer; busy_socket_ssl_state = nsSSLSocketThreadData::ssl_reading_done; } + else if (nsSSLSocketThreadData::ssl_force_handshake == busy_socket_ssl_state) + { + if (SSL_ForceHandshake(realFileDesc) != PR_SUCCESS) + bstd.mPRErrorCode = PR_GetError(); + + busy_socket_ssl_state = nsSSLSocketThreadData::ssl_idle; + forceHandshakeDone = true; +// fprintf (stderr,"completed ssL_forcehandshake on sslthread rv=%X geterror=%X wb=%X eof=%X\n", +// bstd.mPRErrorCode, PR_GetError(), PR_WOULD_BLOCK_ERROR, PR_END_OF_FILE_ERROR); + } } // avoid setting event repeatedly PRBool needToSetPollableEvent = PR_FALSE; { MutexAutoLock threadLock(ssl_thread_singleton->mMutex); mBusySocket->mThreadData->mSSLState = busy_socket_ssl_state; + + if (forceHandshakeDone) { + mBusySocket->mFd->lower = mBusySocket->mThreadData->mReplacedSSLFileDesc; + mBusySocket->mThreadData->mReplacedSSLFileDesc = nsnull; + nsSSLIOLayerHelpers::mSocketOwningPollableEvent = nsnull; + + nsSSLIOLayerHelpers::mPollableEventCurrentlySet = PR_FALSE; + mBusySocket = nsnull; + } if (!nsSSLIOLayerHelpers::mPollableEventCurrentlySet) { needToSetPollableEvent = PR_TRUE; nsSSLIOLayerHelpers::mPollableEventCurrentlySet = PR_TRUE; } + } if (needToSetPollableEvent && nsSSLIOLayerHelpers::mSharedPollableEvent) { // Wake up the file descriptor on the Necko thread, // so it can fetch the results from the SSL I/O call // that we just completed. PR_SetPollableEvent(nsSSLIOLayerHelpers::mSharedPollableEvent); diff --git a/security/manager/ssl/src/nsSSLThread.h b/security/manager/ssl/src/nsSSLThread.h --- a/security/manager/ssl/src/nsSSLThread.h +++ b/security/manager/ssl/src/nsSSLThread.h @@ -36,19 +36,21 @@ * ***** END LICENSE BLOCK ***** */ #ifndef _NSSSLTHREAD_H_ #define _NSSSLTHREAD_H_ #include "nsCOMPtr.h" #include "nsIRequest.h" #include "nsPSMBackgroundThread.h" +#include "nsTArray.h" class nsNSSSocketInfo; class nsIHttpChannel; +class nsCString; class nsSSLThread : public nsPSMBackgroundThread { private: // We use mMutex contained in our base class // to protect access to these variables: // mBusySocket, mSocketScheduledToBeDestroyed // and to nsSSLSocketThreadData::mSSLState @@ -146,13 +148,18 @@ public: static PRStatus requestSetsocketoption(nsNSSSocketInfo *si, const PRSocketOptionData *data); static PRStatus requestConnectcontinue(nsNSSSocketInfo *si, PRInt16 out_flags); static nsresult requestActivateSSL(nsNSSSocketInfo *si); + + static nsresult requestForceHandshake(nsNSSSocketInfo *si); + static nsresult requestSetNextProtoNego(nsNSSSocketInfo *si, + nsTArray &protocolArray); + static PRBool stoppedOrStopping(); }; #endif //_NSSSLTHREAD_H_ diff --git a/security/nss/cmd/tstclnt/tstclnt.c b/security/nss/cmd/tstclnt/tstclnt.c --- a/security/nss/cmd/tstclnt/tstclnt.c +++ b/security/nss/cmd/tstclnt/tstclnt.c @@ -870,16 +870,22 @@ int main(int argc, char **argv) /* enable compression. */ rv = SSL_OptionSet(s, SSL_ENABLE_DEFLATE, enableCompression); if (rv != SECSuccess) { SECU_PrintError(progName, "error enabling compression"); return 1; } + rv = SSL_SetNextProtoNego(s, "\004flip\004http1.1", 10); + if (rv != SECSuccess) { + SECU_PrintError(progName, "error enabling next protocol negotiation"); + return 1; + } + /* enable false start. */ rv = SSL_OptionSet(s, SSL_ENABLE_FALSE_START, enableFalseStart); if (rv != SECSuccess) { SECU_PrintError(progName, "error enabling false start"); return 1; } SSL_SetPKCS11PinArg(s, &pwdata); diff --git a/security/nss/lib/ssl/ssl.def b/security/nss/lib/ssl/ssl.def --- a/security/nss/lib/ssl/ssl.def +++ b/security/nss/lib/ssl/ssl.def @@ -153,8 +153,15 @@ SSL_SNISocketConfigHook; ;+*; ;+}; ;+NSS_3.12.10 { # NSS 3.12.10 release ;+ global: SSL_ConfigSecureServerWithCertChain; ;+ local: ;+*; ;+}; +;+NSS_CHROMIUM { +;+ global: +SSL_GetNextProto; +SSL_SetNextProtoNego; +;+ local: +;+*; +;+}; diff --git a/security/nss/lib/ssl/ssl.h b/security/nss/lib/ssl/ssl.h --- a/security/nss/lib/ssl/ssl.h +++ b/security/nss/lib/ssl/ssl.h @@ -148,16 +148,28 @@ SSL_IMPORT SECStatus SSL_EnableDefault(i /* New function names */ SSL_IMPORT SECStatus SSL_OptionSet(PRFileDesc *fd, PRInt32 option, PRBool on); SSL_IMPORT SECStatus SSL_OptionGet(PRFileDesc *fd, PRInt32 option, PRBool *on); SSL_IMPORT SECStatus SSL_OptionSetDefault(PRInt32 option, PRBool on); SSL_IMPORT SECStatus SSL_OptionGetDefault(PRInt32 option, PRBool *on); SSL_IMPORT SECStatus SSL_CertDBHandleSet(PRFileDesc *fd, CERTCertDBHandle *dbHandle); +SSL_IMPORT SECStatus SSL_SetNextProtoNego(PRFileDesc *fd, + const unsigned char *data, + unsigned short length); +SSL_IMPORT SECStatus SSL_GetNextProto(PRFileDesc *fd, + int *state, + unsigned char *buf, + unsigned *length, + unsigned buf_len); +#define SSL_NEXT_PROTO_NO_SUPPORT 0 /* No peer support */ +#define SSL_NEXT_PROTO_NEGOTIATED 1 /* Mutual agreement */ +#define SSL_NEXT_PROTO_NO_OVERLAP 2 /* No protocol overlap found */ + /* ** Control ciphers that SSL uses. If on is non-zero then the named cipher ** is enabled, otherwise it is disabled. ** The "cipher" values are defined in sslproto.h (the SSL_EN_* values). ** EnableCipher records user preferences. ** SetPolicy sets the policy according to the policy module. */ #ifdef SSL_DEPRECATED_FUNCTION diff --git a/security/nss/lib/ssl/ssl3con.c b/security/nss/lib/ssl/ssl3con.c --- a/security/nss/lib/ssl/ssl3con.c +++ b/security/nss/lib/ssl/ssl3con.c @@ -76,16 +76,17 @@ static PK11SymKey *ssl3_GenerateRSAPMS(s PK11SlotInfo * serverKeySlot); static SECStatus ssl3_DeriveMasterSecret(sslSocket *ss, PK11SymKey *pms); static SECStatus ssl3_DeriveConnectionKeysPKCS11(sslSocket *ss); static SECStatus ssl3_HandshakeFailure( sslSocket *ss); static SECStatus ssl3_InitState( sslSocket *ss); static SECStatus ssl3_SendCertificate( sslSocket *ss); static SECStatus ssl3_SendEmptyCertificate( sslSocket *ss); static SECStatus ssl3_SendCertificateRequest(sslSocket *ss); +static SECStatus ssl3_SendNextProto( sslSocket *ss); static SECStatus ssl3_SendFinished( sslSocket *ss, PRInt32 flags); static SECStatus ssl3_SendServerHello( sslSocket *ss); static SECStatus ssl3_SendServerHelloDone( sslSocket *ss); static SECStatus ssl3_SendServerKeyExchange( sslSocket *ss); static SECStatus ssl3_NewHandshakeHashes( sslSocket *ss); static SECStatus ssl3_UpdateHandshakeHashes( sslSocket *ss, unsigned char *b, unsigned int l); @@ -5735,16 +5736,22 @@ ssl3_HandleServerHelloDone(sslSocket *ss if (rv != SECSuccess) { goto loser; /* err is set. */ } } rv = ssl3_SendChangeCipherSpecs(ss); if (rv != SECSuccess) { goto loser; /* err code was set. */ } + + rv = ssl3_SendNextProto(ss); + if (rv != SECSuccess) { + goto loser; /* err code was set. */ + } + rv = ssl3_SendFinished(ss, 0); if (rv != SECSuccess) { goto loser; /* err code was set. */ } ssl_ReleaseXmitBufLock(ss); /*******************************/ if (ssl3_ExtensionNegotiated(ss, ssl_session_ticket_xtn)) @@ -8162,16 +8169,50 @@ ssl3_ComputeTLSFinished(ssl3CipherSpec * rv = TLS_PRF(&spec->msItem, label, &inData, &outData, isFIPS); PORT_Assert(rv != SECSuccess || \ outData.len == sizeof tlsFinished->verify_data); } return rv; } /* called from ssl3_HandleServerHelloDone + */ +static SECStatus +ssl3_SendNextProto(sslSocket *ss) +{ + SECStatus rv; + int padding_len; + static const unsigned char padding[32] = {0}; + + if (ss->ssl3.nextProtoState == SSL_NEXT_PROTO_NO_SUPPORT) + return SECSuccess; + + PORT_Assert( ss->opt.noLocks || ssl_HaveXmitBufLock(ss)); + PORT_Assert( ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss)); + + padding_len = 32 - ((ss->ssl3.nextProto.len + 2) % 32); + + rv = ssl3_AppendHandshakeHeader(ss, next_proto, ss->ssl3.nextProto.len + + 2 + padding_len); + if (rv != SECSuccess) { + return rv; /* error code set by AppendHandshakeHeader */ + } + rv = ssl3_AppendHandshakeVariable(ss, ss->ssl3.nextProto.data, + ss->ssl3.nextProto.len, 1); + if (rv != SECSuccess) { + return rv; /* error code set by AppendHandshake */ + } + rv = ssl3_AppendHandshakeVariable(ss, padding, padding_len, 1); + if (rv != SECSuccess) { + return rv; /* error code set by AppendHandshake */ + } + return rv; +} + +/* called from ssl3_HandleServerHelloDone * ssl3_HandleClientHello * ssl3_HandleFinished */ static SECStatus ssl3_SendFinished(sslSocket *ss, PRInt32 flags) { ssl3CipherSpec *cwSpec; PRBool isTLS; @@ -8414,16 +8455,24 @@ ssl3_HandleFinished(sslSocket *ss, SSL3O ** or if it is going to step up, ** then set the ssl_SEND_FLAG_FORCE_INTO_BUFFER flag, so that the ** last two handshake messages (change cipher spec and finished) ** will be sent in the same send/write call as the application data. */ if (doStepUp || ss->writerThread == PR_GetCurrentThread()) { flags = ssl_SEND_FLAG_FORCE_INTO_BUFFER; } + + if (!isServer) { + rv = ssl3_SendNextProto(ss); + if (rv != SECSuccess) { + goto xmit_loser; /* err code was set. */ + } + } + rv = ssl3_SendFinished(ss, flags); if (rv != SECSuccess) { goto xmit_loser; /* err is set. */ } } /* Optimization: don't cache this connection if we're going to step up. */ if (doStepUp) { @@ -9481,11 +9530,16 @@ ssl3_DestroySSL3Info(sslSocket *ss) /* free the SSL3Buffer (msg_body) */ PORT_Free(ss->ssl3.hs.msg_body.buf); /* free up the CipherSpecs */ ssl3_DestroyCipherSpec(&ss->ssl3.specs[0], PR_TRUE/*freeSrvName*/); ssl3_DestroyCipherSpec(&ss->ssl3.specs[1], PR_TRUE/*freeSrvName*/); ss->ssl3.initialized = PR_FALSE; + + if (ss->ssl3.nextProto.data) { + PORT_Free(ss->ssl3.nextProto.data); + ss->ssl3.nextProto.data = NULL; + } } /* End of ssl3con.c */ diff --git a/security/nss/lib/ssl/ssl3ext.c b/security/nss/lib/ssl/ssl3ext.c --- a/security/nss/lib/ssl/ssl3ext.c +++ b/security/nss/lib/ssl/ssl3ext.c @@ -230,26 +230,28 @@ ssl3_GetSessionTicketKeys(const unsigned static const ssl3HelloExtensionHandler clientHelloHandlers[] = { { ssl_server_name_xtn, &ssl3_HandleServerNameXtn }, #ifdef NSS_ENABLE_ECC { ssl_elliptic_curves_xtn, &ssl3_HandleSupportedCurvesXtn }, { ssl_ec_point_formats_xtn, &ssl3_HandleSupportedPointFormatsXtn }, #endif { ssl_session_ticket_xtn, &ssl3_ServerHandleSessionTicketXtn }, { ssl_renegotiation_info_xtn, &ssl3_HandleRenegotiationInfoXtn }, + { ssl_next_proto_neg_xtn, &ssl3_ServerHandleNextProtoNegoXtn }, { -1, NULL } }; /* These two tables are used by the client, to handle server hello * extensions. */ static const ssl3HelloExtensionHandler serverHelloHandlersTLS[] = { { ssl_server_name_xtn, &ssl3_HandleServerNameXtn }, /* TODO: add a handler for ssl_ec_point_formats_xtn */ { ssl_session_ticket_xtn, &ssl3_ClientHandleSessionTicketXtn }, { ssl_renegotiation_info_xtn, &ssl3_HandleRenegotiationInfoXtn }, + { ssl_next_proto_neg_xtn, &ssl3_ClientHandleNextProtoNegoXtn }, { -1, NULL } }; static const ssl3HelloExtensionHandler serverHelloHandlersSSL3[] = { { ssl_renegotiation_info_xtn, &ssl3_HandleRenegotiationInfoXtn }, { -1, NULL } }; @@ -262,17 +264,18 @@ static const ssl3HelloExtensionHandler s static const ssl3HelloExtensionSender clientHelloSendersTLS[SSL_MAX_EXTENSIONS] = { { ssl_server_name_xtn, &ssl3_SendServerNameXtn }, { ssl_renegotiation_info_xtn, &ssl3_SendRenegotiationInfoXtn }, #ifdef NSS_ENABLE_ECC { ssl_elliptic_curves_xtn, &ssl3_SendSupportedCurvesXtn }, { ssl_ec_point_formats_xtn, &ssl3_SendSupportedPointFormatsXtn }, #endif - { ssl_session_ticket_xtn, &ssl3_SendSessionTicketXtn } + { ssl_session_ticket_xtn, &ssl3_SendSessionTicketXtn }, + { ssl_next_proto_neg_xtn, &ssl3_ClientSendNextProtoNegoXtn } /* any extra entries will appear as { 0, NULL } */ }; static const ssl3HelloExtensionSender clientHelloSendersSSL3[SSL_MAX_EXTENSIONS] = { { ssl_renegotiation_info_xtn, &ssl3_SendRenegotiationInfoXtn } /* any extra entries will appear as { 0, NULL } */ }; @@ -529,16 +532,133 @@ ssl3_SendSessionTicketXtn( } return extension_length; loser: ss->xtnData.ticketTimestampVerified = PR_FALSE; return -1; } +/* handle an incoming Next Protocol Negotiation extension. */ +SECStatus +ssl3_ServerHandleNextProtoNegoXtn(sslSocket * ss, PRUint16 ex_type, SECItem *data) +{ + if (data->len != 0) { + /* Clients MUST send an empty NPN extension, if any. */ + return SECFailure; + } + + ss->ssl3.hs.nextProtoNego = PR_TRUE; + return SECSuccess; +} + +/* ssl3_ValidateNextProtoNego checks that the given block of data is valid: none + * of the length may be 0 and the sum of the lengths must equal the length of + * the block. */ +SECStatus +ssl3_ValidateNextProtoNego(const unsigned char* data, unsigned short length) +{ + unsigned int offset = 0; + + while (offset < length) { + if (data[offset] == 0) { + return SECFailure; + } + offset += (unsigned int)data[offset] + 1; + } + + if (offset > length) + return SECFailure; + + return SECSuccess; +} + +SECStatus +ssl3_ClientHandleNextProtoNegoXtn(sslSocket *ss, PRUint16 ex_type, + SECItem *data) +{ + unsigned int i, j; + SECStatus rv; + unsigned char *result; + + if (data->len == 0) { + /* The server supports the extension, but doesn't have any + * protocols configured. In this case we request our favoured + * protocol. */ + goto pick_first; + } + + rv = ssl3_ValidateNextProtoNego(data->data, data->len); + if (rv != SECSuccess) + return rv; + + /* For each protocol in server preference order, see if we support it. */ + for (i = 0; i < data->len; ) { + for (j = 0; j < ss->opt.nextProtoNego.len; ) { + if (data->data[i] == ss->opt.nextProtoNego.data[j] && + memcmp(&data->data[i+1], &ss->opt.nextProtoNego.data[j+1], + data->data[i]) == 0) { + /* We found a match */ + ss->ssl3.nextProtoState = SSL_NEXT_PROTO_NEGOTIATED; + result = &data->data[i]; + goto found; + } + j += (unsigned int)ss->opt.nextProtoNego.data[j] + 1; + } + + i += (unsigned int)data->data[i] + 1; + } + + pick_first: + ss->ssl3.nextProtoState = SSL_NEXT_PROTO_NO_OVERLAP; + result = ss->opt.nextProtoNego.data; + + found: + if (ss->ssl3.nextProto.data) + PORT_Free(ss->ssl3.nextProto.data); + ss->ssl3.nextProto.data = PORT_Alloc(result[0]); + PORT_Memcpy(ss->ssl3.nextProto.data, result + 1, result[0]); + ss->ssl3.nextProto.len = result[0]; + return SECSuccess; +} + +PRInt32 +ssl3_ClientSendNextProtoNegoXtn(sslSocket * ss, + PRBool append, + PRUint32 maxBytes) +{ + PRInt32 extension_length; + + /* Renegotiations do not send this extension. */ + if (ss->opt.nextProtoNego.len == 0 || ss->firstHsDone) { + return 0; + } + + extension_length = 4; + + if (append && maxBytes >= extension_length) { + SECStatus rv; + rv = ssl3_AppendHandshakeNumber(ss, ssl_next_proto_neg_xtn, 2); + if (rv != SECSuccess) + goto loser; + rv = ssl3_AppendHandshakeNumber(ss, 0, 2); + if (rv != SECSuccess) + goto loser; + ss->xtnData.advertised[ss->xtnData.numAdvertised++] = + ssl_next_proto_neg_xtn; + } else if (maxBytes < extension_length) { + return 0; + } + + return extension_length; + + loser: + return -1; +} + /* * NewSessionTicket * Called from ssl3_HandleFinished */ SECStatus ssl3_SendNewSessionTicket(sslSocket *ss) { int i; diff --git a/security/nss/lib/ssl/ssl3prot.h b/security/nss/lib/ssl/ssl3prot.h --- a/security/nss/lib/ssl/ssl3prot.h +++ b/security/nss/lib/ssl/ssl3prot.h @@ -152,17 +152,18 @@ typedef enum { server_hello = 2, new_session_ticket = 4, certificate = 11, server_key_exchange = 12, certificate_request = 13, server_hello_done = 14, certificate_verify = 15, client_key_exchange = 16, - finished = 20 + finished = 20, + next_proto = 67 } SSL3HandshakeType; typedef struct { uint8 empty; } SSL3HelloRequest; typedef struct { SSL3Opaque rand[SSL3_RANDOM_LENGTH]; diff --git a/security/nss/lib/ssl/sslimpl.h b/security/nss/lib/ssl/sslimpl.h --- a/security/nss/lib/ssl/sslimpl.h +++ b/security/nss/lib/ssl/sslimpl.h @@ -308,16 +308,21 @@ typedef struct { #ifdef NSS_ENABLE_ECC #define ssl_V3_SUITES_IMPLEMENTED 50 #else #define ssl_V3_SUITES_IMPLEMENTED 30 #endif /* NSS_ENABLE_ECC */ typedef struct sslOptionsStr { + /* For clients, this is a validated list of protocols in preference order + * and wire format. For servers, this is the list of support protocols, + * also in wire format. */ + SECItem nextProtoNego; + unsigned int useSecurity : 1; /* 1 */ unsigned int useSocks : 1; /* 2 */ unsigned int requestCertificate : 1; /* 3 */ unsigned int requireCertificate : 2; /* 4-5 */ unsigned int handshakeAsClient : 1; /* 6 */ unsigned int handshakeAsServer : 1; /* 7 */ unsigned int enableSSL2 : 1; /* 8 */ unsigned int enableSSL3 : 1; /* 9 */ @@ -781,16 +786,17 @@ const ssl3CipherSuiteDef *suite_def; union { TLSFinished tFinished[2]; /* client, then server */ SSL3Hashes sFinished[2]; SSL3Opaque data[72]; } finishedMsgs; #ifdef NSS_ENABLE_ECC PRUint32 negotiatedECCurves; /* bit mask */ #endif /* NSS_ENABLE_ECC */ + PRBool nextProtoNego;/* Our peer has sent this extension */ } SSL3HandshakeState; /* ** This is the "ssl3" struct, as in "ss->ssl3". ** note: ** usually, crSpec == cwSpec and prSpec == pwSpec. @@ -822,16 +828,26 @@ struct ssl3StateStr { /* These are used to keep track of the peer CA */ void * peerCertChain; /* chain while we are trying to validate it. */ CERTDistNames * ca_list; /* used by server. trusted CAs for this socket. */ PRBool initialized; SSL3HandshakeState hs; ssl3CipherSpec specs[2]; /* one is current, one is pending. */ + + /* In a client: if the server supports Next Protocol Negotiation, then + * this is the protocol that was requested. + * In a server: this is the protocol that the client requested via Next + * Protocol Negotiation. + * + * In either case, if the data pointer is non-NULL, then it is malloced + * data. */ + SECItem nextProto; + int nextProtoState; /* See SSL_NEXT_PROTO_* defines */ }; typedef struct { SSL3ContentType type; SSL3ProtocolVersion version; sslBuffer * buf; } SSL3Ciphertext; @@ -1489,18 +1505,22 @@ extern SECStatus ssl3_CacheWrappedMaster extern SECStatus ssl3_HandleServerNameXtn(sslSocket * ss, PRUint16 ex_type, SECItem *data); extern SECStatus ssl3_HandleSupportedCurvesXtn(sslSocket * ss, PRUint16 ex_type, SECItem *data); extern SECStatus ssl3_HandleSupportedPointFormatsXtn(sslSocket * ss, PRUint16 ex_type, SECItem *data); extern SECStatus ssl3_ClientHandleSessionTicketXtn(sslSocket *ss, PRUint16 ex_type, SECItem *data); +extern SECStatus ssl3_ClientHandleNextProtoNegoXtn(sslSocket *ss, + PRUint16 ex_type, SECItem *data); extern SECStatus ssl3_ServerHandleSessionTicketXtn(sslSocket *ss, PRUint16 ex_type, SECItem *data); +extern SECStatus ssl3_ServerHandleNextProtoNegoXtn(sslSocket *ss, + PRUint16 ex_type, SECItem *data); /* ClientHello and ServerHello extension senders. * Note that not all extension senders are exposed here; only those that * that need exposure. */ extern PRInt32 ssl3_SendSessionTicketXtn(sslSocket *ss, PRBool append, PRUint32 maxBytes); @@ -1521,16 +1541,20 @@ extern SECStatus ssl_ConfigSecureServer( extern SSLKEAType ssl_FindCertKEAType(CERTCertificate * cert); #ifdef NSS_ENABLE_ECC extern PRInt32 ssl3_SendSupportedCurvesXtn(sslSocket *ss, PRBool append, PRUint32 maxBytes); extern PRInt32 ssl3_SendSupportedPointFormatsXtn(sslSocket *ss, PRBool append, PRUint32 maxBytes); #endif +extern PRInt32 ssl3_ClientSendNextProtoNegoXtn(sslSocket *ss, PRBool append, + PRUint32 maxBytes); +extern SECStatus ssl3_ValidateNextProtoNego(const unsigned char* data, + unsigned short length); /* call the registered extension handlers. */ extern SECStatus ssl3_HandleHelloExtensions(sslSocket *ss, SSL3Opaque **b, PRUint32 *length); /* Hello Extension related routines. */ extern PRBool ssl3_ExtensionNegotiated(sslSocket *ss, PRUint16 ex_type); extern SECStatus ssl3_SetSIDSessionTicket(sslSessionID *sid, diff --git a/security/nss/lib/ssl/sslsock.c b/security/nss/lib/ssl/sslsock.c --- a/security/nss/lib/ssl/sslsock.c +++ b/security/nss/lib/ssl/sslsock.c @@ -158,16 +158,17 @@ static const sslSocketOps ssl_secure_ops ssl_DefGetpeername, ssl_DefGetsockname }; /* ** default settings for socket enables */ static sslOptions ssl_defaults = { + { siBuffer, NULL, 0 }, /* nextProtoNego */ PR_TRUE, /* useSecurity */ PR_FALSE, /* useSocks */ PR_FALSE, /* requestCertificate */ 2, /* requireCertificate */ PR_FALSE, /* handshakeAsClient */ PR_FALSE, /* handshakeAsServer */ PR_TRUE, /* enableSSL2 */ PR_TRUE, /* enableSSL3 */ @@ -434,16 +435,20 @@ ssl_DestroySocketContents(sslSocket *ss) if (ss->stepDownKeyPair) { ssl3_FreeKeyPair(ss->stepDownKeyPair); ss->stepDownKeyPair = NULL; } if (ss->ephemeralECDHKeyPair) { ssl3_FreeKeyPair(ss->ephemeralECDHKeyPair); ss->ephemeralECDHKeyPair = NULL; } + if (ss->opt.nextProtoNego.data) { + PORT_Free(ss->opt.nextProtoNego.data); + ss->opt.nextProtoNego.data = NULL; + } PORT_Assert(!ss->xtnData.sniNameArr); if (ss->xtnData.sniNameArr) { PORT_Free(ss->xtnData.sniNameArr); ss->xtnData.sniNameArr = NULL; } } /* @@ -1266,16 +1271,85 @@ SSL_ImportFD(PRFileDesc *model, PRFileDe #endif ns = ssl_FindSocket(fd); PORT_Assert(ns); if (ns) ns->TCPconnected = (PR_SUCCESS == ssl_DefGetpeername(ns, &addr)); return fd; } +/* SSL_SetNextProtoNego sets the list of supported protocols for the given + * socket. The list is a series of 8-bit, length prefixed strings. */ +SECStatus +SSL_SetNextProtoNego(PRFileDesc *fd, const unsigned char *data, + unsigned short length) +{ + sslSocket *ss = ssl_FindSocket(fd); + + if (!ss) { + SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetNextProtoNego", SSL_GETPID(), + fd)); + return SECFailure; + } + + if (ssl3_ValidateNextProtoNego(data, length) != SECSuccess) + return SECFailure; + + ssl_GetSSL3HandshakeLock(ss); + if (ss->opt.nextProtoNego.data) + PORT_Free(ss->opt.nextProtoNego.data); + ss->opt.nextProtoNego.data = PORT_Alloc(length); + if (!ss->opt.nextProtoNego.data) { + ssl_ReleaseSSL3HandshakeLock(ss); + return SECFailure; + } + memcpy(ss->opt.nextProtoNego.data, data, length); + ss->opt.nextProtoNego.len = length; + ss->opt.nextProtoNego.type = siBuffer; + ssl_ReleaseSSL3HandshakeLock(ss); + + return SECSuccess; +} + +/* SSL_GetNextProto reads the resulting Next Protocol Negotiation result for + * the given socket. It's only valid to call this once the handshake has + * completed. + * + * state is set to one of the SSL_NEXT_PROTO_* constants. The negotiated + * protocol, if any, is written into buf, which must be at least buf_len + * bytes long. If the negotiated protocol is longer than this, it is truncated. + * The number of bytes copied is written into length. + */ +SECStatus +SSL_GetNextProto(PRFileDesc *fd, int *state, unsigned char *buf, + unsigned int *length, unsigned int buf_len) +{ + sslSocket *ss = ssl_FindSocket(fd); + + if (!ss) { + SSL_DBG(("%d: SSL[%d]: bad socket in SSL_GetNextProto", SSL_GETPID(), + fd)); + return SECFailure; + } + + *state = ss->ssl3.nextProtoState; + + if (ss->ssl3.nextProtoState != SSL_NEXT_PROTO_NO_SUPPORT && + ss->ssl3.nextProto.data) { + *length = ss->ssl3.nextProto.len; + if (*length > buf_len) + *length = buf_len; + PORT_Memcpy(buf, ss->ssl3.nextProto.data, *length); + } else { + *length = 0; + } + + return SECSuccess; +} + PRFileDesc * SSL_ReconfigFD(PRFileDesc *model, PRFileDesc *fd) { sslSocket * sm = NULL, *ss = NULL; int i; sslServerCerts * mc = NULL; sslServerCerts * sc = NULL; diff --git a/security/nss/lib/ssl/sslt.h b/security/nss/lib/ssl/sslt.h --- a/security/nss/lib/ssl/sslt.h +++ b/security/nss/lib/ssl/sslt.h @@ -198,14 +198,15 @@ typedef enum { /* Update SSL_MAX_EXTENSIONS whenever a new extension type is added. */ typedef enum { ssl_server_name_xtn = 0, #ifdef NSS_ENABLE_ECC ssl_elliptic_curves_xtn = 10, ssl_ec_point_formats_xtn = 11, #endif ssl_session_ticket_xtn = 35, + ssl_next_proto_neg_xtn = 13172, ssl_renegotiation_info_xtn = 0xff01 /* experimental number */ } SSLExtensionType; -#define SSL_MAX_EXTENSIONS 5 +#define SSL_MAX_EXTENSIONS 6 #endif /* __sslt_h_ */ diff --git a/toolkit/components/telemetry/TelemetryHistograms.h b/toolkit/components/telemetry/TelemetryHistograms.h --- a/toolkit/components/telemetry/TelemetryHistograms.h +++ b/toolkit/components/telemetry/TelemetryHistograms.h @@ -119,16 +119,34 @@ HISTOGRAM(HTTP_REQUEST_PER_PAGE_FROM_CAC _HTTP_HIST(HTTP_##prefix##_REVALIDATION, labelprefix "Positive cache validation time (ms)") \ _HTTP_HIST(HTTP_##prefix##_COMPLETE_LOAD, labelprefix "Overall load time - all (ms)") \ _HTTP_HIST(HTTP_##prefix##_COMPLETE_LOAD_CACHED, labelprefix "Overall load time - cache hits (ms)") \ _HTTP_HIST(HTTP_##prefix##_COMPLETE_LOAD_NET, labelprefix "Overall load time - network (ms)") \ HTTP_HISTOGRAMS(PAGE, "page: ") HTTP_HISTOGRAMS(SUB, "subitem: ") +HISTOGRAM(SPDY_PARALLEL_STREAMS, 1, 1000, 50, EXPONENTIAL, "SPDY: Streams concurrent active per connection") +HISTOGRAM(SPDY_TOTAL_STREAMS, 1, 100000, 50, EXPONENTIAL, "SPDY: Streams created per connection") +HISTOGRAM(SPDY_SERVER_INITIATED_STREAMS, 1, 100000, 250, EXPONENTIAL, "SPDY: Streams recevied per connection") +HISTOGRAM(SPDY_CHUNK_RECVD, 1, 1000, 100, EXPONENTIAL, "SPDY: Recvd Chunk Size (rounded to KB)") +HISTOGRAM(SPDY_SYN_SIZE, 20, 20000, 50, EXPONENTIAL, "SPDY: SYN Frame Header Size") +HISTOGRAM(SPDY_SYN_RATIO, 1, 99, 20, LINEAR, "SPDY: SYN Frame Header Ratio (lower better)") +HISTOGRAM(SPDY_SYN_REPLY_SIZE, 16, 20000, 50, EXPONENTIAL, "SPDY: SYN Reply Header Size") +HISTOGRAM(SPDY_SYN_REPLY_RATIO, 1, 99, 20, LINEAR, "SPDY: SYN Reply Header Ratio (lower better)") +HISTOGRAM(SPDY_NPN_CONNECT, 0, 1, 2, BOOLEAN, "SPDY: NPN Negotiated") + +HISTOGRAM(SPDY_SETTINGS_UL_BW, 1, 10000, 100, EXPONENTIAL, "SPDY: Settings Upload Bandwidth") +HISTOGRAM(SPDY_SETTINGS_DL_BW, 1, 10000, 100, EXPONENTIAL, "SPDY: Settings Download Bandwidth") +HISTOGRAM(SPDY_SETTINGS_RTT, 1, 1000, 100, EXPONENTIAL, "SPDY: Settings RTT") +HISTOGRAM(SPDY_SETTINGS_MAX_STREAMS, 1, 5000, 100, EXPONENTIAL, "SPDY: Settings Max Streams parameter") +HISTOGRAM(SPDY_SETTINGS_CWND, 1, 500, 50, EXPONENTIAL, "SPDY: Settings CWND (packets)") +HISTOGRAM(SPDY_SETTINGS_RETRANS, 1, 100, 50, EXPONENTIAL, "SPDY: Retransmission Rate") +HISTOGRAM(SPDY_SETTINGS_IW, 1, 1000, 50, EXPONENTIAL, "SPDY: Settings IW (rounded to KB)") + #undef _HTTP_HIST #undef HTTP_HISTOGRAMS HISTOGRAM(FIND_PLUGINS, 1, 3000, 10, EXPONENTIAL, "Time spent scanning filesystem for plugins (ms)") HISTOGRAM(CHECK_JAVA_ENABLED, 1, 3000, 10, EXPONENTIAL, "Time spent checking if Java is enabled (ms)") /* Define 2 histograms: MOZ_SQLITE_(NAME)_MS and * MOZ_SQLITE_(NAME)_MAIN_THREAD_MS. These are meant to be used by * IOThreadAutoTimer which relies on _MAIN_THREAD_MS histogram being