/* 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 "mozilla/net/SocketProcessChild.h"
#include "mozilla/net/SocketProcessParent.h"
#include "nsHttpActivityDistributor.h"
#include "nsHttpHandler.h"
#include "nsCOMPtr.h"
#include "nsIOService.h"
#include "nsNetUtil.h"
#include "nsQueryObject.h"
#include "nsThreadUtils.h"
#include "NullHttpChannel.h"

namespace mozilla {
namespace net {

using ObserverHolder = nsMainThreadPtrHolder<nsIHttpActivityObserver>;
using ObserverHandle = nsMainThreadPtrHandle<nsIHttpActivityObserver>;

NS_IMPL_ISUPPORTS(nsHttpActivityDistributor, nsIHttpActivityDistributor,
                  nsIHttpActivityObserver)

NS_IMETHODIMP
nsHttpActivityDistributor::ObserveActivity(nsISupports* aHttpChannel,
                                           uint32_t aActivityType,
                                           uint32_t aActivitySubtype,
                                           PRTime aTimestamp,
                                           uint64_t aExtraSizeData,
                                           const nsACString& aExtraStringData) {
  MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread());
  RefPtr<nsHttpActivityDistributor> self(this);

  ObserverArray observers;
  {
    MutexAutoLock lock(mLock);
    observers = mObservers.Clone();
  }
  for (size_t i = 0; i < observers.Length(); i++) {
    (void)observers[i]->ObserveActivity(aHttpChannel, aActivityType,
                                        aActivitySubtype, aTimestamp,
                                        aExtraSizeData, aExtraStringData);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsHttpActivityDistributor::ObserveConnectionActivity(
    const nsACString& aHost, int32_t aPort, bool aSSL, bool aHasECH,
    bool aIsHttp3, uint32_t aActivityType, uint32_t aActivitySubtype,
    PRTime aTimestamp, const nsACString& aExtraStringData) {
  MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread());
  RefPtr<nsHttpActivityDistributor> self(this);

  ObserverArray observers;
  {
    MutexAutoLock lock(mLock);
    observers = mObservers.Clone();
  }
  for (size_t i = 0; i < observers.Length(); i++) {
    (void)observers[i]->ObserveConnectionActivity(
        aHost, aPort, aSSL, aHasECH, aIsHttp3, aActivityType, aActivitySubtype,
        aTimestamp, aExtraStringData);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsHttpActivityDistributor::ObserveActivityWithArgs(
    const HttpActivityArgs& aArgs, uint32_t aActivityType,
    uint32_t aActivitySubtype, PRTime aTimestamp, uint64_t aExtraSizeData,
    const nsACString& aExtraStringData) {
  HttpActivityArgs args(aArgs);
  nsCString extraStringData(aExtraStringData);
  if (XRE_IsSocketProcess()) {
    auto task = [args{std::move(args)}, aActivityType, aActivitySubtype,
                 aTimestamp, aExtraSizeData,
                 extraStringData{std::move(extraStringData)}]() {
      SocketProcessChild::GetSingleton()->SendObserveHttpActivity(
          args, aActivityType, aActivitySubtype, aTimestamp, aExtraSizeData,
          extraStringData);
    };

    if (!NS_IsMainThread()) {
      return NS_DispatchToMainThread(NS_NewRunnableFunction(
          "net::nsHttpActivityDistributor::ObserveActivityWithArgs", task));
    }

    task();
    return NS_OK;
  }

  MOZ_ASSERT(XRE_IsParentProcess());

  RefPtr<nsHttpActivityDistributor> self = this;
  auto task = [args{std::move(args)}, aActivityType, aActivitySubtype,
               aTimestamp, aExtraSizeData,
               extraStringData{std::move(extraStringData)},
               self{std::move(self)}]() {
    if (args.type() == HttpActivityArgs::Tuint64_t) {
      nsWeakPtr weakPtr = gHttpHandler->GetWeakHttpChannel(args.get_uint64_t());
      if (nsCOMPtr<nsIHttpChannel> channel = do_QueryReferent(weakPtr)) {
        (void)self->ObserveActivity(channel, aActivityType, aActivitySubtype,
                                    aTimestamp, aExtraSizeData,
                                    extraStringData);
      }
    } else if (args.type() == HttpActivityArgs::THttpActivity) {
      nsCOMPtr<nsIURI> uri;
      nsAutoCString portStr(""_ns);
      int32_t port = args.get_HttpActivity().port();
      bool endToEndSSL = args.get_HttpActivity().endToEndSSL();
      if (port != -1 &&
          ((endToEndSSL && port != 443) || (!endToEndSSL && port != 80))) {
        portStr.AppendInt(port);
      }

      nsresult rv =
          NS_NewURI(getter_AddRefs(uri),
                    (endToEndSSL ? "https://"_ns : "http://"_ns) +
                        args.get_HttpActivity().host() + ":"_ns + portStr);
      if (NS_FAILED(rv)) {
        return;
      }

      RefPtr<NullHttpChannel> channel = new NullHttpChannel();
      rv = channel->Init(uri, 0, nullptr, 0, nullptr);
      MOZ_ASSERT(NS_SUCCEEDED(rv));

      (void)self->ObserveActivity(static_cast<nsIChannel*>(channel),
                                  aActivityType, aActivitySubtype, aTimestamp,
                                  aExtraSizeData, extraStringData);
    } else if (args.type() == HttpActivityArgs::THttpConnectionActivity) {
      const HttpConnectionActivity& activity =
          args.get_HttpConnectionActivity();
      (void)self->ObserveConnectionActivity(
          activity.host(), activity.port(), activity.ssl(), activity.hasECH(),
          activity.isHttp3(), aActivityType, aActivitySubtype, aTimestamp,
          activity.connInfoKey());
    }
  };

  if (!NS_IsMainThread()) {
    return NS_DispatchToMainThread(NS_NewRunnableFunction(
        "net::nsHttpActivityDistributor::ObserveActivityWithArgs", task));
  }

  task();
  return NS_OK;
}

bool nsHttpActivityDistributor::Activated() { return mActivated; }

bool nsHttpActivityDistributor::ObserveProxyResponseEnabled() {
  return mObserveProxyResponse;
}

bool nsHttpActivityDistributor::ObserveConnectionEnabled() {
  return mObserveConnection;
}

NS_IMETHODIMP
nsHttpActivityDistributor::GetIsActive(bool* isActive) {
  NS_ENSURE_ARG_POINTER(isActive);
  if (XRE_IsSocketProcess()) {
    *isActive = mActivated;
    return NS_OK;
  }

  MutexAutoLock lock(mLock);
  *isActive = mActivated = !!mObservers.Length();
  return NS_OK;
}

NS_IMETHODIMP nsHttpActivityDistributor::SetIsActive(bool aActived) {
  MOZ_RELEASE_ASSERT(XRE_IsSocketProcess());

  mActivated = aActived;
  return NS_OK;
}

NS_IMETHODIMP
nsHttpActivityDistributor::AddObserver(nsIHttpActivityObserver* aObserver) {
  MOZ_ASSERT(XRE_IsParentProcess());
  if (!NS_IsMainThread()) {
    // We only support calling this from the main thread.
    return NS_ERROR_FAILURE;
  }

  ObserverHandle observer(
      new ObserverHolder("nsIHttpActivityObserver", aObserver));

  bool wasEmpty = false;
  {
    MutexAutoLock lock(mLock);
    wasEmpty = mObservers.IsEmpty();
    // XXX(Bug 1631371) Check if this should use a fallible operation as it
    // pretended earlier.
    mObservers.AppendElement(observer);
  }

  if (wasEmpty) {
    mActivated = true;
    if (nsIOService::UseSocketProcess()) {
      auto task = []() {
        RefPtr<SocketProcessParent> parent =
            SocketProcessParent::GetSingleton();
        if (parent && parent->CanSend()) {
          (void)parent->SendOnHttpActivityDistributorActivated(true);
        }
      };
      gIOService->CallOrWaitForSocketProcess(task);
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
nsHttpActivityDistributor::RemoveObserver(nsIHttpActivityObserver* aObserver) {
  MOZ_ASSERT(XRE_IsParentProcess());
  if (!NS_IsMainThread()) {
    // We only support calling this from the main thread.
    return NS_ERROR_FAILURE;
  }

  ObserverHandle observer(
      new ObserverHolder("nsIHttpActivityObserver", aObserver));

  {
    MutexAutoLock lock(mLock);
    if (!mObservers.RemoveElement(observer)) {
      return NS_ERROR_FAILURE;
    }
    mActivated = mObservers.IsEmpty();
  }

  if (nsIOService::UseSocketProcess() && !mActivated) {
    auto task = []() {
      RefPtr<SocketProcessParent> parent = SocketProcessParent::GetSingleton();
      if (parent && parent->CanSend()) {
        (void)parent->SendOnHttpActivityDistributorActivated(false);
      }
    };
    gIOService->CallOrWaitForSocketProcess(task);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsHttpActivityDistributor::GetObserveProxyResponse(
    bool* aObserveProxyResponse) {
  NS_ENSURE_ARG_POINTER(aObserveProxyResponse);
  bool result = mObserveProxyResponse;
  *aObserveProxyResponse = result;
  return NS_OK;
}

NS_IMETHODIMP
nsHttpActivityDistributor::SetObserveProxyResponse(bool aObserveProxyResponse) {
  if (!NS_IsMainThread()) {
    // We only support calling this from the main thread.
    return NS_ERROR_FAILURE;
  }

  mObserveProxyResponse = aObserveProxyResponse;
  if (nsIOService::UseSocketProcess()) {
    auto task = [aObserveProxyResponse]() {
      RefPtr<SocketProcessParent> parent = SocketProcessParent::GetSingleton();
      if (parent && parent->CanSend()) {
        (void)parent->SendOnHttpActivityDistributorObserveProxyResponse(
            aObserveProxyResponse);
      }
    };
    gIOService->CallOrWaitForSocketProcess(task);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsHttpActivityDistributor::GetObserveConnection(bool* aObserveConnection) {
  NS_ENSURE_ARG_POINTER(aObserveConnection);

  *aObserveConnection = mObserveConnection;
  return NS_OK;
}

NS_IMETHODIMP
nsHttpActivityDistributor::SetObserveConnection(bool aObserveConnection) {
  if (!NS_IsMainThread()) {
    // We only support calling this from the main thread.
    return NS_ERROR_FAILURE;
  }

  mObserveConnection = aObserveConnection;
  if (nsIOService::UseSocketProcess()) {
    auto task = [aObserveConnection]() {
      RefPtr<SocketProcessParent> parent = SocketProcessParent::GetSingleton();
      if (parent && parent->CanSend()) {
        (void)parent->SendOnHttpActivityDistributorObserveConnection(
            aObserveConnection);
      }
    };
    gIOService->CallOrWaitForSocketProcess(task);
  }
  return NS_OK;
}

}  // namespace net
}  // namespace mozilla
