use std::fmt;
use super::asn1;
use crate::error::{
Error,
Result,
};
pub const APDU_TAG_SIZE: usize = 3;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct ApduTag(u32);
impl ApduTag {
pub const PROFILE_ENQ: Self = Self(0x9F8010);
pub const PROFILE: Self = Self(0x9F8011);
pub const PROFILE_CHANGE: Self = Self(0x9F8012);
pub const APPLICATION_INFO_ENQ: Self = Self(0x9F8020);
pub const APPLICATION_INFO: Self = Self(0x9F8021);
pub const ENTER_MENU: Self = Self(0x9F8022);
pub const CA_INFO_ENQ: Self = Self(0x9F8030);
pub const CA_INFO: Self = Self(0x9F8031);
pub const CA_PMT: Self = Self(0x9F8032);
pub const CA_PMT_REPLY: Self = Self(0x9F8033);
pub const CA_UPDATE: Self = Self(0x9F8034);
pub const TUNE: Self = Self(0x9F8400);
pub const REPLACE: Self = Self(0x9F8401);
pub const CLEAR_REPLACE: Self = Self(0x9F8402);
pub const ASK_RELEASE: Self = Self(0x9F8403);
pub const DATE_TIME_ENQ: Self = Self(0x9F8440);
pub const DATE_TIME: Self = Self(0x9F8441);
pub const CLOSE_MMI: Self = Self(0x9F8800);
pub const DISPLAY_CONTROL: Self = Self(0x9F8801);
pub const DISPLAY_REPLY: Self = Self(0x9F8802);
pub const TEXT_LAST: Self = Self(0x9F8803);
pub const TEXT_MORE: Self = Self(0x9F8804);
pub const ENQ: Self = Self(0x9F8807);
pub const ANSW: Self = Self(0x9F8808);
pub const MENU_LAST: Self = Self(0x9F8809);
pub const MENU_MORE: Self = Self(0x9F880A);
pub const MENU_ANSW: Self = Self(0x9F880B);
pub const LIST_LAST: Self = Self(0x9F880C);
pub const LIST_MORE: Self = Self(0x9F880D);
pub const fn new(raw: u32) -> Self {
Self(raw)
}
pub const fn raw(self) -> u32 {
self.0
}
}
impl fmt::Debug for ApduTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match *self {
Self::PROFILE_ENQ => "PROFILE_ENQ",
Self::PROFILE => "PROFILE",
Self::PROFILE_CHANGE => "PROFILE_CHANGE",
Self::APPLICATION_INFO_ENQ => "APPLICATION_INFO_ENQ",
Self::APPLICATION_INFO => "APPLICATION_INFO",
Self::ENTER_MENU => "ENTER_MENU",
Self::CA_INFO_ENQ => "CA_INFO_ENQ",
Self::CA_INFO => "CA_INFO",
Self::CA_PMT => "CA_PMT",
Self::CA_PMT_REPLY => "CA_PMT_REPLY",
Self::CA_UPDATE => "CA_UPDATE",
Self::TUNE => "TUNE",
Self::REPLACE => "REPLACE",
Self::CLEAR_REPLACE => "CLEAR_REPLACE",
Self::ASK_RELEASE => "ASK_RELEASE",
Self::DATE_TIME_ENQ => "DATE_TIME_ENQ",
Self::DATE_TIME => "DATE_TIME",
Self::CLOSE_MMI => "CLOSE_MMI",
Self::DISPLAY_CONTROL => "DISPLAY_CONTROL",
Self::DISPLAY_REPLY => "DISPLAY_REPLY",
Self::TEXT_LAST => "TEXT_LAST",
Self::TEXT_MORE => "TEXT_MORE",
Self::ENQ => "ENQ",
Self::ANSW => "ANSW",
Self::MENU_LAST => "MENU_LAST",
Self::MENU_MORE => "MENU_MORE",
Self::MENU_ANSW => "MENU_ANSW",
Self::LIST_LAST => "LIST_LAST",
Self::LIST_MORE => "LIST_MORE",
_ => return write!(f, "ApduTag(0x{:06X})", self.0),
};
write!(f, "ApduTag({})", name)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Apdu<'a> {
pub tag: ApduTag,
pub body: &'a [u8],
}
pub fn parse_at(data: &[u8]) -> Result<(Apdu<'_>, usize)> {
let tag_bytes = data
.get(.. APDU_TAG_SIZE)
.ok_or_else(|| Error::InvalidData("ca apdu is too short".to_owned()))?;
let tag = ApduTag::new(
(u32::from(tag_bytes[0]) << 16) | (u32::from(tag_bytes[1]) << 8) | u32::from(tag_bytes[2]),
);
let (length, consumed) = asn1::decode(&data[APDU_TAG_SIZE ..])?;
let body_start = APDU_TAG_SIZE + consumed;
let body_end = body_start + usize::from(length);
let body = data
.get(body_start .. body_end)
.ok_or_else(|| Error::InvalidData(format!("ca apdu {:?} body is truncated", tag)))?;
Ok((Apdu { tag, body }, body_end))
}
pub struct ApduIter<'a> {
data: &'a [u8],
}
pub fn iter(data: &[u8]) -> ApduIter<'_> {
ApduIter { data }
}
impl<'a> Iterator for ApduIter<'a> {
type Item = Result<Apdu<'a>>;
fn next(&mut self) -> Option<Self::Item> {
if self.data.is_empty() {
return None;
}
match parse_at(self.data) {
Ok((apdu, size)) => {
self.data = &self.data[size ..];
Some(Ok(apdu))
}
Err(e) => {
self.data = &[];
Some(Err(e))
}
}
}
}
pub fn build(out: &mut Vec<u8>, tag: ApduTag, body: &[u8]) {
let tag = tag.raw();
out.push((tag >> 16) as u8);
out.push((tag >> 8) as u8);
out.push(tag as u8);
asn1::encode(body.len() as u16, out);
out.extend_from_slice(body);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build() {
let mut out = Vec::new();
build(&mut out, ApduTag::PROFILE_ENQ, &[]);
assert_eq!(out, vec![0x9F, 0x80, 0x10, 0x00]);
let mut out = Vec::new();
build(&mut out, ApduTag::MENU_ANSW, &[0x01]);
assert_eq!(out, vec![0x9F, 0x88, 0x0B, 0x01, 0x01]);
let mut out = Vec::new();
build(&mut out, ApduTag::PROFILE, &[0x42; 128]);
assert_eq!(&out[.. 5], &[0x9F, 0x80, 0x11, 0x81, 0x80]);
assert_eq!(out.len(), 5 + 128);
}
#[test]
fn test_parse_roundtrip() {
let mut data = Vec::new();
build(
&mut data,
ApduTag::APPLICATION_INFO,
&[0x01, 0x06, 0x00, 0x12, 0x34],
);
let (apdu, size) = parse_at(&data).unwrap();
assert_eq!(apdu.tag, ApduTag::APPLICATION_INFO);
assert_eq!(apdu.body, &[0x01, 0x06, 0x00, 0x12, 0x34]);
assert_eq!(size, data.len());
}
#[test]
fn test_parse_ignores_trailing_bytes() {
let (apdu, size) = parse_at(&[0x9F, 0x80, 0x10, 0x01, 0xAA, 0xBB, 0xCC]).unwrap();
assert_eq!(apdu.tag, ApduTag::PROFILE_ENQ);
assert_eq!(apdu.body, &[0xAA]);
assert_eq!(size, 5);
}
#[test]
fn test_parse_errors() {
assert!(parse_at(&[0x9F, 0x80]).is_err());
assert!(parse_at(&[0x9F, 0x80, 0x10]).is_err());
assert!(parse_at(&[0x9F, 0x80, 0x10, 0x02, 0xAA]).is_err());
assert!(parse_at(&[0x9F, 0x80, 0x10, 0x82, 0x01]).is_err());
}
#[test]
fn test_iter() {
let mut data = Vec::new();
build(&mut data, ApduTag::PROFILE_ENQ, &[]);
build(&mut data, ApduTag::MENU_ANSW, &[0x01]);
build(&mut data, ApduTag::PROFILE, &[0x42; 128]);
let mut iter = iter(&data);
assert_eq!(
iter.next().unwrap().unwrap(),
Apdu {
tag: ApduTag::PROFILE_ENQ,
body: &[],
}
);
assert_eq!(
iter.next().unwrap().unwrap(),
Apdu {
tag: ApduTag::MENU_ANSW,
body: &[0x01],
}
);
assert_eq!(
iter.next().unwrap().unwrap(),
Apdu {
tag: ApduTag::PROFILE,
body: &[0x42; 128],
}
);
assert!(iter.next().is_none());
}
#[test]
fn test_iter_empty() {
assert!(iter(&[]).next().is_none());
}
#[test]
fn test_iter_stops_on_error() {
let mut data = Vec::new();
build(&mut data, ApduTag::PROFILE_ENQ, &[]);
data.extend_from_slice(&[0x9F, 0x80]);
let mut iter = iter(&data);
assert!(iter.next().unwrap().is_ok());
assert!(iter.next().unwrap().is_err());
assert!(iter.next().is_none());
}
#[test]
fn test_tag_debug() {
assert_eq!(
format!("{:?}", ApduTag::PROFILE_ENQ),
"ApduTag(PROFILE_ENQ)"
);
assert_eq!(ApduTag::CA_INFO_ENQ.raw(), 0x9F8030);
assert_eq!(ApduTag::CA_INFO.raw(), 0x9F8031);
assert_eq!(format!("{:?}", ApduTag::CA_INFO), "ApduTag(CA_INFO)");
assert_eq!(format!("{:?}", ApduTag::new(0x9F8888)), "ApduTag(0x9F8888)");
}
}