blob: 3a326756b2db0a23f3676eaa0bddd1ba2a09cc47 [file] [log] [blame]
// 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 "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gatt/remote_characteristic.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/assert.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/slab_allocator.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gatt/client.h"
namespace bt::gatt {
RemoteCharacteristic::PendingNotifyRequest::PendingNotifyRequest(
ValueCallback value_cb, NotifyStatusCallback status_cb)
: value_callback(std::move(value_cb)),
status_callback(std::move(status_cb)) {
BT_DEBUG_ASSERT(value_callback);
BT_DEBUG_ASSERT(status_callback);
}
RemoteCharacteristic::RemoteCharacteristic(Client::WeakPtr client,
const CharacteristicData& info)
: info_(info),
discovery_error_(false),
ccc_handle_(att::kInvalidHandle),
ext_prop_handle_(att::kInvalidHandle),
next_notify_handler_id_(1u),
client_(std::move(client)),
weak_self_(this) {
BT_DEBUG_ASSERT(client_.is_alive());
}
RemoteCharacteristic::~RemoteCharacteristic() {
ResolvePendingNotifyRequests(ToResult(HostError::kFailed));
// Clear the CCC if we have enabled notifications and destructor was not
// called as a result of a Service Changed notification.
if (!notify_handlers_.empty()) {
notify_handlers_.clear();
// Don't disable notifications if the service changed as this characteristic
// may no longer exist, may have been changed, or may have moved. If the
// characteristic is still valid, the server may continue to send
// notifications, but they will be ignored until a new handler is
// registered.
if (!service_changed_) {
DisableNotificationsInternal();
}
}
}
void RemoteCharacteristic::UpdateDataWithExtendedProperties(
ExtendedProperties ext_props) {
// |CharacteristicData| is an immutable snapshot into the data associated with
// this Characteristic. Update |info_| with the most recent snapshot - the
// only new member is the recently read |ext_props|.
info_ = CharacteristicData(info_.properties,
ext_props,
info_.handle,
info_.value_handle,
info_.type);
}
void RemoteCharacteristic::DiscoverDescriptors(att::Handle range_end,
att::ResultFunction<> callback) {
BT_DEBUG_ASSERT(client_.is_alive());
BT_DEBUG_ASSERT(callback);
BT_DEBUG_ASSERT(range_end >= info().value_handle);
discovery_error_ = false;
descriptors_.clear();
if (info().value_handle == range_end) {
callback(fit::ok());
return;
}
auto self = weak_self_.GetWeakPtr();
auto desc_cb = [self](const DescriptorData& desc) {
if (!self.is_alive())
return;
if (self->discovery_error_)
return;
if (desc.type == types::kClientCharacteristicConfig) {
if (self->ccc_handle_ != att::kInvalidHandle) {
bt_log(
DEBUG, "gatt", "characteristic has more than one CCC descriptor!");
self->discovery_error_ = true;
return;
}
self->ccc_handle_ = desc.handle;
} else if (desc.type == types::kCharacteristicExtProperties) {
if (self->ext_prop_handle_ != att::kInvalidHandle) {
bt_log(DEBUG,
"gatt",
"characteristic has more than one Extended Prop descriptor!");
self->discovery_error_ = true;
return;
}
// If the characteristic properties has the ExtendedProperties bit set,
// then update the handle.
if (self->properties() & Property::kExtendedProperties) {
self->ext_prop_handle_ = desc.handle;
} else {
bt_log(DEBUG, "gatt", "characteristic extended properties not set");
}
}
// As descriptors must be strictly increasing, this emplace should always
// succeed
auto [_unused, success] =
self->descriptors_.try_emplace(DescriptorHandle(desc.handle), desc);
BT_DEBUG_ASSERT(success);
};
auto status_cb = [self,
cb = std::move(callback)](att::Result<> status) mutable {
if (!self.is_alive()) {
cb(ToResult(HostError::kFailed));
return;
}
if (self->discovery_error_) {
status = ToResult(HostError::kFailed);
}
if (status.is_error()) {
self->descriptors_.clear();
cb(status);
return;
}
// If the characteristic contains the ExtendedProperties descriptor, perform
// a Read operation to get the extended properties before notifying the
// callback.
if (self->ext_prop_handle_ != att::kInvalidHandle) {
auto read_cb = [self, cb = std::move(cb)](att::Result<> status,
const ByteBuffer& data,
bool /*maybe_truncated*/) {
if (status.is_error()) {
cb(status);
return;
}
// The ExtendedProperties descriptor value is a |uint16_t| representing
// the ExtendedProperties bitfield. If the retrieved |data| is
// malformed, respond with an error and return early.
if (data.size() != sizeof(uint16_t)) {
cb(ToResult(HostError::kPacketMalformed));
return;
}
auto ext_props = le16toh(data.To<uint16_t>());
self->UpdateDataWithExtendedProperties(ext_props);
cb(status);
};
self->client_->ReadRequest(self->ext_prop_handle_, std::move(read_cb));
return;
}
cb(status);
};
client_->DiscoverDescriptors(info().value_handle + 1,
range_end,
std::move(desc_cb),
std::move(status_cb));
}
void RemoteCharacteristic::EnableNotifications(
ValueCallback value_callback, NotifyStatusCallback status_callback) {
BT_DEBUG_ASSERT(client_.is_alive());
BT_DEBUG_ASSERT(value_callback);
BT_DEBUG_ASSERT(status_callback);
if (!(info().properties & (Property::kNotify | Property::kIndicate))) {
bt_log(DEBUG, "gatt", "characteristic does not support notifications");
status_callback(ToResult(HostError::kNotSupported), kInvalidId);
return;
}
// If notifications are already enabled then succeed right away.
if (!notify_handlers_.empty()) {
BT_DEBUG_ASSERT(pending_notify_reqs_.empty());
IdType id = next_notify_handler_id_++;
notify_handlers_[id] = std::move(value_callback);
status_callback(fit::ok(), id);
return;
}
pending_notify_reqs_.emplace(std::move(value_callback),
std::move(status_callback));
// If there are other pending requests to enable notifications then we'll wait
// until the descriptor write completes.
if (pending_notify_reqs_.size() > 1u)
return;
// It is possible for some characteristics that support notifications or
// indications to not have a CCC descriptor. Such characteristics do not need
// to be directly configured to consider notifications to have been enabled.
if (ccc_handle_ == att::kInvalidHandle) {
bt_log(TRACE,
"gatt",
"notications enabled without characteristic configuration");
ResolvePendingNotifyRequests(fit::ok());
return;
}
StaticByteBuffer<2> ccc_value;
ccc_value.SetToZeros();
// Enable indications if supported. Otherwise enable notifications.
if (info().properties & Property::kIndicate) {
ccc_value[0] = static_cast<uint8_t>(kCCCIndicationBit);
} else {
ccc_value[0] = static_cast<uint8_t>(kCCCNotificationBit);
}
auto self = weak_self_.GetWeakPtr();
auto ccc_write_cb = [self](att::Result<> status) {
bt_log(DEBUG, "gatt", "CCC write status (enable): %s", bt_str(status));
if (self.is_alive()) {
self->ResolvePendingNotifyRequests(status);
}
};
client_->WriteRequest(ccc_handle_, ccc_value, std::move(ccc_write_cb));
}
bool RemoteCharacteristic::DisableNotifications(IdType handler_id) {
BT_DEBUG_ASSERT(client_.is_alive());
auto handler_iter = notify_handlers_.find(handler_id);
if (handler_iter == notify_handlers_.end()) {
bt_log(TRACE, "gatt", "notify handler not found (id: %lu)", handler_id);
return false;
}
// Don't modify handlers map while handlers are being notified.
if (notifying_handlers_) {
handlers_pending_disable_.push_back(handler_id);
return true;
}
notify_handlers_.erase(handler_iter);
if (!notify_handlers_.empty())
return true;
DisableNotificationsInternal();
return true;
}
void RemoteCharacteristic::DisableNotificationsInternal() {
if (ccc_handle_ == att::kInvalidHandle) {
// Nothing to do.
return;
}
if (!client_.is_alive()) {
bt_log(TRACE, "gatt", "client bearer invalid!");
return;
}
// Disable notifications.
StaticByteBuffer<2> ccc_value;
ccc_value.SetToZeros();
auto ccc_write_cb = [](att::Result<> status) {
bt_log(DEBUG, "gatt", "CCC write status (disable): %s", bt_str(status));
};
// We send the request without handling the status as there is no good way to
// recover from failing to disable notifications. If the peer continues to
// send notifications, they will be dropped as no handlers are registered.
client_->WriteRequest(ccc_handle_, ccc_value, std::move(ccc_write_cb));
}
void RemoteCharacteristic::ResolvePendingNotifyRequests(att::Result<> status) {
// Don't iterate requests as callbacks can add new requests.
while (!pending_notify_reqs_.empty()) {
auto req = std::move(pending_notify_reqs_.front());
pending_notify_reqs_.pop();
IdType id = kInvalidId;
if (status.is_ok()) {
id = next_notify_handler_id_++;
// Add handler to map before calling status callback in case callback
// removes the handler.
notify_handlers_[id] = std::move(req.value_callback);
}
req.status_callback(status, id);
}
}
void RemoteCharacteristic::HandleNotification(const ByteBuffer& value,
bool maybe_truncated) {
BT_DEBUG_ASSERT(client_.is_alive());
notifying_handlers_ = true;
for (auto& iter : notify_handlers_) {
auto& handler = iter.second;
handler(value, maybe_truncated);
}
notifying_handlers_ = false;
// If handlers disabled themselves when notified, remove them from the map.
for (IdType handler_id : handlers_pending_disable_) {
notify_handlers_.erase(handler_id);
}
handlers_pending_disable_.clear();
}
} // namespace bt::gatt