mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 14:52:16 +00:00
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:
parent
fca49d99b7
commit
7fcc11f19b
@ -320,6 +320,10 @@ var NetUtil = {
|
||||
return channel;
|
||||
},
|
||||
|
||||
newWebTransport: function NetUtil_newWebTransport() {
|
||||
return Services.io.newWebTransport();
|
||||
},
|
||||
|
||||
/**
|
||||
* Reads aCount bytes from aInputStream into a string.
|
||||
*
|
||||
|
@ -314,6 +314,7 @@ LOCAL_INCLUDES += [
|
||||
"/dom/base",
|
||||
"/netwerk/dns",
|
||||
"/netwerk/protocol/http",
|
||||
"/netwerk/protocol/webtransport",
|
||||
"/netwerk/socket",
|
||||
"/netwerk/url-classifier",
|
||||
"/security/manager/ssl",
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
|
7
netwerk/docs/webtransport/webtransport.md
Normal file
7
netwerk/docs/webtransport/webtransport.md
Normal file
@ -0,0 +1,7 @@
|
||||
WebTransport
|
||||
============
|
||||
|
||||
Components:
|
||||
|
||||
- [WebTransportSessionProxy](webtransportsessionproxy.md)
|
||||
|
19
netwerk/docs/webtransport/webtransportsessionproxy.md
Normal file
19
netwerk/docs/webtransport/webtransportsessionproxy.md
Normal 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
|
||||
```
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
351
netwerk/protocol/http/Http3WebTransportSession.cpp
Normal file
351
netwerk/protocol/http/Http3WebTransportSession.cpp
Normal 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
|
88
netwerk/protocol/http/Http3WebTransportSession.h
Normal file
88
netwerk/protocol/http/Http3WebTransportSession.h
Normal 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
|
@ -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; }
|
||||
|
||||
|
@ -5,11 +5,11 @@
|
||||
#ifndef HttpWinUtils_h__
|
||||
#define HttpWinUtils_h__
|
||||
|
||||
class nsHttpChannel;
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
class nsHttpChannel;
|
||||
|
||||
void AddWindowsSSO(nsHttpChannel* channel);
|
||||
|
||||
} // namespace net
|
||||
|
@ -114,6 +114,7 @@ UNIFIED_SOURCES += [
|
||||
"Http2StreamWebSocket.cpp",
|
||||
"Http3Session.cpp",
|
||||
"Http3Stream.cpp",
|
||||
"Http3WebTransportSession.cpp",
|
||||
"HttpAuthUtils.cpp",
|
||||
"HttpBackgroundChannelChild.cpp",
|
||||
"HttpBackgroundChannelParent.cpp",
|
||||
|
@ -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; \
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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"]
|
||||
|
21
netwerk/protocol/webtransport/WebTransportLog.h
Normal file
21
netwerk/protocol/webtransport/WebTransportLog.h
Normal 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
|
545
netwerk/protocol/webtransport/WebTransportSessionProxy.cpp
Normal file
545
netwerk/protocol/webtransport/WebTransportSessionProxy.cpp
Normal 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
|
168
netwerk/protocol/webtransport/WebTransportSessionProxy.h
Normal file
168
netwerk/protocol/webtransport/WebTransportSessionProxy.h
Normal 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
|
31
netwerk/protocol/webtransport/moz.build
Normal file
31
netwerk/protocol/webtransport/moz.build
Normal 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")
|
76
netwerk/protocol/webtransport/nsIWebTransport.idl
Normal file
76
netwerk/protocol/webtransport/nsIWebTransport.idl
Normal 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);
|
||||
};
|
@ -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,
|
||||
|
@ -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!"),
|
||||
|
117
netwerk/test/unit/test_webtransport_simple.js
Normal file
117
netwerk/test/unit/test_webtransport_simple.js
Normal 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");
|
||||
});
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user