burble_const/
lib.rs

1//! Bluetooth LE assigned numbers.
2
3#![warn(unused_crate_dependencies)]
4
5use std::fmt::{Debug, Display, Formatter};
6
7pub use uuid::*;
8
9mod uuid;
10
11/// Company identifier ([Assigned Numbers] Section 7.1).
12#[derive(Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)]
13#[repr(transparent)]
14pub struct CompanyId(pub u16);
15
16impl CompanyId {
17    /// Returns the raw company ID.
18    #[inline(always)]
19    #[must_use]
20    pub const fn raw(self) -> u16 {
21        self.0
22    }
23
24    /// Returns the associated company name or [`None`] if the identifier is
25    /// unknown.
26    #[must_use]
27    pub const fn name(self) -> Option<&'static str> {
28        let i = match self.0.checked_sub(Self::MIN) {
29            Some(0) => return Some(Self::FIRST),
30            Some(i) if (i as usize) < Self::IDX.len() => i as usize,
31            _ => return None,
32        };
33        let off = Self::IDX[i - 1] as usize;
34        // SAFETY: `TAB[IDX[i - 1]..IDX[i]]` contains a valid UTF-8 string
35        Some(unsafe {
36            std::str::from_utf8_unchecked(std::slice::from_raw_parts(
37                Self::TAB.as_ptr().add(off),
38                Self::IDX[i] as usize - off,
39            ))
40        })
41    }
42}
43
44impl Debug for CompanyId {
45    #[inline]
46    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
47        let name = self.name().unwrap_or("<unknown>");
48        f.debug_tuple("CompanyId")
49            .field(&format_args!("{:#06X} => \"{name}\"", self.0))
50            .finish()
51    }
52}
53
54impl Display for CompanyId {
55    #[inline]
56    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
57        f.write_str(self.name().unwrap_or("<unknown>"))
58    }
59}
60
61impl From<CompanyId> for u16 {
62    #[inline(always)]
63    fn from(id: CompanyId) -> Self {
64        id.raw()
65    }
66}
67
68/// Generates company name look-up table. This representation saves several KB
69/// in the release binary over a `match` table.
70macro_rules! id_map {
71    {$first:literal => $name:literal, $($id:literal => $name_:literal,)+} => {
72        impl CompanyId {
73            const MIN: u16 = $first;
74            #[allow(clippy::no_effect)]
75            const MAX: u16 = { $($id);+ };
76            const FIRST: &'static str = $name;
77            #[allow(clippy::no_effect)]
78            #[cfg(test)]
79            const LAST: &'static str = { $($name_);+ };
80            const TAB: &'static [u8] = concat!($($name_),*).as_bytes();
81            const IDX: [u16; Self::MAX as usize - $first + 1] = {
82                let mut v = [0_u16; Self::MAX as usize - $first + 1];
83                let mut i = 0;
84                $(
85                    i += 1;
86                    assert!(i == $id - $first);
87                    let (n, overflow) = v[i - 1].overflowing_add($name_.len() as u16);
88                    assert!(!overflow);
89                    v[i] = n;
90                )+
91                v
92            };
93        }
94    };
95}
96
97include!("company_id.rs");
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn company_ids() {
105        assert_eq!(CompanyId(CompanyId::MIN).name(), Some(CompanyId::FIRST));
106        assert_eq!(CompanyId(0x01F4).name(), Some("UTC Fire and Security"));
107        assert_eq!(CompanyId(CompanyId::MAX).name(), Some(CompanyId::LAST));
108        assert_eq!(CompanyId(CompanyId::MAX + 1).name(), None);
109        assert_eq!(CompanyId(u16::MAX).name(), None);
110    }
111}