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

#ifndef mozilla_mscom_COMPtrHolder_h
#define mozilla_mscom_COMPtrHolder_h

#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Move.h"
#include "mozilla/mscom/ProxyStream.h"
#include "mozilla/mscom/Ptr.h"
#if defined(MOZ_CONTENT_SANDBOX)
#include "mozilla/SandboxSettings.h"
#endif // defined(MOZ_CONTENT_SANDBOX)
#if defined(MOZ_CRASHREPORTER)
#include "nsExceptionHandler.h"
#endif // defined(MOZ_CRASHREPORTER)

namespace mozilla {
namespace mscom {

template<typename Interface, const IID& _IID>
class COMPtrHolder
{
public:
  typedef ProxyUniquePtr<Interface> COMPtrType;
  typedef COMPtrHolder<Interface, _IID> ThisType;
  typedef typename detail::EnvironmentSelector<Interface>::Type EnvType;

  COMPtrHolder() {}

  MOZ_IMPLICIT COMPtrHolder(decltype(nullptr))
  {
  }

  explicit COMPtrHolder(COMPtrType&& aPtr)
    : mPtr(Forward<COMPtrType>(aPtr))
  {
  }

  COMPtrHolder(COMPtrType&& aPtr, const ActivationContext& aActCtx)
    : mPtr(Forward<COMPtrType>(aPtr))
    , mActCtx(aActCtx)
  {
  }

  Interface* Get() const
  {
    return mPtr.get();
  }

  MOZ_MUST_USE Interface* Release()
  {
    return mPtr.release();
  }

  void Set(COMPtrType&& aPtr)
  {
    mPtr = Forward<COMPtrType>(aPtr);
  }

  void SetActCtx(const ActivationContext& aActCtx)
  {
    mActCtx = aActCtx;
  }

#if defined(MOZ_CONTENT_SANDBOX)
  // This method is const because we need to call it during IPC write, where
  // we are passed as a const argument. At higher sandboxing levels we need to
  // save this artifact from the serialization process for later deletion.
  void PreserveStream(PreservedStreamPtr aPtr) const
  {
    MOZ_ASSERT(!mMarshaledStream);
    mMarshaledStream = Move(aPtr);
  }

  PreservedStreamPtr GetPreservedStream()
  {
    return Move(mMarshaledStream);
  }
#endif // defined(MOZ_CONTENT_SANDBOX)

  COMPtrHolder(const COMPtrHolder& aOther) = delete;

  COMPtrHolder(COMPtrHolder&& aOther)
    : mPtr(Move(aOther.mPtr))
#if defined(MOZ_CONTENT_SANDBOX)
    , mMarshaledStream(Move(aOther.mMarshaledStream))
#endif // defined(MOZ_CONTENT_SANDBOX)
  {
  }

  // COMPtrHolder is eventually added as a member of a struct that is declared
  // in IPDL. The generated C++ code for that IPDL struct includes copy
  // constructors and assignment operators that assume that all members are
  // copyable. I don't think that those copy constructors and operator= are
  // actually used by any generated code, but they are made available. Since no
  // move semantics are available, this terrible hack makes COMPtrHolder build
  // when used as a member of an IPDL struct.
  ThisType& operator=(const ThisType& aOther)
  {
    Set(Move(aOther.mPtr));

#if defined(MOZ_CONTENT_SANDBOX)
    mMarshaledStream = Move(aOther.mMarshaledStream);
#endif // defined(MOZ_CONTENT_SANDBOX)

    return *this;
  }

  ThisType& operator=(ThisType&& aOther)
  {
    Set(Move(aOther.mPtr));

#if defined(MOZ_CONTENT_SANDBOX)
    mMarshaledStream = Move(aOther.mMarshaledStream);
#endif // defined(MOZ_CONTENT_SANDBOX)

    return *this;
  }

  bool operator==(const ThisType& aOther) const
  {
    return mPtr == aOther.mPtr;
  }

  bool IsNull() const
  {
    return !mPtr;
  }

private:
  // This is mutable to facilitate the above operator= hack
  mutable COMPtrType  mPtr;
  ActivationContext   mActCtx;

#if defined(MOZ_CONTENT_SANDBOX)
  // This is mutable so that we may optionally store a reference to a marshaled
  // stream to be cleaned up later via PreserveStream().
  mutable PreservedStreamPtr mMarshaledStream;
#endif // defined(MOZ_CONTENT_SANDBOX)
};

} // namespace mscom
} // namespace mozilla

namespace IPC {

template<typename Interface, const IID& _IID>
struct ParamTraits<mozilla::mscom::COMPtrHolder<Interface, _IID>>
{
  typedef mozilla::mscom::COMPtrHolder<Interface, _IID> paramType;

  static void Write(Message* aMsg, const paramType& aParam)
  {
#if defined(MOZ_CONTENT_SANDBOX)
    static const bool sIsStreamPreservationNeeded =
      XRE_IsParentProcess() && mozilla::GetEffectiveContentSandboxLevel() >= 3;
#else
    const bool sIsStreamPreservationNeeded = false;
#endif // defined(MOZ_CONTENT_SANDBOX)

    paramType::EnvType env;

    mozilla::mscom::ProxyStreamFlags flags = sIsStreamPreservationNeeded ?
         mozilla::mscom::ProxyStreamFlags::ePreservable :
         mozilla::mscom::ProxyStreamFlags::eDefault;

    mozilla::mscom::ProxyStream proxyStream(_IID, aParam.Get(), &env, flags);
    int bufLen;
    const BYTE* buf = proxyStream.GetBuffer(bufLen);
    MOZ_ASSERT(buf || !bufLen);
    aMsg->WriteInt(bufLen);
    if (bufLen) {
      aMsg->WriteBytes(reinterpret_cast<const char*>(buf), bufLen);
    }

#if defined(MOZ_CONTENT_SANDBOX)
    if (sIsStreamPreservationNeeded) {
      /**
       * When we're sending a ProxyStream from parent to content and the
       * content sandboxing level is >= 3, content is unable to communicate
       * its releasing of its reference to the proxied object. We preserve the
       * marshaled proxy data here and later manually release it on content's
       * behalf.
       */
      aParam.PreserveStream(proxyStream.GetPreservedStream());
    }
#endif // defined(MOZ_CONTENT_SANDBOX)
  }

  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
  {
    int length;
    if (!aMsg->ReadLength(aIter, &length)) {
      return false;
    }

    mozilla::UniquePtr<BYTE[]> buf;
    if (length) {
      buf = mozilla::MakeUnique<BYTE[]>(length);
      if (!aMsg->ReadBytesInto(aIter, buf.get(), length)) {
        return false;
      }
    }

    paramType::EnvType env;

    mozilla::mscom::ProxyStream proxyStream(_IID, buf.get(), length, &env);
    if (!proxyStream.IsValid()) {
#if defined(MOZ_CRASHREPORTER)
      CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("ProxyStreamValid"),
                                         NS_LITERAL_CSTRING("false"));
#endif // defined(MOZ_CRASHREPORTER)
      return false;
    }

    typename paramType::COMPtrType ptr;
    if (!proxyStream.GetInterface(mozilla::mscom::getter_AddRefs(ptr))) {
      return false;
    }

    aResult->Set(mozilla::Move(ptr));
    return true;
  }
};

} // namespace IPC

#endif // mozilla_mscom_COMPtrHolder_h

