1 // Copyright 2024 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 #pragma once
16
17 #include <cstdint>
18 #include <numeric>
19 #include <variant>
20 #include <vector>
21
22 #include "pw_assert/check.h"
23 #include "pw_bluetooth/emboss_util.h"
24 #include "pw_bluetooth/hci_common.emb.h"
25 #include "pw_bluetooth/hci_data.emb.h"
26 #include "pw_bluetooth/hci_events.emb.h"
27 #include "pw_bluetooth/hci_h4.emb.h"
28 #include "pw_bluetooth/l2cap_frames.emb.h"
29 #include "pw_bluetooth_proxy/basic_l2cap_channel.h"
30 #include "pw_bluetooth_proxy/gatt_notify_channel.h"
31 #include "pw_bluetooth_proxy/h4_packet.h"
32 #include "pw_bluetooth_proxy/internal/l2cap_channel.h"
33 #include "pw_bluetooth_proxy/internal/logical_transport.h"
34 #include "pw_bluetooth_proxy/l2cap_channel_common.h"
35 #include "pw_bluetooth_proxy/l2cap_coc.h"
36 #include "pw_bluetooth_proxy/l2cap_status_delegate.h"
37 #include "pw_bluetooth_proxy/proxy_host.h"
38 #include "pw_bluetooth_proxy/rfcomm_channel.h"
39 #include "pw_containers/flat_map.h"
40 #include "pw_function/function.h"
41 #include "pw_multibuf/simple_allocator_for_test.h"
42 #include "pw_status/status.h"
43 #include "pw_status/try.h"
44 #include "pw_unit_test/framework.h"
45
46 namespace pw::bluetooth::proxy {
47
48 // ########## Util functions
49
50 struct AclFrameWithStorage {
51 std::vector<uint8_t> storage;
52 emboss::AclDataFrameWriter writer;
53
54 static constexpr size_t kH4HeaderSize = 1;
h4_spanAclFrameWithStorage55 pw::span<uint8_t> h4_span() { return storage; }
hci_spanAclFrameWithStorage56 pw::span<uint8_t> hci_span() {
57 return pw::span(storage).subspan(kH4HeaderSize);
58 }
59 };
60
61 // Allocate storage and populate an ACL packet header with the given length.
62 Result<AclFrameWithStorage> SetupAcl(uint16_t handle, uint16_t l2cap_length);
63
64 struct BFrameWithStorage {
65 AclFrameWithStorage acl;
66 emboss::BFrameWriter writer;
67 };
68
69 Result<BFrameWithStorage> SetupBFrame(uint16_t handle,
70 uint16_t channel_id,
71 uint16_t bframe_len);
72
73 struct CFrameWithStorage {
74 AclFrameWithStorage acl;
75 emboss::CFrameWriter writer;
76 };
77
78 Result<CFrameWithStorage> SetupCFrame(uint16_t handle,
79 uint16_t channel_id,
80 uint16_t cframe_len);
81
82 struct KFrameWithStorage {
83 AclFrameWithStorage acl;
84 std::variant<emboss::FirstKFrameWriter, emboss::SubsequentKFrameWriter>
85 writer;
86 };
87
88 // Size of sdu_length field in first K-frames.
89 inline constexpr uint8_t kSduLengthFieldSize = 2;
90
91 // Populate a KFrame that encodes a particular segment of `payload` based on the
92 // `mps`, or maximum PDU payload size of a segment. `segment_no` is the nth
93 // segment that would be generated based on the `mps`. The first segment is
94 // `segment_no == 0` and returns the `FirstKFrameWriter` variant in
95 // `KFrameWithStorage`.
96 //
97 // Returns PW_STATUS_OUT_OF_RANGE if a segment is requested beyond the last
98 // segment that would be generated based on `mps`.
99 Result<KFrameWithStorage> SetupKFrame(uint16_t handle,
100 uint16_t channel_id,
101 uint16_t mps,
102 uint16_t segment_no,
103 span<const uint8_t> payload);
104
105 // Populate passed H4 command buffer and return Emboss view on it.
106 template <typename EmbossT>
CreateAndPopulateToControllerView(H4PacketWithH4 & h4_packet,emboss::OpCode opcode,size_t parameter_total_size)107 Result<EmbossT> CreateAndPopulateToControllerView(H4PacketWithH4& h4_packet,
108 emboss::OpCode opcode,
109 size_t parameter_total_size) {
110 h4_packet.SetH4Type(emboss::H4PacketType::COMMAND);
111 PW_TRY_ASSIGN(auto view, MakeEmbossWriter<EmbossT>(h4_packet.GetHciSpan()));
112 view.header().opcode().Write(opcode);
113 view.header().parameter_total_size().Write(parameter_total_size);
114 return view;
115 }
116
117 // Populate passed H4 event buffer and return Emboss writer on it. Suitable for
118 // use with EmbossT types whose `SizeInBytes()` accurately represents
119 // the `parameter_total_size` that should be written (minus `EventHeader` size).
120 template <typename EmbossT>
121 Result<EmbossT> CreateAndPopulateToHostEventWriter(
122 H4PacketWithHci& h4_packet,
123 emboss::EventCode event_code,
124 size_t parameter_total_size = EmbossT::SizeInBytes() -
125 emboss::EventHeader::IntrinsicSizeInBytes()) {
126 h4_packet.SetH4Type(emboss::H4PacketType::EVENT);
127 PW_TRY_ASSIGN(auto view, MakeEmbossWriter<EmbossT>(h4_packet.GetHciSpan()));
128 view.header().event_code().Write(event_code);
129 view.header().parameter_total_size().Write(parameter_total_size);
130 view.status().Write(emboss::StatusCode::SUCCESS);
131 EXPECT_TRUE(view.IsComplete());
132 return view;
133 }
134
135 // Send an LE_Read_Buffer_Size (V2) CommandComplete event to `proxy` to request
136 // the reservation of a number of LE ACL send credits.
137 Status SendLeReadBufferResponseFromController(
138 ProxyHost& proxy,
139 uint8_t num_credits_to_reserve,
140 uint16_t le_acl_data_packet_length = 251);
141
142 Status SendReadBufferResponseFromController(ProxyHost& proxy,
143 uint8_t num_credits_to_reserve);
144
145 // Send a Number_of_Completed_Packets event to `proxy` that reports each
146 // {connection handle, number of completed packets} entry provided.
147 template <size_t kNumConnections>
SendNumberOfCompletedPackets(ProxyHost & proxy,containers::FlatMap<uint16_t,uint16_t,kNumConnections> packets_per_connection)148 Status SendNumberOfCompletedPackets(
149 ProxyHost& proxy,
150 containers::FlatMap<uint16_t, uint16_t, kNumConnections>
151 packets_per_connection) {
152 std::array<
153 uint8_t,
154 emboss::NumberOfCompletedPacketsEvent::MinSizeInBytes() +
155 kNumConnections *
156 emboss::NumberOfCompletedPacketsEventData::IntrinsicSizeInBytes()>
157 hci_arr;
158 hci_arr.fill(0);
159 H4PacketWithHci nocp_event{emboss::H4PacketType::EVENT, hci_arr};
160 PW_TRY_ASSIGN(auto view,
161 MakeEmbossWriter<emboss::NumberOfCompletedPacketsEventWriter>(
162 nocp_event.GetHciSpan()));
163 view.header().event_code().Write(
164 emboss::EventCode::NUMBER_OF_COMPLETED_PACKETS);
165 view.header().parameter_total_size().Write(
166 nocp_event.GetHciSpan().size() -
167 emboss::EventHeader::IntrinsicSizeInBytes());
168 view.num_handles().Write(kNumConnections);
169
170 size_t i = 0;
171 for (const auto& [handle, num_packets] : packets_per_connection) {
172 view.nocp_data()[i].connection_handle().Write(handle);
173 view.nocp_data()[i].num_completed_packets().Write(num_packets);
174 ++i;
175 }
176
177 proxy.HandleH4HciFromController(std::move(nocp_event));
178 return OkStatus();
179 }
180
181 // Send a Connection_Complete event to `proxy` indicating the provided
182 // `handle` has disconnected.
183 Status SendConnectionCompleteEvent(ProxyHost& proxy,
184 uint16_t handle,
185 emboss::StatusCode status);
186
187 // Send a LE_Connection_Complete event to `proxy` indicating the provided
188 // `handle` has disconnected.
189 Status SendLeConnectionCompleteEvent(ProxyHost& proxy,
190 uint16_t handle,
191 emboss::StatusCode status);
192
193 // Send a Disconnection_Complete event to `proxy` indicating the provided
194 // `handle` has disconnected.
195 Status SendDisconnectionCompleteEvent(
196 ProxyHost& proxy,
197 uint16_t handle,
198 Direction direction = Direction::kFromController,
199 bool successful = true);
200
201 Status SendL2capConnectionReq(ProxyHost& proxy,
202 uint16_t handle,
203 uint16_t source_cid,
204 uint16_t psm);
205
206 Status SendL2capConnectionRsp(ProxyHost& proxy,
207 uint16_t handle,
208 uint16_t source_cid,
209 uint16_t destination_cid,
210 emboss::L2capConnectionRspResultCode result_code);
211
212 Status SendL2capDisconnectRsp(ProxyHost& proxy,
213 AclTransportType transport,
214 uint16_t handle,
215 uint16_t source_cid,
216 uint16_t destination_cid,
217 Direction direction = Direction::kFromHost);
218
219 /// Sends an L2CAP B-Frame.
220 ///
221 /// This can be either a complete PDU (pdu_length == payload.size()) or an
222 /// initial fragment (pdu_length > payload.size()).
223 void SendL2capBFrame(ProxyHost& proxy,
224 uint16_t handle,
225 pw::span<const uint8_t> payload,
226 size_t pdu_length,
227 uint16_t channel_id);
228
229 /// Sends an ACL frame with CONTINUING_FRAGMENT boundary flag.
230 ///
231 /// No L2CAP header is included.
232 void SendAclContinuingFrag(ProxyHost& proxy,
233 uint16_t handle,
234 pw::span<const uint8_t> payload);
235
236 // TODO: https://pwbug.dev/382783733 - Migrate to L2capChannelEvent callback.
237 struct CocParameters {
238 uint16_t handle = 123;
239 uint16_t local_cid = 234;
240 uint16_t remote_cid = 456;
241 uint16_t rx_mtu = 100;
242 uint16_t rx_mps = 100;
243 uint16_t rx_credits = 1;
244 uint16_t tx_mtu = 100;
245 uint16_t tx_mps = 100;
246 uint16_t tx_credits = 1;
247 Function<void(multibuf::MultiBuf&& payload)>&& receive_fn = nullptr;
248 ChannelEventCallback&& event_fn = nullptr;
249 };
250
251 struct BasicL2capParameters {
252 uint16_t handle = 123;
253 uint16_t local_cid = 234;
254 uint16_t remote_cid = 456;
255 AclTransportType transport = AclTransportType::kLe;
256 OptionalPayloadReceiveCallback&& payload_from_controller_fn = nullptr;
257 OptionalPayloadReceiveCallback&& payload_from_host_fn = nullptr;
258 ChannelEventCallback&& event_fn = nullptr;
259 };
260
261 struct GattNotifyChannelParameters {
262 uint16_t handle = 0xAB;
263 uint16_t attribute_handle = 0xBC;
264 ChannelEventCallback&& event_fn = nullptr;
265 };
266
267 struct RfcommConfigParameters {
268 uint16_t cid = 123;
269 uint16_t max_information_length = 900;
270 uint16_t credits = 10;
271 };
272
273 struct RfcommParameters {
274 uint16_t handle = 123;
275 RfcommConfigParameters rx_config = {
276 .cid = 234, .max_information_length = 900, .credits = 10};
277 RfcommConfigParameters tx_config = {
278 .cid = 456, .max_information_length = 900, .credits = 10};
279 uint8_t rfcomm_channel = 3;
280 };
281
282 // See BuildOneOfEachChannel
283 struct OneOfEachChannelParameters {
284 Function<void(multibuf::MultiBuf&& payload)>&& receive_fn = nullptr;
285 ChannelEventCallback&& event_fn = nullptr;
286 };
287
288 // See BuildOneOfEachChannel
289 struct OneOfEachChannel {
OneOfEachChannelOneOfEachChannel290 OneOfEachChannel(BasicL2capChannel&& basic,
291 L2capCoc&& coc,
292 RfcommChannel&& rfcomm,
293 GattNotifyChannel&& gatt)
294 : basic_{std::move(basic)},
295 coc_{std::move(coc)},
296 rfcomm_{std::move(rfcomm)},
297 gatt_{std::move(gatt)} {}
298
AllChannelsOneOfEachChannel299 std::vector<L2capChannel*> AllChannels() {
300 return std::vector<L2capChannel*>{&basic_, &coc_, &rfcomm_, &gatt_};
301 }
302
303 BasicL2capChannel basic_;
304 L2capCoc coc_;
305 RfcommChannel rfcomm_;
306 GattNotifyChannel gatt_;
307 };
308
309 // ########## Test Suites
310
311 class ProxyHostTest : public testing::Test {
312 protected:
313 pw::Result<L2capCoc> BuildCocWithResult(ProxyHost& proxy,
314 CocParameters params);
315
316 L2capCoc BuildCoc(ProxyHost& proxy, CocParameters params);
317
318 Result<BasicL2capChannel> BuildBasicL2capChannelWithResult(
319 ProxyHost& proxy, BasicL2capParameters params);
320
321 BasicL2capChannel BuildBasicL2capChannel(ProxyHost& proxy,
322 BasicL2capParameters params);
323
324 Result<GattNotifyChannel> BuildGattNotifyChannelWithResult(
325 ProxyHost& proxy, GattNotifyChannelParameters params);
326
327 GattNotifyChannel BuildGattNotifyChannel(ProxyHost& proxy,
328 GattNotifyChannelParameters params);
329
330 RfcommChannel BuildRfcomm(
331 ProxyHost& proxy,
332 RfcommParameters params = {},
333 Function<void(multibuf::MultiBuf&& payload)>&& receive_fn = nullptr,
334 ChannelEventCallback&& event_fn = nullptr);
335
336 template <typename T, size_t N>
MultiBufFromSpan(span<T,N> buf)337 pw::multibuf::MultiBuf MultiBufFromSpan(span<T, N> buf) {
338 std::optional<pw::multibuf::MultiBuf> multibuf =
339 test_multibuf_allocator_.AllocateContiguous(buf.size());
340 PW_ASSERT(multibuf.has_value());
341 std::optional<ConstByteSpan> multibuf_span = multibuf->ContiguousSpan();
342 PW_ASSERT(multibuf_span);
343 PW_TEST_EXPECT_OK(multibuf->CopyFrom(as_bytes(buf)));
344 return std::move(*multibuf);
345 }
346
347 // Builds a struct with one of each channel to support tests across all
348 // of them.
349 //
350 // Note, shared_event_fn is a reference (rather than a rvalue) so it can
351 // be shared across each channel.
352 OneOfEachChannel BuildOneOfEachChannel(ProxyHost& proxy,
353 ChannelEventCallback& shared_event_fn);
354
355 template <typename T, size_t N>
MultiBufFromArray(const std::array<T,N> & arr)356 pw::multibuf::MultiBuf MultiBufFromArray(const std::array<T, N>& arr) {
357 return MultiBufFromSpan(pw::span{arr});
358 }
359
360 private:
361 // MultiBuf allocator for creating objects to pass to the system under
362 // test (e.g. creating test packets to send to proxy host).
363 pw::multibuf::test::SimpleAllocatorForTest</*kDataSizeBytes=*/2 * 1024,
364 /*kMetaSizeBytes=*/2 * 1024>
365 test_multibuf_allocator_{};
366
367 // Default MultiBuf allocator to be passed to system under test (e.g.
368 // to pass to AcquireL2capCoc).
369 pw::multibuf::test::SimpleAllocatorForTest</*kDataSizeBytes=*/1024,
370 /*kMetaSizeBytes=*/2 * 1024>
371 sut_multibuf_allocator_{};
372 };
373
374 } // namespace pw::bluetooth::proxy
375