• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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