| // Copyright 2017 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 "server.h" |
| |
| #include <lib/fit/function.h> |
| #include <zircon/assert.h> |
| |
| #include "gatt_defs.h" |
| #include "src/connectivity/bluetooth/core/bt-host/att/database.h" |
| #include "src/connectivity/bluetooth/core/bt-host/att/permissions.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/slab_allocator.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/uuid.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| namespace bt { |
| namespace gatt { |
| |
| Server::Server(PeerId peer_id, fxl::RefPtr<att::Database> database, |
| fxl::RefPtr<att::Bearer> bearer) |
| : peer_id_(peer_id), db_(database), att_(bearer), weak_ptr_factory_(this) { |
| ZX_DEBUG_ASSERT(db_); |
| ZX_DEBUG_ASSERT(att_); |
| |
| exchange_mtu_id_ = att_->RegisterHandler( |
| att::kExchangeMTURequest, fit::bind_member(this, &Server::OnExchangeMTU)); |
| find_information_id_ = |
| att_->RegisterHandler(att::kFindInformationRequest, |
| fit::bind_member(this, &Server::OnFindInformation)); |
| read_by_group_type_id_ = |
| att_->RegisterHandler(att::kReadByGroupTypeRequest, |
| fit::bind_member(this, &Server::OnReadByGroupType)); |
| read_by_type_id_ = att_->RegisterHandler( |
| att::kReadByTypeRequest, fit::bind_member(this, &Server::OnReadByType)); |
| read_req_id_ = att_->RegisterHandler( |
| att::kReadRequest, fit::bind_member(this, &Server::OnReadRequest)); |
| write_req_id_ = att_->RegisterHandler( |
| att::kWriteRequest, fit::bind_member(this, &Server::OnWriteRequest)); |
| write_cmd_id_ = att_->RegisterHandler( |
| att::kWriteCommand, fit::bind_member(this, &Server::OnWriteCommand)); |
| read_blob_req_id_ = |
| att_->RegisterHandler(att::kReadBlobRequest, |
| fit::bind_member(this, &Server::OnReadBlobRequest)); |
| find_by_type_value_id_ = att_->RegisterHandler( |
| att::kFindByTypeValueRequest, |
| fit::bind_member(this, &Server::OnFindByTypeValueRequest)); |
| prepare_write_id_ = att_->RegisterHandler( |
| att::kPrepareWriteRequest, |
| fit::bind_member(this, &Server::OnPrepareWriteRequest)); |
| exec_write_id_ = att_->RegisterHandler( |
| att::kExecuteWriteRequest, |
| fit::bind_member(this, &Server::OnExecuteWriteRequest)); |
| } |
| |
| Server::~Server() { |
| att_->UnregisterHandler(exec_write_id_); |
| att_->UnregisterHandler(prepare_write_id_); |
| att_->UnregisterHandler(find_by_type_value_id_); |
| att_->UnregisterHandler(read_blob_req_id_); |
| att_->UnregisterHandler(write_cmd_id_); |
| att_->UnregisterHandler(write_req_id_); |
| att_->UnregisterHandler(read_req_id_); |
| att_->UnregisterHandler(read_by_type_id_); |
| att_->UnregisterHandler(read_by_group_type_id_); |
| att_->UnregisterHandler(find_information_id_); |
| att_->UnregisterHandler(exchange_mtu_id_); |
| } |
| |
| void Server::SendNotification(att::Handle handle, const ByteBuffer& value, |
| bool indicate) { |
| auto buffer = |
| NewSlabBuffer(sizeof(att::Header) + sizeof(handle) + value.size()); |
| ZX_ASSERT(buffer); |
| |
| att::PacketWriter writer(indicate ? att::kIndication : att::kNotification, |
| buffer.get()); |
| auto rsp_params = writer.mutable_payload<att::AttributeData>(); |
| rsp_params->handle = htole16(handle); |
| writer.mutable_payload_data().Write(value, sizeof(att::AttributeData)); |
| |
| if (!indicate) { |
| att_->SendWithoutResponse(std::move(buffer)); |
| return; |
| } |
| |
| att_->StartTransaction( |
| std::move(buffer), |
| [](const auto&) { bt_log(SPEW, "gatt", "got confirmation!"); }, |
| [](att::Status status, att::Handle handle) { |
| bt_log(TRACE, "gatt", "indication failed (result %s, handle: %#.4x)", |
| status.ToString().c_str(), handle); |
| }); |
| } |
| |
| void Server::OnExchangeMTU(att::Bearer::TransactionId tid, |
| const att::PacketReader& packet) { |
| ZX_DEBUG_ASSERT(packet.opcode() == att::kExchangeMTURequest); |
| |
| if (packet.payload_size() != sizeof(att::ExchangeMTURequestParams)) { |
| att_->ReplyWithError(tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU); |
| return; |
| } |
| |
| const auto& params = packet.payload<att::ExchangeMTURequestParams>(); |
| uint16_t client_mtu = le16toh(params.client_rx_mtu); |
| uint16_t server_mtu = att_->preferred_mtu(); |
| |
| auto buffer = NewSlabBuffer(sizeof(att::Header) + |
| sizeof(att::ExchangeMTUResponseParams)); |
| ZX_ASSERT(buffer); |
| |
| att::PacketWriter writer(att::kExchangeMTUResponse, buffer.get()); |
| auto rsp_params = writer.mutable_payload<att::ExchangeMTUResponseParams>(); |
| rsp_params->server_rx_mtu = htole16(server_mtu); |
| |
| att_->Reply(tid, std::move(buffer)); |
| |
| // If the minimum value is less than the default MTU, then go with the default |
| // MTU (Vol 3, Part F, 3.4.2.2). |
| // TODO(armansito): This needs to use on kBREDRMinATTMTU for BR/EDR. Make the |
| // default MTU configurable. |
| att_->set_mtu(std::max(att::kLEMinMTU, std::min(client_mtu, server_mtu))); |
| } |
| |
| void Server::OnFindInformation(att::Bearer::TransactionId tid, |
| const att::PacketReader& packet) { |
| ZX_DEBUG_ASSERT(packet.opcode() == att::kFindInformationRequest); |
| |
| if (packet.payload_size() != sizeof(att::FindInformationRequestParams)) { |
| att_->ReplyWithError(tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU); |
| return; |
| } |
| |
| const auto& params = packet.payload<att::FindInformationRequestParams>(); |
| att::Handle start = le16toh(params.start_handle); |
| att::Handle end = le16toh(params.end_handle); |
| |
| constexpr size_t kRspStructSize = sizeof(att::FindInformationResponseParams); |
| constexpr size_t kHeaderSize = sizeof(att::Header) + kRspStructSize; |
| ZX_DEBUG_ASSERT(kHeaderSize <= att_->mtu()); |
| |
| if (start == att::kInvalidHandle || start > end) { |
| att_->ReplyWithError(tid, start, att::ErrorCode::kInvalidHandle); |
| return; |
| } |
| |
| // Find all attributes within range with the same compact UUID size that can |
| // fit within the current MTU. |
| size_t max_payload_size = att_->mtu() - kHeaderSize; |
| size_t uuid_size; |
| size_t entry_size; |
| std::list<const att::Attribute*> results; |
| for (auto it = db_->GetIterator(start, end); !it.AtEnd(); it.Advance()) { |
| const auto* attr = it.get(); |
| ZX_DEBUG_ASSERT(attr); |
| |
| // GATT does not allow 32-bit UUIDs |
| size_t compact_size = attr->type().CompactSize(false /* allow_32bit */); |
| if (results.empty()) { |
| // |uuid_size| is determined by the first attribute. |
| uuid_size = compact_size; |
| entry_size = std::min(uuid_size + sizeof(att::Handle), max_payload_size); |
| } else if (compact_size != uuid_size || entry_size > max_payload_size) { |
| break; |
| } |
| |
| results.push_back(attr); |
| max_payload_size -= entry_size; |
| } |
| |
| if (results.empty()) { |
| att_->ReplyWithError(tid, start, att::ErrorCode::kAttributeNotFound); |
| return; |
| } |
| |
| ZX_DEBUG_ASSERT(!results.empty()); |
| |
| size_t pdu_size = kHeaderSize + entry_size * results.size(); |
| |
| auto buffer = NewSlabBuffer(pdu_size); |
| ZX_ASSERT(buffer); |
| |
| att::PacketWriter writer(att::kFindInformationResponse, buffer.get()); |
| auto rsp_params = |
| writer.mutable_payload<att::FindInformationResponseParams>(); |
| rsp_params->format = |
| (entry_size == 4) ? att::UUIDType::k16Bit : att::UUIDType::k128Bit; |
| |
| // |out_entries| initially references |params->information_data|. The loop |
| // below modifies it as entries are written into the list. |
| auto out_entries = writer.mutable_payload_data().mutable_view(kRspStructSize); |
| for (const auto& attr : results) { |
| *reinterpret_cast<att::Handle*>(out_entries.mutable_data()) = |
| htole16(attr->handle()); |
| auto uuid_view = out_entries.mutable_view(sizeof(att::Handle)); |
| attr->type().ToBytes(&uuid_view, false /* allow32_bit */); |
| |
| // advance |
| out_entries = out_entries.mutable_view(entry_size); |
| } |
| |
| att_->Reply(tid, std::move(buffer)); |
| } |
| |
| void Server::OnFindByTypeValueRequest(att::Bearer::TransactionId tid, |
| const att::PacketReader& packet) { |
| ZX_DEBUG_ASSERT(packet.opcode() == att::kFindByTypeValueRequest); |
| |
| if (packet.payload_size() < sizeof(att::FindByTypeValueRequestParams)) { |
| att_->ReplyWithError(tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU); |
| return; |
| } |
| |
| const auto& params = packet.payload<att::FindByTypeValueRequestParams>(); |
| att::Handle start = le16toh(params.start_handle); |
| att::Handle end = le16toh(params.end_handle); |
| UUID type(params.type); |
| constexpr size_t kParamsSize = sizeof(att::FindByTypeValueRequestParams); |
| |
| BufferView value = packet.payload_data().view( |
| kParamsSize, packet.payload_size() - kParamsSize); |
| |
| if (start == att::kInvalidHandle || start > end) { |
| att_->ReplyWithError(tid, att::kInvalidHandle, |
| att::ErrorCode::kInvalidHandle); |
| return; |
| } |
| |
| auto iter = db_->GetIterator(start, end, &type, false); |
| if (iter.AtEnd()) { |
| att_->ReplyWithError(tid, att::kInvalidHandle, |
| att::ErrorCode::kAttributeNotFound); |
| return; |
| } |
| |
| std::list<const att::Attribute*> results; |
| |
| // Filter for identical values |
| for (; !iter.AtEnd(); iter.Advance()) { |
| const auto* attr = iter.get(); |
| ZX_DEBUG_ASSERT(attr); |
| |
| // Only support static values for this Request type |
| if (attr->value()) { |
| if (*attr->value() == value) { |
| results.push_back(attr); |
| } |
| } |
| } |
| |
| // No attributes match the value |
| if (results.size() == 0) { |
| att_->ReplyWithError(tid, att::kInvalidHandle, |
| att::ErrorCode::kAttributeNotFound); |
| return; |
| } |
| |
| constexpr size_t kRspStructSize = sizeof(att::HandlesInformationList); |
| size_t pdu_size = sizeof(att::Header) + kRspStructSize * results.size(); |
| auto buffer = NewSlabBuffer(pdu_size); |
| ZX_ASSERT(buffer); |
| |
| att::PacketWriter writer(att::kFindByTypeValueResponse, buffer.get()); |
| |
| // Points to the next entry in the target PDU. |
| auto next_entry = writer.mutable_payload_data(); |
| for (const auto& attr : results) { |
| auto* entry = reinterpret_cast<att::HandlesInformationList*>( |
| next_entry.mutable_data()); |
| entry->handle = htole16(attr->handle()); |
| if (attr->group().active()) { |
| entry->group_end_handle = htole16(attr->group().end_handle()); |
| } else { |
| entry->group_end_handle = htole16(attr->handle()); |
| } |
| next_entry = next_entry.mutable_view(kRspStructSize); |
| } |
| |
| att_->Reply(tid, std::move(buffer)); |
| } |
| |
| void Server::OnReadByGroupType(att::Bearer::TransactionId tid, |
| const att::PacketReader& packet) { |
| ZX_DEBUG_ASSERT(packet.opcode() == att::kReadByGroupTypeRequest); |
| |
| att::Handle start, end; |
| UUID group_type; |
| |
| // The group type is represented as either a 16-bit or 128-bit UUID. |
| if (packet.payload_size() == sizeof(att::ReadByTypeRequestParams16)) { |
| const auto& params = packet.payload<att::ReadByTypeRequestParams16>(); |
| start = le16toh(params.start_handle); |
| end = le16toh(params.end_handle); |
| group_type = UUID(le16toh(params.type)); |
| } else if (packet.payload_size() == sizeof(att::ReadByTypeRequestParams128)) { |
| const auto& params = packet.payload<att::ReadByTypeRequestParams128>(); |
| start = le16toh(params.start_handle); |
| end = le16toh(params.end_handle); |
| group_type = UUID(params.type); |
| } else { |
| att_->ReplyWithError(tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU); |
| return; |
| } |
| |
| if (group_type != types::kPrimaryService && |
| group_type != types::kSecondaryService) { |
| att_->ReplyWithError(tid, start, att::ErrorCode::kUnsupportedGroupType); |
| return; |
| } |
| |
| constexpr size_t kRspStructSize = sizeof(att::ReadByGroupTypeResponseParams); |
| constexpr size_t kHeaderSize = sizeof(att::Header) + kRspStructSize; |
| ZX_DEBUG_ASSERT(kHeaderSize <= att_->mtu()); |
| |
| size_t value_size; |
| std::list<const att::Attribute*> results; |
| auto error_code = ReadByTypeHelper( |
| start, end, group_type, true /* group_type */, att_->mtu() - kHeaderSize, |
| att::kMaxReadByGroupTypeValueLength, sizeof(att::AttributeGroupDataEntry), |
| &value_size, &results); |
| if (error_code != att::ErrorCode::kNoError) { |
| att_->ReplyWithError(tid, start, error_code); |
| return; |
| } |
| |
| ZX_DEBUG_ASSERT(!results.empty()); |
| |
| size_t entry_size = value_size + sizeof(att::AttributeGroupDataEntry); |
| size_t pdu_size = kHeaderSize + entry_size * results.size(); |
| ZX_DEBUG_ASSERT(pdu_size <= att_->mtu()); |
| |
| auto buffer = NewSlabBuffer(pdu_size); |
| ZX_ASSERT(buffer); |
| |
| att::PacketWriter writer(att::kReadByGroupTypeResponse, buffer.get()); |
| auto params = writer.mutable_payload<att::ReadByGroupTypeResponseParams>(); |
| |
| ZX_DEBUG_ASSERT(entry_size <= std::numeric_limits<uint8_t>::max()); |
| params->length = static_cast<uint8_t>(entry_size); |
| |
| // Points to the next entry in the target PDU. |
| auto next_entry = writer.mutable_payload_data().mutable_view(kRspStructSize); |
| for (const auto& attr : results) { |
| auto* entry = reinterpret_cast<att::AttributeGroupDataEntry*>( |
| next_entry.mutable_data()); |
| entry->start_handle = htole16(attr->group().start_handle()); |
| entry->group_end_handle = htole16(attr->group().end_handle()); |
| next_entry.Write(attr->group().decl_value().view(0, value_size), |
| sizeof(att::AttributeGroupDataEntry)); |
| |
| next_entry = next_entry.mutable_view(entry_size); |
| } |
| |
| att_->Reply(tid, std::move(buffer)); |
| } |
| |
| void Server::OnReadByType(att::Bearer::TransactionId tid, |
| const att::PacketReader& packet) { |
| ZX_DEBUG_ASSERT(packet.opcode() == att::kReadByTypeRequest); |
| |
| att::Handle start, end; |
| UUID type; |
| |
| // The attribute type is represented as either a 16-bit or 128-bit UUID. |
| if (packet.payload_size() == sizeof(att::ReadByTypeRequestParams16)) { |
| const auto& params = packet.payload<att::ReadByTypeRequestParams16>(); |
| start = le16toh(params.start_handle); |
| end = le16toh(params.end_handle); |
| type = UUID(le16toh(params.type)); |
| } else if (packet.payload_size() == sizeof(att::ReadByTypeRequestParams128)) { |
| const auto& params = packet.payload<att::ReadByTypeRequestParams128>(); |
| start = le16toh(params.start_handle); |
| end = le16toh(params.end_handle); |
| type = UUID(params.type); |
| } else { |
| att_->ReplyWithError(tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU); |
| return; |
| } |
| |
| constexpr size_t kRspStructSize = sizeof(att::ReadByTypeResponseParams); |
| constexpr size_t kHeaderSize = sizeof(att::Header) + kRspStructSize; |
| ZX_DEBUG_ASSERT(kHeaderSize <= att_->mtu()); |
| |
| size_t value_size; |
| std::list<const att::Attribute*> results; |
| auto error_code = ReadByTypeHelper( |
| start, end, type, false /* group_type */, att_->mtu() - kHeaderSize, |
| att::kMaxReadByTypeValueLength, sizeof(att::AttributeData), &value_size, |
| &results); |
| if (error_code != att::ErrorCode::kNoError) { |
| att_->ReplyWithError(tid, start, error_code); |
| return; |
| } |
| |
| ZX_DEBUG_ASSERT(!results.empty()); |
| |
| // If the value is dynamic, then delegate the read to any registered handler. |
| if (!results.front()->value()) { |
| ZX_DEBUG_ASSERT(results.size() == 1u); |
| |
| const size_t kMaxValueSize = |
| std::min(att_->mtu() - kHeaderSize - sizeof(att::AttributeData), |
| static_cast<size_t>(att::kMaxReadByTypeValueLength)); |
| |
| att::Handle handle = results.front()->handle(); |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| auto result_cb = [self, tid, handle, kMaxValueSize](att::ErrorCode ecode, |
| const auto& value) { |
| if (!self) |
| return; |
| |
| if (ecode != att::ErrorCode::kNoError) { |
| self->att_->ReplyWithError(tid, handle, ecode); |
| return; |
| } |
| |
| // Respond with just a single entry. |
| size_t value_size = std::min(value.size(), kMaxValueSize); |
| size_t entry_size = value_size + sizeof(att::AttributeData); |
| auto buffer = NewSlabBuffer(entry_size + kHeaderSize); |
| att::PacketWriter writer(att::kReadByTypeResponse, buffer.get()); |
| |
| auto params = writer.mutable_payload<att::ReadByTypeResponseParams>(); |
| params->length = static_cast<uint8_t>(entry_size); |
| params->attribute_data_list->handle = htole16(handle); |
| writer.mutable_payload_data().Write( |
| value.data(), value_size, sizeof(params->length) + sizeof(handle)); |
| |
| self->att_->Reply(tid, std::move(buffer)); |
| }; |
| |
| // Respond with an error if no read handler was registered. |
| if (!results.front()->ReadAsync(peer_id_, 0, result_cb)) { |
| att_->ReplyWithError(tid, handle, att::ErrorCode::kReadNotPermitted); |
| } |
| return; |
| } |
| |
| size_t entry_size = sizeof(att::AttributeData) + value_size; |
| ZX_DEBUG_ASSERT(entry_size <= std::numeric_limits<uint8_t>::max()); |
| |
| size_t pdu_size = kHeaderSize + entry_size * results.size(); |
| ZX_DEBUG_ASSERT(pdu_size <= att_->mtu()); |
| |
| auto buffer = NewSlabBuffer(pdu_size); |
| ZX_ASSERT(buffer); |
| |
| att::PacketWriter writer(att::kReadByTypeResponse, buffer.get()); |
| auto params = writer.mutable_payload<att::ReadByTypeResponseParams>(); |
| params->length = static_cast<uint8_t>(entry_size); |
| |
| // Points to the next entry in the target PDU. |
| auto next_entry = writer.mutable_payload_data().mutable_view(kRspStructSize); |
| for (const auto& attr : results) { |
| auto* entry = |
| reinterpret_cast<att::AttributeData*>(next_entry.mutable_data()); |
| entry->handle = htole16(attr->handle()); |
| next_entry.Write(attr->value()->view(0, value_size), sizeof(entry->handle)); |
| |
| next_entry = next_entry.mutable_view(entry_size); |
| } |
| |
| att_->Reply(tid, std::move(buffer)); |
| } |
| |
| void Server::OnReadBlobRequest(att::Bearer::TransactionId tid, |
| const att::PacketReader& packet) { |
| ZX_DEBUG_ASSERT(packet.opcode() == att::kReadBlobRequest); |
| |
| if (packet.payload_size() != sizeof(att::ReadBlobRequestParams)) { |
| att_->ReplyWithError(tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU); |
| return; |
| } |
| |
| const auto& params = packet.payload<att::ReadBlobRequestParams>(); |
| att::Handle handle = le16toh(params.handle); |
| uint16_t offset = le16toh(params.offset); |
| |
| const auto* attr = db_->FindAttribute(handle); |
| if (!attr) { |
| att_->ReplyWithError(tid, handle, att::ErrorCode::kInvalidHandle); |
| return; |
| } |
| |
| att::ErrorCode ecode = |
| att::CheckReadPermissions(attr->read_reqs(), att_->security()); |
| if (ecode != att::ErrorCode::kNoError) { |
| att_->ReplyWithError(tid, handle, ecode); |
| return; |
| } |
| |
| constexpr size_t kHeaderSize = sizeof(att::Header); |
| |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| auto callback = [self, tid, handle](att::ErrorCode ecode, const auto& value) { |
| if (!self) |
| return; |
| |
| if (ecode != att::ErrorCode::kNoError) { |
| self->att_->ReplyWithError(tid, handle, ecode); |
| return; |
| } |
| |
| size_t value_size = std::min(value.size(), self->att_->mtu() - kHeaderSize); |
| auto buffer = NewSlabBuffer(value_size + kHeaderSize); |
| ZX_ASSERT(buffer); |
| |
| att::PacketWriter writer(att::kReadBlobResponse, buffer.get()); |
| writer.mutable_payload_data().Write(value.view(0, value_size)); |
| |
| self->att_->Reply(tid, std::move(buffer)); |
| }; |
| |
| // Use the cached value if there is one. |
| if (attr->value()) { |
| if (offset >= attr->value()->size()) { |
| att_->ReplyWithError(tid, handle, att::ErrorCode::kInvalidOffset); |
| return; |
| } |
| size_t value_size = |
| std::min(attr->value()->size(), self->att_->mtu() - kHeaderSize); |
| callback(att::ErrorCode::kNoError, attr->value()->view(offset, value_size)); |
| return; |
| } |
| |
| // TODO(bwb): Add a timeout to this as per NET-434 |
| if (!attr->ReadAsync(peer_id_, offset, callback)) { |
| att_->ReplyWithError(tid, handle, att::ErrorCode::kReadNotPermitted); |
| } |
| } |
| |
| void Server::OnReadRequest(att::Bearer::TransactionId tid, |
| const att::PacketReader& packet) { |
| ZX_DEBUG_ASSERT(packet.opcode() == att::kReadRequest); |
| |
| if (packet.payload_size() != sizeof(att::ReadRequestParams)) { |
| att_->ReplyWithError(tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU); |
| return; |
| } |
| |
| const auto& params = packet.payload<att::WriteRequestParams>(); |
| att::Handle handle = le16toh(params.handle); |
| |
| const auto* attr = db_->FindAttribute(handle); |
| if (!attr) { |
| att_->ReplyWithError(tid, handle, att::ErrorCode::kInvalidHandle); |
| return; |
| } |
| |
| att::ErrorCode ecode = |
| att::CheckReadPermissions(attr->read_reqs(), att_->security()); |
| if (ecode != att::ErrorCode::kNoError) { |
| att_->ReplyWithError(tid, handle, ecode); |
| return; |
| } |
| |
| constexpr size_t kHeaderSize = sizeof(att::Header); |
| |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| auto callback = [self, tid, handle](att::ErrorCode ecode, const auto& value) { |
| if (!self) |
| return; |
| |
| if (ecode != att::ErrorCode::kNoError) { |
| self->att_->ReplyWithError(tid, handle, ecode); |
| return; |
| } |
| |
| size_t value_size = std::min(value.size(), self->att_->mtu() - kHeaderSize); |
| auto buffer = NewSlabBuffer(value_size + kHeaderSize); |
| ZX_ASSERT(buffer); |
| |
| att::PacketWriter writer(att::kReadResponse, buffer.get()); |
| writer.mutable_payload_data().Write(value.view(0, value_size)); |
| |
| self->att_->Reply(tid, std::move(buffer)); |
| }; |
| |
| // Use the cached value if there is one. |
| if (attr->value()) { |
| callback(att::ErrorCode::kNoError, *attr->value()); |
| return; |
| } |
| |
| if (!attr->ReadAsync(peer_id_, 0, callback)) { |
| att_->ReplyWithError(tid, handle, att::ErrorCode::kReadNotPermitted); |
| } |
| } |
| |
| void Server::OnWriteCommand(att::Bearer::TransactionId tid, |
| const att::PacketReader& packet) { |
| ZX_DEBUG_ASSERT(packet.opcode() == att::kWriteCommand); |
| |
| if (packet.payload_size() < sizeof(att::WriteRequestParams)) { |
| // Ignore if wrong size, no response allowed |
| return; |
| } |
| |
| const auto& params = packet.payload<att::WriteRequestParams>(); |
| att::Handle handle = le16toh(params.handle); |
| const auto* attr = db_->FindAttribute(handle); |
| |
| // Attributes can be invalid if the handle is invalid |
| if (!attr) { |
| return; |
| } |
| |
| att::ErrorCode ecode = |
| att::CheckWritePermissions(attr->write_reqs(), att_->security()); |
| if (ecode != att::ErrorCode::kNoError) { |
| return; |
| } |
| |
| // Attributes with a static value cannot be written. |
| if (attr->value()) { |
| return; |
| } |
| |
| auto value_view = packet.payload_data().view(sizeof(params.handle)); |
| if (value_view.size() > att::kMaxAttributeValueLength) { |
| return; |
| } |
| |
| // No response allowed for commands, ignore the cb |
| attr->WriteAsync(peer_id_, 0, value_view, nullptr); |
| } |
| |
| void Server::OnWriteRequest(att::Bearer::TransactionId tid, |
| const att::PacketReader& packet) { |
| ZX_DEBUG_ASSERT(packet.opcode() == att::kWriteRequest); |
| |
| if (packet.payload_size() < sizeof(att::WriteRequestParams)) { |
| att_->ReplyWithError(tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU); |
| return; |
| } |
| |
| const auto& params = packet.payload<att::WriteRequestParams>(); |
| att::Handle handle = le16toh(params.handle); |
| |
| const auto* attr = db_->FindAttribute(handle); |
| if (!attr) { |
| att_->ReplyWithError(tid, handle, att::ErrorCode::kInvalidHandle); |
| return; |
| } |
| |
| att::ErrorCode ecode = |
| att::CheckWritePermissions(attr->write_reqs(), att_->security()); |
| if (ecode != att::ErrorCode::kNoError) { |
| att_->ReplyWithError(tid, handle, ecode); |
| return; |
| } |
| |
| // Attributes with a static value cannot be written. |
| if (attr->value()) { |
| att_->ReplyWithError(tid, handle, att::ErrorCode::kWriteNotPermitted); |
| return; |
| } |
| |
| auto value_view = packet.payload_data().view(sizeof(params.handle)); |
| if (value_view.size() > att::kMaxAttributeValueLength) { |
| att_->ReplyWithError(tid, handle, |
| att::ErrorCode::kInvalidAttributeValueLength); |
| return; |
| } |
| |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| auto result_cb = [self, tid, handle](att::ErrorCode error_code) { |
| if (!self) |
| return; |
| |
| if (error_code != att::ErrorCode::kNoError) { |
| self->att_->ReplyWithError(tid, handle, error_code); |
| return; |
| } |
| |
| auto buffer = NewSlabBuffer(1); |
| (*buffer)[0] = att::kWriteResponse; |
| self->att_->Reply(tid, std::move(buffer)); |
| }; |
| |
| if (!attr->WriteAsync(peer_id_, 0, value_view, result_cb)) { |
| att_->ReplyWithError(tid, handle, att::ErrorCode::kWriteNotPermitted); |
| } |
| } |
| |
| att::ErrorCode Server::ReadByTypeHelper( |
| att::Handle start, att::Handle end, const UUID& type, bool group_type, |
| size_t max_data_list_size, size_t max_value_size, size_t entry_prefix_size, |
| size_t* out_value_size, std::list<const att::Attribute*>* out_results) { |
| ZX_DEBUG_ASSERT(out_results); |
| ZX_DEBUG_ASSERT(out_value_size); |
| |
| if (start == att::kInvalidHandle || start > end) |
| return att::ErrorCode::kInvalidHandle; |
| |
| auto iter = db_->GetIterator(start, end, &type, group_type); |
| if (iter.AtEnd()) |
| return att::ErrorCode::kAttributeNotFound; |
| |
| // |value_size| is the size of the complete attribute value for each result |
| // entry. |entry_size| = |value_size| + |entry_prefix_size|. We store these |
| // separately to avoid recalculating one every it gets checked. |
| size_t value_size; |
| size_t entry_size; |
| std::list<const att::Attribute*> results; |
| |
| for (; !iter.AtEnd(); iter.Advance()) { |
| const auto* attr = iter.get(); |
| ZX_DEBUG_ASSERT(attr); |
| |
| att::ErrorCode security_result = |
| att::CheckReadPermissions(attr->read_reqs(), att_->security()); |
| if (security_result != att::ErrorCode::kNoError) { |
| // Return error only if this is the first result that matched. We simply |
| // stop the search otherwise. |
| if (results.empty()) { |
| return security_result; |
| } |
| break; |
| } |
| |
| // The first result determines |value_size| and |entry_size|. |
| if (results.empty()) { |
| if (!attr->value()) { |
| // If the first value is dynamic then this is the only attribute that |
| // this call will return. No need to calculate the value size. |
| results.push_back(attr); |
| break; |
| } |
| |
| value_size = attr->value()->size(); // untruncated value size |
| entry_size = |
| std::min(std::min(value_size, max_value_size) + entry_prefix_size, |
| max_data_list_size); |
| |
| // Actual value size to include in a PDU. |
| *out_value_size = entry_size - entry_prefix_size; |
| |
| } else if (!attr->value() || attr->value()->size() != value_size || |
| entry_size > max_data_list_size) { |
| // Stop the search and exclude this attribute because either: |
| // a. we ran into a dynamic value in a result that contains static values, |
| // b. the matching attribute has a different value size than the first |
| // attribute, |
| // c. there is no remaning space in the response PDU. |
| break; |
| } |
| |
| results.push_back(attr); |
| max_data_list_size -= entry_size; |
| } |
| |
| if (results.empty()) |
| return att::ErrorCode::kAttributeNotFound; |
| |
| *out_results = std::move(results); |
| return att::ErrorCode::kNoError; |
| } |
| |
| void Server::OnPrepareWriteRequest(att::Bearer::TransactionId tid, |
| const att::PacketReader& packet) { |
| ZX_DEBUG_ASSERT(packet.opcode() == att::kPrepareWriteRequest); |
| |
| if (packet.payload_size() < sizeof(att::PrepareWriteRequestParams)) { |
| att_->ReplyWithError(tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU); |
| return; |
| } |
| |
| const auto& params = packet.payload<att::PrepareWriteRequestParams>(); |
| att::Handle handle = le16toh(params.handle); |
| uint16_t offset = le16toh(params.offset); |
| auto value_view = |
| packet.payload_data().view(sizeof(params.handle) + sizeof(params.offset)); |
| |
| if (prepare_queue_.size() >= att::kPrepareQueueMaxCapacity) { |
| att_->ReplyWithError(tid, handle, att::ErrorCode::kPrepareQueueFull); |
| return; |
| } |
| |
| // Validate attribute handle and perform security checks (see Vol 3, Part F, |
| // 3.4.6.1 for required checks) |
| const auto* attr = db_->FindAttribute(handle); |
| if (!attr) { |
| att_->ReplyWithError(tid, handle, att::ErrorCode::kInvalidHandle); |
| return; |
| } |
| |
| att::ErrorCode ecode = |
| att::CheckWritePermissions(attr->write_reqs(), att_->security()); |
| if (ecode != att::ErrorCode::kNoError) { |
| att_->ReplyWithError(tid, handle, ecode); |
| return; |
| } |
| |
| prepare_queue_.push(att::QueuedWrite(handle, offset, value_view)); |
| |
| // Reply back with the request payload. |
| auto buffer = std::make_unique<DynamicByteBuffer>(packet.size()); |
| att::PacketWriter writer(att::kPrepareWriteResponse, buffer.get()); |
| writer.mutable_payload_data().Write(packet.payload_data()); |
| |
| att_->Reply(tid, std::move(buffer)); |
| } |
| |
| void Server::OnExecuteWriteRequest(att::Bearer::TransactionId tid, |
| const att::PacketReader& packet) { |
| ZX_DEBUG_ASSERT(packet.opcode() == att::kExecuteWriteRequest); |
| |
| if (packet.payload_size() != sizeof(att::ExecuteWriteRequestParams)) { |
| att_->ReplyWithError(tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU); |
| return; |
| } |
| |
| const auto& params = packet.payload<att::ExecuteWriteRequestParams>(); |
| if (params.flags == att::ExecuteWriteFlag::kCancelAll) { |
| prepare_queue_ = {}; |
| |
| auto buffer = std::make_unique<DynamicByteBuffer>(1); |
| att::PacketWriter writer(att::kExecuteWriteResponse, buffer.get()); |
| att_->Reply(tid, std::move(buffer)); |
| return; |
| } |
| |
| if (params.flags != att::ExecuteWriteFlag::kWritePending) { |
| att_->ReplyWithError(tid, att::kInvalidHandle, att::ErrorCode::kInvalidPDU); |
| return; |
| } |
| |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| auto result_cb = [self, tid](att::Handle handle, |
| att::ErrorCode ecode) mutable { |
| if (!self) |
| return; |
| |
| if (ecode != att::ErrorCode::kNoError) { |
| self->att_->ReplyWithError(tid, handle, ecode); |
| return; |
| } |
| |
| auto rsp = std::make_unique<DynamicByteBuffer>(1); |
| att::PacketWriter writer(att::kExecuteWriteResponse, rsp.get()); |
| self->att_->Reply(tid, std::move(rsp)); |
| }; |
| db_->ExecuteWriteQueue(peer_id_, std::move(prepare_queue_), att_->security(), |
| std::move(result_cb)); |
| } |
| |
| } // namespace gatt |
| } // namespace bt |