chromium/device/fido/cable/fido_cable_discovery_unittest.cc

// 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 "device/fido/cable/fido_cable_discovery.h"

#include <algorithm>
#include <memory>
#include <string_view>
#include <utility>

#include "base/containers/contains.h"
#include "base/containers/span.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/bluetooth_advertisement.h"
#include "device/bluetooth/test/bluetooth_test.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/fido/cable/fido_ble_uuids.h"
#include "device/fido/cable/fido_cable_device.h"
#include "device/fido/cable/fido_cable_handshake_handler.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/mock_fido_discovery_observer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "base/test/scoped_feature_list.h"
#include "device/bluetooth/floss/floss_features.h"
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chromeos/startup/browser_init_params.h"
#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)

#if BUILDFLAG(IS_MAC)
#include "device/fido/mac/util.h"
#endif  //  BUILDFLAG(IS_MAC)

_;
NiceMock;
Sequence;

namespace device {

namespace {

constexpr auto kTestCableVersion =;
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
constexpr auto kTestCableVersionNumber =;
#endif

// Constants required for discovering and constructing a Cable device that
// are given by the relying party via an extension.
constexpr CableEidArray kClientEid =;

constexpr char kUuidFormattedClientEid[] =;

constexpr CableEidArray kAuthenticatorEid =;

constexpr CableEidArray kInvalidAuthenticatorEid =;

constexpr CableSessionPreKeyArray kTestSessionPreKey =;

// TODO(crbug.com/40573698): Add support for multiple EIDs on Windows.
#if !BUILDFLAG(IS_WIN)
constexpr CableEidArray kSecondaryClientEid =;

constexpr char kUuidFormattedSecondaryClientEid[] =;

constexpr CableEidArray kSecondaryAuthenticatorEid =;

constexpr CableSessionPreKeyArray kSecondarySessionPreKey =;
#endif  // !BUILDFLAG(IS_WIN)

// Below constants are used to construct MockBluetoothDevice for testing.
constexpr char kTestBleDeviceAddress[] =;

constexpr char kTestBleDeviceName[] =;

std::unique_ptr<MockBluetoothDevice> CreateTestBluetoothDevice() {}

ACTION_P(ReturnFromAsyncCall, closure) {}

// Matcher to compare the content of advertisement data received from the
// client.
MATCHER_P2(IsAdvertisementContent,
           expected_client_eid,
           expected_uuid_formatted_client_eid,
           "") {}

class CableMockBluetoothAdvertisement : public BluetoothAdvertisement {};

// Mock BLE adapter that abstracts out authenticator logic with the following
// logic:
//  - Responds to BluetoothAdapter::RegisterAdvertisement() by always invoking
//    success callback.
//  - Responds to BluetoothAdapter::StartDiscoverySessionWithFilter() by
//    invoking BluetoothAdapter::Observer::DeviceAdded() on a test bluetooth
//    device that includes service data containing authenticator EID.
class CableMockAdapter : public MockBluetoothAdapter {};

class FakeHandshakeHandler : public FidoCableV1HandshakeHandler {};

// Fake discovery that encapsulates exactly the same behavior as
// FidoCableDiscovery except that it uses FakeHandshakeHandler instead of
// FidoHandshakeHandler to conduct handshake with the authenticator.
class FakeFidoCableDiscovery : public FidoCableDiscovery {};

}  // namespace

class FidoCableDiscoveryTest : public ::testing::Test {};

// Tests discovery without a BLE adapter.
TEST_F(FidoCableDiscoveryTest, TestDiscoveryFails) {}

// Tests discovery with a powered-off BLE adapter.  Not calling
// DiscoveryStarted() in the case of a present-but-unpowered adapter leads to a
// deadlock between the discovery and the UI (see crbug.com/1018416).
TEST_F(FidoCableDiscoveryTest, TestDiscoveryStartedWithUnpoweredAdapter) {}

// Tests regular successful discovery flow for Cable device.
TEST_F(FidoCableDiscoveryTest, TestDiscoveryFindsNewDevice) {}

// Tests successful discovery flow for Apple Cable device.
TEST_F(FidoCableDiscoveryTest, TestDiscoveryFindsNewAppleDevice) {}

#if BUILDFLAG(IS_MAC)

// Tests that the discovery will not attempt to call bluetooth functions like
// IsPowered() if the build is signed and the OS reports an undetermined
// permission status.
TEST_F(FidoCableDiscoveryTest, TestDiscoveryDoesNotUseBluetoothIfUnauthorized) {
  fido::mac::ScopedProcessIsSignedOverride scoped_process_is_signed_override(
      fido::mac::CodeSigningState::kSigned);
  auto cable_discovery = CreateDiscovery();
  NiceMock<MockFidoDiscoveryObserver> mock_observer;
  EXPECT_CALL(mock_observer,
              DiscoveryStarted(cable_discovery.get(), true,
                               std::vector<FidoAuthenticator*>()));
  cable_discovery->set_observer(&mock_observer);

  auto mock_adapter = CableMockAdapter::MakeWithUndeterminedPermission();
  EXPECT_CALL(*mock_adapter, IsPowered()).Times(0);
  BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter);
  cable_discovery->Start();
  task_environment_.FastForwardUntilNoTasksRemain();
}

// Tests that the discovery will assume bluetooth permission is granted if the
// build is not signed.
TEST_F(FidoCableDiscoveryTest,
       TestDiscoveryAssumesBluetoothAuthorizedIfUnsigned) {
  fido::mac::ScopedProcessIsSignedOverride scoped_process_is_signed_override(
      fido::mac::CodeSigningState::kNotSigned);
  auto cable_discovery = CreateDiscovery();
  NiceMock<MockFidoDiscoveryObserver> mock_observer;
  EXPECT_CALL(mock_observer,
              DiscoveryStarted(cable_discovery.get(), true,
                               std::vector<FidoAuthenticator*>()));
  EXPECT_CALL(mock_observer, AuthenticatorAdded(_, _));
  cable_discovery->set_observer(&mock_observer);

  auto mock_adapter = CableMockAdapter::MakeWithUndeterminedPermission();
  EXPECT_CALL(*mock_adapter, IsPowered())
      .WillRepeatedly(::testing::Return(true));
  mock_adapter->ExpectDiscoveryWithScanCallback(kAuthenticatorEid);
  mock_adapter->ExpectRegisterAdvertisementWithResponse(
      true /* simulate_success */, kClientEid, kUuidFormattedClientEid);

  BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter);
  cable_discovery->Start();
  task_environment_.FastForwardUntilNoTasksRemain();
}

#endif  // BUILDFLAG(IS_MAC)

// Tests a scenario where upon broadcasting advertisement and scanning, client
// discovers a device with an incorrect authenticator EID. Observer::AddDevice()
// must not be called.
TEST_F(FidoCableDiscoveryTest, TestDiscoveryFindsIncorrectDevice) {}

// Windows currently does not support multiple EIDs, so the following tests are
// not applicable.
// TODO(crbug.com/40573698): Support multiple EIDs on Windows and enable
// these tests.
#if !BUILDFLAG(IS_WIN)
// Tests Cable discovery flow when multiple(2) sets of client/authenticator EIDs
// are passed on from the relying party. We should expect 2 invocations of
// BluetoothAdapter::RegisterAdvertisement().
TEST_F(FidoCableDiscoveryTest, TestDiscoveryWithMultipleEids) {}

// Tests a scenario where only one of the two client EID's are advertised
// successfully. Since at least one advertisement are successfully processed,
// scanning process should be invoked.
TEST_F(FidoCableDiscoveryTest, TestDiscoveryWithPartialAdvertisementSuccess) {}

// Test the scenario when all advertisement for client EID's fails.
TEST_F(FidoCableDiscoveryTest, TestDiscoveryWithAdvertisementFailures) {}
#endif  // !BUILDFLAG(IS_WIN)

TEST_F(FidoCableDiscoveryTest, TestUnregisterAdvertisementUponDestruction) {}

TEST_F(FidoCableDiscoveryTest, TestUnregisterAdvertisementUponStop) {}

TEST_F(FidoCableDiscoveryTest, TestStopWithNoAdvertisementsSucceeds) {}

// Tests that cable discovery resumes after Bluetooth adapter is powered on.
TEST_F(FidoCableDiscoveryTest, TestResumeDiscoveryAfterPoweredOn) {}

#if BUILDFLAG(IS_CHROMEOS)
// Tests regular successful discovery flow for Cable device on Floss.
TEST_F(FidoCableDiscoveryTest, TestDiscoveryFindsNewDeviceFloss) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(floss::features::kFlossEnabled);
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
#if BUILDFLAG(IS_CHROMEOS_LACROS)
  crosapi::mojom::BrowserInitParamsPtr init_params =
      chromeos::BrowserInitParams::GetForTests()->Clone();
  init_params->is_floss_available = true;
  init_params->use_floss_bluetooth = true;
  chromeos::BrowserInitParams::SetInitParamsForTests(std::move(init_params));
#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)

  auto cable_discovery = CreateDiscovery();
  NiceMock<MockFidoDiscoveryObserver> mock_observer;
  EXPECT_CALL(mock_observer,
              DiscoveryStarted(cable_discovery.get(), true,
                               std::vector<FidoAuthenticator*>()));
  EXPECT_CALL(mock_observer, AuthenticatorAdded(_, _));
  cable_discovery->set_observer(&mock_observer);

  auto mock_adapter = CableMockAdapter::MakePoweredOn();
  mock_adapter->ExpectLEScan(kAuthenticatorEid);
  mock_adapter->ExpectRegisterAdvertisementWithResponse(
      true /* simulate_success */, kClientEid, kUuidFormattedClientEid);

  BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter);
  cable_discovery->Start();
  task_environment_.FastForwardUntilNoTasksRemain();
}
#endif  // BUILDFLAG(IS_CHROMEOS)

}  // namespace device