1 //! This module takes the set of attempts from the AttemptManager, determines
2 //! the target state of the LE manager, and drives it to this target state
3
4 use std::collections::HashSet;
5
6 use log::info;
7
8 use crate::core::address::AddressWithType;
9
10 use super::{
11 attempt_manager::{ConnectionAttempt, ConnectionMode},
12 le_manager::LeAclManager,
13 };
14
15 /// This struct represents the target state of the LeManager based on the
16 /// set of all active connection attempts
17 pub struct TargetState {
18 /// These addresses should go to the LE background connect list
19 pub background_list: HashSet<AddressWithType>,
20 /// These addresses should go to the direct list (we are not connected to any of them)
21 pub direct_list: HashSet<AddressWithType>,
22 }
23
24 /// Takes a list of connection attempts, and determines the target state of the LE ACL manager
determine_target_state(attempts: &[ConnectionAttempt]) -> TargetState25 pub fn determine_target_state(attempts: &[ConnectionAttempt]) -> TargetState {
26 let background_list = attempts
27 .iter()
28 .filter(|attempt| attempt.mode == ConnectionMode::Background)
29 .map(|attempt| attempt.remote_address)
30 .collect();
31
32 let direct_list = attempts
33 .iter()
34 .filter(|attempt| attempt.mode == ConnectionMode::Direct)
35 .map(|attempt| attempt.remote_address)
36 .collect();
37
38 TargetState { background_list, direct_list }
39 }
40
41 /// This struct monitors the state of the LE connect list,
42 /// and drives it to the target state.
43 #[derive(Debug)]
44 pub struct LeAcceptlistManager {
45 /// The connect list in the ACL manager
46 direct_list: HashSet<AddressWithType>,
47 /// The background connect list in the ACL manager
48 background_list: HashSet<AddressWithType>,
49 /// An interface into the LE ACL manager (le_impl.h)
50 le_manager: Box<dyn LeAclManager>,
51 }
52
53 impl LeAcceptlistManager {
54 /// Constructor
new(le_manager: impl LeAclManager + 'static) -> Self55 pub fn new(le_manager: impl LeAclManager + 'static) -> Self {
56 Self {
57 direct_list: HashSet::new(),
58 background_list: HashSet::new(),
59 le_manager: Box::new(le_manager),
60 }
61 }
62
63 /// The state of the LE connect list (as per le_impl.h) updates on a completed connection
on_connect_complete(&mut self, address: AddressWithType)64 pub fn on_connect_complete(&mut self, address: AddressWithType) {
65 if address == AddressWithType::EMPTY {
66 return;
67 }
68 // le_impl pulls the device out of the direct connect list (but not the background list) on connection (regardless of status)
69 self.direct_list.remove(&address);
70 }
71
72 /// Drive the state of the connect list to the target state
drive_to_state(&mut self, target: TargetState)73 pub fn drive_to_state(&mut self, target: TargetState) {
74 // First, pull out anything in the ACL manager that we don't need
75 // recall that cancel_connect() removes addresses from *both* lists (!)
76 for address in self.direct_list.difference(&target.direct_list) {
77 info!("Cancelling connection attempt to {address:?}");
78 self.le_manager.remove_from_all_lists(*address);
79 self.background_list.remove(address);
80 }
81 self.direct_list = self.direct_list.intersection(&target.direct_list).copied().collect();
82
83 for address in self.background_list.difference(&target.background_list) {
84 info!("Cancelling connection attempt to {address:?}");
85 self.le_manager.remove_from_all_lists(*address);
86 self.direct_list.remove(address);
87 }
88 self.background_list =
89 self.background_list.intersection(&target.background_list).copied().collect();
90
91 // now everything extra has been removed, we can put things back in
92 for address in target.direct_list.difference(&self.direct_list) {
93 info!("Starting direct connection to {address:?}");
94 self.le_manager.add_to_direct_list(*address);
95 }
96 for address in target.background_list.difference(&self.background_list) {
97 info!("Starting background connection to {address:?}");
98 self.le_manager.add_to_background_list(*address);
99 }
100
101 // we should now be in a consistent state!
102 self.direct_list = target.direct_list;
103 self.background_list = target.background_list;
104 }
105 }
106
107 #[cfg(test)]
108 mod test {
109 use crate::{
110 connection::{
111 le_manager::ErrorCode, mocks::mock_le_manager::MockActiveLeAclManager,
112 ConnectionManagerClient,
113 },
114 core::address::AddressType,
115 };
116
117 use super::*;
118
119 const CLIENT: ConnectionManagerClient = ConnectionManagerClient::GattClient(1);
120
121 const ADDRESS_1: AddressWithType =
122 AddressWithType { address: [1, 2, 3, 4, 5, 6], address_type: AddressType::Public };
123 const ADDRESS_2: AddressWithType =
124 AddressWithType { address: [1, 2, 3, 4, 5, 6], address_type: AddressType::Random };
125 const ADDRESS_3: AddressWithType =
126 AddressWithType { address: [1, 2, 3, 4, 5, 7], address_type: AddressType::Random };
127
128 #[test]
test_determine_target_state()129 fn test_determine_target_state() {
130 let target = determine_target_state(&[
131 ConnectionAttempt {
132 client: CLIENT,
133 mode: ConnectionMode::Background,
134 remote_address: ADDRESS_1,
135 },
136 ConnectionAttempt {
137 client: CLIENT,
138 mode: ConnectionMode::Background,
139 remote_address: ADDRESS_1,
140 },
141 ConnectionAttempt {
142 client: CLIENT,
143 mode: ConnectionMode::Background,
144 remote_address: ADDRESS_2,
145 },
146 ConnectionAttempt {
147 client: CLIENT,
148 mode: ConnectionMode::Direct,
149 remote_address: ADDRESS_2,
150 },
151 ConnectionAttempt {
152 client: CLIENT,
153 mode: ConnectionMode::Direct,
154 remote_address: ADDRESS_3,
155 },
156 ]);
157
158 assert_eq!(target.background_list.len(), 2);
159 assert!(target.background_list.contains(&ADDRESS_1));
160 assert!(target.background_list.contains(&ADDRESS_2));
161 assert_eq!(target.direct_list.len(), 2);
162 assert!(target.direct_list.contains(&ADDRESS_2));
163 assert!(target.direct_list.contains(&ADDRESS_3));
164 }
165
166 #[test]
test_add_to_direct_list()167 fn test_add_to_direct_list() {
168 // arrange
169 let mock_le_manager = MockActiveLeAclManager::new();
170 let mut manager = LeAcceptlistManager::new(mock_le_manager.clone());
171
172 // act: request a device to be present in the direct list
173 manager.drive_to_state(TargetState {
174 background_list: [].into(),
175 direct_list: [ADDRESS_1].into(),
176 });
177
178 // assert: that the device has been added
179 assert_eq!(mock_le_manager.current_connection_mode(), Some(ConnectionMode::Direct));
180 assert_eq!(mock_le_manager.current_acceptlist().len(), 1);
181 assert!(mock_le_manager.current_acceptlist().contains(&ADDRESS_1));
182 }
183
184 #[test]
test_add_to_background_list()185 fn test_add_to_background_list() {
186 // arrange
187 let mock_le_manager = MockActiveLeAclManager::new();
188 let mut manager = LeAcceptlistManager::new(mock_le_manager.clone());
189
190 // act: request a device to be present in the direct list
191 manager.drive_to_state(TargetState {
192 background_list: [ADDRESS_1].into(),
193 direct_list: [].into(),
194 });
195
196 // assert: that the device has been added
197 assert_eq!(mock_le_manager.current_connection_mode(), Some(ConnectionMode::Background));
198 assert_eq!(mock_le_manager.current_acceptlist().len(), 1);
199 assert!(mock_le_manager.current_acceptlist().contains(&ADDRESS_1));
200 }
201
202 #[test]
test_background_connection_upgrade_to_direct()203 fn test_background_connection_upgrade_to_direct() {
204 // arrange: a pending background connection
205 let mock_le_manager = MockActiveLeAclManager::new();
206 let mut manager = LeAcceptlistManager::new(mock_le_manager.clone());
207 manager.drive_to_state(TargetState {
208 background_list: [ADDRESS_1].into(),
209 direct_list: [].into(),
210 });
211
212 // act: initiate a direct connection to the same device
213 manager.drive_to_state(TargetState {
214 background_list: [ADDRESS_1].into(),
215 direct_list: [ADDRESS_1].into(),
216 });
217
218 // assert: we are now doing a direct connection
219 assert_eq!(mock_le_manager.current_connection_mode(), Some(ConnectionMode::Direct));
220 }
221
222 #[test]
test_direct_connection_cancel_while_background()223 fn test_direct_connection_cancel_while_background() {
224 // arrange: a pending background connection
225 let mock_le_manager = MockActiveLeAclManager::new();
226 let mut manager = LeAcceptlistManager::new(mock_le_manager.clone());
227 manager.drive_to_state(TargetState {
228 background_list: [ADDRESS_1].into(),
229 direct_list: [].into(),
230 });
231
232 // act: initiate a direct connection to the same device, then remove it
233 manager.drive_to_state(TargetState {
234 background_list: [ADDRESS_1].into(),
235 direct_list: [ADDRESS_1].into(),
236 });
237 manager.drive_to_state(TargetState {
238 background_list: [ADDRESS_1].into(),
239 direct_list: [].into(),
240 });
241
242 // assert: we have returned to a background connection
243 assert_eq!(mock_le_manager.current_connection_mode(), Some(ConnectionMode::Background));
244 }
245
246 #[test]
test_direct_connection_cancel_then_resume_while_background()247 fn test_direct_connection_cancel_then_resume_while_background() {
248 // arrange: a pending background connection
249 let mock_le_manager = MockActiveLeAclManager::new();
250 let mut manager = LeAcceptlistManager::new(mock_le_manager.clone());
251 manager.drive_to_state(TargetState {
252 background_list: [ADDRESS_1].into(),
253 direct_list: [].into(),
254 });
255
256 // act: initiate a direct connection to the same device, cancel it, then resume
257 manager.drive_to_state(TargetState {
258 background_list: [ADDRESS_1].into(),
259 direct_list: [ADDRESS_1].into(),
260 });
261 manager.drive_to_state(TargetState {
262 background_list: [ADDRESS_1].into(),
263 direct_list: [].into(),
264 });
265 manager.drive_to_state(TargetState {
266 background_list: [ADDRESS_1].into(),
267 direct_list: [ADDRESS_1].into(),
268 });
269
270 // assert: we have returned to a direct connection
271 assert_eq!(mock_le_manager.current_connection_mode(), Some(ConnectionMode::Direct));
272 }
273
274 #[test]
test_remove_background_connection_then_add()275 fn test_remove_background_connection_then_add() {
276 // arrange
277 let mock_le_manager = MockActiveLeAclManager::new();
278 let mut manager = LeAcceptlistManager::new(mock_le_manager.clone());
279
280 // act: add then remove a background connection
281 manager.drive_to_state(TargetState {
282 background_list: [ADDRESS_1].into(),
283 direct_list: [].into(),
284 });
285 manager.drive_to_state(TargetState { background_list: [].into(), direct_list: [].into() });
286
287 // assert: we have stopped our connection
288 assert_eq!(mock_le_manager.current_connection_mode(), None);
289 }
290
291 #[test]
test_background_connection_remove_then_add()292 fn test_background_connection_remove_then_add() {
293 // arrange
294 let mock_le_manager = MockActiveLeAclManager::new();
295 let mut manager = LeAcceptlistManager::new(mock_le_manager.clone());
296
297 // act: add, remove, then re-add a background connection
298 manager.drive_to_state(TargetState {
299 background_list: [ADDRESS_1].into(),
300 direct_list: [].into(),
301 });
302 manager.drive_to_state(TargetState { background_list: [].into(), direct_list: [].into() });
303 manager.drive_to_state(TargetState {
304 background_list: [ADDRESS_1].into(),
305 direct_list: [].into(),
306 });
307
308 // assert: we resume our background connection
309 assert_eq!(mock_le_manager.current_connection_mode(), Some(ConnectionMode::Background));
310 }
311 #[test]
test_retry_direct_connection_after_disconnect()312 fn test_retry_direct_connection_after_disconnect() {
313 // arrange
314 let mock_le_manager = MockActiveLeAclManager::new();
315 let mut manager = LeAcceptlistManager::new(mock_le_manager.clone());
316
317 // act: initiate a direct connection
318 manager.drive_to_state(TargetState {
319 background_list: [].into(),
320 direct_list: [ADDRESS_1].into(),
321 });
322 // act: the connection succeeds (and later disconnects)
323 mock_le_manager.on_le_connect(ADDRESS_1, ErrorCode::SUCCESS);
324 manager.on_connect_complete(ADDRESS_1);
325 // the peer later disconnects
326 mock_le_manager.on_le_disconnect(ADDRESS_1);
327 // act: retry the direct connection
328 manager.drive_to_state(TargetState {
329 background_list: [].into(),
330 direct_list: [ADDRESS_1].into(),
331 });
332
333 // assert: we have resumed the direct connection
334 assert_eq!(mock_le_manager.current_connection_mode(), Some(ConnectionMode::Direct));
335 assert_eq!(mock_le_manager.current_acceptlist().len(), 1);
336 assert!(mock_le_manager.current_acceptlist().contains(&ADDRESS_1));
337 }
338
339 #[test]
test_background_connection_remove_then_add_while_direct()340 fn test_background_connection_remove_then_add_while_direct() {
341 // arrange: a pending direct connection
342 let mock_le_manager = MockActiveLeAclManager::new();
343 let mut manager = LeAcceptlistManager::new(mock_le_manager.clone());
344 manager.drive_to_state(TargetState {
345 background_list: [].into(),
346 direct_list: [ADDRESS_1].into(),
347 });
348
349 // act: add, remove, then re-add a background connection
350 manager.drive_to_state(TargetState {
351 background_list: [ADDRESS_1].into(),
352 direct_list: [ADDRESS_1].into(),
353 });
354 manager.drive_to_state(TargetState {
355 background_list: [].into(),
356 direct_list: [ADDRESS_1].into(),
357 });
358 manager.drive_to_state(TargetState {
359 background_list: [ADDRESS_1].into(),
360 direct_list: [ADDRESS_1].into(),
361 });
362
363 // assert: we remain doing our direct connection
364 assert_eq!(mock_le_manager.current_connection_mode(), Some(ConnectionMode::Direct));
365 }
366 }
367