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