Skip to main content

scll_core/
report.rs

1//! Per-function reports — PDD §7.
2//!
3//! The universal `OperationResult`/`EffectiveParams`/`OperationKind`/
4//! `CommonParams`/`StepSpecific` envelope is removed (v0.6). Each workflow
5//! function returns `Result<XReport, ScllError>`; the report carries the typed
6//! payload, the *effective* parameters, and any non-fatal warnings. No per-call
7//! timestamp/version (kept deterministic for snapshot tests, §10.3).
8//!
9//! `no_std` + heapless: every former `Vec` is a fixed-capacity `heapless::Vec`
10//! sized from [`crate::limits`].
11
12use heapless::Vec;
13
14use crate::aid::Aid;
15use crate::error::Warning;
16use crate::limits::{
17    CAPDU_MAX, HASH_MAX, INSTALL_PARAMS_MAX, MAX_REMOVED_OBJECTS, MAX_WARNINGS, RAPDU_MAX,
18    TRANSPORT_NAME_MAX,
19};
20use crate::model::{CardInventory, KeyType};
21use crate::scp::ScpSession;
22use crate::transport::TransportCaps;
23
24// --- Reports (one per workflow function) ---
25#[derive(Debug)]
26pub struct ProbeReport {
27    pub effective: ProbeParams,
28    pub warnings: Vec<Warning, MAX_WARNINGS>,
29}
30#[derive(Debug)]
31pub struct PutKeysReport {
32    pub effective: PutKeysParams,
33    pub warnings: Vec<Warning, MAX_WARNINGS>,
34}
35#[derive(Debug)]
36pub struct DeleteKeyReport {
37    pub effective: DeleteKeyParams,
38    pub warnings: Vec<Warning, MAX_WARNINGS>,
39}
40#[derive(Debug)]
41pub struct CreateSsdReport {
42    pub effective: CreateSsdParams,
43    pub warnings: Vec<Warning, MAX_WARNINGS>,
44}
45#[derive(Debug)]
46pub struct LoadPackageReport {
47    pub effective: LoadPackageParams,
48    pub warnings: Vec<Warning, MAX_WARNINGS>,
49}
50/// Shared by `delete_ssd` and `delete_applet`.
51#[derive(Debug)]
52pub struct DeleteObjectReport {
53    pub effective: DeleteObjectParams,
54    pub warnings: Vec<Warning, MAX_WARNINGS>,
55}
56#[derive(Debug)]
57pub struct InstallAppletReport {
58    pub effective: InstallAppletParams,
59    pub warnings: Vec<Warning, MAX_WARNINGS>,
60}
61pub struct AppletTransmitReport {
62    pub rapdu: Vec<u8, RAPDU_MAX>,
63    pub sw: u16,
64    pub effective: AppletTransmitParams,
65    pub warnings: Vec<Warning, MAX_WARNINGS>,
66}
67#[derive(Debug)]
68pub struct SetCardStatusReport {
69    pub effective: SetCardStatusParams,
70    pub warnings: Vec<Warning, MAX_WARNINGS>,
71}
72#[derive(Debug)]
73pub struct GetCardStatusReport {
74    pub state: CardLifeCycle,
75    pub effective: GetCardStatusParams,
76    pub warnings: Vec<Warning, MAX_WARNINGS>,
77}
78/// `get_card_inventory` (§5.12a) yields the enumerated object inventory as its
79/// payload — Security Domains, Applications, and ELFs in one [`CardInventory`].
80#[derive(Debug)]
81pub struct GetCardInventoryReport {
82    pub inventory: CardInventory,
83    pub effective: GetCardInventoryParams,
84    pub warnings: Vec<Warning, MAX_WARNINGS>,
85}
86/// `open_scp` additionally yields the session as its payload.
87pub struct OpenScpReport {
88    pub session: ScpSession,
89    pub effective: OpenScpParams,
90    pub warnings: Vec<Warning, MAX_WARNINGS>,
91}
92
93// Optional APDU trace lives on the report when enabled (keys redacted); omitted
94// from the structs above for brevity, per §7.
95
96// --- Effective-parameter payloads ---
97
98#[derive(Debug)]
99pub struct DiscoverCardParams {
100    pub isd_select_strategy: IsdSelectStrategy,
101    pub used_cached_isd_aid: bool,
102}
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum IsdSelectStrategy {
105    Empty,
106    ByAid,
107}
108
109/// PUT KEY is Add-only over a session against the target SD itself (P1 =
110/// `0x00`, GPCS v2.3.1 §11.8.2.1 Table 11-66) since patch #29 — the mode /
111/// mechanism selectors were removed with the unverified Replace / Generate /
112/// parent-mediated paths.
113pub struct PutKeysParams {
114    pub target_sd_aid: Aid,
115    pub scp_protocol: ScpProtocol,
116    pub new_kvn: u8,
117    pub key_type: KeyType,
118    pub key_length: u8,
119    pub kcvs: [u8; 9], // 3 bytes × 3 keys (ENC, MAC, DEK)
120}
121#[derive(Debug, Clone, Copy)]
122pub enum ScpProtocol {
123    Scp02,
124    Scp03,
125}
126
127/// DELETE KEY is KVN-only (single `'D2'` reference, GPCS v2.3.1 §11.2.2.3.2)
128/// since patch #29 — the per-KID path was removed (`6A88` on JCOP 4 P71).
129pub struct DeleteKeyParams {
130    pub target_sd_aid: Aid,
131    pub kvn: u8, // tag 'D2'; every key of this version is deleted
132}
133
134impl core::fmt::Debug for DeleteKeyParams {
135    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
136        f.debug_struct("DeleteKeyParams")
137            .field("target_sd_aid", &self.target_sd_aid)
138            .field("kvn", &crate::hexfmt::HexByte(self.kvn))
139            .finish()
140    }
141}
142
143pub struct CreateSsdParams {
144    pub ssd_aid_effective: Aid,
145    pub aid_was_generated: bool,
146    pub parent_sd_aid: Aid,
147    pub privileges_used: [u8; 3],
148    pub elf_aid_used: Aid,
149    pub module_aid_used: Aid,
150    pub install_params_used: Vec<u8, INSTALL_PARAMS_MAX>,
151}
152
153pub struct LoadPackageParams {
154    pub package_aid: Aid,
155    pub load_file_size: u32,
156    pub hash_value: Vec<u8, HASH_MAX>,
157    pub block_count: u16,
158    pub target_sd_aid: Aid,
159}
160
161#[derive(Debug)]
162pub struct DeleteObjectParams {
163    pub target_aid: Aid,
164    pub target_kind: DeleteTargetKind,
165    pub cascade_requested: DeleteCascade,
166    pub cascade_used: bool,
167    pub instances_removed: Vec<Aid, MAX_REMOVED_OBJECTS>,
168    pub elfs_removed: Vec<Aid, MAX_REMOVED_OBJECTS>,
169}
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub enum DeleteTargetKind {
172    Ssd,
173    AppletInstance,
174    ExecutableLoadFile,
175}
176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub enum DeleteCascade {
178    Never,
179    OnlyIfEmpty,
180    IfLastInstance,
181    Cascade,
182    Always,
183}
184
185pub struct InstallAppletParams {
186    pub instance_aid: Aid,
187    pub package_aid_used: Aid,
188    pub module_aid_used: Aid,
189    pub privileges_used: [u8; 3],
190    pub system_install_params: Vec<u8, INSTALL_PARAMS_MAX>,
191    pub applet_install_params: Vec<u8, INSTALL_PARAMS_MAX>,
192    pub parent_sd_aid: Aid,
193}
194
195pub struct OpenScpParams {
196    pub target_aid: Aid,
197    pub target_kind: ScpTargetKind,
198    pub sd_aid_used_for_keys: Aid,
199    pub scp_protocol_effective: ScpProtocol, // outcome of §4.3 selection
200    pub kvn_requested: u8,
201    pub kvn_effective: u8,
202    pub i_param_effective: u8,
203    pub security_level_requested: u8,
204    pub security_level_effective: u8,
205    pub session_id: u64,
206    pub invoker_aid_used: Aid,
207}
208
209// Manual `Debug`: the key version numbers, the SCP `i` parameter, and the
210// security level are protocol scalars whose hex/bitmask form is the
211// meaningful one for smart-card debugging (e.g. KVN `0x30`, i = `0x70`,
212// level `0x13` = C-MAC|C-DECRYPTION|R-MAC per GPCS v2.3.1 §E, Table 10-1),
213// not the derive's decimal `48`/`112`/`19`. All other fields keep their
214// normal `Debug`.
215impl core::fmt::Debug for OpenScpParams {
216    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
217        f.debug_struct("OpenScpParams")
218            .field("target_aid", &self.target_aid)
219            .field("target_kind", &self.target_kind)
220            .field("sd_aid_used_for_keys", &self.sd_aid_used_for_keys)
221            .field("scp_protocol_effective", &self.scp_protocol_effective)
222            .field("kvn_requested", &crate::hexfmt::HexByte(self.kvn_requested))
223            .field("kvn_effective", &crate::hexfmt::HexByte(self.kvn_effective))
224            .field(
225                "i_param_effective",
226                &crate::hexfmt::HexByte(self.i_param_effective),
227            )
228            .field(
229                "security_level_requested",
230                &crate::hexfmt::HexByte(self.security_level_requested),
231            )
232            .field(
233                "security_level_effective",
234                &crate::hexfmt::HexByte(self.security_level_effective),
235            )
236            .field("session_id", &self.session_id)
237            .field("invoker_aid_used", &self.invoker_aid_used)
238            .finish()
239    }
240}
241#[derive(Debug, Clone, Copy, PartialEq, Eq)]
242pub enum ScpTargetKind {
243    SecurityDomainAid,
244    ApplicationAid,
245}
246
247pub struct AppletTransmitParams {
248    pub session_id: u64,
249    pub capdu_plaintext_len: u16,
250    pub rapdu_plaintext_len: u16,
251    pub sw: [u8; 2],
252    pub sec_level: u8,
253    pub scp_protocol: ScpProtocol,
254}
255
256#[derive(Debug)]
257pub struct SetCardStatusParams {
258    pub state_before: CardLifeCycle,
259    pub target_state: CardLifeCycle,
260    pub p1_status_type: u8, // ISD scope (conventionally 0x80)
261    pub p2_state_byte: u8,  // e.g. 0x0F SECURED, 0x7F CARD_LOCKED
262    pub was_no_op: bool,
263    pub force_used: bool,
264    pub irreversible: bool,
265}
266
267#[derive(Debug)]
268pub struct GetCardStatusParams {
269    pub raw_state_byte: u8,
270    pub decoded_state: CardLifeCycle,
271    pub isd_aid: Aid,
272}
273
274/// Effective parameters of a `get_card_inventory` run (§5.12a). The counts are
275/// the *retained* totals (after any capacity truncation); `truncated` is set
276/// when a `CardInventory` bound or the per-scope page cap was hit, mirroring
277/// the `WarningKind::InventoryTruncated` on the report.
278#[derive(Debug)]
279pub struct GetCardInventoryParams {
280    pub isd_aid: Aid,
281    pub security_domain_count: usize,
282    pub application_count: usize,
283    pub elf_count: usize,
284    pub truncated: bool,
285}
286
287/// Card life-cycle state (GPCS v2.3.1 Table 11-6).
288#[derive(Debug, Clone, Copy, PartialEq, Eq)]
289pub enum CardLifeCycle {
290    OpReady,     // 0x01
291    Initialized, // 0x07
292    Secured,     // 0x0F
293    CardLocked,  // 0x7F
294    Terminated,  // 0xFF (read-only; never a set target — §2.2)
295    Unknown(u8), // raises WarningKind::UnknownLifecycleByte
296}
297
298#[derive(Debug)]
299pub struct ProbeParams {
300    pub transport_name: TransportName,
301    pub transport_capabilities: TransportCaps,
302}
303
304/// Transport identity (was a `String`). The known transports are a static set,
305/// so an enum is alloc-free and exhaustively matchable; `Other` keeps
306/// caller-supplied transports open. The `Other` buffer is the only heap-free
307/// string left on this struct.
308#[derive(Debug, Clone, PartialEq, Eq)]
309pub enum TransportName {
310    Pcsc,
311    Jcsim,
312    User,
313    Other(heapless::String<TRANSPORT_NAME_MAX>),
314}
315
316/// One recorded APDU (opt-in trace; keys/key-derived material redacted).
317pub struct ApduRecord {
318    pub direction: ApduDirection,
319    pub cla_ins_p1_p2: [u8; 4],
320    pub lc: u16,
321    pub plaintext_data: Option<Vec<u8, CAPDU_MAX>>, // pre-wrap if session active
322    pub wire_data: Vec<u8, CAPDU_MAX>,              // post-wrap actually transmitted
323    pub le: Option<u8>,
324    pub sw: Option<[u8; 2]>,
325    pub timestamp_us: u64,
326}
327pub enum ApduDirection {
328    CommandToCard,
329    ResponseFromCard,
330}
331
332// ─── Debug impls: render raw byte fields as hex strings (AIDs handled by `Aid`) ──
333
334impl core::fmt::Debug for CreateSsdParams {
335    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
336        f.debug_struct("CreateSsdParams")
337            .field("ssd_aid_effective", &self.ssd_aid_effective)
338            .field("aid_was_generated", &self.aid_was_generated)
339            .field("parent_sd_aid", &self.parent_sd_aid)
340            .field(
341                "privileges_used",
342                &crate::hexfmt::HexBytes(&self.privileges_used[..]),
343            )
344            .field("elf_aid_used", &self.elf_aid_used)
345            .field("module_aid_used", &self.module_aid_used)
346            .field(
347                "install_params_used",
348                &crate::hexfmt::HexBytes(self.install_params_used.as_slice()),
349            )
350            .finish()
351    }
352}
353
354impl core::fmt::Debug for LoadPackageParams {
355    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
356        f.debug_struct("LoadPackageParams")
357            .field("package_aid", &self.package_aid)
358            .field("load_file_size", &self.load_file_size)
359            .field(
360                "hash_value",
361                &crate::hexfmt::HexBytes(self.hash_value.as_slice()),
362            )
363            .field("block_count", &self.block_count)
364            .field("target_sd_aid", &self.target_sd_aid)
365            .finish()
366    }
367}
368
369impl core::fmt::Debug for InstallAppletParams {
370    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
371        f.debug_struct("InstallAppletParams")
372            .field("instance_aid", &self.instance_aid)
373            .field("package_aid_used", &self.package_aid_used)
374            .field("module_aid_used", &self.module_aid_used)
375            .field(
376                "privileges_used",
377                &crate::hexfmt::HexBytes(&self.privileges_used[..]),
378            )
379            .field(
380                "system_install_params",
381                &crate::hexfmt::HexBytes(self.system_install_params.as_slice()),
382            )
383            .field(
384                "applet_install_params",
385                &crate::hexfmt::HexBytes(self.applet_install_params.as_slice()),
386            )
387            .field("parent_sd_aid", &self.parent_sd_aid)
388            .finish()
389    }
390}
391
392impl core::fmt::Debug for PutKeysParams {
393    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
394        f.debug_struct("PutKeysParams")
395            .field("target_sd_aid", &self.target_sd_aid)
396            .field("scp_protocol", &self.scp_protocol)
397            .field("new_kvn", &crate::hexfmt::HexByte(self.new_kvn))
398            .field("key_type", &self.key_type)
399            .field("key_length", &self.key_length)
400            .field("kcvs", &crate::hexfmt::HexBytes(&self.kcvs[..]))
401            .finish()
402    }
403}
404
405impl core::fmt::Debug for AppletTransmitReport {
406    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
407        f.debug_struct("AppletTransmitReport")
408            .field("rapdu", &crate::hexfmt::HexBytes(self.rapdu.as_slice()))
409            .field("sw", &format_args!("0x{:04X}", self.sw))
410            .field("effective", &self.effective)
411            .field("warnings", &self.warnings)
412            .finish()
413    }
414}
415
416impl core::fmt::Debug for AppletTransmitParams {
417    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
418        f.debug_struct("AppletTransmitParams")
419            .field("session_id", &self.session_id)
420            .field("capdu_plaintext_len", &self.capdu_plaintext_len)
421            .field("rapdu_plaintext_len", &self.rapdu_plaintext_len)
422            .field("sw", &crate::hexfmt::HexBytes(&self.sw[..]))
423            .field("sec_level", &self.sec_level)
424            .field("scp_protocol", &self.scp_protocol)
425            .finish()
426    }
427}