/* 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 "HttpConnectionMgrParent.h"
#include "AltSvcTransactionParent.h"
#include "mozilla/net/HttpTransactionParent.h"
#include "mozilla/net/WebSocketConnectionParent.h"
#include "nsHttpConnectionInfo.h"
#include "nsIHttpChannelInternal.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsISpeculativeConnect.h"
#include "nsIOService.h"
#include "nsQueryObject.h"

namespace mozilla::net {

constinit nsTHashMap<uint32_t, nsCOMPtr<nsIHttpUpgradeListener>>
    HttpConnectionMgrParent::sHttpUpgradeListenerMap;
uint32_t HttpConnectionMgrParent::sListenerId = 0;
StaticMutex HttpConnectionMgrParent::sLock;

// static
uint32_t HttpConnectionMgrParent::AddHttpUpgradeListenerToMap(
    nsIHttpUpgradeListener* aListener) {
  StaticMutexAutoLock lock(sLock);
  uint32_t id = sListenerId++;
  sHttpUpgradeListenerMap.InsertOrUpdate(id, nsCOMPtr{aListener});
  return id;
}

// static
void HttpConnectionMgrParent::RemoveHttpUpgradeListenerFromMap(uint32_t aId) {
  StaticMutexAutoLock lock(sLock);
  sHttpUpgradeListenerMap.Remove(aId);
}

// static
Maybe<nsCOMPtr<nsIHttpUpgradeListener>>
HttpConnectionMgrParent::GetAndRemoveHttpUpgradeListener(uint32_t aId) {
  StaticMutexAutoLock lock(sLock);
  return sHttpUpgradeListenerMap.Extract(aId);
}

NS_IMPL_ISUPPORTS0(HttpConnectionMgrParent)

nsresult HttpConnectionMgrParent::Init(
    uint16_t maxUrgentExcessiveConns, uint16_t maxConnections,
    uint16_t maxPersistentConnectionsPerHost,
    uint16_t maxPersistentConnectionsPerProxy, uint16_t maxRequestDelay,
    bool throttleEnabled, uint32_t throttleSuspendFor,
    uint32_t throttleResumeFor, uint32_t throttleHoldTime,
    uint32_t throttleMaxTime, bool beConservativeForProxy) {
  // We don't have to do anything here. nsHttpConnectionMgr in socket process is
  // initialized by nsHttpHandler.
  return NS_OK;
}

nsresult HttpConnectionMgrParent::Shutdown() {
  if (mShutDown) {
    return NS_OK;
  }

  if (!CanSend()) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  mShutDown = true;
  (void)Send__delete__(this);
  return NS_OK;
}

nsresult HttpConnectionMgrParent::UpdateRequestTokenBucket(
    EventTokenBucket* aBucket) {
  // We don't have to do anything here. UpdateRequestTokenBucket() will be
  // triggered by pref change in socket process.
  return NS_OK;
}

nsresult HttpConnectionMgrParent::DoShiftReloadConnectionCleanup() {
  // Do nothing here. DoShiftReloadConnectionCleanup() will be triggered by
  // observer notification or pref change in socket process.
  return NS_OK;
}

nsresult HttpConnectionMgrParent::DoShiftReloadConnectionCleanupWithConnInfo(
    nsHttpConnectionInfo* aCi) {
  if (!aCi) {
    return NS_ERROR_INVALID_ARG;
  }

  HttpConnectionInfoCloneArgs connInfoArgs;
  nsHttpConnectionInfo::SerializeHttpConnectionInfo(aCi, connInfoArgs);

  RefPtr<HttpConnectionMgrParent> self = this;
  auto task = [self, connInfoArgs{std::move(connInfoArgs)}]() {
    (void)self->SendDoShiftReloadConnectionCleanupWithConnInfo(connInfoArgs);
  };
  gIOService->CallOrWaitForSocketProcess(std::move(task));
  return NS_OK;
}

nsresult HttpConnectionMgrParent::PruneDeadConnections() {
  // Do nothing here. PruneDeadConnections() will be triggered by
  // observer notification or pref change in socket process.
  return NS_OK;
}

void HttpConnectionMgrParent::AbortAndCloseAllConnections(int32_t, ARefBase*) {
  // Do nothing here. AbortAndCloseAllConnections() will be triggered by
  // observer notification in socket process.
}

nsresult HttpConnectionMgrParent::UpdateParam(nsParamName name,
                                              uint16_t value) {
  // Do nothing here. UpdateParam() will be triggered by pref change in
  // socket process.
  return NS_OK;
}

void HttpConnectionMgrParent::PrintDiagnostics() {
  // Do nothing here. PrintDiagnostics() will be triggered by pref change in
  // socket process.
}

nsresult HttpConnectionMgrParent::UpdateCurrentBrowserId(uint64_t aId) {
  RefPtr<HttpConnectionMgrParent> self = this;
  auto task = [self, aId]() { (void)self->SendUpdateCurrentBrowserId(aId); };
  gIOService->CallOrWaitForSocketProcess(std::move(task));
  return NS_OK;
}

nsresult HttpConnectionMgrParent::AddTransaction(HttpTransactionShell* aTrans,
                                                 int32_t aPriority) {
  MOZ_ASSERT(gIOService->SocketProcessReady());

  if (!CanSend()) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  (void)SendAddTransaction(WrapNotNull(aTrans->AsHttpTransactionParent()),
                           aPriority);
  return NS_OK;
}

nsresult HttpConnectionMgrParent::AddTransactionWithStickyConn(
    HttpTransactionShell* aTrans, int32_t aPriority,
    HttpTransactionShell* aTransWithStickyConn) {
  MOZ_ASSERT(gIOService->SocketProcessReady());

  if (!CanSend()) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  (void)SendAddTransactionWithStickyConn(
      WrapNotNull(aTrans->AsHttpTransactionParent()), aPriority,
      WrapNotNull(aTransWithStickyConn->AsHttpTransactionParent()));
  return NS_OK;
}

nsresult HttpConnectionMgrParent::RescheduleTransaction(
    HttpTransactionShell* aTrans, int32_t aPriority) {
  MOZ_ASSERT(gIOService->SocketProcessReady());

  if (!CanSend()) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  (void)SendRescheduleTransaction(
      WrapNotNull(aTrans->AsHttpTransactionParent()), aPriority);
  return NS_OK;
}

void HttpConnectionMgrParent::UpdateClassOfServiceOnTransaction(
    HttpTransactionShell* aTrans, const ClassOfService& aClassOfService) {
  MOZ_ASSERT(gIOService->SocketProcessReady());

  if (!CanSend()) {
    return;
  }

  (void)SendUpdateClassOfServiceOnTransaction(
      WrapNotNull(aTrans->AsHttpTransactionParent()), aClassOfService);
}

nsresult HttpConnectionMgrParent::CancelTransaction(
    HttpTransactionShell* aTrans, nsresult aReason) {
  MOZ_ASSERT(gIOService->SocketProcessReady());

  if (!CanSend()) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  (void)SendCancelTransaction(WrapNotNull(aTrans->AsHttpTransactionParent()),
                              aReason);
  return NS_OK;
}

nsresult HttpConnectionMgrParent::ReclaimConnection(HttpConnectionBase*) {
  MOZ_ASSERT_UNREACHABLE("ReclaimConnection should not be called");
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult HttpConnectionMgrParent::ProcessPendingQ(nsHttpConnectionInfo*) {
  MOZ_ASSERT_UNREACHABLE("ProcessPendingQ should not be called");
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult HttpConnectionMgrParent::ProcessPendingQ() {
  MOZ_ASSERT_UNREACHABLE("ProcessPendingQ should not be called");
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult HttpConnectionMgrParent::GetSocketThreadTarget(nsIEventTarget**) {
  MOZ_ASSERT_UNREACHABLE("GetSocketThreadTarget should not be called");
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsresult HttpConnectionMgrParent::SpeculativeConnect(
    nsHttpConnectionInfo* aConnInfo, nsIInterfaceRequestor* aCallbacks,
    uint32_t aCaps, SpeculativeTransaction* aTransaction, bool aFetchHTTPSRR) {
  NS_ENSURE_ARG_POINTER(aConnInfo);

  nsCOMPtr<nsISpeculativeConnectionOverrider> overrider =
      do_GetInterface(aCallbacks);
  Maybe<SpeculativeConnectionOverriderArgs> overriderArgs;
  if (overrider) {
    overriderArgs.emplace();
    overriderArgs->parallelSpeculativeConnectLimit() =
        overrider->GetParallelSpeculativeConnectLimit();
    overriderArgs->ignoreIdle() = overrider->GetIgnoreIdle();
    overriderArgs->allow1918() = overrider->GetAllow1918();
  }

  HttpConnectionInfoCloneArgs connInfo;
  nsHttpConnectionInfo::SerializeHttpConnectionInfo(aConnInfo, connInfo);
  RefPtr<AltSvcTransactionParent> trans = do_QueryObject(aTransaction);
  RefPtr<HttpConnectionMgrParent> self = this;
  auto task = [self, connInfo{std::move(connInfo)},
               overriderArgs{std::move(overriderArgs)}, aCaps,
               trans{std::move(trans)}, aFetchHTTPSRR]() {
    Maybe<NotNull<AltSvcTransactionParent*>> maybeTrans;
    if (trans) {
      maybeTrans.emplace(WrapNotNull(trans.get()));
    }
    (void)self->SendSpeculativeConnect(connInfo, overriderArgs, aCaps,
                                       maybeTrans, aFetchHTTPSRR);
  };

  gIOService->CallOrWaitForSocketProcess(std::move(task));
  return NS_OK;
}

nsresult HttpConnectionMgrParent::VerifyTraffic() {
  // Do nothing here. VerifyTraffic() will be triggered by observer notification
  // in socket process.
  return NS_OK;
}

void HttpConnectionMgrParent::ExcludeHttp2(const nsHttpConnectionInfo* ci) {
  // Do nothing.
}

void HttpConnectionMgrParent::ExcludeHttp3(const nsHttpConnectionInfo* ci) {
  // Do nothing.
}

nsresult HttpConnectionMgrParent::ClearConnectionHistory() {
  // Do nothing here. ClearConnectionHistory() will be triggered by
  // observer notification in socket process.
  return NS_OK;
}

nsresult HttpConnectionMgrParent::CompleteUpgrade(
    HttpTransactionShell* aTrans, nsIHttpUpgradeListener* aUpgradeListener) {
  MOZ_ASSERT(aTrans->AsHttpTransactionParent());

  if (!CanSend()) {
    // OnUpgradeFailed is expected to be called on socket thread.
    nsCOMPtr<nsIEventTarget> target =
        do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
    if (target) {
      nsCOMPtr<nsIHttpUpgradeListener> listener = aUpgradeListener;
      target->Dispatch(NS_NewRunnableFunction(
          "HttpConnectionMgrParent::CompleteUpgrade", [listener]() {
            (void)listener->OnUpgradeFailed(NS_ERROR_NOT_AVAILABLE);
          }));
    }
    return NS_OK;
  }

  // We need to link the id and the upgrade listener here, so
  // WebSocketConnectionParent can connect to the listener correctly later.
  uint32_t id = AddHttpUpgradeListenerToMap(aUpgradeListener);
  (void)SendStartWebSocketConnection(
      WrapNotNull(aTrans->AsHttpTransactionParent()), id);
  return NS_OK;
}

nsHttpConnectionMgr* HttpConnectionMgrParent::AsHttpConnectionMgr() {
  return nullptr;
}

HttpConnectionMgrParent* HttpConnectionMgrParent::AsHttpConnectionMgrParent() {
  return this;
}

}  // namespace mozilla::net
