Skip to main content

scll_core/command/
delete.rs

1//! DELETE (CLA 84, INS E4) — PDD §5.3.3 (key) / §5.5 / §5.8 (object), GPCS §11.2.
2//!
3//! Object scope: Data `'4F' len AID`; P2 `0x00` delete-object, `0x80` cascade
4//! (Table 11-22). Key scope: P1 `0x00` last/only (Table 11-21), P2 `0x00`
5//! delete-object, Data `'D0' KID` / `'D2' KVN` (both Conditional, Table 11-24);
6//! omit one tag for multi-key delete; response single `'00'` (§11.2.3.1).
7//! All **verified** against GPCS v2.3.1.
8
9use crate::command::{build, push, push_lv, BuildError, Capdu};
10
11/// Build DELETE [object] for an AID (P2 selects cascade).
12///
13/// `CLA=84 INS=E4 P1=00`. P2 = `0x80` (delete object + related, e.g. an SD's
14/// contents) when `cascade`, else `0x00` (delete object only) — Table 11-22.
15/// Data = `'4F' len <aid>`. `Le=00`.
16///
17/// # Errors
18/// Returns [`BuildError::Overflow`] if the encoded inputs would exceed the
19/// short-APDU plaintext buffer (`CAPDU_MAX`).
20#[allow(clippy::module_name_repetitions)] // GP command name; intentional public API
21pub fn delete_object(aid: &[u8], cascade: bool) -> Result<Capdu, BuildError> {
22    let mut data = Capdu::new();
23    push(&mut data, &[0x4F])?;
24    push_lv(&mut data, aid)?;
25    let p2 = if cascade { 0x80 } else { 0x00 };
26    build(0x84, 0xE4, 0x00, p2, &data, true)
27}
28
29/// Build DELETE [key]. `kid` → tag `'D0'`, `kvn` → tag `'D2'`; `None` omits the
30/// tag (multi-key delete, GPCS Table 11-24). Supplying both deletes a single
31/// key. Omission is encoded by leaving the tag off the wire — never a
32/// `0xFF`/`0x00` sentinel byte.
33///
34/// `CLA=84 INS=E4 P1=00 P2=00`, Data = `['D0' 01 kid] ['D2' 01 kvn]`. `Le=00`.
35///
36/// # Errors
37/// Returns [`BuildError::EmptyKeyScope`] if both `kid` and `kvn` are `None`
38/// (Table 11-24 needs at least one), or [`BuildError::Overflow`] if the encoded
39/// inputs would exceed the short-APDU plaintext buffer (`CAPDU_MAX`).
40#[allow(clippy::module_name_repetitions)] // GP command name; intentional public API
41pub fn delete_key(kid: Option<u8>, kvn: Option<u8>) -> Result<Capdu, BuildError> {
42    if kid.is_none() && kvn.is_none() {
43        return Err(BuildError::EmptyKeyScope);
44    }
45    let mut data = Capdu::new();
46    if let Some(kid) = kid {
47        push(&mut data, &[0xD0, 0x01, kid])?;
48    }
49    if let Some(kvn) = kvn {
50        push(&mut data, &[0xD2, 0x01, kvn])?;
51    }
52    build(0x84, 0xE4, 0x00, 0x00, &data, true)
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use proptest::prelude::*;
59    use scll_test_util::HexSlice;
60
61    #[test]
62    fn delete_object_only_uses_p2_00() {
63        let aid = [0xA0, 0x00, 0x00, 0x01, 0x51];
64        let apdu = delete_object(&aid, false).unwrap();
65        assert_eq!(
66            HexSlice(&apdu),
67            HexSlice([
68                0x84, 0xE4, 0x00, 0x00, 0x07, 0x4F, 0x05, 0xA0, 0x00, 0x00, 0x01, 0x51, 0x00
69            ])
70        );
71    }
72
73    #[test]
74    fn delete_object_cascade_uses_p2_80() {
75        let aid = [0xA0, 0x00, 0x00, 0x01, 0x51];
76        let apdu = delete_object(&aid, true).unwrap();
77        assert_eq!(apdu[3], 0x80); // cascade bit
78    }
79
80    #[test]
81    fn delete_single_key_carries_both_tags_d0_then_d2() {
82        let apdu = delete_key(Some(0x01), Some(0x30)).unwrap();
83        assert_eq!(
84            HexSlice(&apdu),
85            HexSlice([0x84, 0xE4, 0x00, 0x00, 0x06, 0xD0, 0x01, 0x01, 0xD2, 0x01, 0x30, 0x00])
86        );
87    }
88
89    #[test]
90    fn delete_all_kvns_for_a_kid_omits_d2() {
91        let apdu = delete_key(Some(0x02), None).unwrap();
92        assert_eq!(
93            HexSlice(&apdu),
94            HexSlice([0x84, 0xE4, 0x00, 0x00, 0x03, 0xD0, 0x01, 0x02, 0x00])
95        );
96    }
97
98    #[test]
99    fn delete_all_kids_for_a_kvn_omits_d0() {
100        let apdu = delete_key(None, Some(0x30)).unwrap();
101        assert_eq!(
102            HexSlice(&apdu),
103            HexSlice([0x84, 0xE4, 0x00, 0x00, 0x03, 0xD2, 0x01, 0x30, 0x00])
104        );
105    }
106
107    #[test]
108    fn delete_key_needs_at_least_one_scope() {
109        assert_eq!(delete_key(None, None), Err(BuildError::EmptyKeyScope));
110    }
111
112    #[test]
113    fn oversized_object_aid_overflows() {
114        let big = [0x00u8; 255];
115        assert_eq!(delete_object(&big, false), Err(BuildError::Overflow));
116    }
117
118    proptest! {
119        /// Both-None always errs; otherwise the present tags land in order.
120        #[test]
121        fn key_scope_combinations(
122            kid in proptest::option::of(any::<u8>()),
123            kvn in proptest::option::of(any::<u8>()),
124        ) {
125            if kid.is_none() && kvn.is_none() {
126                prop_assert_eq!(delete_key(kid, kvn), Err(BuildError::EmptyKeyScope));
127            } else {
128                let apdu = delete_key(kid, kvn).unwrap();
129                prop_assert_eq!(&apdu[0..4], &[0x84, 0xE4, 0x00, 0x00]);
130                let body = &apdu[5..apdu.len() - 1]; // strip header+Lc and Le
131                if let Some(k) = kid {
132                    prop_assert_eq!(&body[0..3], &[0xD0, 0x01, k]);
133                }
134                if let Some(v) = kvn {
135                    prop_assert_eq!(&body[body.len() - 3..], &[0xD2, 0x01, v]);
136                }
137            }
138        }
139    }
140}