xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/l2cap/fake_l2cap.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2023 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_sapphire/internal/host/l2cap/fake_l2cap.h"
16 
17 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
18 #include "pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h"
19 #include "pw_bluetooth_sapphire/internal/host/l2cap/types.h"
20 
21 namespace bt {
22 
23 using l2cap::testing::FakeChannel;
24 
25 namespace l2cap::testing {
26 namespace {
27 
28 // Use plausible ERTM parameters that do not necessarily match values in
29 // production. See Core Spec v5.0 Vol 3, Part A, Sec 5.4 for meanings.
30 constexpr uint8_t kErtmNFramesInTxWindow = 32;
31 constexpr uint8_t kErtmMaxTransmissions = 8;
32 constexpr uint16_t kMaxTxPduPayloadSize = 1024;
33 
34 }  // namespace
35 
IsLinkConnected(hci_spec::ConnectionHandle handle) const36 bool FakeL2cap::IsLinkConnected(hci_spec::ConnectionHandle handle) const {
37   auto link_iter = links_.find(handle);
38   if (link_iter == links_.end()) {
39     return false;
40   }
41   return link_iter->second.connected;
42 }
43 
TriggerLEConnectionParameterUpdate(hci_spec::ConnectionHandle handle,const hci_spec::LEPreferredConnectionParameters & params)44 void FakeL2cap::TriggerLEConnectionParameterUpdate(
45     hci_spec::ConnectionHandle handle,
46     const hci_spec::LEPreferredConnectionParameters& params) {
47   LinkData& link_data = ConnectedLinkData(handle);
48   link_data.le_conn_param_cb(params);
49 }
50 
ExpectOutboundL2capChannel(hci_spec::ConnectionHandle handle,l2cap::Psm psm,l2cap::ChannelId id,l2cap::ChannelId remote_id,l2cap::ChannelParameters params)51 void FakeL2cap::ExpectOutboundL2capChannel(hci_spec::ConnectionHandle handle,
52                                            l2cap::Psm psm,
53                                            l2cap::ChannelId id,
54                                            l2cap::ChannelId remote_id,
55                                            l2cap::ChannelParameters params) {
56   LinkData& link_data = GetLinkData(handle);
57   ChannelData chan_data;
58   chan_data.local_id = id;
59   chan_data.remote_id = remote_id;
60   chan_data.params = params;
61   link_data.expected_outbound_conns[psm].push(chan_data);
62 }
63 
TriggerInboundL2capChannel(hci_spec::ConnectionHandle handle,l2cap::Psm psm,l2cap::ChannelId id,l2cap::ChannelId remote_id,uint16_t max_tx_sdu_size)64 bool FakeL2cap::TriggerInboundL2capChannel(hci_spec::ConnectionHandle handle,
65                                            l2cap::Psm psm,
66                                            l2cap::ChannelId id,
67                                            l2cap::ChannelId remote_id,
68                                            uint16_t max_tx_sdu_size) {
69   LinkData& link_data = ConnectedLinkData(handle);
70   auto cb_iter = registered_services_.find(psm);
71 
72   // No service registered for the PSM.
73   if (cb_iter == registered_services_.end()) {
74     return false;
75   }
76 
77   l2cap::ChannelCallback& cb = cb_iter->second.channel_cb;
78   auto chan_params = cb_iter->second.channel_params;
79   auto mode = chan_params.mode.value_or(
80       l2cap::RetransmissionAndFlowControlMode::kBasic);
81   auto max_rx_sdu_size =
82       chan_params.max_rx_sdu_size.value_or(l2cap::kDefaultMTU);
83   auto channel_info =
84       l2cap::ChannelInfo::MakeBasicMode(max_rx_sdu_size, max_tx_sdu_size);
85   if (mode ==
86       l2cap::RetransmissionAndFlowControlMode::kEnhancedRetransmission) {
87     channel_info = l2cap::ChannelInfo::MakeEnhancedRetransmissionMode(
88         max_rx_sdu_size,
89         max_tx_sdu_size,
90         /*n_frames_in_tx_window=*/kErtmNFramesInTxWindow,
91         /*max_transmissions=*/kErtmMaxTransmissions,
92         /*max_tx_pdu_payload_size=*/kMaxTxPduPayloadSize);
93   }
94 
95   auto chan = OpenFakeChannel(&link_data, id, remote_id, channel_info);
96   cb(chan->GetWeakPtr());
97 
98   return true;
99 }
100 
TriggerLinkError(hci_spec::ConnectionHandle handle)101 void FakeL2cap::TriggerLinkError(hci_spec::ConnectionHandle handle) {
102   LinkData& link_data = ConnectedLinkData(handle);
103 
104   // Safely handle re-entrancy.
105   if (link_data.link_error_signaled) {
106     return;
107   }
108   link_data.link_error_signaled = true;
109 
110   for (auto chan_iter = link_data.channels_.begin();
111        chan_iter != link_data.channels_.end();) {
112     auto& [id, channel] = *chan_iter++;
113     channel->Close();
114     link_data.channels_.erase(id);
115   }
116   link_data.link_error_cb();
117 }
118 
AddACLConnection(hci_spec::ConnectionHandle handle,pw::bluetooth::emboss::ConnectionRole role,l2cap::LinkErrorCallback link_error_cb,l2cap::SecurityUpgradeCallback)119 ChannelManager::BrEdrFixedChannels FakeL2cap::AddACLConnection(
120     hci_spec::ConnectionHandle handle,
121     pw::bluetooth::emboss::ConnectionRole role,
122     l2cap::LinkErrorCallback link_error_cb,
123     l2cap::SecurityUpgradeCallback) {
124   LinkData* link = RegisterInternal(
125       handle, role, bt::LinkType::kACL, std::move(link_error_cb));
126   auto smp = OpenFakeFixedChannel(link, l2cap::kSMPChannelId);
127   return BrEdrFixedChannels{.smp = std::move(smp)};
128 }
129 
AddLEConnection(hci_spec::ConnectionHandle handle,pw::bluetooth::emboss::ConnectionRole role,l2cap::LinkErrorCallback link_error_cb,l2cap::LEConnectionParameterUpdateCallback conn_param_cb,l2cap::SecurityUpgradeCallback)130 ChannelManager::LEFixedChannels FakeL2cap::AddLEConnection(
131     hci_spec::ConnectionHandle handle,
132     pw::bluetooth::emboss::ConnectionRole role,
133     l2cap::LinkErrorCallback link_error_cb,
134     l2cap::LEConnectionParameterUpdateCallback conn_param_cb,
135     l2cap::SecurityUpgradeCallback) {
136   LinkData* data = RegisterInternal(
137       handle, role, bt::LinkType::kLE, std::move(link_error_cb));
138   data->le_conn_param_cb = std::move(conn_param_cb);
139 
140   // Open the ATT and SMP fixed channels.
141   auto att = OpenFakeFixedChannel(data, l2cap::kATTChannelId);
142   auto smp = OpenFakeFixedChannel(data, l2cap::kLESMPChannelId);
143   return LEFixedChannels{.att = att->GetWeakPtr(), .smp = smp->GetWeakPtr()};
144 }
145 
RemoveConnection(hci_spec::ConnectionHandle handle)146 void FakeL2cap::RemoveConnection(hci_spec::ConnectionHandle handle) {
147   links_.erase(handle);
148 }
149 
AssignLinkSecurityProperties(hci_spec::ConnectionHandle,sm::SecurityProperties)150 void FakeL2cap::AssignLinkSecurityProperties(hci_spec::ConnectionHandle,
151                                              sm::SecurityProperties) {
152   // TODO(armansito): implement
153 }
154 
RequestConnectionParameterUpdate(hci_spec::ConnectionHandle handle,hci_spec::LEPreferredConnectionParameters params,l2cap::ConnectionParameterUpdateRequestCallback request_cb)155 void FakeL2cap::RequestConnectionParameterUpdate(
156     hci_spec::ConnectionHandle handle,
157     hci_spec::LEPreferredConnectionParameters params,
158     l2cap::ConnectionParameterUpdateRequestCallback request_cb) {
159   bool response =
160       connection_parameter_update_request_responder_
161           ? connection_parameter_update_request_responder_(handle, params)
162           : true;
163   // Simulate async response.
164   (void)heap_dispatcher_.Post(
165       [request_cb = std::move(request_cb), response](pw::async::Context /*ctx*/,
166                                                      pw::Status status) {
167         if (status.ok()) {
168           request_cb(response);
169         }
170       });
171 }
172 
OpenL2capChannel(hci_spec::ConnectionHandle handle,l2cap::Psm psm,l2cap::ChannelParameters params,l2cap::ChannelCallback cb)173 void FakeL2cap::OpenL2capChannel(hci_spec::ConnectionHandle handle,
174                                  l2cap::Psm psm,
175                                  l2cap::ChannelParameters params,
176                                  l2cap::ChannelCallback cb) {
177   LinkData& link_data = ConnectedLinkData(handle);
178   auto psm_it = link_data.expected_outbound_conns.find(psm);
179 
180   PW_DCHECK(psm_it != link_data.expected_outbound_conns.end() &&
181                 !psm_it->second.empty(),
182             "Unexpected outgoing L2CAP connection (PSM %#.4x)",
183             psm);
184 
185   auto chan_data = psm_it->second.front();
186   psm_it->second.pop();
187 
188   auto mode =
189       params.mode.value_or(l2cap::RetransmissionAndFlowControlMode::kBasic);
190   auto max_rx_sdu_size = params.max_rx_sdu_size.value_or(l2cap::kMaxMTU);
191 
192   PW_CHECK(chan_data.params == params,
193            "Didn't receive expected L2CAP channel parameters (expected: "
194            "%s, found: %s)",
195            bt_str(chan_data.params),
196            bt_str(params));
197 
198   auto channel_info =
199       l2cap::ChannelInfo::MakeBasicMode(max_rx_sdu_size, l2cap::kDefaultMTU);
200   if (mode ==
201       l2cap::RetransmissionAndFlowControlMode::kEnhancedRetransmission) {
202     channel_info = l2cap::ChannelInfo::MakeEnhancedRetransmissionMode(
203         max_rx_sdu_size,
204         l2cap::kDefaultMTU,
205         /*n_frames_in_tx_window=*/kErtmNFramesInTxWindow,
206         /*max_transmissions=*/kErtmMaxTransmissions,
207         /*max_tx_pdu_payload_size=*/kMaxTxPduPayloadSize);
208   } else if (auto* credit_mode =
209                  std::get_if<l2cap::CreditBasedFlowControlMode>(&mode)) {
210     channel_info = l2cap::ChannelInfo::MakeCreditBasedFlowControlMode(
211         *credit_mode,
212         max_rx_sdu_size,
213         l2cap::kDefaultMTU,
214         l2cap::kMaxInboundPduPayloadSize,
215         /*remote_initial_credits*/ 0);
216   }
217 
218   auto fake_chan = OpenFakeChannel(
219       &link_data, chan_data.local_id, chan_data.remote_id, channel_info);
220   l2cap::Channel::WeakPtr chan;
221   if (fake_chan.is_alive()) {
222     chan = fake_chan->GetWeakPtr();
223   }
224 
225   // Simulate async channel creation process.
226   (void)heap_dispatcher_.Post(
227       [cb = std::move(cb), chan = std::move(chan)](pw::async::Context /*ctx*/,
228                                                    pw::Status status) {
229         if (status.ok()) {
230           cb(chan);
231         }
232       });
233 }
234 
RegisterService(l2cap::Psm psm,l2cap::ChannelParameters params,l2cap::ChannelCallback channel_callback)235 bool FakeL2cap::RegisterService(l2cap::Psm psm,
236                                 l2cap::ChannelParameters params,
237                                 l2cap::ChannelCallback channel_callback) {
238   PW_DCHECK(registered_services_.count(psm) == 0);
239   registered_services_.emplace(
240       psm, ServiceInfo(params, std::move(channel_callback)));
241   return true;
242 }
243 
UnregisterService(l2cap::Psm psm)244 void FakeL2cap::UnregisterService(l2cap::Psm psm) {
245   registered_services_.erase(psm);
246 }
247 
~FakeL2cap()248 FakeL2cap::~FakeL2cap() {
249   for (auto& link_it : links_) {
250     for (auto& psm_it : link_it.second.expected_outbound_conns) {
251       PW_DCHECK(psm_it.second.empty(),
252                 "didn't receive expected connection on PSM %#.4x",
253                 psm_it.first);
254     }
255   }
256 }
257 
RegisterInternal(hci_spec::ConnectionHandle handle,pw::bluetooth::emboss::ConnectionRole role,bt::LinkType link_type,l2cap::LinkErrorCallback link_error_cb)258 FakeL2cap::LinkData* FakeL2cap::RegisterInternal(
259     hci_spec::ConnectionHandle handle,
260     pw::bluetooth::emboss::ConnectionRole role,
261     bt::LinkType link_type,
262     l2cap::LinkErrorCallback link_error_cb) {
263   auto& data = GetLinkData(handle);
264   PW_DCHECK(
265       !data.connected, "connection handle re-used (handle: %#.4x)", handle);
266 
267   data.connected = true;
268   data.role = role;
269   data.type = link_type;
270   data.link_error_cb = std::move(link_error_cb);
271 
272   return &data;
273 }
274 
OpenFakeChannel(LinkData * link,l2cap::ChannelId id,l2cap::ChannelId remote_id,l2cap::ChannelInfo info)275 FakeChannel::WeakPtr FakeL2cap::OpenFakeChannel(LinkData* link,
276                                                 l2cap::ChannelId id,
277                                                 l2cap::ChannelId remote_id,
278                                                 l2cap::ChannelInfo info) {
279   FakeChannel::WeakPtr chan;
280   if (!simulate_open_channel_failure_) {
281     auto channel = std::make_unique<FakeChannel>(
282         id, remote_id, link->handle, link->type, info);
283     chan = channel->AsWeakPtr();
284     channel->SetLinkErrorCallback(
285         [this, handle = link->handle] { TriggerLinkError(handle); });
286     link->channels_.emplace(id, std::move(channel));
287   }
288 
289   if (chan_cb_) {
290     chan_cb_(chan);
291   }
292 
293   return chan;
294 }
295 
OpenFakeFixedChannel(LinkData * link,l2cap::ChannelId id)296 FakeChannel::WeakPtr FakeL2cap::OpenFakeFixedChannel(LinkData* link,
297                                                      l2cap::ChannelId id) {
298   return OpenFakeChannel(link, id, id);
299 }
300 
GetLinkData(hci_spec::ConnectionHandle handle)301 FakeL2cap::LinkData& FakeL2cap::GetLinkData(hci_spec::ConnectionHandle handle) {
302   auto [it, inserted] = links_.try_emplace(handle);
303   auto& data = it->second;
304   if (inserted) {
305     data.connected = false;
306     data.handle = handle;
307   }
308   return data;
309 }
310 
ConnectedLinkData(hci_spec::ConnectionHandle handle)311 FakeL2cap::LinkData& FakeL2cap::ConnectedLinkData(
312     hci_spec::ConnectionHandle handle) {
313   auto link_iter = links_.find(handle);
314   PW_DCHECK(
315       link_iter != links_.end(), "fake link not found (handle: %#.4x)", handle);
316   PW_DCHECK(link_iter->second.connected,
317             "fake link not connected yet (handle: %#.4x)",
318             handle);
319   return link_iter->second;
320 }
321 
322 }  // namespace l2cap::testing
323 }  // namespace bt
324