// 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.

#ifndef MOJO_PUBLIC_CPP_PLATFORM_PLATFORM_HANDLE_H_
#define MOJO_PUBLIC_CPP_PLATFORM_PLATFORM_HANDLE_H_

#include "base/check_op.h"
#include "base/component_export.h"
#include "base/files/platform_file.h"
#include "base/macros.h"
#include "build/build_config.h"
#include "mojo/public/c/system/platform_handle.h"

#if defined(OS_WIN)
#include "base/win/scoped_handle.h"
#elif defined(OS_FUCHSIA)
#include <lib/zx/handle.h>
#elif defined(OS_MAC)
#include "base/mac/scoped_mach_port.h"
#endif

#if defined(OS_POSIX) || defined(OS_FUCHSIA)
#include "base/files/scoped_file.h"
#endif

namespace mojo {

// A PlatformHandle is a generic wrapper around a platform-specific system
// handle type, e.g. a POSIX file descriptor, Windows HANDLE, or macOS Mach
// port. This can wrap any of various such types depending on the host platform
// for which it's compiled.
//
// This is useful primarily for two reasons:
//
// - Interacting with the Mojo invitation API, which use OS primitives to
//   bootstrap Mojo IPC connections.
// - Interacting with Mojo platform handle wrapping and unwrapping API, which
//   allows handles to OS primitives to be transmitted over Mojo IPC with a
//   stable wire representation via Mojo handles.
//
// NOTE: This assumes ownership if the handle it represents.
class COMPONENT_EXPORT(MOJO_CPP_PLATFORM) PlatformHandle {
 public:
  enum class Type {
    kNone,
#if defined(OS_WIN) || defined(OS_FUCHSIA)
    kHandle,
#elif defined(OS_MAC)
    kMachSend,
    kMachReceive,
#endif
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
    kFd,
#endif
  };

  PlatformHandle();
  PlatformHandle(PlatformHandle&& other);

#if defined(OS_WIN)
  explicit PlatformHandle(base::win::ScopedHandle handle);
#elif defined(OS_FUCHSIA)
  explicit PlatformHandle(zx::handle handle);
#elif defined(OS_MAC)
  explicit PlatformHandle(base::mac::ScopedMachSendRight mach_port);
  explicit PlatformHandle(base::mac::ScopedMachReceiveRight mach_port);
#endif

#if defined(OS_POSIX) || defined(OS_FUCHSIA)
  explicit PlatformHandle(base::ScopedFD fd);
#endif

  PlatformHandle(const PlatformHandle&) = delete;
  PlatformHandle& operator=(const PlatformHandle&) = delete;

  ~PlatformHandle();

  PlatformHandle& operator=(PlatformHandle&& other);

  // Takes ownership of |handle|'s underlying platform handle and fills in
  // |mojo_handle| with a representation of it. The caller assumes ownership of
  // the platform handle.
  static void ToMojoPlatformHandle(PlatformHandle handle,
                                   MojoPlatformHandle* mojo_handle);

  // Closes the underlying platform handle.
  // Assumes ownership of the platform handle described by |handle|, and returns
  // it as a new PlatformHandle.
  static PlatformHandle FromMojoPlatformHandle(
      const MojoPlatformHandle* handle);

  Type type() const { return type_; }

  void reset();

  // Relinquishes ownership of the underlying handle, regardless of type, and
  // discards its value. To release and obtain the underlying handle value, use
  // one of the specific |Release*()| methods below.
  void release();

  // Duplicates the underlying platform handle, returning a new PlatformHandle
  // which owns it.
  PlatformHandle Clone() const;

#if defined(OS_WIN)
  bool is_valid() const { return is_valid_handle(); }
  bool is_valid_handle() const { return handle_.IsValid(); }
  bool is_handle() const { return type_ == Type::kHandle; }
  const base::win::ScopedHandle& GetHandle() const { return handle_; }
  base::win::ScopedHandle TakeHandle() {
    DCHECK_EQ(type_, Type::kHandle);
    type_ = Type::kNone;
    return std::move(handle_);
  }
  HANDLE ReleaseHandle() WARN_UNUSED_RESULT {
    DCHECK_EQ(type_, Type::kHandle);
    type_ = Type::kNone;
    return handle_.Take();
  }
#elif defined(OS_FUCHSIA)
  bool is_valid() const { return is_valid_fd() || is_valid_handle(); }
  bool is_valid_handle() const { return handle_.is_valid(); }
  bool is_handle() const { return type_ == Type::kHandle; }
  const zx::handle& GetHandle() const { return handle_; }
  zx::handle TakeHandle() {
    if (type_ == Type::kHandle)
      type_ = Type::kNone;
    return std::move(handle_);
  }
  zx_handle_t ReleaseHandle() WARN_UNUSED_RESULT {
    if (type_ == Type::kHandle)
      type_ = Type::kNone;
    return handle_.release();
  }
#elif defined(OS_MAC)
  bool is_valid() const { return is_valid_fd() || is_valid_mach_port(); }
  bool is_valid_mach_port() const {
    return is_valid_mach_send() || is_valid_mach_receive();
  }

  bool is_valid_mach_send() const { return mach_send_.is_valid(); }
  bool is_mach_send() const { return type_ == Type::kMachSend; }
  const base::mac::ScopedMachSendRight& GetMachSendRight() const {
    return mach_send_;
  }
  base::mac::ScopedMachSendRight TakeMachSendRight() {
    if (type_ == Type::kMachSend)
      type_ = Type::kNone;
    return std::move(mach_send_);
  }
  mach_port_t ReleaseMachSendRight() WARN_UNUSED_RESULT {
    return TakeMachSendRight().release();
  }

  bool is_valid_mach_receive() const { return mach_receive_.is_valid(); }
  bool is_mach_receive() const { return type_ == Type::kMachReceive; }
  const base::mac::ScopedMachReceiveRight& GetMachReceiveRight() const {
    return mach_receive_;
  }
  base::mac::ScopedMachReceiveRight TakeMachReceiveRight() {
    if (type_ == Type::kMachReceive)
      type_ = Type::kNone;
    return std::move(mach_receive_);
  }
  mach_port_t ReleaseMachReceiveRight() WARN_UNUSED_RESULT {
    return TakeMachReceiveRight().release();
  }
#elif defined(OS_POSIX)
  bool is_valid() const { return is_valid_fd(); }
#else
#error "Unsupported platform."
#endif

#if defined(OS_POSIX) || defined(OS_FUCHSIA)
  bool is_valid_fd() const { return fd_.is_valid(); }
  bool is_fd() const { return type_ == Type::kFd; }
  const base::ScopedFD& GetFD() const { return fd_; }
  base::ScopedFD TakeFD() {
    if (type_ == Type::kFd)
      type_ = Type::kNone;
    return std::move(fd_);
  }
  int ReleaseFD() WARN_UNUSED_RESULT {
    if (type_ == Type::kFd)
      type_ = Type::kNone;
    return fd_.release();
  }
#endif

  bool is_valid_platform_file() const {
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
    return is_valid_fd();
#elif defined(OS_WIN)
    return is_valid_handle();
#else
#error "Unsupported platform"
#endif
  }
  base::ScopedPlatformFile TakePlatformFile() {
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
    return TakeFD();
#elif defined(OS_WIN)
    return TakeHandle();
#else
#error "Unsupported platform"
#endif
  }
  base::PlatformFile ReleasePlatformFile() WARN_UNUSED_RESULT {
#if defined(OS_POSIX) || defined(OS_FUCHSIA)
    return ReleaseFD();
#elif defined(OS_WIN)
    return ReleaseHandle();
#else
#error "Unsupported platform"
#endif
  }

 private:
  Type type_ = Type::kNone;

#if defined(OS_WIN)
  base::win::ScopedHandle handle_;
#elif defined(OS_FUCHSIA)
  zx::handle handle_;
#elif defined(OS_MAC)
  base::mac::ScopedMachSendRight mach_send_;
  base::mac::ScopedMachReceiveRight mach_receive_;
#endif

#if defined(OS_POSIX) || defined(OS_FUCHSIA)
  base::ScopedFD fd_;
#endif
};

}  // namespace mojo

#endif  // MOJO_PUBLIC_CPP_PLATFORM_PLATFORM_HANDLE_H_
