Skip to main content

btuuid/
lib.rs

1#![no_std]
2
3//! Bluetooth UUID values, including 16-, 32- and 128-bit UUIDs.
4
5#[cfg(feature = "alloc")]
6extern crate alloc;
7
8#[cfg(feature = "std")]
9extern crate std;
10
11use core::array::TryFromSliceError;
12use core::fmt::{Debug, Display};
13
14pub mod ad_types;
15pub mod appearance;
16pub mod browse_group_identifiers;
17pub mod characteristic;
18pub mod declarations;
19pub mod descriptors;
20pub mod mesh_profile;
21pub mod object_types;
22pub mod protocol_identifiers;
23pub mod service;
24pub mod service_class;
25pub mod units;
26
27#[deprecated(since = "0.1.0", note = "use BluetoothUuid128::base() instead.")]
28#[doc(hidden)]
29/// Included for bt-hci semver compatibility
30pub const BLUETOOTH_BASE_UUID: [u8; 16] = [
31    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB,
32];
33
34/// A Bluetooth UUID of any valid length (16, 32, or 128 bits).
35#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub enum BluetoothUuid {
38    /// A 16-bit Bluetooth UUID.
39    Uuid16(BluetoothUuid16),
40    /// A 32-bit Bluetooth UUID.
41    Uuid32(BluetoothUuid32),
42    /// A 128-bit Bluetooth UUID.
43    Uuid128(BluetoothUuid128),
44}
45
46/// An error returned when a byte slice has an invalid length for a Bluetooth UUID.
47///
48/// A valid slice must be 2, 4, or 16 bytes long.
49#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
50#[cfg_attr(feature = "defmt", derive(defmt::Format))]
51pub struct InvalidLengthError;
52
53impl Display for InvalidLengthError {
54    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
55        f.write_str("Invalid byte length for Bluetooth UUID")
56    }
57}
58
59impl core::error::Error for InvalidLengthError {}
60
61impl From<TryFromSliceError> for InvalidLengthError {
62    fn from(_: TryFromSliceError) -> Self {
63        InvalidLengthError
64    }
65}
66
67impl BluetoothUuid {
68    /// Creates a nil (all-zero) UUID.
69    pub const fn nil() -> Self {
70        Self::Uuid128(BluetoothUuid128::nil())
71    }
72
73    /// Creates a `BluetoothUuid` from a 16-bit unsigned integer.
74    pub const fn from_u16(v: u16) -> Self {
75        Self::Uuid16(BluetoothUuid16::new(v))
76    }
77
78    /// Creates a `BluetoothUuid` from a 32-bit unsigned integer.
79    ///
80    /// This will create a `Uuid16` if the value fits, otherwise it will create a `Uuid32`.
81    pub const fn from_u32(v: u32) -> Self {
82        if v <= u16::MAX as u32 {
83            Self::from_u16(v as u16)
84        } else {
85            Self::Uuid32(BluetoothUuid32::new(v))
86        }
87    }
88
89    /// Creates a `BluetoothUuid` from a 128-bit unsigned integer.
90    ///
91    /// If the 128-bit value corresponds to a standard Bluetooth Base UUID,
92    /// it will be shortened to a `Uuid16` or `Uuid32` representation.
93    pub const fn from_u128(v: u128) -> Self {
94        let uuid = BluetoothUuid128::new(v);
95        let (_, base_d2, base_d3, base_d4) = BluetoothUuid128::base().to_fields();
96        let (d1, d2, d3, d4) = uuid.to_fields();
97        if d2 == base_d2 && d3 == base_d3 && d4 == base_d4 {
98            Self::from_u32(d1)
99        } else {
100            Self::Uuid128(uuid)
101        }
102    }
103
104    /// Attempts to create a `BluetoothUuid` from a little-endian byte slice.
105    ///
106    /// The slice must have a length of 2, 4, or 16 bytes.
107    pub fn from_le_slice(b: &[u8]) -> Result<Self, InvalidLengthError> {
108        match b.len() {
109            2 => Ok(BluetoothUuid16::from_le_slice(b)?.into()),
110            4 => Ok(BluetoothUuid32::from_le_slice(b)?.into()),
111            16 => Ok(BluetoothUuid128::from_le_slice(b)?.into()),
112            _ => Err(InvalidLengthError),
113        }
114    }
115
116    /// Attempts to create a `BluetoothUuid` from a big-endian byte slice.
117    ///
118    /// The slice must have a length of 2, 4, or 16 bytes.
119    pub fn from_be_slice(b: &[u8]) -> Result<Self, InvalidLengthError> {
120        match b.len() {
121            2 => Ok(BluetoothUuid16::from_be_slice(b)?.into()),
122            4 => Ok(BluetoothUuid32::from_be_slice(b)?.into()),
123            16 => Ok(BluetoothUuid128::from_be_slice(b)?.into()),
124            _ => Err(InvalidLengthError),
125        }
126    }
127
128    /// Converts any `BluetoothUuid` variant to its full 128-bit representation.
129    pub const fn to_u128(&self) -> u128 {
130        match self {
131            Self::Uuid16(uuid) => BluetoothUuid128::base().set_initial_group(uuid.to_u16() as u32),
132            Self::Uuid32(uuid) => BluetoothUuid128::base().set_initial_group(uuid.to_u32()),
133            Self::Uuid128(uuid) => *uuid,
134        }
135        .to_u128()
136    }
137
138    /// Returns a little-endian byte slice representation of the UUID.
139    ///
140    /// The length of the slice will be 2, 4, or 16 bytes depending on the UUID variant.
141    pub const fn as_le_slice(&self) -> &[u8] {
142        match self {
143            Self::Uuid16(uuid) => uuid.as_le_slice(),
144            Self::Uuid32(uuid) => uuid.as_le_slice(),
145            Self::Uuid128(uuid) => uuid.as_le_slice(),
146        }
147    }
148}
149
150impl Debug for BluetoothUuid {
151    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
152        write!(f, "BluetoothUuid({})", self)
153    }
154}
155
156impl Display for BluetoothUuid {
157    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
158        match self {
159            BluetoothUuid::Uuid16(uuid) => Display::fmt(uuid, f),
160            BluetoothUuid::Uuid32(uuid) => Display::fmt(uuid, f),
161            BluetoothUuid::Uuid128(uuid) => Display::fmt(uuid, f),
162        }
163    }
164}
165
166#[cfg(feature = "defmt")]
167impl defmt::Format for BluetoothUuid {
168    fn format(&self, fmt: defmt::Formatter) {
169        match self {
170            BluetoothUuid::Uuid16(uuid) => defmt::write!(fmt, "{}", uuid),
171            BluetoothUuid::Uuid32(uuid) => defmt::write!(fmt, "{}", uuid),
172            BluetoothUuid::Uuid128(uuid) => defmt::write!(fmt, "{}", uuid),
173        }
174    }
175}
176
177impl Default for BluetoothUuid {
178    fn default() -> Self {
179        Self::nil()
180    }
181}
182
183impl AsRef<[u8]> for BluetoothUuid {
184    fn as_ref(&self) -> &[u8] {
185        self.as_le_slice()
186    }
187}
188
189impl From<BluetoothUuid16> for BluetoothUuid {
190    fn from(value: BluetoothUuid16) -> Self {
191        Self::Uuid16(value)
192    }
193}
194
195impl From<u16> for BluetoothUuid {
196    fn from(value: u16) -> Self {
197        Self::from_u16(value)
198    }
199}
200
201impl From<[u8; 2]> for BluetoothUuid {
202    fn from(value: [u8; 2]) -> Self {
203        // Assumes value is in little-endian order
204        Self::Uuid16(BluetoothUuid16(value))
205    }
206}
207
208impl From<BluetoothUuid32> for BluetoothUuid {
209    fn from(value: BluetoothUuid32) -> Self {
210        Self::from_u32(value.to_u32())
211    }
212}
213
214impl From<u32> for BluetoothUuid {
215    fn from(value: u32) -> Self {
216        Self::from_u32(value)
217    }
218}
219
220impl From<[u8; 4]> for BluetoothUuid {
221    fn from(value: [u8; 4]) -> Self {
222        // Assumes value is in little-endian order
223        Self::Uuid32(BluetoothUuid32(value))
224    }
225}
226
227impl From<BluetoothUuid128> for BluetoothUuid {
228    fn from(value: BluetoothUuid128) -> Self {
229        Self::from_u128(value.to_u128())
230    }
231}
232
233impl From<u128> for BluetoothUuid {
234    fn from(value: u128) -> Self {
235        Self::from_u128(value)
236    }
237}
238
239impl From<[u8; 16]> for BluetoothUuid {
240    fn from(value: [u8; 16]) -> Self {
241        // Assumes value is in little-endian order
242        Self::Uuid128(BluetoothUuid128(value))
243    }
244}
245
246#[cfg(feature = "uuid")]
247impl From<uuid::Uuid> for BluetoothUuid {
248    fn from(value: uuid::Uuid) -> Self {
249        BluetoothUuid128::new(value.as_u128()).into()
250    }
251}
252
253#[cfg(feature = "uuid")]
254impl From<BluetoothUuid> for uuid::Uuid {
255    fn from(value: BluetoothUuid) -> Self {
256        uuid::Uuid::from_u128(value.to_u128())
257    }
258}
259
260#[cfg(feature = "alloc")]
261impl From<BluetoothUuid> for alloc::vec::Vec<u8> {
262    fn from(value: BluetoothUuid) -> Self {
263        value.as_le_slice().to_vec()
264    }
265}
266
267#[cfg(feature = "alloc")]
268impl TryFrom<alloc::vec::Vec<u8>> for BluetoothUuid {
269    type Error = InvalidLengthError;
270
271    fn try_from(value: alloc::vec::Vec<u8>) -> Result<Self, Self::Error> {
272        Self::from_le_slice(&value)
273    }
274}
275
276macro_rules! impl_uuid {
277    {
278        $(#[$attrs:meta])*
279        struct $name:ident($num:ty, $bytes:expr, $to:ident);
280    } => {
281        #[repr(transparent)]
282        #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
283        #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
284        $(#[$attrs])*
285        pub struct $name([u8; $bytes]);
286
287        impl $name {
288            /// Creates a new UUID.
289            pub const fn new(uuid: $num) -> Self {
290                Self(uuid.to_le_bytes())
291            }
292
293            /// Creates a UUID from a little-endian byte array.
294            pub const fn from_le_bytes(bytes: [u8; $bytes]) -> Self {
295                Self(bytes)
296            }
297
298            /// Creates a UUID from a big-endian byte array.
299            pub fn from_be_bytes(mut bytes: [u8; $bytes]) -> Self {
300                bytes.reverse();
301                Self(bytes)
302            }
303
304            /// Attempts to create a UUID from a little-endian byte slice.
305            pub fn from_le_slice(bytes: &[u8]) -> Result<Self, InvalidLengthError> {
306                Ok(Self::from_le_bytes(bytes.try_into()?))
307            }
308
309            /// Attempts to create a UUID from a big-endian byte slice.
310            pub fn from_be_slice(bytes: &[u8]) -> Result<Self, InvalidLengthError> {
311                let mut bytes: [u8; $bytes] = bytes.try_into()?;
312                bytes.reverse();
313                Ok(Self::from_le_bytes(bytes))
314            }
315
316            /// Returns the UUID as a little-endian byte array.
317            pub const fn as_le_bytes(&self) -> &[u8; $bytes] {
318                &self.0
319            }
320
321            /// Consumes the UUID and returns its little-endian byte array representation.
322            pub const fn to_le_bytes(self) -> [u8; $bytes] {
323                self.0
324            }
325
326            /// Consumes the UUID and returns its big-endian byte array representation.
327            pub fn to_be_bytes(self) -> [u8; $bytes] {
328                let mut bytes = self.0;
329                bytes.reverse();
330                bytes
331            }
332
333            /// Returns the UUID as a little-endian byte slice.
334            pub const fn as_le_slice(&self) -> &[u8] {
335                &self.0
336            }
337
338            /// Returns the integer representation of this UUID.
339            pub const fn $to(self) -> $num {
340                <$num>::from_le_bytes(self.0)
341            }
342        }
343
344        impl AsRef<[u8]> for $name {
345            fn as_ref(&self) -> &[u8] {
346                self.as_le_bytes()
347            }
348        }
349
350        impl From<$num> for $name {
351            fn from(value: $num) -> Self {
352                Self::new(value)
353            }
354        }
355
356        impl From<$name> for $num {
357            fn from(value: $name) -> Self {
358                value.$to()
359            }
360        }
361
362        impl From<[u8; $bytes]> for $name {
363            fn from(value: [u8; $bytes]) -> Self {
364                // Assumes value is in little-endian order
365                Self(value)
366            }
367        }
368
369        impl From<$name> for [u8; $bytes] {
370            fn from(value: $name) -> Self {
371                value.0
372            }
373        }
374
375        #[cfg(feature = "uuid")]
376        impl From<$name> for uuid::Uuid {
377            fn from(value: $name) -> Self {
378                BluetoothUuid::from(value).into()
379            }
380        }
381    };
382
383    {
384        $(#[$attrs:meta])*
385        struct $name:ident($num:ty, $bytes:expr, $to:ident, $fmt:literal, $defmt:literal);
386    } => {
387        impl_uuid! { $(#[$attrs])* struct $name($num, $bytes, $to); }
388
389        impl Debug for $name {
390            fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
391                write!(f, concat!(stringify!($name), "(0x{})"), self)
392            }
393        }
394
395        impl Display for $name {
396            fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
397                write!(f, $fmt, self.$to())
398            }
399        }
400
401        #[cfg(feature = "defmt")]
402        impl defmt::Format for $name {
403            fn format(&self, f: defmt::Formatter) {
404                defmt::write!(f, $defmt, self.0)
405            }
406        }
407    };
408}
409
410impl_uuid! {
411    /// A 16-bit Bluetooth UUID
412    struct BluetoothUuid16(u16, 2, to_u16, "0x{:04X}", "BluetoothUuid16({:04X})");
413}
414impl_uuid! {
415    /// A 32-bit Bluetooth UUID
416    struct BluetoothUuid32(u32, 4, to_u32, "0x{:08X}", "BluetoothUuid32({:08X})");
417}
418impl_uuid! {
419    /// A 128-bit Bluetooth UUID
420    struct BluetoothUuid128(u128, 16, to_u128);
421}
422
423impl BluetoothUuid128 {
424    /// The standard Bluetooth Base UUID.
425    ///
426    /// The value is `00000000-0000-1000-8000-00805F9B34FB`.
427    ///
428    /// The full 128-bit value of a 16-bit or 32-bit UUID may be computed by a simple arithmetic operation.
429    ///
430    /// `128_bit_value = 16_bit_value * 2^96 + Bluetooth_Base_UUID`
431    ///
432    /// See BLUETOOTH CORE SPECIFICATION Version 6.0 | Vol 3, Part B | Page 1250
433    /// [(link)](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-54/out/en/host/service-discovery-protocol--sdp--specification.html#UUID-ef710684-4c7e-6793-4350-4a190ea9a7a4)
434    pub const fn base() -> Self {
435        BluetoothUuid128::new(0x00000000_0000_1000_8000_00805F9B34FB)
436    }
437
438    /// Creates a new nil (all-zero) 128-bit UUID.
439    pub const fn nil() -> Self {
440        Self([0; 16])
441    }
442
443    /// Returns the raw components of the 128-bit UUID.
444    ///
445    /// The fields are returned in big-endian order: `(time_low, time_mid, time_high_and_version, clock_seq_and_node)`.
446    pub const fn to_fields(&self) -> (u32, u16, u16, u64) {
447        let b = &self.0;
448        let d4 = u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]);
449        let d3 = u16::from_le_bytes([b[8], b[9]]);
450        let d2 = u16::from_le_bytes([b[10], b[11]]);
451        let d1 = u32::from_le_bytes([b[12], b[13], b[14], b[15]]);
452        (d1, d2, d3, d4)
453    }
454
455    /// Sets the value of the initial 32-bits of the UUID (in big-endian format) to `val`.
456    ///
457    /// This can be used to construct a 128-bit UUID value from a 16- or 32-bit UUID
458    /// value using [`Self::base()`] as the base 128-bit UUID value.
459    pub const fn set_initial_group(mut self, val: u32) -> Self {
460        let bytes = val.to_le_bytes();
461        self.0[12] = bytes[0];
462        self.0[13] = bytes[1];
463        self.0[14] = bytes[2];
464        self.0[15] = bytes[3];
465        self
466    }
467}
468
469impl Default for BluetoothUuid128 {
470    fn default() -> Self {
471        Self::nil()
472    }
473}
474
475impl Debug for BluetoothUuid128 {
476    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
477        write!(f, concat!(stringify!($name), "(0x{})"), self)
478    }
479}
480
481impl Display for BluetoothUuid128 {
482    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
483        let (d1, d2, d3, d4) = self.to_fields();
484        write!(
485            f,
486            "{:08X}-{:04X}-{:04X}-{:04X}-{:012X}",
487            d1,
488            d2,
489            d3,
490            d4 >> 48,
491            d4 & ((1 << 48) - 1)
492        )
493    }
494}
495
496#[cfg(feature = "defmt")]
497impl defmt::Format for BluetoothUuid128 {
498    fn format(&self, f: defmt::Formatter) {
499        let (d1, d2, d3, d4) = self.to_fields();
500        defmt::write!(
501            f,
502            "BluetoothUuid128({=u32:08X}-{=u16:04X}-{=u16:04X}-{=u16:04X}-{=u64:012X})",
503            d1,
504            d2,
505            d3,
506            (d4 >> 48) as u16,
507            d4 & ((1 << 48) - 1)
508        )
509    }
510}
511
512#[cfg(test)]
513mod test {
514    use super::*;
515
516    #[test]
517    fn test_ble_uuid() {
518        const BLE_UUID: BluetoothUuid16 = BluetoothUuid16::new(0x1234);
519        assert_eq!(u16::from(BLE_UUID), 0x1234);
520        let uuid: u16 = BLE_UUID.into();
521        assert_eq!(uuid, 0x1234);
522        const UUID: [u8; 2] = BLE_UUID.to_le_bytes();
523        assert_eq!(UUID, [0x34, 0x12]);
524    }
525
526    #[cfg(feature = "uuid")]
527    #[test]
528    fn test_uuid_conversion() {
529        let result = uuid::Uuid::from(BluetoothUuid16::new(0x1234));
530        let expected = "00001234-0000-1000-8000-00805f9b34fb".parse::<uuid::Uuid>().unwrap();
531
532        // defmt::Format not implemented on Uuid
533        assert_eq!(result.into_bytes(), expected.into_bytes());
534    }
535}