apdu_core/
command.rs

1/// An APDU command to be transmitted
2#[derive(Debug)]
3pub struct Command<'a> {
4    pub cla: u8,
5    pub ins: u8,
6    pub p1: u8,
7    pub p2: u8,
8    pub le: Option<u16>,
9    pub payload: Option<&'a [u8]>,
10}
11
12impl<'a> Command<'a> {
13    /// Constructs an command with CLA, INS, P1, and P2.
14    /// No payloads will be transmitted or received.
15    pub fn new(cla: u8, ins: u8, p1: u8, p2: u8) -> Self {
16        Self {
17            cla,
18            ins,
19            p1,
20            p2,
21            le: None,
22            payload: None,
23        }
24    }
25
26    /// Constructs an command with CLA, INS, P1, P2, and Le.
27    /// A payload will be received.
28    pub fn new_with_le(cla: u8, ins: u8, p1: u8, p2: u8, le: u16) -> Self {
29        Self {
30            cla,
31            ins,
32            p1,
33            p2,
34            le: Some(le),
35            payload: None,
36        }
37    }
38
39    /// Constructs an command with CLA, INS, P1, P2, and a payload.
40    /// No payload will be received.
41    pub fn new_with_payload(cla: u8, ins: u8, p1: u8, p2: u8, payload: &'a [u8]) -> Self {
42        Self {
43            cla,
44            ins,
45            p1,
46            p2,
47            le: None,
48            payload: Some(payload),
49        }
50    }
51
52    /// Constructs an command with CLA, INS, P1, P2, Le, and a payload.
53    /// A payload will be received.
54    pub fn new_with_payload_le(
55        cla: u8,
56        ins: u8,
57        p1: u8,
58        p2: u8,
59        le: u16,
60        payload: &'a [u8],
61    ) -> Self {
62        Self {
63            cla,
64            ins,
65            p1,
66            p2,
67            le: Some(le),
68            payload: Some(payload),
69        }
70    }
71
72    /// Writes a serialised byte stream onto the mutable buffer.
73    pub fn write(&self, buf: &mut [u8]) {
74        let Command {
75            cla,
76            ins,
77            p1,
78            p2,
79            le,
80            payload,
81        } = self;
82
83        // &mut [u8] does not have push or extend methods,
84        // So we are using a mutator to have a reference of the buffer and a cursor on them.
85        struct Mutator<'a> {
86            buf: &'a mut [u8],
87            i: usize,
88        }
89
90        impl<'a> Mutator<'a> {
91            fn push(&mut self, b: u8) {
92                self.buf[self.i] = b;
93                self.i += 1;
94            }
95
96            fn extend(&mut self, b: &[u8]) {
97                let len = b.len();
98                self.buf[self.i..self.i + len].copy_from_slice(b);
99                self.i += len;
100            }
101        }
102
103        let mut m = Mutator { buf, i: 0 };
104        m.extend(&[*cla, *ins, *p1, *p2]);
105
106        let has_payload = &payload.is_some();
107        if let Some(p) = payload {
108            // According to spec, length can be 0, 1 or 2 bytes
109            // 2 bytes is prefaced by 00 to differentiate between 1 byte lengths
110            if cfg!(feature = "longer_payloads") && p.len() > u8::MAX as usize {
111                m.push(0u8);
112                m.push(p.len() as u8);
113                m.push((p.len() >> 8) as u8);
114            } else {
115                m.push(p.len() as u8);
116            }
117
118            m.extend(p);
119        }
120
121        if let Some(l) = *le {
122            if cfg!(feature = "longer_payloads") && l > u8::MAX.into() {
123                if !has_payload {
124                    m.push(0u8);
125                }
126                m.push(l as u8);
127                m.push((l >> 8) as u8);
128            } else {
129                m.push(l as u8);
130            }
131        }
132    }
133
134    /// Calculates the length of entire the command.
135    #[allow(clippy::len_without_is_empty)]
136    pub fn len(&self) -> usize {
137        let (lc, payload) = match self.payload {
138            Some(p) => (
139                match cfg!(feature = "longer_payloads") && p.len() > u8::MAX as usize {
140                    true => 2,
141                    _ => 1,
142                },
143                p.len(),
144            ),
145            _ => (0, 0),
146        };
147
148        let le = match self.le {
149            Some(l) => match cfg!(feature = "longer_payloads") && l > u8::MAX.into() {
150                true => match self.payload.is_some() {
151                    true => 2,
152                    _ => 3,
153                },
154                _ => 1,
155            },
156            _ => 0,
157        };
158
159        // Header is always 4 bytes
160        4 + lc + payload + le
161    }
162}
163
164#[cfg(feature = "std")]
165impl<'a> From<Command<'a>> for Vec<u8> {
166    /// Converts the command into octets.
167    fn from(command: Command) -> Self {
168        let len = command.len();
169        let mut buf = Vec::with_capacity(len);
170
171        #[allow(clippy::uninit_vec)]
172        unsafe {
173            buf.set_len(len);
174        }
175
176        command.write(&mut buf);
177
178        buf
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn command_to_vec() {
188        assert_eq!(
189            vec![0x01, 0x02, 0x03, 0x04, 0x03, 0x05, 0x06, 0x07, 0x08],
190            Vec::from(Command::new_with_payload_le(
191                0x01,
192                0x02,
193                0x03,
194                0x04,
195                0x08,
196                &[0x05, 0x06, 0x07]
197            )),
198        );
199    }
200}