1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_bluetooth_sapphire/internal/host/testing/fake_gatt_server.h"
16
17 #include <lib/fit/function.h>
18 #include <pw_bytes/endian.h>
19
20 #include "pw_bluetooth_sapphire/internal/host/att/packet.h"
21 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
22 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
23 #include "pw_bluetooth_sapphire/internal/host/gap/gap.h"
24 #include "pw_bluetooth_sapphire/internal/host/gatt/gatt_defs.h"
25 #include "pw_bluetooth_sapphire/internal/host/testing/fake_controller.h"
26 #include "pw_bluetooth_sapphire/internal/host/testing/fake_peer.h"
27
28 namespace bt::testing {
29
FakeGattServer(FakePeer * dev)30 FakeGattServer::FakeGattServer(FakePeer* dev) : dev_(dev) {
31 PW_CHECK(dev_);
32
33 // Initialize services
34 services_.insert({/*start_handle=*/1,
35 Service{.start_handle = 1,
36 .end_handle = 1,
37 .type = gap::kGenericAccessService}});
38 services_.insert({/*start_handle=*/2,
39 Service{.start_handle = 2,
40 .end_handle = 2,
41 .type = gatt::types::kGenericAttributeService}});
42 }
43
HandlePdu(hci_spec::ConnectionHandle conn,const ByteBuffer & pdu)44 void FakeGattServer::HandlePdu(hci_spec::ConnectionHandle conn,
45 const ByteBuffer& pdu) {
46 if (pdu.size() < sizeof(att::OpCode)) {
47 bt_log(WARN, "fake-hci", "malformed ATT packet!");
48 return;
49 }
50
51 att::OpCode opcode =
52 pw::bytes::ConvertOrderFrom(cpp20::endian::little, pdu.To<att::OpCode>());
53 switch (opcode) {
54 case att::kExchangeMTURequest:
55 // Always reply back with the default ATT_MTU.
56 Send(conn,
57 StaticByteBuffer(att::kExchangeMTUResponse, att::kLEMinMTU, 0x00));
58 break;
59 case att::kReadByGroupTypeRequest:
60 HandleReadByGrpType(conn, pdu.view(sizeof(att::OpCode)));
61 break;
62 case att::kFindByTypeValueRequest:
63 HandleFindByTypeValue(conn, pdu.view(sizeof(att::OpCode)));
64 break;
65 default:
66 SendErrorRsp(conn, opcode, 0, att::ErrorCode::kRequestNotSupported);
67 break;
68 }
69 }
70
RegisterWithL2cap(FakeL2cap * l2cap_)71 void FakeGattServer::RegisterWithL2cap(FakeL2cap* l2cap_) {
72 auto cb = fit::bind_member<&FakeGattServer::HandlePdu>(this);
73 l2cap_->RegisterHandler(l2cap::kATTChannelId, cb);
74 }
75
HandleReadByGrpType(hci_spec::ConnectionHandle conn,const ByteBuffer & bytes)76 void FakeGattServer::HandleReadByGrpType(hci_spec::ConnectionHandle conn,
77 const ByteBuffer& bytes) {
78 // Don't support 128-bit group types.
79 if (bytes.size() != sizeof(att::ReadByGroupTypeRequestParams16)) {
80 SendErrorRsp(
81 conn, att::kReadByGroupTypeRequest, 0, att::ErrorCode::kInvalidPDU);
82 return;
83 }
84
85 const auto& params = bytes.To<att::ReadByGroupTypeRequestParams16>();
86 att::Handle start =
87 pw::bytes::ConvertOrderFrom(cpp20::endian::little, params.start_handle);
88 att::Handle end =
89 pw::bytes::ConvertOrderFrom(cpp20::endian::little, params.end_handle);
90 if (!start || end < start) {
91 SendErrorRsp(conn,
92 att::kReadByGroupTypeRequest,
93 start,
94 att::ErrorCode::kInvalidHandle);
95 return;
96 }
97
98 // Only support primary service discovery.
99 uint16_t grp_type =
100 pw::bytes::ConvertOrderFrom(cpp20::endian::little, params.type);
101 if (grp_type != gatt::types::kPrimaryService16 || start > att::kHandleMin) {
102 SendErrorRsp(conn,
103 att::kReadByGroupTypeRequest,
104 start,
105 att::ErrorCode::kAttributeNotFound);
106 return;
107 }
108
109 // We report back the standard services.
110 // TODO(armansito): Support standard characteristics and more services.
111 constexpr uint8_t entry_size =
112 (sizeof(att::AttributeGroupDataEntry) + sizeof(att::AttributeType16));
113 DynamicByteBuffer rsp(sizeof(att::OpCode) +
114 sizeof(att::ReadByGroupTypeResponseParams) +
115 services_.size() * entry_size);
116 att::PacketWriter rsp_writer(att::kReadByGroupTypeResponse, &rsp);
117 auto rsp_params =
118 rsp_writer.mutable_payload<att::ReadByGroupTypeResponseParams>();
119 rsp_params->length = entry_size;
120 MutableBufferView next_entry = rsp_writer.mutable_payload_data().mutable_view(
121 sizeof(att::ReadByGroupTypeResponseParams));
122
123 for (auto& [_, service] : services_) {
124 // FakeGattServer only supports 16bit UUIDs currently.
125 PW_CHECK(service.type.CompactSize(/*allow_32bit=*/false) ==
126 UUIDElemSize::k16Bit);
127 att::AttributeGroupDataEntry* entry =
128 next_entry.AsMutable<att::AttributeGroupDataEntry>();
129 entry->start_handle =
130 pw::bytes::ConvertOrderTo(cpp20::endian::little, service.start_handle);
131 entry->group_end_handle =
132 pw::bytes::ConvertOrderTo(cpp20::endian::little, service.end_handle);
133 next_entry.Write(service.type.CompactView(/*allow_32bit=*/false),
134 sizeof(att::AttributeGroupDataEntry));
135 next_entry = next_entry.mutable_view(entry_size);
136 }
137
138 Send(conn, rsp);
139 }
140
HandleFindByTypeValue(hci_spec::ConnectionHandle conn,const ByteBuffer & bytes)141 void FakeGattServer::HandleFindByTypeValue(hci_spec::ConnectionHandle conn,
142 const ByteBuffer& bytes) {
143 if (bytes.size() < sizeof(att::FindByTypeValueRequestParams)) {
144 bt_log(WARN, "fake-gatt", "find by type value request buffer too small");
145 SendErrorRsp(
146 conn, att::kFindByTypeValueRequest, 0, att::ErrorCode::kInvalidPDU);
147 return;
148 }
149
150 // It is safe to read members because bytes is at least the size of the
151 // params, and the params struct is packed.
152 att::Handle start = pw::bytes::ConvertOrderFrom(
153 cpp20::endian::little,
154 bytes.ReadMember<&att::FindByTypeValueRequestParams::start_handle>());
155 att::Handle end = pw::bytes::ConvertOrderFrom(
156 cpp20::endian::little,
157 bytes.ReadMember<&att::FindByTypeValueRequestParams::end_handle>());
158 att::AttributeType16 service_kind = pw::bytes::ConvertOrderFrom(
159 cpp20::endian::little,
160 bytes.ReadMember<&att::FindByTypeValueRequestParams::type>());
161 BufferView service_uuid_bytes =
162 bytes.view(sizeof(att::FindByTypeValueRequestParams));
163 UUID service_uuid;
164 if (!UUID::FromBytes(service_uuid_bytes, &service_uuid)) {
165 bt_log(WARN,
166 "fake-gatt",
167 "find by type value request has invalid service UUID");
168 SendErrorRsp(
169 conn, att::kFindByTypeValueRequest, 0, att::ErrorCode::kInvalidPDU);
170 return;
171 }
172
173 // Support only primary service discovery by service UUID. Support only a
174 // single request/response per UUID (additional requests return
175 // kAttributeNotFound, ending the procedure).
176 if (service_kind != gatt::types::kPrimaryService16 ||
177 start > att::kHandleMin || end < att::kHandleMax) {
178 SendErrorRsp(conn,
179 att::kFindByTypeValueRequest,
180 start,
181 att::ErrorCode::kAttributeNotFound);
182 return;
183 }
184
185 // Send a response with the first service with a matching UUID.
186 auto entry =
187 std::find_if(services_.begin(), services_.end(), [&](auto entry) {
188 return entry.second.type == service_uuid;
189 });
190 if (entry == services_.end()) {
191 bt_log(WARN,
192 "fake-gatt",
193 "attempt to discover unsupported service UUID (uuid: %s)",
194 bt_str(service_uuid));
195 SendErrorRsp(conn,
196 att::kFindByTypeValueRequest,
197 start,
198 att::ErrorCode::kAttributeNotFound);
199 return;
200 }
201
202 Service service = entry->second;
203 StaticByteBuffer<sizeof(att::OpCode) +
204 sizeof(att::FindByTypeValueResponseParams)>
205 rsp;
206 att::PacketWriter writer(att::kFindByTypeValueResponse, &rsp);
207 att::FindByTypeValueResponseParams* rsp_params =
208 writer.mutable_payload<att::FindByTypeValueResponseParams>();
209 rsp_params->handles_information_list[0].handle =
210 pw::bytes::ConvertOrderTo(cpp20::endian::little, service.start_handle);
211 rsp_params->handles_information_list[0].group_end_handle =
212 pw::bytes::ConvertOrderTo(cpp20::endian::little, service.end_handle);
213 Send(conn, rsp);
214 }
215
Send(hci_spec::ConnectionHandle conn,const ByteBuffer & pdu)216 void FakeGattServer::Send(hci_spec::ConnectionHandle conn,
217 const ByteBuffer& pdu) {
218 if (dev_->controller()) {
219 dev_->controller()->SendL2CAPBFrame(conn, l2cap::kATTChannelId, pdu);
220 } else {
221 bt_log(WARN, "fake-hci", "no assigned FakeController!");
222 }
223 }
224
SendErrorRsp(hci_spec::ConnectionHandle conn,att::OpCode opcode,att::Handle handle,att::ErrorCode ecode)225 void FakeGattServer::SendErrorRsp(hci_spec::ConnectionHandle conn,
226 att::OpCode opcode,
227 att::Handle handle,
228 att::ErrorCode ecode) {
229 StaticByteBuffer<sizeof(att::ErrorResponseParams) + sizeof(att::OpCode)>
230 buffer;
231 att::PacketWriter writer(att::kErrorResponse, &buffer);
232 auto* params = writer.mutable_payload<att::ErrorResponseParams>();
233 params->request_opcode =
234 pw::bytes::ConvertOrderTo(cpp20::endian::little, opcode);
235 params->attribute_handle =
236 pw::bytes::ConvertOrderTo(cpp20::endian::little, handle);
237 params->error_code = ecode;
238
239 Send(conn, buffer);
240 }
241
242 } // namespace bt::testing
243