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

#include "services/network/socket_factory.h"

#include <memory>
#include <optional>
#include <string>
#include <utility>

#include "mojo/public/cpp/bindings/type_converter.h"
#include "net/base/completion_once_callback.h"
#include "net/base/net_errors.h"
#include "net/cert/cert_verifier.h"
#include "net/cert/multi_log_ct_verifier.h"
#include "net/http/http_network_session.h"
#include "net/log/net_log.h"
#include "net/socket/client_socket_factory.h"
#include "net/socket/client_socket_handle.h"
#include "net/socket/tcp_server_socket.h"
#include "net/ssl/ssl_config.h"
#include "net/ssl/ssl_config_service.h"
#include "net/url_request/url_request_context.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/simple_host_resolver.h"
#include "services/network/restricted_udp_socket.h"
#include "services/network/tls_client_socket.h"
#include "services/network/udp_socket.h"

namespace network {

SocketFactory::SocketFactory(net::NetLog* net_log,
                             net::URLRequestContext* url_request_context)
    : net_log_(net_log),
      client_socket_factory_(nullptr),
      tls_socket_factory_(url_request_context) {
  if (url_request_context->GetNetworkSessionContext()) {
    client_socket_factory_ =
        url_request_context->GetNetworkSessionContext()->client_socket_factory;
  }
  if (!client_socket_factory_) {
    client_socket_factory_ = net::ClientSocketFactory::GetDefaultFactory();
  }
}

SocketFactory::~SocketFactory() = default;

void SocketFactory::CreateUDPSocket(
    mojo::PendingReceiver<mojom::UDPSocket> receiver,
    mojo::PendingRemote<mojom::UDPSocketListener> listener) {
  udp_socket_receivers_.Add(
      std::make_unique<UDPSocket>(std::move(listener), net_log_),
      std::move(receiver));
}

void SocketFactory::CreateRestrictedUDPSocket(
    const net::IPEndPoint& addr,
    mojom::RestrictedUDPSocketMode mode,
    const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
    mojom::RestrictedUDPSocketParamsPtr params,
    mojo::PendingReceiver<mojom::RestrictedUDPSocket> receiver,
    mojo::PendingRemote<mojom::UDPSocketListener> listener,
    std::unique_ptr<SimpleHostResolver> resolver,
    bool allow_multicast,
    bool allow_source_specific_multicast,
    mojom::NetworkContext::CreateRestrictedUDPSocketCallback callback) {
  auto udp_socket = std::make_unique<UDPSocket>(std::move(listener), net_log_);
  switch (mode) {
    case mojom::RestrictedUDPSocketMode::BOUND:
      udp_socket->Bind(addr, /*options=*/
                       params ? std::move(params->socket_options) : nullptr,
                       std::move(callback));
      break;
    case mojom::RestrictedUDPSocketMode::CONNECTED:
      if (base::FeatureList::IsEnabled(
              features::
                  kDirectSocketsUdpSendRequireMulticastPermissionPolicy)) {
        // Checked in DirectSocketsServiceImpl::OnResolveCompleteForUDPSocket.
        CHECK(allow_multicast || !addr.address().IsMulticast());
      }
      udp_socket->Connect(addr, /*options=*/
                          params ? std::move(params->socket_options) : nullptr,
                          std::move(callback));
      break;
  }
  auto restricted_udp_socket = std::make_unique<RestrictedUDPSocket>(
      std::move(udp_socket), traffic_annotation, std::move(resolver),
      allow_multicast, allow_source_specific_multicast);
#if BUILDFLAG(IS_CHROMEOS)
  if (params && params->connection_tracker) {
    restricted_udp_socket->AttachConnectionTracker(
        std::move(params->connection_tracker));
  }
#endif  // BUILDFLAG(IS_CHROMEOS)
  restricted_udp_socket_receivers_.Add(std::move(restricted_udp_socket),
                                       std::move(receiver));
}

void SocketFactory::CreateTCPServerSocket(
    const net::IPEndPoint& local_addr,
    mojom::TCPServerSocketOptionsPtr options,
    const net::NetworkTrafficAnnotationTag& traffic_annotation,
    mojo::PendingReceiver<mojom::TCPServerSocket> receiver,
    mojom::NetworkContext::CreateTCPServerSocketCallback callback) {
#if BUILDFLAG(IS_WIN)
  if (socket_broker_client_) {
    socket_broker_client_->CreateTcpSocket(
        local_addr.GetFamily(),
        base::BindOnce(&SocketFactory::DidCompleteCreate,
                       weak_ptr_factory_.GetWeakPtr(), local_addr,
                       std::move(options), traffic_annotation,
                       std::move(receiver), std::move(callback)));
    return;
  }
#endif
  auto socket =
      std::make_unique<TCPServerSocket>(this, net_log_, traffic_annotation);
  CreateTCPServerSocketHelper(std::move(socket), local_addr, std::move(options),
                              traffic_annotation, std::move(receiver),
                              std::move(callback));
}

#if BUILDFLAG(IS_WIN)
void SocketFactory::DidCompleteCreate(
    const net::IPEndPoint& local_addr,
    mojom::TCPServerSocketOptionsPtr options,
    const net::NetworkTrafficAnnotationTag& traffic_annotation,
    mojo::PendingReceiver<mojom::TCPServerSocket> receiver,
    mojom::NetworkContext::CreateTCPServerSocketCallback callback,
    network::TransferableSocket socket,
    int result) {
  if (result != net::OK) {
    std::move(callback).Run(result, std::nullopt);
    return;
  }
  auto tcp_socket =
      std::make_unique<net::TCPServerSocket>(net_log_, net::NetLogSource());
  tcp_socket->AdoptSocket(socket.TakeSocket());

  auto tcp_server_socket = std::make_unique<TCPServerSocket>(
      std::move(tcp_socket), 0, this, traffic_annotation);

  CreateTCPServerSocketHelper(std::move(tcp_server_socket), local_addr,
                              std::move(options), traffic_annotation,
                              std::move(receiver), std::move(callback));
}

void SocketFactory::BindSocketBroker(
    mojo::PendingRemote<mojom::SocketBroker> pending_remote) {
  socket_broker_client_.emplace(std::move(pending_remote));
}
#endif

void SocketFactory::CreateTCPServerSocketHelper(
    std::unique_ptr<TCPServerSocket> socket,
    const net::IPEndPoint& local_addr,
    mojom::TCPServerSocketOptionsPtr options,
    const net::NetworkTrafficAnnotationTag& traffic_annotation,
    mojo::PendingReceiver<mojom::TCPServerSocket> receiver,
    mojom::NetworkContext::CreateTCPServerSocketCallback callback) {
#if BUILDFLAG(IS_CHROMEOS)
  if (options->connection_tracker) {
    socket->AttachConnectionTracker(std::move(options->connection_tracker));
  }
#endif  // BUILDFLAG(IS_CHROMEOS)
  base::expected<net::IPEndPoint, int32_t> result =
      socket->Listen(local_addr, options->backlog, options->ipv6_only);
  if (!result.has_value()) {
    std::move(callback).Run(result.error(), std::nullopt);
    return;
  }
  tcp_server_socket_receivers_.Add(std::move(socket), std::move(receiver));
  std::move(callback).Run(net::OK, result.value());
}

void SocketFactory::CreateTCPConnectedSocket(
    const std::optional<net::IPEndPoint>& local_addr,
    const net::AddressList& remote_addr_list,
    mojom::TCPConnectedSocketOptionsPtr tcp_connected_socket_options,
    const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
    mojo::PendingReceiver<mojom::TCPConnectedSocket> receiver,
    mojo::PendingRemote<mojom::SocketObserver> observer,
    mojom::SocketFactory::CreateTCPConnectedSocketCallback callback) {
  auto socket = std::make_unique<TCPConnectedSocket>(
      std::move(observer), net_log_, &tls_socket_factory_,
      client_socket_factory_,
      static_cast<net::NetworkTrafficAnnotationTag>(traffic_annotation));
  TCPConnectedSocket* socket_raw = socket.get();
  tcp_connected_socket_receiver_.Add(std::move(socket), std::move(receiver));
  socket_raw->Connect(local_addr, remote_addr_list,
                      std::move(tcp_connected_socket_options),
                      std::move(callback));
}

void SocketFactory::CreateTCPBoundSocket(
    const net::IPEndPoint& local_addr,
    const net::NetworkTrafficAnnotationTag& traffic_annotation,
    mojo::PendingReceiver<mojom::TCPBoundSocket> receiver,
    mojom::NetworkContext::CreateTCPBoundSocketCallback callback) {
  auto socket =
      std::make_unique<TCPBoundSocket>(this, net_log_, traffic_annotation);
  net::IPEndPoint local_addr_out;
  int result = socket->Bind(local_addr, &local_addr_out);
  if (result != net::OK) {
    std::move(callback).Run(result, std::nullopt);
    return;
  }
  TCPBoundSocket* socket_ptr = socket.get();
  socket_ptr->set_id(
      tcp_bound_socket_receivers_.Add(std::move(socket), std::move(receiver)));
  std::move(callback).Run(result, local_addr_out);
}

void SocketFactory::DestroyBoundSocket(mojo::ReceiverId bound_socket_id) {
  tcp_bound_socket_receivers_.Remove(bound_socket_id);
}

void SocketFactory::OnBoundSocketListening(
    mojo::ReceiverId bound_socket_id,
    std::unique_ptr<TCPServerSocket> server_socket,
    mojo::PendingReceiver<mojom::TCPServerSocket> server_socket_receiver) {
  tcp_server_socket_receivers_.Add(std::move(server_socket),
                                   std::move(server_socket_receiver));
  tcp_bound_socket_receivers_.Remove(bound_socket_id);
}

void SocketFactory::OnBoundSocketConnected(
    mojo::ReceiverId bound_socket_id,
    std::unique_ptr<TCPConnectedSocket> connected_socket,
    mojo::PendingReceiver<mojom::TCPConnectedSocket>
        connected_socket_receiver) {
  tcp_connected_socket_receiver_.Add(std::move(connected_socket),
                                     std::move(connected_socket_receiver));
  tcp_bound_socket_receivers_.Remove(bound_socket_id);
}

void SocketFactory::OnAccept(
    std::unique_ptr<TCPConnectedSocket> socket,
    mojo::PendingReceiver<mojom::TCPConnectedSocket> receiver) {
  tcp_connected_socket_receiver_.Add(std::move(socket), std::move(receiver));
}

}  // namespace network
