Bug 1790398 - Core implemention of the WebTransport session r=necko-reviewers,kershaw,valentin

AltSvc and 0RTT will not be used with WebTransport.

Differential Revision: https://phabricator.services.mozilla.com/D157821
This commit is contained in:
Dragana Damjanovic 2022-10-20 12:54:51 +00:00
parent fca49d99b7
commit 7fcc11f19b
32 changed files with 1891 additions and 43 deletions

View File

@ -320,6 +320,10 @@ var NetUtil = {
return channel;
},
newWebTransport: function NetUtil_newWebTransport() {
return Services.io.newWebTransport();
},
/**
* Reads aCount bytes from aInputStream into a string.
*

View File

@ -314,6 +314,7 @@ LOCAL_INCLUDES += [
"/dom/base",
"/netwerk/dns",
"/netwerk/protocol/http",
"/netwerk/protocol/webtransport",
"/netwerk/socket",
"/netwerk/url-classifier",
"/security/manager/ssl",

View File

@ -12,6 +12,7 @@ interface nsIURI;
interface nsIFile;
interface nsIPrincipal;
interface nsILoadInfo;
interface nsIWebTransport;
webidl Node;
@ -149,6 +150,11 @@ interface nsIIOService : nsISupports
in unsigned long aSecurityFlags,
in nsContentPolicyType aContentPolicyType);
/**
* Creates a WebTransport.
*/
nsIWebTransport newWebTransport();
/**
* Returns true if networking is in "offline" mode. When in offline mode,
* attempts to access the network will fail (although this does not

View File

@ -35,8 +35,10 @@
#include "nsIProtocolProxyService2.h"
#include "MainThreadUtils.h"
#include "nsINode.h"
#include "nsIWebTransport.h"
#include "nsIWidget.h"
#include "nsThreadUtils.h"
#include "WebTransportSessionProxy.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/net/NeckoCommon.h"
@ -1239,6 +1241,18 @@ nsIOService::NewChannel(const nsACString& aSpec, const char* aCharset,
aContentPolicyType, result);
}
NS_IMETHODIMP
nsIOService::NewWebTransport(nsIWebTransport** result) {
if (!XRE_IsParentProcess()) {
return NS_ERROR_NOT_AVAILABLE;
}
nsCOMPtr<nsIWebTransport> webTransport = new WebTransportSessionProxy();
webTransport.forget(result);
return NS_OK;
}
bool nsIOService::IsLinkUp() {
InitializeNetworkLinkService();

View File

@ -17,6 +17,7 @@ There is also documentation for the `HTTP server we use for unit tests`_.
new_to_necko_resources
network_test_guidelines.md
url_parsers.md
webtransport/webtransport
captive_portals.md
.. _HTTP server we use for unit tests: http_server_for_testing.html

View File

@ -0,0 +1,7 @@
WebTransport
============
Components:
- [WebTransportSessionProxy](webtransportsessionproxy.md)

View File

@ -0,0 +1,19 @@
# WebTransportSessionProxy
WebTransportSessionProxy is introduced to enable the creation of a Http3WebTransportSession and coordination of actions that are performed on the main thread and on the socket thread.
WebTransportSessionProxy can be in different states and the following diagram depicts the transition between the states. “MT” and “ST” mean: the action is happening on the main and socket thread. More details about this class can be found in [WebTransportSessionProxy.h](https://searchfox.org/mozilla-central/source/netwerk/protocol/webtransport/WebTransportSessionProxy.h).
```{mermaid}
graph TD
A[INIT] -->|"nsIWebTransport::AsyncConnect; MT"| B[NEGOTIATING]
B -->|"200 response; ST"| C[NEGOTIATING_SUCCEEDED]
B -->|"nsHttpChannel::OnStart/OnStop failed; MT"| D[DONE]
B -->|"nsIWebTransport::CloseSession; MT"| D
C -->|"nsHttpChannel::OnStart/OnStop failed; MT"| F[SESSION_CLOSE_PENDING]
C -->|"nsHttpChannel::OnStart/OnStop succeeded; MT"| E[ACTIVE]
E -->|"nsIWebTransport::CloseSession; MT"| F
E -->|"The peer closed the session or HTTP/3 connection error; ST"| G[CLOSE_CALLBACK_PENDING]
F -->|"CloseSessionInternal called, The peer closed the session or HTTP/3 connection error; ST"| D
G -->|"CallOnSessionClosed or nsIWebTransport::CloseSession; MT"| D
```

View File

@ -49,6 +49,11 @@ nsresult ConnectionHandle::TakeTransport(nsISocketTransport** aTransport,
return mConn->TakeTransport(aTransport, aInputStream, aOutputStream);
}
Http3WebTransportSession* ConnectionHandle::GetWebTransportSession(
nsAHttpTransaction* aTransaction) {
return mConn->GetWebTransportSession(aTransaction);
}
bool ConnectionHandle::IsPersistent() {
MOZ_ASSERT(OnSocketThread());
return mConn->IsPersistent();

View File

@ -4271,6 +4271,12 @@ nsresult Http2Session::TakeTransport(nsISocketTransport**,
return NS_ERROR_UNEXPECTED;
}
Http3WebTransportSession* Http2Session::GetWebTransportSession(
nsAHttpTransaction* aTransaction) {
MOZ_ASSERT(false, "GetWebTransportSession of Http2Session");
return nullptr;
}
already_AddRefed<HttpConnectionBase> Http2Session::TakeHttpConnection() {
MOZ_ASSERT(false, "TakeHttpConnection of Http2Session");
return nullptr;

View File

@ -8,6 +8,7 @@
#include "Http3Session.h"
#include "Http3Stream.h"
#include "Http3StreamBase.h"
#include "Http3WebTransportSession.h"
#include "mozilla/net/DNS.h"
#include "nsHttpHandler.h"
#include "mozilla/RefPtr.h"
@ -253,8 +254,14 @@ void Http3Session::Shutdown() {
mStreamIdHash.Remove(stream->StreamId());
}
}
mStreamTransactionHash.Clear();
for (const auto& stream : mWebTransportSessions) {
stream->Close(NS_ERROR_ABORT);
RemoveStreamFromQueues(stream);
mStreamIdHash.Remove(stream->StreamId());
}
mWebTransportSessions.Clear();
}
Http3Session::~Http3Session() {
@ -606,24 +613,82 @@ nsresult Http3Session::ProcessEvents() {
event.web_transport._0.negotiated._0
? WebTransportNegotiation::SUCCEEDED
: WebTransportNegotiation::FAILED;
WebTransportNegotiationDone();
break;
case WebTransportEventExternal::Tag::Session: {
MOZ_ASSERT(mState == CONNECTED);
uint64_t id = event.web_transport._0.session.stream_id;
uint16_t status = event.web_transport._0.session.status;
uint64_t id = event.web_transport._0.session._0;
LOG(
("Http3Session::ProcessEvents - WebTransport Session "
" sessionId=0x%" PRIx64 " status=%d",
id, status));
" sessionId=0x%" PRIx64,
id));
RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(id);
if (!stream) {
LOG(
("Http3Session::ProcessEvents - WebTransport Session - "
"stream not found "
"stream_id=0x%" PRIx64 " [this=%p].",
id, this));
break;
}
MOZ_RELEASE_ASSERT(stream->GetHttp3WebTransportSession(),
"It must be a WebTransport session");
stream->SetResponseHeaders(data, false, false);
rv = stream->WriteSegments();
if (ASpdySession::SoftStreamError(rv) || stream->Done()) {
LOG3(
("Http3Session::ProcessSingleTransactionRead session=%p "
"stream=%p "
"0x%" PRIx64 " cleanup stream rv=0x%" PRIx32 " done=%d.\n",
this, stream.get(), stream->StreamId(),
static_cast<uint32_t>(rv), stream->Done()));
if (mStreamTransactionHash.Contains(stream->Transaction())) {
CloseStream(stream, (rv == NS_BINDING_RETARGETED)
? NS_BINDING_RETARGETED
: NS_OK);
} else {
stream->GetHttp3WebTransportSession()->TransactionIsDone(
(rv == NS_BINDING_RETARGETED) ? NS_BINDING_RETARGETED
: NS_OK);
}
return NS_OK;
}
if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this,
static_cast<uint32_t>(rv)));
return rv;
}
} break;
case WebTransportEventExternal::Tag::SessionClosed:
case WebTransportEventExternal::Tag::SessionClosed: {
uint64_t id = event.web_transport._0.session_closed.stream_id;
LOG(
("Http3Session::ProcessEvents - WebTransport SessionClosed "
" sessionId=0x%" PRIx64,
event.web_transport._0.session_closed.stream_id));
break;
id));
RefPtr<Http3StreamBase> stream = mStreamIdHash.Get(id);
if (!stream) {
LOG(
("Http3Session::ProcessEvents - WebTransport SessionClosed - "
"stream not found "
"stream_id=0x%" PRIx64 " [this=%p].",
id, this));
break;
}
RefPtr<Http3WebTransportSession> wt =
stream->GetHttp3WebTransportSession();
MOZ_RELEASE_ASSERT(wt, "It must be a WebTransport session");
// TODO read status and reason properly.
// TODO we do not hanlde the case when a WebTransport session stream
// is closed before headers are send.
nsCString reason = ""_ns;
wt->OnSessionClosed(0, reason);
} break;
case WebTransportEventExternal::Tag::NewStream:
LOG(
("Http3Session::ProcessEvents - WebTransport NewStream "
@ -805,9 +870,17 @@ bool Http3Session::AddStream(nsAHttpTransaction* aHttpTransaction,
cos = trans->GetClassOfService();
}
LOG3(("Http3Session::AddStream %p atrans=%p.\n", this, aHttpTransaction));
Http3StreamBase* stream = new Http3Stream(aHttpTransaction, this, cos,
mCurrentTopBrowsingContextId);
Http3StreamBase* stream = nullptr;
if (trans && trans->IsForWebTransport()) {
LOG3(("Http3Session::AddStream new WeTransport session %p atrans=%p.\n",
this, aHttpTransaction));
stream = new Http3WebTransportSession(aHttpTransaction, this);
} else {
LOG3(("Http3Session::AddStream %p atrans=%p.\n", this, aHttpTransaction));
stream = new Http3Stream(aHttpTransaction, this, cos,
mCurrentTopBrowsingContextId);
}
mStreamTransactionHash.InsertOrUpdate(aHttpTransaction, RefPtr{stream});
@ -823,6 +896,13 @@ bool Http3Session::AddStream(nsAHttpTransaction* aHttpTransaction,
m0RTTStreams.AppendElement(stream);
}
if ((mWebTransportNegotiationStatus ==
WebTransportNegotiation::NEGOTIATING) &&
(trans && trans->IsForWebTransport())) {
mWaitingForWebTransportNegotiation.AppendElement(stream);
return true;
}
if (!mFirstHttpTransaction && !IsConnected()) {
mFirstHttpTransaction = aHttpTransaction->QueryHttpTransaction();
LOG3(("Http3Session::AddStream first session=%p trans=%p ", this,
@ -911,11 +991,19 @@ nsresult Http3Session::TryActivating(
}
}
nsresult rv = NS_OK;
RefPtr<Http3Stream> httpStream = aStream->GetHttp3Stream();
MOZ_RELEASE_ASSERT(httpStream, "This must be a Http3Stream");
nsresult rv = mHttp3Connection->Fetch(
aMethod, aScheme, aAuthorityHeader, aPath, aHeaders, aStreamId,
httpStream->PriorityUrgency(), httpStream->PriorityIncremental());
if (httpStream) {
rv = mHttp3Connection->Fetch(
aMethod, aScheme, aAuthorityHeader, aPath, aHeaders, aStreamId,
httpStream->PriorityUrgency(), httpStream->PriorityIncremental());
} else {
MOZ_RELEASE_ASSERT(aStream->GetHttp3WebTransportSession(),
"It must be a WebTransport session");
rv = mHttp3Connection->CreateWebTransport(aAuthorityHeader, aPath, aHeaders,
aStreamId);
}
if (NS_FAILED(rv)) {
LOG(("Http3Session::TryActivating returns error=0x%" PRIx32 "[stream=%p, "
"this=%p]",
@ -1435,7 +1523,9 @@ void Http3Session::CloseStream(Http3StreamBase* aStream, nsresult aResult) {
}
RemoveStreamFromQueues(aStream);
mStreamTransactionHash.Remove(aStream->Transaction());
if ((mShouldClose || mGoawayReceived) && !mStreamTransactionHash.Count()) {
mWebTransportSessions.RemoveElement(aStream);
if ((mShouldClose || mGoawayReceived) &&
(!mStreamTransactionHash.Count() || !mWebTransportSessions.IsEmpty())) {
MOZ_ASSERT(!IsClosing());
Close(NS_OK);
}
@ -1448,6 +1538,20 @@ nsresult Http3Session::TakeTransport(nsISocketTransport**,
return NS_ERROR_UNEXPECTED;
}
Http3WebTransportSession* Http3Session::GetWebTransportSession(
nsAHttpTransaction* aTransaction) {
RefPtr<Http3StreamBase> stream = mStreamTransactionHash.Get(aTransaction);
if (!stream || !stream->GetHttp3WebTransportSession()) {
MOZ_ASSERT(false, "There must be a stream");
return nullptr;
}
RemoveStreamFromQueues(stream);
mStreamTransactionHash.Remove(aTransaction);
mWebTransportSessions.AppendElement(stream);
return stream->GetHttp3WebTransportSession();
}
bool Http3Session::IsPersistent() { return true; }
void Http3Session::DontReuse() {
@ -1954,4 +2058,29 @@ nsresult Http3Session::GetTransactionTLSSocketControl(
PRIntervalTime Http3Session::LastWriteTime() { return mLastWriteTime; }
void Http3Session::WebTransportNegotiationDone() {
for (size_t i = 0; i < mWaitingForWebTransportNegotiation.Length(); ++i) {
if (mWaitingForWebTransportNegotiation[i]) {
mReadyForWrite.Push(mWaitingForWebTransportNegotiation[i]);
}
}
mWaitingForWebTransportNegotiation.Clear();
}
//=========================================================================
// WebTransport
//=========================================================================
nsresult Http3Session::CloseWebTransport(uint64_t aSessionId, uint32_t aError,
const nsACString& aMessage) {
return mHttp3Connection->CloseWebTransport(aSessionId, aError, aMessage);
}
nsresult Http3Session::CreateWebTransportStream(
uint64_t aSessionId, WebTransportStreamType aStreamType,
uint64_t* aStreamId) {
return mHttp3Connection->CreateWebTransportStream(aSessionId, aStreamType,
aStreamId);
}
} // namespace mozilla::net

View File

@ -19,6 +19,85 @@
#include "mozilla/WeakPtr.h"
#include "nsDeque.h"
/*
* WebTransport
*
* Http3Session and the underlying neqo code support multiplexing of multiple
* WebTransport and multiplexing WebTransport sessions with regular HTTP
* traffic. Whether WebTransport sessions are polled, will be controlled by the
* nsHttpConnectionMgr.
*
* WebTransport support is negotiated using HTTP/3 setting. Before the settings
* are available all WebTransport transactions are queued in
* mWaitingForWebTransportNegotiation. The information on whether WebTransport
* is supported is received via an HTTP/3 event. The event is
* Http3Event::Tag::WebTransport with the value
* WebTransportEventExternal::Tag::Negotiated that can be true or false. If
* the WebTransport feature has been negotiated, queued transactions will be
* activated otherwise they will be canceled(i.e. WebTransportNegotiationDone).
*
* The lifetime of a WebTransport session
*
* A WebTransport lifetime consists of 2 parts:
* - WebTransport session setup
* - WebTransport session active time
*
* WebTransport session setup:
* A WebTransport session uses a regular HTTP request for negotiation.
* Therefore when a new WebTransport is started a nsHttpChannel and the
* corresponding nsHttpTransaction are created. The nsHttpTransaction is
* dispatched to a Http3Session and after the
* WebTransportEventExternal::Tag::Negotiated event it behaves almost the same
* as a regular transaction, e.g. it is added to mStreamTransactionHash and
* mStreamIdHash. For activating the session NeqoHttp3Conn::CreateWebTransport
* is called instead of NeqoHttp3Conn::Fetch(this is called for the regular
* HTTP requests). In this phase, the WebTransport session is canceled in the
* same way a regular request is canceled, by canceling the corresponding
* nsHttpChannel. If HTTP/3 connection is closed in this phase the
* corresponding nsHttpTransaction is canceled and this may cause the
* transaction to be restarted (this is the existing restart logic) or the
* error is propagated to the nsHttpChannel and its listener(via OnStartRequest
* and OnStopRequest as the regular HTTP request).
* The phase ends when a connection breaks or when the event
* Http3Event::Tag::WebTransport with the value
* WebTransportEventExternal::Tag::Session is received. The parameter
* aData(from NeqoHttp3Conn::GetEvent) contain the HTTP head of the response.
* The headers may be:
* - failed code, i.e. anything except 200. In this case, the nsHttpTransaction
* behaves the same as a normal HTTP request and the nsHttpChannel listener
* will be informed via OnStartRequest and OnStopRequest calls.
* - success code, i.e. 200. The code will be propagated to the
* nsHttpTransaction. The transaction will parse the header and call
* Http3Session::GetWebTransportSession. The function transfers WebTransport
* session into the next phase:
* - Removes nsHttpTransaction from mStreamTransactionHash.
* - Adds the stream to mWebTransportSessions
* - The nsHttpTransaction supplies Http3WebTransportSession to the
* WebTransportSessionProxy and vice versa.
* TODO remove this circular referencing.
*
* WebTransport session active time:
* During this phase the following actions are possible:
* - Cancelling a WebTransport session by the application:
* The application calls Http3WebTransportSession::CloseSession. This
* transfers Http3WebTransportSession into the CLOSE_PENDING state and calls
* Http3Session::ConnectSlowConsumer to add itself to the ready for reading
* queue. Consequently, the Http3Session will call
* Http3WebTransportSession::WriteSegments and
* Http3Session::CloseWebTransport will be called to send the closing signal
* to the peer. After this, the Http3WebTransportSession is in the state DONE
* and it will be removed from the Http3Session(the CloseStream function
* takes care of this).
* - The peer sending a session closing signal:
* Http3Session will receive a Http3Event::Tag::WebTransport event with value
* WebTransportEventExternal::Tag::SessionClosed. The
* Http3WebTransportSession::OnSessionClosed function for the corresponding
* stream wil be called. The function will inform the corresponding
* WebTransportSessionProxy by calling OnSessionClosed function. The
* Http3WebTransportSession is in the state DONE and will be removed from the
* Http3Session(the CloseStream function takes care of this).
*/
namespace mozilla::net {
class HttpConnectionUDP;
@ -58,11 +137,13 @@ class Http3Session final : public nsAHttpTransaction, public nsAHttpConnection {
bool CanReuse();
// The following functions are used by Http3Stream:
// The following functions are used by Http3Stream and
// Http3WebTransportSession:
nsresult TryActivating(const nsACString& aMethod, const nsACString& aScheme,
const nsACString& aAuthorityHeader,
const nsACString& aPath, const nsACString& aHeaders,
uint64_t* aStreamId, Http3StreamBase* aStream);
// The folowing functions are used by Http3Stream:
void CloseSendingSide(uint64_t aStreamId);
nsresult SendRequestBody(uint64_t aStreamId, const char* buf, uint32_t count,
uint32_t* countRead);
@ -71,6 +152,12 @@ class Http3Session final : public nsAHttpTransaction, public nsAHttpConnection {
nsresult ReadResponseData(uint64_t aStreamId, char* aBuf, uint32_t aCount,
uint32_t* aCountWritten, bool* aFin);
// The folowing functions are used by Http3WebTransportSession:
nsresult CloseWebTransport(uint64_t aSessionId, uint32_t aError,
const nsACString& aMessage);
nsresult CreateWebTransportStream(uint64_t aSessionId,
WebTransportStreamType aStreamType,
uint64_t* aStreamId);
void CloseStream(Http3StreamBase* aStream, nsresult aResult);
void SetCleanShutdown(bool aCleanShutdown) {
@ -104,6 +191,8 @@ class Http3Session final : public nsAHttpTransaction, public nsAHttpConnection {
nsresult SendPriorityUpdateFrame(uint64_t aStreamId, uint8_t aPriorityUrgency,
bool aPriorityIncremental);
void ConnectSlowConsumer(Http3StreamBase* stream);
private:
~Http3Session();
@ -120,7 +209,6 @@ class Http3Session final : public nsAHttpTransaction, public nsAHttpConnection {
nsresult ProcessTransactionRead(uint64_t stream_id);
nsresult ProcessTransactionRead(Http3StreamBase* stream);
nsresult ProcessSlowConsumers();
void ConnectSlowConsumer(Http3StreamBase* stream);
void SetupTimer(uint64_t aTimeout);
@ -229,6 +317,14 @@ class Http3Session final : public nsAHttpTransaction, public nsAHttpConnection {
enum WebTransportNegotiation { DISABLED, NEGOTIATING, FAILED, SUCCEEDED };
WebTransportNegotiation mWebTransportNegotiationStatus{
WebTransportNegotiation::DISABLED};
nsTArray<WeakPtr<Http3StreamBase>> mWaitingForWebTransportNegotiation;
// 1795854 implement the case when WebTransport is not supported.
// Also, implement the case when the HTTP/3 session fails before settings
// are exchanged.
void WebTransportNegotiationDone();
nsTArray<RefPtr<Http3StreamBase>> mWebTransportSessions;
};
NS_DEFINE_STATIC_IID_ACCESSOR(Http3Session, NS_HTTP3SESSION_IID);

View File

@ -24,7 +24,7 @@ class Http3Stream final : public nsAHttpSegmentReader,
NS_DECL_NSAHTTPSEGMENTREADER
NS_DECL_NSAHTTPSEGMENTWRITER
// for RefPtr
NS_INLINE_DECL_REFCOUNTING(Http3Stream, override)
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Http3Stream, override)
Http3Stream(nsAHttpTransaction*, Http3Session*, const ClassOfService&,
uint64_t);

View File

@ -0,0 +1,351 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// HttpLog.h should generally be included first
#include "HttpLog.h"
#include "Http3WebTransportSession.h"
#include "Http3Session.h"
#include "Http3Stream.h"
#include "nsHttpRequestHead.h"
#include "nsHttpTransaction.h"
#include "nsIClassOfService.h"
#include "nsISocketTransport.h"
#include "nsSocketTransportService2.h"
#include "nsIOService.h"
#include "nsHttpHandler.h"
namespace mozilla::net {
Http3WebTransportSession::Http3WebTransportSession(nsAHttpTransaction* trans,
Http3Session* aHttp3Session)
: Http3StreamBase(trans, aHttp3Session) {}
nsresult Http3WebTransportSession::ReadSegments() {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
if ((mRecvState == RECV_DONE) || (mRecvState == ACTIVE) ||
(mRecvState == CLOSE_PENDING)) {
// Don't transmit any request frames if the peer cannot respond or respone
// is already done.
LOG3((
"Http3WebTransportSession %p ReadSegments request stream aborted due to"
" response side closure\n",
this));
return NS_ERROR_ABORT;
}
nsresult rv = NS_OK;
uint32_t transactionBytes;
bool again = true;
do {
transactionBytes = 0;
rv = mSocketOutCondition = NS_OK;
LOG(("Http3WebTransportSession::ReadSegments state=%d [this=%p]",
mSendState, this));
switch (mSendState) {
case PREPARING_HEADERS: {
rv = mTransaction->ReadSegmentsAgain(
this, nsIOService::gDefaultSegmentSize, &transactionBytes, &again);
} break;
case WAITING_TO_ACTIVATE: {
// A transaction that had already generated its headers before it was
// queued at the session level (due to concurrency concerns) may not
// call onReadSegment off the ReadSegments() stack above.
LOG3(
("Http3WebTransportSession %p ReadSegments forcing OnReadSegment "
"call\n",
this));
uint32_t wasted = 0;
nsresult rv2 = OnReadSegment("", 0, &wasted);
LOG3((" OnReadSegment returned 0x%08" PRIx32,
static_cast<uint32_t>(rv2)));
} break;
default:
transactionBytes = 0;
rv = NS_OK;
break;
}
LOG(("Http3WebTransportSession::ReadSegments rv=0x%" PRIx32
" read=%u sock-cond=%" PRIx32 " again=%d [this=%p]",
static_cast<uint32_t>(rv), transactionBytes,
static_cast<uint32_t>(mSocketOutCondition), again, this));
// XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF.
if (rv == NS_BASE_STREAM_CLOSED && !mTransaction->IsDone()) {
rv = NS_OK;
transactionBytes = 0;
}
if (NS_FAILED(rv)) {
// if the transaction didn't want to write any more data, then
// wait for the transaction to call ResumeSend.
if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
rv = NS_OK;
}
again = false;
} else if (NS_FAILED(mSocketOutCondition)) {
if (mSocketOutCondition != NS_BASE_STREAM_WOULD_BLOCK) {
rv = mSocketOutCondition;
}
again = false;
} else if (!transactionBytes) {
mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_WAITING_FOR, 0);
mSendState = SEND_DONE;
rv = NS_OK;
again = false;
}
// write more to the socket until error or end-of-request...
} while (again && gHttpHandler->Active());
return rv;
}
bool Http3WebTransportSession::ConsumeHeaders(const char* buf, uint32_t avail,
uint32_t* countUsed) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG3(("Http3WebTransportSession::ConsumeHeaders %p avail=%u.", this, avail));
mFlatHttpRequestHeaders.Append(buf, avail);
// We can use the simple double crlf because firefox is the
// only client we are parsing
int32_t endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n");
if (endHeader == kNotFound) {
// We don't have all the headers yet
LOG3(
("Http3WebTransportSession::ConsumeHeaders %p "
"Need more header bytes. Len = %zu",
this, mFlatHttpRequestHeaders.Length()));
*countUsed = avail;
return false;
}
uint32_t oldLen = mFlatHttpRequestHeaders.Length();
mFlatHttpRequestHeaders.SetLength(endHeader + 2);
*countUsed = avail - (oldLen - endHeader) + 4;
return true;
}
nsresult Http3WebTransportSession::TryActivating() {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("Http3WebTransportSession::TryActivating [this=%p]", this));
nsHttpRequestHead* head = mTransaction->RequestHead();
nsAutoCString host;
nsresult rv = head->GetHeader(nsHttp::Host, host);
if (NS_FAILED(rv)) {
MOZ_ASSERT(false);
return rv;
}
nsAutoCString path;
head->Path(path);
return mSession->TryActivating(""_ns, ""_ns, host, path,
mFlatHttpRequestHeaders, &mStreamId, this);
}
nsresult Http3WebTransportSession::OnReadSegment(const char* buf,
uint32_t count,
uint32_t* countRead) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("Http3WebTransportSession::OnReadSegment count=%u state=%d [this=%p]",
count, mSendState, this));
nsresult rv = NS_OK;
switch (mSendState) {
case PREPARING_HEADERS: {
if (!ConsumeHeaders(buf, count, countRead)) {
break;
}
mSendState = WAITING_TO_ACTIVATE;
}
[[fallthrough]];
case WAITING_TO_ACTIVATE:
rv = TryActivating();
if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
LOG3(
("Http3WebTransportSession::OnReadSegment %p cannot activate now. "
"queued.\n",
this));
break;
}
if (NS_FAILED(rv)) {
LOG3(
("Http3WebTransportSession::OnReadSegment %p cannot activate "
"error=0x%" PRIx32 ".",
this, static_cast<uint32_t>(rv)));
break;
}
// Successfully activated.
mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_SENDING_TO, 0);
mSendState = SEND_DONE;
break;
default:
MOZ_ASSERT(false, "We are done sending this request!");
rv = NS_ERROR_UNEXPECTED;
break;
}
mSocketOutCondition = rv;
return mSocketOutCondition;
}
nsresult Http3WebTransportSession::WriteSegments() {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("Http3WebTransportSession::WriteSegments [this=%p]", this));
nsresult rv = NS_OK;
uint32_t countWrittenSingle = 0;
bool again = true;
if (mRecvState == CLOSE_PENDING) {
mSession->CloseWebTransport(mStreamId, mStatus, mReason);
mRecvState = RECV_DONE;
// This will closed the steam because the stream is Done().
return NS_OK;
}
do {
mSocketInCondition = NS_OK;
countWrittenSingle = 0;
rv = mTransaction->WriteSegmentsAgain(
this, nsIOService::gDefaultSegmentSize, &countWrittenSingle, &again);
LOG(("Http3WebTransportSession::WriteSegments rv=0x%" PRIx32
" countWrittenSingle=%" PRIu32 " socketin=%" PRIx32 " [this=%p]",
static_cast<uint32_t>(rv), countWrittenSingle,
static_cast<uint32_t>(mSocketInCondition), this));
if (mTransaction->IsDone()) {
// An HTTP transaction used for setting up a WebTransport session will
// receive only response headers and afterward, it will be marked as
// done. At this point, the session negotiation has finished and the
// WebTransport session transfers into the ACTIVE state.
mRecvState = ACTIVE;
}
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 = false;
} else if (NS_FAILED(mSocketInCondition)) {
if (mSocketInCondition != NS_BASE_STREAM_WOULD_BLOCK) {
rv = mSocketInCondition;
}
again = false;
}
// read more from the socket until error...
} while (again && gHttpHandler->Active());
return rv;
}
void Http3WebTransportSession::SetResponseHeaders(
nsTArray<uint8_t>& aResponseHeaders, bool fin, bool interim) {
MOZ_ASSERT(mRecvState == BEFORE_HEADERS ||
mRecvState == READING_INTERIM_HEADERS);
mFlatResponseHeaders.AppendElements(aResponseHeaders);
mRecvState = (interim) ? READING_INTERIM_HEADERS : READING_HEADERS;
}
nsresult Http3WebTransportSession::OnWriteSegment(char* buf, uint32_t count,
uint32_t* countWritten) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("Http3WebTransportSession::OnWriteSegment [this=%p, state=%d", this,
mRecvState));
nsresult rv = NS_OK;
switch (mRecvState) {
case BEFORE_HEADERS: {
*countWritten = 0;
rv = NS_BASE_STREAM_WOULD_BLOCK;
} break;
case READING_HEADERS:
case READING_INTERIM_HEADERS: {
// SetResponseHeaders should have been previously called.
MOZ_ASSERT(!mFlatResponseHeaders.IsEmpty(), "Headers empty!");
*countWritten = (mFlatResponseHeaders.Length() > count)
? count
: mFlatResponseHeaders.Length();
memcpy(buf, mFlatResponseHeaders.Elements(), *countWritten);
mFlatResponseHeaders.RemoveElementsAt(0, *countWritten);
if (mFlatResponseHeaders.Length() == 0) {
if (mRecvState == READING_INTERIM_HEADERS) {
// neqo makes sure that fin cannot be received before the final
// headers are received.
mRecvState = BEFORE_HEADERS;
} else {
mRecvState = ACTIVE;
}
}
if (*countWritten == 0) {
rv = NS_BASE_STREAM_WOULD_BLOCK;
} else {
mTransaction->OnTransportStatus(nullptr, NS_NET_STATUS_RECEIVING_FROM,
0);
}
} break;
case ACTIVE:
case CLOSE_PENDING:
case RECV_DONE:
rv = NS_ERROR_UNEXPECTED;
}
// Remember the error received from lower layers. A stream pipe may overwrite
// it.
// If rv == NS_OK this will reset mSocketInCondition.
mSocketInCondition = rv;
return rv;
}
void Http3WebTransportSession::Close(nsresult aResult) {
if (mListener) {
mListener->OnSessionClosed(0, ""_ns);
mListener = nullptr;
}
if (mTransaction) {
mTransaction->Close(aResult);
mTransaction = nullptr;
}
mRecvState = RECV_DONE;
}
void Http3WebTransportSession::OnSessionClosed(uint32_t aStatus,
nsACString& aReason) {
MOZ_ASSERT(!mTransaction);
if (mListener) {
mListener->OnSessionClosed(aStatus, aReason);
mListener = nullptr;
}
mRecvState = RECV_DONE;
}
void Http3WebTransportSession::CloseSession(uint32_t aStatus,
nsACString& aReason) {
if ((mRecvState != CLOSE_PENDING) && (mRecvState != RECV_DONE)) {
mStatus = aStatus;
mReason = aReason;
mSession->ConnectSlowConsumer(this);
mRecvState = CLOSE_PENDING;
}
mListener = nullptr;
}
void Http3WebTransportSession::TransactionIsDone(nsresult aResult) {
mTransaction->Close(aResult);
mTransaction = nullptr;
}
} // namespace mozilla::net

View File

@ -0,0 +1,88 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_net_Http3WebTransportSession_h
#define mozilla_net_Http3WebTransportSession_h
#include "ARefBase.h"
#include "Http3StreamBase.h"
#include "nsIWebTransport.h"
#include "mozilla/WeakPtr.h"
namespace mozilla::net {
class Http3Session;
// TODO Http3WebTransportSession is very similar to Http3Stream. It should
// be built on top of it with a couple of small changes. The docs will be added
// when this is implemented.
class Http3WebTransportSession final : public Http3StreamBase,
public nsAHttpSegmentWriter,
public nsAHttpSegmentReader {
public:
NS_DECL_NSAHTTPSEGMENTWRITER
NS_DECL_NSAHTTPSEGMENTREADER
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Http3WebTransportSession, override)
Http3WebTransportSession(nsAHttpTransaction*, Http3Session*);
Http3WebTransportSession* GetHttp3WebTransportSession() override {
return this;
}
Http3Stream* GetHttp3Stream() override { return nullptr; }
[[nodiscard]] nsresult ReadSegments() override;
[[nodiscard]] nsresult WriteSegments() override;
bool Done() const override { return mRecvState == RECV_DONE; }
void Close(nsresult aResult) override;
void SetResponseHeaders(nsTArray<uint8_t>& aResponseHeaders, bool fin,
bool interim) override;
void SetWebTransportSessionEventListener(
WebTransportSessionEventListener* listener) {
mListener = listener;
}
nsresult TryActivating();
void TransactionIsDone(nsresult aResult);
void CloseSession(uint32_t aStatus, nsACString& aReason);
void OnSessionClosed(uint32_t aStatus, nsACString& aReason);
private:
~Http3WebTransportSession() = default;
bool ConsumeHeaders(const char* buf, uint32_t avail, uint32_t* countUsed);
enum RecvStreamState {
BEFORE_HEADERS,
READING_HEADERS,
READING_INTERIM_HEADERS,
ACTIVE,
CLOSE_PENDING,
RECV_DONE
} mRecvState{BEFORE_HEADERS};
enum SendStreamState {
PREPARING_HEADERS,
WAITING_TO_ACTIVATE,
SEND_DONE
} mSendState{PREPARING_HEADERS};
nsCString mFlatHttpRequestHeaders;
nsTArray<uint8_t> mFlatResponseHeaders;
nsresult mSocketInCondition = NS_ERROR_NOT_INITIALIZED;
nsresult mSocketOutCondition = NS_ERROR_NOT_INITIALIZED;
RefPtr<WebTransportSessionEventListener> mListener;
uint32_t mStatus{0};
nsCString mReason;
};
} // namespace mozilla::net
#endif // mozilla_net_Http3WebTransportSession_h

View File

@ -31,6 +31,7 @@ namespace net {
class nsHttpHandler;
class ASpdySession;
class Http3WebTransportSession;
// 1dcc863e-db90-4652-a1fe-13fea0b54e46
#define HTTPCONNECTIONBASE_IID \
@ -82,6 +83,11 @@ class HttpConnectionBase : public nsSupportsWeakReference {
nsIAsyncInputStream**,
nsIAsyncOutputStream**) = 0;
Http3WebTransportSession* GetWebTransportSession(
nsAHttpTransaction* aTransaction) {
return nullptr;
}
virtual bool UsingSpdy() { return false; }
virtual bool UsingHttp3() { return false; }

View File

@ -5,11 +5,11 @@
#ifndef HttpWinUtils_h__
#define HttpWinUtils_h__
class nsHttpChannel;
namespace mozilla {
namespace net {
class nsHttpChannel;
void AddWindowsSSO(nsHttpChannel* channel);
} // namespace net

View File

@ -114,6 +114,7 @@ UNIFIED_SOURCES += [
"Http2StreamWebSocket.cpp",
"Http3Session.cpp",
"Http3Stream.cpp",
"Http3WebTransportSession.cpp",
"HttpAuthUtils.cpp",
"HttpBackgroundChannelChild.cpp",
"HttpBackgroundChannelParent.cpp",

View File

@ -9,6 +9,7 @@
#include "nsHttp.h"
#include "nsISupports.h"
#include "nsAHttpTransaction.h"
#include "Http3WebTransportSession.h"
#include "HttpTrafficAnalyzer.h"
class nsIAsyncInputStream;
@ -106,6 +107,9 @@ class nsAHttpConnection : public nsISupports {
nsIAsyncInputStream**,
nsIAsyncOutputStream**) = 0;
[[nodiscard]] virtual Http3WebTransportSession* GetWebTransportSession(
nsAHttpTransaction* aTransaction) = 0;
// called by a transaction to get the TLS socket control from the socket.
virtual void GetTLSSocketControl(nsISSLSocketControl**) = 0;
@ -178,6 +182,8 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsAHttpConnection, NS_AHTTPCONNECTION_IID)
[[nodiscard]] nsresult TakeTransport( \
nsISocketTransport**, nsIAsyncInputStream**, nsIAsyncOutputStream**) \
override; \
[[nodiscard]] Http3WebTransportSession* GetWebTransportSession( \
nsAHttpTransaction* aTransaction) override; \
bool IsPersistent() override; \
bool IsReused() override; \
void DontReuse() override; \

View File

@ -34,6 +34,7 @@
#include "nsIStreamListenerTee.h"
#include "nsISeekableStream.h"
#include "nsIProtocolProxyService2.h"
#include "nsIWebTransport.h"
#include "nsCRT.h"
#include "nsMimeTypes.h"
#include "nsNetCID.h"
@ -1341,6 +1342,10 @@ nsresult nsHttpChannel::SetupTransaction() {
mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
}
if (mIsForWebTransport) {
mCaps |= NS_HTTP_STICKY_CONNECTION;
}
nsCOMPtr<nsIHttpPushListener> pushListener;
NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
NS_GET_IID(nsIHttpPushListener),
@ -1374,6 +1379,7 @@ nsresult nsHttpChannel::SetupTransaction() {
aResult.closeReason());
};
}
mTransaction->SetIsForWebTransport(mIsForWebTransport);
rv = mTransaction->Init(
mCaps, mConnectionInfo, &mRequestHead, mUploadStream, mReqContentLength,
LoadUploadStreamHasHeaders(), GetCurrentEventTarget(), callbacks, this,
@ -1934,6 +1940,10 @@ void nsHttpChannel::ProcessAltService() {
return;
}
if (mIsForWebTransport) {
return;
}
if (!gHttpHandler->AllowAltSvc() || (mCaps & NS_HTTP_DISALLOW_SPDY)) {
return;
}
@ -5792,6 +5802,10 @@ nsHttpChannel::AsyncOpen(nsIStreamListener* aListener) {
ReleaseListeners();
return rv;
}
nsCOMPtr<WebTransportSessionEventListener> wt = do_QueryInterface(listener);
mIsForWebTransport = !!wt;
MOZ_ASSERT(
mLoadInfo->GetSecurityMode() == 0 ||
mLoadInfo->GetInitialSecurityCheckDone() ||
@ -6100,8 +6114,14 @@ nsresult nsHttpChannel::BeginConnect() {
originAttributes, host, port, true);
} else {
#endif
connInfo = new nsHttpConnectionInfo(host, port, ""_ns, mUsername, proxyInfo,
originAttributes, isHttps);
if (mIsForWebTransport) {
connInfo =
new nsHttpConnectionInfo(host, port, "h3"_ns, mUsername, proxyInfo,
originAttributes, isHttps, true, true);
} else {
connInfo = new nsHttpConnectionInfo(host, port, ""_ns, mUsername,
proxyInfo, originAttributes, isHttps);
}
#ifdef FUZZING
}
#endif
@ -6115,7 +6135,8 @@ nsresult nsHttpChannel::BeginConnect() {
RefPtr<AltSvcMapping> mapping;
if (!mConnectionInfo && LoadAllowAltSvc() && // per channel
(http2Allowed || http3Allowed) && !(mLoadFlags & LOAD_FRESH_CONNECTION) &&
!mIsForWebTransport && (http2Allowed || http3Allowed) &&
!(mLoadFlags & LOAD_FRESH_CONNECTION) &&
AltSvcMapping::AcceptableProxy(proxyInfo) &&
(scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https")) &&
(mapping = gHttpHandler->GetAltServiceMapping(
@ -9777,5 +9798,11 @@ nsHttpChannel::EarlyHint(const nsACString& linkHeader) {
return NS_OK;
}
WebTransportSessionEventListener*
nsHttpChannel::GetWebTransportSessionEventListener() {
nsCOMPtr<WebTransportSessionEventListener> wt = do_QueryInterface(mListener);
return wt;
}
} // namespace net
} // namespace mozilla

View File

@ -267,6 +267,8 @@ class nsHttpChannel final : public HttpBaseChannel,
true>;
[[nodiscard]] RefPtr<ChildEndpointPromise> AttachStreamFilter();
WebTransportSessionEventListener* GetWebTransportSessionEventListener();
private: // used for alternate service validation
RefPtr<TransactionObserver> mTransactionObserver;
@ -844,6 +846,8 @@ class nsHttpChannel final : public HttpBaseChannel,
RefPtr<nsIEarlyHintObserver> mEarlyHintObserver;
Maybe<nsCString> mOpenerCallingScriptLocation;
bool mIsForWebTransport{false};
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsHttpChannel, NS_HTTPCHANNEL_IID)

View File

@ -24,6 +24,7 @@
#include "nsCRT.h"
#include "nsComponentManagerUtils.h" // do_CreateInstance
#include "nsHttpBasicAuth.h"
#include "nsHttpChannel.h"
#include "nsHttpChunkedDecoder.h"
#include "nsHttpDigestAuth.h"
#include "nsHttpHandler.h"
@ -434,6 +435,13 @@ nsresult nsHttpTransaction::AsyncRead(nsIStreamListener* listener,
transactionPump.forget(pump);
MutexAutoLock lock(mLock);
mEarlyHintObserver = do_QueryInterface(listener);
RefPtr<nsHttpChannel> httpChannel = do_QueryObject(listener);
if (httpChannel) {
mWebTransportSessionEventListener =
httpChannel->GetWebTransportSessionEventListener();
}
return NS_OK;
}
@ -1350,6 +1358,7 @@ void nsHttpTransaction::Close(nsresult reason) {
{
MutexAutoLock lock(mLock);
mEarlyHintObserver = nullptr;
mWebTransportSessionEventListener = nullptr;
}
if (!mClosed) {
@ -2178,6 +2187,22 @@ nsresult nsHttpTransaction::HandleContentStart() {
// check if this is a no-content response
switch (mResponseHead->Status()) {
case 200: {
if (!mIsForWebTransport) {
break;
}
RefPtr<Http3WebTransportSession> wtSession =
mConnection->GetWebTransportSession(this);
if (wtSession) {
mWebTransportSessionEventListener->OnSessionReadyInternal(wtSession);
wtSession->SetWebTransportSessionEventListener(
mWebTransportSessionEventListener);
}
mWebTransportSessionEventListener = nullptr;
}
// Fall through to WebSocket cases (nsHttpTransaction behaviar is the
// same):
[[fallthrough]];
case 101:
mPreserveStream = true;
[[fallthrough]]; // to other no content cases:

View File

@ -24,6 +24,7 @@
#include "nsIInterfaceRequestor.h"
#include "nsISSLSocketControl.h"
#include "nsITimer.h"
#include "nsIWebTransport.h"
#include "nsTHashMap.h"
#include "nsThreadUtils.h"
@ -180,6 +181,8 @@ class nsHttpTransaction final : public nsAHttpTransaction,
void GetHashKeyOfConnectionEntry(nsACString& aResult);
bool IsForWebTransport() { return mIsForWebTransport; }
private:
friend class DeleteHttpTransaction;
virtual ~nsHttpTransaction();
@ -548,7 +551,7 @@ class nsHttpTransaction final : public nsAHttpTransaction,
nsTHashMap<nsUint32HashKey, uint32_t> mEchRetryCounterMap;
bool mSupportsHTTP3 = false;
bool mIsForWebTransport{false};
Atomic<bool, Relaxed> mIsForWebTransport{false};
bool mEarlyDataWasAvailable = false;
bool ShouldRestartOn0RttError(nsresult reason);
@ -560,6 +563,8 @@ class nsHttpTransaction final : public nsAHttpTransaction,
// be associated with the connection entry whose hash key is not the same as
// this transaction's.
nsCString mHashKeyOfConnectionEntry;
nsCOMPtr<WebTransportSessionEventListener> mWebTransportSessionEventListener;
};
} // namespace mozilla::net

View File

@ -7,4 +7,4 @@
DIRS += ["about", "data", "file"]
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
DIRS += ["gio"]
DIRS += ["http", "res", "viewsource", "websocket"]
DIRS += ["http", "res", "viewsource", "websocket", "webtransport"]

View File

@ -0,0 +1,21 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef WebTransportLog_h
#define WebTransportLog_h
#include "mozilla/Logging.h"
#include "mozilla/net/NeckoChild.h"
namespace mozilla::net {
extern LazyLogModule webTransportLog;
} // namespace mozilla::net
#undef LOG
#define LOG(args) \
MOZ_LOG(mozilla::net::webTransportLog, mozilla::LogLevel::Debug, args)
#endif

View File

@ -0,0 +1,545 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "WebTransportLog.h"
#include "Http3WebTransportSession.h"
#include "WebTransportSessionProxy.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "nsIHttpChannel.h"
#include "nsIRequest.h"
#include "nsNetUtil.h"
#include "nsSocketTransportService2.h"
#include "mozilla/Logging.h"
namespace mozilla::net {
LazyLogModule webTransportLog("nsWebTransport");
NS_IMPL_ISUPPORTS(WebTransportSessionProxy, WebTransportSessionEventListener,
nsIWebTransport, nsIRedirectResultListener, nsIStreamListener,
nsIChannelEventSink, nsIInterfaceRequestor);
WebTransportSessionProxy::WebTransportSessionProxy()
: mMutex("WebTransportSessionProxy::mMutex") {
LOG(("WebTransportSessionProxy constructor"));
}
WebTransportSessionProxy::~WebTransportSessionProxy() {
if (OnSocketThread()) {
return;
}
MutexAutoLock lock(mMutex);
if ((mState != WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED) &&
(mState != WebTransportSessionProxyState::ACTIVE) &&
(mState != WebTransportSessionProxyState::SESSION_CLOSE_PENDING)) {
return;
}
MOZ_ASSERT(mState != WebTransportSessionProxyState::SESSION_CLOSE_PENDING,
"We can not be in the SESSION_CLOSE_PENDING state in destructor, "
"because should e an runnable that holds reference to this"
"object.");
Unused << gSocketTransportService->Dispatch(NS_NewRunnableFunction(
"WebTransportSessionProxy::ProxyHttp3WebTransportSessionRelease",
[self{std::move(mWebTransportSession)}]() {}));
}
//-----------------------------------------------------------------------------
// WebTransportSessionProxy::nsIWebTransport
//-----------------------------------------------------------------------------
nsresult WebTransportSessionProxy::AsyncConnect(
nsIURI* aURI, nsIPrincipal* aPrincipal, uint32_t aSecurityFlags,
WebTransportSessionEventListener* aListener) {
MOZ_ASSERT(NS_IsMainThread());
LOG(("WebTransportSessionProxy::AsyncConnect"));
mListener = aListener;
nsSecurityFlags flags = nsILoadInfo::SEC_COOKIES_OMIT | aSecurityFlags;
nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
nsIRequest::LOAD_BYPASS_CACHE |
nsIRequest::INHIBIT_CACHING;
nsresult rv = NS_NewChannel(getter_AddRefs(mChannel), aURI, aPrincipal, flags,
nsContentPolicyType::TYPE_OTHER,
/* aCookieJarSettings */ nullptr,
/* aPerformanceStorage */ nullptr,
/* aLoadGroup */ nullptr,
/* aCallbacks */ this, loadFlags);
NS_ENSURE_SUCCESS(rv, rv);
// configure HTTP specific stuff
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
if (!httpChannel) {
mChannel = nullptr;
return NS_ERROR_ABORT;
}
{
MutexAutoLock lock(mMutex);
ChangeState(WebTransportSessionProxyState::NEGOTIATING);
}
rv = mChannel->AsyncOpen(this);
if (NS_FAILED(rv)) {
MutexAutoLock lock(mMutex);
ChangeState(WebTransportSessionProxyState::DONE);
}
return rv;
}
NS_IMETHODIMP
WebTransportSessionProxy::GetStats() { return NS_ERROR_NOT_IMPLEMENTED; }
NS_IMETHODIMP
WebTransportSessionProxy::CloseSession(uint32_t status,
const nsACString& reason) {
MOZ_ASSERT(NS_IsMainThread());
MutexAutoLock lock(mMutex);
mCloseStatus = status;
mReason = reason;
mListener = nullptr;
switch (mState) {
case WebTransportSessionProxyState::INIT:
case WebTransportSessionProxyState::DONE:
return NS_ERROR_NOT_INITIALIZED;
case WebTransportSessionProxyState::NEGOTIATING:
mChannel->Cancel(NS_ERROR_ABORT);
mChannel = nullptr;
ChangeState(WebTransportSessionProxyState::DONE);
break;
case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
mChannel->Cancel(NS_ERROR_ABORT);
mChannel = nullptr;
ChangeState(WebTransportSessionProxyState::SESSION_CLOSE_PENDING);
CloseSessionInternal();
break;
case WebTransportSessionProxyState::ACTIVE:
ChangeState(WebTransportSessionProxyState::SESSION_CLOSE_PENDING);
CloseSessionInternal();
break;
case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
ChangeState(WebTransportSessionProxyState::DONE);
break;
case SESSION_CLOSE_PENDING:
break;
}
return NS_OK;
}
void WebTransportSessionProxy::CloseSessionInternal() {
if (!OnSocketThread()) {
mMutex.AssertCurrentThreadOwns();
RefPtr<WebTransportSessionProxy> self(this);
Unused << gSocketTransportService->Dispatch(NS_NewRunnableFunction(
"WebTransportSessionProxy::CallCloseWebTransportSession",
[self{std::move(self)}]() { self->CloseSessionInternal(); }));
return;
}
RefPtr<Http3WebTransportSession> wt;
uint32_t closeStatus = 0;
nsCString reason;
{
MutexAutoLock lock(mMutex);
if (mState == WebTransportSessionProxyState::SESSION_CLOSE_PENDING) {
MOZ_ASSERT(mWebTransportSession);
wt = mWebTransportSession;
mWebTransportSession = nullptr;
closeStatus = mCloseStatus;
reason = mReason;
ChangeState(WebTransportSessionProxyState::DONE);
} else {
MOZ_ASSERT(mState == WebTransportSessionProxyState::DONE);
}
}
if (wt) {
wt->CloseSession(closeStatus, reason);
}
}
NS_IMETHODIMP
WebTransportSessionProxy::CreateOutgoingUnidirectionalStream(
nsIWebTransportStreamCallback* callback) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
WebTransportSessionProxy::CreateOutgoingBidirectionalStream(
nsIWebTransportStreamCallback* callback) {
return NS_ERROR_NOT_IMPLEMENTED;
}
//-----------------------------------------------------------------------------
// WebTransportSessionProxy::nsIStreamListener
//-----------------------------------------------------------------------------
NS_IMETHODIMP
WebTransportSessionProxy::OnStartRequest(nsIRequest* aRequest) {
MOZ_ASSERT(NS_IsMainThread());
LOG(("WebTransportSessionProxy::OnStartRequest\n"));
nsCOMPtr<WebTransportSessionEventListener> listener;
nsAutoCString reason;
uint32_t closeStatus = 0;
{
MutexAutoLock lock(mMutex);
switch (mState) {
case WebTransportSessionProxyState::INIT:
case WebTransportSessionProxyState::DONE:
case WebTransportSessionProxyState::ACTIVE:
case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
MOZ_ASSERT(false, "OnStartRequest cannot be called in this state.");
break;
case WebTransportSessionProxyState::NEGOTIATING:
case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
listener = mListener;
mListener = nullptr;
mChannel = nullptr;
reason = mReason;
closeStatus = mCloseStatus;
ChangeState(WebTransportSessionProxyState::DONE);
break;
case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED: {
uint32_t status;
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
if (!httpChannel ||
NS_FAILED(httpChannel->GetResponseStatus(&status)) ||
status != 200) {
listener = mListener;
mListener = nullptr;
mChannel = nullptr;
mReason = ""_ns;
reason = ""_ns;
mCloseStatus =
0; // TODO: find a better error. Currently error code 0 is used
ChangeState(WebTransportSessionProxyState::SESSION_CLOSE_PENDING);
CloseSessionInternal(); // TODO: find a better error. Currently error
// code 0 is used.
}
// The success cases will be handled in OnStopRequest.
} break;
}
}
if (listener) {
listener->OnSessionClosed(closeStatus, reason);
}
return NS_OK;
}
NS_IMETHODIMP
WebTransportSessionProxy::OnDataAvailable(nsIRequest* aRequest,
nsIInputStream* aStream,
uint64_t aOffset, uint32_t aCount) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_RELEASE_ASSERT(
false, "WebTransportSessionProxy::OnDataAvailable should not be called");
return NS_OK;
}
NS_IMETHODIMP
WebTransportSessionProxy::OnStopRequest(nsIRequest* aRequest,
nsresult aStatus) {
MOZ_ASSERT(NS_IsMainThread());
mChannel = nullptr;
nsCOMPtr<WebTransportSessionEventListener> listener;
nsAutoCString reason;
uint32_t closeStatus = 0;
uint64_t sessionId;
bool succeeded = false;
{
MutexAutoLock lock(mMutex);
switch (mState) {
case WebTransportSessionProxyState::INIT:
case WebTransportSessionProxyState::ACTIVE:
case WebTransportSessionProxyState::NEGOTIATING:
MOZ_ASSERT(false, "OnStotRequest cannot be called in this state.");
break;
case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
reason = mReason;
closeStatus = mCloseStatus;
listener = mListener;
mListener = nullptr;
ChangeState(WebTransportSessionProxyState::DONE);
break;
case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
if (NS_FAILED(aStatus)) {
listener = mListener;
mListener = nullptr;
mReason = ""_ns;
reason = ""_ns;
mCloseStatus = 0;
ChangeState(WebTransportSessionProxyState::SESSION_CLOSE_PENDING);
CloseSessionInternal(); // TODO: find a better error. Currently error
// code 0 is used.
} else {
succeeded = true;
sessionId = mSessionId;
listener = mListener;
ChangeState(WebTransportSessionProxyState::ACTIVE);
}
break;
case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
case WebTransportSessionProxyState::DONE:
break;
}
}
if (listener) {
if (succeeded) {
listener->OnSessionReady(sessionId);
} else {
listener->OnSessionClosed(closeStatus,
reason); // TODO: find a better error.
// Currently error code 0 is used.
}
}
return NS_OK;
}
//-----------------------------------------------------------------------------
// WebTransportSessionProxy::nsIChannelEventSink
//-----------------------------------------------------------------------------
NS_IMETHODIMP
WebTransportSessionProxy::AsyncOnChannelRedirect(
nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
nsIAsyncVerifyRedirectCallback* callback) {
nsCOMPtr<nsIURI> newURI;
nsresult rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
NS_ENSURE_SUCCESS(rv, rv);
rv = aNewChannel->GetURI(getter_AddRefs(newURI));
if (NS_FAILED(rv)) {
callback->OnRedirectVerifyCallback(rv);
return NS_OK;
}
// abort the request if redirecting to insecure context
if (!newURI->SchemeIs("https")) {
callback->OnRedirectVerifyCallback(NS_ERROR_ABORT);
return NS_OK;
}
// Assign to mChannel after we get notification about success of the
// redirect in OnRedirectResult.
mRedirectChannel = aNewChannel;
callback->OnRedirectVerifyCallback(NS_OK);
return NS_OK;
}
//-----------------------------------------------------------------------------
// WebTransportSessionProxy::nsIRedirectResultListener
//-----------------------------------------------------------------------------
NS_IMETHODIMP
WebTransportSessionProxy::OnRedirectResult(bool aProceeding) {
if (aProceeding && mRedirectChannel) {
mChannel = mRedirectChannel;
}
mRedirectChannel = nullptr;
return NS_OK;
}
//-----------------------------------------------------------------------------
// WebTransportSessionProxy::nsIInterfaceRequestor
//-----------------------------------------------------------------------------
NS_IMETHODIMP
WebTransportSessionProxy::GetInterface(const nsIID& aIID, void** aResult) {
if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
NS_ADDREF_THIS();
*aResult = static_cast<nsIChannelEventSink*>(this);
return NS_OK;
}
if (aIID.Equals(NS_GET_IID(nsIRedirectResultListener))) {
NS_ADDREF_THIS();
*aResult = static_cast<nsIRedirectResultListener*>(this);
return NS_OK;
}
return NS_ERROR_NO_INTERFACE;
}
//-----------------------------------------------------------------------------
// WebTransportSessionProxy::WebTransportSessionEventListener
//-----------------------------------------------------------------------------
// This function is called when the Http3WebTransportSession is ready. After
// this call WebTransportSessionProxy is responsible for the
// Http3WebTransportSession, i.e. it is responsible for closing it.
// The listener of the WebTransportSessionProxy will be informed during
// OnStopRequest call.
NS_IMETHODIMP
WebTransportSessionProxy::OnSessionReadyInternal(
Http3WebTransportSession* aSession) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("WebTransportSessionProxy::OnSessionReadyInternal"));
MutexAutoLock lock(mMutex);
switch (mState) {
case WebTransportSessionProxyState::INIT:
case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
case WebTransportSessionProxyState::ACTIVE:
case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
MOZ_ASSERT(false,
"OnSessionReadyInternal cannot be called in this state.");
return NS_ERROR_ABORT;
case WebTransportSessionProxyState::NEGOTIATING:
mWebTransportSession = aSession;
mSessionId = aSession->StreamId();
ChangeState(WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED);
break;
case WebTransportSessionProxyState::DONE:
// The session has been canceled. We do not need to set
// mWebTransportSession.
break;
}
return NS_OK;
}
NS_IMETHODIMP
WebTransportSessionProxy::OnSessionReady(uint64_t ready) {
MOZ_ASSERT(false, "Should not b called");
return NS_OK;
}
NS_IMETHODIMP
WebTransportSessionProxy::OnSessionClosed(uint32_t status,
const nsACString& reason) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
LOG(("WebTransportSessionProxy::OnSessionClosed"));
MutexAutoLock lock(mMutex);
switch (mState) {
case WebTransportSessionProxyState::INIT:
case WebTransportSessionProxyState::NEGOTIATING:
case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
MOZ_ASSERT(false, "OnSessionClosed cannot be called in this state.");
return NS_ERROR_ABORT;
case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
case WebTransportSessionProxyState::ACTIVE: {
mCloseStatus = status;
mReason = reason;
mWebTransportSession = nullptr;
ChangeState(WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING);
RefPtr<WebTransportSessionProxy> self(this);
Unused << NS_DispatchToMainThread(NS_NewRunnableFunction(
"WebTransportSessionProxy::CallOnSessionClose",
[self{std::move(self)}]() { self->CallOnSessionClosed(); }));
} break;
case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
ChangeState(WebTransportSessionProxyState::DONE);
break;
case WebTransportSessionProxyState::DONE:
// The session has been canceled. We do not need to set
// mWebTransportSession.
break;
}
return NS_OK;
}
void WebTransportSessionProxy::CallOnSessionClosed() {
MOZ_ASSERT(NS_IsMainThread(), "not on socket thread");
nsCOMPtr<WebTransportSessionEventListener> listener;
nsAutoCString reason;
uint32_t closeStatus = 0;
{
MutexAutoLock lock(mMutex);
switch (mState) {
case WebTransportSessionProxyState::INIT:
case WebTransportSessionProxyState::NEGOTIATING:
case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
case WebTransportSessionProxyState::ACTIVE:
case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
MOZ_ASSERT(false,
"CallOnSessionClosed cannot be called in this state.");
break;
case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
listener = mListener;
mListener = nullptr;
reason = mReason;
closeStatus = mCloseStatus;
ChangeState(WebTransportSessionProxyState::DONE);
break;
case WebTransportSessionProxyState::DONE:
break;
}
}
if (listener) {
listener->OnSessionClosed(closeStatus, reason);
}
}
void WebTransportSessionProxy::ChangeState(
WebTransportSessionProxyState newState) {
mMutex.AssertCurrentThreadOwns();
LOG(("WebTransportSessionProxy::ChangeState %d -> %d [this=%p]", mState,
newState, this));
switch (newState) {
case WebTransportSessionProxyState::INIT:
MOZ_ASSERT(false, "Cannot change into INIT sate.");
break;
case WebTransportSessionProxyState::NEGOTIATING:
MOZ_ASSERT(mState == WebTransportSessionProxyState::INIT,
"Only from INIT can be change into NEGOTIATING");
MOZ_ASSERT(mChannel);
MOZ_ASSERT(mListener);
break;
case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
MOZ_ASSERT(
mState == WebTransportSessionProxyState::NEGOTIATING,
"Only from NEGOTIATING can be change into NEGOTIATING_SUCCEEDED");
MOZ_ASSERT(mChannel);
MOZ_ASSERT(mWebTransportSession);
MOZ_ASSERT(mListener);
break;
case WebTransportSessionProxyState::ACTIVE:
MOZ_ASSERT(mState == WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED,
"Only from NEGOTIATING_SUCCEEDED can be change into ACTIVE");
MOZ_ASSERT(!mChannel);
MOZ_ASSERT(mWebTransportSession);
MOZ_ASSERT(mListener);
break;
case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
MOZ_ASSERT(
(mState == WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED) ||
(mState == WebTransportSessionProxyState::ACTIVE),
"Only from NEGOTIATING_SUCCEEDED and ACTIVE can be change into"
" SESSION_CLOSE_PENDING");
MOZ_ASSERT(!mChannel);
MOZ_ASSERT(mWebTransportSession);
MOZ_ASSERT(!mListener);
break;
case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
MOZ_ASSERT(
(mState == WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED) ||
(mState == WebTransportSessionProxyState::ACTIVE),
"Only from NEGOTIATING_SUCCEEDED and ACTIVE can be change into"
" CLOSE_CALLBACK_PENDING");
MOZ_ASSERT(!mWebTransportSession);
MOZ_ASSERT(mListener);
break;
case WebTransportSessionProxyState::DONE:
MOZ_ASSERT(
(mState == WebTransportSessionProxyState::NEGOTIATING) ||
(mState ==
WebTransportSessionProxyState::SESSION_CLOSE_PENDING) ||
(mState == WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING),
"Only from NEGOTIATING, SESSION_CLOSE_PENDING and "
"CLOSE_CALLBACK_PENDING can be change into DONE");
MOZ_ASSERT(!mChannel);
MOZ_ASSERT(!mWebTransportSession);
MOZ_ASSERT(!mListener);
break;
}
mState = newState;
}
} // namespace mozilla::net

View File

@ -0,0 +1,168 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_net_WebTransportProxy_h
#define mozilla_net_WebTransportProxy_h
#include "nsIChannelEventSink.h"
#include "nsIInterfaceRequestor.h"
#include "nsIRedirectResultListener.h"
#include "nsIStreamListener.h"
#include "nsIWebTransport.h"
/*
* WebTransportSessionProxy is introduced to enable the creation of a
* Http3WebTransportSession and coordination of actions that are performed on
* the main thread and on the socket thread.
*
* mChannel, mRedirectChannel, and mListener are used only on the main thread.
*
* mWebTransportSession is used only on the socket thread.
*
* mState and mSessionId are used on both threads, socket and main thread and it
* is only used with a lock.
*
*
* WebTransportSessionProxyState:
* - INIT: before AsyncConnect is called.
*
* - NEGOTIATING: It is set during AsyncConnect. During this state HttpChannel
* is open but OnStartRequest has not been called yet. This state can
* transfer into:
* - NEGOTIATING_SUCCEEDED: when a Http3WebTransportSession has been
* negotiated.
* - DONE: when a WebTransport session has been canceled.
*
* - NEGOTIATING_SUCCEEDED: It is set during parsing of
* Http3WebTransportSession response when the response has been successful.
* mWebTransport is set to the Http3WebTransportSession at the same time the
* session changes to this state. This state can transfer into:
* - ACTIVE: during the OnStopRequest call if the WebTransport has not been
* canceled or failed for other reason, e.g. a browser shutdown or content
* blocking policies.
* - SESSION_CLOSE_PENDING: if the WebTransport has been canceled via an API
* call or content blocking policies. (the main thread initiated close).
* - CLOSE_CALLBACK_PENDING: if Http3WebTransportSession has been canceled
* due to a shutdown or a server closing a session. (the socket thread
* initiated close).
*
* - ACTIVE: In this state the session is negotiated and ready to use. This
* state can transfer into:
* - SESSION_CLOSE_PENDING: if the WebTransport has been canceled via an API
* call(nsIWebTransport::closeSession) or content blocking policies. (the
* main thread initiated close).
* - CLOSE_CALLBACK_PENDING: if Http3WebTransportSession has been canceled
* due to a shutdown or a server closing a session. (the socket thread
* initiated close).
*
* - CLOSE_CALLBACK_PENDING: This is the socket thread initiated close. In this
* state, the Http3WebTransportSession has been closed and a
* CallOnSessionClosed call is dispatched to the main thread to call the
* appropriate listener.
*
* - SESSION_CLOSE_PENDING: This is the main thread initiated close. In this
* state, the WebTransport has been closed via an API call
* (nsIWebTransport::closeSession) and a CloseSessionInternal call is
* dispatched to the socket thread to close the appropriate
* Http3WebTransportSession.
*
* - DONE: everything has been cleaned up on both threads.
*
*
* AsyncConnect creates mChannel on the main thread. Redirect callbacks are also
* performed on the main thread (mRedirectChannel set and access only on the
* main thread). Before this point, there are no activities on the socket thread
* and Http3WebTransportSession is nullptr. mChannel is going to create a
* nsHttpTransaction. The transaction will be dispatched on a nsAHttpConnection,
* i.e. currently only the HTTP/3 version is implemented, therefore this will be
* a HttpConnectionUDP and a Http3Session. The Http3Session creates a
* Http3WebTransportSession. Until a response is received
* Http3WebTransportSession is only accessed by Http3Session. During parsing of
* a successful received from a server on the socket thread,
* WebTransportSessionProxy::mWebTransportSession will take a reference to
* Http3WebTransportSession and mState will be set to NEGOTIATING_SUCCEEDED.
* From now on WebTransportSessionProxy is responsible for closing
* Http3WebTransportSession if the closing of the session is initiated on the
* main thread. OnStartRequest and OnStopRequest will be called on the main
* thread. The session negotiation can have 2 outcomes:
* - If both calls, i.e. OnStartRequest an OnStopRequest, indicate that the
* request has succeeded and mState is NEGOTIATING_SUCCEEDED, the
* mListener->OnSessionReady will be called during OnStopRequest.
* - Otherwise, mListener->OnSessionClosed will be called, the state transferred
* into SESSION_CLOSE_PENDING, and CloseSessionInternal will be dispatched to
* the socket thread.
*
* CloseSession is called on the main thread. If the session is already closed
* it returns an error. If the session is in state NEGOTIATING or
* NEGOTIATING_SUCCEEDED mChannel will be canceled. If the session is in state
* NEGOTIATING_SUCCEEDED or ACTIVE the state transferred into
* SESSION_CLOSE_PENDING, and CloseSessionInternal will be dispatched to the
* socket thread
*
* OnSessionReadyInternal is called on the socket thread. If mState is
* NEGOTIATING the state will be set to NEGOTIATING_SUCCEEDED and mWebTransport
* will be set to the newly negotiated Http3WebTransportSession. If mState is
* DONE, the Http3WebTransportSession will be close.
*
* OnSessionClosed is called on the socket thread. mState will be set to
* CLOSE_CALLBACK_PENDING and CallOnSessionClosed will be dispatched to the main
* thread.
*
* mWebTransport is set during states NEGOTIATING_SUCCEEDED, ACTIVE and
* SESSION_CLOSE_PENDING.
*/
namespace mozilla::net {
class WebTransportSessionProxy final : public nsIWebTransport,
public WebTransportSessionEventListener,
public nsIStreamListener,
public nsIChannelEventSink,
public nsIRedirectResultListener,
public nsIInterfaceRequestor {
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIWEBTRANSPORT
NS_DECL_WEBTRANSPORTSESSIONEVENTLISTENER
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSICHANNELEVENTSINK
NS_DECL_NSIREDIRECTRESULTLISTENER
NS_DECL_NSIINTERFACEREQUESTOR
WebTransportSessionProxy();
private:
~WebTransportSessionProxy();
void CloseSessionInternal();
void CallOnSessionClosed();
enum WebTransportSessionProxyState {
INIT,
NEGOTIATING,
NEGOTIATING_SUCCEEDED,
ACTIVE,
CLOSE_CALLBACK_PENDING,
SESSION_CLOSE_PENDING,
DONE,
};
mozilla::Mutex mMutex;
WebTransportSessionProxyState mState MOZ_GUARDED_BY(mMutex) =
WebTransportSessionProxyState::INIT;
void ChangeState(WebTransportSessionProxyState newState);
nsCOMPtr<nsIChannel> mChannel;
nsCOMPtr<nsIChannel> mRedirectChannel;
nsCOMPtr<WebTransportSessionEventListener> mListener;
RefPtr<Http3WebTransportSession> mWebTransportSession;
uint64_t mSessionId MOZ_GUARDED_BY(mMutex) = UINT64_MAX;
uint32_t mCloseStatus MOZ_GUARDED_BY(mMutex) = 0;
nsCString mReason MOZ_GUARDED_BY(mMutex);
};
} // namespace mozilla::net
#endif // mozilla_net_WebTransportProxy_h

View File

@ -0,0 +1,31 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
with Files("**"):
BUG_COMPONENT = ("Core", "Networking: WebTransport")
XPIDL_SOURCES += [
"nsIWebTransport.idl",
]
XPIDL_MODULE = "necko_webtransport"
EXPORTS.mozilla.net += [
"WebTransportSessionProxy.h",
]
UNIFIED_SOURCES += [
"WebTransportSessionProxy.cpp",
]
FINAL_LIBRARY = "xul"
LOCAL_INCLUDES += [
"/netwerk/base",
"/netwerk/protocol/http",
]
include("/ipc/chromium/chromium-config.mozbuild")

View File

@ -0,0 +1,76 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
#include "nsIURI.idl"
#include "nsIPrincipal.idl"
interface WebTransportSessionEventListener;
interface nsIWebTransportStreamCallback;
%{C++
namespace mozilla::net {
class Http3WebTransportSession;
}
%}
[ptr] native Http3WebTransportSessionPtr(mozilla::net::Http3WebTransportSession);
[builtinclass, scriptable, uuid(c20d6e77-8cb1-4838-a88d-fff826080aa3)]
interface nsIWebTransport : nsISupports {
// When called, perform steps in "Initialization WebTransport over HTTP".
void asyncConnect(in nsIURI aURI,
in nsIPrincipal aLoadingPrincipal,
in unsigned long aSecurityFlags,
in WebTransportSessionEventListener aListener);
// Asynchronously get states.
void getStats();
// Close the session.
void closeSession(in uint32_t aErrorCode,
in ACString aReason);
// Create and open a new WebTransport stream. Returns a nsICancelable
// object, so the consumer can cancel this operation.
void createOutgoingBidirectionalStream(in nsIWebTransportStreamCallback aListener);
void createOutgoingUnidirectionalStream(in nsIWebTransportStreamCallback aListener);
// bool sendDatagram(in AUTF8String aDatagram);
};
// Events related to a WebTransport session.
[scriptable, uuid(0e3cb269-f318-43c8-959e-897f57894b71)]
interface WebTransportSessionEventListener : nsISupports {
// This is used to let the consumer of nsIWebTransport know that the
// underlying WebTransportSession object is ready to use.
void onSessionReady(in uint64_t aSessionId);
// This is used internally to pass the reference of WebTransportSession
// object to WebTransportSessionProxy.
void onSessionReadyInternal(in Http3WebTransportSessionPtr aSession);
void onSessionClosed(in uint32_t aErrorCode,
in ACString aReason);
// When a new stream has been received.
// void onIncomingBidirectionalStreamAvailable(in nsIWebTransportBidirectionalStream aStream);
// void onIncomingUnidirectionalStreamAvailable(in nsIWebTransportReceiveStream aStream);
// When a new stream has been opened.
// void onOutgoingBidirectionalStreamAvailable(in nsIWebTransportBidirectionalStream aStream);
// void onOutgoingUnidirectionalStreamAvailable(in nsIWebTransportSendStream aStream);
// When a new datagram has been received.
// void onDatagramReceived(in AUTF8String aDatagram);
// void onStatsAvailable(in WebTransportStats aStats);
};
// This interface is used as a callback when creating an outgoing
// unidirectional or bidirectional stream.
[scriptable, uuid(c6eeff1d-599b-40a8-9157-c7a40c3d51a2)]
interface nsIWebTransportStreamCallback : nsISupports {
// void onStreamReady(in nsIWebTransportSendStream aStream);
void onError(in uint8_t aError);
};

View File

@ -661,10 +661,7 @@ impl SessionCloseReasonExternal {
#[repr(C)]
pub enum WebTransportEventExternal {
Negotiated(bool),
Session {
stream_id: u64,
status: u16,
},
Session(u64),
SessionClosed {
stream_id: u64,
reason: SessionCloseReasonExternal,
@ -681,17 +678,23 @@ impl WebTransportEventExternal {
match event {
WebTransportEvent::Negotiated(n) => WebTransportEventExternal::Negotiated(n),
WebTransportEvent::Session { stream_id, status } => {
WebTransportEventExternal::Session {
stream_id: stream_id.as_u64(),
status,
}
data.extend_from_slice(b"HTTP/3 ");
data.extend_from_slice(&status.to_string().as_bytes());
data.extend_from_slice(b"\r\n\r\n");
WebTransportEventExternal::Session(stream_id.as_u64())
}
WebTransportEvent::SessionClosed { stream_id, reason } => {
WebTransportEventExternal::SessionClosed {
WebTransportEvent::SessionClosed { stream_id, reason } => match reason {
SessionCloseReason::Status(status) => {
data.extend_from_slice(b"HTTP/3 ");
data.extend_from_slice(&status.to_string().as_bytes());
data.extend_from_slice(b"\r\n\r\n");
WebTransportEventExternal::Session(stream_id.as_u64())
}
_ => WebTransportEventExternal::SessionClosed {
stream_id: stream_id.as_u64(),
reason: SessionCloseReasonExternal::new(reason, data),
}
}
},
},
WebTransportEvent::NewStream {
stream_id,
session_id,

View File

@ -10,6 +10,7 @@ use neqo_common::{event::Provider, qdebug, qinfo, qtrace, Datagram, Header};
use neqo_crypto::{generate_ech_keys, init_db, AllowZeroRtt, AntiReplay};
use neqo_http3::{
Error, Http3OrWebTransportStream, Http3Parameters, Http3Server, Http3ServerEvent,
WebTransportRequest, WebTransportServerEvent,
};
use neqo_transport::server::Server;
use neqo_transport::{ConnectionEvent, ConnectionParameters, Output, RandomConnectionIdGenerator};
@ -27,6 +28,7 @@ use core::fmt::Display;
use mio::net::UdpSocket;
use mio::{Events, Poll, PollOpt, Ready, Token};
use mio_extras::timer::{Builder, Timeout, Timer};
use std::cmp::{max, min};
use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap;
use std::collections::HashSet;
@ -52,6 +54,9 @@ const HTTP_RESPONSE_WITH_WRONG_FRAME: &[u8] = &[
trait HttpServer: Display {
fn process(&mut self, dgram: Option<Datagram>) -> Output;
fn process_events(&mut self);
fn get_timeout(&self) -> Option<Duration> {
None
}
}
struct Http3TestServer {
@ -61,6 +66,7 @@ struct Http3TestServer {
posts: HashMap<Http3OrWebTransportStream, usize>,
responses: HashMap<Http3OrWebTransportStream, Vec<u8>>,
current_connection_hash: u64,
sessions_to_close: HashMap<Instant, Vec<WebTransportRequest>>,
}
impl ::std::fmt::Display for Http3TestServer {
@ -76,6 +82,7 @@ impl Http3TestServer {
posts: HashMap::new(),
responses: HashMap::new(),
current_connection_hash: 0,
sessions_to_close: HashMap::new(),
}
}
@ -110,6 +117,18 @@ impl Http3TestServer {
}
}
}
fn maybe_close_session(&mut self) {
let now = Instant::now();
for (expires, sessions) in self.sessions_to_close.iter_mut() {
if *expires <= now {
for s in sessions.iter_mut() {
s.close_session(0, "").unwrap();
}
}
}
self.sessions_to_close.retain(|expires, _| *expires >= now);
}
}
impl HttpServer for Http3TestServer {
@ -118,6 +137,8 @@ impl HttpServer for Http3TestServer {
}
fn process_events(&mut self) {
self.maybe_close_session();
while let Some(event) = self.server.next_event() {
qtrace!("Event: {:?}", event);
match event {
@ -345,11 +366,69 @@ impl HttpServer for Http3TestServer {
}
Http3ServerEvent::PriorityUpdate { .. }
| Http3ServerEvent::StreamReset { .. }
| Http3ServerEvent::StreamStopSending { .. }
| Http3ServerEvent::WebTransport(_) => {}
| Http3ServerEvent::StreamStopSending { .. } => {}
Http3ServerEvent::WebTransport(WebTransportServerEvent::NewSession {
mut session,
headers,
}) => {
qdebug!(
"WebTransportServerEvent::NewSession {:?} {:?}",
session,
headers
);
let path_hdr = headers.iter().find(|&h| h.name() == ":path");
match path_hdr {
Some(ph) if !ph.value().is_empty() => {
let path = ph.value();
qtrace!("Serve request {}", path);
if path == "/success" {
session.response(true).unwrap();
} else if path == "/reject" {
session.response(false).unwrap();
} else if path == "/closeafter0ms" {
session.response(true).unwrap();
} else if path == "/closeafter100ms" {
session.response(true).unwrap();
let expires = Instant::now() + Duration::from_millis(100);
if !self.sessions_to_close.contains_key(&expires) {
self.sessions_to_close.insert(expires, Vec::new());
}
self.sessions_to_close
.get_mut(&expires)
.unwrap()
.push(session);
} else {
session.response(true).unwrap();
}
}
_ => {
session.response(false).unwrap();
}
}
}
Http3ServerEvent::WebTransport(WebTransportServerEvent::SessionClosed {
session,
reason,
}) => {
qdebug!(
"WebTransportServerEvent::SessionClosed {:?} {:?}",
session,
reason
);
}
Http3ServerEvent::WebTransport(WebTransportServerEvent::NewStream(id)) => {
qdebug!("WebTransportServerEvent::NewStream {:?}", id);
}
}
}
}
fn get_timeout(&self) -> Option<Duration> {
if let Some(next) = self.sessions_to_close.keys().min() {
return Some(max(*next - Instant::now(), Duration::from_millis(0)));
}
None
}
}
impl HttpServer for Server {
@ -425,7 +504,10 @@ fn process(
emit_packet(socket, dgram);
true
}
Output::Callback(new_timeout) => {
Output::Callback(mut new_timeout) => {
if let Some(t) = server.get_timeout() {
new_timeout = min(new_timeout, t);
}
if let Some(svr_timeout) = svr_timeout {
timer.cancel_timeout(svr_timeout);
}
@ -570,7 +652,8 @@ impl ServersRunner {
Http3Parameters::default()
.max_table_size_encoder(MAX_TABLE_SIZE)
.max_table_size_decoder(MAX_TABLE_SIZE)
.max_blocked_streams(MAX_BLOCKED_STREAMS),
.max_blocked_streams(MAX_BLOCKED_STREAMS)
.webtransport(true),
None,
)
.expect("We cannot make a server!"),

View File

@ -0,0 +1,117 @@
//
// Simple WebTransport test
//
"use strict";
var h3Port;
var host;
let WebTransportListener = function() {};
WebTransportListener.prototype = {
onSessionReady(sessionId) {
info("SessionId " + sessionId);
this.ready();
},
onSessionClosed(errorCode, reason) {
info("Error: " + errorCode + " reason: " + reason);
this.closed();
},
QueryInterface: ChromeUtils.generateQI(["WebTransportSessionEventListener"]),
};
registerCleanupFunction(async () => {
Services.prefs.clearUserPref("network.dns.localDomains");
});
add_task(async function setup() {
let env = Cc["@mozilla.org/process/environment;1"].getService(
Ci.nsIEnvironment
);
Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com");
h3Port = env.get("MOZHTTP3_PORT");
Assert.notEqual(h3Port, null);
Assert.notEqual(h3Port, "");
host = "foo.example.com:" + h3Port;
do_get_profile();
let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
Ci.nsIX509CertDB
);
// `../unit/` so that unit_ipc tests can use as well
addCertFromFile(certdb, "../unit/http2-ca.pem", "CTu,u,u");
});
add_task(async function test_connect_wt() {
let webTransport = NetUtil.newWebTransport();
await new Promise(resolve => {
let listener = new WebTransportListener().QueryInterface(
Ci.WebTransportSessionEventListener
);
listener.ready = resolve;
webTransport.asyncConnect(
NetUtil.newURI("https://" + host + "/success"),
Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal),
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
listener
);
});
webTransport.closeSession(0, "");
});
add_task(async function test_reject() {
let webTransport = NetUtil.newWebTransport();
await new Promise(resolve => {
let listener = new WebTransportListener().QueryInterface(
Ci.WebTransportSessionEventListener
);
listener.closed = resolve;
webTransport.asyncConnect(
NetUtil.newURI("https://" + host + "/reject"),
Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal),
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
listener
);
});
});
async function test_closed(path) {
let webTransport = NetUtil.newWebTransport();
let listener = new WebTransportListener().QueryInterface(
Ci.WebTransportSessionEventListener
);
let pReady = new Promise(resolve => {
listener.ready = resolve;
});
let pClose = new Promise(resolve => {
listener.closed = resolve;
});
webTransport.asyncConnect(
NetUtil.newURI("https://" + host + path),
Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal),
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
listener
);
await pReady;
await pClose;
}
add_task(async function test_closed_0ms() {
test_closed("/closeafter0ms");
});
add_task(async function test_closed_100ms() {
test_closed("/closeafter100ms");
});

View File

@ -646,3 +646,6 @@ run-sequentially = node server exceptions dont replay well
[test_pac_reload_after_network_change.js]
[test_proxy_cancel.js]
[test_connection_based_auth.js]
[test_webtransport_simple.js]
# This test will be fixed in bug 1796556
skip-if = true