blob: 2bfe8a667cbe84d4ebfca865a4bcf0fd45e16c89 [file] [log] [blame]
// 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/testing/fake_gatt_server.h"
#include <endian.h>
#include <lib/fit/function.h>
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/att/packet.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/assert.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/gap/gap.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gatt/gatt_defs.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/fake_controller.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/fake_peer.h"
namespace bt::testing {
FakeGattServer::FakeGattServer(FakePeer* dev) : dev_(dev) {
BT_ASSERT(dev_);
// Initialize services
services_.insert({/*start_handle=*/1,
Service{.start_handle = 1,
.end_handle = 1,
.type = gap::kGenericAccessService}});
services_.insert({/*start_handle=*/2,
Service{.start_handle = 2,
.end_handle = 2,
.type = gatt::types::kGenericAttributeService}});
}
void FakeGattServer::HandlePdu(hci_spec::ConnectionHandle conn,
const ByteBuffer& pdu) {
if (pdu.size() < sizeof(att::OpCode)) {
bt_log(WARN, "fake-hci", "malformed ATT packet!");
return;
}
att::OpCode opcode = le16toh(pdu.To<att::OpCode>());
switch (opcode) {
case att::kExchangeMTURequest:
// Always reply back with the default ATT_MTU.
Send(conn,
StaticByteBuffer(att::kExchangeMTUResponse, att::kLEMinMTU, 0x00));
break;
case att::kReadByGroupTypeRequest:
HandleReadByGrpType(conn, pdu.view(sizeof(att::OpCode)));
break;
case att::kFindByTypeValueRequest:
HandleFindByTypeValue(conn, pdu.view(sizeof(att::OpCode)));
break;
default:
SendErrorRsp(conn, opcode, 0, att::ErrorCode::kRequestNotSupported);
break;
}
}
void FakeGattServer::RegisterWithL2cap(FakeL2cap* l2cap_) {
auto cb = fit::bind_member<&FakeGattServer::HandlePdu>(this);
l2cap_->RegisterHandler(l2cap::kATTChannelId, cb);
}
void FakeGattServer::HandleReadByGrpType(hci_spec::ConnectionHandle conn,
const ByteBuffer& bytes) {
// Don't support 128-bit group types.
if (bytes.size() != sizeof(att::ReadByGroupTypeRequestParams16)) {
SendErrorRsp(
conn, att::kReadByGroupTypeRequest, 0, att::ErrorCode::kInvalidPDU);
return;
}
const auto& params = bytes.To<att::ReadByGroupTypeRequestParams16>();
att::Handle start = le16toh(params.start_handle);
att::Handle end = le16toh(params.end_handle);
if (!start || end < start) {
SendErrorRsp(conn,
att::kReadByGroupTypeRequest,
start,
att::ErrorCode::kInvalidHandle);
return;
}
// Only support primary service discovery.
uint16_t grp_type = le16toh(params.type);
if (grp_type != gatt::types::kPrimaryService16 || start > att::kHandleMin) {
SendErrorRsp(conn,
att::kReadByGroupTypeRequest,
start,
att::ErrorCode::kAttributeNotFound);
return;
}
// We report back the standard services.
// TODO(armansito): Support standard characteristics and more services.
constexpr uint8_t entry_size =
(sizeof(att::AttributeGroupDataEntry) + sizeof(att::AttributeType16));
DynamicByteBuffer rsp(sizeof(att::OpCode) +
sizeof(att::ReadByGroupTypeResponseParams) +
services_.size() * entry_size);
att::PacketWriter rsp_writer(att::kReadByGroupTypeResponse, &rsp);
auto rsp_params =
rsp_writer.mutable_payload<att::ReadByGroupTypeResponseParams>();
rsp_params->length = entry_size;
MutableBufferView next_entry = rsp_writer.mutable_payload_data().mutable_view(
sizeof(att::ReadByGroupTypeResponseParams));
for (auto& [_, service] : services_) {
// FakeGattServer only supports 16bit UUIDs currently.
BT_ASSERT(service.type.CompactSize(/*allow_32bit=*/false) ==
UUIDElemSize::k16Bit);
att::AttributeGroupDataEntry* entry =
next_entry.AsMutable<att::AttributeGroupDataEntry>();
entry->start_handle = htole16(service.start_handle);
entry->group_end_handle = htole16(service.end_handle);
next_entry.Write(service.type.CompactView(/*allow_32bit=*/false),
sizeof(att::AttributeGroupDataEntry));
next_entry = next_entry.mutable_view(entry_size);
}
Send(conn, rsp);
}
void FakeGattServer::HandleFindByTypeValue(hci_spec::ConnectionHandle conn,
const ByteBuffer& bytes) {
if (bytes.size() < sizeof(att::FindByTypeValueRequestParams)) {
bt_log(WARN, "fake-gatt", "find by type value request buffer too small");
SendErrorRsp(
conn, att::kFindByTypeValueRequest, 0, att::ErrorCode::kInvalidPDU);
return;
}
// It is safe to read members because bytes is at least the size of the
// params, and the params struct is packed.
att::Handle start = le16toh(
bytes.ReadMember<&att::FindByTypeValueRequestParams::start_handle>());
att::Handle end = le16toh(
bytes.ReadMember<&att::FindByTypeValueRequestParams::end_handle>());
att::AttributeType16 service_kind =
le16toh(bytes.ReadMember<&att::FindByTypeValueRequestParams::type>());
BufferView service_uuid_bytes =
bytes.view(sizeof(att::FindByTypeValueRequestParams));
UUID service_uuid;
if (!UUID::FromBytes(service_uuid_bytes, &service_uuid)) {
bt_log(WARN,
"fake-gatt",
"find by type value request has invalid service UUID");
SendErrorRsp(
conn, att::kFindByTypeValueRequest, 0, att::ErrorCode::kInvalidPDU);
return;
}
// Support only primary service discovery by service UUID. Support only a
// single request/response per UUID (additional requests return
// kAttributeNotFound, ending the procedure).
if (service_kind != gatt::types::kPrimaryService16 ||
start > att::kHandleMin || end < att::kHandleMax) {
SendErrorRsp(conn,
att::kFindByTypeValueRequest,
start,
att::ErrorCode::kAttributeNotFound);
return;
}
// Send a response with the first service with a matching UUID.
auto entry =
std::find_if(services_.begin(), services_.end(), [&](auto entry) {
return entry.second.type == service_uuid;
});
if (entry == services_.end()) {
bt_log(WARN,
"fake-gatt",
"attempt to discover unsupported service UUID (uuid: %s)",
bt_str(service_uuid));
SendErrorRsp(conn,
att::kFindByTypeValueRequest,
start,
att::ErrorCode::kAttributeNotFound);
return;
}
Service service = entry->second;
StaticByteBuffer<sizeof(att::OpCode) +
sizeof(att::FindByTypeValueResponseParams)>
rsp;
att::PacketWriter writer(att::kFindByTypeValueResponse, &rsp);
att::FindByTypeValueResponseParams* rsp_params =
writer.mutable_payload<att::FindByTypeValueResponseParams>();
rsp_params->handles_information_list[0].handle =
htole16(service.start_handle);
rsp_params->handles_information_list[0].group_end_handle =
htole16(service.end_handle);
Send(conn, rsp);
}
void FakeGattServer::Send(hci_spec::ConnectionHandle conn,
const ByteBuffer& pdu) {
if (dev_->controller()) {
dev_->controller()->SendL2CAPBFrame(conn, l2cap::kATTChannelId, pdu);
} else {
bt_log(WARN, "fake-hci", "no assigned FakeController!");
}
}
void FakeGattServer::SendErrorRsp(hci_spec::ConnectionHandle conn,
att::OpCode opcode,
att::Handle handle,
att::ErrorCode ecode) {
StaticByteBuffer<sizeof(att::ErrorResponseParams) + sizeof(att::OpCode)>
buffer;
att::PacketWriter writer(att::kErrorResponse, &buffer);
auto* params = writer.mutable_payload<att::ErrorResponseParams>();
params->request_opcode = htole16(opcode);
params->attribute_handle = htole16(handle);
params->error_code = ecode;
Send(conn, buffer);
}
} // namespace bt::testing