• 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/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