tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

WebrtcTCPSocket.cpp (23945B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "WebrtcTCPSocket.h"
      8 
      9 #include "WebrtcTCPSocketCallback.h"
     10 #include "WebrtcTCPSocketLog.h"
     11 #include "mozilla/StaticPrefs_media.h"
     12 #include "mozilla/dom/BrowserParent.h"
     13 #include "mozilla/dom/ContentProcessManager.h"
     14 #include "nsHttpChannel.h"
     15 #include "nsICancelable.h"
     16 #include "nsIChannel.h"
     17 #include "nsIClassOfService.h"
     18 #include "nsIContentPolicy.h"
     19 #include "nsICookieJarSettings.h"
     20 #include "nsIIOService.h"
     21 #include "nsILoadInfo.h"
     22 #include "nsIProtocolProxyService.h"
     23 #include "nsISocketTransportService.h"
     24 #include "nsIURIMutator.h"
     25 #include "nsProxyRelease.h"
     26 #include "nsSocketTransportService2.h"
     27 #include "nsString.h"
     28 
     29 namespace mozilla::net {
     30 
     31 class WebrtcTCPData {
     32 public:
     33  explicit WebrtcTCPData(nsTArray<uint8_t>&& aData) : mData(std::move(aData)) {
     34    MOZ_COUNT_CTOR(WebrtcTCPData);
     35  }
     36 
     37  MOZ_COUNTED_DTOR(WebrtcTCPData)
     38 
     39  const nsTArray<uint8_t>& GetData() const { return mData; }
     40 
     41 private:
     42  nsTArray<uint8_t> mData;
     43 };
     44 
     45 NS_IMPL_ISUPPORTS(WebrtcTCPSocket, nsIAuthPromptProvider,
     46                  nsIHttpUpgradeListener, nsIInputStreamCallback,
     47                  nsIInterfaceRequestor, nsIOutputStreamCallback,
     48                  nsIRequestObserver, nsIStreamListener,
     49                  nsIProtocolProxyCallback)
     50 
     51 WebrtcTCPSocket::WebrtcTCPSocket(WebrtcTCPSocketCallback* aCallbacks)
     52    : mProxyCallbacks(aCallbacks),
     53      mClosed(false),
     54      mOpened(false),
     55      mWriteOffset(0),
     56      mAuthProvider(nullptr),
     57      mTransport(nullptr),
     58      mSocketIn(nullptr),
     59      mSocketOut(nullptr) {
     60  LOG(("WebrtcTCPSocket::WebrtcTCPSocket %p\n", this));
     61  mMainThread = GetMainThreadSerialEventTarget();
     62  mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
     63  MOZ_RELEASE_ASSERT(mMainThread, "no main thread");
     64  MOZ_RELEASE_ASSERT(mSocketThread, "no socket thread");
     65 }
     66 
     67 WebrtcTCPSocket::~WebrtcTCPSocket() {
     68  LOG(("WebrtcTCPSocket::~WebrtcTCPSocket %p\n", this));
     69 
     70  NS_ProxyRelease("WebrtcTCPSocket::CleanUpAuthProvider", mMainThread,
     71                  mAuthProvider.forget());
     72 }
     73 
     74 void WebrtcTCPSocket::SetTabId(dom::TabId aTabId) {
     75  MOZ_ASSERT(NS_IsMainThread());
     76  dom::ContentProcessManager* cpm = dom::ContentProcessManager::GetSingleton();
     77  if (cpm) {
     78    dom::ContentParentId cpId = cpm->GetTabProcessId(aTabId);
     79    mAuthProvider = cpm->GetBrowserParentByProcessAndTabId(cpId, aTabId);
     80  }
     81 }
     82 
     83 nsresult WebrtcTCPSocket::Write(nsTArray<uint8_t>&& aWriteData) {
     84  LOG(("WebrtcTCPSocket::Write %p\n", this));
     85  MOZ_ASSERT(NS_IsMainThread());
     86  nsresult rv = mSocketThread->Dispatch(NewRunnableMethod<nsTArray<uint8_t>&&>(
     87      "WebrtcTCPSocket::Write", this, &WebrtcTCPSocket::EnqueueWrite_s,
     88      std::move(aWriteData)));
     89  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch to STS");
     90 
     91  return rv;
     92 }
     93 
     94 nsresult WebrtcTCPSocket::Close() {
     95  LOG(("WebrtcTCPSocket::Close %p\n", this));
     96 
     97  CloseWithReason(NS_OK);
     98 
     99  return NS_OK;
    100 }
    101 
    102 void WebrtcTCPSocket::CloseWithReason(nsresult aReason) {
    103  LOG(("WebrtcTCPSocket::CloseWithReason %p reason=%u\n", this,
    104       static_cast<uint32_t>(aReason)));
    105 
    106  if (!OnSocketThread()) {
    107    MOZ_ASSERT(NS_IsMainThread(), "not on main thread");
    108 
    109    // Let's pretend we got an open even if we didn't to prevent an Open later.
    110    mOpened = true;
    111 
    112    DebugOnly<nsresult> rv =
    113        mSocketThread->Dispatch(NewRunnableMethod<nsresult>(
    114            "WebrtcTCPSocket::CloseWithReason", this,
    115            &WebrtcTCPSocket::CloseWithReason, aReason));
    116 
    117    // This was MOZ_ALWAYS_SUCCEEDS, but that now uses NS_WARNING_ASSERTION.
    118    // In order to convert this back to MOZ_ALWAYS_SUCCEEDS we would need
    119    // OnSocketThread to return true if we're shutting down and doing the
    120    // "running all of STS's queued events on main" thing.
    121    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch to STS");
    122 
    123    return;
    124  }
    125 
    126  if (mClosed) {
    127    return;
    128  }
    129 
    130  mClosed = true;
    131 
    132  if (mSocketIn) {
    133    mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
    134    mSocketIn = nullptr;
    135  }
    136 
    137  if (mSocketOut) {
    138    mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
    139    mSocketOut = nullptr;
    140  }
    141 
    142  if (mTransport) {
    143    mTransport->Close(NS_BASE_STREAM_CLOSED);
    144    mTransport = nullptr;
    145  }
    146 
    147  NS_ProxyRelease("WebrtcTCPSocket::CleanUpAuthProvider", mMainThread,
    148                  mAuthProvider.forget());
    149  InvokeOnClose(aReason);
    150 }
    151 
    152 nsresult WebrtcTCPSocket::Open(
    153    const nsACString& aHost, const int& aPort, const nsACString& aLocalAddress,
    154    const int& aLocalPort, bool aUseTls,
    155    const Maybe<net::WebrtcProxyConfig>& aProxyConfig) {
    156  LOG(("WebrtcTCPSocket::Open %p remote-host=%s local-addr=%s local-port=%d",
    157       this, PromiseFlatCString(aHost).get(),
    158       PromiseFlatCString(aLocalAddress).get(), aLocalPort));
    159  MOZ_ASSERT(NS_IsMainThread());
    160 
    161  if (NS_WARN_IF(mOpened)) {
    162    LOG(("WebrtcTCPSocket %p: TCP socket already open\n", this));
    163    CloseWithReason(NS_ERROR_FAILURE);
    164    return NS_ERROR_FAILURE;
    165  }
    166 
    167  mOpened = true;
    168  const nsLiteralCString schemePrefix = aUseTls ? "https://"_ns : "http://"_ns;
    169  nsAutoCString spec(schemePrefix);
    170 
    171  bool ipv6Literal = aHost.Find(":") != kNotFound;
    172  if (ipv6Literal) {
    173    spec += "[";
    174    spec += aHost;
    175    spec += "]";
    176  } else {
    177    spec += aHost;
    178  }
    179 
    180  nsresult rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
    181                    .SetSpec(spec)
    182                    .SetPort(aPort)
    183                    .Finalize(mURI);
    184 
    185  if (NS_WARN_IF(NS_FAILED(rv))) {
    186    CloseWithReason(NS_ERROR_FAILURE);
    187    return NS_ERROR_FAILURE;
    188  }
    189 
    190  mTls = aUseTls;
    191  mLocalAddress = aLocalAddress;
    192  mLocalPort = aLocalPort;
    193  mProxyConfig = aProxyConfig;
    194 
    195  if (!mProxyConfig.isSome()) {
    196    OpenWithoutHttpProxy(nullptr);
    197    return NS_OK;
    198  }
    199 
    200  // We need to figure out whether a proxy needs to be used for mURI before
    201  // we can start on establishing a connection.
    202  rv = DoProxyConfigLookup();
    203 
    204  if (NS_WARN_IF(NS_FAILED(rv))) {
    205    CloseWithReason(rv);
    206  }
    207 
    208  return rv;
    209 }
    210 
    211 nsresult WebrtcTCPSocket::DoProxyConfigLookup() {
    212  MOZ_ASSERT(NS_IsMainThread());
    213  nsresult rv;
    214  nsCOMPtr<nsIProtocolProxyService> pps =
    215      do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
    216  if (NS_WARN_IF(NS_FAILED(rv))) {
    217    return rv;
    218  }
    219 
    220  nsCOMPtr<nsIChannel> channel;
    221  rv = NS_NewChannel(getter_AddRefs(channel), mURI,
    222                     nsContentUtils::GetSystemPrincipal(),
    223                     nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
    224                     nsIContentPolicy::TYPE_OTHER);
    225  if (NS_WARN_IF(NS_FAILED(rv))) {
    226    return rv;
    227  }
    228 
    229  rv = pps->AsyncResolve(channel,
    230                         nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
    231                             nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
    232                         this, nullptr, getter_AddRefs(mProxyRequest));
    233  if (NS_WARN_IF(NS_FAILED(rv))) {
    234    return rv;
    235  }
    236 
    237  // We pick back up in OnProxyAvailable
    238 
    239  return NS_OK;
    240 }
    241 
    242 NS_IMETHODIMP WebrtcTCPSocket::OnProxyAvailable(nsICancelable* aRequest,
    243                                                nsIChannel* aChannel,
    244                                                nsIProxyInfo* aProxyinfo,
    245                                                nsresult aResult) {
    246  MOZ_ASSERT(NS_IsMainThread());
    247  mProxyRequest = nullptr;
    248 
    249  if (NS_SUCCEEDED(aResult) && aProxyinfo) {
    250    nsresult rv = aProxyinfo->GetType(mProxyType);
    251    if (NS_WARN_IF(NS_FAILED(rv))) {
    252      CloseWithReason(rv);
    253      return rv;
    254    }
    255 
    256    if (mProxyType == "http" || mProxyType == "https") {
    257      rv = OpenWithHttpProxy();
    258      if (NS_WARN_IF(NS_FAILED(rv))) {
    259        CloseWithReason(rv);
    260      }
    261      return rv;
    262    }
    263 
    264    if (mProxyType == "socks" || mProxyType == "socks4" ||
    265        mProxyType == "direct") {
    266      OpenWithoutHttpProxy(aProxyinfo);
    267      return NS_OK;
    268    }
    269  }
    270 
    271  OpenWithoutHttpProxy(nullptr);
    272 
    273  return NS_OK;
    274 }
    275 
    276 void WebrtcTCPSocket::OpenWithoutHttpProxy(nsIProxyInfo* aSocksProxyInfo) {
    277  if (!OnSocketThread()) {
    278    DebugOnly<nsresult> rv =
    279        mSocketThread->Dispatch(NewRunnableMethod<nsCOMPtr<nsIProxyInfo>>(
    280            "WebrtcTCPSocket::OpenWithoutHttpProxy", this,
    281            &WebrtcTCPSocket::OpenWithoutHttpProxy, aSocksProxyInfo));
    282    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch to STS");
    283    return;
    284  }
    285 
    286  LOG(("WebrtcTCPSocket::OpenWithoutHttpProxy %p\n", this));
    287 
    288  if (mClosed) {
    289    return;
    290  }
    291 
    292  if (NS_WARN_IF(mProxyConfig.isSome() && mProxyConfig->forceProxy() &&
    293                 !aSocksProxyInfo)) {
    294    CloseWithReason(NS_ERROR_FAILURE);
    295    return;
    296  }
    297 
    298  nsCString host;
    299  int32_t port;
    300 
    301  nsresult rv = mURI->GetHost(host);
    302  if (NS_WARN_IF(NS_FAILED(rv))) {
    303    CloseWithReason(rv);
    304    return;
    305  }
    306 
    307  rv = mURI->GetPort(&port);
    308  if (NS_WARN_IF(NS_FAILED(rv))) {
    309    CloseWithReason(rv);
    310    return;
    311  }
    312 
    313  AutoTArray<nsCString, 1> socketTypes;
    314  if (mTls) {
    315    socketTypes.AppendElement("ssl"_ns);
    316  }
    317 
    318  nsCOMPtr<nsISocketTransportService> sts =
    319      do_GetService("@mozilla.org/network/socket-transport-service;1");
    320  rv = sts->CreateTransport(socketTypes, host, port, aSocksProxyInfo, nullptr,
    321                            getter_AddRefs(mTransport));
    322  if (NS_WARN_IF(NS_FAILED(rv))) {
    323    CloseWithReason(rv);
    324    return;
    325  }
    326 
    327  mTransport->SetReuseAddrPort(true);
    328 
    329  PRNetAddr prAddr;
    330  if (NS_WARN_IF(PR_SUCCESS !=
    331                 PR_InitializeNetAddr(PR_IpAddrAny, mLocalPort, &prAddr))) {
    332    CloseWithReason(NS_ERROR_FAILURE);
    333    return;
    334  }
    335 
    336  if (NS_WARN_IF(PR_SUCCESS !=
    337                 PR_StringToNetAddr(mLocalAddress.BeginReading(), &prAddr))) {
    338    CloseWithReason(NS_ERROR_FAILURE);
    339    return;
    340  }
    341 
    342  mozilla::net::NetAddr addr(&prAddr);
    343  rv = mTransport->Bind(&addr);
    344  if (NS_WARN_IF(NS_FAILED(rv))) {
    345    CloseWithReason(rv);
    346    return;
    347  }
    348 
    349  // Binding to a V4 address is not sufficient to cause this socket to use
    350  // V4, and the same goes for V6. So, we disable as needed here.
    351  uint32_t flags = 0;
    352  if (addr.raw.family == AF_INET) {
    353    flags |= nsISocketTransport::DISABLE_IPV6;
    354  } else if (addr.raw.family == AF_INET6) {
    355    flags |= nsISocketTransport::DISABLE_IPV4;
    356  } else {
    357    MOZ_CRASH();
    358  }
    359 
    360  mTransport->SetConnectionFlags(flags);
    361 
    362  nsCOMPtr<nsIInputStream> socketIn;
    363  rv = mTransport->OpenInputStream(0, 0, 0, getter_AddRefs(socketIn));
    364  if (NS_WARN_IF(NS_FAILED(rv))) {
    365    CloseWithReason(rv);
    366    return;
    367  }
    368  mSocketIn = do_QueryInterface(socketIn);
    369  if (NS_WARN_IF(!mSocketIn)) {
    370    CloseWithReason(NS_ERROR_NULL_POINTER);
    371    return;
    372  }
    373 
    374  nsCOMPtr<nsIOutputStream> socketOut;
    375  rv = mTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
    376                                    getter_AddRefs(socketOut));
    377  if (NS_WARN_IF(NS_FAILED(rv))) {
    378    CloseWithReason(rv);
    379    return;
    380  }
    381  mSocketOut = do_QueryInterface(socketOut);
    382  if (NS_WARN_IF(!mSocketOut)) {
    383    CloseWithReason(NS_ERROR_NULL_POINTER);
    384    return;
    385  }
    386 
    387  FinishOpen();
    388 }
    389 
    390 nsresult WebrtcTCPSocket::OpenWithHttpProxy() {
    391  MOZ_ASSERT(NS_IsMainThread(), "not on main thread");
    392  LOG(("WebrtcTCPSocket::OpenWithHttpProxy %p\n", this));
    393  nsresult rv;
    394  nsCOMPtr<nsIIOService> ioService;
    395  ioService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
    396  if (NS_FAILED(rv)) {
    397    LOG(("WebrtcTCPSocket %p: io service missing\n", this));
    398    return rv;
    399  }
    400 
    401  nsCOMPtr<nsILoadInfo> loadInfo;
    402 
    403  // FIXME: We don't know the remote type of the process which provided these
    404  // LoadInfoArgs. Pass in `NOT_REMOTE_TYPE` as the origin process to blindly
    405  // accept whatever value was passed by the other side for now, as we aren't
    406  // using it for security checks here.
    407  // If this code ever starts checking the triggering remote type, this needs to
    408  // be changed.
    409  rv = ipc::LoadInfoArgsToLoadInfo(mProxyConfig->loadInfoArgs(),
    410                                   NOT_REMOTE_TYPE, getter_AddRefs(loadInfo));
    411  if (NS_FAILED(rv)) {
    412    LOG(("WebrtcTCPSocket %p: could not init load info\n", this));
    413    return rv;
    414  }
    415 
    416  // -need to always tunnel since we're using a proxy
    417  // -there shouldn't be an opportunity to send cookies, but explicitly disallow
    418  // them anyway.
    419  // -the previous proxy tunnel didn't support redirects e.g. 307. don't need to
    420  // introduce new behavior. can't follow redirects on connect anyway.
    421  nsCOMPtr<nsIChannel> localChannel;
    422  rv = ioService->NewChannelFromURIWithProxyFlags(
    423      mURI, mURI,
    424      nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
    425          nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
    426      loadInfo->LoadingNode(), loadInfo->GetLoadingPrincipal(),
    427      loadInfo->TriggeringPrincipal(),
    428      // We need this flag to allow loads from any origin since this channel
    429      // is being used to CONNECT to an HTTP proxy.
    430      nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
    431      nsIContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA,
    432      getter_AddRefs(localChannel));
    433  if (NS_FAILED(rv)) {
    434    LOG(("WebrtcTCPSocket %p: bad open channel\n", this));
    435    return rv;
    436  }
    437 
    438  nsCOMPtr<nsILoadInfo> channelLoadInfo = localChannel->LoadInfo();
    439  nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
    440  loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
    441  channelLoadInfo->SetCookieJarSettings(cookieJarSettings);
    442 
    443  RefPtr<nsHttpChannel> httpChannel;
    444  CallQueryInterface(localChannel, httpChannel.StartAssignment());
    445 
    446  if (!httpChannel) {
    447    LOG(("WebrtcTCPSocket %p: not an http channel\n", this));
    448    return NS_ERROR_FAILURE;
    449  }
    450 
    451  if (!mTls &&
    452      mozilla::StaticPrefs::media_webrtc_disallow_HTTPS_upgrade_for_TURN()) {
    453    loadInfo->SetSkipHTTPSUpgrade(true);
    454  }
    455 
    456  rv = localChannel->SetLoadInfo(loadInfo);
    457  NS_ENSURE_SUCCESS(rv, rv);
    458 
    459  httpChannel->SetNotificationCallbacks(this);
    460 
    461  // don't block webrtc proxy setup with other requests
    462  // often more than one of these channels will be created all at once by ICE
    463  nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(localChannel);
    464  if (cos) {
    465    cos->AddClassFlags(nsIClassOfService::Unblocked |
    466                       nsIClassOfService::DontThrottle);
    467  } else {
    468    LOG(("WebrtcTCPSocket %p: could not set class of service\n", this));
    469    return NS_ERROR_FAILURE;
    470  }
    471 
    472  rv = httpChannel->HTTPUpgrade(mProxyConfig->alpn(), this);
    473  if (NS_WARN_IF(NS_FAILED(rv))) {
    474    return rv;
    475  }
    476  rv = httpChannel->SetConnectOnly(
    477      mozilla::StaticPrefs::media_webrtc_tls_tunnel_for_all_proxy());
    478  if (NS_WARN_IF(NS_FAILED(rv))) {
    479    return rv;
    480  }
    481 
    482  rv = httpChannel->AsyncOpen(this);
    483 
    484  if (NS_FAILED(rv)) {
    485    LOG(("WebrtcTCPSocket %p: cannot async open\n", this));
    486    return rv;
    487  }
    488 
    489  // This picks back up in OnTransportAvailable once we have connected to the
    490  // proxy, and performed the http upgrade to switch the proxy into passthrough
    491  // mode.
    492 
    493  return NS_OK;
    494 }
    495 
    496 void WebrtcTCPSocket::EnqueueWrite_s(nsTArray<uint8_t>&& aWriteData) {
    497  LOG(("WebrtcTCPSocket::EnqueueWrite %p\n", this));
    498  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
    499 
    500  if (mClosed) {
    501    return;
    502  }
    503 
    504  mWriteQueue.emplace_back(std::move(aWriteData));
    505 
    506  if (mSocketOut) {
    507    mSocketOut->AsyncWait(this, 0, 0, nullptr);
    508  }
    509 }
    510 
    511 void WebrtcTCPSocket::InvokeOnClose(nsresult aReason) {
    512  LOG(("WebrtcTCPSocket::InvokeOnClose %p\n", this));
    513 
    514  if (!NS_IsMainThread()) {
    515    MOZ_ALWAYS_SUCCEEDS(mMainThread->Dispatch(
    516        NewRunnableMethod<nsresult>("WebrtcTCPSocket::InvokeOnClose", this,
    517                                    &WebrtcTCPSocket::InvokeOnClose, aReason)));
    518    return;
    519  }
    520 
    521  MOZ_ASSERT(mProxyCallbacks, "webrtc TCP callback should be non-null");
    522 
    523  if (mProxyRequest) {
    524    mProxyRequest->Cancel(aReason);
    525    mProxyRequest = nullptr;
    526  }
    527 
    528  mProxyCallbacks->OnClose(aReason);
    529  mProxyCallbacks = nullptr;
    530 }
    531 
    532 void WebrtcTCPSocket::InvokeOnConnected() {
    533  LOG(("WebrtcTCPSocket::InvokeOnConnected %p\n", this));
    534 
    535  if (!NS_IsMainThread()) {
    536    MOZ_ALWAYS_SUCCEEDS(mMainThread->Dispatch(
    537        NewRunnableMethod("WebrtcTCPSocket::InvokeOnConnected", this,
    538                          &WebrtcTCPSocket::InvokeOnConnected)));
    539    return;
    540  }
    541 
    542  MOZ_ASSERT(mProxyCallbacks, "webrtc TCP callback should be non-null");
    543 
    544  mProxyCallbacks->OnConnected(mProxyType);
    545 }
    546 
    547 void WebrtcTCPSocket::InvokeOnRead(nsTArray<uint8_t>&& aReadData) {
    548  LOG(("WebrtcTCPSocket::InvokeOnRead %p count=%zu\n", this,
    549       aReadData.Length()));
    550 
    551  if (!NS_IsMainThread()) {
    552    MOZ_ALWAYS_SUCCEEDS(
    553        mMainThread->Dispatch(NewRunnableMethod<nsTArray<uint8_t>&&>(
    554            "WebrtcTCPSocket::InvokeOnRead", this,
    555            &WebrtcTCPSocket::InvokeOnRead, std::move(aReadData))));
    556    return;
    557  }
    558 
    559  MOZ_ASSERT(mProxyCallbacks, "webrtc TCP callback should be non-null");
    560 
    561  mProxyCallbacks->OnRead(std::move(aReadData));
    562 }
    563 
    564 // nsIHttpUpgradeListener
    565 NS_IMETHODIMP
    566 WebrtcTCPSocket::OnTransportAvailable(nsISocketTransport* aTransport,
    567                                      nsIAsyncInputStream* aSocketIn,
    568                                      nsIAsyncOutputStream* aSocketOut) {
    569  // This is called only in the http proxy case, once we have connected to the
    570  // http proxy and performed the http upgrade to switch it over to passthrough
    571  // mode. That process is started async by OpenWithHttpProxy.
    572  LOG(("WebrtcTCPSocket::OnTransportAvailable %p\n", this));
    573  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
    574  MOZ_ASSERT(!mTransport,
    575             "already called transport available on webrtc TCP socket");
    576 
    577  // Cancel any pending callbacks. The caller doesn't always cancel these
    578  // awaits. We need to make sure they don't get them.
    579  aSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
    580  aSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
    581 
    582  if (mClosed) {
    583    LOG(("WebrtcTCPSocket::OnTransportAvailable %p closed\n", this));
    584    return NS_OK;
    585  }
    586 
    587  mTransport = aTransport;
    588  mSocketIn = aSocketIn;
    589  mSocketOut = aSocketOut;
    590 
    591  // pulled from nr_socket_prsock.cpp
    592  uint32_t minBufferSize = 256 * 1024;
    593  nsresult rv = mTransport->SetSendBufferSize(minBufferSize);
    594  if (NS_FAILED(rv)) {
    595    LOG(("WebrtcProxyChannel::OnTransportAvailable %p send failed\n", this));
    596    CloseWithReason(rv);
    597    return rv;
    598  }
    599  rv = mTransport->SetRecvBufferSize(minBufferSize);
    600  if (NS_FAILED(rv)) {
    601    LOG(("WebrtcProxyChannel::OnTransportAvailable %p recv failed\n", this));
    602    CloseWithReason(rv);
    603    return rv;
    604  }
    605 
    606  FinishOpen();
    607  return NS_OK;
    608 }
    609 
    610 void WebrtcTCPSocket::FinishOpen() {
    611  MOZ_ASSERT(OnSocketThread());
    612  // mTransport, mSocketIn, and mSocketOut are all set. We may have set them in
    613  // OnTransportAvailable (in the http/https proxy case), or in
    614  // OpenWithoutHttpProxy. From here on out, this class functions the same for
    615  // these two cases.
    616 
    617  mSocketIn->AsyncWait(this, 0, 0, nullptr);
    618 
    619  InvokeOnConnected();
    620 }
    621 
    622 NS_IMETHODIMP
    623 WebrtcTCPSocket::OnUpgradeFailed(nsresult aErrorCode) {
    624  LOG(("WebrtcTCPSocket::OnUpgradeFailed %p\n", this));
    625  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
    626  MOZ_ASSERT(!mTransport,
    627             "already called transport available on webrtc TCP socket");
    628 
    629  if (mClosed) {
    630    LOG(("WebrtcTCPSocket::OnUpgradeFailed %p closed\n", this));
    631    return NS_OK;
    632  }
    633 
    634  CloseWithReason(aErrorCode);
    635 
    636  return NS_OK;
    637 }
    638 
    639 NS_IMETHODIMP
    640 WebrtcTCPSocket::OnWebSocketConnectionAvailable(
    641    WebSocketConnectionBase* aConnection) {
    642  return NS_ERROR_NOT_IMPLEMENTED;
    643 }
    644 
    645 // nsIRequestObserver (from nsIStreamListener)
    646 NS_IMETHODIMP
    647 WebrtcTCPSocket::OnStartRequest(nsIRequest* aRequest) {
    648  LOG(("WebrtcTCPSocket::OnStartRequest %p\n", this));
    649 
    650  return NS_OK;
    651 }
    652 
    653 NS_IMETHODIMP
    654 WebrtcTCPSocket::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
    655  LOG(("WebrtcTCPSocket::OnStopRequest %p status=%u\n", this,
    656       static_cast<uint32_t>(aStatusCode)));
    657 
    658  // see nsHttpChannel::ProcessFailedProxyConnect for most error codes
    659  if (NS_FAILED(aStatusCode)) {
    660    CloseWithReason(aStatusCode);
    661    return aStatusCode;
    662  }
    663 
    664  return NS_OK;
    665 }
    666 
    667 // nsIStreamListener
    668 NS_IMETHODIMP
    669 WebrtcTCPSocket::OnDataAvailable(nsIRequest* aRequest,
    670                                 nsIInputStream* aInputStream, uint64_t aOffset,
    671                                 uint32_t aCount) {
    672  LOG(("WebrtcTCPSocket::OnDataAvailable %p count=%u\n", this, aCount));
    673  MOZ_ASSERT(0, "unreachable data available");
    674  return NS_OK;
    675 }
    676 
    677 // nsIInputStreamCallback
    678 NS_IMETHODIMP
    679 WebrtcTCPSocket::OnInputStreamReady(nsIAsyncInputStream* in) {
    680  LOG(("WebrtcTCPSocket::OnInputStreamReady %p unwritten=%zu\n", this,
    681       CountUnwrittenBytes()));
    682  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
    683  MOZ_ASSERT(!mClosed, "webrtc TCP socket closed");
    684  MOZ_ASSERT(mTransport, "webrtc TCP socket not connected");
    685  MOZ_ASSERT(mSocketIn == in, "wrong input stream");
    686 
    687  while (true) {
    688    char buffer[9216];
    689    uint32_t remainingCapacity = sizeof(buffer);
    690    uint32_t read = 0;
    691 
    692    while (remainingCapacity > 0) {
    693      uint32_t count = 0;
    694      nsresult rv = mSocketIn->Read(buffer + read, remainingCapacity, &count);
    695      if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
    696        break;
    697      }
    698 
    699      if (NS_FAILED(rv)) {
    700        LOG(("WebrtcTCPSocket::OnInputStreamReady %p failed %u\n", this,
    701             static_cast<uint32_t>(rv)));
    702        CloseWithReason(rv);
    703        return rv;
    704      }
    705 
    706      // base stream closed
    707      if (count == 0) {
    708        LOG(("WebrtcTCPSocket::OnInputStreamReady %p connection closed\n",
    709             this));
    710        CloseWithReason(NS_ERROR_FAILURE);
    711        return NS_OK;
    712      }
    713 
    714      remainingCapacity -= count;
    715      read += count;
    716    }
    717 
    718    if (read > 0) {
    719      nsTArray<uint8_t> array(read);
    720      array.AppendElements(buffer, read);
    721 
    722      InvokeOnRead(std::move(array));
    723    }
    724 
    725    if (remainingCapacity != 0) {
    726      // Loop exited above, but not because we ran out of space. We're actually
    727      // done, break out of the while(true) loop.
    728      break;
    729    }
    730  }
    731 
    732  mSocketIn->AsyncWait(this, 0, 0, nullptr);
    733  return NS_OK;
    734 }
    735 
    736 // nsIOutputStreamCallback
    737 NS_IMETHODIMP
    738 WebrtcTCPSocket::OnOutputStreamReady(nsIAsyncOutputStream* out) {
    739  LOG(("WebrtcTCPSocket::OnOutputStreamReady %p unwritten=%zu\n", this,
    740       CountUnwrittenBytes()));
    741  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
    742  MOZ_ASSERT(!mClosed, "webrtc TCP socket closed");
    743  MOZ_ASSERT(mTransport, "webrtc TCP socket not connected");
    744  MOZ_ASSERT(mSocketOut == out, "wrong output stream");
    745 
    746  while (!mWriteQueue.empty()) {
    747    const WebrtcTCPData& data = mWriteQueue.front();
    748 
    749    char* buffer = reinterpret_cast<char*>(
    750                       const_cast<uint8_t*>(data.GetData().Elements())) +
    751                   mWriteOffset;
    752    uint32_t toWrite = data.GetData().Length() - mWriteOffset;
    753 
    754    uint32_t wrote = 0;
    755    nsresult rv = mSocketOut->Write(buffer, toWrite, &wrote);
    756    if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
    757      mSocketOut->AsyncWait(this, 0, 0, nullptr);
    758      return NS_OK;
    759    }
    760 
    761    if (NS_FAILED(rv)) {
    762      LOG(("WebrtcTCPSocket::OnOutputStreamReady %p failed %u\n", this,
    763           static_cast<uint32_t>(rv)));
    764      CloseWithReason(rv);
    765      return NS_OK;
    766    }
    767 
    768    mWriteOffset += wrote;
    769 
    770    if (toWrite == wrote) {
    771      mWriteOffset = 0;
    772      mWriteQueue.pop_front();
    773    }
    774  }
    775 
    776  return NS_OK;
    777 }
    778 
    779 // nsIInterfaceRequestor
    780 NS_IMETHODIMP
    781 WebrtcTCPSocket::GetInterface(const nsIID& iid, void** result) {
    782  LOG(("WebrtcTCPSocket::GetInterface %p\n", this));
    783 
    784  return QueryInterface(iid, result);
    785 }
    786 
    787 size_t WebrtcTCPSocket::CountUnwrittenBytes() const {
    788  size_t count = 0;
    789 
    790  for (const WebrtcTCPData& data : mWriteQueue) {
    791    count += data.GetData().Length();
    792  }
    793 
    794  MOZ_ASSERT(count >= mWriteOffset, "offset exceeds write buffer length");
    795 
    796  count -= mWriteOffset;
    797 
    798  return count;
    799 }
    800 
    801 }  // namespace mozilla::net