blob: cc30767de9564661ec52051419b2d45acc736eff [file] [log] [blame]
// Copyright 2021 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 "gatt2_remote_service_server.h"
#include <utility>
#include "src/connectivity/bluetooth/core/bt-host/fidl/helpers.h"
#include "src/connectivity/bluetooth/core/bt-host/fidl/measure_tape/hlcpp_measure_tape_for_read_by_type_result.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/att/att.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/identifier.h"
namespace fbg = fuchsia::bluetooth::gatt2;
namespace measure_fbg = measure_tape::fuchsia::bluetooth::gatt2;
namespace bthost {
namespace {
bt::att::ResultFunction<> MakeStatusCallback(
bt::PeerId peer_id, const char* request_name, fbg::Handle fidl_handle,
fit::function<void(fpromise::result<void, fbg::Error>)> callback) {
return [peer_id, fidl_handle, callback = std::move(callback),
request_name](bt::att::Result<> status) {
if (bt_is_error(status, INFO, "fidl", "%s: error (peer: %s, handle: 0x%lX)", request_name,
bt_str(peer_id), fidl_handle.value)) {
callback(fpromise::error(fidl_helpers::AttErrorToGattFidlError(status.error_value())));
return;
}
callback(fpromise::ok());
};
}
fbg::Characteristic CharacteristicToFidl(
const bt::gatt::CharacteristicData& characteristic,
const std::map<bt::gatt::DescriptorHandle, bt::gatt::DescriptorData>& descriptors) {
fbg::Characteristic fidl_char;
fidl_char.set_handle(fbg::Handle{characteristic.value_handle});
fidl_char.set_type(fuchsia::bluetooth::Uuid{characteristic.type.value()});
// The FIDL property bitfield combines the properties and extended properties bits.
// We mask away the kExtendedProperties property.
constexpr uint8_t kRemoveExtendedPropertiesMask = 0x7F;
fbg::CharacteristicPropertyBits fidl_properties = static_cast<fbg::CharacteristicPropertyBits>(
characteristic.properties & kRemoveExtendedPropertiesMask);
if (characteristic.extended_properties) {
if (*characteristic.extended_properties & bt::gatt::ExtendedProperty::kReliableWrite) {
fidl_properties |= fbg::CharacteristicPropertyBits::RELIABLE_WRITE;
}
if (*characteristic.extended_properties & bt::gatt::ExtendedProperty::kWritableAuxiliaries) {
fidl_properties |= fbg::CharacteristicPropertyBits::WRITABLE_AUXILIARIES;
}
}
fidl_char.set_properties(fidl_properties);
if (!descriptors.empty()) {
std::vector<fbg::Descriptor> fidl_descriptors;
for (const auto& [handle, data] : descriptors) {
fbg::Descriptor fidl_descriptor;
fidl_descriptor.set_handle(fbg::Handle{handle.value});
fidl_descriptor.set_type(fuchsia::bluetooth::Uuid{data.type.value()});
fidl_descriptors.push_back(std::move(fidl_descriptor));
}
fidl_char.set_descriptors(std::move(fidl_descriptors));
}
return fidl_char;
}
// Returned result is supposed to match Read{Characteristic, Descriptor}Callback (result type is
// converted by FIDL move constructor).
[[nodiscard]] fpromise::result<::fuchsia::bluetooth::gatt2::ReadValue,
::fuchsia::bluetooth::gatt2::Error>
ReadResultToFidl(bt::PeerId peer_id, fbg::Handle handle, bt::att::Result<> status,
const bt::ByteBuffer& value, bool maybe_truncated, const char* request) {
if (bt_is_error(status, INFO, "fidl", "%s: error (peer: %s, handle: 0x%lX)", request,
bt_str(peer_id), handle.value)) {
return fpromise::error(fidl_helpers::AttErrorToGattFidlError(status.error_value()));
}
fbg::ReadValue fidl_value;
fidl_value.set_handle(handle);
fidl_value.set_value(value.ToVector());
fidl_value.set_maybe_truncated(maybe_truncated);
return fpromise::ok(std::move(fidl_value));
}
void FillInReadOptionsDefaults(fbg::ReadOptions& options) {
if (options.is_short_read()) {
return;
}
if (!options.long_read().has_offset()) {
options.long_read().set_offset(0);
}
if (!options.long_read().has_max_bytes()) {
options.long_read().set_max_bytes(fbg::MAX_VALUE_LENGTH);
}
}
void FillInDefaultWriteOptions(fbg::WriteOptions& options) {
if (!options.has_write_mode()) {
*options.mutable_write_mode() = fbg::WriteMode::DEFAULT;
}
if (!options.has_offset()) {
*options.mutable_offset() = 0;
}
}
bt::gatt::ReliableMode ReliableModeFromFidl(const fbg::WriteMode& mode) {
return mode == fbg::WriteMode::RELIABLE ? bt::gatt::ReliableMode::kEnabled
: bt::gatt::ReliableMode::kDisabled;
}
} // namespace
Gatt2RemoteServiceServer::Gatt2RemoteServiceServer(
bt::gatt::RemoteService::WeakPtr service, bt::gatt::GATT::WeakPtr gatt, bt::PeerId peer_id,
fidl::InterfaceRequest<fuchsia::bluetooth::gatt2::RemoteService> request)
: GattServerBase(std::move(gatt), this, std::move(request)),
service_(std::move(service)),
peer_id_(peer_id),
weak_self_(this) {}
Gatt2RemoteServiceServer::~Gatt2RemoteServiceServer() {
// Disable all notifications to prevent leaks.
for (auto& [_, notifier] : characteristic_notifiers_) {
service_->DisableNotifications(notifier.characteristic_handle, notifier.handler_id,
/*status_callback=*/[](auto /*status*/) {});
}
characteristic_notifiers_.clear();
}
void Gatt2RemoteServiceServer::Close(zx_status_t status) { binding()->Close(status); }
void Gatt2RemoteServiceServer::DiscoverCharacteristics(DiscoverCharacteristicsCallback callback) {
auto res_cb = [callback = std::move(callback)](
bt::att::Result<> status, const bt::gatt::CharacteristicMap& characteristics) {
if (status.is_error()) {
callback({});
return;
}
std::vector<fbg::Characteristic> fidl_characteristics;
for (const auto& [_, characteristic] : characteristics) {
const auto& [data, descriptors] = characteristic;
fidl_characteristics.push_back(CharacteristicToFidl(data, descriptors));
}
callback(std::move(fidl_characteristics));
};
service_->DiscoverCharacteristics(std::move(res_cb));
}
void Gatt2RemoteServiceServer::ReadByType(::fuchsia::bluetooth::Uuid uuid,
ReadByTypeCallback callback) {
service_->ReadByType(
fidl_helpers::UuidFromFidl(uuid),
[self = weak_self_.GetWeakPtr(), cb = std::move(callback), func = __FUNCTION__](
bt::att::Result<> status,
std::vector<bt::gatt::RemoteService::ReadByTypeResult> results) {
if (!self.is_alive()) {
return;
}
if (status == ToResult(bt::HostError::kInvalidParameters)) {
bt_log(WARN, "fidl", "%s: called with invalid parameters (peer: %s)", func,
bt_str(self->peer_id_));
cb(fpromise::error(fbg::Error::INVALID_PARAMETERS));
return;
} else if (status.is_error()) {
cb(fpromise::error(fbg::Error::UNLIKELY_ERROR));
return;
}
const size_t kVectorOverhead = sizeof(fidl_message_header_t) + sizeof(fidl_vector_t);
const size_t kMaxBytes = ZX_CHANNEL_MAX_MSG_BYTES - kVectorOverhead;
size_t bytes_used = 0;
std::vector<fuchsia::bluetooth::gatt2::ReadByTypeResult> fidl_results;
fidl_results.reserve(results.size());
for (const bt::gatt::RemoteService::ReadByTypeResult& result : results) {
fuchsia::bluetooth::gatt2::ReadByTypeResult fidl_result;
fidl_result.set_handle(fbg::Handle{result.handle.value});
if (result.result.is_ok()) {
fbg::ReadValue read_value;
read_value.set_handle(fbg::Handle{result.handle.value});
read_value.set_value(result.result.value()->ToVector());
read_value.set_maybe_truncated(result.maybe_truncated);
fidl_result.set_value(std::move(read_value));
} else {
fidl_result.set_error(
fidl_helpers::AttErrorToGattFidlError(bt::att::Error(result.result.error_value())));
}
measure_fbg::Size result_size = measure_fbg::Measure(fidl_result);
BT_ASSERT(result_size.num_handles == 0);
bytes_used += result_size.num_bytes;
if (bytes_used > kMaxBytes) {
cb(fpromise::error(fuchsia::bluetooth::gatt2::Error::TOO_MANY_RESULTS));
return;
}
fidl_results.push_back(std::move(fidl_result));
}
cb(fpromise::ok(std::move(fidl_results)));
});
}
void Gatt2RemoteServiceServer::ReadCharacteristic(fbg::Handle fidl_handle, fbg::ReadOptions options,
ReadCharacteristicCallback callback) {
if (!fidl_helpers::IsFidlGattHandleValid(fidl_handle)) {
callback(fpromise::error(fbg::Error::INVALID_HANDLE));
return;
}
bt::gatt::CharacteristicHandle handle(static_cast<bt::att::Handle>(fidl_handle.value));
FillInReadOptionsDefaults(options);
const char* kRequestName = __FUNCTION__;
bt::gatt::RemoteService::ReadValueCallback read_cb =
[peer_id = peer_id_, fidl_handle, kRequestName, callback = std::move(callback)](
bt::att::Result<> status, const bt::ByteBuffer& value, bool maybe_truncated) {
callback(
ReadResultToFidl(peer_id, fidl_handle, status, value, maybe_truncated, kRequestName));
};
if (options.is_short_read()) {
service_->ReadCharacteristic(handle, std::move(read_cb));
return;
}
service_->ReadLongCharacteristic(handle, options.long_read().offset(),
options.long_read().max_bytes(), std::move(read_cb));
}
void Gatt2RemoteServiceServer::WriteCharacteristic(fbg::Handle fidl_handle,
std::vector<uint8_t> value,
fbg::WriteOptions options,
WriteCharacteristicCallback callback) {
if (!fidl_helpers::IsFidlGattHandleValid(fidl_handle)) {
callback(fpromise::error(fbg::Error::INVALID_HANDLE));
return;
}
bt::gatt::CharacteristicHandle handle(static_cast<bt::att::Handle>(fidl_handle.value));
FillInDefaultWriteOptions(options);
bt::att::ResultFunction<> write_cb =
MakeStatusCallback(peer_id_, __FUNCTION__, fidl_handle, std::move(callback));
if (options.write_mode() == fbg::WriteMode::WITHOUT_RESPONSE) {
if (options.offset() != 0) {
write_cb(bt::ToResult(bt::HostError::kInvalidParameters));
return;
}
service_->WriteCharacteristicWithoutResponse(handle, std::move(value), std::move(write_cb));
return;
}
const uint16_t kMaxShortWriteValueLength =
service_->att_mtu() - sizeof(bt::att::OpCode) - sizeof(bt::att::WriteRequestParams);
if (options.offset() == 0 && options.write_mode() == fbg::WriteMode::DEFAULT &&
value.size() <= kMaxShortWriteValueLength) {
service_->WriteCharacteristic(handle, std::move(value), std::move(write_cb));
return;
}
service_->WriteLongCharacteristic(handle, options.offset(), std::move(value),
ReliableModeFromFidl(options.write_mode()),
std::move(write_cb));
}
void Gatt2RemoteServiceServer::ReadDescriptor(::fuchsia::bluetooth::gatt2::Handle fidl_handle,
::fuchsia::bluetooth::gatt2::ReadOptions options,
ReadDescriptorCallback callback) {
if (!fidl_helpers::IsFidlGattHandleValid(fidl_handle)) {
callback(fpromise::error(fbg::Error::INVALID_HANDLE));
return;
}
bt::gatt::DescriptorHandle handle(static_cast<bt::att::Handle>(fidl_handle.value));
FillInReadOptionsDefaults(options);
const char* kRequestName = __FUNCTION__;
bt::gatt::RemoteService::ReadValueCallback read_cb =
[peer_id = peer_id_, fidl_handle, kRequestName, callback = std::move(callback)](
bt::att::Result<> status, const bt::ByteBuffer& value, bool maybe_truncated) {
callback(
ReadResultToFidl(peer_id, fidl_handle, status, value, maybe_truncated, kRequestName));
};
if (options.is_short_read()) {
service_->ReadDescriptor(handle, std::move(read_cb));
return;
}
service_->ReadLongDescriptor(handle, options.long_read().offset(),
options.long_read().max_bytes(), std::move(read_cb));
}
void Gatt2RemoteServiceServer::WriteDescriptor(fbg::Handle fidl_handle, std::vector<uint8_t> value,
fbg::WriteOptions options,
WriteDescriptorCallback callback) {
if (!fidl_helpers::IsFidlGattHandleValid(fidl_handle)) {
callback(fpromise::error(fbg::Error::INVALID_HANDLE));
return;
}
bt::gatt::DescriptorHandle handle(static_cast<bt::att::Handle>(fidl_handle.value));
FillInDefaultWriteOptions(options);
bt::att::ResultFunction<> write_cb =
MakeStatusCallback(peer_id_, __FUNCTION__, fidl_handle, std::move(callback));
// WITHOUT_RESPONSE and RELIABLE write modes are not supported for descriptors.
if (options.write_mode() == fbg::WriteMode::WITHOUT_RESPONSE ||
options.write_mode() == fbg::WriteMode::RELIABLE) {
write_cb(bt::ToResult(bt::HostError::kInvalidParameters));
return;
}
const uint16_t kMaxShortWriteValueLength =
service_->att_mtu() - sizeof(bt::att::OpCode) - sizeof(bt::att::WriteRequestParams);
if (options.offset() == 0 && value.size() <= kMaxShortWriteValueLength) {
service_->WriteDescriptor(handle, std::move(value), std::move(write_cb));
return;
}
service_->WriteLongDescriptor(handle, options.offset(), std::move(value), std::move(write_cb));
}
void Gatt2RemoteServiceServer::RegisterCharacteristicNotifier(
fbg::Handle fidl_handle, fidl::InterfaceHandle<fbg::CharacteristicNotifier> notifier_handle,
RegisterCharacteristicNotifierCallback callback) {
bt::gatt::CharacteristicHandle char_handle(static_cast<bt::att::Handle>(fidl_handle.value));
NotifierId notifier_id = next_notifier_id_++;
auto self = weak_self_.GetWeakPtr();
auto value_cb = [self, notifier_id, fidl_handle](const bt::ByteBuffer& value,
bool maybe_truncated) {
if (!self.is_alive()) {
return;
}
auto notifier_iter = self->characteristic_notifiers_.find(notifier_id);
// The lower layers guarantee that the status callback is always invoked before sending
// notifications. Notifiers are only removed during destruction (addressed by previous `self`
// check) and in the `DisableNotifications` completion callback in
// `OnCharacteristicNotifierError`, so no notifications should be received after removing a
// notifier.
BT_ASSERT_MSG(notifier_iter != self->characteristic_notifiers_.end(),
"characteristic notification value received after notifier unregistered"
"(peer: %s, characteristic: 0x%lX) ",
bt_str(self->peer_id_), fidl_handle.value);
CharacteristicNotifier& notifier = notifier_iter->second;
// The `- 1` is needed because there is one unacked notification that we've already sent to the
// client aside from the values in the queue.
if (notifier.queued_values.size() == kMaxPendingNotifierValues - 1) {
bt_log(WARN, "fidl",
"GATT CharacteristicNotifier pending values limit reached, closing protocol (peer: "
"%s, characteristic: %#.2x)",
bt_str(self->peer_id_), notifier.characteristic_handle.value);
self->OnCharacteristicNotifierError(notifier_id, notifier.characteristic_handle,
notifier.handler_id);
return;
}
fbg::ReadValue fidl_value;
fidl_value.set_handle(fidl_handle);
fidl_value.set_value(value.ToVector());
fidl_value.set_maybe_truncated(maybe_truncated);
bt_log(TRACE, "fidl", "Queueing GATT notification value (characteristic: %#.2x)",
notifier.characteristic_handle.value);
notifier.queued_values.push(std::move(fidl_value));
self->MaybeNotifyNextValue(notifier_id);
};
auto status_cb = [self, service = service_, char_handle, notifier_id,
notifier_handle = std::move(notifier_handle), callback = std::move(callback)](
bt::att::Result<> status, bt::gatt::IdType handler_id) mutable {
if (!self.is_alive()) {
if (status.is_ok()) {
// Disable this handler so it doesn't leak.
service->DisableNotifications(char_handle, handler_id, [](auto /*status*/) {
// There is no notifier to clean up because the server has been destroyed.
});
}
return;
}
if (status.is_error()) {
callback(fpromise::error(fidl_helpers::AttErrorToGattFidlError(status.error_value())));
return;
}
CharacteristicNotifier notifier{.handler_id = handler_id,
.characteristic_handle = char_handle,
.notifier = notifier_handle.Bind()};
auto [notifier_iter, emplaced] =
self->characteristic_notifiers_.emplace(notifier_id, std::move(notifier));
BT_ASSERT(emplaced);
// When the client closes the protocol, unregister the notifier.
notifier_iter->second.notifier.set_error_handler(
[self, char_handle, handler_id, notifier_id](auto /*status*/) {
self->OnCharacteristicNotifierError(notifier_id, char_handle, handler_id);
});
callback(fpromise::ok());
};
service_->EnableNotifications(char_handle, std::move(value_cb), std::move(status_cb));
}
void Gatt2RemoteServiceServer::MaybeNotifyNextValue(NotifierId notifier_id) {
auto notifier_iter = characteristic_notifiers_.find(notifier_id);
if (notifier_iter == characteristic_notifiers_.end()) {
return;
}
CharacteristicNotifier& notifier = notifier_iter->second;
if (notifier.queued_values.empty()) {
return;
}
if (!notifier.last_value_ack) {
return;
}
notifier.last_value_ack = false;
fbg::ReadValue value = std::move(notifier.queued_values.front());
notifier.queued_values.pop();
bt_log(DEBUG, "fidl", "Sending GATT notification value (handle: 0x%lX)", value.handle().value);
auto self = weak_self_.GetWeakPtr();
notifier.notifier->OnNotification(std::move(value), [self, notifier_id]() {
if (!self.is_alive()) {
return;
}
auto notifier_iter = self->characteristic_notifiers_.find(notifier_id);
if (notifier_iter == self->characteristic_notifiers_.end()) {
return;
}
notifier_iter->second.last_value_ack = true;
self->MaybeNotifyNextValue(notifier_id);
});
}
void Gatt2RemoteServiceServer::OnCharacteristicNotifierError(
NotifierId notifier_id, bt::gatt::CharacteristicHandle char_handle,
bt::gatt::IdType handler_id) {
auto self = weak_self_.GetWeakPtr();
service_->DisableNotifications(char_handle, handler_id, [self, notifier_id](auto /*status*/) {
if (!self.is_alive()) {
return;
}
// Clear the notifier regardless of status. Wait until this callback is called in order to
// prevent the value callback from being called for an erased notifier.
self->characteristic_notifiers_.erase(notifier_id);
});
}
} // namespace bthost