// Copyright 2016 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/system/platform_handle.h"

#include "base/memory/platform_shared_memory_region.h"
#include "base/numerics/safe_conversions.h"
#include "build/build_config.h"

#if defined(OS_MACOSX) && !defined(OS_IOS)
#include <mach/mach.h>
#include "base/mac/mach_logging.h"
#endif

namespace mojo {

namespace {

uint64_t PlatformHandleValueFromPlatformFile(base::PlatformFile file) {
#if defined(OS_WIN)
  return reinterpret_cast<uint64_t>(file);
#else
  return static_cast<uint64_t>(file);
#endif
}

base::PlatformFile PlatformFileFromPlatformHandleValue(uint64_t value) {
#if defined(OS_WIN)
  return reinterpret_cast<base::PlatformFile>(value);
#else
  return static_cast<base::PlatformFile>(value);
#endif
}

ScopedSharedBufferHandle WrapPlatformSharedMemoryRegion(
    base::subtle::PlatformSharedMemoryRegion region) {
  if (!region.IsValid())
    return ScopedSharedBufferHandle();

  MojoPlatformSharedMemoryRegionAccessMode access_mode;
  switch (region.GetMode()) {
    case base::subtle::PlatformSharedMemoryRegion::Mode::kReadOnly:
      access_mode = MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_READ_ONLY;
      break;
    case base::subtle::PlatformSharedMemoryRegion::Mode::kWritable:
      access_mode = MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_WRITABLE;
      break;
    case base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe:
      access_mode = MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_UNSAFE;
      break;
    default:
      NOTREACHED();
      return ScopedSharedBufferHandle();
  }

  base::subtle::PlatformSharedMemoryRegion::ScopedPlatformHandle handle =
      region.PassPlatformHandle();
  MojoPlatformHandle platform_handles[2];
  uint32_t num_platform_handles = 1;
  platform_handles[0].struct_size = sizeof(platform_handles[0]);
#if defined(OS_WIN)
  platform_handles[0].type = MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE;
  platform_handles[0].value = reinterpret_cast<uint64_t>(handle.Take());
#elif defined(OS_FUCHSIA)
  platform_handles[0].type = MOJO_PLATFORM_HANDLE_TYPE_FUCHSIA_HANDLE;
  platform_handles[0].value = static_cast<uint64_t>(handle.release());
#elif defined(OS_MACOSX) && !defined(OS_IOS)
  platform_handles[0].type = MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT;
  platform_handles[0].value = static_cast<uint64_t>(handle.release());
#elif defined(OS_ANDROID)
  platform_handles[0].type = MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR;
  platform_handles[0].value = static_cast<uint64_t>(handle.release());
#else
  platform_handles[0].type = MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR;
  platform_handles[0].value = static_cast<uint64_t>(handle.fd.release());

  if (region.GetMode() ==
      base::subtle::PlatformSharedMemoryRegion::Mode::kWritable) {
    num_platform_handles = 2;
    platform_handles[1].struct_size = sizeof(platform_handles[1]);
    platform_handles[1].type = MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR;
    platform_handles[1].value =
        static_cast<uint64_t>(handle.readonly_fd.release());
  }
#endif
  const auto& guid = region.GetGUID();
  MojoSharedBufferGuid mojo_guid = {guid.GetHighForSerialization(),
                                    guid.GetLowForSerialization()};
  MojoHandle mojo_handle;
  MojoResult result = MojoWrapPlatformSharedMemoryRegion(
      platform_handles, num_platform_handles, region.GetSize(), &mojo_guid,
      access_mode, nullptr, &mojo_handle);
  if (result != MOJO_RESULT_OK)
    return ScopedSharedBufferHandle();
  return ScopedSharedBufferHandle(SharedBufferHandle(mojo_handle));
}

base::subtle::PlatformSharedMemoryRegion UnwrapPlatformSharedMemoryRegion(
    ScopedSharedBufferHandle mojo_handle) {
  if (!mojo_handle.is_valid())
    return base::subtle::PlatformSharedMemoryRegion();

  MojoPlatformHandle platform_handles[2];
  platform_handles[0].struct_size = sizeof(platform_handles[0]);
  platform_handles[1].struct_size = sizeof(platform_handles[1]);
  uint32_t num_platform_handles = 2;
  uint64_t size;
  MojoSharedBufferGuid mojo_guid;
  MojoPlatformSharedMemoryRegionAccessMode access_mode;
  MojoResult result = MojoUnwrapPlatformSharedMemoryRegion(
      mojo_handle.release().value(), nullptr, platform_handles,
      &num_platform_handles, &size, &mojo_guid, &access_mode);
  if (result != MOJO_RESULT_OK)
    return base::subtle::PlatformSharedMemoryRegion();

  base::subtle::PlatformSharedMemoryRegion::ScopedPlatformHandle region_handle;
#if defined(OS_WIN)
  if (num_platform_handles != 1)
    return base::subtle::PlatformSharedMemoryRegion();
  if (platform_handles[0].type != MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE)
    return base::subtle::PlatformSharedMemoryRegion();
  region_handle.Set(reinterpret_cast<HANDLE>(platform_handles[0].value));
#elif defined(OS_FUCHSIA)
  if (num_platform_handles != 1)
    return base::subtle::PlatformSharedMemoryRegion();
  if (platform_handles[0].type != MOJO_PLATFORM_HANDLE_TYPE_FUCHSIA_HANDLE)
    return base::subtle::PlatformSharedMemoryRegion();
  region_handle.reset(static_cast<zx_handle_t>(platform_handles[0].value));
#elif defined(OS_MACOSX) && !defined(OS_IOS)
  if (num_platform_handles != 1)
    return base::subtle::PlatformSharedMemoryRegion();
  if (platform_handles[0].type != MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT)
    return base::subtle::PlatformSharedMemoryRegion();
  region_handle.reset(static_cast<mach_port_t>(platform_handles[0].value));
#elif defined(OS_ANDROID)
  if (num_platform_handles != 1)
    return base::subtle::PlatformSharedMemoryRegion();
  if (platform_handles[0].type != MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR)
    return base::subtle::PlatformSharedMemoryRegion();
  region_handle.reset(static_cast<int>(platform_handles[0].value));
#else
  if (access_mode == MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_WRITABLE) {
    if (num_platform_handles != 2)
      return base::subtle::PlatformSharedMemoryRegion();
  } else if (num_platform_handles != 1) {
    return base::subtle::PlatformSharedMemoryRegion();
  }
  if (platform_handles[0].type != MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR)
    return base::subtle::PlatformSharedMemoryRegion();
  region_handle.fd.reset(static_cast<int>(platform_handles[0].value));
  if (num_platform_handles == 2) {
    if (platform_handles[1].type != MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR)
      return base::subtle::PlatformSharedMemoryRegion();
    region_handle.readonly_fd.reset(
        static_cast<int>(platform_handles[1].value));
  }
#endif

  base::subtle::PlatformSharedMemoryRegion::Mode mode;
  switch (access_mode) {
    case MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_READ_ONLY:
      mode = base::subtle::PlatformSharedMemoryRegion::Mode::kReadOnly;
      break;
    case MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_WRITABLE:
      mode = base::subtle::PlatformSharedMemoryRegion::Mode::kWritable;
      break;
    case MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_UNSAFE:
      mode = base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe;
      break;
    default:
      return base::subtle::PlatformSharedMemoryRegion();
  }

  return base::subtle::PlatformSharedMemoryRegion::Take(
      std::move(region_handle), mode, size,
      base::UnguessableToken::Deserialize(mojo_guid.high, mojo_guid.low));
}

}  // namespace

ScopedHandle WrapPlatformHandle(PlatformHandle handle) {
  MojoPlatformHandle platform_handle;
  PlatformHandle::ToMojoPlatformHandle(std::move(handle), &platform_handle);

  MojoHandle wrapped_handle;
  MojoResult result =
      MojoWrapPlatformHandle(&platform_handle, nullptr, &wrapped_handle);
  if (result != MOJO_RESULT_OK)
    return ScopedHandle();
  return ScopedHandle(Handle(wrapped_handle));
}

PlatformHandle UnwrapPlatformHandle(ScopedHandle handle) {
  MojoPlatformHandle platform_handle;
  platform_handle.struct_size = sizeof(platform_handle);
  MojoResult result = MojoUnwrapPlatformHandle(handle.release().value(),
                                               nullptr, &platform_handle);
  if (result != MOJO_RESULT_OK)
    return PlatformHandle();
  return PlatformHandle::FromMojoPlatformHandle(&platform_handle);
}

// Wraps a PlatformFile as a Mojo handle. Takes ownership of the file object.
ScopedHandle WrapPlatformFile(base::PlatformFile platform_file) {
  MojoPlatformHandle platform_handle;
  platform_handle.struct_size = sizeof(MojoPlatformHandle);
  platform_handle.type = kPlatformFileHandleType;
  platform_handle.value = PlatformHandleValueFromPlatformFile(platform_file);

  MojoHandle mojo_handle;
  MojoResult result =
      MojoWrapPlatformHandle(&platform_handle, nullptr, &mojo_handle);
  CHECK_EQ(result, MOJO_RESULT_OK);

  return ScopedHandle(Handle(mojo_handle));
}

MojoResult UnwrapPlatformFile(ScopedHandle handle, base::PlatformFile* file) {
  MojoPlatformHandle platform_handle;
  platform_handle.struct_size = sizeof(MojoPlatformHandle);
  MojoResult result = MojoUnwrapPlatformHandle(handle.release().value(),
                                               nullptr, &platform_handle);
  if (result != MOJO_RESULT_OK)
    return result;

  if (platform_handle.type == MOJO_PLATFORM_HANDLE_TYPE_INVALID) {
    *file = base::kInvalidPlatformFile;
  } else {
    CHECK_EQ(platform_handle.type, kPlatformFileHandleType);
    *file = PlatformFileFromPlatformHandleValue(platform_handle.value);
  }

  return MOJO_RESULT_OK;
}

ScopedSharedBufferHandle WrapSharedMemoryHandle(
    const base::SharedMemoryHandle& memory_handle,
    size_t size,
    UnwrappedSharedMemoryHandleProtection protection) {
  if (!memory_handle.IsValid())
    return ScopedSharedBufferHandle();
  MojoPlatformHandle platform_handle;
  platform_handle.struct_size = sizeof(MojoPlatformHandle);
  platform_handle.type = kPlatformSharedBufferHandleType;
#if defined(OS_MACOSX) && !defined(OS_IOS)
  platform_handle.value =
      static_cast<uint64_t>(memory_handle.GetMemoryObject());
#else
  platform_handle.value =
      PlatformHandleValueFromPlatformFile(memory_handle.GetHandle());
#endif

  MojoPlatformSharedMemoryRegionAccessMode access_mode =
      MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_UNSAFE;
  if (protection == UnwrappedSharedMemoryHandleProtection::kReadOnly) {
    access_mode = MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_READ_ONLY;

#if defined(OS_ANDROID)
    // Many callers assume that base::SharedMemory::GetReadOnlyHandle() gives
    // them a handle which is actually read-only. This assumption is invalid on
    // Android. As a precursor to migrating all base::SharedMemory usage --
    // including Mojo internals -- to the new base shared memory API, we ensure
    // that regions are set to read-only if any of their handles are wrapped
    // read-only. This relies on existing usages not attempting to map the
    // region writable any time after this call.
    if (!memory_handle.IsRegionReadOnly())
      memory_handle.SetRegionReadOnly();
#endif
  }

  MojoSharedBufferGuid guid;
  guid.high = memory_handle.GetGUID().GetHighForSerialization();
  guid.low = memory_handle.GetGUID().GetLowForSerialization();
  MojoHandle mojo_handle;
  MojoResult result = MojoWrapPlatformSharedMemoryRegion(
      &platform_handle, 1, size, &guid, access_mode, nullptr, &mojo_handle);
  CHECK_EQ(result, MOJO_RESULT_OK);

  return ScopedSharedBufferHandle(SharedBufferHandle(mojo_handle));
}

MojoResult UnwrapSharedMemoryHandle(
    ScopedSharedBufferHandle handle,
    base::SharedMemoryHandle* memory_handle,
    size_t* size,
    UnwrappedSharedMemoryHandleProtection* protection) {
  if (!handle.is_valid())
    return MOJO_RESULT_INVALID_ARGUMENT;
  MojoPlatformHandle platform_handles[2];
  platform_handles[0].struct_size = sizeof(platform_handles[0]);
  platform_handles[1].struct_size = sizeof(platform_handles[1]);

  uint32_t num_platform_handles = 2;
  uint64_t num_bytes;
  MojoSharedBufferGuid mojo_guid;
  MojoPlatformSharedMemoryRegionAccessMode access_mode;
  MojoResult result = MojoUnwrapPlatformSharedMemoryRegion(
      handle.release().value(), nullptr, platform_handles,
      &num_platform_handles, &num_bytes, &mojo_guid, &access_mode);
  if (result != MOJO_RESULT_OK)
    return result;

  if (size) {
    DCHECK(base::IsValueInRangeForNumericType<size_t>(num_bytes));
    *size = static_cast<size_t>(num_bytes);
  }

  if (protection) {
    *protection =
        access_mode == MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_READ_ONLY
            ? UnwrappedSharedMemoryHandleProtection::kReadOnly
            : UnwrappedSharedMemoryHandleProtection::kReadWrite;
  }

  base::UnguessableToken guid =
      base::UnguessableToken::Deserialize(mojo_guid.high, mojo_guid.low);
#if defined(OS_MACOSX) && !defined(OS_IOS)
  DCHECK_EQ(platform_handles[0].type, MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT);
  DCHECK_EQ(num_platform_handles, 1u);
  *memory_handle = base::SharedMemoryHandle(
      static_cast<mach_port_t>(platform_handles[0].value), num_bytes, guid);
#elif defined(OS_FUCHSIA)
  DCHECK_EQ(platform_handles[0].type, MOJO_PLATFORM_HANDLE_TYPE_FUCHSIA_HANDLE);
  DCHECK_EQ(num_platform_handles, 1u);
  *memory_handle = base::SharedMemoryHandle(
      static_cast<zx_handle_t>(platform_handles[0].value), num_bytes, guid);
#elif defined(OS_POSIX)
  DCHECK_EQ(platform_handles[0].type,
            MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR);
  *memory_handle = base::SharedMemoryHandle(
      base::FileDescriptor(static_cast<int>(platform_handles[0].value), false),
      num_bytes, guid);
#if !defined(OS_ANDROID)
  if (access_mode == MOJO_PLATFORM_SHARED_MEMORY_REGION_ACCESS_MODE_WRITABLE) {
    DCHECK_EQ(num_platform_handles, 2u);
    // When unwrapping as a base::SharedMemoryHandle, make sure to discard the
    // extra file descriptor if the region is writable. base::SharedMemoryHandle
    // effectively only supports read-only and unsafe usage modes when wrapping
    // or unwrapping to and from Mojo handles.
    base::ScopedFD discarded_readonly_fd(
        static_cast<int>(platform_handles[1].value));
  } else {
    DCHECK_EQ(num_platform_handles, 1u);
  }
#else   // !defined(OS_ANDROID)
  DCHECK_EQ(num_platform_handles, 1u);
#endif  // !defined(OS_ANDROID)
#elif defined(OS_WIN)
  DCHECK_EQ(platform_handles[0].type, MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE);
  DCHECK_EQ(num_platform_handles, 1u);
  *memory_handle = base::SharedMemoryHandle(
      reinterpret_cast<HANDLE>(platform_handles[0].value), num_bytes, guid);
#endif

  return MOJO_RESULT_OK;
}

ScopedSharedBufferHandle WrapReadOnlySharedMemoryRegion(
    base::ReadOnlySharedMemoryRegion region) {
  return WrapPlatformSharedMemoryRegion(
      base::ReadOnlySharedMemoryRegion::TakeHandleForSerialization(
          std::move(region)));
}

ScopedSharedBufferHandle WrapUnsafeSharedMemoryRegion(
    base::UnsafeSharedMemoryRegion region) {
  return WrapPlatformSharedMemoryRegion(
      base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
          std::move(region)));
}

ScopedSharedBufferHandle WrapWritableSharedMemoryRegion(
    base::WritableSharedMemoryRegion region) {
  return WrapPlatformSharedMemoryRegion(
      base::WritableSharedMemoryRegion::TakeHandleForSerialization(
          std::move(region)));
}

base::ReadOnlySharedMemoryRegion UnwrapReadOnlySharedMemoryRegion(
    ScopedSharedBufferHandle handle) {
  return base::ReadOnlySharedMemoryRegion::Deserialize(
      UnwrapPlatformSharedMemoryRegion(std::move(handle)));
}

base::UnsafeSharedMemoryRegion UnwrapUnsafeSharedMemoryRegion(
    ScopedSharedBufferHandle handle) {
  return base::UnsafeSharedMemoryRegion::Deserialize(
      UnwrapPlatformSharedMemoryRegion(std::move(handle)));
}

base::WritableSharedMemoryRegion UnwrapWritableSharedMemoryRegion(
    ScopedSharedBufferHandle handle) {
  return base::WritableSharedMemoryRegion::Deserialize(
      UnwrapPlatformSharedMemoryRegion(std::move(handle)));
}

#if defined(OS_MACOSX) && !defined(OS_IOS)
ScopedHandle WrapMachPort(mach_port_t port) {
  kern_return_t kr =
      mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_SEND, 1);
  MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr)
      << "MachPortAttachmentMac mach_port_mod_refs";
  if (kr != KERN_SUCCESS)
    return ScopedHandle();

  MojoPlatformHandle platform_handle;
  platform_handle.struct_size = sizeof(MojoPlatformHandle);
  platform_handle.type = MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT;
  platform_handle.value = static_cast<uint64_t>(port);

  MojoHandle mojo_handle;
  MojoResult result =
      MojoWrapPlatformHandle(&platform_handle, nullptr, &mojo_handle);
  CHECK_EQ(result, MOJO_RESULT_OK);

  return ScopedHandle(Handle(mojo_handle));
}

MojoResult UnwrapMachPort(ScopedHandle handle, mach_port_t* port) {
  MojoPlatformHandle platform_handle;
  platform_handle.struct_size = sizeof(MojoPlatformHandle);
  MojoResult result = MojoUnwrapPlatformHandle(handle.release().value(),
                                               nullptr, &platform_handle);
  if (result != MOJO_RESULT_OK)
    return result;

  CHECK(platform_handle.type == MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT ||
        platform_handle.type == MOJO_PLATFORM_HANDLE_TYPE_INVALID);
  *port = static_cast<mach_port_t>(platform_handle.value);
  return MOJO_RESULT_OK;
}
#endif  // defined(OS_MACOSX) && !defined(OS_IOS)

}  // namespace mojo
