1 //! The migrate module is intended to make it possible to migrate device
2 //! information and settings between BlueZ and Floss.
3 //!
4 //! The rules for [source] -> [target] migration:
5 //! - All devices that exist in [source] must exist in [target]. Delete
6 //!   ones that don't exist in [source].
7 //! - If the device exists in both [source] and [target], replace [target]
8 //!   keys with transferred [source] keys, but keep all keys that don't
9 //!   exist in [source]
10 //! - Drop devices that run into issues, but continue trying to migrate
11 //!   all others
12 
13 use std::collections::HashMap;
14 use std::fmt::Write;
15 use std::fs;
16 use std::path::Path;
17 
18 use configparser::ini::Ini;
19 use glob::glob;
20 
21 use log::{debug, error, info, warn};
22 
23 const BT_LIBDIR: &str = "/var/lib/bluetooth";
24 pub const FLOSS_CONF_FILE: &str = "/var/lib/bluetooth/bt_config.conf";
25 
26 const ADAPTER_SECTION_NAME: &str = "Adapter";
27 const GENERAL_SECTION_NAME: &str = "General";
28 const LINKKEY_SECTION_NAME: &str = "LinkKey";
29 const DEVICEID_SECTION_NAME: &str = "DeviceID";
30 const IRK_SECTION_NAME: &str = "IdentityResolvingKey";
31 const LTK_SECTION_NAME: &str = "LongTermKey";
32 const BLUEZ_PERIPHERAL_LTK_SECTION_NAME: &str = "PeripheralLongTermKey";
33 const BLUEZ_LOCAL_LTK_SECTION_NAME: &str = "SlaveLongTermKey";
34 const REPORT_MAP_SECTION_NAME: &str = "ReportMap";
35 
36 const CLASSIC_TYPE: &str = "BR/EDR;";
37 const LE_TYPE: &str = "LE;";
38 const DUAL_TYPE: &str = "BR/EDR;LE;";
39 
40 /// Represents LTK info
41 #[derive(Debug, Default)]
42 struct LtkInfo {
43     key: u128,
44     rand: u64,
45     ediv: u16,
46     auth: u8,
47     len: u8,
48 }
49 
50 impl LtkInfo {
51     // BlueZ has 5 valid possibilities of auth (b/329392926).
52     // For simplicity, we only map it to the 2 values Floss supported.
53     // This way we can't distinguish whether the device is using legacy or secure pairing.
auth_from_bluez(bluez_auth: u8) -> u854     fn auth_from_bluez(bluez_auth: u8) -> u8 {
55         match bluez_auth {
56             0 | 2 | 4 => 1, // unauthenticated
57             1 | 3 => 2,     // authenticated
58             _ => 0,         // invalid
59         }
60     }
61 
auth_to_bluez(floss_auth: u8) -> u862     fn auth_to_bluez(floss_auth: u8) -> u8 {
63         match floss_auth {
64             1 => 2, // unauthenticated, secure pairing
65             2 => 3, // authenticated, secure pairing
66             _ => 5, // invalid
67         }
68     }
69 
new_from_bluez(bluez_conf: &Ini, sec: &str) -> Self70     fn new_from_bluez(bluez_conf: &Ini, sec: &str) -> Self {
71         LtkInfo {
72             key: u128::from_str_radix(bluez_conf.get(sec, "Key").unwrap_or_default().as_str(), 16)
73                 .unwrap_or_default(),
74             rand: bluez_conf
75                 .get(sec, "Rand")
76                 .unwrap_or_default()
77                 .parse::<u64>()
78                 .unwrap_or_default(),
79             ediv: bluez_conf
80                 .get(sec, "EDiv")
81                 .unwrap_or_default()
82                 .parse::<u16>()
83                 .unwrap_or_default(),
84             auth: LtkInfo::auth_from_bluez(
85                 bluez_conf
86                     .get(sec, "Authenticated")
87                     .unwrap_or_default()
88                     .parse::<u8>()
89                     .unwrap_or_default(),
90             ),
91             len: bluez_conf
92                 .get(sec, "EncSize")
93                 .unwrap_or_default()
94                 .parse::<u8>()
95                 .unwrap_or_default(),
96         }
97     }
98 
99     // LE_KEY_PENC = LTK + RAND (64) + EDIV (16) + Security Level (8) + Key Length (8)
try_from_penc(val: String) -> Result<Self, &'static str>100     fn try_from_penc(val: String) -> Result<Self, &'static str> {
101         if val.len() != 56 {
102             return Err("PENC String provided to LtkInfo is not the right size");
103         }
104 
105         Ok(LtkInfo {
106             key: u128::from_str_radix(&val[0..32], 16).unwrap_or_default(),
107             rand: u64::from_str_radix(&val[32..48], 16).unwrap_or_default().swap_bytes(),
108             ediv: u16::from_str_radix(&val[48..52], 16).unwrap_or_default().swap_bytes(),
109             auth: u8::from_str_radix(&val[52..54], 16).unwrap_or_default(),
110             len: u8::from_str_radix(&val[54..56], 16).unwrap_or_default(),
111         })
112     }
113 
try_into_penc(self) -> Result<String, &'static str>114     fn try_into_penc(self) -> Result<String, &'static str> {
115         Ok(format!(
116             "{:032x}{:016x}{:04x}{:02x}{:02x}",
117             self.key,
118             self.rand.swap_bytes(),
119             self.ediv.swap_bytes(),
120             self.auth,
121             self.len
122         ))
123     }
124 
125     // LE_KEY_LENC = LTK + EDIV (16) + Key Size (8) + Security Level (8)
try_from_lenc(val: String) -> Result<Self, &'static str>126     fn try_from_lenc(val: String) -> Result<Self, &'static str> {
127         if val.len() != 40 {
128             return Err("LENC String provided to LtkInfo is not the right size");
129         }
130 
131         Ok(LtkInfo {
132             key: u128::from_str_radix(&val[0..32], 16).unwrap_or_default(),
133             ediv: u16::from_str_radix(&val[32..36], 16).unwrap_or_default().swap_bytes(),
134             len: u8::from_str_radix(&val[36..38], 16).unwrap_or_default(),
135             auth: u8::from_str_radix(&val[38..40], 16).unwrap_or_default(),
136             rand: 0,
137         })
138     }
139 
try_into_lenc(self) -> Result<String, &'static str>140     fn try_into_lenc(self) -> Result<String, &'static str> {
141         Ok(format!(
142             "{:032x}{:04x}{:02x}{:02x}",
143             self.key,
144             self.ediv.swap_bytes(),
145             self.len,
146             self.auth,
147         ))
148     }
149 }
150 
151 /// Represents the different conversions that can be done on keys
152 pub enum Converter {
153     HexToDec,
154     DecToHex,
155     Base64ToHex,
156     HexToBase64,
157 
158     TypeB2F,
159     TypeF2B,
160     AddrTypeB2F,
161     AddrTypeF2B,
162 
163     ReverseEndianLowercase,
164     ReverseEndianUppercase,
165 
166     ReplaceSemiColonWithSpace,
167     ReplaceSpaceWithSemiColon,
168 }
169 
170 /// Represents the different actions to perform on a DeviceKey
171 pub enum KeyAction {
172     WrapOk,
173     Apply(Converter),
174     ToSection(&'static str),
175     ApplyToSection(Converter, &'static str),
176 }
177 
178 pub type DeviceKeyError = String;
179 
180 /// Represents required info needed to convert keys between Floss and BlueZ
181 struct DeviceKey {
182     pub key: &'static str,
183     action: KeyAction,
184     // Needed in Floss to BlueZ conversion
185     pub section: &'static str,
186 }
187 
188 impl DeviceKey {
189     /// Returns a DeviceKey with the key and action given
new(key: &'static str, action: KeyAction) -> Self190     fn new(key: &'static str, action: KeyAction) -> Self {
191         Self { key, action, section: "" }
192     }
193 
194     /// Performs the KeyAction stored and returns the result of the key conversion
apply_action(&mut self, value: String) -> Result<String, DeviceKeyError>195     fn apply_action(&mut self, value: String) -> Result<String, DeviceKeyError> {
196         // Helper function to do the actual conversion
197         fn apply_conversion(conv: &Converter, value: String) -> Result<String, DeviceKeyError> {
198             match conv {
199                 Converter::HexToDec => hex_str_to_dec_str(value),
200                 Converter::DecToHex => dec_str_to_hex_str(value),
201                 Converter::Base64ToHex => base64_str_to_hex_str(value),
202                 Converter::HexToBase64 => hex_str_to_base64_str(value),
203                 Converter::TypeB2F => bluez_to_floss_type(value),
204                 Converter::TypeF2B => floss_to_bluez_type(value),
205                 Converter::AddrTypeB2F => bluez_to_floss_addr_type(value),
206                 Converter::AddrTypeF2B => floss_to_bluez_addr_type(value),
207                 Converter::ReverseEndianLowercase => reverse_endianness(value, false),
208                 Converter::ReverseEndianUppercase => reverse_endianness(value, true),
209                 Converter::ReplaceSemiColonWithSpace => Ok(value.replace(';', " ")),
210                 Converter::ReplaceSpaceWithSemiColon => Ok(value.replace(' ', ";")),
211             }
212         }
213 
214         match &self.action {
215             KeyAction::WrapOk => Ok(value),
216             KeyAction::Apply(converter) => apply_conversion(converter, value),
217             KeyAction::ToSection(sec) => {
218                 self.section = sec;
219                 Ok(value)
220             }
221             KeyAction::ApplyToSection(converter, sec) => {
222                 self.section = sec;
223                 apply_conversion(converter, value)
224             }
225         }
226     }
227 }
228 
hex_str_to_dec_str(str: String) -> Result<String, String>229 fn hex_str_to_dec_str(str: String) -> Result<String, String> {
230     match u32::from_str_radix(str.trim_start_matches("0x"), 16) {
231         Ok(str) => Ok(format!("{}", str)),
232         Err(err) => Err(format!("Error converting from hex string to dec string: {}", err)),
233     }
234 }
235 
dec_str_to_hex_str(str: String) -> Result<String, String>236 fn dec_str_to_hex_str(str: String) -> Result<String, String> {
237     match str.parse::<u32>() {
238         Ok(x) => Ok(format!("0x{:X}", x)),
239         Err(err) => Err(format!("Error converting from dec string to hex string: {}", err)),
240     }
241 }
242 
base64_str_to_hex_str(str: String) -> Result<String, String>243 fn base64_str_to_hex_str(str: String) -> Result<String, String> {
244     match base64::decode(str) {
245         Ok(bytes) => {
246             let res: String = bytes.iter().fold(String::new(), |mut res, b| {
247                 let _ = write!(res, "{:02x}", b);
248                 res
249             });
250             Ok(res)
251         }
252         Err(err) => Err(format!("Error converting from base64 string to hex string: {}", err)),
253     }
254 }
255 
hex_str_to_base64_str(str: String) -> Result<String, String>256 fn hex_str_to_base64_str(str: String) -> Result<String, String> {
257     // Make vector of bytes from octets
258     let mut bytes = Vec::new();
259     for i in 0..(str.len() / 2) {
260         let res = u8::from_str_radix(&str[2 * i..2 * i + 2], 16);
261         match res {
262             Ok(v) => bytes.push(v),
263             Err(err) => {
264                 return Err(format!("Error converting from hex string to base64 string: {}", err));
265             }
266         }
267     }
268 
269     Ok(base64::encode(&bytes))
270 }
271 
bluez_to_floss_type(str: String) -> Result<String, String>272 fn bluez_to_floss_type(str: String) -> Result<String, String> {
273     match str.as_str() {
274         CLASSIC_TYPE => Ok("1".into()),
275         LE_TYPE => Ok("2".into()),
276         DUAL_TYPE => Ok("3".into()),
277         x => Err(format!("Error converting type. Unknown type: {}", x)),
278     }
279 }
280 
floss_to_bluez_type(str: String) -> Result<String, String>281 fn floss_to_bluez_type(str: String) -> Result<String, String> {
282     match str.as_str() {
283         "1" => Ok(CLASSIC_TYPE.into()),
284         "2" => Ok(LE_TYPE.into()),
285         "3" => Ok(DUAL_TYPE.into()),
286         x => Err(format!("Error converting type. Unknown type: {}", x)),
287     }
288 }
289 
bluez_to_floss_addr_type(str: String) -> Result<String, String>290 fn bluez_to_floss_addr_type(str: String) -> Result<String, String> {
291     match str.as_str() {
292         "public" => Ok("0".into()),
293         "static" => Ok("1".into()),
294         x => Err(format!("Error converting address type. Unknown type: {}", x)),
295     }
296 }
297 
floss_to_bluez_addr_type(str: String) -> Result<String, String>298 fn floss_to_bluez_addr_type(str: String) -> Result<String, String> {
299     match str.as_str() {
300         "0" => Ok("public".into()),
301         "1" => Ok("static".into()),
302         x => Err(format!("Error converting address type. Unknown type: {}", x)),
303     }
304 }
305 
306 // BlueZ stores link keys as little endian and Floss as big endian
reverse_endianness(str: String, uppercase: bool) -> Result<String, String>307 fn reverse_endianness(str: String, uppercase: bool) -> Result<String, String> {
308     match u128::from_str_radix(str.as_str(), 16) {
309         Ok(x) => {
310             if uppercase {
311                 Ok(format!("{:0>32X}", x.swap_bytes()))
312             } else {
313                 Ok(format!("{:0>32x}", x.swap_bytes()))
314             }
315         }
316         Err(err) => Err(format!("Error converting link key: {}", err)),
317     }
318 }
319 
320 /// Helper function that does the conversion from BlueZ to Floss for a single device
321 ///
322 /// # Arguments
323 /// * `filename` - A string slice that holds the path of the BlueZ file to get info from
324 /// * `addr` - A string slice that holds the address of the BlueZ device that we're converting
325 /// * `floss_conf` - The Floss Ini that we're adding to
326 /// * `is_hid_file` - Whether the file is a BlueZ hog-uhid-cache file or BlueZ info file
327 ///
328 /// # Returns
329 /// Whether the conversion was successful or not
convert_from_bluez_device( filename: &str, addr: &str, floss_conf: &mut Ini, is_hid_file: bool, ) -> bool330 fn convert_from_bluez_device(
331     filename: &str,
332     addr: &str,
333     floss_conf: &mut Ini,
334     is_hid_file: bool,
335 ) -> bool {
336     // Floss device address strings need to be lower case
337     let addr_lower = addr.to_lowercase();
338 
339     let mut bluez_conf = Ini::new_cs();
340     // Default Ini uses ";" and "#" for comments
341     bluez_conf.set_comment_symbols(&['!', '#']);
342     let bluez_map = match bluez_conf.load(filename) {
343         Ok(map) => map,
344         Err(err) => {
345             error!(
346                 "Error converting BlueZ conf to Floss conf: {}. Dropping conversion for device {}",
347                 err, addr
348             );
349             floss_conf.remove_section(addr_lower.as_str());
350             return false;
351         }
352     };
353 
354     // Floss will not load the HID info unless it sees this key and BlueZ does not have a matching key
355     if is_hid_file {
356         floss_conf.set(addr_lower.as_str(), "HidAttrMask", Some("0".into()));
357     }
358 
359     for (sec, props) in bluez_map {
360         // Special handling for LE keys since in Floss they are a combination of values in BlueZ
361         let handled = match sec.as_str() {
362             IRK_SECTION_NAME => {
363                 // In Floss, LE_KEY_PID = IRK + Identity Address Type (8) + Identity Address
364                 let irk = reverse_endianness(
365                     bluez_conf.get(sec.as_str(), "Key").unwrap_or_default(),
366                     false,
367                 )
368                 .unwrap_or_default();
369                 let addr_type = bluez_to_floss_addr_type(
370                     bluez_conf.get(GENERAL_SECTION_NAME, "AddressType").unwrap_or_default(),
371                 )
372                 .unwrap_or_default()
373                 .parse::<u8>()
374                 .unwrap_or_default();
375                 floss_conf.set(
376                     addr_lower.as_str(),
377                     "LE_KEY_PID",
378                     Some(format!("{}{:02x}{}", irk, addr_type, addr_lower.replace(':', ""))),
379                 );
380                 true
381             }
382             BLUEZ_PERIPHERAL_LTK_SECTION_NAME | LTK_SECTION_NAME => {
383                 // Special handling since in Floss LE_KEY_PENC is a combination of values in BlueZ
384                 let ltk: LtkInfo = LtkInfo::new_from_bluez(&bluez_conf, sec.as_str());
385                 floss_conf.set(
386                     addr_lower.as_str(),
387                     "LE_KEY_PENC",
388                     Some(ltk.try_into_penc().unwrap_or_default()),
389                 );
390                 true
391             }
392             BLUEZ_LOCAL_LTK_SECTION_NAME => {
393                 // Special handling since in Floss LE_KEY_LENC is a combination of values in BlueZ
394                 let lltk: LtkInfo = LtkInfo::new_from_bluez(&bluez_conf, sec.as_str());
395                 floss_conf.set(
396                     addr_lower.as_str(),
397                     "LE_KEY_LENC",
398                     Some(lltk.try_into_lenc().unwrap_or_default()),
399                 );
400                 true
401             }
402             _ => false,
403         };
404 
405         if handled {
406             continue;
407         }
408 
409         let mut map: HashMap<&str, Vec<DeviceKey>> = if is_hid_file {
410             match sec.as_str() {
411                 REPORT_MAP_SECTION_NAME => [(
412                     "report_map",
413                     vec![DeviceKey::new("HidDescriptor", KeyAction::Apply(Converter::Base64ToHex))],
414                 )]
415                 .into(),
416                 GENERAL_SECTION_NAME => [
417                     ("bcdhid", vec![DeviceKey::new("HidVersion", KeyAction::WrapOk)]),
418                     ("bcountrycode", vec![DeviceKey::new("HidCountryCode", KeyAction::WrapOk)]),
419                 ]
420                 .into(),
421                 _ => [].into(),
422             }
423         } else {
424             // info file
425             match sec.as_str() {
426                 GENERAL_SECTION_NAME => [
427                     ("Name", vec![DeviceKey::new("Name", KeyAction::WrapOk)]),
428                     (
429                         "Class",
430                         vec![DeviceKey::new("DevClass", KeyAction::Apply(Converter::HexToDec))],
431                     ),
432                     (
433                         "Appearance",
434                         vec![DeviceKey::new("Appearance", KeyAction::Apply(Converter::HexToDec))],
435                     ),
436                     (
437                         "SupportedTechnologies",
438                         vec![DeviceKey::new("DevType", KeyAction::Apply(Converter::TypeB2F))],
439                     ),
440                     (
441                         "Services",
442                         vec![DeviceKey::new(
443                             "Service",
444                             KeyAction::Apply(Converter::ReplaceSemiColonWithSpace),
445                         )],
446                     ),
447                     (
448                         "AddressType",
449                         vec![DeviceKey::new("AddrType", KeyAction::Apply(Converter::AddrTypeB2F))],
450                     ),
451                 ]
452                 .into(),
453                 LINKKEY_SECTION_NAME => [
454                     (
455                         "Key",
456                         vec![DeviceKey::new(
457                             "LinkKey",
458                             KeyAction::Apply(Converter::ReverseEndianLowercase),
459                         )],
460                     ),
461                     ("Type", vec![DeviceKey::new("LinkKeyType", KeyAction::WrapOk)]),
462                     ("PINLength", vec![DeviceKey::new("PinLength", KeyAction::WrapOk)]),
463                 ]
464                 .into(),
465                 DEVICEID_SECTION_NAME => [
466                     (
467                         "Source",
468                         vec![
469                             DeviceKey::new("SdpDiVendorIdSource", KeyAction::WrapOk),
470                             DeviceKey::new("VendorIdSource", KeyAction::WrapOk),
471                         ],
472                     ),
473                     (
474                         "Vendor",
475                         vec![
476                             DeviceKey::new("SdpDiManufacturer", KeyAction::WrapOk),
477                             DeviceKey::new("VendorId", KeyAction::WrapOk),
478                         ],
479                     ),
480                     (
481                         "Product",
482                         vec![
483                             DeviceKey::new("SdpDiModel", KeyAction::WrapOk),
484                             DeviceKey::new("ProductId", KeyAction::WrapOk),
485                         ],
486                     ),
487                     (
488                         "Version",
489                         vec![
490                             DeviceKey::new("SdpDiHardwareVersion", KeyAction::WrapOk),
491                             DeviceKey::new("ProductVersion", KeyAction::WrapOk),
492                         ],
493                     ),
494                 ]
495                 .into(),
496                 _ => [].into(),
497             }
498         };
499 
500         // Do the conversion for all keys found in BlueZ
501         for (k, v) in props {
502             match map.get_mut(k.as_str()) {
503                 Some(keys) => {
504                     for key in keys {
505                         let new_val = match key.apply_action(v.clone().unwrap_or_default()) {
506                             Ok(val) => val,
507                             Err(err) => {
508                                 error!(
509                                     "Error converting BlueZ conf to Floss conf: {}. \
510                                         Dropping conversion for device {}",
511                                     err, addr
512                                 );
513                                 floss_conf.remove_section(addr_lower.as_str());
514                                 return false;
515                             }
516                         };
517                         floss_conf.set(addr_lower.as_str(), key.key, Some(new_val));
518                     }
519                 }
520                 None => {
521                     debug!("No key match: {}", k);
522                 }
523             }
524         }
525     }
526 
527     true
528 }
529 
530 /// This is the main function that handles the device migration from BlueZ to Floss.
migrate_bluez_devices()531 pub fn migrate_bluez_devices() {
532     // Maps adapter address to Ini
533     let mut adapter_conf_map: HashMap<String, Ini> = HashMap::new();
534 
535     // Find and parse all device files
536     // In BlueZ, device info files look like /var/lib/bluetooth/<adapter address>/<device address>/info
537     let globbed = match glob(format!("{}/*:*/*:*/info", BT_LIBDIR).as_str()) {
538         Ok(v) => v,
539         Err(_) => {
540             warn!("Didn't find any BlueZ adapters to migrate");
541             return;
542         }
543     };
544     for entry in globbed {
545         let info_path = entry.unwrap_or_default();
546         let hid_path = info_path.to_str().unwrap_or_default().replace("info", "hog-uhid-cache");
547         let addrs = info_path.to_str().unwrap_or_default().split('/').collect::<Vec<&str>>();
548         let adapter_addr = addrs[addrs.len() - 3];
549         let device_addr = addrs[addrs.len() - 2];
550         // Create new Ini file if it doesn't already exist
551         adapter_conf_map.entry(adapter_addr.into()).or_insert(Ini::new_cs());
552         if !convert_from_bluez_device(
553             info_path.to_str().unwrap_or_default(),
554             device_addr,
555             adapter_conf_map.get_mut(adapter_addr).unwrap_or(&mut Ini::new_cs()),
556             /*is_hid_file=*/ false,
557         ) {
558             continue;
559         }
560 
561         // Check if we have HID info
562         if Path::new(hid_path.as_str()).exists() {
563             convert_from_bluez_device(
564                 hid_path.as_str(),
565                 device_addr,
566                 adapter_conf_map.get_mut(adapter_addr).unwrap_or(&mut Ini::new_cs()),
567                 /*is_hid_file=*/ true,
568             );
569         }
570     }
571 
572     // Write migration to appropriate adapter files
573     // TODO(b/232138101): Update for multi-adapter support
574     for (adapter, conf) in adapter_conf_map.iter_mut() {
575         let mut existing_conf = Ini::new_cs();
576         match existing_conf.load(FLOSS_CONF_FILE) {
577             Ok(ini) => {
578                 let devices = conf.sections();
579                 for (sec, props) in ini {
580                     // Drop devices that don't exist in BlueZ
581                     if sec.contains(':') && !devices.contains(&sec) {
582                         info!("Dropping a device in Floss that doesn't exist in BlueZ");
583                         continue;
584                     }
585                     // Keep keys that weren't transferrable
586                     for (k, v) in props {
587                         if conf.get(sec.as_str(), k.as_str()).is_none() {
588                             conf.set(sec.as_str(), k.as_str(), v);
589                         }
590                     }
591                 }
592             }
593             // Conf file doesn't exist yet
594             Err(_) => {
595                 conf.set(ADAPTER_SECTION_NAME, "Address", Some(adapter.clone()));
596             }
597         }
598         // Write contents to file
599         match conf.write(FLOSS_CONF_FILE) {
600             Ok(_) => {
601                 info!("Successfully migrated devices from BlueZ to Floss for adapter {}", adapter);
602             }
603             Err(err) => {
604                 error!(
605                     "Error migrating devices from BlueZ to Floss for adapter {}: {}",
606                     adapter, err
607                 );
608             }
609         }
610     }
611 }
612 
613 /// Helper function in Floss to BlueZ conversion that takes a Floss device that already
614 /// exists in BlueZ and keeps keys that weren't available from Floss conf file. Then
615 /// writes to BlueZ file to complete device migration.
616 ///
617 /// # Arguments
618 /// * `filepath` - A string that holds the path of the BlueZ info file
619 /// * `conf` - BlueZ Ini file that contains migrated Floss device
merge_and_write_bluez_conf(filepath: String, conf: &mut Ini)620 fn merge_and_write_bluez_conf(filepath: String, conf: &mut Ini) {
621     let mut existing_conf = Ini::new_cs();
622     existing_conf.set_comment_symbols(&['!', '#']);
623     if let Ok(ini) = existing_conf.load(filepath.clone()) {
624         // Device already exists in BlueZ
625         for (sec, props) in ini {
626             // Keep keys that weren't transferrable
627             for (k, v) in props {
628                 if conf.get(sec.as_str(), k.as_str()).is_none() {
629                     conf.set(sec.as_str(), k.as_str(), v);
630                 }
631             }
632         }
633     }
634     // Write BlueZ file
635     match conf.write(filepath.clone()) {
636         Ok(_) => {
637             info!("Successfully migrated Floss to BlueZ: {}", filepath);
638         }
639         Err(err) => {
640             error!("Error writing Floss to BlueZ: {}: {}", filepath, err);
641         }
642     }
643 }
644 
645 /// Helper function that does the conversion from Floss to BlueZ for a single adapter
646 ///
647 /// # Arguments
648 /// * `filename` - A string slice that holds the path of the Floss conf file to get device info from
convert_floss_conf(filename: &str)649 fn convert_floss_conf(filename: &str) {
650     let mut floss_conf = Ini::new_cs();
651     let floss_map = match floss_conf.load(filename) {
652         Ok(map) => map,
653         Err(err) => {
654             warn!(
655                 "Error opening ini file while converting Floss to BlueZ for {}: {}",
656                 filename, err
657             );
658             return;
659         }
660     };
661 
662     let adapter_addr = match floss_conf.get(ADAPTER_SECTION_NAME, "Address") {
663         Some(addr) => addr.to_uppercase(),
664         None => {
665             warn!("No adapter address during Floss to BlueZ migration in {}", filename);
666             return;
667         }
668     };
669 
670     // BlueZ info file map
671     let mut info_map: HashMap<&str, DeviceKey> = [
672         // General
673         ("Name", DeviceKey::new("Name", KeyAction::ToSection(GENERAL_SECTION_NAME))),
674         (
675             "DevClass",
676             DeviceKey::new(
677                 "Class",
678                 KeyAction::ApplyToSection(Converter::DecToHex, GENERAL_SECTION_NAME),
679             ),
680         ),
681         (
682             "Appearance",
683             DeviceKey::new(
684                 "Appearance",
685                 KeyAction::ApplyToSection(Converter::DecToHex, GENERAL_SECTION_NAME),
686             ),
687         ),
688         (
689             "DevType",
690             DeviceKey::new(
691                 "SupportedTechnologies",
692                 KeyAction::ApplyToSection(Converter::TypeF2B, GENERAL_SECTION_NAME),
693             ),
694         ),
695         (
696             "Service",
697             DeviceKey::new(
698                 "Services",
699                 KeyAction::ApplyToSection(
700                     Converter::ReplaceSpaceWithSemiColon,
701                     GENERAL_SECTION_NAME,
702                 ),
703             ),
704         ),
705         (
706             "AddrType",
707             DeviceKey::new(
708                 "AddressType",
709                 KeyAction::ApplyToSection(Converter::AddrTypeF2B, GENERAL_SECTION_NAME),
710             ),
711         ),
712         // LinkKey
713         (
714             "LinkKey",
715             DeviceKey::new(
716                 "Key",
717                 KeyAction::ApplyToSection(Converter::ReverseEndianUppercase, LINKKEY_SECTION_NAME),
718             ),
719         ),
720         ("LinkKeyType", DeviceKey::new("Type", KeyAction::ToSection(LINKKEY_SECTION_NAME))),
721         ("PinLength", DeviceKey::new("PINLength", KeyAction::ToSection(LINKKEY_SECTION_NAME))),
722         // DeviceID
723         ("VendorIdSource", DeviceKey::new("Source", KeyAction::ToSection(DEVICEID_SECTION_NAME))),
724         ("VendorId", DeviceKey::new("Vendor", KeyAction::ToSection(DEVICEID_SECTION_NAME))),
725         ("ProductId", DeviceKey::new("Product", KeyAction::ToSection(DEVICEID_SECTION_NAME))),
726         ("ProductVersion", DeviceKey::new("Version", KeyAction::ToSection(DEVICEID_SECTION_NAME))),
727     ]
728     .into();
729 
730     // BlueZ hog-uhid-cache file map
731     let mut hid_map: HashMap<&str, DeviceKey> = [
732         // General
733         ("HidVersion", DeviceKey::new("bcdhid", KeyAction::ToSection(GENERAL_SECTION_NAME))),
734         (
735             "HidCountryCode",
736             DeviceKey::new("bcountrycode", KeyAction::ToSection(GENERAL_SECTION_NAME)),
737         ),
738         // ReportMap
739         (
740             "HidDescriptor",
741             DeviceKey::new(
742                 "report_map",
743                 KeyAction::ApplyToSection(Converter::HexToBase64, REPORT_MAP_SECTION_NAME),
744             ),
745         ),
746     ]
747     .into();
748 
749     let mut devices: Vec<String> = Vec::new();
750     for (sec, props) in floss_map {
751         // Skip all the non-adapter sections
752         if !sec.contains(':') {
753             continue;
754         }
755         // Keep track of Floss devices we've seen so we can remove BlueZ devices that don't exist on Floss
756         devices.push(sec.clone());
757         let mut device_addr = sec.to_uppercase();
758         let mut bluez_info = Ini::new_cs();
759         let mut bluez_hid = Ini::new_cs();
760         let mut is_hid: bool = false;
761         let has_local_keys: bool = props.contains_key("LE_KEY_LENC");
762         for (k, v) in props {
763             // Special handling since in Floss LE_KEY_* are a combination of values in BlueZ
764             if k == "LE_KEY_PID" {
765                 // In Floss, LE_KEY_PID = IRK (128) + Identity Address Type (8) + Identity Address (48)
766                 // Identity Address Type is also found as "AddrType" key so no need to parse it here.
767                 let val = v.unwrap_or_default();
768                 if val.len() != 46 {
769                     warn!("LE_KEY_PID is not the correct size: {}", val);
770                     continue;
771                 }
772                 bluez_info.set(
773                     IRK_SECTION_NAME,
774                     "Key",
775                     Some(reverse_endianness(val[0..32].to_string(), true).unwrap_or_default()),
776                 );
777                 // BlueZ uses the identity address as the device path
778                 devices.retain(|d| *d != device_addr);
779                 device_addr = format!(
780                     "{}:{}:{}:{}:{}:{}",
781                     &val[34..36],
782                     &val[36..38],
783                     &val[38..40],
784                     &val[40..42],
785                     &val[42..44],
786                     &val[44..46]
787                 )
788                 .to_uppercase();
789                 devices.push(device_addr.clone().to_lowercase());
790                 continue;
791             } else if k == "LE_KEY_PENC" {
792                 let ltk = LtkInfo::try_from_penc(v.unwrap_or_default()).unwrap_or_default();
793                 let section_name = if has_local_keys {
794                     BLUEZ_PERIPHERAL_LTK_SECTION_NAME
795                 } else {
796                     LTK_SECTION_NAME
797                 };
798                 bluez_info.set(section_name, "Key", Some(format!("{:032X}", ltk.key)));
799                 bluez_info.set(section_name, "Rand", Some(format!("{}", ltk.rand)));
800                 bluez_info.set(section_name, "EDiv", Some(format!("{}", ltk.ediv)));
801                 bluez_info.set(
802                     section_name,
803                     "Authenticated",
804                     Some(format!("{}", LtkInfo::auth_to_bluez(ltk.auth))),
805                 );
806                 bluez_info.set(section_name, "EncSize", Some(format!("{}", ltk.len)));
807                 continue;
808             } else if k == "LE_KEY_LENC" {
809                 let ltk = LtkInfo::try_from_lenc(v.unwrap_or_default()).unwrap_or_default();
810                 bluez_info.set(
811                     BLUEZ_LOCAL_LTK_SECTION_NAME,
812                     "Key",
813                     Some(format!("{:032X}", ltk.key)),
814                 );
815                 bluez_info.set(BLUEZ_LOCAL_LTK_SECTION_NAME, "Rand", Some(format!("{}", ltk.rand)));
816                 bluez_info.set(BLUEZ_LOCAL_LTK_SECTION_NAME, "EDiv", Some(format!("{}", ltk.ediv)));
817                 bluez_info.set(
818                     BLUEZ_LOCAL_LTK_SECTION_NAME,
819                     "Authenticated",
820                     Some(format!("{}", LtkInfo::auth_to_bluez(ltk.auth))),
821                 );
822                 bluez_info.set(
823                     BLUEZ_LOCAL_LTK_SECTION_NAME,
824                     "EncSize",
825                     Some(format!("{}", ltk.len)),
826                 );
827                 continue;
828             }
829             // Convert matching info file keys
830             match info_map.get_mut(k.as_str()) {
831                 Some(key) => {
832                     let new_val = match key.apply_action(v.unwrap_or_default()) {
833                         Ok(val) => val,
834                         Err(err) => {
835                             warn!("Error converting Floss to Bluez key for adapter {}, device {}, key {}: {}", adapter_addr, device_addr, k, err);
836                             continue;
837                         }
838                     };
839                     bluez_info.set(key.section, key.key, Some(new_val));
840                     continue;
841                 }
842                 None => {
843                     debug!("No key match: {}", k)
844                 }
845             }
846             // Convert matching hog-uhid-cache file keys
847             match hid_map.get_mut(k.as_str()) {
848                 Some(key) => {
849                     is_hid = true;
850                     let new_val = match key.apply_action(v.unwrap_or_default()) {
851                         Ok(val) => val,
852                         Err(err) => {
853                             warn!("Error converting Floss to Bluez key for adapter {}, device {}, key {}: {}", adapter_addr, device_addr, k, err);
854                             continue;
855                         }
856                     };
857                     bluez_hid.set(key.section, key.key, Some(new_val));
858                 }
859                 None => {
860                     debug!("No key match: {}", k)
861                 }
862             }
863         }
864 
865         let path = format!("{}/{}/{}", BT_LIBDIR, adapter_addr, device_addr);
866 
867         // Create BlueZ device dir and all its parents if they're missing
868         match fs::create_dir_all(path.clone()) {
869             Ok(_) => (),
870             Err(err) => {
871                 error!("Error creating dirs during Floss to BlueZ device migration for adapter{}, device {}: {}", adapter_addr, device_addr, err);
872             }
873         }
874         // Write info file
875         merge_and_write_bluez_conf(format!("{}/{}", path, "info"), &mut bluez_info);
876 
877         // Write hog-uhid-cache file
878         if is_hid {
879             merge_and_write_bluez_conf(format!("{}/{}", path, "hog-uhid-cache"), &mut bluez_hid);
880         }
881     }
882 
883     // Delete devices that exist in BlueZ but not in Floss
884     if let Ok(globbed) = glob(format!("{}/{}/*:*", BT_LIBDIR, adapter_addr).as_str()) {
885         for entry in globbed {
886             let pathbuf = entry.unwrap_or_default();
887             let addrs = pathbuf.to_str().unwrap_or_default().split('/').collect::<Vec<&str>>();
888             let device_addr: String = addrs[addrs.len() - 1].into();
889             if !devices.contains(&device_addr.to_lowercase()) {
890                 if let Err(err) = fs::remove_dir_all(pathbuf) {
891                     warn!(
892                         "Error removing {} during Floss to BlueZ device migration: {}",
893                         device_addr, err
894                     );
895                 }
896             }
897         }
898     }
899 }
900 
901 /// This is the main function that handles the device migration from Floss to BlueZ.
migrate_floss_devices()902 pub fn migrate_floss_devices() {
903     // Find and parse all Floss conf files
904     // TODO(b/232138101): Currently Floss only supports a single adapter; update here for multi-adapter support
905     let globbed = match glob(FLOSS_CONF_FILE) {
906         Ok(v) => v,
907         Err(_) => {
908             warn!("Didn't find Floss conf file to migrate");
909             return;
910         }
911     };
912 
913     for entry in globbed {
914         convert_floss_conf(entry.unwrap_or_default().to_str().unwrap_or_default());
915     }
916 }
917 
918 #[cfg(test)]
919 mod tests {
920     use super::*;
921 
922     #[test]
test_device_key_wrapok()923     fn test_device_key_wrapok() {
924         let test_str = String::from("do_nothing");
925         let mut key = DeviceKey::new("", KeyAction::WrapOk);
926         assert_eq!(key.apply_action(test_str.clone()), Ok(test_str));
927     }
928 
929     #[test]
test_device_key_to_section()930     fn test_device_key_to_section() {
931         let test_str = String::from("do_nothing");
932         let mut key = DeviceKey::new("", KeyAction::ToSection(LINKKEY_SECTION_NAME));
933         assert_eq!(key.apply_action(test_str.clone()), Ok(test_str));
934         assert_eq!(key.section, LINKKEY_SECTION_NAME)
935     }
936 
937     #[test]
test_device_key_apply_dec_to_hex()938     fn test_device_key_apply_dec_to_hex() {
939         // DevClass example
940         let mut key = DeviceKey::new("", KeyAction::Apply(Converter::DecToHex));
941         assert_eq!(key.apply_action("2360344".to_string()), Ok("0x240418".to_string()));
942         assert_eq!(
943             key.apply_action("236034B".to_string()),
944             Err("Error converting from dec string to hex string: invalid digit found in string"
945                 .to_string())
946         );
947     }
948 
949     #[test]
test_device_key_apply_to_section_hex_to_dec()950     fn test_device_key_apply_to_section_hex_to_dec() {
951         // DevClass example
952         let mut key = DeviceKey::new(
953             "",
954             KeyAction::ApplyToSection(Converter::HexToDec, GENERAL_SECTION_NAME),
955         );
956         assert_eq!(key.apply_action("0x240418".to_string()), Ok("2360344".to_string()));
957         assert_eq!(key.section, GENERAL_SECTION_NAME);
958         assert_eq!(
959             key.apply_action("236034T".to_string()),
960             Err("Error converting from hex string to dec string: invalid digit found in string"
961                 .to_string())
962         );
963     }
964 
965     #[test]
test_hex_to_base64()966     fn test_hex_to_base64() {
967         // HID report map example taken from real HID mouse conversion
968         let mut key = DeviceKey::new("", KeyAction::Apply(Converter::HexToBase64));
969         assert_eq!(
970             key.apply_action("05010906a1018501050719e029e71500250175019508810295067508150026a400050719002aa4008100c005010902a10185020901a1009510750115002501050919012910810205011601f826ff07750c95020930093181061581257f75089501093881069501050c0a38028106c0c00643ff0a0202a101851175089513150026ff000902810009029100c0".to_string()),
971             Ok("BQEJBqEBhQEFBxngKecVACUBdQGVCIEClQZ1CBUAJqQABQcZACqkAIEAwAUBCQKhAYUCCQGhAJUQdQEVACUBBQkZASkQgQIFARYB+Cb/B3UMlQIJMAkxgQYVgSV/dQiVAQk4gQaVAQUMCjgCgQbAwAZD/woCAqEBhRF1CJUTFQAm/wAJAoEACQKRAMA=".to_string())
972         );
973         assert_eq!(
974             key.apply_action("x5010906a1018501050719e029e71500250175019508810295067508150026a400050719002aa4008100c005010902a10185020901a1009510750115002501050919012910810205011601f826ff07750c95020930093181061581257f75089501093881069501050c0a38028106c0c00643ff0a0202a101851175089513150026ff000902810009029100c0".to_string()),
975             Err("Error converting from hex string to base64 string: invalid digit found in string".to_string())
976         );
977     }
978 
979     #[test]
test_hex_to_base64_to_hex()980     fn test_hex_to_base64_to_hex() {
981         // HID report map example taken from real HID mouse conversion
982         let mut key = DeviceKey::new("", KeyAction::Apply(Converter::Base64ToHex));
983         assert_eq!(
984             key.apply_action("BQEJBqEBhQEFBxngKecVACUBdQGVCIEClQZ1CBUAJqQABQcZACqkAIEAwAUBCQKhAYUCCQGhAJUQdQEVACUBBQkZASkQgQIFARYB+Cb/B3UMlQIJMAkxgQYVgSV/dQiVAQk4gQaVAQUMCjgCgQbAwAZD/woCAqEBhRF1CJUTFQAm/wAJAoEACQKRAMA=".to_string()),
985             Ok("05010906a1018501050719e029e71500250175019508810295067508150026a400050719002aa4008100c005010902a10185020901a1009510750115002501050919012910810205011601f826ff07750c95020930093181061581257f75089501093881069501050c0a38028106c0c00643ff0a0202a101851175089513150026ff000902810009029100c0".to_string())
986         );
987         assert_eq!(
988             key.apply_action("!BQEJBqEBhQEFBxngKecVACUBdQGVCIEClQZ1CBUAJqQABQcZACqkAIEAwAUBCQKhAYUCCQGhAJUQdQEVACUBBQkZASkQgQIFARYB+Cb/B3UMlQIJMAkxgQYVgSV/dQiVAQk4gQaVAQUMCjgCgQbAwAZD/woCAqEBhRF1CJUTFQAm/wAJAoEACQKRAMA=".to_string()),
989             Err("Error converting from base64 string to hex string: Encoded text cannot have a 6-bit remainder.".to_string())
990         );
991     }
992 
993     #[test]
test_typeb2f()994     fn test_typeb2f() {
995         let mut key = DeviceKey::new("", KeyAction::Apply(Converter::TypeB2F));
996         assert_eq!(key.apply_action(CLASSIC_TYPE.to_string()), Ok("1".to_string()));
997         assert_eq!(key.apply_action(LE_TYPE.to_string()), Ok("2".to_string()));
998         assert_eq!(key.apply_action(DUAL_TYPE.to_string()), Ok("3".to_string()));
999         assert_eq!(
1000             key.apply_action("FAKE_TYPE".to_string()),
1001             Err("Error converting type. Unknown type: FAKE_TYPE".to_string())
1002         );
1003     }
1004 
1005     #[test]
test_typef2b()1006     fn test_typef2b() {
1007         let mut key = DeviceKey::new("", KeyAction::Apply(Converter::TypeF2B));
1008         assert_eq!(key.apply_action("1".to_string()), Ok(CLASSIC_TYPE.to_string()));
1009         assert_eq!(key.apply_action("2".to_string()), Ok(LE_TYPE.to_string()));
1010         assert_eq!(key.apply_action("3".to_string()), Ok(DUAL_TYPE.to_string()));
1011         assert_eq!(
1012             key.apply_action("FAKE_TYPE".to_string()),
1013             Err("Error converting type. Unknown type: FAKE_TYPE".to_string())
1014         );
1015     }
1016 
1017     #[test]
test_addrtypeb2f()1018     fn test_addrtypeb2f() {
1019         let mut key = DeviceKey::new("", KeyAction::Apply(Converter::AddrTypeB2F));
1020         assert_eq!(key.apply_action("public".to_string()), Ok("0".to_string()));
1021         assert_eq!(key.apply_action("static".to_string()), Ok("1".to_string()));
1022         assert_eq!(
1023             key.apply_action("FAKE_TYPE".to_string()),
1024             Err("Error converting address type. Unknown type: FAKE_TYPE".to_string())
1025         );
1026     }
1027 
1028     #[test]
test_addrtypef2b()1029     fn test_addrtypef2b() {
1030         let mut key = DeviceKey::new("", KeyAction::Apply(Converter::AddrTypeF2B));
1031         assert_eq!(key.apply_action("0".to_string()), Ok("public".to_string()));
1032         assert_eq!(key.apply_action("1".to_string()), Ok("static".to_string()));
1033         assert_eq!(
1034             key.apply_action("FAKE_TYPE".to_string()),
1035             Err("Error converting address type. Unknown type: FAKE_TYPE".to_string())
1036         );
1037     }
1038 
1039     #[test]
test_reverseendian()1040     fn test_reverseendian() {
1041         let mut key = DeviceKey::new("", KeyAction::Apply(Converter::ReverseEndianLowercase));
1042         assert_eq!(
1043             key.apply_action("00112233445566778899AABBCCDDEEFF".to_string()),
1044             Ok("ffeeddccbbaa99887766554433221100".to_string())
1045         );
1046         // Link key too small shouldn't panic
1047         assert_eq!(
1048             key.apply_action("00112233445566778899AABBCCDDEE".to_string()),
1049             Ok("eeddccbbaa9988776655443322110000".to_string())
1050         );
1051         // Conversion shouldn't lose leading zeros
1052         assert_eq!(
1053             key.apply_action("112233445566778899AABBCCDDEE0000".to_string()),
1054             Ok("0000eeddccbbaa998877665544332211".to_string())
1055         );
1056     }
1057 
1058     #[test]
test_replacespacewithsemicolon()1059     fn test_replacespacewithsemicolon() {
1060         // UUID example
1061         let mut key = DeviceKey::new("", KeyAction::Apply(Converter::ReplaceSpaceWithSemiColon));
1062         assert_eq!(
1063             key.apply_action(
1064                 "00001800-0000-1000-8000-00805f9b34fb 00001801-0000-1000-8000-00805f9b34fb "
1065                     .to_string()
1066             ),
1067             Ok("00001800-0000-1000-8000-00805f9b34fb;00001801-0000-1000-8000-00805f9b34fb;"
1068                 .to_string())
1069         );
1070     }
1071 
1072     #[test]
test_replacesemicolonwithspace()1073     fn test_replacesemicolonwithspace() {
1074         // UUID example
1075         let mut key = DeviceKey::new("", KeyAction::Apply(Converter::ReplaceSemiColonWithSpace));
1076         assert_eq!(
1077             key.apply_action(
1078                 "00001800-0000-1000-8000-00805f9b34fb;00001801-0000-1000-8000-00805f9b34fb;"
1079                     .to_string()
1080             ),
1081             Ok("00001800-0000-1000-8000-00805f9b34fb 00001801-0000-1000-8000-00805f9b34fb "
1082                 .to_string())
1083         );
1084     }
1085 
1086     #[test]
test_irk_conversion()1087     fn test_irk_conversion() {
1088         let mut key = DeviceKey::new("", KeyAction::Apply(Converter::ReverseEndianUppercase));
1089         assert_eq!(
1090             key.apply_action("d584da72ceccfdf462405b558441ed44".to_string()),
1091             Ok("44ED4184555B4062F4FDCCCE72DA84D5".to_string())
1092         );
1093         assert_eq!(
1094             key.apply_action("td584da72ceccfdf462405b558441ed44".to_string()),
1095             Err("Error converting link key: invalid digit found in string".to_string())
1096         );
1097     }
1098 
1099     #[test]
test_ltk_conversion()1100     fn test_ltk_conversion() {
1101         let floss_penc_key =
1102             String::from("48fdc93d776cd8cc918f31e422ece00d2322924fa9a09fb30eb20110");
1103         let pltk = LtkInfo::try_from_penc(floss_penc_key).unwrap_or_default();
1104         assert_eq!(pltk.key, 0x48FDC93D776CD8CC918F31E422ECE00D);
1105         assert_eq!(pltk.rand, 12943240503130989091);
1106         assert_eq!(pltk.ediv, 45582);
1107         assert_eq!(pltk.auth, 1);
1108         assert_eq!(pltk.len, 16);
1109         assert_eq!(
1110             LtkInfo::try_from_penc(
1111                 "48fdc93d776cd8cc918f31e422ece00d2322924fa9a09fb30eb2011".to_string()
1112             )
1113             .unwrap_err(),
1114             "PENC String provided to LtkInfo is not the right size"
1115         );
1116 
1117         let floss_lenc_key = String::from("07a104a6d2b464d74ff7e2e80b20295600001001");
1118         let lltk = LtkInfo::try_from_lenc(floss_lenc_key).unwrap_or_default();
1119         assert_eq!(lltk.key, 0x07A104A6D2B464D74FF7E2E80B202956);
1120         assert_eq!(lltk.rand, 0);
1121         assert_eq!(lltk.ediv, 0);
1122         assert_eq!(lltk.auth, 1);
1123         assert_eq!(lltk.len, 16);
1124         assert_eq!(
1125             LtkInfo::try_from_lenc("07a104a6d2b464d74ff7e2e80b2029560000100".to_string())
1126                 .unwrap_err(),
1127             "LENC String provided to LtkInfo is not the right size"
1128         );
1129     }
1130 
1131     #[test]
test_convert_from_bluez_device()1132     fn test_convert_from_bluez_device() {
1133         let test_addr = "00:11:22:33:44:55";
1134         let mut conf = Ini::new_cs();
1135         assert!(convert_from_bluez_device(
1136             "test/migrate/fake_bluez_info.toml",
1137             test_addr,
1138             &mut conf,
1139             false
1140         ));
1141         assert!(convert_from_bluez_device(
1142             "test/migrate/fake_bluez_hid.toml",
1143             test_addr,
1144             &mut conf,
1145             true
1146         ));
1147 
1148         assert_eq!(conf.get(test_addr, "Name"), Some(String::from("Test Device")));
1149         assert_eq!(conf.get(test_addr, "DevClass"), Some(String::from("2360344")));
1150         assert_eq!(conf.get(test_addr, "Appearance"), Some(String::from("962")));
1151         assert_eq!(conf.get(test_addr, "DevType"), Some(String::from("1")));
1152         assert_eq!(
1153             conf.get(test_addr, "Service"),
1154             Some(String::from(
1155                 "0000110b-0000-1000-8000-00805f9b34fb 0000110c-0000-1000-8000-00805f9b34fb "
1156             ))
1157         );
1158         assert_eq!(conf.get(test_addr, "AddrType"), Some(String::from("1")));
1159 
1160         assert_eq!(
1161             conf.get(test_addr, "LinkKey"),
1162             Some(String::from("ffeeddccbbaa99887766554433221100"))
1163         );
1164         assert_eq!(conf.get(test_addr, "LinkKeyType"), Some(String::from("4")));
1165         assert_eq!(conf.get(test_addr, "PinLength"), Some(String::from("0")));
1166 
1167         assert_eq!(conf.get(test_addr, "SdpDiVendorIdSource"), Some(String::from("1")));
1168         assert_eq!(conf.get(test_addr, "SdpDiManufacturer"), Some(String::from("100")));
1169         assert_eq!(conf.get(test_addr, "SdpDiModel"), Some(String::from("22222")));
1170         assert_eq!(conf.get(test_addr, "SdpDiHardwareVersion"), Some(String::from("3")));
1171 
1172         assert_eq!(conf.get(test_addr, "VendorIdSource"), Some(String::from("1")));
1173         assert_eq!(conf.get(test_addr, "VendorId"), Some(String::from("100")));
1174         assert_eq!(conf.get(test_addr, "ProductId"), Some(String::from("22222")));
1175         assert_eq!(conf.get(test_addr, "ProductVersion"), Some(String::from("3")));
1176 
1177         assert_eq!(
1178             conf.get(test_addr, "LE_KEY_PID"),
1179             Some(String::from("ffeeddccbbaa9988776655443322110001001122334455"))
1180         );
1181         assert_eq!(
1182             conf.get(test_addr, "LE_KEY_PENC"),
1183             Some(String::from("00112233445566778899aabbccddeeff8877665544332211bbaa0210"))
1184         );
1185 
1186         assert_eq!(conf.get(test_addr, "HidAttrMask"), Some(String::from("0")));
1187         assert_eq!(
1188             conf.get(test_addr, "HidDescriptor"),
1189             Some(String::from("05010906a1018501050719e029e7150025017501950881029505050819012905910295017503910195067508150026a400050719002aa4008100c005010902a10185020901a1009510750115002501050919012910810205011601f826ff07750c95020930093181061581257f75089501093881069501050c0a38028106c0c0050c0901a1018503751095021501268c0219012a8c028160c00643ff0a0202a101851175089513150026ff000902810009029100c0"))
1190         );
1191         assert_eq!(conf.get(test_addr, "HidVersion"), Some(String::from("273")));
1192         assert_eq!(conf.get(test_addr, "HidCountryCode"), Some(String::from("3")));
1193     }
1194 }
1195