// 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/barrier_closure.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/bluetooth_gatt_connection.h"
#include "device/bluetooth/bluetooth_gatt_notify_session.h"
#include "device/bluetooth/bluetooth_remote_gatt_characteristic.h"
#include "device/bluetooth/bluetooth_remote_gatt_service.h"
#include "device/bluetooth/bluetooth_uuid.h"
#include "device/fido/fido_ble_uuids.h"

namespace device {

namespace {

const char* ToString(BluetoothDevice::ConnectErrorCode error_code) {
  switch (error_code) {
    case BluetoothDevice::ERROR_AUTH_CANCELED:
      return "ERROR_AUTH_CANCELED";
    case BluetoothDevice::ERROR_AUTH_FAILED:
      return "ERROR_AUTH_FAILED";
    case BluetoothDevice::ERROR_AUTH_REJECTED:
      return "ERROR_AUTH_REJECTED";
    case BluetoothDevice::ERROR_AUTH_TIMEOUT:
      return "ERROR_AUTH_TIMEOUT";
    case BluetoothDevice::ERROR_FAILED:
      return "ERROR_FAILED";
    case BluetoothDevice::ERROR_INPROGRESS:
      return "ERROR_INPROGRESS";
    case BluetoothDevice::ERROR_UNKNOWN:
      return "ERROR_UNKNOWN";
    case BluetoothDevice::ERROR_UNSUPPORTED_DEVICE:
      return "ERROR_UNSUPPORTED_DEVICE";
    default:
      NOTREACHED();
      return "";
  }
}

const char* ToString(BluetoothGattService::GattErrorCode error_code) {
  switch (error_code) {
    case BluetoothGattService::GATT_ERROR_UNKNOWN:
      return "GATT_ERROR_UNKNOWN";
    case BluetoothGattService::GATT_ERROR_FAILED:
      return "GATT_ERROR_FAILED";
    case BluetoothGattService::GATT_ERROR_IN_PROGRESS:
      return "GATT_ERROR_IN_PROGRESS";
    case BluetoothGattService::GATT_ERROR_INVALID_LENGTH:
      return "GATT_ERROR_INVALID_LENGTH";
    case BluetoothGattService::GATT_ERROR_NOT_PERMITTED:
      return "GATT_ERROR_NOT_PERMITTED";
    case BluetoothGattService::GATT_ERROR_NOT_AUTHORIZED:
      return "GATT_ERROR_NOT_AUTHORIZED";
    case BluetoothGattService::GATT_ERROR_NOT_PAIRED:
      return "GATT_ERROR_NOT_PAIRED";
    case BluetoothGattService::GATT_ERROR_NOT_SUPPORTED:
      return "GATT_ERROR_NOT_SUPPORTED";
    default:
      NOTREACHED();
      return "";
  }
}

}  // namespace

FidoBleConnection::FidoBleConnection(std::string device_address)
    : address_(std::move(device_address)), weak_factory_(this) {}

FidoBleConnection::FidoBleConnection(
    std::string device_address,
    ConnectionStatusCallback connection_status_callback,
    ReadCallback read_callback)
    : address_(std::move(device_address)),
      connection_status_callback_(std::move(connection_status_callback)),
      read_callback_(std::move(read_callback)),
      weak_factory_(this) {
  DCHECK(!address_.empty());
}

FidoBleConnection::~FidoBleConnection() {
  if (adapter_)
    adapter_->RemoveObserver(this);
}

const BluetoothDevice* FidoBleConnection::GetBleDevice() const {
  return adapter_ ? adapter_->GetDevice(address()) : nullptr;
}

void FidoBleConnection::Connect() {
  BluetoothAdapterFactory::GetAdapter(
      base::Bind(&FidoBleConnection::OnGetAdapter, weak_factory_.GetWeakPtr()));
}

void FidoBleConnection::ReadControlPointLength(
    ControlPointLengthCallback callback) {
  const BluetoothRemoteGattService* u2f_service = GetFidoService();
  if (!u2f_service) {
    std::move(callback).Run(base::nullopt);
    return;
  }

  BluetoothRemoteGattCharacteristic* control_point_length =
      u2f_service->GetCharacteristic(*control_point_length_id_);
  if (!control_point_length) {
    DLOG(ERROR) << "No Control Point Length characteristic present.";
    std::move(callback).Run(base::nullopt);
    return;
  }

  auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback));
  control_point_length->ReadRemoteCharacteristic(
      base::Bind(OnReadControlPointLength, copyable_callback),
      base::Bind(OnReadControlPointLengthError, copyable_callback));
}

void FidoBleConnection::ReadServiceRevisions(
    ServiceRevisionsCallback callback) {
  const BluetoothRemoteGattService* u2f_service = GetFidoService();
  if (!u2f_service) {
    std::move(callback).Run({});
    return;
  }

  DCHECK(service_revision_id_ || service_revision_bitfield_id_);
  BluetoothRemoteGattCharacteristic* service_revision =
      service_revision_id_
          ? u2f_service->GetCharacteristic(*service_revision_id_)
          : nullptr;

  BluetoothRemoteGattCharacteristic* service_revision_bitfield =
      service_revision_bitfield_id_
          ? u2f_service->GetCharacteristic(*service_revision_bitfield_id_)
          : nullptr;

  if (!service_revision && !service_revision_bitfield) {
    DLOG(ERROR) << "Service Revision Characteristics do not exist.";
    std::move(callback).Run({});
    return;
  }

  // Start from a clean state.
  service_revisions_.clear();

  // In order to obtain the full set of supported service revisions it is
  // possible that both the |service_revision_| and |service_revision_bitfield_|
  // characteristics must be read. Potentially we need to take the union of
  // the individually supported service revisions, hence the indirection to
  // ReturnServiceRevisions() is introduced.
  base::Closure copyable_callback = base::AdaptCallbackForRepeating(
      base::BindOnce(&FidoBleConnection::ReturnServiceRevisions,
                     weak_factory_.GetWeakPtr(), std::move(callback)));

  // If the Service Revision Bitfield characteristic is not present, only
  // attempt to read the Service Revision characteristic.
  if (!service_revision_bitfield) {
    service_revision->ReadRemoteCharacteristic(
        base::Bind(&FidoBleConnection::OnReadServiceRevision,
                   weak_factory_.GetWeakPtr(), copyable_callback),
        base::Bind(&FidoBleConnection::OnReadServiceRevisionError,
                   weak_factory_.GetWeakPtr(), copyable_callback));
    return;
  }

  // If the Service Revision characteristic is not present, only
  // attempt to read the Service Revision Bitfield characteristic.
  if (!service_revision) {
    service_revision_bitfield->ReadRemoteCharacteristic(
        base::Bind(&FidoBleConnection::OnReadServiceRevisionBitfield,
                   weak_factory_.GetWeakPtr(), copyable_callback),
        base::Bind(&FidoBleConnection::OnReadServiceRevisionBitfieldError,
                   weak_factory_.GetWeakPtr(), copyable_callback));
    return;
  }

  // This is the case where both characteristics are present. These reads can
  // happen in parallel, but both must finish before a result can be returned.
  // Hence a BarrierClosure is introduced invoking ReturnServiceRevisions() once
  // both characteristic reads are done.
  base::RepeatingClosure barrier_closure =
      base::BarrierClosure(2, copyable_callback);

  service_revision->ReadRemoteCharacteristic(
      base::Bind(&FidoBleConnection::OnReadServiceRevision,
                 weak_factory_.GetWeakPtr(), barrier_closure),
      base::Bind(&FidoBleConnection::OnReadServiceRevisionError,
                 weak_factory_.GetWeakPtr(), barrier_closure));

  service_revision_bitfield->ReadRemoteCharacteristic(
      base::Bind(&FidoBleConnection::OnReadServiceRevisionBitfield,
                 weak_factory_.GetWeakPtr(), barrier_closure),
      base::Bind(&FidoBleConnection::OnReadServiceRevisionBitfieldError,
                 weak_factory_.GetWeakPtr(), barrier_closure));
}

void FidoBleConnection::WriteControlPoint(const std::vector<uint8_t>& data,
                                          WriteCallback callback) {
  const BluetoothRemoteGattService* u2f_service = GetFidoService();
  if (!u2f_service) {
    std::move(callback).Run(false);
    return;
  }

  BluetoothRemoteGattCharacteristic* control_point =
      u2f_service->GetCharacteristic(*control_point_id_);
  if (!control_point) {
    DLOG(ERROR) << "Control Point characteristic not present.";
    std::move(callback).Run(false);
    return;
  }

  // Attempt a write without response for performance reasons. Fall back to a
  // confirmed write in case of failure, e.g. when the characteristic does not
  // provide the required property.
  if (control_point->WriteWithoutResponse(data)) {
    DVLOG(2) << "Write without response succeeded.";
    std::move(callback).Run(true);
  } else {
    auto copyable_callback =
        base::AdaptCallbackForRepeating(std::move(callback));
    control_point->WriteRemoteCharacteristic(
        data, base::Bind(OnWrite, copyable_callback),
        base::Bind(OnWriteError, copyable_callback));
  }
}

void FidoBleConnection::WriteServiceRevision(ServiceRevision service_revision,
                                             WriteCallback callback) {
  const BluetoothRemoteGattService* u2f_service = GetFidoService();
  if (!u2f_service) {
    std::move(callback).Run(false);
    return;
  }

  BluetoothRemoteGattCharacteristic* service_revision_bitfield =
      u2f_service->GetCharacteristic(*service_revision_bitfield_id_);
  if (!service_revision_bitfield) {
    DLOG(ERROR) << "Service Revision Bitfield characteristic not present.";
    std::move(callback).Run(false);
    return;
  }

  std::vector<uint8_t> payload;
  switch (service_revision) {
    case ServiceRevision::VERSION_1_1:
      payload.push_back(0x80);
      break;
    case ServiceRevision::VERSION_1_2:
      payload.push_back(0x40);
      break;
    default:
      DLOG(ERROR)
          << "Write Service Revision Failed: Unsupported Service Revision.";
      std::move(callback).Run(false);
      return;
  }

  auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback));
  service_revision_bitfield->WriteRemoteCharacteristic(
      payload, base::Bind(OnWrite, copyable_callback),
      base::Bind(OnWriteError, copyable_callback));
}

void FidoBleConnection::OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter) {
  if (!adapter) {
    DLOG(ERROR) << "Failed to get Adapter.";
    OnConnectionError();
    return;
  }

  DVLOG(2) << "Got Adapter: " << adapter->GetAddress();
  adapter_ = std::move(adapter);
  adapter_->AddObserver(this);
  CreateGattConnection();
}

void FidoBleConnection::CreateGattConnection() {
  BluetoothDevice* device = adapter_->GetDevice(address_);
  if (!device) {
    DLOG(ERROR) << "Failed to get Device.";
    OnConnectionError();
    return;
  }

  device->CreateGattConnection(
      base::Bind(&FidoBleConnection::OnCreateGattConnection,
                 weak_factory_.GetWeakPtr()),
      base::Bind(&FidoBleConnection::OnCreateGattConnectionError,
                 weak_factory_.GetWeakPtr()));
}

void FidoBleConnection::OnCreateGattConnection(
    std::unique_ptr<BluetoothGattConnection> connection) {
  connection_ = std::move(connection);

  BluetoothDevice* device = adapter_->GetDevice(address_);
  if (!device) {
    DLOG(ERROR) << "Failed to get Device.";
    OnConnectionError();
    return;
  }

  if (device->IsGattServicesDiscoveryComplete())
    ConnectToU2fService();
}

void FidoBleConnection::OnCreateGattConnectionError(
    BluetoothDevice::ConnectErrorCode error_code) {
  DLOG(ERROR) << "CreateGattConnection() failed: " << ToString(error_code);
  OnConnectionError();
}

void FidoBleConnection::ConnectToU2fService() {
  BluetoothDevice* device = adapter_->GetDevice(address_);
  if (!device) {
    DLOG(ERROR) << "Failed to get Device.";
    OnConnectionError();
    return;
  }

  DCHECK(device->IsGattServicesDiscoveryComplete());
  const std::vector<BluetoothRemoteGattService*> services =
      device->GetGattServices();
  auto found =
      std::find_if(services.begin(), services.end(), [](const auto* service) {
        return service->GetUUID().canonical_value() == kFidoServiceUUID;
      });

  if (found == services.end()) {
    DLOG(ERROR) << "Failed to get U2F Service.";
    OnConnectionError();
    return;
  }

  const BluetoothRemoteGattService* u2f_service = *found;
  u2f_service_id_ = u2f_service->GetIdentifier();
  DVLOG(2) << "Got U2F Service: " << *u2f_service_id_;

  for (const auto* characteristic : u2f_service->GetCharacteristics()) {
    // NOTE: Since GetUUID() returns a temporary |uuid| can't be a reference,
    // even though canonical_value() returns a const reference.
    const std::string uuid = characteristic->GetUUID().canonical_value();
    if (uuid == kFidoControlPointLengthUUID) {
      control_point_length_id_ = characteristic->GetIdentifier();
      DVLOG(2) << "Got U2F Control Point Length: " << *control_point_length_id_;
    } else if (uuid == kFidoControlPointUUID) {
      control_point_id_ = characteristic->GetIdentifier();
      DVLOG(2) << "Got U2F Control Point: " << *control_point_id_;
    } else if (uuid == kFidoStatusUUID) {
      status_id_ = characteristic->GetIdentifier();
      DVLOG(2) << "Got U2F Status: " << *status_id_;
    } else if (uuid == kFidoServiceRevisionUUID) {
      service_revision_id_ = characteristic->GetIdentifier();
      DVLOG(2) << "Got U2F Service Revision: " << *service_revision_id_;
    } else if (uuid == kFidoServiceRevisionBitfieldUUID) {
      service_revision_bitfield_id_ = characteristic->GetIdentifier();
      DVLOG(2) << "Got U2F Service Revision Bitfield: "
               << *service_revision_bitfield_id_;
    }
  }

  if (!control_point_length_id_ || !control_point_id_ || !status_id_ ||
      (!service_revision_id_ && !service_revision_bitfield_id_)) {
    DLOG(ERROR) << "U2F characteristics missing.";
    OnConnectionError();
    return;
  }

  u2f_service->GetCharacteristic(*status_id_)
      ->StartNotifySession(
          base::Bind(&FidoBleConnection::OnStartNotifySession,
                     weak_factory_.GetWeakPtr()),
          base::Bind(&FidoBleConnection::OnStartNotifySessionError,
                     weak_factory_.GetWeakPtr()));
}

void FidoBleConnection::OnStartNotifySession(
    std::unique_ptr<BluetoothGattNotifySession> notify_session) {
  notify_session_ = std::move(notify_session);
  DVLOG(2) << "Created notification session. Connection established.";
  connection_status_callback_.Run(true);
}

void FidoBleConnection::OnStartNotifySessionError(
    BluetoothGattService::GattErrorCode error_code) {
  DLOG(ERROR) << "StartNotifySession() failed: " << ToString(error_code);
  OnConnectionError();
}

void FidoBleConnection::OnConnectionError() {
  connection_status_callback_.Run(false);

  connection_.reset();
  notify_session_.reset();

  u2f_service_id_.reset();
  control_point_length_id_.reset();
  control_point_id_.reset();
  status_id_.reset();
  service_revision_id_.reset();
  service_revision_bitfield_id_.reset();
}

const BluetoothRemoteGattService* FidoBleConnection::GetFidoService() const {
  if (!adapter_) {
    DLOG(ERROR) << "No adapter present.";
    return nullptr;
  }

  const BluetoothDevice* device = adapter_->GetDevice(address_);
  if (!device) {
    DLOG(ERROR) << "No device present.";
    return nullptr;
  }

  if (!u2f_service_id_) {
    DLOG(ERROR) << "Unknown U2F service id.";
    return nullptr;
  }

  const BluetoothRemoteGattService* u2f_service =
      device->GetGattService(*u2f_service_id_);
  if (!u2f_service) {
    DLOG(ERROR) << "No U2F service present.";
    return nullptr;
  }

  return u2f_service;
}

void FidoBleConnection::DeviceAdded(BluetoothAdapter* adapter,
                                    BluetoothDevice* device) {
  if (adapter != adapter_ || device->GetAddress() != address_)
    return;
  CreateGattConnection();
}

void FidoBleConnection::DeviceAddressChanged(BluetoothAdapter* adapter,
                                             BluetoothDevice* device,
                                             const std::string& old_address) {
  if (adapter != adapter_ || old_address != address_)
    return;
  address_ = device->GetAddress();
}

void FidoBleConnection::DeviceChanged(BluetoothAdapter* adapter,
                                      BluetoothDevice* device) {
  if (adapter != adapter_ || device->GetAddress() != address_)
    return;
  if (connection_ && !device->IsGattConnected()) {
    DLOG(ERROR) << "GATT Disconnected: " << device->GetAddress();
    OnConnectionError();
  }
}

void FidoBleConnection::GattCharacteristicValueChanged(
    BluetoothAdapter* adapter,
    BluetoothRemoteGattCharacteristic* characteristic,
    const std::vector<uint8_t>& value) {
  if (characteristic->GetIdentifier() != status_id_)
    return;
  DVLOG(2) << "Status characteristic value changed.";
  read_callback_.Run(value);
}

void FidoBleConnection::GattServicesDiscovered(BluetoothAdapter* adapter,
                                               BluetoothDevice* device) {
  if (adapter != adapter_ || device->GetAddress() != address_)
    return;
  ConnectToU2fService();
}

// static
void FidoBleConnection::OnReadControlPointLength(
    ControlPointLengthCallback callback,
    const std::vector<uint8_t>& value) {
  if (value.size() != 2) {
    DLOG(ERROR) << "Wrong Control Point Length: " << value.size() << " bytes";
    std::move(callback).Run(base::nullopt);
    return;
  }

  uint16_t length = (value[0] << 8) | value[1];
  DVLOG(2) << "Control Point Length: " << length;
  std::move(callback).Run(length);
}

// static
void FidoBleConnection::OnReadControlPointLengthError(
    ControlPointLengthCallback callback,
    BluetoothGattService::GattErrorCode error_code) {
  DLOG(ERROR) << "Error reading Control Point Length: " << ToString(error_code);
  std::move(callback).Run(base::nullopt);
}

void FidoBleConnection::OnReadServiceRevision(
    base::OnceClosure callback,
    const std::vector<uint8_t>& value) {
  std::string service_revision(value.begin(), value.end());
  DVLOG(2) << "Service Revision: " << service_revision;

  if (service_revision == "1.0") {
    service_revisions_.insert(ServiceRevision::VERSION_1_0);
  } else if (service_revision == "1.1") {
    service_revisions_.insert(ServiceRevision::VERSION_1_1);
  } else if (service_revision == "1.2") {
    service_revisions_.insert(ServiceRevision::VERSION_1_2);
  } else {
    DLOG(ERROR) << "Unknown Service Revision: " << service_revision;
    std::move(callback).Run();
    return;
  }

  std::move(callback).Run();
}

void FidoBleConnection::OnReadServiceRevisionError(
    base::OnceClosure callback,
    BluetoothGattService::GattErrorCode error_code) {
  DLOG(ERROR) << "Error reading Service Revision: " << ToString(error_code);
  std::move(callback).Run();
}

void FidoBleConnection::OnReadServiceRevisionBitfield(
    base::OnceClosure callback,
    const std::vector<uint8_t>& value) {
  if (value.empty()) {
    DLOG(ERROR) << "Service Revision Bitfield is empty.";
    std::move(callback).Run();
    return;
  }

  if (value.size() != 1u) {
    DLOG(ERROR) << "Service Revision Bitfield has unexpected size: "
                << value.size() << ". Ignoring all but the first byte.";
  }

  const uint8_t bitset = value[0];
  if (bitset & 0x3F) {
    DLOG(ERROR) << "Service Revision Bitfield has unexpected bits set: 0x"
                << std::hex << (bitset & 0x3F)
                << ". Ignoring all but the first two bits.";
  }

  if (bitset & 0x80) {
    service_revisions_.insert(ServiceRevision::VERSION_1_1);
    DVLOG(2) << "Detected Support for Service Revision 1.1";
  }

  if (bitset & 0x40) {
    service_revisions_.insert(ServiceRevision::VERSION_1_2);
    DVLOG(2) << "Detected Support for Service Revision 1.2";
  }

  std::move(callback).Run();
}

void FidoBleConnection::OnReadServiceRevisionBitfieldError(
    base::OnceClosure callback,
    BluetoothGattService::GattErrorCode error_code) {
  DLOG(ERROR) << "Error reading Service Revision Bitfield: "
              << ToString(error_code);
  std::move(callback).Run();
}

void FidoBleConnection::ReturnServiceRevisions(
    ServiceRevisionsCallback callback) {
  std::move(callback).Run(std::move(service_revisions_));
}

// static
void FidoBleConnection::OnWrite(WriteCallback callback) {
  DVLOG(2) << "Write succeeded.";
  std::move(callback).Run(true);
}

// static
void FidoBleConnection::OnWriteError(
    WriteCallback callback,
    BluetoothGattService::GattErrorCode error_code) {
  DLOG(ERROR) << "Write Failed: " << ToString(error_code);
  std::move(callback).Run(false);
}

}  // namespace device
