| // 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 |