1 //! Anything related to the Admin API (IBluetoothAdmin). 2 3 use std::collections::{HashMap, HashSet}; 4 use std::fs::File; 5 use std::io::{Error, ErrorKind, Read, Result, Write}; 6 use std::sync::{Arc, Mutex}; 7 8 use crate::bluetooth::{Bluetooth, BluetoothDevice, IBluetooth, IBluetoothCallback}; 9 use crate::bluetooth_media::BluetoothMedia; 10 use crate::callbacks::Callbacks; 11 use crate::socket_manager::BluetoothSocketManager; 12 use crate::uuid::{Profile, UuidHelper}; 13 use crate::{APIMessage, BluetoothAPI, Message, RPCProxy}; 14 15 use bt_topshim::btif::{BtPropertyType, BtSspVariant, RawAddress, Uuid}; 16 use bt_topshim::profiles::sdp::BtSdpRecord; 17 use log::{info, warn}; 18 use serde_json::{json, Value}; 19 use tokio::sync::mpsc::Sender; 20 21 /// Defines the Admin API 22 pub trait IBluetoothAdmin { 23 /// Check if the given UUID is in the allowlist is_service_allowed(&self, service: Uuid) -> bool24 fn is_service_allowed(&self, service: Uuid) -> bool; 25 /// Overwrite the current settings and store it to a file. set_allowed_services(&mut self, services: Vec<Uuid>) -> bool26 fn set_allowed_services(&mut self, services: Vec<Uuid>) -> bool; 27 /// Get the allowlist in UUIDs get_allowed_services(&self) -> Vec<Uuid>28 fn get_allowed_services(&self) -> Vec<Uuid>; 29 /// Get the PolicyEffect struct of a device get_device_policy_effect(&self, device: BluetoothDevice) -> Option<PolicyEffect>30 fn get_device_policy_effect(&self, device: BluetoothDevice) -> Option<PolicyEffect>; 31 /// Register client callback register_admin_policy_callback( &mut self, callback: Box<dyn IBluetoothAdminPolicyCallback + Send>, ) -> u3232 fn register_admin_policy_callback( 33 &mut self, 34 callback: Box<dyn IBluetoothAdminPolicyCallback + Send>, 35 ) -> u32; 36 /// Unregister client callback via callback ID unregister_admin_policy_callback(&mut self, callback_id: u32) -> bool37 fn unregister_admin_policy_callback(&mut self, callback_id: u32) -> bool; 38 } 39 40 /// Information of the effects to a remote device by the admin policies 41 #[derive(PartialEq, Clone, Debug)] 42 pub struct PolicyEffect { 43 /// Array of services that are blocked by policy 44 pub service_blocked: Vec<Uuid>, 45 /// Indicate if the device has an adapter-supported profile that is blocked by the policy 46 pub affected: bool, 47 } 48 49 /// A helper struct that tells whether a service or a profile is allowed. 50 #[derive(Clone)] 51 pub(crate) struct BluetoothAdminPolicyHelper { 52 allowed_services: HashSet<Uuid>, 53 } 54 55 impl Default for BluetoothAdminPolicyHelper { default() -> Self56 fn default() -> Self { 57 Self { allowed_services: HashSet::default() } 58 } 59 } 60 61 impl BluetoothAdminPolicyHelper { is_service_allowed(&self, service: &Uuid) -> bool62 pub(crate) fn is_service_allowed(&self, service: &Uuid) -> bool { 63 self.allowed_services.is_empty() || self.allowed_services.contains(service) 64 } 65 is_profile_allowed(&self, profile: &Profile) -> bool66 pub(crate) fn is_profile_allowed(&self, profile: &Profile) -> bool { 67 self.is_service_allowed(UuidHelper::get_profile_uuid(&profile).unwrap()) 68 } 69 set_allowed_services(&mut self, services: Vec<Uuid>) -> bool70 fn set_allowed_services(&mut self, services: Vec<Uuid>) -> bool { 71 let services: HashSet<Uuid> = services.into_iter().collect(); 72 if self.allowed_services != services { 73 self.allowed_services = services; 74 true 75 } else { 76 false 77 } 78 } 79 get_allowed_services(&self) -> Vec<Uuid>80 fn get_allowed_services(&self) -> Vec<Uuid> { 81 self.allowed_services.iter().cloned().collect() 82 } 83 get_blocked_services(&self, remote_uuids: &Vec<Uuid>) -> Vec<Uuid>84 fn get_blocked_services(&self, remote_uuids: &Vec<Uuid>) -> Vec<Uuid> { 85 remote_uuids.iter().filter(|&uu| !self.is_service_allowed(uu)).cloned().collect() 86 } 87 } 88 89 pub trait IBluetoothAdminPolicyCallback: RPCProxy { 90 /// This gets called when service allowlist changed. on_service_allowlist_changed(&mut self, allowlist: Vec<Uuid>)91 fn on_service_allowlist_changed(&mut self, allowlist: Vec<Uuid>); 92 /// This gets called when 93 /// 1. a new device is found by adapter 94 /// 2. the policy effect to a device is changed due to 95 /// the remote services changed or 96 /// the service allowlist changed. on_device_policy_effect_changed( &mut self, device: BluetoothDevice, new_policy_effect: Option<PolicyEffect>, )97 fn on_device_policy_effect_changed( 98 &mut self, 99 device: BluetoothDevice, 100 new_policy_effect: Option<PolicyEffect>, 101 ); 102 } 103 104 pub struct BluetoothAdmin { 105 path: String, 106 adapter: Arc<Mutex<Box<Bluetooth>>>, 107 bluetooth_media: Arc<Mutex<Box<BluetoothMedia>>>, 108 socket_manager: Arc<Mutex<Box<BluetoothSocketManager>>>, 109 admin_helper: BluetoothAdminPolicyHelper, 110 callbacks: Callbacks<dyn IBluetoothAdminPolicyCallback + Send>, 111 device_policy_affect_cache: HashMap<BluetoothDevice, Option<PolicyEffect>>, 112 tx: Sender<Message>, 113 } 114 115 impl BluetoothAdmin { new( path: String, tx: Sender<Message>, adapter: Arc<Mutex<Box<Bluetooth>>>, bluetooth_media: Arc<Mutex<Box<BluetoothMedia>>>, socket_manager: Arc<Mutex<Box<BluetoothSocketManager>>>, ) -> Self116 pub fn new( 117 path: String, 118 tx: Sender<Message>, 119 adapter: Arc<Mutex<Box<Bluetooth>>>, 120 bluetooth_media: Arc<Mutex<Box<BluetoothMedia>>>, 121 socket_manager: Arc<Mutex<Box<BluetoothSocketManager>>>, 122 ) -> Self { 123 Self { 124 path, 125 adapter, 126 bluetooth_media, 127 socket_manager, 128 admin_helper: Default::default(), // By default allowed all services 129 callbacks: Callbacks::new(tx.clone(), Message::AdminCallbackDisconnected), 130 device_policy_affect_cache: HashMap::new(), 131 tx, 132 } 133 } 134 initialize(&mut self, api_tx: Sender<APIMessage>)135 pub fn initialize(&mut self, api_tx: Sender<APIMessage>) { 136 if let Err(e) = self.load_config() { 137 warn!("Admin: Failed to load config file: {}", e); 138 } else { 139 info!("Admin: Load settings from {} successfully", &self.path); 140 } 141 142 // Listen to the device events from adapter. 143 self.adapter 144 .lock() 145 .unwrap() 146 .register_callback(Box::new(BluetoothDeviceCallbacks::new(self.tx.clone()))); 147 let devices_and_uuids = self.adapter.lock().unwrap().get_all_devices_and_uuids(); 148 for (remote_device, uuids) in devices_and_uuids.into_iter() { 149 self.on_device_found(&remote_device); 150 if let Some(uuids) = uuids { 151 self.on_device_uuid_changed(&remote_device, uuids); 152 } 153 } 154 155 // Now toggle the profiles based on the loaded config. 156 self.adapter.lock().unwrap().handle_admin_policy_changed(self.admin_helper.clone()); 157 self.bluetooth_media.lock().unwrap().handle_admin_policy_changed(self.admin_helper.clone()); 158 self.socket_manager.lock().unwrap().handle_admin_policy_changed(self.admin_helper.clone()); 159 160 // DBus API is ready now. 161 tokio::spawn(async move { 162 let _ = api_tx.send(APIMessage::IsReady(BluetoothAPI::Admin)).await; 163 }); 164 } 165 get_blocked_services(&self, remote_uuids: &Vec<Uuid>) -> Vec<Uuid>166 fn get_blocked_services(&self, remote_uuids: &Vec<Uuid>) -> Vec<Uuid> { 167 self.admin_helper.get_blocked_services(remote_uuids) 168 } 169 get_affected_status(&self, blocked_services: &Vec<Uuid>) -> bool170 fn get_affected_status(&self, blocked_services: &Vec<Uuid>) -> bool { 171 // return true if a supported profile is in blocked services. 172 blocked_services 173 .iter() 174 .find(|&uuid| { 175 UuidHelper::is_known_profile(uuid) 176 .map_or(false, |p| UuidHelper::is_profile_supported(&p)) 177 }) 178 .is_some() 179 } 180 load_config(&mut self) -> Result<()>181 fn load_config(&mut self) -> Result<()> { 182 let mut file = File::open(&self.path)?; 183 let mut contents = String::new(); 184 file.read_to_string(&mut contents)?; 185 let json = serde_json::from_str::<Value>(contents.as_str())?; 186 let allowed_services = Self::get_config_from_json(&json) 187 .ok_or(Error::new(ErrorKind::Other, "Failed converting json to config"))?; 188 if !self.admin_helper.set_allowed_services(allowed_services) { 189 info!("Admin: load_config: Unchanged"); 190 } 191 Ok(()) 192 } 193 get_config_from_json(json: &Value) -> Option<Vec<Uuid>>194 fn get_config_from_json(json: &Value) -> Option<Vec<Uuid>> { 195 Some( 196 json.get("allowed_services")? 197 .as_array()? 198 .iter() 199 .filter_map(|v| Uuid::from_string(v.as_str()?)) 200 .collect(), 201 ) 202 } 203 write_config(&self) -> Result<()>204 fn write_config(&self) -> Result<()> { 205 let mut f = File::create(&self.path)?; 206 f.write_all( 207 Self::get_config_json_string(self.admin_helper.get_allowed_services()).as_bytes(), 208 ) 209 } 210 get_config_json_string(uuids: Vec<Uuid>) -> String211 fn get_config_json_string(uuids: Vec<Uuid>) -> String { 212 serde_json::to_string_pretty(&json!({ 213 "allowed_services": 214 uuids 215 .iter() 216 .map(|uu| uu.to_string()) 217 .collect::<Vec<String>>() 218 })) 219 .ok() 220 .unwrap() 221 } 222 new_device_policy_effect(&self, uuids: Option<Vec<Uuid>>) -> Option<PolicyEffect>223 fn new_device_policy_effect(&self, uuids: Option<Vec<Uuid>>) -> Option<PolicyEffect> { 224 uuids.map(|uuids| { 225 let service_blocked = self.get_blocked_services(&uuids); 226 let affected = self.get_affected_status(&service_blocked); 227 PolicyEffect { service_blocked, affected } 228 }) 229 } 230 on_device_found(&mut self, remote_device: &BluetoothDevice)231 pub fn on_device_found(&mut self, remote_device: &BluetoothDevice) { 232 self.device_policy_affect_cache.insert(remote_device.clone(), None).or_else(|| { 233 self.callbacks.for_all_callbacks(|cb| { 234 cb.on_device_policy_effect_changed(remote_device.clone(), None); 235 }); 236 None 237 }); 238 } 239 on_device_cleared(&mut self, remote_device: &BluetoothDevice)240 pub fn on_device_cleared(&mut self, remote_device: &BluetoothDevice) { 241 self.device_policy_affect_cache.remove(remote_device); 242 } 243 on_device_uuid_changed( &mut self, remote_device: &BluetoothDevice, new_uuids: Vec<Uuid>, )244 pub fn on_device_uuid_changed( 245 &mut self, 246 remote_device: &BluetoothDevice, 247 new_uuids: Vec<Uuid>, 248 ) { 249 let new_effect = self.new_device_policy_effect(Some(new_uuids)); 250 let cur_effect = self.device_policy_affect_cache.get(remote_device); 251 252 if cur_effect.is_none() || *cur_effect.unwrap() != new_effect.clone() { 253 self.callbacks.for_all_callbacks(|cb| { 254 cb.on_device_policy_effect_changed(remote_device.clone(), new_effect.clone()) 255 }); 256 self.device_policy_affect_cache.insert(remote_device.clone(), new_effect.clone()); 257 } 258 } 259 handle_action(&mut self, action: AdminActions)260 pub(crate) fn handle_action(&mut self, action: AdminActions) { 261 match action { 262 AdminActions::OnDeviceCleared(remote_device) => self.on_device_cleared(&remote_device), 263 AdminActions::OnDeviceFound(remote_device) => self.on_device_found(&remote_device), 264 AdminActions::OnDeviceUuidChanged(remote_device) => { 265 let new_uuids = 266 self.adapter.lock().unwrap().get_remote_uuids(remote_device.clone()); 267 self.on_device_uuid_changed(&remote_device, new_uuids); 268 } 269 } 270 } 271 } 272 273 impl IBluetoothAdmin for BluetoothAdmin { is_service_allowed(&self, service: Uuid) -> bool274 fn is_service_allowed(&self, service: Uuid) -> bool { 275 self.admin_helper.is_service_allowed(&service) 276 } 277 set_allowed_services(&mut self, services: Vec<Uuid>) -> bool278 fn set_allowed_services(&mut self, services: Vec<Uuid>) -> bool { 279 if !self.admin_helper.set_allowed_services(services) { 280 // Allowlist is not changed. 281 return true; 282 } 283 284 self.adapter.lock().unwrap().handle_admin_policy_changed(self.admin_helper.clone()); 285 self.bluetooth_media.lock().unwrap().handle_admin_policy_changed(self.admin_helper.clone()); 286 self.socket_manager.lock().unwrap().handle_admin_policy_changed(self.admin_helper.clone()); 287 288 if let Err(e) = self.write_config() { 289 warn!("Admin: Failed to write config: {}", e); 290 } else { 291 info!("Admin: Write settings into {} successfully", &self.path); 292 } 293 294 let allowed_services = self.admin_helper.get_allowed_services(); 295 self.callbacks.for_all_callbacks(|cb| { 296 cb.on_service_allowlist_changed(allowed_services.clone()); 297 }); 298 299 for (device, effect) in self.device_policy_affect_cache.clone().iter() { 300 let uuids = self.adapter.lock().unwrap().get_remote_uuids(device.clone()); 301 let new_effect = self.new_device_policy_effect(Some(uuids)); 302 303 if new_effect.clone() != *effect { 304 self.callbacks.for_all_callbacks(|cb| { 305 cb.on_device_policy_effect_changed(device.clone(), new_effect.clone()) 306 }); 307 self.device_policy_affect_cache.insert(device.clone(), new_effect.clone()); 308 } 309 } 310 311 true 312 } 313 get_allowed_services(&self) -> Vec<Uuid>314 fn get_allowed_services(&self) -> Vec<Uuid> { 315 self.admin_helper.get_allowed_services() 316 } 317 get_device_policy_effect(&self, device: BluetoothDevice) -> Option<PolicyEffect>318 fn get_device_policy_effect(&self, device: BluetoothDevice) -> Option<PolicyEffect> { 319 if let Some(effect) = self.device_policy_affect_cache.get(&device) { 320 effect.clone() 321 } else { 322 warn!("Device not found in cache"); 323 None 324 } 325 } 326 register_admin_policy_callback( &mut self, callback: Box<dyn IBluetoothAdminPolicyCallback + Send>, ) -> u32327 fn register_admin_policy_callback( 328 &mut self, 329 callback: Box<dyn IBluetoothAdminPolicyCallback + Send>, 330 ) -> u32 { 331 self.callbacks.add_callback(callback) 332 } 333 unregister_admin_policy_callback(&mut self, callback_id: u32) -> bool334 fn unregister_admin_policy_callback(&mut self, callback_id: u32) -> bool { 335 self.callbacks.remove_callback(callback_id) 336 } 337 } 338 339 pub enum AdminActions { 340 OnDeviceFound(BluetoothDevice), 341 OnDeviceCleared(BluetoothDevice), 342 OnDeviceUuidChanged(BluetoothDevice), 343 } 344 345 /// Handles the callbacks from Bluetooth Device 346 struct BluetoothDeviceCallbacks { 347 tx: Sender<Message>, 348 } 349 350 impl BluetoothDeviceCallbacks { new(tx: Sender<Message>) -> Self351 fn new(tx: Sender<Message>) -> Self { 352 Self { tx } 353 } 354 } 355 356 impl IBluetoothCallback for BluetoothDeviceCallbacks { on_device_properties_changed( &mut self, remote_device: BluetoothDevice, props: Vec<BtPropertyType>, )357 fn on_device_properties_changed( 358 &mut self, 359 remote_device: BluetoothDevice, 360 props: Vec<BtPropertyType>, 361 ) { 362 if props.contains(&BtPropertyType::Uuids) { 363 let tx = self.tx.clone(); 364 tokio::spawn(async move { 365 let _ = tx 366 .send(Message::AdminActions(AdminActions::OnDeviceUuidChanged(remote_device))) 367 .await; 368 }); 369 } 370 } 371 on_device_found(&mut self, remote_device: BluetoothDevice)372 fn on_device_found(&mut self, remote_device: BluetoothDevice) { 373 let tx = self.tx.clone(); 374 tokio::spawn(async move { 375 let _ = 376 tx.send(Message::AdminActions(AdminActions::OnDeviceFound(remote_device))).await; 377 }); 378 } 379 on_device_cleared(&mut self, remote_device: BluetoothDevice)380 fn on_device_cleared(&mut self, remote_device: BluetoothDevice) { 381 let tx = self.tx.clone(); 382 tokio::spawn(async move { 383 let _ = 384 tx.send(Message::AdminActions(AdminActions::OnDeviceCleared(remote_device))).await; 385 }); 386 } 387 388 // Unused callbacks on_adapter_property_changed(&mut self, _prop: BtPropertyType)389 fn on_adapter_property_changed(&mut self, _prop: BtPropertyType) {} on_address_changed(&mut self, _addr: RawAddress)390 fn on_address_changed(&mut self, _addr: RawAddress) {} on_name_changed(&mut self, _name: String)391 fn on_name_changed(&mut self, _name: String) {} on_discoverable_changed(&mut self, _discoverable: bool)392 fn on_discoverable_changed(&mut self, _discoverable: bool) {} on_discovering_changed(&mut self, _discovering: bool)393 fn on_discovering_changed(&mut self, _discovering: bool) {} on_ssp_request( &mut self, _remote_device: BluetoothDevice, _cod: u32, _variant: BtSspVariant, _passkey: u32, )394 fn on_ssp_request( 395 &mut self, 396 _remote_device: BluetoothDevice, 397 _cod: u32, 398 _variant: BtSspVariant, 399 _passkey: u32, 400 ) { 401 } on_pin_request(&mut self, _remote_device: BluetoothDevice, _cod: u32, _min_16_digit: bool)402 fn on_pin_request(&mut self, _remote_device: BluetoothDevice, _cod: u32, _min_16_digit: bool) {} on_pin_display(&mut self, _remote_device: BluetoothDevice, _pincode: String)403 fn on_pin_display(&mut self, _remote_device: BluetoothDevice, _pincode: String) {} on_bond_state_changed(&mut self, _status: u32, _device_address: RawAddress, _state: u32)404 fn on_bond_state_changed(&mut self, _status: u32, _device_address: RawAddress, _state: u32) {} on_sdp_search_complete( &mut self, _remote_device: BluetoothDevice, _searched_uuid: Uuid, _sdp_records: Vec<BtSdpRecord>, )405 fn on_sdp_search_complete( 406 &mut self, 407 _remote_device: BluetoothDevice, 408 _searched_uuid: Uuid, 409 _sdp_records: Vec<BtSdpRecord>, 410 ) { 411 } on_sdp_record_created(&mut self, _record: BtSdpRecord, _handle: i32)412 fn on_sdp_record_created(&mut self, _record: BtSdpRecord, _handle: i32) {} 413 } 414 415 impl RPCProxy for BluetoothDeviceCallbacks { get_object_id(&self) -> String416 fn get_object_id(&self) -> String { 417 "BluetoothAdmin's Bluetooth Device Callback".to_string() 418 } 419 } 420 421 #[cfg(test)] 422 mod tests { 423 use crate::bluetooth_admin::{BluetoothAdmin, BluetoothAdminPolicyHelper}; 424 use bt_topshim::btif::Uuid; 425 use serde_json::{json, Value}; 426 427 #[test] test_set_service_allowed()428 fn test_set_service_allowed() { 429 let mut admin_helper = BluetoothAdminPolicyHelper::default(); 430 let uuid1: Uuid = [1; 16].into(); 431 let uuid2: Uuid = [2; 16].into(); 432 let uuid3: Uuid = [3; 16].into(); 433 let uuids = vec![uuid1, uuid2, uuid3]; 434 435 // Default admin allows everything 436 assert!(admin_helper.is_service_allowed(&uuid1)); 437 assert!(admin_helper.is_service_allowed(&uuid2)); 438 assert!(admin_helper.is_service_allowed(&uuid3)); 439 assert_eq!(admin_helper.get_blocked_services(&uuids), Vec::<Uuid>::new()); 440 441 admin_helper.set_allowed_services(vec![uuid1, uuid3]); 442 443 // Admin disallows uuid2 now 444 assert!(admin_helper.is_service_allowed(&uuid1)); 445 assert!(!admin_helper.is_service_allowed(&uuid2)); 446 assert!(admin_helper.is_service_allowed(&uuid3)); 447 assert_eq!(admin_helper.get_blocked_services(&uuids), vec![uuid2]); 448 449 admin_helper.set_allowed_services(vec![uuid2]); 450 451 // Allowed services were overwritten. 452 assert!(!admin_helper.is_service_allowed(&uuid1)); 453 assert!(admin_helper.is_service_allowed(&uuid2)); 454 assert!(!admin_helper.is_service_allowed(&uuid3)); 455 assert_eq!(admin_helper.get_blocked_services(&uuids), vec![uuid1, uuid3]); 456 } 457 get_sorted_allowed_services_from_config( admin_helper: &BluetoothAdminPolicyHelper, ) -> Vec<String>458 fn get_sorted_allowed_services_from_config( 459 admin_helper: &BluetoothAdminPolicyHelper, 460 ) -> Vec<String> { 461 let mut v = serde_json::from_str::<Value>( 462 BluetoothAdmin::get_config_json_string(admin_helper.get_allowed_services()).as_str(), 463 ) 464 .unwrap() 465 .get("allowed_services") 466 .unwrap() 467 .as_array() 468 .unwrap() 469 .iter() 470 .map(|v| String::from(v.as_str().unwrap())) 471 .collect::<Vec<String>>(); 472 v.sort(); 473 v 474 } 475 get_sorted_allowed_services(admin_helper: &BluetoothAdminPolicyHelper) -> Vec<Uuid>476 fn get_sorted_allowed_services(admin_helper: &BluetoothAdminPolicyHelper) -> Vec<Uuid> { 477 let mut v = admin_helper.get_allowed_services(); 478 v.sort_by(|lhs, rhs| lhs.uu.cmp(&rhs.uu)); 479 v 480 } 481 482 #[test] test_config()483 fn test_config() { 484 let mut admin_helper = BluetoothAdminPolicyHelper::default(); 485 let a2dp_sink_str = "0000110b-0000-1000-8000-00805f9b34fb"; 486 let a2dp_source_str = "0000110a-0000-1000-8000-00805f9b34fb"; 487 488 let a2dp_sink_uuid = Uuid::from_string(a2dp_sink_str).unwrap(); 489 let a2dp_source_uuid = Uuid::from_string(a2dp_source_str).unwrap(); 490 491 let mut allowed_services_str = vec![a2dp_sink_str, a2dp_source_str]; 492 493 let mut allowed_services_uuid = vec![a2dp_sink_uuid, a2dp_source_uuid]; 494 495 allowed_services_str.sort(); 496 allowed_services_uuid.sort_by(|lhs, rhs| lhs.uu.cmp(&rhs.uu)); 497 498 // valid configuration 499 assert_eq!( 500 BluetoothAdmin::get_config_from_json(&json!({ 501 "allowed_services": allowed_services_str.clone() 502 })) 503 .map(|uuids| admin_helper.set_allowed_services(uuids)), 504 Some(true) 505 ); 506 assert_eq!(get_sorted_allowed_services(&admin_helper), allowed_services_uuid); 507 assert_eq!(get_sorted_allowed_services_from_config(&admin_helper), allowed_services_str); 508 509 // invalid configuration 510 assert_eq!( 511 BluetoothAdmin::get_config_from_json(&json!({ "allowed_services": a2dp_sink_str })) 512 .map(|uuids| admin_helper.set_allowed_services(uuids)), 513 None 514 ); 515 // config should remain unchanged 516 assert_eq!(get_sorted_allowed_services(&admin_helper), allowed_services_uuid); 517 assert_eq!(get_sorted_allowed_services_from_config(&admin_helper), allowed_services_str); 518 } 519 } 520