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

#include "device/fido/fido_ble_connection.h"

#include <utility>

#include "base/bind.h"
#include "base/run_loop.h"
#include "base/strings/string_piece.h"
#include "base/test/scoped_task_environment.h"
#include "build/build_config.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/test/bluetooth_test.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/bluetooth/test/mock_bluetooth_device.h"
#include "device/bluetooth/test/mock_bluetooth_gatt_characteristic.h"
#include "device/bluetooth/test/mock_bluetooth_gatt_connection.h"
#include "device/bluetooth/test/mock_bluetooth_gatt_notify_session.h"
#include "device/bluetooth/test/mock_bluetooth_gatt_service.h"
#include "device/fido/fido_ble_uuids.h"
#include "device/fido/test_callback_receiver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

#if defined(OS_ANDROID)
#include "device/bluetooth/test/bluetooth_test_android.h"
#elif defined(OS_MACOSX)
#include "device/bluetooth/test/bluetooth_test_mac.h"
#elif defined(OS_WIN)
#include "device/bluetooth/test/bluetooth_test_win.h"
#elif defined(OS_CHROMEOS) || defined(OS_LINUX)
#include "device/bluetooth/test/bluetooth_test_bluez.h"
#endif

namespace device {

using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Invoke;
using ::testing::IsEmpty;
using ::testing::Return;

using NiceMockBluetoothAdapter = ::testing::NiceMock<MockBluetoothAdapter>;
using NiceMockBluetoothDevice = ::testing::NiceMock<MockBluetoothDevice>;
using NiceMockBluetoothGattService =
    ::testing::NiceMock<MockBluetoothGattService>;
using NiceMockBluetoothGattCharacteristic =
    ::testing::NiceMock<MockBluetoothGattCharacteristic>;
using NiceMockBluetoothGattConnection =
    ::testing::NiceMock<MockBluetoothGattConnection>;
using NiceMockBluetoothGattNotifySession =
    ::testing::NiceMock<MockBluetoothGattNotifySession>;

namespace {

std::vector<uint8_t> ToByteVector(base::StringPiece str) {
  return std::vector<uint8_t>(str.begin(), str.end());
}

BluetoothDevice* GetMockDevice(MockBluetoothAdapter* adapter,
                               const std::string& address) {
  const std::vector<BluetoothDevice*> devices = adapter->GetMockDevices();
  auto found = std::find_if(devices.begin(), devices.end(),
                            [&address](const auto* device) {
                              return device->GetAddress() == address;
                            });
  return found != devices.end() ? *found : nullptr;
}

class TestConnectionStatusCallback {
 public:
  void OnStatus(bool status) {
    status_ = status;
    run_loop_->Quit();
  }

  bool WaitForResult() {
    run_loop_->Run();
    run_loop_.emplace();
    return status_;
  }

  FidoBleConnection::ConnectionStatusCallback GetCallback() {
    return base::BindRepeating(&TestConnectionStatusCallback::OnStatus,
                               base::Unretained(this));
  }

 private:
  bool status_ = false;
  base::Optional<base::RunLoop> run_loop_{base::in_place};
};

class TestReadCallback {
 public:
  void OnRead(std::vector<uint8_t> value) {
    value_ = std::move(value);
    run_loop_->Quit();
  }

  const std::vector<uint8_t> WaitForResult() {
    run_loop_->Run();
    run_loop_.emplace();
    return value_;
  }

  FidoBleConnection::ReadCallback GetCallback() {
    return base::BindRepeating(&TestReadCallback::OnRead,
                               base::Unretained(this));
  }

 private:
  std::vector<uint8_t> value_;
  base::Optional<base::RunLoop> run_loop_{base::in_place};
};

using TestReadControlPointLengthCallback =
    test::ValueCallbackReceiver<base::Optional<uint16_t>>;

using TestReadServiceRevisionsCallback =
    test::ValueCallbackReceiver<std::set<FidoBleConnection::ServiceRevision>>;

using TestWriteCallback = test::ValueCallbackReceiver<bool>;
}  // namespace

class FidoBleConnectionTest : public ::testing::Test {
 public:
  FidoBleConnectionTest() {
    ON_CALL(*adapter_, GetDevice(_))
        .WillByDefault(Invoke([this](const std::string& address) {
          return GetMockDevice(adapter_.get(), address);
        }));

    BluetoothAdapterFactory::SetAdapterForTesting(adapter_);
  }

  void AddU2Device(const std::string& device_address) {
    auto u2f_device = std::make_unique<NiceMockBluetoothDevice>(
        adapter_.get(), /* bluetooth_class */ 0u,
        BluetoothTest::kTestDeviceNameU2f, device_address, /* paired */ true,
        /* connected */ false);
    u2f_device_ = u2f_device.get();
    adapter_->AddMockDevice(std::move(u2f_device));

    ON_CALL(*u2f_device_, GetGattServices())
        .WillByDefault(
            Invoke(u2f_device_, &MockBluetoothDevice::GetMockServices));

    ON_CALL(*u2f_device_, GetGattService(_))
        .WillByDefault(
            Invoke(u2f_device_, &MockBluetoothDevice::GetMockService));
    AddU2fService();
  }

  void SetupConnectingU2fDevice(const std::string& device_address) {
    auto run_cb_with_connection = [this, &device_address](
                                      const auto& callback,
                                      const auto& error_callback) {
      auto connection = std::make_unique<NiceMockBluetoothGattConnection>(
          adapter_, device_address);
      connection_ = connection.get();
      callback.Run(std::move(connection));
    };

    auto run_cb_with_notify_session = [this](const auto& callback,
                                             const auto& error_callback) {
      auto notify_session =
          std::make_unique<NiceMockBluetoothGattNotifySession>(
              u2f_status_->GetWeakPtr());
      notify_session_ = notify_session.get();
      callback.Run(std::move(notify_session));
    };

    ON_CALL(*u2f_device_, CreateGattConnection(_, _))
        .WillByDefault(Invoke(run_cb_with_connection));

    ON_CALL(*u2f_device_, IsGattServicesDiscoveryComplete())
        .WillByDefault(Return(true));

    ON_CALL(*u2f_status_, StartNotifySession(_, _))
        .WillByDefault(Invoke(run_cb_with_notify_session));
  }

  void SimulateDisconnect(const std::string& device_address) {
    if (u2f_device_->GetAddress() != device_address)
      return;

    u2f_device_->SetConnected(false);
    adapter_->NotifyDeviceChanged(u2f_device_);
  }

  void SimulateDeviceAddressChange(const std::string& old_address,
                                   const std::string& new_address) {
    if (!u2f_device_ || u2f_device_->GetAddress() != old_address)
      return;

    ON_CALL(*u2f_device_, GetAddress()).WillByDefault(Return(new_address));

    adapter_->NotifyDeviceChanged(u2f_device_);
    for (auto& observer : adapter_->GetObservers())
      observer.DeviceAddressChanged(adapter_.get(), u2f_device_, old_address);
  }

  void NotifyDeviceAdded(const std::string& device_address) {
    auto* device = adapter_->GetDevice(device_address);
    if (!device)
      return;

    for (auto& observer : adapter_->GetObservers())
      observer.DeviceAdded(adapter_.get(), device);
  }

  void NotifyStatusChanged(const std::vector<uint8_t>& value) {
    for (auto& observer : adapter_->GetObservers())
      observer.GattCharacteristicValueChanged(adapter_.get(), u2f_status_,
                                              value);
  }

  void SetNextReadControlPointLengthReponse(bool success,
                                            const std::vector<uint8_t>& value) {
    EXPECT_CALL(*u2f_control_point_length_, ReadRemoteCharacteristic(_, _))
        .WillOnce(Invoke([success, value](const auto& callback,
                                          const auto& error_callback) {
          success ? callback.Run(value)
                  : error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED);
        }));
  }

  void SetNextReadServiceRevisionResponse(bool success,
                                          const std::vector<uint8_t>& value) {
    EXPECT_CALL(*u2f_service_revision_, ReadRemoteCharacteristic(_, _))
        .WillOnce(Invoke([success, value](const auto& callback,
                                          const auto& error_callback) {
          success ? callback.Run(value)
                  : error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED);
        }));
  }

  void SetNextReadServiceRevisionBitfieldResponse(
      bool success,
      const std::vector<uint8_t>& value) {
    EXPECT_CALL(*u2f_service_revision_bitfield_, ReadRemoteCharacteristic(_, _))
        .WillOnce(Invoke([success, value](const auto& callback,
                                          const auto& error_callback) {
          success ? callback.Run(value)
                  : error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED);
        }));
  }

  void SetNextWriteControlPointResponse(bool success) {
    EXPECT_CALL(*u2f_control_point_, WriteRemoteCharacteristic(_, _, _))
        .WillOnce(Invoke([success](const auto& data, const auto& callback,
                                   const auto& error_callback) {
          success ? callback.Run()
                  : error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED);
        }));
  }

  void SetNextWriteServiceRevisionResponse(bool success) {
    EXPECT_CALL(*u2f_service_revision_bitfield_,
                WriteRemoteCharacteristic(_, _, _))
        .WillOnce(Invoke([success](const auto& data, const auto& callback,
                                   const auto& error_callback) {
          success ? callback.Run()
                  : error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED);
        }));
  }

  void AddU2fService() {
    auto u2f_service = std::make_unique<NiceMockBluetoothGattService>(
        u2f_device_, "u2f_service", BluetoothUUID(kFidoServiceUUID),
        /* is_primary */ true, /* is_local */ false);
    u2f_service_ = u2f_service.get();
    u2f_device_->AddMockService(std::move(u2f_service));

    AddU2fCharacteristics();
  }

  void AddU2fCharacteristics() {
    const bool is_local = false;
    {
      auto u2f_control_point =
          std::make_unique<NiceMockBluetoothGattCharacteristic>(
              u2f_service_, "u2f_control_point",
              BluetoothUUID(kFidoControlPointUUID), is_local,
              BluetoothGattCharacteristic::PROPERTY_WRITE,
              BluetoothGattCharacteristic::PERMISSION_NONE);
      u2f_control_point_ = u2f_control_point.get();
      u2f_service_->AddMockCharacteristic(std::move(u2f_control_point));
    }

    {
      auto u2f_status = std::make_unique<NiceMockBluetoothGattCharacteristic>(
          u2f_service_, "u2f_status", BluetoothUUID(kFidoStatusUUID), is_local,
          BluetoothGattCharacteristic::PROPERTY_NOTIFY,
          BluetoothGattCharacteristic::PERMISSION_NONE);
      u2f_status_ = u2f_status.get();
      u2f_service_->AddMockCharacteristic(std::move(u2f_status));
    }

    {
      auto u2f_control_point_length =
          std::make_unique<NiceMockBluetoothGattCharacteristic>(
              u2f_service_, "u2f_control_point_length",
              BluetoothUUID(kFidoControlPointLengthUUID), is_local,
              BluetoothGattCharacteristic::PROPERTY_READ,
              BluetoothGattCharacteristic::PERMISSION_NONE);
      u2f_control_point_length_ = u2f_control_point_length.get();
      u2f_service_->AddMockCharacteristic(std::move(u2f_control_point_length));
    }

    {
      auto u2f_service_revision =
          std::make_unique<NiceMockBluetoothGattCharacteristic>(
              u2f_service_, "u2f_service_revision",
              BluetoothUUID(kFidoServiceRevisionUUID), is_local,
              BluetoothGattCharacteristic::PROPERTY_READ,
              BluetoothGattCharacteristic::PERMISSION_NONE);
      u2f_service_revision_ = u2f_service_revision.get();
      u2f_service_->AddMockCharacteristic(std::move(u2f_service_revision));
    }

    {
      auto u2f_service_revision_bitfield =
          std::make_unique<NiceMockBluetoothGattCharacteristic>(
              u2f_service_, "u2f_service_revision_bitfield",
              BluetoothUUID(kFidoServiceRevisionBitfieldUUID), is_local,
              BluetoothGattCharacteristic::PROPERTY_READ |
                  BluetoothGattCharacteristic::PROPERTY_WRITE,
              BluetoothGattCharacteristic::PERMISSION_NONE);
      u2f_service_revision_bitfield_ = u2f_service_revision_bitfield.get();
      u2f_service_->AddMockCharacteristic(
          std::move(u2f_service_revision_bitfield));
    }
  }

 private:
  base::test::ScopedTaskEnvironment scoped_task_environment_;

  scoped_refptr<MockBluetoothAdapter> adapter_ =
      base::MakeRefCounted<NiceMockBluetoothAdapter>();

  MockBluetoothDevice* u2f_device_;
  MockBluetoothGattService* u2f_service_;

  MockBluetoothGattCharacteristic* u2f_control_point_;
  MockBluetoothGattCharacteristic* u2f_status_;
  MockBluetoothGattCharacteristic* u2f_control_point_length_;
  MockBluetoothGattCharacteristic* u2f_service_revision_;
  MockBluetoothGattCharacteristic* u2f_service_revision_bitfield_;

  MockBluetoothGattConnection* connection_;
  MockBluetoothGattNotifySession* notify_session_;
};

TEST_F(FidoBleConnectionTest, Address) {
  const std::string device_address = BluetoothTest::kTestDeviceAddress1;
  auto connect_do_nothing = [](bool) {};
  auto read_do_nothing = [](std::vector<uint8_t>) {};

  FidoBleConnection connection(device_address,
                               base::BindRepeating(connect_do_nothing),
                               base::BindRepeating(read_do_nothing));
  connection.Connect();
  EXPECT_EQ(device_address, connection.address());
  AddU2Device(device_address);

  SimulateDeviceAddressChange(device_address, "new_device_address");
  EXPECT_EQ("new_device_address", connection.address());
}

TEST_F(FidoBleConnectionTest, DeviceNotPresent) {
  const std::string device_address = BluetoothTest::kTestDeviceAddress1;
  TestConnectionStatusCallback connection_status_callback;
  auto do_nothing = [](std::vector<uint8_t>) {};

  FidoBleConnection connection(device_address,
                               connection_status_callback.GetCallback(),
                               base::BindRepeating(do_nothing));
  connection.Connect();
  bool result = connection_status_callback.WaitForResult();
  EXPECT_FALSE(result);
}

TEST_F(FidoBleConnectionTest, PreConnected) {
  const std::string device_address = BluetoothTest::kTestDeviceAddress1;
  TestConnectionStatusCallback connection_status_callback;
  AddU2Device(device_address);
  SetupConnectingU2fDevice(device_address);

  auto do_nothing = [](std::vector<uint8_t>) {};
  FidoBleConnection connection(device_address,
                               connection_status_callback.GetCallback(),
                               base::BindRepeating(do_nothing));
  connection.Connect();
  EXPECT_TRUE(connection_status_callback.WaitForResult());
}

TEST_F(FidoBleConnectionTest, PostConnected) {
  const std::string device_address = BluetoothTest::kTestDeviceAddress1;
  TestConnectionStatusCallback connection_status_callback;
  auto do_nothing = [](std::vector<uint8_t>) {};
  FidoBleConnection connection(device_address,
                               connection_status_callback.GetCallback(),
                               base::BindRepeating(do_nothing));
  connection.Connect();
  bool result = connection_status_callback.WaitForResult();
  EXPECT_FALSE(result);

  AddU2Device(device_address);
  SetupConnectingU2fDevice(device_address);
  NotifyDeviceAdded(device_address);
  EXPECT_TRUE(connection_status_callback.WaitForResult());
}

TEST_F(FidoBleConnectionTest, DeviceDisconnect) {
  const std::string device_address = BluetoothTest::kTestDeviceAddress1;
  TestConnectionStatusCallback connection_status_callback;

  AddU2Device(device_address);
  SetupConnectingU2fDevice(device_address);
  auto do_nothing = [](std::vector<uint8_t>) {};
  FidoBleConnection connection(device_address,
                               connection_status_callback.GetCallback(),
                               base::BindRepeating(do_nothing));
  connection.Connect();
  bool result = connection_status_callback.WaitForResult();
  EXPECT_TRUE(result);

  SimulateDisconnect(device_address);
  result = connection_status_callback.WaitForResult();
  EXPECT_FALSE(result);
}

TEST_F(FidoBleConnectionTest, ReadStatusNotifications) {
  const std::string device_address = BluetoothTest::kTestDeviceAddress1;
  TestConnectionStatusCallback connection_status_callback;
  TestReadCallback read_callback;

  AddU2Device(device_address);
  SetupConnectingU2fDevice(device_address);
  FidoBleConnection connection(device_address,
                               connection_status_callback.GetCallback(),
                               read_callback.GetCallback());
  connection.Connect();
  EXPECT_TRUE(connection_status_callback.WaitForResult());

  std::vector<uint8_t> payload = ToByteVector("foo");
  NotifyStatusChanged(payload);
  EXPECT_EQ(payload, read_callback.WaitForResult());

  payload = ToByteVector("bar");
  NotifyStatusChanged(payload);
  EXPECT_EQ(payload, read_callback.WaitForResult());
}

TEST_F(FidoBleConnectionTest, ReadControlPointLength) {
  const std::string device_address = BluetoothTest::kTestDeviceAddress1;
  TestConnectionStatusCallback connection_status_callback;
  AddU2Device(device_address);
  SetupConnectingU2fDevice(device_address);
  auto read_do_nothing = [](std::vector<uint8_t>) {};

  FidoBleConnection connection(device_address,
                               connection_status_callback.GetCallback(),
                               base::BindRepeating(read_do_nothing));
  connection.Connect();
  EXPECT_TRUE(connection_status_callback.WaitForResult());

  TestReadControlPointLengthCallback length_callback;
  SetNextReadControlPointLengthReponse(false, {});
  connection.ReadControlPointLength(length_callback.callback());
  EXPECT_EQ(base::nullopt, length_callback.value());

  // The Control Point Length should consist of exactly two bytes, hence we
  // EXPECT_EQ(base::nullopt) for payloads of size 0, 1 and 3.
  SetNextReadControlPointLengthReponse(true, {});
  connection.ReadControlPointLength(length_callback.callback());
  EXPECT_EQ(base::nullopt, length_callback.value());

  SetNextReadControlPointLengthReponse(true, {0xAB});
  connection.ReadControlPointLength(length_callback.callback());
  EXPECT_EQ(base::nullopt, length_callback.value());

  SetNextReadControlPointLengthReponse(true, {0xAB, 0xCD});
  connection.ReadControlPointLength(length_callback.callback());
  EXPECT_EQ(0xABCD, *length_callback.value());

  SetNextReadControlPointLengthReponse(true, {0xAB, 0xCD, 0xEF});
  connection.ReadControlPointLength(length_callback.callback());
  EXPECT_EQ(base::nullopt, length_callback.value());
}

TEST_F(FidoBleConnectionTest, ReadServiceRevisions) {
  const std::string device_address = BluetoothTest::kTestDeviceAddress1;
  TestConnectionStatusCallback connection_status_callback;
  AddU2Device(device_address);
  SetupConnectingU2fDevice(device_address);
  auto read_do_nothing = [](std::vector<uint8_t>) {};

  FidoBleConnection connection(device_address,
                               connection_status_callback.GetCallback(),
                               base::BindRepeating(read_do_nothing));
  connection.Connect();
  EXPECT_TRUE(connection_status_callback.WaitForResult());

  TestReadServiceRevisionsCallback revisions_callback;
  SetNextReadServiceRevisionResponse(false, {});
  SetNextReadServiceRevisionBitfieldResponse(false, {});
  connection.ReadServiceRevisions(revisions_callback.callback());
  EXPECT_THAT(revisions_callback.value(), IsEmpty());

  SetNextReadServiceRevisionResponse(true, ToByteVector("bogus"));
  SetNextReadServiceRevisionBitfieldResponse(false, {});
  connection.ReadServiceRevisions(revisions_callback.callback());
  EXPECT_THAT(revisions_callback.value(), IsEmpty());

  SetNextReadServiceRevisionResponse(true, ToByteVector("1.0"));
  SetNextReadServiceRevisionBitfieldResponse(false, {});
  connection.ReadServiceRevisions(revisions_callback.callback());
  EXPECT_THAT(revisions_callback.value(),
              ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_0));

  SetNextReadServiceRevisionResponse(true, ToByteVector("1.1"));
  SetNextReadServiceRevisionBitfieldResponse(false, {});
  connection.ReadServiceRevisions(revisions_callback.callback());
  EXPECT_THAT(revisions_callback.value(),
              ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_1));

  SetNextReadServiceRevisionResponse(true, ToByteVector("1.2"));
  SetNextReadServiceRevisionBitfieldResponse(false, {});
  connection.ReadServiceRevisions(revisions_callback.callback());
  EXPECT_THAT(revisions_callback.value(),
              ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_2));

  // Version 1.3 currently does not exist, so this should be treated as an
  // error.
  SetNextReadServiceRevisionResponse(true, ToByteVector("1.3"));
  SetNextReadServiceRevisionBitfieldResponse(false, {});
  connection.ReadServiceRevisions(revisions_callback.callback());
  EXPECT_THAT(revisions_callback.value(), IsEmpty());

  SetNextReadServiceRevisionResponse(false, {});
  SetNextReadServiceRevisionBitfieldResponse(true, {0x00});
  connection.ReadServiceRevisions(revisions_callback.callback());
  EXPECT_THAT(revisions_callback.value(), IsEmpty());

  SetNextReadServiceRevisionResponse(false, {});
  SetNextReadServiceRevisionBitfieldResponse(true, {0x80});
  connection.ReadServiceRevisions(revisions_callback.callback());
  EXPECT_THAT(revisions_callback.value(),
              ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_1));

  SetNextReadServiceRevisionResponse(false, {});
  SetNextReadServiceRevisionBitfieldResponse(true, {0x40});
  connection.ReadServiceRevisions(revisions_callback.callback());
  EXPECT_THAT(revisions_callback.value(),
              ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_2));

  SetNextReadServiceRevisionResponse(false, {});
  SetNextReadServiceRevisionBitfieldResponse(true, {0xC0});
  connection.ReadServiceRevisions(revisions_callback.callback());
  EXPECT_THAT(revisions_callback.value(),
              ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_1,
                          FidoBleConnection::ServiceRevision::VERSION_1_2));

  // All bits except the first two should be ignored.
  SetNextReadServiceRevisionResponse(false, {});
  SetNextReadServiceRevisionBitfieldResponse(true, {0xFF});
  connection.ReadServiceRevisions(revisions_callback.callback());
  EXPECT_THAT(revisions_callback.value(),
              ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_1,
                          FidoBleConnection::ServiceRevision::VERSION_1_2));

  // All bytes except the first one should be ignored.
  SetNextReadServiceRevisionResponse(false, {});
  SetNextReadServiceRevisionBitfieldResponse(true, {0xC0, 0xFF});
  connection.ReadServiceRevisions(revisions_callback.callback());
  EXPECT_THAT(revisions_callback.value(),
              ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_1,
                          FidoBleConnection::ServiceRevision::VERSION_1_2));

  // The combination of a service revision string and bitfield should be
  // supported as well.
  SetNextReadServiceRevisionResponse(true, ToByteVector("1.0"));
  SetNextReadServiceRevisionBitfieldResponse(true, {0xC0});
  connection.ReadServiceRevisions(revisions_callback.callback());
  EXPECT_THAT(revisions_callback.value(),
              ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_0,
                          FidoBleConnection::ServiceRevision::VERSION_1_1,
                          FidoBleConnection::ServiceRevision::VERSION_1_2));
}

TEST_F(FidoBleConnectionTest, WriteControlPoint) {
  const std::string device_address = BluetoothTest::kTestDeviceAddress1;
  TestConnectionStatusCallback connection_status_callback;
  AddU2Device(device_address);
  SetupConnectingU2fDevice(device_address);
  auto read_do_nothing = [](std::vector<uint8_t>) {};

  FidoBleConnection connection(device_address,
                               connection_status_callback.GetCallback(),
                               base::BindRepeating(read_do_nothing));
  connection.Connect();
  bool result = connection_status_callback.WaitForResult();
  EXPECT_TRUE(result);

  TestWriteCallback write_callback;
  SetNextWriteControlPointResponse(false);
  connection.WriteControlPoint({}, write_callback.callback());
  result = write_callback.value();
  EXPECT_FALSE(result);

  SetNextWriteControlPointResponse(true);
  connection.WriteControlPoint({}, write_callback.callback());
  result = write_callback.value();
  EXPECT_TRUE(result);
}

TEST_F(FidoBleConnectionTest, WriteServiceRevision) {
  const std::string device_address = BluetoothTest::kTestDeviceAddress1;
  TestConnectionStatusCallback connection_status_callback;
  AddU2Device(device_address);
  SetupConnectingU2fDevice(device_address);
  auto read_do_nothing = [](std::vector<uint8_t>) {};

  FidoBleConnection connection(device_address,
                               connection_status_callback.GetCallback(),
                               base::BindRepeating(read_do_nothing));
  connection.Connect();
  bool result = connection_status_callback.WaitForResult();
  EXPECT_TRUE(result);

  // Expect that errors are properly propagated.
  TestWriteCallback write_callback;
  SetNextWriteServiceRevisionResponse(false);
  connection.WriteServiceRevision(
      FidoBleConnection::ServiceRevision::VERSION_1_1,
      write_callback.callback());
  result = write_callback.value();
  EXPECT_FALSE(result);

  // Expect a successful write of version 1.1.
  SetNextWriteServiceRevisionResponse(true);
  connection.WriteServiceRevision(
      FidoBleConnection::ServiceRevision::VERSION_1_1,
      write_callback.callback());
  result = write_callback.value();
  EXPECT_TRUE(result);

  // Expect a successful write of version 1.2.
  SetNextWriteServiceRevisionResponse(true);
  connection.WriteServiceRevision(
      FidoBleConnection::ServiceRevision::VERSION_1_2,
      write_callback.callback());
  result = write_callback.value();
  EXPECT_TRUE(result);

  // Writing version 1.0 to the bitfield is not intended, so this should fail.
  connection.WriteServiceRevision(
      FidoBleConnection::ServiceRevision::VERSION_1_0,
      write_callback.callback());
  result = write_callback.value();
  EXPECT_FALSE(result);
}

TEST_F(FidoBleConnectionTest, ReadsAndWriteFailWhenDisconnected) {
  const std::string device_address = BluetoothTest::kTestDeviceAddress1;
  TestConnectionStatusCallback connection_status_callback;

  AddU2Device(device_address);
  SetupConnectingU2fDevice(device_address);
  auto do_nothing = [](std::vector<uint8_t>) {};
  FidoBleConnection connection(device_address,
                               connection_status_callback.GetCallback(),
                               base::BindRepeating(do_nothing));
  connection.Connect();
  bool result = connection_status_callback.WaitForResult();
  EXPECT_TRUE(result);

  SimulateDisconnect(device_address);
  result = connection_status_callback.WaitForResult();
  EXPECT_FALSE(result);

  // Reads should always fail on a disconnected device.
  TestReadControlPointLengthCallback length_callback;
  connection.ReadControlPointLength(length_callback.callback());
  EXPECT_EQ(base::nullopt, length_callback.value());

  TestReadServiceRevisionsCallback revisions_callback;
  connection.ReadServiceRevisions(revisions_callback.callback());
  EXPECT_THAT(revisions_callback.value(), IsEmpty());

  // Writes should always fail on a disconnected device.
  TestWriteCallback write_callback;
  connection.WriteServiceRevision(
      FidoBleConnection::ServiceRevision::VERSION_1_1,
      write_callback.callback());
  result = write_callback.value();
  EXPECT_FALSE(result);

  connection.WriteControlPoint({}, write_callback.callback());
  result = write_callback.value();
  EXPECT_FALSE(result);
}

}  // namespace device
