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