// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "mojo/public/cpp/platform/platform_handle.h"

#include "base/logging.h"
#include "build/build_config.h"

#if defined(OS_WIN)
#include <windows.h>

#include "base/win/scoped_handle.h"
#elif defined(OS_FUCHSIA)
#include <lib/fdio/limits.h>
#include <unistd.h>
#include <zircon/status.h>

#include "base/fuchsia/fuchsia_logging.h"
#elif defined(OS_MACOSX) && !defined(OS_IOS)
#include <mach/mach_vm.h>

#include "base/mac/mach_logging.h"
#include "base/mac/scoped_mach_port.h"
#endif

#if defined(OS_POSIX)
#include <unistd.h>

#include "base/files/scoped_file.h"
#endif

namespace mojo {

namespace {

#if defined(OS_WIN)
base::win::ScopedHandle CloneHandle(const base::win::ScopedHandle& handle) {
  DCHECK(handle.IsValid());

  HANDLE dupe;
  BOOL result = ::DuplicateHandle(::GetCurrentProcess(), handle.Get(),
                                  ::GetCurrentProcess(), &dupe, 0, FALSE,
                                  DUPLICATE_SAME_ACCESS);
  if (!result)
    return base::win::ScopedHandle();
  DCHECK_NE(dupe, INVALID_HANDLE_VALUE);
  return base::win::ScopedHandle(dupe);
}
#elif defined(OS_FUCHSIA)
zx::handle CloneHandle(const zx::handle& handle) {
  DCHECK(handle.is_valid());

  zx::handle dupe;
  zx_status_t result = handle.duplicate(ZX_RIGHT_SAME_RIGHTS, &dupe);
  if (result != ZX_OK)
    ZX_DLOG(ERROR, result) << "zx_duplicate_handle";
  return std::move(dupe);
}
#elif defined(OS_MACOSX) && !defined(OS_IOS)
base::mac::ScopedMachSendRight CloneMachPort(
    const base::mac::ScopedMachSendRight& mach_port) {
  DCHECK(mach_port.is_valid());

  kern_return_t kr = mach_port_mod_refs(mach_task_self(), mach_port.get(),
                                        MACH_PORT_RIGHT_SEND, 1);
  if (kr != KERN_SUCCESS) {
    MACH_DLOG(ERROR, kr) << "mach_port_mod_refs";
    return base::mac::ScopedMachSendRight();
  }
  return base::mac::ScopedMachSendRight(mach_port.get());
}
#endif

#if defined(OS_POSIX) || defined(OS_FUCHSIA)
base::ScopedFD CloneFD(const base::ScopedFD& fd) {
  DCHECK(fd.is_valid());
  return base::ScopedFD(dup(fd.get()));
}
#endif

}  // namespace

PlatformHandle::PlatformHandle() = default;

PlatformHandle::PlatformHandle(PlatformHandle&& other) {
  *this = std::move(other);
}

#if defined(OS_WIN)
PlatformHandle::PlatformHandle(base::win::ScopedHandle handle)
    : type_(Type::kHandle), handle_(std::move(handle)) {}
#elif defined(OS_FUCHSIA)
PlatformHandle::PlatformHandle(zx::handle handle)
    : type_(Type::kHandle), handle_(std::move(handle)) {}
#elif defined(OS_MACOSX) && !defined(OS_IOS)
PlatformHandle::PlatformHandle(base::mac::ScopedMachSendRight mach_port)
    : type_(Type::kMachPort), mach_port_(std::move(mach_port)) {}
#endif

#if defined(OS_POSIX) || defined(OS_FUCHSIA)
PlatformHandle::PlatformHandle(base::ScopedFD fd)
    : type_(Type::kFd), fd_(std::move(fd)) {
#if defined(OS_FUCHSIA)
  DCHECK_LT(fd_.get(), FDIO_MAX_FD);
#endif
}
#endif

PlatformHandle::~PlatformHandle() = default;

PlatformHandle& PlatformHandle::operator=(PlatformHandle&& other) {
  type_ = other.type_;
  other.type_ = Type::kNone;

#if defined(OS_WIN)
  handle_ = std::move(other.handle_);
#elif defined(OS_FUCHSIA)
  handle_ = std::move(other.handle_);
#elif defined(OS_MACOSX) && !defined(OS_IOS)
  mach_port_ = std::move(other.mach_port_);
#endif

#if defined(OS_POSIX) || defined(OS_FUCHSIA)
  fd_ = std::move(other.fd_);
#endif

  return *this;
}

// static
void PlatformHandle::ToMojoPlatformHandle(PlatformHandle handle,
                                          MojoPlatformHandle* out_handle) {
  DCHECK(out_handle);
  out_handle->struct_size = sizeof(MojoPlatformHandle);
  if (handle.type_ == Type::kNone) {
    out_handle->type = MOJO_PLATFORM_HANDLE_TYPE_INVALID;
    out_handle->value = 0;
    return;
  }

  do {
#if defined(OS_WIN)
    out_handle->type = MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE;
    out_handle->value =
        static_cast<uint64_t>(HandleToLong(handle.TakeHandle().Take()));
    break;
#elif defined(OS_FUCHSIA)
    if (handle.is_handle()) {
      out_handle->type = MOJO_PLATFORM_HANDLE_TYPE_FUCHSIA_HANDLE;
      out_handle->value = handle.TakeHandle().release();
      break;
    }
#elif defined(OS_MACOSX) && !defined(OS_IOS)
    if (handle.is_mach_port()) {
      out_handle->type = MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT;
      out_handle->value =
          static_cast<uint64_t>(handle.TakeMachPort().release());
      break;
    }
#endif

#if defined(OS_POSIX) || defined(OS_FUCHSIA)
    DCHECK(handle.is_fd());
    out_handle->type = MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR;
    out_handle->value = static_cast<uint64_t>(handle.TakeFD().release());
#endif
  } while (false);

  // One of the above cases must take ownership of |handle|.
  DCHECK(!handle.is_valid());
}

// static
PlatformHandle PlatformHandle::FromMojoPlatformHandle(
    const MojoPlatformHandle* handle) {
  if (handle->struct_size < sizeof(*handle) ||
      handle->type == MOJO_PLATFORM_HANDLE_TYPE_INVALID) {
    return PlatformHandle();
  }

#if defined(OS_WIN)
  if (handle->type != MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE)
    return PlatformHandle();
  return PlatformHandle(
      base::win::ScopedHandle(LongToHandle(static_cast<long>(handle->value))));
#elif defined(OS_FUCHSIA)
  if (handle->type == MOJO_PLATFORM_HANDLE_TYPE_FUCHSIA_HANDLE)
    return PlatformHandle(zx::handle(handle->value));
#elif defined(OS_MACOSX) && !defined(OS_IOS)
  if (handle->type == MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT) {
    return PlatformHandle(base::mac::ScopedMachSendRight(
        static_cast<mach_port_t>(handle->value)));
  }
#endif

#if defined(OS_POSIX) || defined(OS_FUCHSIA)
  if (handle->type != MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR)
    return PlatformHandle();
  return PlatformHandle(base::ScopedFD(static_cast<int>(handle->value)));
#endif
}

void PlatformHandle::reset() {
  type_ = Type::kNone;

#if defined(OS_WIN)
  handle_.Close();
#elif defined(OS_FUCHSIA)
  handle_.reset();
#elif defined(OS_MACOSX) && !defined(OS_IOS)
  mach_port_.reset();
#endif

#if defined(OS_POSIX) || defined(OS_FUCHSIA)
  fd_.reset();
#endif
}

void PlatformHandle::release() {
  type_ = Type::kNone;

#if defined(OS_WIN)
  ignore_result(handle_.Take());
#elif defined(OS_FUCHSIA)
  ignore_result(handle_.release());
#elif defined(OS_MACOSX) && !defined(OS_IOS)
  ignore_result(mach_port_.release());
#endif

#if defined(OS_POSIX) || defined(OS_FUCHSIA)
  ignore_result(fd_.release());
#endif
}

PlatformHandle PlatformHandle::Clone() const {
#if defined(OS_WIN)
  return PlatformHandle(CloneHandle(handle_));
#elif defined(OS_FUCHSIA)
  if (is_valid_handle())
    return PlatformHandle(CloneHandle(handle_));
  return PlatformHandle(CloneFD(fd_));
#elif defined(OS_MACOSX) && !defined(OS_IOS)
  if (is_valid_mach_port())
    return PlatformHandle(CloneMachPort(mach_port_));
  return PlatformHandle(CloneFD(fd_));
#elif defined(OS_POSIX)
  return PlatformHandle(CloneFD(fd_));
#endif
}

}  // namespace mojo
