macro_rules! const_assert {
($bool:expr, $msg:expr) => {
[$msg][!$bool as usize]
};
}
#[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.len <= self.truncated_len {
f.write_fmt(format_args!("'{} {}'",
hexstr!(&self.bytes[..5]),
hexstr!(&self.bytes[5..self.len as _])))
} else {
f.write_fmt(format_args!("'{} {} {}'",
hexstr!(&self.bytes[..5]),
hexstr!(&self.bytes[5..self.truncated_len as _]),
hexstr!(&self.bytes[self.truncated_len as _..self.len as _])))
}
}
}
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 {
const_assert!(!aid.is_empty(), "AID needs at least a category identifier");
const_assert!(aid.len() <= Self::MAX_LEN, "AID too long");
const_assert!(truncated_len <= aid.len(), "truncated length too long");
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);
const_assert!(!s.is_national() || aid.len() >= 5, "National RID must have length 5");
const_assert!(!s.is_international() || aid.len() >= 5, "International RID must have length 5");
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 {
match self.category() {
Category::International => true,
_ => false,
}
}
pub const fn is_national(&self) -> bool {
match self.category() {
Category::National => true,
_ => false,
}
}
pub const fn is_standard(&self) -> bool {
match self.category() {
Category::Standard => true,
_ => false,
}
}
pub const fn is_proprietary(&self) -> bool {
match self.category() {
Category::Proprietary => true,
_ => false,
}
}
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));
}
}