1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// Originally: https://github.com/pulse-loop/bluedroid/blob/develop/src/utilities/ble_uuid.rs

use alloc::string::String;
use esp_idf_svc::sys as esp_idf_sys;

/// A Bluetooth UUID.
#[derive(Copy, Clone)]
pub enum BleUuid {
  /// A 16-bit UUID.
  Uuid16(u16),
  /// A 32-bit UUID.
  Uuid32(u32),
  /// A 128-bit UUID.
  Uuid128([u8; 16]),
}

impl BleUuid {
  /// Creates a new [`BleUuid`] from a 16-bit integer.
  #[must_use]
  pub const fn from_uuid16(uuid: u16) -> Self {
    Self::Uuid16(uuid)
  }

  /// Creates a new [`BleUuid`] from a 32-bit integer.
  #[must_use]
  pub const fn from_uuid32(uuid: u32) -> Self {
    Self::Uuid32(uuid)
  }

  /// Creates a new [`BleUuid`] from a 16 byte array.
  #[must_use]
  pub const fn from_uuid128(uuid: [u8; 16]) -> Self {
    Self::Uuid128(uuid)
  }

  /// Creates a new [`BleUuid`] from a formatted string.
  ///
  /// # Panics
  ///
  /// Panics if the string contains invalid characters.
  pub const fn from_uuid128_string(uuid: &str) -> Result<Self, uuid::Error> {
    // Accepts the following formats:
    // "00000000-0000-0000-0000-000000000000"
    // "00000000000000000000000000000000"

    match uuid::Uuid::try_parse(uuid) {
      Ok(uuid) => Ok(Self::Uuid128(uuid.as_u128().to_le_bytes())),
      Err(err) => Err(err),
    }
  }

  #[must_use]
  pub(crate) fn as_uuid128_array(&self) -> [u8; 16] {
    let base_ble_uuid = [
      0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00,
    ];

    match self {
      Self::Uuid16(uuid) => {
        let mut uuid128 = base_ble_uuid;

        let mut uuid_as_bytes: [u8; 2] = uuid.to_be_bytes();
        uuid_as_bytes.reverse();

        uuid128[12..=13].copy_from_slice(&uuid_as_bytes[..]);
        uuid128
      }
      Self::Uuid32(uuid) => {
        let mut uuid128 = base_ble_uuid;

        let mut uuid_as_bytes: [u8; 4] = uuid.to_be_bytes();
        uuid_as_bytes.reverse();

        uuid128[12..=15].copy_from_slice(&uuid_as_bytes[..]);
        uuid128
      }
      Self::Uuid128(uuid) => *uuid,
    }
  }
}

impl PartialEq for BleUuid {
  fn eq(&self, other: &Self) -> bool {
    self.as_uuid128_array() == other.as_uuid128_array()
  }
}

impl From<BleUuid> for esp_idf_sys::ble_uuid_any_t {
  #[allow(clippy::cast_possible_truncation)]
  fn from(val: BleUuid) -> Self {
    let mut result: Self = Self::default();

    match val {
      BleUuid::Uuid16(uuid) => {
        result.u.type_ = esp_idf_sys::BLE_UUID_TYPE_16 as _;
        result.u16_.value = uuid;
      }
      BleUuid::Uuid32(uuid) => {
        result.u.type_ = esp_idf_sys::BLE_UUID_TYPE_32 as _;
        result.u32_.value = uuid;
      }
      BleUuid::Uuid128(uuid) => {
        result.u.type_ = esp_idf_sys::BLE_UUID_TYPE_128 as _;
        result.u128_.value = uuid;
      }
    }

    result
  }
}

impl From<esp_idf_sys::ble_uuid_any_t> for BleUuid {
  fn from(uuid: esp_idf_sys::ble_uuid_any_t) -> Self {
    unsafe {
      match uuid.u.type_ as _ {
        esp_idf_sys::BLE_UUID_TYPE_16 => Self::Uuid16(uuid.u16_.value),
        esp_idf_sys::BLE_UUID_TYPE_32 => Self::Uuid32(uuid.u32_.value),
        esp_idf_sys::BLE_UUID_TYPE_128 => Self::Uuid128(uuid.u128_.value),
        // Never happens
        _ => unreachable!("Invalid UUID length"),
      }
    }
  }
}

impl core::fmt::Display for BleUuid {
  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
    match self {
      Self::Uuid16(uuid) => write!(f, "0x{uuid:04x}"),
      Self::Uuid32(uuid) => write!(f, "0x{uuid:08x}"),
      Self::Uuid128(uuid) => {
        let mut uuid = *uuid;
        uuid.reverse();

        let mut uuid_str = String::new();

        for byte in &uuid {
          uuid_str.push_str(&alloc::format!("{byte:02x}"));
        }
        uuid_str.insert(8, '-');
        uuid_str.insert(13, '-');
        uuid_str.insert(18, '-');
        uuid_str.insert(23, '-');

        write!(f, "{uuid_str}")
      }
    }
  }
}

impl core::fmt::Debug for BleUuid {
  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
    write!(f, "{self}")
  }
}

impl From<uuid::Uuid> for BleUuid {
  fn from(uuid: uuid::Uuid) -> Self {
    let mut bytes = *uuid.as_bytes();
    bytes.reverse();
    Self::Uuid128(bytes)
  }
}

#[macro_export]
/// Parse Uuid128 from string literals at compile time.
macro_rules! uuid128 {
  ($uuid:expr) => {{
    $crate::utilities::BleUuid::Uuid128($crate::uuid_macro!($uuid).as_u128().to_le_bytes())
  }};
}