| // 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 "client.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/log.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/run_or_post.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/slab_allocator.h" |
| |
| namespace bt { |
| namespace gatt { |
| |
| 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 ByteBuffer& value, |
| RemoteCharacteristic::ValueCallback callback, |
| async_dispatcher_t* dispatcher) { |
| if (!dispatcher) { |
| callback(value); |
| return; |
| } |
| |
| auto buffer = 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; |
| |
| 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: %lu)", 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. |
| 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 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 bt |