1 //! This module mocks the behavior of le_impl in GD (excluding timers).
2 //! It tracks both the internal state of le_impl, as well as the connect list in the controller.
3 //! It also enforces all (implicit) invariants of le_impl as documented in le_manager.rs, and
4 //! asserts on violation.
5 
6 use std::{cell::RefCell, collections::HashSet, fmt::Debug, rc::Rc};
7 
8 use crate::{
9     connection::{
10         attempt_manager::ConnectionMode,
11         le_manager::{
12             ErrorCode, InactiveLeAclManager, LeAclManager, LeAclManagerConnectionCallbacks,
13         },
14         LeConnection,
15     },
16     core::address::AddressWithType,
17 };
18 
19 #[derive(Clone)]
20 pub struct MockLeAclManager {
21     active: Rc<RefCell<Option<Rc<MockActiveLeAclManager>>>>,
22     callbacks: Rc<RefCell<Option<Box<dyn LeAclManagerConnectionCallbacks>>>>,
23 }
24 
25 impl MockLeAclManager {
new() -> Self26     pub fn new() -> Self {
27         Self { active: Rc::new(RefCell::new(None)), callbacks: Rc::new(RefCell::new(None)) }
28     }
29 
inner(&self) -> Rc<MockActiveLeAclManager>30     fn inner(&self) -> Rc<MockActiveLeAclManager> {
31         self.active.borrow().as_ref().unwrap().clone()
32     }
33 
current_acceptlist(&self) -> HashSet<AddressWithType>34     pub fn current_acceptlist(&self) -> HashSet<AddressWithType> {
35         self.inner().current_acceptlist()
36     }
37 
current_connection_mode(&self) -> Option<ConnectionMode>38     pub fn current_connection_mode(&self) -> Option<ConnectionMode> {
39         self.inner().current_connection_mode()
40     }
41 
on_le_connect(&self, address: AddressWithType, status: ErrorCode)42     pub fn on_le_connect(&self, address: AddressWithType, status: ErrorCode) {
43         let inner = self.inner();
44         inner.on_le_connect(address, status);
45         drop(inner);
46 
47         if status == ErrorCode::SUCCESS {
48             self.callbacks
49                 .borrow()
50                 .as_deref()
51                 .unwrap()
52                 .on_le_connect(address, Ok(LeConnection { remote_address: address }));
53         } else {
54             self.callbacks.borrow().as_deref().unwrap().on_le_connect(address, Err(status));
55         }
56     }
57 
on_le_disconnect(&self, address: AddressWithType)58     pub fn on_le_disconnect(&self, address: AddressWithType) {
59         let inner = self.inner();
60         inner.on_le_disconnect(address);
61         drop(inner);
62 
63         self.callbacks.borrow().as_deref().unwrap().on_disconnect(address);
64     }
65 }
66 
67 impl InactiveLeAclManager for MockLeAclManager {
68     type ActiveManager = Rc<MockActiveLeAclManager>;
69 
register_callbacks( self, callbacks: impl LeAclManagerConnectionCallbacks + 'static, ) -> Self::ActiveManager70     fn register_callbacks(
71         self,
72         callbacks: impl LeAclManagerConnectionCallbacks + 'static,
73     ) -> Self::ActiveManager {
74         let out = MockActiveLeAclManager::new();
75         *self.active.borrow_mut() = Some(out.clone());
76         *self.callbacks.borrow_mut() = Some(Box::new(callbacks));
77         out
78     }
79 }
80 
81 #[derive(Debug)]
82 pub struct MockActiveLeAclManager {
83     state: RefCell<MockLeManagerInternalState>,
84 }
85 
86 #[derive(Clone, Debug)]
87 struct MockLeManagerInternalState {
88     direct_connect_list: HashSet<AddressWithType>,
89     background_connect_list: HashSet<AddressWithType>,
90     currently_connected: HashSet<AddressWithType>,
91 }
92 
93 impl MockActiveLeAclManager {
new() -> Rc<Self>94     pub fn new() -> Rc<Self> {
95         Rc::new(MockActiveLeAclManager {
96             state: RefCell::new(MockLeManagerInternalState {
97                 direct_connect_list: HashSet::new(),
98                 background_connect_list: HashSet::new(),
99                 currently_connected: HashSet::new(),
100             }),
101         })
102     }
103 
current_acceptlist(&self) -> HashSet<AddressWithType>104     pub fn current_acceptlist(&self) -> HashSet<AddressWithType> {
105         let state = self.state.borrow();
106         &(&state.direct_connect_list | &state.background_connect_list)
107             - (&state.currently_connected)
108     }
109 
current_connection_mode(&self) -> Option<ConnectionMode>110     pub fn current_connection_mode(&self) -> Option<ConnectionMode> {
111         let state = self.state.borrow();
112 
113         if !state.direct_connect_list.is_empty() {
114             Some(ConnectionMode::Direct)
115         } else if state
116             .background_connect_list
117             .difference(&state.currently_connected)
118             .next()
119             .is_some()
120         {
121             Some(ConnectionMode::Background)
122         } else {
123             None
124         }
125     }
126 
on_le_connect(&self, address: AddressWithType, status: ErrorCode)127     pub fn on_le_connect(&self, address: AddressWithType, status: ErrorCode) {
128         let mut state = self.state.borrow_mut();
129         state.direct_connect_list.remove(&address);
130         if status == ErrorCode::SUCCESS {
131             let ok = state.currently_connected.insert(address);
132             assert!(ok, "Already connected");
133         }
134     }
135 
on_le_disconnect(&self, address: AddressWithType)136     pub fn on_le_disconnect(&self, address: AddressWithType) {
137         let mut state = self.state.borrow_mut();
138         let ok = state.currently_connected.remove(&address);
139         assert!(ok, "Not connected");
140     }
141 }
142 
143 impl LeAclManager for Rc<MockActiveLeAclManager> {
add_to_direct_list(&self, address: AddressWithType)144     fn add_to_direct_list(&self, address: AddressWithType) {
145         let mut state = self.state.borrow_mut();
146         assert!(
147             !state.currently_connected.contains(&address),
148             "Must NOT be currently connected to this address"
149         );
150         let ok = state.direct_connect_list.insert(address);
151         assert!(ok, "Already in direct connect list");
152     }
153 
add_to_background_list(&self, address: AddressWithType)154     fn add_to_background_list(&self, address: AddressWithType) {
155         let mut state = self.state.borrow_mut();
156         assert!(
157             !state.currently_connected.contains(&address),
158             "Must NOT be currently connected to this address"
159         );
160         let ok = state.background_connect_list.insert(address);
161         assert!(ok, "Already in background connect list");
162     }
163 
remove_from_all_lists(&self, address: AddressWithType)164     fn remove_from_all_lists(&self, address: AddressWithType) {
165         let mut state = self.state.borrow_mut();
166         assert!(
167             !state.currently_connected.contains(&address),
168             "Must NOT be currently connected to this address"
169         );
170         let ok1 = state.direct_connect_list.remove(&address);
171         let ok2 = state.background_connect_list.remove(&address);
172         assert!(ok1 || ok2, "Present in neither direct nor background connect list");
173     }
174 }
175