diff --git a/netwerk/protocol/http/ASpdySession.cpp b/netwerk/protocol/http/ASpdySession.cpp index 6c8fed403..1962d9b3e 100644 --- a/netwerk/protocol/http/ASpdySession.cpp +++ b/netwerk/protocol/http/ASpdySession.cpp @@ -36,7 +36,8 @@ ASpdySession::~ASpdySession() ASpdySession * ASpdySession::NewSpdySession(uint32_t version, - nsISocketTransport *aTransport) + nsISocketTransport *aTransport, + bool attemptingEarlyData) { // This is a necko only interface, so we can enforce version // requests as a precondition @@ -49,12 +50,12 @@ ASpdySession::NewSpdySession(uint32_t version, // from a list provided in the SERVER HELLO filtered by our acceptable // versions, so there is no risk of the server ignoring our prefs. - Telemetry::Accumulate(Telemetry::SPDY_VERSION2, version); + //Telemetry::Accumulate(Telemetry::SPDY_VERSION2, version); if (version == SPDY_VERSION_31) { return new SpdySession31(aTransport); } else if (version == HTTP_VERSION_2) { - return new Http2Session(aTransport, version); + return new Http2Session(aTransport, version, attemptingEarlyData); } return nullptr; diff --git a/netwerk/protocol/http/ASpdySession.h b/netwerk/protocol/http/ASpdySession.h index b8d9899a6..f5cc47d83 100644 --- a/netwerk/protocol/http/ASpdySession.h +++ b/netwerk/protocol/http/ASpdySession.h @@ -28,8 +28,9 @@ public: virtual PRIntervalTime IdleTime() = 0; virtual uint32_t ReadTimeoutTick(PRIntervalTime now) = 0; virtual void DontReuse() = 0; + virtual uint32_t SpdyVersion() = 0; - static ASpdySession *NewSpdySession(uint32_t version, nsISocketTransport *); + static ASpdySession *NewSpdySession(uint32_t version, nsISocketTransport *, bool); // MaybeReTunnel() is called by the connection manager when it cannot // dispatch a tunneled transaction. That might be because the tunnels it diff --git a/netwerk/protocol/http/Http2Session.cpp b/netwerk/protocol/http/Http2Session.cpp index 173593e2a..163eb371c 100644 --- a/netwerk/protocol/http/Http2Session.cpp +++ b/netwerk/protocol/http/Http2Session.cpp @@ -63,7 +63,7 @@ do { \ return NS_ERROR_ILLEGAL_VALUE; \ } while (0) -Http2Session::Http2Session(nsISocketTransport *aSocketTransport, uint32_t version) +Http2Session::Http2Session(nsISocketTransport *aSocketTransport, uint32_t version, bool attemptingEarlyData) : mSocketTransport(aSocketTransport) , mSegmentReader(nullptr) , mSegmentWriter(nullptr) @@ -110,6 +110,7 @@ Http2Session::Http2Session(nsISocketTransport *aSocketTransport, uint32_t versio , mWaitingForSettingsAck(false) , mGoAwayOnPush(false) , mUseH2Deps(false) + , mAttemptingEarlyData(attemptingEarlyData) { MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); @@ -510,6 +511,12 @@ Http2Session::SetWriteCallbacks() void Http2Session::RealignOutputQueue() { + if (mAttemptingEarlyData) { + // We can't realign right now, because we may need what's in there if early + // data fails. + return; + } + mOutputQueueUsed -= mOutputQueueSent; memmove(mOutputQueueBuffer.get(), mOutputQueueBuffer.get() + mOutputQueueSent, @@ -527,6 +534,14 @@ Http2Session::FlushOutputQueue() uint32_t countRead; uint32_t avail = mOutputQueueUsed - mOutputQueueSent; + if (!avail && mAttemptingEarlyData) { + // This is kind of a hack, but there are cases where we'll have already + // written the data we want whlie doing early data, but we get called again + // with a reader, and we need to avoid calling the reader when there's + // nothing for it to read. + return; + } + rv = mSegmentReader-> OnReadSegment(mOutputQueueBuffer.get() + mOutputQueueSent, avail, &countRead); @@ -537,14 +552,18 @@ Http2Session::FlushOutputQueue() if (NS_FAILED(rv)) return; + mOutputQueueSent += countRead; + + if (mAttemptingEarlyData) { + return; + } + if (countRead == avail) { mOutputQueueUsed = 0; mOutputQueueSent = 0; return; } - mOutputQueueSent += countRead; - // If the output queue is close to filling up and we have sent out a good // chunk of data from the beginning then realign it. @@ -562,6 +581,12 @@ Http2Session::DontReuse() Close(NS_OK); } +uint32_t +Http2Session::SpdyVersion() +{ + return HTTP_VERSION_2; +} + uint32_t Http2Session::GetWriteQueueSize() { @@ -2316,7 +2341,36 @@ Http2Session::ReadSegmentsAgain(nsAHttpSegmentReader *reader, ResumeRecv(); } SetWriteCallbacks(); - return NS_BASE_STREAM_WOULD_BLOCK; + if (mAttemptingEarlyData) { + // We can still try to send our preamble as early-data + *countRead = mOutputQueueUsed - mOutputQueueSent; + } + return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; + } + + uint32_t earlyDataUsed = 0; + if (mAttemptingEarlyData) { + if (!stream->Do0RTT()) { + LOG3(("Http2Session %p will not get early data from Http2Stream %p 0x%X", + this, stream, stream->StreamID())); + FlushOutputQueue(); + SetWriteCallbacks(); + // We can still send our preamble + *countRead = mOutputQueueUsed - mOutputQueueSent; + return *countRead ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; + } + + if (!m0RTTStreams.Contains(stream->StreamID())) { + m0RTTStreams.AppendElement(stream->StreamID()); + } + + // Need to adjust this to only take as much as we can fit in with the + // preamble/settings/priority stuff + count -= (mOutputQueueUsed - mOutputQueueSent); + + // Keep track of this to add it into countRead later, as + // stream->ReadSegments will likely change the value of mOutputQueueUsed. + earlyDataUsed = mOutputQueueUsed - mOutputQueueSent; } LOG3(("Http2Session %p will write from Http2Stream %p 0x%X " @@ -2325,6 +2379,13 @@ Http2Session::ReadSegmentsAgain(nsAHttpSegmentReader *reader, rv = stream->ReadSegments(this, count, countRead); + if (earlyDataUsed) { + // Do this here because countRead could get reset somewhere down the rabbit + // hole of stream->ReadSegments, and we want to make sure we return the + // proper value to our caller. + *countRead += earlyDataUsed; + } + // Not every permutation of stream->ReadSegents produces data (and therefore // tries to flush the output queue) - SENDING_FIN_STREAM can be an example // of that. But we might still have old data buffered that would be good @@ -2873,6 +2934,58 @@ Http2Session::WriteSegments(nsAHttpSegmentWriter *writer, return WriteSegmentsAgain(writer, count, countWritten, &again); } +nsresult +Http2Session::Finish0RTT(bool aRestart, bool aAlpnChanged) +{ + MOZ_ASSERT(mAttemptingEarlyData); + LOG3(("Http2Session::Finish0RTT %p aRestart=%d aAlpnChanged=%d", this, + aRestart, aAlpnChanged)); + + for (size_t i = 0; i < m0RTTStreams.Length(); ++i) { + // Instead of passing (aRestart, aAlpnChanged) here, we use aAlpnChanged for + // both arguments because as long as the alpn token stayed the same, we can + // just reuse what we have in our buffer to send instead of having to have + // the transaction rewind and read it all over again. We only need to rewind + // the transaction if we're switching to a new protocol, because our buffer + // won't get used in that case. + Http2Stream *stream = mStreamIDHash.Get(m0RTTStreams[i]); + if (stream) { + stream->Finish0RTT(aAlpnChanged, aAlpnChanged); + } + } + + if (aRestart) { + // 0RTT failed + if (aAlpnChanged) { + // This is a slightly more involved case - we need to get all our streams/ + // transactions back in the queue so they can restart as http/1 + + // These must be set this way to ensure we gracefully restart all streams + mGoAwayID = 0; + mCleanShutdown = true; + + // Close takes care of the rest of our work for us. The reason code here + // doesn't matter, as we aren't actually going to send a GOAWAY frame, but + // we use NS_ERROR_NET_RESET as it's closest to the truth. + Close(NS_ERROR_NET_RESET); + } else { + // This is the easy case - early data failed, but we're speaking h2, so + // we just need to rewind to the beginning of the preamble and try again. + mOutputQueueSent = 0; + } + } else { + // 0RTT succeeded + // Make sure we look for any incoming data in repsonse to our early data. + ResumeRecv(); + } + + mAttemptingEarlyData = false; + m0RTTStreams.Clear(); + RealignOutputQueue(); + + return NS_OK; +} + nsresult Http2Session::ProcessConnectedPush(Http2Stream *pushConnectedStream, nsAHttpSegmentWriter * writer, @@ -3080,7 +3193,9 @@ Http2Session::Close(nsresult aReason) } else { goAwayReason = INTERNAL_ERROR; } - GenerateGoAway(goAwayReason); + if (!mAttemptingEarlyData) { + GenerateGoAway(goAwayReason); + } mConnection = nullptr; mSegmentReader = nullptr; mSegmentWriter = nullptr; @@ -3181,7 +3296,7 @@ Http2Session::OnReadSegment(const char *buf, nsresult Http2Session::CommitToSegmentSize(uint32_t count, bool forceCommitment) { - if (mOutputQueueUsed) + if (mOutputQueueUsed && !mAttemptingEarlyData) FlushOutputQueue(); // would there be enough room to buffer this if needed? @@ -3500,12 +3615,18 @@ Http2Session::ALPNCallback(nsISupports *securityInfo) nsresult Http2Session::ConfirmTLSProfile() { - if (mTLSProfileConfirmed) + if (mTLSProfileConfirmed) { return NS_OK; + } LOG3(("Http2Session::ConfirmTLSProfile %p mConnection=%p\n", this, mConnection.get())); + if (mAttemptingEarlyData) { + LOG3(("Http2Session::ConfirmTLSProfile %p temporarily passing due to early data\n", this)); + return NS_OK; + } + if (!gHttpHandler->EnforceHttp2TlsProfile()) { LOG3(("Http2Session::ConfirmTLSProfile %p passed due to configuration bypass\n", this)); mTLSProfileConfirmed = true; diff --git a/netwerk/protocol/http/Http2Session.h b/netwerk/protocol/http/Http2Session.h index c2a630c55..2e9a09c61 100644 --- a/netwerk/protocol/http/Http2Session.h +++ b/netwerk/protocol/http/Http2Session.h @@ -43,12 +43,13 @@ public: NS_DECL_NSAHTTPSEGMENTREADER NS_DECL_NSAHTTPSEGMENTWRITER - Http2Session(nsISocketTransport *, uint32_t version); + Http2Session(nsISocketTransport *, uint32_t version, bool attemptingEarlyData); bool AddStream(nsAHttpTransaction *, int32_t, bool, nsIInterfaceRequestor *) override; bool CanReuse() override { return !mShouldGoAway && !mClosed; } bool RoomForMoreStreams() override; + uint32_t SpdyVersion() override; // When the connection is active this is called up to once every 1 second // return the interval (in seconds) that the connection next wants to @@ -235,6 +236,8 @@ public: // overload of nsAHttpTransaction nsresult ReadSegmentsAgain(nsAHttpSegmentReader *, uint32_t, uint32_t *, bool *) override final; nsresult WriteSegmentsAgain(nsAHttpSegmentWriter *, uint32_t , uint32_t *, bool *) override final; + bool Do0RTT() override final { return true; } + nsresult Finish0RTT(bool aRestart, bool aAlpnChanged) override final; private: @@ -505,6 +508,10 @@ private: bool mUseH2Deps; + bool mAttemptingEarlyData; + // The ID(s) of the stream(s) that we are getting 0RTT data from. + nsTArray m0RTTStreams; + private: /// connect tunnels void DispatchOnTunnel(nsAHttpTransaction *, nsIInterfaceRequestor *); diff --git a/netwerk/protocol/http/Http2Stream.cpp b/netwerk/protocol/http/Http2Stream.cpp index 27c4f2fad..aa11b5ccc 100644 --- a/netwerk/protocol/http/Http2Stream.cpp +++ b/netwerk/protocol/http/Http2Stream.cpp @@ -70,6 +70,7 @@ Http2Stream::Http2Stream(nsAHttpTransaction *httpTransaction, , mTotalSent(0) , mTotalRead(0) , mPushSource(nullptr) + , mAttempting0RTT(false) , mIsTunnel(false) , mPlainTextTunnel(false) { @@ -950,7 +951,9 @@ Http2Stream::TransmitFrame(const char *buf, *countUsed += mTxStreamFrameSize; } - mSession->FlushOutputQueue(); + if (!mAttempting0RTT) { + mSession->FlushOutputQueue(); + } // calling this will trigger waiting_for if mRequestBodyLenRemaining is 0 UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize); @@ -1485,6 +1488,26 @@ Http2Stream::MapStreamToHttpConnection() mTransaction->ConnectionInfo()); } +// ----------------------------------------------------------------------------- +// mirror nsAHttpTransaction +// ----------------------------------------------------------------------------- + +bool +Http2Stream::Do0RTT() +{ + MOZ_ASSERT(mTransaction); + mAttempting0RTT = true; + return mTransaction->Do0RTT(); +} + +nsresult +Http2Stream::Finish0RTT(bool aRestart, bool aAlpnChanged) +{ + MOZ_ASSERT(mTransaction); + mAttempting0RTT = false; + return mTransaction->Finish0RTT(aRestart, aAlpnChanged); +} + } // namespace net } // namespace mozilla diff --git a/netwerk/protocol/http/Http2Stream.h b/netwerk/protocol/http/Http2Stream.h index 6c42c5742..5a6b09cf8 100644 --- a/netwerk/protocol/http/Http2Stream.h +++ b/netwerk/protocol/http/Http2Stream.h @@ -154,6 +154,10 @@ public: const nsACString &origin, RefPtr &url); + // Mirrors nsAHttpTransaction + bool Do0RTT(); + nsresult Finish0RTT(bool aRestart, bool aAlpnIgnored); + protected: static void CreatePushHashKey(const nsCString &scheme, const nsCString &hostHeader, @@ -328,6 +332,8 @@ private: nsCOMPtr mInputBufferIn; nsCOMPtr mInputBufferOut; + bool mAttempting0RTT; + /// connect tunnels public: bool IsTunnel() { return mIsTunnel; } diff --git a/netwerk/protocol/http/SpdySession31.h b/netwerk/protocol/http/SpdySession31.h index 8267686ca..353081203 100644 --- a/netwerk/protocol/http/SpdySession31.h +++ b/netwerk/protocol/http/SpdySession31.h @@ -46,6 +46,7 @@ public: bool, nsIInterfaceRequestor *) override; bool CanReuse() override { return !mShouldGoAway && !mClosed; } bool RoomForMoreStreams() override; + uint32_t SpdyVersion() override { return SPDY_VERSION_31; } // When the connection is active this is called up to once every 1 second // return the interval (in seconds) that the connection next wants to diff --git a/netwerk/protocol/http/nsAHttpTransaction.h b/netwerk/protocol/http/nsAHttpTransaction.h index 9b1e33ecc..6852ccdb4 100644 --- a/netwerk/protocol/http/nsAHttpTransaction.h +++ b/netwerk/protocol/http/nsAHttpTransaction.h @@ -216,8 +216,11 @@ public: // If aRestart parameter is true we need to restart the transaction, // otherwise the erly-data has been accepted and we can continue the // transaction. + // If aAlpnChanged is true (and we were assuming http/2), we'll need to take + // the transactions out of the session, rewind them all, and start them back + // over as http/1 transactions // The function will return success or failure of the transaction restart. - virtual nsresult Finish0RTT(bool aRestart) { + virtual nsresult Finish0RTT(bool aRestart, bool aAlpnChanged) { return NS_ERROR_NOT_IMPLEMENTED; } }; diff --git a/netwerk/protocol/http/nsHttpConnection.cpp b/netwerk/protocol/http/nsHttpConnection.cpp index 31229a82d..2066117e6 100644 --- a/netwerk/protocol/http/nsHttpConnection.cpp +++ b/netwerk/protocol/http/nsHttpConnection.cpp @@ -82,6 +82,7 @@ nsHttpConnection::nsHttpConnection() , m0RTTChecked(false) , mWaitingFor0RTTResponse(false) , mContentBytesWritten0RTT(0) + , mDid0RTTSpdy(false) { LOG(("Creating nsHttpConnection @%p\n", this)); @@ -157,15 +158,114 @@ nsHttpConnection::Init(nsHttpConnectionInfo *info, return NS_OK; } +nsresult +nsHttpConnection::TryTakeSubTransactions(nsTArray > &list) +{ + nsresult rv = mTransaction->TakeSubTransactions(list); + + if (rv == NS_ERROR_ALREADY_OPENED) { + // Has the interface for TakeSubTransactions() changed? + LOG(("TakeSubTransactions somehow called after " + "nsAHttpTransaction began processing\n")); + MOZ_ASSERT(false, + "TakeSubTransactions somehow called after " + "nsAHttpTransaction began processing"); + mTransaction->Close(NS_ERROR_ABORT); + return rv; + } + + if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) { + // Has the interface for TakeSubTransactions() changed? + LOG(("unexpected rv from nnsAHttpTransaction::TakeSubTransactions()")); + MOZ_ASSERT(false, + "unexpected result from " + "nsAHttpTransaction::TakeSubTransactions()"); + mTransaction->Close(NS_ERROR_ABORT); + return rv; + } + + return rv; +} + +nsresult +nsHttpConnection::MoveTransactionsToSpdy(nsresult status, nsTArray > &list) +{ + if (NS_FAILED(status)) { // includes NS_ERROR_NOT_IMPLEMENTED + MOZ_ASSERT(list.IsEmpty(), "sub transaction list not empty"); + + // This is ok - treat mTransaction as a single real request. + // Wrap the old http transaction into the new spdy session + // as the first stream. + LOG(("nsHttpConnection::MoveTransactionsToSpdy moves single transaction %p " + "into SpdySession %p\n", mTransaction.get(), mSpdySession.get())); + nsresult rv = AddTransaction(mTransaction, mPriority); + if (NS_FAILED(rv)) { + return rv; + } + } else { + int32_t count = list.Length(); + + LOG(("nsHttpConnection::MoveTransactionsToSpdy moving transaction list len=%d " + "into SpdySession %p\n", count, mSpdySession.get())); + + if (!count) { + mTransaction->Close(NS_ERROR_ABORT); + return NS_ERROR_ABORT; + } + + for (int32_t index = 0; index < count; ++index) { + nsresult rv = AddTransaction(list[index], mPriority); + if (NS_FAILED(rv)) { + return rv; + } + } + } + + return NS_OK; +} + +void +nsHttpConnection::Start0RTTSpdy(uint8_t spdyVersion) +{ + LOG(("nsHttpConnection::Start0RTTSpdy [this=%p]", this)); + mDid0RTTSpdy = true; + mUsingSpdyVersion = spdyVersion; + mSpdySession = ASpdySession::NewSpdySession(spdyVersion, mSocketTransport, + true); + + nsTArray > list; + nsresult rv = TryTakeSubTransactions(list); + if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) { + LOG(("nsHttpConnection::Start0RTTSpdy [this=%p] failed taking " + "subtransactions rv=%" PRIx32 , this, static_cast(rv))); + return; + } + + rv = MoveTransactionsToSpdy(rv, list); + if (NS_FAILED(rv)) { + LOG(("nsHttpConnection::Start0RTTSpdy [this=%p] failed moving " + "transactions rv=%" PRIx32 , this, static_cast(rv))); + return; + } + + mTransaction = mSpdySession; +} + void nsHttpConnection::StartSpdy(uint8_t spdyVersion) { - LOG(("nsHttpConnection::StartSpdy [this=%p]\n", this)); + LOG(("nsHttpConnection::StartSpdy [this=%p, mDid0RTTSpdy=%d]\n", this, mDid0RTTSpdy)); - MOZ_ASSERT(!mSpdySession); + MOZ_ASSERT(!mSpdySession || mDid0RTTSpdy); mUsingSpdyVersion = spdyVersion; mEverUsedSpdy = true; + // see also bug 865314 for backbugs + // https://hg.mozilla.org/mozilla-central/rev/b1e8d6cf54e6 + if (!mDid0RTTSpdy) { + mSpdySession = ASpdySession::NewSpdySession(spdyVersion, + mSocketTransport, false); + } if (!mReportedSpdy) { mReportedSpdy = true; @@ -183,27 +283,12 @@ nsHttpConnection::StartSpdy(uint8_t spdyVersion) // pack them all into a new spdy session. nsTArray > list; - nsresult rv = mTransaction->TakeSubTransactions(list); - - if (rv == NS_ERROR_ALREADY_OPENED) { - // Has the interface for TakeSubTransactions() changed? - LOG(("TakeSubTransactions somehow called after " - "nsAHttpTransaction began processing\n")); - MOZ_ASSERT(false, - "TakeSubTransactions somehow called after " - "nsAHttpTransaction began processing"); - mTransaction->Close(NS_ERROR_ABORT); - return; - } - - if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) { - // Has the interface for TakeSubTransactions() changed? - LOG(("unexpected rv from nnsAHttpTransaction::TakeSubTransactions()")); - MOZ_ASSERT(false, - "unexpected result from " - "nsAHttpTransaction::TakeSubTransactions()"); - mTransaction->Close(NS_ERROR_ABORT); - return; + nsresult rv = NS_OK; + if (!mDid0RTTSpdy) { + rv = TryTakeSubTransactions(list); + if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) { + return; + } } if (NeedSpdyTunnel()) { @@ -216,7 +301,6 @@ nsHttpConnection::StartSpdy(uint8_t spdyVersion) mProxyConnectInProgress = false; } - mSpdySession = ASpdySession::NewSpdySession(spdyVersion, mSocketTransport); bool spdyProxy = mConnInfo->UsingHttpsProxy() && !mTLSFilter; if (spdyProxy) { RefPtr wildCardProxyCi; @@ -226,35 +310,11 @@ nsHttpConnection::StartSpdy(uint8_t spdyVersion) mConnInfo = wildCardProxyCi; } - if (NS_FAILED(rv)) { // includes NS_ERROR_NOT_IMPLEMENTED - MOZ_ASSERT(list.IsEmpty(), "sub transaction list not empty"); - - // This is ok - treat mTransaction as a single real request. - // Wrap the old http transaction into the new spdy session - // as the first stream. - LOG(("nsHttpConnection::StartSpdy moves single transaction %p " - "into SpdySession %p\n", mTransaction.get(), mSpdySession.get())); - rv = AddTransaction(mTransaction, mPriority); + if (!mDid0RTTSpdy) { + rv = MoveTransactionsToSpdy(rv, list); if (NS_FAILED(rv)) { return; } - } else { - int32_t count = list.Length(); - - LOG(("nsHttpConnection::StartSpdy moving transaction list len=%d " - "into SpdySession %p\n", count, mSpdySession.get())); - - if (!count) { - mTransaction->Close(NS_ERROR_ABORT); - return; - } - - for (int32_t index = 0; index < count; ++index) { - rv = AddTransaction(list[index], mPriority); - if (NS_FAILED(rv)) { - return; - } - } } // Disable TCP Keepalives - use SPDY ping instead. @@ -324,8 +384,7 @@ nsHttpConnection::EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue, // (AlpnEarlySelection), we are using HTTP/1, and the request data can // be safely retried. m0RTTChecked = true; - nsAutoCString earlyNegotiatedNPN; - nsresult rvEarlyAlpn = ssl->GetAlpnEarlySelection(earlyNegotiatedNPN); + nsresult rvEarlyAlpn = ssl->GetAlpnEarlySelection(mEarlyNegotiatedALPN); if (NS_FAILED(rvEarlyAlpn)) { // if ssl->DriveHandshake() has never been called the value // for AlpnEarlySelection is still not set. So call it here and @@ -342,7 +401,7 @@ nsHttpConnection::EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue, // Check NegotiatedNPN first. rv = ssl->GetNegotiatedNPN(negotiatedNPN); if (rv == NS_ERROR_NOT_CONNECTED) { - rvEarlyAlpn = ssl->GetAlpnEarlySelection(earlyNegotiatedNPN); + rvEarlyAlpn = ssl->GetAlpnEarlySelection(mEarlyNegotiatedALPN); } } @@ -351,17 +410,24 @@ nsHttpConnection::EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue, "early selected alpn not available", this)); } else { LOG(("nsHttpConnection::EnsureNPNComplete %p -" - "early selected alpn: %s", this, earlyNegotiatedNPN.get())); + "early selected alpn: %s", this, mEarlyNegotiatedALPN.get())); uint32_t infoIndex; const SpdyInformation *info = gHttpHandler->SpdyInfo(); - // We are doing 0RTT only with Http/1 right now! - if (NS_FAILED(info->GetNPNIndex(earlyNegotiatedNPN, &infoIndex))) { + if (NS_FAILED(info->GetNPNIndex(mEarlyNegotiatedALPN, &infoIndex))) { + // This is the HTTP/1 case. // Check if early-data is allowed for this transaction. if (mTransaction->Do0RTT()) { LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - We " - "can do 0RTT!", this)); + "can do 0RTT (http/1)!", this)); mWaitingFor0RTTResponse = true; } + } else { + // We have h2, we can at least 0-RTT the preamble and opening + // SETTINGS, etc, and maybe some of the first request + LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - Starting " + "0RTT for h2!", this)); + mWaitingFor0RTTResponse = true; + Start0RTTSpdy(info->Version[infoIndex]); } } } @@ -392,49 +458,71 @@ nsHttpConnection::EnsureNPNComplete(nsresult &aOut0RTTWriteHandshakeValue, this, mConnInfo->HashKey().get(), negotiatedNPN.get(), mTLSFilter ? " [Double Tunnel]" : "")); - bool ealyDataAccepted = false; + bool earlyDataAccepted = false; if (mWaitingFor0RTTResponse) { mWaitingFor0RTTResponse = false; // Check if early data has been accepted. - rv = ssl->GetEarlyDataAccepted(&ealyDataAccepted); + rv = ssl->GetEarlyDataAccepted(&earlyDataAccepted); LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - early data " - "that was sent during 0RTT %s been accepted.", - this, ealyDataAccepted ? "has" : "has not")); + "that was sent during 0RTT %s been accepted [rv=%" PRIx32 "].", + this, earlyDataAccepted ? "has" : "has not", static_cast(rv))); if (NS_FAILED(rv) || - NS_FAILED(mTransaction->Finish0RTT(!ealyDataAccepted))) { + NS_FAILED(mTransaction->Finish0RTT(!earlyDataAccepted, negotiatedNPN != mEarlyNegotiatedALPN))) { + LOG(("nsHttpConection::EnsureNPNComplete [this=%p] closing transaction %p", this, mTransaction.get())); mTransaction->Close(NS_ERROR_NET_RESET); goto npnComplete; } } - if (!ealyDataAccepted) { + if (!earlyDataAccepted) { uint32_t infoIndex; + LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] early data not accepted", this)); const SpdyInformation *info = gHttpHandler->SpdyInfo(); if (NS_SUCCEEDED(info->GetNPNIndex(negotiatedNPN, &infoIndex))) { StartSpdy(info->Version[infoIndex]); } } else { - LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - %d bytes " + LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - %" PRId64 " bytes " "has been sent during 0RTT.", this, mContentBytesWritten0RTT)); mContentBytesWritten = mContentBytesWritten0RTT; + if (mSpdySession) { + // We had already started 0RTT-spdy, now we need to fully set up + // spdy, since we know we're sticking with it. + LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] - finishing " + "StartSpdy for 0rtt spdy session %p", this, mSpdySession.get())); + StartSpdy(mSpdySession->SpdyVersion()); + } } //Telemetry::Accumulate(Telemetry::SPDY_NPN_CONNECT, UsingSpdy()); } npnComplete: - LOG(("nsHttpConnection::EnsureNPNComplete setting complete to true")); + LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] setting complete to true", this)); mNPNComplete = true; mTransaction->OnTransportStatus(mSocketTransport, NS_NET_STATUS_TLS_HANDSHAKE_ENDED, 0); if (mWaitingFor0RTTResponse) { + // Didn't get 0RTT OK, back out of the "attempting 0RTT" state mWaitingFor0RTTResponse = false; - if (NS_FAILED(mTransaction->Finish0RTT(true))) { + LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] 0rtt failed", this)); + if (NS_FAILED(mTransaction->Finish0RTT(true, negotiatedNPN != mEarlyNegotiatedALPN))) { mTransaction->Close(NS_ERROR_NET_RESET); } mContentBytesWritten0RTT = 0; } + + if (mDid0RTTSpdy && negotiatedNPN != mEarlyNegotiatedALPN) { + // Reset the work done by Start0RTTSpdy + LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] resetting Start0RTTSpdy", this)); + mUsingSpdyVersion = 0; + mTransaction = nullptr; + mSpdySession = nullptr; + // We have to reset this here, just in case we end up starting spdy again, + // so it can actually do everything it needs to do. + mDid0RTTSpdy = false; + } return true; } diff --git a/netwerk/protocol/http/nsHttpConnection.h b/netwerk/protocol/http/nsHttpConnection.h index f7ddde320..fb98c5c18 100644 --- a/netwerk/protocol/http/nsHttpConnection.h +++ b/netwerk/protocol/http/nsHttpConnection.h @@ -247,6 +247,13 @@ private: // Start the Spdy transaction handler when NPN indicates spdy/* void StartSpdy(uint8_t versionLevel); + // Like the above, but do the bare minimum to do 0RTT data, so we can back + // it out, if necessary + void Start0RTTSpdy(uint8_t versionLevel); + + // Helpers for Start*Spdy + nsresult TryTakeSubTransactions(nsTArray > &list); + nsresult MoveTransactionsToSpdy(nsresult status, nsTArray > &list); // Directly Add a transaction to an active connection for SPDY nsresult AddTransaction(nsAHttpTransaction *, int32_t); @@ -361,6 +368,8 @@ private: // for the end of // the handsake. int64_t mContentBytesWritten0RTT; + nsCString mEarlyNegotiatedALPN; + bool mDid0RTTSpdy; private: // For ForceSend() diff --git a/netwerk/protocol/http/nsHttpTransaction.cpp b/netwerk/protocol/http/nsHttpTransaction.cpp index 3ee234738..3581b8147 100644 --- a/netwerk/protocol/http/nsHttpTransaction.cpp +++ b/netwerk/protocol/http/nsHttpTransaction.cpp @@ -2368,7 +2368,7 @@ nsHttpTransaction::Do0RTT() } nsresult -nsHttpTransaction::Finish0RTT(bool aRestart) +nsHttpTransaction::Finish0RTT(bool aRestart, bool aAlpnChanged /* ignored */) { LOG(("nsHttpTransaction::Finish0RTT %p %d\n", this, aRestart)); MOZ_ASSERT(m0RTTInProgress); diff --git a/netwerk/protocol/http/nsHttpTransaction.h b/netwerk/protocol/http/nsHttpTransaction.h index 4a6154a8c..094f0b4b3 100644 --- a/netwerk/protocol/http/nsHttpTransaction.h +++ b/netwerk/protocol/http/nsHttpTransaction.h @@ -167,7 +167,7 @@ public: int64_t GetTransferSize() { return mTransferSize; } bool Do0RTT() override; - nsresult Finish0RTT(bool aRestart) override; + nsresult Finish0RTT(bool aRestart, bool aAlpnChanged /* ignored */) override; private: friend class DeleteHttpTransaction; virtual ~nsHttpTransaction();