esp32_nimble/utilities/
ble_uuid.rs

1// Originally: https://github.com/pulse-loop/bluedroid/blob/develop/src/utilities/ble_uuid.rs
2
3use alloc::string::String;
4use esp_idf_svc::sys as esp_idf_sys;
5
6/// A Bluetooth UUID.
7#[derive(Copy, Clone)]
8pub enum BleUuid {
9    /// A 16-bit UUID.
10    Uuid16(u16),
11    /// A 32-bit UUID.
12    Uuid32(u32),
13    /// A 128-bit UUID.
14    Uuid128([u8; 16]),
15}
16
17impl BleUuid {
18    /// Creates a new [`BleUuid`] from a 16-bit integer.
19    #[must_use]
20    pub const fn from_uuid16(uuid: u16) -> Self {
21        Self::Uuid16(uuid)
22    }
23
24    /// Creates a new [`BleUuid`] from a 32-bit integer.
25    #[must_use]
26    pub const fn from_uuid32(uuid: u32) -> Self {
27        Self::Uuid32(uuid)
28    }
29
30    /// Creates a new [`BleUuid`] from a 16 byte array.
31    #[must_use]
32    pub const fn from_uuid128(uuid: [u8; 16]) -> Self {
33        Self::Uuid128(uuid)
34    }
35
36    /// Creates a new [`BleUuid`] from a formatted string.
37    ///
38    /// # Panics
39    ///
40    /// Panics if the string contains invalid characters.
41    pub const fn from_uuid128_string(uuid: &str) -> Result<Self, uuid::Error> {
42        // Accepts the following formats:
43        // "00000000-0000-0000-0000-000000000000"
44        // "00000000000000000000000000000000"
45
46        match uuid::Uuid::try_parse(uuid) {
47            Ok(uuid) => Ok(Self::Uuid128(uuid.as_u128().to_le_bytes())),
48            Err(err) => Err(err),
49        }
50    }
51
52    #[must_use]
53    pub(crate) fn as_uuid128_array(&self) -> [u8; 16] {
54        let base_ble_uuid = [
55            0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
56            0x00, 0x00,
57        ];
58
59        match self {
60            Self::Uuid16(uuid) => {
61                let mut uuid128 = base_ble_uuid;
62
63                let mut uuid_as_bytes: [u8; 2] = uuid.to_be_bytes();
64                uuid_as_bytes.reverse();
65
66                uuid128[12..=13].copy_from_slice(&uuid_as_bytes[..]);
67                uuid128
68            }
69            Self::Uuid32(uuid) => {
70                let mut uuid128 = base_ble_uuid;
71
72                let mut uuid_as_bytes: [u8; 4] = uuid.to_be_bytes();
73                uuid_as_bytes.reverse();
74
75                uuid128[12..=15].copy_from_slice(&uuid_as_bytes[..]);
76                uuid128
77            }
78            Self::Uuid128(uuid) => *uuid,
79        }
80    }
81}
82
83impl PartialEq for BleUuid {
84    fn eq(&self, other: &Self) -> bool {
85        self.as_uuid128_array() == other.as_uuid128_array()
86    }
87}
88
89impl From<BleUuid> for esp_idf_sys::ble_uuid_any_t {
90    #[allow(clippy::cast_possible_truncation)]
91    fn from(val: BleUuid) -> Self {
92        let mut result: Self = Self::default();
93
94        match val {
95            BleUuid::Uuid16(uuid) => {
96                result.u.type_ = esp_idf_sys::BLE_UUID_TYPE_16 as _;
97                result.u16_.value = uuid;
98            }
99            BleUuid::Uuid32(uuid) => {
100                result.u.type_ = esp_idf_sys::BLE_UUID_TYPE_32 as _;
101                result.u32_.value = uuid;
102            }
103            BleUuid::Uuid128(uuid) => {
104                result.u.type_ = esp_idf_sys::BLE_UUID_TYPE_128 as _;
105                result.u128_.value = uuid;
106            }
107        }
108
109        result
110    }
111}
112
113impl From<esp_idf_sys::ble_uuid_any_t> for BleUuid {
114    fn from(uuid: esp_idf_sys::ble_uuid_any_t) -> Self {
115        unsafe {
116            match uuid.u.type_ as _ {
117                esp_idf_sys::BLE_UUID_TYPE_16 => Self::Uuid16(uuid.u16_.value),
118                esp_idf_sys::BLE_UUID_TYPE_32 => Self::Uuid32(uuid.u32_.value),
119                esp_idf_sys::BLE_UUID_TYPE_128 => Self::Uuid128(uuid.u128_.value),
120                // Never happens
121                _ => unreachable!("Invalid UUID length"),
122            }
123        }
124    }
125}
126
127impl core::fmt::Display for BleUuid {
128    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
129        match self {
130            Self::Uuid16(uuid) => write!(f, "0x{uuid:04x}"),
131            Self::Uuid32(uuid) => write!(f, "0x{uuid:08x}"),
132            Self::Uuid128(uuid) => {
133                let mut uuid = *uuid;
134                uuid.reverse();
135
136                let mut uuid_str = String::new();
137
138                for byte in &uuid {
139                    uuid_str.push_str(&alloc::format!("{byte:02x}"));
140                }
141                uuid_str.insert(8, '-');
142                uuid_str.insert(13, '-');
143                uuid_str.insert(18, '-');
144                uuid_str.insert(23, '-');
145
146                write!(f, "{uuid_str}")
147            }
148        }
149    }
150}
151
152impl core::fmt::Debug for BleUuid {
153    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
154        write!(f, "{self}")
155    }
156}
157
158impl From<uuid::Uuid> for BleUuid {
159    fn from(uuid: uuid::Uuid) -> Self {
160        let mut bytes = *uuid.as_bytes();
161        bytes.reverse();
162        Self::Uuid128(bytes)
163    }
164}
165
166#[macro_export]
167/// Parse Uuid128 from string literals at compile time.
168macro_rules! uuid128 {
169    ($uuid:expr) => {{ $crate::utilities::BleUuid::Uuid128($crate::uuid_macro!($uuid).as_u128().to_le_bytes()) }};
170}