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