// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "device/bluetooth/floss/bluetooth_remote_gatt_characteristic_floss.h"

#include "base/containers/to_vector.h"
#include "base/memory/ptr_util.h"
#include "base/notreached.h"
#include "base/strings/stringprintf.h"
#include "device/bluetooth/bluetooth_gatt_service.h"
#include "device/bluetooth/bluetooth_remote_gatt_descriptor.h"
#include "device/bluetooth/floss/bluetooth_adapter_floss.h"
#include "device/bluetooth/floss/bluetooth_device_floss.h"
#include "device/bluetooth/floss/bluetooth_gatt_service_floss.h"
#include "device/bluetooth/floss/bluetooth_remote_gatt_descriptor_floss.h"
#include "device/bluetooth/floss/bluetooth_remote_gatt_service_floss.h"
#include "device/bluetooth/floss/floss_dbus_manager.h"
#include "device/bluetooth/floss/floss_gatt_manager_client.h"

namespace floss {

// static
std::unique_ptr<BluetoothRemoteGattCharacteristicFloss>
BluetoothRemoteGattCharacteristicFloss::Create(
    BluetoothRemoteGattServiceFloss* service,
    GattCharacteristic* characteristic) {
  return base::WrapUnique(
      new BluetoothRemoteGattCharacteristicFloss(service, characteristic));
}

BluetoothRemoteGattCharacteristicFloss::BluetoothRemoteGattCharacteristicFloss(
    BluetoothRemoteGattServiceFloss* service,
    GattCharacteristic* characteristic)
    : characteristic_(characteristic), service_(service) {
  DCHECK(service);
  DCHECK(service->GetDevice());
  DCHECK(characteristic);

  service_->AddObserverForHandle(characteristic_->instance_id, this);
  device_address_ = service_->GetDevice()->GetAddress();

  for (GattDescriptor& d : characteristic_->descriptors) {
    AddDescriptor(
        BluetoothRemoteGattDescriptorFloss::Create(service_, this, &d));
  }
}

BluetoothRemoteGattCharacteristicFloss::
    ~BluetoothRemoteGattCharacteristicFloss() {
  // Reply to pending callbacks
  if (std::get<1>(pending_write_callbacks_)) {
    auto [callback, error_callback, data] = std::move(pending_write_callbacks_);
    if (error_callback) {
      std::move(error_callback)
          .Run(BluetoothGattServiceFloss::GattErrorCode::kUnknown);
    }
  }
  if (pending_read_callback_) {
    std::move(pending_read_callback_)
        .Run(BluetoothGattServiceFloss::GattErrorCode::kUnknown, {});
  }

  descriptors_.clear();
  service_->RemoveObserverForHandle(characteristic_->instance_id);
}

std::string BluetoothRemoteGattCharacteristicFloss::GetIdentifier() const {
  return base::StringPrintf("%s/%04x", service_->GetIdentifier().c_str(),
                            characteristic_->instance_id);
}

device::BluetoothUUID BluetoothRemoteGattCharacteristicFloss::GetUUID() const {
  return characteristic_->uuid;
}

BluetoothRemoteGattCharacteristicFloss::Properties
BluetoothRemoteGattCharacteristicFloss::GetProperties() const {
  const auto& [props, perms] = ConvertPropsAndPermsFromFloss(
      characteristic_->properties, characteristic_->permissions);
  return props;
}

BluetoothRemoteGattCharacteristicFloss::Permissions
BluetoothRemoteGattCharacteristicFloss::GetPermissions() const {
  const auto& [props, perms] = ConvertPropsAndPermsFromFloss(
      characteristic_->properties, characteristic_->permissions);
  return perms;
}

const std::vector<uint8_t>& BluetoothRemoteGattCharacteristicFloss::GetValue()
    const {
  return cached_data_;
}

device::BluetoothRemoteGattService*
BluetoothRemoteGattCharacteristicFloss::GetService() const {
  return static_cast<device::BluetoothRemoteGattService*>(service_.get());
}

void BluetoothRemoteGattCharacteristicFloss::ReadRemoteCharacteristic(
    ValueCallback callback) {
  DCHECK_GE(num_of_reads_in_progress_, 0);
  ++num_of_reads_in_progress_;

  AuthRequired auth = GetAuthForRead();

  FlossDBusManager::Get()->GetGattManagerClient()->ReadCharacteristic(
      base::BindOnce(
          &BluetoothRemoteGattCharacteristicFloss::OnReadCharacteristic,
          weak_ptr_factory_.GetWeakPtr(), std::move(callback)),
      device_address_, characteristic_->instance_id, auth);
}

void BluetoothRemoteGattCharacteristicFloss::WriteRemoteCharacteristic(
    base::span<const uint8_t> value,
    device::BluetoothRemoteGattCharacteristic::WriteType write_type,
    base::OnceClosure callback,
    ErrorCallback error_callback) {
  floss::WriteType gatt_write_type = floss::WriteType::kWriteNoResponse;
  if (write_type ==
      device::BluetoothRemoteGattCharacteristic::WriteType::kWithResponse) {
    gatt_write_type = floss::WriteType::kWrite;
  }

  WriteRemoteCharacteristicImpl(value, gatt_write_type, std::move(callback),
                                std::move(error_callback));
}

void BluetoothRemoteGattCharacteristicFloss::
    DeprecatedWriteRemoteCharacteristic(base::span<const uint8_t> value,
                                        base::OnceClosure callback,
                                        ErrorCallback error_callback) {
  Properties props = GetProperties();
  floss::WriteType write_type = floss::WriteType::kWrite;
  if (props & PROPERTY_WRITE_WITHOUT_RESPONSE) {
    write_type = floss::WriteType::kWriteNoResponse;
  }

  WriteRemoteCharacteristicImpl(value, write_type, std::move(callback),
                                std::move(error_callback));
}

#if BUILDFLAG(IS_CHROMEOS)
void BluetoothRemoteGattCharacteristicFloss::PrepareWriteRemoteCharacteristic(
    base::span<const uint8_t> value,
    base::OnceClosure callback,
    ErrorCallback error_callback) {
  // Make sure we're using reliable writes before starting a prepared write.
  BluetoothDeviceFloss* device =
      static_cast<BluetoothDeviceFloss*>(service_->GetDevice());
  if (device && !device->UsingReliableWrite()) {
    device->BeginReliableWrite();
  }

  WriteRemoteCharacteristicImpl(value, floss::WriteType::kWritePrepare,
                                std::move(callback), std::move(error_callback));
}
#endif  // BUILDFLAG(IS_CHROMEOS)

void BluetoothRemoteGattCharacteristicFloss::WriteRemoteCharacteristicImpl(
    base::span<const uint8_t> value,
    floss::WriteType write_type,
    base::OnceClosure callback,
    ErrorCallback error_callback) {
  AuthRequired auth = GetAuthForWrite();

  FlossDBusManager::Get()->GetGattManagerClient()->WriteCharacteristic(
      base::BindOnce(
          &BluetoothRemoteGattCharacteristicFloss::OnWriteCharacteristic,
          weak_ptr_factory_.GetWeakPtr(), std::move(callback),
          std::move(error_callback), base::ToVector(value)),
      device_address_, characteristic_->instance_id, write_type, auth, value);
}

void BluetoothRemoteGattCharacteristicFloss::GattCharacteristicRead(
    std::string address,
    GattStatus status,
    int32_t handle,
    const std::vector<uint8_t>& data) {
  // Make sure this notification is for this characteristic.
  if (handle != characteristic_->instance_id || address != device_address_) {
    return;
  }

  if (num_of_reads_in_progress_ == 0 || !pending_read_callback_) {
    return;
  }

  --num_of_reads_in_progress_;
  DCHECK_GE(num_of_reads_in_progress_, 0);

  if (status == GattStatus::kSuccess) {
    cached_data_ = data;

    std::move(pending_read_callback_)
        .Run(/*error_code=*/std::nullopt, cached_data_);
  } else {
    std::move(pending_read_callback_)
        .Run(BluetoothGattServiceFloss::GattStatusToServiceError(status), {});
  }
}

void BluetoothRemoteGattCharacteristicFloss::GattCharacteristicWrite(
    std::string address,
    GattStatus status,
    int32_t handle) {
  // Make sure this notification is for this characteristic.
  if (handle != characteristic_->instance_id || address != device_address_) {
    return;
  }

  auto [callback, error_callback, data] = std::move(pending_write_callbacks_);

  if (!callback || !error_callback) {
    return;
  }

  if (status == GattStatus::kSuccess) {
    cached_data_ = data;

    std::move(callback).Run();
  } else {
    std::move(error_callback)
        .Run(BluetoothGattServiceFloss::GattStatusToServiceError(status));
  }
}

void BluetoothRemoteGattCharacteristicFloss::GattNotify(
    std::string address,
    int32_t handle,
    const std::vector<uint8_t>& data) {
  // Make sure this notification is for this characteristic.
  if (handle != characteristic_->instance_id || address != device_address_) {
    return;
  }

  cached_data_ = data;
  NotifyValueChanged();
}

void BluetoothRemoteGattCharacteristicFloss::OnReadCharacteristic(
    ValueCallback callback,
    DBusResult<Void> result) {
  if (!result.has_value()) {
    --num_of_reads_in_progress_;
    DCHECK_GE(num_of_reads_in_progress_, 0);

    std::move(callback).Run(
        /*error_code=*/BluetoothGattServiceFloss::GattErrorCode::kFailed, {});
    return;
  }

  pending_read_callback_ = std::move(callback);
}

void BluetoothRemoteGattCharacteristicFloss::OnWriteCharacteristic(
    base::OnceClosure callback,
    ErrorCallback error_callback,
    std::vector<uint8_t> data,
    DBusResult<GattWriteRequestStatus> result) {
  if (!result.has_value()) {
    std::move(error_callback)
        .Run(/*error_code=*/BluetoothGattServiceFloss::GattErrorCode::kFailed);
    return;
  }

  if (result.value() != GattWriteRequestStatus::kSuccess) {
    BluetoothGattServiceFloss::GattErrorCode error_code =
        (result.value() == GattWriteRequestStatus::kBusy
             ? BluetoothGattServiceFloss::GattErrorCode::kInProgress
             : BluetoothGattServiceFloss::GattErrorCode::kFailed);
    std::move(error_callback).Run(error_code);
    return;
  }

  pending_write_callbacks_ = std::make_tuple(
      std::move(callback), std::move(error_callback), std::move(data));
}

void BluetoothRemoteGattCharacteristicFloss::SubscribeToNotifications(
    device::BluetoothRemoteGattDescriptor* ccc_descriptor,
#if BUILDFLAG(IS_CHROMEOS)
    NotificationType notification_type,
#endif  // BUILDFLAG(IS_CHROMEOS)
    base::OnceClosure callback,
    ErrorCallback error_callback) {
#if !BUILDFLAG(IS_CHROMEOS)
  NotificationType notification_type = NotificationType::kNotification;
#endif

  // Set CCCD value to notification type
  std::vector<uint8_t> value = {static_cast<uint8_t>(notification_type), 0};

  // Register this characteristic for notifications
  FlossDBusManager::Get()->GetGattManagerClient()->RegisterForNotification(
      base::BindOnce(
          &BluetoothRemoteGattCharacteristicFloss::OnRegisterForNotification,
          weak_ptr_factory_.GetWeakPtr(), ccc_descriptor, value,
          std::move(callback), std::move(error_callback)),
      device_address_, characteristic_->instance_id);
}

void BluetoothRemoteGattCharacteristicFloss::UnsubscribeFromNotifications(
    device::BluetoothRemoteGattDescriptor* ccc_descriptor,
    base::OnceClosure callback,
    ErrorCallback error_callback) {
  // Set CCCD value back to default
  std::vector<uint8_t> value = {0, 0};

  // Unregister this characteristic for notifications
  FlossDBusManager::Get()->GetGattManagerClient()->UnregisterNotification(
      base::BindOnce(
          &BluetoothRemoteGattCharacteristicFloss::OnRegisterForNotification,
          weak_ptr_factory_.GetWeakPtr(), ccc_descriptor, value,
          std::move(callback), std::move(error_callback)),
      device_address_, characteristic_->instance_id);
}

void BluetoothRemoteGattCharacteristicFloss::OnRegisterForNotification(
    device::BluetoothRemoteGattDescriptor* ccc_descriptor,
    const std::vector<uint8_t>& value,
    base::OnceClosure callback,
    ErrorCallback error_callback,
    DBusResult<GattStatus> result) {
  if (!result.has_value() || result.value() != GattStatus::kSuccess) {
    std::move(error_callback)
        .Run(/*error_code=*/BluetoothGattServiceFloss::GattStatusToServiceError(
            result.value()));

    return;
  }

  BluetoothRemoteGattDescriptorFloss* descriptor =
      static_cast<BluetoothRemoteGattDescriptorFloss*>(ccc_descriptor);

  DCHECK(descriptor);

  // Write to the Client Characteristic Configuration descriptor.
  descriptor->WriteRemoteDescriptor(value, std::move(callback),
                                    std::move(error_callback));
}

AuthRequired BluetoothRemoteGattCharacteristicFloss::GetAuthForRead() const {
  AuthRequired auth = AuthRequired::kNoAuth;
  Properties props = GetProperties();

  if (props & PROPERTY_READ_ENCRYPTED_AUTHENTICATED) {
    auth = AuthRequired::kReqMitm;
  } else if (props & PROPERTY_READ_ENCRYPTED) {
    auth = AuthRequired::kNoMitm;
  }

  return auth;
}

AuthRequired BluetoothRemoteGattCharacteristicFloss::GetAuthForWrite() const {
  AuthRequired auth = AuthRequired::kNoAuth;
  Properties props = GetProperties();

  if (props & PROPERTY_WRITE_ENCRYPTED_AUTHENTICATED) {
    auth = AuthRequired::kReqMitm;
    if (props & PROPERTY_AUTHENTICATED_SIGNED_WRITES) {
      auth = AuthRequired::kSignedReqMitm;
    }
  } else if (props & PROPERTY_WRITE_ENCRYPTED) {
    auth = AuthRequired::kNoMitm;
    if (props & PROPERTY_AUTHENTICATED_SIGNED_WRITES) {
      auth = AuthRequired::kSignedNoMitm;
    }
  }

  return auth;
}

void BluetoothRemoteGattCharacteristicFloss::NotifyValueChanged() {
  DCHECK(service_->GetAdapter());

  service_->GetAdapter()->NotifyGattCharacteristicValueChanged(this,
                                                               cached_data_);
}

}  // namespace floss
