use std::fmt;
use super::resource::ResourceId;
use crate::error::{
Error,
Result,
};
pub const SPDU_HEADER_SIZE: usize = 4;
pub const SS_OK: u8 = 0x00;
pub const SS_NOT_ALLOCATED: u8 = 0xF0;
pub const SS_LOWER_VERSION: u8 = 0xF2;
pub const SS_BUSY: u8 = 0xF3;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct SpduTag(u8);
impl SpduTag {
pub const SESSION_NUMBER: Self = Self(0x90);
pub const OPEN_SESSION_REQUEST: Self = Self(0x91);
pub const OPEN_SESSION_RESPONSE: Self = Self(0x92);
pub const CREATE_SESSION: Self = Self(0x93);
pub const CREATE_SESSION_RESPONSE: Self = Self(0x94);
pub const CLOSE_SESSION_REQUEST: Self = Self(0x95);
pub const CLOSE_SESSION_RESPONSE: Self = Self(0x96);
pub const fn new(raw: u8) -> Self {
Self(raw)
}
pub const fn raw(self) -> u8 {
self.0
}
}
impl fmt::Debug for SpduTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match *self {
Self::SESSION_NUMBER => "SESSION_NUMBER",
Self::OPEN_SESSION_REQUEST => "OPEN_SESSION_REQUEST",
Self::OPEN_SESSION_RESPONSE => "OPEN_SESSION_RESPONSE",
Self::CREATE_SESSION => "CREATE_SESSION",
Self::CREATE_SESSION_RESPONSE => "CREATE_SESSION_RESPONSE",
Self::CLOSE_SESSION_REQUEST => "CLOSE_SESSION_REQUEST",
Self::CLOSE_SESSION_RESPONSE => "CLOSE_SESSION_RESPONSE",
_ => return write!(f, "SpduTag(0x{:02X})", self.0),
};
write!(f, "SpduTag({})", name)
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Spdu<'a> {
SessionNumber { session_id: u16, apdu: &'a [u8] },
OpenSessionRequest { resource_id: ResourceId },
CreateSessionResponse {
status: u8,
resource_id: ResourceId,
session_id: u16,
},
CloseSessionRequest { session_id: u16 },
CloseSessionResponse { status: u8, session_id: u16 },
}
fn read_u16(data: &[u8]) -> u16 {
(u16::from(data[0]) << 8) | u16::from(data[1])
}
fn read_u32(data: &[u8]) -> u32 {
(u32::from(data[0]) << 24)
| (u32::from(data[1]) << 16)
| (u32::from(data[2]) << 8)
| u32::from(data[3])
}
pub fn parse(data: &[u8]) -> Result<Spdu<'_>> {
let tag = match data.first() {
Some(&tag) => SpduTag::new(tag),
None => {
return Err(Error::InvalidData("ca spdu is empty".to_owned()));
}
};
let check = |size: usize, exact: bool| -> Result<()> {
let size_ok = if exact {
data.len() == size
} else {
data.len() >= size
};
if size_ok && data[1] == (size - 2) as u8 {
Ok(())
} else {
Err(Error::InvalidData(format!(
"ca spdu invalid size for tag {:?}",
tag
)))
}
};
match tag {
SpduTag::SESSION_NUMBER => {
check(SPDU_HEADER_SIZE, false)?;
Ok(Spdu::SessionNumber {
session_id: read_u16(&data[2 .. 4]),
apdu: &data[SPDU_HEADER_SIZE ..],
})
}
SpduTag::OPEN_SESSION_REQUEST => {
check(6, true)?;
Ok(Spdu::OpenSessionRequest {
resource_id: ResourceId::new(read_u32(&data[2 .. 6])),
})
}
SpduTag::CREATE_SESSION_RESPONSE => {
check(9, true)?;
Ok(Spdu::CreateSessionResponse {
status: data[2],
resource_id: ResourceId::new(read_u32(&data[3 .. 7])),
session_id: read_u16(&data[7 .. 9]),
})
}
SpduTag::CLOSE_SESSION_REQUEST => {
check(4, true)?;
Ok(Spdu::CloseSessionRequest {
session_id: read_u16(&data[2 .. 4]),
})
}
SpduTag::CLOSE_SESSION_RESPONSE => {
check(5, true)?;
Ok(Spdu::CloseSessionResponse {
status: data[2],
session_id: read_u16(&data[3 .. 5]),
})
}
tag => Err(Error::InvalidData(format!("ca spdu unknown tag {:?}", tag))),
}
}
pub fn build_session_number(session_id: u16) -> Vec<u8> {
vec![
SpduTag::SESSION_NUMBER.raw(),
2,
(session_id >> 8) as u8,
session_id as u8,
]
}
pub fn build_open_session_response(
status: u8,
resource_id: ResourceId,
session_id: u16,
) -> Vec<u8> {
let resource_id = resource_id.raw();
vec![
SpduTag::OPEN_SESSION_RESPONSE.raw(),
7,
status,
(resource_id >> 24) as u8,
(resource_id >> 16) as u8,
(resource_id >> 8) as u8,
resource_id as u8,
(session_id >> 8) as u8,
session_id as u8,
]
}
pub fn build_close_session_request(session_id: u16) -> Vec<u8> {
vec![
SpduTag::CLOSE_SESSION_REQUEST.raw(),
2,
(session_id >> 8) as u8,
session_id as u8,
]
}
pub fn build_close_session_response(status: u8, session_id: u16) -> Vec<u8> {
vec![
SpduTag::CLOSE_SESSION_RESPONSE.raw(),
3,
status,
(session_id >> 8) as u8,
session_id as u8,
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_session_number() {
assert_eq!(build_session_number(1), vec![0x90, 0x02, 0x00, 0x01]);
assert_eq!(build_session_number(0x1234), vec![0x90, 0x02, 0x12, 0x34]);
}
#[test]
fn test_build_open_session_response() {
assert_eq!(
build_open_session_response(SS_OK, ResourceId::RESOURCE_MANAGER, 1),
vec![0x92, 0x07, 0x00, 0x00, 0x01, 0x00, 0x41, 0x00, 0x01]
);
assert_eq!(
build_open_session_response(SS_NOT_ALLOCATED, ResourceId::new(0x00FF_0041), 0),
vec![0x92, 0x07, 0xF0, 0x00, 0xFF, 0x00, 0x41, 0x00, 0x00]
);
}
#[test]
fn test_build_close_session() {
assert_eq!(build_close_session_request(3), vec![0x95, 0x02, 0x00, 0x03]);
assert_eq!(
build_close_session_response(SS_OK, 3),
vec![0x96, 0x03, 0x00, 0x00, 0x03]
);
}
#[test]
fn test_parse_session_number() {
let data = [0x90, 0x02, 0x00, 0x01, 0x9F, 0x80, 0x10, 0x00];
assert_eq!(
parse(&data).unwrap(),
Spdu::SessionNumber {
session_id: 1,
apdu: &[0x9F, 0x80, 0x10, 0x00],
}
);
let data = [0x90, 0x02, 0x00, 0x01];
assert!(parse(&data).is_ok());
assert!(parse(&[0x90, 0x03, 0x00, 0x01]).is_err());
}
#[test]
fn test_parse_open_session_request() {
let data = [0x91, 0x04, 0x00, 0x01, 0x00, 0x41];
assert_eq!(
parse(&data).unwrap(),
Spdu::OpenSessionRequest {
resource_id: ResourceId::RESOURCE_MANAGER,
}
);
assert!(parse(&[0x91, 0x04, 0x00, 0x01, 0x00]).is_err());
assert!(parse(&[0x91, 0x04, 0x00, 0x01, 0x00, 0x41, 0x00]).is_err());
assert!(parse(&[0x91, 0x05, 0x00, 0x01, 0x00, 0x41]).is_err());
}
#[test]
fn test_parse_session_responses() {
let data = [0x94, 0x07, 0x00, 0x00, 0x40, 0x00, 0x41, 0x00, 0x02];
assert_eq!(
parse(&data).unwrap(),
Spdu::CreateSessionResponse {
status: SS_OK,
resource_id: ResourceId::MMI,
session_id: 2,
}
);
assert_eq!(
parse(&[0x95, 0x02, 0x00, 0x03]).unwrap(),
Spdu::CloseSessionRequest { session_id: 3 }
);
assert_eq!(
parse(&[0x96, 0x03, 0x00, 0x00, 0x03]).unwrap(),
Spdu::CloseSessionResponse {
status: SS_OK,
session_id: 3,
}
);
assert!(parse(&[0x94, 0x07, 0x00]).is_err());
assert!(parse(&[0x95, 0x02, 0x00]).is_err());
assert!(parse(&[0x96, 0x03, 0x00, 0x00]).is_err());
}
#[test]
fn test_parse_rejects_unknown_and_empty() {
assert!(parse(&[]).is_err());
assert!(parse(&[0x93, 0x06, 0, 0, 0, 0, 0, 0]).is_err());
assert!(parse(&[0xFF, 0x00]).is_err());
}
#[test]
fn test_tag_debug() {
assert_eq!(
format!("{:?}", SpduTag::SESSION_NUMBER),
"SpduTag(SESSION_NUMBER)"
);
assert_eq!(format!("{:?}", SpduTag::new(0xFF)), "SpduTag(0xFF)");
}
}