From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- dom/media/webrtc/transport/ipc/WebrtcTCPSocket.cpp | 785 +++++++++++++++++++++ 1 file changed, 785 insertions(+) create mode 100644 dom/media/webrtc/transport/ipc/WebrtcTCPSocket.cpp (limited to 'dom/media/webrtc/transport/ipc/WebrtcTCPSocket.cpp') diff --git a/dom/media/webrtc/transport/ipc/WebrtcTCPSocket.cpp b/dom/media/webrtc/transport/ipc/WebrtcTCPSocket.cpp new file mode 100644 index 0000000000..447b4cc741 --- /dev/null +++ b/dom/media/webrtc/transport/ipc/WebrtcTCPSocket.cpp @@ -0,0 +1,785 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */ +/* 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 "WebrtcTCPSocket.h" + +#include "nsHttpChannel.h" +#include "nsIChannel.h" +#include "nsIClassOfService.h" +#include "nsIContentPolicy.h" +#include "nsIIOService.h" +#include "nsILoadInfo.h" +#include "nsIProtocolProxyService.h" +#include "nsIURIMutator.h" +#include "nsICookieJarSettings.h" +#include "nsProxyRelease.h" +#include "nsString.h" +#include "mozilla/dom/ContentProcessManager.h" +#include "mozilla/dom/BrowserParent.h" +#include "nsISocketTransportService.h" +#include "nsICancelable.h" +#include "nsSocketTransportService2.h" + +#include "WebrtcTCPSocketCallback.h" +#include "WebrtcTCPSocketLog.h" + +namespace mozilla::net { + +class WebrtcTCPData { + public: + explicit WebrtcTCPData(nsTArray&& aData) : mData(std::move(aData)) { + MOZ_COUNT_CTOR(WebrtcTCPData); + } + + MOZ_COUNTED_DTOR(WebrtcTCPData) + + const nsTArray& GetData() const { return mData; } + + private: + nsTArray mData; +}; + +NS_IMPL_ISUPPORTS(WebrtcTCPSocket, nsIAuthPromptProvider, + nsIHttpUpgradeListener, nsIInputStreamCallback, + nsIInterfaceRequestor, nsIOutputStreamCallback, + nsIRequestObserver, nsIStreamListener, + nsIProtocolProxyCallback) + +WebrtcTCPSocket::WebrtcTCPSocket(WebrtcTCPSocketCallback* aCallbacks) + : mProxyCallbacks(aCallbacks), + mClosed(false), + mOpened(false), + mWriteOffset(0), + mAuthProvider(nullptr), + mTransport(nullptr), + mSocketIn(nullptr), + mSocketOut(nullptr) { + LOG(("WebrtcTCPSocket::WebrtcTCPSocket %p\n", this)); + mMainThread = GetMainThreadSerialEventTarget(); + mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID); + MOZ_RELEASE_ASSERT(mMainThread, "no main thread"); + MOZ_RELEASE_ASSERT(mSocketThread, "no socket thread"); +} + +WebrtcTCPSocket::~WebrtcTCPSocket() { + LOG(("WebrtcTCPSocket::~WebrtcTCPSocket %p\n", this)); + + NS_ProxyRelease("WebrtcTCPSocket::CleanUpAuthProvider", mMainThread, + mAuthProvider.forget()); +} + +void WebrtcTCPSocket::SetTabId(dom::TabId aTabId) { + MOZ_ASSERT(NS_IsMainThread()); + dom::ContentProcessManager* cpm = dom::ContentProcessManager::GetSingleton(); + if (cpm) { + dom::ContentParentId cpId = cpm->GetTabProcessId(aTabId); + mAuthProvider = cpm->GetBrowserParentByProcessAndTabId(cpId, aTabId); + } +} + +nsresult WebrtcTCPSocket::Write(nsTArray&& aWriteData) { + LOG(("WebrtcTCPSocket::Write %p\n", this)); + MOZ_ASSERT(NS_IsMainThread()); + nsresult rv = mSocketThread->Dispatch(NewRunnableMethod&&>( + "WebrtcTCPSocket::Write", this, &WebrtcTCPSocket::EnqueueWrite_s, + std::move(aWriteData))); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch to STS"); + + return rv; +} + +nsresult WebrtcTCPSocket::Close() { + LOG(("WebrtcTCPSocket::Close %p\n", this)); + + CloseWithReason(NS_OK); + + return NS_OK; +} + +void WebrtcTCPSocket::CloseWithReason(nsresult aReason) { + LOG(("WebrtcTCPSocket::CloseWithReason %p reason=%u\n", this, + static_cast(aReason))); + + if (!OnSocketThread()) { + MOZ_ASSERT(NS_IsMainThread(), "not on main thread"); + + // Let's pretend we got an open even if we didn't to prevent an Open later. + mOpened = true; + + DebugOnly rv = + mSocketThread->Dispatch(NewRunnableMethod( + "WebrtcTCPSocket::CloseWithReason", this, + &WebrtcTCPSocket::CloseWithReason, aReason)); + + // This was MOZ_ALWAYS_SUCCEEDS, but that now uses NS_WARNING_ASSERTION. + // In order to convert this back to MOZ_ALWAYS_SUCCEEDS we would need + // OnSocketThread to return true if we're shutting down and doing the + // "running all of STS's queued events on main" thing. + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch to STS"); + + return; + } + + if (mClosed) { + return; + } + + mClosed = true; + + if (mSocketIn) { + mSocketIn->AsyncWait(nullptr, 0, 0, nullptr); + mSocketIn = nullptr; + } + + if (mSocketOut) { + mSocketOut->AsyncWait(nullptr, 0, 0, nullptr); + mSocketOut = nullptr; + } + + if (mTransport) { + mTransport->Close(NS_BASE_STREAM_CLOSED); + mTransport = nullptr; + } + + NS_ProxyRelease("WebrtcTCPSocket::CleanUpAuthProvider", mMainThread, + mAuthProvider.forget()); + InvokeOnClose(aReason); +} + +nsresult WebrtcTCPSocket::Open( + const nsACString& aHost, const int& aPort, const nsACString& aLocalAddress, + const int& aLocalPort, bool aUseTls, + const Maybe& aProxyConfig) { + LOG(("WebrtcTCPSocket::Open %p remote-host=%s local-addr=%s local-port=%d", + this, PromiseFlatCString(aHost).get(), + PromiseFlatCString(aLocalAddress).get(), aLocalPort)); + MOZ_ASSERT(NS_IsMainThread()); + + if (NS_WARN_IF(mOpened)) { + LOG(("WebrtcTCPSocket %p: TCP socket already open\n", this)); + CloseWithReason(NS_ERROR_FAILURE); + return NS_ERROR_FAILURE; + } + + mOpened = true; + const nsLiteralCString schemePrefix = aUseTls ? "https://"_ns : "http://"_ns; + nsAutoCString spec(schemePrefix); + + bool ipv6Literal = aHost.Find(":") != kNotFound; + if (ipv6Literal) { + spec += "["; + spec += aHost; + spec += "]"; + } else { + spec += aHost; + } + + nsresult rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID) + .SetSpec(spec) + .SetPort(aPort) + .Finalize(mURI); + + if (NS_WARN_IF(NS_FAILED(rv))) { + CloseWithReason(NS_ERROR_FAILURE); + return NS_ERROR_FAILURE; + } + + mTls = aUseTls; + mLocalAddress = aLocalAddress; + mLocalPort = aLocalPort; + mProxyConfig = aProxyConfig; + + if (!mProxyConfig.isSome()) { + OpenWithoutHttpProxy(nullptr); + return NS_OK; + } + + // We need to figure out whether a proxy needs to be used for mURI before + // we can start on establishing a connection. + rv = DoProxyConfigLookup(); + + if (NS_WARN_IF(NS_FAILED(rv))) { + CloseWithReason(rv); + } + + return rv; +} + +nsresult WebrtcTCPSocket::DoProxyConfigLookup() { + MOZ_ASSERT(NS_IsMainThread()); + nsresult rv; + nsCOMPtr pps = + do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr channel; + rv = NS_NewChannel(getter_AddRefs(channel), mURI, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = pps->AsyncResolve(channel, + nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY | + nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL, + this, nullptr, getter_AddRefs(mProxyRequest)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // We pick back up in OnProxyAvailable + + return NS_OK; +} + +NS_IMETHODIMP WebrtcTCPSocket::OnProxyAvailable(nsICancelable* aRequest, + nsIChannel* aChannel, + nsIProxyInfo* aProxyinfo, + nsresult aResult) { + MOZ_ASSERT(NS_IsMainThread()); + mProxyRequest = nullptr; + + if (NS_SUCCEEDED(aResult) && aProxyinfo) { + nsresult rv = aProxyinfo->GetType(mProxyType); + if (NS_WARN_IF(NS_FAILED(rv))) { + CloseWithReason(rv); + return rv; + } + + if (mProxyType == "http" || mProxyType == "https") { + rv = OpenWithHttpProxy(); + if (NS_WARN_IF(NS_FAILED(rv))) { + CloseWithReason(rv); + } + return rv; + } + + if (mProxyType == "socks" || mProxyType == "socks4" || + mProxyType == "direct") { + OpenWithoutHttpProxy(aProxyinfo); + return NS_OK; + } + } + + OpenWithoutHttpProxy(nullptr); + + return NS_OK; +} + +void WebrtcTCPSocket::OpenWithoutHttpProxy(nsIProxyInfo* aSocksProxyInfo) { + if (!OnSocketThread()) { + DebugOnly rv = + mSocketThread->Dispatch(NewRunnableMethod>( + "WebrtcTCPSocket::OpenWithoutHttpProxy", this, + &WebrtcTCPSocket::OpenWithoutHttpProxy, aSocksProxyInfo)); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch to STS"); + return; + } + + LOG(("WebrtcTCPSocket::OpenWithoutHttpProxy %p\n", this)); + + if (mClosed) { + return; + } + + if (NS_WARN_IF(mProxyConfig.isSome() && mProxyConfig->forceProxy() && + !aSocksProxyInfo)) { + CloseWithReason(NS_ERROR_FAILURE); + return; + } + + nsCString host; + int32_t port; + + nsresult rv = mURI->GetHost(host); + if (NS_WARN_IF(NS_FAILED(rv))) { + CloseWithReason(rv); + return; + } + + rv = mURI->GetPort(&port); + if (NS_WARN_IF(NS_FAILED(rv))) { + CloseWithReason(rv); + return; + } + + AutoTArray socketTypes; + if (mTls) { + socketTypes.AppendElement("ssl"_ns); + } + + nsCOMPtr sts = + do_GetService("@mozilla.org/network/socket-transport-service;1"); + rv = sts->CreateTransport(socketTypes, host, port, aSocksProxyInfo, nullptr, + getter_AddRefs(mTransport)); + if (NS_WARN_IF(NS_FAILED(rv))) { + CloseWithReason(rv); + return; + } + + mTransport->SetReuseAddrPort(true); + + PRNetAddr prAddr; + if (NS_WARN_IF(PR_SUCCESS != + PR_InitializeNetAddr(PR_IpAddrAny, mLocalPort, &prAddr))) { + CloseWithReason(NS_ERROR_FAILURE); + return; + } + + if (NS_WARN_IF(PR_SUCCESS != + PR_StringToNetAddr(mLocalAddress.BeginReading(), &prAddr))) { + CloseWithReason(NS_ERROR_FAILURE); + return; + } + + mozilla::net::NetAddr addr(&prAddr); + rv = mTransport->Bind(&addr); + if (NS_WARN_IF(NS_FAILED(rv))) { + CloseWithReason(rv); + return; + } + + // Binding to a V4 address is not sufficient to cause this socket to use + // V4, and the same goes for V6. So, we disable as needed here. + uint32_t flags = 0; + if (addr.raw.family == AF_INET) { + flags |= nsISocketTransport::DISABLE_IPV6; + } else if (addr.raw.family == AF_INET6) { + flags |= nsISocketTransport::DISABLE_IPV4; + } else { + MOZ_CRASH(); + } + + mTransport->SetConnectionFlags(flags); + + nsCOMPtr socketIn; + rv = mTransport->OpenInputStream(0, 0, 0, getter_AddRefs(socketIn)); + if (NS_WARN_IF(NS_FAILED(rv))) { + CloseWithReason(rv); + return; + } + mSocketIn = do_QueryInterface(socketIn); + if (NS_WARN_IF(!mSocketIn)) { + CloseWithReason(NS_ERROR_NULL_POINTER); + return; + } + + nsCOMPtr socketOut; + rv = mTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0, + getter_AddRefs(socketOut)); + if (NS_WARN_IF(NS_FAILED(rv))) { + CloseWithReason(rv); + return; + } + mSocketOut = do_QueryInterface(socketOut); + if (NS_WARN_IF(!mSocketOut)) { + CloseWithReason(NS_ERROR_NULL_POINTER); + return; + } + + FinishOpen(); +} + +nsresult WebrtcTCPSocket::OpenWithHttpProxy() { + MOZ_ASSERT(NS_IsMainThread(), "not on main thread"); + LOG(("WebrtcTCPSocket::OpenWithHttpProxy %p\n", this)); + nsresult rv; + nsCOMPtr ioService; + ioService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + LOG(("WebrtcTCPSocket %p: io service missing\n", this)); + return rv; + } + + nsCOMPtr loadInfo; + Maybe loadInfoArgs = Some(mProxyConfig->loadInfoArgs()); + + // FIXME: We don't know the remote type of the process which provided these + // LoadInfoArgs. Pass in `NOT_REMOTE_TYPE` as the origin process to blindly + // accept whatever value was passed by the other side for now, as we aren't + // using it for security checks here. + // If this code ever starts checking the triggering remote type, this needs to + // be changed. + rv = ipc::LoadInfoArgsToLoadInfo(loadInfoArgs, NOT_REMOTE_TYPE, + getter_AddRefs(loadInfo)); + if (NS_FAILED(rv)) { + LOG(("WebrtcTCPSocket %p: could not init load info\n", this)); + return rv; + } + + // -need to always tunnel since we're using a proxy + // -there shouldn't be an opportunity to send cookies, but explicitly disallow + // them anyway. + // -the previous proxy tunnel didn't support redirects e.g. 307. don't need to + // introduce new behavior. can't follow redirects on connect anyway. + nsCOMPtr localChannel; + rv = ioService->NewChannelFromURIWithProxyFlags( + mURI, nullptr, + // Proxy flags are overridden by SetConnectOnly() + 0, loadInfo->LoadingNode(), loadInfo->GetLoadingPrincipal(), + loadInfo->TriggeringPrincipal(), + nsILoadInfo::SEC_COOKIES_OMIT | + // We need this flag to allow loads from any origin since this channel + // is being used to CONNECT to an HTTP proxy. + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA, + getter_AddRefs(localChannel)); + if (NS_FAILED(rv)) { + LOG(("WebrtcTCPSocket %p: bad open channel\n", this)); + return rv; + } + + nsCOMPtr channelLoadInfo = localChannel->LoadInfo(); + nsCOMPtr cookieJarSettings; + loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)); + channelLoadInfo->SetCookieJarSettings(cookieJarSettings); + + RefPtr httpChannel; + CallQueryInterface(localChannel, httpChannel.StartAssignment()); + + if (!httpChannel) { + LOG(("WebrtcTCPSocket %p: not an http channel\n", this)); + return NS_ERROR_FAILURE; + } + + httpChannel->SetNotificationCallbacks(this); + + // don't block webrtc proxy setup with other requests + // often more than one of these channels will be created all at once by ICE + nsCOMPtr cos = do_QueryInterface(localChannel); + if (cos) { + cos->AddClassFlags(nsIClassOfService::Unblocked | + nsIClassOfService::DontThrottle); + } else { + LOG(("WebrtcTCPSocket %p: could not set class of service\n", this)); + return NS_ERROR_FAILURE; + } + + rv = httpChannel->HTTPUpgrade(mProxyConfig->alpn(), this); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rv = httpChannel->SetConnectOnly(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = httpChannel->AsyncOpen(this); + + if (NS_FAILED(rv)) { + LOG(("WebrtcTCPSocket %p: cannot async open\n", this)); + return rv; + } + + // This picks back up in OnTransportAvailable once we have connected to the + // proxy, and performed the http upgrade to switch the proxy into passthrough + // mode. + + return NS_OK; +} + +void WebrtcTCPSocket::EnqueueWrite_s(nsTArray&& aWriteData) { + LOG(("WebrtcTCPSocket::EnqueueWrite %p\n", this)); + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + if (mClosed) { + return; + } + + mWriteQueue.emplace_back(std::move(aWriteData)); + + if (mSocketOut) { + mSocketOut->AsyncWait(this, 0, 0, nullptr); + } +} + +void WebrtcTCPSocket::InvokeOnClose(nsresult aReason) { + LOG(("WebrtcTCPSocket::InvokeOnClose %p\n", this)); + + if (!NS_IsMainThread()) { + MOZ_ALWAYS_SUCCEEDS(mMainThread->Dispatch( + NewRunnableMethod("WebrtcTCPSocket::InvokeOnClose", this, + &WebrtcTCPSocket::InvokeOnClose, aReason))); + return; + } + + MOZ_ASSERT(mProxyCallbacks, "webrtc TCP callback should be non-null"); + + if (mProxyRequest) { + mProxyRequest->Cancel(aReason); + mProxyRequest = nullptr; + } + + mProxyCallbacks->OnClose(aReason); + mProxyCallbacks = nullptr; +} + +void WebrtcTCPSocket::InvokeOnConnected() { + LOG(("WebrtcTCPSocket::InvokeOnConnected %p\n", this)); + + if (!NS_IsMainThread()) { + MOZ_ALWAYS_SUCCEEDS(mMainThread->Dispatch( + NewRunnableMethod("WebrtcTCPSocket::InvokeOnConnected", this, + &WebrtcTCPSocket::InvokeOnConnected))); + return; + } + + MOZ_ASSERT(mProxyCallbacks, "webrtc TCP callback should be non-null"); + + mProxyCallbacks->OnConnected(mProxyType); +} + +void WebrtcTCPSocket::InvokeOnRead(nsTArray&& aReadData) { + LOG(("WebrtcTCPSocket::InvokeOnRead %p count=%zu\n", this, + aReadData.Length())); + + if (!NS_IsMainThread()) { + MOZ_ALWAYS_SUCCEEDS( + mMainThread->Dispatch(NewRunnableMethod&&>( + "WebrtcTCPSocket::InvokeOnRead", this, + &WebrtcTCPSocket::InvokeOnRead, std::move(aReadData)))); + return; + } + + MOZ_ASSERT(mProxyCallbacks, "webrtc TCP callback should be non-null"); + + mProxyCallbacks->OnRead(std::move(aReadData)); +} + +// nsIHttpUpgradeListener +NS_IMETHODIMP +WebrtcTCPSocket::OnTransportAvailable(nsISocketTransport* aTransport, + nsIAsyncInputStream* aSocketIn, + nsIAsyncOutputStream* aSocketOut) { + // This is called only in the http proxy case, once we have connected to the + // http proxy and performed the http upgrade to switch it over to passthrough + // mode. That process is started async by OpenWithHttpProxy. + LOG(("WebrtcTCPSocket::OnTransportAvailable %p\n", this)); + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + MOZ_ASSERT(!mTransport, + "already called transport available on webrtc TCP socket"); + + // Cancel any pending callbacks. The caller doesn't always cancel these + // awaits. We need to make sure they don't get them. + aSocketIn->AsyncWait(nullptr, 0, 0, nullptr); + aSocketOut->AsyncWait(nullptr, 0, 0, nullptr); + + if (mClosed) { + LOG(("WebrtcTCPSocket::OnTransportAvailable %p closed\n", this)); + return NS_OK; + } + + mTransport = aTransport; + mSocketIn = aSocketIn; + mSocketOut = aSocketOut; + + // pulled from nr_socket_prsock.cpp + uint32_t minBufferSize = 256 * 1024; + nsresult rv = mTransport->SetSendBufferSize(minBufferSize); + if (NS_FAILED(rv)) { + LOG(("WebrtcProxyChannel::OnTransportAvailable %p send failed\n", this)); + CloseWithReason(rv); + return rv; + } + rv = mTransport->SetRecvBufferSize(minBufferSize); + if (NS_FAILED(rv)) { + LOG(("WebrtcProxyChannel::OnTransportAvailable %p recv failed\n", this)); + CloseWithReason(rv); + return rv; + } + + FinishOpen(); + return NS_OK; +} + +void WebrtcTCPSocket::FinishOpen() { + MOZ_ASSERT(OnSocketThread()); + // mTransport, mSocketIn, and mSocketOut are all set. We may have set them in + // OnTransportAvailable (in the http/https proxy case), or in + // OpenWithoutHttpProxy. From here on out, this class functions the same for + // these two cases. + + mSocketIn->AsyncWait(this, 0, 0, nullptr); + + InvokeOnConnected(); +} + +NS_IMETHODIMP +WebrtcTCPSocket::OnUpgradeFailed(nsresult aErrorCode) { + LOG(("WebrtcTCPSocket::OnUpgradeFailed %p\n", this)); + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + MOZ_ASSERT(!mTransport, + "already called transport available on webrtc TCP socket"); + + if (mClosed) { + LOG(("WebrtcTCPSocket::OnUpgradeFailed %p closed\n", this)); + return NS_OK; + } + + CloseWithReason(aErrorCode); + + return NS_OK; +} + +NS_IMETHODIMP +WebrtcTCPSocket::OnWebSocketConnectionAvailable( + WebSocketConnectionBase* aConnection) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +// nsIRequestObserver (from nsIStreamListener) +NS_IMETHODIMP +WebrtcTCPSocket::OnStartRequest(nsIRequest* aRequest) { + LOG(("WebrtcTCPSocket::OnStartRequest %p\n", this)); + + return NS_OK; +} + +NS_IMETHODIMP +WebrtcTCPSocket::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) { + LOG(("WebrtcTCPSocket::OnStopRequest %p status=%u\n", this, + static_cast(aStatusCode))); + + // see nsHttpChannel::ProcessFailedProxyConnect for most error codes + if (NS_FAILED(aStatusCode)) { + CloseWithReason(aStatusCode); + return aStatusCode; + } + + return NS_OK; +} + +// nsIStreamListener +NS_IMETHODIMP +WebrtcTCPSocket::OnDataAvailable(nsIRequest* aRequest, + nsIInputStream* aInputStream, uint64_t aOffset, + uint32_t aCount) { + LOG(("WebrtcTCPSocket::OnDataAvailable %p count=%u\n", this, aCount)); + MOZ_ASSERT(0, "unreachable data available"); + return NS_OK; +} + +// nsIInputStreamCallback +NS_IMETHODIMP +WebrtcTCPSocket::OnInputStreamReady(nsIAsyncInputStream* in) { + LOG(("WebrtcTCPSocket::OnInputStreamReady %p unwritten=%zu\n", this, + CountUnwrittenBytes())); + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + MOZ_ASSERT(!mClosed, "webrtc TCP socket closed"); + MOZ_ASSERT(mTransport, "webrtc TCP socket not connected"); + MOZ_ASSERT(mSocketIn == in, "wrong input stream"); + + char buffer[9216]; + uint32_t remainingCapacity = sizeof(buffer); + uint32_t read = 0; + + while (remainingCapacity > 0) { + uint32_t count = 0; + nsresult rv = mSocketIn->Read(buffer + read, remainingCapacity, &count); + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + break; + } + + if (NS_FAILED(rv)) { + LOG(("WebrtcTCPSocket::OnInputStreamReady %p failed %u\n", this, + static_cast(rv))); + CloseWithReason(rv); + return rv; + } + + // base stream closed + if (count == 0) { + LOG(("WebrtcTCPSocket::OnInputStreamReady %p connection closed\n", this)); + CloseWithReason(NS_ERROR_FAILURE); + return NS_OK; + } + + remainingCapacity -= count; + read += count; + } + + if (read > 0) { + nsTArray array(read); + array.AppendElements(buffer, read); + + InvokeOnRead(std::move(array)); + } + + mSocketIn->AsyncWait(this, 0, 0, nullptr); + + return NS_OK; +} + +// nsIOutputStreamCallback +NS_IMETHODIMP +WebrtcTCPSocket::OnOutputStreamReady(nsIAsyncOutputStream* out) { + LOG(("WebrtcTCPSocket::OnOutputStreamReady %p unwritten=%zu\n", this, + CountUnwrittenBytes())); + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + MOZ_ASSERT(!mClosed, "webrtc TCP socket closed"); + MOZ_ASSERT(mTransport, "webrtc TCP socket not connected"); + MOZ_ASSERT(mSocketOut == out, "wrong output stream"); + + while (!mWriteQueue.empty()) { + const WebrtcTCPData& data = mWriteQueue.front(); + + char* buffer = reinterpret_cast( + const_cast(data.GetData().Elements())) + + mWriteOffset; + uint32_t toWrite = data.GetData().Length() - mWriteOffset; + + uint32_t wrote = 0; + nsresult rv = mSocketOut->Write(buffer, toWrite, &wrote); + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + mSocketOut->AsyncWait(this, 0, 0, nullptr); + return NS_OK; + } + + if (NS_FAILED(rv)) { + LOG(("WebrtcTCPSocket::OnOutputStreamReady %p failed %u\n", this, + static_cast(rv))); + CloseWithReason(rv); + return NS_OK; + } + + mWriteOffset += wrote; + + if (toWrite == wrote) { + mWriteOffset = 0; + mWriteQueue.pop_front(); + } + } + + return NS_OK; +} + +// nsIInterfaceRequestor +NS_IMETHODIMP +WebrtcTCPSocket::GetInterface(const nsIID& iid, void** result) { + LOG(("WebrtcTCPSocket::GetInterface %p\n", this)); + + return QueryInterface(iid, result); +} + +size_t WebrtcTCPSocket::CountUnwrittenBytes() const { + size_t count = 0; + + for (const WebrtcTCPData& data : mWriteQueue) { + count += data.GetData().Length(); + } + + MOZ_ASSERT(count >= mWriteOffset, "offset exceeds write buffer length"); + + count -= mWriteOffset; + + return count; +} + +} // namespace mozilla::net -- cgit v1.2.3