1 //! This module determines which GATT server should be exposed to a given connection.
2 
3 use std::collections::HashMap;
4 
5 use log::{error, info};
6 
7 use crate::gatt::ids::{AdvertiserId, ServerId, TransportIndex};
8 
9 /// This class is responsible for tracking which connections and advertising we
10 /// own, and using this information to decide what servers should be exposed to
11 /// a given connetion.
12 #[derive(Default)]
13 pub struct IsolationManager {
14     advertiser_to_server: HashMap<AdvertiserId, ServerId>,
15     transport_to_server: HashMap<TransportIndex, ServerId>,
16 }
17 
18 impl IsolationManager {
19     /// Constructor
new() -> Self20     pub fn new() -> Self {
21         IsolationManager {
22             advertiser_to_server: HashMap::new(),
23             transport_to_server: HashMap::new(),
24         }
25     }
26 
27     /// Link a given GATT server to an LE advertising set, so incoming
28     /// connections to this advertiser will be visible only by the linked
29     /// server
associate_server_with_advertiser( &mut self, server_id: ServerId, advertiser_id: AdvertiserId, )30     pub fn associate_server_with_advertiser(
31         &mut self,
32         server_id: ServerId,
33         advertiser_id: AdvertiserId,
34     ) {
35         info!("associating server {server_id:?} with advertising set {advertiser_id:?}");
36         let old = self.advertiser_to_server.insert(advertiser_id, server_id);
37         if let Some(old) = old {
38             error!("new server {server_id:?} associated with same advertiser {advertiser_id:?}, displacing old server {old:?}");
39         }
40     }
41 
42     /// Clear the server associated with this advertiser, if one exists
clear_advertiser(&mut self, advertiser_id: AdvertiserId)43     pub fn clear_advertiser(&mut self, advertiser_id: AdvertiserId) {
44         info!("removing server (if any) associated with advertiser {advertiser_id:?}");
45         self.advertiser_to_server.remove(&advertiser_id);
46     }
47 
48     /// Check if this transport is currently owned by the Rust stack
is_connection_isolated(&self, tcb_idx: TransportIndex) -> bool49     pub fn is_connection_isolated(&self, tcb_idx: TransportIndex) -> bool {
50         self.transport_to_server.contains_key(&tcb_idx)
51     }
52 
53     /// Check if this advertiser is tied to a private server
is_advertiser_isolated(&self, advertiser_id: AdvertiserId) -> bool54     pub fn is_advertiser_isolated(&self, advertiser_id: AdvertiserId) -> bool {
55         self.advertiser_to_server.contains_key(&advertiser_id)
56     }
57 
58     /// Look up the server_id tied to a given tcb_idx, if one exists
get_server_id(&self, tcb_idx: TransportIndex) -> Option<ServerId>59     pub fn get_server_id(&self, tcb_idx: TransportIndex) -> Option<ServerId> {
60         self.transport_to_server.get(&tcb_idx).copied()
61     }
62 
63     /// Remove all linked advertising sets from the provided server
64     ///
65     /// This is invoked by the GATT server module, not separately from the upper layer.
clear_server(&mut self, server_id: ServerId)66     pub fn clear_server(&mut self, server_id: ServerId) {
67         info!("clearing advertisers associated with {server_id:?}");
68         self.advertiser_to_server.retain(|_, server| *server != server_id);
69     }
70 
71     /// Handles an incoming connection
72     ///
73     /// This event should be supplied from the enclosing module, not directly from the upper layer.
on_le_connect(&mut self, tcb_idx: TransportIndex, advertiser: Option<AdvertiserId>)74     pub fn on_le_connect(&mut self, tcb_idx: TransportIndex, advertiser: Option<AdvertiserId>) {
75         info!(
76             "processing incoming connection on transport {tcb_idx:?} to advertiser {advertiser:?}"
77         );
78         let Some(advertiser) = advertiser else {
79             info!("processing outgoing connection, granting access to all servers");
80             return;
81         };
82         let Some(server_id) = self.advertiser_to_server.get(&advertiser).copied() else {
83             info!("connection can access all servers");
84             return;
85         };
86         info!("connection is isolated to server {server_id:?}");
87         let old = self.transport_to_server.insert(tcb_idx, server_id);
88         if old.is_some() {
89             error!("new server {server_id:?} on transport {tcb_idx:?} displacing existing server {server_id:?}")
90         }
91     }
92 
93     /// Handle a disconnection
94     ///
95     /// This event should be supplied from the enclosing module, not directly from the upper layer.
on_le_disconnect(&mut self, tcb_idx: TransportIndex)96     pub fn on_le_disconnect(&mut self, tcb_idx: TransportIndex) {
97         info!("processing disconnection on transport {tcb_idx:?}");
98         self.transport_to_server.remove(&tcb_idx);
99     }
100 }
101 
102 #[cfg(test)]
103 mod test {
104     use super::*;
105 
106     const TCB_IDX: TransportIndex = TransportIndex(1);
107     const ADVERTISER_ID: AdvertiserId = AdvertiserId(3);
108     const SERVER_ID: ServerId = ServerId(4);
109 
110     const ANOTHER_ADVERTISER_ID: AdvertiserId = AdvertiserId(5);
111 
112     #[test]
test_non_isolated_connect()113     fn test_non_isolated_connect() {
114         let mut isolation_manager = IsolationManager::new();
115 
116         isolation_manager.on_le_connect(TCB_IDX, Some(ADVERTISER_ID));
117         let server_id = isolation_manager.get_server_id(TCB_IDX);
118 
119         assert!(server_id.is_none())
120     }
121 
122     #[test]
test_isolated_connect()123     fn test_isolated_connect() {
124         let mut isolation_manager = IsolationManager::new();
125         isolation_manager.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
126 
127         isolation_manager.on_le_connect(TCB_IDX, Some(ADVERTISER_ID));
128         let server_id = isolation_manager.get_server_id(TCB_IDX);
129 
130         assert_eq!(server_id, Some(SERVER_ID));
131     }
132 
133     #[test]
test_non_isolated_connect_with_isolated_advertiser()134     fn test_non_isolated_connect_with_isolated_advertiser() {
135         let mut isolation_manager = IsolationManager::new();
136         isolation_manager.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
137 
138         isolation_manager.on_le_connect(TCB_IDX, Some(ANOTHER_ADVERTISER_ID));
139         let server_id = isolation_manager.get_server_id(TCB_IDX);
140 
141         assert!(server_id.is_none())
142     }
143 
144     #[test]
test_advertiser_id_reuse()145     fn test_advertiser_id_reuse() {
146         let mut isolation_manager = IsolationManager::new();
147         // start an advertiser associated with the server, then kill the advertiser
148         isolation_manager.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
149         isolation_manager.clear_advertiser(ADVERTISER_ID);
150 
151         // a new advertiser appeared with the same ID and got a connection
152         isolation_manager.on_le_connect(TCB_IDX, Some(ADVERTISER_ID));
153         let server_id = isolation_manager.get_server_id(TCB_IDX);
154 
155         // but we should not be isolated since this is a new advertiser reusing the old
156         // ID
157         assert!(server_id.is_none())
158     }
159 
160     #[test]
test_server_closed()161     fn test_server_closed() {
162         let mut isolation_manager = IsolationManager::new();
163         // start an advertiser associated with the server, then kill the server
164         isolation_manager.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
165         isolation_manager.clear_server(SERVER_ID);
166 
167         // then afterwards we get a connection to this advertiser
168         isolation_manager.on_le_connect(TCB_IDX, Some(ADVERTISER_ID));
169         let server_id = isolation_manager.get_server_id(TCB_IDX);
170 
171         // since the server is gone, we should not capture the connection
172         assert!(server_id.is_none())
173     }
174 
175     #[test]
test_connection_isolated_after_advertiser_stops()176     fn test_connection_isolated_after_advertiser_stops() {
177         let mut isolation_manager = IsolationManager::new();
178         isolation_manager.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
179         isolation_manager.on_le_connect(TCB_IDX, Some(ADVERTISER_ID));
180         isolation_manager.clear_advertiser(ADVERTISER_ID);
181 
182         let is_isolated = isolation_manager.is_connection_isolated(TCB_IDX);
183 
184         assert!(is_isolated)
185     }
186 
187     #[test]
test_connection_isolated_after_server_stops()188     fn test_connection_isolated_after_server_stops() {
189         let mut isolation_manager = IsolationManager::new();
190         isolation_manager.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
191         isolation_manager.on_le_connect(TCB_IDX, Some(ADVERTISER_ID));
192         isolation_manager.clear_server(SERVER_ID);
193 
194         let is_isolated = isolation_manager.is_connection_isolated(TCB_IDX);
195 
196         assert!(is_isolated)
197     }
198 
199     #[test]
test_not_isolated_after_disconnection()200     fn test_not_isolated_after_disconnection() {
201         let mut isolation_manager = IsolationManager::new();
202         isolation_manager.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
203         isolation_manager.on_le_connect(TCB_IDX, Some(ADVERTISER_ID));
204 
205         isolation_manager.on_le_disconnect(TCB_IDX);
206         let is_isolated = isolation_manager.is_connection_isolated(TCB_IDX);
207 
208         assert!(!is_isolated);
209     }
210 
211     #[test]
test_tcb_idx_reuse_after_isolated()212     fn test_tcb_idx_reuse_after_isolated() {
213         let mut isolation_manager = IsolationManager::new();
214         isolation_manager.associate_server_with_advertiser(SERVER_ID, ADVERTISER_ID);
215         isolation_manager.on_le_connect(TCB_IDX, Some(ADVERTISER_ID));
216         isolation_manager.clear_advertiser(ADVERTISER_ID);
217         isolation_manager.on_le_disconnect(TCB_IDX);
218 
219         isolation_manager.on_le_connect(TCB_IDX, Some(ADVERTISER_ID));
220         let server_id = isolation_manager.get_server_id(TCB_IDX);
221 
222         assert!(server_id.is_none());
223         assert!(!isolation_manager.is_connection_isolated(TCB_IDX));
224     }
225 }
226