From 05c4dcd62f28242dcdd088c85d018f7e6a424e77 Mon Sep 17 00:00:00 2001
From: Dragana Damjanovic
Date: Thu, 10 Sep 2020 20:48:56 +0000
Subject: [PATCH] Bug 1650388 - Send 0RTT data. r=kershaw
Differential Revision: https://phabricator.services.mozilla.com/D87231
---
netwerk/protocol/http/Http3Session.cpp | 103 ++++++++++++++++++++---
netwerk/protocol/http/Http3Session.h | 13 ++-
netwerk/protocol/http/Http3Stream.cpp | 31 +++++++
netwerk/protocol/http/Http3Stream.h | 8 ++
netwerk/socket/neqo_glue/NeqoHttp3Conn.h | 4 +
netwerk/socket/neqo_glue/src/lib.rs | 5 ++
6 files changed, 150 insertions(+), 14 deletions(-)
diff --git a/netwerk/protocol/http/Http3Session.cpp b/netwerk/protocol/http/Http3Session.cpp
index 5c3b157acebe..76b60b6a531a 100644
--- a/netwerk/protocol/http/Http3Session.cpp
+++ b/netwerk/protocol/http/Http3Session.cpp
@@ -156,6 +156,24 @@ nsresult Http3Session::Init(const nsACString& aOrigin,
if (NS_SUCCEEDED(SSLTokensCache::Get(peerId, token))) {
LOG(("Found a resumption token in the cache."));
mHttp3Connection->SetResumptionToken(token);
+ if (mHttp3Connection->IsZeroRtt()) {
+ LOG(("Can send ZeroRtt data"));
+ RefPtr self(this);
+ mState = ZERORTT;
+ // Let the nsHttpConnectionMgr know that the connection can accept
+ // transactions.
+ // We need to dispatch the following function to this thread so that
+ // it is executed after the current function. At this point a
+ // Http3Session is still being initialized and ReportHttp3Connection
+ // will try to dispatch transaction on this session therefore it
+ // needs to be executed after the initializationg is done.
+ DebugOnly rv = NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "Http3Session::ReportHttp3Connection",
+ [self]() {
+ self->ReportHttp3Connection();
+ }));
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
+ }
}
return NS_OK;
}
@@ -360,11 +378,10 @@ nsresult Http3Session::ProcessEvents(uint32_t count) {
static_cast(rv)));
return rv;
}
-
break;
}
case Http3Event::Tag::DataWritable:
- MOZ_ASSERT(mState == CONNECTED);
+ MOZ_ASSERT(CanSandData());
LOG(("Http3Session::ProcessEvents - DataWritable"));
if (mReadyForWriteButBlocked.RemoveElement(
event.data_writable.stream_id)) {
@@ -417,12 +434,23 @@ nsresult Http3Session::ProcessEvents(uint32_t count) {
CallCertVerification();
}
break;
+ case Http3Event::Tag::ZeroRttRejected:
+ LOG(("Http3Session::ProcessEvents - ZeroRttRejected"));
+ if (mState == ZERORTT) {
+ mState = INITIALIZING;
+ Finish0Rtt(true);
+ }
+ break;
case Http3Event::Tag::ConnectionConnected:
{
LOG(("Http3Session::ProcessEvents - ConnectionConnected"));
+ bool was0RTT = mState == ZERORTT;
mState = CONNECTED;
SetSecInfo();
mSocketControl->HandshakeCompleted();
+ if (was0RTT) {
+ Finish0Rtt(false);
+ }
nsTArray token;
mHttp3Connection->GetResumptionToken(token);
if (!token.IsEmpty()) {
@@ -436,8 +464,7 @@ nsresult Http3Session::ProcessEvents(uint32_t count) {
}
}
- gHttpHandler->ConnMgr()->ReportHttp3Connection(mSegmentReaderWriter);
- MaybeResumeSend();
+ ReportHttp3Connection();
}
break;
case Http3Event::Tag::GoawayReceived:
@@ -600,13 +627,25 @@ bool Http3Session::AddStream(nsAHttpTransaction* aHttpTransaction,
Http3Stream* stream = new Http3Stream(aHttpTransaction, this);
mStreamTransactionHash.Put(aHttpTransaction, RefPtr{stream});
+ if (mState == ZERORTT) {
+ if (!stream->Do0RTT()) {
+ LOG(("Http3Session %p will not get early data from Http3Stream %p",
+ this, stream));
+ if (!mCannotDo0RTTStreams.Contains(stream)) {
+ mCannotDo0RTTStreams.AppendElement(stream);
+ }
+ return true;
+ } else {
+ m0RTTStreams.AppendElement(stream);
+ }
+ }
StreamReadyToWrite(stream);
return true;
}
bool Http3Session::CanReuse() {
- return (mState == CONNECTED) && !(mGoawayReceived || mShouldClose);
+ return CanSandData() && !(mGoawayReceived || mShouldClose);
}
void Http3Session::QueueStream(Http3Stream* stream) {
@@ -677,6 +716,13 @@ nsresult Http3Session::TryActivating(
return NS_BASE_STREAM_WOULD_BLOCK;
}
+ if (mState == ZERORTT) {
+ if (!aStream->Do0RTT()) {
+ MOZ_ASSERT(!mCannotDo0RTTStreams.Contains(aStream));
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ }
+
nsresult rv = mHttp3Connection->Fetch(aMethod, aScheme, aAuthorityHeader,
aPath, aHeaders, aStreamId);
if (NS_FAILED(rv)) {
@@ -808,10 +854,7 @@ nsresult Http3Session::ReadSegmentsAgain(nsAHttpSegmentReader* reader,
Http3Stream* stream = nullptr;
// Step 1)
- while (
- (mState ==
- CONNECTED) && // Do not send transaction data untill we are connected.
- (stream = mReadyForWrite.PopFront())) {
+ while (CanSandData() && (stream = mReadyForWrite.PopFront())) {
LOG(
("Http3Session::ReadSegmentsAgain call ReadSegments from stream=%p "
"[this=%p]",
@@ -882,13 +925,13 @@ nsresult Http3Session::ReadSegmentsAgain(nsAHttpSegmentReader* reader,
void Http3Session::StreamReadyToWrite(Http3Stream* aStream) {
MOZ_ASSERT(aStream);
mReadyForWrite.Push(aStream);
- if ((mState == CONNECTED) && mConnection) {
+ if (CanSandData() && mConnection) {
Unused << mConnection->ResumeSend();
}
}
void Http3Session::MaybeResumeSend() {
- if ((mReadyForWrite.GetSize() > 0) && (mState == CONNECTED) && mConnection) {
+ if ((mReadyForWrite.GetSize() > 0) && CanSandData() && mConnection) {
Unused << mConnection->ResumeSend();
}
}
@@ -1271,7 +1314,7 @@ bool Http3Session::JoinConnection(const nsACString& hostname, int32_t port) {
bool Http3Session::RealJoinConnection(const nsACString& hostname, int32_t port,
bool justKidding) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
- if (!mConnection || (mState != CONNECTED) || mShouldClose ||
+ if (!mConnection || !CanSandData() || mShouldClose ||
mGoawayReceived) {
return false;
}
@@ -1374,7 +1417,7 @@ void Http3Session::CallCertVerification() {
void Http3Session::Authenticated(int32_t aError) {
LOG(("Http3Session::Authenticated error=0x%" PRIx32 " [this=%p].", aError,
this));
- if (mState == INITIALIZING) {
+ if ((mState == INITIALIZING) || (mState == ZERORTT)) {
if (psm::IsNSSErrorCode(aError)) {
mError = psm::GetXPCOMFromNSSError(aError);
LOG(("Http3Session::Authenticated psm-error=0x%" PRIx32 " [this=%p].",
@@ -1452,5 +1495,39 @@ void Http3Session::CloseConnectionTelemetry(CloseError& aError, bool aClosing) {
aClosing ? "closing"_ns : "closed"_ns, value);
}
+void Http3Session::Finish0Rtt(bool aRestart) {
+ for (size_t i = 0; i < m0RTTStreams.Length(); ++i) {
+ if (m0RTTStreams[i]) {
+ if (aRestart) {
+ // When we need ot restart transactions remove them from all lists.
+ if (m0RTTStreams[i]->HasStreamId()) {
+ mStreamIdHash.Remove(m0RTTStreams[i]->StreamId());
+ }
+ RemoveStreamFromQueues(m0RTTStreams[i]);
+ // The stream is ready to write again.
+ mReadyForWrite.Push(m0RTTStreams[i]);
+ }
+ m0RTTStreams[i]->Finish0RTT(aRestart);
+ }
+ }
+
+ for (size_t i = 0; i < mCannotDo0RTTStreams.Length(); ++i) {
+ if (mCannotDo0RTTStreams[i]) {
+ mReadyForWrite.Push(mCannotDo0RTTStreams[i]);
+ }
+ }
+ m0RTTStreams.Clear();
+ mCannotDo0RTTStreams.Clear();
+ MaybeResumeSend();
+}
+
+void Http3Session::ReportHttp3Connection() {
+ if (CanSandData() && !mHttp3ConnectionReported) {
+ mHttp3ConnectionReported = true;
+ gHttpHandler->ConnMgr()->ReportHttp3Connection(mSegmentReaderWriter);
+ MaybeResumeSend();
+ }
+}
+
} // namespace net
} // namespace mozilla
diff --git a/netwerk/protocol/http/Http3Session.h b/netwerk/protocol/http/Http3Session.h
index 69597f244a54..698c9e0a93b7 100644
--- a/netwerk/protocol/http/Http3Session.h
+++ b/netwerk/protocol/http/Http3Session.h
@@ -51,6 +51,7 @@ class Http3Session final : public nsAHttpTransaction,
HttpConnectionUDP* readerWriter);
bool IsConnected() const { return mState == CONNECTED; }
+ bool CanSandData() const { return (mState == CONNECTED) || (mState == ZERORTT); }
bool IsClosing() const { return (mState == CLOSING || mState == CLOSED); }
bool IsClosed() const { return mState == CLOSED; }
@@ -100,6 +101,8 @@ class Http3Session final : public nsAHttpTransaction,
const nsCString& GetAlpnToken() { return mAlpnToken; }
+ void ReportHttp3Connection();
+
private:
~Http3Session();
@@ -137,6 +140,7 @@ class Http3Session final : public nsAHttpTransaction,
void MaybeResumeSend();
void CloseConnectionTelemetry(CloseError& aError, bool aClosing);
+ void Finish0Rtt(bool aRestart);
RefPtr mHttp3Connection;
RefPtr mConnection;
@@ -149,13 +153,14 @@ class Http3Session final : public nsAHttpTransaction,
nsTArray> mSlowConsumersReadyForRead;
nsDeque mQueuedStreams;
- enum State { INITIALIZING, CONNECTED, CLOSING, CLOSED } mState;
+ enum State { INITIALIZING, ZERORTT, CONNECTED, CLOSING, CLOSED } mState;
bool mAuthenticationStarted;
bool mCleanShutdown;
bool mGoawayReceived;
bool mShouldClose;
bool mIsClosedByNeqo;
+ bool mHttp3ConnectionReported = false;
// mError is neqo error (a protocol error) and that may mean that we will
// send some packets after that.
nsresult mError;
@@ -180,6 +185,12 @@ class Http3Session final : public nsAHttpTransaction,
nsCString mAlpnToken;
uint64_t mTransactionCount = 0;
+
+ // The stream(s) that we are getting 0RTT data from.
+ nsTArray> m0RTTStreams;
+ // The stream(s) that are not able to send 0RTT data. We need to
+ // remember them put them into mReadyForWrite queue when 0RTT finishes.
+ nsTArray> mCannotDo0RTTStreams;
};
NS_DEFINE_STATIC_IID_ACCESSOR(Http3Session, NS_HTTP3SESSION_IID);
diff --git a/netwerk/protocol/http/Http3Stream.cpp b/netwerk/protocol/http/Http3Stream.cpp
index ab9ecd7a9914..02c506d62050 100644
--- a/netwerk/protocol/http/Http3Stream.cpp
+++ b/netwerk/protocol/http/Http3Stream.cpp
@@ -375,5 +375,36 @@ nsresult Http3Stream::WriteSegments(nsAHttpSegmentWriter* writer,
return rv;
}
+bool Http3Stream::Do0RTT() {
+ MOZ_ASSERT(mTransaction);
+ mAttempting0RTT = mTransaction->Do0RTT();
+ return mAttempting0RTT;
+}
+
+nsresult Http3Stream::Finish0RTT(bool aRestart) {
+ MOZ_ASSERT(mTransaction);
+ mAttempting0RTT = false;
+ nsresult rv = mTransaction->Finish0RTT(aRestart, false);
+ if (aRestart) {
+ nsHttpTransaction* trans = mTransaction->QueryHttpTransaction();
+ if (trans) {
+ trans->Refused0RTT();
+ }
+ }
+ mSendState = PREPARING_HEADERS;
+ mRecvState = READING_HEADERS;
+ mStreamId = UINT64_MAX;
+ mQueued = false;
+ mRequestBlockedOnRead = false;
+ mDataReceived = false;
+ mResetRecv = false;
+ mRequestBodyLenRemaining = 0;
+ mTotalSent = 0;
+ mTotalRead = 0;
+ mFin = false;
+
+ return rv;
+}
+
} // namespace net
} // namespace mozilla
diff --git a/netwerk/protocol/http/Http3Stream.h b/netwerk/protocol/http/Http3Stream.h
index ca5ce45d0c1e..d0dd504c0102 100644
--- a/netwerk/protocol/http/Http3Stream.h
+++ b/netwerk/protocol/http/Http3Stream.h
@@ -8,6 +8,7 @@
#include "nsAHttpTransaction.h"
#include "ARefBase.h"
+#include "mozilla/WeakPtr.h"
namespace mozilla {
namespace net {
@@ -16,6 +17,7 @@ class Http3Session;
class Http3Stream final : public nsAHttpSegmentReader,
public nsAHttpSegmentWriter,
+ public SupportsWeakPtr,
public ARefBase {
public:
NS_DECL_NSAHTTPSEGMENTREADER
@@ -60,6 +62,10 @@ class Http3Stream final : public nsAHttpSegmentReader,
void SetResponseHeaders(nsTArray& aResponseHeaders, bool fin);
+ // Mirrors nsAHttpTransaction
+ bool Do0RTT();
+ nsresult Finish0RTT(bool aRestart);
+
private:
~Http3Stream() = default;
@@ -142,6 +148,8 @@ class Http3Stream final : public nsAHttpSegmentReader,
uint64_t mTotalRead;
bool mFin;
+
+ bool mAttempting0RTT = false;
};
} // namespace net
diff --git a/netwerk/socket/neqo_glue/NeqoHttp3Conn.h b/netwerk/socket/neqo_glue/NeqoHttp3Conn.h
index eec0e22069ff..2f249d1c0a95 100644
--- a/netwerk/socket/neqo_glue/NeqoHttp3Conn.h
+++ b/netwerk/socket/neqo_glue/NeqoHttp3Conn.h
@@ -88,6 +88,10 @@ class NeqoHttp3Conn final {
neqo_http3conn_set_resumption_token(this, &aToken);
}
+ bool IsZeroRtt() {
+ return neqo_http3conn_is_zero_rtt(this);
+ }
+
nsrefcnt AddRef() { return neqo_http3conn_addref(this); }
nsrefcnt Release() { return neqo_http3conn_release(this); }
diff --git a/netwerk/socket/neqo_glue/src/lib.rs b/netwerk/socket/neqo_glue/src/lib.rs
index eef5018b6b51..e3a13bf408ee 100644
--- a/netwerk/socket/neqo_glue/src/lib.rs
+++ b/netwerk/socket/neqo_glue/src/lib.rs
@@ -690,3 +690,8 @@ pub extern "C" fn neqo_http3conn_resumption_token(conn: &mut NeqoHttp3Conn, toke
pub extern "C" fn neqo_http3conn_set_resumption_token(conn: &mut NeqoHttp3Conn, token: &mut ThinVec,) {
let _ = conn.conn.set_resumption_token(Instant::now(), token);
}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_is_zero_rtt(conn: &mut NeqoHttp3Conn) -> bool {
+ conn.conn.state() == Http3State::ZeroRtt
+}