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