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