From 08d3631ef2ae3c77a0d9212682df247bb61a3280 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Mon, 14 Sep 2020 20:06:51 -0700 Subject: [PATCH] rework apdu-dispatch, add apdu chaining - APDU parsing now only occurs in apdu-dispatch, instead of both iso14443 and usbd-ccid - apdu-dispatch now handles apdu chaining transparently to the apps. - More tests added to cover chaining and apdu parsing --- README.md | 2 +- src/dispatch.rs | 225 +++++++++++-- src/types.rs | 4 +- tests/dispatch.rs | 733 +++++++++++++++++++++++++++++++++++++++++ tests/dispatch_test.rs | 397 ---------------------- 5 files changed, 930 insertions(+), 431 deletions(-) create mode 100644 tests/dispatch.rs delete mode 100644 tests/dispatch_test.rs diff --git a/README.md b/README.md index e349fa8..48dfc63 100644 --- a/README.md +++ b/README.md @@ -3,5 +3,5 @@ For the tests to run locally for ApduDispatch, you need to enable std for logs. ``` -cargo test --features std,logging/std --target $(rustc -Vv | awk 'NR==5{print $2}') +cargo test --features std,logging/std,log-all --target $(rustc -Vv | awk 'NR==5{print $2}') ``` diff --git a/src/dispatch.rs b/src/dispatch.rs index b99eed6..66b324a 100644 --- a/src/dispatch.rs +++ b/src/dispatch.rs @@ -17,6 +17,8 @@ use iso7816::{ Instruction, Response, Status, + response, + command::FromSliceError, }; #[derive(Copy, Clone)] @@ -25,6 +27,12 @@ pub enum InterfaceType{ Contactless, } +pub enum ApduType{ + Select(Aid), + GetResponse, + Other, +} + use crate::logger::info; use interchange::Responder; @@ -36,15 +44,20 @@ pub struct ApduDispatch { contact: Responder, contactless: Responder, current_interface: InterfaceType, + + chain_buffer: response::Data, + is_chaining_response: bool, } impl ApduDispatch { - fn aid_to_select(apdu: &Command) -> Option { + fn apdu_type(apdu: &Command) -> ApduType { if apdu.instruction() == Instruction::Select && (apdu.p1 & 0x04) != 0 { - Some(Aid::from_slice(apdu.data()).unwrap()) + ApduType::Select(Aid::from_slice(apdu.data()).unwrap()) + } else if apdu.instruction() == Instruction::GetResponse { + ApduType::GetResponse } else { - None + ApduType::Other } } @@ -57,6 +70,8 @@ impl ApduDispatch contact: contact, contactless: contactless, current_interface: InterfaceType::Contact, + chain_buffer: response::Data::new(), + is_chaining_response: false, } } @@ -95,18 +110,116 @@ impl ApduDispatch contactless_busy || contact_busy } + fn buffer_chained_apdu_if_needed(&mut self, command: Command, inferface: InterfaceType) -> Option{ + self.current_interface = inferface; + // iso 7816-4 5.1.1 + // check Apdu level chaining and buffer if necessary. + if command.class().chain().last_or_only() { + if self.chain_buffer.len() > 0 && !self.is_chaining_response{ + // Merge the chained buffer with the new apdu. + self.chain_buffer.extend_from_slice(command.data()).unwrap(); + let length: u16 = (self.chain_buffer.len() - 7) as u16; + + self.chain_buffer[0] = command.class().into_inner(); + self.chain_buffer[1] = command.instruction().into(); + self.chain_buffer[2] = command.p1; + self.chain_buffer[3] = command.p2; + // chain_buffer[4] == 0 + self.chain_buffer[5] = ((length & 0xff00) >> 8) as u8; + self.chain_buffer[6] = (length & 0xff) as u8; + + info!("merging {} bytes", length).ok(); + let merged_apdu = Command::try_from(&self.chain_buffer).unwrap(); + self.chain_buffer.clear(); + + // Response now needs to be chained. + self.is_chaining_response = true; + + Some(merged_apdu) + } else { + Some(command) + } + } else { + match inferface { + // acknowledge + InterfaceType::Contact => { + self.contact.respond(Response::Data(Default::default()).into_message()) + .expect("Could not respond"); + } + InterfaceType::Contactless => { + self.contactless.respond(Response::Data(Default::default()).into_message()) + .expect("Could not respond"); + } + } + if self.is_chaining_response { + info!("Was chaining the last response, but aborting that now for this new request.").ok(); + self.is_chaining_response = false; + self.chain_buffer.clear(); + } + if self.chain_buffer.len() == 0 { + // Prepend an extended length apdu header. + self.chain_buffer.push(0x00).ok(); // cla + self.chain_buffer.push(0x00).ok(); // ins + self.chain_buffer.push(0x00).ok(); // p1 + self.chain_buffer.push(0x00).ok(); // p2 + self.chain_buffer.push(0x00).ok(); // 0x00 + self.chain_buffer.push(0x00).ok(); // length upper byte + self.chain_buffer.push(0x00).ok(); // length lower byte + } + info!("chaining {} bytes", command.data().len()).ok(); + self.chain_buffer.extend_from_slice(&command.data()).ok(); + None + } + } + + fn parse_apdu(message: &iso7816::command::Data) -> core::result::Result { + + match Command::try_from(message) { + Ok(command) => { + Ok(command) + }, + Err(_error) => { + logging::info!("apdu bad").ok(); + match _error { + FromSliceError::TooShort => { info!("TooShort").ok(); }, + FromSliceError::InvalidClass => { info!("InvalidClass").ok(); }, + FromSliceError::InvalidFirstBodyByteForExtended => { info!("InvalidFirstBodyByteForExtended").ok(); }, + FromSliceError::CanThisReallyOccur => { info!("CanThisReallyOccur").ok(); }, + } + Err(Response::Status(Status::UnspecifiedCheckingError)) + } + } + + } + fn check_for_request(&mut self) -> Option { if !self.busy() { - // prioritize contactless interface - if let Some(apdu) = self.contactless.take_request() { - self.current_interface = InterfaceType::Contactless; - Some(apdu) - } else if let Some(apdu) = self.contact.take_request() { - self.current_interface = InterfaceType::Contact; - Some(apdu) + // Check to see if we have gotten a message, giving priority to contactless. + let (message, interface) = if let Some(message) = self.contactless.take_request() { + (message, InterfaceType::Contactless) + } else if let Some(message) = self.contact.take_request() { + (message, InterfaceType::Contact) } else { - None + return None; + }; + + // Parse the message as an APDU. + match Self::parse_apdu(message.as_ref()) { + Ok(command) => { + // The Apdu may be standalone or part of a chain. + self.buffer_chained_apdu_if_needed(command, interface) + }, + Err(response) => { + // If not a valid APDU, return error and don't pass to app. + match self.current_interface { + InterfaceType::Contactless => + self.contactless.respond(response.into_message()).expect("cant respond"), + InterfaceType::Contact => + self.contact.respond(response.into_message()).expect("cant respond"), + } + None + } } } else { @@ -129,10 +242,10 @@ impl ApduDispatch let response = match request { // have new command APDU Some(apdu) => { - // two cases: SELECT or not SELECT - match Self::aid_to_select(&apdu) { + // three cases: SELECT, GET RESPONSE, or Other + match Self::apdu_type(&apdu) { // SELECT case - Some(aid) => { + ApduType::Select(aid) => { // three cases: // - currently selected app has different AID -> deselect it, to give it // the chance to clear sensitive state @@ -166,8 +279,25 @@ impl ApduDispatch } - // command that is not a SELECT command - None => { + + ApduType::GetResponse => { + // The reader/host is using chaining. On behalf of the app, + // we will return the response in chunks. + if self.chain_buffer.len() == 0 || !self.is_chaining_response { + info!("Unexpected GetResponse").ok(); + Err(Status::UnspecifiedCheckingError) + } else { + // This is a bit unclear, but am returning this + // just to continue the chaining response. + Ok(AppletResponse::Respond(Default::default())) + } + } + + // command that is not a special command -- goes to applet. + ApduType::Other => { + // Invalidate the chain_buffer + self.chain_buffer.clear(); + // if there is a selected app, send it the command if let Some(applet) = Self::find_applet(self.current_aid.as_ref(), applets) { applet.call(apdu) @@ -192,29 +322,62 @@ impl ApduDispatch } }; - match response { + let message = match response { Ok(AppletResponse::Respond(response)) => { - use InterfaceType::*; - match self.current_interface { - Contactless => - self.contactless.respond(Response::Data(response)).expect("cant respond"), - Contact => - self.contact.respond(Response::Data(response)).expect("cant respond"), + + // Consider if we need to reply via chaining method. + // If the reader is using chaining, we will simply + // reply 61XX, and put the response in a buffer. + // It is up to the reader to then send GetResponse + // requests, to which we will return up to 256 bytes at a time. + if self.is_chaining_response { + if self.chain_buffer.len() == 0 { + self.chain_buffer.extend_from_slice(&response).ok(); + info!("Putting response of {} bytes into chain buffer", response.len()).ok(); + } + + // Send 256 bytes max at a time. + let boundary = core::cmp::min(256, self.chain_buffer.len()); + let to_send = &self.chain_buffer[..boundary]; + let remaining = &self.chain_buffer[boundary..]; + let mut message = response::Data::from_slice(to_send).unwrap(); + let return_code = if remaining.len() > 255 { + // XX = 00 indicates more than 255 bytes of data + 0x6100u16 + } else if remaining.len() > 0 { + 0x6100u16 + (remaining.len() as u16) + } else { + // Last chunk has success code + 0x9000 + }; + message.extend_from_slice(&return_code.to_be_bytes()).ok(); + self.chain_buffer = response::Data::from_slice(remaining).unwrap(); + message + + } else { + // Just reply normally + Response::Data(response).into_message() } } - Ok(AppletResponse::Defer) => {} + Ok(AppletResponse::Defer) => { + return; + } Err(status) => { info!("applet error").ok(); - use InterfaceType::*; - match self.current_interface { - Contactless => - self.contactless.respond(Response::Status(status)).expect("cant respond"), - Contact => - self.contact.respond(Response::Status(status)).expect("cant respond"), - } + Response::Status(status).into_message() } + }; + + match self.current_interface { + InterfaceType::Contactless => + self.contactless.respond(message).expect("cant respond"), + InterfaceType::Contact => + self.contact.respond(message).expect("cant respond"), } + + + } } diff --git a/src/types.rs b/src/types.rs index e1f5067..7fd1716 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,9 +1,9 @@ interchange::interchange! { - ContactInterchange: (iso7816::Command, iso7816::Response) + ContactInterchange: (iso7816::command::Data, iso7816::response::Data) } interchange::interchange! { - ContactlessInterchange: (iso7816::Command, iso7816::Response) + ContactlessInterchange: (iso7816::command::Data, iso7816::response::Data) } \ No newline at end of file diff --git a/tests/dispatch.rs b/tests/dispatch.rs new file mode 100644 index 0000000..fe25aaf --- /dev/null +++ b/tests/dispatch.rs @@ -0,0 +1,733 @@ +use apdu_dispatch::applet::{ + Applet, + Aid, + Response as AppletResponse, + Result as AppletResult, +}; +use apdu_dispatch::types::{ + ContactlessInterchange, + ContactInterchange, +}; +use iso7816::{ + Command, + Status, + command, +}; +use interchange::Interchange; + +use heapless::ByteBuf; + +#[macro_use] +extern crate serial_test; + +#[allow(dead_code)] +enum TestInstruction { + Echo = 0x10, + Add = 0x11, + GetData = 0x12, +} + +fn dump_hex(data: &[u8]){ + for i in 0 .. data.len() { + print!("{:02X} ", data[i]); + } + println!(); +} + +pub struct TestApp1 {} + +impl Aid for TestApp1 { + fn aid(&self) -> &'static [u8] { + &[ 0x0Au8, 1, 0, 0, 1] + } + + fn right_truncated_length(&self) -> usize { + 5 + } +} + +// This app echos to Ins code 0x10 +impl Applet for TestApp1 { + + fn select(&mut self, _apdu: Command) -> AppletResult { + Ok(Default::default()) + } + + fn deselect(&mut self) { + } + + fn call (&mut self, apdu: Command) -> AppletResult { + println!("TestApp1::call"); + match apdu.instruction().into() { + 0x10 => { + let mut buf = ByteBuf::new(); + // Just echo 5x 0's for the request apdu header + buf.push(0).unwrap(); + buf.push(0).unwrap(); + buf.push(0).unwrap(); + buf.push(0).unwrap(); + buf.push(0).unwrap(); + buf.extend_from_slice(apdu.data()).unwrap(); + Ok(AppletResponse::Respond(buf)) + } + _ => + Err(Status::InstructionNotSupportedOrInvalid) + } + } + + fn poll (&mut self) -> AppletResult { + Ok(AppletResponse::Defer) + } +} + +pub struct TestApp2 {} + +impl Aid for TestApp2 { + fn aid(&self) -> &'static [u8] { + &[ 0x0Au8, 1, 0, 0, 2] + } + + fn right_truncated_length(&self) -> usize { + 5 + } +} + +// This app echos to Ins code 0x20 +impl Applet for TestApp2 { + + fn select(&mut self, _apdu: Command) -> AppletResult { + Ok(Default::default()) + } + + fn deselect(&mut self) { + } + + fn call (&mut self, apdu: Command) -> AppletResult { + println!("TestApp2::call"); + match apdu.instruction().into() { + 0x20 => { + let mut buf = ByteBuf::new(); + buf.push(0).unwrap(); + buf.push(0).unwrap(); + buf.push(0).unwrap(); + buf.push(0).unwrap(); + buf.push(0).unwrap(); + buf.extend_from_slice(apdu.data()).unwrap(); + Ok(AppletResponse::Respond(buf)) + }, + _ => + Err(Status::InstructionNotSupportedOrInvalid) + } + } + + fn poll (&mut self) -> AppletResult { + panic!("Should not have idle polls for TestApp2!"); + } +} + +pub struct PanicApp {} + +impl Aid for PanicApp{ + fn aid(&self) -> &'static [u8] { + &[ 0x0Au8, 1, 0, 0, 3] + } + + fn right_truncated_length(&self) -> usize { + 5 + } +} + +// This app should never get selected +impl Applet for PanicApp { + + fn select(&mut self, _apdu: Command) -> AppletResult { + panic!("Dont call the panic app"); + } + + fn deselect(&mut self) { + panic!("Dont call the panic app"); + } + + fn call (&mut self, _apdu: Command) -> AppletResult { + panic!("Dont call the panic app"); + } + + fn poll (&mut self) -> AppletResult { + panic!("Dont call the panic app"); + } +} + +fn run_apdus( + apdu_response_pairs: &[&[u8]], +){ + assert!(apdu_response_pairs.len() > 0); + assert!((apdu_response_pairs.len() & 1) == 0); + let (mut contact_requester, contact_responder) = ContactInterchange::claim(0) + .expect("could not setup ccid ApduInterchange"); + + let (_contactless_requester, contactless_responder) = ContactlessInterchange::claim(0) + .expect("could not setup iso14443 ApduInterchange"); + + let mut apdu_dispatch = apdu_dispatch::dispatch::ApduDispatch::new(contact_responder, contactless_responder); + + let mut app0 = PanicApp{}; + let mut app1 = TestApp1{}; + let mut app2 = PanicApp{}; + let mut app3 = TestApp2{}; + let mut app4 = PanicApp{}; + + // for i in 0..apdu_response_pairs.len() { + // print!("- "); + // dump_hex(apdu_response_pairs[i]); + // } + for i in (0..apdu_response_pairs.len()).step_by(2) { + let raw_req = apdu_response_pairs[i]; + let raw_expected_res = apdu_response_pairs[i + 1]; + + // let command = Command::try_from(raw_req).unwrap(); + // let expected_response = Response::Data::from_slice(&raw_res); + + print!("<< "); + dump_hex(&raw_req); + + contact_requester.request(command::Data::from_slice(&raw_req).unwrap()) + .expect("could not deposit command"); + + apdu_dispatch.poll(&mut[&mut app0, &mut app1, &mut app2, &mut app3, &mut app4]); + + let response = contact_requester.take_response().unwrap(); + + print!(">> "); + dump_hex(&response); + + if raw_expected_res != response.as_slice() + { + print!("expected: "); + dump_hex(&raw_expected_res); + print!("got: "); + dump_hex(&response); + panic!("Expected responses do not match"); + } + } +} + +#[test] +#[serial] +fn malformed_apdus(){ + run_apdus( + &[ + // Too short + &[0x00u8,], + &[0x6F, 0x00], + // Too short + &[0x00u8,0x00u8], + &[0x6F, 0x00], + // Too short + &[0x00u8,0x00,0x00,], + &[0x6F, 0x00], + // Wrong length + &[0x00u8,0x00,0x00,0x00,0x10,1,1,1], + &[0x6F, 0x00], + // Extra data + &[0x00u8,0x00,0x00,0x00,0x5,1,1,1,1,1,1,1,1,1,1,1,1,1], + &[0x6F, 0x00], + // Invalid CLA + &[0xFFu8,0x00,0x00,0x00], + &[0x6F, 0x00], + // Invalid extended length + &[0x00u8,0x00,0x00,0x00,0xff,0x00,0x05,1,1,1,1,1], + &[0x6F, 0x00], + // sanity check with Valid APDU with extended length + &[0x00u8,0x00,0x00,0x00,0x00,0x00,0x05,1,1,1,1,1], + &[0x6A, 0x82], + ] + ) +} + + +#[test] +#[serial] +fn select_1(){ + run_apdus( + &[ + // Select + &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x00, 0x01], + // Ok + &[0x90, 0x00], + ] + ) +} + +#[test] +#[serial] +fn select_2(){ + run_apdus( + &[ + // Select + &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x00, 0x02], + // Ok + &[0x90, 0x00], + ] + ) +} + +#[test] +#[serial] +fn select_not_found(){ + run_apdus( + &[ + // Select + &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x01, 0x00], + // Not found + &[0x6A, 0x82], + ] + ) +} + +#[test] +#[serial] +fn echo_1(){ + run_apdus( + &[ + // Select + &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x00, 0x01], + // Ok + &[0x90, 0x00], + + // Echo + &[0x00u8, 0x10, 0x00, 0x00, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05], + // Echo + Ok + &[0x00u8, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x90, 0x00], + ] + ) +} + +#[test] +#[serial] +fn echo_wrong_instruction(){ + run_apdus( + &[ + // Select + &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x00, 0x01], + // Ok + &[0x90, 0x00], + + // Echo + &[0x00u8, 0x20, 0x00, 0x00, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05], + // Wrong Ins + &[0x6d, 0x00], + ] + ) +} + +#[test] +#[serial] +fn echo_2(){ + run_apdus( + &[ + // Select + &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x00, 0x02], + // Ok + &[0x90, 0x00], + + // Echo + &[0x00u8, 0x20, 0x00, 0x00, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05], + // Echo + Ok + &[0x00u8, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x90, 0x00], + ] + ) +} + +#[test] +#[serial] +fn echo_wrong_instruction_2(){ + run_apdus( + &[ + // Select + &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x00, 0x02], + // Ok + &[0x90, 0x00], + + // Echo + &[0x00u8, 0x10, 0x00, 0x00, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05], + // Wrong Ins + &[0x6d, 0x00], + ] + ) +} + +#[test] +#[serial] +fn unsolicited_instruction(){ + run_apdus( + &[ + // Echo + &[0x00u8, 0x10, 0x00, 0x00, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05], + // Not found + &[0x6a, 0x82], + ] + ) +} + +#[test] +#[serial] +fn deselect (){ + run_apdus( + &[ + // Select 1 + &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x00, 0x01], + &[0x90, 0x00], + + // Echo 1 + &[0x00u8, 0x10, 0x00, 0x00, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05], + &[0x00u8, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x90, 0x00], + + // Select 2 + &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x00, 0x02], + &[0x90, 0x00], + + // Echo 1 + &[0x00u8, 0x10, 0x00, 0x00, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05], + &[0x6d, 0x00], + ] + ) +} + +#[test] +#[serial] +fn extended_length_echo (){ + run_apdus( + &[ + // Select 1 + &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x00, 0x01], + &[0x90, 0x00], + + // To be echo'd + &[0x00u8, 0x10, 0x00, 0x00, 0x00, 0x01, 0x23, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1, + ], + // echo Success + &[0x00, 0x00, 0x00, 0x00, 0x00, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1, + 0x90,00 + ] + ] + ) +} + +#[test] +#[serial] +fn chained_apdu_1 (){ + run_apdus( + &[ + // Select 1 + &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x00, 0x01], + &[0x90, 0x00u8], + + // Set chaining bit + &[0x10u8, 0x20, 0x00, 0x00, 0xFF, + /* 1 8 16 24 32 */ + /* 1 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 2 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 3 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 4 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 5 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 6 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 7 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 8 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + ], + &[ 0x90,00 ], + + // Set chaining bit + &[0x10u8, 0x20, 0x00, 0x00, 0xFF, + /* 1 8 16 24 32 */ + /* 1 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 2 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 3 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 4 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 5 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 6 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 7 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 8 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + ], + &[ 0x90,00 ], + + // Send last command + &[0x00u8, 0x10, 0x00, 0x00, 0x20, + /* 1 8 16 24 32 */ + /* 1 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + ], + // Expect 0xff + 0xff + 0x20 + 5 == 547 bytes back + // Echo chunk + remaining + &[ + /* 1 8 16 24 32 */ + /* 1 */ 0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 2 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 3 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 4 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 5 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 6 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 7 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 8 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 0x61,00 // Still 291 bytes left + ], + + // Get Response + &[0x00u8, 0xC0, 0x00, 0x00], + // Echo chunk + remaining + &[ + /* 1 8 16 24 32 */ + /* 1 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 2 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 3 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 4 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 5 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 6 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 7 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 8 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 0x61,0x23 // Still 35 bytes left + ], + + // Get Response + &[0x00u8, 0xC0, 0x00, 0x00], + // Echo chunk + success + &[ + /* 1 8 16 24 32 */ + /* 1 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 2 */ 1,1,1, + 0x90,0x00 + ], + + // Get Response + &[0x00u8, 0xC0, 0x00, 0x00], + // Error + &[ 0x6F,0x00 ], + ] + ) +} + + +#[test] +#[serial] +fn multiple_chained_apdu_1 (){ + run_apdus( + &[ + // Select 1 + &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x00, 0x01], + &[0x90, 0x00u8], + + // Set chaining bit + &[0x10u8, 0x20, 0x00, 0x00, 0xFF, + /* 1 8 16 24 32 */ + /* 1 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 2 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 3 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 4 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 5 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 6 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 7 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 8 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + ], + &[ 0x90,00 ], + + + // Send last command + &[0x00u8, 0x10, 0x00, 0x00, 0x20, + /* 1 8 16 24 32 */ + /* 1 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + ], + // Expect 0xff + 0xff + 0x20 + 5 == 292 bytes back + // Data + remaining bytes + &[ + /* 1 8 16 24 32 */ + /* 1 */ 0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 2 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 3 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 4 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 5 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 6 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 7 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 8 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 0x61,0x24 // Still 36 bytes left + ], + + // Get Response + &[0x00u8, 0xC0, 0x00, 0x00], + // Echo chunk + success + &[ + /* 1 8 16 24 32 */ + /* 1 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 2 */ 1,1,1,1, + 0x90,0x00 + ], + + // Check short commands still work + // Echo 1 + &[0x00u8, 0x10, 0x00, 0x00, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05], + &[0x00u8, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x90, 0x00], + + // Echo 2 + &[0x00u8, 0x20, 0x00, 0x00, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05], + &[0x6d, 0x00], + + // Check chaining command still works + // Set chaining bit + &[0x10u8, 0x20, 0x00, 0x00, 0xFF, + /* 1 8 16 24 32 */ + /* 1 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 2 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 3 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 4 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 5 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 6 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 7 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 8 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + ], + &[ 0x90,00 ], + + + // Send last command + &[0x00u8, 0x10, 0x00, 0x00, 0x20, + /* 1 8 16 24 32 */ + /* 1 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + ], + // Expect 0xff + 0xff + 0x20 + 5 == 292 bytes back + // Data + remaining bytes + &[ + /* 1 8 16 24 32 */ + /* 1 */ 0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 2 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 3 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 4 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 5 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 6 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 7 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 8 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 0x61,0x24 // Still 36 bytes left + ], + + // Get Response + &[0x00u8, 0xC0, 0x00, 0x00], + // Echo chunk + success + &[ + /* 1 8 16 24 32 */ + /* 1 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 2 */ 1,1,1,1, + 0x90,0x00 + ], + ] + ) +} + + +#[test] +#[serial] +fn multiple_chained_apdu_interruption (){ + run_apdus( + &[ + // Select 1 + &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x00, 0x01], + &[0x90, 0x00u8], + + // Set chaining bit + &[0x10u8, 0x20, 0x00, 0x00, 0xFF, + /* 1 8 16 24 32 */ + /* 1 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 2 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 3 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 4 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 5 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 6 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 7 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 8 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + ], + &[ 0x90,00 ], + + + // Send last command + &[0x00u8, 0x10, 0x00, 0x00, 0x20, + /* 1 8 16 24 32 */ + /* 1 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + ], + // Expect 0xff + 0xff + 0x20 + 5 == 292 bytes back + // Data + remaining bytes + &[ + /* 1 8 16 24 32 */ + /* 1 */ 0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 2 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 3 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 4 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 5 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 6 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 7 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 8 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 0x61,0x24 // Still 36 bytes left + ], + + // Just ignore those 36 bytes and do something different + // Echo 1 + &[0x00u8, 0x10, 0x00, 0x00, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05], + &[0x00u8, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x90, 0x00], + + // GetResponse no longer works + &[0x00u8, 0xC0, 0x00, 0x00], + &[ 0x6F,0x00 ], + + // Check that new chaining transaction works + &[0x10u8, 0x20, 0x00, 0x00, 0xFF, + /* 1 8 16 24 32 */ + /* 1 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 2 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 3 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 4 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 5 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 6 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 7 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 8 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + ], + &[ 0x90,00 ], + + + // Send last command + &[0x00u8, 0x10, 0x00, 0x00, 0x20, + /* 1 8 16 24 32 */ + /* 1 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + ], + // Expect 0xff + 0xff + 0x20 + 5 == 292 bytes back + // Data + remaining bytes + &[ + /* 1 8 16 24 32 */ + /* 1 */ 0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 2 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 3 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 4 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 5 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 6 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 7 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + /* 8 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 0x61,0x24 // Still 36 bytes left + ], + + ] + ) +} \ No newline at end of file diff --git a/tests/dispatch_test.rs b/tests/dispatch_test.rs deleted file mode 100644 index d33a0cc..0000000 --- a/tests/dispatch_test.rs +++ /dev/null @@ -1,397 +0,0 @@ -use apdu_dispatch::applet::{ - Applet, - Aid, - Response as AppletResponse, - Result as AppletResult, -}; -use apdu_dispatch::types::{ - ContactlessInterchange, - ContactInterchange, -}; -use iso7816::{ - Command, - Status, -}; -use interchange::Interchange; - -use heapless::ByteBuf; - -#[macro_use] -extern crate serial_test; - -#[allow(dead_code)] -enum TestInstruction { - Echo = 0x10, - Add = 0x11, - GetData = 0x12, -} - -fn dump_hex(data: &[u8]){ - for i in 0 .. data.len() { - print!("{:02X} ", data[i]); - } - println!(); -} - -pub struct TestApp1 {} - -impl Aid for TestApp1 { - fn aid(&self) -> &'static [u8] { - &[ 0x0Au8, 1, 0, 0, 1] - } - - fn right_truncated_length(&self) -> usize { - 5 - } -} - -impl Applet for TestApp1 { - - fn select(&mut self, _apdu: Command) -> AppletResult { - Ok(Default::default()) - } - - fn deselect(&mut self) { - } - - fn call (&mut self, apdu: Command) -> AppletResult { - println!("TestApp1::call"); - match apdu.instruction().into() { - 0x10 => { - let mut buf = ByteBuf::new(); - buf.push(0).unwrap(); - buf.push(0).unwrap(); - buf.push(0).unwrap(); - buf.push(0).unwrap(); - buf.push(0).unwrap(); - buf.extend_from_slice(apdu.data()).unwrap(); - Ok(AppletResponse::Respond(buf)) - } - _ => - Err(Status::InstructionNotSupportedOrInvalid) - } - } - - fn poll (&mut self) -> AppletResult { - panic!("Should not have idle polls here!"); - } -} - -pub struct TestApp2 {} - -impl Aid for TestApp2 { - fn aid(&self) -> &'static [u8] { - &[ 0x0Au8, 1, 0, 0, 2] - } - - fn right_truncated_length(&self) -> usize { - 5 - } -} - -impl Applet for TestApp2 { - - fn select(&mut self, _apdu: Command) -> AppletResult { - Ok(Default::default()) - } - - fn deselect(&mut self) { - } - - fn call (&mut self, apdu: Command) -> AppletResult { - println!("TestApp2::call"); - match apdu.instruction().into() { - 0x20 => { - let mut buf = ByteBuf::new(); - buf.push(0).unwrap(); - buf.push(0).unwrap(); - buf.push(0).unwrap(); - buf.push(0).unwrap(); - buf.push(0).unwrap(); - buf.extend_from_slice(apdu.data()).unwrap(); - Ok(AppletResponse::Respond(buf)) - }, - _ => - Err(Status::InstructionNotSupportedOrInvalid) - } - } - - fn poll (&mut self) -> AppletResult { - panic!("Should not have idle polls here!"); - } -} - -pub struct PanicApp {} - -impl Aid for PanicApp{ - fn aid(&self) -> &'static [u8] { - &[ 0x0Au8, 1, 0, 0, 3] - } - - fn right_truncated_length(&self) -> usize { - 5 - } -} - -impl Applet for PanicApp { - - fn select(&mut self, _apdu: Command) -> AppletResult { - panic!("Dont call the panic app"); - } - - fn deselect(&mut self) { - panic!("Dont call the panic app"); - } - - fn call (&mut self, _apdu: Command) -> AppletResult { - panic!("Dont call the panic app"); - } - - fn poll (&mut self) -> AppletResult { - panic!("Dont call the panic app"); - } -} - -fn run_apdus( - apdu_response_pairs: &[&[u8]], -){ - assert!(apdu_response_pairs.len() > 0); - assert!((apdu_response_pairs.len() & 1) == 0); - let (mut contact_requester, contact_responder) = ContactInterchange::claim(0) - .expect("could not setup ccid ApduInterchange"); - - let (_contactless_requester, contactless_responder) = ContactlessInterchange::claim(0) - .expect("could not setup iso14443 ApduInterchange"); - - let mut apdu_dispatch = apdu_dispatch::dispatch::ApduDispatch::new(contact_responder, contactless_responder); - - let mut app1 = TestApp1{}; - let mut app2 = TestApp2{}; - let mut app3 = PanicApp{}; - - // for i in 0..apdu_response_pairs.len() { - // print!("- "); - // dump_hex(apdu_response_pairs[i]); - // } - for i in (0..apdu_response_pairs.len()).step_by(2) { - let raw_req = apdu_response_pairs[i]; - let raw_expected_res = apdu_response_pairs[i + 1]; - - let command = Command::try_from(raw_req).unwrap(); - // let expected_response = Response::Data::from_slice(&raw_res); - - print!("<< "); - dump_hex(&raw_req); - - contact_requester.request(command).expect("could not deposit command"); - - apdu_dispatch.poll(&mut[&mut app1, &mut app2, &mut app3]); - - let response = contact_requester.take_response().unwrap().into_message(); - - print!(">> "); - dump_hex(&response); - - if raw_expected_res != response.as_slice() - { - print!("expected: "); - dump_hex(&raw_expected_res); - print!("got: "); - dump_hex(&response); - panic!("Expected responses do not match"); - } - } -} - -#[test] -#[serial] -fn test_select_1(){ - run_apdus( - &[ - // Select - &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x00, 0x01], - // Ok - &[0x90, 0x00], - ] - ) -} - -#[test] -#[serial] -fn test_select_2(){ - run_apdus( - &[ - // Select - &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x00, 0x02], - // Ok - &[0x90, 0x00], - ] - ) -} - -#[test] -#[serial] -fn test_select_not_found(){ - run_apdus( - &[ - // Select - &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x01, 0x00], - // Not found - &[0x6A, 0x82], - ] - ) -} - -#[test] -#[serial] -fn test_echo_1(){ - run_apdus( - &[ - // Select - &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x00, 0x01], - // Ok - &[0x90, 0x00], - - // Echo - &[0x00u8, 0x10, 0x00, 0x00, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05], - // Echo + Ok - &[0x00u8, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x90, 0x00], - ] - ) -} - -#[test] -#[serial] -fn test_echo_wrong_instruction(){ - run_apdus( - &[ - // Select - &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x00, 0x01], - // Ok - &[0x90, 0x00], - - // Echo - &[0x00u8, 0x20, 0x00, 0x00, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05], - // Wrong Ins - &[0x6d, 0x00], - ] - ) -} - -#[test] -#[serial] -fn test_echo_2(){ - run_apdus( - &[ - // Select - &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x00, 0x02], - // Ok - &[0x90, 0x00], - - // Echo - &[0x00u8, 0x20, 0x00, 0x00, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05], - // Echo + Ok - &[0x00u8, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x90, 0x00], - ] - ) -} - -#[test] -#[serial] -fn test_echo_wrong_instruction_2(){ - run_apdus( - &[ - // Select - &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x00, 0x02], - // Ok - &[0x90, 0x00], - - // Echo - &[0x00u8, 0x10, 0x00, 0x00, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05], - // Wrong Ins - &[0x6d, 0x00], - ] - ) -} - -#[test] -#[serial] -fn test_unsolicited_instruction(){ - run_apdus( - &[ - // Echo - &[0x00u8, 0x10, 0x00, 0x00, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05], - // Not found - &[0x6a, 0x82], - ] - ) -} - -#[test] -#[serial] -fn test_deselect (){ - run_apdus( - &[ - // Select 1 - &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x00, 0x01], - &[0x90, 0x00], - - // Echo 1 - &[0x00u8, 0x10, 0x00, 0x00, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05], - &[0x00u8, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x90, 0x00], - - // Select 2 - &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x00, 0x02], - &[0x90, 0x00], - - // Echo 1 - &[0x00u8, 0x10, 0x00, 0x00, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05], - &[0x6d, 0x00], - ] - ) -} - -#[test] -#[serial] -fn test_extended_length_echo (){ - run_apdus( - &[ - // Select 1 - &[0x00u8, 0xA4, 0x04, 0x00, 0x05, 0x0A, 0x01, 0x00, 0x00, 0x01], - &[0x90, 0x00], - - // To be echo'd - &[0x00u8, 0x10, 0x00, 0x00, 0x00, 0x01, 0x23, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1, - ], - // echo Success - &[0x00, 0x00, 0x00, 0x00, 0x00, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1, - 0x90,00 - ] - ] - ) -} - - -