// 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/system/invitation.h"

#include "base/numerics/safe_conversions.h"
#include "build/build_config.h"
#include "mojo/public/c/system/invitation.h"
#include "mojo/public/c/system/platform_handle.h"
#include "mojo/public/cpp/system/platform_handle.h"

namespace mojo {

namespace {

static constexpr base::StringPiece kIsolatedPipeName = {"\0\0\0\0", 4};

void ProcessHandleToMojoProcessHandle(base::ProcessHandle target_process,
                                      MojoPlatformProcessHandle* handle) {
  handle->struct_size = sizeof(*handle);
#if defined(OS_WIN)
  handle->value =
      static_cast<uint64_t>(reinterpret_cast<uintptr_t>(target_process));
#else
  handle->value = static_cast<uint64_t>(target_process);
#endif
}

void PlatformHandleToTransportEndpoint(
    PlatformHandle platform_handle,
    MojoPlatformHandle* endpoint_handle,
    MojoInvitationTransportEndpoint* endpoint) {
  PlatformHandle::ToMojoPlatformHandle(std::move(platform_handle),
                                       endpoint_handle);
  CHECK_NE(endpoint_handle->type, MOJO_PLATFORM_HANDLE_TYPE_INVALID);

  endpoint->struct_size = sizeof(*endpoint);
  endpoint->num_platform_handles = 1;
  endpoint->platform_handles = endpoint_handle;
}

void RunErrorCallback(uintptr_t context,
                      const MojoProcessErrorDetails* details) {
  auto* callback = reinterpret_cast<ProcessErrorCallback*>(context);
  std::string error_message;
  if (details->error_message) {
    error_message =
        std::string(details->error_message, details->error_message_length - 1);
    callback->Run(error_message);
  } else if (details->flags & MOJO_PROCESS_ERROR_FLAG_DISCONNECTED) {
    delete callback;
  }
}

void SendInvitation(ScopedInvitationHandle invitation,
                    base::ProcessHandle target_process,
                    PlatformHandle endpoint_handle,
                    MojoInvitationTransportType transport_type,
                    MojoSendInvitationFlags flags,
                    const ProcessErrorCallback& error_callback,
                    base::StringPiece isolated_connection_name) {
  MojoPlatformProcessHandle process_handle;
  ProcessHandleToMojoProcessHandle(target_process, &process_handle);

  MojoPlatformHandle platform_handle;
  MojoInvitationTransportEndpoint endpoint;
  PlatformHandleToTransportEndpoint(std::move(endpoint_handle),
                                    &platform_handle, &endpoint);
  endpoint.type = transport_type;

  MojoProcessErrorHandler error_handler = nullptr;
  uintptr_t error_handler_context = 0;
  if (error_callback) {
    error_handler = &RunErrorCallback;

    // NOTE: The allocated callback is effectively owned by the error handler,
    // which will delete it on the final invocation for this context (i.e.
    // process disconnection).
    error_handler_context =
        reinterpret_cast<uintptr_t>(new ProcessErrorCallback(error_callback));
  }

  MojoSendInvitationOptions options;
  options.struct_size = sizeof(options);
  options.flags = flags;
  if (flags & MOJO_SEND_INVITATION_FLAG_ISOLATED) {
    options.isolated_connection_name = isolated_connection_name.data();
    options.isolated_connection_name_length =
        static_cast<uint32_t>(isolated_connection_name.size());
  }
  MojoResult result =
      MojoSendInvitation(invitation.get().value(), &process_handle, &endpoint,
                         error_handler, error_handler_context, &options);
  // If successful, the invitation handle is already closed for us.
  if (result == MOJO_RESULT_OK)
    ignore_result(invitation.release());
}

}  // namespace

OutgoingInvitation::OutgoingInvitation() {
  MojoHandle invitation_handle;
  MojoResult result = MojoCreateInvitation(nullptr, &invitation_handle);
  DCHECK_EQ(result, MOJO_RESULT_OK);

  handle_.reset(InvitationHandle(invitation_handle));
}

OutgoingInvitation::OutgoingInvitation(OutgoingInvitation&& other) = default;

OutgoingInvitation::~OutgoingInvitation() = default;

OutgoingInvitation& OutgoingInvitation::operator=(OutgoingInvitation&& other) =
    default;

ScopedMessagePipeHandle OutgoingInvitation::AttachMessagePipe(
    base::StringPiece name) {
  DCHECK(!name.empty());
  DCHECK(base::IsValueInRangeForNumericType<uint32_t>(name.size()));
  MojoHandle message_pipe_handle;
  MojoResult result = MojoAttachMessagePipeToInvitation(
      handle_.get().value(), name.data(), static_cast<uint32_t>(name.size()),
      nullptr, &message_pipe_handle);
  DCHECK_EQ(MOJO_RESULT_OK, result);
  return ScopedMessagePipeHandle(MessagePipeHandle(message_pipe_handle));
}

ScopedMessagePipeHandle OutgoingInvitation::AttachMessagePipe(uint64_t name) {
  return AttachMessagePipe(
      base::StringPiece(reinterpret_cast<const char*>(&name), sizeof(name)));
}

ScopedMessagePipeHandle OutgoingInvitation::ExtractMessagePipe(
    base::StringPiece name) {
  DCHECK(!name.empty());
  DCHECK(base::IsValueInRangeForNumericType<uint32_t>(name.size()));
  MojoHandle message_pipe_handle;
  MojoResult result = MojoExtractMessagePipeFromInvitation(
      handle_.get().value(), name.data(), static_cast<uint32_t>(name.size()),
      nullptr, &message_pipe_handle);
  DCHECK_EQ(MOJO_RESULT_OK, result);
  return ScopedMessagePipeHandle(MessagePipeHandle(message_pipe_handle));
}

ScopedMessagePipeHandle OutgoingInvitation::ExtractMessagePipe(uint64_t name) {
  return ExtractMessagePipe(
      base::StringPiece(reinterpret_cast<const char*>(&name), sizeof(name)));
}

// static
void OutgoingInvitation::Send(OutgoingInvitation invitation,
                              base::ProcessHandle target_process,
                              PlatformChannelEndpoint channel_endpoint,
                              const ProcessErrorCallback& error_callback) {
  SendInvitation(std::move(invitation.handle_), target_process,
                 channel_endpoint.TakePlatformHandle(),
                 MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL,
                 MOJO_SEND_INVITATION_FLAG_NONE, error_callback, "");
}

// static
void OutgoingInvitation::Send(OutgoingInvitation invitation,
                              base::ProcessHandle target_process,
                              PlatformChannelServerEndpoint server_endpoint,
                              const ProcessErrorCallback& error_callback) {
  SendInvitation(std::move(invitation.handle_), target_process,
                 server_endpoint.TakePlatformHandle(),
                 MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL_SERVER,
                 MOJO_SEND_INVITATION_FLAG_NONE, error_callback, "");
}

// static
ScopedMessagePipeHandle OutgoingInvitation::SendIsolated(
    PlatformChannelEndpoint channel_endpoint,
    base::StringPiece connection_name) {
  mojo::OutgoingInvitation invitation;
  ScopedMessagePipeHandle pipe =
      invitation.AttachMessagePipe(kIsolatedPipeName);
  SendInvitation(std::move(invitation.handle_), base::kNullProcessHandle,
                 channel_endpoint.TakePlatformHandle(),
                 MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL,
                 MOJO_SEND_INVITATION_FLAG_ISOLATED, ProcessErrorCallback(),
                 connection_name);
  return pipe;
}

// static
ScopedMessagePipeHandle OutgoingInvitation::SendIsolated(
    PlatformChannelServerEndpoint server_endpoint,
    base::StringPiece connection_name) {
  mojo::OutgoingInvitation invitation;
  ScopedMessagePipeHandle pipe =
      invitation.AttachMessagePipe(kIsolatedPipeName);
  SendInvitation(std::move(invitation.handle_), base::kNullProcessHandle,
                 server_endpoint.TakePlatformHandle(),
                 MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL_SERVER,
                 MOJO_SEND_INVITATION_FLAG_ISOLATED, ProcessErrorCallback(),
                 connection_name);
  return pipe;
}

IncomingInvitation::IncomingInvitation() = default;

IncomingInvitation::IncomingInvitation(IncomingInvitation&& other) = default;

IncomingInvitation::IncomingInvitation(ScopedInvitationHandle handle)
    : handle_(std::move(handle)) {}

IncomingInvitation::~IncomingInvitation() = default;

IncomingInvitation& IncomingInvitation::operator=(IncomingInvitation&& other) =
    default;

// static
IncomingInvitation IncomingInvitation::Accept(
    PlatformChannelEndpoint channel_endpoint) {
  MojoPlatformHandle endpoint_handle;
  PlatformHandle::ToMojoPlatformHandle(channel_endpoint.TakePlatformHandle(),
                                       &endpoint_handle);
  CHECK_NE(endpoint_handle.type, MOJO_PLATFORM_HANDLE_TYPE_INVALID);

  MojoInvitationTransportEndpoint transport_endpoint;
  transport_endpoint.struct_size = sizeof(transport_endpoint);
  transport_endpoint.type = MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL;
  transport_endpoint.num_platform_handles = 1;
  transport_endpoint.platform_handles = &endpoint_handle;

  MojoHandle invitation_handle;
  MojoResult result =
      MojoAcceptInvitation(&transport_endpoint, nullptr, &invitation_handle);
  if (result != MOJO_RESULT_OK)
    return IncomingInvitation();

  return IncomingInvitation(
      ScopedInvitationHandle(InvitationHandle(invitation_handle)));
}

// static
ScopedMessagePipeHandle IncomingInvitation::AcceptIsolated(
    PlatformChannelEndpoint channel_endpoint) {
  MojoPlatformHandle endpoint_handle;
  PlatformHandle::ToMojoPlatformHandle(channel_endpoint.TakePlatformHandle(),
                                       &endpoint_handle);
  CHECK_NE(endpoint_handle.type, MOJO_PLATFORM_HANDLE_TYPE_INVALID);

  MojoInvitationTransportEndpoint transport_endpoint;
  transport_endpoint.struct_size = sizeof(transport_endpoint);
  transport_endpoint.type = MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL;
  transport_endpoint.num_platform_handles = 1;
  transport_endpoint.platform_handles = &endpoint_handle;

  MojoAcceptInvitationOptions options;
  options.struct_size = sizeof(options);
  options.flags = MOJO_ACCEPT_INVITATION_FLAG_ISOLATED;

  MojoHandle invitation_handle;
  MojoResult result =
      MojoAcceptInvitation(&transport_endpoint, &options, &invitation_handle);
  if (result != MOJO_RESULT_OK)
    return ScopedMessagePipeHandle();

  IncomingInvitation invitation{
      ScopedInvitationHandle(InvitationHandle(invitation_handle))};
  return invitation.ExtractMessagePipe(kIsolatedPipeName);
}

ScopedMessagePipeHandle IncomingInvitation::ExtractMessagePipe(
    base::StringPiece name) {
  DCHECK(!name.empty());
  DCHECK(base::IsValueInRangeForNumericType<uint32_t>(name.size()));
  DCHECK(handle_.is_valid());
  MojoHandle message_pipe_handle;
  MojoResult result = MojoExtractMessagePipeFromInvitation(
      handle_.get().value(), name.data(), static_cast<uint32_t>(name.size()),
      nullptr, &message_pipe_handle);
  DCHECK_EQ(MOJO_RESULT_OK, result);
  return ScopedMessagePipeHandle(MessagePipeHandle(message_pipe_handle));
}

ScopedMessagePipeHandle IncomingInvitation::ExtractMessagePipe(uint64_t name) {
  return ExtractMessagePipe(
      base::StringPiece(reinterpret_cast<const char*>(&name), sizeof(name)));
}

}  // namespace mojo
