blob: 6c02a1377b96bd51a0329f411f76a9d061412043 [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 "remote_characteristic.h"
#include <zircon/assert.h>
#include "garnet/drivers/bluetooth/lib/common/log.h"
#include "garnet/drivers/bluetooth/lib/common/run_or_post.h"
#include "garnet/drivers/bluetooth/lib/common/slab_allocator.h"
#include "client.h"
namespace btlib {
namespace gatt {
using common::HostError;
using common::RunOrPost;
namespace {
void ReportNotifyStatus(att::Status status, IdType id,
RemoteCharacteristic::NotifyStatusCallback callback,
async_dispatcher_t* dispatcher) {
RunOrPost([status, id, cb = std::move(callback)] { cb(status, id); },
dispatcher);
}
void NotifyValue(const common::ByteBuffer& value,
RemoteCharacteristic::ValueCallback callback,
async_dispatcher_t* dispatcher) {
if (!dispatcher) {
callback(value);
return;
}
auto buffer = common::NewSlabBuffer(value.size());
if (buffer) {
value.Copy(buffer.get());
async::PostTask(dispatcher,
[callback = std::move(callback), val = std::move(buffer)] { callback(*val); });
} else {
bt_log(TRACE, "gatt", "out of memory!");
}
}
} // namespace
RemoteCharacteristic::PendingNotifyRequest::PendingNotifyRequest(
async_dispatcher_t* d, ValueCallback value_cb, NotifyStatusCallback status_cb)
: dispatcher(d),
value_callback(std::move(value_cb)),
status_callback(std::move(status_cb)) {
ZX_DEBUG_ASSERT(value_callback);
ZX_DEBUG_ASSERT(status_callback);
}
RemoteCharacteristic::NotifyHandler::NotifyHandler(async_dispatcher_t* d, ValueCallback cb)
: dispatcher(d), callback(std::move(cb)) {
ZX_DEBUG_ASSERT(callback);
}
RemoteCharacteristic::Descriptor::Descriptor(IdType id,
const DescriptorData& info)
: id_(id), info_(info) {}
RemoteCharacteristic::RemoteCharacteristic(fxl::WeakPtr<Client> client,
IdType id,
const CharacteristicData& info)
: id_(id),
info_(info),
discovery_error_(false),
shut_down_(false),
ccc_handle_(att::kInvalidHandle),
next_notify_handler_id_(1u),
client_(client),
weak_ptr_factory_(this) {
// See comments about "ID scheme" in remote_characteristics.h
ZX_DEBUG_ASSERT(id_ <= std::numeric_limits<uint16_t>::max());
ZX_DEBUG_ASSERT(client_);
}
RemoteCharacteristic::RemoteCharacteristic(RemoteCharacteristic&& other)
: id_(other.id_),
info_(other.info_),
discovery_error_(other.discovery_error_),
shut_down_(other.shut_down_.load()),
ccc_handle_(other.ccc_handle_),
next_notify_handler_id_(other.next_notify_handler_id_),
client_(other.client_),
weak_ptr_factory_(this) {
other.weak_ptr_factory_.InvalidateWeakPtrs();
}
void RemoteCharacteristic::ShutDown() {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
// Make sure that all weak pointers are invalidated on the GATT thread.
weak_ptr_factory_.InvalidateWeakPtrs();
shut_down_ = true;
if (ccc_handle_ != att::kInvalidHandle) {
ResolvePendingNotifyRequests(att::Status(HostError::kFailed));
// Clear the CCC if we have enabled notifications.
// TODO(armansito): Don't write to the descriptor if ShutDown() was called
// as a result of a "Service Changed" indication.
if (!notify_handlers_.empty()) {
notify_handlers_.clear();
DisableNotificationsInternal();
}
}
}
void RemoteCharacteristic::DiscoverDescriptors(att::Handle range_end,
att::StatusCallback callback) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(client_);
ZX_DEBUG_ASSERT(callback);
ZX_DEBUG_ASSERT(!shut_down_);
ZX_DEBUG_ASSERT(range_end >= info().value_handle);
discovery_error_ = false;
descriptors_.clear();
if (info().value_handle == range_end) {
callback(att::Status());
return;
}
auto self = weak_ptr_factory_.GetWeakPtr();
auto desc_cb = [self](const DescriptorData& desc) {
if (!self)
return;
ZX_DEBUG_ASSERT(self->thread_checker_.IsCreationThreadCurrent());
if (self->discovery_error_)
return;
if (desc.type == types::kClientCharacteristicConfig) {
if (self->ccc_handle_ != att::kInvalidHandle) {
bt_log(TRACE, "gatt",
"characteristic has more than one CCC descriptor!");
self->discovery_error_ = true;
return;
}
self->ccc_handle_ = desc.handle;
}
// See comments about "ID scheme" in remote_characteristics.h
ZX_DEBUG_ASSERT(self->descriptors_.size() <=
std::numeric_limits<uint16_t>::max());
IdType id = (self->id_ << 16) | self->descriptors_.size();
self->descriptors_.push_back(Descriptor(id, desc));
};
auto status_cb = [self, cb = std::move(callback)](att::Status status) {
if (!self) {
cb(att::Status(HostError::kFailed));
return;
}
ZX_DEBUG_ASSERT(self->thread_checker_.IsCreationThreadCurrent());
if (self->discovery_error_) {
status = att::Status(HostError::kFailed);
}
if (!status) {
self->descriptors_.clear();
}
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,
async_dispatcher_t* dispatcher) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(client_);
ZX_DEBUG_ASSERT(value_callback);
ZX_DEBUG_ASSERT(status_callback);
ZX_DEBUG_ASSERT(!shut_down_);
if (!(info().properties & (Property::kNotify | Property::kIndicate)) ||
ccc_handle_ == att::kInvalidHandle) {
bt_log(TRACE, "gatt", "characteristic does not support notifications");
ReportNotifyStatus(att::Status(HostError::kNotSupported), kInvalidId,
std::move(status_callback), dispatcher);
return;
}
// If notifications are already enabled then succeed right away.
if (!notify_handlers_.empty()) {
ZX_DEBUG_ASSERT(pending_notify_reqs_.empty());
IdType id = next_notify_handler_id_++;
notify_handlers_[id] = NotifyHandler(dispatcher, std::move(value_callback));
ReportNotifyStatus(att::Status(), id, std::move(status_callback),
dispatcher);
return;
}
pending_notify_reqs_.emplace(dispatcher, 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;
common::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_ptr_factory_.GetWeakPtr();
auto ccc_write_cb = [self](att::Status status) {
bt_log(TRACE, "gatt", "CCC write status (enable): %s",
status.ToString().c_str());
if (self) {
self->ResolvePendingNotifyRequests(status);
}
};
client_->WriteRequest(ccc_handle_, ccc_value, std::move(ccc_write_cb));
}
bool RemoteCharacteristic::DisableNotifications(IdType handler_id) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(client_);
ZX_DEBUG_ASSERT(!shut_down_);
if (!notify_handlers_.erase(handler_id)) {
bt_log(SPEW, "gatt", "notify handler not found (id: %u)", handler_id);
return false;
}
if (!notify_handlers_.empty())
return true;
DisableNotificationsInternal();
return true;
}
void RemoteCharacteristic::DisableNotificationsInternal() {
ZX_DEBUG_ASSERT(ccc_handle_ != att::kInvalidHandle);
if (!client_) {
bt_log(SPEW, "gatt", "client bearer invalid!");
return;
}
// Disable notifications.
common::StaticByteBuffer<2> ccc_value;
ccc_value.SetToZeros();
auto ccc_write_cb = [](att::Status status) {
bt_log(TRACE, "gatt", "CCC write status (disable): %s",
status.ToString().c_str());
};
// 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::Status status) {
// Move the contents of the queue so that a handler can remove itself (this
// matters when no dispatcher is provided).
auto pending = std::move(pending_notify_reqs_);
while (!pending.empty()) {
auto req = std::move(pending.front());
pending.pop();
IdType id = kInvalidId;
if (status) {
id = next_notify_handler_id_++;
notify_handlers_[id] =
NotifyHandler(req.dispatcher, std::move(req.value_callback));
}
ReportNotifyStatus(status, id, std::move(req.status_callback),
req.dispatcher);
}
}
void RemoteCharacteristic::HandleNotification(const common::ByteBuffer& value) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(client_);
ZX_DEBUG_ASSERT(!shut_down_);
for (auto& iter : notify_handlers_) {
auto& handler = iter.second;
NotifyValue(value, handler.callback.share(), handler.dispatcher);
}
}
} // namespace gatt
} // namespace btlib