Skip to main content

scll_core/
aid.rs

1//! `Aid` newtype — Application Identifier (PDD §6).
2//!
3//! 5..=16 bytes, validated on construction (RID 5 B + PIX ≤ 11 B,
4//! ISO/IEC 7816-5; AID structure also ISO/IEC 7816-4:2020 §8).
5//!
6//! `no_std`/heapless: backed by `heapless::Vec<u8, AID_MAX>` instead of `Vec`.
7//! The constructor now takes `&[u8]` (the previous `impl Into<Vec<u8>>` bound
8//! pulled in `alloc`).
9
10use heapless::Vec;
11
12use crate::error::ScllError;
13use crate::limits::AID_MAX;
14
15/// Smallest legal AID: a 5-byte RID with an empty PIX (ISO/IEC 7816-5).
16const AID_MIN: usize = 5;
17
18/// Validated AID. Construction enforces the 5..=16 byte length.
19#[derive(Clone, PartialEq, Eq, Hash)]
20pub struct Aid(Vec<u8, AID_MAX>);
21
22impl core::fmt::Debug for Aid {
23    /// Renders as `Aid("A0000001515344")` — the AID bytes as an uppercase hex
24    /// string rather than a decimal array.
25    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
26        write!(f, "Aid({:?})", crate::hexfmt::HexBytes(&self.0))
27    }
28}
29
30impl Aid {
31    /// Build an `Aid`, rejecting lengths outside 5..=16.
32    ///
33    /// # Errors
34    /// Returns [`ScllError::InvalidAid`] if `bytes` is not a valid AID length
35    /// (outside 5..=16 bytes; RID 5 B + PIX ≤ 11 B, ISO/IEC 7816-5).
36    pub fn new(bytes: &[u8]) -> Result<Self, ScllError> {
37        if !(AID_MIN..=AID_MAX).contains(&bytes.len()) {
38            return Err(ScllError::InvalidAid { len: bytes.len() });
39        }
40        // Cannot fail: the bound above guarantees `bytes.len() <= AID_MAX`.
41        let mut v = Vec::new();
42        v.extend_from_slice(bytes)
43            .map_err(|()| ScllError::InvalidAid { len: bytes.len() })?;
44        Ok(Self(v))
45    }
46
47    /// Borrow the raw AID bytes.
48    #[must_use]
49    pub fn as_bytes(&self) -> &[u8] {
50        &self.0
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57    use scll_test_util::HexSlice;
58
59    #[test]
60    fn accepts_every_length_in_range() {
61        for len in AID_MIN..=AID_MAX {
62            let n = u8::try_from(len).expect("len <= AID_MAX fits u8");
63            let raw: heapless::Vec<u8, AID_MAX> = (0..n).collect();
64            let aid = Aid::new(&raw).expect("5..=16 bytes must be accepted");
65            assert_eq!(HexSlice(aid.as_bytes()), HexSlice(&raw));
66        }
67    }
68
69    #[test]
70    fn rejects_too_short() {
71        for len in 0..AID_MIN {
72            let n = u8::try_from(len).expect("len < AID_MIN fits u8");
73            let raw: heapless::Vec<u8, AID_MAX> = (0..n).collect();
74            assert!(
75                matches!(Aid::new(&raw), Err(ScllError::InvalidAid { len: got }) if got == len),
76                "len {len} must be rejected"
77            );
78        }
79    }
80
81    #[test]
82    fn rejects_too_long() {
83        for len in (AID_MAX + 1)..=(AID_MAX + 4) {
84            // Build an over-length input without exceeding the heapless buffer.
85            let raw: [u8; AID_MAX + 4] =
86                core::array::from_fn(|i| u8::try_from(i).expect("index < 20 fits u8"));
87            assert!(
88                matches!(Aid::new(&raw[..len]), Err(ScllError::InvalidAid { len: got }) if got == len),
89                "len {len} must be rejected"
90            );
91        }
92    }
93
94    #[test]
95    fn as_bytes_round_trips_a_realistic_rid() {
96        // ISD AID example shape: 5-byte RID + 4-byte PIX (GPCS A000000151…).
97        let raw = [0xA0, 0x00, 0x00, 0x01, 0x51, 0x00, 0x00, 0x00, 0x00];
98        let aid = Aid::new(&raw).unwrap();
99        assert_eq!(HexSlice(aid.as_bytes()), HexSlice(&raw));
100    }
101}