diff options
Diffstat (limited to 'dom/webtransport')
32 files changed, 4144 insertions, 0 deletions
diff --git a/dom/webtransport/api/WebTransport.cpp b/dom/webtransport/api/WebTransport.cpp new file mode 100644 index 0000000000..d18e18cb98 --- /dev/null +++ b/dom/webtransport/api/WebTransport.cpp @@ -0,0 +1,966 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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/. */ + +#include "WebTransport.h" + +#include "WebTransportBidirectionalStream.h" +#include "mozilla/RefPtr.h" +#include "nsUTF8Utils.h" +#include "nsIURL.h" +#include "nsIWebTransportStream.h" +#include "mozilla/Assertions.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/DOMExceptionBinding.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/PWebTransport.h" +#include "mozilla/dom/ReadableStream.h" +#include "mozilla/dom/ReadableStreamDefaultController.h" +#include "mozilla/dom/RemoteWorkerChild.h" +#include "mozilla/dom/WebTransportDatagramDuplexStream.h" +#include "mozilla/dom/WebTransportError.h" +#include "mozilla/dom/WebTransportLog.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerRunnable.h" +#include "mozilla/dom/WritableStream.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/PBackgroundChild.h" + +using namespace mozilla::ipc; + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WebTransport) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WebTransport) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingUnidirectionalStreams) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingBidirectionalStreams) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingUnidirectionalAlgorithm) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingBidirectionalAlgorithm) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDatagrams) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReady) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mClosed) + for (const auto& hashEntry : tmp->mSendStreams.Values()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSendStreams entry item"); + cb.NoteXPCOMChild(hashEntry); + } + for (const auto& hashEntry : tmp->mReceiveStreams.Values()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mReceiveStreams entry item"); + cb.NoteXPCOMChild(hashEntry); + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WebTransport) + tmp->mSendStreams.Clear(); + tmp->mReceiveStreams.Clear(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mUnidirectionalStreams) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mBidirectionalStreams) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingUnidirectionalStreams) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingBidirectionalStreams) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingUnidirectionalAlgorithm) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingBidirectionalAlgorithm) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDatagrams) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mReady) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mClosed) + if (tmp->mChild) { + tmp->mChild->Shutdown(false); + tmp->mChild = nullptr; + } +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(WebTransport) +NS_IMPL_CYCLE_COLLECTING_RELEASE(WebTransport) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebTransport) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +WebTransport::WebTransport(nsIGlobalObject* aGlobal) + : mGlobal(aGlobal), + mState(WebTransportState::CONNECTING), + mReliability(WebTransportReliabilityMode::Pending) { + LOG(("Creating WebTransport %p", this)); +} + +WebTransport::~WebTransport() { + // Should be empty by this point, because we should always have run cleanup: + // https://w3c.github.io/webtransport/#webtransport-procedures + LOG(("~WebTransport() for %p", this)); + MOZ_ASSERT(mSendStreams.IsEmpty()); + MOZ_ASSERT(mReceiveStreams.IsEmpty()); + // If this WebTransport was destroyed without being closed properly, make + // sure to clean up the channel. + // Since child has a raw ptr to us, we MUST call Shutdown() before we're + // destroyed + if (mChild) { + mChild->Shutdown(true); + } +} + +// From parent +void WebTransport::NewBidirectionalStream( + uint64_t aStreamId, const RefPtr<DataPipeReceiver>& aIncoming, + const RefPtr<DataPipeSender>& aOutgoing) { + LOG_VERBOSE(("NewBidirectionalStream()")); + // Create a Bidirectional stream and push it into the + // IncomingBidirectionalStreams stream. Must be added to the ReceiveStreams + // and SendStreams arrays + + UniquePtr<BidirectionalPair> streams( + new BidirectionalPair(aIncoming, aOutgoing)); + auto tuple = std::tuple<uint64_t, UniquePtr<BidirectionalPair>>( + aStreamId, std::move(streams)); + mBidirectionalStreams.AppendElement(std::move(tuple)); + // We need to delete them all! + + // Notify something to wake up readers of IncomingReceiveStreams + // The callback is always set/used from the same thread (MainThread or a + // Worker thread). + if (mIncomingBidirectionalAlgorithm) { + RefPtr<WebTransportIncomingStreamsAlgorithms> callback = + mIncomingBidirectionalAlgorithm; + LOG(("NotifyIncomingStream")); + callback->NotifyIncomingStream(); + } +} + +void WebTransport::NewUnidirectionalStream( + uint64_t aStreamId, const RefPtr<mozilla::ipc::DataPipeReceiver>& aStream) { + LOG_VERBOSE(("NewUnidirectionalStream()")); + // Create a Unidirectional stream and push it into the + // IncomingUnidirectionalStreams stream. Must be added to the ReceiveStreams + // array + + mUnidirectionalStreams.AppendElement( + std::tuple<uint64_t, RefPtr<mozilla::ipc::DataPipeReceiver>>(aStreamId, + aStream)); + // Notify something to wake up readers of IncomingReceiveStreams + // The callback is always set/used from the same thread (MainThread or a + // Worker thread). + if (mIncomingUnidirectionalAlgorithm) { + RefPtr<WebTransportIncomingStreamsAlgorithms> callback = + mIncomingUnidirectionalAlgorithm; + LOG(("NotifyIncomingStream")); + callback->NotifyIncomingStream(); + } +} + +void WebTransport::NewDatagramReceived(nsTArray<uint8_t>&& aData, + const mozilla::TimeStamp& aTimeStamp) { + mDatagrams->NewDatagramReceived(std::move(aData), aTimeStamp); +} + +// WebIDL Boilerplate + +nsIGlobalObject* WebTransport::GetParentObject() const { return mGlobal; } + +JSObject* WebTransport::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return WebTransport_Binding::Wrap(aCx, this, aGivenProto); +} + +// WebIDL Interface + +/* static */ +already_AddRefed<WebTransport> WebTransport::Constructor( + const GlobalObject& aGlobal, const nsAString& aURL, + const WebTransportOptions& aOptions, ErrorResult& aError) { + LOG(("Creating WebTransport for %s", NS_ConvertUTF16toUTF8(aURL).get())); + // https://w3c.github.io/webtransport/#webtransport-constructor + + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<WebTransport> result = new WebTransport(global); + result->Init(aGlobal, aURL, aOptions, aError); + if (aError.Failed()) { + return nullptr; + } + + // Don't let this document go into BFCache + result->NotifyToWindow(true); + + // Step 25 Return transport + return result.forget(); +} + +void WebTransport::Init(const GlobalObject& aGlobal, const nsAString& aURL, + const WebTransportOptions& aOptions, + ErrorResult& aError) { + // https://w3c.github.io/webtransport/#webtransport-constructor + // Initiate connection with parent + using mozilla::ipc::BackgroundChild; + using mozilla::ipc::Endpoint; + using mozilla::ipc::PBackgroundChild; + + // https://w3c.github.io/webtransport/#webtransport-constructor + // Steps 1-4: Parse string for validity and Throw a SyntaxError if it isn't + // Let parsedURL be the URL record resulting from parsing url. + // If parsedURL is a failure, throw a SyntaxError exception. + // If parsedURL scheme is not https, throw a SyntaxError exception. + // If parsedURL fragment is not null, throw a SyntaxError exception. + if (!ParseURL(aURL)) { + aError.ThrowSyntaxError("Invalid WebTransport URL"); + return; + } + // Step 5: Let allowPooling be options's allowPooling if it exists, and false + // otherwise. + // Step 6: Let dedicated be the negation of allowPooling. + bool dedicated = !aOptions.mAllowPooling; + // Step 7: Let serverCertificateHashes be options's serverCertificateHashes if + // it exists, and null otherwise. + // Step 8: If dedicated is false and serverCertificateHashes is non-null, + // then throw a TypeError. + nsTArray<mozilla::ipc::WebTransportHash> aServerCertHashes; + if (aOptions.mServerCertificateHashes.WasPassed()) { + if (!dedicated) { + aError.ThrowNotSupportedError( + "serverCertificateHashes not supported for non-dedicated " + "connections"); + return; + } + for (const auto& hash : aOptions.mServerCertificateHashes.Value()) { + if (!hash.mAlgorithm.WasPassed() || !hash.mValue.WasPassed()) continue; + + if (hash.mAlgorithm.Value() != u"sha-256") { + LOG(("Algorithms other than SHA-256 are not supported")); + continue; + } + + nsTArray<uint8_t> data; + if (!AppendTypedArrayDataTo(hash.mValue.Value(), data)) { + aError.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + + nsCString alg = NS_ConvertUTF16toUTF8(hash.mAlgorithm.Value()); + aServerCertHashes.EmplaceBack(alg, data); + } + } + // Step 9: Let requireUnreliable be options's requireUnreliable. + bool requireUnreliable = aOptions.mRequireUnreliable; + // Step 10: Let congestionControl be options's congestionControl. + // Step 11: If congestionControl is not "default", and the user agent + // does not support any congestion control algorithms that optimize for + // congestionControl, as allowed by [RFC9002] section 7, then set + // congestionControl to "default". + WebTransportCongestionControl congestionControl = + WebTransportCongestionControl::Default; // aOptions.mCongestionControl; + // Set this to 'default' until we add congestion control setting + + // Setup up WebTransportDatagramDuplexStream + // Step 12: Let incomingDatagrams be a new ReadableStream. + // Step 13: Let outgoingDatagrams be a new WritableStream. + // Step 14: Let datagrams be the result of creating a + // WebTransportDatagramDuplexStream, its readable set to + // incomingDatagrams and its writable set to outgoingDatagrams. + mDatagrams = new WebTransportDatagramDuplexStream(mGlobal, this); + mDatagrams->Init(aError); + if (aError.Failed()) { + return; + } + + // XXX TODO + + // Step 15 Let transport be a newly constructed WebTransport object, with: + // SendStreams: empty ordered set + // ReceiveStreams: empty ordered set + // Ready: new promise + mReady = Promise::CreateInfallible(mGlobal); + + // Closed: new promise + mClosed = Promise::CreateInfallible(mGlobal); + + PBackgroundChild* backgroundChild = + BackgroundChild::GetOrCreateForCurrentThread(); + if (NS_WARN_IF(!backgroundChild)) { + return; + } + + nsCOMPtr<nsIPrincipal> principal = mGlobal->PrincipalOrNull(); + mozilla::Maybe<IPCClientInfo> ipcClientInfo; + + if (mGlobal->GetClientInfo().isSome()) { + ipcClientInfo = mozilla::Some(mGlobal->GetClientInfo().ref().ToIPC()); + } + // Create a new IPC connection + Endpoint<PWebTransportParent> parentEndpoint; + Endpoint<PWebTransportChild> childEndpoint; + MOZ_ALWAYS_SUCCEEDS( + PWebTransport::CreateEndpoints(&parentEndpoint, &childEndpoint)); + + RefPtr<WebTransportChild> child = new WebTransportChild(this); + if (NS_IsMainThread()) { + if (!childEndpoint.Bind(child)) { + return; + } + } else if (!childEndpoint.Bind(child, mGlobal->SerialEventTarget())) { + return; + } + + mState = WebTransportState::CONNECTING; + + JSContext* cx = aGlobal.Context(); + // Set up Datagram streams + // Step 16: Let pullDatagramsAlgorithm be an action that runs pullDatagrams + // with transport. + // Step 17: Let writeDatagramsAlgorithm be an action that runs writeDatagrams + // with transport. + // Step 18: Set up incomingDatagrams with pullAlgorithm set to + // pullDatagramsAlgorithm, and highWaterMark set to 0. + // Step 19: Set up outgoingDatagrams with writeAlgorithm set to + // writeDatagramsAlgorithm. + + // XXX TODO + + // Step 20: Let pullBidirectionalStreamAlgorithm be an action that runs + // pullBidirectionalStream with transport. + // Step 21: Set up transport.[[IncomingBidirectionalStreams]] with + // pullAlgorithm set to pullBidirectionalStreamAlgorithm, and highWaterMark + // set to 0. + Optional<JS::Handle<JSObject*>> underlying; + // Suppress warnings about risk of mGlobal getting nulled during script. + // We set the global from the aGlobalObject parameter of the constructor, so + // it must still be set here. + const nsCOMPtr<nsIGlobalObject> global(mGlobal); + + mIncomingBidirectionalAlgorithm = new WebTransportIncomingStreamsAlgorithms( + WebTransportIncomingStreamsAlgorithms::StreamType::Bidirectional, this); + + RefPtr<WebTransportIncomingStreamsAlgorithms> algorithm = + mIncomingBidirectionalAlgorithm; + mIncomingBidirectionalStreams = ReadableStream::CreateNative( + cx, global, *algorithm, Some(0.0), nullptr, aError); + if (aError.Failed()) { + return; + } + // Step 22: Let pullUnidirectionalStreamAlgorithm be an action that runs + // pullUnidirectionalStream with transport. + // Step 23: Set up transport.[[IncomingUnidirectionalStreams]] with + // pullAlgorithm set to pullUnidirectionalStreamAlgorithm, and highWaterMark + // set to 0. + + mIncomingUnidirectionalAlgorithm = new WebTransportIncomingStreamsAlgorithms( + WebTransportIncomingStreamsAlgorithms::StreamType::Unidirectional, this); + + algorithm = mIncomingUnidirectionalAlgorithm; + mIncomingUnidirectionalStreams = ReadableStream::CreateNative( + cx, global, *algorithm, Some(0.0), nullptr, aError); + if (aError.Failed()) { + return; + } + + // Step 24: Initialize WebTransport over HTTP with transport, parsedURL, + // dedicated, requireUnreliable, and congestionControl. + LOG(("Connecting WebTransport to parent for %s", + NS_ConvertUTF16toUTF8(aURL).get())); + + // https://w3c.github.io/webtransport/#webtransport-constructor Spec 5.2 + mChild = child; + backgroundChild + ->SendCreateWebTransportParent( + aURL, principal, ipcClientInfo, dedicated, requireUnreliable, + (uint32_t)congestionControl, std::move(aServerCertHashes), + std::move(parentEndpoint)) + ->Then(GetCurrentSerialEventTarget(), __func__, + [self = RefPtr{this}]( + PBackgroundChild::CreateWebTransportParentPromise:: + ResolveOrRejectValue&& aResult) { + // aResult is a std::tuple<nsresult, uint8_t> + // TODO: is there a better/more-spec-compliant error in the + // reject case? Which begs the question, why would we get a + // reject? + nsresult rv = aResult.IsReject() + ? NS_ERROR_FAILURE + : std::get<0>(aResult.ResolveValue()); + LOG(("isreject: %d nsresult 0x%x", aResult.IsReject(), + (uint32_t)rv)); + if (NS_FAILED(rv)) { + self->RejectWaitingConnection(rv); + } else { + // This will process anything waiting for the connection to + // complete; + + self->ResolveWaitingConnection( + static_cast<WebTransportReliabilityMode>( + std::get<1>(aResult.ResolveValue()))); + } + }); +} + +void WebTransport::ResolveWaitingConnection( + WebTransportReliabilityMode aReliability) { + LOG(("Resolved Connection %p, reliability = %u", this, + (unsigned)aReliability)); + // https://w3c.github.io/webtransport/#webtransport-constructor + // Step 17 of initialize WebTransport over HTTP + // Step 17.1 If transport.[[State]] is not "connecting": + if (mState != WebTransportState::CONNECTING) { + // Step 17.1.1: In parallel, terminate session. + // Step 17.1.2: abort these steps + // Cleanup should have been called, which means Ready has been rejected + return; + } + + // Step 17.2: Set transport.[[State]] to "connected". + mState = WebTransportState::CONNECTED; + // Step 17.3: Set transport.[[Session]] to session. + // Step 17.4: Set transport’s [[Reliability]] to "supports-unreliable". + mReliability = aReliability; + + mChild->SendGetMaxDatagramSize()->Then( + GetCurrentSerialEventTarget(), __func__, + [self = RefPtr{this}](uint64_t&& aMaxDatagramSize) { + MOZ_ASSERT(self->mDatagrams); + self->mDatagrams->SetMaxDatagramSize(aMaxDatagramSize); + LOG(("max datagram size for the session is %" PRIu64, + aMaxDatagramSize)); + }, + [](const mozilla::ipc::ResponseRejectReason& aReason) { + LOG(("WebTransport fetching maxDatagramSize failed")); + }); + + // Step 17.5: Resolve transport.[[Ready]] with undefined. + mReady->MaybeResolveWithUndefined(); + + // We can now release any queued datagrams + mDatagrams->SetChild(mChild); +} + +void WebTransport::RejectWaitingConnection(nsresult aRv) { + LOG(("Rejected connection %p %x", this, (uint32_t)aRv)); + // https://w3c.github.io/webtransport/#initialize-webtransport-over-http + + // Step 10: If connection is failure, then abort the remaining steps and + // queue a network task with transport to run these steps: + // Step 10.1: If transport.[[State]] is "closed" or "failed", then abort + // these steps. + + // Step 14: If the previous step fails, abort the remaining steps and + // queue a network task with transport to run these steps: + // Step 14.1: If transport.[[State]] is "closed" or "failed", then abort + // these steps. + if (mState == WebTransportState::CLOSED || + mState == WebTransportState::FAILED) { + if (mChild) { + mChild->Shutdown(true); + mChild = nullptr; + } + // Cleanup should have been called, which means Ready has been + // rejected and pulls resolved + return; + } + + // Step 14.2: Let error be the result of creating a WebTransportError with + // "session". + RefPtr<WebTransportError> error = new WebTransportError( + "WebTransport connection rejected"_ns, WebTransportErrorSource::Session); + // Step 14.3: Cleanup transport with error. + Cleanup(error, nullptr, IgnoreErrors()); + + mChild->Shutdown(true); + mChild = nullptr; +} + +bool WebTransport::ParseURL(const nsAString& aURL) const { + NS_ENSURE_TRUE(!aURL.IsEmpty(), false); + + // 5.4 = https://w3c.github.io/webtransport/#webtransport-constructor + // 5.4 #1 and #2 + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL); + NS_ENSURE_SUCCESS(rv, false); + + // 5.4 #3 + if (!uri->SchemeIs("https")) { + return false; + } + + // 5.4 #4 no fragments + bool hasRef; + rv = uri->GetHasRef(&hasRef); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !hasRef, false); + + return true; +} + +already_AddRefed<Promise> WebTransport::GetStats(ErrorResult& aError) { + aError.Throw(NS_ERROR_NOT_IMPLEMENTED); + return nullptr; +} + +WebTransportReliabilityMode WebTransport::Reliability() { return mReliability; } + +WebTransportCongestionControl WebTransport::CongestionControl() { + // XXX not implemented + return WebTransportCongestionControl::Default; +} + +void WebTransport::RemoteClosed(bool aCleanly, const uint32_t& aCode, + const nsACString& aReason) { + LOG(("Server closed: cleanly: %d, code %u, reason %s", aCleanly, aCode, + PromiseFlatCString(aReason).get())); + // Step 2 of https://w3c.github.io/webtransport/#web-transport-termination + // We calculate cleanly on the parent + // Step 2.1: If transport.[[State]] is "closed" or "failed", abort these + // steps. + if (mState == WebTransportState::CLOSED || + mState == WebTransportState::FAILED) { + return; + } + // Step 2.2: Let error be the result of creating a WebTransportError with + // "session". + RefPtr<WebTransportError> error = new WebTransportError( + "remote WebTransport close"_ns, WebTransportErrorSource::Session); + // Step 2.3: If cleanly is false, then cleanup transport with error, and + // abort these steps. + ErrorResult errorresult; + if (!aCleanly) { + Cleanup(error, nullptr, errorresult); + return; + } + // Step 2.4: Let closeInfo be a new WebTransportCloseInfo. + // Step 2.5: If code is given, set closeInfo’s closeCode to code. + // Step 2.6: If reasonBytes is given, set closeInfo’s reason to reasonBytes, + // UTF-8 decoded. + WebTransportCloseInfo closeinfo; + closeinfo.mCloseCode = aCode; + closeinfo.mReason = aReason; + + // Step 2.7: Cleanup transport with error and closeInfo. + Cleanup(error, &closeinfo, errorresult); +} + +template <typename Stream> +void WebTransport::PropagateError(Stream* aStream, WebTransportError* aError) { + ErrorResult rv; + AutoJSAPI jsapi; + if (!jsapi.Init(mGlobal)) { + rv.ThrowUnknownError("Internal error"); + return; + } + JSContext* cx = jsapi.cx(); + JS::Rooted<JS::Value> errorValue(cx); + bool ok = ToJSValue(cx, aError, &errorValue); + if (!ok) { + rv.ThrowUnknownError("Internal error"); + return; + } + + aStream->ErrorNative(cx, errorValue, IgnoreErrors()); +} + +void WebTransport::OnStreamResetOrStopSending( + uint64_t aStreamId, const StreamResetOrStopSendingError& aError) { + LOG(("WebTransport::OnStreamResetOrStopSending %p id=%" PRIx64, this, + aStreamId)); + if (aError.type() == StreamResetOrStopSendingError::TStopSendingError) { + RefPtr<WebTransportSendStream> stream = mSendStreams.Get(aStreamId); + if (!stream) { + return; + } + uint8_t errorCode = net::GetWebTransportErrorFromNSResult( + aError.get_StopSendingError().error()); + RefPtr<WebTransportError> error = new WebTransportError( + "WebTransportStream StopSending"_ns, WebTransportErrorSource::Stream, + Nullable<uint8_t>(errorCode)); + PropagateError(stream.get(), error); + } else if (aError.type() == StreamResetOrStopSendingError::TResetError) { + RefPtr<WebTransportReceiveStream> stream = mReceiveStreams.Get(aStreamId); + LOG(("WebTransport::OnStreamResetOrStopSending reset %p stream=%p", this, + stream.get())); + if (!stream) { + return; + } + uint8_t errorCode = + net::GetWebTransportErrorFromNSResult(aError.get_ResetError().error()); + RefPtr<WebTransportError> error = new WebTransportError( + "WebTransportStream Reset"_ns, WebTransportErrorSource::Stream, + Nullable<uint8_t>(errorCode)); + PropagateError(stream.get(), error); + } +} + +void WebTransport::Close(const WebTransportCloseInfo& aOptions, + ErrorResult& aRv) { + LOG(("Close() called")); + // https://w3c.github.io/webtransport/#dom-webtransport-close + // Step 1 and Step 2: If transport.[[State]] is "closed" or "failed", then + // abort these steps. + if (mState == WebTransportState::CLOSED || + mState == WebTransportState::FAILED) { + return; + } + // Step 3: If transport.[[State]] is "connecting": + if (mState == WebTransportState::CONNECTING) { + // Step 3.1: Let error be the result of creating a WebTransportError with + // "session". + RefPtr<WebTransportError> error = new WebTransportError( + "close() called on WebTransport while connecting"_ns, + WebTransportErrorSource::Session); + // Step 3.2: Cleanup transport with error. + Cleanup(error, nullptr, aRv); + // Step 3.3: Abort these steps. + mChild->Shutdown(true); + mChild = nullptr; + return; + } + LOG(("Sending Close")); + MOZ_ASSERT(mChild); + // Step 4: Let session be transport.[[Session]]. + // Step 5: Let code be closeInfo.closeCode. + // Step 6: "Let reasonString be the maximal code unit prefix of + // closeInfo.reason where the length of the UTF-8 encoded prefix + // doesn’t exceed 1024." + // Take the maximal "code unit prefix" of mReason and limit to 1024 bytes + // Step 7: Let reason be reasonString, UTF-8 encoded. + // Step 8: In parallel, terminate session with code and reason. + if (aOptions.mReason.Length() > 1024u) { + // We want to start looking for the previous code point at one past the + // limit, since if a code point ends exactly at the specified length, the + // next byte will be the start of a new code point. Note + // RewindToPriorUTF8Codepoint doesn't reduce the index if it points to the + // start of a code point. We know reason[1024] is accessible since + // Length() > 1024 + mChild->SendClose( + aOptions.mCloseCode, + Substring(aOptions.mReason, 0, + RewindToPriorUTF8Codepoint(aOptions.mReason.get(), 1024u))); + } else { + mChild->SendClose(aOptions.mCloseCode, aOptions.mReason); + LOG(("Close sent")); + } + + // Step 9: Cleanup transport with AbortError and closeInfo. (sets mState to + // Closed) + RefPtr<WebTransportError> error = + new WebTransportError("close()"_ns, WebTransportErrorSource::Session, + DOMException_Binding::ABORT_ERR); + Cleanup(error, &aOptions, aRv); + LOG(("Cleanup done")); + + // The other side will call `Close()` for us now, make sure we don't call it + // in our destructor. + mChild->Shutdown(false); + mChild = nullptr; + LOG(("Close done")); +} + +already_AddRefed<WebTransportDatagramDuplexStream> WebTransport::GetDatagrams( + ErrorResult& aError) { + return do_AddRef(mDatagrams); +} + +already_AddRefed<Promise> WebTransport::CreateBidirectionalStream( + const WebTransportSendStreamOptions& aOptions, ErrorResult& aRv) { + LOG(("CreateBidirectionalStream() called")); + // https://w3c.github.io/webtransport/#dom-webtransport-createbidirectionalstream + RefPtr<Promise> promise = Promise::CreateInfallible(GetParentObject()); + + // Step 2: If transport.[[State]] is "closed" or "failed", return a new + // rejected promise with an InvalidStateError. + if (mState == WebTransportState::CLOSED || + mState == WebTransportState::FAILED || !mChild) { + aRv.ThrowInvalidStateError("WebTransport closed or failed"); + return nullptr; + } + + // Step 3: Let sendOrder be options's sendOrder. + Maybe<int64_t> sendOrder; + if (!aOptions.mSendOrder.IsNull()) { + sendOrder = Some(aOptions.mSendOrder.Value()); + } + // Step 4: Let p be a new promise. + // Step 5: Run the following steps in parallel, but abort them whenever + // transport’s [[State]] becomes "closed" or "failed", and instead queue + // a network task with transport to reject p with an InvalidStateError. + + // Ask the parent to create the stream and send us the DataPipeSender/Receiver + // pair + mChild->SendCreateBidirectionalStream( + sendOrder, + [self = RefPtr{this}, sendOrder, promise]( + BidirectionalStreamResponse&& aPipes) MOZ_CAN_RUN_SCRIPT_BOUNDARY { + LOG(("CreateBidirectionalStream response")); + if (BidirectionalStreamResponse::Tnsresult == aPipes.type()) { + promise->MaybeReject(aPipes.get_nsresult()); + return; + } + // Step 5.2.1: If transport.[[State]] is "closed" or "failed", + // reject p with an InvalidStateError and abort these steps. + if (BidirectionalStreamResponse::Tnsresult == aPipes.type()) { + promise->MaybeReject(aPipes.get_nsresult()); + return; + } + if (self->mState == WebTransportState::CLOSED || + self->mState == WebTransportState::FAILED) { + promise->MaybeRejectWithInvalidStateError( + "Transport close/errored before CreateBidirectional finished"); + return; + } + uint64_t id = aPipes.get_BidirectionalStream().streamId(); + LOG(("Create WebTransportBidirectionalStream id=%" PRIx64, id)); + ErrorResult error; + RefPtr<WebTransportBidirectionalStream> newStream = + WebTransportBidirectionalStream::Create( + self, self->mGlobal, id, + aPipes.get_BidirectionalStream().inStream(), + aPipes.get_BidirectionalStream().outStream(), sendOrder, error); + LOG(("Returning a bidirectionalStream")); + promise->MaybeResolve(newStream); + }, + [self = RefPtr{this}, promise](mozilla::ipc::ResponseRejectReason) { + LOG(("CreateBidirectionalStream reject")); + promise->MaybeRejectWithInvalidStateError( + "Transport close/errored before CreateBidirectional started"); + }); + + // Step 6: return p + return promise.forget(); +} + +already_AddRefed<ReadableStream> WebTransport::IncomingBidirectionalStreams() { + return do_AddRef(mIncomingBidirectionalStreams); +} + +already_AddRefed<Promise> WebTransport::CreateUnidirectionalStream( + const WebTransportSendStreamOptions& aOptions, ErrorResult& aRv) { + LOG(("CreateUnidirectionalStream() called")); + // https://w3c.github.io/webtransport/#dom-webtransport-createunidirectionalstream + // Step 2: If transport.[[State]] is "closed" or "failed", return a new + // rejected promise with an InvalidStateError. + if (mState == WebTransportState::CLOSED || + mState == WebTransportState::FAILED || !mChild) { + aRv.ThrowInvalidStateError("WebTransport closed or failed"); + return nullptr; + } + + // Step 3: Let sendOrder be options's sendOrder. + Maybe<int64_t> sendOrder; + if (!aOptions.mSendOrder.IsNull()) { + sendOrder = Some(aOptions.mSendOrder.Value()); + } + // Step 4: Let p be a new promise. + RefPtr<Promise> promise = Promise::CreateInfallible(GetParentObject()); + + // Step 5: Run the following steps in parallel, but abort them whenever + // transport’s [[State]] becomes "closed" or "failed", and instead queue + // a network task with transport to reject p with an InvalidStateError. + + // Ask the parent to create the stream and send us the DataPipeSender + mChild->SendCreateUnidirectionalStream( + sendOrder, + [self = RefPtr{this}, sendOrder, + promise](UnidirectionalStreamResponse&& aResponse) + MOZ_CAN_RUN_SCRIPT_BOUNDARY { + LOG(("CreateUnidirectionalStream response")); + if (UnidirectionalStreamResponse::Tnsresult == aResponse.type()) { + promise->MaybeReject(aResponse.get_nsresult()); + return; + } + // Step 5.1: Let internalStream be the result of creating an + // outgoing unidirectional stream with transport.[[Session]]. + // Step 5.2: Queue a network task with transport to run the + // following steps: + // Step 5.2.1 If transport.[[State]] is "closed" or "failed", + // reject p with an InvalidStateError and abort these steps. + if (self->mState == WebTransportState::CLOSED || + self->mState == WebTransportState::FAILED || + aResponse.type() != + UnidirectionalStreamResponse::TUnidirectionalStream) { + promise->MaybeRejectWithInvalidStateError( + "Transport close/errored during CreateUnidirectional"); + return; + } + + // Step 5.2.2.: Let stream be the result of creating a + // WebTransportSendStream with internalStream, transport, and + // sendOrder. + ErrorResult error; + uint64_t id = aResponse.get_UnidirectionalStream().streamId(); + LOG(("Create WebTransportSendStream id=%" PRIx64, id)); + RefPtr<WebTransportSendStream> writableStream = + WebTransportSendStream::Create( + self, self->mGlobal, id, + aResponse.get_UnidirectionalStream().outStream(), sendOrder, + error); + if (!writableStream) { + promise->MaybeReject(std::move(error)); + return; + } + LOG(("Returning a writableStream")); + // Step 5.2.3: Resolve p with stream. + promise->MaybeResolve(writableStream); + }, + [self = RefPtr{this}, promise](mozilla::ipc::ResponseRejectReason) { + LOG(("CreateUnidirectionalStream reject")); + promise->MaybeRejectWithInvalidStateError( + "Transport close/errored during CreateUnidirectional"); + }); + + // Step 6: return p + return promise.forget(); +} + +already_AddRefed<ReadableStream> WebTransport::IncomingUnidirectionalStreams() { + return do_AddRef(mIncomingUnidirectionalStreams); +} + +// Can be invoked with "error", "error, error, and true/false", or "error and +// closeInfo", but reason and abruptly are never used, and it does use closeinfo +void WebTransport::Cleanup(WebTransportError* aError, + const WebTransportCloseInfo* aCloseInfo, + ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#webtransport-cleanup + // Step 1: Let sendStreams be a copy of transport.[[SendStreams]] + // Step 2: Let receiveStreams be a copy of transport.[[ReceiveStreams]] + // Step 3: Let ready be transport.[[Ready]] -> (mReady) + // Step 4: Let closed be transport.[[Closed]] -> (mClosed) + // Step 5: Let incomingBidirectionalStreams be + // transport.[[IncomingBidirectionalStreams]]. + // Step 6: Let incomingUnidirectionalStreams be + // transport.[[IncomingUnidirectionalStreams]]. + // Step 7: Set transport.[[SendStreams]] to an empty set. + // Step 8: Set transport.[[ReceiveStreams]] to an empty set. + LOG(("Cleanup started")); + nsTHashMap<uint64_t, RefPtr<WebTransportSendStream>> sendStreams; + sendStreams.SwapElements(mSendStreams); + nsTHashMap<uint64_t, RefPtr<WebTransportReceiveStream>> receiveStreams; + receiveStreams.SwapElements(mReceiveStreams); + + // Step 9: If closeInfo is given, then set transport.[[State]] to "closed". + // Otherwise, set transport.[[State]] to "failed". + mState = aCloseInfo ? WebTransportState::CLOSED : WebTransportState::FAILED; + + // Step 10: For each sendStream in sendStreams, error sendStream with error. + AutoJSAPI jsapi; + if (!jsapi.Init(mGlobal)) { + aRv.ThrowUnknownError("Internal error"); + return; + } + JSContext* cx = jsapi.cx(); + JS::Rooted<JS::Value> errorValue(cx); + bool ok = ToJSValue(cx, aError, &errorValue); + if (!ok) { + aRv.ThrowUnknownError("Internal error"); + return; + } + + for (const auto& stream : sendStreams.Values()) { + // This MOZ_KnownLive is redundant, see bug 1620312 + MOZ_KnownLive(stream)->ErrorNative(cx, errorValue, IgnoreErrors()); + } + // Step 11: For each receiveStream in receiveStreams, error receiveStream with + // error. + for (const auto& stream : receiveStreams.Values()) { + stream->ErrorNative(cx, errorValue, IgnoreErrors()); + } + // Step 12: + if (aCloseInfo) { + // 12.1: Resolve closed with closeInfo. + LOG(("Resolving mClosed with closeinfo")); + mClosed->MaybeResolve(*aCloseInfo); + // 12.2: Assert: ready is settled. + MOZ_ASSERT(mReady->State() != Promise::PromiseState::Pending); + // 12.3: Close incomingBidirectionalStreams + // This keeps the clang-plugin happy + RefPtr<ReadableStream> stream = mIncomingBidirectionalStreams; + stream->CloseNative(cx, IgnoreErrors()); + // 12.4: Close incomingUnidirectionalStreams + stream = mIncomingUnidirectionalStreams; + stream->CloseNative(cx, IgnoreErrors()); + } else { + // Step 13 + // 13.1: Reject closed with error + LOG(("Rejecting mClosed")); + mClosed->MaybeReject(errorValue); + // 13.2: Reject ready with error + mReady->MaybeReject(errorValue); + // 13.3: Error incomingBidirectionalStreams with error + mIncomingBidirectionalStreams->ErrorNative(cx, errorValue, IgnoreErrors()); + // 13.4: Error incomingUnidirectionalStreams with error + mIncomingUnidirectionalStreams->ErrorNative(cx, errorValue, IgnoreErrors()); + } + // Let go of the algorithms + mIncomingBidirectionalAlgorithm = nullptr; + mIncomingUnidirectionalAlgorithm = nullptr; + + // We no longer block BFCache + NotifyToWindow(false); +} + +void WebTransport::SendSetSendOrder(uint64_t aStreamId, + Maybe<int64_t> aSendOrder) { + mChild->SendSetSendOrder(aStreamId, aSendOrder); +} + +void WebTransport::NotifyBFCacheOnMainThread(nsPIDOMWindowInner* aInner, + bool aCreated) { + AssertIsOnMainThread(); + if (!aInner) { + return; + } + if (aCreated) { + aInner->RemoveFromBFCacheSync(); + } + + uint32_t count = aInner->UpdateWebTransportCount(aCreated); + // It's okay for WindowGlobalChild to not exist, as it should mean it already + // is destroyed and can't enter bfcache anyway. + if (WindowGlobalChild* child = aInner->GetWindowGlobalChild()) { + if (aCreated && count == 1) { + // The first WebTransport is active. + child->BlockBFCacheFor(BFCacheStatus::ACTIVE_WEBTRANSPORT); + } else if (count == 0) { + child->UnblockBFCacheFor(BFCacheStatus::ACTIVE_WEBTRANSPORT); + } + } +} + +class BFCacheNotifyWTRunnable final : public WorkerProxyToMainThreadRunnable { + public: + explicit BFCacheNotifyWTRunnable(bool aCreated) : mCreated(aCreated) {} + + void RunOnMainThread(WorkerPrivate* aWorkerPrivate) override { + MOZ_ASSERT(aWorkerPrivate); + AssertIsOnMainThread(); + if (aWorkerPrivate->IsDedicatedWorker()) { + WebTransport::NotifyBFCacheOnMainThread( + aWorkerPrivate->GetAncestorWindow(), mCreated); + return; + } + if (aWorkerPrivate->IsSharedWorker()) { + aWorkerPrivate->GetRemoteWorkerController()->NotifyWebTransport(mCreated); + return; + } + MOZ_ASSERT_UNREACHABLE("Unexpected worker type"); + } + + void RunBackOnWorkerThreadForCleanup(WorkerPrivate* aWorkerPrivate) override { + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + } + + private: + bool mCreated; +}; + +void WebTransport::NotifyToWindow(bool aCreated) const { + if (NS_IsMainThread()) { + NotifyBFCacheOnMainThread(GetParentObject()->GetAsInnerWindow(), aCreated); + return; + } + + WorkerPrivate* wp = GetCurrentThreadWorkerPrivate(); + if (wp->IsDedicatedWorker() || wp->IsSharedWorker()) { + RefPtr<BFCacheNotifyWTRunnable> runnable = + new BFCacheNotifyWTRunnable(aCreated); + + runnable->Dispatch(wp); + } +}; + +} // namespace mozilla::dom diff --git a/dom/webtransport/api/WebTransport.h b/dom/webtransport/api/WebTransport.h new file mode 100644 index 0000000000..c0bfcd1d24 --- /dev/null +++ b/dom/webtransport/api/WebTransport.h @@ -0,0 +1,174 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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 DOM_WEBTRANSPORT_API_WEBTRANSPORT__H_ +#define DOM_WEBTRANSPORT_API_WEBTRANSPORT__H_ + +#include "nsCOMPtr.h" +#include "nsTArray.h" +#include "nsISupports.h" +#include "nsTHashMap.h" +#include "nsWrapperCache.h" +#include "nsPIDOMWindow.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/WebTransportBinding.h" +#include "mozilla/dom/WebTransportChild.h" +#include "mozilla/dom/WebTransportSendStream.h" +#include "mozilla/dom/WebTransportReceiveStream.h" +#include "mozilla/dom/WebTransportStreams.h" +#include "mozilla/ipc/DataPipe.h" + +namespace mozilla::dom { + +class WebTransportError; +class WebTransportDatagramDuplexStream; +class WebTransportIncomingStreamsAlgorithms; +class ReadableStream; +class WritableStream; +using BidirectionalPair = std::pair<RefPtr<mozilla::ipc::DataPipeReceiver>, + RefPtr<mozilla::ipc::DataPipeSender>>; + +struct DatagramEntry { + DatagramEntry(nsTArray<uint8_t>&& aData, const mozilla::TimeStamp& aTimeStamp) + : mBuffer(std::move(aData)), mTimeStamp(aTimeStamp) {} + DatagramEntry(Span<uint8_t>& aData, const mozilla::TimeStamp& aTimeStamp) + : mBuffer(aData), mTimeStamp(aTimeStamp) {} + + nsTArray<uint8_t> mBuffer; + mozilla::TimeStamp mTimeStamp; +}; + +class WebTransport final : public nsISupports, public nsWrapperCache { + friend class WebTransportIncomingStreamsAlgorithms; + // For mSendStreams/mReceiveStreams + friend class WebTransportSendStream; + friend class WebTransportReceiveStream; + + public: + explicit WebTransport(nsIGlobalObject* aGlobal); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WebTransport) + + enum class WebTransportState { CONNECTING, CONNECTED, CLOSED, FAILED }; + + static void NotifyBFCacheOnMainThread(nsPIDOMWindowInner* aInner, + bool aCreated); + void NotifyToWindow(bool aCreated) const; + + void Init(const GlobalObject& aGlobal, const nsAString& aUrl, + const WebTransportOptions& aOptions, ErrorResult& aError); + void ResolveWaitingConnection(WebTransportReliabilityMode aReliability); + void RejectWaitingConnection(nsresult aRv); + bool ParseURL(const nsAString& aURL) const; + // this calls CloseNative(), which doesn't actually run script. See bug + // 1810942 + MOZ_CAN_RUN_SCRIPT_BOUNDARY void Cleanup( + WebTransportError* aError, const WebTransportCloseInfo* aCloseInfo, + ErrorResult& aRv); + + // From Parent + void NewBidirectionalStream( + uint64_t aStreamId, + const RefPtr<mozilla::ipc::DataPipeReceiver>& aIncoming, + const RefPtr<mozilla::ipc::DataPipeSender>& aOutgoing); + + void NewUnidirectionalStream( + uint64_t aStreamId, + const RefPtr<mozilla::ipc::DataPipeReceiver>& aStream); + + void NewDatagramReceived(nsTArray<uint8_t>&& aData, + const mozilla::TimeStamp& aTimeStamp); + + void RemoteClosed(bool aCleanly, const uint32_t& aCode, + const nsACString& aReason); + + void OnStreamResetOrStopSending(uint64_t aStreamId, + const StreamResetOrStopSendingError& aError); + // WebIDL Boilerplate + nsIGlobalObject* GetParentObject() const; + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // WebIDL Interface + static already_AddRefed<WebTransport> Constructor( + const GlobalObject& aGlobal, const nsAString& aUrl, + const WebTransportOptions& aOptions, ErrorResult& aError); + + already_AddRefed<Promise> GetStats(ErrorResult& aError); + + already_AddRefed<Promise> Ready() { return do_AddRef(mReady); } + WebTransportReliabilityMode Reliability(); + WebTransportCongestionControl CongestionControl(); + already_AddRefed<Promise> Closed() { return do_AddRef(mClosed); } + MOZ_CAN_RUN_SCRIPT void Close(const WebTransportCloseInfo& aOptions, + ErrorResult& aRv); + already_AddRefed<WebTransportDatagramDuplexStream> GetDatagrams( + ErrorResult& aRv); + already_AddRefed<Promise> CreateBidirectionalStream( + const WebTransportSendStreamOptions& aOptions, ErrorResult& aRv); + already_AddRefed<Promise> CreateUnidirectionalStream( + const WebTransportSendStreamOptions& aOptions, ErrorResult& aRv); + MOZ_CAN_RUN_SCRIPT_BOUNDARY already_AddRefed<ReadableStream> + IncomingBidirectionalStreams(); + MOZ_CAN_RUN_SCRIPT_BOUNDARY already_AddRefed<ReadableStream> + IncomingUnidirectionalStreams(); + + void SendSetSendOrder(uint64_t aStreamId, Maybe<int64_t> aSendOrder); + + void Shutdown() {} + + private: + ~WebTransport(); + + template <typename Stream> + MOZ_CAN_RUN_SCRIPT_BOUNDARY void PropagateError(Stream* aStream, + WebTransportError* aError); + + nsCOMPtr<nsIGlobalObject> mGlobal; + // We are the owner of WebTransportChild. We must call Shutdown() on it + // before we're destroyed. + RefPtr<WebTransportChild> mChild; + + // Spec in 5.8 says it can't be GC'd while CONNECTING or CONNECTED. We won't + // hold ref which we drop on CLOSED or FAILED because a reference is also held + // by IPC. We drop the IPC connection and by proxy the reference when it goes + // to FAILED or CLOSED. + + // Spec-defined slots: + // ordered sets, but we can't have duplicates, and this spec only appends. + // Order is visible due to + // https://w3c.github.io/webtransport/#webtransport-procedures step 10: "For + // each sendStream in sendStreams, error sendStream with error." + nsTHashMap<uint64_t, RefPtr<WebTransportSendStream>> mSendStreams; + nsTHashMap<uint64_t, RefPtr<WebTransportReceiveStream>> mReceiveStreams; + + WebTransportState mState; + RefPtr<Promise> mReady; + // XXX may not need to be a RefPtr, since we own it through the Streams + RefPtr<WebTransportIncomingStreamsAlgorithms> mIncomingBidirectionalAlgorithm; + RefPtr<WebTransportIncomingStreamsAlgorithms> + mIncomingUnidirectionalAlgorithm; + WebTransportReliabilityMode mReliability; + // Incoming streams get queued here. Use a TArray though it's working as + // a FIFO - rarely will there be more than one entry in these arrays, so + // the overhead of mozilla::Queue is unneeded + nsTArray<std::tuple<uint64_t, RefPtr<mozilla::ipc::DataPipeReceiver>>> + mUnidirectionalStreams; + nsTArray<std::tuple<uint64_t, UniquePtr<BidirectionalPair>>> + mBidirectionalStreams; + + // These are created in the constructor + RefPtr<ReadableStream> mIncomingUnidirectionalStreams; + RefPtr<ReadableStream> mIncomingBidirectionalStreams; + RefPtr<WebTransportDatagramDuplexStream> mDatagrams; + RefPtr<Promise> mClosed; +}; + +} // namespace mozilla::dom + +#endif // DOM_WEBTRANSPORT_API_WEBTRANSPORT__H_ diff --git a/dom/webtransport/api/WebTransportBidirectionalStream.cpp b/dom/webtransport/api/WebTransportBidirectionalStream.cpp new file mode 100644 index 0000000000..7165646b84 --- /dev/null +++ b/dom/webtransport/api/WebTransportBidirectionalStream.cpp @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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/. */ + +#include "WebTransportBidirectionalStream.h" +#include "mozilla/dom/Promise.h" + +namespace mozilla::dom { + +using namespace mozilla::ipc; + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebTransportBidirectionalStream, mGlobal, + mReadable, mWritable) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(WebTransportBidirectionalStream) +NS_IMPL_CYCLE_COLLECTING_RELEASE(WebTransportBidirectionalStream) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebTransportBidirectionalStream) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +// WebIDL Boilerplate + +nsIGlobalObject* WebTransportBidirectionalStream::GetParentObject() const { + return mGlobal; +} + +JSObject* WebTransportBidirectionalStream::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return WebTransportBidirectionalStream_Binding::Wrap(aCx, this, aGivenProto); +} + +// static +already_AddRefed<WebTransportBidirectionalStream> +WebTransportBidirectionalStream::Create( + WebTransport* aWebTransport, nsIGlobalObject* aGlobal, uint64_t aStreamId, + DataPipeReceiver* receiver, DataPipeSender* aSender, + Maybe<int64_t> aSendOrder, ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#pullbidirectionalstream (and + // createBidirectionalStream) + + // Step 7.1: Let stream be the result of creating a + // WebTransportBidirectionalStream with internalStream and transport + RefPtr<WebTransportReceiveStream> readableStream = + WebTransportReceiveStream::Create(aWebTransport, aGlobal, aStreamId, + receiver, aRv); + if (!readableStream) { + return nullptr; + } + RefPtr<WebTransportSendStream> writableStream = + WebTransportSendStream::Create(aWebTransport, aGlobal, aStreamId, aSender, + aSendOrder, aRv); + if (!writableStream) { + return nullptr; + ; + } + + auto stream = MakeRefPtr<WebTransportBidirectionalStream>( + aGlobal, readableStream, writableStream); + return stream.forget(); +} + +// WebIDL Interface + +} // namespace mozilla::dom diff --git a/dom/webtransport/api/WebTransportBidirectionalStream.h b/dom/webtransport/api/WebTransportBidirectionalStream.h new file mode 100644 index 0000000000..2979acd2fd --- /dev/null +++ b/dom/webtransport/api/WebTransportBidirectionalStream.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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 DOM_WEBTRANSPORT_API_WEBTRANSPORTBIDIRECTIONALSTREAM__H_ +#define DOM_WEBTRANSPORT_API_WEBTRANSPORTBIDIRECTIONALSTREAM__H_ + +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "nsWrapperCache.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/ReadableStream.h" +#include "mozilla/dom/WebTransport.h" +#include "mozilla/dom/WritableStream.h" +#include "mozilla/dom/WebTransportSendReceiveStreamBinding.h" +#include "mozilla/ipc/DataPipe.h" + +// #include "mozilla/dom/WebTransportReceiveStream.h" +// #include "mozilla/dom/WebTransportSendStream.h" + +namespace mozilla::dom { +class WebTransportBidirectionalStream final : public nsISupports, + public nsWrapperCache { + public: + explicit WebTransportBidirectionalStream(nsIGlobalObject* aGlobal, + WebTransportReceiveStream* aReadable, + WebTransportSendStream* aWritable) + : mGlobal(aGlobal), mReadable(aReadable), mWritable(aWritable) {} + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WebTransportBidirectionalStream) + + static already_AddRefed<WebTransportBidirectionalStream> Create( + WebTransport* aWebTransport, nsIGlobalObject* aGlobal, uint64_t aStreamId, + ::mozilla::ipc::DataPipeReceiver* receiver, + ::mozilla::ipc::DataPipeSender* aSender, Maybe<int64_t> aSendOrder, + ErrorResult& aRv); + + // WebIDL Boilerplate + nsIGlobalObject* GetParentObject() const; + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // WebIDL Interface + already_AddRefed<WebTransportReceiveStream> Readable() const { + return do_AddRef(mReadable); + } + already_AddRefed<WebTransportSendStream> Writable() const { + return do_AddRef(mWritable); + } + + private: + ~WebTransportBidirectionalStream() = default; + + nsCOMPtr<nsIGlobalObject> mGlobal; + RefPtr<WebTransportReceiveStream> mReadable; + RefPtr<WebTransportSendStream> mWritable; +}; + +} // namespace mozilla::dom + +#endif diff --git a/dom/webtransport/api/WebTransportDatagramDuplexStream.cpp b/dom/webtransport/api/WebTransportDatagramDuplexStream.cpp new file mode 100644 index 0000000000..30d7451414 --- /dev/null +++ b/dom/webtransport/api/WebTransportDatagramDuplexStream.cpp @@ -0,0 +1,349 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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/. */ + +#include "WebTransportDatagramDuplexStream.h" +#include "mozilla/dom/AutoEntryScript.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/Promise-inl.h" +#include "mozilla/dom/WebTransportLog.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebTransportDatagramDuplexStream, mGlobal, + mReadable, mWritable, mWebTransport, + mIncomingAlgorithms, mOutgoingAlgorithms) +// mIncomingDatagramsQueue can't participate in a cycle +NS_IMPL_CYCLE_COLLECTING_ADDREF(WebTransportDatagramDuplexStream) +NS_IMPL_CYCLE_COLLECTING_RELEASE(WebTransportDatagramDuplexStream) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebTransportDatagramDuplexStream) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +WebTransportDatagramDuplexStream::WebTransportDatagramDuplexStream( + nsIGlobalObject* aGlobal, WebTransport* aWebTransport) + : mGlobal(aGlobal), mWebTransport(aWebTransport) {} + +void WebTransportDatagramDuplexStream::Init(ErrorResult& aError) { + // https://w3c.github.io/webtransport/#webtransport-constructor + // We are only called synchronously from JS creating a WebTransport object + AutoEntryScript aes(mGlobal, "WebTransportDatagrams"); + JSContext* cx = aes.cx(); + + mIncomingAlgorithms = new IncomingDatagramStreamAlgorithms(this); + nsCOMPtr<nsIGlobalObject> global(mGlobal); + RefPtr<IncomingDatagramStreamAlgorithms> incomingAlgorithms = + mIncomingAlgorithms; + // Step 18: Set up incomingDatagrams with pullAlgorithm set to + // pullDatagramsAlgorithm, and highWaterMark set to 0. + mReadable = ReadableStream::CreateNative(cx, global, *incomingAlgorithms, + Some(0.0), nullptr, aError); + if (aError.Failed()) { + return; + } + + mOutgoingAlgorithms = new OutgoingDatagramStreamAlgorithms(this); + RefPtr<OutgoingDatagramStreamAlgorithms> outgoingAlgorithms = + mOutgoingAlgorithms; + // Step 19: Set up outgoingDatagrams with writeAlgorithm set to + // writeDatagramsAlgorithm. + mWritable = WritableStream::CreateNative(cx, *global, *outgoingAlgorithms, + Nothing(), nullptr, aError); + if (aError.Failed()) { + return; + } + LOG(("Created datagram streams")); +} + +void WebTransportDatagramDuplexStream::SetIncomingMaxAge(double aMaxAge, + ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#dom-webtransportdatagramduplexstream-incomingmaxage + // Step 1 + if (isnan(aMaxAge) || aMaxAge < 0.) { + aRv.ThrowRangeError("Invalid IncomingMaxAge"); + return; + } + // Step 2 + if (aMaxAge == 0) { + aMaxAge = INFINITY; + } + // Step 3 + mIncomingMaxAge = aMaxAge; +} + +void WebTransportDatagramDuplexStream::SetOutgoingMaxAge(double aMaxAge, + ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#dom-webtransportdatagramduplexstream-outgoingmaxage + // Step 1 + if (isnan(aMaxAge) || aMaxAge < 0.) { + aRv.ThrowRangeError("Invalid OutgoingMaxAge"); + return; + } + // Step 2 + if (aMaxAge == 0.) { + aMaxAge = INFINITY; + } + // Step 3 + mOutgoingMaxAge = aMaxAge; +} + +void WebTransportDatagramDuplexStream::SetIncomingHighWaterMark( + double aWaterMark, ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#dom-webtransportdatagramduplexstream-incominghighwatermark + // Step 1 + if (isnan(aWaterMark) || aWaterMark < 0.) { + aRv.ThrowRangeError("Invalid OutgoingMaxAge"); + return; + } + // Step 2 + if (aWaterMark < 1.0) { + aWaterMark = 1.0; + } + // Step 3 + mIncomingHighWaterMark = aWaterMark; +} + +void WebTransportDatagramDuplexStream::SetOutgoingHighWaterMark( + double aWaterMark, ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#dom-webtransportdatagramduplexstream-outgoinghighwatermark + // Step 1 + if (isnan(aWaterMark) || aWaterMark < 0.) { + aRv.ThrowRangeError("Invalid OutgoingHighWaterMark"); + return; + } + // Step 2 of setter: If value is < 1, set value to 1. + if (aWaterMark < 1.0) { + aWaterMark = 1.0; + } + // Step 3 + mOutgoingHighWaterMark = aWaterMark; +} + +void WebTransportDatagramDuplexStream::NewDatagramReceived( + nsTArray<uint8_t>&& aData, const mozilla::TimeStamp& aTimeStamp) { + LOG(("received Datagram, size = %zu", aData.Length())); + mIncomingDatagramsQueue.Push(UniquePtr<DatagramEntry>( + new DatagramEntry(std::move(aData), aTimeStamp))); + mIncomingAlgorithms->NotifyDatagramAvailable(); +} + +// WebIDL Boilerplate + +nsIGlobalObject* WebTransportDatagramDuplexStream::GetParentObject() const { + return mGlobal; +} + +JSObject* WebTransportDatagramDuplexStream::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return WebTransportDatagramDuplexStream_Binding::Wrap(aCx, this, aGivenProto); +} + +// WebIDL Interface + +using namespace mozilla::ipc; + +NS_IMPL_CYCLE_COLLECTION_INHERITED(IncomingDatagramStreamAlgorithms, + UnderlyingSourceAlgorithmsWrapper, + mDatagrams, mIncomingDatagramsPullPromise) +NS_IMPL_ADDREF_INHERITED(IncomingDatagramStreamAlgorithms, + UnderlyingSourceAlgorithmsWrapper) +NS_IMPL_RELEASE_INHERITED(IncomingDatagramStreamAlgorithms, + UnderlyingSourceAlgorithmsWrapper) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IncomingDatagramStreamAlgorithms) +NS_INTERFACE_MAP_END_INHERITING(UnderlyingSourceAlgorithmsWrapper) + +IncomingDatagramStreamAlgorithms::IncomingDatagramStreamAlgorithms( + WebTransportDatagramDuplexStream* aDatagrams) + : mDatagrams(aDatagrams) {} + +IncomingDatagramStreamAlgorithms::~IncomingDatagramStreamAlgorithms() = default; + +already_AddRefed<Promise> IncomingDatagramStreamAlgorithms::PullCallbackImpl( + JSContext* aCx, ReadableStreamController& aController, ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#datagram-duplex-stream-procedures + + RefPtr<Promise> promise = + Promise::CreateInfallible(mDatagrams->GetParentObject()); + // Step 1: Let datagrams be transport.[[Datagrams]]. + // Step 2: Assert: datagrams.[[IncomingDatagramsPullPromise]] is null. + MOZ_ASSERT(!mIncomingDatagramsPullPromise); + + RefPtr<IncomingDatagramStreamAlgorithms> self(this); + // The real work of PullCallback() + // Step 3: Let queue be datagrams.[[IncomingDatagramsQueue]]. + // Step 4: If queue is empty, then: + if (mDatagrams->mIncomingDatagramsQueue.IsEmpty()) { + // We need to wait. + // Per + // https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-pulling + // we can't be called again until the promise is resolved + // Step 4.1: Set datagrams.[[IncomingDatagramsPullPromise]] to a new + // promise. + // Step 4.2: Return datagrams.[[IncomingDatagramsPullPromise]]. + mIncomingDatagramsPullPromise = promise; + + LOG(("Datagrams Pull waiting for a datagram")); + Result<RefPtr<Promise>, nsresult> returnResult = + promise->ThenWithCycleCollectedArgs( + [](JSContext* aCx, JS::Handle<JS::Value>, ErrorResult& aRv, + RefPtr<IncomingDatagramStreamAlgorithms> self, + RefPtr<Promise> aPromise) + MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION -> already_AddRefed<Promise> { + // https://w3c.github.io/webtransport/#receivedatagrams + // step 10 + self->ReturnDatagram(aCx, aRv); + return nullptr; + }, + self, promise); + if (returnResult.isErr()) { + aRv.Throw(returnResult.unwrapErr()); + return nullptr; + } + // Step 4: Return p and run the remaining steps in parallel. + return returnResult.unwrap().forget(); + } + // Steps 5-7 are covered here: + self->ReturnDatagram(aCx, aRv); + // Step 8: Return a promise resolved with undefined. + promise->MaybeResolveWithUndefined(); + return promise.forget(); +} + +// Note: fallible +void IncomingDatagramStreamAlgorithms::ReturnDatagram(JSContext* aCx, + ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#datagram-duplex-stream-procedures + // Pull and Receive + LOG(("Returning a Datagram")); + + MOZ_ASSERT(!mDatagrams->mIncomingDatagramsQueue.IsEmpty()); + // Pull Step 5: Let bytes and timestamp be the result of dequeuing queue. + UniquePtr<DatagramEntry> entry = mDatagrams->mIncomingDatagramsQueue.Pop(); + + // Pull Step 6: Let chunk be a new Uint8Array object representing bytes. + JSObject* outView = Uint8Array::Create(aCx, entry->mBuffer, aRv); + if (aRv.Failed()) { + return; + } + JS::Rooted<JSObject*> chunk(aCx, outView); + + // Pull Step 7: Enqueue chunk to transport.[[Datagrams]].[[Readable]] + JS::Rooted<JS::Value> jsDatagram(aCx, JS::ObjectValue(*chunk)); + // EnqueueNative is CAN_RUN_SCRIPT + RefPtr<ReadableStream> stream = mDatagrams->mReadable; + stream->EnqueueNative(aCx, jsDatagram, aRv); + if (MOZ_UNLIKELY(aRv.Failed())) { + return; + } + // (caller) Step 8: return a promise resolved with Undefined +} + +void IncomingDatagramStreamAlgorithms::NotifyDatagramAvailable() { + if (RefPtr<Promise> promise = mIncomingDatagramsPullPromise.forget()) { + promise->MaybeResolveWithUndefined(); + } +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(OutgoingDatagramStreamAlgorithms, + UnderlyingSinkAlgorithmsWrapper, mDatagrams, + mWaitConnectPromise) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(OutgoingDatagramStreamAlgorithms) +NS_INTERFACE_MAP_END_INHERITING(UnderlyingSinkAlgorithmsWrapper) +NS_IMPL_ADDREF_INHERITED(OutgoingDatagramStreamAlgorithms, + UnderlyingSinkAlgorithmsWrapper) +NS_IMPL_RELEASE_INHERITED(OutgoingDatagramStreamAlgorithms, + UnderlyingSinkAlgorithmsWrapper) + +already_AddRefed<Promise> OutgoingDatagramStreamAlgorithms::WriteCallback( + JSContext* aCx, JS::Handle<JS::Value> aChunk, + WritableStreamDefaultController& aController, ErrorResult& aError) { + // https://w3c.github.io/webtransport/#writedatagrams + // Step 1. Let timestamp be a timestamp representing now. + TimeStamp now = TimeStamp::Now(); + + // Step 2: If data is not a BufferSource object, then return a promise + // rejected with a TypeError. { BufferSource == ArrayBuffer/ArrayBufferView } + ArrayBufferViewOrArrayBuffer arrayBuffer; + if (!arrayBuffer.Init(aCx, aChunk)) { + return Promise::CreateRejectedWithTypeError( + mDatagrams->GetParentObject(), + "Wrong type for Datagram stream write"_ns, aError); + } + + // Step 3: Let datagrams be transport.[[Datagrams]]. + // (mDatagrams is transport.[[Datagrams]]) + + nsTArray<uint8_t> data; + Unused << AppendTypedArrayDataTo(arrayBuffer, data); + + // Step 4: If datagrams.[[OutgoingMaxDatagramSize]] is less than data’s + // [[ByteLength]], return a promise resolved with undefined. + if (mDatagrams->mOutgoingMaxDataSize < static_cast<uint64_t>(data.Length())) { + return Promise::CreateResolvedWithUndefined(mDatagrams->GetParentObject(), + aError); + } + + // Step 5: Let promise be a new Promise + RefPtr<Promise> promise = + Promise::CreateInfallible(mDatagrams->GetParentObject()); + + // mChild is set when we move to Connected + if (mChild) { + // We pass along the datagram to the parent immediately. + // The OutgoingDatagramsQueue lives there, and steps 6-9 generally are + // implemented there + LOG(("Sending Datagram, size = %zu", data.Length())); + mChild->SendOutgoingDatagram( + std::move(data), now, + [promise](nsresult&&) { + // XXX result + LOG(("Datagram was sent")); + promise->MaybeResolveWithUndefined(); + }, + [promise](mozilla::ipc::ResponseRejectReason&&) { + LOG(("Datagram failed")); + // there's no description in the spec of rejecting if a datagram + // can't be sent; to the contrary, it says we should resolve with + // undefined if we throw the datagram away + promise->MaybeResolveWithUndefined(); + }); + } else { + LOG(("Queuing datagram for connect")); + // Queue locally until we can send it. + // We should be guaranteed that we don't get called again until the + // promise is resolved. + MOZ_ASSERT(mWaitConnect == nullptr); + mWaitConnect.reset(new DatagramEntry(std::move(data), now)); + mWaitConnectPromise = promise; + } + + // Step 10: return promise + return promise.forget(); +} + +// XXX should we allow datagrams to be sent before connect? Check IETF spec +void OutgoingDatagramStreamAlgorithms::SetChild(WebTransportChild* aChild) { + LOG(("Setting child in datagrams")); + mChild = aChild; + if (mWaitConnect) { + LOG(("Sending queued datagram")); + mChild->SendOutgoingDatagram( + mWaitConnect->mBuffer, mWaitConnect->mTimeStamp, + [promise = mWaitConnectPromise](nsresult&&) { + LOG_VERBOSE(("Early Datagram was sent")); + promise->MaybeResolveWithUndefined(); + }, + [promise = mWaitConnectPromise](mozilla::ipc::ResponseRejectReason&&) { + LOG(("Early Datagram failed")); + promise->MaybeResolveWithUndefined(); + }); + mWaitConnectPromise = nullptr; + mWaitConnect.reset(nullptr); + } +} + +} // namespace mozilla::dom diff --git a/dom/webtransport/api/WebTransportDatagramDuplexStream.h b/dom/webtransport/api/WebTransportDatagramDuplexStream.h new file mode 100644 index 0000000000..510cbcac71 --- /dev/null +++ b/dom/webtransport/api/WebTransportDatagramDuplexStream.h @@ -0,0 +1,166 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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 DOM_WEBTRANSPORT_API_WEBTRANSPORTDATAGRAMDUPLEXSTREAM__H_ +#define DOM_WEBTRANSPORT_API_WEBTRANSPORTDATAGRAMDUPLEXSTREAM__H_ + +#include <utility> +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "nsWrapperCache.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/ReadableStream.h" +#include "mozilla/dom/TypedArray.h" +#include "mozilla/dom/WritableStream.h" +#include "mozilla/dom/WebTransport.h" +#include "mozilla/dom/WebTransportDatagramDuplexStreamBinding.h" + +namespace mozilla::dom { + +class IncomingDatagramStreamAlgorithms + : public UnderlyingSourceAlgorithmsWrapper { + public: + explicit IncomingDatagramStreamAlgorithms( + WebTransportDatagramDuplexStream* aDatagrams); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(IncomingDatagramStreamAlgorithms, + UnderlyingSourceAlgorithmsWrapper) + + MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> PullCallbackImpl( + JSContext* aCx, ReadableStreamController& aController, + ErrorResult& aRv) override; + + MOZ_CAN_RUN_SCRIPT void ReturnDatagram(JSContext* aCx, ErrorResult& aRv); + + void NotifyDatagramAvailable(); + + protected: + ~IncomingDatagramStreamAlgorithms() override; + + private: + RefPtr<WebTransportDatagramDuplexStream> mDatagrams; + RefPtr<Promise> mIncomingDatagramsPullPromise; +}; + +class EarlyDatagram final { + public: + EarlyDatagram(DatagramEntry* aDatagram, Promise* aPromise) + : mDatagram(aDatagram), mWaitConnectPromise(aPromise) {} + + UniquePtr<DatagramEntry> mDatagram; + RefPtr<Promise> mWaitConnectPromise; + + ~EarlyDatagram() = default; +}; + +class OutgoingDatagramStreamAlgorithms final + : public UnderlyingSinkAlgorithmsWrapper { + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(OutgoingDatagramStreamAlgorithms, + UnderlyingSinkAlgorithmsWrapper) + + explicit OutgoingDatagramStreamAlgorithms( + WebTransportDatagramDuplexStream* aDatagrams) + : mDatagrams(aDatagrams) {} + + void SetChild(WebTransportChild* aChild); + + // Streams algorithms + + already_AddRefed<Promise> WriteCallback( + JSContext* aCx, JS::Handle<JS::Value> aChunk, + WritableStreamDefaultController& aController, + ErrorResult& aError) override; + + private: + ~OutgoingDatagramStreamAlgorithms() override = default; + + RefPtr<WebTransportDatagramDuplexStream> mDatagrams; + RefPtr<WebTransportChild> mChild; + // only used for datagrams sent before Ready + UniquePtr<DatagramEntry> mWaitConnect; + RefPtr<Promise> mWaitConnectPromise; +}; + +class WebTransportDatagramDuplexStream final : public nsISupports, + public nsWrapperCache { + friend class IncomingDatagramStreamAlgorithms; + friend class OutgoingDatagramStreamAlgorithms; + + public: + WebTransportDatagramDuplexStream(nsIGlobalObject* aGlobal, + WebTransport* aWebTransport); + + void Init(ErrorResult& aError); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WebTransportDatagramDuplexStream) + + void SetChild(WebTransportChild* aChild) { + mOutgoingAlgorithms->SetChild(aChild); + } + + void NewDatagramReceived(nsTArray<uint8_t>&& aData, + const mozilla::TimeStamp& aTimeStamp); + + // WebIDL Boilerplate + nsIGlobalObject* GetParentObject() const; + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // WebIDL Interface + already_AddRefed<ReadableStream> Readable() const { + RefPtr<ReadableStream> result(mReadable); + return result.forget(); + } + already_AddRefed<WritableStream> Writable() { + RefPtr<WritableStream> result(mWritable); + return result.forget(); + } + + uint64_t MaxDatagramSize() const { return mOutgoingMaxDataSize; } + void SetMaxDatagramSize(const uint64_t& aMaxDatagramSize) { + mOutgoingMaxDataSize = aMaxDatagramSize; + } + double GetIncomingMaxAge(ErrorResult& aRv) const { return mIncomingMaxAge; } + void SetIncomingMaxAge(double aMaxAge, ErrorResult& aRv); + double GetOutgoingMaxAge(ErrorResult& aRv) const { return mOutgoingMaxAge; } + void SetOutgoingMaxAge(double aMaxAge, ErrorResult& aRv); + double GetIncomingHighWaterMark(ErrorResult& aRv) const { + return mIncomingHighWaterMark; + } + void SetIncomingHighWaterMark(double aWaterMark, ErrorResult& aRv); + double GetOutgoingHighWaterMark(ErrorResult& aRv) const { + return mOutgoingHighWaterMark; + } + void SetOutgoingHighWaterMark(double aWaterMark, ErrorResult& aRv); + + private: + ~WebTransportDatagramDuplexStream() = default; + + nsCOMPtr<nsIGlobalObject> mGlobal; + RefPtr<WebTransport> mWebTransport; + RefPtr<ReadableStream> mReadable; + RefPtr<WritableStream> mWritable; + RefPtr<IncomingDatagramStreamAlgorithms> mIncomingAlgorithms; + RefPtr<OutgoingDatagramStreamAlgorithms> mOutgoingAlgorithms; + + // https://w3c.github.io/webtransport/#webtransportdatagramduplexstream-create + double mIncomingMaxAge = INFINITY; + double mOutgoingMaxAge = INFINITY; + // These are implementation-defined + double mIncomingHighWaterMark = 1.0; + double mOutgoingHighWaterMark = 5.0; + uint64_t mOutgoingMaxDataSize = 1024; // implementation-defined integer + + mozilla::Queue<UniquePtr<DatagramEntry>, 32> mIncomingDatagramsQueue; +}; + +} // namespace mozilla::dom + +#endif diff --git a/dom/webtransport/api/WebTransportError.cpp b/dom/webtransport/api/WebTransportError.cpp new file mode 100644 index 0000000000..8451e3937f --- /dev/null +++ b/dom/webtransport/api/WebTransportError.cpp @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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/. */ + +#include "WebTransportError.h" + +namespace mozilla::dom { + +JSObject* WebTransportError::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return WebTransportError_Binding::Wrap(aCx, this, aGivenProto); +} + +/* static */ +already_AddRefed<WebTransportError> WebTransportError::Constructor( + const GlobalObject& aGlobal, const WebTransportErrorInit& aInit) { + // https://w3c.github.io/webtransport/#web-transport-error-constructor1 + + // Step 2: Let message be init.message if it exists, and "" otherwise. + nsCString message(""_ns); + if (aInit.mMessage.WasPassed()) { + CopyUTF16toUTF8(aInit.mMessage.Value(), message); + } + + // Step 1: Let error be this. + // Step 3: Set up error with message and "stream". + RefPtr<WebTransportError> error(new WebTransportError(message)); + + // Step 4: Set error.[[StreamErrorCode]] to init.streamErrorCode if it exists. + if (aInit.mStreamErrorCode.WasPassed()) { + error->mStreamErrorCode = Nullable(aInit.mStreamErrorCode.Value()); + } + return error.forget(); +} + +} // namespace mozilla::dom diff --git a/dom/webtransport/api/WebTransportError.h b/dom/webtransport/api/WebTransportError.h new file mode 100644 index 0000000000..a4b4e088e3 --- /dev/null +++ b/dom/webtransport/api/WebTransportError.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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 DOM_WEBTRANSPORT_API_WEBTRANSPORTERROR__H_ +#define DOM_WEBTRANSPORT_API_WEBTRANSPORTERROR__H_ + +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/WebTransportErrorBinding.h" + +namespace mozilla::dom { +class WebTransportError final : public DOMException { + public: + explicit WebTransportError( + const nsACString& aMessage, + WebTransportErrorSource aSource = WebTransportErrorSource::Stream, + Nullable<uint8_t> aCode = Nullable<uint8_t>()) + : DOMException(NS_OK, aMessage, "WebTransportError"_ns, 0), + mStreamErrorCode(aCode), + mSource(aSource) {} + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + static already_AddRefed<WebTransportError> Constructor( + const GlobalObject& aGlobal, const WebTransportErrorInit& aInit); + + WebTransportErrorSource Source() { return mSource; } + Nullable<uint8_t> GetStreamErrorCode() const { return mStreamErrorCode; } + + private: + Nullable<uint8_t> mStreamErrorCode; + const WebTransportErrorSource mSource; +}; + +} // namespace mozilla::dom + +#endif diff --git a/dom/webtransport/api/WebTransportReceiveStream.cpp b/dom/webtransport/api/WebTransportReceiveStream.cpp new file mode 100644 index 0000000000..ef9eba8d9c --- /dev/null +++ b/dom/webtransport/api/WebTransportReceiveStream.cpp @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "mozilla/dom/WebTransportReceiveStream.h" + +#include "mozilla/dom/ReadableByteStreamController.h" +#include "mozilla/dom/UnderlyingSourceCallbackHelpers.h" +#include "mozilla/dom/ReadableStream.h" +#include "mozilla/dom/WebTransport.h" +#include "mozilla/dom/WebTransportSendReceiveStreamBinding.h" +#include "mozilla/ipc/DataPipe.h" + +using namespace mozilla::ipc; + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_INHERITED(WebTransportReceiveStream, ReadableStream, + mTransport) +NS_IMPL_ADDREF_INHERITED(WebTransportReceiveStream, ReadableStream) +NS_IMPL_RELEASE_INHERITED(WebTransportReceiveStream, ReadableStream) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebTransportReceiveStream) +NS_INTERFACE_MAP_END_INHERITING(ReadableStream) + +WebTransportReceiveStream::WebTransportReceiveStream(nsIGlobalObject* aGlobal, + WebTransport* aTransport) + : ReadableStream(aGlobal, + ReadableStream::HoldDropJSObjectsCaller::Explicit), + mTransport(aTransport) { + mozilla::HoldJSObjects(this); +} + +// WebIDL Boilerplate + +JSObject* WebTransportReceiveStream::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return WebTransportReceiveStream_Binding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed<WebTransportReceiveStream> WebTransportReceiveStream::Create( + WebTransport* aWebTransport, nsIGlobalObject* aGlobal, uint64_t aStreamId, + DataPipeReceiver* receiver, ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#webtransportreceivestream-create + AutoJSAPI jsapi; + if (!jsapi.Init(aGlobal)) { + return nullptr; + } + JSContext* cx = jsapi.cx(); + + auto stream = MakeRefPtr<WebTransportReceiveStream>(aGlobal, aWebTransport); + + nsCOMPtr<nsIAsyncInputStream> inputStream = receiver; + auto algorithms = MakeRefPtr<InputToReadableStreamAlgorithms>( + cx, inputStream, (ReadableStream*)stream); + + stream->SetUpByteNative(cx, *algorithms, Some(0.0), aRv); + if (aRv.Failed()) { + return nullptr; + } + // Add to ReceiveStreams + aWebTransport->mReceiveStreams.InsertOrUpdate(aStreamId, stream); + return stream.forget(); +} + +already_AddRefed<Promise> WebTransportReceiveStream::GetStats() { + RefPtr<Promise> promise = Promise::CreateInfallible(ReadableStream::mGlobal); + promise->MaybeRejectWithNotSupportedError("GetStats isn't supported yet"); + return promise.forget(); +} + +} // namespace mozilla::dom diff --git a/dom/webtransport/api/WebTransportReceiveStream.h b/dom/webtransport/api/WebTransportReceiveStream.h new file mode 100644 index 0000000000..d27a6673d9 --- /dev/null +++ b/dom/webtransport/api/WebTransportReceiveStream.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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 DOM_WEBTRANSPORT_API_WEBTRANSPORTRECEIVESTREAM__H_ +#define DOM_WEBTRANSPORT_API_WEBTRANSPORTRECEIVESTREAM__H_ + +#include "mozilla/dom/ReadableStream.h" + +namespace mozilla::ipc { +class DataPipeReceiver; +} + +namespace mozilla::dom { + +class WebTransport; + +class WebTransportReceiveStream final : public ReadableStream { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(WebTransportReceiveStream, + ReadableStream) + + WebTransportReceiveStream(nsIGlobalObject* aGlobal, WebTransport* aTransport); + + static already_AddRefed<WebTransportReceiveStream> Create( + WebTransport* aWebTransport, nsIGlobalObject* aGlobal, uint64_t aStreamId, + mozilla::ipc::DataPipeReceiver* receiver, ErrorResult& aRv); + + // WebIDL Boilerplate + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // WebIDL Interface + already_AddRefed<Promise> GetStats(); + + private: + ~WebTransportReceiveStream() override { mozilla::DropJSObjects(this); } + + // We must hold a reference to the WebTransport so it can't go away on + // us. This forms a cycle with WebTransport that will be broken when the + // CC runs. WebTransport::CleanUp() will destroy all the send and receive + // streams, breaking the cycle. + RefPtr<WebTransport> mTransport; +}; +} // namespace mozilla::dom + +#endif diff --git a/dom/webtransport/api/WebTransportSendStream.cpp b/dom/webtransport/api/WebTransportSendStream.cpp new file mode 100644 index 0000000000..99c0992f10 --- /dev/null +++ b/dom/webtransport/api/WebTransportSendStream.cpp @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "mozilla/dom/WebTransportSendStream.h" + +#include "mozilla/dom/UnderlyingSinkCallbackHelpers.h" +#include "mozilla/dom/WritableStream.h" +#include "mozilla/dom/WebTransport.h" +#include "mozilla/dom/WebTransportSendReceiveStreamBinding.h" +#include "mozilla/ipc/DataPipe.h" + +using namespace mozilla::ipc; + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_INHERITED(WebTransportSendStream, WritableStream, + mTransport) +NS_IMPL_ADDREF_INHERITED(WebTransportSendStream, WritableStream) +NS_IMPL_RELEASE_INHERITED(WebTransportSendStream, WritableStream) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebTransportSendStream) +NS_INTERFACE_MAP_END_INHERITING(WritableStream) + +WebTransportSendStream::WebTransportSendStream(nsIGlobalObject* aGlobal, + WebTransport* aTransport) + : WritableStream(aGlobal, + WritableStream::HoldDropJSObjectsCaller::Explicit), + mTransport(aTransport) { + mozilla::HoldJSObjects(this); +} + +JSObject* WebTransportSendStream::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return WebTransportSendStream_Binding::Wrap(aCx, this, aGivenProto); +} + +// NOTE: this does not yet implement SendOrder; see bug 1816925 +/* static */ +already_AddRefed<WebTransportSendStream> WebTransportSendStream::Create( + WebTransport* aWebTransport, nsIGlobalObject* aGlobal, uint64_t aStreamId, + DataPipeSender* aSender, Maybe<int64_t> aSendOrder, ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#webtransportsendstream-create + AutoJSAPI jsapi; + if (!jsapi.Init(aGlobal)) { + return nullptr; + } + JSContext* cx = jsapi.cx(); + + auto stream = MakeRefPtr<WebTransportSendStream>(aGlobal, aWebTransport); + + nsCOMPtr<nsIAsyncOutputStream> outputStream = aSender; + auto algorithms = MakeRefPtr<WritableStreamToOutput>( + stream->GetParentObject(), outputStream); + + stream->mStreamId = aStreamId; + + if (aSendOrder.isSome()) { + stream->mSendOrder.SetValue(aSendOrder.value()); + } + + // Steps 2-5 + RefPtr<QueuingStrategySize> writableSizeAlgorithm; + stream->SetUpNative(cx, *algorithms, Nothing(), writableSizeAlgorithm, aRv); + + // Step 6: Add the following steps to stream’s [[controller]]'s [[signal]]. + // Step 6.1: If stream.[[PendingOperation]] is null, then abort these steps. + // Step 6.2: Let reason be stream’s [[controller]]'s [[signal]]'s abort + // reason. Step 6.3: Let abortPromise be the result of aborting stream with + // reason. Step 6.4: Upon fulfillment of abortPromise, reject promise with + // reason. Step 6.5: Let pendingOperation be stream.[[PendingOperation]]. + // Step 6.6: Set stream.[[PendingOperation]] to null. + // Step 6.7: Resolve pendingOperation with promise. + // XXX TODO + + // Step 7: Append stream to SendStreams + aWebTransport->mSendStreams.InsertOrUpdate(aStreamId, stream); + // Step 8: return stream + return stream.forget(); +} + +void WebTransportSendStream::SetSendOrder(Nullable<int64_t> aSendOrder) { + mSendOrder = aSendOrder; + mTransport->SendSetSendOrder( + mStreamId, aSendOrder.IsNull() ? Nothing() : Some(aSendOrder.Value())); +} + +already_AddRefed<Promise> WebTransportSendStream::GetStats() { + RefPtr<Promise> promise = Promise::CreateInfallible(WritableStream::mGlobal); + promise->MaybeRejectWithNotSupportedError("GetStats isn't supported yet"); + return promise.forget(); +} + +} // namespace mozilla::dom diff --git a/dom/webtransport/api/WebTransportSendStream.h b/dom/webtransport/api/WebTransportSendStream.h new file mode 100644 index 0000000000..aa7c08d1a3 --- /dev/null +++ b/dom/webtransport/api/WebTransportSendStream.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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 DOM_WEBTRANSPORT_API_WEBTRANSPORTSENDSTREAM__H_ +#define DOM_WEBTRANSPORT_API_WEBTRANSPORTSENDSTREAM__H_ + +#include "mozilla/dom/WritableStream.h" + +namespace mozilla::ipc { +class DataPipeSender; +} + +namespace mozilla::dom { + +class WebTransport; + +class WebTransportSendStream final : public WritableStream { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(WebTransportSendStream, + WritableStream) + + WebTransportSendStream(nsIGlobalObject* aGlobal, WebTransport* aTransport); + + static already_AddRefed<WebTransportSendStream> Create( + WebTransport* aWebTransport, nsIGlobalObject* aGlobal, uint64_t aStreamId, + mozilla::ipc::DataPipeSender* aSender, Maybe<int64_t> aSendOrder, + ErrorResult& aRv); + + // WebIDL Boilerplate + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // WebIDL Interface + Nullable<int64_t> GetSendOrder() { return mSendOrder; } + + void SetSendOrder(Nullable<int64_t> aSendOrder); + + already_AddRefed<Promise> GetStats(); + + private: + ~WebTransportSendStream() override { mozilla::DropJSObjects(this); }; + + // We must hold a reference to the WebTransport so it can't go away on + // us. This forms a cycle with WebTransport that will be broken when the + // CC runs. WebTransport::CleanUp() will destroy all the send and receive + // streams, breaking the cycle. + RefPtr<WebTransport> mTransport; + uint64_t mStreamId; + Nullable<int64_t> mSendOrder; +}; +} // namespace mozilla::dom + +#endif diff --git a/dom/webtransport/api/WebTransportStreams.cpp b/dom/webtransport/api/WebTransportStreams.cpp new file mode 100644 index 0000000000..30c2d00c52 --- /dev/null +++ b/dom/webtransport/api/WebTransportStreams.cpp @@ -0,0 +1,197 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "mozilla/dom/WebTransportStreams.h" + +#include "mozilla/dom/WebTransportLog.h" +#include "mozilla/dom/Promise-inl.h" +#include "mozilla/dom/WebTransport.h" +#include "mozilla/dom/WebTransportBidirectionalStream.h" +#include "mozilla/dom/WebTransportReceiveStream.h" +#include "mozilla/dom/WebTransportSendStream.h" +#include "mozilla/Result.h" + +using namespace mozilla::ipc; + +namespace mozilla::dom { +NS_IMPL_CYCLE_COLLECTION_INHERITED(WebTransportIncomingStreamsAlgorithms, + UnderlyingSourceAlgorithmsWrapper, + mTransport, mCallback) +NS_IMPL_ADDREF_INHERITED(WebTransportIncomingStreamsAlgorithms, + UnderlyingSourceAlgorithmsWrapper) +NS_IMPL_RELEASE_INHERITED(WebTransportIncomingStreamsAlgorithms, + UnderlyingSourceAlgorithmsWrapper) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebTransportIncomingStreamsAlgorithms) +NS_INTERFACE_MAP_END_INHERITING(UnderlyingSourceAlgorithmsWrapper) + +WebTransportIncomingStreamsAlgorithms::WebTransportIncomingStreamsAlgorithms( + StreamType aUnidirectional, WebTransport* aTransport) + : mUnidirectional(aUnidirectional), mTransport(aTransport) {} + +WebTransportIncomingStreamsAlgorithms:: + ~WebTransportIncomingStreamsAlgorithms() = default; + +already_AddRefed<Promise> +WebTransportIncomingStreamsAlgorithms::PullCallbackImpl( + JSContext* aCx, ReadableStreamController& aController, ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#pullbidirectionalstream and + // https://w3c.github.io/webtransport/#pullunidirectionalstream + + // Step 1: If transport.[[State]] is "connecting", then return the result + // of performing the following steps upon fulfillment of + // transport.[[Ready]]: + // We don't explicitly check mState here, since we'll reject + // mIncomingStreamPromise if we go to FAILED or CLOSED + // + // Step 2: Let session be transport.[[Session]]. + // Step 3: Let p be a new promise. + RefPtr<Promise> promise = + Promise::CreateInfallible(mTransport->GetParentObject()); + RefPtr<WebTransportIncomingStreamsAlgorithms> self(this); + // The real work of PullCallback() + // Step 5: Wait until there is an available incoming unidirectional stream. + auto length = (mUnidirectional == StreamType::Unidirectional) + ? mTransport->mUnidirectionalStreams.Length() + : mTransport->mBidirectionalStreams.Length(); + if (length == 0) { + // We need to wait. + // Per + // https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-pulling + // we can't be called again until the promise is resolved + MOZ_ASSERT(!mCallback); + mCallback = promise; + + LOG(("Incoming%sDirectionalStreams Pull waiting for a stream", + mUnidirectional == StreamType::Unidirectional ? "Uni" : "Bi")); + Result<RefPtr<Promise>, nsresult> returnResult = + promise->ThenWithCycleCollectedArgs( + [](JSContext* aCx, JS::Handle<JS::Value>, ErrorResult& aRv, + RefPtr<WebTransportIncomingStreamsAlgorithms> self, + RefPtr<Promise> aPromise) -> already_AddRefed<Promise> { + self->BuildStream(aCx, aRv); + return nullptr; + }, + self, promise); + if (returnResult.isErr()) { + // XXX Reject? + aRv.Throw(returnResult.unwrapErr()); + return nullptr; + } + // Step 4: Return p and run the remaining steps in parallel. + return returnResult.unwrap().forget(); + } + self->BuildStream(aCx, aRv); + // Step 4: Return p and run the remaining steps in parallel. + return promise.forget(); +} + +// Note: fallible +void WebTransportIncomingStreamsAlgorithms::BuildStream(JSContext* aCx, + ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#pullbidirectionalstream and + // https://w3c.github.io/webtransport/#pullunidirectionalstream + LOG(("Incoming%sDirectionalStreams Pull building a stream", + mUnidirectional == StreamType::Unidirectional ? "Uni" : "Bi")); + if (mUnidirectional == StreamType::Unidirectional) { + // Step 6: Let internalStream be the result of receiving an incoming + // unidirectional stream. + MOZ_ASSERT(mTransport->mUnidirectionalStreams.Length() > 0); + std::tuple<uint64_t, RefPtr<mozilla::ipc::DataPipeReceiver>> tuple = + mTransport->mUnidirectionalStreams[0]; + mTransport->mUnidirectionalStreams.RemoveElementAt(0); + + // Step 7.1: Let stream be the result of creating a + // WebTransportReceiveStream with internalStream and transport + RefPtr<WebTransportReceiveStream> readableStream = + WebTransportReceiveStream::Create(mTransport, mTransport->mGlobal, + std::get<0>(tuple), + std::get<1>(tuple), aRv); + if (MOZ_UNLIKELY(!readableStream)) { + aRv.ThrowUnknownError("Internal error"); + return; + } + // Step 7.2 Enqueue stream to transport.[[IncomingUnidirectionalStreams]]. + JS::Rooted<JS::Value> jsStream(aCx); + if (MOZ_UNLIKELY(!ToJSValue(aCx, readableStream, &jsStream))) { + aRv.ThrowUnknownError("Internal error"); + return; + } + // EnqueueNative is CAN_RUN_SCRIPT + RefPtr<ReadableStream> incomingStream = + mTransport->mIncomingUnidirectionalStreams; + incomingStream->EnqueueNative(aCx, jsStream, aRv); + if (MOZ_UNLIKELY(aRv.Failed())) { + aRv.ThrowUnknownError("Internal error"); + return; + } + } else { + // Step 6: Let internalStream be the result of receiving a bidirectional + // stream + MOZ_ASSERT(mTransport->mBidirectionalStreams.Length() > 0); + std::tuple<uint64_t, UniquePtr<BidirectionalPair>> tuple = + std::move(mTransport->mBidirectionalStreams.ElementAt(0)); + mTransport->mBidirectionalStreams.RemoveElementAt(0); + RefPtr<DataPipeReceiver> input = std::get<1>(tuple)->first.forget(); + RefPtr<DataPipeSender> output = std::get<1>(tuple)->second.forget(); + + RefPtr<WebTransportBidirectionalStream> stream = + WebTransportBidirectionalStream::Create(mTransport, mTransport->mGlobal, + std::get<0>(tuple), input, + output, Nothing(), aRv); + + // Step 7.2 Enqueue stream to transport.[[IncomingBidirectionalStreams]]. + JS::Rooted<JS::Value> jsStream(aCx); + if (MOZ_UNLIKELY(!ToJSValue(aCx, stream, &jsStream))) { + return; + } + LOG(("Enqueuing bidirectional stream\n")); + // EnqueueNative is CAN_RUN_SCRIPT + RefPtr<ReadableStream> incomingStream = + mTransport->mIncomingBidirectionalStreams; + incomingStream->EnqueueNative(aCx, jsStream, aRv); + if (MOZ_UNLIKELY(aRv.Failed())) { + return; + } + } + // Step 7.3: Resolve p with undefined. +} + +void WebTransportIncomingStreamsAlgorithms::NotifyIncomingStream() { + if (mUnidirectional == StreamType::Unidirectional) { + LOG(("NotifyIncomingStream: %zu Unidirectional ", + mTransport->mUnidirectionalStreams.Length())); +#ifdef DEBUG + auto number = mTransport->mUnidirectionalStreams.Length(); + MOZ_ASSERT(number > 0); +#endif + RefPtr<Promise> promise = mCallback.forget(); + if (promise) { + promise->MaybeResolveWithUndefined(); + } + } else { + LOG(("NotifyIncomingStream: %zu Bidirectional ", + mTransport->mBidirectionalStreams.Length())); +#ifdef DEBUG + auto number = mTransport->mBidirectionalStreams.Length(); + MOZ_ASSERT(number > 0); +#endif + RefPtr<Promise> promise = mCallback.forget(); + if (promise) { + promise->MaybeResolveWithUndefined(); + } + } +} + +void WebTransportIncomingStreamsAlgorithms::NotifyRejectAll() { + // cancel all pulls + LOG(("Cancel all WebTransport Pulls")); + // Ensure we clear the callback before resolving/rejecting it + if (RefPtr<Promise> promise = mCallback.forget()) { + promise->MaybeReject(NS_ERROR_FAILURE); + } +} + +} // namespace mozilla::dom diff --git a/dom/webtransport/api/WebTransportStreams.h b/dom/webtransport/api/WebTransportStreams.h new file mode 100644 index 0000000000..e9cc06176d --- /dev/null +++ b/dom/webtransport/api/WebTransportStreams.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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 DOM_WEBTRANSPORT_API_WEBTRANSPORTSTREAMS__H_ +#define DOM_WEBTRANSPORT_API_WEBTRANSPORTSTREAMS__H_ + +#include "mozilla/dom/UnderlyingSourceCallbackHelpers.h" +#include "mozilla/dom/WritableStream.h" + +namespace mozilla::dom { + +class WebTransport; + +class WebTransportIncomingStreamsAlgorithms + : public UnderlyingSourceAlgorithmsWrapper { + public: + enum class StreamType : uint8_t { Unidirectional, Bidirectional }; + WebTransportIncomingStreamsAlgorithms(StreamType aUnidirectional, + WebTransport* aTransport); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED( + WebTransportIncomingStreamsAlgorithms, UnderlyingSourceAlgorithmsWrapper) + + already_AddRefed<Promise> PullCallbackImpl( + JSContext* aCx, ReadableStreamController& aController, + ErrorResult& aRv) override; + + // We call EnqueueNative, which is MOZ_CAN_RUN_SCRIPT but won't in this case + MOZ_CAN_RUN_SCRIPT_BOUNDARY void BuildStream(JSContext* aCx, + ErrorResult& aRv); + + void NotifyIncomingStream(); + + void NotifyRejectAll(); + + protected: + ~WebTransportIncomingStreamsAlgorithms() override; + + private: + const StreamType mUnidirectional; + RefPtr<WebTransport> mTransport; + RefPtr<Promise> mCallback; +}; + +} // namespace mozilla::dom + +#endif // DOM_WEBTRANSPORT_API_WEBTRANSPORTSTREAMS__H_ diff --git a/dom/webtransport/api/moz.build b/dom/webtransport/api/moz.build new file mode 100644 index 0000000000..7a20c9b501 --- /dev/null +++ b/dom/webtransport/api/moz.build @@ -0,0 +1,29 @@ +# -*- 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/. + +EXPORTS.mozilla.dom += [ + "WebTransport.h", + "WebTransportBidirectionalStream.h", + "WebTransportDatagramDuplexStream.h", + "WebTransportError.h", + "WebTransportReceiveStream.h", + "WebTransportSendStream.h", + "WebTransportStreams.h", +] + +UNIFIED_SOURCES += [ + "WebTransport.cpp", + "WebTransportBidirectionalStream.cpp", + "WebTransportDatagramDuplexStream.cpp", + "WebTransportError.cpp", + "WebTransportReceiveStream.cpp", + "WebTransportSendStream.cpp", + "WebTransportStreams.cpp", +] + +FINAL_LIBRARY = "xul" + +include("/ipc/chromium/chromium-config.mozbuild") diff --git a/dom/webtransport/child/WebTransportChild.cpp b/dom/webtransport/child/WebTransportChild.cpp new file mode 100644 index 0000000000..12714b003e --- /dev/null +++ b/dom/webtransport/child/WebTransportChild.cpp @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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/. */ + +#include "mozilla/dom/WebTransport.h" +#include "mozilla/dom/WebTransportChild.h" +#include "mozilla/dom/WebTransportLog.h" + +namespace mozilla::dom { + +void WebTransportChild::Shutdown(bool aClose) { + LOG(("WebTransportChild::Shutdown() for %p (%p)", this, mTransport)); + mTransport = nullptr; + if (!aClose || !CanSend()) { + return; + } + + Close(); +} + +void WebTransportChild::CloseAll() { + // XXX need impl +} + +::mozilla::ipc::IPCResult WebTransportChild::RecvCloseAll( + CloseAllResolver&& aResolver) { + CloseAll(); + aResolver(NS_OK); + return IPC_OK(); +} + +::mozilla::ipc::IPCResult WebTransportChild::RecvRemoteClosed( + const bool& aCleanly, const uint32_t& aCode, const nsACString& aReason) { + if (mTransport) { + mTransport->RemoteClosed(aCleanly, aCode, aReason); + } + return IPC_OK(); +} + +::mozilla::ipc::IPCResult WebTransportChild::RecvIncomingBidirectionalStream( + const uint64_t& aStreamId, const RefPtr<DataPipeReceiver>& aIncoming, + const RefPtr<DataPipeSender>& aOutgoing) { + if (mTransport) { + mTransport->NewBidirectionalStream(aStreamId, aIncoming, aOutgoing); + } + return IPC_OK(); +} + +::mozilla::ipc::IPCResult WebTransportChild::RecvIncomingUnidirectionalStream( + const uint64_t& aStreamId, const RefPtr<DataPipeReceiver>& aStream) { + if (mTransport) { + mTransport->NewUnidirectionalStream(aStreamId, aStream); + } + return IPC_OK(); +} + +::mozilla::ipc::IPCResult WebTransportChild::RecvIncomingDatagram( + nsTArray<uint8_t>&& aData, const TimeStamp& aRecvTimeStamp) { + if (mTransport) { + mTransport->NewDatagramReceived(std::move(aData), aRecvTimeStamp); + } + return IPC_OK(); +} + +::mozilla::ipc::IPCResult WebTransportChild::RecvOnStreamResetOrStopSending( + const uint64_t& aStreamId, const StreamResetOrStopSendingError& aError) { + if (mTransport) { + mTransport->OnStreamResetOrStopSending(aStreamId, aError); + } + return IPC_OK(); +} + +} // namespace mozilla::dom diff --git a/dom/webtransport/child/WebTransportChild.h b/dom/webtransport/child/WebTransportChild.h new file mode 100644 index 0000000000..7e89896b89 --- /dev/null +++ b/dom/webtransport/child/WebTransportChild.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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 DOM_WEBTRANSPORT_WEBTRANSPORTCHILD_H_ +#define DOM_WEBTRANSPORT_WEBTRANSPORTCHILD_H_ + +#include "mozilla/TimeStamp.h" +#include "nsISupportsImpl.h" +#include "mozilla/dom/PWebTransportChild.h" +#include "mozilla/ipc/DataPipe.h" + +namespace mozilla::dom { + +class WebTransport; + +class WebTransportChild : public PWebTransportChild { + public: + NS_INLINE_DECL_REFCOUNTING(WebTransportChild) + explicit WebTransportChild(WebTransport* aTransport) + : mTransport(aTransport) {} + + virtual void CloseAll(); + + void Shutdown(bool aClose); + + ::mozilla::ipc::IPCResult RecvCloseAll(CloseAllResolver&& aResolver); + + ::mozilla::ipc::IPCResult RecvRemoteClosed(const bool& aCleanly, + const uint32_t& aCode, + const nsACString& aReason); + + ::mozilla::ipc::IPCResult RecvIncomingBidirectionalStream( + const uint64_t& aStreamId, + const RefPtr<mozilla::ipc::DataPipeReceiver>& aIncoming, + const RefPtr<mozilla::ipc::DataPipeSender>& aOutgoing); + + ::mozilla::ipc::IPCResult RecvIncomingUnidirectionalStream( + const uint64_t& aStreamId, + const RefPtr<mozilla::ipc::DataPipeReceiver>& aStream); + + ::mozilla::ipc::IPCResult RecvIncomingDatagram( + nsTArray<uint8_t>&& aData, const TimeStamp& aRecvTimeStamp); + + ::mozilla::ipc::IPCResult RecvOnStreamResetOrStopSending( + const uint64_t& aStreamId, const StreamResetOrStopSendingError& aError); + + protected: + WebTransport* mTransport; // WebTransport holds a strong reference to us, and + // calls Shutdown() before releasing it + virtual ~WebTransportChild() { MOZ_ASSERT(!mTransport); } +}; + +} // namespace mozilla::dom + +#endif // DOM_WEBTRANSPORT_WEBTRANSPORTCHILD_H_ diff --git a/dom/webtransport/child/moz.build b/dom/webtransport/child/moz.build new file mode 100644 index 0000000000..21df93c138 --- /dev/null +++ b/dom/webtransport/child/moz.build @@ -0,0 +1,17 @@ +# -*- 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/. + +EXPORTS.mozilla.dom += [ + "WebTransportChild.h", +] + +UNIFIED_SOURCES += [ + "WebTransportChild.cpp", +] + +FINAL_LIBRARY = "xul" + +include("/ipc/chromium/chromium-config.mozbuild") diff --git a/dom/webtransport/moz.build b/dom/webtransport/moz.build new file mode 100644 index 0000000000..0c825bca62 --- /dev/null +++ b/dom/webtransport/moz.build @@ -0,0 +1,16 @@ +# -*- 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", "DOM: Networking") + +DIRS += [ + "api", + "child", + "parent", + "shared", + "test", +] diff --git a/dom/webtransport/parent/WebTransportParent.cpp b/dom/webtransport/parent/WebTransportParent.cpp new file mode 100644 index 0000000000..c9f7943cc3 --- /dev/null +++ b/dom/webtransport/parent/WebTransportParent.cpp @@ -0,0 +1,836 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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/. */ + +#include "WebTransportParent.h" +#include "Http3WebTransportSession.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/ClientInfo.h" +#include "mozilla/dom/WebTransportBinding.h" +#include "mozilla/dom/WebTransportLog.h" +#include "mozilla/net/WebTransportHash.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "nsIEventTarget.h" +#include "nsIOService.h" +#include "nsIPrincipal.h" +#include "nsIWebTransport.h" +#include "nsStreamUtils.h" +#include "nsIWebTransportStream.h" + +using IPCResult = mozilla::ipc::IPCResult; + +namespace mozilla::dom { + +NS_IMPL_ISUPPORTS(WebTransportParent, WebTransportSessionEventListener); + +using CreateWebTransportPromise = + MozPromise<WebTransportReliabilityMode, nsresult, true>; +WebTransportParent::~WebTransportParent() { + LOG(("Destroying WebTransportParent %p", this)); +} + +void WebTransportParent::Create( + const nsAString& aURL, nsIPrincipal* aPrincipal, + const mozilla::Maybe<IPCClientInfo>& aClientInfo, const bool& aDedicated, + const bool& aRequireUnreliable, const uint32_t& aCongestionControl, + nsTArray<WebTransportHash>&& aServerCertHashes, + Endpoint<PWebTransportParent>&& aParentEndpoint, + std::function<void(std::tuple<const nsresult&, const uint8_t&>)>&& + aResolver) { + LOG(("Created WebTransportParent %p %s %s %s congestion=%s", this, + NS_ConvertUTF16toUTF8(aURL).get(), + aDedicated ? "Dedicated" : "AllowPooling", + aRequireUnreliable ? "RequireUnreliable" : "", + aCongestionControl == + (uint32_t)dom::WebTransportCongestionControl::Throughput + ? "ThroughPut" + : (aCongestionControl == + (uint32_t)dom::WebTransportCongestionControl::Low_latency + ? "Low-Latency" + : "Default"))); + + if (!StaticPrefs::network_webtransport_enabled()) { + aResolver(ResolveType( + NS_ERROR_DOM_NOT_ALLOWED_ERR, + static_cast<uint8_t>(WebTransportReliabilityMode::Pending))); + return; + } + + if (!aParentEndpoint.IsValid()) { + aResolver(ResolveType( + NS_ERROR_INVALID_ARG, + static_cast<uint8_t>(WebTransportReliabilityMode::Pending))); + return; + } + + MOZ_DIAGNOSTIC_ASSERT(mozilla::net::gIOService); + nsresult rv = + mozilla::net::gIOService->NewWebTransport(getter_AddRefs(mWebTransport)); + if (NS_FAILED(rv)) { + aResolver(ResolveType( + rv, static_cast<uint8_t>(WebTransportReliabilityMode::Pending))); + return; + } + + mOwningEventTarget = GetCurrentSerialEventTarget(); + MOZ_ASSERT(aPrincipal); + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), aURL); + if (NS_FAILED(rv)) { + aResolver(ResolveType( + NS_ERROR_INVALID_ARG, + static_cast<uint8_t>(WebTransportReliabilityMode::Pending))); + return; + } + + nsTArray<RefPtr<nsIWebTransportHash>> nsServerCertHashes; + for (const auto& hash : aServerCertHashes) { + nsCString alg = hash.algorithm(); + nsServerCertHashes.AppendElement( + new net::WebTransportHash(alg, hash.value())); + } + + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + "WebTransport AsyncConnect", + [self = RefPtr{this}, uri = std::move(uri), + nsServerCertHashes = std::move(nsServerCertHashes), + principal = RefPtr{aPrincipal}, + flags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + clientInfo = aClientInfo] { + LOG(("WebTransport %p AsyncConnect", self.get())); + if (NS_FAILED(self->mWebTransport->AsyncConnectWithClient( + uri, std::move(nsServerCertHashes), principal, flags, self, + clientInfo))) { + LOG(("AsyncConnect failure; we should get OnSessionClosed")); + } + }); + + // Bind to SocketThread for IPC - connection creation/destruction must + // hit MainThread, but keep all other traffic on SocketThread. Note that + // we must call aResolver() on this (PBackground) thread. + mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + InvokeAsync(mSocketThread, __func__, + [parentEndpoint = std::move(aParentEndpoint), runnable = r, + resolver = std::move(aResolver), p = RefPtr{this}]() mutable { + { + MutexAutoLock lock(p->mMutex); + p->mResolver = resolver; + } + + LOG(("Binding parent endpoint")); + if (!parentEndpoint.Bind(p)) { + return CreateWebTransportPromise::CreateAndReject( + NS_ERROR_FAILURE, __func__); + } + // IPC now holds a ref to parent + // Send connection to the server via MainThread + NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL); + + return CreateWebTransportPromise::CreateAndResolve( + WebTransportReliabilityMode::Supports_unreliable, __func__); + }) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [p = RefPtr{this}]( + const CreateWebTransportPromise::ResolveOrRejectValue& aValue) { + if (aValue.IsReject()) { + std::function<void(ResolveType)> resolver; + { + MutexAutoLock lock(p->mMutex); + resolver = std::move(p->mResolver); + } + if (resolver) { + resolver( + ResolveType(aValue.RejectValue(), + static_cast<uint8_t>( + WebTransportReliabilityMode::Pending))); + } + } + }); +} + +void WebTransportParent::ActorDestroy(ActorDestroyReason aWhy) { + LOG(("ActorDestroy WebTransportParent %d", aWhy)); +} + +// We may not receive this response if the child side is destroyed without +// `Close` or `Shutdown` being explicitly called. +IPCResult WebTransportParent::RecvClose(const uint32_t& aCode, + const nsACString& aReason) { + LOG(("Close for %p received, code = %u, reason = %s", this, aCode, + PromiseFlatCString(aReason).get())); + { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(!mClosed); + mClosed.Flip(); + } + mWebTransport->CloseSession(aCode, aReason); + Close(); + return IPC_OK(); +} + +class BidiReceiveStream : public nsIWebTransportStreamCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIWEBTRANSPORTSTREAMCALLBACK + + BidiReceiveStream( + WebTransportParent::CreateBidirectionalStreamResolver&& aResolver, + std::function< + void(uint64_t, WebTransportParent::OnResetOrStopSendingCallback&&, + nsIWebTransportBidirectionalStream* aStream)>&& aStreamCallback, + Maybe<int64_t> aSendOrder, nsCOMPtr<nsISerialEventTarget>& aSocketThread) + : mResolver(aResolver), + mStreamCallback(std::move(aStreamCallback)), + mSendOrder(aSendOrder), + mSocketThread(aSocketThread) {} + + private: + virtual ~BidiReceiveStream() = default; + WebTransportParent::CreateBidirectionalStreamResolver mResolver; + std::function<void(uint64_t, + WebTransportParent::OnResetOrStopSendingCallback&&, + nsIWebTransportBidirectionalStream* aStream)> + mStreamCallback; + Maybe<int64_t> mSendOrder; + nsCOMPtr<nsISerialEventTarget> mSocketThread; +}; + +class UniReceiveStream : public nsIWebTransportStreamCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIWEBTRANSPORTSTREAMCALLBACK + + UniReceiveStream( + WebTransportParent::CreateUnidirectionalStreamResolver&& aResolver, + std::function<void(uint64_t, + WebTransportParent::OnResetOrStopSendingCallback&&, + nsIWebTransportSendStream* aStream)>&& aStreamCallback, + Maybe<int64_t> aSendOrder, nsCOMPtr<nsISerialEventTarget>& aSocketThread) + : mResolver(aResolver), + mStreamCallback(std::move(aStreamCallback)), + mSendOrder(aSendOrder), + mSocketThread(aSocketThread) {} + + private: + virtual ~UniReceiveStream() = default; + WebTransportParent::CreateUnidirectionalStreamResolver mResolver; + std::function<void(uint64_t, + WebTransportParent::OnResetOrStopSendingCallback&&, + nsIWebTransportSendStream* aStream)> + mStreamCallback; + Maybe<int64_t> mSendOrder; + nsCOMPtr<nsISerialEventTarget> mSocketThread; +}; + +NS_IMPL_ISUPPORTS(BidiReceiveStream, nsIWebTransportStreamCallback) +NS_IMPL_ISUPPORTS(UniReceiveStream, nsIWebTransportStreamCallback) + +// nsIWebTransportStreamCallback: +NS_IMETHODIMP BidiReceiveStream::OnBidirectionalStreamReady( + nsIWebTransportBidirectionalStream* aStream) { + LOG(("Bidirectional stream ready!")); + MOZ_ASSERT(mSocketThread->IsOnCurrentThread()); + + aStream->SetSendOrder(mSendOrder); + + RefPtr<mozilla::ipc::DataPipeSender> inputsender; + RefPtr<mozilla::ipc::DataPipeReceiver> inputreceiver; + nsresult rv = + NewDataPipe(mozilla::ipc::kDefaultDataPipeCapacity, + getter_AddRefs(inputsender), getter_AddRefs(inputreceiver)); + if (NS_WARN_IF(NS_FAILED(rv))) { + mResolver(rv); + return rv; + } + + uint64_t id; + Unused << aStream->GetStreamId(&id); + nsCOMPtr<nsIAsyncInputStream> inputStream; + aStream->GetInputStream(getter_AddRefs(inputStream)); + MOZ_ASSERT(inputStream); + nsCOMPtr<nsISupports> inputCopyContext; + rv = NS_AsyncCopy(inputStream, inputsender, mSocketThread, + NS_ASYNCCOPY_VIA_WRITESEGMENTS, // can we use READSEGMENTS? + mozilla::ipc::kDefaultDataPipeCapacity, nullptr, nullptr, + true, true, getter_AddRefs(inputCopyContext)); + if (NS_WARN_IF(NS_FAILED(rv))) { + mResolver(rv); + return rv; + } + + RefPtr<mozilla::ipc::DataPipeSender> outputsender; + RefPtr<mozilla::ipc::DataPipeReceiver> outputreceiver; + rv = + NewDataPipe(mozilla::ipc::kDefaultDataPipeCapacity, + getter_AddRefs(outputsender), getter_AddRefs(outputreceiver)); + if (NS_WARN_IF(NS_FAILED(rv))) { + mResolver(rv); + return rv; + } + + nsCOMPtr<nsIAsyncOutputStream> outputStream; + aStream->GetOutputStream(getter_AddRefs(outputStream)); + MOZ_ASSERT(outputStream); + nsCOMPtr<nsISupports> outputCopyContext; + rv = NS_AsyncCopy(outputreceiver, outputStream, mSocketThread, + NS_ASYNCCOPY_VIA_READSEGMENTS, + mozilla::ipc::kDefaultDataPipeCapacity, nullptr, nullptr, + true, true, getter_AddRefs(outputCopyContext)); + if (NS_WARN_IF(NS_FAILED(rv))) { + mResolver(rv); + return rv; + } + + LOG(("Returning BidirectionalStream pipe to content")); + mResolver(BidirectionalStream(id, inputreceiver, outputsender)); + + auto onResetOrStopSending = + [inputCopyContext(inputCopyContext), outputCopyContext(outputCopyContext), + inputsender(inputsender), + outputreceiver(outputreceiver)](nsresult aError) { + LOG(("onResetOrStopSending err=%x", static_cast<uint32_t>(aError))); + NS_CancelAsyncCopy(inputCopyContext, aError); + inputsender->CloseWithStatus(aError); + NS_CancelAsyncCopy(outputCopyContext, aError); + outputreceiver->CloseWithStatus(aError); + }; + + // Store onResetOrStopSending in WebTransportParent::mBidiStreamCallbackMap + // and onResetOrStopSending will be called when a stream receives STOP_SENDING + // or RESET. + mStreamCallback(id, + WebTransportParent::OnResetOrStopSendingCallback( + std::move(onResetOrStopSending)), + aStream); + return NS_OK; +} + +NS_IMETHODIMP UniReceiveStream::OnBidirectionalStreamReady( + nsIWebTransportBidirectionalStream* aStream) { + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +UniReceiveStream::OnUnidirectionalStreamReady( + nsIWebTransportSendStream* aStream) { + LOG(("Unidirectional stream ready!")); + // We should be on the Socket Thread + MOZ_ASSERT(mSocketThread->IsOnCurrentThread()); + + aStream->SetSendOrder(mSendOrder); + + RefPtr<::mozilla::ipc::DataPipeSender> sender; + RefPtr<::mozilla::ipc::DataPipeReceiver> receiver; + nsresult rv = NewDataPipe(mozilla::ipc::kDefaultDataPipeCapacity, + getter_AddRefs(sender), getter_AddRefs(receiver)); + if (NS_WARN_IF(NS_FAILED(rv))) { + mResolver(rv); + return rv; + } + + uint64_t id; + Unused << aStream->GetStreamId(&id); + nsCOMPtr<nsIAsyncOutputStream> outputStream; + aStream->GetOutputStream(getter_AddRefs(outputStream)); + MOZ_ASSERT(outputStream); + nsCOMPtr<nsISupports> copyContext; + rv = NS_AsyncCopy(receiver, outputStream, mSocketThread, + NS_ASYNCCOPY_VIA_READSEGMENTS, + mozilla::ipc::kDefaultDataPipeCapacity, nullptr, nullptr, + true, true, getter_AddRefs(copyContext)); + if (NS_WARN_IF(NS_FAILED(rv))) { + mResolver(rv); + return rv; + } + + LOG(("Returning UnidirectionalStream pipe to content")); + // pass the DataPipeSender to the content process + mResolver(UnidirectionalStream(id, sender)); + + auto onResetOrStopSending = [copyContext(copyContext), + receiver(receiver)](nsresult aError) { + LOG(("onResetOrStopSending err=%x", static_cast<uint32_t>(aError))); + NS_CancelAsyncCopy(copyContext, aError); + receiver->CloseWithStatus(aError); + }; + + // Store onResetOrStopSending in WebTransportParent::mStreamCallbackMap and + // onResetOrStopSending will be called when a stream receives STOP_SENDING. + mStreamCallback(id, + WebTransportParent::OnResetOrStopSendingCallback( + std::move(onResetOrStopSending)), + aStream); + + return NS_OK; +} + +NS_IMETHODIMP +BidiReceiveStream::OnUnidirectionalStreamReady( + nsIWebTransportSendStream* aStream) { + return NS_ERROR_FAILURE; +} + +JS_HAZ_CAN_RUN_SCRIPT NS_IMETHODIMP UniReceiveStream::OnError(uint8_t aError) { + nsresult rv = aError == nsIWebTransport::INVALID_STATE_ERROR + ? NS_ERROR_DOM_INVALID_STATE_ERR + : NS_ERROR_FAILURE; + LOG(("CreateStream OnError: %u", aError)); + mResolver(rv); + return NS_OK; +} + +JS_HAZ_CAN_RUN_SCRIPT NS_IMETHODIMP BidiReceiveStream::OnError(uint8_t aError) { + nsresult rv = aError == nsIWebTransport::INVALID_STATE_ERROR + ? NS_ERROR_DOM_INVALID_STATE_ERR + : NS_ERROR_FAILURE; + LOG(("CreateStream OnError: %u", aError)); + mResolver(rv); + return NS_OK; +} + +IPCResult WebTransportParent::RecvSetSendOrder(uint64_t aStreamId, + Maybe<int64_t> aSendOrder) { + if (aSendOrder) { + LOG(("Set sendOrder=%" PRIi64 " for streamId %" PRIu64, aSendOrder.value(), + aStreamId)); + } else { + LOG(("Set sendOrder=null for streamId %" PRIu64, aStreamId)); + } + if (auto entry = mUniStreamCallbackMap.Lookup(aStreamId)) { + entry->mStream->SetSendOrder(aSendOrder); + } else if (auto entry = mBidiStreamCallbackMap.Lookup(aStreamId)) { + entry->mStream->SetSendOrder(aSendOrder); + } + return IPC_OK(); +} + +IPCResult WebTransportParent::RecvCreateUnidirectionalStream( + Maybe<int64_t> aSendOrder, CreateUnidirectionalStreamResolver&& aResolver) { + LOG(("%s for %p received, useSendOrder=%d, sendOrder=%" PRIi64, __func__, + this, aSendOrder.isSome(), + aSendOrder.isSome() ? aSendOrder.value() : 0)); + + auto streamCb = + [self = RefPtr{this}]( + uint64_t aStreamId, + WebTransportParent::OnResetOrStopSendingCallback&& aCallback, + nsIWebTransportSendStream* aStream) { + self->mUniStreamCallbackMap.InsertOrUpdate( + aStreamId, StreamHash<nsIWebTransportSendStream>{ + std::move(aCallback), aStream}); + }; + RefPtr<UniReceiveStream> callback = new UniReceiveStream( + std::move(aResolver), std::move(streamCb), aSendOrder, mSocketThread); + nsresult rv; + rv = mWebTransport->CreateOutgoingUnidirectionalStream(callback); + if (NS_FAILED(rv)) { + callback->OnError(0); // XXX + } + return IPC_OK(); +} + +IPCResult WebTransportParent::RecvCreateBidirectionalStream( + Maybe<int64_t> aSendOrder, CreateBidirectionalStreamResolver&& aResolver) { + LOG(("%s for %p received, useSendOrder=%d, sendOrder=%" PRIi64, __func__, + this, aSendOrder.isSome(), + aSendOrder.isSome() ? aSendOrder.value() : 0)); + + auto streamCb = + [self = RefPtr{this}]( + uint64_t aStreamId, + WebTransportParent::OnResetOrStopSendingCallback&& aCallback, + nsIWebTransportBidirectionalStream* aStream) { + self->mBidiStreamCallbackMap.InsertOrUpdate( + aStreamId, StreamHash<nsIWebTransportBidirectionalStream>{ + std::move(aCallback), aStream}); + }; + RefPtr<BidiReceiveStream> callback = new BidiReceiveStream( + std::move(aResolver), std::move(streamCb), aSendOrder, mSocketThread); + nsresult rv; + rv = mWebTransport->CreateOutgoingBidirectionalStream(callback); + if (NS_FAILED(rv)) { + callback->OnError(0); // XXX + } + return IPC_OK(); +} + +// We recieve this notification from the WebTransportSessionProxy if session was +// successfully created at the end of +// WebTransportSessionProxy::OnStopRequest +NS_IMETHODIMP +WebTransportParent::OnSessionReady(uint64_t aSessionId) { + MOZ_ASSERT(mOwningEventTarget); + MOZ_ASSERT(!mOwningEventTarget->IsOnCurrentThread()); + + LOG(("Created web transport session, sessionID = %" PRIu64 ", for %p", + aSessionId, this)); + + mSessionReady = true; + + // Retarget to socket thread. After this, WebTransportParent and + // |mWebTransport| should be only accessed on the socket thread. + nsresult rv = mWebTransport->RetargetTo(mSocketThread); + if (NS_FAILED(rv)) { + mOwningEventTarget->Dispatch(NS_NewRunnableFunction( + "WebTransportParent::OnSessionReady Failed", + [self = RefPtr{this}, result = rv] { + MutexAutoLock lock(self->mMutex); + if (!self->mClosed && self->mResolver) { + self->mResolver(ResolveType( + result, static_cast<uint8_t>( + WebTransportReliabilityMode::Supports_unreliable))); + self->mResolver = nullptr; + } + })); + return NS_OK; + } + + mOwningEventTarget->Dispatch(NS_NewRunnableFunction( + "WebTransportParent::OnSessionReady", [self = RefPtr{this}] { + MutexAutoLock lock(self->mMutex); + if (!self->mClosed && self->mResolver) { + self->mResolver(ResolveType( + NS_OK, static_cast<uint8_t>( + WebTransportReliabilityMode::Supports_unreliable))); + self->mResolver = nullptr; + if (self->mExecuteAfterResolverCallback) { + self->mExecuteAfterResolverCallback(); + self->mExecuteAfterResolverCallback = nullptr; + } + } else { + if (self->mClosed) { + LOG(("Session already closed at OnSessionReady %p", self.get())); + } else { + LOG(("No resolver at OnSessionReady %p", self.get())); + } + } + })); + + return NS_OK; +} + +// We receive this notification from the WebTransportSessionProxy if session +// creation was unsuccessful at the end of +// WebTransportSessionProxy::OnStopRequest +NS_IMETHODIMP +WebTransportParent::OnSessionClosed(const bool aCleanly, + const uint32_t aErrorCode, + const nsACString& aReason) { + nsresult rv = NS_OK; + + MOZ_ASSERT(mOwningEventTarget); + MOZ_ASSERT(!mOwningEventTarget->IsOnCurrentThread()); + + // currently we just know if session was closed gracefully or not. + // we need better error propagation from lower-levels of http3 + // webtransport session and it's subsequent error mapping to DOM. + // XXX See Bug 1806834 + if (!mSessionReady) { + // this is an unclean close (we never got to ready) + LOG(("webtransport %p session creation failed code= %u, reason= %s", this, + aErrorCode, PromiseFlatCString(aReason).get())); + // we know we haven't gone Ready yet + rv = NS_ERROR_FAILURE; + mOwningEventTarget->Dispatch(NS_NewRunnableFunction( + "WebTransportParent::OnSessionClosed", + [self = RefPtr{this}, result = rv] { + MutexAutoLock lock(self->mMutex); + if (!self->mClosed && self->mResolver) { + self->mResolver(ResolveType( + result, static_cast<uint8_t>( + WebTransportReliabilityMode::Supports_unreliable))); + self->mResolver = nullptr; + } + })); + } else { + { + MutexAutoLock lock(mMutex); + if (mResolver) { + LOG(("[%p] NotifyRemoteClosed to be called later", this)); + // NotifyRemoteClosed needs to wait until mResolver is invoked. + mExecuteAfterResolverCallback = [self = RefPtr{this}, aCleanly, + aErrorCode, + reason = nsCString{aReason}]() { + self->NotifyRemoteClosed(aCleanly, aErrorCode, reason); + }; + return NS_OK; + } + } + // https://w3c.github.io/webtransport/#web-transport-termination + // Step 1: Let cleanly be a boolean representing whether the HTTP/3 + // stream associated with the CONNECT request that initiated + // transport.[[Session]] is in the "Data Recvd" state. [QUIC] + // XXX not calculated yet + NotifyRemoteClosed(aCleanly, aErrorCode, aReason); + } + + return NS_OK; +} + +NS_IMETHODIMP WebTransportParent::OnStopSending(uint64_t aStreamId, + nsresult aError) { + MOZ_ASSERT(mSocketThread->IsOnCurrentThread()); + LOG(("WebTransportParent::OnStopSending %p stream id=%" PRIx64, this, + aStreamId)); + if (auto entry = mUniStreamCallbackMap.Lookup(aStreamId)) { + entry->mCallback.OnResetOrStopSending(aError); + mUniStreamCallbackMap.Remove(aStreamId); + } else if (auto entry = mBidiStreamCallbackMap.Lookup(aStreamId)) { + entry->mCallback.OnResetOrStopSending(aError); + mBidiStreamCallbackMap.Remove(aStreamId); + } + if (CanSend()) { + Unused << SendOnStreamResetOrStopSending(aStreamId, + StopSendingError(aError)); + } + return NS_OK; +} + +NS_IMETHODIMP WebTransportParent::OnResetReceived(uint64_t aStreamId, + nsresult aError) { + MOZ_ASSERT(mSocketThread->IsOnCurrentThread()); + LOG(("WebTransportParent::OnResetReceived %p stream id=%" PRIx64, this, + aStreamId)); + if (auto entry = mUniStreamCallbackMap.Lookup(aStreamId)) { + entry->mCallback.OnResetOrStopSending(aError); + mUniStreamCallbackMap.Remove(aStreamId); + } else if (auto entry = mBidiStreamCallbackMap.Lookup(aStreamId)) { + entry->mCallback.OnResetOrStopSending(aError); + mBidiStreamCallbackMap.Remove(aStreamId); + } + if (CanSend()) { + Unused << SendOnStreamResetOrStopSending(aStreamId, ResetError(aError)); + } + return NS_OK; +} + +void WebTransportParent::NotifyRemoteClosed(bool aCleanly, uint32_t aErrorCode, + const nsACString& aReason) { + LOG(("webtransport %p session remote closed cleanly=%d code= %u, reason= %s", + this, aCleanly, aErrorCode, PromiseFlatCString(aReason).get())); + mSocketThread->Dispatch(NS_NewRunnableFunction( + __func__, [self = RefPtr{this}, aErrorCode, reason = nsCString{aReason}, + aCleanly]() { + // Tell the content side we were closed by the server + Unused << self->SendRemoteClosed(aCleanly, aErrorCode, reason); + // Let the other end shut down the IPC channel after RecvClose() + })); +} + +// This method is currently not used by WebTransportSessionProxy to inform of +// any session related events. All notification is recieved via +// WebTransportSessionProxy::OnSessionReady and +// WebTransportSessionProxy::OnSessionClosed methods +NS_IMETHODIMP +WebTransportParent::OnSessionReadyInternal( + mozilla::net::Http3WebTransportSession* aSession) { + Unused << aSession; + return NS_OK; +} + +NS_IMETHODIMP +WebTransportParent::OnIncomingStreamAvailableInternal( + mozilla::net::Http3WebTransportStream* aStream) { + // XXX implement once DOM WebAPI supports creation of streams + Unused << aStream; + return NS_OK; +} + +NS_IMETHODIMP +WebTransportParent::OnIncomingUnidirectionalStreamAvailable( + nsIWebTransportReceiveStream* aStream) { + // Note: we need to hold a reference to the stream if we want to get stats, + // etc + LOG(("%p IncomingUnidirectonalStream available", this)); + + // We must be on the Socket Thread + MOZ_ASSERT(mSocketThread->IsOnCurrentThread()); + + RefPtr<DataPipeSender> sender; + RefPtr<DataPipeReceiver> receiver; + nsresult rv = NewDataPipe(mozilla::ipc::kDefaultDataPipeCapacity, + getter_AddRefs(sender), getter_AddRefs(receiver)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIAsyncInputStream> inputStream; + aStream->GetInputStream(getter_AddRefs(inputStream)); + MOZ_ASSERT(inputStream); + rv = NS_AsyncCopy(inputStream, sender, mSocketThread, + NS_ASYNCCOPY_VIA_WRITESEGMENTS, + mozilla::ipc::kDefaultDataPipeCapacity); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + LOG(("%p Sending UnidirectionalStream pipe to content", this)); + // pass the DataPipeReceiver to the content process + uint64_t id; + Unused << aStream->GetStreamId(&id); + Unused << SendIncomingUnidirectionalStream(id, receiver); + + return NS_OK; +} + +NS_IMETHODIMP +WebTransportParent::OnIncomingBidirectionalStreamAvailable( + nsIWebTransportBidirectionalStream* aStream) { + // Note: we need to hold a reference to the stream if we want to get stats, + // etc + LOG(("%p IncomingBidirectonalStream available", this)); + + // We must be on the Socket Thread + MOZ_ASSERT(mSocketThread->IsOnCurrentThread()); + + RefPtr<DataPipeSender> inputSender; + RefPtr<DataPipeReceiver> inputReceiver; + nsresult rv = + NewDataPipe(mozilla::ipc::kDefaultDataPipeCapacity, + getter_AddRefs(inputSender), getter_AddRefs(inputReceiver)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIAsyncInputStream> inputStream; + aStream->GetInputStream(getter_AddRefs(inputStream)); + MOZ_ASSERT(inputStream); + rv = NS_AsyncCopy(inputStream, inputSender, mSocketThread, + NS_ASYNCCOPY_VIA_WRITESEGMENTS, + mozilla::ipc::kDefaultDataPipeCapacity); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + RefPtr<DataPipeSender> outputSender; + RefPtr<DataPipeReceiver> outputReceiver; + rv = + NewDataPipe(mozilla::ipc::kDefaultDataPipeCapacity, + getter_AddRefs(outputSender), getter_AddRefs(outputReceiver)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIAsyncOutputStream> outputStream; + aStream->GetOutputStream(getter_AddRefs(outputStream)); + MOZ_ASSERT(outputStream); + rv = NS_AsyncCopy(outputReceiver, outputStream, mSocketThread, + NS_ASYNCCOPY_VIA_READSEGMENTS, + mozilla::ipc::kDefaultDataPipeCapacity); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + LOG(("%p Sending BidirectionalStream pipe to content", this)); + // pass the DataPipeSender to the content process + uint64_t id; + Unused << aStream->GetStreamId(&id); + Unused << SendIncomingBidirectionalStream(id, inputReceiver, outputSender); + return NS_OK; +} + +::mozilla::ipc::IPCResult WebTransportParent::RecvGetMaxDatagramSize( + GetMaxDatagramSizeResolver&& aResolver) { + LOG(("WebTransportParent RecvGetMaxDatagramSize")); + MOZ_ASSERT(mSocketThread->IsOnCurrentThread()); + MOZ_ASSERT(mWebTransport); + MOZ_ASSERT(!mMaxDatagramSizeResolver); + + mMaxDatagramSizeResolver = std::move(aResolver); + // maximum datagram size for the session is returned from network stack + // synchronously via WebTransportSessionEventListener::OnMaxDatagramSize + // interface + mWebTransport->GetMaxDatagramSize(); + return IPC_OK(); +} + +// The promise sent in this request will be resolved +// in OnOutgoingDatagramOutCome which is called synchronously from +// WebTransportSessionProxy::SendDatagram +::mozilla::ipc::IPCResult WebTransportParent::RecvOutgoingDatagram( + nsTArray<uint8_t>&& aData, const TimeStamp& aExpirationTime, + OutgoingDatagramResolver&& aResolver) { + LOG(("WebTransportParent sending datagram")); + MOZ_ASSERT(mSocketThread->IsOnCurrentThread()); + MOZ_ASSERT(mWebTransport); + + Unused << aExpirationTime; + + MOZ_ASSERT(!mOutgoingDatagramResolver); + mOutgoingDatagramResolver = std::move(aResolver); + // XXX we need to forward the timestamps to the necko stack + // timestamp should be checked in the necko for expiry + // See Bug 1818300 + // currently this calls OnOutgoingDatagramOutCome synchronously + // Neqo won't call us back if the id == 0! + // We don't use the ID for anything currently; rework of the stack + // to implement proper HighWatermark buffering will require + // changes here anyways. + static uint64_t sDatagramId = 1; + LOG_VERBOSE(("Sending datagram %" PRIu64 ", length %zu", sDatagramId, + aData.Length())); + Unused << mWebTransport->SendDatagram(aData, sDatagramId++); + + return IPC_OK(); +} + +NS_IMETHODIMP WebTransportParent::OnDatagramReceived( + const nsTArray<uint8_t>& aData) { + // We must be on the Socket Thread + MOZ_ASSERT(mSocketThread->IsOnCurrentThread()); + + LOG(("WebTransportParent received datagram length = %zu", aData.Length())); + + TimeStamp ts = TimeStamp::Now(); + Unused << SendIncomingDatagram(aData, ts); + + return NS_OK; +} + +NS_IMETHODIMP WebTransportParent::OnDatagramReceivedInternal( + nsTArray<uint8_t>&& aData) { + // this method is used only for internal notificaiton within necko + // we dont expect to receive any notification with on this interface + return NS_OK; +} + +NS_IMETHODIMP +WebTransportParent::OnOutgoingDatagramOutCome( + uint64_t aId, WebTransportSessionEventListener::DatagramOutcome aOutCome) { + MOZ_ASSERT(mSocketThread->IsOnCurrentThread()); + // XXX - do we need better error mappings for failures? + nsresult result = NS_ERROR_FAILURE; + Unused << result; + Unused << aId; + + if (aOutCome == WebTransportSessionEventListener::DatagramOutcome::SENT) { + result = NS_OK; + LOG(("Sent datagram id= %" PRIu64, aId)); + } else { + LOG(("Didn't send datagram id= %" PRIu64, aId)); + } + + // This assumes the stack is calling us back synchronously! + MOZ_ASSERT(mOutgoingDatagramResolver); + mOutgoingDatagramResolver(result); + mOutgoingDatagramResolver = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP WebTransportParent::OnMaxDatagramSize(uint64_t aSize) { + LOG(("Max datagram size is %" PRIu64, aSize)); + MOZ_ASSERT(mSocketThread->IsOnCurrentThread()); + MOZ_ASSERT(mMaxDatagramSizeResolver); + mMaxDatagramSizeResolver(aSize); + mMaxDatagramSizeResolver = nullptr; + return NS_OK; +} +} // namespace mozilla::dom diff --git a/dom/webtransport/parent/WebTransportParent.h b/dom/webtransport/parent/WebTransportParent.h new file mode 100644 index 0000000000..8b1b1a6867 --- /dev/null +++ b/dom/webtransport/parent/WebTransportParent.h @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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 DOM_WEBTRANSPORT_PARENT_WEBTRANSPORTPARENT_H_ +#define DOM_WEBTRANSPORT_PARENT_WEBTRANSPORTPARENT_H_ + +#include "ErrorList.h" +#include "mozilla/dom/ClientIPCTypes.h" +#include "mozilla/dom/FlippedOnce.h" +#include "mozilla/dom/PWebTransportParent.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "nsISupports.h" +#include "nsIPrincipal.h" +#include "nsIWebTransport.h" +#include "nsIWebTransportStream.h" +#include "nsTHashMap.h" + +namespace mozilla::dom { + +enum class WebTransportReliabilityMode : uint8_t; + +class WebTransportParent : public PWebTransportParent, + public WebTransportSessionEventListener { + using IPCResult = mozilla::ipc::IPCResult; + + public: + WebTransportParent() = default; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_WEBTRANSPORTSESSIONEVENTLISTENER + + void Create(const nsAString& aURL, nsIPrincipal* aPrincipal, + const mozilla::Maybe<IPCClientInfo>& aClientInfo, + const bool& aDedicated, const bool& aRequireUnreliable, + const uint32_t& aCongestionControl, + nsTArray<WebTransportHash>&& aServerCertHashes, + Endpoint<PWebTransportParent>&& aParentEndpoint, + std::function<void(std::tuple<const nsresult&, const uint8_t&>)>&& + aResolver); + + IPCResult RecvClose(const uint32_t& aCode, const nsACString& aReason); + + IPCResult RecvSetSendOrder(uint64_t aStreamId, Maybe<int64_t> aSendOrder); + + IPCResult RecvCreateUnidirectionalStream( + Maybe<int64_t> aSendOrder, + CreateUnidirectionalStreamResolver&& aResolver); + IPCResult RecvCreateBidirectionalStream( + Maybe<int64_t> aSendOrder, CreateBidirectionalStreamResolver&& aResolver); + + ::mozilla::ipc::IPCResult RecvOutgoingDatagram( + nsTArray<uint8_t>&& aData, const TimeStamp& aExpirationTime, + OutgoingDatagramResolver&& aResolver); + + ::mozilla::ipc::IPCResult RecvGetMaxDatagramSize( + GetMaxDatagramSizeResolver&& aResolver); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + class OnResetOrStopSendingCallback final { + public: + explicit OnResetOrStopSendingCallback( + std::function<void(nsresult)>&& aCallback) + : mCallback(std::move(aCallback)) {} + ~OnResetOrStopSendingCallback() = default; + + void OnResetOrStopSending(nsresult aError) { mCallback(aError); } + + private: + std::function<void(nsresult)> mCallback; + }; + + protected: + virtual ~WebTransportParent(); + + private: + void NotifyRemoteClosed(bool aCleanly, uint32_t aErrorCode, + const nsACString& aReason); + + using ResolveType = std::tuple<const nsresult&, const uint8_t&>; + nsCOMPtr<nsISerialEventTarget> mSocketThread; + Atomic<bool> mSessionReady{false}; + + mozilla::Mutex mMutex{"WebTransportParent::mMutex"}; + std::function<void(ResolveType)> mResolver MOZ_GUARDED_BY(mMutex); + // This is needed because mResolver is resolved on the background thread and + // OnSessionClosed is called on the socket thread. + std::function<void()> mExecuteAfterResolverCallback MOZ_GUARDED_BY(mMutex); + OutgoingDatagramResolver mOutgoingDatagramResolver; + GetMaxDatagramSizeResolver mMaxDatagramSizeResolver; + FlippedOnce<false> mClosed MOZ_GUARDED_BY(mMutex); + + nsCOMPtr<nsIWebTransport> mWebTransport; + nsCOMPtr<nsIEventTarget> mOwningEventTarget; + + // What we need to be able to lookup by streamId + template <typename T> + struct StreamHash { + OnResetOrStopSendingCallback mCallback; + nsCOMPtr<T> mStream; + }; + nsTHashMap<nsUint64HashKey, StreamHash<nsIWebTransportBidirectionalStream>> + mBidiStreamCallbackMap; + nsTHashMap<nsUint64HashKey, StreamHash<nsIWebTransportSendStream>> + mUniStreamCallbackMap; +}; + +} // namespace mozilla::dom + +#endif // DOM_WEBTRANSPORT_PARENT_WEBTRANSPORTPARENT_H_ diff --git a/dom/webtransport/parent/moz.build b/dom/webtransport/parent/moz.build new file mode 100644 index 0000000000..f3486cc8c7 --- /dev/null +++ b/dom/webtransport/parent/moz.build @@ -0,0 +1,22 @@ +# -*- 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/. + +EXPORTS.mozilla.dom += [ + "WebTransportParent.h", +] + +UNIFIED_SOURCES += [ + "WebTransportParent.cpp", +] + +FINAL_LIBRARY = "xul" + +include("/ipc/chromium/chromium-config.mozbuild") + +LOCAL_INCLUDES += [ + "/netwerk/base", + "/netwerk/protocol/http", +] diff --git a/dom/webtransport/shared/PWebTransport.ipdl b/dom/webtransport/shared/PWebTransport.ipdl new file mode 100644 index 0000000000..6256322425 --- /dev/null +++ b/dom/webtransport/shared/PWebTransport.ipdl @@ -0,0 +1,102 @@ +/* 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 PBackgroundSharedTypes; + +[RefCounted] using class mozilla::ipc::DataPipeReceiver from "mozilla/ipc/DataPipe.h"; +[RefCounted] using class mozilla::ipc::DataPipeSender from "mozilla/ipc/DataPipe.h"; +using mozilla::TimeStamp from "mozilla/TimeStamp.h"; + +namespace mozilla { +namespace dom { + +struct UnidirectionalStream { + uint64_t streamId; + nullable DataPipeSender outStream; +}; + +union UnidirectionalStreamResponse { + nsresult; + UnidirectionalStream; +}; + +struct BidirectionalStream { + uint64_t streamId; + nullable DataPipeReceiver inStream; + nullable DataPipeSender outStream; +}; + +union BidirectionalStreamResponse { + nsresult; + BidirectionalStream; +}; + +struct ResetError { + nsresult error; +}; + +struct StopSendingError { + nsresult error; +}; + +union StreamResetOrStopSendingError { + ResetError; + StopSendingError; +}; + +[ChildProc=anydom] +async protocol PWebTransport +{ + parent: + /** + * TODO: documentation + */ + async Close(uint32_t code, nsCString reason); + async CreateUnidirectionalStream(int64_t? sendOrder) + returns(UnidirectionalStreamResponse response); + async CreateBidirectionalStream(int64_t? sendOrder) + returns(BidirectionalStreamResponse response); + + /** + * IPC for sending webtransport datagrams + * @param expirationTime time at which the datagram expires + * @param data represents the datagram to be transferred + */ + async OutgoingDatagram(uint8_t[] data, TimeStamp expirationTime) + returns(nsresult response); + + /** + * Get the maximum supported datagram size from necko stack + */ + async GetMaxDatagramSize() + returns(uint64_t maxDatagramSize); + + /** + * Set the sendOrder for an existing stream + */ + async SetSendOrder(uint64_t streamId, int64_t? sendOrder); + + child: + + async IncomingUnidirectionalStream(uint64_t streamId, nullable DataPipeReceiver receive); + async IncomingBidirectionalStream(uint64_t streamId, nullable DataPipeReceiver receive, nullable DataPipeSender send); + + /** + * IPC for receiving webtransport datagrams + * @param receivedTime is the time at which the parent received the datagram + * @param data is the datagram received + */ + async IncomingDatagram(uint8_t[] data, TimeStamp receivedTime); + + async RemoteClosed(bool cleanly, uint32_t code, nsCString reason); + + async OnStreamResetOrStopSending(uint64_t streamId, + StreamResetOrStopSendingError error); + + async CloseAll() + returns(nsresult rv); +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/webtransport/shared/WebTransportLog.cpp b/dom/webtransport/shared/WebTransportLog.cpp new file mode 100644 index 0000000000..0193921fec --- /dev/null +++ b/dom/webtransport/shared/WebTransportLog.cpp @@ -0,0 +1,13 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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/. */ + +#include "WebTransportLog.h" + +namespace mozilla { + +LazyLogModule gWebTransportLog("WebTransport"); + +} diff --git a/dom/webtransport/shared/WebTransportLog.h b/dom/webtransport/shared/WebTransportLog.h new file mode 100644 index 0000000000..5bd6da29bd --- /dev/null +++ b/dom/webtransport/shared/WebTransportLog.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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 DOM_WEBTRANSPORT_SHARED_WEBTRANSPORTLOG_H_ +#define DOM_WEBTRANSPORT_SHARED_WEBTRANSPORTLOG_H_ + +#include "mozilla/Logging.h" + +namespace mozilla { +extern LazyLogModule gWebTransportLog; +} + +#define LOG(args) \ + MOZ_LOG(mozilla::gWebTransportLog, mozilla::LogLevel::Debug, args) + +#define LOG_VERBOSE(args) \ + MOZ_LOG(mozilla::gWebTransportLog, mozilla::LogLevel::Verbose, args) + +#define LOG_ENABLED() \ + MOZ_LOG_TEST(mozilla::gWebTransportLog, mozilla::LogLevel::Debug) + +#endif // DOM_WEBTRANSPORT_SHARED_WEBTRANSPORTLOG_H diff --git a/dom/webtransport/shared/moz.build b/dom/webtransport/shared/moz.build new file mode 100644 index 0000000000..2992b7dbeb --- /dev/null +++ b/dom/webtransport/shared/moz.build @@ -0,0 +1,21 @@ +# -*- 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/. + +EXPORTS.mozilla.dom += [ + "WebTransportLog.h", +] + +UNIFIED_SOURCES += [ + "WebTransportLog.cpp", +] + +FINAL_LIBRARY = "xul" + +IPDL_SOURCES += [ + "PWebTransport.ipdl", +] + +include("/ipc/chromium/chromium-config.mozbuild") diff --git a/dom/webtransport/test/moz.build b/dom/webtransport/test/moz.build new file mode 100644 index 0000000000..ddf7e1f9a9 --- /dev/null +++ b/dom/webtransport/test/moz.build @@ -0,0 +1,9 @@ +# -*- 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/. + +TEST_DIRS += [ + "xpcshell", +] diff --git a/dom/webtransport/test/xpcshell/moz.build b/dom/webtransport/test/xpcshell/moz.build new file mode 100644 index 0000000000..0eb0836320 --- /dev/null +++ b/dom/webtransport/test/xpcshell/moz.build @@ -0,0 +1,9 @@ +# -*- 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/. + +XPCSHELL_TESTS_MANIFESTS += [ + "xpcshell.toml", +] diff --git a/dom/webtransport/test/xpcshell/test_close.js b/dom/webtransport/test/xpcshell/test_close.js new file mode 100644 index 0000000000..5e3e9c5941 --- /dev/null +++ b/dom/webtransport/test/xpcshell/test_close.js @@ -0,0 +1,77 @@ +// +// Simple WebTransport test +// +// keep eslint happy until it knows about WebTransport +/* global WebTransport:false */ + +"use strict"; + +var h3Port; +var host; + +registerCleanupFunction(async () => { + Services.prefs.clearUserPref("network.dns.localDomains"); +}); + +var { NetUtil } = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs" +); + +function readFile(file) { + let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + fstream.init(file, -1, 0, 0); + let data = NetUtil.readInputStreamToString(fstream, fstream.available()); + fstream.close(); + return data; +} + +function addCertFromFile(certdb, filename, trustString) { + let certFile = do_get_file(filename, false); + let pem = readFile(certFile) + .replace(/-----BEGIN CERTIFICATE-----/, "") + .replace(/-----END CERTIFICATE-----/, "") + .replace(/[\r\n]/g, ""); + certdb.addCertFromBase64(pem, trustString); +} + +add_task(async function setup() { + Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com"); + + h3Port = Services.env.get("MOZHTTP3_PORT"); + Assert.notEqual(h3Port, null); + Assert.notEqual(h3Port, ""); + host = "foo.example.com:" + h3Port; + do_get_profile(); + + let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( + Ci.nsIX509CertDB + ); + // `../unit/` so that unit_ipc tests can use as well + addCertFromFile( + certdb, + "../../../../netwerk/test/unit/http2-ca.pem", + "CTu,u,u" + ); +}); + +add_task(async function test_webtransport_create() { + Services.prefs.setBoolPref("network.webtransport.enabled", true); + + const wt = new WebTransport("https://" + host + "/success"); + await wt.ready; + dump("**** ready\n"); + + wt.close(); +}); + +// bug 1840626 - cancel and then close +add_task(async function test_wt_stream_create_bidi_cancel_close() { + let wt = new WebTransport("https://" + host + "/success"); + await wt.ready; + + await wt.createBidirectionalStream(); + await wt.incomingBidirectionalStreams.cancel(undefined); + wt.close(); +}); diff --git a/dom/webtransport/test/xpcshell/test_simple_conn.js b/dom/webtransport/test/xpcshell/test_simple_conn.js new file mode 100644 index 0000000000..aa4a4131e1 --- /dev/null +++ b/dom/webtransport/test/xpcshell/test_simple_conn.js @@ -0,0 +1,134 @@ +/* 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/. */ + +// Some basic WebTransport tests for: +// * session rejection and redirection +// * session and stream creation +// * reading from incoming streams (uni) +// +// keep eslint happy until it knows about WebTransport +/* global WebTransport:false */ + +"use strict"; + +var h3Port; +var host; + +/* eslint no-unused-vars: 0 */ +const dns = Services.dns; + +registerCleanupFunction(async () => { + Services.prefs.clearUserPref("network.dns.localDomains"); + Services.prefs.clearUserPref("network.webtransport.enabled"); + Services.prefs.clearUserPref("network.webtransport.datagrams.enabled"); + Services.prefs.clearUserPref("network.webtransport.redirect.enabled"); +}); + +var { NetUtil } = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs" +); + +function readFile(file) { + let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + fstream.init(file, -1, 0, 0); + let data = NetUtil.readInputStreamToString(fstream, fstream.available()); + fstream.close(); + return data; +} + +function addCertFromFile(certdb, filename, trustString) { + let certFile = do_get_file(filename, false); + let pem = readFile(certFile) + .replace(/-----BEGIN CERTIFICATE-----/, "") + .replace(/-----END CERTIFICATE-----/, "") + .replace(/[\r\n]/g, ""); + certdb.addCertFromBase64(pem, trustString); +} + +add_setup(async function setup() { + Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com"); + Services.prefs.setBoolPref("network.webtransport.enabled", true); + Services.prefs.setBoolPref("network.webtransport.datagrams.enabled", true); + Services.prefs.setBoolPref("network.webtransport.redirect.enabled", true); + + h3Port = Services.env.get("MOZHTTP3_PORT"); + Assert.notEqual(h3Port, null); + Assert.notEqual(h3Port, ""); + host = "foo.example.com:" + h3Port; + do_get_profile(); + + let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( + Ci.nsIX509CertDB + ); + // `../unit/` so that unit_ipc tests can use as well + addCertFromFile( + certdb, + "../../../../netwerk/test/unit/http2-ca.pem", + "CTu,u,u" + ); +}); + +add_task(async function test_webtransport_create() { + const wt = new WebTransport("https://" + host + "/success"); + await wt.ready; + wt.close(); +}); + +add_task(async function test_redirect_wt() { + let wt = new WebTransport("https://" + host + "/redirect"); + const e1 = await wt.ready.catch(e => e); + const e2 = await wt.closed.catch(e => e); + + Assert.equal(e1, "WebTransportError: WebTransport connection rejected"); + Assert.equal(e2, "WebTransportError: WebTransport connection rejected"); +}); + +add_task(async function test_reject_wt() { + let wt = new WebTransport("https://" + host + "/reject"); + const e1 = await wt.ready.catch(e => e); + const e2 = await wt.closed.catch(e => e); + Assert.equal(e1, "WebTransportError: WebTransport connection rejected"); + Assert.equal(e2, "WebTransportError: WebTransport connection rejected"); +}); + +add_task(async function test_immediate_server_close() { + let wt = new WebTransport("https://" + host + "/closeafter0ms"); + await wt.ready; + await wt.closed; + Assert.ok(true); +}); + +add_task(async function test_delayed_server_close() { + let wt = new WebTransport("https://" + host + "/closeafter100ms"); + await wt.ready; + await wt.closed; + Assert.ok(true); +}); + +add_task(async function test_wt_stream_create_bidi() { + let wt = new WebTransport("https://" + host + "/success"); + await wt.ready; + + let bds = await wt.createBidirectionalStream(); + await bds.writable.close(); + await bds.readable.cancel(); + Assert.notEqual(bds, null); + wt.close(); +}); + +add_task(async function test_wt_stream_create_uni() { + let wt = new WebTransport("https://" + host + "/success"); + await wt.ready; + + let uds = await wt.createUnidirectionalStream(); + Assert.notEqual(uds, null); + await uds.close(); + wt.close(); +}); + +// TODO: datagram test +// TODO: getStats tests +// TODO: fix the crash discussed in bug 1822154 diff --git a/dom/webtransport/test/xpcshell/test_simple_stream.js b/dom/webtransport/test/xpcshell/test_simple_stream.js new file mode 100644 index 0000000000..49fad973a4 --- /dev/null +++ b/dom/webtransport/test/xpcshell/test_simple_stream.js @@ -0,0 +1,170 @@ +/* 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/. */ + +// keep eslint happy until it knows about WebTransport +/* global WebTransport:false */ +/* global TextDecoderStream:false */ + +// Using multiple files to reduce racing +// This file tests reading/writing to incoming/outgoing streams (uni & bidi) +// +"use strict"; + +var h3Port; +var host; + +registerCleanupFunction(async () => { + Services.prefs.clearUserPref("network.dns.localDomains"); + Services.prefs.clearUserPref("network.webtransport.enabled"); + Services.prefs.clearUserPref("network.webtransport.datagrams.enabled"); + Services.prefs.clearUserPref("network.webtransport.redirect.enabled"); +}); + +var { NetUtil } = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs" +); + +function readFile(file) { + let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + fstream.init(file, -1, 0, 0); + let data = NetUtil.readInputStreamToString(fstream, fstream.available()); + fstream.close(); + return data; +} + +function addCertFromFile(certdb, filename, trustString) { + let certFile = do_get_file(filename, false); + let pem = readFile(certFile) + .replace(/-----BEGIN CERTIFICATE-----/, "") + .replace(/-----END CERTIFICATE-----/, "") + .replace(/[\r\n]/g, ""); + certdb.addCertFromBase64(pem, trustString); +} + +add_setup(async function setup() { + Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com"); + Services.prefs.setBoolPref("network.webtransport.enabled", true); + Services.prefs.setBoolPref("network.webtransport.datagrams.enabled", true); + Services.prefs.setBoolPref("network.webtransport.redirect.enabled", true); + + h3Port = Services.env.get("MOZHTTP3_PORT"); + Assert.notEqual(h3Port, null); + Assert.notEqual(h3Port, ""); + host = "foo.example.com:" + h3Port; + do_get_profile(); + + let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( + Ci.nsIX509CertDB + ); + // `../unit/` so that unit_ipc tests can use as well + addCertFromFile( + certdb, + "../../../../netwerk/test/unit/http2-ca.pem", + "CTu,u,u" + ); +}); + +// Read all chunks from |readable_stream|, decode chunks to a utf-8 string, then +// return the string. (borrowed from wpt tests) +async function read_stream_as_string(readable_stream) { + const decoder = new TextDecoderStream(); + const decode_stream = readable_stream.pipeThrough(decoder); + const reader = decode_stream.getReader(); + + let chunks = ""; + while (true) { + const { value: chunk, done } = await reader.read(); + if (done) { + break; + } + chunks += chunk; + } + reader.releaseLock(); + return chunks; +} + +add_task(async function test_wt_incoming_unidi_stream() { + // trigger stream creation server side and default echo + let wt = new WebTransport( + "https://" + host + "/create_unidi_stream_and_hello" + ); + await wt.ready; + + const streams = await wt.incomingUnidirectionalStreams; + const stream_reader = streams.getReader(); + const { value: recv_stream } = await stream_reader.read(); + let str = await read_stream_as_string(recv_stream); + stream_reader.releaseLock(); + Assert.equal(str, "qwerty"); + + wt.close(); +}); + +add_task(async function test_wt_incoming_and_outgoing_unidi_stream() { + // create the client's incoming stream from the server side + // we need it to listen to the echo back + let wt = new WebTransport("https://" + host + "/create_unidi_stream"); + await wt.ready; + + // send hello to server + let expected = "uni_hello"; + let writableStream = await wt.createUnidirectionalStream(); // only triggers NewStream OnWrite + let wsDefaultWriter = writableStream.getWriter(); + await wsDefaultWriter.ready; + let data = new TextEncoder().encode(expected); + await wsDefaultWriter.write(data); // triggers Http3ServerEvent::Data + await wsDefaultWriter.close(); + wsDefaultWriter.releaseLock(); + + // read the echo + const streams = await wt.incomingUnidirectionalStreams; + const stream_reader = streams.getReader(); + const { value: recv_stream } = await stream_reader.read(); + let str = await read_stream_as_string(recv_stream); + Assert.equal(str, expected); + stream_reader.releaseLock(); + await recv_stream.closed; + + wt.close(); +}); + +add_task(async function test_wt_outgoing_bidi_stream() { + let wt = new WebTransport("https://" + host + "/success"); + await wt.ready; + + // write to server + let wtbds = await wt.createBidirectionalStream(); + let writableStream = wtbds.writable; + let wsDefaultWriter = writableStream.getWriter(); + await wsDefaultWriter.ready; + let expected = "xyzhello"; + let data = new TextEncoder().encode(expected); + await wsDefaultWriter.write(data); + await wsDefaultWriter.close(); + wsDefaultWriter.releaseLock(); + + // string goes through server and is echoed back here + const str = await read_stream_as_string(wtbds.readable); + Assert.equal(str, expected); + + wt.close(); +}); + +add_task(async function test_wt_incoming_bidi_stream() { + let wt = new WebTransport( + "https://" + host + "/create_bidi_stream_and_hello" + ); + // await wt.ready; // causes occasional hang on release --verify + + const stream_reader = wt.incomingBidirectionalStreams.getReader(); + const { value: bidi_stream } = await stream_reader.read(); + stream_reader.releaseLock(); + + const str = await read_stream_as_string(bidi_stream.readable); + Assert.equal(str, "asdfg"); + + wt.close(); +}); diff --git a/dom/webtransport/test/xpcshell/xpcshell.toml b/dom/webtransport/test/xpcshell/xpcshell.toml new file mode 100644 index 0000000000..0695d452af --- /dev/null +++ b/dom/webtransport/test/xpcshell/xpcshell.toml @@ -0,0 +1,29 @@ +# 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/. + +[DEFAULT] +# XXX figure out why android doesn't get MOZHTTP3_PORT +skip-if = [ + "os == 'android'", + "socketprocess_networking", + "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1807925 +] + +["test_close.js"] + +["test_simple_conn.js"] +skip-if = [ + "!fission && debug", + "verify && tsan", + "verify && debug", +] +run-sequentially = "http3server" + +["test_simple_stream.js"] +skip-if = [ + "!fission && debug", + "verify && tsan", + "verify && debug", +] +run-sequentially = "http3server" |