diff options
Diffstat (limited to 'dom/webtransport/api/WebTransport.cpp')
-rw-r--r-- | dom/webtransport/api/WebTransport.cpp | 306 |
1 files changed, 306 insertions, 0 deletions
diff --git a/dom/webtransport/api/WebTransport.cpp b/dom/webtransport/api/WebTransport.cpp new file mode 100644 index 0000000000..5915827f62 --- /dev/null +++ b/dom/webtransport/api/WebTransport.cpp @@ -0,0 +1,306 @@ +/* -*- 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 "nsUTF8Utils.h" +#include "nsIURL.h" +#include "mozilla/Assertions.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/PWebTransport.h" +#include "mozilla/dom/ReadableStream.h" +#include "mozilla/dom/WebTransportDatagramDuplexStream.h" +#include "mozilla/dom/WebTransportLog.h" +#include "mozilla/dom/WritableStream.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/PBackgroundChild.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebTransport, mGlobal, + mIncomingUnidirectionalStreams, + mIncomingBidirectionalStreams, + mSendStreams, mReceiveStreams, mDatagrams, + mReady, mClosed) + +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)); +} + +// 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())); + + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<WebTransport> result = new WebTransport(global); + if (!result->Init(aGlobal, aURL, aOptions, aError)) { + return nullptr; + } + + return result.forget(); +} + +bool WebTransport::Init(const GlobalObject& aGlobal, const nsAString& aURL, + const WebTransportOptions& aOptions, + ErrorResult& aError) { + // Initiate connection with parent + using mozilla::ipc::BackgroundChild; + using mozilla::ipc::Endpoint; + using mozilla::ipc::PBackgroundChild; + + mReady = Promise::Create(mGlobal, aError); + if (NS_WARN_IF(aError.Failed())) { + return false; + } + + mClosed = Promise::Create(mGlobal, aError); + if (NS_WARN_IF(aError.Failed())) { + return false; + } + + QueuingStrategy strategy; + Optional<JS::Handle<JSObject*>> underlying; + mIncomingUnidirectionalStreams = + ReadableStream::Constructor(aGlobal, underlying, strategy, aError); + if (aError.Failed()) { + return false; + } + mIncomingBidirectionalStreams = + ReadableStream::Constructor(aGlobal, underlying, strategy, aError); + if (aError.Failed()) { + return false; + } + + PBackgroundChild* backgroundChild = + BackgroundChild::GetOrCreateForCurrentThread(); + if (NS_WARN_IF(!backgroundChild)) { + return false; + } + + nsCOMPtr<nsIPrincipal> principal = nsContentUtils::GetSystemPrincipal(); + // Create a new IPC connection + Endpoint<PWebTransportParent> parentEndpoint; + Endpoint<PWebTransportChild> childEndpoint; + MOZ_ALWAYS_SUCCEEDS( + PWebTransport::CreateEndpoints(&parentEndpoint, &childEndpoint)); + + RefPtr<WebTransportChild> child = new WebTransportChild(); + if (!childEndpoint.Bind(child)) { + return false; + } + + mState = WebTransportState::CONNECTING; + LOG(("Connecting WebTransport to parent for %s", + NS_ConvertUTF16toUTF8(aURL).get())); + + // Parse string for validity and Throw a SyntaxError if it isn't + if (!ParseURL(aURL)) { + aError.ThrowSyntaxError("Invalid WebTransport URL"); + return false; + } + bool dedicated = + !aOptions.mAllowPooling; // spec language, optimizer will eliminate this + bool requireUnreliable = aOptions.mRequireUnreliable; + WebTransportCongestionControl congestionControl = aOptions.mCongestionControl; + if (aOptions.mServerCertificateHashes.WasPassed()) { + // XXX bug 1806693 + aError.ThrowNotSupportedError("No support for serverCertificateHashes yet"); + // XXX if dedicated is false and serverCertificateHashes is non-null, then + // throw a TypeError. Also should enforce in parent + return false; + } + + // https://w3c.github.io/webtransport/#webtransport-constructor Spec 5.2 + backgroundChild + ->SendCreateWebTransportParent(aURL, principal, dedicated, + requireUnreliable, + (uint32_t)congestionControl, + // XXX serverCertHashes, + std::move(parentEndpoint)) + ->Then(GetCurrentSerialEventTarget(), __func__, + [self = RefPtr{this}, + child](PBackgroundChild::CreateWebTransportParentPromise:: + ResolveOrRejectValue&& aResult) { + // aResult is a 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 + : Get<0>(aResult.ResolveValue()); + if (NS_FAILED(rv)) { + self->RejectWaitingConnection(rv); + } else { + // This will process anything waiting for the connection to + // complete; + self->ResolveWaitingConnection( + static_cast<WebTransportReliabilityMode>( + Get<1>(aResult.ResolveValue())), + child); + } + }); + + return true; +} + +void WebTransport::ResolveWaitingConnection( + WebTransportReliabilityMode aReliability, WebTransportChild* aChild) { + LOG(("Resolved Connection %p, reliability = %u", this, + (unsigned)aReliability)); + + MOZ_ASSERT(mState == WebTransportState::CONNECTING); + mChild = aChild; + mState = WebTransportState::CONNECTED; + mReliability = aReliability; + + mReady->MaybeResolve(true); +} + +void WebTransport::RejectWaitingConnection(nsresult aRv) { + LOG(("Reject Connection %p", this)); + MOZ_ASSERT(mState == WebTransportState::CONNECTING); + mState = WebTransportState::FAILED; + LOG(("Rejected connection %x", (uint32_t)aRv)); + + // https://w3c.github.io/webtransport/#webtransport-internal-slots + // "Reliability returns "pending" until a connection is established" so + // we leave it pending + mReady->MaybeReject(aRv); +} + +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; +} + +already_AddRefed<Promise> WebTransport::Ready() { return do_AddRef(mReady); } + +WebTransportReliabilityMode WebTransport::Reliability() { return mReliability; } + +WebTransportCongestionControl WebTransport::CongestionControl() { + // XXX not implemented + return WebTransportCongestionControl::Default; +} + +already_AddRefed<Promise> WebTransport::Closed() { + ErrorResult error; + RefPtr<Promise> promise = Promise::Create(GetParentObject(), error); + if (error.Failed()) { + return nullptr; + } + promise->MaybeResolve(mState == WebTransportState::CLOSED); + return promise.forget(); +} + +void WebTransport::Close(const WebTransportCloseInfo& aOptions) { + LOG(("Close() called")); + if (mState == WebTransportState::CONNECTED || + mState == WebTransportState::CONNECTING) { + MOZ_ASSERT(mChild); + LOG(("Sending Close")); + // "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 + // https://w3c.github.io/webtransport/#webtransport-methods 5.4 + + 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); + } + mState = WebTransportState::CLOSED; + + // The other side will call `Close()` for us now, make sure we don't call it + // in our destructor. + mChild = nullptr; + } +} + +already_AddRefed<WebTransportDatagramDuplexStream> WebTransport::Datagrams() { + LOG(("Datagrams() called")); + // XXX not implemented + return nullptr; +} + +already_AddRefed<Promise> WebTransport::CreateBidirectionalStream( + ErrorResult& aError) { + LOG(("CreateBidirectionalStream() called")); + aError.Throw(NS_ERROR_NOT_IMPLEMENTED); + return nullptr; +} + +already_AddRefed<ReadableStream> WebTransport::IncomingBidirectionalStreams() { + return do_AddRef(mIncomingBidirectionalStreams); +} + +already_AddRefed<Promise> WebTransport::CreateUnidirectionalStream( + ErrorResult& aError) { + LOG(("CreateUnidirectionalStream() called")); + aError.Throw(NS_ERROR_NOT_IMPLEMENTED); + return nullptr; +} + +already_AddRefed<ReadableStream> WebTransport::IncomingUnidirectionalStreams() { + return do_AddRef(mIncomingUnidirectionalStreams); +} + +} // namespace mozilla::dom |