void nsSocketTransport::OnSocketEvent()

in netwerk/base/nsSocketTransport2.cpp [1991:3448]


void nsSocketTransport::OnSocketEvent(uint32_t type, nsresult status,
                                      nsISupports* param,
                                      std::function<void()>&& task) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  SOCKET_LOG(
      ("nsSocketTransport::OnSocketEvent [this=%p type=%u status=%" PRIx32
       " param=%p]\n",
       this, type, static_cast<uint32_t>(status), param));

  if (NS_FAILED(mCondition)) {
    // block event since we're apparently already dead.
    SOCKET_LOG(("  blocking event [condition=%" PRIx32 "]\n",
                static_cast<uint32_t>(mCondition)));
    //
    // notify input/output streams in case either has a pending notify.
    //
    mInput->OnSocketReady(mCondition);
    mOutput->OnSocketReady(mCondition);
    return;
  }

  switch (type) {
    case MSG_ENSURE_CONNECT:
      SOCKET_LOG(("  MSG_ENSURE_CONNECT\n"));
      if (task) {
        task();
      }

      // Apply port remapping here so that we do it on the socket thread and
      // before we process the resolved DNS name or create the socket the first
      // time.
      if (!mPortRemappingApplied) {
        mPortRemappingApplied = true;

        mSocketTransportService->ApplyPortRemap(&mPort);
        mSocketTransportService->ApplyPortRemap(&mOriginPort);
      }

      //
      // ensure that we have created a socket, attached it, and have a
      // connection.
      //
      if (mState == STATE_CLOSED) {
        // Unix domain sockets are ready to connect; mNetAddr is all we
        // need. Internet address families require a DNS lookup (or possibly
        // several) before we can connect.
#if defined(XP_UNIX)
        if (mNetAddrIsSet && mNetAddr.raw.family == AF_LOCAL) {
          mCondition = InitiateSocket();
        } else {
#else
        {
#endif
          mCondition = ResolveHost();
        }

      } else {
        SOCKET_LOG(("  ignoring redundant event\n"));
      }
      break;

    case MSG_DNS_LOOKUP_COMPLETE:
      if (mDNSRequest) {  // only send this if we actually resolved anything
        SendStatus(NS_NET_STATUS_RESOLVED_HOST);
      }

      SOCKET_LOG(("  MSG_DNS_LOOKUP_COMPLETE\n"));
      mDNSRequest = nullptr;

      if (mDNSRecord) {
        mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr);
        mDNSRecord->IsTRR(&mResolvedByTRR);
        mDNSRecord->GetEffectiveTRRMode(&mEffectiveTRRMode);
        mDNSRecord->GetTrrSkipReason(&mTRRSkipReason);
      }
      // status contains DNS lookup status
      if (NS_FAILED(status)) {
        // When using a HTTP proxy, NS_ERROR_UNKNOWN_HOST means the HTTP
        // proxy host is not found, so we fixup the error code.
        // For SOCKS proxies (mProxyTransparent == true), the socket
        // transport resolves the real host here, so there's no fixup
        // (see bug 226943).
        if ((status == NS_ERROR_UNKNOWN_HOST) && !mProxyTransparent &&
            !mProxyHost.IsEmpty()) {
          mCondition = NS_ERROR_UNKNOWN_PROXY_HOST;
        } else {
          mCondition = status;
        }
      } else if (mState == STATE_RESOLVING) {
        mCondition = InitiateSocket();
      }
      break;

    case MSG_RETRY_INIT_SOCKET:
      mCondition = InitiateSocket();
      break;

    case MSG_INPUT_CLOSED:
      SOCKET_LOG(("  MSG_INPUT_CLOSED\n"));
      OnMsgInputClosed(status);
      break;

    case MSG_INPUT_PENDING:
      SOCKET_LOG(("  MSG_INPUT_PENDING\n"));
      OnMsgInputPending();
      break;

    case MSG_OUTPUT_CLOSED:
      SOCKET_LOG(("  MSG_OUTPUT_CLOSED\n"));
      OnMsgOutputClosed(status);
      break;

    case MSG_OUTPUT_PENDING:
      SOCKET_LOG(("  MSG_OUTPUT_PENDING\n"));
      OnMsgOutputPending();
      break;
    case MSG_TIMEOUT_CHANGED:
      SOCKET_LOG(("  MSG_TIMEOUT_CHANGED\n"));
      {
        MutexAutoLock lock(mLock);
        mPollTimeout =
            mTimeouts[(mState == STATE_TRANSFERRING) ? TIMEOUT_READ_WRITE
                                                     : TIMEOUT_CONNECT];
      }
      break;
    default:
      SOCKET_LOG(("  unhandled event!\n"));
  }

  if (NS_FAILED(mCondition)) {
    SOCKET_LOG(("  after event [this=%p cond=%" PRIx32 "]\n", this,
                static_cast<uint32_t>(mCondition)));
    if (!mAttached) {  // need to process this error ourselves...
      OnSocketDetached(nullptr);
    }
  } else if (mPollFlags == PR_POLL_EXCEPT) {
    mPollFlags = 0;  // make idle
  }
}

uint64_t nsSocketTransport::ByteCountReceived() { return mInput->ByteCount(); }

uint64_t nsSocketTransport::ByteCountSent() { return mOutput->ByteCount(); }

//-----------------------------------------------------------------------------
// socket handler impl

void nsSocketTransport::OnSocketReady(PRFileDesc* fd, int16_t outFlags) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  SOCKET_LOG1(("nsSocketTransport::OnSocketReady [this=%p outFlags=%hd]\n",
               this, outFlags));

  if (outFlags == -1) {
    SOCKET_LOG(("socket timeout expired\n"));
    mCondition = NS_ERROR_NET_TIMEOUT;
    return;
  }

  if (mState == STATE_TRANSFERRING) {
    // if waiting to write and socket is writable or hit an exception.
    if ((mPollFlags & PR_POLL_WRITE) && (outFlags & ~PR_POLL_READ)) {
      // assume that we won't need to poll any longer (the stream will
      // request that we poll again if it is still pending).
      mPollFlags &= ~PR_POLL_WRITE;
      mOutput->OnSocketReady(NS_OK);
    }
    // if waiting to read and socket is readable or hit an exception.
    if ((mPollFlags & PR_POLL_READ) && (outFlags & ~PR_POLL_WRITE)) {
      // assume that we won't need to poll any longer (the stream will
      // request that we poll again if it is still pending).
      mPollFlags &= ~PR_POLL_READ;
      mInput->OnSocketReady(NS_OK);
    }
    // Update poll timeout in case it was changed
    {
      MutexAutoLock lock(mLock);
      mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
    }
  } else if ((mState == STATE_CONNECTING) && !gIOService->IsNetTearingDown()) {
    // We do not need to do PR_ConnectContinue when we are already
    // shutting down.

    // We use PRIntervalTime here because we need
    // nsIOService::LastOfflineStateChange time and
    // nsIOService::LastConectivityChange time to be atomic.
    PRIntervalTime connectStarted = 0;
    if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
      connectStarted = PR_IntervalNow();
    }

    PRStatus status = PR_ConnectContinue(fd, outFlags);

    if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() &&
        connectStarted) {
      SendPRBlockingTelemetry(
          connectStarted,
          glean::networking::prconnectcontinue_blocking_time_normal,
          glean::networking::prconnectcontinue_blocking_time_shutdown,
          glean::networking::
              prconnectcontinue_blocking_time_connectivity_change,
          glean::networking::prconnectcontinue_blocking_time_link_change,
          glean::networking::prconnectcontinue_blocking_time_offline);
    }

    if (status == PR_SUCCESS) {
      //
      // we are connected!
      //
      OnSocketConnected();

      if (mNetAddr.raw.family == AF_INET) {
        if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
          glean::network::ipv4_and_ipv6_address_connectivity
              .AccumulateSingleSample(SUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS);
        }
      } else if (mNetAddr.raw.family == AF_INET6) {
        if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
          glean::network::ipv4_and_ipv6_address_connectivity
              .AccumulateSingleSample(SUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS);
        }
      }
    } else {
      PRErrorCode code = PR_GetError();
#if defined(TEST_CONNECT_ERRORS)
      code = RandomizeConnectError(code);
#endif
      //
      // If the connect is still not ready, then continue polling...
      //
      if ((PR_WOULD_BLOCK_ERROR == code) || (PR_IN_PROGRESS_ERROR == code)) {
        // Set up the select flags for connect...
        mPollFlags = (PR_POLL_EXCEPT | PR_POLL_WRITE);
        // Update poll timeout in case it was changed
        {
          MutexAutoLock lock(mLock);
          mPollTimeout = mTimeouts[TIMEOUT_CONNECT];
        }
      }
      //
      // The SOCKS proxy rejected our request. Find out why.
      //
      else if (PR_UNKNOWN_ERROR == code && mProxyTransparent &&
               !mProxyHost.IsEmpty()) {
        code = PR_GetOSError();
        mCondition = ErrorAccordingToNSPR(code);
      } else {
        //
        // else, the connection failed...
        //
        mCondition = ErrorAccordingToNSPR(code);
        if ((mCondition == NS_ERROR_CONNECTION_REFUSED) &&
            !mProxyHost.IsEmpty()) {
          mCondition = NS_ERROR_PROXY_CONNECTION_REFUSED;
        }
        SOCKET_LOG(("  connection failed! [reason=%" PRIx32 "]\n",
                    static_cast<uint32_t>(mCondition)));
      }
    }
  } else if ((mState == STATE_CONNECTING) && gIOService->IsNetTearingDown()) {
    // We do not need to do PR_ConnectContinue when we are already
    // shutting down.
    SOCKET_LOG(
        ("We are in shutdown so skip PR_ConnectContinue and set "
         "and error.\n"));
    mCondition = NS_ERROR_ABORT;
  } else {
    NS_ERROR("unexpected socket state");
    mCondition = NS_ERROR_UNEXPECTED;
  }

  if (mPollFlags == PR_POLL_EXCEPT) mPollFlags = 0;  // make idle
}

// called on the socket thread only
void nsSocketTransport::OnSocketDetached(PRFileDesc* fd) {
  SOCKET_LOG(("nsSocketTransport::OnSocketDetached [this=%p cond=%" PRIx32
              "]\n",
              this, static_cast<uint32_t>(mCondition)));

  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  mAttached = false;

  // if we didn't initiate this detach, then be sure to pass an error
  // condition up to our consumers.  (e.g., STS is shutting down.)
  if (NS_SUCCEEDED(mCondition)) {
    if (gIOService->IsOffline()) {
      mCondition = NS_ERROR_OFFLINE;
    } else {
      mCondition = NS_ERROR_ABORT;
    }
  }

  // If we are not shutting down try again.
  if (!gIOService->IsNetTearingDown() && RecoverFromError()) {
    mCondition = NS_OK;
  } else {
    mState = STATE_CLOSED;

    // make sure there isn't any pending DNS request
    if (mDNSRequest) {
      mDNSRequest->Cancel(NS_ERROR_ABORT);
      mDNSRequest = nullptr;
    }

    //
    // notify input/output streams
    //
    mInput->OnSocketReady(mCondition);
    mOutput->OnSocketReady(mCondition);
    if (gIOService->IsNetTearingDown()) {
      if (mInputCopyContext) {
        NS_CancelAsyncCopy(mInputCopyContext, mCondition);
      }
      if (mOutputCopyContext) {
        NS_CancelAsyncCopy(mOutputCopyContext, mCondition);
      }
    }
  }

  if (mCondition == NS_ERROR_NET_RESET && mDNSRecord &&
      mOutput->ByteCount() == 0) {
    // If we are here, it's likely that we are retrying a transaction. Blocking
    // the already used address could increase the successful rate of the retry.
    mDNSRecord->ReportUnusable(SocketPort());
  }

  // finally, release our reference to the socket (must do this within
  // the transport lock) possibly closing the socket. Also release our
  // listeners to break potential refcount cycles.

  // We should be careful not to release mEventSink and mCallbacks while
  // we're locked, because releasing it might require acquiring the lock
  // again, so we just null out mEventSink and mCallbacks while we're
  // holding the lock, and let the stack based objects' destuctors take
  // care of destroying it if needed.
  nsCOMPtr<nsIInterfaceRequestor> ourCallbacks;
  nsCOMPtr<nsITransportEventSink> ourEventSink;
  {
    MutexAutoLock lock(mLock);
    if (mFD.IsInitialized()) {
      ReleaseFD_Locked(mFD);
      // flag mFD as unusable; this prevents other consumers from
      // acquiring a reference to mFD.
      mFDconnected = false;
    }

    // We must release mCallbacks and mEventSink to avoid memory leak
    // but only when RecoverFromError() above failed. Otherwise we lose
    // link with UI and security callbacks on next connection attempt
    // round. That would lead e.g. to a broken certificate exception page.
    if (NS_FAILED(mCondition)) {
      mCallbacks.swap(ourCallbacks);
      mEventSink.swap(ourEventSink);
    }
  }
}

void nsSocketTransport::IsLocal(bool* aIsLocal) {
  {
    MutexAutoLock lock(mLock);

#if defined(XP_UNIX)
    // Unix-domain sockets are always local.
    if (mNetAddr.raw.family == PR_AF_LOCAL) {
      *aIsLocal = true;
      return;
    }
#endif

    *aIsLocal = mNetAddr.IsLoopbackAddr();
  }
}

//-----------------------------------------------------------------------------
// xpcom api

NS_IMPL_ISUPPORTS(nsSocketTransport, nsISocketTransport, nsITransport,
                  nsIDNSListener, nsIClassInfo, nsIInterfaceRequestor)
NS_IMPL_CI_INTERFACE_GETTER(nsSocketTransport, nsISocketTransport, nsITransport,
                            nsIDNSListener, nsIInterfaceRequestor)

NS_IMETHODIMP
nsSocketTransport::OpenInputStream(uint32_t flags, uint32_t segsize,
                                   uint32_t segcount,
                                   nsIInputStream** aResult) {
  SOCKET_LOG(
      ("nsSocketTransport::OpenInputStream [this=%p flags=%x]\n", this, flags));

  NS_ENSURE_TRUE(!mInput->IsReferenced(), NS_ERROR_UNEXPECTED);

  nsresult rv;
  nsCOMPtr<nsIAsyncInputStream> pipeIn;
  nsCOMPtr<nsIInputStream> result;
  nsCOMPtr<nsISupports> inputCopyContext;

  if (!(flags & OPEN_UNBUFFERED) || (flags & OPEN_BLOCKING)) {
    // XXX if the caller wants blocking, then the caller also gets buffered!
    // bool openBuffered = !(flags & OPEN_UNBUFFERED);
    bool openBlocking = (flags & OPEN_BLOCKING);

    net_ResolveSegmentParams(segsize, segcount);

    // create a pipe
    nsCOMPtr<nsIAsyncOutputStream> pipeOut;
    NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), !openBlocking,
                true, segsize, segcount);

    // async copy from socket to pipe
    rv = NS_AsyncCopy(mInput.get(), pipeOut, mSocketTransportService,
                      NS_ASYNCCOPY_VIA_WRITESEGMENTS, segsize, nullptr, nullptr,
                      true, true, getter_AddRefs(inputCopyContext));
    if (NS_FAILED(rv)) return rv;

    result = pipeIn;
  } else {
    result = mInput.get();
  }

  // flag input stream as open
  mInputClosed = false;
  // mInputCopyContext can be only touched on socket thread
  auto task = [self = RefPtr{this}, inputCopyContext(inputCopyContext)]() {
    MOZ_ASSERT(OnSocketThread());
    self->mInputCopyContext = inputCopyContext;
  };
  rv = PostEvent(MSG_ENSURE_CONNECT, NS_OK, nullptr, std::move(task));
  if (NS_FAILED(rv)) {
    return rv;
  }

  result.forget(aResult);
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::OpenOutputStream(uint32_t flags, uint32_t segsize,
                                    uint32_t segcount,
                                    nsIOutputStream** aResult) {
  SOCKET_LOG(("nsSocketTransport::OpenOutputStream [this=%p flags=%x]\n", this,
              flags));

  NS_ENSURE_TRUE(!mOutput->IsReferenced(), NS_ERROR_UNEXPECTED);

  nsresult rv;
  nsCOMPtr<nsIAsyncOutputStream> pipeOut;
  nsCOMPtr<nsIOutputStream> result;
  nsCOMPtr<nsISupports> outputCopyContext;
  if (!(flags & OPEN_UNBUFFERED) || (flags & OPEN_BLOCKING)) {
    // XXX if the caller wants blocking, then the caller also gets buffered!
    // bool openBuffered = !(flags & OPEN_UNBUFFERED);
    bool openBlocking = (flags & OPEN_BLOCKING);

    net_ResolveSegmentParams(segsize, segcount);

    // create a pipe
    nsCOMPtr<nsIAsyncInputStream> pipeIn;
    NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), true,
                !openBlocking, segsize, segcount);

    // async copy from socket to pipe
    rv = NS_AsyncCopy(pipeIn, mOutput.get(), mSocketTransportService,
                      NS_ASYNCCOPY_VIA_READSEGMENTS, segsize, nullptr, nullptr,
                      true, true, getter_AddRefs(outputCopyContext));
    if (NS_FAILED(rv)) return rv;

    result = pipeOut;
  } else {
    result = mOutput.get();
  }

  // flag output stream as open
  mOutputClosed = false;

  // mOutputCopyContext can be only touched on socket thread
  auto task = [self = RefPtr{this}, outputCopyContext(outputCopyContext)]() {
    MOZ_ASSERT(OnSocketThread());
    self->mOutputCopyContext = outputCopyContext;
  };
  rv = PostEvent(MSG_ENSURE_CONNECT, NS_OK, nullptr, std::move(task));
  if (NS_FAILED(rv)) return rv;

  result.forget(aResult);
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::Close(nsresult reason) {
  SOCKET_LOG(("nsSocketTransport::Close %p reason=%" PRIx32, this,
              static_cast<uint32_t>(reason)));

  if (NS_SUCCEEDED(reason)) reason = NS_BASE_STREAM_CLOSED;

  mDoNotRetryToConnect = true;

  mInput->CloseWithStatus(reason);
  mOutput->CloseWithStatus(reason);
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetTlsSocketControl(nsITLSSocketControl** tlsSocketControl) {
  MutexAutoLock lock(mLock);
  *tlsSocketControl = do_AddRef(mTLSSocketControl).take();
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetSecurityCallbacks(nsIInterfaceRequestor** callbacks) {
  MutexAutoLock lock(mLock);
  *callbacks = do_AddRef(mCallbacks).take();
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::SetSecurityCallbacks(nsIInterfaceRequestor* callbacks) {
  nsCOMPtr<nsIInterfaceRequestor> threadsafeCallbacks;
  NS_NewNotificationCallbacksAggregation(callbacks, nullptr,
                                         GetCurrentSerialEventTarget(),
                                         getter_AddRefs(threadsafeCallbacks));
  MutexAutoLock lock(mLock);
  mCallbacks = threadsafeCallbacks;
  SOCKET_LOG(("Reset callbacks for tlsSocketInfo=%p callbacks=%p\n",
              mTLSSocketControl.get(), mCallbacks.get()));
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::SetEventSink(nsITransportEventSink* sink,
                                nsIEventTarget* target) {
  nsCOMPtr<nsITransportEventSink> temp;
  if (target) {
    nsresult rv =
        net_NewTransportEventSinkProxy(getter_AddRefs(temp), sink, target);
    if (NS_FAILED(rv)) return rv;
    sink = temp.get();
  }

  MutexAutoLock lock(mLock);
  mEventSink = sink;
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::IsAlive(bool* result) {
  *result = false;

  nsresult conditionWhileLocked = NS_OK;
  PRFileDescAutoLock fd(this, &conditionWhileLocked);
  if (NS_FAILED(conditionWhileLocked) || !fd.IsInitialized()) {
    return NS_OK;
  }

  // XXX do some idle-time based checks??

  char c;
  int32_t rval = PR_Recv(fd, &c, 1, PR_MSG_PEEK, 0);

  if ((rval > 0) || (rval < 0 && PR_GetError() == PR_WOULD_BLOCK_ERROR)) {
    *result = true;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetHost(nsACString& host) {
  host = SocketHost();
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetPort(int32_t* port) {
  *port = (int32_t)SocketPort();
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetScriptableOriginAttributes(
    JSContext* aCx, JS::MutableHandle<JS::Value> aOriginAttributes) {
  if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aOriginAttributes))) {
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::SetScriptableOriginAttributes(
    JSContext* aCx, JS::Handle<JS::Value> aOriginAttributes) {
  MutexAutoLock lock(mLock);
  NS_ENSURE_FALSE(mFD.IsInitialized(), NS_ERROR_FAILURE);

  OriginAttributes attrs;
  if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
    return NS_ERROR_INVALID_ARG;
  }

  mOriginAttributes = attrs;
  return NS_OK;
}

nsresult nsSocketTransport::GetOriginAttributes(
    OriginAttributes* aOriginAttributes) {
  NS_ENSURE_ARG(aOriginAttributes);
  *aOriginAttributes = mOriginAttributes;
  return NS_OK;
}

nsresult nsSocketTransport::SetOriginAttributes(
    const OriginAttributes& aOriginAttributes) {
  MutexAutoLock lock(mLock);
  NS_ENSURE_FALSE(mFD.IsInitialized(), NS_ERROR_FAILURE);

  mOriginAttributes = aOriginAttributes;
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetPeerAddr(NetAddr* addr) {
  // once we are in the connected state, mNetAddr will not change.
  // so if we can verify that we are in the connected state, then
  // we can freely access mNetAddr from any thread without being
  // inside a critical section.

  if (!mNetAddrIsSet) {
    SOCKET_LOG(
        ("nsSocketTransport::GetPeerAddr [this=%p state=%d] "
         "NOT_AVAILABLE because not yet connected.",
         this, mState));
    return NS_ERROR_NOT_AVAILABLE;
  }

  *addr = mNetAddr;
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetSelfAddr(NetAddr* addr) {
  // once we are in the connected state, mSelfAddr will not change.
  // so if we can verify that we are in the connected state, then
  // we can freely access mSelfAddr from any thread without being
  // inside a critical section.

  if (!mSelfAddrIsSet) {
    SOCKET_LOG(
        ("nsSocketTransport::GetSelfAddr [this=%p state=%d] "
         "NOT_AVAILABLE because not yet connected.",
         this, mState));
    return NS_ERROR_NOT_AVAILABLE;
  }

  *addr = mSelfAddr;
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::Bind(NetAddr* aLocalAddr) {
  NS_ENSURE_ARG(aLocalAddr);

  MutexAutoLock lock(mLock);
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  if (mAttached) {
    return NS_ERROR_FAILURE;
  }

  mBindAddr = MakeUnique<NetAddr>(*aLocalAddr);

  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetScriptablePeerAddr(nsINetAddr** addr) {
  NetAddr rawAddr;

  nsresult rv;
  rv = GetPeerAddr(&rawAddr);
  if (NS_FAILED(rv)) return rv;

  RefPtr<nsNetAddr> netaddr = new nsNetAddr(&rawAddr);
  netaddr.forget(addr);
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetScriptableSelfAddr(nsINetAddr** addr) {
  NetAddr rawAddr;

  nsresult rv;
  rv = GetSelfAddr(&rawAddr);
  if (NS_FAILED(rv)) return rv;

  RefPtr<nsNetAddr> netaddr = new nsNetAddr(&rawAddr);
  netaddr.forget(addr);

  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetTimeout(uint32_t type, uint32_t* value) {
  NS_ENSURE_ARG_MAX(type, nsISocketTransport::TIMEOUT_READ_WRITE);
  MutexAutoLock lock(mLock);
  *value = (uint32_t)mTimeouts[type];
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::SetTimeout(uint32_t type, uint32_t value) {
  NS_ENSURE_ARG_MAX(type, nsISocketTransport::TIMEOUT_READ_WRITE);

  SOCKET_LOG(("nsSocketTransport::SetTimeout %p type=%u, value=%u", this, type,
              value));

  // truncate overly large timeout values.
  {
    MutexAutoLock lock(mLock);
    mTimeouts[type] = (uint16_t)std::min<uint32_t>(value, UINT16_MAX);
  }
  PostEvent(MSG_TIMEOUT_CHANGED);
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::SetReuseAddrPort(bool reuseAddrPort) {
  mReuseAddrPort = reuseAddrPort;
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::SetLinger(bool aPolarity, int16_t aTimeout) {
  MutexAutoLock lock(mLock);

  mLingerPolarity = aPolarity;
  mLingerTimeout = aTimeout;

  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::SetQoSBits(uint8_t aQoSBits) {
  // Don't do any checking here of bits.  Why?  Because as of RFC-4594
  // several different Class Selector and Assured Forwarding values
  // have been defined, but that isn't to say more won't be added later.
  // In that case, any checking would be an impediment to interoperating
  // with newer QoS definitions.

  mQoSBits = aQoSBits;
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetQoSBits(uint8_t* aQoSBits) {
  *aQoSBits = mQoSBits;
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetRecvBufferSize(uint32_t* aSize) {
  PRFileDescAutoLock fd(this);
  if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED;

  nsresult rv = NS_OK;
  PRSocketOptionData opt;
  opt.option = PR_SockOpt_RecvBufferSize;
  if (PR_GetSocketOption(fd, &opt) == PR_SUCCESS) {
    *aSize = opt.value.recv_buffer_size;
  } else {
    rv = NS_ERROR_FAILURE;
  }

  return rv;
}

NS_IMETHODIMP
nsSocketTransport::GetSendBufferSize(uint32_t* aSize) {
  PRFileDescAutoLock fd(this);
  if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED;

  nsresult rv = NS_OK;
  PRSocketOptionData opt;
  opt.option = PR_SockOpt_SendBufferSize;
  if (PR_GetSocketOption(fd, &opt) == PR_SUCCESS) {
    *aSize = opt.value.send_buffer_size;
  } else {
    rv = NS_ERROR_FAILURE;
  }

  return rv;
}

NS_IMETHODIMP
nsSocketTransport::SetRecvBufferSize(uint32_t aSize) {
  PRFileDescAutoLock fd(this);
  if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED;

  nsresult rv = NS_OK;
  PRSocketOptionData opt;
  opt.option = PR_SockOpt_RecvBufferSize;
  opt.value.recv_buffer_size = aSize;
  if (PR_SetSocketOption(fd, &opt) != PR_SUCCESS) rv = NS_ERROR_FAILURE;

  return rv;
}

NS_IMETHODIMP
nsSocketTransport::SetSendBufferSize(uint32_t aSize) {
  PRFileDescAutoLock fd(this);
  if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED;

  nsresult rv = NS_OK;
  PRSocketOptionData opt;
  opt.option = PR_SockOpt_SendBufferSize;
  opt.value.send_buffer_size = aSize;
  if (PR_SetSocketOption(fd, &opt) != PR_SUCCESS) rv = NS_ERROR_FAILURE;

  return rv;
}

NS_IMETHODIMP
nsSocketTransport::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec,
                                    nsresult status) {
  SOCKET_LOG(("nsSocketTransport::OnLookupComplete: this=%p status %" PRIx32
              ".",
              this, static_cast<uint32_t>(status)));

  if (NS_SUCCEEDED(status)) {
    mDNSRecord = do_QueryInterface(rec);
    MOZ_ASSERT(mDNSRecord);
  }

  if (nsCOMPtr<nsIDNSAddrRecord> addrRecord = do_QueryInterface(rec)) {
    addrRecord->IsTRR(&mResolvedByTRR);
    addrRecord->GetEffectiveTRRMode(&mEffectiveTRRMode);
    addrRecord->GetTrrSkipReason(&mTRRSkipReason);
  }

  // flag host lookup complete for the benefit of the ResolveHost method.
  mResolving = false;
  nsresult rv = PostEvent(MSG_DNS_LOOKUP_COMPLETE, status, nullptr);

  // if posting a message fails, then we should assume that the socket
  // transport has been shutdown.  this should never happen!  if it does
  // it means that the socket transport service was shutdown before the
  // DNS service.
  if (NS_FAILED(rv)) {
    NS_WARNING("unable to post DNS lookup complete message");
  }

  return NS_OK;
}

// nsIInterfaceRequestor
NS_IMETHODIMP
nsSocketTransport::GetInterface(const nsIID& iid, void** result) {
  if (iid.Equals(NS_GET_IID(nsIDNSRecord)) ||
      iid.Equals(NS_GET_IID(nsIDNSAddrRecord))) {
    return mDNSRecord ? mDNSRecord->QueryInterface(iid, result)
                      : NS_ERROR_NO_INTERFACE;
  }
  return this->QueryInterface(iid, result);
}

NS_IMETHODIMP
nsSocketTransport::GetInterfaces(nsTArray<nsIID>& array) {
  return NS_CI_INTERFACE_GETTER_NAME(nsSocketTransport)(array);
}

NS_IMETHODIMP
nsSocketTransport::GetScriptableHelper(nsIXPCScriptable** _retval) {
  *_retval = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetContractID(nsACString& aContractID) {
  aContractID.SetIsVoid(true);
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetClassDescription(nsACString& aClassDescription) {
  aClassDescription.SetIsVoid(true);
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetClassID(nsCID** aClassID) {
  *aClassID = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetFlags(uint32_t* aFlags) {
  *aFlags = nsIClassInfo::THREADSAFE;
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) {
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsSocketTransport::GetConnectionFlags(uint32_t* value) {
  *value = mConnectionFlags;
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::SetConnectionFlags(uint32_t value) {
  SOCKET_LOG(
      ("nsSocketTransport::SetConnectionFlags %p flags=%u", this, value));

  mConnectionFlags = value;
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::SetIsPrivate(bool aIsPrivate) {
  mIsPrivate = aIsPrivate;
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetTlsFlags(uint32_t* value) {
  *value = mTlsFlags;
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::SetTlsFlags(uint32_t value) {
  mTlsFlags = value;
  return NS_OK;
}

void nsSocketTransport::OnKeepaliveEnabledPrefChange(bool aEnabled) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  // The global pref toggles keepalive as a system feature; it only affects
  // an individual socket if keepalive has been specifically enabled for it.
  // So, ensure keepalive is configured correctly if previously enabled.
  if (mKeepaliveEnabled) {
    nsresult rv = SetKeepaliveEnabledInternal(aEnabled);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      SOCKET_LOG(("  SetKeepaliveEnabledInternal [%s] failed rv[0x%" PRIx32 "]",
                  aEnabled ? "enable" : "disable", static_cast<uint32_t>(rv)));
    }
  }
}

nsresult nsSocketTransport::SetKeepaliveEnabledInternal(bool aEnable) {
  MOZ_ASSERT(mKeepaliveIdleTimeS > 0 && mKeepaliveIdleTimeS <= kMaxTCPKeepIdle);
  MOZ_ASSERT(mKeepaliveRetryIntervalS > 0 &&
             mKeepaliveRetryIntervalS <= kMaxTCPKeepIntvl);
  MOZ_ASSERT(mKeepaliveProbeCount > 0 &&
             mKeepaliveProbeCount <= kMaxTCPKeepCount);

  PRFileDescAutoLock fd(this);
  if (NS_WARN_IF(!fd.IsInitialized())) {
    return NS_ERROR_NOT_INITIALIZED;
  }

  // Only enable if keepalives are globally enabled, but ensure other
  // options are set correctly on the fd.
  bool enable = aEnable && mSocketTransportService->IsKeepaliveEnabled();
  nsresult rv =
      fd.SetKeepaliveVals(enable, mKeepaliveIdleTimeS, mKeepaliveRetryIntervalS,
                          mKeepaliveProbeCount);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    SOCKET_LOG(("  SetKeepaliveVals failed rv[0x%" PRIx32 "]",
                static_cast<uint32_t>(rv)));
    return rv;
  }
  rv = fd.SetKeepaliveEnabled(enable);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    SOCKET_LOG(("  SetKeepaliveEnabled failed rv[0x%" PRIx32 "]",
                static_cast<uint32_t>(rv)));
    return rv;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetKeepaliveEnabled(bool* aResult) {
  MOZ_ASSERT(aResult);

  *aResult = mKeepaliveEnabled;
  return NS_OK;
}

nsresult nsSocketTransport::EnsureKeepaliveValsAreInitialized() {
  nsresult rv = NS_OK;
  int32_t val = -1;
  if (mKeepaliveIdleTimeS == -1) {
    rv = mSocketTransportService->GetKeepaliveIdleTime(&val);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    mKeepaliveIdleTimeS = val;
  }
  if (mKeepaliveRetryIntervalS == -1) {
    rv = mSocketTransportService->GetKeepaliveRetryInterval(&val);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    mKeepaliveRetryIntervalS = val;
  }
  if (mKeepaliveProbeCount == -1) {
    rv = mSocketTransportService->GetKeepaliveProbeCount(&val);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    mKeepaliveProbeCount = val;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::SetKeepaliveEnabled(bool aEnable) {
#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  if (aEnable == mKeepaliveEnabled) {
    SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled [%p] already %s.", this,
                aEnable ? "enabled" : "disabled"));
    return NS_OK;
  }

  nsresult rv = NS_OK;
  if (aEnable) {
    rv = EnsureKeepaliveValsAreInitialized();
    if (NS_WARN_IF(NS_FAILED(rv))) {
      SOCKET_LOG(
          ("  SetKeepaliveEnabled [%p] "
           "error [0x%" PRIx32 "] initializing keepalive vals",
           this, static_cast<uint32_t>(rv)));
      return rv;
    }
  }
  SOCKET_LOG(
      ("nsSocketTransport::SetKeepaliveEnabled [%p] "
       "%s, idle time[%ds] retry interval[%ds] packet count[%d]: "
       "globally %s.",
       this, aEnable ? "enabled" : "disabled", mKeepaliveIdleTimeS,
       mKeepaliveRetryIntervalS, mKeepaliveProbeCount,
       mSocketTransportService->IsKeepaliveEnabled() ? "enabled" : "disabled"));

  // Set mKeepaliveEnabled here so that state is maintained; it is possible
  // that we're in between fds, e.g. the 1st IP address failed, so we're about
  // to retry on a 2nd from the DNS record.
  mKeepaliveEnabled = aEnable;

  rv = SetKeepaliveEnabledInternal(aEnable);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    SOCKET_LOG(("  SetKeepaliveEnabledInternal failed rv[0x%" PRIx32 "]",
                static_cast<uint32_t>(rv)));
    return rv;
  }

  return NS_OK;
#else /* !(defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)) */
  SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled unsupported platform"));
  return NS_ERROR_NOT_IMPLEMENTED;
#endif
}

NS_IMETHODIMP
nsSocketTransport::SetKeepaliveVals(int32_t aIdleTime, int32_t aRetryInterval) {
#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  if (NS_WARN_IF(aIdleTime <= 0 || kMaxTCPKeepIdle < aIdleTime)) {
    return NS_ERROR_INVALID_ARG;
  }
  if (NS_WARN_IF(aRetryInterval <= 0 || kMaxTCPKeepIntvl < aRetryInterval)) {
    return NS_ERROR_INVALID_ARG;
  }

  if (aIdleTime == mKeepaliveIdleTimeS &&
      aRetryInterval == mKeepaliveRetryIntervalS) {
    SOCKET_LOG(
        ("nsSocketTransport::SetKeepaliveVals [%p] idle time "
         "already %ds and retry interval already %ds.",
         this, mKeepaliveIdleTimeS, mKeepaliveRetryIntervalS));
    return NS_OK;
  }
  mKeepaliveIdleTimeS = aIdleTime;
  mKeepaliveRetryIntervalS = aRetryInterval;

  nsresult rv = NS_OK;
  if (mKeepaliveProbeCount == -1) {
    int32_t val = -1;
    nsresult rv = mSocketTransportService->GetKeepaliveProbeCount(&val);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
    mKeepaliveProbeCount = val;
  }

  SOCKET_LOG(
      ("nsSocketTransport::SetKeepaliveVals [%p] "
       "keepalive %s, idle time[%ds] retry interval[%ds] "
       "packet count[%d]",
       this, mKeepaliveEnabled ? "enabled" : "disabled", mKeepaliveIdleTimeS,
       mKeepaliveRetryIntervalS, mKeepaliveProbeCount));

  PRFileDescAutoLock fd(this);
  if (NS_WARN_IF(!fd.IsInitialized())) {
    return NS_ERROR_NULL_POINTER;
  }

  rv = fd.SetKeepaliveVals(mKeepaliveEnabled, mKeepaliveIdleTimeS,
                           mKeepaliveRetryIntervalS, mKeepaliveProbeCount);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  return NS_OK;
#else
  SOCKET_LOG(("nsSocketTransport::SetKeepaliveVals unsupported platform"));
  return NS_ERROR_NOT_IMPLEMENTED;
#endif
}

#ifdef ENABLE_SOCKET_TRACING

#  include <stdio.h>
#  include <ctype.h>
#  include "prenv.h"

static void DumpBytesToFile(const char* path, const char* header,
                            const char* buf, int32_t n) {
  FILE* fp = fopen(path, "a");

  fprintf(fp, "\n%s [%d bytes]\n", header, n);

  const unsigned char* p;
  while (n) {
    p = (const unsigned char*)buf;

    int32_t i, row_max = std::min(16, n);

    for (i = 0; i < row_max; ++i) fprintf(fp, "%02x  ", *p++);
    for (i = row_max; i < 16; ++i) fprintf(fp, "    ");

    p = (const unsigned char*)buf;
    for (i = 0; i < row_max; ++i, ++p) {
      if (isprint(*p))
        fprintf(fp, "%c", *p);
      else
        fprintf(fp, ".");
    }

    fprintf(fp, "\n");
    buf += row_max;
    n -= row_max;
  }

  fprintf(fp, "\n");
  fclose(fp);
}

void nsSocketTransport::TraceInBuf(const char* buf, int32_t n) {
  char* val = PR_GetEnv("NECKO_SOCKET_TRACE_LOG");
  if (!val || !*val) return;

  nsAutoCString header;
  header.AssignLiteral("Reading from: ");
  header.Append(mHost);
  header.Append(':');
  header.AppendInt(mPort);

  DumpBytesToFile(val, header.get(), buf, n);
}

void nsSocketTransport::TraceOutBuf(const char* buf, int32_t n) {
  char* val = PR_GetEnv("NECKO_SOCKET_TRACE_LOG");
  if (!val || !*val) return;

  nsAutoCString header;
  header.AssignLiteral("Writing to: ");
  header.Append(mHost);
  header.Append(':');
  header.AppendInt(mPort);

  DumpBytesToFile(val, header.get(), buf, n);
}

#endif

static void LogNSPRError(const char* aPrefix, const void* aObjPtr) {
#if defined(DEBUG)
  PRErrorCode errCode = PR_GetError();
  int errLen = PR_GetErrorTextLength();
  nsAutoCString errStr;
  if (errLen > 0) {
    errStr.SetLength(errLen);
    PR_GetErrorText(errStr.BeginWriting());
  }
  NS_WARNING(
      nsPrintfCString("%s [%p] NSPR error[0x%x] %s.",
                      aPrefix ? aPrefix : "nsSocketTransport", aObjPtr, errCode,
                      errLen > 0 ? errStr.BeginReading() : "<no error text>")
          .get());
#endif
}

nsresult nsSocketTransport::PRFileDescAutoLock::SetKeepaliveEnabled(
    bool aEnable) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  MOZ_ASSERT(!(aEnable && !gSocketTransportService->IsKeepaliveEnabled()),
             "Cannot enable keepalive if global pref is disabled!");
  if (aEnable && !gSocketTransportService->IsKeepaliveEnabled()) {
    return NS_ERROR_ILLEGAL_VALUE;
  }

  PRSocketOptionData opt;

  opt.option = PR_SockOpt_Keepalive;
  opt.value.keep_alive = aEnable;
  PRStatus status = PR_SetSocketOption(mFd, &opt);
  if (NS_WARN_IF(status != PR_SUCCESS)) {
    LogNSPRError("nsSocketTransport::PRFileDescAutoLock::SetKeepaliveEnabled",
                 mSocketTransport);
    return ErrorAccordingToNSPR(PR_GetError());
  }
  return NS_OK;
}

static void LogOSError(const char* aPrefix, const void* aObjPtr) {
#if defined(DEBUG)
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

#  ifdef XP_WIN
  DWORD errCode = WSAGetLastError();
  char* errMessage;
  FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
                     FORMAT_MESSAGE_IGNORE_INSERTS,
                 NULL, errCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                 (LPSTR)&errMessage, 0, NULL);
  NS_WARNING(nsPrintfCString("%s [%p] OS error[0x%lx] %s",
                             aPrefix ? aPrefix : "nsSocketTransport", aObjPtr,
                             errCode,
                             errMessage ? errMessage : "<no error text>")
                 .get());
  LocalFree(errMessage);
#  else
  int errCode = errno;
  char* errMessage = strerror(errno);
  NS_WARNING(nsPrintfCString("%s [%p] OS error[0x%x] %s",
                             aPrefix ? aPrefix : "nsSocketTransport", aObjPtr,
                             errCode,
                             errMessage ? errMessage : "<no error text>")
                 .get());
#  endif
#endif
}

/* XXX PR_SetSockOpt does not support setting keepalive values, so native
 * handles and platform specific apis (setsockopt, WSAIOCtl) are used in this
 * file. Requires inclusion of NSPR private/pprio.h, and platform headers.
 */

nsresult nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals(
    bool aEnabled, int aIdleTime, int aRetryInterval, int aProbeCount) {
#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
  if (NS_WARN_IF(aIdleTime <= 0 || kMaxTCPKeepIdle < aIdleTime)) {
    return NS_ERROR_INVALID_ARG;
  }
  if (NS_WARN_IF(aRetryInterval <= 0 || kMaxTCPKeepIntvl < aRetryInterval)) {
    return NS_ERROR_INVALID_ARG;
  }
  if (NS_WARN_IF(aProbeCount <= 0 || kMaxTCPKeepCount < aProbeCount)) {
    return NS_ERROR_INVALID_ARG;
  }

  PROsfd sock = PR_FileDesc2NativeHandle(mFd);
  if (NS_WARN_IF(sock == -1)) {
    LogNSPRError("nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals",
                 mSocketTransport);
    return ErrorAccordingToNSPR(PR_GetError());
  }
#endif

#if defined(XP_WIN)
  // Windows allows idle time and retry interval to be set; NOT ping count.
  struct tcp_keepalive keepalive_vals = {(u_long)aEnabled,
                                         // Windows uses msec.
                                         (u_long)(aIdleTime * 1000UL),
                                         (u_long)(aRetryInterval * 1000UL)};
  DWORD bytes_returned;
  int err =
      WSAIoctl(sock, SIO_KEEPALIVE_VALS, &keepalive_vals,
               sizeof(keepalive_vals), NULL, 0, &bytes_returned, NULL, NULL);
  if (NS_WARN_IF(err)) {
    LogOSError("nsSocketTransport WSAIoctl failed", mSocketTransport);
    return NS_ERROR_UNEXPECTED;
  }
  return NS_OK;

#elif defined(XP_DARWIN)
  // Darwin uses sec; only supports idle time being set.
  int err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE, &aIdleTime,
                       sizeof(aIdleTime));
  if (NS_WARN_IF(err)) {
    LogOSError("nsSocketTransport Failed setting TCP_KEEPALIVE",
               mSocketTransport);
    return NS_ERROR_UNEXPECTED;
  }
  return NS_OK;

#elif defined(XP_UNIX)
  // Not all *nix OSes support the following setsockopt() options
  // ... but we assume they are supported in the Android kernel;
  // build errors will tell us if they are not.
#  if defined(ANDROID) || defined(TCP_KEEPIDLE)
  // Idle time until first keepalive probe; interval between ack'd probes;
  // seconds.
  int err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &aIdleTime,
                       sizeof(aIdleTime));
  if (NS_WARN_IF(err)) {
    LogOSError("nsSocketTransport Failed setting TCP_KEEPIDLE",
               mSocketTransport);
    return NS_ERROR_UNEXPECTED;
  }

#  endif
#  if defined(ANDROID) || defined(TCP_KEEPINTVL)
  // Interval between unack'd keepalive probes; seconds.
  err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &aRetryInterval,
                   sizeof(aRetryInterval));
  if (NS_WARN_IF(err)) {
    LogOSError("nsSocketTransport Failed setting TCP_KEEPINTVL",
               mSocketTransport);
    return NS_ERROR_UNEXPECTED;
  }

#  endif
#  if defined(ANDROID) || defined(TCP_KEEPCNT)
  // Number of unack'd keepalive probes before connection times out.
  err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &aProbeCount,
                   sizeof(aProbeCount));
  if (NS_WARN_IF(err)) {
    LogOSError("nsSocketTransport Failed setting TCP_KEEPCNT",
               mSocketTransport);
    return NS_ERROR_UNEXPECTED;
  }

#  endif
  return NS_OK;
#else
  MOZ_ASSERT(false,
             "nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals "
             "called on unsupported platform!");
  return NS_ERROR_UNEXPECTED;
#endif
}

void nsSocketTransport::CloseSocket(PRFileDesc* aFd, bool aTelemetryEnabled) {
#if defined(XP_WIN)
  AttachShutdownLayer(aFd);
#endif

  // We use PRIntervalTime here because we need
  // nsIOService::LastOfflineStateChange time and
  // nsIOService::LastConectivityChange time to be atomic.
  PRIntervalTime closeStarted;
  if (aTelemetryEnabled) {
    closeStarted = PR_IntervalNow();
  }

  PR_Close(aFd);

  if (aTelemetryEnabled) {
    SendPRBlockingTelemetry(
        closeStarted, glean::networking::prclose_tcp_blocking_time_normal,
        glean::networking::prclose_tcp_blocking_time_shutdown,
        glean::networking::prclose_tcp_blocking_time_connectivity_change,
        glean::networking::prclose_tcp_blocking_time_link_change,
        glean::networking::prclose_tcp_blocking_time_offline);
  }
}

void nsSocketTransport::SendPRBlockingTelemetry(
    PRIntervalTime aStart,
    const glean::impl::TimingDistributionMetric& aMetricNormal,
    const glean::impl::TimingDistributionMetric& aMetricShutdown,
    const glean::impl::TimingDistributionMetric& aMetricConnectivityChange,
    const glean::impl::TimingDistributionMetric& aMetricLinkChange,
    const glean::impl::TimingDistributionMetric& aMetricOffline) {
  PRIntervalTime now = PR_IntervalNow();
  TimeDuration delta =
      TimeDuration::FromMilliseconds(PR_IntervalToMilliseconds(now - aStart));
  if (gIOService->IsNetTearingDown()) {
    aMetricShutdown.AccumulateRawDuration(delta);
  } else if (PR_IntervalToSeconds(now - gIOService->LastConnectivityChange()) <
             60) {
    aMetricConnectivityChange.AccumulateRawDuration(delta);
  } else if (PR_IntervalToSeconds(now - gIOService->LastNetworkLinkChange()) <
             60) {
    aMetricLinkChange.AccumulateRawDuration(delta);
  } else if (PR_IntervalToSeconds(now - gIOService->LastOfflineStateChange()) <
             60) {
    aMetricOffline.AccumulateRawDuration(delta);
  } else {
    aMetricNormal.AccumulateRawDuration(delta);
  }
}

NS_IMETHODIMP
nsSocketTransport::GetResetIPFamilyPreference(bool* aReset) {
  *aReset = mResetFamilyPreference;
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetEchConfigUsed(bool* aEchConfigUsed) {
  *aEchConfigUsed = mEchConfigUsed;
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::SetEchConfig(const nsACString& aEchConfig) {
  mEchConfig = aEchConfig;
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::ResolvedByTRR(bool* aResolvedByTRR) {
  *aResolvedByTRR = mResolvedByTRR;
  return NS_OK;
}

NS_IMETHODIMP nsSocketTransport::GetEffectiveTRRMode(
    nsIRequest::TRRMode* aEffectiveTRRMode) {
  *aEffectiveTRRMode = mEffectiveTRRMode;
  return NS_OK;
}

NS_IMETHODIMP nsSocketTransport::GetTrrSkipReason(
    nsITRRSkipReason::value* aSkipReason) {
  *aSkipReason = mTRRSkipReason;
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetRetryDnsIfPossible(bool* aRetryDns) {
  *aRetryDns = mRetryDnsIfPossible;
  return NS_OK;
}

NS_IMETHODIMP
nsSocketTransport::GetStatus(nsresult* aStatus) {
  MOZ_ASSERT(OnSocketThread(), "not on socket thread");

  *aStatus = mCondition;
  return NS_OK;
}

}  // namespace net