summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/webtransport
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/protocol/webtransport')
-rw-r--r--netwerk/protocol/webtransport/WebTransportLog.h21
-rw-r--r--netwerk/protocol/webtransport/WebTransportSessionProxy.cpp1198
-rw-r--r--netwerk/protocol/webtransport/WebTransportSessionProxy.h192
-rw-r--r--netwerk/protocol/webtransport/WebTransportStreamProxy.cpp368
-rw-r--r--netwerk/protocol/webtransport/WebTransportStreamProxy.h83
-rw-r--r--netwerk/protocol/webtransport/moz.build34
-rw-r--r--netwerk/protocol/webtransport/nsIWebTransport.idl122
-rw-r--r--netwerk/protocol/webtransport/nsIWebTransportStream.idl84
8 files changed, 2102 insertions, 0 deletions
diff --git a/netwerk/protocol/webtransport/WebTransportLog.h b/netwerk/protocol/webtransport/WebTransportLog.h
new file mode 100644
index 0000000000..99f211b893
--- /dev/null
+++ b/netwerk/protocol/webtransport/WebTransportLog.h
@@ -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
diff --git a/netwerk/protocol/webtransport/WebTransportSessionProxy.cpp b/netwerk/protocol/webtransport/WebTransportSessionProxy.cpp
new file mode 100644
index 0000000000..d6091b9ab4
--- /dev/null
+++ b/netwerk/protocol/webtransport/WebTransportSessionProxy.cpp
@@ -0,0 +1,1198 @@
+/* -*- 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 "Http3WebTransportStream.h"
+#include "WebTransportSessionProxy.h"
+#include "WebTransportStreamProxy.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIRequest.h"
+#include "nsNetUtil.h"
+#include "nsProxyRelease.h"
+#include "nsSocketTransportService2.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_network.h"
+
+namespace mozilla::net {
+
+LazyLogModule webTransportLog("nsWebTransport");
+
+NS_IMPL_ISUPPORTS(WebTransportSessionProxy, WebTransportSessionEventListener,
+ nsIWebTransport, nsIRedirectResultListener, nsIStreamListener,
+ nsIChannelEventSink, nsIInterfaceRequestor);
+
+WebTransportSessionProxy::WebTransportSessionProxy()
+ : mMutex("WebTransportSessionProxy::mMutex"),
+ mTarget(GetMainThreadSerialEventTarget()) {
+ 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) {
+ return AsyncConnectWithClient(aURI, aPrincipal, aSecurityFlags, aListener,
+ Maybe<dom::ClientInfo>());
+}
+
+nsresult WebTransportSessionProxy::AsyncConnectWithClient(
+ nsIURI* aURI, nsIPrincipal* aPrincipal, uint32_t aSecurityFlags,
+ WebTransportSessionEventListener* aListener,
+ const Maybe<dom::ClientInfo>& aClientInfo) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ LOG(("WebTransportSessionProxy::AsyncConnect"));
+ {
+ MutexAutoLock lock(mMutex);
+ mListener = aListener;
+ }
+ auto cleanup = MakeScopeExit([self = RefPtr<WebTransportSessionProxy>(this)] {
+ MutexAutoLock lock(self->mMutex);
+ self->mListener->OnSessionClosed(0, ""_ns); // TODO: find a better error.
+ self->mChannel = nullptr;
+ self->mListener = nullptr;
+ self->ChangeState(WebTransportSessionProxyState::DONE);
+ });
+
+ nsSecurityFlags flags = nsILoadInfo::SEC_COOKIES_OMIT | aSecurityFlags;
+ nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
+ nsIRequest::LOAD_BYPASS_CACHE |
+ nsIRequest::INHIBIT_CACHING;
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (aClientInfo.isSome()) {
+ rv = NS_NewChannel(getter_AddRefs(mChannel), aURI, aPrincipal,
+ aClientInfo.ref(), Maybe<dom::ServiceWorkerDescriptor>(),
+ flags, nsContentPolicyType::TYPE_WEB_TRANSPORT,
+ /* aCookieJarSettings */ nullptr,
+ /* aPerformanceStorage */ nullptr,
+ /* aLoadGroup */ nullptr,
+ /* aCallbacks */ this, loadFlags);
+ } else {
+ rv = NS_NewChannel(getter_AddRefs(mChannel), aURI, aPrincipal, flags,
+ nsContentPolicyType::TYPE_WEB_TRANSPORT,
+ /* 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);
+ }
+
+ // https://www.ietf.org/archive/id/draft-ietf-webtrans-http3-04.html#section-6
+ rv = httpChannel->SetRequestHeader("Sec-Webtransport-Http3-Draft02"_ns,
+ "1"_ns, false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // To establish a WebTransport session with an origin origin, follow
+ // [WEB-TRANSPORT-HTTP3] section 3.3, with using origin, serialized and
+ // isomorphic encoded, as the `Origin` header of the request.
+ // https://www.w3.org/TR/webtransport/#protocol-concepts
+ nsAutoCString serializedOrigin;
+ if (NS_FAILED(aPrincipal->GetAsciiOrigin(serializedOrigin))) {
+ // origin/URI will be missing for system principals
+ // assign null origin
+ serializedOrigin = "null"_ns;
+ }
+
+ rv = httpChannel->SetRequestHeader("Origin"_ns, serializedOrigin, false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> internalChannel =
+ do_QueryInterface(mChannel);
+ if (!internalChannel) {
+ mChannel = nullptr;
+ return NS_ERROR_ABORT;
+ }
+ Unused << internalChannel->SetWebTransportSessionEventListener(this);
+
+ rv = mChannel->AsyncOpen(this);
+ if (NS_SUCCEEDED(rv)) {
+ cleanup.release();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::RetargetTo(nsIEventTarget* aTarget) {
+ if (!aTarget) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ LOG(("WebTransportSessionProxy::RetargetTo mState=%d", mState));
+ // RetargetTo should be only called after the session is ready.
+ if (mState != WebTransportSessionProxyState::ACTIVE) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mTarget = aTarget;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::GetStats() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+WebTransportSessionProxy::CloseSession(uint32_t status,
+ const nsACString& reason) {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mTarget->IsOnCurrentThread());
+ mCloseStatus = status;
+ mReason = reason;
+ mListener = nullptr;
+ mPendingEvents.Clear();
+ 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::CloseSessionInternalLocked() {
+ MutexAutoLock lock(mMutex);
+ CloseSessionInternal();
+}
+
+void WebTransportSessionProxy::CloseSessionInternal() {
+ if (!OnSocketThread()) {
+ mMutex.AssertCurrentThreadOwns();
+ RefPtr<WebTransportSessionProxy> self(this);
+ Unused << gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::CallCloseWebTransportSession",
+ [self{std::move(self)}]() { self->CloseSessionInternalLocked(); }));
+ return;
+ }
+
+ mMutex.AssertCurrentThreadOwns();
+
+ RefPtr<Http3WebTransportSession> wt;
+ uint32_t closeStatus = 0;
+ nsCString reason;
+
+ 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) {
+ MutexAutoUnlock unlock(mMutex);
+ wt->CloseSession(closeStatus, reason);
+ }
+}
+
+class WebTransportStreamCallbackWrapper final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebTransportStreamCallbackWrapper)
+
+ explicit WebTransportStreamCallbackWrapper(
+ nsIWebTransportStreamCallback* aCallback, bool aBidi)
+ : mCallback(aCallback),
+ mTarget(GetCurrentSerialEventTarget()),
+ mBidi(aBidi) {}
+
+ void CallOnError(nsresult aError) {
+ if (!mTarget->IsOnCurrentThread()) {
+ RefPtr<WebTransportStreamCallbackWrapper> self(this);
+ Unused << mTarget->Dispatch(NS_NewRunnableFunction(
+ "WebTransportStreamCallbackWrapper::CallOnError",
+ [self{std::move(self)}, error{aError}]() {
+ self->CallOnError(error);
+ }));
+ return;
+ }
+
+ LOG(("WebTransportStreamCallbackWrapper::OnError aError=0x%" PRIx32,
+ static_cast<uint32_t>(aError)));
+ Unused << mCallback->OnError(nsIWebTransport::INVALID_STATE_ERROR);
+ }
+
+ void CallOnStreamReady(WebTransportStreamProxy* aStream) {
+ if (!mTarget->IsOnCurrentThread()) {
+ RefPtr<WebTransportStreamCallbackWrapper> self(this);
+ RefPtr<WebTransportStreamProxy> stream = aStream;
+ Unused << mTarget->Dispatch(NS_NewRunnableFunction(
+ "WebTransportStreamCallbackWrapper::CallOnStreamReady",
+ [self{std::move(self)}, stream{std::move(stream)}]() {
+ self->CallOnStreamReady(stream);
+ }));
+ return;
+ }
+
+ if (mBidi) {
+ Unused << mCallback->OnBidirectionalStreamReady(aStream);
+ return;
+ }
+
+ Unused << mCallback->OnUnidirectionalStreamReady(aStream);
+ }
+
+ private:
+ virtual ~WebTransportStreamCallbackWrapper() {
+ NS_ProxyRelease(
+ "WebTransportStreamCallbackWrapper::~WebTransportStreamCallbackWrapper",
+ mTarget, mCallback.forget());
+ }
+
+ nsCOMPtr<nsIWebTransportStreamCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mTarget;
+ bool mBidi = false;
+};
+
+void WebTransportSessionProxy::CreateStreamInternal(
+ nsIWebTransportStreamCallback* callback, bool aBidi) {
+ mMutex.AssertCurrentThreadOwns();
+ LOG(
+ ("WebTransportSessionProxy::CreateStreamInternal %p "
+ "mState=%d, bidi=%d",
+ this, mState, aBidi));
+ switch (mState) {
+ case WebTransportSessionProxyState::INIT:
+ case WebTransportSessionProxyState::NEGOTIATING:
+ case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
+ case WebTransportSessionProxyState::ACTIVE: {
+ RefPtr<WebTransportStreamCallbackWrapper> wrapper =
+ new WebTransportStreamCallbackWrapper(callback, aBidi);
+ if (mState == WebTransportSessionProxyState::ACTIVE &&
+ mWebTransportSession) {
+ DoCreateStream(wrapper, mWebTransportSession, aBidi);
+ } else {
+ LOG(
+ ("WebTransportSessionProxy::CreateStreamInternal %p "
+ " queue create stream event",
+ this));
+ auto task = [self = RefPtr{this}, wrapper{std::move(wrapper)},
+ bidi(aBidi)](nsresult aStatus) {
+ if (NS_FAILED(aStatus)) {
+ wrapper->CallOnError(aStatus);
+ return;
+ }
+
+ self->DoCreateStream(wrapper, nullptr, bidi);
+ };
+ // TODO: we should do this properly in bug 1830362.
+ mPendingCreateStreamEvents.AppendElement(std::move(task));
+ }
+ } break;
+ case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
+ case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
+ case WebTransportSessionProxyState::DONE: {
+ nsCOMPtr<nsIWebTransportStreamCallback> cb(callback);
+ NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::CreateStreamInternal",
+ [cb{std::move(cb)}]() {
+ cb->OnError(nsIWebTransport::INVALID_STATE_ERROR);
+ }));
+ } break;
+ }
+}
+
+void WebTransportSessionProxy::DoCreateStream(
+ WebTransportStreamCallbackWrapper* aCallback,
+ Http3WebTransportSession* aSession, bool aBidi) {
+ if (!OnSocketThread()) {
+ RefPtr<WebTransportSessionProxy> self(this);
+ RefPtr<WebTransportStreamCallbackWrapper> wrapper(aCallback);
+ Unused << gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::DoCreateStream",
+ [self{std::move(self)}, wrapper{std::move(wrapper)}, bidi(aBidi)]() {
+ self->DoCreateStream(wrapper, nullptr, bidi);
+ }));
+ return;
+ }
+
+ LOG(("WebTransportSessionProxy::DoCreateStream %p bidi=%d", this, aBidi));
+
+ RefPtr<Http3WebTransportSession> session = aSession;
+ // Having no session here means that this is called by dispatching tasks.
+ // The mState may be already changed, so we need to check it again.
+ if (!aSession) {
+ MutexAutoLock lock(mMutex);
+ switch (mState) {
+ case WebTransportSessionProxyState::INIT:
+ case WebTransportSessionProxyState::NEGOTIATING:
+ case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED:
+ MOZ_ASSERT(false, "DoCreateStream called with invalid state");
+ aCallback->CallOnError(NS_ERROR_UNEXPECTED);
+ return;
+ case WebTransportSessionProxyState::ACTIVE: {
+ session = mWebTransportSession;
+ } break;
+ case WebTransportSessionProxyState::SESSION_CLOSE_PENDING:
+ case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING:
+ case WebTransportSessionProxyState::DONE:
+ // Session is going to be closed.
+ aCallback->CallOnError(NS_ERROR_NOT_AVAILABLE);
+ return;
+ }
+ }
+
+ if (!session) {
+ MOZ_ASSERT_UNREACHABLE("This should not happen");
+ aCallback->CallOnError(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ RefPtr<WebTransportStreamCallbackWrapper> wrapper(aCallback);
+ auto callback =
+ [wrapper{std::move(wrapper)}](
+ Result<RefPtr<Http3WebTransportStream>, nsresult>&& aResult) {
+ if (aResult.isErr()) {
+ wrapper->CallOnError(aResult.unwrapErr());
+ return;
+ }
+
+ RefPtr<Http3WebTransportStream> stream = aResult.unwrap();
+ RefPtr<WebTransportStreamProxy> streamProxy =
+ new WebTransportStreamProxy(stream);
+ wrapper->CallOnStreamReady(streamProxy);
+ };
+
+ if (aBidi) {
+ session->CreateOutgoingBidirectionalStream(std::move(callback));
+ } else {
+ session->CreateOutgoingUnidirectionalStream(std::move(callback));
+ }
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::CreateOutgoingUnidirectionalStream(
+ nsIWebTransportStreamCallback* callback) {
+ if (!callback) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ MutexAutoLock lock(mMutex);
+ CreateStreamInternal(callback, false);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::CreateOutgoingBidirectionalStream(
+ nsIWebTransportStreamCallback* callback) {
+ if (!callback) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ MutexAutoLock lock(mMutex);
+ CreateStreamInternal(callback, true);
+ return NS_OK;
+}
+
+void WebTransportSessionProxy::SendDatagramInternal(
+ const RefPtr<Http3WebTransportSession>& aSession, nsTArray<uint8_t>&& aData,
+ uint64_t aTrackingId) {
+ MOZ_ASSERT(OnSocketThread());
+
+ aSession->SendDatagram(std::move(aData), aTrackingId);
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::SendDatagram(const nsTArray<uint8_t>& aData,
+ uint64_t aTrackingId) {
+ RefPtr<Http3WebTransportSession> session;
+ {
+ MutexAutoLock lock(mMutex);
+ if (mState != WebTransportSessionProxyState::ACTIVE ||
+ !mWebTransportSession) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ session = mWebTransportSession;
+ }
+
+ nsTArray<uint8_t> copied;
+ copied.Assign(aData);
+ if (!OnSocketThread()) {
+ return gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::SendDatagramInternal",
+ [self = RefPtr{this}, session{std::move(session)},
+ data{std::move(copied)}, trackingId(aTrackingId)]() mutable {
+ self->SendDatagramInternal(session, std::move(data), trackingId);
+ }));
+ }
+
+ SendDatagramInternal(session, std::move(copied), aTrackingId);
+ return NS_OK;
+}
+
+void WebTransportSessionProxy::GetMaxDatagramSizeInternal(
+ const RefPtr<Http3WebTransportSession>& aSession) {
+ MOZ_ASSERT(OnSocketThread());
+
+ aSession->GetMaxDatagramSize();
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::GetMaxDatagramSize() {
+ RefPtr<Http3WebTransportSession> session;
+ {
+ MutexAutoLock lock(mMutex);
+ if (mState != WebTransportSessionProxyState::ACTIVE ||
+ !mWebTransportSession) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ session = mWebTransportSession;
+ }
+
+ if (!OnSocketThread()) {
+ return gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::GetMaxDatagramSizeInternal",
+ [self = RefPtr{this}, session{std::move(session)}]() {
+ self->GetMaxDatagramSizeInternal(session);
+ }));
+ }
+
+ GetMaxDatagramSizeInternal(session);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// 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 && status < 300)) {
+ 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;
+ nsTArray<std::function<void()>> pendingEvents;
+ nsTArray<std::function<void(nsresult)>> pendingCreateStreamEvents;
+ {
+ MutexAutoLock lock(mMutex);
+ switch (mState) {
+ case WebTransportSessionProxyState::INIT:
+ case WebTransportSessionProxyState::ACTIVE:
+ case WebTransportSessionProxyState::NEGOTIATING:
+ MOZ_ASSERT(false, "OnStopRequest 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;
+ }
+ pendingEvents = std::move(mPendingEvents);
+ pendingCreateStreamEvents = std::move(mPendingCreateStreamEvents);
+ if (!pendingCreateStreamEvents.IsEmpty()) {
+ if (NS_SUCCEEDED(aStatus) &&
+ (mState == WebTransportSessionProxyState::DONE ||
+ mState == WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING ||
+ mState == WebTransportSessionProxyState::SESSION_CLOSE_PENDING)) {
+ aStatus = NS_ERROR_FAILURE;
+ }
+ }
+
+ mStopRequestCalled = true;
+ }
+
+ if (!pendingCreateStreamEvents.IsEmpty()) {
+ Unused << gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::DispatchPendingCreateStreamEvents",
+ [pendingCreateStreamEvents = std::move(pendingCreateStreamEvents),
+ status(aStatus)]() {
+ for (const auto& event : pendingCreateStreamEvents) {
+ event(status);
+ }
+ }));
+ } // otherwise let the CreateStreams just go away
+
+ if (listener) {
+ if (succeeded) {
+ listener->OnSessionReady(sessionId);
+ if (!pendingEvents.IsEmpty()) {
+ Unused << gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::DispatchPendingEvents",
+ [pendingEvents = std::move(pendingEvents)]() {
+ for (const auto& event : pendingEvents) {
+ event();
+ }
+ }));
+ }
+ } 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) {
+ // Currently implementation we do not reach this part of the code
+ // as location headers are not forwarded by the http3 stack to the applicaion.
+ // Hence, the channel is aborted due to the location header check in
+ // nsHttpChannel::AsyncProcessRedirection This comment must be removed after
+ // the following neqo bug is resolved
+ // https://github.com/mozilla/neqo/issues/1364
+ if (!StaticPrefs::network_webtransport_redirect_enabled()) {
+ LOG(("Channel Redirects are disabled for WebTransport sessions"));
+ return NS_ERROR_ABORT;
+ }
+
+ 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(nsresult aStatus) {
+ if (NS_SUCCEEDED(aStatus) && 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::OnIncomingStreamAvailableInternal(
+ Http3WebTransportStream* aStream) {
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+
+ LOG(
+ ("WebTransportSessionProxy::OnIncomingStreamAvailableInternal %p "
+ "mState=%d "
+ "mStopRequestCalled=%d",
+ this, mState, mStopRequestCalled));
+ // Since OnSessionReady on the listener is called on the main thread,
+ // OnIncomingStreamAvailableInternal and OnSessionReady can be racy. If
+ // OnStopRequest is not called yet, OnIncomingStreamAvailableInternal needs
+ // to wait.
+ if (!mStopRequestCalled) {
+ mPendingEvents.AppendElement(
+ [self = RefPtr{this}, stream = RefPtr{aStream}]() {
+ self->OnIncomingStreamAvailableInternal(stream);
+ });
+ return NS_OK;
+ }
+
+ if (!mTarget->IsOnCurrentThread()) {
+ RefPtr<WebTransportSessionProxy> self(this);
+ RefPtr<Http3WebTransportStream> stream = aStream;
+ Unused << mTarget->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::OnIncomingStreamAvailableInternal",
+ [self{std::move(self)}, stream{std::move(stream)}]() {
+ self->OnIncomingStreamAvailableInternal(stream);
+ }));
+ return NS_OK;
+ }
+
+ LOG(
+ ("WebTransportSessionProxy::OnIncomingStreamAvailableInternal %p "
+ "mState=%d mListener=%p",
+ this, mState, mListener.get()));
+ if (mState == WebTransportSessionProxyState::ACTIVE) {
+ listener = mListener;
+ }
+ }
+
+ if (!listener) {
+ // Session can be already closed.
+ return NS_OK;
+ }
+
+ RefPtr<WebTransportStreamProxy> streamProxy =
+ new WebTransportStreamProxy(aStream);
+ if (aStream->StreamType() == WebTransportStreamType::BiDi) {
+ Unused << listener->OnIncomingBidirectionalStreamAvailable(streamProxy);
+ } else {
+ Unused << listener->OnIncomingUnidirectionalStreamAvailable(streamProxy);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnIncomingBidirectionalStreamAvailable(
+ nsIWebTransportBidirectionalStream* aStream) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnIncomingUnidirectionalStreamAvailable(
+ nsIWebTransportReceiveStream* aStream) {
+ 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");
+ MutexAutoLock lock(mMutex);
+ LOG(
+ ("WebTransportSessionProxy::OnSessionClosed %p mState=%d "
+ "mStopRequestCalled=%d",
+ this, mState, mStopRequestCalled));
+ // Since OnSessionReady on the listener is called on the main thread,
+ // OnSessionClosed and OnSessionReady can be racy. If OnStopRequest is not
+ // called yet, OnSessionClosed needs to wait.
+ if (!mStopRequestCalled) {
+ nsCString closeReason(reason);
+ mPendingEvents.AppendElement(
+ [self = RefPtr{this}, status(status), closeReason(closeReason)]() {
+ Unused << self->OnSessionClosed(status, closeReason);
+ });
+ return NS_OK;
+ }
+
+ 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);
+ 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::CallOnSessionClosedLocked() {
+ MutexAutoLock lock(mMutex);
+ CallOnSessionClosed();
+}
+
+void WebTransportSessionProxy::CallOnSessionClosed() {
+ mMutex.AssertCurrentThreadOwns();
+
+ if (!mTarget->IsOnCurrentThread()) {
+ RefPtr<WebTransportSessionProxy> self(this);
+ Unused << mTarget->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::CallOnSessionClosed",
+ [self{std::move(self)}]() { self->CallOnSessionClosedLocked(); }));
+ return;
+ }
+
+ MOZ_ASSERT(mTarget->IsOnCurrentThread());
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ nsAutoCString reason;
+ uint32_t closeStatus = 0;
+
+ 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) {
+ // Don't invoke the callback under the lock.
+ MutexAutoUnlock unlock(mMutex);
+ 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;
+}
+
+void WebTransportSessionProxy::NotifyDatagramReceived(
+ nsTArray<uint8_t>&& aData) {
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mTarget->IsOnCurrentThread());
+
+ if (!mStopRequestCalled) {
+ CopyableTArray<uint8_t> copied(aData);
+ mPendingEvents.AppendElement(
+ [self = RefPtr{this}, data = std::move(copied)]() mutable {
+ self->NotifyDatagramReceived(std::move(data));
+ });
+ return;
+ }
+
+ if (mState != WebTransportSessionProxyState::ACTIVE || !mListener) {
+ return;
+ }
+ listener = mListener;
+ }
+
+ listener->OnDatagramReceived(aData);
+}
+
+NS_IMETHODIMP WebTransportSessionProxy::OnDatagramReceivedInternal(
+ nsTArray<uint8_t>&& aData) {
+ MOZ_ASSERT(OnSocketThread());
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (!mTarget->IsOnCurrentThread()) {
+ return mTarget->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::OnDatagramReceived",
+ [self = RefPtr{this}, data{std::move(aData)}]() mutable {
+ self->NotifyDatagramReceived(std::move(data));
+ }));
+ }
+ }
+
+ NotifyDatagramReceived(std::move(aData));
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportSessionProxy::OnDatagramReceived(
+ const nsTArray<uint8_t>& aData) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void WebTransportSessionProxy::OnMaxDatagramSizeInternal(uint64_t aSize) {
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mTarget->IsOnCurrentThread());
+
+ if (!mStopRequestCalled) {
+ mPendingEvents.AppendElement([self = RefPtr{this}, size(aSize)]() {
+ self->OnMaxDatagramSizeInternal(size);
+ });
+ return;
+ }
+
+ if (mState != WebTransportSessionProxyState::ACTIVE || !mListener) {
+ return;
+ }
+ listener = mListener;
+ }
+
+ listener->OnMaxDatagramSize(aSize);
+}
+
+NS_IMETHODIMP WebTransportSessionProxy::OnMaxDatagramSize(uint64_t aSize) {
+ MOZ_ASSERT(OnSocketThread());
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (!mTarget->IsOnCurrentThread()) {
+ return mTarget->Dispatch(
+ NS_NewRunnableFunction("WebTransportSessionProxy::OnMaxDatagramSize",
+ [self = RefPtr{this}, size(aSize)] {
+ self->OnMaxDatagramSizeInternal(size);
+ }));
+ }
+ }
+
+ OnMaxDatagramSizeInternal(aSize);
+ return NS_OK;
+}
+
+void WebTransportSessionProxy::OnOutgoingDatagramOutComeInternal(
+ uint64_t aId, WebTransportSessionEventListener::DatagramOutcome aOutCome) {
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mTarget->IsOnCurrentThread());
+ if (mState != WebTransportSessionProxyState::ACTIVE || !mListener) {
+ return;
+ }
+ listener = mListener;
+ }
+
+ listener->OnOutgoingDatagramOutCome(aId, aOutCome);
+}
+
+NS_IMETHODIMP
+WebTransportSessionProxy::OnOutgoingDatagramOutCome(
+ uint64_t aId, WebTransportSessionEventListener::DatagramOutcome aOutCome) {
+ MOZ_ASSERT(OnSocketThread());
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (!mTarget->IsOnCurrentThread()) {
+ return mTarget->Dispatch(NS_NewRunnableFunction(
+ "WebTransportSessionProxy::OnOutgoingDatagramOutCome",
+ [self = RefPtr{this}, id(aId), outcome(aOutCome)] {
+ self->OnOutgoingDatagramOutComeInternal(id, outcome);
+ }));
+ }
+ }
+
+ OnOutgoingDatagramOutComeInternal(aId, aOutCome);
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportSessionProxy::OnStopSending(uint64_t aStreamId,
+ nsresult aError) {
+ MOZ_ASSERT(OnSocketThread());
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mTarget->IsOnCurrentThread());
+
+ if (mState != WebTransportSessionProxyState::ACTIVE || !mListener) {
+ return NS_OK;
+ }
+ listener = mListener;
+ }
+
+ listener->OnStopSending(aStreamId, aError);
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportSessionProxy::OnResetReceived(uint64_t aStreamId,
+ nsresult aError) {
+ MOZ_ASSERT(OnSocketThread());
+ nsCOMPtr<WebTransportSessionEventListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mTarget->IsOnCurrentThread());
+
+ if (mState != WebTransportSessionProxyState::ACTIVE || !mListener) {
+ return NS_OK;
+ }
+ listener = mListener;
+ }
+
+ listener->OnResetReceived(aStreamId, aError);
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/webtransport/WebTransportSessionProxy.h b/netwerk/protocol/webtransport/WebTransportSessionProxy.h
new file mode 100644
index 0000000000..1e8eacc60b
--- /dev/null
+++ b/netwerk/protocol/webtransport/WebTransportSessionProxy.h
@@ -0,0 +1,192 @@
+/* -*- 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 <functional>
+#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 WebTransportStreamCallbackWrapper;
+
+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 CloseSessionInternalLocked();
+ void CallOnSessionClosed();
+ void CallOnSessionClosedLocked();
+
+ 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);
+ void CreateStreamInternal(nsIWebTransportStreamCallback* callback,
+ bool aBidi);
+ void DoCreateStream(WebTransportStreamCallbackWrapper* aCallback,
+ Http3WebTransportSession* aSession, bool aBidi);
+ void SendDatagramInternal(const RefPtr<Http3WebTransportSession>& aSession,
+ nsTArray<uint8_t>&& aData, uint64_t aTrackingId);
+ void NotifyDatagramReceived(nsTArray<uint8_t>&& aData);
+ void GetMaxDatagramSizeInternal(
+ const RefPtr<Http3WebTransportSession>& aSession);
+ void OnMaxDatagramSizeInternal(uint64_t aSize);
+ void OnOutgoingDatagramOutComeInternal(
+ uint64_t aId, WebTransportSessionEventListener::DatagramOutcome aOutCome);
+
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ nsCOMPtr<WebTransportSessionEventListener> mListener MOZ_GUARDED_BY(mMutex);
+ RefPtr<Http3WebTransportSession> mWebTransportSession MOZ_GUARDED_BY(mMutex);
+ uint64_t mSessionId MOZ_GUARDED_BY(mMutex) = UINT64_MAX;
+ uint32_t mCloseStatus MOZ_GUARDED_BY(mMutex) = 0;
+ nsCString mReason MOZ_GUARDED_BY(mMutex);
+ bool mStopRequestCalled MOZ_GUARDED_BY(mMutex) = false;
+ // This is used to store events happened before OnSessionReady.
+ // Note that these events will be dispatched to the socket thread.
+ nsTArray<std::function<void()>> mPendingEvents MOZ_GUARDED_BY(mMutex);
+ nsTArray<std::function<void(nsresult)>> mPendingCreateStreamEvents
+ MOZ_GUARDED_BY(mMutex);
+ nsCOMPtr<nsIEventTarget> mTarget MOZ_GUARDED_BY(mMutex);
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_WebTransportProxy_h
diff --git a/netwerk/protocol/webtransport/WebTransportStreamProxy.cpp b/netwerk/protocol/webtransport/WebTransportStreamProxy.cpp
new file mode 100644
index 0000000000..090b1cceb9
--- /dev/null
+++ b/netwerk/protocol/webtransport/WebTransportStreamProxy.cpp
@@ -0,0 +1,368 @@
+/* -*- 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 "WebTransportStreamProxy.h"
+
+#include "WebTransportLog.h"
+#include "Http3WebTransportStream.h"
+#include "nsProxyRelease.h"
+#include "nsSocketTransportService2.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ADDREF(WebTransportStreamProxy)
+NS_IMPL_RELEASE(WebTransportStreamProxy)
+
+NS_INTERFACE_MAP_BEGIN(WebTransportStreamProxy)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebTransportReceiveStream)
+ NS_INTERFACE_MAP_ENTRY(nsIWebTransportReceiveStream)
+ NS_INTERFACE_MAP_ENTRY(nsIWebTransportSendStream)
+ NS_INTERFACE_MAP_ENTRY(nsIWebTransportBidirectionalStream)
+NS_INTERFACE_MAP_END
+
+WebTransportStreamProxy::WebTransportStreamProxy(
+ Http3WebTransportStream* aStream)
+ : mWebTransportStream(aStream) {
+ nsCOMPtr<nsIAsyncInputStream> inputStream;
+ nsCOMPtr<nsIAsyncOutputStream> outputStream;
+ mWebTransportStream->GetWriterAndReader(getter_AddRefs(outputStream),
+ getter_AddRefs(inputStream));
+ if (outputStream) {
+ mWriter = new AsyncOutputStreamWrapper(outputStream);
+ }
+ if (inputStream) {
+ mReader = new AsyncInputStreamWrapper(inputStream, mWebTransportStream);
+ }
+}
+
+WebTransportStreamProxy::~WebTransportStreamProxy() {
+ // mWebTransportStream needs to be destroyed on the socket thread.
+ NS_ProxyRelease("WebTransportStreamProxy::~WebTransportStreamProxy",
+ gSocketTransportService, mWebTransportStream.forget());
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::SendStopSending(uint8_t aError) {
+ if (!OnSocketThread()) {
+ RefPtr<WebTransportStreamProxy> self(this);
+ return gSocketTransportService->Dispatch(
+ NS_NewRunnableFunction("WebTransportStreamProxy::SendStopSending",
+ [self{std::move(self)}, error(aError)]() {
+ self->SendStopSending(error);
+ }));
+ }
+
+ mWebTransportStream->SendStopSending(aError);
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::SendFin(void) {
+ if (!mWriter) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mWriter->Close();
+
+ if (!OnSocketThread()) {
+ RefPtr<WebTransportStreamProxy> self(this);
+ return gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportStreamProxy::SendFin",
+ [self{std::move(self)}]() { self->mWebTransportStream->SendFin(); }));
+ }
+
+ mWebTransportStream->SendFin();
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::Reset(uint8_t aErrorCode) {
+ if (!mWriter) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mWriter->Close();
+
+ if (!OnSocketThread()) {
+ RefPtr<WebTransportStreamProxy> self(this);
+ return gSocketTransportService->Dispatch(
+ NS_NewRunnableFunction("WebTransportStreamProxy::Reset",
+ [self{std::move(self)}, error(aErrorCode)]() {
+ self->mWebTransportStream->Reset(error);
+ }));
+ }
+
+ mWebTransportStream->Reset(aErrorCode);
+ return NS_OK;
+}
+
+namespace {
+
+class StatsCallbackWrapper : public nsIWebTransportStreamStatsCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit StatsCallbackWrapper(nsIWebTransportStreamStatsCallback* aCallback)
+ : mCallback(aCallback), mTarget(GetCurrentSerialEventTarget()) {}
+
+ NS_IMETHOD OnSendStatsAvailable(
+ nsIWebTransportSendStreamStats* aStats) override {
+ if (!mTarget->IsOnCurrentThread()) {
+ RefPtr<StatsCallbackWrapper> self(this);
+ nsCOMPtr<nsIWebTransportSendStreamStats> stats = aStats;
+ Unused << mTarget->Dispatch(NS_NewRunnableFunction(
+ "StatsCallbackWrapper::OnSendStatsAvailable",
+ [self{std::move(self)}, stats{std::move(stats)}]() {
+ self->OnSendStatsAvailable(stats);
+ }));
+ return NS_OK;
+ }
+
+ mCallback->OnSendStatsAvailable(aStats);
+ return NS_OK;
+ }
+
+ NS_IMETHOD OnReceiveStatsAvailable(
+ nsIWebTransportReceiveStreamStats* aStats) override {
+ if (!mTarget->IsOnCurrentThread()) {
+ RefPtr<StatsCallbackWrapper> self(this);
+ nsCOMPtr<nsIWebTransportReceiveStreamStats> stats = aStats;
+ Unused << mTarget->Dispatch(NS_NewRunnableFunction(
+ "StatsCallbackWrapper::OnReceiveStatsAvailable",
+ [self{std::move(self)}, stats{std::move(stats)}]() {
+ self->OnReceiveStatsAvailable(stats);
+ }));
+ return NS_OK;
+ }
+
+ mCallback->OnReceiveStatsAvailable(aStats);
+ return NS_OK;
+ }
+
+ private:
+ virtual ~StatsCallbackWrapper() {
+ NS_ProxyRelease("StatsCallbackWrapper::~StatsCallbackWrapper", mTarget,
+ mCallback.forget());
+ }
+
+ nsCOMPtr<nsIWebTransportStreamStatsCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mTarget;
+};
+
+NS_IMPL_ISUPPORTS(StatsCallbackWrapper, nsIWebTransportStreamStatsCallback)
+
+} // namespace
+
+NS_IMETHODIMP WebTransportStreamProxy::GetSendStreamStats(
+ nsIWebTransportStreamStatsCallback* aCallback) {
+ if (!OnSocketThread()) {
+ RefPtr<WebTransportStreamProxy> self(this);
+ nsCOMPtr<nsIWebTransportStreamStatsCallback> callback =
+ new StatsCallbackWrapper(aCallback);
+ return gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportStreamProxy::GetSendStreamStats",
+ [self{std::move(self)}, callback{std::move(callback)}]() {
+ self->GetSendStreamStats(callback);
+ }));
+ }
+
+ nsCOMPtr<nsIWebTransportSendStreamStats> stats =
+ mWebTransportStream->GetSendStreamStats();
+ aCallback->OnSendStatsAvailable(stats);
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::GetReceiveStreamStats(
+ nsIWebTransportStreamStatsCallback* aCallback) {
+ if (!OnSocketThread()) {
+ RefPtr<WebTransportStreamProxy> self(this);
+ nsCOMPtr<nsIWebTransportStreamStatsCallback> callback =
+ new StatsCallbackWrapper(aCallback);
+ return gSocketTransportService->Dispatch(NS_NewRunnableFunction(
+ "WebTransportStreamProxy::GetReceiveStreamStats",
+ [self{std::move(self)}, callback{std::move(callback)}]() {
+ self->GetReceiveStreamStats(callback);
+ }));
+ }
+
+ nsCOMPtr<nsIWebTransportReceiveStreamStats> stats =
+ mWebTransportStream->GetReceiveStreamStats();
+ aCallback->OnReceiveStatsAvailable(stats);
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::GetHasReceivedFIN(
+ bool* aHasReceivedFIN) {
+ *aHasReceivedFIN = mWebTransportStream->RecvDone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::GetInputStream(
+ nsIAsyncInputStream** aOut) {
+ if (!mReader) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<AsyncInputStreamWrapper> stream = mReader;
+ stream.forget(aOut);
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::GetOutputStream(
+ nsIAsyncOutputStream** aOut) {
+ if (!mWriter) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<AsyncOutputStreamWrapper> stream = mWriter;
+ stream.forget(aOut);
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::GetStreamId(uint64_t* aId) {
+ *aId = mWebTransportStream->StreamId();
+ return NS_OK;
+}
+
+//------------------------------------------------------------------------------
+// WebTransportStreamProxy::AsyncInputStreamWrapper
+//------------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(WebTransportStreamProxy::AsyncInputStreamWrapper,
+ nsIInputStream, nsIAsyncInputStream)
+
+WebTransportStreamProxy::AsyncInputStreamWrapper::AsyncInputStreamWrapper(
+ nsIAsyncInputStream* aStream, Http3WebTransportStream* aWebTransportStream)
+ : mStream(aStream), mWebTransportStream(aWebTransportStream) {}
+
+WebTransportStreamProxy::AsyncInputStreamWrapper::~AsyncInputStreamWrapper() =
+ default;
+
+void WebTransportStreamProxy::AsyncInputStreamWrapper::MaybeCloseStream() {
+ if (!mWebTransportStream->RecvDone()) {
+ return;
+ }
+
+ uint64_t available = 0;
+ Unused << Available(&available);
+ if (available) {
+ // Don't close the InputStream if there's unread data available, since it
+ // would be lost. We exit above unless we know no more data will be received
+ // for the stream.
+ return;
+ }
+
+ LOG(
+ ("AsyncInputStreamWrapper::MaybeCloseStream close stream due to FIN "
+ "stream=%p",
+ mWebTransportStream.get()));
+ Close();
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::Close() {
+ return mStream->CloseWithStatus(NS_BASE_STREAM_CLOSED);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::Available(
+ uint64_t* aAvailable) {
+ return mStream->Available(aAvailable);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::StreamStatus() {
+ return mStream->StreamStatus();
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::Read(
+ char* aBuf, uint32_t aCount, uint32_t* aResult) {
+ LOG(("WebTransportStreamProxy::AsyncInputStreamWrapper::Read %p", this));
+ nsresult rv = mStream->Read(aBuf, aCount, aResult);
+ MaybeCloseStream();
+ return rv;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::ReadSegments(
+ nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount,
+ uint32_t* aResult) {
+ LOG(("WebTransportStreamProxy::AsyncInputStreamWrapper::ReadSegments %p",
+ this));
+ nsresult rv = mStream->ReadSegments(aWriter, aClosure, aCount, aResult);
+ MaybeCloseStream();
+ return rv;
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::IsNonBlocking(
+ bool* aResult) {
+ return mStream->IsNonBlocking(aResult);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::CloseWithStatus(
+ nsresult aStatus) {
+ return mStream->CloseWithStatus(aStatus);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncInputStreamWrapper::AsyncWait(
+ nsIInputStreamCallback* aCallback, uint32_t aFlags,
+ uint32_t aRequestedCount, nsIEventTarget* aEventTarget) {
+ return mStream->AsyncWait(aCallback, aFlags, aRequestedCount, aEventTarget);
+}
+
+//------------------------------------------------------------------------------
+// WebTransportStreamProxy::AsyncOutputStreamWrapper
+//------------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(WebTransportStreamProxy::AsyncOutputStreamWrapper,
+ nsIOutputStream, nsIAsyncOutputStream)
+
+WebTransportStreamProxy::AsyncOutputStreamWrapper::AsyncOutputStreamWrapper(
+ nsIAsyncOutputStream* aStream)
+ : mStream(aStream) {}
+
+WebTransportStreamProxy::AsyncOutputStreamWrapper::~AsyncOutputStreamWrapper() =
+ default;
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncOutputStreamWrapper::Flush() {
+ return mStream->Flush();
+}
+
+NS_IMETHODIMP
+WebTransportStreamProxy::AsyncOutputStreamWrapper::StreamStatus() {
+ return mStream->StreamStatus();
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncOutputStreamWrapper::Write(
+ const char* aBuf, uint32_t aCount, uint32_t* aResult) {
+ return mStream->Write(aBuf, aCount, aResult);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncOutputStreamWrapper::WriteFrom(
+ nsIInputStream* aFromStream, uint32_t aCount, uint32_t* aResult) {
+ return mStream->WriteFrom(aFromStream, aCount, aResult);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncOutputStreamWrapper::WriteSegments(
+ nsReadSegmentFun aReader, void* aClosure, uint32_t aCount,
+ uint32_t* aResult) {
+ return mStream->WriteSegments(aReader, aClosure, aCount, aResult);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncOutputStreamWrapper::AsyncWait(
+ nsIOutputStreamCallback* aCallback, uint32_t aFlags,
+ uint32_t aRequestedCount, nsIEventTarget* aEventTarget) {
+ return mStream->AsyncWait(aCallback, aFlags, aRequestedCount, aEventTarget);
+}
+
+NS_IMETHODIMP
+WebTransportStreamProxy::AsyncOutputStreamWrapper::CloseWithStatus(
+ nsresult aStatus) {
+ return mStream->CloseWithStatus(aStatus);
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncOutputStreamWrapper::Close() {
+ return mStream->Close();
+}
+
+NS_IMETHODIMP WebTransportStreamProxy::AsyncOutputStreamWrapper::IsNonBlocking(
+ bool* aResult) {
+ return mStream->IsNonBlocking(aResult);
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/webtransport/WebTransportStreamProxy.h b/netwerk/protocol/webtransport/WebTransportStreamProxy.h
new file mode 100644
index 0000000000..972eac3f05
--- /dev/null
+++ b/netwerk/protocol/webtransport/WebTransportStreamProxy.h
@@ -0,0 +1,83 @@
+/* -*- 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_WebTransportStreamProxy_h
+#define mozilla_net_WebTransportStreamProxy_h
+
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIWebTransportStream.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla::net {
+
+class Http3WebTransportStream;
+
+class WebTransportStreamProxy final
+ : public nsIWebTransportReceiveStream,
+ public nsIWebTransportSendStream,
+ public nsIWebTransportBidirectionalStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit WebTransportStreamProxy(Http3WebTransportStream* aStream);
+
+ NS_IMETHOD SendStopSending(uint8_t aError) override;
+ NS_IMETHOD SendFin() override;
+ NS_IMETHOD Reset(uint8_t aErrorCode) override;
+ NS_IMETHOD GetSendStreamStats(
+ nsIWebTransportStreamStatsCallback* aCallback) override;
+ NS_IMETHOD GetReceiveStreamStats(
+ nsIWebTransportStreamStatsCallback* aCallback) override;
+
+ NS_IMETHOD GetHasReceivedFIN(bool* aHasReceivedFIN) override;
+
+ NS_IMETHOD GetInputStream(nsIAsyncInputStream** aOut) override;
+ NS_IMETHOD GetOutputStream(nsIAsyncOutputStream** aOut) override;
+
+ NS_IMETHOD GetStreamId(uint64_t* aId) override;
+
+ private:
+ virtual ~WebTransportStreamProxy();
+
+ class AsyncInputStreamWrapper : public nsIAsyncInputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ AsyncInputStreamWrapper(nsIAsyncInputStream* aStream,
+ Http3WebTransportStream* aWebTransportStream);
+
+ private:
+ virtual ~AsyncInputStreamWrapper();
+ void MaybeCloseStream();
+
+ nsCOMPtr<nsIAsyncInputStream> mStream;
+ RefPtr<Http3WebTransportStream> mWebTransportStream;
+ };
+
+ class AsyncOutputStreamWrapper : public nsIAsyncOutputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+
+ explicit AsyncOutputStreamWrapper(nsIAsyncOutputStream* aStream);
+
+ private:
+ virtual ~AsyncOutputStreamWrapper();
+
+ nsCOMPtr<nsIAsyncOutputStream> mStream;
+ };
+
+ RefPtr<Http3WebTransportStream> mWebTransportStream;
+ RefPtr<AsyncOutputStreamWrapper> mWriter;
+ RefPtr<AsyncInputStreamWrapper> mReader;
+};
+
+} // namespace mozilla::net
+
+#endif
diff --git a/netwerk/protocol/webtransport/moz.build b/netwerk/protocol/webtransport/moz.build
new file mode 100644
index 0000000000..ef0f6705ab
--- /dev/null
+++ b/netwerk/protocol/webtransport/moz.build
@@ -0,0 +1,34 @@
+# -*- 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",
+ "nsIWebTransportStream.idl",
+]
+
+XPIDL_MODULE = "necko_webtransport"
+
+EXPORTS.mozilla.net += [
+ "WebTransportSessionProxy.h",
+ "WebTransportStreamProxy.h",
+]
+
+UNIFIED_SOURCES += [
+ "WebTransportSessionProxy.cpp",
+ "WebTransportStreamProxy.cpp",
+]
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+ "/netwerk/protocol/http",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/netwerk/protocol/webtransport/nsIWebTransport.idl b/netwerk/protocol/webtransport/nsIWebTransport.idl
new file mode 100644
index 0000000000..2740236daa
--- /dev/null
+++ b/netwerk/protocol/webtransport/nsIWebTransport.idl
@@ -0,0 +1,122 @@
+/* -*- 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;
+interface nsIWebTransportBidirectionalStream;
+interface nsIWebTransportSendStream;
+interface nsIWebTransportReceiveStream;
+
+%{C++
+namespace mozilla::dom {
+class ClientInfo;
+}
+namespace mozilla::net {
+class Http3WebTransportSession;
+class Http3WebTransportStream;
+}
+%}
+
+[ptr] native Http3WebTransportSessionPtr(mozilla::net::Http3WebTransportSession);
+[ptr] native Http3WebTransportStreamPtr(mozilla::net::Http3WebTransportStream);
+native Datagram(nsTArray<uint8_t>&&);
+[ref] native const_MaybeClientInfoRef(const mozilla::Maybe<mozilla::dom::ClientInfo>);
+
+[builtinclass, scriptable, uuid(c20d6e77-8cb1-4838-a88d-fff826080aa3)]
+interface nsIWebTransport : nsISupports {
+ cenum WebTransportError : 16 {
+ UNKNOWN_ERROR,
+ INVALID_STATE_ERROR,
+ };
+
+ // When called, perform steps in "Initialization WebTransport over HTTP".
+ void asyncConnect(in nsIURI aURI,
+ in nsIPrincipal aLoadingPrincipal,
+ in unsigned long aSecurityFlags,
+ in WebTransportSessionEventListener aListener);
+
+ void asyncConnectWithClient(in nsIURI aURI,
+ in nsIPrincipal aLoadingPrincipal,
+ in unsigned long aSecurityFlags,
+ in WebTransportSessionEventListener aListener,
+ in const_MaybeClientInfoRef aClientInfo);
+
+ // Asynchronously get states.
+ void getStats();
+
+ // Close the session.
+ void closeSession(in uint32_t aErrorCode,
+ in ACString aReason);
+
+ // Create and open a new WebTransport stream.
+ void createOutgoingBidirectionalStream(in nsIWebTransportStreamCallback aListener);
+ void createOutgoingUnidirectionalStream(in nsIWebTransportStreamCallback aListener);
+
+ void sendDatagram(in Array<uint8_t> aData, in uint64_t aTrackingId);
+
+ void getMaxDatagramSize();
+
+ // This can be only called after onSessionReady().
+ // After this point, we can retarget the underlying WebTransportSessionProxy
+ // object off main thread.
+ [noscript] void retargetTo(in nsIEventTarget aTarget);
+};
+
+// 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);
+
+ // This is used internally to pass the reference of Http3WebTransportStream
+ // object to WebTransportSessionProxy.
+ void onIncomingStreamAvailableInternal(in Http3WebTransportStreamPtr aStream);
+
+ void onStopSending(in uint64_t aStreamId, in nsresult aError);
+ void onResetReceived(in uint64_t aStreamId, in nsresult aError);
+
+ // When a new datagram has been received.
+ void onDatagramReceived(in Array<uint8_t> aData);
+
+ // This is used internally to pass the datagram to WebTransportSessionProxy.
+ void onDatagramReceivedInternal(in Datagram aData);
+
+ void onMaxDatagramSize(in uint64_t aSize);
+
+ cenum DatagramOutcome: 32 {
+ UNKNOWN = 0,
+ DROPPED_TOO_MUCH_DATA = 1,
+ SENT = 2,
+ };
+
+ void onOutgoingDatagramOutCome(
+ in uint64_t aId,
+ in WebTransportSessionEventListener_DatagramOutcome aOutCome);
+
+ // 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 onBidirectionalStreamReady(in nsIWebTransportBidirectionalStream aStream);
+ void onUnidirectionalStreamReady(in nsIWebTransportSendStream aStream);
+ void onError(in uint8_t aError);
+};
diff --git a/netwerk/protocol/webtransport/nsIWebTransportStream.idl b/netwerk/protocol/webtransport/nsIWebTransportStream.idl
new file mode 100644
index 0000000000..c620b213ee
--- /dev/null
+++ b/netwerk/protocol/webtransport/nsIWebTransportStream.idl
@@ -0,0 +1,84 @@
+/* -*- 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"
+
+interface nsIAsyncInputStream;
+interface nsIAsyncOutputStream;
+interface nsIInputStreamCallback;
+interface nsIEventTarget;
+
+%{C++
+namespace mozilla {
+class TimeStamp;
+}
+%}
+
+native TimeStamp(mozilla::TimeStamp);
+
+[builtinclass, scriptable, uuid(ccc3e685-8411-48f0-8b3e-ff6d1fae4809)]
+interface nsIWebTransportSendStreamStats : nsISupports {
+ [noscript] readonly attribute TimeStamp timestamp;
+ readonly attribute unsigned long long bytesSent;
+ readonly attribute unsigned long long bytesAcknowledged;
+};
+
+[builtinclass, scriptable, uuid(43ce1145-30ef-41a7-b97d-fa797f7f7d18)]
+interface nsIWebTransportReceiveStreamStats : nsISupports {
+ [noscript] readonly attribute TimeStamp timestamp;
+ readonly attribute unsigned long long bytesReceived;
+};
+
+[scriptable, uuid(9c1df3f5-bf04-46b6-9977-eb6389076db8)]
+interface nsIWebTransportStreamStatsCallback : nsISupports {
+ void onSendStatsAvailable(in nsIWebTransportSendStreamStats aStats);
+ void onReceiveStatsAvailable(in nsIWebTransportReceiveStreamStats aStats);
+};
+
+[builtinclass, scriptable, uuid(d461b235-6291-4817-adcc-a2a3b3dfc10b)]
+interface nsIWebTransportReceiveStream : nsISupports {
+ // Sends the STOP_SENDING on the stream.
+ void sendStopSending(in uint8_t aError);
+
+ void getReceiveStreamStats(
+ in nsIWebTransportStreamStatsCallback aCallback);
+
+ // When true, this indicates that FIN had been received.
+ readonly attribute boolean hasReceivedFIN;
+ readonly attribute nsIAsyncInputStream inputStream;
+ readonly attribute uint64_t streamId;
+};
+
+[builtinclass, scriptable, uuid(804f245c-52ea-403c-8a78-f751533bdd70)]
+interface nsIWebTransportSendStream : nsISupports {
+ // Sends the FIN on the stream.
+ void sendFin();
+
+ // Reset the stream with the specified error code.
+ void reset(in uint8_t aErrorCode);
+
+ void getSendStreamStats(in nsIWebTransportStreamStatsCallback aCallback);
+ readonly attribute nsIAsyncOutputStream outputStream;
+ readonly attribute uint64_t streamId;
+};
+
+[builtinclass, scriptable, uuid(f9ecb509-36db-4689-97d6-137639a08750)]
+interface nsIWebTransportBidirectionalStream : nsISupports {
+ // Sends the STOP_SENDING on the stream.
+ void sendStopSending(in uint8_t aError);
+
+ // Sends the FIN on the stream.
+ void sendFin();
+
+ // Reset the stream with the specified error code.
+ void reset(in uint8_t aErrorCode);
+
+ // When true, this indicates that FIN had been received.
+ readonly attribute boolean hasReceivedFIN;
+
+ readonly attribute nsIAsyncInputStream inputStream;
+ readonly attribute nsIAsyncOutputStream outputStream;
+ readonly attribute uint64_t streamId;
+};