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
Reset()29 void AclDataChannel::Reset() {
30 std::lock_guard lock(mutex_);
31 le_credits_.Reset();
32 br_edr_credits_.Reset();
33 active_acl_connections_.clear();
34 }
35
Reset()36 void AclDataChannel::Credits::Reset() {
37 proxy_max_ = 0;
38 proxy_pending_ = 0;
39 }
40
Reserve(uint16_t controller_max)41 uint16_t AclDataChannel::Credits::Reserve(uint16_t controller_max) {
42 PW_CHECK(proxy_max_ == 0,
43 "AclDataChannel is already initialized, but encountered another "
44 "ReadBufferSizeCommandCompleteEvent.");
45
46 proxy_max_ = std::min(controller_max, to_reserve_);
47 const uint16_t host_max = controller_max - proxy_max_;
48
49 PW_LOG_INFO(
50 "Bluetooth Proxy reserved %d ACL data credits. Passed %d on to host.",
51 proxy_max_,
52 host_max);
53
54 if (proxy_max_ < to_reserve_) {
55 PW_LOG_ERROR(
56 "Only was able to reserve %d acl data credits rather than the "
57 "configured %d from the controller provided's data credits of %d. ",
58 proxy_max_,
59 to_reserve_,
60 controller_max);
61 }
62
63 return host_max;
64 }
65
MarkPending(uint16_t num_credits)66 Status AclDataChannel::Credits::MarkPending(uint16_t num_credits) {
67 if (num_credits > Available()) {
68 return Status::ResourceExhausted();
69 }
70
71 proxy_pending_ += num_credits;
72
73 return OkStatus();
74 }
75
MarkCompleted(uint16_t num_credits)76 void AclDataChannel::Credits::MarkCompleted(uint16_t num_credits) {
77 if (num_credits > proxy_pending_) {
78 PW_LOG_ERROR("Tried to mark completed more packets than were pending.");
79 proxy_pending_ = 0;
80 } else {
81 proxy_pending_ -= num_credits;
82 }
83 }
84
LookupCredits(AclTransportType transport)85 AclDataChannel::Credits& AclDataChannel::LookupCredits(
86 AclTransportType transport) {
87 switch (transport) {
88 case AclTransportType::kBrEdr:
89 return br_edr_credits_;
90 case AclTransportType::kLe:
91 return le_credits_;
92 default:
93 PW_CHECK(false, "Invalid transport type");
94 }
95 }
96
LookupCredits(AclTransportType transport) const97 const AclDataChannel::Credits& AclDataChannel::LookupCredits(
98 AclTransportType transport) const {
99 switch (transport) {
100 case AclTransportType::kBrEdr:
101 return br_edr_credits_;
102 case AclTransportType::kLe:
103 return le_credits_;
104 default:
105 PW_CHECK(false, "Invalid transport type");
106 }
107 }
108
ProcessReadBufferSizeCommandCompleteEvent(emboss::ReadBufferSizeCommandCompleteEventWriter read_buffer_event)109 void AclDataChannel::ProcessReadBufferSizeCommandCompleteEvent(
110 emboss::ReadBufferSizeCommandCompleteEventWriter read_buffer_event) {
111 {
112 std::lock_guard lock(mutex_);
113 const uint16_t controller_max =
114 read_buffer_event.total_num_acl_data_packets().Read();
115 const uint16_t host_max = br_edr_credits_.Reserve(controller_max);
116 read_buffer_event.total_num_acl_data_packets().Write(host_max);
117 }
118
119 l2cap_channel_manager_.DrainWriteChannelQueues();
120 }
121
122 template <class EventT>
ProcessSpecificLEReadBufferSizeCommandCompleteEvent(EventT read_buffer_event)123 void AclDataChannel::ProcessSpecificLEReadBufferSizeCommandCompleteEvent(
124 EventT read_buffer_event) {
125 {
126 std::lock_guard lock(mutex_);
127 const uint16_t controller_max =
128 read_buffer_event.total_num_le_acl_data_packets().Read();
129 // TODO: https://pwbug.dev/380316252 - Support shared buffers.
130 const uint16_t host_max = le_credits_.Reserve(controller_max);
131 read_buffer_event.total_num_le_acl_data_packets().Write(host_max);
132 }
133
134 // Send packets that may have queued before we acquired any LE ACL credits.
135 l2cap_channel_manager_.DrainWriteChannelQueues();
136 }
137
138 template void
139 AclDataChannel::ProcessSpecificLEReadBufferSizeCommandCompleteEvent<
140 emboss::LEReadBufferSizeV1CommandCompleteEventWriter>(
141 emboss::LEReadBufferSizeV1CommandCompleteEventWriter read_buffer_event);
142
143 template void
144 AclDataChannel::ProcessSpecificLEReadBufferSizeCommandCompleteEvent<
145 emboss::LEReadBufferSizeV2CommandCompleteEventWriter>(
146 emboss::LEReadBufferSizeV2CommandCompleteEventWriter read_buffer_event);
147
HandleNumberOfCompletedPacketsEvent(H4PacketWithHci && h4_packet)148 void AclDataChannel::HandleNumberOfCompletedPacketsEvent(
149 H4PacketWithHci&& h4_packet) {
150 Result<emboss::NumberOfCompletedPacketsEventWriter> nocp_event =
151 MakeEmbossWriter<emboss::NumberOfCompletedPacketsEventWriter>(
152 h4_packet.GetHciSpan());
153 if (!nocp_event.ok()) {
154 PW_LOG_ERROR(
155 "Buffer is too small for NUMBER_OF_COMPLETED_PACKETS event. So "
156 "will not process.");
157 hci_transport_.SendToHost(std::move(h4_packet));
158 return;
159 }
160
161 bool should_send_to_host = false;
162 bool did_reclaim_credits = false;
163 {
164 std::lock_guard lock(mutex_);
165 for (uint8_t i = 0; i < nocp_event->num_handles().Read(); ++i) {
166 uint16_t handle = nocp_event->nocp_data()[i].connection_handle().Read();
167 uint16_t num_completed_packets =
168 nocp_event->nocp_data()[i].num_completed_packets().Read();
169
170 if (num_completed_packets == 0) {
171 continue;
172 }
173
174 AclConnection* connection_ptr = FindAclConnection(handle);
175 if (!connection_ptr) {
176 // Credits for connection we are not tracking, so should pass event on
177 // to host.
178 should_send_to_host = true;
179 continue;
180 }
181
182 // Reclaim proxy's credits before event is forwarded to host
183 uint16_t num_pending_packets = connection_ptr->num_pending_packets();
184 uint16_t num_reclaimed =
185 std::min(num_completed_packets, num_pending_packets);
186
187 if (num_reclaimed > 0) {
188 did_reclaim_credits = true;
189 }
190
191 LookupCredits(connection_ptr->transport()).MarkCompleted(num_reclaimed);
192
193 connection_ptr->set_num_pending_packets(num_pending_packets -
194 num_reclaimed);
195
196 uint16_t credits_remaining = num_completed_packets - num_reclaimed;
197 nocp_event->nocp_data()[i].num_completed_packets().Write(
198 credits_remaining);
199 if (credits_remaining > 0) {
200 // Connection has credits remaining, so should past event on to host.
201 should_send_to_host = true;
202 }
203 }
204 }
205
206 if (did_reclaim_credits) {
207 l2cap_channel_manager_.DrainWriteChannelQueues();
208 }
209 if (should_send_to_host) {
210 hci_transport_.SendToHost(std::move(h4_packet));
211 }
212 }
213
HandleConnectionCompleteEvent(H4PacketWithHci && h4_packet)214 void AclDataChannel::HandleConnectionCompleteEvent(
215 H4PacketWithHci&& h4_packet) {
216 pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan();
217 Result<emboss::ConnectionCompleteEventView> connection_complete_event =
218 MakeEmbossView<emboss::ConnectionCompleteEventView>(hci_buffer);
219 if (!connection_complete_event.ok()) {
220 hci_transport_.SendToHost(std::move(h4_packet));
221 return;
222 }
223
224 if (connection_complete_event->status().Read() !=
225 emboss::StatusCode::SUCCESS) {
226 hci_transport_.SendToHost(std::move(h4_packet));
227 return;
228 }
229
230 const uint16_t conn_handle =
231 connection_complete_event->connection_handle().Read();
232
233 if (CreateAclConnection(conn_handle, AclTransportType::kBrEdr) ==
234 Status::ResourceExhausted()) {
235 PW_LOG_ERROR(
236 "Could not track connection like requested. Max connections "
237 "reached.");
238 }
239
240 hci_transport_.SendToHost(std::move(h4_packet));
241 }
242
HandleLeConnectionCompleteEvent(uint16_t connection_handle,emboss::StatusCode status)243 void AclDataChannel::HandleLeConnectionCompleteEvent(
244 uint16_t connection_handle, emboss::StatusCode status) {
245 if (status != emboss::StatusCode::SUCCESS) {
246 return;
247 }
248
249 if (CreateAclConnection(connection_handle, AclTransportType::kLe) ==
250 Status::ResourceExhausted()) {
251 PW_LOG_ERROR(
252 "Could not track connection like requested. Max connections "
253 "reached.");
254 }
255 }
256
HandleLeConnectionCompleteEvent(H4PacketWithHci && h4_packet)257 void AclDataChannel::HandleLeConnectionCompleteEvent(
258 H4PacketWithHci&& h4_packet) {
259 pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan();
260 Result<emboss::LEConnectionCompleteSubeventView> event =
261 MakeEmbossView<emboss::LEConnectionCompleteSubeventView>(hci_buffer);
262 if (!event.ok()) {
263 hci_transport_.SendToHost(std::move(h4_packet));
264 return;
265 }
266
267 HandleLeConnectionCompleteEvent(event->connection_handle().Read(),
268 event->status().Read());
269
270 hci_transport_.SendToHost(std::move(h4_packet));
271 }
272
HandleLeEnhancedConnectionCompleteV1Event(H4PacketWithHci && h4_packet)273 void AclDataChannel::HandleLeEnhancedConnectionCompleteV1Event(
274 H4PacketWithHci&& h4_packet) {
275 pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan();
276 Result<emboss::LEEnhancedConnectionCompleteSubeventV1View> event =
277 MakeEmbossView<emboss::LEEnhancedConnectionCompleteSubeventV1View>(
278 hci_buffer);
279 if (!event.ok()) {
280 hci_transport_.SendToHost(std::move(h4_packet));
281 return;
282 }
283
284 HandleLeConnectionCompleteEvent(event->connection_handle().Read(),
285 event->status().Read());
286
287 hci_transport_.SendToHost(std::move(h4_packet));
288 }
289
HandleLeEnhancedConnectionCompleteV2Event(H4PacketWithHci && h4_packet)290 void AclDataChannel::HandleLeEnhancedConnectionCompleteV2Event(
291 H4PacketWithHci&& h4_packet) {
292 pw::span<uint8_t> hci_buffer = h4_packet.GetHciSpan();
293 Result<emboss::LEEnhancedConnectionCompleteSubeventV2View> event =
294 MakeEmbossView<emboss::LEEnhancedConnectionCompleteSubeventV2View>(
295 hci_buffer);
296 if (!event.ok()) {
297 hci_transport_.SendToHost(std::move(h4_packet));
298 return;
299 }
300
301 HandleLeConnectionCompleteEvent(event->connection_handle().Read(),
302 event->status().Read());
303
304 hci_transport_.SendToHost(std::move(h4_packet));
305 }
306
HandleDisconnectionCompleteEvent(H4PacketWithHci && h4_packet)307 void AclDataChannel::HandleDisconnectionCompleteEvent(
308 H4PacketWithHci&& h4_packet) {
309 Result<emboss::DisconnectionCompleteEventView> dc_event =
310 MakeEmbossView<emboss::DisconnectionCompleteEventView>(
311 h4_packet.GetHciSpan());
312 if (!dc_event.ok()) {
313 PW_LOG_ERROR(
314 "Buffer is too small for DISCONNECTION_COMPLETE event. So will not "
315 "process.");
316 hci_transport_.SendToHost(std::move(h4_packet));
317 return;
318 }
319
320 {
321 std::lock_guard lock(mutex_);
322 uint16_t conn_handle = dc_event->connection_handle().Read();
323
324 AclConnection* connection_ptr = FindAclConnection(conn_handle);
325 if (!connection_ptr) {
326 hci_transport_.SendToHost(std::move(h4_packet));
327 return;
328 }
329
330 emboss::StatusCode status = dc_event->status().Read();
331 if (status == emboss::StatusCode::SUCCESS) {
332 if (connection_ptr->num_pending_packets() > 0) {
333 PW_LOG_WARN(
334 "Proxy viewed disconnect (reason: %#.2hhx) for connection %#.4hx "
335 "with packets in flight. Releasing associated credits",
336 cpp23::to_underlying(dc_event->reason().Read()),
337 conn_handle);
338
339 LookupCredits(connection_ptr->transport())
340 .MarkCompleted(connection_ptr->num_pending_packets());
341 }
342
343 active_acl_connections_.erase(connection_ptr);
344 } else {
345 if (connection_ptr->num_pending_packets() > 0) {
346 PW_LOG_WARN(
347 "Proxy viewed failed disconnect (status: %#.2hhx) for connection "
348 "%#.4hx with packets in flight. Not releasing associated credits.",
349 cpp23::to_underlying(status),
350 conn_handle);
351 }
352 }
353 }
354 hci_transport_.SendToHost(std::move(h4_packet));
355 }
356
HasSendAclCapability(AclTransportType transport) const357 bool AclDataChannel::HasSendAclCapability(AclTransportType transport) const {
358 std::lock_guard lock(mutex_);
359 return LookupCredits(transport).HasSendCapability();
360 }
361
GetNumFreeAclPackets(AclTransportType transport) const362 uint16_t AclDataChannel::GetNumFreeAclPackets(
363 AclTransportType transport) const {
364 std::lock_guard lock(mutex_);
365 return LookupCredits(transport).Remaining();
366 }
367
SendAcl(H4PacketWithH4 && h4_packet)368 pw::Status AclDataChannel::SendAcl(H4PacketWithH4&& h4_packet) {
369 std::lock_guard lock(mutex_);
370 Result<emboss::AclDataFrameHeaderView> acl_view =
371 MakeEmbossView<emboss::AclDataFrameHeaderView>(h4_packet.GetHciSpan());
372 if (!acl_view.ok()) {
373 PW_LOG_ERROR("An invalid ACL packet was provided. So will not send.");
374 return pw::Status::InvalidArgument();
375 }
376 uint16_t handle = acl_view->handle().Read();
377
378 AclConnection* connection_ptr = FindAclConnection(handle);
379 if (!connection_ptr) {
380 PW_LOG_ERROR("Tried to send ACL packet on unregistered connection.");
381 return pw::Status::NotFound();
382 }
383
384 if (const auto status =
385 LookupCredits(connection_ptr->transport()).MarkPending(1);
386 !status.ok()) {
387 PW_LOG_WARN("No ACL send credits available. So will not send.");
388 return pw::Status::Unavailable();
389 }
390
391 connection_ptr->set_num_pending_packets(
392 connection_ptr->num_pending_packets() + 1);
393
394 hci_transport_.SendToController(std::move(h4_packet));
395 return pw::OkStatus();
396 }
397
CreateAclConnection(uint16_t connection_handle,AclTransportType transport)398 Status AclDataChannel::CreateAclConnection(uint16_t connection_handle,
399 AclTransportType transport) {
400 std::lock_guard lock(mutex_);
401 AclConnection* connection_it = FindAclConnection(connection_handle);
402 if (connection_it) {
403 return Status::AlreadyExists();
404 }
405 if (active_acl_connections_.full()) {
406 return Status::ResourceExhausted();
407 }
408 active_acl_connections_.emplace_back(transport,
409 /*connection_handle=*/connection_handle,
410 /*num_pending_packets=*/0,
411 l2cap_channel_manager_);
412 return OkStatus();
413 }
414
FragmentedPduStarted(Direction direction,uint16_t connection_handle)415 pw::Status AclDataChannel::FragmentedPduStarted(Direction direction,
416 uint16_t connection_handle) {
417 std::lock_guard lock(mutex_);
418 AclConnection* connection_ptr = FindAclConnection(connection_handle);
419 if (!connection_ptr) {
420 return Status::NotFound();
421 }
422 if (connection_ptr->is_receiving_fragmented_pdu(direction)) {
423 return Status::FailedPrecondition();
424 }
425 connection_ptr->set_is_receiving_fragmented_pdu(direction, true);
426 return OkStatus();
427 }
428
IsReceivingFragmentedPdu(Direction direction,uint16_t connection_handle)429 pw::Result<bool> AclDataChannel::IsReceivingFragmentedPdu(
430 Direction direction, uint16_t connection_handle) {
431 std::lock_guard lock(mutex_);
432 AclConnection* connection_ptr = FindAclConnection(connection_handle);
433 if (!connection_ptr) {
434 return Status::NotFound();
435 }
436 return connection_ptr->is_receiving_fragmented_pdu(direction);
437 }
438
FragmentedPduFinished(Direction direction,uint16_t connection_handle)439 pw::Status AclDataChannel::FragmentedPduFinished(Direction direction,
440 uint16_t connection_handle) {
441 std::lock_guard lock(mutex_);
442 AclConnection* connection_ptr = FindAclConnection(connection_handle);
443 if (!connection_ptr) {
444 return Status::NotFound();
445 }
446 if (!connection_ptr->is_receiving_fragmented_pdu(direction)) {
447 return Status::FailedPrecondition();
448 }
449 connection_ptr->set_is_receiving_fragmented_pdu(direction, false);
450 return OkStatus();
451 }
452
FindSignalingChannel(uint16_t connection_handle,uint16_t local_cid)453 L2capSignalingChannel* AclDataChannel::FindSignalingChannel(
454 uint16_t connection_handle, uint16_t local_cid) {
455 std::lock_guard lock(mutex_);
456
457 AclConnection* connection_ptr = FindAclConnection(connection_handle);
458 if (!connection_ptr) {
459 return nullptr;
460 }
461
462 if (local_cid == connection_ptr->signaling_channel()->local_cid()) {
463 return connection_ptr->signaling_channel();
464 }
465 return nullptr;
466 }
467
FindAclConnection(uint16_t connection_handle)468 AclDataChannel::AclConnection* AclDataChannel::FindAclConnection(
469 uint16_t connection_handle) {
470 AclConnection* connection_it = containers::FindIf(
471 active_acl_connections_,
472 [&connection_handle](const AclConnection& connection) {
473 return connection.connection_handle() == connection_handle;
474 });
475 return connection_it == active_acl_connections_.end() ? nullptr
476 : connection_it;
477 }
478
479 } // namespace pw::bluetooth::proxy
480