Skip to main content

scll_core/command/
load.rs

1//! LOAD (CLA 84, INS E8) — PDD §5.4a, GPCS v2.3.1 §11.6.
2//!
3//! P1: `0x00` more blocks, `0x80` last block. P2: block number from `0x00`.
4//! Chunk size ≤ `LOAD_BLOCK_DATA` (223 B) plaintext (short APDU); ≤ 256 blocks.
5//!
6//! Scope note: `load_block` is a pure per-block APDU framer. The first block's
7//! `'C4'` (Load File) BER wrapper carries the length of the **whole** assembled
8//! Load File Data Block, which a per-block framer cannot know, so it is **not**
9//! added here. It is the first bytes of the LFDB byte stream produced upstream
10//! by the CAP/LFDB streamer (`cap::LoadFileDataBlock::next_block`, S3) — so the
11//! first chunk handed to this framer already begins with `'C4' len …`. This
12//! keeps the fixed `(block_no, last, chunk)` signature and the "never hold the
13//! whole LFDB in RAM" contract (§5.4a).
14
15use crate::command::{build, BuildError, Capdu};
16
17/// Build one LOAD block. `block_no` is the P2 counter; `last` sets P1 `0x80`
18/// (otherwise `0x00`, "more blocks"). `chunk` is one streamed LFDB slice
19/// (≤ `LOAD_BLOCK_DATA`); see the module note on the `'C4'` wrapper.
20///
21/// # Errors
22/// Returns [`BuildError::Overflow`] if the encoded inputs would exceed the
23/// short-APDU plaintext buffer (`CAPDU_MAX`).
24#[allow(clippy::module_name_repetitions)] // GP command name; intentional public API
25pub fn load_block(block_no: u8, last: bool, chunk: &[u8]) -> Result<Capdu, BuildError> {
26    let p1 = if last { 0x80 } else { 0x00 };
27    build(0x84, 0xE8, p1, block_no, chunk, true)
28}
29
30#[cfg(test)]
31mod tests {
32    use super::*;
33    use scll_test_util::HexSlice;
34
35    #[test]
36    fn intermediate_block_uses_p1_00() {
37        let apdu = load_block(0x00, false, &[0x01, 0x02, 0x03]).unwrap();
38        assert_eq!(
39            HexSlice(&apdu),
40            HexSlice([0x84, 0xE8, 0x00, 0x00, 0x03, 0x01, 0x02, 0x03, 0x00])
41        );
42    }
43
44    #[test]
45    fn last_block_sets_p1_80_and_carries_block_number() {
46        let apdu = load_block(0x05, true, &[0xAA]).unwrap();
47        assert_eq!(
48            HexSlice(&apdu),
49            HexSlice([0x84, 0xE8, 0x80, 0x05, 0x01, 0xAA, 0x00])
50        );
51    }
52
53    #[test]
54    fn full_block_chunk_fits() {
55        use crate::limits::LOAD_BLOCK_DATA;
56        let chunk = [0x5Au8; LOAD_BLOCK_DATA];
57        let apdu = load_block(0x10, false, &chunk).unwrap();
58        // header(4) + Lc(1) + LOAD_BLOCK_DATA + Le(1).
59        assert_eq!(apdu.len(), 4 + 1 + LOAD_BLOCK_DATA + 1);
60        assert_eq!(apdu[4] as usize, LOAD_BLOCK_DATA); // Lc
61    }
62
63    #[test]
64    fn oversized_chunk_overflows() {
65        let big = [0x00u8; 256];
66        assert_eq!(load_block(0x00, false, &big), Err(BuildError::Overflow));
67    }
68}