use thiserror::Error;
use crate::codec::{Decode, DecodeError, Encode, EncodeError, take};
#[derive(Debug, Error)]
pub enum Error {
#[error("invalid UID length, must be 4, 7 or 10")]
InvalidLength,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum NfcUid {
Single([u8; 4]),
Double([u8; 7]),
Triple([u8; 10]),
}
impl NfcUid {
pub fn as_bytes(&self) -> &[u8] {
match self {
NfcUid::Single(data) => data,
NfcUid::Double(data) => data,
NfcUid::Triple(data) => data,
}
}
pub fn to_vec(&self) -> Vec<u8> {
self.as_bytes().to_vec()
}
}
impl Default for NfcUid {
fn default() -> Self {
Self::Single(Default::default())
}
}
impl TryFrom<&[u8]> for NfcUid {
type Error = Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
match value.len() {
4 => Ok(NfcUid::Single(value.try_into().expect("length checked"))),
7 => Ok(NfcUid::Double(value.try_into().expect("length checked"))),
10 => Ok(NfcUid::Triple(value.try_into().expect("length checked"))),
_ => Err(Error::InvalidLength),
}
}
}
impl TryFrom<Vec<u8>> for NfcUid {
type Error = Error;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
Self::try_from(&value[..])
}
}
impl Encode for NfcUid {
fn encode(&self, out: &mut Vec<u8>) -> Result<(), EncodeError> {
let bytes = self.as_bytes();
out.push(bytes.len() as u8);
out.extend_from_slice(bytes);
Ok(())
}
}
impl Decode for NfcUid {
fn decode(input: &mut &[u8]) -> Result<Self, DecodeError> {
let length = u8::decode(input)?;
if !matches!(length, 4 | 7 | 10) {
return Err(DecodeError::InvalidNfcUidLength(length));
}
let bytes = take(input, length as usize)?;
Ok(Self::try_from(bytes).expect("length checked"))
}
}
#[cfg(feature = "hex")]
impl hex::FromHex for NfcUid {
type Error = hex::FromHexError;
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let hex_bytes = hex.as_ref();
let mut decoded = vec![0u8; hex_bytes.len() / 2];
hex::decode_to_slice(hex, &mut decoded)?;
NfcUid::try_from(decoded.as_slice()).map_err(|_| hex::FromHexError::InvalidStringLength)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for NfcUid {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use hex::FromHex;
let hex = String::deserialize(deserializer)?;
NfcUid::from_hex(&hex).map_err(serde::de::Error::custom)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for NfcUid {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&hex::encode(self.as_bytes()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codec::{decode_payload, encode_payload};
#[test]
fn from_valid_bytes() {
let bytes4 = [0x01, 0x02, 0x03, 0x04];
let bytes7 = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
let bytes10 = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a];
let uid4 = NfcUid::try_from(&bytes4[..]).unwrap();
let uid7 = NfcUid::try_from(&bytes7[..]).unwrap();
let uid10 = NfcUid::try_from(&bytes10[..]).unwrap();
assert_eq!(uid4.as_bytes(), &bytes4);
assert_eq!(uid7.as_bytes(), &bytes7);
assert_eq!(uid10.as_bytes(), &bytes10);
}
#[test]
fn from_invalid_bytes_length() {
let bytes = [0x01, 0x02];
let error = NfcUid::try_from(&bytes[..]).unwrap_err();
assert!(matches!(error, Error::InvalidLength));
let bytes = [0u8; 5];
let error = NfcUid::try_from(&bytes[..]).unwrap_err();
assert!(matches!(error, Error::InvalidLength));
}
#[test]
fn uid_is_length_prefixed_on_the_wire() {
let uid = NfcUid::try_from(&[1u8, 2, 3, 4][..]).unwrap();
assert_eq!(encode_payload(&uid).unwrap(), [4, 1, 2, 3, 4]);
assert_eq!(decode_payload::<NfcUid>(&[4, 1, 2, 3, 4]).unwrap(), uid);
}
#[test]
fn invalid_wire_length_fails_to_decode() {
let error = decode_payload::<NfcUid>(&[5, 1, 2, 3, 4, 5]).unwrap_err();
assert!(matches!(error, DecodeError::InvalidNfcUidLength(5)));
}
#[cfg(feature = "hex")]
#[test]
fn from_valid_hex() {
use hex::FromHex;
let uid4 = NfcUid::from_hex("01020304").unwrap();
let uid7 = NfcUid::from_hex("01020304050607").unwrap();
let uid10 = NfcUid::from_hex("0102030405060708090a").unwrap();
assert_eq!(uid4.as_bytes(), &[0x01, 0x02, 0x03, 0x04]);
assert_eq!(uid7.as_bytes(), &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]);
assert_eq!(
uid10.as_bytes(),
&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a]
);
}
#[cfg(feature = "hex")]
#[test]
fn from_invalid_hex_format() {
use hex::{FromHex, FromHexError};
assert!(NfcUid::from_hex("xyz").is_err());
let result = NfcUid::from_hex("010203");
assert!(matches!(result, Err(FromHexError::InvalidStringLength)));
}
#[cfg(feature = "serde")]
#[test]
fn serde_round_trips_as_hex_string() {
let uid = NfcUid::try_from(&[0x01u8, 0x02, 0x03, 0x04][..]).unwrap();
let json = serde_json::to_string(&uid).unwrap();
assert_eq!(json, "\"01020304\"");
let parsed: NfcUid = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, uid);
}
}