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/l2cap_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/hci_h4.emb.h"
23 #include "pw_bluetooth/l2cap_frames.emb.h"
24 #include "pw_bluetooth_proxy/internal/l2cap_channel_manager.h"
25 #include "pw_bluetooth_proxy/l2cap_channel_common.h"
26 #include "pw_log/log.h"
27 #include "pw_span/cast.h"
28 #include "pw_status/status.h"
29 #include "pw_status/try.h"
30
31 namespace pw::bluetooth::proxy {
32
MoveFields(L2capChannel & other)33 void L2capChannel::MoveFields(L2capChannel& other) {
34 // TODO: https://pwbug.dev/380504851 - Add tests for move operators.
35 state_ = other.state();
36 connection_handle_ = other.connection_handle();
37 transport_ = other.transport();
38 local_cid_ = other.local_cid();
39 remote_cid_ = other.remote_cid();
40 event_fn_ = std::move(other.event_fn_);
41 payload_from_controller_fn_ = std::move(other.payload_from_controller_fn_);
42 payload_from_host_fn_ = std::move(other.payload_from_host_fn_);
43 rx_multibuf_allocator_ = other.rx_multibuf_allocator_;
44 {
45 std::lock_guard lock(send_queue_mutex_);
46 std::lock_guard other_lock(other.send_queue_mutex_);
47 payload_queue_ = std::move(other.payload_queue_);
48 notify_on_dequeue_ = other.notify_on_dequeue_;
49 l2cap_channel_manager_.DeregisterChannel(other);
50 l2cap_channel_manager_.RegisterChannel(*this);
51 }
52 other.Undefine();
53 }
54
L2capChannel(L2capChannel && other)55 L2capChannel::L2capChannel(L2capChannel&& other)
56 : l2cap_channel_manager_(other.l2cap_channel_manager_) {
57 MoveFields(other);
58 }
59
operator =(L2capChannel && other)60 L2capChannel& L2capChannel::operator=(L2capChannel&& other) {
61 if (this != &other) {
62 l2cap_channel_manager_.DeregisterChannel(*this);
63 MoveFields(other);
64 }
65 return *this;
66 }
67
~L2capChannel()68 L2capChannel::~L2capChannel() {
69 // Don't log dtor of moved-from channels.
70 if (state_ != State::kUndefined) {
71 PW_LOG_INFO(
72 "btproxy: L2capChannel dtor - transport_: %u, connection_handle_ : "
73 "%#x, local_cid_: %#x, remote_cid_: %#x, state_: %u",
74 cpp23::to_underlying(transport_),
75 connection_handle_,
76 local_cid_,
77 remote_cid_,
78 cpp23::to_underlying(state_));
79 }
80
81 // Channel objects may outlive `ProxyHost`, but they are closed on
82 // `ProxyHost` dtor, so this check will prevent a crash from trying to access
83 // a destructed `L2capChannelManager`.
84 if (state_ != State::kClosed) {
85 l2cap_channel_manager_.DeregisterChannel(*this);
86 ClearQueue();
87 }
88 }
89
Stop()90 void L2capChannel::Stop() {
91 PW_LOG_INFO(
92 "btproxy: L2capChannel::Stop - transport_: %u, connection_handle_: %#x, "
93 "local_cid_: %#x, remote_cid_: %#x, previous state_: %u",
94 cpp23::to_underlying(transport_),
95 connection_handle_,
96 local_cid_,
97 remote_cid_,
98 cpp23::to_underlying(state_));
99
100 PW_CHECK(state_ != State::kUndefined && state_ != State::kClosed);
101
102 state_ = State::kStopped;
103 ClearQueue();
104 }
105
Close()106 void L2capChannel::Close() {
107 l2cap_channel_manager_.DeregisterChannel(*this);
108 InternalClose();
109 }
110
InternalClose(L2capChannelEvent event)111 void L2capChannel::InternalClose(L2capChannelEvent event) {
112 PW_LOG_INFO(
113 "btproxy: L2capChannel::Close - transport_: %u, "
114 "connection_handle_: %#x, local_cid_: %#x, remote_cid_: %#x, previous "
115 "state_: %u",
116 cpp23::to_underlying(transport_),
117 connection_handle_,
118 local_cid_,
119 remote_cid_,
120 cpp23::to_underlying(state_));
121
122 PW_CHECK(state_ != State::kUndefined);
123 if (state_ == State::kClosed) {
124 return;
125 }
126 state_ = State::kClosed;
127
128 ClearQueue();
129 DoClose();
130 SendEvent(event);
131 }
132
Undefine()133 void L2capChannel::Undefine() { state_ = State::kUndefined; }
134
Write(pw::multibuf::MultiBuf && payload)135 StatusWithMultiBuf L2capChannel::Write(pw::multibuf::MultiBuf&& payload) {
136 StatusWithMultiBuf result = WriteLocked(std::move(payload));
137 l2cap_channel_manager_.DrainChannelQueuesIfNewTx();
138 return result;
139 }
140
WriteLocked(pw::multibuf::MultiBuf && payload)141 StatusWithMultiBuf L2capChannel::WriteLocked(pw::multibuf::MultiBuf&& payload) {
142 if (UsesPayloadQueue()) {
143 return WriteToPayloadQueue(std::move(payload));
144 } else {
145 return WriteToPduQueue(std::move(payload));
146 }
147 }
148
QueuePacket(H4PacketWithH4 && packet)149 Status L2capChannel::QueuePacket(H4PacketWithH4&& packet) {
150 PW_CHECK(!UsesPayloadQueue());
151
152 if (state() != State::kRunning) {
153 return Status::FailedPrecondition();
154 }
155
156 Status status;
157 {
158 std::lock_guard lock(send_queue_mutex_);
159 if (send_queue_.full()) {
160 status = Status::Unavailable();
161 notify_on_dequeue_ = true;
162 } else {
163 send_queue_.push(std::move(packet));
164 status = OkStatus();
165 }
166 }
167 l2cap_channel_manager_.ForceDrainChannelQueues();
168 return status;
169 }
170
WriteToPayloadQueue(multibuf::MultiBuf && payload)171 StatusWithMultiBuf L2capChannel::WriteToPayloadQueue(
172 multibuf::MultiBuf&& payload) {
173 if (!payload.IsContiguous()) {
174 return {Status::InvalidArgument(), std::move(payload)};
175 }
176
177 if (state() != State::kRunning) {
178 return {Status::FailedPrecondition(), std::move(payload)};
179 }
180
181 PW_CHECK(UsesPayloadQueue());
182
183 return QueuePayload(std::move(payload));
184 }
185
186 // TODO: https://pwbug.dev/379337272 - Delete when all channels are
187 // transitioned to using payload queues.
WriteToPduQueue(multibuf::MultiBuf && payload)188 StatusWithMultiBuf L2capChannel::WriteToPduQueue(multibuf::MultiBuf&& payload) {
189 if (!payload.IsContiguous()) {
190 return {Status::InvalidArgument(), std::move(payload)};
191 }
192
193 if (state() != State::kRunning) {
194 return {Status::FailedPrecondition(), std::move(payload)};
195 }
196
197 PW_CHECK(!UsesPayloadQueue());
198
199 std::optional<ByteSpan> span = payload.ContiguousSpan();
200 PW_CHECK(span.has_value());
201 Status status = Write(span_cast<const uint8_t>(*span));
202
203 if (!status.ok()) {
204 return {status, std::move(payload)};
205 }
206
207 return {OkStatus(), std::nullopt};
208 }
209
Write(pw::span<const uint8_t> payload)210 pw::Status L2capChannel::Write(
211 [[maybe_unused]] pw::span<const uint8_t> payload) {
212 PW_LOG_ERROR(
213 "btproxy: Write(span) called on class that only supports "
214 "Write(MultiBuf)");
215 return Status::Unimplemented();
216 }
217
IsWriteAvailable()218 Status L2capChannel::IsWriteAvailable() {
219 if (state() != State::kRunning) {
220 return Status::FailedPrecondition();
221 }
222
223 std::lock_guard lock(send_queue_mutex_);
224
225 // TODO: https://pwbug.dev/379337272 - Only check payload_queue_ once all
226 // channels have transitioned to payload_queue_.
227 const bool queue_full =
228 UsesPayloadQueue() ? payload_queue_.full() : send_queue_.full();
229 if (queue_full) {
230 notify_on_dequeue_ = true;
231 return Status::Unavailable();
232 }
233
234 notify_on_dequeue_ = false;
235 return OkStatus();
236 }
237
DequeuePacket()238 std::optional<H4PacketWithH4> L2capChannel::DequeuePacket() {
239 std::optional<H4PacketWithH4> packet;
240 bool should_notify = false;
241 {
242 std::lock_guard lock(send_queue_mutex_);
243 packet = GenerateNextTxPacket();
244 if (packet) {
245 should_notify = notify_on_dequeue_;
246 notify_on_dequeue_ = false;
247 }
248 }
249
250 if (should_notify) {
251 SendEvent(L2capChannelEvent::kWriteAvailable);
252 }
253
254 return packet;
255 }
256
QueuePayload(multibuf::MultiBuf && buf)257 StatusWithMultiBuf L2capChannel::QueuePayload(multibuf::MultiBuf&& buf) {
258 PW_CHECK(UsesPayloadQueue());
259
260 PW_CHECK(state() == State::kRunning);
261 PW_CHECK(buf.IsContiguous());
262
263 {
264 std::lock_guard lock(send_queue_mutex_);
265 if (payload_queue_.full()) {
266 notify_on_dequeue_ = true;
267 return {Status::Unavailable(), std::move(buf)};
268 }
269 payload_queue_.push(std::move(buf));
270 }
271
272 ReportNewTxPacketsOrCredits();
273 return {OkStatus(), std::nullopt};
274 }
275
PopFrontPayload()276 void L2capChannel::PopFrontPayload() {
277 PW_CHECK(!payload_queue_.empty());
278 payload_queue_.pop();
279 }
280
GetFrontPayloadSpan() const281 ConstByteSpan L2capChannel::GetFrontPayloadSpan() const {
282 PW_CHECK(!payload_queue_.empty());
283 const multibuf::MultiBuf& buf = payload_queue_.front();
284 std::optional<ConstByteSpan> span = buf.ContiguousSpan();
285 PW_CHECK(span);
286 return *span;
287 }
288
PayloadQueueEmpty() const289 bool L2capChannel::PayloadQueueEmpty() const { return payload_queue_.empty(); }
290
HandlePduFromController(pw::span<uint8_t> l2cap_pdu)291 bool L2capChannel::HandlePduFromController(pw::span<uint8_t> l2cap_pdu) {
292 if (state() != State::kRunning) {
293 PW_LOG_ERROR(
294 "btproxy: L2capChannel::OnPduReceivedFromController on non-running "
295 "channel. local_cid: %#x, remote_cid: %#x, state: %u",
296 local_cid(),
297 remote_cid(),
298 cpp23::to_underlying(state()));
299 SendEvent(L2capChannelEvent::kRxWhileStopped);
300 return true;
301 }
302 return DoHandlePduFromController(l2cap_pdu);
303 }
304
L2capChannel(L2capChannelManager & l2cap_channel_manager,multibuf::MultiBufAllocator * rx_multibuf_allocator,uint16_t connection_handle,AclTransportType transport,uint16_t local_cid,uint16_t remote_cid,OptionalPayloadReceiveCallback && payload_from_controller_fn,OptionalPayloadReceiveCallback && payload_from_host_fn,ChannelEventCallback && event_fn)305 L2capChannel::L2capChannel(
306 L2capChannelManager& l2cap_channel_manager,
307 multibuf::MultiBufAllocator* rx_multibuf_allocator,
308 uint16_t connection_handle,
309 AclTransportType transport,
310 uint16_t local_cid,
311 uint16_t remote_cid,
312 OptionalPayloadReceiveCallback&& payload_from_controller_fn,
313 OptionalPayloadReceiveCallback&& payload_from_host_fn,
314 ChannelEventCallback&& event_fn)
315 : l2cap_channel_manager_(l2cap_channel_manager),
316 state_(State::kRunning),
317 connection_handle_(connection_handle),
318 transport_(transport),
319 local_cid_(local_cid),
320 remote_cid_(remote_cid),
321 event_fn_(std::move(event_fn)),
322 rx_multibuf_allocator_(rx_multibuf_allocator),
323 payload_from_controller_fn_(std::move(payload_from_controller_fn)),
324 payload_from_host_fn_(std::move(payload_from_host_fn)) {
325 PW_LOG_INFO(
326 "btproxy: L2capChannel ctor - transport_: %u, connection_handle_ : %u, "
327 "local_cid_ : %#x, remote_cid_: %#x",
328 cpp23::to_underlying(transport_),
329 connection_handle_,
330 local_cid_,
331 remote_cid_);
332
333 l2cap_channel_manager_.RegisterChannel(*this);
334 }
335
336 // Send `event` to client if an event callback was provided.
SendEvent(L2capChannelEvent event)337 void L2capChannel::SendEvent(L2capChannelEvent event) {
338 // We don't log kWriteAvailable since they happen often. Optimally we would
339 // just debug log them also, but one of our downstreams logs all levels.
340 if (event != L2capChannelEvent::kWriteAvailable) {
341 PW_LOG_INFO(
342 "btproxy: SendEvent - event: %u, transport_: %u, "
343 "connection_handle_: %#x, local_cid_ : %#x, remote_cid_: %#x, "
344 "state_: %u",
345 cpp23::to_underlying(event),
346 cpp23::to_underlying(transport_),
347 connection_handle_,
348 local_cid_,
349 remote_cid_,
350 cpp23::to_underlying(state_));
351 }
352
353 if (event_fn_) {
354 event_fn_(event);
355 }
356 }
357
AreValidParameters(uint16_t connection_handle,uint16_t local_cid,uint16_t remote_cid)358 bool L2capChannel::AreValidParameters(uint16_t connection_handle,
359 uint16_t local_cid,
360 uint16_t remote_cid) {
361 if (connection_handle > kMaxValidConnectionHandle) {
362 PW_LOG_ERROR(
363 "Invalid connection handle %#x. Maximum connection handle is 0x0EFF.",
364 connection_handle);
365 return false;
366 }
367 if (local_cid == 0 || remote_cid == 0) {
368 PW_LOG_ERROR("L2CAP channel identifier 0 is not valid.");
369 return false;
370 }
371 return true;
372 }
373
GenerateNextTxPacket()374 std::optional<H4PacketWithH4> L2capChannel::GenerateNextTxPacket() {
375 if (send_queue_.empty()) {
376 return std::nullopt;
377 }
378 H4PacketWithH4 packet = std::move(send_queue_.front());
379 send_queue_.pop();
380 return packet;
381 }
382
PopulateTxL2capPacket(uint16_t data_length)383 pw::Result<H4PacketWithH4> L2capChannel::PopulateTxL2capPacket(
384 uint16_t data_length) {
385 return PopulateL2capPacket(data_length);
386 }
387
PopulateTxL2capPacketDuringWrite(uint16_t data_length)388 pw::Result<H4PacketWithH4> L2capChannel::PopulateTxL2capPacketDuringWrite(
389 uint16_t data_length) {
390 pw::Result<H4PacketWithH4> packet_result = PopulateL2capPacket(data_length);
391 if (packet_result.status().IsUnavailable()) {
392 std::lock_guard lock(send_queue_mutex_);
393 // If there were no buffers, they are all in the queue currently. This can
394 // happen if queue size == buffer count. Mark that a writer is getting an
395 // Unavailable status, and should be notified when queue space opens up.
396 notify_on_dequeue_ = true;
397 }
398 return packet_result;
399 }
400
401 namespace {
402
H4SizeForL2capData(uint16_t data_length)403 constexpr size_t H4SizeForL2capData(uint16_t data_length) {
404 const size_t l2cap_packet_size =
405 emboss::BasicL2capHeader::IntrinsicSizeInBytes() + data_length;
406 const size_t acl_packet_size =
407 emboss::AclDataFrameHeader::IntrinsicSizeInBytes() + l2cap_packet_size;
408 return sizeof(emboss::H4PacketType) + acl_packet_size;
409 }
410
411 } // namespace
412
IsOkL2capDataLength(uint16_t data_length)413 bool L2capChannel::IsOkL2capDataLength(uint16_t data_length) {
414 return H4SizeForL2capData(data_length) <=
415 l2cap_channel_manager_.GetH4BuffSize();
416 }
417
PopulateL2capPacket(uint16_t data_length)418 pw::Result<H4PacketWithH4> L2capChannel::PopulateL2capPacket(
419 uint16_t data_length) {
420 const size_t l2cap_packet_size =
421 emboss::BasicL2capHeader::IntrinsicSizeInBytes() + data_length;
422 const size_t h4_packet_size = H4SizeForL2capData(data_length);
423
424 pw::Result<H4PacketWithH4> h4_packet_res =
425 l2cap_channel_manager_.GetAclH4Packet(h4_packet_size);
426 if (!h4_packet_res.ok()) {
427 return h4_packet_res.status();
428 }
429 H4PacketWithH4 h4_packet = std::move(h4_packet_res.value());
430 h4_packet.SetH4Type(emboss::H4PacketType::ACL_DATA);
431
432 PW_TRY_ASSIGN(
433 auto acl,
434 MakeEmbossWriter<emboss::AclDataFrameWriter>(h4_packet.GetHciSpan()));
435 acl.header().handle().Write(connection_handle_);
436 // TODO: https://pwbug.dev/360932103 - Support packet segmentation, so this
437 // value will not always be FIRST_NON_FLUSHABLE.
438 acl.header().packet_boundary_flag().Write(
439 emboss::AclDataPacketBoundaryFlag::FIRST_NON_FLUSHABLE);
440 acl.header().broadcast_flag().Write(
441 emboss::AclDataPacketBroadcastFlag::POINT_TO_POINT);
442 acl.data_total_length().Write(l2cap_packet_size);
443
444 PW_TRY_ASSIGN(auto l2cap_header,
445 MakeEmbossWriter<emboss::BasicL2capHeaderWriter>(
446 acl.payload().BackingStorage().data(),
447 emboss::BasicL2capHeader::IntrinsicSizeInBytes()));
448 l2cap_header.pdu_length().Write(data_length);
449 l2cap_header.channel_id().Write(remote_cid_);
450
451 return h4_packet;
452 }
453
MaxL2capPayloadSize() const454 std::optional<uint16_t> L2capChannel::MaxL2capPayloadSize() const {
455 std::optional<uint16_t> le_acl_data_packet_length =
456 l2cap_channel_manager_.le_acl_data_packet_length();
457 if (!le_acl_data_packet_length) {
458 return std::nullopt;
459 }
460
461 uint16_t max_acl_data_size_based_on_h4_buffer =
462 l2cap_channel_manager_.GetH4BuffSize() - sizeof(emboss::H4PacketType) -
463 emboss::AclDataFrameHeader::IntrinsicSizeInBytes();
464 uint16_t max_acl_data_size = std::min(max_acl_data_size_based_on_h4_buffer,
465 *le_acl_data_packet_length);
466 return max_acl_data_size - emboss::BasicL2capHeader::IntrinsicSizeInBytes();
467 }
468
ReportNewTxPacketsOrCredits()469 void L2capChannel::ReportNewTxPacketsOrCredits() {
470 l2cap_channel_manager_.ReportNewTxPacketsOrCredits();
471 }
472
DrainChannelQueuesIfNewTx()473 void L2capChannel::DrainChannelQueuesIfNewTx()
474 PW_LOCKS_EXCLUDED(send_queue_mutex_) {
475 l2cap_channel_manager_.DrainChannelQueuesIfNewTx();
476 }
477
ClearQueue()478 void L2capChannel::ClearQueue() {
479 std::lock_guard lock(send_queue_mutex_);
480 send_queue_.clear();
481 }
482
483 //-------
484 // Rx (protected)
485 //-------
486
SendPayloadFromHostToClient(pw::span<uint8_t> payload)487 bool L2capChannel::SendPayloadFromHostToClient(pw::span<uint8_t> payload) {
488 return SendPayloadToClient(payload, payload_from_host_fn_);
489 }
490
SendPayloadFromControllerToClient(pw::span<uint8_t> payload)491 bool L2capChannel::SendPayloadFromControllerToClient(
492 pw::span<uint8_t> payload) {
493 return SendPayloadToClient(payload, payload_from_controller_fn_);
494 }
495
SendPayloadToClient(pw::span<uint8_t> payload,OptionalPayloadReceiveCallback & callback)496 bool L2capChannel::SendPayloadToClient(
497 pw::span<uint8_t> payload, OptionalPayloadReceiveCallback& callback) {
498 if (!callback) {
499 return false;
500 }
501
502 std::optional<multibuf::MultiBuf> buffer =
503 rx_multibuf_allocator()->AllocateContiguous(payload.size());
504
505 if (!buffer) {
506 PW_LOG_ERROR(
507 "(CID %#x) Rx MultiBuf allocator out of memory. So stopping "
508 "channel "
509 "and reporting it needs to be closed.",
510 local_cid());
511 StopAndSendEvent(L2capChannelEvent::kRxOutOfMemory);
512 return true;
513 }
514
515 StatusWithSize status = buffer->CopyFrom(/*source=*/as_bytes(payload),
516 /*position=*/0);
517 PW_CHECK_OK(status);
518
519 std::optional<multibuf::MultiBuf> client_multibuf =
520 callback(std::move(*buffer));
521 // If client returned multibuf to us, we drop it and indicate to caller that
522 // packet should be forwarded. In the future when whole path is operating
523 // with multibuf's, we could pass it back up to container to be forwarded.
524 return !client_multibuf.has_value();
525 }
526
527 } // namespace pw::bluetooth::proxy
528