mirror of
https://github.com/trussed-dev/apdu-dispatch.git
synced 2026-06-20 04:16:15 -07:00
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
This commit is contained in:
committed by
Nicolas Stalder
parent
76d9f28692
commit
08d3631ef2
@@ -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}')
|
||||
```
|
||||
|
||||
+194
-31
@@ -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<ContactInterchange>,
|
||||
contactless: Responder<ContactlessInterchange>,
|
||||
current_interface: InterfaceType,
|
||||
|
||||
chain_buffer: response::Data,
|
||||
is_chaining_response: bool,
|
||||
}
|
||||
|
||||
impl ApduDispatch
|
||||
{
|
||||
fn aid_to_select(apdu: &Command) -> Option<Aid> {
|
||||
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<Command>{
|
||||
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<Command,Response> {
|
||||
|
||||
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<Command> {
|
||||
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"),
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
]
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user