/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "GMPDecryptorChild.h"
#include "GMPContentChild.h"
#include "GMPChild.h"
#include "base/task.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/Unused.h"
#include "runnable_utils.h"
#include <ctime>

#define ON_GMP_THREAD() (mPlugin->GMPMessageLoop() == MessageLoop::current())

#define CALL_ON_GMP_THREAD(_func, ...) \
  CallOnGMPThread(&GMPDecryptorChild::_func, __VA_ARGS__)

namespace mozilla {
namespace gmp {

GMPDecryptorChild::GMPDecryptorChild(GMPContentChild* aPlugin,
                                     const nsTArray<uint8_t>& aPluginVoucher,
                                     const nsTArray<uint8_t>& aSandboxVoucher)
  : mSession(nullptr)
  , mPlugin(aPlugin)
  , mPluginVoucher(aPluginVoucher)
  , mSandboxVoucher(aSandboxVoucher)
{
  MOZ_ASSERT(mPlugin);
}

GMPDecryptorChild::~GMPDecryptorChild()
{
}

template <typename MethodType, typename... ParamType>
void
GMPDecryptorChild::CallMethod(MethodType aMethod, ParamType&&... aParams)
{
  MOZ_ASSERT(ON_GMP_THREAD());
  // Don't send IPC messages after tear-down.
  if (mSession) {
    (this->*aMethod)(Forward<ParamType>(aParams)...);
  }
}

template<typename T>
struct AddConstReference {
  typedef const typename RemoveReference<T>::Type& Type;
};

template<typename MethodType, typename... ParamType>
void
GMPDecryptorChild::CallOnGMPThread(MethodType aMethod, ParamType&&... aParams)
{
  if (ON_GMP_THREAD()) {
    // Use forwarding reference when we can.
    CallMethod(aMethod, Forward<ParamType>(aParams)...);
  } else {
    // Use const reference when we have to.
    auto m = &GMPDecryptorChild::CallMethod<
        decltype(aMethod), typename AddConstReference<ParamType>::Type...>;
    RefPtr<mozilla::Runnable> t =
      dont_add_new_uses_of_this::NewRunnableMethod(this, m, aMethod, Forward<ParamType>(aParams)...);
    mPlugin->GMPMessageLoop()->PostTask(t.forget());
  }
}

void
GMPDecryptorChild::Init(GMPDecryptor* aSession)
{
  MOZ_ASSERT(aSession);
  mSession = aSession;
  // The ID of this decryptor is the IPDL actor ID. Note it's unique inside
  // the child process, but not necessarily across all gecko processes. However,
  // since GMPDecryptors are segregated by node ID/origin, we shouldn't end up
  // with clashes in the content process.
  SendSetDecryptorId(Id());
}

void
GMPDecryptorChild::SetSessionId(uint32_t aCreateSessionToken,
                                const char* aSessionId,
                                uint32_t aSessionIdLength)
{
  CALL_ON_GMP_THREAD(SendSetSessionId,
                     aCreateSessionToken, nsCString(aSessionId, aSessionIdLength));
}

void
GMPDecryptorChild::ResolveLoadSessionPromise(uint32_t aPromiseId,
                                             bool aSuccess)
{
  CALL_ON_GMP_THREAD(SendResolveLoadSessionPromise, aPromiseId, aSuccess);
}

void
GMPDecryptorChild::ResolvePromise(uint32_t aPromiseId)
{
  CALL_ON_GMP_THREAD(SendResolvePromise, aPromiseId);
}

void
GMPDecryptorChild::RejectPromise(uint32_t aPromiseId,
                                 GMPDOMException aException,
                                 const char* aMessage,
                                 uint32_t aMessageLength)
{
  CALL_ON_GMP_THREAD(SendRejectPromise,
                     aPromiseId, aException, nsCString(aMessage, aMessageLength));
}

void
GMPDecryptorChild::SessionMessage(const char* aSessionId,
                                  uint32_t aSessionIdLength,
                                  GMPSessionMessageType aMessageType,
                                  const uint8_t* aMessage,
                                  uint32_t aMessageLength)
{
  nsTArray<uint8_t> msg;
  msg.AppendElements(aMessage, aMessageLength);
  CALL_ON_GMP_THREAD(SendSessionMessage,
                     nsCString(aSessionId, aSessionIdLength),
                     aMessageType, Move(msg));
}

void
GMPDecryptorChild::ExpirationChange(const char* aSessionId,
                                    uint32_t aSessionIdLength,
                                    GMPTimestamp aExpiryTime)
{
  CALL_ON_GMP_THREAD(SendExpirationChange,
                     nsCString(aSessionId, aSessionIdLength), aExpiryTime);
}

void
GMPDecryptorChild::SessionClosed(const char* aSessionId,
                                 uint32_t aSessionIdLength)
{
  CALL_ON_GMP_THREAD(SendSessionClosed,
                     nsCString(aSessionId, aSessionIdLength));
}

void
GMPDecryptorChild::SessionError(const char* aSessionId,
                                uint32_t aSessionIdLength,
                                GMPDOMException aException,
                                uint32_t aSystemCode,
                                const char* aMessage,
                                uint32_t aMessageLength)
{
  CALL_ON_GMP_THREAD(SendSessionError,
                     nsCString(aSessionId, aSessionIdLength),
                     aException, aSystemCode,
                     nsCString(aMessage, aMessageLength));
}

void
GMPDecryptorChild::KeyStatusChanged(const char* aSessionId,
                                    uint32_t aSessionIdLength,
                                    const uint8_t* aKeyId,
                                    uint32_t aKeyIdLength,
                                    GMPMediaKeyStatus aStatus)
{
  AutoTArray<uint8_t, 16> kid;
  kid.AppendElements(aKeyId, aKeyIdLength);

  nsTArray<GMPKeyInformation> keyInfos;
  keyInfos.AppendElement(GMPKeyInformation(kid, aStatus));
  CALL_ON_GMP_THREAD(SendBatchedKeyStatusChanged,
                     nsCString(aSessionId, aSessionIdLength),
                     keyInfos);
}

void
GMPDecryptorChild::BatchedKeyStatusChanged(const char* aSessionId,
                                           uint32_t aSessionIdLength,
                                           const GMPMediaKeyInfo* aKeyInfos,
                                           uint32_t aKeyInfosLength)
{
  nsTArray<GMPKeyInformation> keyInfos;
  for (uint32_t i = 0; i < aKeyInfosLength; i++) {
    nsTArray<uint8_t> keyId;
    keyId.AppendElements(aKeyInfos[i].keyid, aKeyInfos[i].keyid_size);
    keyInfos.AppendElement(GMPKeyInformation(keyId, aKeyInfos[i].status));
  }
  CALL_ON_GMP_THREAD(SendBatchedKeyStatusChanged,
                     nsCString(aSessionId, aSessionIdLength),
                     keyInfos);
}

void
GMPDecryptorChild::Decrypted(GMPBuffer* aBuffer, GMPErr aResult)
{
  if (!ON_GMP_THREAD()) {
    // We should run this whole method on the GMP thread since the buffer needs
    // to be deleted after the SendDecrypted call.
    mPlugin->GMPMessageLoop()->PostTask(NewRunnableMethod
                                        <GMPBuffer*, GMPErr>(this,
                                                             &GMPDecryptorChild::Decrypted,
                                                             aBuffer, aResult));
    return;
  }

  if (!aBuffer) {
    NS_WARNING("GMPDecryptorCallback passed bull GMPBuffer");
    return;
  }

  auto buffer = static_cast<GMPBufferImpl*>(aBuffer);
  if (mSession) {
    SendDecrypted(buffer->mId, aResult, buffer->mData);
  }
  delete buffer;
}

void
GMPDecryptorChild::SetCapabilities(uint64_t aCaps)
{
  // Deprecated.
}

void
GMPDecryptorChild::GetSandboxVoucher(const uint8_t** aVoucher,
                                     uint32_t* aVoucherLength)
{
  if (!aVoucher || !aVoucherLength) {
    return;
  }
  *aVoucher = mSandboxVoucher.Elements();
  *aVoucherLength = mSandboxVoucher.Length();
}

void
GMPDecryptorChild::GetPluginVoucher(const uint8_t** aVoucher,
                                    uint32_t* aVoucherLength)
{
  if (!aVoucher || !aVoucherLength) {
    return;
  }
  *aVoucher = mPluginVoucher.Elements();
  *aVoucherLength = mPluginVoucher.Length();
}

bool
GMPDecryptorChild::RecvInit(const bool& aDistinctiveIdentifierRequired,
                            const bool& aPersistentStateRequired)
{
  if (!mSession) {
    return false;
  }
  mSession->Init(this, aDistinctiveIdentifierRequired, aPersistentStateRequired);
  return true;
}

bool
GMPDecryptorChild::RecvCreateSession(const uint32_t& aCreateSessionToken,
                                     const uint32_t& aPromiseId,
                                     const nsCString& aInitDataType,
                                     InfallibleTArray<uint8_t>&& aInitData,
                                     const GMPSessionType& aSessionType)
{
  if (!mSession) {
    return false;
  }

  mSession->CreateSession(aCreateSessionToken,
                          aPromiseId,
                          aInitDataType.get(),
                          aInitDataType.Length(),
                          aInitData.Elements(),
                          aInitData.Length(),
                          aSessionType);

  return true;
}

bool
GMPDecryptorChild::RecvLoadSession(const uint32_t& aPromiseId,
                                   const nsCString& aSessionId)
{
  if (!mSession) {
    return false;
  }

  mSession->LoadSession(aPromiseId,
                        aSessionId.get(),
                        aSessionId.Length());

  return true;
}

bool
GMPDecryptorChild::RecvUpdateSession(const uint32_t& aPromiseId,
                                     const nsCString& aSessionId,
                                     InfallibleTArray<uint8_t>&& aResponse)
{
  if (!mSession) {
    return false;
  }

  mSession->UpdateSession(aPromiseId,
                          aSessionId.get(),
                          aSessionId.Length(),
                          aResponse.Elements(),
                          aResponse.Length());

  return true;
}

bool
GMPDecryptorChild::RecvCloseSession(const uint32_t& aPromiseId,
                                    const nsCString& aSessionId)
{
  if (!mSession) {
    return false;
  }

  mSession->CloseSession(aPromiseId,
                         aSessionId.get(),
                         aSessionId.Length());

  return true;
}

bool
GMPDecryptorChild::RecvRemoveSession(const uint32_t& aPromiseId,
                                     const nsCString& aSessionId)
{
  if (!mSession) {
    return false;
  }

  mSession->RemoveSession(aPromiseId,
                          aSessionId.get(),
                          aSessionId.Length());

  return true;
}

bool
GMPDecryptorChild::RecvSetServerCertificate(const uint32_t& aPromiseId,
                                            InfallibleTArray<uint8_t>&& aServerCert)
{
  if (!mSession) {
    return false;
  }

  mSession->SetServerCertificate(aPromiseId,
                                 aServerCert.Elements(),
                                 aServerCert.Length());

  return true;
}

bool
GMPDecryptorChild::RecvDecrypt(const uint32_t& aId,
                               InfallibleTArray<uint8_t>&& aBuffer,
                               const GMPDecryptionData& aMetadata)
{
  if (!mSession) {
    return false;
  }

  // Note: the GMPBufferImpl created here is deleted when the GMP passes
  // it back in the Decrypted() callback above.
  GMPBufferImpl* buffer = new GMPBufferImpl(aId, aBuffer);

  // |metadata| lifetime is managed by |buffer|.
  GMPEncryptedBufferDataImpl* metadata = new GMPEncryptedBufferDataImpl(aMetadata);
  buffer->SetMetadata(metadata);

  mSession->Decrypt(buffer, metadata);
  return true;
}

bool
GMPDecryptorChild::RecvDecryptingComplete()
{
  // Reset |mSession| before calling DecryptingComplete(). We should not send
  // any IPC messages during tear-down.
  auto session = mSession;
  mSession = nullptr;

  if (!session) {
    return false;
  }

  session->DecryptingComplete();

  Unused << Send__delete__(this);

  return true;
}

} // namespace gmp
} // namespace mozilla

// avoid redefined macro in unified build
#undef ON_GMP_THREAD
#undef CALL_ON_GMP_THREAD