chromium/fuchsia_web/webengine/browser/message_port.cc

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

#include "fuchsia_web/webengine/browser/message_port.h"

#include <fuchsia/mem/cpp/fidl.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/fit/function.h>
#include <lib/fpromise/result.h>
#include <stdint.h>

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/containers/circular_deque.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/mem_buffer_util.h"
#include "base/functional/bind.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
namespace {

using BlinkMessage = blink::WebMessagePort::Message;

// Converts a fuchsia::web::WebMessage to a BlinkMessage.
// An empty result indicates that conversion was successful.
// Data validation errors are returned as a FrameError.
std::optional<fuchsia::web::FrameError> BlinkMessageFromFidl(
    fuchsia::web::WebMessage fidl_message,
    BlinkMessage* blink_message) {
  if (!fidl_message.has_data()) {
    return fuchsia::web::FrameError::NO_DATA_IN_MESSAGE;
  }

  std::optional<std::u16string> data_utf16 =
      base::ReadUTF8FromVMOAsUTF16(fidl_message.data());
  if (!data_utf16) {
    return fuchsia::web::FrameError::BUFFER_NOT_UTF8;
  }
  blink_message->data = std::move(*data_utf16);

  if (fidl_message.has_outgoing_transfer() &&
      fidl_message.has_incoming_transfer()) {
    DLOG(WARNING) << "WebMessage may only have incoming or outgoing transfer.";
    return fuchsia::web::FrameError::INTERNAL_ERROR;
  }
  if (fidl_message.has_outgoing_transfer()) {
    for (fuchsia::web::OutgoingTransferable& transferrable :
         *fidl_message.mutable_outgoing_transfer()) {
      if (!transferrable.is_message_port())
        return fuchsia::web::FrameError::INTERNAL_ERROR;
      blink_message->ports.push_back(
          BlinkMessagePortFromFidl(std::move(transferrable.message_port())));
    }
  } else if (fidl_message.has_incoming_transfer()) {
    for (fuchsia::web::IncomingTransferable& transferrable :
         *fidl_message.mutable_incoming_transfer()) {
      if (!transferrable.is_message_port())
        return fuchsia::web::FrameError::INTERNAL_ERROR;
      blink_message->ports.push_back(
          BlinkMessagePortFromFidl(std::move(transferrable.message_port())));
    }
  }

  return std::nullopt;
}

// Defines a MessagePortAdapter, which translates and routes messages between a
// FIDL MessagePort and a blink::WebMessagePort. Every MessagePortAdapter has
// exactly one FIDL MessagePort and one blink::WebMessagePort.
//
// MessagePortAdapter instances are self-managed; they destroy themselves when
// the connection is terminated from either the Blink or FIDL side.
class MessagePortAdapter : public blink::WebMessagePort::MessageReceiver {
 public:
  MessagePortAdapter(const MessagePortAdapter&) = delete;
  MessagePortAdapter& operator=(const MessagePortAdapter&) = delete;

 protected:
  explicit MessagePortAdapter(blink::WebMessagePort blink_port)
      : blink_port_(std::move(blink_port)) {
    blink_port_.SetReceiver(this,
                            base::SingleThreadTaskRunner::GetCurrentDefault());
  }

  ~MessagePortAdapter() override = default;

  // Deletes |this|, implicitly disconnecting the FIDL and Blink ports.
  void Destroy() { delete this; }

  // Sends a message to |blink_port_|.
  void SendBlinkMessage(BlinkMessage message) {
    CHECK(blink_port_.PostMessage(std::move(message)));
  }

  // Called when a Blink message was received through |blink_port_|.
  virtual void DeliverMessageToFidl() = 0;

  // Returns the next messagefrom Blink, or an empty value if there
  // are no more messages in the incoming queue.
  std::optional<fuchsia::web::WebMessage> GetNextBlinkMessage() {
    if (message_queue_.empty())
      return std::nullopt;

    return std::move(message_queue_.front());
  }

  void OnDeliverMessageToFidlComplete() {
    DCHECK(!message_queue_.empty());
    message_queue_.pop_front();
  }

 private:
  // blink::WebMessagePort::MessageReceiver implementation:
  bool OnMessage(BlinkMessage message) override {
    std::optional<fuchsia::web::WebMessage> message_converted =
        FidlWebMessageFromBlink(std::move(message),
                                TransferableHostType::kLocal);
    if (!message_converted) {
      DLOG(ERROR) << "Couldn't decode WebMessage from blink::WebMessagePort.";
      Destroy();
      return false;
    }
    message_queue_.emplace_back(std::move(*message_converted));

    // Start draining the queue if it was empty beforehand.
    if (message_queue_.size() == 1u)
      DeliverMessageToFidl();

    return true;
  }

  // blink::WebMessagePort::MessageReceiver implementation:
  void OnPipeError() override { Destroy(); }

  base::circular_deque<fuchsia::web::WebMessage> message_queue_;
  blink::WebMessagePort blink_port_;
};

// Binds a handle to a remote MessagePort to a blink::WebMessagePort.
class FidlMessagePortClientAdapter : public MessagePortAdapter {
 public:
  FidlMessagePortClientAdapter(
      blink::WebMessagePort blink_port,
      fidl::InterfaceHandle<fuchsia::web::MessagePort> fidl_port)
      : MessagePortAdapter(std::move(blink_port)), port_(fidl_port.Bind()) {
    ReadMessageFromFidl();

    port_.set_error_handler([this](zx_status_t status) {
      ZX_LOG_IF(ERROR,
                status != ZX_ERR_PEER_CLOSED && status != ZX_ERR_CANCELED,
                status)
          << " MessagePort disconnected.";
      Destroy();
    });
  }

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

  fidl::InterfaceRequest<fuchsia::web::MessagePort> NewRequest() {
    return port_.NewRequest();
  }

 private:
  ~FidlMessagePortClientAdapter() override = default;

  void ReadMessageFromFidl() {
    port_->ReceiveMessage(fit::bind_member(
        this, &FidlMessagePortClientAdapter::OnMessageReceived));
  }

  void OnMessageReceived(fuchsia::web::WebMessage message) {
    BlinkMessage blink_message;
    std::optional<fuchsia::web::FrameError> result =
        BlinkMessageFromFidl(std::move(message), &blink_message);
    if (result) {
      LOG(WARNING) << "Received bad message, error: "
                   << static_cast<int32_t>(*result);
      Destroy();
      return;
    }

    SendBlinkMessage(std::move(blink_message));

    ReadMessageFromFidl();
  }

  void OnMessagePosted(fuchsia::web::MessagePort_PostMessage_Result result) {
    if (result.is_err()) {
      LOG(ERROR) << "PostMessage failed, reason: "
                 << static_cast<int32_t>(result.err());
      Destroy();
      return;
    }

    DeliverMessageToFidl();
  }

  // MessagePortAdapter implementation.
  void DeliverMessageToFidl() override {
    std::optional<fuchsia::web::WebMessage> message = GetNextBlinkMessage();
    if (!message)
      return;

    port_->PostMessage(
        std::move(*message),
        fit::bind_member(this, &FidlMessagePortClientAdapter::OnMessagePosted));

    OnDeliverMessageToFidlComplete();
  }

  fuchsia::web::MessagePortPtr port_;
};

// Binds a MessagePort FIDL service from a blink::WebMessagePort.
class FidlMessagePortServerAdapter : public fuchsia::web::MessagePort,
                                     public MessagePortAdapter {
 public:
  explicit FidlMessagePortServerAdapter(blink::WebMessagePort blink_port)
      : MessagePortAdapter(std::move(blink_port)), binding_(this) {
    binding_.set_error_handler([this](zx_status_t status) {
      ZX_LOG_IF(ERROR,
                status != ZX_ERR_PEER_CLOSED && status != ZX_ERR_CANCELED,
                status)
          << " MessagePort disconnected.";
      Destroy();
    });
  }

  FidlMessagePortServerAdapter(
      blink::WebMessagePort blink_port,
      fidl::InterfaceRequest<fuchsia::web::MessagePort> request)
      : FidlMessagePortServerAdapter(std::move(blink_port)) {
    binding_.Bind(std::move(request));
  }

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

  fidl::InterfaceHandle<fuchsia::web::MessagePort> NewBinding() {
    return binding_.NewBinding();
  }

 private:
  ~FidlMessagePortServerAdapter() override = default;

  // MessagePortAdapter implementation.
  void DeliverMessageToFidl() override {
    // Do nothing if the client hasn't requested a read, or if there's nothing
    // to read.
    if (!pending_receive_message_callback_)
      return;

    std::optional<fuchsia::web::WebMessage> message = GetNextBlinkMessage();
    if (!message)
      return;

    pending_receive_message_callback_(std::move(*message));
    pending_receive_message_callback_ = {};
    OnDeliverMessageToFidlComplete();
  }

  // fuchsia::web::MessagePort implementation.
  void PostMessage(fuchsia::web::WebMessage message,
                   PostMessageCallback callback) override {
    BlinkMessage blink_message;
    std::optional<fuchsia::web::FrameError> status =
        BlinkMessageFromFidl(std::move(message), &blink_message);

    if (status) {
      LOG(ERROR) << "Error when reading message from FIDL: "
                 << static_cast<int32_t>(*status);
      Destroy();
      return;
    }

    SendBlinkMessage(std::move(blink_message));
    callback(fpromise::ok());
  }

  void ReceiveMessage(ReceiveMessageCallback callback) override {
    if (pending_receive_message_callback_) {
      LOG(WARNING)
          << "ReceiveMessage called multiple times without acknowledgement.";
      Destroy();
      return;
    }
    pending_receive_message_callback_ = std::move(callback);
    DeliverMessageToFidl();
  }

  PostMessageCallback post_message_ack_;
  ReceiveMessageCallback pending_receive_message_callback_;
  fidl::Binding<fuchsia::web::MessagePort> binding_;
};

fidl::InterfaceRequest<fuchsia::web::MessagePort>
RemoteFidlMessagePortFromBlink(blink::WebMessagePort blink_port) {
  fidl::InterfaceHandle<fuchsia::web::MessagePort> fidl_handle;
  auto request = fidl_handle.NewRequest();
  new FidlMessagePortClientAdapter(std::move(blink_port),
                                   std::move(fidl_handle));
  return request;
}

}  // namespace

// Methods for constructing MessagePortAdapters for various port types and
// origins. The adapters manage their own lifetimes and will self-delete when
// either endpoint of their channels are disconnected.

blink::WebMessagePort BlinkMessagePortFromFidl(
    fidl::InterfaceRequest<fuchsia::web::MessagePort> fidl_port) {
  auto port_pair = blink::WebMessagePort::CreatePair();
  new FidlMessagePortServerAdapter(std::move(port_pair.first),
                                   std::move(fidl_port));
  return std::move(port_pair.second);
}

blink::WebMessagePort BlinkMessagePortFromFidl(
    fidl::InterfaceHandle<fuchsia::web::MessagePort> fidl_port) {
  auto port_pair = blink::WebMessagePort::CreatePair();
  new FidlMessagePortClientAdapter(std::move(port_pair.first),
                                   std::move(fidl_port));
  return std::move(port_pair.second);
}

fidl::InterfaceHandle<fuchsia::web::MessagePort> FidlMessagePortFromBlink(
    blink::WebMessagePort blink_port) {
  auto* adapter = new FidlMessagePortServerAdapter(std::move(blink_port));
  return adapter->NewBinding();
}

std::optional<fuchsia::web::WebMessage> FidlWebMessageFromBlink(
    BlinkMessage blink_message,
    TransferableHostType port_type) {
  fuchsia::web::WebMessage fidl_message;

  if (!blink_message.ports.empty()) {
    switch (port_type) {
      case TransferableHostType::kLocal:
        for (blink::WebMessagePort& port : blink_message.ports) {
          fuchsia::web::IncomingTransferable incoming;
          incoming.set_message_port(FidlMessagePortFromBlink(std::move(port)));
          fidl_message.mutable_incoming_transfer()->push_back(
              std::move(incoming));
        }
        break;
      case TransferableHostType::kRemote:
        for (blink::WebMessagePort& port : blink_message.ports) {
          fuchsia::web::OutgoingTransferable outgoing;
          outgoing.set_message_port(
              RemoteFidlMessagePortFromBlink(std::move(port)));
          fidl_message.mutable_outgoing_transfer()->push_back(
              std::move(outgoing));
        }
        break;
    }
    blink_message.ports.clear();
  }

  std::u16string data_utf16 = std::move(blink_message.data);
  std::string data_utf8;
  if (!base::UTF16ToUTF8(data_utf16.data(), data_utf16.size(), &data_utf8))
    return std::nullopt;

  base::STLClearObject(&data_utf16);

  constexpr char kBufferVmoName[] = "cr-web-message-from-blink";
  fuchsia::mem::Buffer data_buffer =
      base::MemBufferFromString(data_utf8, kBufferVmoName);
  if (!data_buffer.vmo)
    return std::nullopt;

  fidl_message.set_data(std::move(data_buffer));
  return fidl_message;
}