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}