| // 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 "client.h" |
| |
| #include <zircon/assert.h> |
| |
| #include "garnet/drivers/bluetooth/lib/common/log.h" |
| #include "garnet/drivers/bluetooth/lib/common/slab_allocator.h" |
| |
| #include "gatt_defs.h" |
| |
| using btlib::common::HostError; |
| |
| namespace btlib { |
| |
| using att::StatusCallback; |
| using common::BufferView; |
| using common::HostError; |
| |
| namespace gatt { |
| namespace { |
| |
| common::MutableByteBufferPtr NewPDU(size_t param_size) { |
| auto pdu = common::NewSlabBuffer(sizeof(att::Header) + param_size); |
| if (!pdu) { |
| bt_log(TRACE, "att", "out of memory"); |
| } |
| return pdu; |
| } |
| |
| template <att::UUIDType Format, |
| typename EntryType = att::InformationData<Format>> |
| bool ProcessDescriptorDiscoveryResponse( |
| att::Handle range_start, |
| att::Handle range_end, |
| BufferView entries, |
| Client::DescriptorCallback desc_callback, |
| att::Handle* out_last_handle) { |
| ZX_DEBUG_ASSERT(out_last_handle); |
| |
| if (entries.size() % sizeof(EntryType)) { |
| bt_log(TRACE, "gatt", "malformed information data list"); |
| return false; |
| } |
| |
| att::Handle last_handle = range_end; |
| while (entries.size()) { |
| const EntryType& entry = entries.As<EntryType>(); |
| |
| DescriptorData desc; |
| desc.handle = le16toh(entry.handle); |
| |
| // Stop and report an error if the server erroneously responds with |
| // an attribute outside the requested range. |
| if (desc.handle > range_end || desc.handle < range_start) { |
| bt_log(TRACE, "gatt", |
| "descriptor handle out of range (handle: %#.4x, " |
| "range: %#.4x - %#.4x)", |
| desc.handle, range_start, range_end); |
| return false; |
| } |
| |
| // The handles must be strictly increasing. |
| if (last_handle != range_end && desc.handle <= last_handle) { |
| bt_log(TRACE, "gatt", "descriptor handles not strictly increasing"); |
| return false; |
| } |
| |
| last_handle = desc.handle; |
| desc.type = common::UUID(entry.uuid); |
| |
| // Notify the handler. |
| desc_callback(desc); |
| |
| entries = entries.view(sizeof(EntryType)); |
| } |
| |
| *out_last_handle = last_handle; |
| return true; |
| } |
| |
| } // namespace |
| |
| class Impl final : public Client { |
| public: |
| explicit Impl(fxl::RefPtr<att::Bearer> bearer) |
| : att_(bearer), weak_ptr_factory_(this) { |
| ZX_DEBUG_ASSERT(att_); |
| |
| auto handler = [this](auto txn_id, const att::PacketReader& pdu) { |
| ZX_DEBUG_ASSERT(pdu.opcode() == att::kNotification || |
| pdu.opcode() == att::kIndication); |
| |
| if (pdu.payload_size() < sizeof(att::NotificationParams)) { |
| // Received a malformed notification. Disconnect the link. |
| bt_log(TRACE, "gatt", "malformed notification/indication PDU"); |
| att_->ShutDown(); |
| return; |
| } |
| |
| bool is_ind = pdu.opcode() == att::kIndication; |
| const auto& params = pdu.payload<att::NotificationParams>(); |
| att::Handle handle = le16toh(params.handle); |
| |
| // Auto-confirm indications. |
| if (is_ind) { |
| auto pdu = NewPDU(0u); |
| if (pdu) { |
| att::PacketWriter(att::kConfirmation, pdu.get()); |
| att_->Reply(txn_id, std::move(pdu)); |
| } else { |
| att_->ReplyWithError(txn_id, handle, |
| att::ErrorCode::kInsufficientResources); |
| } |
| } |
| |
| // Run the handler |
| if (notification_handler_) { |
| notification_handler_( |
| is_ind, handle, |
| BufferView(params.value, pdu.payload_size() - sizeof(att::Handle))); |
| } else { |
| bt_log(SPEW, "gatt", "dropped notification/indication without handler"); |
| } |
| }; |
| |
| not_handler_id_ = att_->RegisterHandler(att::kNotification, handler); |
| ind_handler_id_ = att_->RegisterHandler(att::kIndication, handler); |
| } |
| |
| ~Impl() override { |
| att_->UnregisterHandler(not_handler_id_); |
| att_->UnregisterHandler(ind_handler_id_); |
| } |
| |
| private: |
| fxl::WeakPtr<Client> AsWeakPtr() override { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| uint16_t mtu() const override { return att_->mtu(); } |
| |
| void ExchangeMTU(MTUCallback mtu_cb) override { |
| auto pdu = NewPDU(sizeof(att::ExchangeMTURequestParams)); |
| if (!pdu) { |
| mtu_cb(att::Status(HostError::kOutOfMemory), 0); |
| return; |
| } |
| |
| att::PacketWriter writer(att::kExchangeMTURequest, pdu.get()); |
| auto params = writer.mutable_payload<att::ExchangeMTURequestParams>(); |
| params->client_rx_mtu = htole16(att_->preferred_mtu()); |
| |
| auto rsp_cb = BindCallback([this, mtu_cb = mtu_cb.share()]( |
| const att::PacketReader& rsp) { |
| ZX_DEBUG_ASSERT(rsp.opcode() == att::kExchangeMTUResponse); |
| |
| if (rsp.payload_size() != sizeof(att::ExchangeMTUResponseParams)) { |
| // Received a malformed response. Disconnect the link. |
| att_->ShutDown(); |
| |
| mtu_cb(att::Status(HostError::kPacketMalformed), 0); |
| return; |
| } |
| |
| const auto& rsp_params = rsp.payload<att::ExchangeMTUResponseParams>(); |
| uint16_t server_mtu = le16toh(rsp_params.server_rx_mtu); |
| |
| // If the minimum value is less than the default MTU, then go with the |
| // default MTU (Vol 3, Part F, 3.4.2.2). |
| uint16_t final_mtu = |
| std::max(att::kLEMinMTU, std::min(server_mtu, att_->preferred_mtu())); |
| att_->set_mtu(final_mtu); |
| |
| mtu_cb(att::Status(), final_mtu); |
| }); |
| |
| auto error_cb = BindErrorCallback( |
| [this, mtu_cb = mtu_cb.share()](att::Status status, att::Handle handle) { |
| // "If the Error Response is sent by the server with the Error Code |
| // set to Request Not Supported, [...] the default MTU shall be used |
| // (Vol 3, Part G, 4.3.1)" |
| if (status.is_protocol_error() && |
| status.protocol_error() == att::ErrorCode::kRequestNotSupported) { |
| bt_log(TRACE, "gatt", |
| "peer does not support MTU exchange: using default"); |
| att_->set_mtu(att::kLEMinMTU); |
| mtu_cb(status, att::kLEMinMTU); |
| return; |
| } |
| |
| bt_log(TRACE, "gatt", "MTU exchange failed: %s", |
| status.ToString().c_str()); |
| mtu_cb(status, 0); |
| }); |
| |
| att_->StartTransaction(std::move(pdu), std::move(rsp_cb), std::move(error_cb)); |
| } |
| |
| void DiscoverPrimaryServices(ServiceCallback svc_callback, |
| StatusCallback status_callback) override { |
| DiscoverPrimaryServicesInternal(att::kHandleMin, att::kHandleMax, |
| std::move(svc_callback), |
| std::move(status_callback)); |
| } |
| |
| void DiscoverPrimaryServicesInternal(att::Handle start, |
| att::Handle end, |
| ServiceCallback svc_callback, |
| StatusCallback status_callback) { |
| auto pdu = NewPDU(sizeof(att::ReadByGroupTypeRequestParams16)); |
| if (!pdu) { |
| status_callback(att::Status(HostError::kOutOfMemory)); |
| return; |
| } |
| |
| att::PacketWriter writer(att::kReadByGroupTypeRequest, pdu.get()); |
| auto* params = |
| writer.mutable_payload<att::ReadByGroupTypeRequestParams16>(); |
| params->start_handle = htole16(start); |
| params->end_handle = htole16(end); |
| params->type = htole16(types::kPrimaryService16); |
| |
| auto rsp_cb = BindCallback([this, svc_cb = std::move(svc_callback), |
| res_cb = status_callback.share()]( |
| const att::PacketReader& rsp) mutable { |
| ZX_DEBUG_ASSERT(rsp.opcode() == att::kReadByGroupTypeResponse); |
| |
| if (rsp.payload_size() < sizeof(att::ReadByGroupTypeResponseParams)) { |
| // Received malformed response. Disconnect the link. |
| bt_log(TRACE, "gatt", "received malformed Read By Group Type response"); |
| att_->ShutDown(); |
| res_cb(att::Status(HostError::kPacketMalformed)); |
| return; |
| } |
| |
| const auto& rsp_params = |
| rsp.payload<att::ReadByGroupTypeResponseParams>(); |
| uint8_t entry_length = rsp_params.length; |
| |
| // We expect the returned attribute value to be a 16-bit or 128-bit |
| // service UUID. |
| constexpr size_t kAttrDataSize16 = |
| sizeof(att::AttributeGroupDataEntry) + sizeof(att::AttributeType16); |
| constexpr size_t kAttrDataSize128 = |
| sizeof(att::AttributeGroupDataEntry) + sizeof(att::AttributeType128); |
| |
| if (entry_length != kAttrDataSize16 && entry_length != kAttrDataSize128) { |
| bt_log(TRACE, "gatt", "invalid attribute data length"); |
| att_->ShutDown(); |
| res_cb(att::Status(HostError::kPacketMalformed)); |
| return; |
| } |
| |
| BufferView attr_data_list(rsp_params.attribute_data_list, |
| rsp.payload_size() - 1); |
| if (attr_data_list.size() % entry_length) { |
| bt_log(TRACE, "gatt", "malformed attribute data list"); |
| att_->ShutDown(); |
| res_cb(att::Status(HostError::kPacketMalformed)); |
| return; |
| } |
| |
| att::Handle last_handle = att::kHandleMax; |
| while (attr_data_list.size()) { |
| const auto& entry = attr_data_list.As<att::AttributeGroupDataEntry>(); |
| |
| ServiceData service; |
| service.range_start = le16toh(entry.start_handle); |
| service.range_end = le16toh(entry.group_end_handle); |
| |
| if (service.range_end < service.range_start) { |
| bt_log(TRACE, "gatt", "received malformed service range values"); |
| res_cb(att::Status(HostError::kPacketMalformed)); |
| return; |
| } |
| |
| last_handle = service.range_end; |
| |
| BufferView value(entry.value, entry_length - (2 * sizeof(att::Handle))); |
| |
| // This must succeed as we have performed the appropriate checks above. |
| __UNUSED bool result = common::UUID::FromBytes(value, &service.type); |
| ZX_DEBUG_ASSERT(result); |
| |
| // Notify the handler. |
| svc_cb(service); |
| |
| attr_data_list = attr_data_list.view(entry_length); |
| } |
| |
| // The procedure is over if we have reached the end of the handle range. |
| if (last_handle == att::kHandleMax) { |
| res_cb(att::Status()); |
| return; |
| } |
| |
| // Request the next batch. |
| DiscoverPrimaryServicesInternal(last_handle + 1, att::kHandleMax, |
| std::move(svc_cb), std::move(res_cb)); |
| }); |
| |
| auto error_cb = |
| BindErrorCallback([this, res_cb = status_callback.share()](att::Status status, |
| att::Handle handle) { |
| // An Error Response code of "Attribute Not Found" indicates the end |
| // of the procedure (v5.0, Vol 3, Part G, 4.4.1). |
| if (status.is_protocol_error() && |
| status.protocol_error() == att::ErrorCode::kAttributeNotFound) { |
| res_cb(att::Status()); |
| return; |
| } |
| |
| res_cb(status); |
| }); |
| |
| att_->StartTransaction(std::move(pdu), std::move(rsp_cb), std::move(error_cb)); |
| } |
| |
| void DiscoverCharacteristics(att::Handle range_start, |
| att::Handle range_end, |
| CharacteristicCallback chrc_callback, |
| StatusCallback status_callback) override { |
| ZX_DEBUG_ASSERT(range_start <= range_end); |
| ZX_DEBUG_ASSERT(chrc_callback); |
| ZX_DEBUG_ASSERT(status_callback); |
| |
| if (range_start == range_end) { |
| status_callback(att::Status()); |
| return; |
| } |
| |
| auto pdu = NewPDU(sizeof(att::ReadByTypeRequestParams16)); |
| if (!pdu) { |
| status_callback(att::Status(HostError::kOutOfMemory)); |
| return; |
| } |
| |
| att::PacketWriter writer(att::kReadByTypeRequest, pdu.get()); |
| auto* params = writer.mutable_payload<att::ReadByTypeRequestParams16>(); |
| params->start_handle = htole16(range_start); |
| params->end_handle = htole16(range_end); |
| params->type = htole16(types::kCharacteristicDeclaration16); |
| |
| auto rsp_cb = BindCallback( |
| [this, range_start, range_end, chrc_cb = std::move(chrc_callback), |
| res_cb = |
| status_callback.share()](const att::PacketReader& rsp) mutable { |
| ZX_DEBUG_ASSERT(rsp.opcode() == att::kReadByTypeResponse); |
| |
| if (rsp.payload_size() < sizeof(att::ReadByTypeResponseParams)) { |
| bt_log(TRACE, "gatt", "received malformed Read By Type response"); |
| att_->ShutDown(); |
| res_cb(att::Status(HostError::kPacketMalformed)); |
| return; |
| } |
| |
| const auto& rsp_params = rsp.payload<att::ReadByTypeResponseParams>(); |
| uint8_t entry_length = rsp_params.length; |
| |
| // The characteristic declaration value contains: |
| // 1 octet: properties |
| // 2 octets: value handle |
| // 2 or 16 octets: UUID |
| constexpr size_t kCharacDeclSize16 = sizeof(Properties) + |
| sizeof(att::Handle) + |
| sizeof(att::AttributeType16); |
| constexpr size_t kCharacDeclSize128 = sizeof(Properties) + |
| sizeof(att::Handle) + |
| sizeof(att::AttributeType128); |
| |
| constexpr size_t kAttributeDataSize16 = |
| sizeof(att::AttributeData) + kCharacDeclSize16; |
| constexpr size_t kAttributeDataSize128 = |
| sizeof(att::AttributeData) + kCharacDeclSize128; |
| |
| if (entry_length != kAttributeDataSize16 && |
| entry_length != kAttributeDataSize128) { |
| bt_log(TRACE, "gatt", "invalid attribute data length"); |
| att_->ShutDown(); |
| res_cb(att::Status(HostError::kPacketMalformed)); |
| return; |
| } |
| |
| BufferView attr_data_list(rsp_params.attribute_data_list, |
| rsp.payload_size() - 1); |
| if (attr_data_list.size() % entry_length) { |
| bt_log(TRACE, "gatt", "malformed attribute data list"); |
| att_->ShutDown(); |
| res_cb(att::Status(HostError::kPacketMalformed)); |
| return; |
| } |
| |
| att::Handle last_handle = range_end; |
| while (attr_data_list.size()) { |
| const auto& entry = attr_data_list.As<att::AttributeData>(); |
| BufferView value(entry.value, entry_length - sizeof(att::Handle)); |
| |
| CharacteristicData chrc; |
| chrc.handle = le16toh(entry.handle); |
| chrc.properties = value[0]; |
| chrc.value_handle = le16toh(value.view(1, 2).As<att::Handle>()); |
| |
| // Vol 3, Part G, 3.3: "The Characteristic Value declaration shall |
| // exist immediately following the characteristic declaration." |
| if (chrc.value_handle != chrc.handle + 1) { |
| bt_log(TRACE, "gatt", "characteristic value doesn't follow decl"); |
| res_cb(att::Status(HostError::kPacketMalformed)); |
| return; |
| } |
| |
| // Stop and report an error if the server erroneously responds with |
| // an attribute outside the requested range. |
| if (chrc.handle > range_end || chrc.handle < range_start) { |
| bt_log(TRACE, "gatt", |
| "characteristic handle out of range (handle: %#.4x, " |
| "range: %#.4x - %#.4x)", |
| chrc.handle, range_start, range_end); |
| res_cb(att::Status(HostError::kPacketMalformed)); |
| return; |
| } |
| |
| // The handles must be strictly increasing. Check this so that a |
| // server cannot fool us into sending requests forever. |
| if (last_handle != range_end && chrc.handle <= last_handle) { |
| bt_log(TRACE, "gatt", "handles are not strictly increasing"); |
| res_cb(att::Status(HostError::kPacketMalformed)); |
| return; |
| } |
| |
| last_handle = chrc.handle; |
| |
| // This must succeed as we have performed the necessary checks |
| // above. |
| __UNUSED bool result = |
| common::UUID::FromBytes(value.view(3), &chrc.type); |
| ZX_DEBUG_ASSERT(result); |
| |
| // Notify the handler. |
| chrc_cb(chrc); |
| |
| attr_data_list = attr_data_list.view(entry_length); |
| } |
| |
| // The procedure is over if we have reached the end of the handle |
| // range. |
| if (last_handle == range_end) { |
| res_cb(att::Status()); |
| return; |
| } |
| |
| // Request the next batch. |
| DiscoverCharacteristics(last_handle + 1, range_end, |
| std::move(chrc_cb), std::move(res_cb)); |
| }); |
| |
| auto error_cb = |
| BindErrorCallback([this, res_cb = status_callback.share()](att::Status status, |
| att::Handle handle) { |
| // An Error Response code of "Attribute Not Found" indicates the end |
| // of the procedure (v5.0, Vol 3, Part G, 4.6.1). |
| if (status.is_protocol_error() && |
| status.protocol_error() == att::ErrorCode::kAttributeNotFound) { |
| res_cb(att::Status()); |
| return; |
| } |
| |
| res_cb(status); |
| }); |
| |
| att_->StartTransaction(std::move(pdu), std::move(rsp_cb), std::move(error_cb)); |
| } |
| |
| void DiscoverDescriptors(att::Handle range_start, |
| att::Handle range_end, |
| DescriptorCallback desc_callback, |
| StatusCallback status_callback) override { |
| ZX_DEBUG_ASSERT(range_start <= range_end); |
| ZX_DEBUG_ASSERT(desc_callback); |
| ZX_DEBUG_ASSERT(status_callback); |
| |
| auto pdu = NewPDU(sizeof(att::FindInformationRequestParams)); |
| if (!pdu) { |
| status_callback(att::Status(HostError::kOutOfMemory)); |
| return; |
| } |
| |
| att::PacketWriter writer(att::kFindInformationRequest, pdu.get()); |
| auto* params = writer.mutable_payload<att::FindInformationRequestParams>(); |
| params->start_handle = htole16(range_start); |
| params->end_handle = htole16(range_end); |
| |
| auto rsp_cb = BindCallback([this, range_start, range_end, |
| desc_cb = std::move(desc_callback), |
| res_cb = status_callback.share()]( |
| const att::PacketReader& rsp) mutable { |
| ZX_DEBUG_ASSERT(rsp.opcode() == att::kFindInformationResponse); |
| |
| if (rsp.payload_size() < sizeof(att::FindInformationResponseParams)) { |
| bt_log(TRACE, "gatt", "received malformed Find Information response"); |
| att_->ShutDown(); |
| res_cb(att::Status(HostError::kPacketMalformed)); |
| return; |
| } |
| |
| const auto& rsp_params = |
| rsp.payload<att::FindInformationResponseParams>(); |
| BufferView entries = rsp.payload_data().view(sizeof(rsp_params.format)); |
| |
| att::Handle last_handle; |
| bool result; |
| switch (rsp_params.format) { |
| case att::UUIDType::k16Bit: |
| result = ProcessDescriptorDiscoveryResponse<att::UUIDType::k16Bit>( |
| range_start, range_end, entries, desc_cb.share(), &last_handle); |
| break; |
| case att::UUIDType::k128Bit: |
| result = ProcessDescriptorDiscoveryResponse<att::UUIDType::k128Bit>( |
| range_start, range_end, entries, desc_cb.share(), &last_handle); |
| break; |
| default: |
| bt_log(TRACE, "gatt", "invalid information data format"); |
| result = false; |
| break; |
| } |
| |
| if (!result) { |
| att_->ShutDown(); |
| res_cb(att::Status(HostError::kPacketMalformed)); |
| return; |
| } |
| |
| // The procedure is over if we have reached the end of the handle range. |
| if (last_handle == range_end) { |
| res_cb(att::Status()); |
| return; |
| } |
| |
| // Request the next batch. |
| DiscoverDescriptors(last_handle + 1, range_end, std::move(desc_cb), |
| std::move(res_cb)); |
| }); |
| |
| auto error_cb = |
| BindErrorCallback([this, res_cb = status_callback.share()](att::Status status, |
| att::Handle handle) { |
| // An Error Response code of "Attribute Not Found" indicates the end |
| // of the procedure (v5.0, Vol 3, Part G, 4.7.1). |
| if (status.is_protocol_error() && |
| status.protocol_error() == att::ErrorCode::kAttributeNotFound) { |
| res_cb(att::Status()); |
| return; |
| } |
| |
| res_cb(status); |
| }); |
| |
| att_->StartTransaction(std::move(pdu), std::move(rsp_cb), std::move(error_cb)); |
| } |
| |
| void ReadRequest(att::Handle handle, ReadCallback callback) override { |
| auto pdu = NewPDU(sizeof(att::ReadRequestParams)); |
| if (!pdu) { |
| callback(att::Status(HostError::kOutOfMemory), BufferView()); |
| return; |
| } |
| |
| att::PacketWriter writer(att::kReadRequest, pdu.get()); |
| auto params = writer.mutable_payload<att::ReadRequestParams>(); |
| params->handle = htole16(handle); |
| |
| auto rsp_cb = BindCallback( |
| [this, callback = callback.share()](const att::PacketReader& rsp) { |
| ZX_DEBUG_ASSERT(rsp.opcode() == att::kReadResponse); |
| callback(att::Status(), rsp.payload_data()); |
| }); |
| |
| auto error_cb = |
| BindErrorCallback([this, callback = callback.share()]( |
| att::Status status, att::Handle handle) { |
| bt_log(TRACE, "gatt", "read request failed: %s, handle %#.4x", |
| status.ToString().c_str(), handle); |
| callback(status, BufferView()); |
| }); |
| |
| if (!att_->StartTransaction(std::move(pdu), std::move(rsp_cb), |
| std::move(error_cb))) { |
| callback(att::Status(HostError::kPacketMalformed), BufferView()); |
| } |
| } |
| |
| void ReadBlobRequest(att::Handle handle, uint16_t offset, |
| ReadCallback callback) override { |
| auto pdu = NewPDU(sizeof(att::ReadBlobRequestParams)); |
| if (!pdu) { |
| callback(att::Status(HostError::kOutOfMemory), BufferView()); |
| return; |
| } |
| |
| att::PacketWriter writer(att::kReadBlobRequest, pdu.get()); |
| auto params = writer.mutable_payload<att::ReadBlobRequestParams>(); |
| params->handle = htole16(handle); |
| params->offset = htole16(offset); |
| |
| auto rsp_cb = BindCallback( |
| [this, callback = callback.share()](const att::PacketReader& rsp) { |
| ZX_DEBUG_ASSERT(rsp.opcode() == att::kReadBlobResponse); |
| callback(att::Status(), rsp.payload_data()); |
| }); |
| |
| auto error_cb = |
| BindErrorCallback([this, callback = callback.share()]( |
| att::Status status, att::Handle handle) { |
| bt_log(TRACE, "gatt", "read blob request failed: %s, handle: %#.4x", |
| status.ToString().c_str(), handle); |
| callback(status, BufferView()); |
| }); |
| |
| if (!att_->StartTransaction(std::move(pdu), std::move(rsp_cb), |
| std::move(error_cb))) { |
| callback(att::Status(HostError::kPacketMalformed), BufferView()); |
| } |
| } |
| |
| void WriteRequest(att::Handle handle, |
| const common::ByteBuffer& value, |
| StatusCallback callback) override { |
| const size_t payload_size = sizeof(att::WriteRequestParams) + value.size(); |
| if (sizeof(att::OpCode) + payload_size > att_->mtu()) { |
| bt_log(SPEW, "gatt", "write request payload exceeds MTU"); |
| callback(att::Status(HostError::kPacketMalformed)); |
| return; |
| } |
| |
| auto pdu = NewPDU(payload_size); |
| if (!pdu) { |
| callback(att::Status(HostError::kOutOfMemory)); |
| return; |
| } |
| |
| att::PacketWriter writer(att::kWriteRequest, pdu.get()); |
| auto params = writer.mutable_payload<att::WriteRequestParams>(); |
| params->handle = htole16(handle); |
| |
| auto value_view = |
| writer.mutable_payload_data().mutable_view(sizeof(att::Handle)); |
| value.Copy(&value_view); |
| |
| auto rsp_cb = BindCallback( |
| [this, callback = callback.share()](const att::PacketReader& rsp) { |
| ZX_DEBUG_ASSERT(rsp.opcode() == att::kWriteResponse); |
| |
| if (rsp.payload_size()) { |
| att_->ShutDown(); |
| callback(att::Status(HostError::kPacketMalformed)); |
| return; |
| } |
| |
| callback(att::Status()); |
| }); |
| |
| auto error_cb = |
| BindErrorCallback([this, callback = callback.share()]( |
| att::Status status, att::Handle handle) { |
| bt_log(TRACE, "gatt", "write request failed: %s, handle: %#.2x", |
| status.ToString().c_str(), handle); |
| callback(status); |
| }); |
| |
| if (!att_->StartTransaction(std::move(pdu), std::move(rsp_cb), std::move(error_cb))) { |
| callback(att::Status(HostError::kPacketMalformed)); |
| } |
| } |
| |
| void WriteWithoutResponse(att::Handle handle, |
| const common::ByteBuffer& value) override { |
| const size_t payload_size = sizeof(att::WriteRequestParams) + value.size(); |
| if (sizeof(att::OpCode) + payload_size > att_->mtu()) { |
| bt_log(SPEW, "gatt", "write request payload exceeds MTU"); |
| return; |
| } |
| |
| auto pdu = NewPDU(payload_size); |
| if (!pdu) { |
| return; |
| } |
| |
| att::PacketWriter writer(att::kWriteCommand, pdu.get()); |
| auto params = writer.mutable_payload<att::WriteRequestParams>(); |
| params->handle = htole16(handle); |
| |
| auto value_view = |
| writer.mutable_payload_data().mutable_view(sizeof(att::Handle)); |
| value.Copy(&value_view); |
| |
| att_->SendWithoutResponse(std::move(pdu)); |
| } |
| |
| void SetNotificationHandler(NotificationCallback handler) override { |
| notification_handler_ = std::move(handler); |
| } |
| |
| // Wraps |callback| in a TransactionCallback that only runs if this Client is |
| // still alive. |
| att::Bearer::TransactionCallback BindCallback( |
| att::Bearer::TransactionCallback callback) { |
| return [self = weak_ptr_factory_.GetWeakPtr(), callback = std::move(callback)](const auto& rsp) { |
| if (self) { |
| callback(rsp); |
| } |
| }; |
| } |
| |
| // Wraps |callback| in a ErrorCallback that only runs if this Client is still |
| // alive. |
| att::Bearer::ErrorCallback BindErrorCallback( |
| att::Bearer::ErrorCallback callback) { |
| return [self = weak_ptr_factory_.GetWeakPtr(), callback = std::move(callback)]( |
| att::Status status, att::Handle handle) { |
| if (self) { |
| callback(status, handle); |
| } |
| }; |
| } |
| |
| fxl::RefPtr<att::Bearer> att_; |
| att::Bearer::HandlerId not_handler_id_; |
| att::Bearer::HandlerId ind_handler_id_; |
| NotificationCallback notification_handler_; |
| fxl::WeakPtrFactory<Client> weak_ptr_factory_; |
| |
| FXL_DISALLOW_COPY_AND_ASSIGN(Impl); |
| }; |
| |
| // static |
| std::unique_ptr<Client> Client::Create(fxl::RefPtr<att::Bearer> bearer) { |
| return std::make_unique<Impl>(std::move(bearer)); |
| } |
| |
| } // namespace gatt |
| } // namespace btlib |