blob: 7d36b2262d4ab080832d7d974d4c164513825e5e [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_service.h"
#include <lib/async/default.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"
namespace btlib {
namespace gatt {
using att::Status;
using att::StatusCallback;
using common::BufferView;
using common::ByteBuffer;
using common::HostError;
using common::MutableByteBufferPtr;
using common::NewSlabBuffer;
using common::RunOrPost;
namespace {
void ReportStatus(Status status,
StatusCallback callback,
async_dispatcher_t* dispatcher) {
RunOrPost([status, cb = std::move(callback)] { cb(status); }, dispatcher);
}
void ReportValue(att::Status status,
const common::ByteBuffer& value,
RemoteService::ReadValueCallback callback,
async_dispatcher_t* dispatcher) {
if (!dispatcher) {
callback(status, value);
return;
}
// TODO(armansito): Consider making att::Bearer return the ATT PDU buffer
// directly which would remove the need for a copy.
auto buffer = common::NewSlabBuffer(value.size());
value.Copy(buffer.get());
async::PostTask(dispatcher,
[status, callback = std::move(callback),
val = std::move(buffer)] { callback(status, *val); });
}
} // namespace
// static
constexpr size_t RemoteService::kSentinel;
RemoteService::RemoteService(const ServiceData& service_data,
fxl::WeakPtr<Client> client,
async_dispatcher_t* gatt_dispatcher)
: service_data_(service_data),
gatt_dispatcher_(gatt_dispatcher),
client_(client),
remaining_descriptor_requests_(kSentinel),
shut_down_(false) {
ZX_DEBUG_ASSERT(client_);
ZX_DEBUG_ASSERT(gatt_dispatcher_);
}
RemoteService::~RemoteService() {
std::lock_guard<std::mutex> lock(mtx_);
ZX_DEBUG_ASSERT(!alive());
}
void RemoteService::ShutDown() {
ZX_DEBUG_ASSERT(IsOnGattThread());
std::vector<PendingClosure> rm_handlers;
{
std::lock_guard<std::mutex> lock(mtx_);
if (!alive()) {
return;
}
for (auto& chr : characteristics_) {
chr.ShutDown();
}
shut_down_ = true;
rm_handlers = std::move(rm_handlers_);
}
for (auto& handler : rm_handlers) {
RunOrPost(std::move(handler.callback), handler.dispatcher);
}
}
bool RemoteService::AddRemovedHandler(fit::closure handler,
async_dispatcher_t* dispatcher) {
std::lock_guard<std::mutex> lock(mtx_);
if (!alive())
return false;
rm_handlers_.emplace_back(std::move(handler), dispatcher);
return true;
}
void RemoteService::DiscoverCharacteristics(CharacteristicCallback callback,
async_dispatcher_t* dispatcher) {
RunGattTask([this, cb = std::move(callback), dispatcher]() mutable {
if (shut_down_) {
ReportCharacteristics(Status(HostError::kFailed), std::move(cb),
dispatcher);
return;
}
// Characteristics already discovered. Return success.
if (HasCharacteristics()) {
ReportCharacteristics(Status(), std::move(cb), dispatcher);
return;
}
// Queue this request.
pending_discov_reqs_.emplace_back(std::move(cb), dispatcher);
// Nothing to do if a write request is already pending.
if (pending_discov_reqs_.size() > 1u)
return;
auto self = fbl::WrapRefPtr(this);
auto chrc_cb = [self](const CharacteristicData& chrc) {
if (!self->shut_down_) {
IdType id = self->characteristics_.size();
self->characteristics_.emplace_back(self->client_, id, chrc);
}
};
auto res_cb = [self](Status status) mutable {
if (self->shut_down_) {
status = Status(HostError::kFailed);
}
if (bt_is_error(status, TRACE, "gatt",
"characteristic discovery failed")) {
self->characteristics_.clear();
}
if (self->characteristics_.empty()) {
if (status) {
// This marks that characteristic discovery has completed
// successfully.
self->remaining_descriptor_requests_ = 0u;
}
// Skip descriptor discovery and end the procedure as no characteristics
// were found (or the operation failed).
self->CompleteCharacteristicDiscovery(status);
return;
}
self->StartDescriptorDiscovery();
};
client_->DiscoverCharacteristics(service_data_.range_start,
service_data_.range_end,
std::move(chrc_cb), std::move(res_cb));
});
}
bool RemoteService::IsDiscovered() const {
// TODO(armansito): Return true only if included services have also been
// discovered.
return HasCharacteristics();
}
void RemoteService::ReadCharacteristic(IdType id,
ReadValueCallback cb,
async_dispatcher_t* dispatcher) {
RunGattTask([this, id, cb = std::move(cb), dispatcher]() mutable {
RemoteCharacteristic* chrc;
att::Status status = att::Status(GetCharacteristic(id, &chrc));
ZX_DEBUG_ASSERT(chrc || !status);
if (!status) {
ReportValue(status, BufferView(), std::move(cb), dispatcher);
return;
}
if (!(chrc->info().properties & Property::kRead)) {
bt_log(TRACE, "gatt", "characteristic does not support \"read\"");
ReportValue(att::Status(HostError::kNotSupported), BufferView(), std::move(cb), dispatcher);
return;
}
SendReadRequest(chrc->info().value_handle, std::move(cb), dispatcher);
});
}
void RemoteService::ReadLongCharacteristic(IdType id, uint16_t offset,
size_t max_bytes,
ReadValueCallback cb,
async_dispatcher_t* dispatcher) {
RunGattTask(
[this, id, offset, max_bytes, cb = std::move(cb), dispatcher]() mutable {
RemoteCharacteristic* chrc;
att::Status status = att::Status(GetCharacteristic(id, &chrc));
ZX_DEBUG_ASSERT(chrc || !status);
if (!status) {
ReportValue(status, BufferView(), std::move(cb), dispatcher);
return;
}
if (!(chrc->info().properties & Property::kRead)) {
bt_log(TRACE, "gatt", "characteristic does not support \"read\"");
ReportValue(att::Status(HostError::kNotSupported), BufferView(),
std::move(cb), dispatcher);
return;
}
if (max_bytes == 0) {
bt_log(SPEW, "gatt", "invalid value for |max_bytes|: 0");
ReportValue(att::Status(HostError::kInvalidParameters), BufferView(),
std::move(cb), dispatcher);
return;
}
// Set up the buffer in which we'll accumulate the blobs.
auto buffer =
NewSlabBuffer(std::min(max_bytes, att::kMaxAttributeValueLength));
if (!buffer) {
ReportValue(att::Status(HostError::kOutOfMemory), BufferView(),
std::move(cb), dispatcher);
return;
}
ReadLongHelper(chrc->info().value_handle, offset, std::move(buffer),
0u /* bytes_read */, std::move(cb), dispatcher);
});
}
void RemoteService::WriteCharacteristic(IdType id,
std::vector<uint8_t> value,
StatusCallback cb,
async_dispatcher_t* dispatcher) {
RunGattTask([this, id, value = std::move(value), cb = std::move(cb),
dispatcher]() mutable {
RemoteCharacteristic* chrc;
Status status = Status(GetCharacteristic(id, &chrc));
ZX_DEBUG_ASSERT(chrc || !status);
if (!status) {
ReportStatus(status, std::move(cb), dispatcher);
return;
}
// TODO(armansito): Use the "long write" procedure when supported.
if (!(chrc->info().properties & Property::kWrite)) {
bt_log(TRACE, "gatt", "characteristic does not support \"write\"");
ReportStatus(Status(HostError::kNotSupported), std::move(cb), dispatcher);
return;
}
SendWriteRequest(chrc->info().value_handle,
BufferView(value.data(), value.size()), std::move(cb),
dispatcher);
});
}
void RemoteService::WriteCharacteristicWithoutResponse(
IdType id, std::vector<uint8_t> value) {
RunGattTask([this, id, value = std::move(value)]() mutable {
RemoteCharacteristic* chrc;
Status status = Status(GetCharacteristic(id, &chrc));
ZX_DEBUG_ASSERT(chrc || !status);
if (!status) {
return;
}
if (!(chrc->info().properties & Property::kWriteWithoutResponse)) {
bt_log(TRACE, "gatt",
"characteristic does not support \"write without response\"");
return;
}
client_->WriteWithoutResponse(chrc->info().value_handle,
BufferView(value.data(), value.size()));
});
}
void RemoteService::ReadDescriptor(IdType id, ReadValueCallback cb,
async_dispatcher_t* dispatcher) {
RunGattTask([this, id, cb = std::move(cb), dispatcher]() mutable {
const RemoteCharacteristic::Descriptor* desc;
att::Status status = att::Status(GetDescriptor(id, &desc));
ZX_DEBUG_ASSERT(desc || !status);
if (!status) {
ReportValue(status, BufferView(), std::move(cb), dispatcher);
return;
}
SendReadRequest(desc->info().handle, std::move(cb), dispatcher);
});
}
void RemoteService::ReadLongDescriptor(IdType id, uint16_t offset,
size_t max_bytes, ReadValueCallback cb,
async_dispatcher_t* dispatcher) {
RunGattTask(
[this, id, offset, max_bytes, cb = std::move(cb), dispatcher]() mutable {
const RemoteCharacteristic::Descriptor* desc;
att::Status status = att::Status(GetDescriptor(id, &desc));
ZX_DEBUG_ASSERT(desc || !status);
if (!status) {
ReportValue(status, BufferView(), std::move(cb), dispatcher);
return;
}
if (max_bytes == 0) {
bt_log(SPEW, "gatt", "invalid value for |max_bytes|: 0");
ReportValue(att::Status(HostError::kInvalidParameters), BufferView(),
std::move(cb), dispatcher);
return;
}
// Set up the buffer in which we'll accumulate the blobs.
auto buffer =
NewSlabBuffer(std::min(max_bytes, att::kMaxAttributeValueLength));
if (!buffer) {
ReportValue(att::Status(HostError::kOutOfMemory), BufferView(),
std::move(cb), dispatcher);
return;
}
ReadLongHelper(desc->info().handle, offset, std::move(buffer),
0u /* bytes_read */, std::move(cb), dispatcher);
});
}
void RemoteService::WriteDescriptor(IdType id, std::vector<uint8_t> value,
att::StatusCallback cb,
async_dispatcher_t* dispatcher) {
RunGattTask([this, id, value = std::move(value), cb = std::move(cb),
dispatcher]() mutable {
const RemoteCharacteristic::Descriptor* desc;
Status status = Status(GetDescriptor(id, &desc));
ZX_DEBUG_ASSERT(desc || !status);
if (!status) {
ReportStatus(status, std::move(cb), dispatcher);
return;
}
// Do not allow writing to internally reserved descriptors.
if (desc->info().type == types::kClientCharacteristicConfig) {
bt_log(TRACE, "gatt", "writing to CCC descriptor not allowed");
ReportStatus(Status(HostError::kNotSupported), std::move(cb), dispatcher);
return;
}
SendWriteRequest(desc->info().handle,
BufferView(value.data(), value.size()), std::move(cb),
dispatcher);
});
}
void RemoteService::EnableNotifications(IdType id, ValueCallback callback,
NotifyStatusCallback status_callback,
async_dispatcher_t* dispatcher) {
RunGattTask([this, id, cb = std::move(callback),
status_cb = std::move(status_callback), dispatcher]() mutable {
RemoteCharacteristic* chrc;
att::Status status = att::Status(GetCharacteristic(id, &chrc));
ZX_DEBUG_ASSERT(chrc || !status);
if (!status) {
RunOrPost([status, cb = std::move(status_cb)] { cb(status, kInvalidId); },
dispatcher);
return;
}
chrc->EnableNotifications(std::move(cb), std::move(status_cb), dispatcher);
});
}
void RemoteService::DisableNotifications(IdType id, IdType handler_id,
StatusCallback status_callback,
async_dispatcher_t* dispatcher) {
RunGattTask([this, id, handler_id, cb = std::move(status_callback),
dispatcher]() mutable {
RemoteCharacteristic* chrc;
att::Status status = att::Status(GetCharacteristic(id, &chrc));
ZX_DEBUG_ASSERT(chrc || !status);
if (status && !chrc->DisableNotifications(handler_id)) {
status = att::Status(HostError::kNotFound);
}
ReportStatus(status, std::move(cb), dispatcher);
});
}
void RemoteService::StartDescriptorDiscovery() {
ZX_DEBUG_ASSERT(IsOnGattThread());
ZX_DEBUG_ASSERT(!pending_discov_reqs_.empty());
ZX_DEBUG_ASSERT(characteristics_.size());
remaining_descriptor_requests_ = characteristics_.size();
auto self = fbl::WrapRefPtr(this);
// Callback called for each characteristic. This may be called in any
// order since we request the descriptors of all characteristics all at
// once.
auto desc_done_callback = [self](att::Status status) {
// Do nothing if discovery was concluded earlier (which would have cleared
// the pending discovery requests).
if (self->pending_discov_reqs_.empty())
return;
// Report an error if the service was removed.
if (self->shut_down_) {
status = att::Status(HostError::kFailed);
}
if (status) {
self->remaining_descriptor_requests_ -= 1;
// Defer handling
if (self->remaining_descriptor_requests_ > 0)
return;
// HasCharacteristics() should return true now.
ZX_DEBUG_ASSERT(self->HasCharacteristics());
// Fall through and notify clients below.
} else {
ZX_DEBUG_ASSERT(!self->HasCharacteristics());
bt_log(TRACE, "gatt", "descriptor discovery failed %s",
status.ToString().c_str());
self->characteristics_.clear();
// Fall through and notify the clients below.
}
self->CompleteCharacteristicDiscovery(status);
};
for (size_t i = 0; i < characteristics_.size(); ++i) {
// We determine the range end handle based on the start handle of the next
// characteristic. The characteristic ends with the service range if this is
// the last characteristic.
att::Handle end_handle;
if (i == characteristics_.size() - 1) {
end_handle = service_data_.range_end;
} else {
end_handle = characteristics_[i + 1].info().handle - 1;
}
ZX_DEBUG_ASSERT(client_);
characteristics_[i].DiscoverDescriptors(end_handle, desc_done_callback);
}
}
bool RemoteService::IsOnGattThread() const {
return async_get_default_dispatcher() == gatt_dispatcher_;
}
HostError RemoteService::GetCharacteristic(IdType id, RemoteCharacteristic** out_char) {
ZX_DEBUG_ASSERT(IsOnGattThread());
ZX_DEBUG_ASSERT(out_char);
if (shut_down_)
return HostError::kFailed;
if (!HasCharacteristics())
return HostError::kNotReady;
if (id >= characteristics_.size())
return HostError::kNotFound;
*out_char = &characteristics_[id];
return HostError::kNoError;
}
HostError RemoteService::GetDescriptor(
IdType id, const RemoteCharacteristic::Descriptor** out_desc) {
ZX_DEBUG_ASSERT(IsOnGattThread());
ZX_DEBUG_ASSERT(out_desc);
if (shut_down_)
return HostError::kFailed;
if (!HasCharacteristics())
return HostError::kNotReady;
// The second set of 16-bits of |id| represent the characteristic ID and the
// lower bits are the descriptor index. (See the section titled "ID SCHEME" in
// remote_characteristic.h)
IdType desc_idx = id & 0xFFFF;
IdType chrc_idx = (id >> 16) & 0xFFFF;
if (chrc_idx >= characteristics_.size())
return HostError::kNotFound;
auto* chrc = &characteristics_[chrc_idx];
if (desc_idx >= chrc->descriptors().size())
return HostError::kNotFound;
*out_desc = &chrc->descriptors()[desc_idx];
ZX_DEBUG_ASSERT((*out_desc)->id() == id);
return HostError::kNoError;
}
void RemoteService::RunGattTask(fit::closure task) {
// Capture a reference to this object to guarantee its lifetime.
RunOrPost(
[objref = fbl::WrapRefPtr(this), task = std::move(task)] { task(); },
gatt_dispatcher_);
}
void RemoteService::ReportCharacteristics(Status status,
CharacteristicCallback callback,
async_dispatcher_t* dispatcher) {
ZX_DEBUG_ASSERT(IsOnGattThread());
RunOrPost(
[self = fbl::WrapRefPtr(this), status, cb = std::move(callback)] {
// We return a const reference to our |characteristics_| field to avoid
// copying its contents into this lambda.
//
// |characteristics_| is not annotated with __TA_GUARDED() since locking
// |mtx_| can cause a deadlock if |dispatcher| == nullptr. We
// guarantee the validity of this data by keeping the public
// interface of Characteristic small and by never modifying
// |characteristics_| following discovery.
cb(status, self->characteristics_);
},
dispatcher);
}
void RemoteService::CompleteCharacteristicDiscovery(att::Status status) {
ZX_DEBUG_ASSERT(!pending_discov_reqs_.empty());
ZX_DEBUG_ASSERT(!status || remaining_descriptor_requests_ == 0u);
auto pending = std::move(pending_discov_reqs_);
for (auto& req : pending) {
ReportCharacteristics(status, std::move(req.callback), req.dispatcher);
}
}
void RemoteService::SendReadRequest(att::Handle handle, ReadValueCallback cb,
async_dispatcher_t* dispatcher) {
client_->ReadRequest(
handle, [cb = std::move(cb), dispatcher](att::Status status,
const auto& value) mutable {
ReportValue(status, value, std::move(cb), dispatcher);
});
}
void RemoteService::SendWriteRequest(att::Handle handle,
const ByteBuffer& value, StatusCallback cb,
async_dispatcher_t* dispatcher) {
client_->WriteRequest(
handle, value, [cb = std::move(cb), dispatcher](Status status) mutable {
ReportStatus(status, std::move(cb), dispatcher);
});
}
void RemoteService::ReadLongHelper(att::Handle value_handle, uint16_t offset,
MutableByteBufferPtr buffer,
size_t bytes_read,
ReadValueCallback callback,
async_dispatcher_t* dispatcher) {
ZX_DEBUG_ASSERT(IsOnGattThread());
ZX_DEBUG_ASSERT(callback);
ZX_DEBUG_ASSERT(buffer);
ZX_DEBUG_ASSERT(!shut_down_);
// Capture a reference so that this object is alive when the callback runs.
auto self = fbl::WrapRefPtr(this);
auto read_blob_cb = [self, value_handle, offset, buffer = std::move(buffer),
bytes_read, cb = std::move(callback), dispatcher](
att::Status status, const ByteBuffer& blob) mutable {
if (self->shut_down_) {
// The service was removed. Report an error.
ReportValue(att::Status(HostError::kCanceled), BufferView(),
std::move(cb), dispatcher);
return;
}
if (!status) {
ReportValue(status, BufferView(), std::move(cb), dispatcher);
return;
}
// Copy the blob into our |buffer|. |blob| may be truncated depending on the
// size of |buffer|.
ZX_DEBUG_ASSERT(bytes_read < buffer->size());
size_t copy_size = std::min(blob.size(), buffer->size() - bytes_read);
buffer->Write(blob.view(0, copy_size), bytes_read);
bytes_read += copy_size;
// We are done if the blob is smaller than (ATT_MTU - 1) or we have read the
// maximum number of bytes requested.
ZX_DEBUG_ASSERT(bytes_read <= buffer->size());
if (blob.size() < (self->client_->mtu() - 1) ||
bytes_read == buffer->size()) {
ReportValue(att::Status(), buffer->view(0, bytes_read), std::move(cb),
dispatcher);
return;
}
// We have more bytes to read. Read the next blob.
self->ReadLongHelper(value_handle, offset + blob.size(), std::move(buffer),
bytes_read, std::move(cb), dispatcher);
};
client_->ReadBlobRequest(value_handle, offset, std::move(read_blob_cb));
}
void RemoteService::HandleNotification(att::Handle value_handle,
const common::ByteBuffer& value) {
ZX_DEBUG_ASSERT(IsOnGattThread());
if (shut_down_)
return;
// Find the characteristic with the given value handle.
auto iter = std::lower_bound(characteristics_.begin(), characteristics_.end(),
value_handle,
[](const auto& chr, att::Handle value_handle) {
return chr.info().value_handle < value_handle;
});
if (iter != characteristics_.end() &&
iter->info().value_handle == value_handle) {
iter->HandleNotification(value);
}
}
} // namespace gatt
} // namespace btlib