// Copyright 2013 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 "content/browser/renderer_host/pepper/pepper_tcp_server_socket_message_filter.h"

#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/logging.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/pepper/browser_ppapi_host_impl.h"
#include "content/browser/renderer_host/pepper/content_browser_pepper_host_factory.h"
#include "content/browser/renderer_host/pepper/pepper_socket_utils.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/socket_permission_request.h"
#include "net/base/ip_address.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_errors.h"
#include "net/log/net_log_source.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/c/private/ppb_net_address_private.h"
#include "ppapi/host/dispatch_host_message.h"
#include "ppapi/host/error_conversion.h"
#include "ppapi/host/ppapi_host.h"
#include "ppapi/host/resource_host.h"
#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/shared_impl/api_id.h"
#include "ppapi/shared_impl/ppb_tcp_socket_shared.h"
#include "ppapi/shared_impl/private/net_address_private_impl.h"

#if defined(OS_CHROMEOS)
#include "chromeos/network/firewall_hole.h"
#endif  // defined(OS_CHROMEOS)

using ppapi::NetAddressPrivateImpl;
using ppapi::host::NetErrorToPepperError;

namespace {

size_t g_num_instances = 0;

}  // namespace

namespace content {

PepperTCPServerSocketMessageFilter::PepperTCPServerSocketMessageFilter(
    ContentBrowserPepperHostFactory* factory,
    BrowserPpapiHostImpl* host,
    PP_Instance instance,
    bool private_api)
    : ppapi_host_(host->GetPpapiHost()),
      factory_(factory),
      instance_(instance),
      state_(STATE_BEFORE_LISTENING),
      external_plugin_(host->external_plugin()),
      private_api_(private_api),
      render_process_id_(0),
      render_frame_id_(0) {
  ++g_num_instances;
  DCHECK(factory_);
  DCHECK(ppapi_host_);
  if (!host->GetRenderFrameIDsForInstance(
          instance, &render_process_id_, &render_frame_id_)) {
    NOTREACHED();
  }
}

PepperTCPServerSocketMessageFilter::~PepperTCPServerSocketMessageFilter() {
  --g_num_instances;
}

// static
size_t PepperTCPServerSocketMessageFilter::GetNumInstances() {
  return g_num_instances;
}

scoped_refptr<base::TaskRunner>
PepperTCPServerSocketMessageFilter::OverrideTaskRunnerForMessage(
    const IPC::Message& message) {
  switch (message.type()) {
    case PpapiHostMsg_TCPServerSocket_Listen::ID:
      return BrowserThread::GetTaskRunnerForThread(BrowserThread::UI);
    case PpapiHostMsg_TCPServerSocket_Accept::ID:
    case PpapiHostMsg_TCPServerSocket_StopListening::ID:
      return BrowserThread::GetTaskRunnerForThread(BrowserThread::IO);
  }
  return NULL;
}

int32_t PepperTCPServerSocketMessageFilter::OnResourceMessageReceived(
    const IPC::Message& msg,
    ppapi::host::HostMessageContext* context) {
  PPAPI_BEGIN_MESSAGE_MAP(PepperTCPServerSocketMessageFilter, msg)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_TCPServerSocket_Listen,
                                      OnMsgListen)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_TCPServerSocket_Accept,
                                        OnMsgAccept)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(
        PpapiHostMsg_TCPServerSocket_StopListening, OnMsgStopListening)
  PPAPI_END_MESSAGE_MAP()
  return PP_ERROR_FAILED;
}

int32_t PepperTCPServerSocketMessageFilter::OnMsgListen(
    const ppapi::host::HostMessageContext* context,
    const PP_NetAddress_Private& addr,
    int32_t backlog) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(context);

  SocketPermissionRequest request =
      pepper_socket_utils::CreateSocketPermissionRequest(
          content::SocketPermissionRequest::TCP_LISTEN, addr);
  if (!pepper_socket_utils::CanUseSocketAPIs(external_plugin_,
                                             private_api_,
                                             &request,
                                             render_process_id_,
                                             render_frame_id_)) {
    return PP_ERROR_NOACCESS;
  }

  BrowserThread::PostTask(
      BrowserThread::IO,
      FROM_HERE,
      base::Bind(&PepperTCPServerSocketMessageFilter::DoListen,
                 this,
                 context->MakeReplyMessageContext(),
                 addr,
                 backlog));
  return PP_OK_COMPLETIONPENDING;
}

int32_t PepperTCPServerSocketMessageFilter::OnMsgAccept(
    const ppapi::host::HostMessageContext* context) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  DCHECK(context);

  if (state_ != STATE_LISTENING)
    return PP_ERROR_FAILED;

  state_ = STATE_ACCEPT_IN_PROGRESS;
  ppapi::host::ReplyMessageContext reply_context(
      context->MakeReplyMessageContext());
  int net_result = socket_->Accept(
      &accepted_socket_,
      &accepted_address_,
      base::Bind(&PepperTCPServerSocketMessageFilter::OnAcceptCompleted,
                 base::Unretained(this),
                 reply_context));
  if (net_result != net::ERR_IO_PENDING)
    OnAcceptCompleted(reply_context, net_result);
  return PP_OK_COMPLETIONPENDING;
}

int32_t PepperTCPServerSocketMessageFilter::OnMsgStopListening(
    const ppapi::host::HostMessageContext* context) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  DCHECK(context);

  state_ = STATE_CLOSED;
  socket_.reset();
#if defined(OS_CHROMEOS)
  firewall_hole_.reset();
#endif  // defined(OS_CHROMEOS)
  return PP_OK;
}

void PepperTCPServerSocketMessageFilter::DoListen(
    const ppapi::host::ReplyMessageContext& context,
    const PP_NetAddress_Private& addr,
    int32_t backlog) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  std::vector<uint8_t> address;
  uint16_t port;
  if (state_ != STATE_BEFORE_LISTENING ||
      !NetAddressPrivateImpl::NetAddressToIPEndPoint(addr, &address, &port)) {
    SendListenError(context, PP_ERROR_FAILED);
    state_ = STATE_CLOSED;
    return;
  }

  state_ = STATE_LISTEN_IN_PROGRESS;

  socket_.reset(new net::TCPSocket(NULL, NULL, net::NetLogSource()));
  int net_result = net::OK;
  do {
    net::IPEndPoint ip_end_point(net::IPAddress(address), port);
    net_result = socket_->Open(ip_end_point.GetFamily());
    if (net_result != net::OK)
      break;
    net_result = socket_->SetDefaultOptionsForServer();
    if (net_result != net::OK)
      break;
    net_result = socket_->Bind(ip_end_point);
    if (net_result != net::OK)
      break;
    net_result = socket_->Listen(backlog);
  } while (false);

  if (net_result != net::ERR_IO_PENDING) {
#if defined(OS_CHROMEOS)
    OpenFirewallHole(context, net_result);
#else
    OnListenCompleted(context, net_result);
#endif
  }
}

void PepperTCPServerSocketMessageFilter::OnListenCompleted(
    const ppapi::host::ReplyMessageContext& context,
    int net_result) {
  if (state_ != STATE_LISTEN_IN_PROGRESS) {
    SendListenError(context, PP_ERROR_FAILED);
    state_ = STATE_CLOSED;
    return;
  }
  if (net_result != net::OK) {
    SendListenError(context, NetErrorToPepperError(net_result));
    state_ = STATE_BEFORE_LISTENING;
    return;
  }

  DCHECK(socket_.get());

  net::IPEndPoint end_point;
  PP_NetAddress_Private addr;

  int32_t pp_result =
      NetErrorToPepperError(socket_->GetLocalAddress(&end_point));
  if (pp_result != PP_OK) {
    SendListenError(context, pp_result);
    state_ = STATE_BEFORE_LISTENING;
    return;
  }
  if (!NetAddressPrivateImpl::IPEndPointToNetAddress(
          end_point.address().bytes(), end_point.port(), &addr)) {
    SendListenError(context, PP_ERROR_FAILED);
    state_ = STATE_BEFORE_LISTENING;
    return;
  }

  SendListenReply(context, PP_OK, addr);
  state_ = STATE_LISTENING;
}

#if defined(OS_CHROMEOS)
void PepperTCPServerSocketMessageFilter::OpenFirewallHole(
    const ppapi::host::ReplyMessageContext& context,
    int net_result) {
  if (net_result != net::OK) {
    return;
  }
  net::IPEndPoint local_addr;
  socket_->GetLocalAddress(&local_addr);
  pepper_socket_utils::FirewallHoleOpenCallback callback =
      base::Bind(&PepperTCPServerSocketMessageFilter::OnFirewallHoleOpened,
                 this, context, net_result);
  pepper_socket_utils::OpenTCPFirewallHole(local_addr, callback);
}

void PepperTCPServerSocketMessageFilter::OnFirewallHoleOpened(
    const ppapi::host::ReplyMessageContext& context,
    int32_t net_result,
    std::unique_ptr<chromeos::FirewallHole> hole) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  LOG_IF(WARNING, !hole.get()) << "Firewall hole could not be opened.";
  firewall_hole_.reset(hole.release());
  OnListenCompleted(context, net_result);
}
#endif  // defined(OS_CHROMEOS)

void PepperTCPServerSocketMessageFilter::OnAcceptCompleted(
    const ppapi::host::ReplyMessageContext& context,
    int net_result) {
  if (state_ != STATE_ACCEPT_IN_PROGRESS) {
    SendAcceptError(context, PP_ERROR_FAILED);
    state_ = STATE_CLOSED;
    return;
  }

  state_ = STATE_LISTENING;

  if (net_result != net::OK) {
    SendAcceptError(context, NetErrorToPepperError(net_result));
    return;
  }

  DCHECK(accepted_socket_.get());

  net::IPEndPoint ip_end_point_local;
  PP_NetAddress_Private local_addr = NetAddressPrivateImpl::kInvalidNetAddress;
  PP_NetAddress_Private remote_addr = NetAddressPrivateImpl::kInvalidNetAddress;

  int32_t pp_result = NetErrorToPepperError(
      accepted_socket_->GetLocalAddress(&ip_end_point_local));
  if (pp_result != PP_OK) {
    SendAcceptError(context, pp_result);
    return;
  }
  if (!NetAddressPrivateImpl::IPEndPointToNetAddress(
          ip_end_point_local.address().bytes(), ip_end_point_local.port(),
          &local_addr) ||
      !NetAddressPrivateImpl::IPEndPointToNetAddress(
          accepted_address_.address().bytes(), accepted_address_.port(),
          &remote_addr)) {
    SendAcceptError(context, PP_ERROR_FAILED);
    return;
  }

  std::unique_ptr<ppapi::host::ResourceHost> host =
      factory_->CreateAcceptedTCPSocket(instance_,
                                        ppapi::TCP_SOCKET_VERSION_PRIVATE,
                                        std::move(accepted_socket_));
  if (!host) {
    SendAcceptError(context, PP_ERROR_NOSPACE);
    return;
  }
  int pending_resource_id =
      ppapi_host_->AddPendingResourceHost(std::move(host));
  if (pending_resource_id) {
    SendAcceptReply(
        context, PP_OK, pending_resource_id, local_addr, remote_addr);
  } else {
    SendAcceptError(context, PP_ERROR_NOSPACE);
  }
}

void PepperTCPServerSocketMessageFilter::SendListenReply(
    const ppapi::host::ReplyMessageContext& context,
    int32_t pp_result,
    const PP_NetAddress_Private& local_addr) {
  ppapi::host::ReplyMessageContext reply_context(context);
  reply_context.params.set_result(pp_result);
  SendReply(reply_context,
            PpapiPluginMsg_TCPServerSocket_ListenReply(local_addr));
}

void PepperTCPServerSocketMessageFilter::SendListenError(
    const ppapi::host::ReplyMessageContext& context,
    int32_t pp_result) {
  SendListenReply(
      context, pp_result, NetAddressPrivateImpl::kInvalidNetAddress);
}

void PepperTCPServerSocketMessageFilter::SendAcceptReply(
    const ppapi::host::ReplyMessageContext& context,
    int32_t pp_result,
    int pending_resource_id,
    const PP_NetAddress_Private& local_addr,
    const PP_NetAddress_Private& remote_addr) {
  ppapi::host::ReplyMessageContext reply_context(context);
  reply_context.params.set_result(pp_result);
  SendReply(reply_context,
            PpapiPluginMsg_TCPServerSocket_AcceptReply(
                pending_resource_id, local_addr, remote_addr));
}

void PepperTCPServerSocketMessageFilter::SendAcceptError(
    const ppapi::host::ReplyMessageContext& context,
    int32_t pp_result) {
  SendAcceptReply(context,
                  pp_result,
                  0,
                  NetAddressPrivateImpl::kInvalidNetAddress,
                  NetAddressPrivateImpl::kInvalidNetAddress);
}

}  // namespace content
