Skip to main content

apdu_dispatch/
dispatch.rs

1//! This "APDU dispatch" consumes APDUs from either a contactless or contact interface, or both.
2//! Each APDU will be sent to an "App".  The dispatch will manage selecting and deselecting apps,
3//! and will gauruntee only one app will be selected at a time.  Only the selected app will
4//! receive APDU's.  Apps are selected based on their AID.
5//!
6//! Additionally, the APDU dispatch could repeatedly call "poll" on the selected App.  If this was in place, the App
7//! could choose to reply at time of APDU, or can defer and reply later (during one of the poll calls).
8//!
9//! Apps need to implement the App trait to be managed.
10//!
11
12use crate::response::SIZE as ResponseSize;
13use crate::App;
14use crate::{
15    interchanges::{self, Responder},
16    response, Command,
17};
18
19use iso7816::{
20    command::{CommandView, FromSliceError},
21    Aid, Instruction, Result, Status,
22};
23
24/// Maximum length of a data field of a response that can fit in an interchange message after
25/// concatenation of SW1SW2
26const MAX_INTERCHANGE_DATA: usize = if interchanges::SIZE < ResponseSize {
27    interchanges::SIZE
28} else {
29    ResponseSize
30} - 2;
31
32pub use iso7816::Interface;
33
34pub enum RequestType {
35    Select(Aid, Interface),
36    /// Get Response including the Le field of the command
37    GetResponse,
38    NewCommand(Interface),
39    /// Incorrect command, which means an error should be returned
40    BadCommand(Status),
41    None,
42}
43
44#[derive(PartialEq)]
45enum RawApduBuffer {
46    None,
47    Request(Command),
48    Response(response::Data),
49}
50
51struct ApduBuffer {
52    pub raw: RawApduBuffer,
53}
54
55impl ApduBuffer {
56    fn request(&mut self, command: CommandView<'_>) {
57        match &mut self.raw {
58            RawApduBuffer::Request(buffered) => {
59                buffered.extend_from_command_view(command).ok();
60            }
61            _ => {
62                if self.raw != RawApduBuffer::None {
63                    info!("Was buffering the last response, but aborting that now for this new request.");
64                }
65                let mut new_cmd = iso7816::Command::try_from(&[0, 0, 0, 0]).unwrap();
66                new_cmd.extend_from_command_view(command).ok();
67                self.raw = RawApduBuffer::Request(new_cmd);
68            }
69        }
70    }
71
72    fn response(&mut self, response: &response::Data) {
73        self.raw = RawApduBuffer::Response(response.clone());
74    }
75}
76
77pub struct ApduDispatch<'pipe> {
78    // or currently_selected_aid, or...
79    current_aid: Option<Aid>,
80    contact: Responder<'pipe>,
81    contactless: Responder<'pipe>,
82    interface: Option<Interface>,
83
84    buffer: ApduBuffer,
85    response_len_expected: usize,
86    was_request_chained: bool,
87}
88
89impl<'pipe> ApduDispatch<'pipe> {
90    fn apdu_type(apdu: CommandView<'_>, interface: Interface) -> RequestType {
91        info!("instruction: {:?} {}", apdu.instruction(), apdu.p1);
92        if apdu.instruction() == Instruction::Select && (apdu.p1 & 0x04) != 0 {
93            Aid::try_new(apdu.data()).map_or_else(
94                |_err| {
95                    warn!("Failed to parse AID: {:?}", _err);
96                    RequestType::BadCommand(Status::IncorrectDataParameter)
97                },
98                |aid| RequestType::Select(aid, interface),
99            )
100        } else if apdu.instruction() == Instruction::GetResponse {
101            RequestType::GetResponse
102        } else {
103            RequestType::NewCommand(interface)
104        }
105    }
106
107    pub fn new(contact: Responder<'pipe>, contactless: Responder<'pipe>) -> Self {
108        ApduDispatch {
109            current_aid: None,
110            contact,
111            contactless,
112            interface: None,
113            was_request_chained: false,
114            response_len_expected: 0,
115            buffer: ApduBuffer {
116                raw: RawApduBuffer::None,
117            },
118        }
119    }
120
121    // It would be nice to store `current_app` instead of constantly looking up by AID,
122    // but that won't work due to ownership rules
123    fn find_app<'a, 'b>(
124        aid: Option<&Aid>,
125        apps: &'a mut [&'b mut dyn App],
126    ) -> Option<&'a mut &'b mut dyn App> {
127        // match aid {
128        //     Some(aid) => apps.iter_mut().find(|app| aid.starts_with(app.rid())),
129        //     None => None,
130        // }
131        aid.and_then(move |aid| {
132            debug!("matching {:?}", aid);
133            apps.iter_mut().find(|app| {
134                // aid.starts_with(app.aid().truncated())
135                debug!("...against {:?}", app.aid());
136                app.aid().matches(aid)
137            })
138        })
139    }
140
141    fn busy(&self) -> bool {
142        // the correctness of this relies on the properties of interchange - requester can only
143        // send request in the idle state.
144        use interchange::State::*;
145        let contact_busy = !matches!(self.contact.state(), Idle | Requested);
146        let contactless_busy = !matches!(self.contactless.state(), Idle | Requested);
147        contactless_busy || contact_busy
148    }
149
150    #[inline(never)]
151    fn buffer_chained_apdu_if_needed(
152        &mut self,
153        command: CommandView<'_>,
154        interface: Interface,
155    ) -> RequestType {
156        // iso 7816-4 5.1.1
157        // check Apdu level chaining and buffer if necessary.
158        if !command.class().chain().not_the_last() {
159            let is_chaining = matches!(self.buffer.raw, RawApduBuffer::Request(_));
160
161            if is_chaining {
162                self.buffer.request(command);
163
164                // Response now needs to be chained.
165                self.was_request_chained = true;
166                info!("combined chained commands.");
167
168                RequestType::NewCommand(interface)
169            } else {
170                if self.buffer.raw == RawApduBuffer::None {
171                    self.was_request_chained = false;
172                }
173                let apdu_type = Self::apdu_type(command, interface);
174                match apdu_type {
175                    // Keep buffer the same in case of GetResponse
176                    RequestType::GetResponse => (),
177                    // Overwrite for everything else.
178                    _ => self.buffer.request(command),
179                }
180                apdu_type
181            }
182        } else {
183            match interface {
184                // acknowledge
185                Interface::Contact => {
186                    self.contact
187                        .respond(Status::Success.into())
188                        .expect("Could not respond");
189                }
190                Interface::Contactless => {
191                    self.contactless
192                        .respond(Status::Success.into())
193                        .expect("Could not respond");
194                }
195            }
196
197            if !command.data().is_empty() {
198                info!("chaining {} bytes", command.data().len());
199                self.buffer.request(command);
200            }
201
202            // Nothing for the application to consume yet.
203            RequestType::None
204        }
205    }
206
207    fn parse_apdu<const S: usize>(message: &interchanges::Data) -> Result<iso7816::Command<S>> {
208        debug!(">> {}", hex_str!(message.as_slice(), sep:""));
209        match iso7816::Command::try_from(message) {
210            Ok(command) => Ok(command),
211            Err(_error) => {
212                info!("apdu bad");
213                match _error {
214                    FromSliceError::TooShort => {
215                        info!("TooShort");
216                    }
217                    FromSliceError::TooLong => {
218                        info!("TooLong");
219                    }
220                    FromSliceError::InvalidClass => {
221                        info!("InvalidClass");
222                    }
223                    FromSliceError::InvalidFirstBodyByteForExtended => {
224                        info!("InvalidFirstBodyByteForExtended");
225                    }
226                    FromSliceError::InvalidSliceLength => {
227                        info!("InvalidSliceLength");
228                    }
229                }
230                Err(Status::UnspecifiedCheckingError)
231            }
232        }
233    }
234
235    #[inline(never)]
236    fn check_for_request(&mut self) -> RequestType {
237        if !self.busy() {
238            // Check to see if we have gotten a message, giving priority to contactless.
239            let (message, interface) = if let Some(message) = self.contactless.take_request() {
240                (message, Interface::Contactless)
241            } else if let Some(message) = self.contact.take_request() {
242                (message, Interface::Contact)
243            } else {
244                return RequestType::None;
245            };
246
247            let apdu;
248
249            if let Some(i) = self.interface {
250                if i != interface {
251                    apdu = Err(Status::UnspecifiedNonpersistentExecutionError)
252                } else {
253                    apdu = Self::parse_apdu::<{ interchanges::SIZE }>(&message);
254                }
255            } else {
256                self.interface = Some(interface);
257                apdu = Self::parse_apdu::<{ interchanges::SIZE }>(&message);
258            }
259
260            // Parse the message as an APDU.
261            match apdu {
262                Ok(command) => {
263                    self.response_len_expected = command.expected();
264                    // The Apdu may be standalone or part of a chain.
265                    self.buffer_chained_apdu_if_needed(command.as_view(), interface)
266                }
267                Err(response) => {
268                    // If not a valid APDU, return error and don't pass to app.
269                    info!("Invalid apdu");
270                    match interface {
271                        Interface::Contactless => self
272                            .contactless
273                            .respond(response.into())
274                            .expect("cant respond"),
275                        Interface::Contact => {
276                            self.contact.respond(response.into()).expect("cant respond")
277                        }
278                    }
279                    RequestType::None
280                }
281            }
282        } else {
283            RequestType::None
284        }
285    }
286
287    #[inline(never)]
288    fn reply_error(&mut self, status: Status) {
289        self.respond(status.into());
290        self.buffer.raw = RawApduBuffer::None;
291    }
292
293    #[inline(never)]
294    fn handle_reply(&mut self) {
295        // Consider if we need to reply via chaining method.
296        // If the reader is using chaining, we will simply
297        // reply 61XX, and put the response in a buffer.
298        // It is up to the reader to then send GetResponse
299        // requests, to which we will return up to `Le` bytes at a time.
300        let (new_state, response) = match &mut self.buffer.raw {
301            RawApduBuffer::Request(_) | RawApduBuffer::None => {
302                info!("Unexpected GetResponse request.");
303                (RawApduBuffer::None, Status::UnspecifiedCheckingError.into())
304            }
305            RawApduBuffer::Response(res) => {
306                let max_response_len = self.response_len_expected.min(MAX_INTERCHANGE_DATA);
307                if self.was_request_chained || res.len() > max_response_len {
308                    // Do not send more than the expected bytes
309                    let boundary = max_response_len.min(res.len());
310
311                    let to_send = &res[..boundary];
312                    let remaining = &res[boundary..];
313                    let mut message = interchanges::Data::from_slice(to_send).unwrap();
314                    let return_code = if remaining.len() > 255 {
315                        // XX = 00 indicates more than 255 bytes of data
316                        0x6100u16
317                    } else if !remaining.is_empty() {
318                        0x6100 + (remaining.len() as u16)
319                    } else {
320                        // Last chunk has success code
321                        0x9000
322                    };
323                    message
324                        .extend_from_slice(&return_code.to_be_bytes())
325                        .expect("Failed add to status bytes");
326                    if return_code == 0x9000 {
327                        (RawApduBuffer::None, message)
328                    } else {
329                        info!("Still {} bytes in response buffer", remaining.len());
330                        (
331                            RawApduBuffer::Response(response::Data::from_slice(remaining).unwrap()),
332                            message,
333                        )
334                    }
335                } else {
336                    // Add success code
337                    res.extend_from_slice(&[0x90, 00])
338                        .expect("Failed to add the status bytes");
339                    (
340                        RawApduBuffer::None,
341                        interchanges::Data::from_slice(res.as_slice()).unwrap(),
342                    )
343                }
344            }
345        };
346        self.buffer.raw = new_state;
347        self.respond(response);
348    }
349
350    #[inline(never)]
351    fn handle_app_response(&mut self, response: &Result<()>, data: &response::Data) {
352        // put message into the response buffer
353        match response {
354            Ok(()) => {
355                info!("buffered the response of {} bytes.", data.len());
356                self.buffer.response(data);
357                self.handle_reply();
358            }
359            Err(status) => {
360                // Just reply the error immediately.
361                info!("buffered app error");
362                self.reply_error(*status);
363            }
364        }
365    }
366
367    #[inline(never)]
368    fn handle_app_select(&mut self, apps: &mut [&mut dyn App], aid: Aid, interface: Interface) {
369        // three cases:
370        // - currently selected app has different AID -> deselect it, to give it
371        //   the chance to clear sensitive state
372        // - currently selected app has given AID (typical behaviour will be NOP,
373        //   but pass along anyway) -> do not deselect it first
374        // - no currently selected app
375        //
376        // For PIV, "SELECT" is NOP if it was already selected, but this is
377        // not necessarily the case for other apps
378
379        // if there is a selected app with a different AID, deselect it
380
381        // select specified app in any case
382        if let Some(app) = Self::find_app(Some(&aid), apps) {
383            info!("Selected app");
384            let mut response = response::Data::new();
385            let result = match &self.buffer.raw {
386                RawApduBuffer::Request(apdu) => {
387                    app.select(interface, apdu.as_view(), &mut response)
388                }
389                _ => panic!("Unexpected buffer state."),
390            };
391
392            let old_aid = self.current_aid.replace(aid);
393            if let Some(old_aid) = old_aid {
394                if old_aid != aid {
395                    let app = Self::find_app(self.current_aid.as_ref(), apps).unwrap();
396                    // for now all apps will be happy with this.
397                    app.deselect();
398                }
399            }
400
401            self.handle_app_response(&result, &response);
402        } else {
403            info!("could not find app by aid: {}", hex_str!(&aid.as_bytes()));
404            self.reply_error(Status::NotFound);
405        };
406    }
407
408    #[inline(never)]
409    fn handle_app_command(&mut self, apps: &mut [&mut dyn App], interface: Interface) {
410        // if there is a selected app, send it the command
411        let mut response = response::Data::new();
412        if let Some(app) = Self::find_app(self.current_aid.as_ref(), apps) {
413            let result = match &self.buffer.raw {
414                RawApduBuffer::Request(apdu) => app.call(interface, apdu.as_view(), &mut response),
415                _ => panic!("Unexpected buffer state."),
416            };
417            self.handle_app_response(&result, &response);
418        } else {
419            // TODO: correct error?
420            self.reply_error(Status::NotFound);
421        };
422    }
423
424    pub fn poll(&mut self, apps: &mut [&mut dyn App]) -> Option<Interface> {
425        // Only take on one transaction at a time.
426        let request_type = self.check_for_request();
427
428        // if there is a new request:
429        // - if it's a select, handle appropriately
430        // - else pass it on to currently selected app
431        // if there is no new request, poll currently selected app
432        match request_type {
433            // SELECT case
434            RequestType::Select(aid, interface) => {
435                info!("Select");
436                self.handle_app_select(apps, aid, interface);
437            }
438
439            RequestType::GetResponse => {
440                info!("GetResponse");
441                self.handle_reply();
442            }
443
444            // command that is not a special command -- goes to app.
445            RequestType::NewCommand(interface) => {
446                info!("Command");
447                self.handle_app_command(apps, interface);
448            }
449            RequestType::BadCommand(status) => {
450                info!("Bad command");
451                self.reply_error(status);
452            }
453            RequestType::None => {}
454        }
455
456        // slight priority to contactless.
457        if self.contactless.state() == interchange::State::Responded {
458            Some(Interface::Contactless)
459        } else if self.contact.state() == interchange::State::Responded {
460            Some(Interface::Contact)
461        } else {
462            None
463        }
464    }
465
466    #[inline(never)]
467    fn respond(&mut self, message: interchanges::Data) {
468        debug!("<< {}", hex_str!(message.as_slice(), sep:""));
469        match self.interface.unwrap() {
470            Interface::Contactless => self.contactless.respond(message).expect("cant respond"),
471            Interface::Contact => self.contact.respond(message).expect("cant respond"),
472        }
473    }
474}