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