Skip to main content

scll_core/
model.rs

1//! Public card model — PDD §6. Returned by `discover_card` (§5.2). Read-only.
2//!
3//! `no_std` + heapless: every former `Vec`/`String` is a fixed-capacity
4//! `heapless::Vec`/`heapless::String`; capacities live in [`crate::limits`].
5//! `Aid` is the validating newtype (5..=16 bytes; ISO/IEC 7816-5).
6
7use core::fmt;
8
9use heapless::{String, Vec};
10
11use crate::limits::MAX_APPLETS;
12
13use crate::aid::Aid;
14use crate::command::install::PrivLen;
15use crate::limits::{
16    ATR_ATS_MAX, CIN_MAX, GETDATA_RAW_MAX, IIN_MAX, MAX_CIPHERS, MAX_ELFS, MAX_KEYSETS,
17    MAX_KEYS_PER_SET, MAX_MODULES_PER_ELF, MAX_PRIVILEGE_BYTES, MAX_QUIRKS, MAX_SCP_VARIANTS,
18    MAX_SDS, MAX_WARNINGS, OTHER_DETAIL_MAX,
19};
20use crate::transport::TransportProtocol;
21
22/// Everything the card willingly reports before SCP authentication (§5.2).
23pub struct CardInfo {
24    // Basic identity
25    pub isd_aid: Aid,
26    pub atr_or_ats: Vec<u8, ATR_ATS_MAX>,
27    pub transport_protocol: TransportProtocol,
28
29    // SCP capability — drives session-open logic (§4.3)
30    pub scp_supported: Vec<ScpVariant, MAX_SCP_VARIANTS>, // every (scp_id, i) advertised
31    pub scp_default: ScpVariant,                          // first listed in CRD '64'
32
33    // ISD key inventory — drives PUT KEY pre-flight
34    pub isd_keysets: Vec<Keyset, MAX_KEYSETS>, // grouped by KVN
35    pub isd_key_template_format: KeyTemplateFormat,
36
37    // Card capability — drives channel choice, cipher selection
38    pub capabilities: CardCapabilities,
39    pub card_recognition_data_raw: Vec<u8, GETDATA_RAW_MAX>, // raw '66' for diagnostics
40
41    // Optional identifying data
42    pub iin: Option<Vec<u8, IIN_MAX>>, // GET DATA '0042'
43    pub cin: Option<Vec<u8, CIN_MAX>>, // GET DATA '0045'
44    pub card_image_number: Option<Vec<u8, CIN_MAX>>, // alias of cin if present
45    pub jc_platform_version: Option<(u8, u8, u8)>,
46
47    // Card-implementation quirk: GP Privileges field encoding length, chosen at
48    // discovery from CPLC (NXP JCOP → 1-byte, else canonical 3-byte). PDD §5.2.
49    pub privilege_encoding: PrivLen,
50
51    // Diagnostic
52    pub quirks_detected: Vec<String<OTHER_DETAIL_MAX>, MAX_QUIRKS>,
53    pub discovery_warnings: Vec<DiscoveryWarning, MAX_WARNINGS>,
54}
55
56/// Object inventory snapshot — the payload of `get_card_inventory`
57/// (`GetCardInventoryReport`, PDD §5.12a). A point-in-time view, stale after the
58/// next management operation.
59#[derive(Debug)]
60pub struct CardInventory {
61    pub security_domains: Vec<SecurityDomainEntry, MAX_SDS>,
62    pub applets: Vec<ApplicationEntry, MAX_APPLETS>,
63    pub elfs: Vec<ExecutableLoadFileEntry, MAX_ELFS>,
64}
65
66/// One advertised secure-channel variant (scp id + `i` parameter).
67#[derive(Clone, Copy, PartialEq, Eq)]
68pub enum ScpVariant {
69    Scp02 { i_param: u8 }, // i = 0x55 typical modern default
70    Scp03 { i_param: u8 }, // i = 0x70 typical modern default
71}
72
73impl fmt::Debug for ScpVariant {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        let (name, i) = match self {
76            ScpVariant::Scp02 { i_param } => ("Scp02", i_param),
77            ScpVariant::Scp03 { i_param } => ("Scp03", i_param),
78        };
79        f.debug_struct(name)
80            .field("i_param", &format_args!("{i:#04x}"))
81            .finish()
82    }
83}
84
85/// A keyset grouped by Key Version Number.
86pub struct Keyset {
87    pub kvn: u8,
88    pub keys: Vec<KeyInfo, MAX_KEYS_PER_SET>, // typically 3 entries (KID 1,2,3)
89}
90
91// Manual `Debug`: the KVN is a protocol scalar whose hex form is the
92// meaningful one (KVN `0x30`, not the derive's decimal `48`) — same rationale
93// and style as `KeyInfo::kid` below and `OpenScpParams` (v0.9o).
94impl fmt::Debug for Keyset {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        f.debug_struct("Keyset")
97            .field("kvn", &format_args!("{:#04x}", self.kvn))
98            .field("keys", &self.keys)
99            .finish()
100    }
101}
102
103/// One key slot from the Key Information Template (`'00E0'`).
104///
105/// v0.9k: the Extended-format `key_usage`/`key_access` placeholder fields were
106/// removed — the extended sub-template decode was never implemented, so they
107/// were structurally always `None` (an extended entry is still *detected* and
108/// reported via [`KeyTemplateFormat::Extended`]).
109pub struct KeyInfo {
110    pub kid: u8,
111    pub key_type: KeyType,
112    pub key_length: u8,
113}
114
115impl fmt::Debug for KeyInfo {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        f.debug_struct("KeyInfo")
118            .field("kid", &format_args!("{:#04x}", self.kid))
119            .field("key_type", &self.key_type)
120            .field("key_length", &self.key_length)
121            .finish()
122    }
123}
124
125/// GP key-type byte decode (§5.2 step 4).
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127pub enum KeyType {
128    Des,                // 0x80, SCP02
129    Aes,                // 0x88, SCP03
130    RsaPublic,          // 0xA1
131    RsaPrivateCrt,      // 0xA2
132    RsaPrivateExponent, // 0xA3
133    EccPublic,          // 0xB0
134    EccPrivate,         // 0xB1
135    EccParametersRef,   // 0xB2
136    Other(u8),
137}
138
139/// Key Information Template format.
140#[derive(Debug)]
141pub enum KeyTemplateFormat {
142    Basic,    // GPCS 2.2 single-byte fields
143    Extended, // GPCS 2.3+ B9-tagged sub-template
144}
145
146/// Parsed Card Capability Information (`'67'`, §H.4).
147pub struct CardCapabilities {
148    pub max_logical_channels: u8, // default 1 if absent
149    pub ciphers_supported: Vec<CipherAlg, MAX_CIPHERS>,
150    pub privileges_supported: Vec<u8, MAX_PRIVILEGE_BYTES>,
151    pub memory_total_bytes: Option<u32>,
152    pub memory_free_bytes: Option<u32>,
153    pub cci_raw: Vec<u8, GETDATA_RAW_MAX>, // raw '67' for diagnostics
154}
155
156/// Cipher algorithms advertised in CCI `'A1'`.
157#[non_exhaustive]
158#[derive(Debug)]
159pub enum CipherAlg {
160    Aes128,
161    Aes192,
162    Aes256,
163    TripleDes,
164    Rsa1024,
165    Rsa2048,
166    Rsa3072,
167    Rsa4096,
168    EccP256,
169    EccP384,
170    EccP521,
171    Sha1,
172    Sha256,
173    Sha384,
174    Sha512,
175    Other(String<OTHER_DETAIL_MAX>),
176}
177
178/// Security Domain registry entry.
179pub struct SecurityDomainEntry {
180    pub aid: Aid,
181    pub life_cycle_state: u8, // raw GP life-cycle byte
182    pub privileges: [u8; 3],
183    pub associated_sd_aid: Option<Aid>, // present for SSDs; None for ISD
184}
185
186/// Application (applet instance) registry entry.
187pub struct ApplicationEntry {
188    pub aid: Aid,
189    pub life_cycle_state: u8,
190    pub privileges: [u8; 3],
191    pub associated_sd_aid: Aid,          // applets always have a parent SD
192    pub associated_elf_aid: Option<Aid>, // the app's ELF; present if card exposes tag 'C4'
193}
194
195/// Executable Load File registry entry.
196#[derive(Debug)]
197pub struct ExecutableLoadFileEntry {
198    pub aid: Aid,
199    pub life_cycle_state: u8,
200    pub associated_sd_aid: Aid,
201    pub modules: Vec<Aid, MAX_MODULES_PER_ELF>, // class AIDs inside this ELF
202}
203
204/// Typed discovery warning — discovery never errors on missing optional data.
205#[non_exhaustive]
206#[derive(Debug)]
207pub enum DiscoveryWarning {
208    CardRecognitionDataMissing,
209    KeyInformationTemplateMissing,
210    CardCapabilityInfoMissing,
211    UnknownLifecycleByte(u8),
212    GetStatusParseFailed,
213    Other(String<OTHER_DETAIL_MAX>),
214}
215
216// ─── Debug impls: render raw byte fields as hex strings (AIDs handled by `Aid`) ──
217
218impl core::fmt::Debug for CardInfo {
219    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
220        f.debug_struct("CardInfo")
221            .field("isd_aid", &self.isd_aid)
222            .field(
223                "atr_or_ats",
224                &crate::hexfmt::HexBytes(self.atr_or_ats.as_slice()),
225            )
226            .field("transport_protocol", &self.transport_protocol)
227            .field("scp_supported", &self.scp_supported)
228            .field("scp_default", &self.scp_default)
229            .field("isd_keysets", &self.isd_keysets)
230            .field("isd_key_template_format", &self.isd_key_template_format)
231            .field("capabilities", &self.capabilities)
232            .field(
233                "card_recognition_data_raw",
234                &crate::hexfmt::HexBytes(self.card_recognition_data_raw.as_slice()),
235            )
236            .field("iin", &self.iin.as_deref().map(crate::hexfmt::HexBytes))
237            .field("cin", &self.cin.as_deref().map(crate::hexfmt::HexBytes))
238            .field(
239                "card_image_number",
240                &self
241                    .card_image_number
242                    .as_deref()
243                    .map(crate::hexfmt::HexBytes),
244            )
245            .field("jc_platform_version", &self.jc_platform_version)
246            .field("privilege_encoding", &self.privilege_encoding)
247            .field("quirks_detected", &self.quirks_detected)
248            .field("discovery_warnings", &self.discovery_warnings)
249            .finish()
250    }
251}
252
253impl core::fmt::Debug for CardCapabilities {
254    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
255        f.debug_struct("CardCapabilities")
256            .field("max_logical_channels", &self.max_logical_channels)
257            .field("ciphers_supported", &self.ciphers_supported)
258            .field(
259                "privileges_supported",
260                &crate::hexfmt::HexBytes(self.privileges_supported.as_slice()),
261            )
262            .field("memory_total_bytes", &self.memory_total_bytes)
263            .field("memory_free_bytes", &self.memory_free_bytes)
264            .field("cci_raw", &crate::hexfmt::HexBytes(self.cci_raw.as_slice()))
265            .finish()
266    }
267}
268
269impl core::fmt::Debug for SecurityDomainEntry {
270    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
271        f.debug_struct("SecurityDomainEntry")
272            .field("aid", &self.aid)
273            .field("life_cycle_state", &self.life_cycle_state)
274            .field("privileges", &crate::hexfmt::HexBytes(&self.privileges[..]))
275            .field("associated_sd_aid", &self.associated_sd_aid)
276            .finish()
277    }
278}
279
280impl core::fmt::Debug for ApplicationEntry {
281    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
282        f.debug_struct("ApplicationEntry")
283            .field("aid", &self.aid)
284            .field("life_cycle_state", &self.life_cycle_state)
285            .field("privileges", &crate::hexfmt::HexBytes(&self.privileges[..]))
286            .field("associated_sd_aid", &self.associated_sd_aid)
287            .field("associated_elf_aid", &self.associated_elf_aid)
288            .finish()
289    }
290}