| 1 | //! Bluetooth addresses and UUIDs. |
| 2 | |
| 3 | use std::fmt; |
| 4 | use std::str::FromStr; |
| 5 | |
| 6 | use serde::{Serialize, Serializer}; |
| 7 | |
| 8 | #[cfg(target_os = "linux")] |
| 9 | use crate::error::{Error, Result}; |
| 10 | |
| 11 | /// A Bluetooth device address, kept in the order people write it (`AA:BB:CC:DD:EE:FF`). |
| 12 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| 13 | pub struct BdAddr([u8; 6]); |
| 14 | |
| 15 | impl BdAddr { |
| 16 | /// An address from its bytes, in the order people write it. |
| 17 | pub const fn new(bytes: [u8; 6]) -> Self { |
| 18 | BdAddr(bytes) |
| 19 | } |
| 20 | |
| 21 | /// The address as the kernel's `bdaddr_t` stores it: reversed. |
| 22 | /// Spec: transport.md#sockets |
| 23 | #[cfg(target_os = "linux")] |
| 24 | pub(crate) fn kernel_order(self) -> [u8; 6] { |
| 25 | let mut bytes = self.0; |
| 26 | bytes.reverse(); |
| 27 | bytes |
| 28 | } |
| 29 | } |
| 30 | |
| 31 | #[derive(Debug, Clone, PartialEq, Eq)] |
| 32 | pub struct AddrParseError(String); |
| 33 | |
| 34 | impl fmt::Display for AddrParseError { |
| 35 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 36 | write!(f, "{:?} is not a Bluetooth address like AA:BB:CC:DD:EE:FF", self.0) |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | impl std::error::Error for AddrParseError {} |
| 41 | |
| 42 | impl FromStr for BdAddr { |
| 43 | type Err = AddrParseError; |
| 44 | |
| 45 | fn from_str(text: &str) -> std::result::Result<Self, Self::Err> { |
| 46 | let error = || AddrParseError(text.to_owned()); |
| 47 | let mut bytes = [0u8; 6]; |
| 48 | let mut parts = text.split(':'); |
| 49 | for byte in &mut bytes { |
| 50 | let part = parts.next().ok_or_else(error)?; |
| 51 | if part.len() != 2 { |
| 52 | return Err(error()); |
| 53 | } |
| 54 | *byte = u8::from_str_radix(part, 16).map_err(|_| error())?; |
| 55 | } |
| 56 | if parts.next().is_some() { |
| 57 | return Err(error()); |
| 58 | } |
| 59 | Ok(BdAddr(bytes)) |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | impl fmt::Display for BdAddr { |
| 64 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 65 | let [a, b, c, d, e, g] = self.0; |
| 66 | write!(f, "{a:02X}:{b:02X}:{c:02X}:{d:02X}:{e:02X}:{g:02X}") |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | impl Serialize for BdAddr { |
| 71 | fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> { |
| 72 | serializer.collect_str(self) |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | /// A 128-bit Bluetooth UUID. |
| 77 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 78 | pub struct Uuid(u128); |
| 79 | |
| 80 | /// `00000000-0000-1000-8000-00805F9B34FB`; short UUIDs are offsets into it. |
| 81 | /// Spec: transport.md#sdp-data-elements |
| 82 | const BASE_UUID: u128 = 0x0000_0000_0000_1000_8000_0080_5F9B_34FB; |
| 83 | |
| 84 | impl Uuid { |
| 85 | pub const fn from_u128(value: u128) -> Self { |
| 86 | Uuid(value) |
| 87 | } |
| 88 | |
| 89 | /// A 16- or 32-bit UUID, expanded with the Bluetooth base UUID. |
| 90 | pub const fn from_u32(short: u32) -> Self { |
| 91 | Uuid(BASE_UUID | ((short as u128) << 96)) |
| 92 | } |
| 93 | |
| 94 | /// RFCOMM's protocol UUID (`0x0003`). |
| 95 | /// Spec: transport.md#sdp-rfcomm-channel |
| 96 | pub const RFCOMM: Uuid = Uuid::from_u32(0x0003); |
| 97 | |
| 98 | pub fn to_be_bytes(self) -> [u8; 16] { |
| 99 | self.0.to_be_bytes() |
| 100 | } |
| 101 | |
| 102 | /// A UUID as SDP encodes it: 2, 4 or 16 big-endian bytes. |
| 103 | /// Spec: transport.md#sdp-data-elements |
| 104 | #[cfg(target_os = "linux")] |
| 105 | pub(crate) fn from_sdp(bytes: &[u8]) -> Result<Self> { |
| 106 | match *bytes { |
| 107 | [a, b] => Ok(Uuid::from_u32(u32::from(u16::from_be_bytes([a, b])))), |
| 108 | [a, b, c, d] => Ok(Uuid::from_u32(u32::from_be_bytes([a, b, c, d]))), |
| 109 | _ => { |
| 110 | let long: [u8; 16] = bytes |
| 111 | .try_into() |
| 112 | .map_err(|_| Error::Protocol(format!("SDP UUID of {} bytes", bytes.len())))?; |
| 113 | Ok(Uuid(u128::from_be_bytes(long))) |
| 114 | } |
| 115 | } |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | impl fmt::Display for Uuid { |
| 120 | /// Lowercase and hyphenated, as BlueZ writes it. |
| 121 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 122 | let hex = format!("{:032x}", self.0); |
| 123 | let part = |range: std::ops::Range<usize>| hex.get(range).unwrap_or_default(); |
| 124 | write!( |
| 125 | f, |
| 126 | "{}-{}-{}-{}-{}", |
| 127 | part(0..8), |
| 128 | part(8..12), |
| 129 | part(12..16), |
| 130 | part(16..20), |
| 131 | part(20..32) |
| 132 | ) |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | #[cfg(test)] |
| 137 | mod tests { |
| 138 | use super::*; |
| 139 | |
| 140 | #[test] |
| 141 | fn address_round_trip_and_kernel_order() { |
| 142 | let addr: BdAddr = "AA:bb:CC:01:02:03".parse().unwrap(); |
| 143 | assert_eq!(addr.to_string(), "AA:BB:CC:01:02:03"); |
| 144 | assert_eq!(addr.kernel_order(), [0x03, 0x02, 0x01, 0xCC, 0xBB, 0xAA]); |
| 145 | } |
| 146 | |
| 147 | #[test] |
| 148 | fn bad_addresses_are_rejected() { |
| 149 | for text in [ |
| 150 | "", |
| 151 | "AA:BB:CC:DD:EE", |
| 152 | "AA:BB:CC:DD:EE:FF:00", |
| 153 | "AA:BB:CC:DD:EE:F", |
| 154 | "GG:BB:CC:DD:EE:FF", |
| 155 | ] { |
| 156 | assert!(text.parse::<BdAddr>().is_err(), "{text}"); |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | #[test] |
| 161 | fn uuid_display_matches_bluez() { |
| 162 | let uuid = Uuid::from_u128(0x0000_079A_D102_11E1_9B23_0002_5B00_A5A5); |
| 163 | assert_eq!(uuid.to_string(), "0000079a-d102-11e1-9b23-00025b00a5a5"); |
| 164 | assert_eq!(Uuid::RFCOMM.to_string(), "00000003-0000-1000-8000-00805f9b34fb"); |
| 165 | } |
| 166 | |
| 167 | #[test] |
| 168 | fn short_sdp_uuids_expand_with_the_base() { |
| 169 | assert_eq!(Uuid::from_sdp(&[0x00, 0x03]).unwrap(), Uuid::RFCOMM); |
| 170 | assert_eq!(Uuid::from_sdp(&[0x00, 0x00, 0x00, 0x03]).unwrap(), Uuid::RFCOMM); |
| 171 | assert_eq!(Uuid::from_sdp(&Uuid::RFCOMM.to_be_bytes()).unwrap(), Uuid::RFCOMM); |
| 172 | assert!(Uuid::from_sdp(&[1, 2, 3]).is_err()); |
| 173 | } |
| 174 | } |