chromium/chrome/browser/nearby_sharing/fast_initiation/fast_initiation_advertiser_unittest.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/nearby_sharing/fast_initiation/fast_initiation_advertiser.h"

#include <memory>
#include <utility>
#include <vector>

#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/test/bind.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/bluetooth/test/mock_bluetooth_advertisement.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

using ::testing::_;
using testing::NiceMock;
using testing::Return;

constexpr const char kNearbySharingFastInitiationServiceUuid[] =
    "0000fe2c-0000-1000-8000-00805f9b34fb";
const uint8_t kNearbySharingFastPairId[] = {0xfc, 0x12, 0x8e};

// Metadata bytes translate to 0b00000000 and 0b10111110, indicating "version
// 0", "type 0 (notify)", and "transmission power of 66".
const uint8_t kFastInitMetadataTypeNotify[] = {0x00, 0x42};

// Metadata bytes translate to 0b00000100 and 0b10111110, indicating "version
// 0", "type 1 (silent)", and "transmission power of 66".
const uint8_t kFastInitMetadataTypeSilent[] = {0x04, 0x42};

struct RegisterAdvertisementArgs {
  RegisterAdvertisementArgs(
      const device::BluetoothAdvertisement::UUIDList& service_uuids,
      const device::BluetoothAdvertisement::ServiceData& service_data,
      device::BluetoothAdapter::CreateAdvertisementCallback callback,
      device::BluetoothAdapter::AdvertisementErrorCallback error_callback)
      : service_uuids(service_uuids),
        service_data(service_data),
        callback(std::move(callback)),
        error_callback(std::move(error_callback)) {}

  device::BluetoothAdvertisement::UUIDList service_uuids;
  device::BluetoothAdvertisement::ServiceData service_data;
  device::BluetoothAdapter::CreateAdvertisementCallback callback;
  device::BluetoothAdapter::AdvertisementErrorCallback error_callback;
};

class MockBluetoothAdapterWithAdvertisements
    : public device::MockBluetoothAdapter {
 public:
  MOCK_METHOD1(RegisterAdvertisementWithArgsStruct,
               void(RegisterAdvertisementArgs*));

  void RegisterAdvertisement(
      std::unique_ptr<device::BluetoothAdvertisement::Data> advertisement_data,
      device::BluetoothAdapter::CreateAdvertisementCallback callback,
      device::BluetoothAdapter::AdvertisementErrorCallback error_callback)
      override {
    RegisterAdvertisementWithArgsStruct(new RegisterAdvertisementArgs(
        *advertisement_data->service_uuids(),
        *advertisement_data->service_data(), std::move(callback),
        std::move(error_callback)));
  }

 protected:
  ~MockBluetoothAdapterWithAdvertisements() override = default;
};

class FakeBluetoothAdvertisement : public device::BluetoothAdvertisement {
 public:
  // device::BluetoothAdvertisement:
  void Unregister(SuccessCallback success_callback,
                  ErrorCallback error_callback) override {
    std::move(success_callback).Run();
  }

  bool HasObserver(Observer* observer) {
    return observers_.HasObserver(observer);
  }

  void ReleaseAdvertisement() {
    for (auto& observer : observers_)
      observer.AdvertisementReleased(this);
  }

 protected:
  ~FakeBluetoothAdvertisement() override = default;
};

}  // namespace

class NearbySharingFastInitiationAdvertiserTest : public testing::Test {
 public:
  NearbySharingFastInitiationAdvertiserTest(
      const NearbySharingFastInitiationAdvertiserTest&) = delete;
  NearbySharingFastInitiationAdvertiserTest& operator=(
      const NearbySharingFastInitiationAdvertiserTest&) = delete;

 protected:
  NearbySharingFastInitiationAdvertiserTest() = default;

  void SetUp() override {
    mock_adapter_ = base::MakeRefCounted<
        NiceMock<MockBluetoothAdapterWithAdvertisements>>();
    ON_CALL(*mock_adapter_, IsPresent()).WillByDefault(Return(true));
    ON_CALL(*mock_adapter_, IsPowered()).WillByDefault(Return(true));
    ON_CALL(*mock_adapter_, RegisterAdvertisementWithArgsStruct(_))
        .WillByDefault(Invoke(this, &NearbySharingFastInitiationAdvertiserTest::
                                        OnAdapterRegisterAdvertisement));

    fast_initiation_advertiser_ =
        std::make_unique<FastInitiationAdvertiser>(mock_adapter_);
  }

  void OnAdapterRegisterAdvertisement(RegisterAdvertisementArgs* args) {
    register_args_ = base::WrapUnique(args);
  }

  void StartAdvertising(FastInitiationAdvertiser::FastInitType type) {
    fast_initiation_advertiser_->StartAdvertising(
        type,
        base::BindOnce(
            &NearbySharingFastInitiationAdvertiserTest::OnStartAdvertising,
            base::Unretained(this)),
        base::BindOnce(
            &NearbySharingFastInitiationAdvertiserTest::OnStartAdvertisingError,
            base::Unretained(this)));
    auto service_uuid_list =
        std::make_unique<device::BluetoothAdvertisement::UUIDList>();
    service_uuid_list->push_back(kNearbySharingFastInitiationServiceUuid);
    EXPECT_EQ(*service_uuid_list, register_args_->service_uuids);

    auto expected_payload =
        std::vector<uint8_t>(std::begin(kNearbySharingFastPairId),
                             std::end(kNearbySharingFastPairId));
    if (type == FastInitiationAdvertiser::FastInitType::kNotify) {
      expected_payload.insert(std::end(expected_payload),
                              std::begin(kFastInitMetadataTypeNotify),
                              std::end(kFastInitMetadataTypeNotify));
    } else {
      expected_payload.insert(std::end(expected_payload),
                              std::begin(kFastInitMetadataTypeSilent),
                              std::end(kFastInitMetadataTypeSilent));
    }

    EXPECT_EQ(
        expected_payload,
        register_args_->service_data[kNearbySharingFastInitiationServiceUuid]);
  }

  void StopAdvertising() {
    fast_initiation_advertiser_->StopAdvertising(base::BindOnce(
        &NearbySharingFastInitiationAdvertiserTest::OnStopAdvertising,
        base::Unretained(this)));
  }

  void OnStartAdvertising() { called_on_start_advertising_ = true; }

  void OnStartAdvertisingError() { called_on_start_advertising_error_ = true; }

  void OnStopAdvertising() { called_on_stop_advertising_ = true; }

  bool called_on_start_advertising() { return called_on_start_advertising_; }
  bool called_on_start_advertising_error() {
    return called_on_start_advertising_error_;
  }
  bool called_on_stop_advertising() { return called_on_stop_advertising_; }

  scoped_refptr<NiceMock<MockBluetoothAdapterWithAdvertisements>> mock_adapter_;
  std::unique_ptr<FastInitiationAdvertiser> fast_initiation_advertiser_;
  std::unique_ptr<RegisterAdvertisementArgs> register_args_;
  bool called_on_start_advertising_ = false;
  bool called_on_start_advertising_error_ = false;
  bool called_on_stop_advertising_ = false;
};

TEST_F(NearbySharingFastInitiationAdvertiserTest,
       TestStartAdvertising_Success_TypeNotify) {
  StartAdvertising(FastInitiationAdvertiser::FastInitType::kNotify);
  auto fake_advertisement = base::MakeRefCounted<FakeBluetoothAdvertisement>();
  std::move(register_args_->callback).Run(fake_advertisement);

  EXPECT_TRUE(called_on_start_advertising());
  EXPECT_FALSE(called_on_start_advertising_error());
  EXPECT_FALSE(called_on_stop_advertising());
  EXPECT_TRUE(
      fake_advertisement->HasObserver(fast_initiation_advertiser_.get()));
}

TEST_F(NearbySharingFastInitiationAdvertiserTest,
       TestStartAdvertising_Success_TypeSilent) {
  StartAdvertising(FastInitiationAdvertiser::FastInitType::kSilent);
  auto fake_advertisement = base::MakeRefCounted<FakeBluetoothAdvertisement>();
  std::move(register_args_->callback).Run(fake_advertisement);

  EXPECT_TRUE(called_on_start_advertising());
  EXPECT_FALSE(called_on_start_advertising_error());
  EXPECT_FALSE(called_on_stop_advertising());
  EXPECT_TRUE(
      fake_advertisement->HasObserver(fast_initiation_advertiser_.get()));
}

TEST_F(NearbySharingFastInitiationAdvertiserTest, TestStartAdvertising_Error) {
  StartAdvertising(FastInitiationAdvertiser::FastInitType::kNotify);
  std::move(register_args_->error_callback)
      .Run(device::BluetoothAdvertisement::ErrorCode::
               INVALID_ADVERTISEMENT_ERROR_CODE);

  EXPECT_FALSE(called_on_start_advertising());
  EXPECT_TRUE(called_on_start_advertising_error());
  EXPECT_FALSE(called_on_stop_advertising());
}

// Regression test for crbug.com/1109581.
TEST_F(NearbySharingFastInitiationAdvertiserTest,
       TestStartAdvertising_DeleteInErrorCallback) {
  fast_initiation_advertiser_->StartAdvertising(
      FastInitiationAdvertiser::FastInitType::kNotify, base::DoNothing(),
      base::BindLambdaForTesting(
          [&]() { fast_initiation_advertiser_.reset(); }));

  std::move(register_args_->error_callback)
      .Run(device::BluetoothAdvertisement::ErrorCode::
               INVALID_ADVERTISEMENT_ERROR_CODE);

  EXPECT_FALSE(fast_initiation_advertiser_);
}

TEST_F(NearbySharingFastInitiationAdvertiserTest, TestStopAdvertising) {
  StartAdvertising(FastInitiationAdvertiser::FastInitType::kNotify);
  auto fake_advertisement = base::MakeRefCounted<FakeBluetoothAdvertisement>();
  std::move(register_args_->callback).Run(fake_advertisement);

  StopAdvertising();

  EXPECT_TRUE(called_on_start_advertising());
  EXPECT_FALSE(called_on_start_advertising_error());
  EXPECT_TRUE(called_on_stop_advertising());
}

TEST_F(NearbySharingFastInitiationAdvertiserTest, TestAdvertisementReleased) {
  StartAdvertising(FastInitiationAdvertiser::FastInitType::kNotify);
  auto fake_advertisement = base::MakeRefCounted<FakeBluetoothAdvertisement>();
  std::move(register_args_->callback).Run(fake_advertisement);

  EXPECT_TRUE(
      fake_advertisement->HasObserver(fast_initiation_advertiser_.get()));

  fake_advertisement->ReleaseAdvertisement();

  EXPECT_TRUE(called_on_start_advertising());
  EXPECT_FALSE(called_on_start_advertising_error());
  EXPECT_FALSE(called_on_stop_advertising());
  EXPECT_FALSE(
      fake_advertisement->HasObserver(fast_initiation_advertiser_.get()));
}