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

#include <cstring>
#include <utility>

#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/post_task.h"
#include "build/build_config.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_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/socket_permission_request.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "net/base/address_family.h"
#include "net/base/host_port_pair.h"
#include "net/base/io_buffer.h"
#include "net/base/ip_address.h"
#include "net/base/net_errors.h"
#include "net/log/net_log_source.h"
#include "net/log/net_log_with_source.h"
#include "net/ssl/ssl_info.h"
#include "net/traffic_annotation/network_traffic_annotation.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/proxy/tcp_socket_resource_constants.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::TCPSocketState;
using ppapi::TCPSocketVersion;
using ppapi::host::NetErrorToPepperError;
using ppapi::proxy::TCPSocketResourceConstants;

namespace {

size_t g_num_tcp_filter_instances = 0;

}  // namespace

namespace content {

network::mojom::NetworkContext*
    PepperTCPSocketMessageFilter::network_context_for_testing = nullptr;

PepperTCPSocketMessageFilter::PepperTCPSocketMessageFilter(
    ContentBrowserPepperHostFactory* factory,
    BrowserPpapiHostImpl* host,
    PP_Instance instance,
    TCPSocketVersion version)
    : version_(version),
      host_(host),
      factory_(factory),
      instance_(instance),
      external_plugin_(host->external_plugin()),
      render_process_id_(0),
      render_frame_id_(0),
      binding_(this),
      socket_observer_binding_(this),
      state_(TCPSocketState::INITIAL),
      bind_input_addr_(NetAddressPrivateImpl::kInvalidNetAddress),
      socket_options_(SOCKET_OPTION_NODELAY),
      rcvbuf_size_(0),
      sndbuf_size_(0),
      pending_accept_(false),
      pending_read_size_(0),
      pending_read_pp_error_(PP_OK_COMPLETIONPENDING),
      pending_read_on_unthrottle_(false),
      pending_write_bytes_written_(0),
      pending_write_pp_error_(PP_OK_COMPLETIONPENDING),
      is_potentially_secure_plugin_context_(
          host->IsPotentiallySecurePluginContext(instance)),
      weak_ptr_factory_(this) {
  DCHECK(host);
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  ++g_num_tcp_filter_instances;
  host_->AddInstanceObserver(instance_, this);
  is_throttled_ = host_->IsThrottled(instance_);
  if (!host->GetRenderFrameIDsForInstance(instance, &render_process_id_,
                                          &render_frame_id_)) {
    NOTREACHED();
  }
}

void PepperTCPSocketMessageFilter::SetConnectedSocket(
    network::mojom::TCPConnectedSocketPtrInfo connected_socket,
    network::mojom::SocketObserverRequest socket_observer_request,
    mojo::ScopedDataPipeConsumerHandle receive_stream,
    mojo::ScopedDataPipeProducerHandle send_stream) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  // This method grabs a reference to |this|, and releases a reference on the UI
  // thread, so something should be holding on to a reference on the current
  // thread to prevent the object from being deleted before this method returns.
  DCHECK(HasOneRef());

  base::PostTaskWithTraits(
      FROM_HERE, {BrowserThread::UI},
      base::BindOnce(
          &PepperTCPSocketMessageFilter::SetConnectedSocketOnUIThread, this,
          std::move(connected_socket), std::move(socket_observer_request),
          std::move(receive_stream), std::move(send_stream)));
}

PepperTCPSocketMessageFilter::~PepperTCPSocketMessageFilter() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  if (host_)
    host_->RemoveInstanceObserver(instance_, this);
  --g_num_tcp_filter_instances;
}

void PepperTCPSocketMessageFilter::SetConnectedSocketOnUIThread(
    network::mojom::TCPConnectedSocketPtrInfo connected_socket,
    network::mojom::SocketObserverRequest socket_observer_request,
    mojo::ScopedDataPipeConsumerHandle receive_stream,
    mojo::ScopedDataPipeProducerHandle send_stream) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK_EQ(state_.state(), TCPSocketState::INITIAL);

  state_ = TCPSocketState(TCPSocketState::CONNECTED);
  connected_socket_.Bind(std::move(connected_socket));
  socket_observer_binding_.Bind(std::move(socket_observer_request));
  socket_observer_binding_.set_connection_error_handler(
      base::BindOnce(&PepperTCPSocketMessageFilter::OnSocketObserverError,
                     base::Unretained(this)));

  SetStreams(std::move(receive_stream), std::move(send_stream));
}

// static
void PepperTCPSocketMessageFilter::SetNetworkContextForTesting(
    network::mojom::NetworkContext* network_context) {
  network_context_for_testing = network_context;
}

// static
size_t PepperTCPSocketMessageFilter::GetNumInstances() {
  return g_num_tcp_filter_instances;
}

void PepperTCPSocketMessageFilter::OnFilterDestroyed() {
  ResourceMessageFilter::OnFilterDestroyed();
  // Need to close all mojo pipes the socket on the UI thread. Calling Close()
  // also ensures that future messages will be ignored, so the mojo pipes won't
  // be re-created, so after Close() runs, |this| can be safely deleted on the
  // IO thread.
  base::PostTaskWithTraits(
      FROM_HERE, {BrowserThread::UI},
      base::BindOnce(&PepperTCPSocketMessageFilter::Close, this));
}

scoped_refptr<base::TaskRunner>
PepperTCPSocketMessageFilter::OverrideTaskRunnerForMessage(
    const IPC::Message& message) {
  switch (message.type()) {
    case PpapiHostMsg_TCPSocket_Bind::ID:
    case PpapiHostMsg_TCPSocket_Connect::ID:
    case PpapiHostMsg_TCPSocket_ConnectWithNetAddress::ID:
    case PpapiHostMsg_TCPSocket_Listen::ID:
    case PpapiHostMsg_TCPSocket_SSLHandshake::ID:
    case PpapiHostMsg_TCPSocket_Read::ID:
    case PpapiHostMsg_TCPSocket_Write::ID:
    case PpapiHostMsg_TCPSocket_Accept::ID:
    case PpapiHostMsg_TCPSocket_Close::ID:
    case PpapiHostMsg_TCPSocket_SetOption::ID:
      return base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::UI});
  }
  return nullptr;
}

int32_t PepperTCPSocketMessageFilter::OnResourceMessageReceived(
    const IPC::Message& msg,
    ppapi::host::HostMessageContext* context) {
  PPAPI_BEGIN_MESSAGE_MAP(PepperTCPSocketMessageFilter, msg)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_TCPSocket_Bind, OnMsgBind)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_TCPSocket_Connect,
                                      OnMsgConnect)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(
        PpapiHostMsg_TCPSocket_ConnectWithNetAddress,
        OnMsgConnectWithNetAddress)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_TCPSocket_SSLHandshake,
                                      OnMsgSSLHandshake)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_TCPSocket_Read, OnMsgRead)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_TCPSocket_Write, OnMsgWrite)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_TCPSocket_Listen,
                                      OnMsgListen)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_TCPSocket_Accept,
                                        OnMsgAccept)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_TCPSocket_Close,
                                        OnMsgClose)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_TCPSocket_SetOption,
                                      OnMsgSetOption)
  PPAPI_END_MESSAGE_MAP()
  return PP_ERROR_FAILED;
}

void PepperTCPSocketMessageFilter::OnThrottleStateChanged(bool is_throttled) {
  base::PostTaskWithTraits(
      FROM_HERE, {BrowserThread::UI},
      base::BindOnce(
          &PepperTCPSocketMessageFilter::ThrottleStateChangedOnUIThread, this,
          is_throttled));
}

void PepperTCPSocketMessageFilter::OnHostDestroyed() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  host_->RemoveInstanceObserver(instance_, this);
  host_ = nullptr;
}

void PepperTCPSocketMessageFilter::ThrottleStateChangedOnUIThread(
    bool is_throttled) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  is_throttled_ = is_throttled;

  if (pending_read_on_unthrottle_ && !is_throttled) {
    pending_read_on_unthrottle_ = false;
    TryRead();
  }
}

void PepperTCPSocketMessageFilter::OnComplete(
    int result,
    const base::Optional<net::AddressList>& resolved_addresses) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  binding_.Close();

  if (!host_resolve_context_.is_valid())
    return;

  ppapi::host::ReplyMessageContext context = host_resolve_context_;
  host_resolve_context_ = ppapi::host::ReplyMessageContext();

  if (!state_.IsPending(TCPSocketState::CONNECT)) {
    DCHECK(state_.state() == TCPSocketState::CLOSED);
    SendConnectError(context, PP_ERROR_FAILED);
    return;
  }

  if (result != net::OK) {
    SendConnectError(context, NetErrorToPepperError(result));
    state_.CompletePendingTransition(false);
    return;
  }

  StartConnect(context, resolved_addresses.value());
}

void PepperTCPSocketMessageFilter::OnReadError(int net_error) {
  // If this method is called more than once, or |net_error| isn't an allowed
  // value, just ignore the message.
  if (pending_read_pp_error_ != PP_OK_COMPLETIONPENDING || net_error > 0 ||
      net_error == net::ERR_IO_PENDING) {
    return;
  }

  pending_read_pp_error_ = NetErrorToPepperError(net_error);
  // Complete pending read with the error message if there's a pending read, and
  // the read data pipe has already been closed. If the pipe is still open, need
  // to wait until all data has been read before can start failing reads.
  if (pending_read_context_.is_valid() && !receive_stream_ &&
      !pending_read_on_unthrottle_) {
    TryRead();
  }
}

void PepperTCPSocketMessageFilter::OnWriteError(int net_error) {
  // If this method is called more than once, or |net_error| isn't an allowed
  // value, just ignore the message.
  if (pending_write_pp_error_ != PP_OK_COMPLETIONPENDING || net_error > 0 ||
      net_error == net::ERR_IO_PENDING) {
    return;
  }

  pending_write_pp_error_ = NetErrorToPepperError(net_error);
  // Complete pending write with the error message if there's a pending write,
  // and the write data pipe has already been closed.
  if (pending_write_context_.is_valid() && !send_stream_)
    TryWrite();
}

void PepperTCPSocketMessageFilter::OnSocketObserverError() {
  // Note that this method may be called while a connection is still being made.
  socket_observer_binding_.Close();

  // Treat this as a read and write error. If read and write errors have already
  // been received, these calls will do nothing.
  OnReadError(PP_ERROR_FAILED);
  OnWriteError(PP_ERROR_FAILED);
}

int32_t PepperTCPSocketMessageFilter::OnMsgBind(
    const ppapi::host::HostMessageContext* context,
    const PP_NetAddress_Private& net_addr) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // This is only supported by PPB_TCPSocket v1.1 or above.
  if (version_ != ppapi::TCP_SOCKET_VERSION_1_1_OR_ABOVE) {
    NOTREACHED();
    return PP_ERROR_NOACCESS;
  }

  if (!pepper_socket_utils::CanUseSocketAPIs(
          external_plugin_, false /* private_api */, nullptr,
          render_process_id_, render_frame_id_)) {
    return PP_ERROR_NOACCESS;
  }

  if (state_.IsPending(TCPSocketState::BIND))
    return PP_ERROR_INPROGRESS;

  if (!state_.IsValidTransition(TCPSocketState::BIND))
    return PP_ERROR_FAILED;

  DCHECK(!bound_socket_);
  DCHECK(!connected_socket_);
  DCHECK(!server_socket_);

  // Validate address.
  net::IPAddressBytes address;
  uint16_t port;
  if (!NetAddressPrivateImpl::NetAddressToIPEndPoint(net_addr, &address,
                                                     &port)) {
    state_.DoTransition(TCPSocketState::BIND, false);
    return PP_ERROR_ADDRESS_INVALID;
  }

  network::mojom::NetworkContext* network_context = GetNetworkContext();
  if (!network_context)
    return PP_ERROR_FAILED;

  // The network service doesn't allow binding a socket without first
  // specifying if it's going to be used as a read or write socket,
  // so just hold onto the address for now, without actually binding anything.
  bind_input_addr_ = net_addr;

  state_.SetPendingTransition(TCPSocketState::BIND);
  network_context->CreateTCPBoundSocket(
      net::IPEndPoint(net::IPAddress(address), port),
      pepper_socket_utils::PepperTCPNetworkAnnotationTag(),
      mojo::MakeRequest(&bound_socket_),
      mojo::WrapCallbackWithDefaultInvokeIfNotRun(
          base::BindOnce(&PepperTCPSocketMessageFilter::OnBindCompleted,
                         weak_ptr_factory_.GetWeakPtr(),
                         context->MakeReplyMessageContext()),
          net::ERR_FAILED, base::nullopt /* local_addr */));

  return PP_OK_COMPLETIONPENDING;
}

int32_t PepperTCPSocketMessageFilter::OnMsgConnect(
    const ppapi::host::HostMessageContext* context,
    const std::string& host,
    uint16_t port) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // This is only supported by PPB_TCPSocket_Private.
  if (!IsPrivateAPI()) {
    NOTREACHED();
    return PP_ERROR_NOACCESS;
  }

  SocketPermissionRequest request(SocketPermissionRequest::TCP_CONNECT, host,
                                  port);
  if (!pepper_socket_utils::CanUseSocketAPIs(
          external_plugin_, true /* private_api */, &request,
          render_process_id_, render_frame_id_)) {
    return PP_ERROR_NOACCESS;
  }

  if (!state_.IsValidTransition(TCPSocketState::CONNECT)) {
    NOTREACHED() << "This shouldn't be reached since the renderer only tries "
                 << "to connect once.";
    return PP_ERROR_FAILED;
  }

  network::mojom::NetworkContext* network_context = GetNetworkContext();
  if (!network_context)
    return PP_ERROR_FAILED;

  network::mojom::ResolveHostClientPtr client_ptr;
  binding_.Bind(mojo::MakeRequest(&client_ptr));
  binding_.set_connection_error_handler(
      base::BindOnce(&PepperTCPSocketMessageFilter::OnComplete,
                     base::Unretained(this), net::ERR_FAILED, base::nullopt));
  network_context->ResolveHost(net::HostPortPair(host, port), nullptr,
                               std::move(client_ptr));

  state_.SetPendingTransition(TCPSocketState::CONNECT);
  host_resolve_context_ = context->MakeReplyMessageContext();
  return PP_OK_COMPLETIONPENDING;
}

int32_t PepperTCPSocketMessageFilter::OnMsgConnectWithNetAddress(
    const ppapi::host::HostMessageContext* context,
    const PP_NetAddress_Private& net_addr) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  content::SocketPermissionRequest request =
      pepper_socket_utils::CreateSocketPermissionRequest(
          content::SocketPermissionRequest::TCP_CONNECT, net_addr);
  if (!pepper_socket_utils::CanUseSocketAPIs(external_plugin_, IsPrivateAPI(),
                                             &request, render_process_id_,
                                             render_frame_id_)) {
    return PP_ERROR_NOACCESS;
  }

  if (!state_.IsValidTransition(TCPSocketState::CONNECT))
    return PP_ERROR_FAILED;

  state_.SetPendingTransition(TCPSocketState::CONNECT);

  net::IPAddressBytes address;
  uint16_t port;
  if (!NetAddressPrivateImpl::NetAddressToIPEndPoint(net_addr, &address,
                                                     &port)) {
    state_.CompletePendingTransition(false);
    return PP_ERROR_ADDRESS_INVALID;
  }

  StartConnect(
      context->MakeReplyMessageContext(),
      net::AddressList(net::IPEndPoint(net::IPAddress(address), port)));
  return PP_OK_COMPLETIONPENDING;
}

int32_t PepperTCPSocketMessageFilter::OnMsgSSLHandshake(
    const ppapi::host::HostMessageContext* context,
    const std::string& server_name,
    uint16_t server_port,
    const std::vector<std::vector<char>>& trusted_certs,
    const std::vector<std::vector<char>>& untrusted_certs) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // Allow to do SSL handshake only if currently the socket has been connected
  // and there isn't pending read or write.
  if (!state_.IsValidTransition(TCPSocketState::SSL_CONNECT) ||
      pending_read_context_.is_valid() || pending_write_context_.is_valid()) {
    return PP_ERROR_FAILED;
  }

  // If there's a pending read or write error, fail the request with that.
  if (pending_read_pp_error_ != PP_OK_COMPLETIONPENDING) {
    if (pending_read_pp_error_ == PP_OK)
      pending_read_pp_error_ = PP_ERROR_FAILED;
    return pending_read_pp_error_;
  }

  if (pending_write_pp_error_ != PP_OK_COMPLETIONPENDING) {
    if (pending_write_pp_error_ == PP_OK)
      pending_write_pp_error_ = PP_ERROR_FAILED;
    return pending_write_pp_error_;
  }

  // TODO(raymes,rsleevi): Use trusted/untrusted certificates when connecting.

  // Close all Mojo pipes except |connected_socket_|.
  receive_stream_.reset();
  read_watcher_.reset();
  send_stream_.reset();
  write_watcher_.reset();
  socket_observer_binding_.Close();

  network::mojom::SocketObserverPtr socket_observer;
  socket_observer_binding_.Bind(mojo::MakeRequest(&socket_observer));
  socket_observer_binding_.set_connection_error_handler(
      base::BindOnce(&PepperTCPSocketMessageFilter::OnSocketObserverError,
                     base::Unretained(this)));

  state_.SetPendingTransition(TCPSocketState::SSL_CONNECT);

  network::mojom::TLSClientSocketOptionsPtr tls_client_socket_options =
      network::mojom::TLSClientSocketOptions::New();
  tls_client_socket_options->send_ssl_info = true;
  net::HostPortPair host_port_pair(server_name, server_port);
  connected_socket_->UpgradeToTLS(
      host_port_pair, std::move(tls_client_socket_options),
      pepper_socket_utils::PepperTCPNetworkAnnotationTag(),
      mojo::MakeRequest(&tls_client_socket_), std::move(socket_observer),
      mojo::WrapCallbackWithDefaultInvokeIfNotRun(
          base::BindOnce(&PepperTCPSocketMessageFilter::OnSSLHandshakeCompleted,
                         base::Unretained(this),
                         context->MakeReplyMessageContext()),
          net::ERR_FAILED, mojo::ScopedDataPipeConsumerHandle(),
          mojo::ScopedDataPipeProducerHandle(), base::nullopt /* ssl_info */));

  return PP_OK_COMPLETIONPENDING;
}

int32_t PepperTCPSocketMessageFilter::OnMsgRead(
    const ppapi::host::HostMessageContext* context,
    int32_t bytes_to_read) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  // This only covers the case where the socket was explicitly closed from the
  // caller, or the filter is being destroyed. Read errors and Mojo errors are
  // handled in TryRead().
  if (!state_.IsConnected())
    return PP_ERROR_FAILED;

  if (pending_read_context_.is_valid())
    return PP_ERROR_INPROGRESS;
  if (bytes_to_read <= 0 ||
      bytes_to_read > TCPSocketResourceConstants::kMaxReadSize) {
    return PP_ERROR_BADARGUMENT;
  }

  pending_read_context_ = context->MakeReplyMessageContext();
  pending_read_size_ = static_cast<uint32_t>(bytes_to_read);
  TryRead();
  return PP_OK_COMPLETIONPENDING;
}

int32_t PepperTCPSocketMessageFilter::OnMsgWrite(
    const ppapi::host::HostMessageContext* context,
    const std::string& data) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (!state_.IsConnected())
    return PP_ERROR_FAILED;
  if (pending_write_context_.is_valid())
    return PP_ERROR_INPROGRESS;

  DCHECK(pending_write_data_.empty());
  DCHECK_EQ(0u, pending_write_bytes_written_);

  size_t data_size = data.size();
  if (data_size == 0 ||
      data_size >
          static_cast<size_t>(TCPSocketResourceConstants::kMaxWriteSize)) {
    return PP_ERROR_BADARGUMENT;
  }

  pending_write_data_ = data;
  pending_write_context_ = context->MakeReplyMessageContext();
  TryWrite();
  return PP_OK_COMPLETIONPENDING;
}

int32_t PepperTCPSocketMessageFilter::OnMsgListen(
    const ppapi::host::HostMessageContext* context,
    int32_t backlog) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (state_.IsPending(TCPSocketState::LISTEN))
    return PP_ERROR_INPROGRESS;

  if (!state_.IsValidTransition(TCPSocketState::LISTEN))
    return PP_ERROR_FAILED;

  // This is only supported by PPB_TCPSocket v1.1 or above.
  if (version_ != ppapi::TCP_SOCKET_VERSION_1_1_OR_ABOVE) {
    NOTREACHED();
    return PP_ERROR_NOACCESS;
  }

  content::SocketPermissionRequest request =
      pepper_socket_utils::CreateSocketPermissionRequest(
          content::SocketPermissionRequest::TCP_LISTEN, bind_input_addr_);
  if (!pepper_socket_utils::CanUseSocketAPIs(
          external_plugin_, false /* private_api */, &request,
          render_process_id_, render_frame_id_)) {
    return PP_ERROR_NOACCESS;
  }

  DCHECK(bound_socket_);
  DCHECK(!server_socket_);

  state_.SetPendingTransition(TCPSocketState::LISTEN);

  bound_socket_->Listen(
      backlog, mojo::MakeRequest(&server_socket_),
      mojo::WrapCallbackWithDefaultInvokeIfNotRun(
          base::BindOnce(&PepperTCPSocketMessageFilter::OnListenCompleted,
                         base::Unretained(this),
                         context->MakeReplyMessageContext()),
          net::ERR_FAILED));
  return PP_OK_COMPLETIONPENDING;
}

int32_t PepperTCPSocketMessageFilter::OnMsgAccept(
    const ppapi::host::HostMessageContext* context) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (pending_accept_)
    return PP_ERROR_INPROGRESS;
  if (state_.state() != TCPSocketState::LISTENING)
    return PP_ERROR_FAILED;

  DCHECK(server_socket_);

  pending_accept_ = true;

  network::mojom::SocketObserverPtr socket_observer;
  network::mojom::SocketObserverRequest socket_observer_request =
      mojo::MakeRequest(&socket_observer);
  server_socket_->Accept(
      std::move(socket_observer),
      mojo::WrapCallbackWithDefaultInvokeIfNotRun(
          base::BindOnce(&PepperTCPSocketMessageFilter::OnAcceptCompleted,
                         base::Unretained(this),
                         context->MakeReplyMessageContext(),
                         std::move(socket_observer_request)),
          net::ERR_FAILED, base::nullopt /* remote_addr */,
          network::mojom::TCPConnectedSocketPtr(),
          mojo::ScopedDataPipeConsumerHandle(),
          mojo::ScopedDataPipeProducerHandle()));
  return PP_OK_COMPLETIONPENDING;
}

int32_t PepperTCPSocketMessageFilter::OnMsgClose(
    const ppapi::host::HostMessageContext* context) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (state_.state() == TCPSocketState::CLOSED)
    return PP_OK;

  Close();
  return PP_OK;
}

int32_t PepperTCPSocketMessageFilter::OnMsgSetOption(
    const ppapi::host::HostMessageContext* context,
    PP_TCPSocket_Option name,
    const ppapi::SocketOptionData& value) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // Options are only applied if |this| is currently being used as a client
  // socket, or is going to be used as one - they are ignored for server
  // sockets.
  switch (name) {
    case PP_TCPSOCKET_OPTION_NO_DELAY: {
      bool boolean_value = false;
      if (!value.GetBool(&boolean_value))
        return PP_ERROR_BADARGUMENT;

      // If |connected_socket_| is connecting or has connected, pass the setting
      // along.
      if (connected_socket_.is_bound()) {
        connected_socket_->SetNoDelay(
            boolean_value,
            // Callback that converts a bool to a net error code which it then
            // passes to the common completion callback routine.
            base::BindOnce(
                [](base::OnceCallback<void(int)> completion_callback,
                   bool success) {
                  std::move(completion_callback)
                      .Run(success ? net::OK : net::ERR_FAILED);
                },
                CreateCompletionCallback<
                    PpapiPluginMsg_TCPSocket_SetOptionReply>(context)));
        return PP_OK_COMPLETIONPENDING;
      }

      // TCPConnectedSocket instance is not yet created. So remember the value
      // here.
      if (boolean_value) {
        socket_options_ |= SOCKET_OPTION_NODELAY;
      } else {
        socket_options_ &= ~SOCKET_OPTION_NODELAY;
      }
      return PP_OK;
    }
    case PP_TCPSOCKET_OPTION_SEND_BUFFER_SIZE: {
      int32_t integer_value = 0;
      if (!value.GetInt32(&integer_value) || integer_value <= 0 ||
          integer_value > TCPSocketResourceConstants::kMaxSendBufferSize)
        return PP_ERROR_BADARGUMENT;

      // If |connected_socket_| is connecting or has connected, pass the setting
      // along.
      if (connected_socket_.is_bound()) {
        connected_socket_->SetSendBufferSize(
            integer_value,
            CreateCompletionCallback<PpapiPluginMsg_TCPSocket_SetOptionReply>(
                context));
        return PP_OK_COMPLETIONPENDING;
      }

      // TCPSocket instance is not yet created. So remember the value here.
      socket_options_ |= SOCKET_OPTION_SNDBUF_SIZE;
      sndbuf_size_ = integer_value;
      return PP_OK;
    }
    case PP_TCPSOCKET_OPTION_RECV_BUFFER_SIZE: {
      int32_t integer_value = 0;
      if (!value.GetInt32(&integer_value) || integer_value <= 0 ||
          integer_value > TCPSocketResourceConstants::kMaxReceiveBufferSize)
        return PP_ERROR_BADARGUMENT;

      // If |connected_socket_| is connecting or has connected, pass the setting
      // along.
      if (connected_socket_.is_bound()) {
        connected_socket_->SetReceiveBufferSize(
            integer_value,
            CreateCompletionCallback<PpapiPluginMsg_TCPSocket_SetOptionReply>(
                context));
        return PP_OK_COMPLETIONPENDING;
      }

      // TCPConnectedSocket instance is not yet created. So remember the value
      // here.
      socket_options_ |= SOCKET_OPTION_RCVBUF_SIZE;
      rcvbuf_size_ = integer_value;
      return PP_OK;
    }
    default: {
      NOTREACHED();
      return PP_ERROR_BADARGUMENT;
    }
  }
  return PP_OK;
}

void PepperTCPSocketMessageFilter::TryRead() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(state_.IsConnected());
  DCHECK(pending_read_context_.is_valid());
  DCHECK_GT(pending_read_size_, 0u);
  DCHECK(!pending_read_on_unthrottle_);

  if (is_throttled_) {
    pending_read_on_unthrottle_ = true;
    return;
  }

  // This loop's body will generally run only once, unless there's a read error,
  // in which case, it will start over, to re-apply the initial logic.
  while (true) {
    // As long as the read stream is still open, try to read data, even if a
    // read error has been received from the SocketObserver, as there may still
    // be data on the pipe.
    if (!receive_stream_.is_valid()) {
      // If no read error has been received yet, wait to receive one through
      // the SocketObserver interface.
      if (pending_read_pp_error_ == PP_OK_COMPLETIONPENDING) {
        DCHECK(socket_observer_binding_.is_bound());
        break;
      }

      // Otherwise, pass along the read error.
      SendReadError(pending_read_pp_error_);
      // If the socket was closed gracefully, only return OK for a single
      // read.
      if (pending_read_pp_error_ == PP_OK)
        pending_read_pp_error_ = PP_ERROR_FAILED;
      break;
    }

    DCHECK(read_watcher_);
    const void* buffer = nullptr;
    uint32_t num_bytes = 0;
    int mojo_result = receive_stream_->BeginReadData(&buffer, &num_bytes,
                                                     MOJO_READ_DATA_FLAG_NONE);
    if (mojo_result == MOJO_RESULT_SHOULD_WAIT) {
      read_watcher_->ArmOrNotify();
      break;
    }

    // On a Mojo pipe error (which may indicate a graceful close, network error,
    // or network service crash), close read pipe and restart the loop.
    if (mojo_result != MOJO_RESULT_OK) {
      read_watcher_.reset();
      receive_stream_.reset();
      continue;
    }

    // This is guaranteed by Mojo.
    DCHECK_GT(num_bytes, 0u);

    uint32_t bytes_to_copy = std::min(num_bytes, pending_read_size_);
    SendReadReply(PP_OK, std::string(reinterpret_cast<const char*>(buffer),
                                     bytes_to_copy));
    receive_stream_->EndReadData(bytes_to_copy);
    break;
  }
}

void PepperTCPSocketMessageFilter::TryWrite() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(state_.IsConnected());
  DCHECK(pending_write_context_.is_valid());
  DCHECK(!pending_write_data_.empty());
  DCHECK_LT(pending_write_bytes_written_, pending_write_data_.size());

  // The structure of this code largely mirrors TryRead() above, with a couple
  // differences. The loop will repeat until all bytes are written, there's an
  // error, or no more buffer space is available. Also, it's possible for a
  // Mojo write to succeed, but a write to the underlying socket to fail. In
  // that case, the failure might not be returned to the caller until it tries
  // to write again. Since the socket APIs themselves don't guarantee that
  // data has been successfully received by the remote server on success, this
  // should not cause unexpected problems for consumers.
  while (true) {
    if (!send_stream_.is_valid()) {
      if (pending_write_pp_error_ == PP_OK_COMPLETIONPENDING) {
        DCHECK(socket_observer_binding_.is_bound());
        break;
      }
      SendWriteReply(pending_write_pp_error_);
      // Mirror handling of "OK" read errors, only sending "OK" for a single
      // write, though getting "OK" from a write is probably nonsense, anyways.
      if (pending_write_pp_error_ == PP_OK)
        pending_write_pp_error_ = PP_ERROR_FAILED;
      break;
    }

    DCHECK(write_watcher_);

    uint32_t num_bytes =
        pending_write_data_.size() - pending_write_bytes_written_;
    DCHECK_GT(num_bytes, 0u);
    int mojo_result = send_stream_->WriteData(
        pending_write_data_.data() + pending_write_bytes_written_, &num_bytes,
        MOJO_WRITE_DATA_FLAG_NONE);
    if (mojo_result == MOJO_RESULT_SHOULD_WAIT) {
      write_watcher_->ArmOrNotify();
      break;
    }

    // On a Mojo pipe error (which may indicate a graceful close, network error,
    // or network service crash), close write pipe and restart the loop.
    if (mojo_result != MOJO_RESULT_OK) {
      write_watcher_.reset();
      send_stream_.reset();
      continue;
    }

    // This is guaranteed by Mojo.
    DCHECK_GT(num_bytes, 0u);

    pending_write_bytes_written_ += num_bytes;
    // If all bytes were written, nothing left to do.
    if (pending_write_bytes_written_ == pending_write_data_.size()) {
      SendWriteReply(pending_write_bytes_written_);
      break;
    }
  }
}

void PepperTCPSocketMessageFilter::StartConnect(
    const ppapi::host::ReplyMessageContext& context,
    const net::AddressList& address_list) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(state_.IsPending(TCPSocketState::CONNECT));
  DCHECK(!address_list.empty());

  network::mojom::SocketObserverPtr socket_observer;
  socket_observer_binding_.Bind(mojo::MakeRequest(&socket_observer));

  socket_observer_binding_.set_connection_error_handler(
      base::BindOnce(&PepperTCPSocketMessageFilter::OnSocketObserverError,
                     base::Unretained(this)));

  network::mojom::TCPConnectedSocketOptionsPtr socket_options =
      network::mojom::TCPConnectedSocketOptions::New();
  socket_options->no_delay = !!(socket_options_ & SOCKET_OPTION_NODELAY);
  if (socket_options_ & SOCKET_OPTION_RCVBUF_SIZE)
    socket_options->receive_buffer_size = rcvbuf_size_;
  if (socket_options_ & SOCKET_OPTION_SNDBUF_SIZE)
    socket_options->send_buffer_size = sndbuf_size_;

  network::mojom::NetworkContext::CreateTCPConnectedSocketCallback callback =
      mojo::WrapCallbackWithDefaultInvokeIfNotRun(
          base::BindOnce(&PepperTCPSocketMessageFilter::OnConnectCompleted,
                         weak_ptr_factory_.GetWeakPtr(), context),
          net::ERR_FAILED, base::nullopt, base::nullopt,
          mojo::ScopedDataPipeConsumerHandle(),
          mojo::ScopedDataPipeProducerHandle());
  if (bound_socket_) {
    bound_socket_->Connect(address_list, std::move(socket_options),
                           mojo::MakeRequest(&connected_socket_),
                           std::move(socket_observer), std::move(callback));
  } else {
    network::mojom::NetworkContext* network_context = GetNetworkContext();
    if (!network_context) {
      // This will delete |callback|, which will invoke OnConnectCompleted()
      // with an error.
      return;
    }
    network_context->CreateTCPConnectedSocket(
        base::nullopt /* local_addr */, address_list, std::move(socket_options),
        pepper_socket_utils::PepperTCPNetworkAnnotationTag(),
        mojo::MakeRequest(&connected_socket_), std::move(socket_observer),
        std::move(callback));
  }
}

void PepperTCPSocketMessageFilter::OnConnectCompleted(
    const ppapi::host::ReplyMessageContext& context,
    int net_result,
    const base::Optional<net::IPEndPoint>& local_addr,
    const base::Optional<net::IPEndPoint>& peer_addr,
    mojo::ScopedDataPipeConsumerHandle receive_stream,
    mojo::ScopedDataPipeProducerHandle send_stream) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  int32_t pp_result = NetErrorToPepperError(net_result);

  if (state_.state() == TCPSocketState::CLOSED) {
    // If this is called as a result of Close() being invoked and closing the
    // pipe, fail the request without doing anything.
    DCHECK_EQ(net_result, net::ERR_FAILED);
    SendConnectError(context, pp_result);
    return;
  }

  DCHECK(state_.IsPending(TCPSocketState::CONNECT));

  do {
    if (pp_result != PP_OK)
      break;

    PP_NetAddress_Private pp_local_addr =
        NetAddressPrivateImpl::kInvalidNetAddress;
    PP_NetAddress_Private pp_remote_addr =
        NetAddressPrivateImpl::kInvalidNetAddress;
    if (!local_addr || !peer_addr ||
        !NetAddressPrivateImpl::IPEndPointToNetAddress(
            local_addr->address().bytes(), local_addr->port(),
            &pp_local_addr) ||
        !NetAddressPrivateImpl::IPEndPointToNetAddress(
            peer_addr->address().bytes(), peer_addr->port(), &pp_remote_addr)) {
      pp_result = PP_ERROR_ADDRESS_INVALID;
      break;
    }

    SetStreams(std::move(receive_stream), std::move(send_stream));

    bound_socket_.reset();

    SendConnectReply(context, PP_OK, pp_local_addr, pp_remote_addr);
    state_.CompletePendingTransition(true);
    return;
  } while (false);

  // Handle errors.

  // This can happen even when the network service is behaving correctly, as
  // we may see the |socket_observer_binding_| closed before receiving an
  // error.
  pending_read_pp_error_ = PP_OK_COMPLETIONPENDING;
  pending_write_pp_error_ = PP_OK_COMPLETIONPENDING;

  Close();
  SendConnectError(context, pp_result);

  if (version_ != ppapi::TCP_SOCKET_VERSION_1_1_OR_ABOVE) {
    // In order to maintain backward compatibility, allow further attempts
    // to connect the socket.
    state_ = TCPSocketState(TCPSocketState::INITIAL);
  }
}

void PepperTCPSocketMessageFilter::OnSSLHandshakeCompleted(
    const ppapi::host::ReplyMessageContext& context,
    int net_result,
    mojo::ScopedDataPipeConsumerHandle receive_stream,
    mojo::ScopedDataPipeProducerHandle send_stream,
    const base::Optional<net::SSLInfo>& ssl_info) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  int pp_result = NetErrorToPepperError(net_result);
  if (state_.state() == TCPSocketState::CLOSED) {
    // If this is called as a result of Close() being invoked and closing the
    // pipe, fail the request without doing anything.
    DCHECK_EQ(net_result, net::ERR_FAILED);
    SendSSLHandshakeReply(context, pp_result, base::nullopt /* ssl_info */);
    return;
  }

  DCHECK(state_.IsPending(TCPSocketState::SSL_CONNECT));

  if (pp_result == PP_OK && !ssl_info)
    pp_result = PP_ERROR_FAILED;

  state_.CompletePendingTransition(pp_result == PP_OK);

  if (pp_result != PP_OK) {
    Close();
  } else {
    SetStreams(std::move(receive_stream), std::move(send_stream));
  }

  SendSSLHandshakeReply(context, pp_result, ssl_info);
}

void PepperTCPSocketMessageFilter::OnBindCompleted(
    const ppapi::host::ReplyMessageContext& context,
    int net_result,
    const base::Optional<net::IPEndPoint>& local_addr) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  int pp_result = NetErrorToPepperError(net_result);
  if (state_.state() == TCPSocketState::CLOSED) {
    // If this is called as a result of Close() being invoked and closing the
    // pipe, fail the request without doing anything.
    DCHECK_EQ(net_result, net::ERR_FAILED);
    SendBindError(context, pp_result);
    return;
  }

  DCHECK(bound_socket_);
  DCHECK(state_.IsPending(TCPSocketState::BIND));

  PP_NetAddress_Private bound_address =
      NetAddressPrivateImpl::kInvalidNetAddress;
  if (pp_result == PP_OK &&
      (!local_addr || !NetAddressPrivateImpl::IPEndPointToNetAddress(
                          local_addr->address().bytes(), local_addr->port(),
                          &bound_address))) {
    pp_result = PP_ERROR_ADDRESS_INVALID;
  }

  if (pp_result != PP_OK) {
    bound_socket_.reset();
  } else {
    bind_output_ip_endpoint_ = *local_addr;
  }

  SendBindReply(context, pp_result, bound_address);
  state_.CompletePendingTransition(pp_result == PP_OK);
}

void PepperTCPSocketMessageFilter::OnListenCompleted(
    const ppapi::host::ReplyMessageContext& context,
    int net_result) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  int pp_result = NetErrorToPepperError(net_result);
  if (state_.state() == TCPSocketState::CLOSED) {
    // If this is called as a result of Close() being invoked and closing the
    // pipe, fail the request without doing anything.
    DCHECK_EQ(net_result, net::ERR_FAILED);
    SendListenReply(context, pp_result);
    return;
  }

  DCHECK(state_.IsPending(TCPSocketState::LISTEN));

#if defined(OS_CHROMEOS)
  if (pp_result == PP_OK) {
    OpenFirewallHole(context);
    return;
  }
#endif  // defined(OS_CHROMEOS)

  SendListenReply(context, pp_result);
  state_.CompletePendingTransition(pp_result == PP_OK);
  if (pp_result != PP_OK)
    Close();
}

void PepperTCPSocketMessageFilter::OnAcceptCompleted(
    const ppapi::host::ReplyMessageContext& context,
    network::mojom::SocketObserverRequest socket_observer_request,
    int net_result,
    const base::Optional<net::IPEndPoint>& remote_addr,
    network::mojom::TCPConnectedSocketPtr connected_socket,
    mojo::ScopedDataPipeConsumerHandle receive_stream,
    mojo::ScopedDataPipeProducerHandle send_stream) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(pending_accept_);

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

  if (!remote_addr || !connected_socket.is_bound()) {
    SendAcceptError(context, NetErrorToPepperError(net_result));
    return;
  }

  DCHECK(socket_observer_request.is_pending());

  PP_NetAddress_Private pp_remote_addr =
      NetAddressPrivateImpl::kInvalidNetAddress;

  if (!NetAddressPrivateImpl::IPEndPointToNetAddress(
          remote_addr->address().bytes(), remote_addr->port(),
          &pp_remote_addr)) {
    SendAcceptError(context, PP_ERROR_ADDRESS_INVALID);
    return;
  }

  PP_NetAddress_Private bound_address =
      NetAddressPrivateImpl::kInvalidNetAddress;
  bool success = NetAddressPrivateImpl::IPEndPointToNetAddress(
      bind_output_ip_endpoint_.address().bytes(),
      bind_output_ip_endpoint_.port(), &bound_address);
  // This conversion should succeed, since it succeeded in OnBindComplete()
  // already.
  DCHECK(success);

  base::PostTaskWithTraits(
      FROM_HERE, {BrowserThread::IO},
      base::BindOnce(&PepperTCPSocketMessageFilter::OnAcceptCompletedOnIOThread,
                     this, context, connected_socket.PassInterface(),
                     std::move(socket_observer_request),
                     std::move(receive_stream), std::move(send_stream),
                     bound_address, pp_remote_addr));
}

void PepperTCPSocketMessageFilter::OnAcceptCompletedOnIOThread(
    const ppapi::host::ReplyMessageContext& context,
    network::mojom::TCPConnectedSocketPtrInfo connected_socket,
    network::mojom::SocketObserverRequest socket_observer_request,
    mojo::ScopedDataPipeConsumerHandle receive_stream,
    mojo::ScopedDataPipeProducerHandle send_stream,
    PP_NetAddress_Private pp_local_addr,
    PP_NetAddress_Private pp_remote_addr) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  // |factory_| is guaranteed to be non-NULL here. Only those instances created
  // in CONNECTED state have a NULL |factory_|, while getting here requires
  // LISTENING state.
  DCHECK(factory_);

  std::unique_ptr<ppapi::host::ResourceHost> host =
      factory_->CreateAcceptedTCPSocket(
          instance_, version_, std::move(connected_socket),
          std::move(socket_observer_request), std::move(receive_stream),
          std::move(send_stream));
  if (!host) {
    SendAcceptError(context, PP_ERROR_NOSPACE);
    return;
  }

  int pending_host_id =
      host_->GetPpapiHost()->AddPendingResourceHost(std::move(host));
  if (pending_host_id) {
    SendAcceptReply(context, PP_OK, pending_host_id, pp_local_addr,
                    pp_remote_addr);
  } else {
    SendAcceptError(context, PP_ERROR_NOSPACE);
  }
}

void PepperTCPSocketMessageFilter::SetStreams(
    mojo::ScopedDataPipeConsumerHandle receive_stream,
    mojo::ScopedDataPipeProducerHandle send_stream) {
  DCHECK(!read_watcher_);
  DCHECK(!write_watcher_);

  receive_stream_ = std::move(receive_stream);
  read_watcher_ = std::make_unique<mojo::SimpleWatcher>(
      FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL);
  read_watcher_->Watch(
      receive_stream_.get(),
      MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
      MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
      base::BindRepeating(
          [](PepperTCPSocketMessageFilter* message_filter,
             MojoResult /* result */,
             const mojo::HandleSignalsState& /* state */) {
            // TryRead will correctly handle both cases (data ready to be
            // read, and the pipe was closed).
            message_filter->TryRead();
          },
          base::Unretained(this)));

  send_stream_ = std::move(send_stream);
  write_watcher_ = std::make_unique<mojo::SimpleWatcher>(
      FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL);
  write_watcher_->Watch(
      send_stream_.get(),
      MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
      MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
      base::BindRepeating(
          [](PepperTCPSocketMessageFilter* message_filter,
             MojoResult /* result */,
             const mojo::HandleSignalsState& /* state */) {
            // TryRead will correctly handle both cases (data ready to be
            // read, and the pipe was closed).
            message_filter->TryWrite();
          },
          base::Unretained(this)));
}

#if defined(OS_CHROMEOS)
void PepperTCPSocketMessageFilter::OpenFirewallHole(
    const ppapi::host::ReplyMessageContext& context) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  pepper_socket_utils::FirewallHoleOpenCallback callback = base::BindRepeating(
      &PepperTCPSocketMessageFilter::OnFirewallHoleOpened, this, context);
  pepper_socket_utils::OpenTCPFirewallHole(bind_output_ip_endpoint_, callback);
}

void PepperTCPSocketMessageFilter::OnFirewallHoleOpened(
    const ppapi::host::ReplyMessageContext& context,
    std::unique_ptr<chromeos::FirewallHole> hole) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(state_.IsPending(TCPSocketState::LISTEN));
  LOG_IF(WARNING, !hole.get()) << "Firewall hole could not be opened.";
  firewall_hole_.reset(hole.release());

  SendListenReply(context, PP_OK);
  state_.CompletePendingTransition(true);
}
#endif  // defined(OS_CHROMEOS)

void PepperTCPSocketMessageFilter::SendBindReply(
    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_TCPSocket_BindReply(local_addr));
}

void PepperTCPSocketMessageFilter::SendBindError(
    const ppapi::host::ReplyMessageContext& context,
    int32_t pp_error) {
  SendBindReply(context, pp_error, NetAddressPrivateImpl::kInvalidNetAddress);
}

void PepperTCPSocketMessageFilter::SendConnectReply(
    const ppapi::host::ReplyMessageContext& context,
    int32_t pp_result,
    const PP_NetAddress_Private& local_addr,
    const PP_NetAddress_Private& remote_addr) {
  UMA_HISTOGRAM_BOOLEAN("Pepper.PluginContextSecurity.TCPConnect",
                        is_potentially_secure_plugin_context_);

  ppapi::host::ReplyMessageContext reply_context(context);
  reply_context.params.set_result(pp_result);
  SendReply(reply_context,
            PpapiPluginMsg_TCPSocket_ConnectReply(local_addr, remote_addr));
}

void PepperTCPSocketMessageFilter::SendConnectError(
    const ppapi::host::ReplyMessageContext& context,
    int32_t pp_error) {
  SendConnectReply(context, pp_error, NetAddressPrivateImpl::kInvalidNetAddress,
                   NetAddressPrivateImpl::kInvalidNetAddress);
}

void PepperTCPSocketMessageFilter::SendSSLHandshakeReply(
    const ppapi::host::ReplyMessageContext& context,
    int32_t pp_result,
    const base::Optional<net::SSLInfo>& ssl_info) {
  ppapi::host::ReplyMessageContext reply_context(context);
  reply_context.params.set_result(pp_result);
  ppapi::PPB_X509Certificate_Fields certificate_fields;
  if (pp_result == PP_OK) {
    DCHECK(ssl_info);
    if (ssl_info->cert.get()) {
      pepper_socket_utils::GetCertificateFields(*ssl_info->cert,
                                                &certificate_fields);
    }
  }
  SendReply(reply_context,
            PpapiPluginMsg_TCPSocket_SSLHandshakeReply(certificate_fields));
}

void PepperTCPSocketMessageFilter::SendReadReply(int32_t pp_result,
                                                 const std::string& data) {
  DCHECK(pending_read_context_.is_valid());
  DCHECK_GT(pending_read_size_, 0u);

  pending_read_context_.params.set_result(pp_result);
  SendReply(pending_read_context_, PpapiPluginMsg_TCPSocket_ReadReply(data));

  pending_read_context_ = ppapi::host::ReplyMessageContext();
  pending_read_size_ = 0;
}

void PepperTCPSocketMessageFilter::SendReadError(int32_t pp_error) {
  SendReadReply(pp_error, std::string());
}

void PepperTCPSocketMessageFilter::SendWriteReply(int32_t pp_result) {
  DCHECK(pending_write_context_.is_valid());
  DCHECK(!pending_write_data_.empty());
  DCHECK(pp_result <= 0 ||
         static_cast<uint32_t>(pp_result) == pending_write_data_.size());
  DCHECK(pp_result <= 0 ||
         static_cast<uint32_t>(pp_result) == pending_write_bytes_written_);

  pending_write_context_.params.set_result(pp_result);
  SendReply(pending_write_context_, PpapiPluginMsg_TCPSocket_WriteReply());

  pending_write_context_ = ppapi::host::ReplyMessageContext();
  pending_write_data_.clear();
  pending_write_bytes_written_ = 0;
}

void PepperTCPSocketMessageFilter::SendListenReply(
    const ppapi::host::ReplyMessageContext& context,
    int32_t pp_result) {
  ppapi::host::ReplyMessageContext reply_context(context);
  reply_context.params.set_result(pp_result);
  SendReply(reply_context, PpapiPluginMsg_TCPSocket_ListenReply());
}

void PepperTCPSocketMessageFilter::SendAcceptReply(
    const ppapi::host::ReplyMessageContext& context,
    int32_t pp_result,
    int pending_host_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_TCPSocket_AcceptReply(
                               pending_host_id, local_addr, remote_addr));
}

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

void PepperTCPSocketMessageFilter::Close() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // Need to do these first, as destroying Mojo pipes may invoke some of the
  // callbacks with failure messages.
  weak_ptr_factory_.InvalidateWeakPtrs();
  state_.DoTransition(TCPSocketState::CLOSE, true);

#if defined(OS_CHROMEOS)
  // Close the firewall hole, it is no longer needed.
  firewall_hole_.reset();
#endif  // defined(OS_CHROMEOS)

  // Make sure there are no further callbacks from Mojo, which could end up in a
  // double free (Add ref on the UI thread, while a deletion is pending on the
  // IO thread), and that they're closed on the correct thread.
  bound_socket_.reset();
  connected_socket_.reset();
  tls_client_socket_.reset();
  server_socket_.reset();
  binding_.Close();
  socket_observer_binding_.Close();

  read_watcher_.reset();
  receive_stream_.reset();
  write_watcher_.reset();
  send_stream_.reset();
}

network::mojom::NetworkContext*
PepperTCPSocketMessageFilter::GetNetworkContext() const {
  if (network_context_for_testing)
    return network_context_for_testing;

  RenderProcessHost* render_process_host =
      RenderProcessHost::FromID(render_process_id_);
  if (!render_process_host)
    return nullptr;

  return render_process_host->GetStoragePartition()->GetNetworkContext();
}

template <class ReturnMessage>
base::OnceCallback<void(int result)>
PepperTCPSocketMessageFilter::CreateCompletionCallback(
    const ppapi::host::HostMessageContext* context) {
  return mojo::WrapCallbackWithDefaultInvokeIfNotRun(
      base::BindOnce(&PepperTCPSocketMessageFilter::ReturnResult<ReturnMessage>,
                     base::Unretained(this),
                     context->MakeReplyMessageContext()),
      net::ERR_FAILED);
}

template <class ReturnMessage>
void PepperTCPSocketMessageFilter::ReturnResult(
    ppapi::host::ReplyMessageContext context,
    int net_result) {
  context.params.set_result(NetErrorToPepperError(net_result));
  SendReply(context, ReturnMessage());
}

}  // namespace content
