chromium/chromecast/device/bluetooth/le/remote_characteristic_impl.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 "chromecast/device/bluetooth/le/remote_characteristic_impl.h"

#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/not_fatal_until.h"
#include "base/task/single_thread_task_runner.h"
#include "chromecast/base/bind_to_task_runner.h"
#include "chromecast/device/bluetooth/le/gatt_client_manager_impl.h"
#include "chromecast/device/bluetooth/le/remote_descriptor_impl.h"
#include "chromecast/device/bluetooth/le/remote_device_impl.h"

#define EXEC_CB_AND_RET(cb, ret, ...)        \
  do {                                       \
    if (cb) {                                \
      std::move(cb).Run(ret, ##__VA_ARGS__); \
    }                                        \
    return;                                  \
  } while (0)

#define RUN_ON_IO_THREAD(method, ...) \
  io_task_runner_->PostTask(          \
      FROM_HERE,                      \
      base::BindOnce(&RemoteCharacteristicImpl::method, this, ##__VA_ARGS__));

#define MAKE_SURE_IO_THREAD(method, ...)            \
  DCHECK(io_task_runner_);                          \
  if (!io_task_runner_->BelongsToCurrentThread()) { \
    RUN_ON_IO_THREAD(method, ##__VA_ARGS__)         \
    return;                                         \
  }

namespace chromecast {
namespace bluetooth {

namespace {

std::vector<uint8_t> GetDescriptorNotificationValue(bool notification_enable) {
  if (notification_enable) {
    return std::vector<uint8_t>(
        std::begin(bluetooth::RemoteDescriptor::kEnableNotificationValue),
        std::end(bluetooth::RemoteDescriptor::kEnableNotificationValue));
  }

  return std::vector<uint8_t>(
      std::begin(bluetooth::RemoteDescriptor::kDisableNotificationValue),
      std::end(bluetooth::RemoteDescriptor::kDisableNotificationValue));
}

std::vector<uint8_t> GetDescriptorIndicationValue(bool indication_enable) {
  if (indication_enable) {
    return std::vector<uint8_t>(
        std::begin(bluetooth::RemoteDescriptor::kEnableIndicationValue),
        std::end(bluetooth::RemoteDescriptor::kEnableIndicationValue));
  }

  return std::vector<uint8_t>(
      std::begin(bluetooth::RemoteDescriptor::kDisableNotificationValue),
      std::end(bluetooth::RemoteDescriptor::kDisableNotificationValue));
}

bool CharacteristicHasNotify(
    const bluetooth_v2_shlib::Gatt::Characteristic* characteristic) {
  return characteristic->properties & bluetooth_v2_shlib::Gatt::PROPERTY_NOTIFY;
}

bool CharacteristicHasIndication(
    const bluetooth_v2_shlib::Gatt::Characteristic* characteristic) {
  return characteristic->properties &
         bluetooth_v2_shlib::Gatt::PROPERTY_INDICATE;
}

std::unique_ptr<bluetooth_v2_shlib::Gatt::Descriptor> MaybeCreateFakeCccd(
    const bluetooth_v2_shlib::Gatt::Characteristic* characteristic) {
  if (!CharacteristicHasNotify(characteristic) &&
      !CharacteristicHasIndication(characteristic)) {
    return nullptr;
  }

  for (const auto& descriptor : characteristic->descriptors) {
    if (descriptor.uuid == RemoteDescriptor::kCccdUuid) {
      return nullptr;
    }
  }

  auto cccd = std::make_unique<bluetooth_v2_shlib::Gatt::Descriptor>();
  cccd->uuid = RemoteDescriptor::kCccdUuid;
  cccd->permissions = static_cast<bluetooth_v2_shlib::Gatt::Permissions>(
      bluetooth_v2_shlib::Gatt::PERMISSION_READ |
      bluetooth_v2_shlib::Gatt::PERMISSION_WRITE);
  return cccd;
}

}  // namespace

RemoteCharacteristicImpl::RemoteCharacteristicImpl(
    RemoteDeviceImpl* device,
    base::WeakPtr<GattClientManagerImpl> gatt_client_manager,
    const bluetooth_v2_shlib::Gatt::Characteristic* characteristic,
    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner)
    : device_(device),
      gatt_client_manager_(gatt_client_manager),
      characteristic_(characteristic),
      io_task_runner_(io_task_runner),
      fake_cccd_(MaybeCreateFakeCccd(characteristic)),
      uuid_to_descriptor_(CreateDescriptorMap()) {
  DCHECK(gatt_client_manager);
  DCHECK(characteristic);
  DCHECK(io_task_runner_->BelongsToCurrentThread());
}

RemoteCharacteristicImpl::~RemoteCharacteristicImpl() = default;

std::map<bluetooth_v2_shlib::Uuid, scoped_refptr<RemoteDescriptorImpl>>
RemoteCharacteristicImpl::CreateDescriptorMap() {
  std::map<bluetooth_v2_shlib::Uuid, scoped_refptr<RemoteDescriptorImpl>> ret;
  for (const auto& descriptor : characteristic_->descriptors) {
    ret[descriptor.uuid] = new RemoteDescriptorImpl(
        device_, gatt_client_manager_, &descriptor, io_task_runner_);
  }

  if (fake_cccd_) {
    DCHECK(!base::Contains(ret, RemoteDescriptor::kCccdUuid));
    ret[fake_cccd_->uuid] = new RemoteDescriptorImpl(
        device_, gatt_client_manager_, fake_cccd_.get(), io_task_runner_);
  }

  return ret;
}

std::vector<scoped_refptr<RemoteDescriptor>>
RemoteCharacteristicImpl::GetDescriptors() {
  std::vector<scoped_refptr<RemoteDescriptor>> ret;
  ret.reserve(uuid_to_descriptor_.size());
  for (const auto& pair : uuid_to_descriptor_) {
    ret.push_back(pair.second);
  }

  return ret;
}

scoped_refptr<RemoteDescriptor> RemoteCharacteristicImpl::GetDescriptorByUuid(
    const bluetooth_v2_shlib::Uuid& uuid) {
  auto it = uuid_to_descriptor_.find(uuid);
  if (it == uuid_to_descriptor_.end()) {
    return nullptr;
  }

  return it->second;
}

void RemoteCharacteristicImpl::SetRegisterNotification(bool enable,
                                                       StatusCallback cb) {
  MAKE_SURE_IO_THREAD(SetRegisterNotification, enable,
                      BindToCurrentSequence(std::move(cb)));

  SetRegisterNotificationOrIndicationInternal(false, enable, std::move(cb));
}

void RemoteCharacteristicImpl::SetNotification(bool enable, StatusCallback cb) {
  MAKE_SURE_IO_THREAD(SetNotification, enable,
                      BindToCurrentSequence(std::move(cb)));
  if (!gatt_client_manager_) {
    LOG(ERROR) << __func__ << " failed: Destroyed";
    EXEC_CB_AND_RET(cb, false);
  }
  if (!gatt_client_manager_->gatt_client()->SetCharacteristicNotification(
          device_->addr(), *characteristic_, enable)) {
    LOG(ERROR) << "Set characteristic notification failed";
    EXEC_CB_AND_RET(cb, false);
  }

  notification_enabled_ = enable;
  EXEC_CB_AND_RET(cb, true);
}

void RemoteCharacteristicImpl::SetRegisterNotificationOrIndication(
    bool enable,
    RemoteCharacteristic::StatusCallback cb) {
  MAKE_SURE_IO_THREAD(SetRegisterNotificationOrIndication, enable,
                      BindToCurrentSequence(std::move(cb)));

  if (CharacteristicHasNotify(characteristic_)) {
    SetRegisterNotificationOrIndicationInternal(false, enable, std::move(cb));
  } else if (CharacteristicHasIndication(characteristic_)) {
    SetRegisterNotificationOrIndicationInternal(true, enable, std::move(cb));
  } else {
    LOG(ERROR) << __func__
               << " failed: Characteristic doesn't support notification or "
                  "indication";
    EXEC_CB_AND_RET(cb, false);
  }
}

void RemoteCharacteristicImpl::SetRegisterNotificationOrIndicationInternal(
    bool indication,
    bool enable,
    RemoteCharacteristic::StatusCallback cb) {
  DCHECK(io_task_runner_->BelongsToCurrentThread());

  if (!gatt_client_manager_) {
    LOG(ERROR) << __func__ << " failed: Destroyed";
    EXEC_CB_AND_RET(cb, false);
  }

  if (indication) {
    if (!CharacteristicHasIndication(characteristic_)) {
      LOG(ERROR) << __func__
                 << " failed: Characteristic doesn't support indication";
      EXEC_CB_AND_RET(cb, false);
    }
  } else {
    if (!CharacteristicHasNotify(characteristic_)) {
      LOG(ERROR) << __func__
                 << " failed: Characteristic doesn't support notifications";
      EXEC_CB_AND_RET(cb, false);
    }
  }

  if (notification_enabled_ == enable) {
    EXEC_CB_AND_RET(cb, true);
  }

  if (!gatt_client_manager_->gatt_client()->SetCharacteristicNotification(
          device_->addr(), *characteristic_, enable)) {
    LOG(ERROR) << "Set characteristic notification failed";
    EXEC_CB_AND_RET(cb, false);
  }

  notification_enabled_ = enable;

  // If device has no CCCD and we needed to create a fake one, just return
  // success.
  if (fake_cccd_) {
    EXEC_CB_AND_RET(cb, true);
  }

  auto it = uuid_to_descriptor_.find(RemoteDescriptor::kCccdUuid);
  CHECK(it != uuid_to_descriptor_.end(), base::NotFatalUntil::M130);

  // CCCD must exist. |fake_cccd_| should have been created if it doesn't exist.
  std::vector<uint8_t> write_val = indication
                                       ? GetDescriptorIndicationValue(enable)
                                       : GetDescriptorNotificationValue(enable);
  it->second->WriteAuth(bluetooth_v2_shlib::Gatt::Client::AUTH_REQ_NONE,
                        write_val, std::move(cb));
}

void RemoteCharacteristicImpl::ReadAuth(
    bluetooth_v2_shlib::Gatt::Client::AuthReq auth_req,
    ReadCallback callback) {
  MAKE_SURE_IO_THREAD(ReadAuth, auth_req,
                      BindToCurrentSequence(std::move(callback)));
  DCHECK(callback);
  if (!gatt_client_manager_) {
    LOG(ERROR) << __func__ << " failed: Destroyed";
    EXEC_CB_AND_RET(callback, false, {});
  }

  device_->ReadCharacteristic(this, auth_req, std::move(callback));
}

void RemoteCharacteristicImpl::Read(ReadCallback callback) {
  ReadAuth(bluetooth_v2_shlib::Gatt::Client::AUTH_REQ_INVALID,
           std::move(callback));
}

void RemoteCharacteristicImpl::WriteAuth(
    bluetooth_v2_shlib::Gatt::Client::AuthReq auth_req,
    bluetooth_v2_shlib::Gatt::WriteType write_type,
    const std::vector<uint8_t>& value,
    StatusCallback callback) {
  MAKE_SURE_IO_THREAD(WriteAuth, auth_req, write_type, value,
                      BindToCurrentSequence(std::move(callback)));
  DCHECK(callback);
  if (!gatt_client_manager_) {
    LOG(ERROR) << __func__ << " failed: Destroyed";
    EXEC_CB_AND_RET(callback, false);
  }

  device_->WriteCharacteristic(this, auth_req, write_type, value,
                               std::move(callback));
}

void RemoteCharacteristicImpl::Write(const std::vector<uint8_t>& value,
                                     StatusCallback callback) {
  using WriteType = bluetooth_v2_shlib::Gatt::WriteType;
  using Properties = bluetooth_v2_shlib::Gatt::Properties;

  WriteType write_type = WriteType::WRITE_TYPE_NONE;
  if (properties() & Properties::PROPERTY_WRITE) {
    write_type = WriteType::WRITE_TYPE_DEFAULT;
  } else if (properties() & Properties::PROPERTY_WRITE_NO_RESPONSE) {
    write_type = WriteType::WRITE_TYPE_NO_RESPONSE;
  } else if (properties() & Properties::PROPERTY_SIGNED_WRITE) {
    write_type = WriteType::WRITE_TYPE_SIGNED;
  } else {
    LOG(ERROR) << "Write not supported. Properties: "
               << static_cast<int>(properties());
    EXEC_CB_AND_RET(callback, false);
  }

  WriteAuth(bluetooth_v2_shlib::Gatt::Client::AUTH_REQ_NONE, write_type, value,
            std::move(callback));
}

bool RemoteCharacteristicImpl::NotificationEnabled() {
  return notification_enabled_;
}

const bluetooth_v2_shlib::Gatt::Characteristic&
RemoteCharacteristicImpl::characteristic() const {
  return *characteristic_;
}

const bluetooth_v2_shlib::Uuid& RemoteCharacteristicImpl::uuid() const {
  return characteristic_->uuid;
}

HandleId RemoteCharacteristicImpl::handle() const {
  return characteristic_->handle;
}

bluetooth_v2_shlib::Gatt::Permissions RemoteCharacteristicImpl::permissions()
    const {
  return characteristic_->permissions;
}

bluetooth_v2_shlib::Gatt::Properties RemoteCharacteristicImpl::properties()
    const {
  return characteristic_->properties;
}

void RemoteCharacteristicImpl::Invalidate() {
  DCHECK(io_task_runner_->BelongsToCurrentThread());
  for (auto& item : uuid_to_descriptor_) {
    static_cast<RemoteDescriptorImpl*>(item.second.get())->Invalidate();
  }
  gatt_client_manager_.reset();
}

}  // namespace bluetooth
}  // namespace chromecast