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

// Copyright 2021 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_lan_medium.h"

#include "base/check.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "chromeos/ash/services/nearby/public/cpp/tcp_server_socket_port.h"
#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
#include "components/cross_device/nearby/nearby_features.h"
#include "net/base/net_errors.h"
#include "net/traffic_annotation/network_traffic_annotation.h"

namespace nearby {
namespace chrome {

namespace {

// The max time spent trying to connect to another device's TCP socket. We
// expect connection attempts to fail in practice, for example, when two devices
// are on different networks. We want to fail quickly so another upgrade medium
// like WebRTC can be used. The default networking stack timeout is too long; it
// can take over 2 minutes.
constexpr base::TimeDelta kConnectTimeout = base::Seconds(2);

// The max size of the server socket's queue of pending connection requests from
// remote sockets. Any additional connection requests are refused.
constexpr uint32_t kBacklog = 10;

constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
    net::DefineNetworkTrafficAnnotation("nearby_connections_wifi_lan", R"(
        semantics {
          sender: "Nearby Connections"
          description:
            "Nearby Connections provides device-to-device communication "
            "mediums such as WLAN, which is relevant here. Both devices open "
            "TCP sockets, connect to each other, and establish an encrypted "
            "and authenticated communication channel. Both devices must be "
            "using the same router or possibly access point."
          trigger:
            "User's Chromebook uses a feature such as Nearby Share or "
            "Phone Hub that leverages Nearby Connections to establish a "
            "communication channel between devices."
          data:
            "After the WLAN connection between devices is established, "
            "encrypted, and authenticated, feature-specific bytes are "
            "transferred. For example, Nearby Share might send/receive files "
            "and Phone Hub might receive message notification data from the "
            "phone."
          destination: OTHER
          destination_other:
            "Another device such as a Chromebook or Android phone."
        }
        policy {
          cookies_allowed: NO
          setting:
            "Features that use WLAN such as Nearby Share and Phone Hub can be "
            "enabled/disabled in Chromebook settings."
          policy_exception_justification:
            "The individual features that leverage Nearby Connections have "
            "their own policies associated with them."
        })");

// Nearby Connections passes service types of the form "_%s._tcp.", which each
// platform is expected to transform as needed to perform discovery. For
// ChromeOS, that looks like "_%s._tcp.local". Provide some helpers below to
// enforce the transformation.
const char kServiceTypeSuffix[] = "local";

std::string LongServiceType(const std::string service_type) {
  // Should not already have the suffix.
  auto found_index =
      service_type.find(kServiceTypeSuffix, /*pos=*/service_type.length() -
                                                strlen(kServiceTypeSuffix));
  CHECK(found_index == std::string::npos);
  return service_type + kServiceTypeSuffix;
}

std::string ShortServiceType(const std::string service_type) {
  // Should already have the suffix.
  CHECK(service_type.length() >= strlen(kServiceTypeSuffix));
  auto found_index =
      service_type.find(kServiceTypeSuffix, /*pos=*/service_type.length() -
                                                strlen(kServiceTypeSuffix));
  CHECK(found_index != std::string::npos);
  return service_type.substr(0, found_index);
}

}  // namespace

WifiLanMedium::WifiLanMedium(
    const mojo::SharedRemote<::sharing::mojom::TcpSocketFactory>&
        socket_factory,
    const mojo::SharedRemote<
        chromeos::network_config::mojom::CrosNetworkConfig>&
        cros_network_config,
    const mojo::SharedRemote<::sharing::mojom::FirewallHoleFactory>&
        firewall_hole_factory,
    const mojo::SharedRemote<::sharing::mojom::MdnsManager>& mdns_manager)
    : task_runner_(
          base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})),
      socket_factory_(socket_factory),
      cros_network_config_(cros_network_config),
      firewall_hole_factory_(firewall_hole_factory),
      mdns_manager_(mdns_manager) {
  // NOTE: We do not set the disconnect handler for the SharedRemotes here. They
  // are fundamental dependencies of the Nearby Connections process, which will
  // crash if any dependency disconnects.
  if (mdns_manager_.is_bound() && ::features::IsNearbyMdnsEnabled()) {
    mdns_manager_->AddObserver(mdns_observer_.BindNewPipeAndPassRemote());
    VLOG(1) << " Added Mdns observer.";
  }
}

WifiLanMedium::~WifiLanMedium() {
  // For thread safety, shut down on the |task_runner_|.
  base::WaitableEvent shutdown_waitable_event;
  task_runner_->PostTask(FROM_HERE, base::BindOnce(&WifiLanMedium::Shutdown,
                                                   base::Unretained(this),
                                                   &shutdown_waitable_event));
  shutdown_waitable_event.Wait();
}

bool WifiLanMedium::IsNetworkConnected() const {
  // This is not used by ChromeOS. A virtual function was created in the base
  // class to support Windows, so this implementation only exists to override
  // that function. This may be implemented correctly at a later date if
  // Wi-Fi LAN would need similar functionality.
  // Context: cl/452402734
  return true;
}

/*============================================================================*/
// Begin: ConnectToService()
/*============================================================================*/
std::unique_ptr<api::WifiLanSocket> WifiLanMedium::ConnectToService(
    const NsdServiceInfo& remote_service_info,
    CancellationFlag* cancellation_flag) {
  if (cancellation_flag && cancellation_flag->Cancelled()) {
    LOG(WARNING) << "WifiLanMedium::" << __func__
                 << ": Cancelled before connect attempt";
    return nullptr;
  }

  return ConnectToService(remote_service_info.GetIPAddress(),
                          remote_service_info.GetPort(), cancellation_flag);
}

std::unique_ptr<api::WifiLanSocket> WifiLanMedium::ConnectToService(
    const std::string& ip_address,
    int port,
    CancellationFlag* cancellation_flag) {
  net::IPAddress ip(base::as_byte_span(ip_address));
  const net::AddressList address_list =
      net::AddressList::CreateFromIPAddress(ip, port);

  // To accommodate the synchronous ConnectToService() signature, block until we
  // connect to the remote TCP server socket or fail.
  base::WaitableEvent connect_waitable_event;
  std::optional<WifiLanSocket::ConnectedSocketParameters>
      connected_socket_parameters;
  task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&WifiLanMedium::DoConnect, base::Unretained(this),
                     address_list, &connected_socket_parameters,
                     &connect_waitable_event));
  connect_waitable_event.Wait();

  if (cancellation_flag && cancellation_flag->Cancelled()) {
    LOG(WARNING) << "WifiLanMedium::" << __func__
                 << ": Cancelled during connect to "
                 << address_list.back().ToString();
    return nullptr;
  }

  bool success = connected_socket_parameters.has_value();
  if (!success) {
    LOG(WARNING) << "WifiLanMedium::" << __func__ << ": Failed to connect to "
                 << address_list.back().ToString();
    return nullptr;
  }

  return std::make_unique<WifiLanSocket>(
      std::move(*connected_socket_parameters));
}

void WifiLanMedium::DoConnect(
    const net::AddressList& address_list,
    std::optional<WifiLanSocket::ConnectedSocketParameters>*
        connected_socket_parameters,
    base::WaitableEvent* connect_waitable_event) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());

  pending_connect_waitable_events_.insert(connect_waitable_event);

  VLOG(1) << "WifiLanMedium::" << __func__ << ": Attempting to connect to "
          << address_list.back().ToString();
  mojo::PendingRemote<network::mojom::TCPConnectedSocket> tcp_connected_socket;
  mojo::PendingReceiver<network::mojom::TCPConnectedSocket> receiver =
      tcp_connected_socket.InitWithNewPipeAndPassReceiver();
  socket_factory_->CreateTCPConnectedSocket(
      /*timeout=*/kConnectTimeout,
      /*local_addr=*/std::nullopt, address_list,
      /*tcp_connected_socket_options=*/nullptr,
      net::MutableNetworkTrafficAnnotationTag(kTrafficAnnotation),
      std::move(receiver), /*observer=*/mojo::NullRemote(),
      base::BindOnce(&WifiLanMedium::OnConnect, base::Unretained(this),
                     connected_socket_parameters, connect_waitable_event,
                     std::move(tcp_connected_socket)));
}

void WifiLanMedium::OnConnect(
    std::optional<WifiLanSocket::ConnectedSocketParameters>*
        connected_socket_parameters,
    base::WaitableEvent* connect_waitable_event,
    mojo::PendingRemote<network::mojom::TCPConnectedSocket>
        tcp_connected_socket,
    int32_t result,
    const std::optional<net::IPEndPoint>& local_addr,
    const std::optional<net::IPEndPoint>& peer_addr,
    mojo::ScopedDataPipeConsumerHandle receive_stream,
    mojo::ScopedDataPipeProducerHandle send_stream) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());

  if (result != net::OK) {
    LOG(WARNING) << "WifiLanMedium::" << __func__
                 << ": Failed to create TCP connected socket. result="
                 << net::ErrorToString(result);
    FinishConnectAttempt(connect_waitable_event,
                         ConnectResult::kErrorFailedToCreateTcpSocket);
    return;
  }

  DCHECK(tcp_connected_socket);
  DCHECK(local_addr);
  DCHECK(peer_addr);
  VLOG(1) << "WifiLanMedium::" << __func__
          << ": Created TCP connected socket. local_addr="
          << local_addr->ToString() << ", peer_addr=" << peer_addr->ToString();
  *connected_socket_parameters = {std::move(tcp_connected_socket),
                                  std::move(receive_stream),
                                  std::move(send_stream)};

  FinishConnectAttempt(connect_waitable_event, ConnectResult::kSuccess);
}
/*============================================================================*/
// End: ConnectToService()
/*============================================================================*/

/*============================================================================*/
// Begin: ListenForService()
/*============================================================================*/
std::unique_ptr<api::WifiLanServerSocket> WifiLanMedium::ListenForService(
    int port) {
  // To accommodate the synchronous ListenForService() signature, block until we
  // create a server socket and start listening for connections or fail.
  base::WaitableEvent listen_waitable_event;
  std::optional<WifiLanServerSocket::ServerSocketParameters>
      server_socket_parameters;
  task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&WifiLanMedium::DoListenForService, base::Unretained(this),
                     &server_socket_parameters, &listen_waitable_event, port));
  listen_waitable_event.Wait();

  bool success = server_socket_parameters.has_value();
  if (!success) {
    LOG(WARNING) << "WifiLanMedium::" << __func__
                 << ": Failed to create server socket on port " << port;
    return nullptr;
  }

  VLOG(1) << "WifiLanMedium::" << __func__ << ": Server socket created on "
          << server_socket_parameters->local_end_point.ToString();
  return std::make_unique<WifiLanServerSocket>(
      std::move(*server_socket_parameters));
}

void WifiLanMedium::DoListenForService(
    std::optional<WifiLanServerSocket::ServerSocketParameters>*
        server_socket_parameters,
    base::WaitableEvent* listen_waitable_event,
    int port) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());

  pending_listen_waitable_events_.insert(listen_waitable_event);

  // TcpServerSocketPort enforces any necessary restrictions on port number
  // ranges. If |port| is 0, choose a random port from the acceptable range.
  std::optional<ash::nearby::TcpServerSocketPort> tcp_port =
      port == 0 ? std::make_optional<ash::nearby::TcpServerSocketPort>(
                      ash::nearby::TcpServerSocketPort::Random())
                : ash::nearby::TcpServerSocketPort::FromInt(port);
  if (!tcp_port) {
    LOG(WARNING)
        << "WifiLanMedium::" << __func__
        << ": Failed to construct a TcpServerSocketPort from port number "
        << port;
    FinishListenAttempt(listen_waitable_event, ListenResult::kErrorInvalidPort);
    return;
  }

  // Local IP fetching: Step 1) Grab the first (i.e., default) active network.
  cros_network_config_->GetNetworkStateList(
      chromeos::network_config::mojom::NetworkFilter::New(
          chromeos::network_config::mojom::FilterType::kActive,
          chromeos::network_config::mojom::NetworkType::kAll,
          /*limit=*/1),
      base::BindOnce(&WifiLanMedium::OnGetNetworkStateList,
                     base::Unretained(this), server_socket_parameters,
                     listen_waitable_event, *tcp_port));
}

void WifiLanMedium::OnGetNetworkStateList(
    std::optional<WifiLanServerSocket::ServerSocketParameters>*
        server_socket_parameters,
    base::WaitableEvent* listen_waitable_event,
    const ash::nearby::TcpServerSocketPort& port,
    std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr>
        result) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  if (result.empty()) {
    LOG(WARNING) << "WifiLanMedium::" << __func__
                 << ": Failed to get network state list. Features will likely "
                 << "check for an active network connection before deciding to "
                 << "use WifiLan as a medium. If that is the case, this "
                 << "failure is unexpected.";
    FinishListenAttempt(listen_waitable_event,
                        ListenResult::kErrorFetchIpFailedToGetNetworkStateList);
    return;
  }

  // Local IP fetching: Step 2) Look up the network properties by GUID.
  cros_network_config_->GetManagedProperties(
      result[0]->guid,
      base::BindOnce(&WifiLanMedium::OnGetNetworkProperties,
                     base::Unretained(this), server_socket_parameters,
                     listen_waitable_event, port));
}

void WifiLanMedium::OnGetNetworkProperties(
    std::optional<WifiLanServerSocket::ServerSocketParameters>*
        server_socket_parameters,
    base::WaitableEvent* listen_waitable_event,
    const ash::nearby::TcpServerSocketPort& port,
    chromeos::network_config::mojom::ManagedPropertiesPtr properties) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  if (!properties) {
    LOG(WARNING) << "WifiLanMedium::" << __func__
                 << ": Failed to get network properties.";
    FinishListenAttempt(
        listen_waitable_event,
        ListenResult::kErrorFetchIpFailedToGetManagedProperties);
    return;
  }

  if (!properties->ip_configs || properties->ip_configs->empty()) {
    LOG(WARNING) << "WifiLanMedium::" << __func__ << ": IP configs are empty.";
    FinishListenAttempt(listen_waitable_event,
                        ListenResult::kErrorFetchIpMissingIpConfigs);
    return;
  }

  // Local IP fetching: Step 3) Take the first valid IPv4 address.
  std::optional<net::IPAddress> ip_address;
  for (const auto& ip_config : *properties->ip_configs) {
    if (!ip_config->ip_address)
      continue;

    net::IPAddress ip;
    if (!ip.AssignFromIPLiteral(*ip_config->ip_address))
      continue;

    if (!ip.IsValid() || !ip.IsIPv4() || ip.IsLoopback() || ip.IsZero())
      continue;

    ip_address = ip;
    break;
  }

  if (!ip_address) {
    LOG(WARNING)
        << "WifiLanMedium::" << __func__
        << ": Failed to get local IPv4 address. This is likely unexpected "
        << "unless an IPv6-only network is used, for instance.";
    FinishListenAttempt(listen_waitable_event,
                        ListenResult::kErrorFetchIpNoValidLocalIpAddress);
    return;
  }

  VLOG(1) << "WifiLanMedium::" << __func__
          << ": Fetched local IP address. ip_address="
          << ip_address->ToString();
  mojo::PendingRemote<network::mojom::TCPServerSocket> tcp_server_socket;
  auto receiver = tcp_server_socket.InitWithNewPipeAndPassReceiver();
  socket_factory_->CreateTCPServerSocket(
      *ip_address, port, kBacklog,
      net::MutableNetworkTrafficAnnotationTag(kTrafficAnnotation),
      std::move(receiver),
      base::BindOnce(&WifiLanMedium::OnTcpServerSocketCreated,
                     base::Unretained(this), server_socket_parameters,
                     listen_waitable_event, std::move(tcp_server_socket),
                     *ip_address, port));
}

void WifiLanMedium::OnTcpServerSocketCreated(
    std::optional<WifiLanServerSocket::ServerSocketParameters>*
        server_socket_parameters,
    base::WaitableEvent* listen_waitable_event,
    mojo::PendingRemote<network::mojom::TCPServerSocket> tcp_server_socket,
    const net::IPAddress& ip_address,
    const ash::nearby::TcpServerSocketPort& port,
    int32_t result,
    const std::optional<net::IPEndPoint>& local_addr) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  if (result != net::OK) {
    LOG(WARNING) << "WifiLanMedium::" << __func__
                 << ": Failed to create TCP server socket. result="
                 << net::ErrorToString(result);
    FinishListenAttempt(listen_waitable_event,
                        ListenResult::kErrorFailedToCreateTcpServerSocket);
    return;
  }

  DCHECK(tcp_server_socket);
  DCHECK(local_addr);
  VLOG(1) << "WifiLanMedium::" << __func__
          << ": Created TCP server socket. local_addr="
          << local_addr->ToString();

  if (local_addr->address() != ip_address ||
      local_addr->port() != port.port()) {
    LOG(WARNING) << "WifiLanMedium::" << __func__
                 << ": TCP server socket's IP:port disagrees with "
                    "input values. in="
                 << ip_address.ToString() << ":" << port.port()
                 << ", out=" << local_addr->ToString();
    FinishListenAttempt(
        listen_waitable_event,
        ListenResult::kErrorUnexpectedTcpServerSocketIpEndpoint);
    return;
  }

  firewall_hole_factory_->OpenFirewallHole(
      port, base::BindOnce(&WifiLanMedium::OnFirewallHoleCreated,
                           base::Unretained(this), server_socket_parameters,
                           listen_waitable_event, std::move(tcp_server_socket),
                           *local_addr));
}

void WifiLanMedium::OnFirewallHoleCreated(
    std::optional<WifiLanServerSocket::ServerSocketParameters>*
        server_socket_parameters,
    base::WaitableEvent* listen_waitable_event,
    mojo::PendingRemote<network::mojom::TCPServerSocket> tcp_server_socket,
    const net::IPEndPoint& local_addr,
    mojo::PendingRemote<::sharing::mojom::FirewallHole> firewall_hole) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());

  if (!firewall_hole) {
    LOG(WARNING) << "WifiLanMedium::" << __func__
                 << ": Failed to create firewall hole. local_addr="
                 << local_addr.ToString();
    FinishListenAttempt(listen_waitable_event,
                        ListenResult::kErrorFailedToCreateFirewallHole);
    return;
  }

  VLOG(1) << "WifiLanMedium::" << __func__
          << ": Created firewall hole. local_addr=" << local_addr.ToString();
  *server_socket_parameters = {local_addr, std::move(tcp_server_socket),
                               std::move(firewall_hole)};

  FinishListenAttempt(listen_waitable_event, ListenResult::kSuccess);
}
/*============================================================================*/
// End: ListenForService()
/*============================================================================*/

/*============================================================================*/
// Begin: StartDiscovery()
/*============================================================================*/
bool WifiLanMedium::StartDiscovery(const std::string& service_type,
                                   DiscoveredServiceCallback callback) {
  VLOG(1) << " Starting mDNS discovery.";

  // This is expected to happen when the feature flag is false.
  if (!mdns_observer_.is_bound()) {
    VLOG(1) << " Cannot start mDNS discovery while observers unbound.";
    return false;
  }

  bool success = false;
  // MdnsManager expects the longer service type with suffix.
  mdns_manager_->StartDiscoverySession(LongServiceType(service_type), &success);
  if (success) {
    discovery_callbacks_[LongServiceType(service_type)] = std::move(callback);
  }

  VLOG(1) << " Start mDNS discovery for service type " << service_type
          << (success ? " succeeded." : " failed.");

  return success;
}

bool WifiLanMedium::StopDiscovery(const std::string& service_type) {
  auto discovery_callback =
      discovery_callbacks_.find(LongServiceType(service_type));
  if (discovery_callback == discovery_callbacks_.end()) {
    VLOG(1) << " Can't stop discovery for service_type we weren't discovering.";
    return false;
  }

  bool success = false;
  // MdnsManager expects the longer service type with suffix.
  mdns_manager_->StopDiscoverySession(LongServiceType(service_type), &success);
  if (success) {
    discovery_callbacks_.erase(discovery_callback);
  }

  VLOG(1) << " Stop mDNS discovery for service type " << service_type
          << (success ? " succeeded." : " failed.");

  return success;
}

void WifiLanMedium::ServiceFound(
    sharing::mojom::NsdServiceInfoPtr service_info) {
  auto discovery_callback =
      discovery_callbacks_.find(service_info->service_type);
  if (discovery_callback == discovery_callbacks_.end()) {
    VLOG(1) << " Ignoring found service while not discovering.";
    return;
  }

  NsdServiceInfo found_service_info;
  found_service_info.SetServiceName(service_info->service_name);
  // A found service that has an active discovery callback must
  // have the service type suffix.
  found_service_info.SetServiceType(
      ShortServiceType(service_info->service_type));
  if (service_info->ip_address.has_value()) {
    found_service_info.SetIPAddress(service_info->ip_address.value());
  }
  if (service_info->port.has_value()) {
    found_service_info.SetPort(service_info->port.value());
  }
  if (service_info->txt_records.has_value()) {
    for (auto& entry : *service_info->txt_records) {
      found_service_info.SetTxtRecord(entry.first, entry.second);
    }
  }

  VLOG(1) << " Announcing service found for service: "
          << service_info->service_name;
  discovery_callback->second.service_discovered_cb(found_service_info);
}

void WifiLanMedium::ServiceLost(
    sharing::mojom::NsdServiceInfoPtr service_info) {
  auto discovery_callback =
      discovery_callbacks_.find(service_info->service_type);
  if (discovery_callback == discovery_callbacks_.end()) {
    VLOG(1) << " Ignoring found service while not discovering.";
    return;
  }

  VLOG(1) << " Announcing service lost for service: "
          << service_info->service_name;
  NsdServiceInfo lost_service_info;
  lost_service_info.SetServiceName(service_info->service_name);
  // A lost service that has an active discovery callback must
  // have the service type suffix.
  lost_service_info.SetServiceType(
      ShortServiceType(service_info->service_type));

  discovery_callback->second.service_lost_cb(lost_service_info);
}

/*============================================================================*/
// End: StartDiscovery()
/*============================================================================*/

std::optional<std::pair<std::int32_t, std::int32_t>>
WifiLanMedium::GetDynamicPortRange() {
  return std::pair<std::int32_t, std::int32_t>(
      ash::nearby::TcpServerSocketPort::kMin,
      ash::nearby::TcpServerSocketPort::kMax);
}

/*============================================================================*/
// Begin: Not implemented
/*============================================================================*/
bool WifiLanMedium::StartAdvertising(const NsdServiceInfo& nsd_service_info) {
  NOTIMPLEMENTED();
  return false;
}
bool WifiLanMedium::StopAdvertising(const NsdServiceInfo& nsd_service_info) {
  NOTIMPLEMENTED();
  return false;
}
/*============================================================================*/
// End: Not implemented
/*============================================================================*/

void WifiLanMedium::FinishConnectAttempt(base::WaitableEvent* event,
                                         ConnectResult result) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  auto it = pending_connect_waitable_events_.find(event);
  if (it == pending_connect_waitable_events_.end())
    return;

  base::UmaHistogramEnumeration("Nearby.Connections.WifiLan.ConnectResult",
                                result);

  base::WaitableEvent* event_copy = *it;
  pending_connect_waitable_events_.erase(it);
  event_copy->Signal();
}

void WifiLanMedium::FinishListenAttempt(base::WaitableEvent* event,
                                        ListenResult result) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());
  auto it = pending_listen_waitable_events_.find(event);
  if (it == pending_listen_waitable_events_.end())
    return;

  base::UmaHistogramEnumeration("Nearby.Connections.WifiLan.ListenResult",
                                result);

  base::WaitableEvent* event_copy = *it;
  pending_listen_waitable_events_.erase(it);
  event_copy->Signal();
}

void WifiLanMedium::Shutdown(base::WaitableEvent* shutdown_waitable_event) {
  DCHECK(task_runner_->RunsTasksInCurrentSequence());

  // Note that resetting the Remote will cancel any pending callbacks, including
  // those already in the task queue.
  VLOG(1) << "WifiLanMedium::" << __func__
          << ": Closing TcpSocketFactory, CrosNetworkConfig, and "
             "FirewallHoleFactory mojo SharedRemotes.";
  socket_factory_.reset();
  cros_network_config_.reset();
  firewall_hole_factory_.reset();
  discovery_callbacks_.clear();

  // Cancel all pending connect/listen calls. This is thread safe because all
  // changes to the pending-event sets are sequenced. Make a copy of the events
  // because elements will be removed from the sets during iteration.
  if (!pending_connect_waitable_events_.empty()) {
    VLOG(1) << "WifiLanMedium::" << __func__ << ": Canceling "
            << pending_connect_waitable_events_.size()
            << " pending ConnectToService() calls.";
  }
  auto pending_connect_waitable_events_copy = pending_connect_waitable_events_;
  for (base::WaitableEvent* event : pending_connect_waitable_events_copy) {
    FinishConnectAttempt(event, ConnectResult::kCanceled);
  }
  if (!pending_listen_waitable_events_.empty()) {
    VLOG(1) << "WifiLanMedium::" << __func__ << ": Canceling "
            << pending_listen_waitable_events_.size()
            << " pending ListenForService() calls.";
  }
  auto pending_listen_waitable_events_copy = pending_listen_waitable_events_;
  for (base::WaitableEvent* event : pending_listen_waitable_events_copy) {
    FinishListenAttempt(event, ListenResult::kCanceled);
  }

  shutdown_waitable_event->Signal();
}

}  // namespace chrome
}  // namespace nearby