diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /netwerk/protocol/webtransport | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk/protocol/webtransport')
-rw-r--r-- | netwerk/protocol/webtransport/WebTransportLog.h | 21 | ||||
-rw-r--r-- | netwerk/protocol/webtransport/WebTransportSessionProxy.cpp | 954 | ||||
-rw-r--r-- | netwerk/protocol/webtransport/WebTransportSessionProxy.h | 183 | ||||
-rw-r--r-- | netwerk/protocol/webtransport/WebTransportStreamProxy.cpp | 277 | ||||
-rw-r--r-- | netwerk/protocol/webtransport/WebTransportStreamProxy.h | 77 | ||||
-rw-r--r-- | netwerk/protocol/webtransport/moz.build | 34 | ||||
-rw-r--r-- | netwerk/protocol/webtransport/nsIWebTransport.idl | 103 | ||||
-rw-r--r-- | netwerk/protocol/webtransport/nsIWebTransportStream.idl | 78 |
8 files changed, 1727 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..1df46995b0 --- /dev/null +++ b/netwerk/protocol/webtransport/WebTransportSessionProxy.cpp @@ -0,0 +1,954 @@ +/* -*- 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 "nsIRequest.h" +#include "nsNetUtil.h" +#include "nsProxyRelease.h" +#include "nsSocketTransportService2.h" +#include "mozilla/Logging.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") { + LOG(("WebTransportSessionProxy constructor")); +} + +WebTransportSessionProxy::~WebTransportSessionProxy() { + if (OnSocketThread()) { + return; + } + + MutexAutoLock lock(mMutex); + if ((mState != WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED) && + (mState != WebTransportSessionProxyState::ACTIVE) && + (mState != WebTransportSessionProxyState::SESSION_CLOSE_PENDING)) { + return; + } + + MOZ_ASSERT(mState != WebTransportSessionProxyState::SESSION_CLOSE_PENDING, + "We can not be in the SESSION_CLOSE_PENDING state in destructor, " + "because should e an runnable that holds reference to this" + "object."); + + Unused << gSocketTransportService->Dispatch(NS_NewRunnableFunction( + "WebTransportSessionProxy::ProxyHttp3WebTransportSessionRelease", + [self{std::move(mWebTransportSession)}]() {})); +} + +//----------------------------------------------------------------------------- +// WebTransportSessionProxy::nsIWebTransport +//----------------------------------------------------------------------------- + +nsresult WebTransportSessionProxy::AsyncConnect( + nsIURI* aURI, nsIPrincipal* aPrincipal, uint32_t aSecurityFlags, + WebTransportSessionEventListener* aListener) { + MOZ_ASSERT(NS_IsMainThread()); + + LOG(("WebTransportSessionProxy::AsyncConnect")); + { + MutexAutoLock lock(mMutex); + mListener = aListener; + } + nsSecurityFlags flags = nsILoadInfo::SEC_COOKIES_OMIT | aSecurityFlags; + nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL | + nsIRequest::LOAD_BYPASS_CACHE | + nsIRequest::INHIBIT_CACHING; + nsresult rv = NS_NewChannel(getter_AddRefs(mChannel), aURI, aPrincipal, flags, + nsContentPolicyType::TYPE_OTHER, + /* aCookieJarSettings */ nullptr, + /* aPerformanceStorage */ nullptr, + /* aLoadGroup */ nullptr, + /* aCallbacks */ this, loadFlags); + + NS_ENSURE_SUCCESS(rv, rv); + + // configure HTTP specific stuff + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel); + if (!httpChannel) { + mChannel = nullptr; + return NS_ERROR_ABORT; + } + + { + MutexAutoLock lock(mMutex); + ChangeState(WebTransportSessionProxyState::NEGOTIATING); + } + + rv = mChannel->AsyncOpen(this); + if (NS_FAILED(rv)) { + MutexAutoLock lock(mMutex); + mChannel = nullptr; + mListener = nullptr; + ChangeState(WebTransportSessionProxyState::DONE); + } + return rv; +} + +NS_IMETHODIMP +WebTransportSessionProxy::GetStats() { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +WebTransportSessionProxy::CloseSession(uint32_t status, + const nsACString& reason) { + MOZ_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(mMutex); + mCloseStatus = status; + mReason = reason; + mListener = nullptr; + 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::CloseSessionInternal() { + if (!OnSocketThread()) { + mMutex.AssertCurrentThreadOwns(); + RefPtr<WebTransportSessionProxy> self(this); + Unused << gSocketTransportService->Dispatch(NS_NewRunnableFunction( + "WebTransportSessionProxy::CallCloseWebTransportSession", + [self{std::move(self)}]() { self->CloseSessionInternal(); })); + return; + } + + RefPtr<Http3WebTransportSession> wt; + uint32_t closeStatus = 0; + nsCString reason; + { + MutexAutoLock lock(mMutex); + if (mState == WebTransportSessionProxyState::SESSION_CLOSE_PENDING) { + MOZ_ASSERT(mWebTransportSession); + wt = mWebTransportSession; + mWebTransportSession = nullptr; + closeStatus = mCloseStatus; + reason = mReason; + ChangeState(WebTransportSessionProxyState::DONE); + } else { + MOZ_ASSERT(mState == WebTransportSessionProxyState::DONE); + } + } + if (wt) { + wt->CloseSession(closeStatus, reason); + } +} + +class WebTransportStreamCallbackWrapper final { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebTransportStreamCallbackWrapper) + + explicit WebTransportStreamCallbackWrapper( + nsIWebTransportStreamCallback* aCallback, bool aBidi) + : mCallback(aCallback), mTarget(GetCurrentEventTarget()), 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( + WebTransportStreamCallbackWrapper* aCallback, bool aBidi) { + if (!OnSocketThread()) { + RefPtr<WebTransportSessionProxy> self(this); + RefPtr<WebTransportStreamCallbackWrapper> wrapper(aCallback); + Unused << gSocketTransportService->Dispatch(NS_NewRunnableFunction( + "WebTransportSessionProxy::CreateStreamInternal", + [self{std::move(self)}, wrapper{std::move(wrapper)}, bidi(aBidi)]() { + self->CreateStreamInternal(wrapper, bidi); + })); + 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); + }; + + RefPtr<Http3WebTransportSession> session; + { + MutexAutoLock lock(mMutex); + session = mWebTransportSession; + } + + if (!session) { + MOZ_ASSERT(false, "This should not happen"); + callback(Err(NS_ERROR_UNEXPECTED)); + return; + } + + 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); + if (mState != WebTransportSessionProxyState::ACTIVE || + !mWebTransportSession) { + nsCOMPtr<nsIWebTransportStreamCallback> cb(callback); + NS_DispatchToCurrentThread(NS_NewRunnableFunction( + "WebTransportSessionProxy::CreateOutgoingUnidirectionalStream", + [cb{std::move(cb)}]() { + cb->OnError(nsIWebTransport::INVALID_STATE_ERROR); + })); + return NS_OK; + } + } + + RefPtr<WebTransportStreamCallbackWrapper> wrapper = + new WebTransportStreamCallbackWrapper(callback, false); + CreateStreamInternal(wrapper, false); + return NS_OK; +} + +NS_IMETHODIMP +WebTransportSessionProxy::CreateOutgoingBidirectionalStream( + nsIWebTransportStreamCallback* callback) { + if (!callback) { + return NS_ERROR_INVALID_ARG; + } + + { + MutexAutoLock lock(mMutex); + if (mState != WebTransportSessionProxyState::ACTIVE || + !mWebTransportSession) { + nsCOMPtr<nsIWebTransportStreamCallback> cb(callback); + NS_DispatchToCurrentThread(NS_NewRunnableFunction( + "WebTransportSessionProxy::CreateOutgoingBidirectionalStream", + [cb{std::move(cb)}]() { + cb->OnError(nsIWebTransport::INVALID_STATE_ERROR); + })); + return NS_OK; + } + } + + RefPtr<WebTransportStreamCallbackWrapper> wrapper = + new WebTransportStreamCallbackWrapper(callback, true); + CreateStreamInternal(wrapper, 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) { + 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; + { + MutexAutoLock lock(mMutex); + switch (mState) { + case WebTransportSessionProxyState::INIT: + case WebTransportSessionProxyState::ACTIVE: + case WebTransportSessionProxyState::NEGOTIATING: + MOZ_ASSERT(false, "OnStotRequest cannot be called in this state."); + break; + case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING: + reason = mReason; + closeStatus = mCloseStatus; + listener = mListener; + mListener = nullptr; + ChangeState(WebTransportSessionProxyState::DONE); + break; + case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED: + if (NS_FAILED(aStatus)) { + listener = mListener; + mListener = nullptr; + mReason = ""_ns; + reason = ""_ns; + mCloseStatus = 0; + ChangeState(WebTransportSessionProxyState::SESSION_CLOSE_PENDING); + CloseSessionInternal(); // TODO: find a better error. Currently error + // code 0 is used. + } else { + succeeded = true; + sessionId = mSessionId; + listener = mListener; + ChangeState(WebTransportSessionProxyState::ACTIVE); + pendingEvents = std::move(mPendingEvents); + } + break; + case WebTransportSessionProxyState::SESSION_CLOSE_PENDING: + case WebTransportSessionProxyState::DONE: + break; + } + } + if (listener) { + if (succeeded) { + listener->OnSessionReady(sessionId); + if (!pendingEvents.IsEmpty()) { + 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) { + if (!NS_IsMainThread()) { + RefPtr<WebTransportSessionProxy> self(this); + RefPtr<Http3WebTransportStream> stream = aStream; + Unused << NS_DispatchToMainThread(NS_NewRunnableFunction( + "WebTransportSessionProxy::OnIncomingStreamAvailableInternal", + [self{std::move(self)}, stream{std::move(stream)}]() { + self->OnIncomingStreamAvailableInternal(stream); + })); + return NS_OK; + } + + nsCOMPtr<WebTransportSessionEventListener> listener; + { + MutexAutoLock lock(mMutex); + LOG( + ("WebTransportSessionProxy::OnIncomingStreamAvailableInternal %p " + "mState=%d mListener=%p", + this, mState, mListener.get())); + switch (mState) { + case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED: + // OnSessionReady is not called yet, so we need to wait. + mPendingEvents.AppendElement( + [self = RefPtr{this}, stream = RefPtr{aStream}]() { + self->OnIncomingStreamAvailableInternal(stream); + }); + break; + case WebTransportSessionProxyState::ACTIVE: + listener = mListener; + break; + default: + return NS_ERROR_ABORT; + } + } + + if (!listener) { + 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"); + LOG(("WebTransportSessionProxy::OnSessionClosed")); + MutexAutoLock lock(mMutex); + switch (mState) { + case WebTransportSessionProxyState::INIT: + case WebTransportSessionProxyState::NEGOTIATING: + case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING: + MOZ_ASSERT(false, "OnSessionClosed cannot be called in this state."); + return NS_ERROR_ABORT; + case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED: + case WebTransportSessionProxyState::ACTIVE: { + mCloseStatus = status; + mReason = reason; + mWebTransportSession = nullptr; + ChangeState(WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING); + RefPtr<WebTransportSessionProxy> self(this); + Unused << NS_DispatchToMainThread(NS_NewRunnableFunction( + "WebTransportSessionProxy::CallOnSessionClose", + [self{std::move(self)}]() { self->CallOnSessionClosed(); })); + } break; + case WebTransportSessionProxyState::SESSION_CLOSE_PENDING: + ChangeState(WebTransportSessionProxyState::DONE); + break; + case WebTransportSessionProxyState::DONE: + // The session has been canceled. We do not need to set + // mWebTransportSession. + break; + } + return NS_OK; +} + +void WebTransportSessionProxy::CallOnSessionClosed() { + MOZ_ASSERT(NS_IsMainThread(), "not on socket thread"); + nsCOMPtr<WebTransportSessionEventListener> listener; + nsAutoCString reason; + uint32_t closeStatus = 0; + { + MutexAutoLock lock(mMutex); + switch (mState) { + case WebTransportSessionProxyState::INIT: + case WebTransportSessionProxyState::NEGOTIATING: + case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED: + case WebTransportSessionProxyState::ACTIVE: + case WebTransportSessionProxyState::SESSION_CLOSE_PENDING: + MOZ_ASSERT(false, + "CallOnSessionClosed cannot be called in this state."); + break; + case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING: + listener = mListener; + mListener = nullptr; + reason = mReason; + closeStatus = mCloseStatus; + ChangeState(WebTransportSessionProxyState::DONE); + break; + case WebTransportSessionProxyState::DONE: + break; + } + } + if (listener) { + listener->OnSessionClosed(closeStatus, reason); + } +} + +void WebTransportSessionProxy::ChangeState( + WebTransportSessionProxyState newState) { + mMutex.AssertCurrentThreadOwns(); + LOG(("WebTransportSessionProxy::ChangeState %d -> %d [this=%p]", mState, + newState, this)); + switch (newState) { + case WebTransportSessionProxyState::INIT: + MOZ_ASSERT(false, "Cannot change into INIT sate."); + break; + case WebTransportSessionProxyState::NEGOTIATING: + MOZ_ASSERT(mState == WebTransportSessionProxyState::INIT, + "Only from INIT can be change into NEGOTIATING"); + MOZ_ASSERT(mChannel); + MOZ_ASSERT(mListener); + break; + case WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED: + MOZ_ASSERT( + mState == WebTransportSessionProxyState::NEGOTIATING, + "Only from NEGOTIATING can be change into NEGOTIATING_SUCCEEDED"); + MOZ_ASSERT(mChannel); + MOZ_ASSERT(mWebTransportSession); + MOZ_ASSERT(mListener); + break; + case WebTransportSessionProxyState::ACTIVE: + MOZ_ASSERT(mState == WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED, + "Only from NEGOTIATING_SUCCEEDED can be change into ACTIVE"); + MOZ_ASSERT(!mChannel); + MOZ_ASSERT(mWebTransportSession); + MOZ_ASSERT(mListener); + break; + case WebTransportSessionProxyState::SESSION_CLOSE_PENDING: + MOZ_ASSERT( + (mState == WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED) || + (mState == WebTransportSessionProxyState::ACTIVE), + "Only from NEGOTIATING_SUCCEEDED and ACTIVE can be change into" + " SESSION_CLOSE_PENDING"); + MOZ_ASSERT(!mChannel); + MOZ_ASSERT(mWebTransportSession); + MOZ_ASSERT(!mListener); + break; + case WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING: + MOZ_ASSERT( + (mState == WebTransportSessionProxyState::NEGOTIATING_SUCCEEDED) || + (mState == WebTransportSessionProxyState::ACTIVE), + "Only from NEGOTIATING_SUCCEEDED and ACTIVE can be change into" + " CLOSE_CALLBACK_PENDING"); + MOZ_ASSERT(!mWebTransportSession); + MOZ_ASSERT(mListener); + break; + case WebTransportSessionProxyState::DONE: + MOZ_ASSERT( + (mState == WebTransportSessionProxyState::NEGOTIATING) || + (mState == + WebTransportSessionProxyState::SESSION_CLOSE_PENDING) || + (mState == WebTransportSessionProxyState::CLOSE_CALLBACK_PENDING), + "Only from NEGOTIATING, SESSION_CLOSE_PENDING and " + "CLOSE_CALLBACK_PENDING can be change into DONE"); + MOZ_ASSERT(!mChannel); + MOZ_ASSERT(!mWebTransportSession); + MOZ_ASSERT(!mListener); + break; + } + mState = newState; +} + +void WebTransportSessionProxy::NotifyDatagramReceived( + nsTArray<uint8_t>&& aData) { + // TODO: this should be on the target thread, but the target thread is main + // thread for now. + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<WebTransportSessionEventListener> listener; + { + MutexAutoLock lock(mMutex); + if (mState != WebTransportSessionProxyState::ACTIVE || !mListener) { + return; + } + listener = mListener; + } + + listener->OnDatagramReceived(aData); +} + +NS_IMETHODIMP WebTransportSessionProxy::OnDatagramReceivedInternal( + nsTArray<uint8_t>&& aData) { + MOZ_ASSERT(OnSocketThread()); + + if (!NS_IsMainThread()) { + return NS_DispatchToMainThread(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) { + // TODO: this should be on the target thread, but the target thread is main + // thread for now. + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<WebTransportSessionEventListener> listener; + { + MutexAutoLock lock(mMutex); + if (mState != WebTransportSessionProxyState::ACTIVE || !mListener) { + return; + } + listener = mListener; + } + + listener->OnMaxDatagramSize(aSize); +} + +NS_IMETHODIMP WebTransportSessionProxy::OnMaxDatagramSize(uint64_t aSize) { + MOZ_ASSERT(OnSocketThread()); + + if (!NS_IsMainThread()) { + return NS_DispatchToMainThread( + 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) { + // TODO: this should be on the target thread, but the target thread is main + // thread for now. + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<WebTransportSessionEventListener> listener; + { + MutexAutoLock lock(mMutex); + 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()); + + if (!NS_IsMainThread()) { + return NS_DispatchToMainThread(NS_NewRunnableFunction( + "WebTransportSessionProxy::OnOutgoingDatagramOutCome", + [self = RefPtr{this}, id(aId), outcome(aOutCome)] { + self->OnOutgoingDatagramOutComeInternal(id, outcome); + })); + } + + OnOutgoingDatagramOutComeInternal(aId, aOutCome); + 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..c2edf2e3c1 --- /dev/null +++ b/netwerk/protocol/webtransport/WebTransportSessionProxy.h @@ -0,0 +1,183 @@ +/* -*- 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 CallOnSessionClosed(); + + enum WebTransportSessionProxyState { + INIT, + NEGOTIATING, + NEGOTIATING_SUCCEEDED, + ACTIVE, + CLOSE_CALLBACK_PENDING, + SESSION_CLOSE_PENDING, + DONE, + }; + mozilla::Mutex mMutex; + WebTransportSessionProxyState mState MOZ_GUARDED_BY(mMutex) = + WebTransportSessionProxyState::INIT; + void ChangeState(WebTransportSessionProxyState newState); + void CreateStreamInternal(WebTransportStreamCallbackWrapper* aCallback, + 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); + // This is used to store events happened before OnSessionReady. + nsTArray<std::function<void()>> mPendingEvents 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..6046872f67 --- /dev/null +++ b/netwerk/protocol/webtransport/WebTransportStreamProxy.cpp @@ -0,0 +1,277 @@ +/* -*- 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_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream) + NS_INTERFACE_MAP_ENTRY(nsIOutputStream) + NS_INTERFACE_MAP_ENTRY(nsIAsyncOutputStream) +NS_INTERFACE_MAP_END + +WebTransportStreamProxy::WebTransportStreamProxy( + Http3WebTransportStream* aStream) + : mWebTransportStream(aStream) { + mWebTransportStream->GetWriterAndReader(getter_AddRefs(mWriter), + getter_AddRefs(mReader)); +} + +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(GetCurrentEventTarget()) {} + + 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::Close() { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP WebTransportStreamProxy::Available(uint64_t* aAvailable) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP WebTransportStreamProxy::Read(char* aBuf, uint32_t aCount, + uint32_t* aResult) { + if (!mReader) { + return NS_ERROR_UNEXPECTED; + } + + return mReader->Read(aBuf, aCount, aResult); +} + +NS_IMETHODIMP WebTransportStreamProxy::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, + uint32_t aCount, + uint32_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP WebTransportStreamProxy::IsNonBlocking(bool* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP WebTransportStreamProxy::CloseWithStatus(nsresult aStatus) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP WebTransportStreamProxy::AsyncWait( + nsIInputStreamCallback* aCallback, uint32_t aFlags, + uint32_t aRequestedCount, nsIEventTarget* aEventTarget) { + if (!mReader) { + return NS_ERROR_UNEXPECTED; + } + + return mReader->AsyncWait(aCallback, aFlags, aRequestedCount, aEventTarget); +} + +NS_IMETHODIMP WebTransportStreamProxy::AsyncWaitForRead( + nsIInputStreamCallback* aCallback, uint32_t aFlags, + uint32_t aRequestedCount, nsIEventTarget* aTarget) { + return AsyncWait(aCallback, aFlags, aRequestedCount, aTarget); +} + +NS_IMETHODIMP WebTransportStreamProxy::Flush() { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP WebTransportStreamProxy::Write(const char* aBuf, uint32_t aCount, + uint32_t* aResult) { + if (!mWriter) { + return NS_ERROR_UNEXPECTED; + } + + return mWriter->Write(aBuf, aCount, aResult); +} + +// static +nsresult WebTransportStreamProxy::WriteFromSegments( + nsIInputStream* input, void* closure, const char* fromSegment, + uint32_t offset, uint32_t count, uint32_t* countRead) { + WebTransportStreamProxy* self = (WebTransportStreamProxy*)closure; + return self->Write(fromSegment, count, countRead); +} + +NS_IMETHODIMP WebTransportStreamProxy::WriteFrom(nsIInputStream* aFromStream, + uint32_t aCount, + uint32_t* aResult) { + return aFromStream->ReadSegments(WriteFromSegments, this, aCount, aResult); +} + +NS_IMETHODIMP WebTransportStreamProxy::WriteSegments(nsReadSegmentFun aReader, + void* aClosure, + uint32_t aCount, + uint32_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP WebTransportStreamProxy::AsyncWait( + nsIOutputStreamCallback* aCallback, uint32_t aFlags, + uint32_t aRequestedCount, nsIEventTarget* aEventTarget) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +} // namespace mozilla::net diff --git a/netwerk/protocol/webtransport/WebTransportStreamProxy.h b/netwerk/protocol/webtransport/WebTransportStreamProxy.h new file mode 100644 index 0000000000..5506e86eed --- /dev/null +++ b/netwerk/protocol/webtransport/WebTransportStreamProxy.h @@ -0,0 +1,77 @@ +/* -*- 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 nsIAsyncInputStream, + public nsIAsyncOutputStream { + 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 Close() override; + NS_IMETHOD Available(uint64_t* aAvailable) override; + NS_IMETHOD Read(char* aBuf, uint32_t aCount, uint32_t* aResult) override; + NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aResult) override; + NS_IMETHOD IsNonBlocking(bool* aResult) override; + NS_IMETHOD CloseWithStatus(nsresult aStatus) override; + NS_IMETHOD AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) override; + // XPCConnect seems to be confused about the other AsyncWait (with + // nsIOutputStreamCallback), so we need this function for reading. + NS_IMETHOD AsyncWaitForRead(nsIInputStreamCallback* aCallback, + uint32_t aFlags, uint32_t aRequestedCount, + nsIEventTarget* aTarget) override; + + NS_IMETHOD Flush() override; + NS_IMETHOD Write(const char* aBuf, uint32_t aCount, + uint32_t* aResult) override; + NS_IMETHOD WriteFrom(nsIInputStream* aFromStream, uint32_t aCount, + uint32_t* aResult) override; + NS_IMETHOD WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* aResult) override; + NS_IMETHOD AsyncWait(nsIOutputStreamCallback* aCallback, uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) override; + + private: + virtual ~WebTransportStreamProxy(); + + static nsresult WriteFromSegments(nsIInputStream*, void*, const char*, + uint32_t offset, uint32_t count, + uint32_t* countRead); + + RefPtr<Http3WebTransportStream> mWebTransportStream; + nsCOMPtr<nsIAsyncOutputStream> mWriter; + nsCOMPtr<nsIAsyncInputStream> 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..38dcf4d588 --- /dev/null +++ b/netwerk/protocol/webtransport/nsIWebTransport.idl @@ -0,0 +1,103 @@ +/* -*- 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::net { +class Http3WebTransportSession; +class Http3WebTransportStream; +} +%} + +[ptr] native Http3WebTransportSessionPtr(mozilla::net::Http3WebTransportSession); +[ptr] native Http3WebTransportStreamPtr(mozilla::net::Http3WebTransportStream); +native Datagram(nsTArray<uint8_t>&&); + +[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); + // 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(); +}; + +// 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); + + // 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..8917807ba7 --- /dev/null +++ b/netwerk/protocol/webtransport/nsIWebTransportStream.idl @@ -0,0 +1,78 @@ +/* -*- 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 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); + + void asyncWaitForRead(in nsIInputStreamCallback aCallback, + in unsigned long aFlags, + in unsigned long aRequestedCount, + in nsIEventTarget aEventTarget); +}; + +[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); +}; + +[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); + + void asyncWaitForRead(in nsIInputStreamCallback aCallback, + in unsigned long aFlags, + in unsigned long aRequestedCount, + in nsIEventTarget aEventTarget); +}; |