chromium/chrome/services/sharing/nearby/platform/wifi_direct_server_socket.cc

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

#include "chrome/services/sharing/nearby/platform/wifi_direct_server_socket.h"

#include "base/metrics/histogram_functions.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/services/sharing/nearby/platform/wifi_direct_socket.h"
#include "net/socket/tcp_client_socket.h"

namespace nearby::chrome {

WifiDirectServerSocket::WifiDirectServerSocket(
    scoped_refptr<base::SequencedTaskRunner> task_runner,
    mojo::PlatformHandle handle,
    mojo::PendingRemote<sharing::mojom::FirewallHole> firewall_hole,
    std::unique_ptr<net::TCPServerSocket> tcp_server_socket)
    : task_runner_(std::move(task_runner)),
      handle_(std::move(handle)),
      firewall_hole_(std::move(firewall_hole)),
      tcp_server_socket_(std::move(tcp_server_socket)) {
  CHECK(task_runner_);
  firewall_hole_.set_disconnect_handler(
      base::BindOnce(&WifiDirectServerSocket::OnFirewallHoleDisconnect,
                     base::Unretained(this)),
      task_runner_);
}

WifiDirectServerSocket::~WifiDirectServerSocket() {
  Close();
}

// api::WifiDirectServerSocket
std::string WifiDirectServerSocket::GetIPAddress() const {
  CHECK(tcp_server_socket_);
  net::IPEndPoint address;
  tcp_server_socket_->GetLocalAddress(&address);
  return address.address().ToString();
}

int WifiDirectServerSocket::GetPort() const {
  CHECK(tcp_server_socket_);
  net::IPEndPoint address;
  tcp_server_socket_->GetLocalAddress(&address);
  return address.port();
}

std::unique_ptr<api::WifiDirectSocket> WifiDirectServerSocket::Accept() {
  // Ensure that the socket has not been closed.
  if (!tcp_server_socket_) {
    base::UmaHistogramBoolean(
        "Nearby.Connections.WifiDirect.ServerSocket.Accept.Result", false);
    base::UmaHistogramEnumeration(
        "Nearby.Connections.WifiDirect.ServerSocket.Accept.Error",
        WifiDirectServerSocketError::kSocketClosed);
    return nullptr;
  }

  bool success = false;
  net::IPEndPoint accepted_address;
  std::unique_ptr<net::StreamSocket> accepted_socket;
  pending_accept_event_.Reset();
  task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&WifiDirectServerSocket::DoAccept, base::Unretained(this),
                     &accepted_address, &accepted_socket, &success));
  pending_accept_event_.Wait();

  base::UmaHistogramBoolean(
      "Nearby.Connections.WifiDirect.ServerSocket.Accept.Result", success);
  if (!success) {
    return nullptr;
  }

  return std::make_unique<WifiDirectSocket>(task_runner_,
                                            std::move(accepted_socket));
}

Exception WifiDirectServerSocket::Close() {
  handle_.reset();

  if (!tcp_server_socket_) {
    return {Exception::kFailed};
  }

  // Cancel the pending `Accept` call, if it exists.
  pending_accept_event_.Signal();

  // Directly call `CloseSocket` if the current sequence is on the appriroiate
  // task runner.
  if (task_runner_->RunsTasksInCurrentSequence()) {
    CloseSocket(nullptr);
    return {Exception::kSuccess};
  }

  // Cleanup the socket on the IO thread.
  base::WaitableEvent waitable_event;
  task_runner_->PostTask(
      FROM_HERE, base::BindOnce(&WifiDirectServerSocket::CloseSocket,
                                base::Unretained(this), &waitable_event));
  waitable_event.Wait();

  return {Exception::kSuccess};
}

void WifiDirectServerSocket::DoAccept(
    net::IPEndPoint* accepted_address,
    std::unique_ptr<net::StreamSocket>* accepted_socket,
    bool* did_succeed) {
  // Ensure that the Accept call has not been cancelled.
  if (pending_accept_event_.IsSignaled()) {
    return;
  }

  // We cannot accept connections if the firewall hole has disconnected.
  if (!firewall_hole_ || !firewall_hole_.is_bound()) {
    // The firewall hole disconnect handler will close the socket, which will in
    // turn trigger this callback. It is important to check the firewall hole
    // availability before checking the socket availability, otherwise this
    // error state will never trigger.
    base::UmaHistogramEnumeration(
        "Nearby.Connections.WifiDirect.ServerSocket.Accept.Error",
        WifiDirectServerSocketError::kFirewallHoleDisconnected);
    pending_accept_event_.Signal();
    return;
  }

  // Ensure that the socket has not been closed.
  if (!tcp_server_socket_) {
    base::UmaHistogramEnumeration(
        "Nearby.Connections.WifiDirect.ServerSocket.Accept.Error",
        WifiDirectServerSocketError::kSocketClosed);
    pending_accept_event_.Signal();
    return;
  }

  int result = tcp_server_socket_->Accept(
      accepted_socket,
      base::BindOnce(&WifiDirectServerSocket::OnAccept, base::Unretained(this),
                     did_succeed),
      accepted_address);
  // In the case where there was not a pending connection request, this call
  // will return `ERR_IO_PENDING`. This means the provided callback will be
  // called with the result, when it occurs. In all other cases, we have the
  // result immediately and should manually propagate this to the callback.
  if (result != net::ERR_IO_PENDING) {
    OnAccept(did_succeed, result);
  }
}

void WifiDirectServerSocket::OnAccept(bool* did_succeed, int result) {
  // Ensure that the Accept call has not been cancelled.
  if (pending_accept_event_.IsSignaled()) {
    return;
  }

  *did_succeed = (result == net::OK);
  if (!did_succeed) {
    base::UmaHistogramEnumeration(
        "Nearby.Connections.WifiDirect.ServerSocket.Accept.Error",
        WifiDirectServerSocketError::kSocketFailure);
  }
  pending_accept_event_.Signal();
}

void WifiDirectServerSocket::CloseSocket(
    base::WaitableEvent* close_waitable_event) {
  tcp_server_socket_.reset();
  if (close_waitable_event) {
    close_waitable_event->Signal();
  }
}

void WifiDirectServerSocket::OnFirewallHoleDisconnect() {
  // Reset the remote to indicate that it has been disconnected.
  firewall_hole_.reset();

  // Close the socket so that Nearby Connections knows that there will not be
  // any new incoming connections.
  // Note: This is a callback that is bound to the provided `task_runner`, which
  // is the appropriate place to destroy the stored socket,
  CloseSocket(nullptr);
}

}  // namespace nearby::chrome