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