Skip to main content

scll_core/command/
mod.rs

1//! `GlobalPlatform` APDU builders — PDD §5 (pure; §3.5).
2//!
3//! Each submodule builds the C-APDU **plaintext**; SCP wrapping (`CLA | 0x04`,
4//! C-MAC, optional C-ENC) is applied later by the session/backend. Verified
5//! wire values are documented per submodule against GPCS v2.3.1 §11.
6//!
7//! `no_std`: builders return a fixed-capacity [`Capdu`] instead of `Vec<u8>`.
8//! Because a `heapless` push is fallible, the builders are total — oversized
9//! input returns [`BuildError::Overflow`] rather than panicking (the no-panic
10//! invariant, §10.5) — so each now returns `Result<Capdu, BuildError>`.
11//!
12//! All builders share the [`build`] short-APDU framer and the [`push`] /
13//! [`push_lv`] helpers below (private to this module; visible to the
14//! submodules). `build` lays out the ISO/IEC 7816-4:2020 §5.1 short-APDU cases:
15//! empty data ⇒ no `Lc` (Case 1/2), `le` ⇒ a trailing `Le = 00` ("all
16//! available"). Only SET STATUS omits `Le` (Case 1/3, Table 11-85).
17
18use heapless::Vec;
19
20use crate::limits::CAPDU_MAX;
21
22pub mod delete; // DELETE (INS E4): object + key scope — §5.3.3/§5.5/§5.8, GPCS §11.2
23pub mod get_data; // GET DATA '66'/'67'/'00E0'/IIN/CIN — §5.2
24pub mod get_status; // GET STATUS (INS F2) — §5.12, GPCS §11.4
25pub mod install; // INSTALL (INS E6): for Load/Install/Personalization — §5.4/§5.4a/§5.6/§5.7
26pub mod load;
27pub mod put_key; // PUT KEY (INS D8) — §5.3.1, GPCS §11.8
28pub mod rmac_session; // BEGIN/END R-MAC SESSION (INS 7A/78) — SCP02, GPCS App E
29pub mod select; // SELECT — ISO/IEC 7816-4; §5.2/§5.9
30pub mod set_status; // SET STATUS (INS F0) — §5.11, GPCS §11.10 // LOAD (INS E8) — §5.4a, GPCS §11.6
31
32/// A built short C-APDU plaintext buffer (≤ `CAPDU_MAX`). SCP wrapping (C-MAC,
33/// optional C-ENC) is applied later by the session, which may grow it — still
34/// within `CAPDU_MAX` for short APDUs.
35pub type Capdu = Vec<u8, CAPDU_MAX>;
36
37/// Command-builder failure. Builders are total (no panic on any input): inputs
38/// that would exceed the short-APDU buffer return `Overflow`.
39#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
40#[non_exhaustive]
41pub enum BuildError {
42    /// Inputs would exceed the short-APDU plaintext buffer (`CAPDU_MAX`).
43    #[error("inputs exceed short-APDU buffer")]
44    Overflow,
45    /// `delete_key` was called with neither `kid` nor `kvn`. GPCS v2.3.1
46    /// Table 11-24 makes both `'D0'`/`'D2'` Conditional, but at least one must
47    /// be present (an empty DELETE [key] data field selects nothing).
48    #[error("DELETE [key] needs at least one of kid/kvn")]
49    EmptyKeyScope,
50}
51
52/// Assemble a short C-APDU plaintext: 4-byte header, optional `Lc`+data,
53/// optional `Le`.
54///
55/// Empty `data` ⇒ no `Lc`/data bytes (ISO/IEC 7816-4:2020 §5.1, Case 1/2).
56/// `le = true` appends a single `0x00` (`Le`, "all available"). Total: any
57/// `data` longer than a short `Lc` (255) yields [`BuildError::Overflow`]
58/// instead of panicking.
59fn build(cla: u8, ins: u8, p1: u8, p2: u8, data: &[u8], le: bool) -> Result<Capdu, BuildError> {
60    let mut apdu = Capdu::new();
61    push(&mut apdu, &[cla, ins, p1, p2])?;
62    if !data.is_empty() {
63        let lc = u8::try_from(data.len()).map_err(|_| BuildError::Overflow)?;
64        push(&mut apdu, &[lc])?;
65        push(&mut apdu, data)?;
66    }
67    if le {
68        push(&mut apdu, &[0x00])?;
69    }
70    Ok(apdu)
71}
72
73/// Append `src` to `out`, mapping the `heapless` capacity error to `Overflow`.
74fn push(out: &mut Capdu, src: &[u8]) -> Result<(), BuildError> {
75    out.extend_from_slice(src)
76        .map_err(|()| BuildError::Overflow)
77}
78
79/// Append a 1-byte-length-prefixed field `len ‖ bytes` — the INSTALL field
80/// layout and the value half of a 1-byte-length BER-TLV. `bytes` longer than
81/// 255 ⇒ [`BuildError::Overflow`].
82fn push_lv(out: &mut Capdu, bytes: &[u8]) -> Result<(), BuildError> {
83    let len = u8::try_from(bytes.len()).map_err(|_| BuildError::Overflow)?;
84    push(out, &[len])?;
85    push(out, bytes)
86}