mirror of
https://gitlab.com/fabinfra/fabaccess/nfc_rs.git
synced 2025-03-12 14:51:50 +01:00
305 lines
9.6 KiB
Rust
305 lines
9.6 KiB
Rust
use std::fmt;
|
|
use std::fmt::Formatter;
|
|
use std::convert::TryFrom;
|
|
use crate::error::{Result, Error};
|
|
|
|
#[repr(u8)]
|
|
#[derive(Eq, PartialEq, Hash, Debug)]
|
|
pub enum IsoCase {
|
|
/// <summary>No command data. No response data.</summary>
|
|
/// <remarks>
|
|
/// <list type="bullet">
|
|
/// <item><description>lc is valued to 0.</description></item>
|
|
/// <item><description>le is valued to 0.</description></item>
|
|
/// <item><description>No data byte is present.</description></item>
|
|
/// </list>
|
|
/// </remarks>
|
|
Case1 = 0,
|
|
|
|
/// <summary>No command data. Expected response data.</summary>
|
|
/// <remarks>
|
|
/// <list type="bullet">
|
|
/// <item><description>lc is valued to 0.</description></item>
|
|
/// <item><description>le is valued from 1 to 256.</description></item>
|
|
/// <item><description>No data byte is present.</description></item>
|
|
/// </list>
|
|
/// </remarks>
|
|
Case2Short = 1,
|
|
|
|
/// <summary>Command data. No response data.</summary>
|
|
/// <remarks>
|
|
/// <list type="bullet">
|
|
/// <item><description>lc is valued from 1 to 255.</description></item>
|
|
/// <item><description>le is valued to 0.</description></item>
|
|
/// <item><description>lc data bytes are present.</description></item>
|
|
/// </list>
|
|
/// </remarks>
|
|
Case3Short = 2,
|
|
|
|
/// <summary>Command data. Expected response data.</summary>
|
|
/// <remarks>
|
|
/// <list type="bullet">
|
|
/// <item><description>lc is valued from 1 to 255.</description></item>
|
|
/// <item><description>le is valued from 1 to 256.</description></item>
|
|
/// <item><description>lc data bytes are present.</description></item>
|
|
/// </list>
|
|
/// </remarks>
|
|
Case4Short = 3,
|
|
|
|
/// <summary>No command data. Expected response data.</summary>
|
|
/// <remarks>
|
|
/// <list type="bullet">
|
|
/// <item><description>lc is valued to 0.</description></item>
|
|
/// <item><description>le is valued from 1 to 65536.</description></item>
|
|
/// <item><description>No data byte is present.</description></item>
|
|
/// </list>
|
|
/// </remarks>
|
|
Case2Extended = 4,
|
|
|
|
/// <summary>Command data. No response data.</summary>
|
|
/// <remarks>
|
|
/// <list type="bullet">
|
|
/// <item><description>lc is valued from 1 to 65536.</description></item>
|
|
/// <item><description>le is valued to 0.</description></item>
|
|
/// <item><description>lc data bytes are present.</description></item>
|
|
/// </list>
|
|
/// </remarks>
|
|
Case3Extended = 5,
|
|
|
|
/// <summary>Command data. Expected response data.</summary>
|
|
/// <remarks>
|
|
/// <list type="bullet">
|
|
/// <item><description>lc is valued from 1 to 65535.</description></item>
|
|
/// <item><description>le is valued from 1 to 65536.</description></item>
|
|
/// <item><description>lc data bytes are present.</description></item>
|
|
/// </list>
|
|
/// </remarks>
|
|
Case4Extended = 6,
|
|
}
|
|
|
|
impl Default for IsoCase {
|
|
fn default() -> Self { IsoCase::Case1 }
|
|
}
|
|
|
|
#[repr(u8)]
|
|
#[derive(Eq, PartialEq, Hash, Debug)]
|
|
pub enum SCardProtocol {
|
|
/// <summary>
|
|
/// protocol not defined.
|
|
/// </summary>
|
|
Unset = 0x0000,
|
|
|
|
/// <summary>T=0 active protocol.</summary>
|
|
T0 = 0x0001,
|
|
|
|
/// <summary>T=1 active protocol.</summary>
|
|
T1 = 0x0002,
|
|
|
|
/// <summary>Raw active protocol. Use with memory type cards.</summary>
|
|
Raw = 0x0004,
|
|
|
|
/// <summary>T=15 protocol.</summary>
|
|
T15 = 0x0008,
|
|
|
|
/// <summary>(<see cref="SCardProtocol.T0" /> | <see cref="SCardProtocol.T1" />). IFD (Interface device) determines protocol.</summary>
|
|
Any = (0x0001 | 0x0002),
|
|
}
|
|
|
|
impl Default for SCardProtocol {
|
|
fn default() -> Self { SCardProtocol::Unset }
|
|
}
|
|
|
|
#[derive(Eq, PartialEq, Hash, Debug, Default)]
|
|
pub struct APDUCommand {
|
|
pub case: IsoCase,
|
|
pub protocol: SCardProtocol,
|
|
pub cla: u8,
|
|
pub ins: u8,
|
|
pub p1: u8,
|
|
pub p2: u8,
|
|
pub data: Option<Vec<u8>>,
|
|
pub lc: i32,
|
|
pub le: i32,
|
|
}
|
|
|
|
impl TryFrom<APDUCommand> for Vec<u8> {
|
|
type Error = Error;
|
|
|
|
fn try_from(cmd: APDUCommand) -> Result<Self> {
|
|
let mut v: Vec<u8> = vec![];
|
|
//build header
|
|
v.push(cmd.cla);
|
|
v.push(cmd.ins);
|
|
v.push(cmd.p1);
|
|
v.push(cmd.p2);
|
|
|
|
//build body according to case
|
|
match cmd.case {
|
|
IsoCase::Case1 => {
|
|
if cmd.protocol == SCardProtocol::T0 {
|
|
v.push(0x00);
|
|
}
|
|
}
|
|
IsoCase::Case2Short => {
|
|
v.push(cmd.le as u8);
|
|
}
|
|
IsoCase::Case3Short => {
|
|
v.push(cmd.lc as u8);
|
|
v.extend(cmd.data.unwrap());
|
|
}
|
|
IsoCase::Case4Short => {
|
|
v.push(cmd.lc as u8);
|
|
v.extend(cmd.data.unwrap());
|
|
if cmd.protocol == SCardProtocol::T0 {
|
|
v.push(0x00);
|
|
} else {
|
|
v.push(cmd.le as u8);
|
|
}
|
|
}
|
|
_ => {
|
|
return Err(Error::InvalidIsoCase)
|
|
}
|
|
}
|
|
Ok(v)
|
|
}
|
|
}
|
|
//FIXME: Do we want pub fields or constructor?
|
|
// We want pub fields! + constructor?
|
|
|
|
// impl APDUCommand {
|
|
// pub fn new(case: IsoCase) -> Self {
|
|
// APDUCommand { case, protocol: (), cla: (), ins: (), p1: (), p2: (), data: (), lc: (), le: () }
|
|
// }
|
|
// }
|
|
|
|
impl fmt::Display for APDUCommand {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
match self.case {
|
|
IsoCase::Case1 => {
|
|
write!(f, "(CASE: 1) cla: {:#X} | ins: {:#X} | p1: {:#X} | p2: {:#X}", self.cla, self.ins, self.p1, self.p2)
|
|
}
|
|
IsoCase::Case2Short | IsoCase::Case2Extended => {
|
|
write!(f, "(CASE: 2) cla: {:#X} | ins: {:#X} | p1: {:#X} | p2: {:#X} | LE: {:#X}", self.cla, self.ins, self.p1, self.p2, self.le)
|
|
}
|
|
IsoCase::Case3Short | IsoCase::Case3Extended => {
|
|
write!(f, "(CASE: 3) cla: {:#X} | ins: {:#X} | p1: {:#X} | p2: {:#X} | LC: {:#X} | data: [ ", self.cla, self.ins, self.p1, self.p2, self.lc)?;
|
|
for b in self.data.as_ref().unwrap() {
|
|
write!(f, "{:#X} ", b)?;
|
|
}
|
|
write!(f, "]")
|
|
}
|
|
IsoCase::Case4Short | IsoCase::Case4Extended => {
|
|
write!(f, "(CASE: 4) cla: {:#X} | ins: {:#X} | p1: {:#X} | p2: {:#X} | LC: {:#X} | data: [ ", self.cla, self.ins, self.p1, self.p2, self.lc)?;
|
|
for b in self.data.as_ref().unwrap() {
|
|
write!(f, "{:#X} ", b)?;
|
|
}
|
|
write!(f, "] | LE: {:#X}", self.le)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::iso7816_4::apducommand::{APDUCommand, SCardProtocol, IsoCase};
|
|
|
|
#[test]
|
|
fn compare() {
|
|
let command1 = APDUCommand{
|
|
case: IsoCase::Case4Short,
|
|
protocol: SCardProtocol::Unset,
|
|
cla: 0x90,
|
|
ins: 0xAA,
|
|
data: Some(vec![0x01, 0x02, 0x03]),
|
|
..Default::default()
|
|
};
|
|
|
|
let command2 = APDUCommand{
|
|
case: IsoCase::Case4Short,
|
|
protocol: SCardProtocol::Unset,
|
|
cla: 0x90,
|
|
ins: 0xAA,
|
|
data: Some(vec![0x01, 0x02, 0x03]),
|
|
..Default::default()
|
|
};
|
|
|
|
assert_eq!(command1, command2);
|
|
}
|
|
|
|
#[test]
|
|
fn compare_diff() {
|
|
let command1 = APDUCommand{
|
|
case: IsoCase::Case4Short,
|
|
protocol: SCardProtocol::Unset,
|
|
cla: 0x90,
|
|
ins: 0xAA,
|
|
data: Some(vec![0x01, 0x02, 0x03]),
|
|
..Default::default()
|
|
};
|
|
|
|
let command2 = APDUCommand{
|
|
case: IsoCase::Case4Short,
|
|
protocol: SCardProtocol::Unset,
|
|
cla: 0x90,
|
|
ins: 0x1A,
|
|
data: Some(vec![0x01, 0x02, 0x03]),
|
|
..Default::default()
|
|
};
|
|
|
|
assert_ne!(command1, command2);
|
|
}
|
|
|
|
#[test]
|
|
fn to_string_case1() {
|
|
let command1 = APDUCommand{
|
|
case: IsoCase::Case1,
|
|
cla: 0x90,
|
|
ins: 0x1A,
|
|
..Default::default()
|
|
};
|
|
|
|
println!("{}", command1.to_string());
|
|
assert_eq!("(CASE: 1) cla: 0x90 | ins: 0x1A | p1: 0x0 | p2: 0x0", command1.to_string());
|
|
}
|
|
|
|
#[test]
|
|
fn to_string_case2() {
|
|
let command1 = APDUCommand{
|
|
case: IsoCase::Case2Short,
|
|
cla: 0x90,
|
|
ins: 0x1A,
|
|
..Default::default()
|
|
};
|
|
|
|
println!("{}", command1.to_string());
|
|
assert_eq!("(CASE: 2) cla: 0x90 | ins: 0x1A | p1: 0x0 | p2: 0x0 | LE: 0x0", command1.to_string());
|
|
}
|
|
|
|
#[test]
|
|
fn to_string_case3() {
|
|
let command1 = APDUCommand{
|
|
case: IsoCase::Case3Short,
|
|
cla: 0x90,
|
|
ins: 0x1A,
|
|
data: Some(vec![0x01, 0x02, 0x03]),
|
|
..Default::default()
|
|
};
|
|
|
|
println!("{}", command1.to_string());
|
|
assert_eq!("(CASE: 3) cla: 0x90 | ins: 0x1A | p1: 0x0 | p2: 0x0 | LC: 0x0 | data: [ 0x1 0x2 0x3 ]", command1.to_string());
|
|
}
|
|
|
|
#[test]
|
|
fn to_string_case4() {
|
|
let command1 = APDUCommand{
|
|
case: IsoCase::Case4Short,
|
|
cla: 0x90,
|
|
ins: 0x1A,
|
|
data: Some(vec![0x01, 0x02, 0x03]),
|
|
..Default::default()
|
|
};
|
|
|
|
println!("{}", command1.to_string());
|
|
assert_eq!("(CASE: 4) cla: 0x90 | ins: 0x1A | p1: 0x0 | p2: 0x0 | LC: 0x0 | data: [ 0x1 0x2 0x3 ] | LE: 0x0", command1.to_string());
|
|
}
|
|
} |