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