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