| // 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 "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gatt/remote_service.h" |
| |
| #include "lib/fit/defer.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/slab_allocator.h" |
| |
| #pragma clang diagnostic ignored "-Wshadow" |
| |
| namespace bt::gatt { |
| namespace { |
| |
| bool IsInternalUuid(const UUID& uuid) { |
| // clang-format off |
| return |
| uuid == types::kPrimaryService || |
| uuid == types::kSecondaryService || |
| uuid == types::kIncludeDeclaration || |
| uuid == types::kCharacteristicDeclaration || |
| uuid == types::kCharacteristicExtProperties || |
| uuid == types::kCharacteristicUserDescription || |
| uuid == types::kClientCharacteristicConfig || |
| uuid == types::kServerCharacteristicConfig || |
| uuid == types::kCharacteristicFormat || |
| uuid == types::kCharacteristicAggregateFormat; |
| // clang-format on |
| } |
| |
| void ReportReadValueError(att::Result<> status, |
| RemoteService::ReadValueCallback callback) { |
| callback(status, BufferView(), /*maybe_truncated=*/false); |
| } |
| |
| CharacteristicMap CharacteristicsToCharacteristicMap( |
| const std::map<CharacteristicHandle, RemoteCharacteristic>& |
| characteristics) { |
| CharacteristicMap characteristic_map; |
| for (const auto& [_handle, chrc] : characteristics) { |
| characteristic_map.try_emplace(_handle, chrc.info(), chrc.descriptors()); |
| } |
| return characteristic_map; |
| } |
| |
| } // namespace |
| |
| RemoteService::RemoteService(const ServiceData& service_data, |
| Client::WeakPtr client) |
| : service_data_(service_data), |
| client_(std::move(client)), |
| remaining_descriptor_requests_(kSentinel) { |
| BT_DEBUG_ASSERT(client_.is_alive()); |
| } |
| |
| RemoteService::~RemoteService() { |
| for (auto& chr : characteristics_) { |
| chr.second.set_service_changed(service_changed_); |
| } |
| characteristics_.clear(); |
| |
| std::vector<fit::callback<void()>> rm_handlers = std::move(rm_handlers_); |
| for (auto& handler : rm_handlers) { |
| handler(); |
| } |
| } |
| |
| bool RemoteService::AddRemovedHandler(fit::closure handler) { |
| rm_handlers_.emplace_back(std::move(handler)); |
| return true; |
| } |
| |
| void RemoteService::DiscoverCharacteristics(CharacteristicCallback callback) { |
| // Characteristics already discovered. Return success. |
| if (HasCharacteristics()) { |
| // We return a new copy of only the immutable data of our characteristics |
| // and their descriptors. This requires a copy, which *could* be expensive |
| // in the (unlikely) case that a service has a very large number of |
| // characteristics. |
| callback(fit::ok(), CharacteristicsToCharacteristicMap(characteristics_)); |
| return; |
| } |
| |
| // Queue this request. |
| pending_discov_reqs_.emplace_back(std::move(callback)); |
| |
| // Nothing to do if a write request is already pending. |
| if (pending_discov_reqs_.size() > 1u) |
| return; |
| |
| auto self = GetWeakPtr(); |
| auto chrc_cb = [self](const CharacteristicData& chr) { |
| if (!self.is_alive()) { |
| return; |
| } |
| // try_emplace should not fail here; our GATT::Client explicitly ensures |
| // that handles are strictly ascending (as described in the spec) so we |
| // should never see a handle collision |
| self->characteristics_.try_emplace( |
| CharacteristicHandle(chr.value_handle), self->client_, chr); |
| }; |
| |
| auto res_cb = [self](att::Result<> status) mutable { |
| if (!self.is_alive()) { |
| return; |
| } |
| |
| if (bt_is_error(status, TRACE, "gatt", "characteristic discovery failed")) { |
| self->characteristics_.clear(); |
| } |
| |
| if (self->characteristics_.empty()) { |
| if (status.is_ok()) { |
| // 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(CharacteristicHandle id, |
| ReadValueCallback callback) { |
| RemoteCharacteristic* chrc; |
| fit::result status = GetCharacteristic(id, &chrc); |
| BT_DEBUG_ASSERT(chrc || status.is_error()); |
| if (status.is_error()) { |
| ReportReadValueError(status, std::move(callback)); |
| return; |
| } |
| |
| if (!(chrc->info().properties & Property::kRead)) { |
| bt_log(DEBUG, "gatt", "characteristic does not support \"read\""); |
| ReportReadValueError(ToResult(HostError::kNotSupported), |
| std::move(callback)); |
| return; |
| } |
| |
| client_->ReadRequest(chrc->info().value_handle, std::move(callback)); |
| } |
| |
| void RemoteService::ReadLongCharacteristic(CharacteristicHandle id, |
| uint16_t offset, |
| size_t max_bytes, |
| ReadValueCallback callback) { |
| RemoteCharacteristic* chrc; |
| fit::result status = GetCharacteristic(id, &chrc); |
| BT_DEBUG_ASSERT(chrc || status.is_error()); |
| if (status.is_error()) { |
| ReportReadValueError(status, std::move(callback)); |
| return; |
| } |
| |
| if (!(chrc->info().properties & Property::kRead)) { |
| bt_log(DEBUG, "gatt", "characteristic does not support \"read\""); |
| ReportReadValueError(ToResult(HostError::kNotSupported), |
| std::move(callback)); |
| return; |
| } |
| |
| if (max_bytes == 0) { |
| bt_log(TRACE, "gatt", "invalid value for |max_bytes|: 0"); |
| ReportReadValueError(ToResult(HostError::kInvalidParameters), |
| std::move(callback)); |
| return; |
| } |
| |
| // Set up the buffer in which we'll accumulate the blobs. |
| auto buffer = NewBuffer(std::min(max_bytes, att::kMaxAttributeValueLength)); |
| if (!buffer) { |
| ReportReadValueError(ToResult(HostError::kOutOfMemory), |
| std::move(callback)); |
| return; |
| } |
| |
| ReadLongHelper(chrc->info().value_handle, |
| offset, |
| std::move(buffer), |
| 0u /* bytes_read */, |
| std::move(callback)); |
| } |
| |
| void RemoteService::ReadByType(const UUID& type, ReadByTypeCallback callback) { |
| // Caller should not request a UUID of an internal attribute (e.g. service |
| // declaration). |
| if (IsInternalUuid(type)) { |
| bt_log(TRACE, |
| "gatt", |
| "ReadByType called with internal GATT type (type: %s)", |
| bt_str(type)); |
| callback(ToResult(HostError::kInvalidParameters), {}); |
| return; |
| } |
| |
| // Read range is entire service range. |
| ReadByTypeHelper(type, |
| service_data_.range_start, |
| service_data_.range_end, |
| {}, |
| std::move(callback)); |
| } |
| |
| void RemoteService::WriteCharacteristic(CharacteristicHandle id, |
| std::vector<uint8_t> value, |
| att::ResultFunction<> cb) { |
| RemoteCharacteristic* chrc; |
| fit::result status = GetCharacteristic(id, &chrc); |
| BT_DEBUG_ASSERT(chrc || status.is_error()); |
| if (status.is_error()) { |
| cb(status); |
| return; |
| } |
| |
| if (!(chrc->info().properties & Property::kWrite)) { |
| bt_log(DEBUG, "gatt", "characteristic does not support \"write\""); |
| cb(ToResult(HostError::kNotSupported)); |
| return; |
| } |
| |
| client_->WriteRequest(chrc->info().value_handle, |
| BufferView(value.data(), value.size()), |
| std::move(cb)); |
| } |
| |
| void RemoteService::WriteLongCharacteristic(CharacteristicHandle id, |
| uint16_t offset, |
| std::vector<uint8_t> value, |
| ReliableMode reliable_mode, |
| att::ResultFunction<> callback) { |
| RemoteCharacteristic* chrc; |
| fit::result status = GetCharacteristic(id, &chrc); |
| BT_DEBUG_ASSERT(chrc || status.is_error()); |
| if (status.is_error()) { |
| callback(status); |
| return; |
| } |
| |
| if (!(chrc->info().properties & Property::kWrite)) { |
| bt_log(DEBUG, "gatt", "characteristic does not support \"write\""); |
| callback(ToResult(HostError::kNotSupported)); |
| return; |
| } |
| |
| if ((reliable_mode == ReliableMode::kEnabled) && |
| ((!chrc->extended_properties().has_value()) || |
| (!(chrc->extended_properties().value() & |
| ExtendedProperty::kReliableWrite)))) { |
| bt_log(DEBUG, |
| "gatt", |
| "characteristic does not support \"reliable write\"; attempting " |
| "request anyway"); |
| } |
| |
| SendLongWriteRequest(chrc->info().value_handle, |
| offset, |
| BufferView(value.data(), value.size()), |
| reliable_mode, |
| std::move(callback)); |
| } |
| |
| void RemoteService::WriteCharacteristicWithoutResponse( |
| CharacteristicHandle id, |
| std::vector<uint8_t> value, |
| att::ResultFunction<> cb) { |
| RemoteCharacteristic* chrc; |
| fit::result status = GetCharacteristic(id, &chrc); |
| BT_DEBUG_ASSERT(chrc || status.is_error()); |
| if (status.is_error()) { |
| cb(status); |
| return; |
| } |
| |
| if (!(chrc->info().properties & |
| (Property::kWrite | Property::kWriteWithoutResponse))) { |
| bt_log(DEBUG, |
| "gatt", |
| "characteristic does not support \"write without response\""); |
| cb(ToResult(HostError::kNotSupported)); |
| return; |
| } |
| |
| client_->WriteWithoutResponse(chrc->info().value_handle, |
| BufferView(value.data(), value.size()), |
| std::move(cb)); |
| } |
| |
| void RemoteService::ReadDescriptor(DescriptorHandle id, |
| ReadValueCallback callback) { |
| const DescriptorData* desc; |
| fit::result status = GetDescriptor(id, &desc); |
| BT_DEBUG_ASSERT(desc || status.is_error()); |
| if (status.is_error()) { |
| ReportReadValueError(status, std::move(callback)); |
| return; |
| } |
| |
| client_->ReadRequest(desc->handle, std::move(callback)); |
| } |
| |
| void RemoteService::ReadLongDescriptor(DescriptorHandle id, |
| uint16_t offset, |
| size_t max_bytes, |
| ReadValueCallback callback) { |
| const DescriptorData* desc; |
| att::Result<> status = GetDescriptor(id, &desc); |
| BT_DEBUG_ASSERT(desc || status.is_error()); |
| if (status.is_error()) { |
| ReportReadValueError(status, std::move(callback)); |
| return; |
| } |
| |
| if (max_bytes == 0) { |
| bt_log(TRACE, "gatt", "invalid value for |max_bytes|: 0"); |
| ReportReadValueError(ToResult(HostError::kInvalidParameters), |
| std::move(callback)); |
| return; |
| } |
| |
| // Set up the buffer in which we'll accumulate the blobs. |
| auto buffer = NewBuffer(std::min(max_bytes, att::kMaxAttributeValueLength)); |
| if (!buffer) { |
| ReportReadValueError(ToResult(HostError::kOutOfMemory), |
| std::move(callback)); |
| return; |
| } |
| |
| ReadLongHelper(desc->handle, |
| offset, |
| std::move(buffer), |
| 0u /* bytes_read */, |
| std::move(callback)); |
| } |
| |
| void RemoteService::WriteDescriptor(DescriptorHandle id, |
| std::vector<uint8_t> value, |
| att::ResultFunction<> callback) { |
| const DescriptorData* desc; |
| fit::result status = GetDescriptor(id, &desc); |
| BT_DEBUG_ASSERT(desc || status.is_error()); |
| if (status.is_error()) { |
| callback(status); |
| return; |
| } |
| |
| // Do not allow writing to internally reserved descriptors. |
| if (desc->type == types::kClientCharacteristicConfig) { |
| bt_log(DEBUG, "gatt", "writing to CCC descriptor not allowed"); |
| callback(ToResult(HostError::kNotSupported)); |
| return; |
| } |
| |
| client_->WriteRequest(desc->handle, |
| BufferView(value.data(), value.size()), |
| std::move(callback)); |
| } |
| |
| void RemoteService::WriteLongDescriptor(DescriptorHandle id, |
| uint16_t offset, |
| std::vector<uint8_t> value, |
| att::ResultFunction<> callback) { |
| const DescriptorData* desc; |
| fit::result status = GetDescriptor(id, &desc); |
| BT_DEBUG_ASSERT(desc || status.is_error()); |
| if (status.is_error()) { |
| callback(status); |
| return; |
| } |
| |
| // Do not allow writing to internally reserved descriptors. |
| if (desc->type == types::kClientCharacteristicConfig) { |
| bt_log(DEBUG, "gatt", "writing to CCC descriptor not allowed"); |
| callback(ToResult(HostError::kNotSupported)); |
| return; |
| } |
| |
| // For writing long descriptors, reliable mode is not supported. |
| auto mode = ReliableMode::kDisabled; |
| SendLongWriteRequest(desc->handle, |
| offset, |
| BufferView(value.data(), value.size()), |
| mode, |
| std::move(callback)); |
| } |
| |
| void RemoteService::EnableNotifications(CharacteristicHandle id, |
| ValueCallback callback, |
| NotifyStatusCallback status_callback) { |
| RemoteCharacteristic* chrc; |
| fit::result status = GetCharacteristic(id, &chrc); |
| BT_DEBUG_ASSERT(chrc || status.is_error()); |
| if (status.is_error()) { |
| status_callback(status, kInvalidId); |
| return; |
| } |
| |
| chrc->EnableNotifications(std::move(callback), std::move(status_callback)); |
| } |
| |
| void RemoteService::DisableNotifications( |
| CharacteristicHandle id, |
| IdType handler_id, |
| att::ResultFunction<> status_callback) { |
| RemoteCharacteristic* chrc; |
| fit::result status = GetCharacteristic(id, &chrc); |
| BT_DEBUG_ASSERT(chrc || status.is_error()); |
| if (status.is_ok() && !chrc->DisableNotifications(handler_id)) { |
| status = ToResult(HostError::kNotFound); |
| } |
| status_callback(status); |
| } |
| |
| void RemoteService::StartDescriptorDiscovery() { |
| BT_DEBUG_ASSERT(!pending_discov_reqs_.empty()); |
| |
| BT_ASSERT(!characteristics_.empty()); |
| remaining_descriptor_requests_ = characteristics_.size(); |
| |
| auto self = GetWeakPtr(); |
| |
| // 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::Result<> status) { |
| if (!self.is_alive()) { |
| return; |
| } |
| |
| // Do nothing if discovery was concluded earlier (which would have cleared |
| // the pending discovery requests). |
| if (self->pending_discov_reqs_.empty()) { |
| return; |
| } |
| |
| if (status.is_ok()) { |
| self->remaining_descriptor_requests_ -= 1; |
| |
| // Defer handling |
| if (self->remaining_descriptor_requests_ > 0) { |
| return; |
| } |
| |
| // HasCharacteristics() should return true now. |
| BT_DEBUG_ASSERT(self->HasCharacteristics()); |
| |
| // Fall through and notify clients below. |
| } else { |
| BT_DEBUG_ASSERT(!self->HasCharacteristics()); |
| bt_log(DEBUG, "gatt", "descriptor discovery failed %s", bt_str(status)); |
| self->characteristics_.clear(); |
| |
| // Fall through and notify the clients below. |
| } |
| |
| self->CompleteCharacteristicDiscovery(status); |
| }; |
| |
| // Characteristics are stored in an (ordered) std::map by value_handle, so we |
| // iterate in order; according to the spec (BT 5.0 Vol 3, part G, 3.3), the |
| // value handle must appear immediately after the characteristic handle so the |
| // handles are also guaranteed to be in order. Therefore we can use the next |
| // in the iteration to calculate the handle range. |
| for (auto iter = characteristics_.begin(); iter != characteristics_.end(); |
| ++iter) { |
| auto next = iter; |
| ++next; |
| att::Handle end_handle; |
| if (next == characteristics_.end()) { |
| end_handle = service_data_.range_end; |
| } else { |
| end_handle = next->second.info().handle - 1; |
| } |
| |
| BT_DEBUG_ASSERT(client_.is_alive()); |
| iter->second.DiscoverDescriptors(end_handle, desc_done_callback); |
| } |
| } |
| |
| fit::result<Error<>> RemoteService::GetCharacteristic( |
| CharacteristicHandle id, RemoteCharacteristic** out_char) { |
| BT_DEBUG_ASSERT(out_char); |
| |
| if (!HasCharacteristics()) { |
| *out_char = nullptr; |
| return fit::error(HostError::kNotReady); |
| } |
| |
| auto chr = characteristics_.find(id); |
| if (chr == characteristics_.end()) { |
| *out_char = nullptr; |
| return fit::error(HostError::kNotFound); |
| } |
| |
| *out_char = &chr->second; |
| return fit::ok(); |
| } |
| |
| fit::result<Error<>> RemoteService::GetDescriptor( |
| DescriptorHandle id, const DescriptorData** out_desc) { |
| BT_DEBUG_ASSERT(out_desc); |
| |
| if (!HasCharacteristics()) { |
| *out_desc = nullptr; |
| return fit::error(HostError::kNotReady); |
| } |
| |
| for (auto iter = characteristics_.begin(); iter != characteristics_.end(); |
| ++iter) { |
| auto next = iter; |
| ++next; |
| if (next == characteristics_.end() || |
| next->second.info().handle > id.value) { |
| const auto& descriptors = iter->second.descriptors(); |
| auto desc = descriptors.find(id); |
| if (desc != descriptors.end()) { |
| *out_desc = &desc->second; |
| return fit::ok(); |
| } |
| } |
| } |
| |
| *out_desc = nullptr; |
| return fit::error(HostError::kNotFound); |
| } |
| |
| void RemoteService::CompleteCharacteristicDiscovery(att::Result<> status) { |
| BT_DEBUG_ASSERT(!pending_discov_reqs_.empty()); |
| BT_DEBUG_ASSERT(status.is_error() || remaining_descriptor_requests_ == 0u); |
| |
| // We return a new copy of only the immutable data of our characteristics and |
| // their descriptors. This requires a copy, which *could* be expensive in the |
| // (unlikely) case that a service has a very large number of characteristics. |
| CharacteristicMap characteristic_map = |
| CharacteristicsToCharacteristicMap(characteristics_); |
| |
| auto pending = std::move(pending_discov_reqs_); |
| for (auto& discovery_req_cb : pending) { |
| discovery_req_cb(status, characteristic_map); |
| } |
| } |
| |
| void RemoteService::SendLongWriteRequest(att::Handle handle, |
| uint16_t offset, |
| BufferView value, |
| ReliableMode reliable_mode, |
| att::ResultFunction<> final_cb) { |
| att::PrepareWriteQueue long_write_queue; |
| auto header_ln = sizeof(att::PrepareWriteRequestParams) + sizeof(att::OpCode); |
| uint16_t bytes_written = 0; |
| |
| // Divide up the long write into it's constituent PreparedWrites and add them |
| // to the queue. |
| while (bytes_written < value.size()) { |
| uint16_t part_value_size = static_cast<uint16_t>( |
| std::min(client_->mtu() - header_ln, value.size() - bytes_written)); |
| auto part_buffer = value.view(bytes_written, part_value_size); |
| |
| long_write_queue.push(att::QueuedWrite(handle, offset, part_buffer)); |
| |
| bytes_written += part_value_size; |
| offset += part_value_size; |
| } |
| |
| client_->ExecutePrepareWrites(std::move(long_write_queue), |
| std::move(reliable_mode), |
| std::move(final_cb)); |
| } |
| |
| void RemoteService::ReadLongHelper(att::Handle value_handle, |
| uint16_t offset, |
| MutableByteBufferPtr buffer, |
| size_t bytes_read, |
| ReadValueCallback callback) { |
| BT_DEBUG_ASSERT(callback); |
| BT_DEBUG_ASSERT(buffer); |
| |
| auto self = GetWeakPtr(); |
| auto read_cb = [self, |
| value_handle, |
| offset, |
| buffer = std::move(buffer), |
| bytes_read, |
| cb = std::move(callback)]( |
| att::Result<> status, |
| const ByteBuffer& blob, |
| bool maybe_truncated_by_mtu) mutable { |
| if (!self.is_alive()) { |
| return; |
| } |
| |
| if (status.is_error()) { |
| // "If the Characteristic Value is not longer than (ATT_MTU – 1) an |
| // ATT_ERROR_RSP PDU with the error code set to kAttributeNotLong shall be |
| // received on the first ATT_READ_BLOB_REQ PDU." (Core Spec v5.2, Vol 3, |
| // Part G, Sec 4.8.3). Report the short value read in the previous |
| // ATT_READ_REQ in this case. |
| if (status.error_value().is(att::ErrorCode::kAttributeNotLong) && |
| offset == self->client_->mtu() - sizeof(att::OpCode)) { |
| cb(fit::ok(), |
| buffer->view(0, bytes_read), |
| /*maybe_truncated=*/false); |
| return; |
| } |
| |
| ReportReadValueError(status, std::move(cb)); |
| return; |
| } |
| |
| // Copy the blob into our |buffer|. |blob| may be truncated depending on the |
| // size of |buffer|. |
| BT_ASSERT(bytes_read < buffer->size()); |
| size_t copy_size = std::min(blob.size(), buffer->size() - bytes_read); |
| bool truncated_by_max_bytes = (blob.size() != copy_size); |
| buffer->Write(blob.view(0, copy_size), bytes_read); |
| bytes_read += copy_size; |
| |
| // We are done if the read was not truncated by the MTU or we have read the |
| // maximum number of bytes requested. |
| BT_ASSERT(bytes_read <= buffer->size()); |
| if (!maybe_truncated_by_mtu || bytes_read == buffer->size()) { |
| cb(fit::ok(), |
| buffer->view(0, bytes_read), |
| maybe_truncated_by_mtu || truncated_by_max_bytes); |
| return; |
| } |
| |
| // We have more bytes to read. Read the next blob. |
| self->ReadLongHelper(value_handle, |
| static_cast<uint16_t>(offset + blob.size()), |
| std::move(buffer), |
| bytes_read, |
| std::move(cb)); |
| }; |
| |
| // "To read the complete Characteristic Value an ATT_READ_REQ PDU should be |
| // used for the first part of the value and ATT_READ_BLOB_REQ PDUs shall used |
| // for the rest." (Core Spec v5.2, Vol 3, part G, Sec 4.8.3). |
| if (offset == 0) { |
| client_->ReadRequest(value_handle, std::move(read_cb)); |
| return; |
| } |
| |
| client_->ReadBlobRequest(value_handle, offset, std::move(read_cb)); |
| } |
| |
| void RemoteService::ReadByTypeHelper( |
| const UUID& type, |
| att::Handle start, |
| att::Handle end, |
| std::vector<RemoteService::ReadByTypeResult> values, |
| ReadByTypeCallback callback) { |
| if (start > end) { |
| callback(fit::ok(), std::move(values)); |
| return; |
| } |
| |
| auto read_cb = [self = GetWeakPtr(), |
| type, |
| start, |
| end, |
| values_accum = std::move(values), |
| cb = std::move(callback)]( |
| Client::ReadByTypeResult result) mutable { |
| if (!self.is_alive()) { |
| return; |
| } |
| |
| // Pass results to client when this goes out of scope. |
| auto deferred_cb = |
| fit::defer_callback([&]() { cb(fit::ok(), std::move(values_accum)); }); |
| if (result.is_error()) { |
| const att::Error& error = result.error_value().error; |
| deferred_cb = [&cb, error] { cb(fit::error(error), /*values=*/{}); }; |
| if (error.is(att::ErrorCode::kAttributeNotFound)) { |
| // Treat kAttributeNotFound error as success, since it's used to |
| // indicate when a sequence of reads has successfully read all matching |
| // attributes. |
| deferred_cb = [&cb, &values_accum]() { |
| cb(fit::ok(), std::move(values_accum)); |
| }; |
| return; |
| } |
| if (error.is_any_of(att::ErrorCode::kRequestNotSupported, |
| att::ErrorCode::kInsufficientResources, |
| att::ErrorCode::kInvalidPDU)) { |
| // Pass up these protocol errors as they aren't handle specific or |
| // recoverable. |
| return; |
| } |
| if (error.is_protocol_error()) { |
| // Other errors may correspond to reads of specific handles, so treat |
| // them as a result and continue reading after the error. |
| |
| // A handle must be provided and in the requested read handle range. |
| if (!result.error_value().handle.has_value()) { |
| return; |
| } |
| att::Handle error_handle = result.error_value().handle.value(); |
| if (error_handle < start || error_handle > end) { |
| deferred_cb = [&cb] { |
| cb(fit::error(Error(HostError::kPacketMalformed)), /*values=*/{}); |
| }; |
| return; |
| } |
| |
| values_accum.push_back( |
| RemoteService::ReadByTypeResult{CharacteristicHandle(error_handle), |
| fit::error(error.protocol_error()), |
| /*maybe_truncated=*/false}); |
| |
| // Do not attempt to read from the next handle if the error handle is |
| // the max handle, as this would cause an overflow. |
| if (error_handle == std::numeric_limits<att::Handle>::max()) { |
| deferred_cb = [&cb, &values_accum]() { |
| cb(fit::ok(), std::move(values_accum)); |
| }; |
| return; |
| } |
| |
| // Start next read right after attribute causing error. |
| att::Handle start_next = error_handle + 1; |
| |
| self->ReadByTypeHelper( |
| type, start_next, end, std::move(values_accum), std::move(cb)); |
| deferred_cb.cancel(); |
| return; |
| } |
| return; |
| } |
| |
| const std::vector<Client::ReadByTypeValue>& values = result.value(); |
| // Client already checks for invalid response where status is success but no |
| // values are returned. |
| BT_ASSERT(!values.empty()); |
| |
| // Convert and accumulate values. |
| for (const auto& result : values) { |
| auto buffer = NewBuffer(result.value.size()); |
| result.value.Copy(buffer.get()); |
| values_accum.push_back( |
| ReadByTypeResult{CharacteristicHandle(result.handle), |
| fit::ok(std::move(buffer)), |
| result.maybe_truncated}); |
| } |
| |
| // Do not attempt to read from the next handle if the last value handle is |
| // the max handle, as this would cause an overflow. |
| if (values.back().handle == std::numeric_limits<att::Handle>::max()) { |
| return; |
| } |
| |
| // Start next read right after last returned attribute. Client already |
| // checks that value handles are ascending and in range, so we are |
| // guaranteed to make progress. |
| att::Handle start_next = values.back().handle + 1; |
| |
| self->ReadByTypeHelper( |
| type, start_next, end, std::move(values_accum), std::move(cb)); |
| deferred_cb.cancel(); |
| }; |
| client_->ReadByTypeRequest(type, start, end, std::move(read_cb)); |
| } |
| |
| void RemoteService::HandleNotification(att::Handle value_handle, |
| const ByteBuffer& value, |
| bool maybe_truncated) { |
| auto iter = characteristics_.find(CharacteristicHandle(value_handle)); |
| if (iter != characteristics_.end()) { |
| iter->second.HandleNotification(value, maybe_truncated); |
| } |
| } |
| |
| } // namespace bt::gatt |