xref: /aosp_15_r20/external/pigweed/pw_bluetooth_proxy/l2cap_coc.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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 <mutex>
18 
19 #include "pw_bluetooth/emboss_util.h"
20 #include "pw_bluetooth/hci_data.emb.h"
21 #include "pw_bluetooth/l2cap_frames.emb.h"
22 #include "pw_bluetooth_proxy/internal/l2cap_write_channel.h"
23 #include "pw_log/log.h"
24 #include "pw_status/try.h"
25 
26 namespace pw::bluetooth::proxy {
27 
L2capCoc(L2capCoc && other)28 L2capCoc::L2capCoc(L2capCoc&& other)
29     : L2capWriteChannel(std::move(static_cast<L2capWriteChannel&>(other))),
30       L2capReadChannel(std::move(static_cast<L2capReadChannel&>(other))),
31       state_(other.state_),
32       rx_mtu_(other.rx_mtu_),
33       rx_mps_(other.rx_mps_),
34       tx_mtu_(other.tx_mtu_),
35       tx_mps_(other.tx_mps_),
36       tx_credits_(other.tx_credits_),
37       remaining_sdu_bytes_to_ignore_(other.remaining_sdu_bytes_to_ignore_),
38       event_fn_(std::move(other.event_fn_)) {}
39 
Stop()40 pw::Status L2capCoc::Stop() {
41   if (state_ == CocState::kStopped) {
42     return Status::InvalidArgument();
43   }
44   state_ = CocState::kStopped;
45   return OkStatus();
46 }
47 
Write(pw::span<const uint8_t> payload)48 pw::Status L2capCoc::Write(pw::span<const uint8_t> payload) {
49   if (state_ == CocState::kStopped) {
50     return Status::FailedPrecondition();
51   }
52 
53   if (payload.size() > tx_mtu_) {
54     PW_LOG_ERROR(
55         "Payload (%zu bytes) exceeds MTU (%d bytes). So will not process.",
56         payload.size(),
57         tx_mtu_);
58     return pw::Status::InvalidArgument();
59   }
60   // We do not currently support segmentation, so the payload is required to fit
61   // within the remote peer's Maximum PDU payload Size.
62   // TODO: https://pwbug.dev/360932103 - Support packet segmentation.
63   if (payload.size() > tx_mps_) {
64     PW_LOG_ERROR(
65         "Payload (%zu bytes) exceeds MPS (%d bytes). So will not process.",
66         payload.size(),
67         tx_mps_);
68     return pw::Status::InvalidArgument();
69   }
70 
71   // 2 bytes for SDU length field
72   size_t l2cap_data_length = payload.size() + 2;
73   pw::Result<H4PacketWithH4> h4_result =
74       PopulateTxL2capPacket(l2cap_data_length);
75   if (!h4_result.ok()) {
76     // This can fail as a result of the L2CAP PDU not fitting in an H4 buffer
77     // or if all buffers are occupied.
78     // TODO: https://pwbug.dev/365179076 - Once we support ACL fragmentation,
79     // this function will not fail due to the L2CAP PDU size not fitting.
80     return h4_result.status();
81   }
82   H4PacketWithH4 h4_packet = std::move(*h4_result);
83 
84   PW_TRY_ASSIGN(
85       auto acl,
86       MakeEmbossWriter<emboss::AclDataFrameWriter>(h4_packet.GetHciSpan()));
87   // Write payload.
88   PW_TRY_ASSIGN(auto kframe,
89                 MakeEmbossWriter<emboss::FirstKFrameWriter>(
90                     acl.payload().BackingStorage().data(),
91                     acl.payload().BackingStorage().SizeInBytes()));
92   kframe.sdu_length().Write(payload.size());
93   std::memcpy(
94       kframe.payload().BackingStorage().data(), payload.data(), payload.size());
95 
96   return QueuePacket(std::move(h4_packet));
97 }
98 
Create(L2capChannelManager & l2cap_channel_manager,uint16_t connection_handle,CocConfig rx_config,CocConfig tx_config,pw::Function<void (pw::span<uint8_t> payload)> && receive_fn,pw::Function<void (Event event)> && event_fn)99 pw::Result<L2capCoc> L2capCoc::Create(
100     L2capChannelManager& l2cap_channel_manager,
101     uint16_t connection_handle,
102     CocConfig rx_config,
103     CocConfig tx_config,
104     pw::Function<void(pw::span<uint8_t> payload)>&& receive_fn,
105     pw::Function<void(Event event)>&& event_fn) {
106   if (!L2capReadChannel::AreValidParameters(connection_handle, rx_config.cid) ||
107       !L2capWriteChannel::AreValidParameters(connection_handle,
108                                              tx_config.cid)) {
109     return pw::Status::InvalidArgument();
110   }
111 
112   if (tx_config.mps < emboss::L2capLeCreditBasedConnectionReq::min_mps() ||
113       tx_config.mps > emboss::L2capLeCreditBasedConnectionReq::max_mps()) {
114     PW_LOG_ERROR(
115         "Tx MPS (%d octets) invalid. L2CAP implementations shall support a "
116         "minimum MPS of 23 octets and may support an MPS up to 65533 octets.",
117         tx_config.mps);
118     return pw::Status::InvalidArgument();
119   }
120 
121   return L2capCoc(/*l2cap_channel_manager=*/l2cap_channel_manager,
122                   /*connection_handle=*/connection_handle,
123                   /*rx_config=*/rx_config,
124                   /*tx_config=*/tx_config,
125                   /*receive_fn=*/std::move(receive_fn),
126                   /*event_fn=*/std::move(event_fn));
127 }
128 
HandlePduFromController(pw::span<uint8_t> kframe)129 bool L2capCoc::HandlePduFromController(pw::span<uint8_t> kframe) {
130   // TODO: https://pwbug.dev/360934030 - Track rx_credits.
131   if (state_ == CocState::kStopped) {
132     StopChannelAndReportError(Event::kRxWhileStopped);
133     return true;
134   }
135 
136   std::lock_guard lock(mutex_);
137   // If `remaining_sdu_bytes_to_ignore_` is nonzero, we are in state where we
138   // are dropping continuing PDUs in a segmented SDU.
139   if (remaining_sdu_bytes_to_ignore_ > 0) {
140     Result<emboss::SubsequentKFrameView> subsequent_kframe_view =
141         MakeEmbossView<emboss::SubsequentKFrameView>(kframe);
142     if (!subsequent_kframe_view.ok()) {
143       PW_LOG_ERROR(
144           "(CID 0x%X) Buffer is too small for subsequent L2CAP K-frame. So "
145           "will drop.",
146           local_cid());
147       return true;
148     }
149     PW_LOG_INFO(
150         "(CID 0x%X) Dropping PDU that is part of current segmented SDU.",
151         local_cid());
152     if (subsequent_kframe_view->payload_size().Read() >
153         remaining_sdu_bytes_to_ignore_) {
154       // Core Spec v6.0 Vol 3, Part A, 3.4.3: "If the sum of the payload sizes
155       // for the K-frames exceeds the specified SDU length, the receiver shall
156       // disconnect the channel."
157       PW_LOG_ERROR(
158           "(CID 0x%X) Sum of K-frame payload sizes exceeds the specified SDU "
159           "length. So stopping channel & reporting it needs to be closed.",
160           local_cid());
161       StopChannelAndReportError(Event::kRxInvalid);
162     } else {
163       remaining_sdu_bytes_to_ignore_ -=
164           subsequent_kframe_view->payload_size().Read();
165     }
166     return true;
167   }
168 
169   Result<emboss::FirstKFrameView> kframe_view =
170       MakeEmbossView<emboss::FirstKFrameView>(kframe);
171   if (!kframe_view.ok()) {
172     PW_LOG_ERROR(
173         "(CID 0x%X) Buffer is too small for L2CAP K-frame. So stopping channel "
174         "& reporting it needs to be closed.",
175         local_cid());
176     StopChannelAndReportError(Event::kRxInvalid);
177     return true;
178   }
179   uint16_t sdu_length = kframe_view->sdu_length().Read();
180   uint16_t payload_size = kframe_view->payload_size().Read();
181 
182   // Core Spec v6.0 Vol 3, Part A, 3.4.3: "If the SDU length field value exceeds
183   // the receiver's MTU, the receiver shall disconnect the channel."
184   if (sdu_length > rx_mtu_) {
185     PW_LOG_ERROR(
186         "(CID 0x%X) Rx K-frame SDU exceeds MTU. So stopping channel & "
187         "reporting it needs to be closed.",
188         local_cid());
189     StopChannelAndReportError(Event::kRxInvalid);
190     return true;
191   }
192 
193   // TODO: https://pwbug.dev/360932103 - Support SDU de-segmentation.
194   // We don't support SDU de-segmentation yet. If we see a SDU size larger than
195   // the current PDU size, we ignore that first PDU and all remaining PDUs for
196   // that SDU (which we track via remaining bytes expected for the SDU).
197   if (sdu_length > payload_size) {
198     PW_LOG_ERROR(
199         "(CID 0x%X) Encountered segmented L2CAP SDU (which is not yet "
200         "supported). So will drop all PDUs in SDU.",
201         local_cid());
202     remaining_sdu_bytes_to_ignore_ = sdu_length - payload_size;
203     return true;
204   }
205 
206   // Core Spec v6.0 Vol 3, Part A, 3.4.3: "If the payload size of any K-frame
207   // exceeds the receiver's MPS, the receiver shall disconnect the channel."
208   if (payload_size > rx_mps_) {
209     PW_LOG_ERROR(
210         "(CID 0x%X) Rx K-frame payload exceeds MPU. So stopping channel & "
211         "reporting it needs to be closed.",
212         local_cid());
213     StopChannelAndReportError(Event::kRxInvalid);
214     return true;
215   }
216 
217   SendPayloadFromControllerToClient(pw::span(
218       const_cast<uint8_t*>(kframe_view->payload().BackingStorage().data()),
219       kframe_view->payload_size().Read()));
220   return true;
221 }
222 
HandlePduFromHost(pw::span<uint8_t>)223 bool L2capCoc::HandlePduFromHost(pw::span<uint8_t>) PW_LOCKS_EXCLUDED(mutex_) {
224   // Always forward data from host to controller
225   return false;
226 }
227 
L2capCoc(L2capChannelManager & l2cap_channel_manager,uint16_t connection_handle,CocConfig rx_config,CocConfig tx_config,pw::Function<void (pw::span<uint8_t> payload)> && receive_fn,pw::Function<void (Event event)> && event_fn)228 L2capCoc::L2capCoc(L2capChannelManager& l2cap_channel_manager,
229                    uint16_t connection_handle,
230                    CocConfig rx_config,
231                    CocConfig tx_config,
232                    pw::Function<void(pw::span<uint8_t> payload)>&& receive_fn,
233                    pw::Function<void(Event event)>&& event_fn)
234     : L2capWriteChannel(l2cap_channel_manager,
235                         connection_handle,
236                         AclTransportType::kLe,
237                         tx_config.cid),
238       L2capReadChannel(l2cap_channel_manager,
239                        std::move(receive_fn),
240                        connection_handle,
241                        rx_config.cid),
242       state_(CocState::kRunning),
243       rx_mtu_(rx_config.mtu),
244       rx_mps_(rx_config.mps),
245       tx_mtu_(tx_config.mtu),
246       tx_mps_(tx_config.mps),
247       tx_credits_(tx_config.credits),
248       remaining_sdu_bytes_to_ignore_(0),
249       event_fn_(std::move(event_fn)) {}
250 
OnFragmentedPduReceived()251 void L2capCoc::OnFragmentedPduReceived() {
252   L2capReadChannel::OnFragmentedPduReceived();
253   StopChannelAndReportError(Event::kRxFragmented);
254 }
255 
StopChannelAndReportError(Event error)256 void L2capCoc::StopChannelAndReportError(Event error) {
257   Stop().IgnoreError();
258   if (event_fn_) {
259     event_fn_(error);
260   }
261 }
262 
DequeuePacket()263 std::optional<H4PacketWithH4> L2capCoc::DequeuePacket() {
264   if (state_ == CocState::kStopped) {
265     return std::nullopt;
266   }
267 
268   std::lock_guard lock(mutex_);
269   if (tx_credits_ == 0) {
270     return std::nullopt;
271   }
272 
273   std::optional<H4PacketWithH4> maybe_packet =
274       L2capWriteChannel::DequeuePacket();
275   if (maybe_packet.has_value()) {
276     --tx_credits_;
277   }
278   return maybe_packet;
279 }
280 
AddCredits(uint16_t credits)281 void L2capCoc::AddCredits(uint16_t credits) {
282   if (state_ == CocState::kStopped) {
283     PW_LOG_ERROR(
284         "(CID 0x%X) Received credits on stopped CoC. So will ignore signal.",
285         local_cid());
286     return;
287   }
288 
289   bool credits_previously_zero;
290   {
291     std::lock_guard lock(mutex_);
292 
293     // Core Spec v6.0 Vol 3, Part A, 10.1: "The device receiving the credit
294     // packet shall disconnect the L2CAP channel if the credit count exceeds
295     // 65535."
296     if (credits > emboss::L2capLeCreditBasedConnectionReq::max_credit_value() -
297                       tx_credits_) {
298       StopChannelAndReportError(Event::kRxInvalid);
299       return;
300     }
301 
302     credits_previously_zero = tx_credits_ == 0;
303     tx_credits_ += credits;
304   }
305   if (credits_previously_zero) {
306     ReportPacketsMayBeReadyToSend();
307   }
308 }
309 
310 }  // namespace pw::bluetooth::proxy
311