https://github.com/mozilla/gecko-dev
Raw File
Tip revision: b0b7a0204fbc245c8f8b09dfac65051e9a1648bb authored by Ryan VanderMeulen on 04 March 2022, 17:12:26 UTC
No bug - Set version number to 91.6.1. a=release
Tip revision: b0b7a02
Dashboard.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 "mozilla/dom/NetDashboardBinding.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/ErrorNames.h"
#include "mozilla/net/Dashboard.h"
#include "mozilla/net/HttpInfo.h"
#include "mozilla/net/HTTPSSVC.h"
#include "mozilla/net/SocketProcessParent.h"
#include "nsHttp.h"
#include "nsICancelable.h"
#include "nsIDNSListener.h"
#include "nsIDNSService.h"
#include "nsIDNSRecord.h"
#include "nsIDNSByTypeRecord.h"
#include "nsIInputStream.h"
#include "nsINamed.h"
#include "nsINetAddr.h"
#include "nsISocketTransport.h"
#include "nsProxyRelease.h"
#include "nsSocketTransportService2.h"
#include "nsThreadUtils.h"
#include "nsURLHelper.h"
#include "mozilla/Logging.h"
#include "nsIOService.h"
#include "../cache2/CacheFileUtils.h"

using mozilla::AutoSafeJSContext;
using mozilla::dom::Sequence;
using mozilla::dom::ToJSValue;

namespace mozilla {
namespace net {

class SocketData : public nsISupports {
 public:
  NS_DECL_THREADSAFE_ISUPPORTS

  SocketData() = default;

  uint64_t mTotalSent{0};
  uint64_t mTotalRecv{0};
  nsTArray<SocketInfo> mData;
  nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
  nsIEventTarget* mEventTarget{nullptr};

 private:
  virtual ~SocketData() = default;
};

static void GetErrorString(nsresult rv, nsAString& errorString);

NS_IMPL_ISUPPORTS0(SocketData)

class HttpData : public nsISupports {
  virtual ~HttpData() = default;

 public:
  NS_DECL_THREADSAFE_ISUPPORTS

  HttpData() = default;

  nsTArray<HttpRetParams> mData;
  nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
  nsIEventTarget* mEventTarget{nullptr};
};

NS_IMPL_ISUPPORTS0(HttpData)

class WebSocketRequest : public nsISupports {
  virtual ~WebSocketRequest() = default;

 public:
  NS_DECL_THREADSAFE_ISUPPORTS

  WebSocketRequest() = default;

  nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
  nsIEventTarget* mEventTarget{nullptr};
};

NS_IMPL_ISUPPORTS0(WebSocketRequest)

class DnsData : public nsISupports {
  virtual ~DnsData() = default;

 public:
  NS_DECL_THREADSAFE_ISUPPORTS

  DnsData() = default;

  nsTArray<DNSCacheEntries> mData;
  nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
  nsIEventTarget* mEventTarget{nullptr};
};

NS_IMPL_ISUPPORTS0(DnsData)

class ConnectionData : public nsITransportEventSink,
                       public nsITimerCallback,
                       public nsINamed {
  virtual ~ConnectionData() {
    if (mTimer) {
      mTimer->Cancel();
    }
  }

 public:
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSITRANSPORTEVENTSINK
  NS_DECL_NSITIMERCALLBACK

  NS_IMETHOD GetName(nsACString& aName) override {
    aName.AssignLiteral("net::ConnectionData");
    return NS_OK;
  }

  void StartTimer(uint32_t aTimeout);
  void StopTimer();

  explicit ConnectionData(Dashboard* target) { mDashboard = target; }

  nsCOMPtr<nsISocketTransport> mSocket;
  nsCOMPtr<nsIInputStream> mStreamIn;
  nsCOMPtr<nsITimer> mTimer;
  nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
  nsIEventTarget* mEventTarget{nullptr};
  Dashboard* mDashboard;

  nsCString mHost;
  uint32_t mPort{0};
  nsCString mProtocol;
  uint32_t mTimeout{0};

  nsString mStatus;
};

NS_IMPL_ISUPPORTS(ConnectionData, nsITransportEventSink, nsITimerCallback,
                  nsINamed)

class RcwnData : public nsISupports {
  virtual ~RcwnData() = default;

 public:
  NS_DECL_THREADSAFE_ISUPPORTS

  RcwnData() = default;

  nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
  nsIEventTarget* mEventTarget{nullptr};
};

NS_IMPL_ISUPPORTS0(RcwnData)

NS_IMETHODIMP
ConnectionData::OnTransportStatus(nsITransport* aTransport, nsresult aStatus,
                                  int64_t aProgress, int64_t aProgressMax) {
  if (aStatus == NS_NET_STATUS_CONNECTED_TO) {
    StopTimer();
  }

  GetErrorString(aStatus, mStatus);
  mEventTarget->Dispatch(NewRunnableMethod<RefPtr<ConnectionData>>(
                             "net::Dashboard::GetConnectionStatus", mDashboard,
                             &Dashboard::GetConnectionStatus, this),
                         NS_DISPATCH_NORMAL);

  return NS_OK;
}

NS_IMETHODIMP
ConnectionData::Notify(nsITimer* aTimer) {
  MOZ_ASSERT(aTimer == mTimer);

  if (mSocket) {
    mSocket->Close(NS_ERROR_ABORT);
    mSocket = nullptr;
    mStreamIn = nullptr;
  }

  mTimer = nullptr;

  mStatus.AssignLiteral(u"NS_ERROR_NET_TIMEOUT");
  mEventTarget->Dispatch(NewRunnableMethod<RefPtr<ConnectionData>>(
                             "net::Dashboard::GetConnectionStatus", mDashboard,
                             &Dashboard::GetConnectionStatus, this),
                         NS_DISPATCH_NORMAL);

  return NS_OK;
}

void ConnectionData::StartTimer(uint32_t aTimeout) {
  if (!mTimer) {
    mTimer = NS_NewTimer();
  }

  mTimer->InitWithCallback(this, aTimeout * 1000, nsITimer::TYPE_ONE_SHOT);
}

void ConnectionData::StopTimer() {
  if (mTimer) {
    mTimer->Cancel();
    mTimer = nullptr;
  }
}

class LookupHelper;

class LookupArgument : public nsISupports {
  virtual ~LookupArgument() = default;

 public:
  NS_DECL_THREADSAFE_ISUPPORTS

  LookupArgument(nsIDNSRecord* aRecord, LookupHelper* aHelper) {
    mRecord = aRecord;
    mHelper = aHelper;
  }

  nsCOMPtr<nsIDNSRecord> mRecord;
  RefPtr<LookupHelper> mHelper;
};

NS_IMPL_ISUPPORTS0(LookupArgument)

class LookupHelper final : public nsIDNSListener {
  virtual ~LookupHelper() {
    if (mCancel) {
      mCancel->Cancel(NS_ERROR_ABORT);
    }
  }

 public:
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSIDNSLISTENER

  LookupHelper() = default;

  nsresult ConstructAnswer(LookupArgument* aArgument);
  nsresult ConstructHTTPSRRAnswer(LookupArgument* aArgument);

 public:
  nsCOMPtr<nsICancelable> mCancel;
  nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
  nsIEventTarget* mEventTarget{nullptr};
  nsresult mStatus{NS_ERROR_NOT_INITIALIZED};
};

NS_IMPL_ISUPPORTS(LookupHelper, nsIDNSListener)

NS_IMETHODIMP
LookupHelper::OnLookupComplete(nsICancelable* aRequest, nsIDNSRecord* aRecord,
                               nsresult aStatus) {
  MOZ_ASSERT(aRequest == mCancel);
  mCancel = nullptr;
  mStatus = aStatus;

  nsCOMPtr<nsIDNSHTTPSSVCRecord> httpsRecord = do_QueryInterface(aRecord);
  if (httpsRecord) {
    RefPtr<LookupArgument> arg = new LookupArgument(aRecord, this);
    mEventTarget->Dispatch(
        NewRunnableMethod<RefPtr<LookupArgument>>(
            "net::LookupHelper::ConstructHTTPSRRAnswer", this,
            &LookupHelper::ConstructHTTPSRRAnswer, arg),
        NS_DISPATCH_NORMAL);
    return NS_OK;
  }

  RefPtr<LookupArgument> arg = new LookupArgument(aRecord, this);
  mEventTarget->Dispatch(NewRunnableMethod<RefPtr<LookupArgument>>(
                             "net::LookupHelper::ConstructAnswer", this,
                             &LookupHelper::ConstructAnswer, arg),
                         NS_DISPATCH_NORMAL);

  return NS_OK;
}

nsresult LookupHelper::ConstructAnswer(LookupArgument* aArgument) {
  nsIDNSRecord* aRecord = aArgument->mRecord;
  AutoSafeJSContext cx;

  mozilla::dom::DNSLookupDict dict;
  dict.mAddress.Construct();

  Sequence<nsString>& addresses = dict.mAddress.Value();
  nsCOMPtr<nsIDNSAddrRecord> record = do_QueryInterface(aRecord);
  if (NS_SUCCEEDED(mStatus) && record) {
    dict.mAnswer = true;
    bool hasMore;
    record->HasMore(&hasMore);
    while (hasMore) {
      nsString* nextAddress = addresses.AppendElement(fallible);
      if (!nextAddress) {
        return NS_ERROR_OUT_OF_MEMORY;
      }

      nsCString nextAddressASCII;
      record->GetNextAddrAsString(nextAddressASCII);
      CopyASCIItoUTF16(nextAddressASCII, *nextAddress);
      record->HasMore(&hasMore);
    }
  } else {
    dict.mAnswer = false;
    GetErrorString(mStatus, dict.mError);
  }

  JS::RootedValue val(cx);
  if (!ToJSValue(cx, dict, &val)) {
    return NS_ERROR_FAILURE;
  }

  this->mCallback->OnDashboardDataAvailable(val);

  return NS_OK;
}

static void CStringToHexString(const nsACString& aIn, nsAString& aOut) {
  static const char* const lut = "0123456789ABCDEF";

  size_t len = aIn.Length();

  aOut.SetCapacity(2 * len);
  for (size_t i = 0; i < aIn.Length(); ++i) {
    const char c = static_cast<char>(aIn[i]);
    aOut.Append(lut[(c >> 4) & 0x0F]);
    aOut.Append(lut[c & 15]);
  }
}

nsresult LookupHelper::ConstructHTTPSRRAnswer(LookupArgument* aArgument) {
  nsCOMPtr<nsIDNSHTTPSSVCRecord> httpsRecord =
      do_QueryInterface(aArgument->mRecord);

  AutoSafeJSContext cx;

  mozilla::dom::HTTPSRRLookupDict dict;
  dict.mRecords.Construct();

  Sequence<dom::HTTPSRecord>& records = dict.mRecords.Value();
  if (NS_SUCCEEDED(mStatus) && httpsRecord) {
    dict.mAnswer = true;
    nsTArray<RefPtr<nsISVCBRecord>> svcbRecords;
    httpsRecord->GetRecords(svcbRecords);

    for (const auto& record : svcbRecords) {
      dom::HTTPSRecord* nextRecord = records.AppendElement(fallible);
      if (!nextRecord) {
        return NS_ERROR_OUT_OF_MEMORY;
      }

      Unused << record->GetPriority(&nextRecord->mPriority);
      nsCString name;
      Unused << record->GetName(name);
      CopyASCIItoUTF16(name, nextRecord->mTargetName);

      nsTArray<RefPtr<nsISVCParam>> values;
      Unused << record->GetValues(values);
      if (values.IsEmpty()) {
        continue;
      }

      for (const auto& value : values) {
        uint16_t type;
        Unused << value->GetType(&type);
        switch (type) {
          case SvcParamKeyAlpn: {
            nextRecord->mAlpn.Construct();
            nextRecord->mAlpn.Value().mType = type;
            nsCOMPtr<nsISVCParamAlpn> alpnParam = do_QueryInterface(value);
            nsTArray<nsCString> alpn;
            Unused << alpnParam->GetAlpn(alpn);
            nsAutoCString alpnStr;
            for (const auto& str : alpn) {
              alpnStr.Append(str);
              alpnStr.Append(',');
            }
            CopyASCIItoUTF16(Span(alpnStr.BeginReading(), alpnStr.Length() - 1),
                             nextRecord->mAlpn.Value().mAlpn);
            break;
          }
          case SvcParamKeyNoDefaultAlpn: {
            nextRecord->mNoDefaultAlpn.Construct();
            nextRecord->mNoDefaultAlpn.Value().mType = type;
            break;
          }
          case SvcParamKeyPort: {
            nextRecord->mPort.Construct();
            nextRecord->mPort.Value().mType = type;
            nsCOMPtr<nsISVCParamPort> portParam = do_QueryInterface(value);
            Unused << portParam->GetPort(&nextRecord->mPort.Value().mPort);
            break;
          }
          case SvcParamKeyIpv4Hint: {
            nextRecord->mIpv4Hint.Construct();
            nextRecord->mIpv4Hint.Value().mType = type;
            nsCOMPtr<nsISVCParamIPv4Hint> ipv4Param = do_QueryInterface(value);
            nsTArray<RefPtr<nsINetAddr>> ipv4Hint;
            Unused << ipv4Param->GetIpv4Hint(ipv4Hint);
            if (!ipv4Hint.IsEmpty()) {
              nextRecord->mIpv4Hint.Value().mAddress.Construct();
              for (const auto& address : ipv4Hint) {
                nsString* nextAddress = nextRecord->mIpv4Hint.Value()
                                            .mAddress.Value()
                                            .AppendElement(fallible);
                if (!nextAddress) {
                  return NS_ERROR_OUT_OF_MEMORY;
                }

                nsCString addressASCII;
                Unused << address->GetAddress(addressASCII);
                CopyASCIItoUTF16(addressASCII, *nextAddress);
              }
            }
            break;
          }
          case SvcParamKeyIpv6Hint: {
            nextRecord->mIpv6Hint.Construct();
            nextRecord->mIpv6Hint.Value().mType = type;
            nsCOMPtr<nsISVCParamIPv6Hint> ipv6Param = do_QueryInterface(value);
            nsTArray<RefPtr<nsINetAddr>> ipv6Hint;
            Unused << ipv6Param->GetIpv6Hint(ipv6Hint);
            if (!ipv6Hint.IsEmpty()) {
              nextRecord->mIpv6Hint.Value().mAddress.Construct();
              for (const auto& address : ipv6Hint) {
                nsString* nextAddress = nextRecord->mIpv6Hint.Value()
                                            .mAddress.Value()
                                            .AppendElement(fallible);
                if (!nextAddress) {
                  return NS_ERROR_OUT_OF_MEMORY;
                }

                nsCString addressASCII;
                Unused << address->GetAddress(addressASCII);
                CopyASCIItoUTF16(addressASCII, *nextAddress);
              }
            }
            break;
          }
          case SvcParamKeyEchConfig: {
            nextRecord->mEchConfig.Construct();
            nextRecord->mEchConfig.Value().mType = type;
            nsCOMPtr<nsISVCParamEchConfig> echConfigParam =
                do_QueryInterface(value);
            nsCString echConfigStr;
            Unused << echConfigParam->GetEchconfig(echConfigStr);
            CStringToHexString(echConfigStr,
                               nextRecord->mEchConfig.Value().mEchConfig);
            break;
          }
          case SvcParamKeyODoHConfig: {
            nextRecord->mODoHConfig.Construct();
            nextRecord->mODoHConfig.Value().mType = type;
            nsCOMPtr<nsISVCParamODoHConfig> ODoHConfigParam =
                do_QueryInterface(value);
            nsCString ODoHConfigStr;
            Unused << ODoHConfigParam->GetODoHConfig(ODoHConfigStr);
            CStringToHexString(ODoHConfigStr,
                               nextRecord->mODoHConfig.Value().mODoHConfig);
            break;
          }
          default:
            break;
        }
      }
    }
  } else {
    dict.mAnswer = false;
    GetErrorString(mStatus, dict.mError);
  }

  JS::RootedValue val(cx);
  if (!ToJSValue(cx, dict, &val)) {
    return NS_ERROR_FAILURE;
  }

  this->mCallback->OnDashboardDataAvailable(val);

  return NS_OK;
}

NS_IMPL_ISUPPORTS(Dashboard, nsIDashboard, nsIDashboardEventNotifier)

Dashboard::Dashboard() { mEnableLogging = false; }

NS_IMETHODIMP
Dashboard::RequestSockets(nsINetDashboardCallback* aCallback) {
  RefPtr<SocketData> socketData = new SocketData();
  socketData->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
      "nsINetDashboardCallback", aCallback, true);
  socketData->mEventTarget = GetCurrentEventTarget();

  if (nsIOService::UseSocketProcess()) {
    if (!gIOService->SocketProcessReady()) {
      return NS_ERROR_NOT_AVAILABLE;
    }

    RefPtr<Dashboard> self(this);
    SocketProcessParent::GetSingleton()->SendGetSocketData()->Then(
        GetMainThreadSerialEventTarget(), __func__,
        [self{std::move(self)},
         socketData{std::move(socketData)}](SocketDataArgs&& args) {
          socketData->mData.Assign(args.info());
          socketData->mTotalSent = args.totalSent();
          socketData->mTotalRecv = args.totalRecv();
          socketData->mEventTarget->Dispatch(
              NewRunnableMethod<RefPtr<SocketData>>(
                  "net::Dashboard::GetSockets", self, &Dashboard::GetSockets,
                  socketData),
              NS_DISPATCH_NORMAL);
        },
        [self](const mozilla::ipc::ResponseRejectReason) {});
    return NS_OK;
  }

  gSocketTransportService->Dispatch(
      NewRunnableMethod<RefPtr<SocketData>>(
          "net::Dashboard::GetSocketsDispatch", this,
          &Dashboard::GetSocketsDispatch, socketData),
      NS_DISPATCH_NORMAL);
  return NS_OK;
}

nsresult Dashboard::GetSocketsDispatch(SocketData* aSocketData) {
  RefPtr<SocketData> socketData = aSocketData;
  if (gSocketTransportService) {
    gSocketTransportService->GetSocketConnections(&socketData->mData);
    socketData->mTotalSent = gSocketTransportService->GetSentBytes();
    socketData->mTotalRecv = gSocketTransportService->GetReceivedBytes();
  }
  socketData->mEventTarget->Dispatch(
      NewRunnableMethod<RefPtr<SocketData>>("net::Dashboard::GetSockets", this,
                                            &Dashboard::GetSockets, socketData),
      NS_DISPATCH_NORMAL);
  return NS_OK;
}

nsresult Dashboard::GetSockets(SocketData* aSocketData) {
  RefPtr<SocketData> socketData = aSocketData;
  AutoSafeJSContext cx;

  mozilla::dom::SocketsDict dict;
  dict.mSockets.Construct();
  dict.mSent = 0;
  dict.mReceived = 0;

  Sequence<mozilla::dom::SocketElement>& sockets = dict.mSockets.Value();

  uint32_t length = socketData->mData.Length();
  if (!sockets.SetCapacity(length, fallible)) {
    JS_ReportOutOfMemory(cx);
    return NS_ERROR_OUT_OF_MEMORY;
  }

  for (uint32_t i = 0; i < socketData->mData.Length(); i++) {
    dom::SocketElement& mSocket = *sockets.AppendElement(fallible);
    CopyASCIItoUTF16(socketData->mData[i].host, mSocket.mHost);
    mSocket.mPort = socketData->mData[i].port;
    mSocket.mActive = socketData->mData[i].active;
    mSocket.mTcp = socketData->mData[i].tcp;
    mSocket.mSent = (double)socketData->mData[i].sent;
    mSocket.mReceived = (double)socketData->mData[i].received;
    dict.mSent += socketData->mData[i].sent;
    dict.mReceived += socketData->mData[i].received;
  }

  dict.mSent += socketData->mTotalSent;
  dict.mReceived += socketData->mTotalRecv;
  JS::RootedValue val(cx);
  if (!ToJSValue(cx, dict, &val)) return NS_ERROR_FAILURE;
  socketData->mCallback->OnDashboardDataAvailable(val);

  return NS_OK;
}

NS_IMETHODIMP
Dashboard::RequestHttpConnections(nsINetDashboardCallback* aCallback) {
  RefPtr<HttpData> httpData = new HttpData();
  httpData->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
      "nsINetDashboardCallback", aCallback, true);
  httpData->mEventTarget = GetCurrentEventTarget();

  if (nsIOService::UseSocketProcess()) {
    if (!gIOService->SocketProcessReady()) {
      return NS_ERROR_NOT_AVAILABLE;
    }

    RefPtr<Dashboard> self(this);
    SocketProcessParent::GetSingleton()->SendGetHttpConnectionData()->Then(
        GetMainThreadSerialEventTarget(), __func__,
        [self{std::move(self)}, httpData](nsTArray<HttpRetParams>&& params) {
          httpData->mData.Assign(std::move(params));
          self->GetHttpConnections(httpData);
          httpData->mEventTarget->Dispatch(
              NewRunnableMethod<RefPtr<HttpData>>(
                  "net::Dashboard::GetHttpConnections", self,
                  &Dashboard::GetHttpConnections, httpData),
              NS_DISPATCH_NORMAL);
        },
        [self](const mozilla::ipc::ResponseRejectReason) {});
    return NS_OK;
  }

  gSocketTransportService->Dispatch(NewRunnableMethod<RefPtr<HttpData>>(
                                        "net::Dashboard::GetHttpDispatch", this,
                                        &Dashboard::GetHttpDispatch, httpData),
                                    NS_DISPATCH_NORMAL);
  return NS_OK;
}

nsresult Dashboard::GetHttpDispatch(HttpData* aHttpData) {
  RefPtr<HttpData> httpData = aHttpData;
  HttpInfo::GetHttpConnectionData(&httpData->mData);
  httpData->mEventTarget->Dispatch(
      NewRunnableMethod<RefPtr<HttpData>>("net::Dashboard::GetHttpConnections",
                                          this, &Dashboard::GetHttpConnections,
                                          httpData),
      NS_DISPATCH_NORMAL);
  return NS_OK;
}

nsresult Dashboard::GetHttpConnections(HttpData* aHttpData) {
  RefPtr<HttpData> httpData = aHttpData;
  AutoSafeJSContext cx;

  mozilla::dom::HttpConnDict dict;
  dict.mConnections.Construct();

  using mozilla::dom::DnsAndSockInfoDict;
  using mozilla::dom::HttpConnectionElement;
  using mozilla::dom::HttpConnInfo;
  Sequence<HttpConnectionElement>& connections = dict.mConnections.Value();

  uint32_t length = httpData->mData.Length();
  if (!connections.SetCapacity(length, fallible)) {
    JS_ReportOutOfMemory(cx);
    return NS_ERROR_OUT_OF_MEMORY;
  }

  for (uint32_t i = 0; i < httpData->mData.Length(); i++) {
    HttpConnectionElement& connection = *connections.AppendElement(fallible);

    CopyASCIItoUTF16(httpData->mData[i].host, connection.mHost);
    connection.mPort = httpData->mData[i].port;
    CopyASCIItoUTF16(httpData->mData[i].httpVersion, connection.mHttpVersion);
    connection.mSsl = httpData->mData[i].ssl;

    connection.mActive.Construct();
    connection.mIdle.Construct();
    connection.mDnsAndSocks.Construct();

    Sequence<HttpConnInfo>& active = connection.mActive.Value();
    Sequence<HttpConnInfo>& idle = connection.mIdle.Value();
    Sequence<DnsAndSockInfoDict>& dnsAndSocks = connection.mDnsAndSocks.Value();

    if (!active.SetCapacity(httpData->mData[i].active.Length(), fallible) ||
        !idle.SetCapacity(httpData->mData[i].idle.Length(), fallible) ||
        !dnsAndSocks.SetCapacity(httpData->mData[i].dnsAndSocks.Length(),
                                 fallible)) {
      JS_ReportOutOfMemory(cx);
      return NS_ERROR_OUT_OF_MEMORY;
    }

    for (uint32_t j = 0; j < httpData->mData[i].active.Length(); j++) {
      HttpConnInfo& info = *active.AppendElement(fallible);
      info.mRtt = httpData->mData[i].active[j].rtt;
      info.mTtl = httpData->mData[i].active[j].ttl;
      info.mProtocolVersion = httpData->mData[i].active[j].protocolVersion;
    }

    for (uint32_t j = 0; j < httpData->mData[i].idle.Length(); j++) {
      HttpConnInfo& info = *idle.AppendElement(fallible);
      info.mRtt = httpData->mData[i].idle[j].rtt;
      info.mTtl = httpData->mData[i].idle[j].ttl;
      info.mProtocolVersion = httpData->mData[i].idle[j].protocolVersion;
    }

    for (uint32_t j = 0; j < httpData->mData[i].dnsAndSocks.Length(); j++) {
      DnsAndSockInfoDict& info = *dnsAndSocks.AppendElement(fallible);
      info.mSpeculative = httpData->mData[i].dnsAndSocks[j].speculative;
    }
  }

  JS::RootedValue val(cx);
  if (!ToJSValue(cx, dict, &val)) {
    return NS_ERROR_FAILURE;
  }

  httpData->mCallback->OnDashboardDataAvailable(val);

  return NS_OK;
}

NS_IMETHODIMP
Dashboard::GetEnableLogging(bool* value) {
  *value = mEnableLogging;
  return NS_OK;
}

NS_IMETHODIMP
Dashboard::SetEnableLogging(const bool value) {
  mEnableLogging = value;
  return NS_OK;
}

NS_IMETHODIMP
Dashboard::AddHost(const nsACString& aHost, uint32_t aSerial, bool aEncrypted) {
  if (mEnableLogging) {
    mozilla::MutexAutoLock lock(mWs.lock);
    LogData mData(nsCString(aHost), aSerial, aEncrypted);
    if (mWs.data.Contains(mData)) {
      return NS_OK;
    }
    // XXX(Bug 1631371) Check if this should use a fallible operation as it
    // pretended earlier.
    mWs.data.AppendElement(mData);
    return NS_OK;
  }
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
Dashboard::RemoveHost(const nsACString& aHost, uint32_t aSerial) {
  if (mEnableLogging) {
    mozilla::MutexAutoLock lock(mWs.lock);
    int32_t index = mWs.IndexOf(nsCString(aHost), aSerial);
    if (index == -1) return NS_ERROR_FAILURE;
    mWs.data.RemoveElementAt(index);
    return NS_OK;
  }
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
Dashboard::NewMsgSent(const nsACString& aHost, uint32_t aSerial,
                      uint32_t aLength) {
  if (mEnableLogging) {
    mozilla::MutexAutoLock lock(mWs.lock);
    int32_t index = mWs.IndexOf(nsCString(aHost), aSerial);
    if (index == -1) return NS_ERROR_FAILURE;
    mWs.data[index].mMsgSent++;
    mWs.data[index].mSizeSent += aLength;
    return NS_OK;
  }
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
Dashboard::NewMsgReceived(const nsACString& aHost, uint32_t aSerial,
                          uint32_t aLength) {
  if (mEnableLogging) {
    mozilla::MutexAutoLock lock(mWs.lock);
    int32_t index = mWs.IndexOf(nsCString(aHost), aSerial);
    if (index == -1) return NS_ERROR_FAILURE;
    mWs.data[index].mMsgReceived++;
    mWs.data[index].mSizeReceived += aLength;
    return NS_OK;
  }
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
Dashboard::RequestWebsocketConnections(nsINetDashboardCallback* aCallback) {
  RefPtr<WebSocketRequest> wsRequest = new WebSocketRequest();
  wsRequest->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
      "nsINetDashboardCallback", aCallback, true);
  wsRequest->mEventTarget = GetCurrentEventTarget();

  wsRequest->mEventTarget->Dispatch(
      NewRunnableMethod<RefPtr<WebSocketRequest>>(
          "net::Dashboard::GetWebSocketConnections", this,
          &Dashboard::GetWebSocketConnections, wsRequest),
      NS_DISPATCH_NORMAL);
  return NS_OK;
}

nsresult Dashboard::GetWebSocketConnections(WebSocketRequest* aWsRequest) {
  RefPtr<WebSocketRequest> wsRequest = aWsRequest;
  AutoSafeJSContext cx;

  mozilla::dom::WebSocketDict dict;
  dict.mWebsockets.Construct();
  Sequence<mozilla::dom::WebSocketElement>& websockets =
      dict.mWebsockets.Value();

  mozilla::MutexAutoLock lock(mWs.lock);
  uint32_t length = mWs.data.Length();
  if (!websockets.SetCapacity(length, fallible)) {
    JS_ReportOutOfMemory(cx);
    return NS_ERROR_OUT_OF_MEMORY;
  }

  for (uint32_t i = 0; i < mWs.data.Length(); i++) {
    dom::WebSocketElement& websocket = *websockets.AppendElement(fallible);
    CopyASCIItoUTF16(mWs.data[i].mHost, websocket.mHostport);
    websocket.mMsgsent = mWs.data[i].mMsgSent;
    websocket.mMsgreceived = mWs.data[i].mMsgReceived;
    websocket.mSentsize = mWs.data[i].mSizeSent;
    websocket.mReceivedsize = mWs.data[i].mSizeReceived;
    websocket.mEncrypted = mWs.data[i].mEncrypted;
  }

  JS::RootedValue val(cx);
  if (!ToJSValue(cx, dict, &val)) {
    return NS_ERROR_FAILURE;
  }
  wsRequest->mCallback->OnDashboardDataAvailable(val);

  return NS_OK;
}

NS_IMETHODIMP
Dashboard::RequestDNSInfo(nsINetDashboardCallback* aCallback) {
  RefPtr<DnsData> dnsData = new DnsData();
  dnsData->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
      "nsINetDashboardCallback", aCallback, true);

  nsresult rv;
  dnsData->mData.Clear();
  dnsData->mEventTarget = GetCurrentEventTarget();

  if (!mDnsService) {
    mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
    if (NS_FAILED(rv)) {
      return rv;
    }
  }

  if (nsIOService::UseSocketProcess()) {
    if (!gIOService->SocketProcessReady()) {
      return NS_ERROR_NOT_AVAILABLE;
    }

    RefPtr<Dashboard> self(this);
    SocketProcessParent::GetSingleton()->SendGetDNSCacheEntries()->Then(
        GetMainThreadSerialEventTarget(), __func__,
        [self{std::move(self)},
         dnsData{std::move(dnsData)}](nsTArray<DNSCacheEntries>&& entries) {
          dnsData->mData.Assign(std::move(entries));
          dnsData->mEventTarget->Dispatch(
              NewRunnableMethod<RefPtr<DnsData>>(
                  "net::Dashboard::GetDNSCacheEntries", self,
                  &Dashboard::GetDNSCacheEntries, dnsData),
              NS_DISPATCH_NORMAL);
        },
        [self](const mozilla::ipc::ResponseRejectReason) {});
    return NS_OK;
  }

  gSocketTransportService->Dispatch(
      NewRunnableMethod<RefPtr<DnsData>>("net::Dashboard::GetDnsInfoDispatch",
                                         this, &Dashboard::GetDnsInfoDispatch,
                                         dnsData),
      NS_DISPATCH_NORMAL);
  return NS_OK;
}

nsresult Dashboard::GetDnsInfoDispatch(DnsData* aDnsData) {
  RefPtr<DnsData> dnsData = aDnsData;
  if (mDnsService) {
    mDnsService->GetDNSCacheEntries(&dnsData->mData);
  }
  dnsData->mEventTarget->Dispatch(
      NewRunnableMethod<RefPtr<DnsData>>("net::Dashboard::GetDNSCacheEntries",
                                         this, &Dashboard::GetDNSCacheEntries,
                                         dnsData),
      NS_DISPATCH_NORMAL);
  return NS_OK;
}

nsresult Dashboard::GetDNSCacheEntries(DnsData* dnsData) {
  AutoSafeJSContext cx;

  mozilla::dom::DNSCacheDict dict;
  dict.mEntries.Construct();
  Sequence<mozilla::dom::DnsCacheEntry>& entries = dict.mEntries.Value();

  uint32_t length = dnsData->mData.Length();
  if (!entries.SetCapacity(length, fallible)) {
    JS_ReportOutOfMemory(cx);
    return NS_ERROR_OUT_OF_MEMORY;
  }

  for (uint32_t i = 0; i < dnsData->mData.Length(); i++) {
    dom::DnsCacheEntry& entry = *entries.AppendElement(fallible);
    entry.mHostaddr.Construct();

    Sequence<nsString>& addrs = entry.mHostaddr.Value();
    if (!addrs.SetCapacity(dnsData->mData[i].hostaddr.Length(), fallible)) {
      JS_ReportOutOfMemory(cx);
      return NS_ERROR_OUT_OF_MEMORY;
    }

    CopyASCIItoUTF16(dnsData->mData[i].hostname, entry.mHostname);
    entry.mExpiration = dnsData->mData[i].expiration;
    entry.mTrr = dnsData->mData[i].TRR;

    for (uint32_t j = 0; j < dnsData->mData[i].hostaddr.Length(); j++) {
      nsString* addr = addrs.AppendElement(fallible);
      if (!addr) {
        JS_ReportOutOfMemory(cx);
        return NS_ERROR_OUT_OF_MEMORY;
      }
      CopyASCIItoUTF16(dnsData->mData[i].hostaddr[j], *addr);
    }

    if (dnsData->mData[i].family == PR_AF_INET6) {
      entry.mFamily.AssignLiteral(u"ipv6");
    } else {
      entry.mFamily.AssignLiteral(u"ipv4");
    }

    entry.mOriginAttributesSuffix =
        NS_ConvertUTF8toUTF16(dnsData->mData[i].originAttributesSuffix);
  }

  JS::RootedValue val(cx);
  if (!ToJSValue(cx, dict, &val)) {
    return NS_ERROR_FAILURE;
  }
  dnsData->mCallback->OnDashboardDataAvailable(val);

  return NS_OK;
}

NS_IMETHODIMP
Dashboard::RequestDNSLookup(const nsACString& aHost,
                            nsINetDashboardCallback* aCallback) {
  nsresult rv;

  if (!mDnsService) {
    mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
    if (NS_FAILED(rv)) {
      return rv;
    }
  }

  RefPtr<LookupHelper> helper = new LookupHelper();
  helper->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
      "nsINetDashboardCallback", aCallback, true);
  helper->mEventTarget = GetCurrentEventTarget();
  OriginAttributes attrs;
  rv = mDnsService->AsyncResolveNative(
      aHost, nsIDNSService::RESOLVE_TYPE_DEFAULT, 0, nullptr, helper.get(),
      NS_GetCurrentThread(), attrs, getter_AddRefs(helper->mCancel));
  return rv;
}

NS_IMETHODIMP
Dashboard::RequestDNSHTTPSRRLookup(const nsACString& aHost,
                                   nsINetDashboardCallback* aCallback) {
  nsresult rv;

  if (!mDnsService) {
    mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
    if (NS_FAILED(rv)) {
      return rv;
    }
  }

  RefPtr<LookupHelper> helper = new LookupHelper();
  helper->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
      "nsINetDashboardCallback", aCallback, true);
  helper->mEventTarget = GetCurrentEventTarget();
  OriginAttributes attrs;
  rv = mDnsService->AsyncResolveNative(
      aHost, nsIDNSService::RESOLVE_TYPE_HTTPSSVC, 0, nullptr, helper.get(),
      NS_GetCurrentThread(), attrs, getter_AddRefs(helper->mCancel));
  return rv;
}

NS_IMETHODIMP
Dashboard::RequestRcwnStats(nsINetDashboardCallback* aCallback) {
  RefPtr<RcwnData> rcwnData = new RcwnData();
  rcwnData->mEventTarget = GetCurrentEventTarget();
  rcwnData->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
      "nsINetDashboardCallback", aCallback, true);

  return rcwnData->mEventTarget->Dispatch(
      NewRunnableMethod<RefPtr<RcwnData>>("net::Dashboard::GetRcwnData", this,
                                          &Dashboard::GetRcwnData, rcwnData),
      NS_DISPATCH_NORMAL);
}

nsresult Dashboard::GetRcwnData(RcwnData* aData) {
  AutoSafeJSContext cx;
  mozilla::dom::RcwnStatus dict;

  dict.mTotalNetworkRequests = gIOService->GetTotalRequestNumber();
  dict.mRcwnCacheWonCount = gIOService->GetCacheWonRequestNumber();
  dict.mRcwnNetWonCount = gIOService->GetNetWonRequestNumber();

  uint32_t cacheSlow, cacheNotSlow;
  CacheFileUtils::CachePerfStats::GetSlowStats(&cacheSlow, &cacheNotSlow);
  dict.mCacheSlowCount = cacheSlow;
  dict.mCacheNotSlowCount = cacheNotSlow;

  dict.mPerfStats.Construct();
  Sequence<mozilla::dom::RcwnPerfStats>& perfStats = dict.mPerfStats.Value();
  uint32_t length = CacheFileUtils::CachePerfStats::LAST;
  if (!perfStats.SetCapacity(length, fallible)) {
    JS_ReportOutOfMemory(cx);
    return NS_ERROR_OUT_OF_MEMORY;
  }

  for (uint32_t i = 0; i < length; i++) {
    CacheFileUtils::CachePerfStats::EDataType perfType =
        static_cast<CacheFileUtils::CachePerfStats::EDataType>(i);
    dom::RcwnPerfStats& elem = *perfStats.AppendElement(fallible);
    elem.mAvgShort =
        CacheFileUtils::CachePerfStats::GetAverage(perfType, false);
    elem.mAvgLong = CacheFileUtils::CachePerfStats::GetAverage(perfType, true);
    elem.mStddevLong =
        CacheFileUtils::CachePerfStats::GetStdDev(perfType, true);
  }

  JS::RootedValue val(cx);
  if (!ToJSValue(cx, dict, &val)) {
    return NS_ERROR_FAILURE;
  }

  aData->mCallback->OnDashboardDataAvailable(val);

  return NS_OK;
}

void HttpConnInfo::SetHTTPProtocolVersion(HttpVersion pv) {
  switch (pv) {
    case HttpVersion::v0_9:
      protocolVersion.AssignLiteral(u"http/0.9");
      break;
    case HttpVersion::v1_0:
      protocolVersion.AssignLiteral(u"http/1.0");
      break;
    case HttpVersion::v1_1:
      protocolVersion.AssignLiteral(u"http/1.1");
      break;
    case HttpVersion::v2_0:
      protocolVersion.AssignLiteral(u"http/2");
      break;
    case HttpVersion::v3_0:
      protocolVersion.AssignLiteral(u"http/3");
      break;
    default:
      protocolVersion.AssignLiteral(u"unknown protocol version");
  }
}

NS_IMETHODIMP
Dashboard::GetLogPath(nsACString& aLogPath) {
  aLogPath.SetLength(2048);
  uint32_t len = LogModule::GetLogFile(aLogPath.BeginWriting(), 2048);
  aLogPath.SetLength(len);
  return NS_OK;
}

NS_IMETHODIMP
Dashboard::RequestConnection(const nsACString& aHost, uint32_t aPort,
                             const char* aProtocol, uint32_t aTimeout,
                             nsINetDashboardCallback* aCallback) {
  nsresult rv;
  RefPtr<ConnectionData> connectionData = new ConnectionData(this);
  connectionData->mHost = aHost;
  connectionData->mPort = aPort;
  connectionData->mProtocol = aProtocol;
  connectionData->mTimeout = aTimeout;

  connectionData->mCallback =
      new nsMainThreadPtrHolder<nsINetDashboardCallback>(
          "nsINetDashboardCallback", aCallback, true);
  connectionData->mEventTarget = GetCurrentEventTarget();

  rv = TestNewConnection(connectionData);
  if (NS_FAILED(rv)) {
    mozilla::net::GetErrorString(rv, connectionData->mStatus);
    connectionData->mEventTarget->Dispatch(
        NewRunnableMethod<RefPtr<ConnectionData>>(
            "net::Dashboard::GetConnectionStatus", this,
            &Dashboard::GetConnectionStatus, connectionData),
        NS_DISPATCH_NORMAL);
    return rv;
  }

  return NS_OK;
}

nsresult Dashboard::GetConnectionStatus(ConnectionData* aConnectionData) {
  RefPtr<ConnectionData> connectionData = aConnectionData;
  AutoSafeJSContext cx;

  mozilla::dom::ConnStatusDict dict;
  dict.mStatus = connectionData->mStatus;

  JS::RootedValue val(cx);
  if (!ToJSValue(cx, dict, &val)) return NS_ERROR_FAILURE;

  connectionData->mCallback->OnDashboardDataAvailable(val);

  return NS_OK;
}

nsresult Dashboard::TestNewConnection(ConnectionData* aConnectionData) {
  RefPtr<ConnectionData> connectionData = aConnectionData;

  nsresult rv;
  if (!connectionData->mHost.Length() ||
      !net_IsValidHostName(connectionData->mHost)) {
    return NS_ERROR_UNKNOWN_HOST;
  }

  if (connectionData->mProtocol.EqualsLiteral("ssl")) {
    AutoTArray<nsCString, 1> socketTypes = {connectionData->mProtocol};
    rv = gSocketTransportService->CreateTransport(
        socketTypes, connectionData->mHost, connectionData->mPort, nullptr,
        nullptr, getter_AddRefs(connectionData->mSocket));
  } else {
    rv = gSocketTransportService->CreateTransport(
        nsTArray<nsCString>(), connectionData->mHost, connectionData->mPort,
        nullptr, nullptr, getter_AddRefs(connectionData->mSocket));
  }
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = connectionData->mSocket->SetEventSink(connectionData,
                                             GetCurrentEventTarget());
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = connectionData->mSocket->OpenInputStream(
      nsITransport::OPEN_BLOCKING, 0, 0,
      getter_AddRefs(connectionData->mStreamIn));
  if (NS_FAILED(rv)) {
    return rv;
  }

  connectionData->StartTimer(connectionData->mTimeout);

  return rv;
}

using ErrorEntry = struct {
  nsresult key;
  const char* error;
};

#undef ERROR
#define ERROR(key, val) \
  { key, #key }

ErrorEntry socketTransportStatuses[] = {
    ERROR(NS_NET_STATUS_RESOLVING_HOST, FAILURE(3)),
    ERROR(NS_NET_STATUS_RESOLVED_HOST, FAILURE(11)),
    ERROR(NS_NET_STATUS_CONNECTING_TO, FAILURE(7)),
    ERROR(NS_NET_STATUS_CONNECTED_TO, FAILURE(4)),
    ERROR(NS_NET_STATUS_TLS_HANDSHAKE_STARTING, FAILURE(12)),
    ERROR(NS_NET_STATUS_TLS_HANDSHAKE_ENDED, FAILURE(13)),
    ERROR(NS_NET_STATUS_SENDING_TO, FAILURE(5)),
    ERROR(NS_NET_STATUS_WAITING_FOR, FAILURE(10)),
    ERROR(NS_NET_STATUS_RECEIVING_FROM, FAILURE(6)),
};
#undef ERROR

static void GetErrorString(nsresult rv, nsAString& errorString) {
  for (auto& socketTransportStatus : socketTransportStatuses) {
    if (socketTransportStatus.key == rv) {
      errorString.AssignASCII(socketTransportStatus.error);
      return;
    }
  }
  nsAutoCString errorCString;
  mozilla::GetErrorName(rv, errorCString);
  CopyUTF8toUTF16(errorCString, errorString);
}

}  // namespace net
}  // namespace mozilla
back to top