bloop_server_framework/
nfc_uid.rs

1use hex::{FromHex, FromHexError, decode_to_slice};
2use thiserror::Error;
3
4#[derive(Debug, Error)]
5pub enum Error {
6    #[error("Invalid UID length, must be 4, 7 or 10")]
7    InvalidLength,
8}
9
10/// Represents an NFC UID of standard lengths.
11///
12/// NFC UIDs come in three typical sizes:
13///
14/// - `Single`: 4 bytes
15/// - `Double`: 7 bytes
16/// - `Triple`: 10 bytes
17///
18/// This enum encapsulates these variants and ensures correct sizing during
19/// construction via `TryFrom<&[u8]>` or hex parsing (`FromHex`).
20///
21/// Use [`as_bytes()`] to get a byte slice of the underlying UID.
22///
23/// # Examples
24///
25/// ```
26/// use bloop_server_framework::nfc_uid::NfcUid;
27///
28/// let uid = NfcUid::try_from(&[0x01, 0x02, 0x03, 0x04][..]).unwrap();
29/// assert_eq!(uid.as_bytes(), &[0x01, 0x02, 0x03, 0x04]);
30/// ```
31#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
32pub enum NfcUid {
33    Single([u8; 4]),
34    Double([u8; 7]),
35    Triple([u8; 10]),
36}
37
38impl Default for NfcUid {
39    fn default() -> Self {
40        Self::Single(Default::default())
41    }
42}
43
44impl TryFrom<&[u8]> for NfcUid {
45    type Error = Error;
46
47    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
48        match value.len() {
49            4 => Ok(NfcUid::Single(value.try_into().unwrap())),
50            7 => Ok(NfcUid::Double(value.try_into().unwrap())),
51            10 => Ok(NfcUid::Triple(value.try_into().unwrap())),
52            _ => Err(Error::InvalidLength),
53        }
54    }
55}
56
57impl TryFrom<Vec<u8>> for NfcUid {
58    type Error = Error;
59
60    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
61        Self::try_from(&value[..])
62    }
63}
64
65impl FromHex for NfcUid {
66    type Error = FromHexError;
67
68    fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
69        let hex_bytes = hex.as_ref();
70        let mut decoded = vec![0u8; hex_bytes.len() / 2];
71        decode_to_slice(hex, &mut decoded)?;
72
73        NfcUid::try_from(decoded.as_slice()).map_err(|_| FromHexError::InvalidStringLength)
74    }
75}
76
77impl NfcUid {
78    pub fn as_bytes(&self) -> &[u8] {
79        match self {
80            NfcUid::Single(data) => data,
81            NfcUid::Double(data) => data,
82            NfcUid::Triple(data) => data,
83        }
84    }
85
86    pub fn to_vec(&self) -> Vec<u8> {
87        self.as_bytes().to_vec()
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use hex::FromHex;
95
96    #[test]
97    fn from_valid_bytes() {
98        let bytes4 = [0x01, 0x02, 0x03, 0x04];
99        let bytes7 = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
100        let bytes10 = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A];
101
102        let uid4 = NfcUid::try_from(&bytes4[..]).unwrap();
103        let uid7 = NfcUid::try_from(&bytes7[..]).unwrap();
104        let uid10 = NfcUid::try_from(&bytes10[..]).unwrap();
105
106        assert_eq!(uid4.as_bytes(), &bytes4);
107        assert_eq!(uid7.as_bytes(), &bytes7);
108        assert_eq!(uid10.as_bytes(), &bytes10);
109    }
110
111    #[test]
112    fn from_invalid_bytes_length() {
113        let bytes = [0x01, 0x02];
114        let err = NfcUid::try_from(&bytes[..]).unwrap_err();
115        assert!(matches!(err, Error::InvalidLength));
116
117        let bytes = [0u8; 5];
118        let err = NfcUid::try_from(&bytes[..]).unwrap_err();
119        assert!(matches!(err, Error::InvalidLength));
120    }
121
122    #[test]
123    fn from_valid_hex() {
124        let hex4 = "01020304";
125        let hex7 = "01020304050607";
126        let hex10 = "0102030405060708090a";
127
128        let uid4 = NfcUid::from_hex(hex4).unwrap();
129        let uid7 = NfcUid::from_hex(hex7).unwrap();
130        let uid10 = NfcUid::from_hex(hex10).unwrap();
131
132        assert_eq!(uid4.as_bytes(), &[0x01, 0x02, 0x03, 0x04]);
133        assert_eq!(uid7.as_bytes(), &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]);
134        assert_eq!(
135            uid10.as_bytes(),
136            &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a]
137        );
138    }
139
140    #[test]
141    fn from_invalid_hex_format() {
142        let invalid_hex = "xyz"; // Invalid hex
143        assert!(NfcUid::from_hex(invalid_hex).is_err());
144
145        let invalid_length = "010203"; // 3 bytes → invalid UID length
146        let result = NfcUid::from_hex(invalid_length);
147        assert!(matches!(result, Err(FromHexError::InvalidStringLength)));
148    }
149}