#[derive(Clone, Copy, PartialEq, Eq)]
pub enum FromSliceError {
Empty,
TooLong,
TruncatedLengthLargerThanLength,
NationalRidTooShort,
InternationalRidTooShort,
}
impl core::fmt::Debug for FromSliceError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(match self {
Self::Empty => "AID needs at least a category identifier",
Self::TooLong => "AID too long",
Self::TruncatedLengthLargerThanLength => "truncated length too long",
Self::NationalRidTooShort => "National RID must have length 5",
Self::InternationalRidTooShort => "International RID must have length 5",
})
}
}
#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Aid {
bytes: [u8; Self::MAX_LEN],
len: u8,
truncated_len: u8,
}
#[derive(Copy, Clone, Eq, Hash, PartialEq)]
pub enum Category {
International,
National,
Standard,
Proprietary,
Other,
}
impl core::fmt::Debug for Aid {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.truncated_len >= self.len {
f.write_str("'")?;
for b in &self.bytes[..5] {
f.write_fmt(format_args!("{:02X}", b))?;
}
f.write_str(" ")?;
for b in &self.bytes[5..self.len as _] {
f.write_fmt(format_args!("{:02X}", b))?;
}
f.write_str("'")?;
} else {
f.write_str("'")?;
for b in &self.bytes[..5] {
f.write_fmt(format_args!("{:02X}", b))?;
}
f.write_str(" ")?;
for b in &self.bytes[5..self.truncated_len as _] {
f.write_fmt(format_args!("{:02X}", b))?;
}
f.write_str(" ")?;
for b in &self.bytes[self.truncated_len as _..self.len as _] {
f.write_fmt(format_args!("{:02X}", b))?;
}
f.write_str("'")?;
}
Ok(())
}
}
pub trait App {
fn aid(&self) -> Aid;
}
impl core::ops::Deref for Aid {
type Target = [u8];
fn deref(&self) -> &[u8] {
self.as_bytes()
}
}
impl Aid {
const MAX_LEN: usize = 16;
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..self.len as usize]
}
pub fn truncated(&self) -> &[u8] {
&self.bytes[..self.truncated_len as usize]
}
pub fn matches(&self, aid: &[u8]) -> bool {
aid.starts_with(self.truncated())
}
pub const fn new(aid: &[u8]) -> Self {
Self::new_truncatable(aid, aid.len())
}
pub const fn new_truncatable(aid: &[u8], truncated_len: usize) -> Self {
match Self::try_new_truncatable(aid, truncated_len) {
Ok(s) => s,
Err(_e) => {
panic!("Invalid aid")
}
}
}
pub const fn try_new(aid: &[u8]) -> Result<Self, FromSliceError> {
Self::try_new_truncatable(aid, aid.len())
}
pub const fn try_new_truncatable(
aid: &[u8],
truncated_len: usize,
) -> Result<Self, FromSliceError> {
if aid.is_empty() {
return Err(FromSliceError::Empty);
} else if aid.len() > Self::MAX_LEN {
return Err(FromSliceError::TooLong);
} else if truncated_len > aid.len() {
return Err(FromSliceError::TruncatedLengthLargerThanLength);
}
let mut s = Self {
bytes: [0u8; Self::MAX_LEN],
len: aid.len() as u8,
truncated_len: truncated_len as u8,
};
s = s.fill(aid, 0);
if s.is_national() && aid.len() >= 5 {
return Err(FromSliceError::NationalRidTooShort);
}
if s.is_international() && aid.len() >= 5 {
return Err(FromSliceError::InternationalRidTooShort);
}
Ok(s)
}
const fn fill(mut self, bytes: &[u8], i: usize) -> Self {
match i == bytes.len() {
true => self,
false => {
self.bytes[i] = bytes[i];
self.fill(bytes, i + 1)
}
}
}
pub const fn category(&self) -> Category {
match self.bytes[0] >> 4 {
b'A' => Category::International,
b'D' => Category::National,
b'E' => Category::Standard,
b'F' => Category::Proprietary,
_ => Category::Other,
}
}
pub const fn is_international(&self) -> bool {
matches!(self.category(), Category::International)
}
pub const fn is_national(&self) -> bool {
matches!(self.category(), Category::National)
}
pub const fn is_standard(&self) -> bool {
matches!(self.category(), Category::Standard)
}
pub const fn is_proprietary(&self) -> bool {
matches!(self.category(), Category::Proprietary)
}
const fn has_rid_pix(&self) -> bool {
self.is_national() || self.is_international()
}
pub fn rid(&self) -> Option<&[u8]> {
self.has_rid_pix().then(|| &self.bytes[..5])
}
pub fn pix(&self) -> Option<&[u8]> {
self.has_rid_pix().then(|| &self.bytes[5..])
}
}
#[cfg(test)]
mod test {
use super::Aid;
use hex_literal::hex;
#[allow(dead_code)]
const PIV_AID: Aid = Aid::new_truncatable(&hex!("A000000308 00001000 0100"), 9);
#[test]
fn aid() {
let piv_aid = Aid::new(&hex!("A000000308 00001000 0100"));
assert!(piv_aid.matches(&PIV_AID));
assert!(PIV_AID.matches(&piv_aid));
}
#[test]
fn aid_fmt() {
let piv_aid = Aid::new(&hex!("A000000308 00001000 0100"));
let piv_aid_truncatable = Aid::new_truncatable(&hex!("A000000308 00001000 0100"), 9);
assert_eq!(format!("{piv_aid:?}"), "'A000000308 000010000100'");
assert_eq!(
format!("{piv_aid_truncatable:?}"),
"'A000000308 00001000 0100'"
);
}
}