Krolyxon
Commit 65fed26

Branches

Refs →

budctlcratesbudctl-coresrcbt.rs

178 lines · 5.3 KB · Rust

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