chromium/chrome/browser/ash/secure_channel/nearby_endpoint_finder_impl.cc

// Copyright 2020 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/browser/ash/secure_channel/nearby_endpoint_finder_impl.h"

#include "base/base64.h"
#include "base/memory/ptr_util.h"
#include "base/rand_util.h"
#include "chrome/browser/ash/secure_channel/util/histogram_util.h"
#include "chromeos/ash/components/multidevice/logging/logging.h"
#include "chromeos/ash/services/secure_channel/public/mojom/nearby_connector.mojom.h"

namespace ash {
namespace secure_channel {
namespace {

using ::nearby::connections::mojom::DiscoveredEndpointInfoPtr;
using ::nearby::connections::mojom::DiscoveryOptions;
using ::nearby::connections::mojom::MediumSelection;
using ::nearby::connections::mojom::Status;
using ::nearby::connections::mojom::Strategy;

NearbyEndpointFinderImpl::Factory* g_test_factory = nullptr;

const size_t kEndpointIdLength = 4u;
const size_t kEndpointInfoLength = 4u;

void OnStopDiscoveryDestructorResult(Status status) {
  util::RecordStopDiscoveryResult(status);

  if (status != Status::kSuccess)
    PA_LOG(WARNING) << "Failed to stop discovery as part of destructor";
}

std::string GenerateEndpointId() {
  // Generate a random array of bytes; as long as it is of size of at least
  // 3/4 kEndpointInfoLength, the final substring of the Base64-encoded array
  // will be of size kEndpointInfoLength.
  std::vector<uint8_t> raw_endpoint_info =
      base::RandBytesAsVector(kEndpointIdLength);

  // Return the first kEndpointIdLength characters of the Base64-encoded string.
  return base::Base64Encode(raw_endpoint_info).substr(0, kEndpointIdLength);
}

std::vector<uint8_t> GenerateEndpointInfo(const std::vector<uint8_t>& eid) {
  if (eid.size() < 2) {
    return base::RandBytesAsVector(kEndpointInfoLength);
  }

  std::vector<uint8_t> endpoint_info = {
      // version number
      1,
      // 2 bytes indicating the EID
      eid[0],
      eid[1],
  };

  return endpoint_info;
}

}  // namespace

// static
std::unique_ptr<NearbyEndpointFinder> NearbyEndpointFinderImpl::Factory::Create(
    const mojo::SharedRemote<::nearby::connections::mojom::NearbyConnections>&
        nearby_connections) {
  if (g_test_factory)
    return g_test_factory->CreateInstance(nearby_connections);

  return base::WrapUnique(new NearbyEndpointFinderImpl(nearby_connections));
}

// static
void NearbyEndpointFinderImpl::Factory::SetFactoryForTesting(
    Factory* test_factory) {
  g_test_factory = test_factory;
}

NearbyEndpointFinderImpl::NearbyEndpointFinderImpl(
    const mojo::SharedRemote<::nearby::connections::mojom::NearbyConnections>&
        nearby_connections)
    : nearby_connections_(nearby_connections),
      endpoint_id_(GenerateEndpointId()) {}

NearbyEndpointFinderImpl::~NearbyEndpointFinderImpl() {
  if (is_discovery_active_) {
    nearby_connections_->StopDiscovery(
        mojom::kServiceId, base::BindOnce(&OnStopDiscoveryDestructorResult));
  }
}

void NearbyEndpointFinderImpl::PerformFindEndpoint() {
  is_discovery_active_ = true;
  endpoint_info_ = GenerateEndpointInfo(eid());
  nearby_connections_->StartDiscovery(
      mojom::kServiceId,
      DiscoveryOptions::New(Strategy::kP2pPointToPoint,
                            MediumSelection::New(/*bluetooth=*/true,
                                                 /*ble=*/false,
                                                 /*webrtc=*/false,
                                                 /*wifi_lan=*/false,
                                                 /*wifi_direct=*/false),
                            /*fast_advertisement_service_uuid=*/std::nullopt,
                            /*is_out_of_band_connection=*/true),
      endpoint_discovery_listener_receiver_.BindNewPipeAndPassRemote(),
      base::BindOnce(&NearbyEndpointFinderImpl::OnStartDiscoveryResult,
                     weak_ptr_factory_.GetWeakPtr()));
}

void NearbyEndpointFinderImpl::OnEndpointFound(const std::string& endpoint_id,
                                               DiscoveredEndpointInfoPtr info) {
  // Only look for endpoints whose endpoint metadata field matches the
  // parameters passed to the InjectEndpoint() call.
  if (endpoint_id_ != endpoint_id || endpoint_info_ != info->endpoint_info)
    return;

  PA_LOG(VERBOSE) << "Found endpoint with ID " << endpoint_id_
                  << ", stopping discovery";
  nearby_connections_->StopDiscovery(
      mojom::kServiceId,
      base::BindOnce(&NearbyEndpointFinderImpl::OnStopDiscoveryResult,
                     weak_ptr_factory_.GetWeakPtr(), std::move(info)));
}

void NearbyEndpointFinderImpl::OnStartDiscoveryResult(Status status) {
  util::RecordStartDiscoveryResult(status);

  if (status != Status::kSuccess) {
    PA_LOG(WARNING) << "Failed to start Nearby discovery: " << status;
    is_discovery_active_ = false;
    NotifyEndpointDiscoveryFailure(status);
    return;
  }

  PA_LOG(VERBOSE) << "Started Nearby discovery";

  nearby_connections_->InjectBluetoothEndpoint(
      mojom::kServiceId, endpoint_id_, endpoint_info_,
      remote_device_bluetooth_address(),
      base::BindOnce(&NearbyEndpointFinderImpl::OnInjectBluetoothEndpointResult,
                     weak_ptr_factory_.GetWeakPtr()));
}

void NearbyEndpointFinderImpl::OnInjectBluetoothEndpointResult(Status status) {
  util::RecordInjectEndpointResult(status);

  if (status != Status::kSuccess) {
    PA_LOG(WARNING) << "Failed to inject Bluetooth endpoint: " << status;
    NotifyEndpointDiscoveryFailure(status);
    return;
  }

  PA_LOG(VERBOSE) << "Injected Bluetooth endpoint";
}

void NearbyEndpointFinderImpl::OnStopDiscoveryResult(
    ::nearby::connections::mojom::DiscoveredEndpointInfoPtr info,
    Status status) {
  util::RecordStopDiscoveryResult(status);

  is_discovery_active_ = false;

  if (status != Status::kSuccess) {
    PA_LOG(WARNING) << "Failed to stop Nearby discovery: " << status;
    NotifyEndpointDiscoveryFailure(status);
    return;
  }

  NotifyEndpointFound(endpoint_id_, std::move(info));
}

}  // namespace secure_channel
}  // namespace ash