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/internal/acl_data_channel.h"
16
17 #include <mutex>
18
19 #include "lib/stdcompat/utility.h"
20 #include "pw_bluetooth/emboss_util.h"
21 #include "pw_bluetooth/hci_data.emb.h"
22 #include "pw_bluetooth_proxy/internal/l2cap_channel_manager.h"
23 #include "pw_containers/algorithm.h" // IWYU pragma: keep
24 #include "pw_log/log.h"
25 #include "pw_status/status.h"
26
27 namespace pw::bluetooth::proxy {
28
AclConnection(AclTransportType transport,uint16_t connection_handle,uint16_t num_pending_packets,L2capChannelManager & l2cap_channel_manager)29 AclDataChannel::AclConnection::AclConnection(
30 AclTransportType transport,
31 uint16_t connection_handle,
32 uint16_t num_pending_packets,
33 L2capChannelManager& l2cap_channel_manager)
34 : transport_(transport),
35 connection_handle_(connection_handle),
36 num_pending_packets_(num_pending_packets),
37 leu_signaling_channel_(l2cap_channel_manager, connection_handle),
38 aclu_signaling_channel_(l2cap_channel_manager, connection_handle) {
39 PW_LOG_INFO(
40 "btproxy: AclConnection ctor. transport_: %u, connection_handle_: %#x",
41 cpp23::to_underlying(transport_),
42 connection_handle_);
43 }
44
SendCredit(SendCredit && other)45 AclDataChannel::SendCredit::SendCredit(SendCredit&& other) {
46 *this = std::move(other);
47 }
48
operator =(SendCredit && other)49 AclDataChannel::SendCredit& AclDataChannel::SendCredit::operator=(
50 SendCredit&& other) {
51 if (this != &other) {
52 transport_ = other.transport_;
53 relinquish_fn_ = std::move(other.relinquish_fn_);
54 other.relinquish_fn_ = nullptr;
55 }
56 return *this;
57 }
58
~SendCredit()59 AclDataChannel::SendCredit::~SendCredit() {
60 if (relinquish_fn_) {
61 relinquish_fn_(transport_);
62 }
63 }
64
SendCredit(AclTransportType transport,Function<void (AclTransportType transport)> && relinquish_fn)65 AclDataChannel::SendCredit::SendCredit(
66 AclTransportType transport,
67 Function<void(AclTransportType transport)>&& relinquish_fn)
68 : transport_(transport), relinquish_fn_(std::move(relinquish_fn)) {}
69
MarkUsed()70 void AclDataChannel::SendCredit::MarkUsed() {
71 PW_CHECK(relinquish_fn_);
72 relinquish_fn_ = nullptr;
73 }
74
Reset()75 void AclDataChannel::Reset() {
76 std::lock_guard lock(mutex_);
77 // Reset credits first so no packets queued in signaling channels can be sent.
78 le_credits_.Reset();
79 br_edr_credits_.Reset();
80 acl_connections_.clear();
81 }
82
ToString(Direction direction)83 const char* AclDataChannel::ToString(Direction direction) {
84 switch (direction) {
85 case Direction::kFromController:
86 return "from controller";
87 case Direction::kFromHost:
88 return "from host";
89 }
90 }
91
Reset()92 void AclDataChannel::Credits::Reset() {
93 proxy_max_ = 0;
94 proxy_pending_ = 0;
95 }
96
Reserve(uint16_t controller_max)97 uint16_t AclDataChannel::Credits::Reserve(uint16_t controller_max) {
98 PW_CHECK(!Initialized(),
99 "AclDataChannel is already initialized. Proxy should have been "
100 "reset before this.");
101
102 proxy_max_ = std::min(controller_max, to_reserve_);
103 const uint16_t host_max = controller_max - proxy_max_;
104
105 PW_LOG_INFO(
106 "Bluetooth Proxy reserved %d ACL data credits. Passed %d on to host.",
107 proxy_max_,
108 host_max);
109
110 if (proxy_max_ < to_reserve_) {
111 PW_LOG_ERROR(
112 "Only was able to reserve %d acl data credits rather than the "
113 "configured %d from the controller provided's data credits of %d. ",
114 proxy_max_,
115 to_reserve_,
116 controller_max);
117 }
118
119 return host_max;
120 }
121
MarkPending(uint16_t num_credits)122 Status AclDataChannel::Credits::MarkPending(uint16_t num_credits) {
123 if (num_credits > Available()) {
124 return Status::ResourceExhausted();
125 }
126
127 proxy_pending_ += num_credits;
128
129 return OkStatus();
130 }
131
MarkCompleted(uint16_t num_credits)132 void AclDataChannel::Credits::MarkCompleted(uint16_t num_credits) {
133 if (num_credits > proxy_pending_) {
134 PW_LOG_ERROR("Tried to mark completed more packets than were pending.");
135 proxy_pending_ = 0;
136 } else {
137 proxy_pending_ -= num_credits;
138 }
139 }
140
LookupCredits(AclTransportType transport)141 AclDataChannel::Credits& AclDataChannel::LookupCredits(
142 AclTransportType transport) {
143 switch (transport) {
144 case AclTransportType::kBrEdr:
145 return br_edr_credits_;
146 case AclTransportType::kLe:
147 return le_credits_;
148 default:
149 PW_CHECK(false, "Invalid transport type");
150 }
151 }
152
LookupCredits(AclTransportType transport) const153 const AclDataChannel::Credits& AclDataChannel::LookupCredits(
154 AclTransportType transport) const {
155 switch (transport) {
156 case AclTransportType::kBrEdr:
157 return br_edr_credits_;
158 case AclTransportType::kLe:
159 return le_credits_;
160 default:
161 PW_CHECK(false, "Invalid transport type");
162 }
163 }
164
ProcessReadBufferSizeCommandCompleteEvent(emboss::ReadBufferSizeCommandCompleteEventWriter read_buffer_event)165 void AclDataChannel::ProcessReadBufferSizeCommandCompleteEvent(
166 emboss::ReadBufferSizeCommandCompleteEventWriter read_buffer_event) {
167 {
168 std::lock_guard lock(mutex_);
169 const uint16_t controller_max =
170 read_buffer_event.total_num_acl_data_packets().Read();
171 const uint16_t host_max = br_edr_credits_.Reserve(controller_max);
172 read_buffer_event.total_num_acl_data_packets().Write(host_max);
173 }
174
175 l2cap_channel_manager_.ForceDrainChannelQueues();
176 }
177
178 template <class EventT>
ProcessSpecificLEReadBufferSizeCommandCompleteEvent(EventT read_buffer_event)179 void AclDataChannel::ProcessSpecificLEReadBufferSizeCommandCompleteEvent(
180 EventT read_buffer_event) {
181 {
182 std::lock_guard lock(mutex_);
183 const uint16_t controller_max =
184 read_buffer_event.total_num_le_acl_data_packets().Read();
185 // TODO: https://pwbug.dev/380316252 - Support shared buffers.
186 const uint16_t host_max = le_credits_.Reserve(controller_max);
187 read_buffer_event.total_num_le_acl_data_packets().Write(host_max);
188 }
189
190 const uint16_t le_acl_data_packet_length =
191 read_buffer_event.le_acl_data_packet_length().Read();
192 // TODO: https://pwbug.dev/380316252 - Support shared buffers.
193 if (le_acl_data_packet_length == 0) {
194 PW_LOG_ERROR(
195 "Controller shares data buffers between BR/EDR and LE transport, which "
196 "is not yet supported. So channels on LE transport will not be "
197 "functional.");
198 }
199 l2cap_channel_manager_.set_le_acl_data_packet_length(
200 le_acl_data_packet_length);
201 // Send packets that may have queued before we acquired any LE ACL credits.
202 l2cap_channel_manager_.ForceDrainChannelQueues();
203 }
204
205 template void
206 AclDataChannel::ProcessSpecificLEReadBufferSizeCommandCompleteEvent<
207 emboss::LEReadBufferSizeV1CommandCompleteEventWriter>(
208 emboss::LEReadBufferSizeV1CommandCompleteEventWriter read_buffer_event);
209
210 template void
211 AclDataChannel::ProcessSpecificLEReadBufferSizeCommandCompleteEvent<
212 emboss::LEReadBufferSizeV2CommandCompleteEventWriter>(
213 emboss::LEReadBufferSizeV2CommandCompleteEventWriter read_buffer_event);
214
HandleNumberOfCompletedPacketsEvent(H4PacketWithHci && h4_packet)215 void AclDataChannel::HandleNumberOfCompletedPacketsEvent(
216 H4PacketWithHci&& h4_packet) {
217 Result<emboss::NumberOfCompletedPacketsEventWriter> nocp_event =
218 MakeEmbossWriter<emboss::NumberOfCompletedPacketsEventWriter>(
219 h4_packet.GetHciSpan());
220 if (!nocp_event.ok()) {
221 PW_LOG_ERROR(
222 "Buffer is too small for NUMBER_OF_COMPLETED_PACKETS event. So "
223 "will not process.");
224 hci_transport_.SendToHost(std::move(h4_packet));
225 return;
226 }
227
228 bool should_send_to_host = false;
229 bool did_reclaim_credits = false;
230 {
231 std::lock_guard lock(mutex_);
232 for (uint8_t i = 0; i < nocp_event->num_handles().Read(); ++i) {
233 uint16_t handle = nocp_event->nocp_data()[i].connection_handle().Read();
234 uint16_t num_completed_packets =
235 nocp_event->nocp_data()[i].num_completed_packets().Read();
236
237 if (num_completed_packets == 0) {
238 continue;
239 }
240
241 AclConnection* connection_ptr = FindAclConnection(handle);
242 if (!connection_ptr) {
243 // Credits for connection we are not tracking or closed connection, so
244 // should pass event on to host.
245 should_send_to_host = true;
246 continue;
247 }
248
249 // Reclaim proxy's credits before event is forwarded to host
250 uint16_t num_pending_packets = connection_ptr->num_pending_packets();
251 uint16_t num_reclaimed =
252 std::min(num_completed_packets, num_pending_packets);
253
254 if (num_reclaimed > 0) {
255 did_reclaim_credits = true;
256 }
257
258 LookupCredits(connection_ptr->transport()).MarkCompleted(num_reclaimed);
259
260 connection_ptr->set_num_pending_packets(num_pending_packets -
261 num_reclaimed);
262
263 uint16_t credits_remaining = num_completed_packets - num_reclaimed;
264 nocp_event->nocp_data()[i].num_completed_packets().Write(
265 credits_remaining);
266 if (credits_remaining > 0) {
267 // Connection has credits remaining, so should past event on to host.
268 should_send_to_host = true;
269 }
270 }
271 }
272
273 if (did_reclaim_credits) {
274 l2cap_channel_manager_.ForceDrainChannelQueues();
275 }
276 if (should_send_to_host) {
277 hci_transport_.SendToHost(std::move(h4_packet));
278 }
279 }
280
HandleConnectionCompleteEvent(H4PacketWithHci && h4_packet)281 void AclDataChannel::HandleConnectionCompleteEvent(
282 H4PacketWithHci&& h4_packet) {
283 pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan();
284 Result<emboss::ConnectionCompleteEventView> connection_complete_event =
285 MakeEmbossView<emboss::ConnectionCompleteEventView>(hci_buffer);
286 if (!connection_complete_event.ok()) {
287 hci_transport_.SendToHost(std::move(h4_packet));
288 return;
289 }
290
291 if (connection_complete_event->status().Read() !=
292 emboss::StatusCode::SUCCESS) {
293 hci_transport_.SendToHost(std::move(h4_packet));
294 return;
295 }
296
297 const uint16_t conn_handle =
298 connection_complete_event->connection_handle().Read();
299
300 if (CreateAclConnection(conn_handle, AclTransportType::kBrEdr) ==
301 Status::ResourceExhausted()) {
302 PW_LOG_ERROR(
303 "Could not track connection like requested. Max connections "
304 "reached.");
305 }
306
307 hci_transport_.SendToHost(std::move(h4_packet));
308 }
309
HandleLeConnectionCompleteEvent(uint16_t connection_handle,emboss::StatusCode status)310 void AclDataChannel::HandleLeConnectionCompleteEvent(
311 uint16_t connection_handle, emboss::StatusCode status) {
312 if (status != emboss::StatusCode::SUCCESS) {
313 return;
314 }
315
316 if (CreateAclConnection(connection_handle, AclTransportType::kLe) ==
317 Status::ResourceExhausted()) {
318 PW_LOG_ERROR(
319 "Could not track connection like requested. Max connections "
320 "reached.");
321 }
322 }
323
HandleLeConnectionCompleteEvent(H4PacketWithHci && h4_packet)324 void AclDataChannel::HandleLeConnectionCompleteEvent(
325 H4PacketWithHci&& h4_packet) {
326 pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan();
327 Result<emboss::LEConnectionCompleteSubeventView> event =
328 MakeEmbossView<emboss::LEConnectionCompleteSubeventView>(hci_buffer);
329 if (!event.ok()) {
330 hci_transport_.SendToHost(std::move(h4_packet));
331 return;
332 }
333
334 HandleLeConnectionCompleteEvent(event->connection_handle().Read(),
335 event->status().Read());
336
337 hci_transport_.SendToHost(std::move(h4_packet));
338 }
339
HandleLeEnhancedConnectionCompleteV1Event(H4PacketWithHci && h4_packet)340 void AclDataChannel::HandleLeEnhancedConnectionCompleteV1Event(
341 H4PacketWithHci&& h4_packet) {
342 pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan();
343 Result<emboss::LEEnhancedConnectionCompleteSubeventV1View> event =
344 MakeEmbossView<emboss::LEEnhancedConnectionCompleteSubeventV1View>(
345 hci_buffer);
346 if (!event.ok()) {
347 hci_transport_.SendToHost(std::move(h4_packet));
348 return;
349 }
350
351 HandleLeConnectionCompleteEvent(event->connection_handle().Read(),
352 event->status().Read());
353
354 hci_transport_.SendToHost(std::move(h4_packet));
355 }
356
HandleLeEnhancedConnectionCompleteV2Event(H4PacketWithHci && h4_packet)357 void AclDataChannel::HandleLeEnhancedConnectionCompleteV2Event(
358 H4PacketWithHci&& h4_packet) {
359 pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan();
360 Result<emboss::LEEnhancedConnectionCompleteSubeventV2View> event =
361 MakeEmbossView<emboss::LEEnhancedConnectionCompleteSubeventV2View>(
362 hci_buffer);
363 if (!event.ok()) {
364 hci_transport_.SendToHost(std::move(h4_packet));
365 return;
366 }
367
368 HandleLeConnectionCompleteEvent(event->connection_handle().Read(),
369 event->status().Read());
370
371 hci_transport_.SendToHost(std::move(h4_packet));
372 }
373
ProcessDisconnectionCompleteEvent(pw::span<uint8_t> hci_span)374 void AclDataChannel::ProcessDisconnectionCompleteEvent(
375 pw::span<uint8_t> hci_span) {
376 Result<emboss::DisconnectionCompleteEventView> dc_event =
377 MakeEmbossView<emboss::DisconnectionCompleteEventView>(hci_span);
378 if (!dc_event.ok()) {
379 PW_LOG_ERROR(
380 "Buffer is too small for DISCONNECTION_COMPLETE event. So will not "
381 "process.");
382 return;
383 }
384
385 {
386 std::lock_guard lock(mutex_);
387 uint16_t conn_handle = dc_event->connection_handle().Read();
388
389 AclConnection* connection_ptr = FindAclConnection(conn_handle);
390
391 if (!connection_ptr) {
392 PW_LOG_WARN(
393 "btproxy: Viewed disconnect (reason: %#.2hhx) for connection %#x, "
394 "but was unable to find an existing open AclConnection.",
395 cpp23::to_underlying(dc_event->reason().Read()),
396 conn_handle);
397 return;
398 }
399
400 emboss::StatusCode status = dc_event->status().Read();
401 if (status == emboss::StatusCode::SUCCESS) {
402 PW_LOG_INFO(
403 "Proxy viewed disconnect (reason: %#.2hhx) for connection %#x.",
404 cpp23::to_underlying(dc_event->reason().Read()),
405 conn_handle);
406 if (connection_ptr->num_pending_packets() > 0) {
407 PW_LOG_WARN(
408 "Connection %#x is disconnecting with packets in flight. Releasing "
409 "associated credits.",
410 conn_handle);
411 LookupCredits(connection_ptr->transport())
412 .MarkCompleted(connection_ptr->num_pending_packets());
413 }
414
415 l2cap_channel_manager_.HandleAclDisconnectionComplete(conn_handle);
416 acl_connections_.erase(connection_ptr);
417 } else { // Failed disconnect status
418 if (connection_ptr->num_pending_packets() > 0) {
419 PW_LOG_WARN(
420 "Proxy viewed failed disconnect (status: %#.2hhx) for connection "
421 "%#x with packets in flight. Not releasing associated credits.",
422 cpp23::to_underlying(status),
423 conn_handle);
424 }
425 }
426 }
427 }
428
HasSendAclCapability(AclTransportType transport) const429 bool AclDataChannel::HasSendAclCapability(AclTransportType transport) const {
430 std::lock_guard lock(mutex_);
431 return LookupCredits(transport).HasSendCapability();
432 }
433
GetNumFreeAclPackets(AclTransportType transport) const434 uint16_t AclDataChannel::GetNumFreeAclPackets(
435 AclTransportType transport) const {
436 std::lock_guard lock(mutex_);
437 return LookupCredits(transport).Remaining();
438 }
439
ReserveSendCredit(AclTransportType transport)440 std::optional<AclDataChannel::SendCredit> AclDataChannel::ReserveSendCredit(
441 AclTransportType transport) {
442 std::lock_guard lock(mutex_);
443 if (const auto status = LookupCredits(transport).MarkPending(1);
444 !status.ok()) {
445 return std::nullopt;
446 }
447 return SendCredit(transport, [this](AclTransportType t) {
448 std::lock_guard fn_lock(mutex_);
449 LookupCredits(t).MarkCompleted(1);
450 });
451 }
452
SendAcl(H4PacketWithH4 && h4_packet,SendCredit && credit)453 pw::Status AclDataChannel::SendAcl(H4PacketWithH4&& h4_packet,
454 SendCredit&& credit) {
455 std::lock_guard lock(mutex_);
456 Result<emboss::AclDataFrameHeaderView> acl_view =
457 MakeEmbossView<emboss::AclDataFrameHeaderView>(h4_packet.GetHciSpan());
458 if (!acl_view.ok()) {
459 PW_LOG_ERROR("An invalid ACL packet was provided. So will not send.");
460 return pw::Status::InvalidArgument();
461 }
462 uint16_t handle = acl_view->handle().Read();
463
464 AclConnection* connection_ptr = FindAclConnection(handle);
465 if (!connection_ptr) {
466 PW_LOG_ERROR("Tried to send ACL packet on unregistered connection.");
467 return pw::Status::NotFound();
468 }
469
470 if (connection_ptr->transport() != credit.transport_) {
471 PW_LOG_WARN("Provided credit for wrong transport. So will not send.");
472 return pw::Status::InvalidArgument();
473 }
474 credit.MarkUsed();
475
476 connection_ptr->set_num_pending_packets(
477 connection_ptr->num_pending_packets() + 1);
478
479 hci_transport_.SendToController(std::move(h4_packet));
480 return pw::OkStatus();
481 }
482
CreateAclConnection(uint16_t connection_handle,AclTransportType transport)483 Status AclDataChannel::CreateAclConnection(uint16_t connection_handle,
484 AclTransportType transport) {
485 std::lock_guard lock(mutex_);
486 AclConnection* connection_it = FindAclConnection(connection_handle);
487 if (connection_it) {
488 PW_LOG_WARN(
489 "btproxy: Attempt to create new AclConnection when existing one is "
490 "already open. connection_handle: %#x",
491 connection_handle);
492 return Status::AlreadyExists();
493 }
494 if (acl_connections_.full()) {
495 PW_LOG_ERROR(
496 "btproxy: Attempt to create new AclConnection when acl_connections_ is"
497 "already full. connection_handle: %#x",
498 connection_handle);
499 return Status::ResourceExhausted();
500 }
501 acl_connections_.emplace_back(transport,
502 /*connection_handle=*/connection_handle,
503 /*num_pending_packets=*/0,
504 l2cap_channel_manager_);
505 return OkStatus();
506 }
507
FindSignalingChannel(uint16_t connection_handle,uint16_t local_cid)508 L2capSignalingChannel* AclDataChannel::FindSignalingChannel(
509 uint16_t connection_handle, uint16_t local_cid) {
510 std::lock_guard lock(mutex_);
511
512 AclConnection* connection_ptr = FindAclConnection(connection_handle);
513 if (!connection_ptr) {
514 return nullptr;
515 }
516
517 if (local_cid == connection_ptr->signaling_channel()->local_cid()) {
518 return connection_ptr->signaling_channel();
519 }
520 return nullptr;
521 }
522
FindAclConnection(uint16_t connection_handle)523 AclDataChannel::AclConnection* AclDataChannel::FindAclConnection(
524 uint16_t connection_handle) {
525 AclConnection* connection_it = containers::FindIf(
526 acl_connections_, [connection_handle](const AclConnection& connection) {
527 return connection.connection_handle() == connection_handle;
528 });
529 return connection_it == acl_connections_.end() ? nullptr : connection_it;
530 }
531
HandleAclData(AclDataChannel::Direction direction,emboss::AclDataFrameWriter & acl)532 bool AclDataChannel::HandleAclData(AclDataChannel::Direction direction,
533 emboss::AclDataFrameWriter& acl) {
534 // This function returns whether or not the frame was handled here.
535 // * Return true if the frame was handled by the proxy and should _not_ be
536 // passed on to the other side (Host/Controller).
537 // * Return false if the frame was _not_ handled by the proxy and should be
538 // passed on to the other side (Host/Controller).
539 //
540 // Special care needs to be taken when handling fragments. We don't want the
541 // proxy to consume an initial fragment, and then decide to pass a subsequent
542 // fragment because we didn't like it. That would cause the receiver to see
543 // an unexpected CONTINUING_FRAGMENT.
544 //
545 // This ACL frame could contain
546 // * A complete L2CAP PDU...
547 // * for an unrecognized channel -> Pass
548 // * for a recognized channel -> Handle and Consume
549 //
550 // * An initial fragment (w/ complete L2CAP header)...
551 // * while already recombining -> Stop recombination and Pass(?)
552 // * for an unrecognized channel -> Pass
553 // * for a recognized channel -> Start recombination and Consume
554 //
555 // * A subsequent fragment (CONTINUING_FRAGMENT)...
556 // * while recombining -> Recombine fragment and Consume
557 // (we know this must be for an L2CAP channel we care about)
558 // * while not recombining -> Pass
559 //
560 // TODO: https://pwbug.dev/392666078 - Consider refactoring to look like
561 // L2capCoc::ProcessPduFromControllerMultibuf() if we are okay with
562 // allocating and copying for every PDU.
563 static constexpr bool kHandled = true;
564 static constexpr bool kUnhandled = false;
565
566 const uint16_t handle = acl.header().handle().Read();
567
568 auto find_l2cap_channel = [this, direction, handle](uint16_t channel_id) {
569 switch (direction) {
570 case Direction::kFromController:
571 return l2cap_channel_manager_.FindChannelByLocalCid(handle, channel_id);
572 case Direction::kFromHost:
573 return l2cap_channel_manager_.FindChannelByRemoteCid(handle,
574 channel_id);
575 }
576 };
577
578 bool is_fragment = false;
579 pw::span<uint8_t> l2cap_pdu;
580 multibuf::MultiBuf recombined_mbuf;
581 {
582 std::lock_guard lock(mutex_);
583 AclConnection* connection = FindAclConnection(handle);
584 if (!connection) {
585 return kUnhandled;
586 }
587
588 // TODO: https://pwbug.dev/392665312 - make this <const uint8_t>
589 const pw::span<uint8_t> acl_payload{
590 acl.payload().BackingStorage().data(),
591 acl.payload().BackingStorage().SizeInBytes()};
592
593 // Is this a fragment?
594 const emboss::AclDataPacketBoundaryFlag boundary_flag =
595 acl.header().packet_boundary_flag().Read();
596 switch (boundary_flag) {
597 // A subsequent fragment of a fragmented PDU.
598 case emboss::AclDataPacketBoundaryFlag::CONTINUING_FRAGMENT:
599 // If recombination is not active, these are probably fragments for a
600 // PDU that we previously chose not to recombine. Simply ignore them.
601 //
602 // TODO: https://pwbug.dev/393417198 - This could also be an erroneous
603 // continuation of an already-recombined PDU, which would be better to
604 // drop.
605 if (!connection->RecombinationActive(direction)) {
606 return kUnhandled;
607 }
608
609 is_fragment = true;
610 break;
611
612 // Non-fragment or the first fragment of a fragmented PDU.
613 case emboss::AclDataPacketBoundaryFlag::FIRST_NON_FLUSHABLE:
614 case emboss::AclDataPacketBoundaryFlag::FIRST_FLUSHABLE: {
615 // Ensure recombination is not already in progress
616 if (connection->RecombinationActive(direction)) {
617 PW_LOG_WARN(
618 "Received non-continuation packet %s on channel %#x while "
619 "recombination is active! Dropping previous partially-recombined "
620 "PDU and handling this first packet normally.",
621 ToString(direction),
622 handle);
623 connection->EndRecombination(direction);
624 }
625
626 // Currently, we require the full L2CAP header: We need the pdu_length
627 // field so we know how much data to recombine, and we need the
628 // channel_id field so we know whether or not this is a recognized L2CAP
629 // channel and therefore whether or not we should recombine it.
630 // TODO: https://pwbug.dev/392652874 - Handle fragmented L2CAP header.
631 emboss::BasicL2capHeaderView l2cap_header =
632 emboss::MakeBasicL2capHeaderView(acl_payload.data(),
633 acl_payload.size());
634 if (!l2cap_header.Ok()) {
635 PW_LOG_ERROR(
636 "ACL packet %s on channel %#x does not include full L2CAP "
637 "header. "
638 "Passing on.",
639 ToString(direction),
640 handle);
641 return kUnhandled;
642 }
643
644 const uint16_t l2cap_channel_id = l2cap_header.channel_id().Read();
645
646 // Is this a channel we care about?
647 std::optional<L2capChannelManager::LockedL2capChannel> channel =
648 find_l2cap_channel(l2cap_channel_id);
649 if (!channel.has_value()) {
650 return kUnhandled;
651 }
652
653 const uint16_t acl_payload_size = acl.data_total_length().Read();
654
655 const uint16_t l2cap_frame_length =
656 emboss::BasicL2capHeader::IntrinsicSizeInBytes() +
657 l2cap_header.pdu_length().Read();
658
659 if (l2cap_frame_length < acl_payload_size) {
660 PW_LOG_ERROR(
661 "ACL packet %s on channel %#x has payload (%u bytes) larger than "
662 "specified L2CAP PDU size (%u bytes). Dropping.",
663 ToString(direction),
664 handle,
665 acl_payload_size,
666 l2cap_frame_length);
667 return kHandled;
668 }
669
670 // Is this the first fragment of a fragmented PDU?
671 // The first fragment is recognized when the L2CAP frame length exceeds
672 // the ACL frame data_total_length.
673 if (l2cap_frame_length > acl_payload_size) {
674 is_fragment = true;
675
676 // Start recombination
677 // Note: this allocator pointer is only valid as long as channel is
678 // registered with L2capChannelManager. So we hold the
679 // LockedL2capChannel channel to ensure it stays valid for duration of
680 // its use.
681 auto* multibuf_allocator = channel->channel().rx_multibuf_allocator();
682 if (!multibuf_allocator) {
683 PW_LOG_ERROR(
684 "Cannot start recombination for L2capChannel %#x: "
685 "no channel rx allocator. Passing on.",
686 l2cap_channel_id);
687 return kUnhandled;
688 }
689 auto status = connection->StartRecombination(
690 direction, *multibuf_allocator, l2cap_frame_length);
691 if (!status.ok()) {
692 PW_LOG_ERROR(
693 "Cannot start recombination for L2capChannel %#x: "
694 "%s. Passing on.",
695 l2cap_channel_id,
696 status.str());
697 return kUnhandled;
698 }
699 }
700 break;
701 }
702
703 default:
704 PW_LOG_ERROR(
705 "Packet %s on channel %#x: Unexpected ACL boundary flag: %u",
706 ToString(direction),
707 handle,
708 cpp23::to_underlying(boundary_flag));
709 return kUnhandled;
710 }
711
712 if (!is_fragment) {
713 // Not a fragment; the complete payload is the payload of this ACL frame.
714 l2cap_pdu = acl_payload;
715 } else {
716 // Recombine this fragment
717 Result<multibuf::MultiBuf> recomb_result =
718 connection->RecombineFragment(direction, acl_payload);
719 if (!recomb_result.ok()) {
720 // Given that RecombinationActive is checked above, the only way this
721 // should fail is if the fragment is larger than expected, which can
722 // only happen on a continuing fragment, because the first fragment
723 // starts recombination above.
724 PW_DCHECK(boundary_flag ==
725 emboss::AclDataPacketBoundaryFlag::CONTINUING_FRAGMENT);
726
727 PW_LOG_ERROR(
728 "Received continuation packet %s on channel %#x over specified PDU "
729 "length. Dropping entire PDU.",
730 ToString(direction),
731 handle);
732 connection->EndRecombination(direction);
733 return kHandled; // We own the channel; drop.
734 }
735
736 if (recomb_result->empty()) {
737 // An empty MultiBuf means we need to await the remaining fragments.
738 return kHandled;
739 }
740
741 // Recombination complete!
742 // RecombineFragment() internally calls EndRecombination() when complete.
743 recombined_mbuf = std::move(*recomb_result);
744
745 // ContiguousSpan() cannot fail because MultiBufWriter::Create() uses
746 // AllocateContiguous().
747 std::optional<ByteSpan> mbuf_span = recombined_mbuf.ContiguousSpan();
748 PW_CHECK(mbuf_span);
749 l2cap_pdu = pw::span(reinterpret_cast<uint8_t*>(mbuf_span->data()),
750 mbuf_span->size());
751 }
752 } // std::lock_guard lock(mutex_)
753
754 // Remember: Past this point, we operate on l2cap_pdu, but our return value
755 // controls the disposition of (what might be) the last fragment!
756
757 // We should have a valid L2CAP frame in `l2cap_pdu`.
758 // This cannot happen if the packet is a fragment, because recombination
759 // only completes when the entire L2CAP PDU has been recombined.
760 // And it cannot happen if the packet is _not_ a fragment due to the check
761 // above.
762 Result<emboss::BasicL2capHeaderView> l2cap_header =
763 MakeEmbossView<emboss::BasicL2capHeaderView>(l2cap_pdu);
764 PW_CHECK(l2cap_header.ok());
765
766 std::optional<L2capChannelManager::LockedL2capChannel> channel =
767 find_l2cap_channel(l2cap_header->channel_id().Read());
768 if (!channel.has_value()) {
769 // This cannot happen if the packet is a fragment, because recombination
770 // only starts for a recognized L2capChannel. So it is safe to return
771 // kUnhandled in this case and pass the frame on.
772 PW_DCHECK(!is_fragment);
773 // EndRecombination not needed here.
774 return kUnhandled;
775 }
776
777 // Pass the L2CAP PDU on to the L2capChannel
778 const bool result =
779 (direction == Direction::kFromController)
780 ? channel->channel().HandlePduFromController(l2cap_pdu)
781 : channel->channel().HandlePduFromHost(l2cap_pdu);
782 if (is_fragment) {
783 if (!result) {
784 // We can't return kUnhandled, as that would pass only this final
785 // fragment to the other side, and all preceding fragments would be
786 // missing.
787 // TODO: https://pwbug.dev/392663102 - Handle rejecting a recombined
788 // L2CAP PDU.
789 PW_LOG_ERROR(
790 "L2capChannel indicates recombined PDU is unhandled, which is "
791 "unsupported. Dropping entire recombined PDU!");
792 return kHandled;
793 }
794 }
795
796 // Unlock channel so we can drain any channels with data queued.
797 // It's possible for a channel handling rx traffic to have queued tx traffic.
798 // So release the channel lock, then call DrainChannelQueuesIfNewTx to handle
799 // that possibility.
800 channel.reset();
801 l2cap_channel_manager_.DrainChannelQueuesIfNewTx();
802 l2cap_channel_manager_.DeliverPendingEvents();
803
804 return result;
805 }
806
StartRecombination(Direction direction,multibuf::MultiBufAllocator & multibuf_allocator,size_t size)807 pw::Status AclDataChannel::AclConnection::StartRecombination(
808 Direction direction,
809 multibuf::MultiBufAllocator& multibuf_allocator,
810 size_t size) {
811 if (RecombinationActive(direction)) {
812 return Status::FailedPrecondition();
813 }
814
815 Result<MultiBufWriter> recomb =
816 MultiBufWriter::Create(multibuf_allocator, size);
817 if (!recomb.ok()) {
818 return recomb.status();
819 }
820 recombination_buffers_[cpp23::to_underlying(direction)].emplace(
821 std::move(*recomb));
822 return pw::OkStatus();
823 }
824
RecombineFragment(Direction direction,pw::span<const uint8_t> data)825 pw::Result<multibuf::MultiBuf> AclDataChannel::AclConnection::RecombineFragment(
826 Direction direction, pw::span<const uint8_t> data) {
827 MultiBufWriter* recomb = get_recombination_buffer(direction);
828 if (!recomb) {
829 return Status::FailedPrecondition();
830 }
831
832 if (Status status = recomb->Write(data); !status.ok()) {
833 return status;
834 }
835
836 if (!recomb->IsComplete()) {
837 // Return an empty multibuf to indicate recombination is not complete.
838 return multibuf::MultiBuf();
839 }
840
841 // Consume and return the resulting multibuf and end recombination.
842 auto mbuf = std::move(recomb->TakeMultiBuf());
843 EndRecombination(direction);
844 return mbuf;
845 }
846
EndRecombination(Direction direction)847 void AclDataChannel::AclConnection::EndRecombination(Direction direction) {
848 recombination_buffers_[cpp23::to_underlying(direction)] = std::nullopt;
849 }
850
851 } // namespace pw::bluetooth::proxy
852