/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: sw=4 ts=4 et :
 * 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/. */

#include "FunctionBrokerParent.h"
#include "FunctionBroker.h"
#include "FunctionBrokerThread.h"

namespace mozilla {
namespace plugins {

#if defined(XP_WIN)
UlongPairToIdMap sPairToIdMap;
IdToUlongPairMap sIdToPairMap;
PtrToIdMap sPtrToIdMap;
IdToPtrMap sIdToPtrMap;
#endif // defined(XP_WIN)

/* static */ FunctionBrokerParent*
FunctionBrokerParent::Create(Endpoint<PFunctionBrokerParent>&& aParentEnd)
{
  FunctionBrokerThread* thread = FunctionBrokerThread::Create();
  if (!thread) {
    return nullptr;
  }

  // We get the FunctionHooks so that they are created here, not on the
  // message thread.
  FunctionHook::GetHooks();

  return new FunctionBrokerParent(thread, std::move(aParentEnd));
}

FunctionBrokerParent::FunctionBrokerParent(FunctionBrokerThread* aThread,
                                           Endpoint<PFunctionBrokerParent>&& aParentEnd) :
    mThread(aThread)
  , mMonitor("FunctionBrokerParent Lock")
  , mShutdownDone(false)
{
  MOZ_ASSERT(mThread);
  mThread->Dispatch(NewNonOwningRunnableMethod<Endpoint<PFunctionBrokerParent>&&>(
          "FunctionBrokerParent::Bind", this, &FunctionBrokerParent::Bind, std::move(aParentEnd)));
}

FunctionBrokerParent::~FunctionBrokerParent()
{
#if defined(XP_WIN) && defined(MOZ_SANDBOX)
  // Clean up any file permissions that we granted to the child process.
  MOZ_RELEASE_ASSERT(NS_IsMainThread());
  RemovePermissionsForProcess(OtherPid());
#endif
}

void
FunctionBrokerParent::Bind(Endpoint<PFunctionBrokerParent>&& aEnd)
{
  MOZ_RELEASE_ASSERT(mThread->IsOnThread());
  DebugOnly<bool> ok = aEnd.Bind(this);
  MOZ_ASSERT(ok);
}

void
FunctionBrokerParent::ShutdownOnBrokerThread()
{
  MOZ_ASSERT(mThread->IsOnThread());
  Close();

  // Notify waiting thread that we are done.
  MonitorAutoLock lock(mMonitor);
  mShutdownDone = true;
  mMonitor.Notify();
}

void
FunctionBrokerParent::Destroy(FunctionBrokerParent* aInst)
{
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(aInst);

  {
    // Hold the lock while we destroy the actor on the broker thread.
    MonitorAutoLock lock(aInst->mMonitor);
    aInst->mThread->Dispatch(NewNonOwningRunnableMethod(
      "FunctionBrokerParent::ShutdownOnBrokerThread", aInst,
      &FunctionBrokerParent::ShutdownOnBrokerThread));

    // Wait for broker thread to complete destruction.
    while (!aInst->mShutdownDone) {
      aInst->mMonitor.Wait();
    }
  }

  delete aInst;
}

void
FunctionBrokerParent::ActorDestroy(ActorDestroyReason aWhy)
{
  MOZ_RELEASE_ASSERT(mThread->IsOnThread());
}

mozilla::ipc::IPCResult
FunctionBrokerParent::RecvBrokerFunction(const FunctionHookId &aFunctionId,
                                         const IpdlTuple &aInTuple,
                                         IpdlTuple *aOutTuple)
{
#if defined(XP_WIN)
  MOZ_ASSERT(mThread->IsOnThread());
  if (RunBrokeredFunction(OtherPid(), aFunctionId, aInTuple, aOutTuple)) {
    return IPC_OK();
  }
  return IPC_FAIL_NO_REASON(this);
#else
  MOZ_ASSERT_UNREACHABLE("BrokerFunction is currently only implemented on Windows.");
  return IPC_FAIL_NO_REASON(this);
#endif
}

// static
bool
FunctionBrokerParent::RunBrokeredFunction(base::ProcessId aClientId,
                                        const FunctionHookId &aFunctionId,
                                        const IPC::IpdlTuple &aInTuple,
                                        IPC::IpdlTuple *aOutTuple)
{
  if ((size_t)aFunctionId >= FunctionHook::GetHooks()->Length()) {
    MOZ_ASSERT_UNREACHABLE("Invalid function ID");
    return false;
  }

  FunctionHook* hook = FunctionHook::GetHooks()->ElementAt(aFunctionId);
  MOZ_ASSERT(hook->FunctionId() == aFunctionId);
  return hook->RunOriginalFunction(aClientId, aInTuple, aOutTuple);
}

#if defined(XP_WIN) && defined(MOZ_SANDBOX)

mozilla::SandboxPermissions FunctionBrokerParent::sSandboxPermissions;

// static
void
FunctionBrokerParent::RemovePermissionsForProcess(base::ProcessId aClientId)
{
  sSandboxPermissions.RemovePermissionsForProcess(aClientId);
}

#endif // defined(XP_WIN) && defined(MOZ_SANDBOX)

} // namespace plugins
} // namespace mozilla
