blob: 4bca4bcbc8a89a0bd96a6315f767bb292f8bdbba [file] [log] [blame]
// Copyright 2022 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 "gatt2_server_server.h"
#include <functional>
#include <utility>
#include "fuchsia/bluetooth/cpp/fidl.h"
#include "fuchsia/bluetooth/gatt2/cpp/fidl.h"
#include "lib/fidl/cpp/interface_ptr.h"
#include "lib/fit/function.h"
#include "src/connectivity/bluetooth/core/bt-host/fidl/gatt2_server_ids.h"
#include "src/connectivity/bluetooth/core/bt-host/fidl/helpers.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/att/att.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/uuid.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gatt/gatt_defs.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gatt/server.h"
namespace fbt = fuchsia::bluetooth;
namespace btg = bt::gatt;
using fuchsia::bluetooth::gatt2::AttributePermissions;
using fuchsia::bluetooth::gatt2::Characteristic;
using fuchsia::bluetooth::gatt2::Descriptor;
using fgatt2Err = fuchsia::bluetooth::gatt2::Error;
using fuchsia::bluetooth::gatt2::Handle;
using fuchsia::bluetooth::gatt2::INITIAL_VALUE_CHANGED_CREDITS;
using fuchsia::bluetooth::gatt2::LocalService;
using fuchsia::bluetooth::gatt2::LocalService_ReadValue_Result;
using fuchsia::bluetooth::gatt2::LocalService_WriteValue_Result;
using fuchsia::bluetooth::gatt2::LocalServicePeerUpdateRequest;
using fuchsia::bluetooth::gatt2::LocalServiceWriteValueRequest;
using fuchsia::bluetooth::gatt2::PublishServiceError;
using fuchsia::bluetooth::gatt2::SecurityRequirements;
using fuchsia::bluetooth::gatt2::ServiceInfo;
using fuchsia::bluetooth::gatt2::ServiceKind;
using fuchsia::bluetooth::gatt2::ValueChangedParameters;
namespace bthost {
Gatt2ServerServer::Gatt2ServerServer(
btg::GATT::WeakPtr gatt, fidl::InterfaceRequest<fuchsia::bluetooth::gatt2::Server> request)
: GattServerBase(std::move(gatt), this, std::move(request)), weak_self_(this) {}
Gatt2ServerServer::~Gatt2ServerServer() {
// Remove all services from the local GATT host.
for (const auto& iter : services_) {
gatt()->UnregisterService(iter.first.value());
}
}
void Gatt2ServerServer::RemoveService(InternalServiceId id) {
// Close the connection to the FIDL LocalService.
services_.erase(id);
// Unregister the service from the local GATT host. Don't remove the ID from service_id_mapping_
// to prevent service ID reuse.
gatt()->UnregisterService(id.value());
}
void Gatt2ServerServer::PublishService(ServiceInfo info,
fidl::InterfaceHandle<LocalService> service,
PublishServiceCallback callback) {
if (!info.has_handle()) {
bt_log(WARN, "fidl", "%s: `info` missing required `handle`", __FUNCTION__);
callback(fpromise::error(PublishServiceError::INVALID_SERVICE_HANDLE));
return;
}
if (!info.has_type()) {
bt_log(WARN, "fidl", "%s: `info` missing required `type` UUID", __FUNCTION__);
callback(fpromise::error(PublishServiceError::INVALID_UUID));
return;
}
if (!info.has_characteristics()) {
bt_log(WARN, "fidl", "%s: `info` missing required `characteristics`", __FUNCTION__);
callback(fpromise::error(PublishServiceError::INVALID_CHARACTERISTICS));
return;
}
ClientServiceId client_svc_id(info.handle().value);
if (service_id_mapping_.count(client_svc_id) != 0) {
bt_log(WARN, "fidl", "%s: cannot publish service with already-used `handle`", __FUNCTION__);
callback(fpromise::error(PublishServiceError::INVALID_SERVICE_HANDLE));
return;
}
bt::UUID service_type(info.type().value);
// The default value for kind is PRIMARY if not present.
bool primary = info.has_kind() ? info.kind() == ServiceKind::PRIMARY : true;
// Process the FIDL service tree.
auto gatt_svc = std::make_unique<btg::Service>(primary, service_type);
for (const auto& fidl_chrc : info.characteristics()) {
btg::CharacteristicPtr maybe_chrc = fidl_helpers::Gatt2CharacteristicFromFidl(fidl_chrc);
if (!maybe_chrc) {
callback(fpromise::error(PublishServiceError::INVALID_CHARACTERISTICS));
return;
}
gatt_svc->AddCharacteristic(std::move(maybe_chrc));
}
auto self = weak_self_.GetWeakPtr();
auto id_cb = [self, service_handle = std::move(service), client_svc_id,
callback = std::move(callback)](btg::IdType raw_id) mutable {
if (!self.is_alive()) {
return;
}
if (raw_id == bt::gatt::kInvalidId) {
bt_log(INFO, "bt-host", "internal error publishing service (handle: %lu)",
client_svc_id.value());
callback(fpromise::error(PublishServiceError::UNLIKELY_ERROR));
return;
}
InternalServiceId internal_id(raw_id);
auto error_handler = [self, client_svc_id, internal_id](zx_status_t status) {
bt_log(INFO, "bt-host", "LocalService shut down, removing GATT service (id: %lu)",
client_svc_id.value());
self->RemoveService(internal_id);
};
fidl::InterfacePtr<LocalService> service_ptr = service_handle.Bind();
service_ptr.set_error_handler(error_handler);
service_ptr.events().OnSuppressDiscovery = [self, internal_id]() {
self->OnSuppressDiscovery(internal_id);
};
service_ptr.events().OnNotifyValue = [self, internal_id](ValueChangedParameters update) {
self->OnNotifyValue(internal_id, std::move(update));
};
service_ptr.events().OnIndicateValue = [self, internal_id](ValueChangedParameters update,
zx::eventpair confirm) {
self->OnIndicateValue(internal_id, std::move(update), std::move(confirm));
};
self->services_.emplace(internal_id, Service{.local_svc_ptr = std::move(service_ptr)});
self->service_id_mapping_[client_svc_id] = internal_id;
callback(fpromise::ok());
};
gatt()->RegisterService(
std::move(gatt_svc), std::move(id_cb),
fit::bind_member<&Gatt2ServerServer::OnReadRequest>(this),
fit::bind_member<&Gatt2ServerServer::OnWriteRequest>(this),
fit::bind_member<&Gatt2ServerServer::OnClientCharacteristicConfiguration>(this));
}
void Gatt2ServerServer::OnReadRequest(bt::PeerId peer_id, bt::gatt::IdType service_id,
btg::IdType id, uint16_t offset,
btg::ReadResponder responder) {
auto svc_iter = services_.find(InternalServiceId(service_id));
// GATT must only send read requests for registered services.
BT_ASSERT(svc_iter != services_.end());
auto cb = [responder = std::move(responder)](LocalService_ReadValue_Result res) mutable {
if (res.is_err()) {
responder(fit::error(fidl_helpers::Gatt2ErrorCodeFromFidl(res.err())), bt::BufferView());
return;
}
responder(fit::ok(), bt::BufferView(res.response().value));
};
svc_iter->second.local_svc_ptr->ReadValue(fbt::PeerId{peer_id.value()}, Handle{id}, offset,
std::move(cb));
}
void Gatt2ServerServer::OnWriteRequest(bt::PeerId peer_id, bt::gatt::IdType service_id,
btg::IdType id, uint16_t offset, const bt::ByteBuffer& value,
btg::WriteResponder responder) {
auto svc_iter = services_.find(InternalServiceId(service_id));
// GATT must only send write requests for registered services.
BT_ASSERT(svc_iter != services_.end());
auto cb = [responder = std::move(responder)](LocalService_WriteValue_Result result) mutable {
// If this was a Write Without Response request, the response callback will be null.
if (responder) {
fit::result<bt::att::ErrorCode> rsp = fit::ok();
if (!result.is_response()) {
rsp = fit::error(fidl_helpers::Gatt2ErrorCodeFromFidl(result.err()));
}
responder(rsp);
}
};
LocalServiceWriteValueRequest params;
params.set_peer_id(fbt::PeerId{peer_id.value()});
params.set_handle(Handle{id});
params.set_offset(offset);
params.set_value(value.ToVector());
svc_iter->second.local_svc_ptr->WriteValue(std::move(params), std::move(cb));
}
void Gatt2ServerServer::OnClientCharacteristicConfiguration(bt::gatt::IdType service_id,
bt::gatt::IdType chrc_id,
bt::PeerId peer_id, bool notify,
bool indicate) {
auto svc_iter = services_.find(InternalServiceId(service_id));
// GATT must only send CCC updates for registered services.
BT_ASSERT(svc_iter != services_.end());
auto cb = []() { bt_log(TRACE, "fidl", "characteristic configuration acknowledged"); };
svc_iter->second.local_svc_ptr->CharacteristicConfiguration(
fbt::PeerId{peer_id.value()}, Handle{chrc_id}, notify, indicate, cb);
}
void Gatt2ServerServer::OnSuppressDiscovery(InternalServiceId service_id) {
// TODO(https://fxbug.dev/42180948): This event is not yet supported
bt_log(ERROR, "fidl", "%s not supported - see https://fxbug.dev/42180948", __FUNCTION__);
}
bool Gatt2ServerServer::ValidateValueChangedEvent(
InternalServiceId service_id, const fuchsia::bluetooth::gatt2::ValueChangedParameters& update,
const char* update_type) {
auto iter = services_.find(service_id);
// It is impossible for clients to send events to a closed service.
BT_ASSERT(iter != services_.end());
// Subtract credit before validating parameters so that credits aren't permanently lost from the
// client's perspective.
SubtractCredit(iter->second);
if (!update.has_handle()) {
bt_log(WARN, "fidl", "ValueChangedParameters missing required `handle`");
return false;
}
if (!update.has_value()) {
bt_log(WARN, "fidl", "ValueChangedParameters missing required `value`");
return false;
}
return true;
}
void Gatt2ServerServer::OnNotifyValue(InternalServiceId service_id,
fuchsia::bluetooth::gatt2::ValueChangedParameters update) {
if (!ValidateValueChangedEvent(service_id, update, "notification")) {
RemoveService(service_id);
return;
}
bt::gatt::IndicationCallback indicate_cb = nullptr;
if (!update.has_peer_ids() || update.peer_ids().empty()) {
gatt()->UpdateConnectedPeers(service_id.value(), update.handle().value, update.value(),
/*indicate_cb=*/nullptr);
return;
}
for (auto peer_id : update.peer_ids()) {
gatt()->SendUpdate(service_id.value(), update.handle().value, bt::PeerId(peer_id.value),
update.value(),
/*indicate_cb=*/nullptr);
}
}
void Gatt2ServerServer::OnIndicateValue(InternalServiceId service_id,
fuchsia::bluetooth::gatt2::ValueChangedParameters update,
zx::eventpair confirmation) {
if (!ValidateValueChangedEvent(service_id, update, "indication")) {
RemoveService(service_id);
return;
}
if (!update.has_peer_ids() || update.peer_ids().empty()) {
auto indicate_cb = [confirm = std::move(confirmation)](bt::att::Result<> status) mutable {
if (status.is_error()) {
bt_log(WARN, "fidl", "indication failed: %s", bt_str(status));
return;
}
confirm.signal_peer(/*clear_mask=*/0, ZX_EVENTPAIR_SIGNALED);
};
gatt()->UpdateConnectedPeers(service_id.value(), update.handle().value, update.value(),
std::move(indicate_cb));
return;
}
bt::att::ResultFunction<> shared_result_fn =
[pending = update.peer_ids().size(),
confirm = std::move(confirmation)](bt::att::Result<> res) mutable {
if (!confirm.is_valid()) {
// An error was already signaled.
return;
}
if (res.is_error()) {
bt_log(INFO, "fidl", "failed to indicate some peers: %s", bt_str(res.error_value()));
confirm.reset(); // signals ZX_EVENTPAIR_PEER_CLOSED
return;
}
pending--;
if (pending == 0) {
confirm.signal_peer(/*clear_mask=*/0, ZX_EVENTPAIR_SIGNALED);
}
};
for (auto peer_id : update.peer_ids()) {
gatt()->SendUpdate(service_id.value(), update.handle().value, bt::PeerId(peer_id.value),
update.value(), shared_result_fn.share());
}
}
void Gatt2ServerServer::SubtractCredit(Service& svc) {
// It is impossible for clients to violate the credit system from the server's
// perspective because new credits are granted before the count reaches 0 (excessive events will
// fill the FIDL channel and eventually crash the client).
BT_ASSERT(svc.credits > 0);
svc.credits--;
if (svc.credits <= REFRESH_CREDITS_AT) {
// Static cast OK because current_credits > 0 and we never add more than
// INITIAL_VALUE_CHANGED_CREDITS.
uint8_t credits_to_add = static_cast<uint8_t>(INITIAL_VALUE_CHANGED_CREDITS - svc.credits);
svc.local_svc_ptr->ValueChangedCredit(credits_to_add);
svc.credits = INITIAL_VALUE_CHANGED_CREDITS;
}
}
} // namespace bthost