blob: c94d2e12d4f1af4adea8faee0e141009a0761be1 [file] [log] [blame]
// Copyright 2023 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "pw_bluetooth_sapphire/internal/host/gap/generic_access_client.h"
#include "pw_bluetooth_sapphire/internal/host/gap/gap.h"
namespace bt::gap::internal {
GenericAccessClient::GenericAccessClient(PeerId peer_id,
gatt::RemoteService::WeakPtr service)
: WeakSelf(this), service_(std::move(service)), peer_id_(peer_id) {
BT_ASSERT(service_.is_alive());
BT_ASSERT(service_->uuid() == kGenericAccessService);
}
void GenericAccessClient::ReadDeviceName(DeviceNameCallback callback) {
service_->DiscoverCharacteristics(
[self = GetWeakPtr(), cb = std::move(callback)](
att::Result<> result, const gatt::CharacteristicMap& chars) mutable {
if (!self.is_alive()) {
return;
}
if (result.is_error()) {
cb(result.take_error());
return;
}
std::optional<gatt::CharacteristicHandle> device_name_value_handle;
for (auto& [handle, chr] : chars) {
auto& data = chr.first;
if (data.type == kDeviceNameCharacteristic) {
device_name_value_handle.emplace(data.value_handle);
break;
}
}
if (!device_name_value_handle) {
bt_log(DEBUG,
"gap-le",
"GAP service does not have device name characteristic "
"(peer: %s)",
bt_str(self->peer_id_));
cb(ToResult(HostError::kNotFound).take_error());
return;
}
// according to Core Spec v5.3, Vol 3, Part C, 12.1: "0 to 248 octets in
// length"
self->service_->ReadLongCharacteristic(
*device_name_value_handle,
/*offset=*/0,
att::kMaxAttributeValueLength,
[self, cb = std::move(cb)](att::Result<> result,
const ByteBuffer& buffer,
bool /*maybe_truncated*/) mutable {
if (!self.is_alive()) {
return;
}
if (bt_is_error(
result,
DEBUG,
"gap-le",
"error reading device name characteristic (peer: %s)",
bt_str(self->peer_id_))) {
cb(result.take_error());
return;
}
const auto device_name_end =
std::find(buffer.begin(), buffer.end(), '\0');
cb(fit::ok(std::string(buffer.begin(), device_name_end)));
});
});
}
void GenericAccessClient::ReadAppearance(AppearanceCallback callback) {
service_->DiscoverCharacteristics([self = GetWeakPtr(),
cb = std::move(callback)](
att::Result<> result,
const gatt::CharacteristicMap&
chars) mutable {
if (!self.is_alive()) {
return;
}
if (result.is_error()) {
cb(result.take_error());
return;
}
std::optional<gatt::CharacteristicHandle> appearance_value_handle;
for (auto& [handle, chr] : chars) {
auto& data = chr.first;
if (data.type == kAppearanceCharacteristic) {
appearance_value_handle.emplace(data.value_handle);
break;
}
}
if (!appearance_value_handle) {
bt_log(DEBUG,
"gap-le",
"GAP service does not have appearance characteristic "
"(peer: %s)",
bt_str(self->peer_id_));
cb(ToResult(HostError::kNotFound).take_error());
return;
}
// according to Core Spec v5.3, Vol 3, Part C, 12.2: "2 octets in length"
self->service_->ReadCharacteristic(
*appearance_value_handle,
[self, cb = std::move(cb)](att::Result<> result,
const ByteBuffer& buffer,
bool /*maybe_truncated*/) mutable {
if (!self.is_alive()) {
return;
}
if (bt_is_error(result,
DEBUG,
"gap-le",
"error reading appearance characteristic (peer: %s)",
bt_str(self->peer_id_))) {
cb(result.take_error());
return;
}
if (buffer.size() != sizeof(uint16_t)) {
bt_log(
DEBUG,
"gap-le",
"appearance characteristic has invalid value size (peer: %s)",
bt_str(self->peer_id_));
cb(ToResult(HostError::kPacketMalformed).take_error());
return;
}
uint16_t char_value = le16toh(buffer.template To<uint16_t>());
cb(fit::ok(char_value));
});
});
}
void GenericAccessClient::ReadPeripheralPreferredConnectionParameters(
ConnectionParametersCallback callback) {
service_->DiscoverCharacteristics([self = GetWeakPtr(),
cb = std::move(callback)](
att::Result<> result,
const gatt::CharacteristicMap&
chars) mutable {
if (!self.is_alive()) {
return;
}
if (result.is_error()) {
cb(result.take_error());
return;
}
std::optional<gatt::CharacteristicHandle> conn_params_value_handle;
for (auto& [handle, chr] : chars) {
auto& data = chr.first;
if (data.type == kPeripheralPreferredConnectionParametersCharacteristic) {
conn_params_value_handle.emplace(data.value_handle);
break;
}
}
if (!conn_params_value_handle) {
bt_log(DEBUG,
"gap-le",
"GAP service does not have peripheral preferred connection "
"parameters characteristic "
"(peer: %s)",
bt_str(self->peer_id_));
cb(ToResult(HostError::kNotFound).take_error());
return;
}
self->service_->ReadCharacteristic(
*conn_params_value_handle,
[self, cb = std::move(cb)](att::Result<> result,
const ByteBuffer& buffer,
bool /*maybe_truncated*/) mutable {
if (!self.is_alive()) {
return;
}
if (bt_is_error(result,
DEBUG,
"gap-le",
"error reading peripheral preferred connection "
"parameters characteristic "
"(peer: %s)",
bt_str(self->peer_id_))) {
cb(result.take_error());
return;
}
if (buffer.size() !=
sizeof(
PeripheralPreferredConnectionParametersCharacteristicValue)) {
bt_log(DEBUG,
"gap-le",
"peripheral preferred connection parameters characteristic "
"has invalid value size "
"(peer: %s)",
bt_str(self->peer_id_));
cb(ToResult(HostError::kPacketMalformed).take_error());
return;
}
auto char_value = buffer.template To<
PeripheralPreferredConnectionParametersCharacteristicValue>();
hci_spec::LEPreferredConnectionParameters params(
le16toh(char_value.min_interval),
le16toh(char_value.max_interval),
le16toh(char_value.max_latency),
le16toh(char_value.supervision_timeout));
cb(fit::ok(params));
});
});
}
} // namespace bt::gap::internal