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