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