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 #include "pw_bluetooth_proxy/proxy_host.h"
16
17 #include <cstdint>
18 #include <numeric>
19
20 #include "emboss_util.h"
21 #include "lib/stdcompat/utility.h"
22 #include "pw_bluetooth/hci_commands.emb.h"
23 #include "pw_bluetooth/hci_common.emb.h"
24 #include "pw_bluetooth/hci_events.emb.h"
25 #include "pw_bluetooth/hci_h4.emb.h"
26 #include "pw_unit_test/framework.h" // IWYU pragma: keep
27
28 namespace pw::bluetooth::proxy {
29
30 namespace {
31
32 // ########## Util functions
33
34 // Populate passed H4 command buffer and return Emboss view on it.
35 template <typename EmbossT>
CreateAndPopulateToControllerView(H4HciPacket & h4_packet,emboss::OpCode opcode)36 EmbossT CreateAndPopulateToControllerView(H4HciPacket& h4_packet,
37 emboss::OpCode opcode) {
38 std::iota(h4_packet.hci_span.begin(), h4_packet.hci_span.end(), 100);
39 h4_packet.h4_type = emboss::H4PacketType::COMMAND;
40 EmbossT view = MakeEmboss<EmbossT>(h4_packet.hci_span);
41 EXPECT_TRUE(view.IsComplete());
42 view.header().opcode_enum().Write(opcode);
43 return view;
44 }
45
46 // Return a populated H4 command buffer of a type that proxy host doesn't
47 // interact with.
PopulateNoninteractingToControllerBuffer(H4HciPacket & h4_packet)48 void PopulateNoninteractingToControllerBuffer(H4HciPacket& h4_packet) {
49 CreateAndPopulateToControllerView<emboss::InquiryCommandWriter>(
50 h4_packet, emboss::OpCode::LINK_KEY_REQUEST_REPLY);
51 }
52
53 // Populate passed H4 event buffer and return Emboss view on it.
54 template <typename EmbossT>
CreateAndPopulateToHostEventView(H4HciPacket & h4_packet,emboss::EventCode event_code)55 EmbossT CreateAndPopulateToHostEventView(H4HciPacket& h4_packet,
56 emboss::EventCode event_code) {
57 std::iota(h4_packet.hci_span.begin(), h4_packet.hci_span.end(), 0x10);
58 h4_packet.h4_type = emboss::H4PacketType::EVENT;
59 EmbossT view = MakeEmboss<EmbossT>(h4_packet.hci_span);
60 view.header().event_code_enum().Write(event_code);
61 view.status().Write(emboss::StatusCode::SUCCESS);
62 EXPECT_TRUE(view.IsComplete());
63 return view;
64 }
65
66 // Return a populated H4 event buffer of a type that proxy host doesn't interact
67 // with.
68
CreateNonInteractingToHostBuffer(H4HciPacket & h4_packet)69 void CreateNonInteractingToHostBuffer(H4HciPacket& h4_packet) {
70 CreateAndPopulateToHostEventView<emboss::InquiryCompleteEventWriter>(
71 h4_packet, emboss::EventCode::INQUIRY_COMPLETE);
72 }
73
74 // ########## Examples
75
76 // Example for docs.rst.
TEST(Example,ExampleUsage)77 TEST(Example, ExampleUsage) {
78 // Populate H4 buffer to send towards controller.
79 std::array<uint8_t, emboss::InquiryCommandView::SizeInBytes()>
80 hci_array_from_host;
81 H4HciPacket h4_packet_from_host{emboss::H4PacketType::UNKNOWN,
82 hci_array_from_host};
83 PopulateNoninteractingToControllerBuffer(h4_packet_from_host);
84
85 // Populate H4 buffer to send towards host.
86 std::array<uint8_t, emboss::InquiryCompleteEventView::SizeInBytes() + 1>
87 hci_array_from_controller;
88 H4HciPacket h4_packet_from_controller{emboss::H4PacketType::UNKNOWN,
89 hci_array_from_controller};
90
91 CreateNonInteractingToHostBuffer(h4_packet_from_controller);
92
93 H4HciPacketSendFn containerSendToHostFn(
94 []([[maybe_unused]] H4HciPacket packet) {});
95
96 H4HciPacketSendFn containerSendToControllerFn(
97 ([]([[maybe_unused]] H4HciPacket packet) {}));
98
99 // DOCSTAG: [pw_bluetooth_proxy-examples-basic]
100
101 #include "pw_bluetooth_proxy/proxy_host.h"
102
103 // Container creates ProxyHost .
104 ProxyHost proxy = ProxyHost(std::move(containerSendToHostFn),
105 std::move(containerSendToControllerFn),
106 2);
107
108 // Container passes H4 packets from host through proxy. Proxy will in turn
109 // call the container-provided `containerSendToControllerFn` to pass them on
110 // to the controller. Some packets may be modified, added, or removed.
111 proxy.HandleH4HciFromHost(h4_packet_from_host);
112
113 // Container passes H4 packets from controller through proxy. Proxy will in
114 // turn call the container-provided `containerSendToHostFn` to pass them on to
115 // the controller. Some packets may be modified, added, or removed.
116 proxy.HandleH4HciFromController(h4_packet_from_controller);
117
118 // DOCSTAG: [pw_bluetooth_proxy-examples-basic]
119 }
120
121 // ########## PassthroughTest
122
123 // Verify buffer is properly passed (contents unaltered and zero-copy).
TEST(PassthroughTest,ToControllerPassesEqualBuffer)124 TEST(PassthroughTest, ToControllerPassesEqualBuffer) {
125 std::array<uint8_t, emboss::InquiryCommandView::SizeInBytes()> hci_arr;
126 H4HciPacket h4_packet{emboss::H4PacketType::UNKNOWN, hci_arr};
127 PopulateNoninteractingToControllerBuffer(h4_packet);
128
129 // Struct for capturing because `pw::Function` can't fit multiple captures.
130 struct {
131 // Use a copy for comparison to catch if proxy incorrectly changes the
132 // passed buffer.
133 std::array<uint8_t, emboss::InquiryCommandView::SizeInBytes()> hci_arr;
134 H4HciPacket* h4_packet;
135 bool send_called;
136 } send_capture = {hci_arr, &h4_packet, false};
137
138 H4HciPacketSendFn send_to_controller_fn([&send_capture](H4HciPacket packet) {
139 send_capture.send_called = true;
140 EXPECT_EQ(packet.h4_type, send_capture.h4_packet->h4_type);
141 EXPECT_TRUE(std::equal(send_capture.h4_packet->hci_span.begin(),
142 send_capture.h4_packet->hci_span.end(),
143 send_capture.h4_packet->hci_span.begin(),
144 send_capture.h4_packet->hci_span.end()));
145 // Verify no copy by verifying buffer is at the same memory location.
146 EXPECT_EQ(packet.hci_span.data(), send_capture.h4_packet->hci_span.data());
147 });
148
149 H4HciPacketSendFn send_to_host_fn([]([[maybe_unused]] H4HciPacket packet) {});
150
151 ProxyHost proxy = ProxyHost(
152 std::move(send_to_host_fn), std::move(send_to_controller_fn), 2);
153
154 proxy.HandleH4HciFromHost(h4_packet);
155
156 // Verify to controller callback was called.
157 EXPECT_EQ(send_capture.send_called, true);
158 }
159
160 // Verify buffer is properly passed (contents unaltered and zero-copy).
TEST(PassthroughTest,ToHostPassesEqualBuffer)161 TEST(PassthroughTest, ToHostPassesEqualBuffer) {
162 std::array<uint8_t, emboss::InquiryCompleteEventView::SizeInBytes()> hci_arr;
163 H4HciPacket h4_packet{emboss::H4PacketType::UNKNOWN, hci_arr};
164 CreateNonInteractingToHostBuffer(h4_packet);
165
166 // Struct for capturing because `pw::Function` can't fit multiple captures.
167 struct {
168 // Use a copy for comparison to catch if proxy incorrectly changes the
169 // passed buffer.
170 std::array<uint8_t, emboss::InquiryCompleteEventView::SizeInBytes()>
171 hci_arr;
172 H4HciPacket* h4_packet;
173 bool send_called;
174 } send_capture = {hci_arr, &h4_packet, false};
175
176 H4HciPacketSendFn send_to_host_fn([&send_capture](H4HciPacket packet) {
177 send_capture.send_called = true;
178 EXPECT_EQ(packet.h4_type, send_capture.h4_packet->h4_type);
179 EXPECT_TRUE(std::equal(send_capture.h4_packet->hci_span.begin(),
180 send_capture.h4_packet->hci_span.end(),
181 send_capture.h4_packet->hci_span.begin(),
182 send_capture.h4_packet->hci_span.end()));
183 // Verify no copy by verifying buffer is at the same memory location.
184 EXPECT_EQ(packet.hci_span.data(), send_capture.h4_packet->hci_span.data());
185 });
186
187 H4HciPacketSendFn send_to_controller_fn(
188 []([[maybe_unused]] H4HciPacket packet) {});
189
190 ProxyHost proxy = ProxyHost(
191 std::move(send_to_host_fn), std::move(send_to_controller_fn), 2);
192
193 proxy.HandleH4HciFromController(h4_packet);
194
195 // Verify to controller callback was called.
196 EXPECT_EQ(send_capture.send_called, true);
197 }
198
199 // Verify a command complete event (of a type that proxy doesn't act on) is
200 // properly passed (contents unaltered and zero-copy).
TEST(PassthroughTest,ToHostPassesEqualCommandComplete)201 TEST(PassthroughTest, ToHostPassesEqualCommandComplete) {
202 std::array<
203 uint8_t,
204 emboss::ReadLocalVersionInfoCommandCompleteEventWriter::SizeInBytes()>
205 hci_arr;
206 H4HciPacket h4_packet{emboss::H4PacketType::UNKNOWN, hci_arr};
207 emboss::ReadLocalVersionInfoCommandCompleteEventWriter view =
208 CreateAndPopulateToHostEventView<
209 emboss::ReadLocalVersionInfoCommandCompleteEventWriter>(
210 h4_packet, emboss::EventCode::COMMAND_COMPLETE);
211 view.command_complete().command_opcode_enum().Write(
212 emboss::OpCode::READ_LOCAL_VERSION_INFO);
213
214 // Struct for capturing because `pw::Function` can't fit multiple captures.
215 struct {
216 std::array<
217 uint8_t,
218 emboss::ReadLocalVersionInfoCommandCompleteEventWriter::SizeInBytes()>
219 hci_arr;
220 H4HciPacket* h4_packet;
221 bool send_called;
222 } send_capture = {hci_arr, &h4_packet, false};
223
224 H4HciPacketSendFn send_to_host_fn([&send_capture](H4HciPacket packet) {
225 send_capture.send_called = true;
226 EXPECT_EQ(packet.h4_type, send_capture.h4_packet->h4_type);
227 EXPECT_TRUE(std::equal(send_capture.h4_packet->hci_span.begin(),
228 send_capture.h4_packet->hci_span.end(),
229 send_capture.h4_packet->hci_span.begin(),
230 send_capture.h4_packet->hci_span.end()));
231 // Verify no copy by verifying buffer is at the same memory location.
232 EXPECT_EQ(packet.hci_span.data(), send_capture.h4_packet->hci_span.data());
233 });
234
235 H4HciPacketSendFn send_to_controller_fn(
236 []([[maybe_unused]] H4HciPacket packet) {});
237
238 ProxyHost proxy = ProxyHost(
239 std::move(send_to_host_fn), std::move(send_to_controller_fn), 2);
240
241 proxy.HandleH4HciFromController(h4_packet);
242
243 // Verify to controller callback was called.
244 EXPECT_EQ(send_capture.send_called, true);
245 }
246
247 // ########## BadPacketTest
248 // The proxy should not affect buffers it can't process (it should just pass
249 // them on).
250
TEST(BadPacketTest,BadH4TypeToControllerIsPassedOn)251 TEST(BadPacketTest, BadH4TypeToControllerIsPassedOn) {
252 std::array<uint8_t, emboss::InquiryCommandView::SizeInBytes()> hci_arr;
253 H4HciPacket h4_packet{emboss::H4PacketType::UNKNOWN, hci_arr};
254 PopulateNoninteractingToControllerBuffer(h4_packet);
255
256 // Set back to an invalid type.
257 h4_packet.h4_type = emboss::H4PacketType::UNKNOWN;
258
259 // Struct for capturing because `pw::Function` can't fit multiple captures.
260 struct {
261 // Use a copy for comparison to catch if proxy incorrectly changes the
262 // passed buffer.
263 std::array<uint8_t, emboss::InquiryCommandView::SizeInBytes()> hci_arr;
264 H4HciPacket* h4_packet;
265 bool send_called;
266 } send_capture = {hci_arr, &h4_packet, false};
267
268 H4HciPacketSendFn send_to_controller_fn([&send_capture](H4HciPacket packet) {
269 send_capture.send_called = true;
270 EXPECT_EQ(packet.h4_type, emboss::H4PacketType::UNKNOWN);
271 EXPECT_TRUE(std::equal(send_capture.h4_packet->hci_span.begin(),
272 send_capture.h4_packet->hci_span.end(),
273 send_capture.h4_packet->hci_span.begin(),
274 send_capture.h4_packet->hci_span.end()));
275 // Verify no copy by verifying buffer is at the same memory location.
276 EXPECT_EQ(packet.hci_span.data(), send_capture.h4_packet->hci_span.data());
277 });
278
279 H4HciPacketSendFn send_to_host_fn([]([[maybe_unused]] H4HciPacket packet) {});
280
281 ProxyHost proxy = ProxyHost(
282 std::move(send_to_host_fn), std::move(send_to_controller_fn), 2);
283
284 proxy.HandleH4HciFromHost(h4_packet);
285
286 // Verify to controller callback was called.
287 EXPECT_EQ(send_capture.send_called, true);
288 }
289
TEST(PBadPacketTest,BadH4TypeToHostIsPassedOn)290 TEST(PBadPacketTest, BadH4TypeToHostIsPassedOn) {
291 std::array<uint8_t, emboss::InquiryCompleteEventView::SizeInBytes()> hci_arr;
292 H4HciPacket h4_packet{emboss::H4PacketType::UNKNOWN, hci_arr};
293 CreateNonInteractingToHostBuffer(h4_packet);
294
295 // Set back to an invalid type.
296 h4_packet.h4_type = emboss::H4PacketType::UNKNOWN;
297
298 // Struct for capturing because `pw::Function` can't fit multiple captures.
299 struct {
300 // Use a copy for comparison to catch if proxy incorrectly changes the
301 // passed buffer.
302 std::array<uint8_t, emboss::InquiryCompleteEventView::SizeInBytes()>
303 hci_arr;
304 H4HciPacket* h4_packet;
305 bool send_called;
306 } send_capture = {hci_arr, &h4_packet, false};
307
308 H4HciPacketSendFn send_to_host_fn([&send_capture](H4HciPacket packet) {
309 send_capture.send_called = true;
310 EXPECT_EQ(packet.h4_type, emboss::H4PacketType::UNKNOWN);
311 EXPECT_TRUE(std::equal(send_capture.h4_packet->hci_span.begin(),
312 send_capture.h4_packet->hci_span.end(),
313 send_capture.h4_packet->hci_span.begin(),
314 send_capture.h4_packet->hci_span.end()));
315 // Verify no copy by verifying buffer is at the same memory location.
316 EXPECT_EQ(packet.hci_span.data(), send_capture.h4_packet->hci_span.data());
317 });
318
319 H4HciPacketSendFn send_to_controller_fn(
320 []([[maybe_unused]] H4HciPacket packet) {});
321
322 ProxyHost proxy = ProxyHost(
323 std::move(send_to_host_fn), std::move(send_to_controller_fn), 2);
324
325 proxy.HandleH4HciFromController(h4_packet);
326
327 // Verify to controller callback was called.
328 EXPECT_EQ(send_capture.send_called, true);
329 }
330
TEST(BadPacketTest,EmptyBufferToControllerIsPassedOn)331 TEST(BadPacketTest, EmptyBufferToControllerIsPassedOn) {
332 std::array<uint8_t, 0> hci_arr;
333 H4HciPacket h4_packet{emboss::H4PacketType::COMMAND, hci_arr};
334
335 bool send_called = false;
336 H4HciPacketSendFn send_to_controller_fn([&send_called](H4HciPacket packet) {
337 send_called = true;
338 EXPECT_EQ(packet.h4_type, emboss::H4PacketType::COMMAND);
339 EXPECT_TRUE(packet.hci_span.empty());
340 });
341
342 H4HciPacketSendFn send_to_host_fn([]([[maybe_unused]] H4HciPacket packet) {});
343
344 ProxyHost proxy = ProxyHost(
345 std::move(send_to_host_fn), std::move(send_to_controller_fn), 2);
346
347 proxy.HandleH4HciFromHost(h4_packet);
348
349 // Verify callback was called.
350 EXPECT_EQ(send_called, true);
351 }
352
TEST(BadPacketTest,EmptyBufferToHostIsPassedOn)353 TEST(BadPacketTest, EmptyBufferToHostIsPassedOn) {
354 std::array<uint8_t, 0> hci_arr;
355 H4HciPacket h4_packet{emboss::H4PacketType::EVENT, hci_arr};
356
357 bool send_called = false;
358 H4HciPacketSendFn send_to_host_fn([&send_called](H4HciPacket packet) {
359 send_called = true;
360 EXPECT_EQ(packet.h4_type, emboss::H4PacketType::EVENT);
361 EXPECT_TRUE(packet.hci_span.empty());
362 });
363
364 H4HciPacketSendFn send_to_controller_fn(
365 []([[maybe_unused]] H4HciPacket packet) {});
366
367 ProxyHost proxy = ProxyHost(
368 std::move(send_to_host_fn), std::move(send_to_controller_fn), 2);
369
370 proxy.HandleH4HciFromController(h4_packet);
371
372 // Verify callback was called.
373 EXPECT_EQ(send_called, true);
374 }
375
TEST(BadPacketTest,TooShortEventToHostIsPassOn)376 TEST(BadPacketTest, TooShortEventToHostIsPassOn) {
377 std::array<uint8_t, emboss::InquiryCompleteEventView::SizeInBytes()>
378 valid_hci_arr;
379 H4HciPacket valid_packet{emboss::H4PacketType::UNKNOWN, valid_hci_arr};
380 CreateNonInteractingToHostBuffer(valid_packet);
381
382 // Create packet for sending whose span size is one less than a valid command
383 // complete event.
384 H4HciPacket h4_packet{valid_packet.h4_type,
385 valid_packet.hci_span.subspan(
386 0, emboss::EventHeaderView::SizeInBytes() - 1)};
387
388 // Struct for capturing because `pw::Function` can't fit multiple captures.
389 struct {
390 std::array<uint8_t, emboss::EventHeaderView::SizeInBytes() - 1> hci_arr;
391 bool send_called;
392 } send_capture;
393 // Copy valid event into a short_array whose size is one less than a valid
394 // EventHeader.
395 std::copy(h4_packet.hci_span.begin(),
396 h4_packet.hci_span.end(),
397 send_capture.hci_arr.begin());
398 send_capture.send_called = false;
399
400 H4HciPacketSendFn send_to_host_fn([&send_capture](H4HciPacket packet) {
401 send_capture.send_called = true;
402 EXPECT_TRUE(std::equal(packet.hci_span.begin(),
403 packet.hci_span.end(),
404 send_capture.hci_arr.begin(),
405 send_capture.hci_arr.end()));
406 });
407
408 H4HciPacketSendFn send_to_controller_fn(
409 []([[maybe_unused]] H4HciPacket packet) {});
410
411 ProxyHost proxy = ProxyHost(
412 std::move(send_to_host_fn), std::move(send_to_controller_fn), 2);
413
414 proxy.HandleH4HciFromController(h4_packet);
415
416 // Verify callback was called.
417 EXPECT_EQ(send_capture.send_called, true);
418 }
419
TEST(BadPacketTest,TooShortCommandCompleteEventToHost)420 TEST(BadPacketTest, TooShortCommandCompleteEventToHost) {
421 std::array<
422 uint8_t,
423 emboss::ReadLocalVersionInfoCommandCompleteEventWriter::SizeInBytes()>
424 valid_hci_arr;
425 H4HciPacket valid_packet{emboss::H4PacketType::UNKNOWN, valid_hci_arr};
426 emboss::ReadLocalVersionInfoCommandCompleteEventWriter view =
427 CreateAndPopulateToHostEventView<
428 emboss::ReadLocalVersionInfoCommandCompleteEventWriter>(
429 valid_packet, emboss::EventCode::COMMAND_COMPLETE);
430 view.command_complete().command_opcode_enum().Write(
431 emboss::OpCode::READ_LOCAL_VERSION_INFO);
432
433 // Create packet for sending whose span size is one less than a valid command
434 // complete event.
435 H4HciPacket h4_packet{
436 valid_packet.h4_type,
437 valid_packet.hci_span.subspan(
438 0,
439 emboss::ReadLocalVersionInfoCommandCompleteEventWriter::
440 SizeInBytes() -
441 1)};
442
443 // Struct for capturing because `pw::Function` capture can't fit multiple
444 // fields .
445 struct {
446 std::array<
447 uint8_t,
448 emboss::ReadLocalVersionInfoCommandCompleteEventWriter::SizeInBytes() -
449 1>
450 hci_arr;
451 bool send_called;
452 } send_capture;
453 std::copy(h4_packet.hci_span.begin(),
454 h4_packet.hci_span.end(),
455 send_capture.hci_arr.begin());
456 send_capture.send_called = false;
457
458 H4HciPacketSendFn send_to_host_fn([&send_capture](H4HciPacket packet) {
459 send_capture.send_called = true;
460 EXPECT_TRUE(std::equal(packet.hci_span.begin(),
461 packet.hci_span.end(),
462 send_capture.hci_arr.begin(),
463 send_capture.hci_arr.end()));
464 });
465
466 H4HciPacketSendFn send_to_controller_fn(
467 []([[maybe_unused]] H4HciPacket packet) {});
468
469 ProxyHost proxy = ProxyHost(
470 std::move(send_to_host_fn), std::move(send_to_controller_fn), 2);
471
472 proxy.HandleH4HciFromController(h4_packet);
473
474 // Verify callback was called.
475 EXPECT_EQ(send_capture.send_called, true);
476 }
477
478 // ########## ReserveLeAclCredits Tests
479
480 // Proxy Host should reserve requested ACL LE credits from controller's ACL LE
481 // credits when using LEReadBufferSizeV1 command.
TEST(ReserveLeAclCredits,ProxyCreditsReserveCreditsWithLEReadBufferSizeV1)482 TEST(ReserveLeAclCredits, ProxyCreditsReserveCreditsWithLEReadBufferSizeV1) {
483 std::array<
484 uint8_t,
485 emboss::LEReadBufferSizeV1CommandCompleteEventWriter::SizeInBytes()>
486 hci_arr;
487 H4HciPacket h4_packet{emboss::H4PacketType::UNKNOWN, hci_arr};
488 emboss::LEReadBufferSizeV1CommandCompleteEventWriter view =
489 CreateAndPopulateToHostEventView<
490 emboss::LEReadBufferSizeV1CommandCompleteEventWriter>(
491 h4_packet, emboss::EventCode::COMMAND_COMPLETE);
492 view.command_complete().command_opcode_enum().Write(
493 emboss::OpCode::LE_READ_BUFFER_SIZE_V1);
494 view.total_num_le_acl_data_packets().Write(10);
495
496 bool send_called = false;
497 H4HciPacketSendFn send_to_host_fn([&send_called](H4HciPacket h4_packet) {
498 send_called = true;
499 emboss::LEReadBufferSizeV1CommandCompleteEventWriter view =
500 MakeEmboss<emboss::LEReadBufferSizeV1CommandCompleteEventWriter>(
501 h4_packet.hci_span);
502 // Should reserve 2 credits from original total of 10 (so 8 left for host).
503 EXPECT_EQ(view.total_num_le_acl_data_packets().Read(), 8);
504 });
505
506 H4HciPacketSendFn send_to_controller_fn(
507 []([[maybe_unused]] H4HciPacket packet) {});
508
509 ProxyHost proxy = ProxyHost(
510 std::move(send_to_host_fn), std::move(send_to_controller_fn), 2);
511
512 proxy.HandleH4HciFromController(h4_packet);
513
514 EXPECT_EQ(proxy.GetNumFreeLeAclPackets(), 2);
515
516 EXPECT_TRUE(proxy.HasSendAclCapability());
517
518 // Verify to controller callback was called.
519 EXPECT_EQ(send_called, true);
520 }
521
522 // Proxy Host should reserve requested ACL LE credits from controller's ACL LE
523 // credits when using LEReadBufferSizeV2 command.
TEST(ReserveLeAclCredits,ProxyCreditsReserveCreditsWithLEReadBufferSizeV2)524 TEST(ReserveLeAclCredits, ProxyCreditsReserveCreditsWithLEReadBufferSizeV2) {
525 std::array<
526 uint8_t,
527 emboss::LEReadBufferSizeV2CommandCompleteEventWriter::SizeInBytes()>
528 hci_arr;
529 H4HciPacket h4_packet{emboss::H4PacketType::UNKNOWN, hci_arr};
530 emboss::LEReadBufferSizeV2CommandCompleteEventWriter view =
531 CreateAndPopulateToHostEventView<
532 emboss::LEReadBufferSizeV2CommandCompleteEventWriter>(
533 h4_packet, emboss::EventCode::COMMAND_COMPLETE);
534 view.command_complete().command_opcode_enum().Write(
535 emboss::OpCode::LE_READ_BUFFER_SIZE_V2);
536 view.total_num_le_acl_data_packets().Write(10);
537
538 bool send_called = false;
539 H4HciPacketSendFn send_to_host_fn([&send_called](H4HciPacket h4_packet) {
540 send_called = true;
541 emboss::LEReadBufferSizeV2CommandCompleteEventWriter view =
542 MakeEmboss<emboss::LEReadBufferSizeV2CommandCompleteEventWriter>(
543 h4_packet.hci_span);
544 // Should reserve 2 credits from original total of 10 (so 8 left for host).
545 EXPECT_EQ(view.total_num_le_acl_data_packets().Read(), 8);
546 });
547
548 H4HciPacketSendFn send_to_controller_fn(
549 []([[maybe_unused]] H4HciPacket packet) {});
550
551 ProxyHost proxy = ProxyHost(
552 std::move(send_to_host_fn), std::move(send_to_controller_fn), 2);
553
554 proxy.HandleH4HciFromController(h4_packet);
555
556 EXPECT_EQ(proxy.GetNumFreeLeAclPackets(), 2);
557
558 EXPECT_TRUE(proxy.HasSendAclCapability());
559
560 // Verify to controller callback was called.
561 EXPECT_EQ(send_called, true);
562 }
563
564 // If controller provides less than wanted credits, we should reserve that
565 // smaller amount.
TEST(ReserveLeAclCredits,ProxyCreditsCappedByControllerCredits)566 TEST(ReserveLeAclCredits, ProxyCreditsCappedByControllerCredits) {
567 std::array<
568 uint8_t,
569 emboss::LEReadBufferSizeV1CommandCompleteEventWriter::SizeInBytes()>
570 hci_arr;
571 H4HciPacket h4_packet{emboss::H4PacketType::UNKNOWN, hci_arr};
572 emboss::LEReadBufferSizeV1CommandCompleteEventWriter view =
573 CreateAndPopulateToHostEventView<
574 emboss::LEReadBufferSizeV1CommandCompleteEventWriter>(
575 h4_packet, emboss::EventCode::COMMAND_COMPLETE);
576 view.command_complete().command_opcode_enum().Write(
577 emboss::OpCode::LE_READ_BUFFER_SIZE_V1);
578 view.total_num_le_acl_data_packets().Write(5);
579
580 bool send_called = false;
581 H4HciPacketSendFn send_to_host_fn([&send_called](H4HciPacket h4_packet) {
582 send_called = true;
583 // We want 7, but can reserve only 5 from original 5 (so 0 left for host).
584 emboss::LEReadBufferSizeV1CommandCompleteEventWriter view =
585 MakeEmboss<emboss::LEReadBufferSizeV1CommandCompleteEventWriter>(
586 h4_packet.hci_span);
587 EXPECT_EQ(view.total_num_le_acl_data_packets().Read(), 0);
588 });
589
590 H4HciPacketSendFn send_to_controller_fn(
591 []([[maybe_unused]] H4HciPacket packet) {});
592
593 ProxyHost proxy = ProxyHost(
594 std::move(send_to_host_fn), std::move(send_to_controller_fn), 7);
595
596 proxy.HandleH4HciFromController(h4_packet);
597
598 EXPECT_EQ(proxy.GetNumFreeLeAclPackets(), 5);
599
600 // Verify to controller callback was called.
601 EXPECT_EQ(send_called, true);
602 }
603
604 // Proxy Host can reserve zero credits from controller's ACL LE credits.
TEST(ReserveLeAclCredits,ProxyCreditsReserveZeroCredits)605 TEST(ReserveLeAclCredits, ProxyCreditsReserveZeroCredits) {
606 std::array<
607 uint8_t,
608 emboss::LEReadBufferSizeV1CommandCompleteEventWriter::SizeInBytes()>
609 hci_arr;
610 H4HciPacket h4_packet{emboss::H4PacketType::UNKNOWN, hci_arr};
611 emboss::LEReadBufferSizeV1CommandCompleteEventWriter view =
612 CreateAndPopulateToHostEventView<
613 emboss::LEReadBufferSizeV1CommandCompleteEventWriter>(
614 h4_packet, emboss::EventCode::COMMAND_COMPLETE);
615 view.command_complete().command_opcode_enum().Write(
616 emboss::OpCode::LE_READ_BUFFER_SIZE_V1);
617 view.total_num_le_acl_data_packets().Write(10);
618
619 bool send_called = false;
620 H4HciPacketSendFn send_to_host_fn([&send_called](H4HciPacket h4_packet) {
621 send_called = true;
622 emboss::LEReadBufferSizeV1CommandCompleteEventWriter view =
623 MakeEmboss<emboss::LEReadBufferSizeV1CommandCompleteEventWriter>(
624 h4_packet.hci_span);
625 // Should reserve 0 credits from original total of 10 (so 10 left for host).
626 EXPECT_EQ(view.total_num_le_acl_data_packets().Read(), 10);
627 });
628
629 H4HciPacketSendFn send_to_controller_fn(
630 []([[maybe_unused]] H4HciPacket packet) {});
631
632 ProxyHost proxy = ProxyHost(
633 std::move(send_to_host_fn), std::move(send_to_controller_fn), 0);
634
635 proxy.HandleH4HciFromController(h4_packet);
636
637 EXPECT_EQ(proxy.GetNumFreeLeAclPackets(), 0);
638
639 EXPECT_FALSE(proxy.HasSendAclCapability());
640
641 // Verify to controller callback was called.
642 EXPECT_EQ(send_called, true);
643 }
644
645 // If controller has no credits, proxy should reserve none.
TEST(ReserveLeAclPackets,ProxyCreditsZeroWhenHostCreditsZero)646 TEST(ReserveLeAclPackets, ProxyCreditsZeroWhenHostCreditsZero) {
647 std::array<
648 uint8_t,
649 emboss::LEReadBufferSizeV1CommandCompleteEventWriter::SizeInBytes()>
650 hci_arr;
651 H4HciPacket h4_packet{emboss::H4PacketType::UNKNOWN, hci_arr};
652 emboss::LEReadBufferSizeV1CommandCompleteEventWriter view =
653 CreateAndPopulateToHostEventView<
654 emboss::LEReadBufferSizeV1CommandCompleteEventWriter>(
655 h4_packet, emboss::EventCode::COMMAND_COMPLETE);
656 view.command_complete().command_opcode_enum().Write(
657 emboss::OpCode::LE_READ_BUFFER_SIZE_V1);
658 view.total_num_le_acl_data_packets().Write(0);
659
660 bool send_called = false;
661 H4HciPacketSendFn send_to_host_fn([&send_called](H4HciPacket h4_packet) {
662 send_called = true;
663 emboss::LEReadBufferSizeV1CommandCompleteEventWriter view =
664 MakeEmboss<emboss::LEReadBufferSizeV1CommandCompleteEventWriter>(
665 h4_packet.hci_span);
666 // Should reserve 0 credit from original total of 0 (so 0 left for host).
667 EXPECT_EQ(view.total_num_le_acl_data_packets().Read(), 0);
668 });
669
670 H4HciPacketSendFn send_to_controller_fn(
671 []([[maybe_unused]] H4HciPacket packet) {});
672
673 ProxyHost proxy = ProxyHost(
674 std::move(send_to_host_fn), std::move(send_to_controller_fn), 2);
675
676 proxy.HandleH4HciFromController(h4_packet);
677
678 EXPECT_EQ(proxy.GetNumFreeLeAclPackets(), 0);
679
680 EXPECT_TRUE(proxy.HasSendAclCapability());
681
682 // Verify to controller callback was called.
683 EXPECT_EQ(send_called, true);
684 }
685
TEST(ReserveLeAclPackets,ProxyCreditsZeroWhenNotInitialized)686 TEST(ReserveLeAclPackets, ProxyCreditsZeroWhenNotInitialized) {
687 H4HciPacketSendFn send_to_host_fn([]([[maybe_unused]] H4HciPacket packet) {});
688
689 H4HciPacketSendFn send_to_controller_fn(
690 []([[maybe_unused]] H4HciPacket packet) {});
691
692 ProxyHost proxy = ProxyHost(
693 std::move(send_to_host_fn), std::move(send_to_controller_fn), 2);
694
695 EXPECT_EQ(proxy.GetNumFreeLeAclPackets(), 0);
696
697 EXPECT_TRUE(proxy.HasSendAclCapability());
698 }
699
700 } // namespace
701 } // namespace pw::bluetooth::proxy
702