text: // Copyright 2018 The Fuchsia 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 "gatt_remote_service_device.h"
  
  #include <ddk/binding.h>
  #include <zircon/status.h>
  
  #include <memory>
  
  #include "src/connectivity/bluetooth/core/bt-host/common/log.h"
  
  using namespace bt;
  
  namespace bthost {
  
  namespace {
  
  void CopyUUIDBytes(bt_gatt_uuid_t* dest, const UUID source) {
    memcpy(dest->bytes, source.value().data(), sizeof(dest->bytes));
  }
  
  bt_gatt_err_t AttErrorToDdkError(bt::att::ErrorCode error) {
    // Both of these enums *should* be identical and values.
    // Being explicit so we get compiler warnings if either changes.
    switch (error) {
      case bt::att::ErrorCode::kNoError:
        return BT_GATT_ERR_NO_ERROR;
      case bt::att::ErrorCode::kInvalidHandle:
        return BT_GATT_ERR_INVALID_HANDLE;
      case bt::att::ErrorCode::kReadNotPermitted:
        return BT_GATT_ERR_READ_NOT_PERMITTED;
      case bt::att::ErrorCode::kWriteNotPermitted:
        return BT_GATT_ERR_WRITE_NOT_PERMITTED;
      case bt::att::ErrorCode::kInvalidPDU:
        return BT_GATT_ERR_INVALID_PDU;
      case bt::att::ErrorCode::kInsufficientAuthentication:
        return BT_GATT_ERR_INSUFFICIENT_AUTHENTICATION;
      case bt::att::ErrorCode::kRequestNotSupported:
        return BT_GATT_ERR_REQUEST_NOT_SUPPORTED;
      case bt::att::ErrorCode::kInvalidOffset:
        return BT_GATT_ERR_INVALID_OFFSET;
      case bt::att::ErrorCode::kInsufficientAuthorization:
        return BT_GATT_ERR_INSUFFICIENT_AUTHORIZATION;
      case bt::att::ErrorCode::kPrepareQueueFull:
        return BT_GATT_ERR_PREPARE_QUEUE_FULL;
      case bt::att::ErrorCode::kAttributeNotFound:
        return BT_GATT_ERR_ATTRIBUTE_NOT_FOUND;
      case bt::att::ErrorCode::kAttributeNotLong:
        return BT_GATT_ERR_INVALID_ATTRIBUTE_VALUE_LENGTH;
      case bt::att::ErrorCode::kInsufficientEncryptionKeySize:
        return BT_GATT_ERR_INSUFFICIENT_ENCRYPTION_KEY_SIZE;
      case bt::att::ErrorCode::kInvalidAttributeValueLength:
        return BT_GATT_ERR_INVALID_ATTRIBUTE_VALUE_LENGTH;
      case bt::att::ErrorCode::kUnlikelyError:
        return BT_GATT_ERR_UNLIKELY_ERROR;
      case bt::att::ErrorCode::kInsufficientEncryption:
        return BT_GATT_ERR_INSUFFICIENT_ENCRYPTION;
      case bt::att::ErrorCode::kUnsupportedGroupType:
        return BT_GATT_ERR_UNSUPPORTED_GROUP_TYPE;
      case bt::att::ErrorCode::kInsufficientResources:
        return BT_GATT_ERR_INSUFFICIENT_RESOURCES;
    }
    return BT_GATT_ERR_NO_ERROR;
  }
  
  zx_status_t HostErrorToZxError(bt::HostError error) {
    switch (error) {
      case bt::HostError::kNoError:
        return ZX_OK;
      case bt::HostError::kNotFound:
        return ZX_ERR_NOT_FOUND;
      case bt::HostError::kNotReady:
        return ZX_ERR_SHOULD_WAIT;
      case bt::HostError::kTimedOut:
        return ZX_ERR_TIMED_OUT;
      case bt::HostError::kInvalidParameters:
        return ZX_ERR_INVALID_ARGS;
      case bt::HostError::kCanceled:
        return ZX_ERR_CANCELED;
      case bt::HostError::kNotSupported:
        return ZX_ERR_NOT_SUPPORTED;
      case bt::HostError::kLinkDisconnected:
        return ZX_ERR_CONNECTION_ABORTED;
      case bt::HostError::kOutOfMemory:
        return ZX_ERR_NO_MEMORY;
      default:
        return ZX_ERR_INTERNAL;
    }
  }
  
  bt_gatt_status_t AttStatusToDdkStatus(const bt::att::Status& status) {
    bt_gatt_status_t ddk_status = {
        .status = HostErrorToZxError(status.error()),
        .att_ecode = BT_GATT_ERR_NO_ERROR,
    };
  
    if (status.is_protocol_error()) {
      ddk_status.att_ecode = AttErrorToDdkError(status.protocol_error());
    }
    return ddk_status;
  }
  
  }  // namespace
  
  GattRemoteServiceDevice::GattRemoteServiceDevice(
      zx_device_t* parent_device, bt::gatt::PeerId peer_id,
      fbl::RefPtr<bt::gatt::RemoteService> service)
      : loop_(&kAsyncLoopConfigNoAttachToThread),
        parent_device_(parent_device),
        dev_(nullptr),
        peer_id_(peer_id),
        service_(service) {
    dev_proto_.version = DEVICE_OPS_VERSION;
    dev_proto_.unbind = &GattRemoteServiceDevice::DdkUnbind;
    dev_proto_.release = &GattRemoteServiceDevice::DdkRelease;
  }
  
  GattRemoteServiceDevice::~GattRemoteServiceDevice() {
    if (dev_ != nullptr) {
      device_remove(dev_);
      dev_ = nullptr;
    }
  }
  
  bt_gatt_svc_protocol_ops_t GattRemoteServiceDevice::proto_ops_ = {
      .connect = &GattRemoteServiceDevice::OpConnect,
      .stop = &GattRemoteServiceDevice::OpStop,
      .read_characteristic = &GattRemoteServiceDevice::OpReadCharacteristic,
      .read_long_characteristic =
          &GattRemoteServiceDevice::OpReadLongCharacteristic,
      .write_characteristic = &GattRemoteServiceDevice::OpWriteCharacteristic,
      .enable_notifications = &GattRemoteServiceDevice::OpEnableNotifications,
  };
  
  zx_status_t GattRemoteServiceDevice::Bind() {
    // The bind program of an attaching device driver can either bind using to the
    // well known short 16 bit UUID of the service if available or the full 128
    // bit UUID (split across 4 32 bit values).
    const UUID& uuid = service_->uuid();
    uint32_t uuid16 = 0;
  
    if (uuid.CompactSize() == 2) {
      uuid16 =
          le16toh(*reinterpret_cast<const uint16_t*>(uuid.CompactView().data()));
    }
  
    uint32_t uuid01, uuid02, uuid03, uuid04 = 0;
    UInt128 uuid_bytes = uuid.value();
  
    uuid01 = le32toh(*reinterpret_cast<uint32_t*>(&uuid_bytes[0]));
    uuid02 = le32toh(*reinterpret_cast<uint32_t*>(&uuid_bytes[4]));
    uuid03 = le32toh(*reinterpret_cast<uint32_t*>(&uuid_bytes[8]));
    uuid04 = le32toh(*reinterpret_cast<uint32_t*>(&uuid_bytes[12]));
  
    zx_device_prop_t props[] = {
        {BIND_BT_GATT_SVC_UUID16, 0, uuid16},
        {BIND_BT_GATT_SVC_UUID128_1, 0, uuid01},
        {BIND_BT_GATT_SVC_UUID128_2, 0, uuid02},
        {BIND_BT_GATT_SVC_UUID128_3, 0, uuid03},
        {BIND_BT_GATT_SVC_UUID128_4, 0, uuid04},
    };
  
    bt_log(TRACE, "bt-host",
           "bt-gatt-svc binding to UUID16(%#04x), UUID128(1: %08x, 2: %08x,"
           " 3: %08x, 4: %08x), peer: %s",
           uuid16, uuid01, uuid02, uuid03, uuid04, bt_str(peer_id_));
  
    device_add_args_t args = {
        .version = DEVICE_ADD_ARGS_VERSION,
        .name = "bt-gatt-svc",
        .ctx = this,
        .ops = &dev_proto_,
        .proto_id = ZX_PROTOCOL_BT_GATT_SVC,
        .proto_ops = &proto_ops_,
        .props = props,
        .prop_count = 5,
        .flags = 0,
    };
  
    zx_status_t status = device_add(parent_device_, &args, &dev_);
  
    if (status != ZX_OK) {
      dev_ = nullptr;
      bt_log(ERROR, "bt-host",
             "bt-gatt-svc: failed to publish child gatt device: %s",
             zx_status_get_string(status));
      return status;
    }
  
    loop_.StartThread("bt-host bt-gatt-svc");
  
    return status;
  }
  
  void GattRemoteServiceDevice::Unbind() {
    bt_log(TRACE, "bt-host", "bt-gatt-svc: unbinding service");
    async::PostTask(loop_.dispatcher(), [this]() { loop_.Shutdown(); });
    loop_.JoinThreads();
  }
  void GattRemoteServiceDevice::Release() { dev_ = nullptr; }
  
  void GattRemoteServiceDevice::Connect(bt_gatt_svc_connect_callback connect_cb,
                                        void* cookie) {
    async::PostTask(loop_.dispatcher(), [this, connect_cb, cookie]() {
      service_->DiscoverCharacteristics(
          [connect_cb, cookie](att::Status cb_status, const auto& chrcs) {
            auto ddk_chars = std::make_unique<bt_gatt_chr[]>(chrcs.size());
            size_t char_idx = 0;
            for (auto& chr : chrcs) {
              ddk_chars[char_idx].id = static_cast<bt_gatt_id_t>(chr.id());
              CopyUUIDBytes(&ddk_chars[char_idx].type, chr.info().type);
              ddk_chars[char_idx].properties = chr.info().properties;
  
              // TODO(zbowling): remote extended properties are not implemented.
              // ddk_chars[char_idx].extended_properties =
              // chr.info().extended_properties;
  
              auto& descriptors = chr.descriptors();
              if (descriptors.size() > 0) {
                ddk_chars[char_idx].descriptor_list =
                    new bt_gatt_descriptor_t[descriptors.size()];
                ddk_chars[char_idx].descriptor_count = descriptors.size();
                size_t desc_idx = 0;
                for (auto& descriptor : descriptors) {
                  ddk_chars[char_idx].descriptor_list[desc_idx].id =
                      static_cast<bt_gatt_id_t>(descriptor.id());
                  CopyUUIDBytes(
                      &ddk_chars[char_idx].descriptor_list[desc_idx].type,
                      descriptor.info().type);
                  desc_idx++;
                }
              } else {
                ddk_chars[char_idx].descriptor_count = 0;
                ddk_chars[char_idx].descriptor_list = nullptr;
              }
  
              char_idx++;
            }
  
            bt_log(TRACE, "bt-host",
                   "bt-gatt-svc: connected; discovered %zu characteristics",
                   char_idx);
            bt_gatt_status_t status = {.status = ZX_OK};
            connect_cb(cookie, &status, ddk_chars.get(), char_idx);
  
            // Cleanup.
            for (char_idx = 0; char_idx < chrcs.size(); char_idx++) {
              if (ddk_chars[char_idx].descriptor_list != nullptr) {
                delete[] ddk_chars[char_idx].descriptor_list;
                ddk_chars[char_idx].descriptor_list = nullptr;
              }
            }
          },
          loop_.dispatcher());
    });
  
    return;
  }
  
  void GattRemoteServiceDevice::Stop() {
    // TODO(zbowling): Unregister notifications on the remote service.
    // We may replace this with an explicit unregister for notifications instead.
  }
  
  void GattRemoteServiceDevice::ReadCharacteristic(
      bt_gatt_id_t id, bt_gatt_svc_read_characteristic_callback read_cb,
      void* cookie) {
    auto read_callback = [id, cookie, read_cb](att::Status status,
                                               const ByteBuffer& buff) {
      bt_gatt_status_t ddk_status = AttStatusToDdkStatus(status);
      read_cb(cookie, &ddk_status, id, buff.data(), buff.size());
    };
    service_->ReadCharacteristic(static_cast<bt::gatt::IdType>(id),
                                 std::move(read_callback), loop_.dispatcher());
  
    return;
  }
  
  void GattRemoteServiceDevice::ReadLongCharacteristic(
      bt_gatt_id_t id, uint16_t offset, size_t max_bytes,
      bt_gatt_svc_read_characteristic_callback read_cb, void* cookie) {
    auto read_callback = [id, cookie, read_cb](att::Status status,
                                               const ByteBuffer& buff) {
      bt_gatt_status_t ddk_status = AttStatusToDdkStatus(status);
      read_cb(cookie, &ddk_status, id, buff.data(), buff.size());
    };
    service_->ReadLongCharacteristic(static_cast<bt::gatt::IdType>(id), offset,
                                     max_bytes, std::move(read_callback),
                                     loop_.dispatcher());
  
    return;
  }
  
  void GattRemoteServiceDevice::WriteCharacteristic(
      bt_gatt_id_t id, const void* buff, size_t len,
      bt_gatt_svc_write_characteristic_callback write_cb, void* cookie) {
    auto* buf = static_cast<const uint8_t*>(buff);
    std::vector<uint8_t> data(buf, buf + len);
    if (write_cb == nullptr) {
      service_->WriteCharacteristicWithoutResponse(
          static_cast<bt::gatt::IdType>(id), std::move(data));
    } else {
      auto status_callback = [cookie, id, write_cb](bt::att::Status status) {
        bt_gatt_status_t ddk_status = AttStatusToDdkStatus(status);
        write_cb(cookie, &ddk_status, id);
      };
  
      service_->WriteCharacteristic(static_cast<bt::gatt::IdType>(id),
                                    std::move(data), std::move(status_callback),
                                    loop_.dispatcher());
    }
    return;
  }
  
  void GattRemoteServiceDevice::EnableNotifications(
      bt_gatt_id_t id, const bt_gatt_notification_value_t* value,
      bt_gatt_svc_enable_notifications_callback status_cb, void* cookie) {
    auto value_cb = *value;
    auto notif_callback = [cookie, id, value_cb](const ByteBuffer& buff) {
      value_cb.callback(value_cb.ctx, id, buff.data(), buff.size());
    };
  
    auto status_callback = [cookie, id, status_cb](bt::att::Status status,
                                                   bt::gatt::IdType handler_id) {
      bt_gatt_status_t ddk_status = AttStatusToDdkStatus(status);
      status_cb(cookie, &ddk_status, id);
    };
  
    service_->EnableNotifications(static_cast<bt::gatt::IdType>(id),
                                  notif_callback, std::move(status_callback),
                                  loop_.dispatcher());
  
    return;
  }
  
  }  // namespace bthost
  