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