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