/* 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/. */

// HttpLog.h should generally be included first
#include "HttpLog.h"

#include "ConnectionEstablisher.h"
#include "HappyEyeballsConnectionAttempt.h"
#include "mozilla/Components.h"
#include "nsSocketTransportService2.h"
#include "nsHttpConnectionMgr.h"
#include "nsHttpHandler.h"
#include "nsIDNSRecord.h"
#include "nsHttpTransaction.h"
#include "HttpConnectionUDP.h"

// Log on level :5, instead of default :4.
#undef LOG
#define LOG(args) LOG5(args)
#undef LOG_ENABLED
#define LOG_ENABLED() LOG5_ENABLED()

namespace mozilla::net {

// -------------------- SingleDNSAddrRecord --------------------

void DnsMetadata::Fill(nsIDNSAddrRecord* aRecord) {
  if (!aRecord) {
    return;
  }
  aRecord->IsTRR(&mIsTRR);
  aRecord->ResolvedInSocketProcess(&mResolvedInSocketProcess);
  aRecord->GetTrrFetchDuration(&mTrrFetchDuration);
  aRecord->GetTrrFetchDurationNetworkOnly(&mTrrFetchDurationNetworkOnly);
  aRecord->GetEffectiveTRRMode(&mEffectiveTRRMode);
  aRecord->GetTrrSkipReason(&mTrrSkipReason);
}

class SingleDNSAddrRecord final : public nsIDNSAddrRecord {
 public:
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSIDNSRECORD
  NS_DECL_NSIDNSADDRRECORD

  SingleDNSAddrRecord(NetAddr aAddr, const DnsMetadata& aMetadata)
      : mAddress(aAddr),
        mIsTRR(aMetadata.mIsTRR),
        mResolvedInSocketProcess(aMetadata.mResolvedInSocketProcess),
        mTrrFetchDuration(aMetadata.mTrrFetchDuration),
        mTrrFetchDurationNetworkOnly(aMetadata.mTrrFetchDurationNetworkOnly),
        mEffectiveTRRMode(aMetadata.mEffectiveTRRMode),
        mTrrSkipReason(aMetadata.mTrrSkipReason) {
    LOG(("SingleDNSAddrRecord ctor:%p mIsTRR=%d mEffectiveTRRMode=%d", this,
         mIsTRR, static_cast<uint32_t>(mEffectiveTRRMode)));
  }

 private:
  ~SingleDNSAddrRecord() { LOG(("SingleDNSAddrRecord dtor:%p", this)); }

  nsCString mCanonicalName;
  NetAddr mAddress;

  bool mIsTRR = false;
  bool mResolvedInSocketProcess = false;
  double mTrrFetchDuration = 0.0;
  double mTrrFetchDurationNetworkOnly = 0.0;
  nsIRequest::TRRMode mEffectiveTRRMode = nsIRequest::TRR_DEFAULT_MODE;
  nsITRRSkipReason::value mTrrSkipReason = nsITRRSkipReason::TRR_UNSET;
  uint32_t mTTL = 60;
  mozilla::TimeStamp mLastUpdate = TimeStamp::Now();
  bool mDone = false;
};

NS_IMPL_ISUPPORTS(SingleDNSAddrRecord, nsIDNSRecord, nsIDNSAddrRecord)

NS_IMETHODIMP
SingleDNSAddrRecord::GetCanonicalName(nsACString& aResult) {
  aResult.Assign(mCanonicalName);
  return NS_OK;
}

NS_IMETHODIMP
SingleDNSAddrRecord::IsTRR(bool* aRetval) {
  *aRetval = mIsTRR;
  return NS_OK;
}

NS_IMETHODIMP
SingleDNSAddrRecord::ResolvedInSocketProcess(bool* aRetval) {
  *aRetval = mResolvedInSocketProcess;
  return NS_OK;
}

NS_IMETHODIMP
SingleDNSAddrRecord::GetTrrFetchDuration(double* aTime) {
  *aTime = mTrrFetchDuration;
  return NS_OK;
}

NS_IMETHODIMP
SingleDNSAddrRecord::GetTrrFetchDurationNetworkOnly(double* aTime) {
  *aTime = mTrrFetchDurationNetworkOnly;
  return NS_OK;
}

NS_IMETHODIMP
SingleDNSAddrRecord::GetScriptableNextAddr(uint16_t aPort,
                                           nsINetAddr** aResult) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
SingleDNSAddrRecord::GetNextAddrAsString(nsACString& aResult) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
SingleDNSAddrRecord::HasMore(bool* aResult) { return NS_ERROR_NOT_IMPLEMENTED; }

NS_IMETHODIMP
SingleDNSAddrRecord::Rewind() { return NS_ERROR_NOT_IMPLEMENTED; }

NS_IMETHODIMP
SingleDNSAddrRecord::ReportUnusable(uint16_t aPort) {
  // TODO: should we block this address?
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
SingleDNSAddrRecord::GetEffectiveTRRMode(nsIRequest::TRRMode* aMode) {
  *aMode = mEffectiveTRRMode;
  return NS_OK;
}

NS_IMETHODIMP
SingleDNSAddrRecord::GetTrrSkipReason(nsITRRSkipReason::value* aReason) {
  *aReason = mTrrSkipReason;
  return NS_OK;
}

NS_IMETHODIMP
SingleDNSAddrRecord::GetTtl(uint32_t* aTtl) {
  *aTtl = mTTL;
  return NS_OK;
}

NS_IMETHODIMP
SingleDNSAddrRecord::GetLastUpdate(mozilla::TimeStamp* aLastUpdate) {
  *aLastUpdate = mLastUpdate;
  return NS_OK;
}

NS_IMETHODIMP
SingleDNSAddrRecord::GetNextAddr(uint16_t aPort, NetAddr* aAddr) {
  if (mDone) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  *aAddr = mAddress;
  mDone = true;

  uint16_t port = htons(aPort);
  if (aAddr->raw.family == AF_INET) {
    aAddr->inet.port = port;
  } else if (aAddr->raw.family == AF_INET6) {
    aAddr->inet6.port = port;
  }

  return NS_OK;
}

NS_IMETHODIMP
SingleDNSAddrRecord::GetAddresses(nsTArray<NetAddr>& aAddressArray) {
  // Match a regular DNS address record, which stores port-less addresses (the
  // port is applied later via GetNextAddr). Connection coalescing compares the
  // stored addresses against a connection's peer address with the port zeroed
  // (see FindCoalescableConnection), so a non-zero port here would prevent the
  // match.
  NetAddr addr = mAddress;
  if (addr.raw.family == AF_INET) {
    addr.inet.port = 0;
  } else if (addr.raw.family == AF_INET6) {
    addr.inet6.port = 0;
  }
  aAddressArray.AppendElement(addr);
  return NS_OK;
}

// -------------------- ConnectionEstablisher --------------------

NS_IMPL_ISUPPORTS(ConnectionEstablisher, nsITransportEventSink,
                  nsIInterfaceRequestor)

ConnectionEstablisher::ConnectionEstablisher(nsHttpConnectionInfo* aConnInfo,
                                             const NetAddr& aAddr,
                                             uint32_t aCaps)
    : mConnInfo(aConnInfo), mAddr(aAddr), mCaps(aCaps) {
  LOG(("ConnectionEstablisher ctor:%p", this));
}

ConnectionEstablisher::~ConnectionEstablisher() {
  LOG(("ConnectionEstablisher dtor:%p", this));
  MaybeSetConnectingDone();

  if (!OnSocketThread() && gSocketTransportService) {
    gSocketTransportService->Dispatch(
        NS_NewRunnableFunction(
            "~ConnectionEstablisher",
            [transaction = std::move(mTransaction), handle = std::move(mHandle),
             resultConn = std::move(mResultConn),
             transportStatusCallback = std::move(mTransportStatusCallback),
             lnaCheckCallback = std::move(mLnaCheckCallback),
             callback = std::move(mCallback)]() {}),
        NS_DISPATCH_NORMAL);
  }
}

void ConnectionEstablisher::SetConnecting() {
  MOZ_ASSERT(!mWaitingForConnect);
  mWaitingForConnect = true;
  gHttpHandler->ConnMgr()->StartedConnect();
}

void ConnectionEstablisher::MaybeSetConnectingDone() {
  if (mWaitingForConnect) {
    mWaitingForConnect = false;
    gHttpHandler->ConnMgr()->RecvdConnect();
  }
}

void ConnectionEstablisher::ClearResultConnection() { mResultConn = nullptr; }

nsresult ConnectionEstablisher::ActivateConnectionWithTransaction(
    RefPtr<HttpConnectionBase> aConn,
    std::function<void(nsresult)> aOnActivated) {
  LOG(
      ("ConnectionEstablisher::ActivateConnectionWithTransaction %p conn=%p "
       "trans=%p",
       this, aConn.get(), mTransaction.get()));

  aConn->SetIsRacing(true);

  mHasConnected = true;
  mResultConn = aConn;
  mHandle = new ConnectionHandle(aConn);

  MOZ_ASSERT(mTransaction,
             "HappyEyeballsConnectionAttempt must hand us a transaction "
             "before we can activate a connection.");

  mTransaction->SetConnectedCallback(
      [self = RefPtr{this},
       onActivated = std::move(aOnActivated)](nsresult aResult) {
        NS_DispatchToCurrentThread(NS_NewRunnableFunction(
            "ConnectionEstablisher::ActivateCallback",
            [self, aResult, onActivated = std::move(onActivated)]() {
              if (NS_FAILED(aResult)) {
                self->Finish(aResult);
                return;
              }

              onActivated(NS_OK);
            }));
      });

  mTransaction->SetConnection(mHandle);
  nsresult rv = aConn->Activate(mTransaction, mCaps, 0);
  if (NS_FAILED(rv)) {
    Finish(rv);
    return rv;
  }

  return NS_OK;
}

void ConnectionEstablisher::FinishInternal(nsresult aResult) {
  LOG(("ConnectionEstablisher::FinishInternal %p result=%x", this,
       static_cast<uint32_t>(aResult)));

  if (mFinished) {
    return;
  }
  mFinished = true;

  MaybeSetConnectingDone();
  mTransportStatusCallback = nullptr;
  mLnaCheckCallback = nullptr;

  if (mTransaction) {
    // Detach the connected-callback so later Close/cleanup on the
    // transaction can't call us back. Keep the ref itself: the winning
    // HandleTCPConnectionResult / HandleUDPConnectionResult still needs
    // to query establisher->Transaction() afterwards to grab the
    // handshake timings.
    mTransaction->SetConnectedCallback(nullptr);
  }

  if (mCallback) {
    auto cb = std::move(mCallback);
    mCallback = nullptr;
    // HappyEyeballsTransaction is speculative — the real transaction is
    // never driving I/O on this conn — so the H1 fallback path always
    // applies: reset the handle so the conn doesn't cling to it after
    // we're done (H2/H3 manage reuse via their sessions).
    if (mHandle && mHandle->Conn() && !mHandle->Conn()->UsingSpdy() &&
        !mHandle->Conn()->UsingHttp3()) {
      mHandle->Reset();
    }

    // Clear mHandle to break the ref cycle: the establisher holds mHandle
    // (which refs the connection), while the connection holds mCallbacks
    // (which refs the establisher).
    mHandle = nullptr;

    // For H3, check CanReuse() to guard against mHttp3Session being
    // destroyed between the async handshake callback and this point.
    // For H1/H2, skip: H1 needs the connection even if the socket is dead
    // (e.g. cert error) to propagate the proper error code; H2 sessions
    // may be incorrectly marked DontReuse by the proxy transaction's caps.
    bool connUsable =
        mResultConn && (!mResultConn->UsingHttp3() || mResultConn->CanReuse());
    if (NS_SUCCEEDED(aResult) && connUsable) {
      if (!mConnectStart.IsNull()) {
        mResultConn->SetConnectBootstrapTimings(mConnectStart, mTcpConnectEnd);
      }
      cb(std::move(mResultConn));
    } else {
      LOG(
          ("ConnectionEstablisher::FinishInternal %p conn rejected "
           "aResult=%x connUsable=%d UsingHttp3=%d",
           this, static_cast<uint32_t>(aResult), connUsable,
           mResultConn ? mResultConn->UsingHttp3() : 0));
      cb(Err(NS_FAILED(aResult) ? aResult : NS_ERROR_ABORT));
    }
  }

  mAddrRecord = nullptr;
}

already_AddRefed<nsIDNSAddrRecord> ConnectionEstablisher::AddrRecord() const {
  nsCOMPtr<nsIDNSAddrRecord> record = mAddrRecord;
  return record.forget();
}

NS_IMETHODIMP
ConnectionEstablisher::GetInterface(const nsIID& iid, void** result) {
  if (mSecurityCallbacks) {
    return mSecurityCallbacks->GetInterface(iid, result);
  }
  return NS_ERROR_NO_INTERFACE;
}

NS_IMETHODIMP
ConnectionEstablisher::OnTransportStatus(nsITransport* trans, nsresult status,
                                         int64_t progress,
                                         int64_t progressMax) {
  if (status == NS_NET_STATUS_CONNECTING_TO) {
    mConnectStart = TimeStamp::Now();
  } else if (status == NS_NET_STATUS_CONNECTED_TO) {
    mConnectedOK = true;
    mTcpConnectEnd = TimeStamp::Now();
  }

  if (mTransportStatusCallback) {
    mTransportStatusCallback(trans, status, progress);
  }

  return NS_OK;
}

NS_IMPL_ISUPPORTS_INHERITED(TCPConnectionEstablisher, ConnectionEstablisher,
                            nsIOutputStreamCallback)

TCPConnectionEstablisher::TCPConnectionEstablisher(
    nsHttpConnectionInfo* aConnInfo, NetAddr aAddr, uint32_t aCaps,
    bool aSpeculative, bool aAllow1918)
    : ConnectionEstablisher(aConnInfo, aAddr, aCaps),
      mSpeculative(aSpeculative),
      mAllow1918(aAllow1918) {}

TCPConnectionEstablisher::~TCPConnectionEstablisher() {
  // mSocketTransport / mStreamOut / mStreamIn must be released on the
  // socket thread.
  if (!OnSocketThread() && gSocketTransportService) {
    gSocketTransportService->Dispatch(
        NS_NewRunnableFunction("~TCPConnectionEstablisher",
                               [socketTransport = std::move(mSocketTransport),
                                streamOut = std::move(mStreamOut),
                                streamIn = std::move(mStreamIn)]() {}),
        NS_DISPATCH_NORMAL);
  }
}

bool TCPConnectionEstablisher::Start(DoneCallback&& aCallback) {
  mCallback = std::move(aCallback);
  mAddrRecord = new SingleDNSAddrRecord(mAddr, mDnsMetadata);

  nsresult rv = CreateAndConfigureSocketTransport();
  if (NS_FAILED(rv)) {
    return false;
  }

  return true;
}

void TCPConnectionEstablisher::ResetSpeculativeFlags() {
  uint32_t flags = 0;
  if (!mSocketTransport ||
      NS_FAILED(mSocketTransport->GetConnectionFlags(&flags))) {
    return;
  }

  flags &= ~nsISocketTransport::DISABLE_RFC1918;
  flags &= ~nsISocketTransport::IS_SPECULATIVE_CONNECTION;
  mSocketTransport->SetConnectionFlags(flags);
}

void TCPConnectionEstablisher::Close(nsresult aReason) {
  LOG(("TCPConnectionEstablisher::Close %p aReason=%x", this,
       static_cast<uint32_t>(aReason)));

  mHandle = nullptr;
  if (mResultConn) {
    // Every connection touched by HE is marked non-reusable: adopted conns
    // finish serving the in-flight real txn then close; losing conns are
    // torn down immediately below.  Either way the CM must never hand a new
    // transaction to a connection that participated in an HE race.
    bool adopted = mTransaction && mTransaction->IsAdopted();
    mResultConn->DontReuse();
    if (adopted) {
      LOG(("TCPConnectionEstablisher::Close %p adopted conn %p DontReuse", this,
           mResultConn.get()));
    } else {
      LOG(("TCPConnectionEstablisher::Close closing connection %p",
           mResultConn.get()));
      // Use CloseTransaction rather than Close to properly clean up the
      // SPDY session: if we only called Close, an Http2Session in
      // mSpdySession/mTransaction would never be released and the conn
      // would stay alive indefinitely without a pending socket read to
      // trigger CloseTransaction naturally.
      mResultConn->CloseTransaction(mResultConn->Transaction(), aReason);
    }
    mResultConn = nullptr;
  }

  if (mSocketTransport) {
    mSocketTransport->SetEventSink(nullptr, nullptr);
    mSocketTransport->SetSecurityCallbacks(nullptr);
    mSocketTransport = nullptr;
  }

  // Tell output stream (and backup) to forget the half open socket.
  if (mStreamOut) {
    mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
    mStreamOut = nullptr;
  }

  // Lose references to input stream (and backup).
  if (mStreamIn) {
    mStreamIn->AsyncWait(nullptr, 0, 0, nullptr);
    mStreamIn = nullptr;
  }

  // Release the DNS address record to avoid leaking SingleDNSAddrRecord
  mAddrRecord = nullptr;

  mConnectedOK = false;
  Finish(aReason);
}

nsresult TCPConnectionEstablisher::CreateAndConfigureSocketTransport() {
  nsresult rv = NS_OK;
  nsTArray<nsCString> socketTypes;
  if (mConnInfo->FirstHopSSL()) {
    socketTypes.AppendElement("ssl"_ns);
  } else {
    const nsCString& defaultType = gHttpHandler->DefaultSocketType();
    if (!defaultType.IsVoid()) {
      socketTypes.AppendElement(defaultType);
    }
  }

  nsCOMPtr<nsISocketTransport> socketTransport;
  nsCOMPtr<nsISocketTransportService> sts =
      components::SocketTransport::Service();
  if (!sts) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  LOG(
      ("TCPConnectionEstablisher::CreateAndConfigureSocketTransport [this=%p "
       "info=%s] "
       "setup routed transport to origin %s:%d via %s:%d\n",
       this, mConnInfo->HashKey().get(), mConnInfo->Origin(),
       mConnInfo->OriginPort(), mConnInfo->RoutedHost(),
       mConnInfo->RoutedPort()));

  nsCOMPtr<nsIRoutedSocketTransportService> routedSTS(do_QueryInterface(sts));
  if (routedSTS) {
    rv = routedSTS->CreateRoutedTransport(
        socketTypes, mConnInfo->GetOrigin(), mConnInfo->OriginPort(),
        mConnInfo->GetRoutedHost(), mConnInfo->RoutedPort(),
        mConnInfo->ProxyInfo(), mAddrRecord, getter_AddRefs(socketTransport));
  } else {
    if (!mConnInfo->GetRoutedHost().IsEmpty()) {
      // There is a route requested, but the legacy nsISocketTransportService
      // can't handle it.
      // Origin should be reachable on origin host name, so this should
      // not be a problem - but log it.
      LOG(
          ("%p using legacy nsISocketTransportService "
           "means explicit route %s:%d will be ignored.\n",
           this, mConnInfo->RoutedHost(), mConnInfo->RoutedPort()));
    }

    rv = sts->CreateTransport(socketTypes, mConnInfo->GetOrigin(),
                              mConnInfo->OriginPort(), mConnInfo->ProxyInfo(),
                              mAddrRecord, getter_AddRefs(socketTransport));
  }

  if (NS_FAILED(rv)) {
    return rv;
  }

  uint32_t tmpFlags = 0;
  if (mCaps & NS_HTTP_REFRESH_DNS) {
    tmpFlags = nsISocketTransport::BYPASS_CACHE;
  }

  tmpFlags |= nsISocketTransport::GetFlagsFromTRRMode(
      NS_HTTP_TRR_MODE_FROM_FLAGS(mCaps));

  if (mCaps & NS_HTTP_LOAD_ANONYMOUS) {
    tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT;
  }

  // When we are making a speculative connection we do not propagate all flags
  // in mCaps, so we need to query nsHttpConnectionInfo directly as well.
  if ((mCaps & NS_HTTP_LOAD_ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT) ||
      mConnInfo->GetAnonymousAllowClientCert()) {
    tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT;
  }

  if (mConnInfo->GetPrivate()) {
    tmpFlags |= nsISocketTransport::NO_PERMANENT_STORAGE;
  }

  if (mCaps & NS_HTTP_DISALLOW_ECH) {
    tmpFlags |= nsISocketTransport::DONT_TRY_ECH;
  }

  if (mCaps & NS_HTTP_IS_RETRY) {
    tmpFlags |= nsISocketTransport::IS_RETRY;
  }

  if (((mCaps & NS_HTTP_BE_CONSERVATIVE) || mConnInfo->GetBeConservative()) &&
      gHttpHandler->ConnMgr()->BeConservativeIfProxied(
          mConnInfo->ProxyInfo())) {
    LOG(("Setting Socket to BE_CONSERVATIVE"));
    tmpFlags |= nsISocketTransport::BE_CONSERVATIVE;
  }

  // IP hint addresses from HTTPS records are handled by the Happy Eyeballs
  // state machine.

  if (!mAllow1918) {
    tmpFlags |= nsISocketTransport::DISABLE_RFC1918;
  }

  if (mSpeculative) {
    tmpFlags |= nsISocketTransport::IS_SPECULATIVE_CONNECTION;
  }

  socketTransport->SetConnectionFlags(tmpFlags);
  socketTransport->SetTlsFlags(mConnInfo->GetTlsFlags());
  socketTransport->SetOriginAttributes(mConnInfo->GetOriginAttributes());

  socketTransport->SetQoSBits(gHttpHandler->GetQoSBits());

  rv = socketTransport->SetEventSink(this, nullptr);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = socketTransport->SetSecurityCallbacks(this);
  NS_ENSURE_SUCCESS(rv, rv);

  if (nsHttpHandler::EchConfigEnabled() &&
      !mConnInfo->GetEchConfig().IsEmpty()) {
    LOG(("Setting ECH"));
    rv = socketTransport->SetEchConfig(mConnInfo->GetEchConfig());
    NS_ENSURE_SUCCESS(rv, rv);
  }

  mSynStarted = TimeStamp::Now();

  nsCOMPtr<nsIOutputStream> sout;
  rv = socketTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
                                         getter_AddRefs(sout));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIInputStream> sin;
  rv = socketTransport->OpenInputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
                                        getter_AddRefs(sin));
  NS_ENSURE_SUCCESS(rv, rv);

  mSocketTransport = socketTransport.forget();
  mStreamIn = do_QueryInterface(sin);
  mStreamOut = do_QueryInterface(sout);

  rv = mStreamOut->AsyncWait(this, 0, 0, nullptr);
  if (NS_SUCCEEDED(rv)) {
    SetConnecting();
  }
  return rv;
}

void TCPConnectionEstablisher::Finish(nsresult aResult) {
  // Release TCP-specific resources first
  mStreamOut = nullptr;
  mStreamIn = nullptr;
  mSocketTransport = nullptr;

  FinishInternal(aResult);
}

NS_IMETHODIMP
TCPConnectionEstablisher::OnOutputStreamReady(nsIAsyncOutputStream* aOut) {
  MOZ_DIAGNOSTIC_ASSERT(mStreamOut == aOut, "stream mismatch");
  LOG(("TCPConnectionEstablisher::OnOutputStreamReady %p mFinished=%d", this,
       mFinished));

  if (mFinished) {
    return NS_OK;
  }

  // Early LNA check: reject the connection before TLS handshake can send SNI.
  if (mLnaCheckCallback && mSocketTransport) {
    nsresult rv = mLnaCheckCallback(mSocketTransport);
    if (NS_FAILED(rv)) {
      mSocketTransport->Close(rv);
      Finish(rv);
      return NS_OK;
    }
  }

  // Create nsHttpConnection when the output stream is ready.
  RefPtr<nsHttpConnection> conn = new nsHttpConnection();
  conn->SetTransactionCaps(mCaps);
  // TODO:
  // 1. BootstrapTimings
  // 2. SetTransactionCaps
  // 3. SetSecurityCallbacks
  // 4. RecordIPFamilyPreference

  nsresult rv = conn->Init(
      mConnInfo, gHttpHandler->ConnMgr()->mMaxRequestDelay, mSocketTransport,
      mStreamIn, mStreamOut, mConnectedOK, NS_OK, this,
      PR_MillisecondsToInterval(static_cast<uint32_t>(
          (TimeStamp::Now() - mSynStarted).ToMilliseconds())),
      mCaps & NS_HTTP_ALLOW_SPDY_WITHOUT_KEEPALIVE);

  if (NS_FAILED(rv)) {
    Finish(rv);
    return NS_OK;
  }

  // Clear TCP-specific resources before activation
  mSocketTransport = nullptr;
  mStreamOut = nullptr;
  mStreamIn = nullptr;

  rv = ActivateConnectionWithTransaction(
      conn, [self = RefPtr{this}](nsresult aResult) { self->Finish(aResult); });

  return rv;
}

// -------------------- UDPConnectionEstablisher --------------------

UDPConnectionEstablisher::UDPConnectionEstablisher(
    nsHttpConnectionInfo* aConnInfo, NetAddr aAddr, uint32_t aCaps,
    bool /* aSpeculative */, bool /* aAllow1918 */)
    : ConnectionEstablisher(aConnInfo, aAddr, aCaps) {
  LOG(("UDPConnectionEstablisher ctor:%p", this));
}

UDPConnectionEstablisher::~UDPConnectionEstablisher() {
  LOG(("UDPConnectionEstablisher dtor:%p", this));
}

bool UDPConnectionEstablisher::Start(DoneCallback&& aCallback) {
  LOG(("UDPConnectionEstablisher::Start %p", this));
  mCallback = std::move(aCallback);
  mAddrRecord = new SingleDNSAddrRecord(mAddr, mDnsMetadata);

  nsresult rv = CreateAndConfigureUDPConn();
  if (NS_FAILED(rv)) {
    return false;
  }

  return true;
}

void UDPConnectionEstablisher::Close(nsresult aReason) {
  LOG(("UDPConnectionEstablisher::Close %p aReason=%x", this,
       static_cast<uint32_t>(aReason)));

  mHandle = nullptr;
  if (mResultConn) {
    // If the HT on this conn has been adopted, the conn is already
    // driving the real nsHttpTransaction — tearing it down here would
    // close the real txn. Just mark the conn non-reusable and let it
    // finish serving the in-flight request naturally.
    bool adopted = mTransaction && mTransaction->IsAdopted();
    if (adopted) {
      LOG(("UDPConnectionEstablisher::Close %p adopted conn %p DontReuse", this,
           mResultConn.get()));
      mResultConn->DontReuse();
    } else {
      LOG(("UDPConnectionEstablisher::Close closing connection %p",
           mResultConn.get()));
      // TODO: for some cases we might want to exclude HTTP/3.
      mResultConn->SetDontExclude();
      mResultConn->Close(aReason);
    }
    mResultConn = nullptr;
  }

  // Release the DNS address record to avoid leaking SingleDNSAddrRecord
  mAddrRecord = nullptr;

  Finish(aReason);
}

nsresult UDPConnectionEstablisher::CreateAndConfigureUDPConn() {
  LOG(
      ("UDPConnectionEstablisher::CreateAndConfigureUDPConn [this=%p "
       "info=%s]",
       this, mConnInfo->HashKey().get()));

  RefPtr<HttpConnectionUDP> connUDP = new HttpConnectionUDP();
  connUDP->SetTransactionCaps(mCaps);

  nsresult rv = connUDP->Init(mConnInfo, mAddrRecord, NS_OK, this, mCaps);
  if (NS_FAILED(rv)) {
    return rv;
  }

  SetConnecting();

  rv = ActivateConnectionWithTransaction(
      connUDP,
      [self = RefPtr{this}](nsresult aResult) { self->Finish(aResult); });

  return rv;
}

void UDPConnectionEstablisher::Finish(nsresult aResult) {
  LOG(("UDPConnectionEstablisher::Finish %p result=%x", this,
       static_cast<uint32_t>(aResult)));

  FinishInternal(aResult);
}

}  // namespace mozilla::net
