diff options
Diffstat (limited to 'netwerk/protocol/webtransport')
-rw-r--r-- | netwerk/protocol/webtransport/WebTransportHash.cpp | 24 | ||||
-rw-r--r-- | netwerk/protocol/webtransport/WebTransportHash.h | 30 | ||||
-rw-r--r-- | netwerk/protocol/webtransport/WebTransportLog.h | 21 | ||||
-rw-r--r-- | netwerk/protocol/webtransport/WebTransportSessionProxy.cpp | 1269 | ||||
-rw-r--r-- | netwerk/protocol/webtransport/WebTransportSessionProxy.h | 195 | ||||
-rw-r--r-- | netwerk/protocol/webtransport/WebTransportStreamProxy.cpp | 386 | ||||
-rw-r--r-- | netwerk/protocol/webtransport/WebTransportStreamProxy.h | 84 | ||||
-rw-r--r-- | netwerk/protocol/webtransport/moz.build | 36 | ||||
-rw-r--r-- | netwerk/protocol/webtransport/nsIWebTransport.idl | 132 | ||||
-rw-r--r-- | netwerk/protocol/webtransport/nsIWebTransportStream.idl | 89 |
10 files changed, 2266 insertions, 0 deletions
diff --git a/netwerk/protocol/webtransport/WebTransportHash.cpp b/netwerk/protocol/webtransport/WebTransportHash.cpp new file mode 100644 index 0000000000..f53fbacbcf --- /dev/null +++ b/netwerk/protocol/webtransport/WebTransportHash.cpp @@ -0,0 +1,24 @@ +/* -*- 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 "WebTransportHash.h" + +namespace mozilla::net { + +NS_IMPL_ISUPPORTS(WebTransportHash, nsIWebTransportHash); + +NS_IMETHODIMP +WebTransportHash::GetValue(nsTArray<uint8_t>& aValue) { + aValue.Clear(); + aValue.AppendElements(mValue); + return NS_OK; +} + +NS_IMETHODIMP +WebTransportHash::GetAlgorithm(nsACString& algorithm) { + algorithm.Assign(mAlgorithm); + return NS_OK; +} +} // namespace mozilla::net diff --git a/netwerk/protocol/webtransport/WebTransportHash.h b/netwerk/protocol/webtransport/WebTransportHash.h new file mode 100644 index 0000000000..b3037f560a --- /dev/null +++ b/netwerk/protocol/webtransport/WebTransportHash.h @@ -0,0 +1,30 @@ +/* -*- 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_WebTransportHash_h +#define mozilla_net_WebTransportHash_h + +#include "nsIWebTransport.h" + +namespace mozilla::net { + +class WebTransportHash final : public nsIWebTransportHash { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIWEBTRANSPORTHASH + + explicit WebTransportHash(const nsACString& algorithm, + const nsTArray<uint8_t>& value) + : mAlgorithm(algorithm), mValue(Span(value)) {} + + private: + ~WebTransportHash() = default; + nsCString mAlgorithm; + nsTArray<uint8_t> mValue; +}; + +} // namespace mozilla::net + +#endif // mozilla_net_WebTransportHash_h 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..e650a74d5b --- /dev/null +++ b/netwerk/protocol/webtransport/WebTransportSessionProxy.cpp @@ -0,0 +1,1269 @@ +/* -*- 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 "ScopedNSSTypes.h" +#include "WebTransportSessionProxy.h" +#include "WebTransportStreamProxy.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIRequest.h" +#include "nsITransportSecurityInfo.h" +#include "nsIX509Cert.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, + const nsTArray<RefPtr<nsIWebTransportHash>>& aServerCertHashes, + nsIPrincipal* aPrincipal, uint32_t aSecurityFlags, + WebTransportSessionEventListener* aListener) { + return AsyncConnectWithClient(aURI, std::move(aServerCertHashes), aPrincipal, + aSecurityFlags, aListener, + Maybe<dom::ClientInfo>()); +} + +nsresult WebTransportSessionProxy::AsyncConnectWithClient( + nsIURI* aURI, + const nsTArray<RefPtr<nsIWebTransportHash>>& aServerCertHashes, + 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(false, 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; + } + + if (!aServerCertHashes.IsEmpty()) { + mServerCertHashes.AppendElements(aServerCertHashes); + } + + { + 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->GetWebExposedOriginSerialization(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(); + mServerCertHashes.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) || + !CheckServerCertificateIfNeeded()) { + 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(false, 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(false, 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(bool aCleanly, uint32_t aStatus, + const nsACString& aReason) { + 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(aReason); + mPendingEvents.AppendElement([self = RefPtr{this}, status(aStatus), + closeReason(std::move(closeReason)), + cleanly(aCleanly)]() { + Unused << self->OnSessionClosed(cleanly, 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: { + mCleanly = aCleanly; + mCloseStatus = aStatus; + mReason = aReason; + 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; + bool cleanly = false; + 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; + cleanly = mCleanly; + 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(cleanly, closeStatus, reason); + } +} + +bool WebTransportSessionProxy::CheckServerCertificateIfNeeded() { + if (mServerCertHashes.IsEmpty()) { + return true; + } + + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel); + MOZ_ASSERT(httpChannel, "Not a http channel ?"); + nsCOMPtr<nsITransportSecurityInfo> tsi; + httpChannel->GetSecurityInfo(getter_AddRefs(tsi)); + MOZ_ASSERT(tsi, + "We shouln't reach this code before setting the security info."); + nsCOMPtr<nsIX509Cert> cert; + nsresult rv = tsi->GetServerCert(getter_AddRefs(cert)); + if (!cert || NS_WARN_IF(NS_FAILED(rv))) return true; + nsTArray<uint8_t> certDER; + if (NS_FAILED(cert->GetRawDER(certDER))) { + return false; + } + // https://w3c.github.io/webtransport/#compute-a-certificate-hash + nsTArray<uint8_t> certHash; + if (NS_FAILED(Digest::DigestBuf(SEC_OID_SHA256, certDER.Elements(), + certDER.Length(), certHash)) || + certHash.Length() != SHA256_LENGTH) { + return false; + } + auto verifyCertDer = [&certHash](const auto& hash) { + return certHash.Length() == hash.Length() && + memcmp(certHash.Elements(), hash.Elements(), certHash.Length()) == 0; + }; + + // https://w3c.github.io/webtransport/#verify-a-certificate-hash + for (const auto& hash : mServerCertHashes) { + nsCString algorithm; + if (NS_FAILED(hash->GetAlgorithm(algorithm)) || algorithm != "sha-256") { + continue; + LOG(("Unexpected non-SHA-256 hash")); + } + + nsTArray<uint8_t> value; + if (NS_FAILED(hash->GetValue(value))) { + continue; + LOG(("Unexpected corrupted hash")); + } + + if (verifyCertDer(value)) { + return true; + } + } + return false; +} + +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..aebb446e21 --- /dev/null +++ b/netwerk/protocol/webtransport/WebTransportSessionProxy.h @@ -0,0 +1,195 @@ +/* -*- 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); + bool CheckServerCertificateIfNeeded(); + + nsCOMPtr<nsIChannel> mChannel; + nsCOMPtr<nsIChannel> mRedirectChannel; + nsTArray<RefPtr<nsIWebTransportHash>> mServerCertHashes; + 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 mCleanly MOZ_GUARDED_BY(mMutex) = false; + 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..6192bda0f7 --- /dev/null +++ b/netwerk/protocol/webtransport/WebTransportStreamProxy.cpp @@ -0,0 +1,386 @@ +/* -*- 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; +} + +NS_IMETHODIMP WebTransportStreamProxy::SetSendOrder(Maybe<int64_t> aSendOrder) { + if (!OnSocketThread()) { + return gSocketTransportService->Dispatch(NS_NewRunnableFunction( + "SetSendOrder", [stream = mWebTransportStream, aSendOrder]() { + stream->SetSendOrder(aSendOrder); + })); + } + mWebTransportStream->SetSendOrder(aSendOrder); + 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); + if (*aResult > 0) { + LOG((" Read %u bytes", *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) { + LOG( + ("WebTransportStreamProxy::AsyncOutputStreamWrapper::Write %p %u bytes, " + "first byte %c", + this, aCount, aBuf[0])); + 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..e6ccc573fa --- /dev/null +++ b/netwerk/protocol/webtransport/WebTransportStreamProxy.h @@ -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/. */ + +#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; + NS_IMETHOD SetSendOrder(Maybe<int64_t> aSendOrder) 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..2dd99978ac --- /dev/null +++ b/netwerk/protocol/webtransport/moz.build @@ -0,0 +1,36 @@ +# -*- 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 += [ + "WebTransportHash.h", + "WebTransportSessionProxy.h", + "WebTransportStreamProxy.h", +] + +UNIFIED_SOURCES += [ + "WebTransportHash.cpp", + "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..faf99e61b6 --- /dev/null +++ b/netwerk/protocol/webtransport/nsIWebTransport.idl @@ -0,0 +1,132 @@ +/* -*- 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; +interface nsIWebTransportHash; + +%{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 Array<nsIWebTransportHash> aServerCertHashes, + in nsIPrincipal aLoadingPrincipal, + in unsigned long aSecurityFlags, + in WebTransportSessionEventListener aListener); + + void asyncConnectWithClient(in nsIURI aURI, + in Array<nsIWebTransportHash> aServerCertHashes, + in nsIPrincipal aLoadingPrincipal, + in unsigned long aSecurityFlags, + in WebTransportSessionEventListener aListener, + in const_MaybeClientInfoRef aClientInfo); + + // Asynchronously get stats. + 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 bool aCleanly, + 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); +}; + +[scriptable, uuid(2523a26e-94be-4de6-8c27-9b4ffff742f0)] +interface nsIWebTransportHash : nsISupports { + readonly attribute ACString algorithm; + readonly attribute Array<uint8_t> value; +}; diff --git a/netwerk/protocol/webtransport/nsIWebTransportStream.idl b/netwerk/protocol/webtransport/nsIWebTransportStream.idl new file mode 100644 index 0000000000..9283f4aa30 --- /dev/null +++ b/netwerk/protocol/webtransport/nsIWebTransportStream.idl @@ -0,0 +1,89 @@ +/* -*- 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++ +#include "mozilla/Maybe.h" + +namespace mozilla { +class TimeStamp; +} +%} + +native TimeStamp(mozilla::TimeStamp); +native MaybeInt64(mozilla::Maybe<int64_t>); + +[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; + [noscript] void setSendOrder(in MaybeInt64 aSendOrder); +}; + +[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; + [noscript] void setSendOrder(in MaybeInt64 aSendOrder); +}; |