blob: a5dca1fd9cc24cef4ef4ff06495052d2cd25a788 [file] [log] [blame]
// Copyright 2017 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 "local_service_manager.h"
#include <endian.h>
#include <zircon/assert.h>
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/gatt/gatt_defs.h"
#include "src/lib/fxl/memory/weak_ptr.h"
namespace bt {
namespace gatt {
namespace {
// Adds characteristic definition attributes to |grouping| for |chrc|. Returns
// the characteristic value handle.
att::Handle InsertCharacteristicAttributes(
att::AttributeGrouping* grouping, const Characteristic& chrc,
att::Attribute::ReadHandler read_handler,
att::Attribute::WriteHandler write_handler) {
ZX_DEBUG_ASSERT(grouping);
ZX_DEBUG_ASSERT(!grouping->complete());
ZX_DEBUG_ASSERT(read_handler);
ZX_DEBUG_ASSERT(write_handler);
// Characteristic Declaration (Vol 3, Part G, 3.3.1).
auto* decl_attr = grouping->AddAttribute(
types::kCharacteristicDeclaration,
att::AccessRequirements(false, false, false), // read (no security)
att::AccessRequirements()); // write (not allowed)
ZX_DEBUG_ASSERT(decl_attr);
// Characteristic Value Declaration (Vol 3, Part G, 3.3.2)
auto* value_attr = grouping->AddAttribute(
chrc.type(), chrc.read_permissions(), chrc.write_permissions());
ZX_DEBUG_ASSERT(value_attr);
value_attr->set_read_handler(std::move(read_handler));
value_attr->set_write_handler(std::move(write_handler));
size_t uuid_size = chrc.type().CompactSize(false /* allow_32bit */);
ZX_DEBUG_ASSERT(uuid_size == 2 || uuid_size == 16);
// The characteristic declaration value contains:
// 1 octet: properties
// 2 octets: value handle
// 2 or 16 octets: UUID
DynamicByteBuffer decl_value(3 + uuid_size);
decl_value[0] = chrc.properties();
decl_value[1] = static_cast<uint8_t>(value_attr->handle());
decl_value[2] = static_cast<uint8_t>(value_attr->handle() >> 8);
auto uuid_view = decl_value.mutable_view(3);
chrc.type().ToBytes(&uuid_view, false /* allow_32bit */);
decl_attr->SetValue(decl_value);
return value_attr->handle();
}
// Adds a characteristic descriptor declaration to |grouping| for |desc|.
void InsertDescriptorAttribute(att::AttributeGrouping* grouping,
const UUID& type,
const att::AccessRequirements& read_reqs,
const att::AccessRequirements& write_reqs,
att::Attribute::ReadHandler read_handler,
att::Attribute::WriteHandler write_handler) {
ZX_DEBUG_ASSERT(grouping);
ZX_DEBUG_ASSERT(!grouping->complete());
ZX_DEBUG_ASSERT(read_handler);
ZX_DEBUG_ASSERT(write_handler);
// There is no special declaration attribute type for descriptors.
auto* attr = grouping->AddAttribute(type, read_reqs, write_reqs);
ZX_DEBUG_ASSERT(attr);
attr->set_read_handler(std::move(read_handler));
attr->set_write_handler(std::move(write_handler));
}
// Returns false if the given service hierarchy contains repeating identifiers.
// Returns the number of attributes that will be in the service attribute group
// (exluding the service declaration) in |out_attrs|.
bool ValidateService(const Service& service, size_t* out_attr_count) {
ZX_DEBUG_ASSERT(out_attr_count);
size_t attr_count = 0u;
std::unordered_set<IdType> ids;
for (const auto& chrc_ptr : service.characteristics()) {
if (ids.count(chrc_ptr->id()) != 0u) {
bt_log(SPEW, "gatt", "server: repeated ID: %lu", chrc_ptr->id());
return false;
}
ids.insert(chrc_ptr->id());
// +1: Characteristic Declaration (Vol 3, Part G, 3.3.1)
// +1: Characteristic Value Declaration (Vol 3, Part G, 3.3.2)
attr_count += 2;
// Increment the count for the CCC descriptor if the characteristic supports
// notifications or indications.
if ((chrc_ptr->properties() & Property::kNotify) ||
(chrc_ptr->properties() & Property::kIndicate)) {
attr_count++;
}
for (const auto& desc_ptr : chrc_ptr->descriptors()) {
if (ids.count(desc_ptr->id()) != 0u) {
bt_log(SPEW, "gatt", "server: repeated ID: %lu", desc_ptr->id());
return false;
}
// Reject descriptors with types that are internally managed by us.
if (desc_ptr->type() == types::kClientCharacteristicConfig ||
desc_ptr->type() == types::kCharacteristicExtProperties ||
desc_ptr->type() == types::kServerCharacteristicConfig) {
bt_log(SPEW, "gatt", "server: disallowed descriptor type: %s",
desc_ptr->type().ToString().c_str());
return false;
}
ids.insert(desc_ptr->id());
// +1: Characteristic Descriptor Declaration (Vol 3, Part G, 3.3.3)
attr_count++;
}
if (chrc_ptr->extended_properties()) {
attr_count++;
}
}
*out_attr_count = attr_count;
return true;
}
} // namespace
class LocalServiceManager::ServiceData final {
public:
ServiceData(IdType id, att::AttributeGrouping* grouping, Service* service,
ReadHandler&& read_handler, WriteHandler&& write_handler,
ClientConfigCallback&& ccc_callback)
: id_(id),
read_handler_(std::forward<ReadHandler>(read_handler)),
write_handler_(std::forward<WriteHandler>(write_handler)),
ccc_callback_(std::forward<ClientConfigCallback>(ccc_callback)),
weak_ptr_factory_(this) {
ZX_DEBUG_ASSERT(read_handler_);
ZX_DEBUG_ASSERT(write_handler_);
ZX_DEBUG_ASSERT(ccc_callback_);
ZX_DEBUG_ASSERT(grouping);
start_handle_ = grouping->start_handle();
end_handle_ = grouping->end_handle();
// Sort characteristics by UUID size (see Vol 3, Part G, 3.3.1).
auto chrcs = service->ReleaseCharacteristics();
std::sort(chrcs.begin(), chrcs.end(),
[](const auto& chrc_ptr1, const auto& chrc_ptr2) {
return chrc_ptr1->type().CompactSize(false /* allow_32bit */) <
chrc_ptr2->type().CompactSize(false /* allow_32bit */);
});
for (auto& chrc : chrcs) {
AddCharacteristic(grouping, std::move(chrc));
}
}
inline IdType id() const { return id_; }
inline att::Handle start_handle() const { return start_handle_; }
inline att::Handle end_handle() const { return end_handle_; }
bool GetCharacteristicConfig(IdType chrc_id, PeerId peer_id,
ClientCharacteristicConfig* out_config) {
ZX_DEBUG_ASSERT(out_config);
auto iter = chrc_configs_.find(chrc_id);
if (iter == chrc_configs_.end())
return false;
uint16_t value = iter->second.Get(peer_id);
out_config->handle = iter->second.handle();
out_config->notify = value & kCCCNotificationBit;
out_config->indicate = value & kCCCIndicationBit;
return true;
}
// Invoke the ClientConfigCallback for each matching client to be removed if
// notify or indicate is enabled to signal that they are cleared, then clears
// them.
void DisconnectClient(PeerId peer_id) {
for (auto& id_config_pair : chrc_configs_) {
const uint16_t value = id_config_pair.second.Get(peer_id);
id_config_pair.second.Erase(peer_id);
if (value != 0) {
ccc_callback_(id_, id_config_pair.first, peer_id, false, false);
}
}
}
private:
class CharacteristicConfig {
public:
explicit CharacteristicConfig(att::Handle handle) : handle_(handle) {}
CharacteristicConfig(CharacteristicConfig&&) = default;
CharacteristicConfig& operator=(CharacteristicConfig&&) = default;
// The characteristic handle.
att::Handle handle() const { return handle_; }
uint16_t Get(PeerId peer_id) {
auto iter = client_states_.find(peer_id);
// If a configuration doesn't exist for |peer_id| then return the default
// value.
if (iter == client_states_.end())
return 0;
return iter->second;
}
void Set(PeerId peer_id, uint16_t value) {
client_states_[peer_id] = value;
}
void Erase(PeerId peer_id) { client_states_.erase(peer_id); }
private:
att::Handle handle_;
std::unordered_map<PeerId, uint16_t> client_states_;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(CharacteristicConfig);
};
// Called when a read request is performed on a CCC descriptor belonging to
// the characteristic identified by |chrc_id|.
void OnReadCCC(IdType chrc_id, PeerId peer_id, att::Handle handle,
uint16_t offset, const ReadResponder& result_cb) {
uint16_t value = 0;
auto iter = chrc_configs_.find(chrc_id);
if (iter != chrc_configs_.end()) {
value = iter->second.Get(peer_id);
}
value = htole16(value);
result_cb(
att::ErrorCode::kNoError,
BufferView(reinterpret_cast<const uint8_t*>(&value), sizeof(value)));
}
// Called when a write request is performed on a CCC descriptor belonging to
// the characteristic identified by |chrc_id|.
void OnWriteCCC(IdType chrc_id, uint8_t chrc_props, PeerId peer_id,
att::Handle handle, uint16_t offset, const ByteBuffer& value,
const WriteResponder& result_cb) {
if (offset != 0u) {
result_cb(att::ErrorCode::kInvalidOffset);
return;
}
if (value.size() != sizeof(uint16_t)) {
result_cb(att::ErrorCode::kInvalidAttributeValueLength);
return;
}
uint16_t ccc_value = le16toh(value.As<uint16_t>());
if (ccc_value > (kCCCNotificationBit | kCCCIndicationBit)) {
result_cb(att::ErrorCode::kInvalidPDU);
return;
}
bool notify = ccc_value & kCCCNotificationBit;
bool indicate = ccc_value & kCCCIndicationBit;
if ((notify && !(chrc_props & Property::kNotify)) ||
(indicate && !(chrc_props & Property::kIndicate))) {
result_cb(att::ErrorCode::kWriteNotPermitted);
return;
}
auto iter = chrc_configs_.find(chrc_id);
if (iter == chrc_configs_.end()) {
auto result_pair =
chrc_configs_.emplace(chrc_id, CharacteristicConfig(handle));
iter = result_pair.first;
}
// Send a reply back.
result_cb(att::ErrorCode::kNoError);
uint16_t current_value = iter->second.Get(peer_id);
iter->second.Set(peer_id, ccc_value);
if (current_value != ccc_value) {
ccc_callback_(id_, chrc_id, peer_id, notify, indicate);
}
}
void AddCharacteristic(att::AttributeGrouping* grouping,
CharacteristicPtr chrc) {
// Set up the characteristic callbacks.
// TODO(armansito): Consider tracking a transaction timeout here (NET-338).
IdType id = chrc->id();
uint8_t props = chrc->properties();
uint16_t ext_props = chrc->extended_properties();
auto self = weak_ptr_factory_.GetWeakPtr();
auto read_handler = [self, id, props](const auto& peer_id,
att::Handle handle, uint16_t offset,
auto result_cb) {
if (!self) {
result_cb(att::ErrorCode::kUnlikelyError, BufferView());
return;
}
// ATT permissions checks passed if we got here; also check the
// characteristic property.
if (!(props & Property::kRead)) {
// TODO(armansito): Return kRequestNotSupported?
result_cb(att::ErrorCode::kReadNotPermitted, BufferView());
return;
}
self->read_handler_(self->id_, id, offset, std::move(result_cb));
};
auto write_handler = [self, id, props](const auto& peer_id,
att::Handle handle, uint16_t offset,
const auto& value, auto result_cb) {
if (!self) {
if (result_cb)
result_cb(att::ErrorCode::kUnlikelyError);
return;
}
// If |result_cb| was provided, then this is a write request and the
// characteristic must support the "write" procedure.
if (result_cb && !(props & Property::kWrite)) {
// TODO(armansito): Return kRequestNotSupported?
result_cb(att::ErrorCode::kWriteNotPermitted);
return;
}
if (!result_cb && !(props & Property::kWriteWithoutResponse))
return;
self->write_handler_(self->id_, id, offset, value, std::move(result_cb));
};
att::Handle chrc_handle = InsertCharacteristicAttributes(
grouping, *chrc, std::move(read_handler), std::move(write_handler));
if (props & Property::kNotify || props & Property::kIndicate) {
AddCCCDescriptor(grouping, *chrc, chrc_handle);
}
if (ext_props) {
auto* decl_attr = grouping->AddAttribute(
types::kCharacteristicExtProperties,
att::AccessRequirements(false, false, false), // read (no security)
att::AccessRequirements()); // write (not allowed)
ZX_DEBUG_ASSERT(decl_attr);
decl_attr->SetValue(CreateStaticByteBuffer(
(uint8_t)(ext_props & 0x00FF), (uint8_t)((ext_props & 0xFF00) >> 8)));
}
// TODO(armansito): Inject a SCC descriptor if the characteristic has the
// broadcast property and if we ever support configured broadcasts.
// Sort descriptors by UUID size. This is not required by the specification
// but we do this to return as many descriptors as possible in a ATT Find
// Information response.
auto descs = chrc->ReleaseDescriptors();
std::sort(descs.begin(), descs.end(),
[](const auto& desc_ptr1, const auto& desc_ptr2) {
return desc_ptr1->type().CompactSize(false /* allow_32bit */) <
desc_ptr2->type().CompactSize(false /* allow_32bit */);
});
for (auto& desc : descs) {
AddDescriptor(grouping, std::move(desc));
}
}
void AddDescriptor(att::AttributeGrouping* grouping, DescriptorPtr desc) {
auto self = weak_ptr_factory_.GetWeakPtr();
auto read_handler = [self, id = desc->id()](
const auto& peer_id, att::Handle handle,
uint16_t offset, auto result_cb) {
if (!self) {
result_cb(att::ErrorCode::kUnlikelyError, BufferView());
return;
}
self->read_handler_(self->id_, id, offset, std::move(result_cb));
};
auto write_handler = [self, id = desc->id()](
const auto& peer_id, att::Handle handle,
uint16_t offset, const auto& value,
auto result_cb) {
// Descriptors cannot be written using the "write without response"
// procedure.
if (!result_cb)
return;
if (!self) {
result_cb(att::ErrorCode::kUnlikelyError);
return;
}
self->write_handler_(self->id_, id, offset, value, std::move(result_cb));
};
InsertDescriptorAttribute(grouping, desc->type(), desc->read_permissions(),
desc->write_permissions(),
std::move(read_handler),
std::move(write_handler));
}
void AddCCCDescriptor(att::AttributeGrouping* grouping,
const Characteristic& chrc, att::Handle chrc_handle) {
ZX_DEBUG_ASSERT(chrc.update_permissions().allowed());
// Readable with no authentication or authorization (Vol 3, Part G,
// 3.3.3.3). We let the service determine the encryption permission.
att::AccessRequirements read_reqs(
chrc.update_permissions().encryption_required(), false, false);
IdType id = chrc.id();
auto self = weak_ptr_factory_.GetWeakPtr();
auto read_handler = [self, id, chrc_handle](
const auto& peer_id, att::Handle handle,
uint16_t offset, auto result_cb) {
if (!self) {
result_cb(att::ErrorCode::kUnlikelyError, BufferView());
return;
}
self->OnReadCCC(id, peer_id, chrc_handle, offset, std::move(result_cb));
};
auto write_handler = [self, id, chrc_handle, props = chrc.properties()](
const auto& peer_id, att::Handle handle,
uint16_t offset, const auto& value,
auto result_cb) {
if (!self) {
result_cb(att::ErrorCode::kUnlikelyError);
return;
}
self->OnWriteCCC(id, props, peer_id, chrc_handle, offset, value,
std::move(result_cb));
};
// The write permission is determined by the service.
InsertDescriptorAttribute(grouping, types::kClientCharacteristicConfig,
read_reqs, chrc.update_permissions(),
std::move(read_handler),
std::move(write_handler));
}
IdType id_;
att::Handle start_handle_;
att::Handle end_handle_;
ReadHandler read_handler_;
WriteHandler write_handler_;
ClientConfigCallback ccc_callback_;
// Characteristic configuration states.
// TODO(armansito): Add a mechanism to persist client configuration for bonded
// devices.
std::unordered_map<IdType, CharacteristicConfig> chrc_configs_;
fxl::WeakPtrFactory<ServiceData> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(ServiceData);
};
LocalServiceManager::LocalServiceManager()
: db_(att::Database::Create()), next_service_id_(1ull) {
ZX_DEBUG_ASSERT(db_);
}
LocalServiceManager::~LocalServiceManager() {}
IdType LocalServiceManager::RegisterService(ServicePtr service,
ReadHandler read_handler,
WriteHandler write_handler,
ClientConfigCallback ccc_callback) {
ZX_DEBUG_ASSERT(service);
ZX_DEBUG_ASSERT(read_handler);
ZX_DEBUG_ASSERT(write_handler);
ZX_DEBUG_ASSERT(ccc_callback);
if (services_.find(next_service_id_) != services_.end()) {
bt_log(SPEW, "gatt", "server: Ran out of service IDs");
return kInvalidId;
}
size_t attr_count;
if (!ValidateService(*service, &attr_count))
return kInvalidId;
// GATT does not support 32-bit UUIDs.
const BufferView service_decl_value =
service->type().CompactView(false /* allow_32bit */);
// TODO(armansito): Cluster services with 16-bit and 128-bit together inside
// |db_| (Vol 3, Part G, 3.1).
att::AttributeGrouping* grouping = db_->NewGrouping(
service->primary() ? types::kPrimaryService : types::kSecondaryService,
attr_count, service_decl_value);
if (!grouping) {
bt_log(TRACE, "gatt",
"server: Failed to allocate attribute grouping for service");
return kInvalidId;
}
// Creating a ServiceData will populate the attribute grouping.
auto service_data = std::make_unique<ServiceData>(
next_service_id_, grouping, service.get(), std::move(read_handler),
std::move(write_handler), std::move(ccc_callback));
ZX_DEBUG_ASSERT(grouping->complete());
grouping->set_active(true);
// TODO(armansito): Handle potential 64-bit unsigned overflow?
IdType id = next_service_id_++;
services_[id] = std::move(service_data);
if (service_changed_callback_) {
service_changed_callback_(id, grouping->start_handle(),
grouping->end_handle());
}
return id;
}
bool LocalServiceManager::UnregisterService(IdType service_id) {
auto iter = services_.find(service_id);
if (iter == services_.end())
return false;
const att::Handle start_handle = iter->second->start_handle();
const att::Handle end_handle = iter->second->end_handle();
db_->RemoveGrouping(start_handle);
services_.erase(iter);
if (service_changed_callback_) {
service_changed_callback_(service_id, start_handle, end_handle);
}
return true;
}
bool LocalServiceManager::GetCharacteristicConfig(
IdType service_id, IdType chrc_id, PeerId peer_id,
ClientCharacteristicConfig* out_config) {
ZX_DEBUG_ASSERT(out_config);
auto iter = services_.find(service_id);
if (iter == services_.end())
return false;
return iter->second->GetCharacteristicConfig(chrc_id, peer_id, out_config);
}
void LocalServiceManager::DisconnectClient(PeerId peer_id) {
for (auto& id_service_pair : services_) {
id_service_pair.second->DisconnectClient(peer_id);
}
}
} // namespace gatt
} // namespace bt