/* 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 "HttpBackgroundChannelChild.h"

#include "HttpChannelChild.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/Endpoint.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/net/BackgroundDataBridgeChild.h"
#include "nsSocketTransportService2.h"

using mozilla::ipc::BackgroundChild;
using mozilla::ipc::IPCResult;

namespace mozilla {
namespace net {

// HttpBackgroundChannelChild
HttpBackgroundChannelChild::HttpBackgroundChannelChild() = default;

HttpBackgroundChannelChild::~HttpBackgroundChannelChild() = default;

nsresult HttpBackgroundChannelChild::Init(HttpChannelChild* aChannelChild) {
  LOG(
      ("HttpBackgroundChannelChild::Init [this=%p httpChannel=%p "
       "channelId=%" PRIu64 "]\n",
       this, aChannelChild, aChannelChild->ChannelId()));
  MOZ_ASSERT(OnSocketThread());
  NS_ENSURE_ARG(aChannelChild);

  mChannelChild = aChannelChild;

  if (NS_WARN_IF(!CreateBackgroundChannel())) {
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
    // mChannelChild may be nulled already. Use aChannelChild
    aChannelChild->mCreateBackgroundChannelFailed = true;
#endif
    mChannelChild = nullptr;
    return NS_ERROR_FAILURE;
  }

  mFirstODASource = ODA_PENDING;
  mOnStopRequestCalled = false;
  return NS_OK;
}

void HttpBackgroundChannelChild::CreateDataBridge(
    Endpoint<PBackgroundDataBridgeChild>&& aEndpoint) {
  MOZ_ASSERT(OnSocketThread());

  if (!mChannelChild) {
    return;
  }

  RefPtr<BackgroundDataBridgeChild> dataBridgeChild =
      new BackgroundDataBridgeChild(this);
  aEndpoint.Bind(dataBridgeChild);
}

void HttpBackgroundChannelChild::OnChannelClosed() {
  LOG(("HttpBackgroundChannelChild::OnChannelClosed [this=%p]\n", this));
  MOZ_ASSERT(OnSocketThread());

#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
  if (mChannelChild) {
    mChannelChild->mBackgroundChildQueueFinalState =
        mQueuedRunnables.IsEmpty() ? HttpChannelChild::BCKCHILD_EMPTY
                                   : HttpChannelChild::BCKCHILD_NON_EMPTY;
  }
#endif

  // HttpChannelChild is not going to handle any incoming message.
  mChannelChild = nullptr;

  // Remove pending IPC messages as well.
  mQueuedRunnables.Clear();
  mConsoleReportTask = nullptr;
}

bool HttpBackgroundChannelChild::ChannelClosed() {
  MOZ_ASSERT(OnSocketThread());

  return !mChannelChild;
}

void HttpBackgroundChannelChild::OnStartRequestReceived(
    Maybe<uint32_t> aMultiPartID) {
  LOG(("HttpBackgroundChannelChild::OnStartRequestReceived [this=%p]\n", this));
  MOZ_ASSERT(OnSocketThread());
  MOZ_ASSERT(mChannelChild);
  MOZ_ASSERT(!mStartReceived || *aMultiPartID > 0);

  mStartReceived = true;

  nsTArray<nsCOMPtr<nsIRunnable>> runnables = std::move(mQueuedRunnables);

  for (const auto& event : runnables) {
    // Note: these runnables call Recv* methods on HttpBackgroundChannelChild
    // but not the Process* methods on HttpChannelChild.
    event->Run();
  }

  // Ensure no new message is enqueued.
  MOZ_ASSERT(mQueuedRunnables.IsEmpty());
}

bool HttpBackgroundChannelChild::CreateBackgroundChannel() {
  LOG(("HttpBackgroundChannelChild::CreateBackgroundChannel [this=%p]\n",
       this));
  MOZ_ASSERT(OnSocketThread());
  MOZ_ASSERT(mChannelChild);

  PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread();
  if (NS_WARN_IF(!actorChild)) {
    return false;
  }

  const uint64_t channelId = mChannelChild->ChannelId();
  if (!actorChild->SendPHttpBackgroundChannelConstructor(this, channelId)) {
    return false;
  }

  mChannelChild->OnBackgroundChildReady(this);
  return true;
}

IPCResult HttpBackgroundChannelChild::RecvOnAfterLastPart(
    const nsresult& aStatus) {
  LOG(("HttpBackgroundChannelChild::RecvOnAfterLastPart [this=%p]\n", this));
  MOZ_ASSERT(OnSocketThread());

  if (NS_WARN_IF(!mChannelChild)) {
    return IPC_OK();
  }

  mChannelChild->ProcessOnAfterLastPart(aStatus);
  return IPC_OK();
}

IPCResult HttpBackgroundChannelChild::RecvOnProgress(
    const int64_t& aProgress, const int64_t& aProgressMax) {
  LOG(("HttpBackgroundChannelChild::RecvOnProgress [this=%p]\n", this));
  MOZ_ASSERT(OnSocketThread());

  if (NS_WARN_IF(!mChannelChild)) {
    return IPC_OK();
  }

  mChannelChild->ProcessOnProgress(aProgress, aProgressMax);
  return IPC_OK();
}

IPCResult HttpBackgroundChannelChild::RecvOnStatus(const nsresult& aStatus) {
  LOG(("HttpBackgroundChannelChild::RecvOnStatus [this=%p]\n", this));
  MOZ_ASSERT(OnSocketThread());

  if (NS_WARN_IF(!mChannelChild)) {
    return IPC_OK();
  }

  mChannelChild->ProcessOnStatus(aStatus);
  return IPC_OK();
}

bool HttpBackgroundChannelChild::IsWaitingOnStartRequest() {
  MOZ_ASSERT(OnSocketThread());

  // Need to wait for OnStartRequest if it is sent by
  // parent process but not received by content process.
  return !mStartReceived;
}

// PHttpBackgroundChannelChild
IPCResult HttpBackgroundChannelChild::RecvOnStartRequest(
    const nsHttpResponseHead& aResponseHead, const bool& aUseResponseHead,
    const nsHttpHeaderArray& aRequestHeaders,
    const HttpChannelOnStartRequestArgs& aArgs,
    const HttpChannelAltDataStream& aAltData,
    const TimeStamp& aOnStartRequestStart) {
  LOG((
      "HttpBackgroundChannelChild::RecvOnStartRequest [this=%p, status=%" PRIx32
      "]\n",
      this, static_cast<uint32_t>(aArgs.channelStatus())));
  MOZ_ASSERT(OnSocketThread());

  if (NS_WARN_IF(!mChannelChild)) {
    return IPC_OK();
  }

  mFirstODASource =
      aArgs.dataFromSocketProcess() ? ODA_FROM_SOCKET : ODA_FROM_PARENT;

  mChannelChild->ProcessOnStartRequest(aResponseHead, aUseResponseHead,
                                       aRequestHeaders, aArgs, aAltData,
                                       aOnStartRequestStart);
  // Allow to queue other runnable since OnStartRequest Event already hits the
  // child's mEventQ.
  OnStartRequestReceived(aArgs.multiPartID());

  return IPC_OK();
}

IPCResult HttpBackgroundChannelChild::RecvOnTransportAndData(
    const nsresult& aChannelStatus, const nsresult& aTransportStatus,
    const uint64_t& aOffset, const nsACString& aData,
    const bool& aDataFromSocketProcess,
    const TimeStamp& aOnDataAvailableStart) {
  RefPtr<HttpBackgroundChannelChild> self = this;
  std::function<void()> callProcessOnTransportAndData =
      [self, aChannelStatus, aTransportStatus, aOffset, data = nsCString(aData),
       aDataFromSocketProcess, aOnDataAvailableStart]() {
        LOG(
            ("HttpBackgroundChannelChild::RecvOnTransportAndData [this=%p, "
             "aDataFromSocketProcess=%d, mFirstODASource=%d]\n",
             self.get(), aDataFromSocketProcess, self->mFirstODASource));
        MOZ_ASSERT(OnSocketThread());

        if (NS_WARN_IF(!self->mChannelChild)) {
          return;
        }

        if (((self->mFirstODASource == ODA_FROM_SOCKET) &&
             !aDataFromSocketProcess) ||
            ((self->mFirstODASource == ODA_FROM_PARENT) &&
             aDataFromSocketProcess)) {
          return;
        }

        // The HttpTransactionChild in socket process may not know that this
        // request is cancelled or failed due to the IPC delay. In this case, we
        // should not forward ODA to HttpChannelChild.
        nsresult channelStatus;
        self->mChannelChild->GetStatus(&channelStatus);
        if (NS_FAILED(channelStatus)) {
          return;
        }

        self->mChannelChild->ProcessOnTransportAndData(
            aChannelStatus, aTransportStatus, aOffset, data,
            aOnDataAvailableStart);
      };

  // Bug 1641336: Race only happens if the data is from socket process.
  if (IsWaitingOnStartRequest()) {
    LOG(("  > pending until OnStartRequest [offset=%" PRIu64 " count=%zu]\n",
         aOffset, aData.Length()));

    mQueuedRunnables.AppendElement(NS_NewRunnableFunction(
        "HttpBackgroundChannelChild::RecvOnTransportAndData",
        std::move(callProcessOnTransportAndData)));
    return IPC_OK();
  }

  callProcessOnTransportAndData();
  return IPC_OK();
}

IPCResult HttpBackgroundChannelChild::RecvOnStopRequest(
    const nsresult& aChannelStatus, const ResourceTimingStructArgs& aTiming,
    const TimeStamp& aLastActiveTabOptHit,
    const nsHttpHeaderArray& aResponseTrailers,
    nsTArray<ConsoleReportCollected>&& aConsoleReports,
    const bool& aFromSocketProcess, const TimeStamp& aOnStopRequestStart) {
  LOG(
      ("HttpBackgroundChannelChild::RecvOnStopRequest [this=%p, "
       "aFromSocketProcess=%d, mFirstODASource=%d]\n",
       this, aFromSocketProcess, mFirstODASource));
  MOZ_ASSERT(gSocketTransportService);
  MOZ_ASSERT(gSocketTransportService->IsOnCurrentThreadInfallible());

  // It's enough to set this from (just before) OnStopRequest notification,
  // since we don't need this value sooner than a channel was done loading -
  // everything this timestamp affects takes place only after a channel's
  // OnStopRequest.
  nsHttp::SetLastActiveTabLoadOptimizationHit(aLastActiveTabOptHit);

  if (NS_WARN_IF(!mChannelChild)) {
    return IPC_OK();
  }

  if (IsWaitingOnStartRequest()) {
    LOG(("  > pending until OnStartRequest [status=%" PRIx32 "]\n",
         static_cast<uint32_t>(aChannelStatus)));

    RefPtr<HttpBackgroundChannelChild> self = this;

    nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
        "HttpBackgroundChannelChild::RecvOnStopRequest",
        [self, aChannelStatus, aTiming, aLastActiveTabOptHit, aResponseTrailers,
         consoleReports = CopyableTArray{std::move(aConsoleReports)},
         aFromSocketProcess, aOnStopRequestStart]() mutable {
          self->RecvOnStopRequest(aChannelStatus, aTiming, aLastActiveTabOptHit,
                                  aResponseTrailers, std::move(consoleReports),
                                  aFromSocketProcess, aOnStopRequestStart);
        });

    mQueuedRunnables.AppendElement(task.forget());
    return IPC_OK();
  }

  if (mFirstODASource != ODA_FROM_SOCKET) {
    if (!aFromSocketProcess) {
      mOnStopRequestCalled = true;
      mChannelChild->ProcessOnStopRequest(
          aChannelStatus, aTiming, aResponseTrailers,
          std::move(aConsoleReports), false, aOnStopRequestStart);
    }
    return IPC_OK();
  }

  MOZ_ASSERT(mFirstODASource == ODA_FROM_SOCKET);

  if (aFromSocketProcess) {
    MOZ_ASSERT(!mOnStopRequestCalled);
    mOnStopRequestCalled = true;
    mChannelChild->ProcessOnStopRequest(
        aChannelStatus, aTiming, aResponseTrailers, std::move(aConsoleReports),
        true, aOnStopRequestStart);
    if (mConsoleReportTask) {
      mConsoleReportTask();
      mConsoleReportTask = nullptr;
    }
    return IPC_OK();
  }

  return IPC_OK();
}

IPCResult HttpBackgroundChannelChild::RecvOnConsoleReport(
    nsTArray<ConsoleReportCollected>&& aConsoleReports) {
  LOG(("HttpBackgroundChannelChild::RecvOnConsoleReport [this=%p]\n", this));
  MOZ_ASSERT(mFirstODASource == ODA_FROM_SOCKET);
  MOZ_ASSERT(gSocketTransportService);
  MOZ_ASSERT(gSocketTransportService->IsOnCurrentThreadInfallible());

  if (IsWaitingOnStartRequest()) {
    LOG(("  > pending until OnStartRequest\n"));

    RefPtr<HttpBackgroundChannelChild> self = this;

    nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
        "HttpBackgroundChannelChild::RecvOnConsoleReport",
        [self, consoleReports =
                   CopyableTArray{std::move(aConsoleReports)}]() mutable {
          self->RecvOnConsoleReport(std::move(consoleReports));
        });

    mQueuedRunnables.AppendElement(task.forget());
    return IPC_OK();
  }

  if (mOnStopRequestCalled) {
    mChannelChild->ProcessOnConsoleReport(std::move(aConsoleReports));
  } else {
    RefPtr<HttpBackgroundChannelChild> self = this;
    mConsoleReportTask = [self, consoleReports = CopyableTArray{
                                    std::move(aConsoleReports)}]() mutable {
      self->mChannelChild->ProcessOnConsoleReport(std::move(consoleReports));
    };
  }

  return IPC_OK();
}

IPCResult HttpBackgroundChannelChild::RecvNotifyClassificationFlags(
    const uint32_t& aClassificationFlags, const bool& aIsThirdParty) {
  LOG(
      ("HttpBackgroundChannelChild::RecvNotifyClassificationFlags "
       "classificationFlags=%" PRIu32 ", thirdparty=%d [this=%p]\n",
       aClassificationFlags, static_cast<int>(aIsThirdParty), this));
  MOZ_ASSERT(OnSocketThread());

  if (NS_WARN_IF(!mChannelChild)) {
    return IPC_OK();
  }

  // NotifyClassificationFlags has no order dependency to OnStartRequest.
  // It this be handled as soon as possible
  mChannelChild->ProcessNotifyClassificationFlags(aClassificationFlags,
                                                  aIsThirdParty);

  return IPC_OK();
}

IPCResult HttpBackgroundChannelChild::RecvSetClassifierMatchedInfo(
    const ClassifierInfo& info) {
  LOG(("HttpBackgroundChannelChild::RecvSetClassifierMatchedInfo [this=%p]\n",
       this));
  MOZ_ASSERT(OnSocketThread());

  if (NS_WARN_IF(!mChannelChild)) {
    return IPC_OK();
  }

  // SetClassifierMatchedInfo has no order dependency to OnStartRequest.
  // It this be handled as soon as possible
  mChannelChild->ProcessSetClassifierMatchedInfo(info.list(), info.provider(),
                                                 info.fullhash());

  return IPC_OK();
}

IPCResult HttpBackgroundChannelChild::RecvSetClassifierMatchedTrackingInfo(
    const ClassifierInfo& info) {
  LOG(
      ("HttpBackgroundChannelChild::RecvSetClassifierMatchedTrackingInfo "
       "[this=%p]\n",
       this));
  MOZ_ASSERT(OnSocketThread());

  if (NS_WARN_IF(!mChannelChild)) {
    return IPC_OK();
  }

  // SetClassifierMatchedTrackingInfo has no order dependency to
  // OnStartRequest. It this be handled as soon as possible
  mChannelChild->ProcessSetClassifierMatchedTrackingInfo(info.list(),
                                                         info.fullhash());

  return IPC_OK();
}

IPCResult HttpBackgroundChannelChild::RecvAttachStreamFilter(
    Endpoint<extensions::PStreamFilterParent>&& aEndpoint) {
  LOG(("HttpBackgroundChannelChild::RecvAttachStreamFilter [this=%p]\n", this));
  MOZ_ASSERT(OnSocketThread());

  if (NS_WARN_IF(!mChannelChild)) {
    return IPC_OK();
  }

  mChannelChild->ProcessAttachStreamFilter(std::move(aEndpoint));
  return IPC_OK();
}

IPCResult HttpBackgroundChannelChild::RecvDetachStreamFilters() {
  LOG(("HttpBackgroundChannelChild::RecvDetachStreamFilters [this=%p]\n",
       this));
  MOZ_ASSERT(OnSocketThread());

  if (NS_WARN_IF(!mChannelChild)) {
    return IPC_OK();
  }

  mChannelChild->ProcessDetachStreamFilters();
  return IPC_OK();
}

void HttpBackgroundChannelChild::ActorDestroy(ActorDestroyReason aWhy) {
  LOG(("HttpBackgroundChannelChild::ActorDestroy[this=%p reason=%d]\n", this,
       static_cast<int>(aWhy)));
  // This function might be called during shutdown phase, so OnSocketThread()
  // might return false even on STS thread. Use IsOnCurrentThreadInfallible()
  // to get correct information.
  MOZ_ASSERT(gSocketTransportService);
  MOZ_ASSERT(gSocketTransportService->IsOnCurrentThreadInfallible());

  // Drop any IPC runnables that were queued waiting for OnStartRequest. After
  // ActorDestroy returns, no further Recv* calls fire to drain the queue, and
  // each entry holds a RefPtr<self> via its capture; without clearing them
  // the actor (and everything it transitively owns) is kept alive forever.
  mQueuedRunnables.Clear();
  mConsoleReportTask = nullptr;

  if (mChannelChild) {
    RefPtr<HttpChannelChild> channelChild = std::move(mChannelChild);

    channelChild->OnBackgroundChildDestroyed(this);
  }
}

}  // namespace net
}  // namespace mozilla
