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_signaling_channel.h"
16
17 #include <mutex>
18 #include <optional>
19
20 #include "pw_bluetooth/emboss_util.h"
21 #include "pw_bluetooth/hci_data.emb.h"
22 #include "pw_bluetooth/l2cap_frames.emb.h"
23 #include "pw_bluetooth_proxy/h4_packet.h"
24 #include "pw_bluetooth_proxy/internal/l2cap_channel_manager.h"
25 #include "pw_bluetooth_proxy/internal/l2cap_coc_internal.h"
26 #include "pw_bluetooth_proxy/l2cap_channel_common.h"
27 #include "pw_log/log.h"
28 #include "pw_multibuf/allocator.h"
29 #include "pw_span/cast.h"
30 #include "pw_status/status.h"
31 #include "pw_status/try.h"
32
33 namespace pw::bluetooth::proxy {
34
L2capSignalingChannel(L2capChannelManager & l2cap_channel_manager,uint16_t connection_handle,AclTransportType transport,uint16_t fixed_cid)35 L2capSignalingChannel::L2capSignalingChannel(
36 L2capChannelManager& l2cap_channel_manager,
37 uint16_t connection_handle,
38 AclTransportType transport,
39 uint16_t fixed_cid)
40 : BasicL2capChannel(l2cap_channel_manager,
41 /*rx_multibuf_allocator=*/nullptr,
42 /*connection_handle=*/connection_handle,
43 /*transport*/ transport,
44 /*local_cid=*/fixed_cid,
45 /*remote_cid=*/fixed_cid,
46 /*payload_from_controller_fn=*/nullptr,
47 /*payload_from_host_fn=*/nullptr,
48 /*event_fn=*/nullptr),
49 l2cap_channel_manager_(l2cap_channel_manager) {}
50
operator =(L2capSignalingChannel && other)51 L2capSignalingChannel& L2capSignalingChannel::operator=(
52 L2capSignalingChannel&& other) {
53 std::lock_guard lock(mutex_);
54 std::lock_guard other_lock(other.mutex_);
55 pending_connections_ = std::move(other.pending_connections_);
56
57 BasicL2capChannel::operator=(std::move(other));
58 return *this;
59 }
60
DoHandlePduFromController(pw::span<uint8_t> cframe)61 bool L2capSignalingChannel::DoHandlePduFromController(
62 pw::span<uint8_t> cframe) {
63 Result<emboss::CFrameView> cframe_view =
64 MakeEmbossView<emboss::CFrameView>(cframe);
65 if (!cframe_view.ok()) {
66 PW_LOG_ERROR(
67 "Buffer is too small for C-frame. So will forward to host without "
68 "processing.");
69 return false;
70 }
71
72 // TODO: https://pwbug.dev/360929142 - "If a device receives a C-frame that
73 // exceeds its L2CAP_SIG_MTU_SIZE then it shall send an
74 // L2CAP_COMMAND_REJECT_RSP packet containing the supported
75 // L2CAP_SIG_MTU_SIZE." We should consider taking the signaling MTU in the
76 // ProxyHost constructor.
77 return OnCFramePayload(
78 Direction::kFromController,
79 pw::span(cframe_view->payload().BackingStorage().data(),
80 cframe_view->payload().BackingStorage().SizeInBytes()));
81 }
82
HandlePduFromHost(pw::span<uint8_t> cframe)83 bool L2capSignalingChannel::HandlePduFromHost(pw::span<uint8_t> cframe) {
84 Result<emboss::CFrameView> cframe_view =
85 MakeEmbossView<emboss::CFrameView>(cframe);
86 if (!cframe_view.ok()) {
87 PW_LOG_ERROR(
88 "Buffer is too small for C-frame. So will forward to controller "
89 "without processing.");
90 return false;
91 }
92
93 return OnCFramePayload(
94 Direction::kFromHost,
95 pw::span(cframe_view->payload().BackingStorage().data(),
96 cframe_view->payload().BackingStorage().SizeInBytes()));
97 }
98
HandleL2capSignalingCommand(Direction direction,emboss::L2capSignalingCommandView cmd)99 bool L2capSignalingChannel::HandleL2capSignalingCommand(
100 Direction direction, emboss::L2capSignalingCommandView cmd) {
101 PW_MODIFY_DIAGNOSTICS_PUSH();
102 PW_MODIFY_DIAGNOSTIC(ignored, "-Wswitch-enum");
103 switch (cmd.command_header().code().Read()) {
104 case emboss::L2capSignalingPacketCode::CONNECTION_REQ: {
105 Result<emboss::L2capConnectionReqView> connection_req_cmd =
106 MakeEmbossView<emboss::L2capConnectionReqView>(
107 cmd.BackingStorage().data(), cmd.SizeInBytes());
108 if (!connection_req_cmd.ok()) {
109 return false;
110 }
111 HandleConnectionReq(direction, *connection_req_cmd);
112 return false;
113 }
114 case emboss::L2capSignalingPacketCode::CONNECTION_RSP: {
115 Result<emboss::L2capConnectionRspView> connection_rsp_cmd =
116 MakeEmbossView<emboss::L2capConnectionRspView>(
117 cmd.BackingStorage().data(), cmd.SizeInBytes());
118 if (!connection_rsp_cmd.ok()) {
119 return false;
120 }
121 HandleConnectionRsp(direction, *connection_rsp_cmd);
122 return false;
123 }
124 case emboss::L2capSignalingPacketCode::DISCONNECTION_REQ: {
125 Result<emboss::L2capDisconnectionReqView> disconnection_req_cmd =
126 MakeEmbossView<emboss::L2capDisconnectionReqView>(
127 cmd.BackingStorage().data(), cmd.SizeInBytes());
128 if (!disconnection_req_cmd.ok()) {
129 return false;
130 }
131 HandleDisconnectionReq(direction, *disconnection_req_cmd);
132 return false;
133 }
134 case emboss::L2capSignalingPacketCode::DISCONNECTION_RSP: {
135 Result<emboss::L2capDisconnectionRspView> disconnection_rsp_cmd =
136 MakeEmbossView<emboss::L2capDisconnectionRspView>(
137 cmd.BackingStorage().data(), cmd.SizeInBytes());
138 if (!disconnection_rsp_cmd.ok()) {
139 return false;
140 }
141 HandleDisconnectionRsp(direction, *disconnection_rsp_cmd);
142 return false;
143 }
144 case emboss::L2capSignalingPacketCode::FLOW_CONTROL_CREDIT_IND: {
145 return HandleFlowControlCreditInd(
146 emboss::MakeL2capFlowControlCreditIndView(cmd.BackingStorage().data(),
147 cmd.SizeInBytes()));
148 }
149 default: {
150 return false;
151 }
152 }
153 PW_MODIFY_DIAGNOSTICS_POP();
154 }
155
HandleConnectionReq(Direction direction,emboss::L2capConnectionReqView cmd)156 void L2capSignalingChannel::HandleConnectionReq(
157 Direction direction, emboss::L2capConnectionReqView cmd) {
158 std::lock_guard lock(mutex_);
159 if (pending_connections_.full()) {
160 PW_LOG_ERROR("Reached max number of tracked pending l2cap connections.");
161 return;
162 }
163 pending_connections_.push_back({.direction = direction,
164 .source_cid = cmd.source_cid().Read(),
165 .psm = cmd.psm().Read()});
166 }
167
HandleConnectionRsp(Direction direction,emboss::L2capConnectionRspView cmd)168 void L2capSignalingChannel::HandleConnectionRsp(
169 Direction direction, emboss::L2capConnectionRspView cmd) {
170 std::lock_guard lock(mutex_);
171 // Match this response to an existing pending connection request. Flip the
172 // response's direction since we will be looking for a match with the
173 // request's direction
174 Direction request_direction = direction == Direction::kFromHost
175 ? Direction::kFromController
176 : Direction::kFromHost;
177
178 struct {
179 Direction direction;
180 uint16_t source_cid;
181 } match_info = {.direction = request_direction,
182 .source_cid = cmd.source_cid().Read()};
183
184 auto match = [&match_info](const PendingConnection& pending) {
185 // TODO: https://pwbug.dev/379558046 - Consider using identifier instead to
186 // match request to response
187 return pending.source_cid == match_info.source_cid &&
188 pending.direction == match_info.direction;
189 };
190 auto pending_it = std::find_if(
191 pending_connections_.begin(), pending_connections_.end(), match);
192 if (pending_it == pending_connections_.end()) {
193 PW_LOG_WARN("No match found for l2cap connection");
194 return;
195 }
196
197 switch (cmd.result().Read()) {
198 case emboss::L2capConnectionRspResultCode::SUCCESSFUL: {
199 // We now have complete connection info
200 l2cap_channel_manager_.HandleConnectionComplete(
201 L2capChannelConnectionInfo{
202 .direction = request_direction,
203 .psm = pending_it->psm,
204 .connection_handle = connection_handle(),
205 .remote_cid = cmd.source_cid().Read(),
206 .local_cid = cmd.destination_cid().Read(),
207 });
208 pending_connections_.erase(pending_it);
209
210 break;
211 }
212 case emboss::L2capConnectionRspResultCode::PENDING: {
213 // Leave pending if result is pending.
214 break;
215 }
216 case emboss::L2capConnectionRspResultCode::PSM_NOT_SUPPORTED:
217 case emboss::L2capConnectionRspResultCode::SECURITY_BLOCK:
218 case emboss::L2capConnectionRspResultCode::NO_RESOURCES_AVAILABLE:
219 case emboss::L2capConnectionRspResultCode::INVALID_SOURCE_CID:
220 case emboss::L2capConnectionRspResultCode::SOURCE_CID_ALREADY_ALLOCATED:
221 default: {
222 // All other codes mean the connection has failed.
223 pending_connections_.erase(pending_it);
224 break;
225 }
226 }
227 }
228
HandleDisconnectionReq(Direction,emboss::L2capDisconnectionReqView)229 void L2capSignalingChannel::HandleDisconnectionReq(
230 Direction, emboss::L2capDisconnectionReqView) {
231 // TODO: https://pwbug.dev/379558046 - On reception of this event we should
232 // stop queueing data on this connection.
233 }
234
HandleDisconnectionRsp(Direction,emboss::L2capDisconnectionRspView cmd)235 void L2capSignalingChannel::HandleDisconnectionRsp(
236 Direction, emboss::L2capDisconnectionRspView cmd) {
237 l2cap_channel_manager_.HandleDisconnectionCompleteLocked(
238 L2capStatusTracker::DisconnectParams{
239 .connection_handle = connection_handle(),
240 .remote_cid = cmd.source_cid().Read(),
241 .local_cid = cmd.destination_cid().Read()});
242 }
243
HandleFlowControlCreditInd(emboss::L2capFlowControlCreditIndView cmd)244 bool L2capSignalingChannel::HandleFlowControlCreditInd(
245 emboss::L2capFlowControlCreditIndView cmd) PW_NO_LOCK_SAFETY_ANALYSIS {
246 // This function is always called with L2capChannelManager channels lock held,
247 // but we can't assert that with annotations since we don't have a complete
248 // type for L2capChannelManager in l2cap_channel.h.
249 // TODO: https://pwbug.dev/390511432 - Figure out way to add annotations to
250 // enforce this invariant.
251
252 if (!cmd.IsComplete()) {
253 PW_LOG_ERROR(
254 "Buffer is too small for L2CAP_FLOW_CONTROL_CREDIT_IND. So will "
255 "forward to host without processing.");
256 return false;
257 }
258
259 // Since this is called as a result of handling a received packet, the
260 // channels lock is already held so we should use the *Locked variant to
261 // lookup.
262 L2capChannel* found_channel =
263 l2cap_channel_manager_.FindChannelByRemoteCidLocked(connection_handle(),
264 cmd.cid().Read());
265 if (found_channel) {
266 // If this L2CAP_FLOW_CONTROL_CREDIT_IND is addressed to a channel managed
267 // by the proxy, it must be an L2CAP connection-oriented channel.
268 // TODO: https://pwbug.dev/360929142 - Validate type in case remote peer
269 // sends indication addressed to wrong CID.
270 L2capCocInternal* coc_ptr = static_cast<L2capCocInternal*>(found_channel);
271 coc_ptr->AddTxCredits(cmd.credits().Read());
272 return true;
273 }
274 return false;
275 }
276
SendFlowControlCreditInd(uint16_t cid,uint16_t credits,multibuf::MultiBufAllocator & multibuf_allocator)277 Status L2capSignalingChannel::SendFlowControlCreditInd(
278 uint16_t cid,
279 uint16_t credits,
280 multibuf::MultiBufAllocator& multibuf_allocator) {
281 if (cid == 0) {
282 PW_LOG_ERROR("Tried to send signaling packet on invalid CID 0x0.");
283 return Status::InvalidArgument();
284 }
285
286 std::optional<pw::multibuf::MultiBuf> command =
287 multibuf_allocator.AllocateContiguous(
288 emboss::L2capFlowControlCreditInd::IntrinsicSizeInBytes());
289 if (!command.has_value()) {
290 PW_LOG_ERROR(
291 "btproxy: SendFlowControlCreditInd unable to allocate command buffer "
292 "from provided multibuf_allocator. cid: %#x, credits: %#x",
293 cid,
294 credits);
295 return Status::Unavailable();
296 }
297 std::optional<ByteSpan> command_span = command->ContiguousSpan();
298
299 Result<emboss::L2capFlowControlCreditIndWriter> command_view =
300 MakeEmbossWriter<emboss::L2capFlowControlCreditIndWriter>(
301 pw::span_cast<uint8_t>(command_span.value()));
302 PW_CHECK(command_view->IsComplete());
303
304 command_view->command_header().code().Write(
305 emboss::L2capSignalingPacketCode::FLOW_CONTROL_CREDIT_IND);
306 command_view->command_header().identifier().Write(
307 GetNextIdentifierAndIncrement());
308 command_view->command_header().data_length().Write(
309 emboss::L2capFlowControlCreditInd::IntrinsicSizeInBytes() -
310 emboss::L2capSignalingCommandHeader::IntrinsicSizeInBytes());
311 command_view->cid().Write(cid);
312 command_view->credits().Write(credits);
313 PW_CHECK(command_view->Ok());
314
315 StatusWithMultiBuf s = WriteDuringRx(*std::move(command));
316
317 return s.status;
318 }
319
GetNextIdentifierAndIncrement()320 uint8_t L2capSignalingChannel::GetNextIdentifierAndIncrement() {
321 if (next_identifier_ == UINT8_MAX) {
322 next_identifier_ = 1;
323 return UINT8_MAX;
324 }
325 return next_identifier_++;
326 }
327
328 } // namespace pw::bluetooth::proxy
329