• 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/l2cap_coc.h"
16 
17 #include <cmath>
18 #include <cstdint>
19 #include <mutex>
20 
21 #include "pw_assert/check.h"
22 #include "pw_bluetooth/emboss_util.h"
23 #include "pw_bluetooth/hci_data.emb.h"
24 #include "pw_bluetooth/l2cap_frames.emb.h"
25 #include "pw_bluetooth_proxy/h4_packet.h"
26 #include "pw_bluetooth_proxy/internal/l2cap_channel.h"
27 #include "pw_bluetooth_proxy/internal/l2cap_signaling_channel.h"
28 #include "pw_bluetooth_proxy/l2cap_channel_common.h"
29 #include "pw_log/log.h"
30 #include "pw_multibuf/multibuf.h"
31 #include "pw_status/status.h"
32 
33 namespace pw::bluetooth::proxy {
34 
35 namespace {
36 
37 // TODO: b/353734827 - Allow client to determine this constant.
38 const float kRxCreditReplenishThreshold = 0.30;
39 
40 }  // namespace
41 
L2capCoc(L2capCoc && other)42 L2capCoc::L2capCoc(L2capCoc&& other)
43     : L2capChannel(static_cast<L2capCoc&&>(other)),
44       signaling_channel_(other.signaling_channel_),
45       rx_mtu_(other.rx_mtu_),
46       rx_mps_(other.rx_mps_),
47       tx_mtu_(other.tx_mtu_),
48       tx_mps_(other.tx_mps_),
49       receive_fn_(std::move(other.receive_fn_)) {
50   {
51     std::lock_guard lock(tx_mutex_);
52     std::lock_guard other_lock(other.tx_mutex_);
53     tx_credits_ = other.tx_credits_;
54   }
55   {
56     std::lock_guard lock(rx_mutex_);
57     std::lock_guard other_lock(other.rx_mutex_);
58     remaining_sdu_bytes_to_ignore_ = other.remaining_sdu_bytes_to_ignore_;
59     rx_sdu_ = std::move(other.rx_sdu_);
60     rx_sdu_offset_ = other.rx_sdu_offset_;
61     rx_sdu_bytes_remaining_ = other.rx_sdu_bytes_remaining_;
62     rx_remaining_credits_ = other.rx_remaining_credits_;
63     rx_total_credits_ = other.rx_total_credits_;
64   }
65 }
66 
Write(multibuf::MultiBuf && payload)67 StatusWithMultiBuf L2capCoc::Write(multibuf::MultiBuf&& payload) {
68   if (payload.size() > tx_mtu_) {
69     PW_LOG_ERROR(
70         "Payload (%zu bytes) exceeds MTU (%d bytes). So will not process.",
71         payload.size(),
72         tx_mtu_);
73     return {Status::InvalidArgument(), std::move(payload)};
74   }
75 
76   return L2capChannel::Write(std::move(payload));
77 }
78 
Create(pw::multibuf::MultiBufAllocator & rx_multibuf_allocator,L2capChannelManager & l2cap_channel_manager,L2capSignalingChannel * signaling_channel,uint16_t connection_handle,CocConfig rx_config,CocConfig tx_config,ChannelEventCallback && event_fn,Function<void (multibuf::MultiBuf && payload)> && receive_fn)79 pw::Result<L2capCoc> L2capCoc::Create(
80     pw::multibuf::MultiBufAllocator& rx_multibuf_allocator,
81     L2capChannelManager& l2cap_channel_manager,
82     L2capSignalingChannel* signaling_channel,
83     uint16_t connection_handle,
84     CocConfig rx_config,
85     CocConfig tx_config,
86     ChannelEventCallback&& event_fn,
87     Function<void(multibuf::MultiBuf&& payload)>&& receive_fn) {
88   if (!AreValidParameters(/*connection_handle=*/connection_handle,
89                           /*local_cid=*/rx_config.cid,
90                           /*remote_cid=*/tx_config.cid)) {
91     return pw::Status::InvalidArgument();
92   }
93 
94   if (tx_config.mps < emboss::L2capLeCreditBasedConnectionReq::min_mps() ||
95       tx_config.mps > emboss::L2capLeCreditBasedConnectionReq::max_mps()) {
96     PW_LOG_ERROR(
97         "Tx MPS (%d octets) invalid. L2CAP implementations shall support a "
98         "minimum MPS of 23 octets and may support an MPS up to 65533 octets.",
99         tx_config.mps);
100     return pw::Status::InvalidArgument();
101   }
102 
103   return L2capCoc(
104       /*rx_multibuf_allocator=*/rx_multibuf_allocator,
105       /*l2cap_channel_manager=*/l2cap_channel_manager,
106       /*signaling_channel=*/signaling_channel,
107       /*connection_handle=*/connection_handle,
108       /*rx_config=*/rx_config,
109       /*tx_config=*/tx_config,
110       /*event_fn=*/std::move(event_fn),
111       /*receive_fn=*/std::move(receive_fn));
112 }
113 
ReplenishRxCredits(uint16_t additional_rx_credits)114 pw::Status L2capCoc::ReplenishRxCredits(uint16_t additional_rx_credits) {
115   if (!signaling_channel_) {
116     return Status::FailedPrecondition();
117   }
118   PW_CHECK(rx_multibuf_allocator());
119   // SendFlowControlCreditInd logs if status is not ok, so no need to log here.
120   return signaling_channel_->SendFlowControlCreditInd(
121       local_cid(), additional_rx_credits, *rx_multibuf_allocator());
122 }
123 
SendAdditionalRxCredits(uint16_t additional_rx_credits)124 pw::Status L2capCoc::SendAdditionalRxCredits(uint16_t additional_rx_credits) {
125   if (state() != State::kRunning) {
126     return Status::FailedPrecondition();
127   }
128   std::lock_guard lock(rx_mutex_);
129   Status status = ReplenishRxCredits(additional_rx_credits);
130 
131   if (status.ok()) {
132     // We treat additional bumps from the client as bumping the total allowed
133     // credits.
134     rx_total_credits_ += additional_rx_credits;
135     rx_remaining_credits_ += additional_rx_credits;
136     PW_LOG_INFO(
137         "btproxy: L2capCoc::SendAdditionalRxCredits - status: %s, "
138         "additional_rx_credits: %u, rx_total_credits_: %u, "
139         "rx_remaining_credits_: %u",
140         status.str(),
141         additional_rx_credits,
142         rx_total_credits_,
143         rx_remaining_credits_);
144   }
145   DrainChannelQueuesIfNewTx();
146   return status;
147 }
148 
DoHandlePduFromController(pw::span<uint8_t> kframe)149 bool L2capCoc::DoHandlePduFromController(pw::span<uint8_t> kframe) {
150   if (state() != State::kRunning) {
151     PW_LOG_ERROR(
152         "btproxy: L2capCoc::HandlePduFromController on non-running "
153         "channel. local_cid: %u, remote_cid: %u, state: %u",
154         local_cid(),
155         remote_cid(),
156         cpp23::to_underlying(state()));
157     StopAndSendEvent(L2capChannelEvent::kRxWhileStopped);
158     return true;
159   }
160 
161   std::lock_guard lock(rx_mutex_);
162   rx_remaining_credits_--;
163 
164   uint16_t rx_credits_used = rx_total_credits_ - rx_remaining_credits_;
165   if (rx_credits_used >=
166       std::ceil(rx_total_credits_ * kRxCreditReplenishThreshold)) {
167     Status status = ReplenishRxCredits(rx_credits_used);
168     if (status.IsUnavailable()) {
169       PW_LOG_INFO(
170           "Unable to send %hu rx credits to remote (it has %hu credits "
171           "remaining). Will try on next PDU receive.",
172           rx_credits_used,
173           rx_total_credits_);
174     } else if (status.IsFailedPrecondition()) {
175       PW_LOG_WARN(
176           "Unable to send rx credits to remote, perhaps the connection has "
177           "been closed?");
178     } else {
179       PW_CHECK(status.ok());
180       rx_remaining_credits_ += rx_credits_used;
181     }
182   }
183 
184   ConstByteSpan kframe_payload;
185   if (rx_sdu_bytes_remaining_ > 0) {
186     // Received PDU that is part of current SDU being assembled.
187     Result<emboss::SubsequentKFrameView> subsequent_kframe_view =
188         MakeEmbossView<emboss::SubsequentKFrameView>(kframe);
189     // Lower layers should not (and cannot) invoke this callback on a packet
190     // with an incomplete basic L2CAP header.
191     PW_CHECK_OK(subsequent_kframe_view);
192 
193     // Core Spec v6.0 Vol 3, Part A, 3.4.3: "If the payload size of any K-frame
194     // exceeds the receiver's MPS, the receiver shall disconnect the channel."
195     uint16_t payload_size = subsequent_kframe_view->payload_size().Read();
196     if (payload_size > rx_mps_) {
197       PW_LOG_ERROR(
198           "(CID %#x) Rx K-frame payload exceeds MPU. So stopping channel & "
199           "reporting it needs to be closed.",
200           local_cid());
201       StopAndSendEvent(L2capChannelEvent::kRxInvalid);
202       return true;
203     }
204 
205     kframe_payload =
206         as_bytes(span(subsequent_kframe_view->payload().BackingStorage().data(),
207                       subsequent_kframe_view->payload_size().Read()));
208   } else {
209     // Received first (or only) PDU of SDU.
210     Result<emboss::FirstKFrameView> first_kframe_view =
211         MakeEmbossView<emboss::FirstKFrameView>(kframe);
212     if (!first_kframe_view.ok()) {
213       PW_LOG_ERROR(
214           "(CID %#x) Buffer is too small for first K-frame. So stopping "
215           "channel and reporting it needs to be closed.",
216           local_cid());
217       StopAndSendEvent(L2capChannelEvent::kRxInvalid);
218       return true;
219     }
220 
221     rx_sdu_bytes_remaining_ = first_kframe_view->sdu_length().Read();
222 
223     // Core Spec v6.0 Vol 3, Part A, 3.4.3: "If the SDU length field value
224     // exceeds the receiver's MTU, the receiver shall disconnect the channel."
225     if (rx_sdu_bytes_remaining_ > rx_mtu_) {
226       PW_LOG_ERROR(
227           "(CID %#x) Rx K-frame SDU exceeds MTU. So stopping channel & "
228           "reporting it needs to be closed.",
229           local_cid());
230       StopAndSendEvent(L2capChannelEvent::kRxInvalid);
231       return true;
232     }
233 
234     // Core Spec v6.0 Vol 3, Part A, 3.4.3: "If the payload size of any K-frame
235     // exceeds the receiver's MPS, the receiver shall disconnect the channel."
236     uint16_t payload_size = first_kframe_view->payload_size().Read();
237     if (payload_size > rx_mps_) {
238       PW_LOG_ERROR(
239           "(CID %#x) Rx K-frame payload exceeds MPU. So stopping channel & "
240           "reporting it needs to be closed.",
241           local_cid());
242       StopAndSendEvent(L2capChannelEvent::kRxInvalid);
243       return true;
244     }
245 
246     rx_sdu_ =
247         rx_multibuf_allocator()->AllocateContiguous(rx_sdu_bytes_remaining_);
248     if (!rx_sdu_) {
249       PW_LOG_ERROR(
250           "(CID %#x) Rx MultiBuf allocator out of memory. So stopping channel "
251           "and reporting it needs to be closed.",
252           local_cid());
253       StopAndSendEvent(L2capChannelEvent::kRxOutOfMemory);
254       return true;
255     }
256 
257     kframe_payload =
258         as_bytes(span(first_kframe_view->payload().BackingStorage().data(),
259                       first_kframe_view->payload_size().Read()));
260   }
261 
262   // Copy segment into rx_sdu_.
263   StatusWithSize status = rx_sdu_->CopyFrom(/*source=*/kframe_payload,
264                                             /*position=*/rx_sdu_offset_);
265   if (status.IsResourceExhausted()) {
266     // Core Spec v6.0 Vol 3, Part A, 3.4.3: "If the sum of the payload sizes
267     // for the K-frames exceeds the specified SDU length, the receiver shall
268     // disconnect the channel."
269     PW_LOG_ERROR(
270         "(CID %#x) Sum of K-frame payload sizes exceeds the specified SDU "
271         "length. So stopping channel and reporting it needs to be closed.",
272         local_cid());
273     StopAndSendEvent(L2capChannelEvent::kRxInvalid);
274     return true;
275   }
276   PW_CHECK_OK(status);
277 
278   rx_sdu_bytes_remaining_ -= kframe_payload.size();
279   rx_sdu_offset_ += kframe_payload.size();
280 
281   if (rx_sdu_bytes_remaining_ == 0) {
282     // We have a full SDU, so invoke client callback.
283     if (receive_fn_) {
284       receive_fn_(std::move(*rx_sdu_));
285     }
286     rx_sdu_ = std::nullopt;
287     rx_sdu_offset_ = 0;
288   }
289 
290   return true;
291 }
292 
HandlePduFromHost(pw::span<uint8_t>)293 bool L2capCoc::HandlePduFromHost(pw::span<uint8_t>) {
294   // Always forward data from host to controller
295   return false;
296 }
297 
DoClose()298 void L2capCoc::DoClose() {
299   std::lock_guard lock(rx_mutex_);
300   signaling_channel_ = nullptr;
301 }
302 
L2capCoc(pw::multibuf::MultiBufAllocator & rx_multibuf_allocator,L2capChannelManager & l2cap_channel_manager,L2capSignalingChannel * signaling_channel,uint16_t connection_handle,CocConfig rx_config,CocConfig tx_config,ChannelEventCallback && event_fn,Function<void (multibuf::MultiBuf && payload)> && receive_fn)303 L2capCoc::L2capCoc(pw::multibuf::MultiBufAllocator& rx_multibuf_allocator,
304                    L2capChannelManager& l2cap_channel_manager,
305                    L2capSignalingChannel* signaling_channel,
306                    uint16_t connection_handle,
307                    CocConfig rx_config,
308                    CocConfig tx_config,
309                    ChannelEventCallback&& event_fn,
310                    Function<void(multibuf::MultiBuf&& payload)>&& receive_fn)
311     : L2capChannel(l2cap_channel_manager,
312                    &rx_multibuf_allocator,
313                    /*connection_handle=*/connection_handle,
314                    /*transport=*/AclTransportType::kLe,
315                    /*local_cid=*/rx_config.cid,
316                    /*remote_cid=*/tx_config.cid,
317                    /*payload_from_controller_fn=*/nullptr,
318                    /*payload_from_host_fn=*/nullptr,
319                    /*event_fn=*/std::move(event_fn)),
320 
321       signaling_channel_(signaling_channel),
322       rx_mtu_(rx_config.mtu),
323       rx_mps_(rx_config.mps),
324       tx_mtu_(tx_config.mtu),
325       tx_mps_(tx_config.mps),
326       receive_fn_(std::move(receive_fn)),
327       rx_remaining_credits_(rx_config.credits),
328       rx_total_credits_(rx_config.credits),
329       tx_credits_(tx_config.credits) {
330   PW_LOG_INFO(
331       "btproxy: L2capCoc ctor - rx_remaining_credits_: %u, "
332       "rx_total_credits_: %u, tx_credits_: %u",
333       rx_remaining_credits_,
334       rx_total_credits_,
335       tx_credits_);
336 }
337 
~L2capCoc()338 L2capCoc::~L2capCoc() {
339   // Don't log dtor of moved-from channels.
340   if (state() != State::kUndefined) {
341     PW_LOG_INFO("btproxy: L2capCoc dtor");
342   }
343 }
344 
MaxL2capPayloadSize() const345 std::optional<uint16_t> L2capCoc::MaxL2capPayloadSize() const {
346   std::optional<uint16_t> max_l2cap_payload_size =
347       L2capChannel::MaxL2capPayloadSize();
348   if (!max_l2cap_payload_size) {
349     return std::nullopt;
350   }
351   return std::min(*max_l2cap_payload_size, tx_mps_);
352 }
353 
GenerateNextTxPacket()354 std::optional<H4PacketWithH4> L2capCoc::GenerateNextTxPacket() {
355   std::lock_guard lock(tx_mutex_);
356   constexpr uint8_t kSduLengthFieldSize = 2;
357   std::optional<uint16_t> max_l2cap_payload_size = MaxL2capPayloadSize();
358   if (state() != State::kRunning || PayloadQueueEmpty() || tx_credits_ == 0 ||
359       !max_l2cap_payload_size ||
360       *max_l2cap_payload_size <= kSduLengthFieldSize) {
361     return std::nullopt;
362   }
363 
364   ConstByteSpan sdu_span = GetFrontPayloadSpan();
365   // Number of client SDU bytes to be encoded in this segment.
366   uint16_t sdu_bytes_in_segment;
367   // Size of PDU payload for this L2CAP frame.
368   uint16_t pdu_data_size;
369   if (!is_continuing_segment_) {
370     // Generating the first (or only) PDU of an SDU.
371     size_t sdu_bytes_max_allowable =
372         *max_l2cap_payload_size - kSduLengthFieldSize;
373     sdu_bytes_in_segment = std::min(sdu_span.size(), sdu_bytes_max_allowable);
374     pdu_data_size = sdu_bytes_in_segment + kSduLengthFieldSize;
375   } else {
376     // Generating a continuing PDU in an SDU.
377     size_t sdu_bytes_max_allowable = *max_l2cap_payload_size;
378     sdu_bytes_in_segment =
379         std::min(sdu_span.size() - tx_sdu_offset_, sdu_bytes_max_allowable);
380     pdu_data_size = sdu_bytes_in_segment;
381   }
382 
383   pw::Result<H4PacketWithH4> h4_result = PopulateTxL2capPacket(pdu_data_size);
384   if (!h4_result.ok()) {
385     // This can fail if all H4 buffers are occupied.
386     return std::nullopt;
387   }
388   H4PacketWithH4 h4_packet = std::move(*h4_result);
389 
390   Result<emboss::AclDataFrameWriter> acl =
391       MakeEmbossWriter<emboss::AclDataFrameWriter>(h4_packet.GetHciSpan());
392   PW_CHECK(acl.ok());
393 
394   if (!is_continuing_segment_) {
395     Result<emboss::FirstKFrameWriter> first_kframe_writer =
396         MakeEmbossWriter<emboss::FirstKFrameWriter>(
397             acl->payload().BackingStorage().data(),
398             acl->payload().SizeInBytes());
399     PW_CHECK(first_kframe_writer.ok());
400     first_kframe_writer->sdu_length().Write(sdu_span.size());
401     PW_CHECK(first_kframe_writer->Ok());
402     PW_CHECK(TryToCopyToEmbossStruct(
403         /*emboss_dest=*/first_kframe_writer->payload(),
404         /*src=*/sdu_span.subspan(tx_sdu_offset_, sdu_bytes_in_segment)));
405   } else {
406     Result<emboss::SubsequentKFrameWriter> subsequent_kframe_writer =
407         MakeEmbossWriter<emboss::SubsequentKFrameWriter>(
408             acl->payload().BackingStorage().data(),
409             acl->payload().SizeInBytes());
410     PW_CHECK(subsequent_kframe_writer.ok());
411     PW_CHECK(TryToCopyToEmbossStruct(
412         /*emboss_dest=*/subsequent_kframe_writer->payload(),
413         /*src=*/sdu_span.subspan(tx_sdu_offset_, sdu_bytes_in_segment)));
414   }
415 
416   tx_sdu_offset_ += sdu_bytes_in_segment;
417 
418   if (tx_sdu_offset_ == sdu_span.size()) {
419     // This segment was the final (or only) PDU of the SDU.
420     PopFrontPayload();
421     tx_sdu_offset_ = 0;
422     is_continuing_segment_ = false;
423   } else {
424     is_continuing_segment_ = true;
425   }
426 
427   --tx_credits_;
428   return h4_packet;
429 }
430 
AddTxCredits(uint16_t credits)431 void L2capCoc::AddTxCredits(uint16_t credits) {
432   if (state() != State::kRunning) {
433     PW_LOG_ERROR(
434         "(CID %#x) Received credits on stopped CoC. So will ignore signal.",
435         local_cid());
436     return;
437   }
438 
439   bool credits_previously_zero;
440   {
441     std::lock_guard lock(tx_mutex_);
442 
443     // Core Spec v6.0 Vol 3, Part A, 10.1: "The device receiving the credit
444     // packet shall disconnect the L2CAP channel if the credit count exceeds
445     // 65535."
446     if (credits > emboss::L2capLeCreditBasedConnectionReq::max_credit_value() -
447                       tx_credits_) {
448       PW_LOG_ERROR(
449           "btproxy: Received additional tx credits %u which put tx_credits_ %u "
450           "beyond max credit value of %ld. So stopping channel and reporting "
451           "it needs to be closed. local_cid: %u, remote_cid: %u",
452           credits,
453           tx_credits_,
454           long{emboss::L2capLeCreditBasedConnectionReq::max_credit_value()},
455           local_cid(),
456           remote_cid());
457       StopAndSendEvent(L2capChannelEvent::kRxInvalid);
458       return;
459     }
460 
461     credits_previously_zero = tx_credits_ == 0;
462     tx_credits_ += credits;
463   }
464   if (credits_previously_zero) {
465     ReportNewTxPacketsOrCredits();
466   }
467 }
468 
469 }  // namespace pw::bluetooth::proxy
470