mirror of
https://github.com/trussed-dev/iso7816.git
synced 2026-06-20 04:16:14 -07:00
Move to heapless 7 = const generics
This commit is contained in:
+5
-3
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "iso7816"
|
||||
version = "0.1.0-alpha.0"
|
||||
version = "0.1.0-alpha.1"
|
||||
authors = ["Nicolas Stalder <n@stalder.io>"]
|
||||
edition = "2018"
|
||||
repository = "https://github.com/ycrypto/iso7816"
|
||||
@@ -12,5 +12,7 @@ documentation = "https://docs.rs/iso7816"
|
||||
|
||||
[dependencies]
|
||||
delog = "0.1.2"
|
||||
heapless = "0.6"
|
||||
heapless-bytes = "0.2.0"
|
||||
heapless = "0.7"
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.3.1"
|
||||
|
||||
+87
-52
@@ -1,7 +1,4 @@
|
||||
// use core::convert::TryInto;
|
||||
|
||||
// // 7816-4, 8.2.1.2
|
||||
// pub type Aid = crate::Bytes<heapless::consts::U16>;
|
||||
// use crate::{Command, Interface, Response, Result};
|
||||
|
||||
/// Constant panicking assertion.
|
||||
// TODO(tarcieri): use const panic when stable.
|
||||
@@ -16,7 +13,9 @@ macro_rules! const_assert {
|
||||
/// ISO 7816-4 Application identifier
|
||||
pub struct Aid {
|
||||
/// Array containing the AID (padded with zeros)
|
||||
bytes: [u8; Self::max_len()],
|
||||
///
|
||||
/// Does not use heapless as its Vec is not `Copy`.
|
||||
bytes: [u8; Self::MAX_LEN],
|
||||
|
||||
/// Length in bytes
|
||||
len: u8,
|
||||
@@ -25,6 +24,20 @@ pub struct Aid {
|
||||
truncated_len: u8,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, Hash, PartialEq)]
|
||||
pub enum Category {
|
||||
/// International registration of application providers according to ISO/IEC 7816-5
|
||||
International,
|
||||
/// National (ISO 3166-1) registration of application providers according to ISO/IEC 7816-5
|
||||
National,
|
||||
/// Identification of a standard by an object identifier according to ISO/IEC 8825-1
|
||||
Standard,
|
||||
/// No registration of application providers
|
||||
Proprietary,
|
||||
/// 0-9 are reserved for backwards compatibility, B-C are RFU.
|
||||
Other,
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for Aid {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
if self.len <= self.truncated_len {
|
||||
@@ -40,13 +53,22 @@ impl core::fmt::Debug for Aid {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub const fn Aid(aid: &[u8], truncated_len: usize) -> Aid {
|
||||
Aid::new(aid, truncated_len)
|
||||
}
|
||||
/// According to ISO 7816-4, "Application selection using AID as DF name":
|
||||
/// A multi-application card shall support the SELECT command with P1='04', P2='00' and a data field
|
||||
/// containing 5 to 16 bytes with the AID of an application that may reside on the card.
|
||||
/// The command shall complete successfully if the AID of an application the card holds matches the data field.
|
||||
///
|
||||
/// It is also specified that:
|
||||
/// In a multi-application card an application in the card shall be identified by
|
||||
/// a single AID in the proprietary, national or international category, and/or
|
||||
/// one or more AIDs in the standard category.
|
||||
|
||||
pub trait App {
|
||||
// using an associated constant here would make the trait object unsafe
|
||||
fn aid(&self) -> Aid;
|
||||
// fn select_via_aid(&mut self, interface: Interface, aid: Aid) -> Result<()>;
|
||||
// fn deselect(&mut self) -> Result<()>;
|
||||
// fn call(&mut self, interface: Interface, command: &Command<C>, response: &mut Response<R>) -> Result<()>;
|
||||
}
|
||||
|
||||
impl core::ops::Deref for Aid {
|
||||
@@ -57,10 +79,7 @@ impl core::ops::Deref for Aid {
|
||||
}
|
||||
|
||||
impl Aid {
|
||||
/// Maximum length of an AID.
|
||||
pub const fn max_len() -> usize {
|
||||
16
|
||||
}
|
||||
const MAX_LEN: usize = 16;
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&self.bytes[..self.len as usize]
|
||||
@@ -74,25 +93,21 @@ impl Aid {
|
||||
aid.starts_with(self.truncated())
|
||||
}
|
||||
|
||||
pub const fn new(aid: &[u8], truncated_len: usize) -> Self {
|
||||
const_assert!(!aid.is_empty(), "AID needs at least a category identifier");
|
||||
const_assert!(aid.len() <= Self::max_len(), "AID too long");
|
||||
const_assert!(truncated_len <= aid.len(), "truncated length too long");
|
||||
let mut s = Self { bytes: [0u8; Self::max_len()], len: aid.len() as u8, truncated_len: truncated_len as u8 };
|
||||
s = s.fill(aid, 0);
|
||||
const_assert!(!s.national() || aid.len() >= 5, "National RID must have length 5");
|
||||
const_assert!(!s.international() || aid.len() >= 5, "International RID must have length 5");
|
||||
s
|
||||
pub const fn new(aid: &[u8]) -> Self {
|
||||
Self::new_truncatable(aid, aid.len())
|
||||
}
|
||||
|
||||
// pub fn try_new(aid: &[u8], truncated_len: u8) -> Result<Self, ()> {
|
||||
// if aid.len() > Self::max_len() {
|
||||
// return Err(());
|
||||
// }
|
||||
// if truncated_len > aid.len() {
|
||||
// return Err(());
|
||||
// }
|
||||
// }
|
||||
pub const fn new_truncatable(aid: &[u8], truncated_len: usize) -> Self {
|
||||
const_assert!(!aid.is_empty(), "AID needs at least a category identifier");
|
||||
const_assert!(aid.len() <= Self::MAX_LEN, "AID too long");
|
||||
const_assert!(truncated_len <= aid.len(), "truncated length too long");
|
||||
let mut s = Self { bytes: [0u8; Self::MAX_LEN], len: aid.len() as u8, truncated_len: truncated_len as u8 };
|
||||
s = s.fill(aid, 0);
|
||||
const_assert!(!s.is_national() || aid.len() >= 5, "National RID must have length 5");
|
||||
const_assert!(!s.is_international() || aid.len() >= 5, "International RID must have length 5");
|
||||
s
|
||||
}
|
||||
|
||||
// workaround to copy in the aid while remaining "const"
|
||||
// maybe there is a better way?
|
||||
@@ -106,41 +121,58 @@ impl Aid {
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn international(&self) -> bool {
|
||||
(self.bytes[0] >> 4) == b'A'
|
||||
pub const fn category(&self) -> Category {
|
||||
match self.bytes[0] >> 4 {
|
||||
b'A' => Category::International,
|
||||
b'D' => Category::National,
|
||||
b'E' => Category::Standard,
|
||||
b'F' => Category::Proprietary,
|
||||
_ => Category::Other,
|
||||
}
|
||||
}
|
||||
pub const fn is_international(&self) -> bool {
|
||||
// This is not "const" yet.
|
||||
// self.category() == Category::International
|
||||
match self.category() {
|
||||
Category::International => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn national(&self) -> bool {
|
||||
(self.bytes[0] >> 4) == b'D'
|
||||
pub const fn is_national(&self) -> bool {
|
||||
match self.category() {
|
||||
Category::National => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn standard(&self) -> bool {
|
||||
(self.bytes[0] >> 4) == b'E'
|
||||
pub const fn is_standard(&self) -> bool {
|
||||
match self.category() {
|
||||
Category::Standard => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn proprietary(&self) -> bool {
|
||||
(self.bytes[0] >> 4) == b'F'
|
||||
pub const fn is_proprietary(&self) -> bool {
|
||||
match self.category() {
|
||||
Category::Proprietary => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
const fn has_rid_pix(&self) -> bool {
|
||||
self.is_national() || self.is_international()
|
||||
}
|
||||
|
||||
// pub fn rid(&self) -> &[u8; 5] {
|
||||
/// International or national registered application provider identifier, 5 bytes.
|
||||
pub fn rid(&self) -> Option<&[u8]> {
|
||||
if self.national() || self.international() {
|
||||
Some(&self.bytes[..5])
|
||||
} else {
|
||||
// "RID not defined"
|
||||
None
|
||||
}
|
||||
self.has_rid_pix().then(|| &self.bytes[..5])
|
||||
}
|
||||
|
||||
/// Proprietary application identifier extension, up to 11 bytes.
|
||||
pub fn pix(&self) -> Option<&[u8]> {
|
||||
if self.national() || self.international() {
|
||||
Some(&self.bytes[5..])
|
||||
} else {
|
||||
// "PIX not defined"
|
||||
None
|
||||
}
|
||||
self.has_rid_pix().then(|| &self.bytes[5..])
|
||||
}
|
||||
|
||||
}
|
||||
@@ -148,12 +180,15 @@ impl Aid {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Aid;
|
||||
use hex_literal::hex;
|
||||
#[allow(dead_code)]
|
||||
const PIV_AID: Aid = Aid::new(&hex_literal::hex!("A000000308 00001000 0100"), 11);
|
||||
const PIV_AID: Aid = Aid::new_truncatable(&hex!("A000000308 00001000 0100"), 9);
|
||||
|
||||
#[test]
|
||||
fn non_const_aid() {
|
||||
let aid = Aid::new(&hex_literal::hex!("A000000308 00001000 0100"), 11);
|
||||
fn aid() {
|
||||
let piv_aid = Aid::new(&hex!("A000000308 00001000 0100"));
|
||||
assert!(piv_aid.matches(&*PIV_AID));
|
||||
assert!(PIV_AID.matches(&*piv_aid));
|
||||
// panics
|
||||
// let aid = Aid::new(&hex_literal::hex!("A000000308 00001000 01001232323333333333333332"));
|
||||
}
|
||||
|
||||
+18
-19
@@ -1,30 +1,29 @@
|
||||
use heapless_bytes::ArrayLength;
|
||||
use core::convert::TryFrom;
|
||||
|
||||
use crate::Data;
|
||||
|
||||
pub mod class;
|
||||
pub mod instruction;
|
||||
|
||||
use crate::Bytes;
|
||||
pub use instruction::Instruction;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Command<SIZE>
|
||||
where SIZE: ArrayLength<u8>
|
||||
pub struct Command<const S: usize>
|
||||
{
|
||||
class: class::Class,
|
||||
instruction: instruction::Instruction,
|
||||
instruction: Instruction,
|
||||
|
||||
pub p1: u8,
|
||||
pub p2: u8,
|
||||
|
||||
/// The main reason this is modeled as Bytes and not
|
||||
/// a fixed array is for serde purposes.
|
||||
data: Bytes<SIZE>,
|
||||
data: Data<S>,
|
||||
|
||||
le: usize,
|
||||
pub extended: bool,
|
||||
}
|
||||
|
||||
impl<SIZE> Command<SIZE>
|
||||
where SIZE: ArrayLength<u8>
|
||||
impl<const S: usize> Command<S>
|
||||
{
|
||||
pub fn try_from(apdu: &[u8]) -> Result<Self, FromSliceError> {
|
||||
use core::convert::TryInto;
|
||||
@@ -35,15 +34,15 @@ where SIZE: ArrayLength<u8>
|
||||
self.class
|
||||
}
|
||||
|
||||
pub fn instruction(&self) -> instruction::Instruction {
|
||||
pub fn instruction(&self) -> Instruction {
|
||||
self.instruction
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &Bytes<SIZE> {
|
||||
pub fn data(&self) -> &Data<S> {
|
||||
&self.data
|
||||
}
|
||||
|
||||
pub fn data_mut(&mut self) -> &mut Bytes<SIZE>{
|
||||
pub fn data_mut(&mut self) -> &mut Data<S> {
|
||||
&mut self.data
|
||||
}
|
||||
|
||||
@@ -54,7 +53,7 @@ where SIZE: ArrayLength<u8>
|
||||
/// This can be use for APDU chaining to convert
|
||||
/// multiple APDU's into one.
|
||||
/// * Global Platform GPC_SPE_055 3.10
|
||||
pub fn extend_from_command(&mut self, command: &Command<impl ArrayLength<u8>>) -> core::result::Result<(),()> {
|
||||
pub fn extend_from_command<const T: usize>(&mut self, command: &Command<T>) -> core::result::Result<(), ()> {
|
||||
|
||||
// Always take the header from the last command;
|
||||
self.class = command.class();
|
||||
@@ -72,6 +71,7 @@ where SIZE: ArrayLength<u8>
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum FromSliceError {
|
||||
TooShort,
|
||||
TooLong,
|
||||
InvalidClass,
|
||||
InvalidFirstBodyByteForExtended,
|
||||
InvalidSliceLength,
|
||||
@@ -83,8 +83,7 @@ impl From<class::InvalidClass> for FromSliceError {
|
||||
}
|
||||
}
|
||||
|
||||
impl<SIZE> core::convert::TryFrom<&[u8]> for Command<SIZE>
|
||||
where SIZE: ArrayLength<u8>
|
||||
impl<const S: usize> TryFrom<&[u8]> for Command<S>
|
||||
{
|
||||
type Error = FromSliceError;
|
||||
fn try_from(apdu: &[u8]) -> core::result::Result<Self, Self::Error> {
|
||||
@@ -95,7 +94,7 @@ where SIZE: ArrayLength<u8>
|
||||
println!("{}", apdu.len());
|
||||
let (header, body) = apdu.split_at(4);
|
||||
let class = class::Class::try_from(header[0])?;
|
||||
let instruction = instruction::Instruction::from(header[1]);
|
||||
let instruction = Instruction::from(header[1]);
|
||||
let p1 = header[2];
|
||||
let p2 = header[3];
|
||||
let parsed = parse_lengths(body)?;
|
||||
@@ -107,7 +106,8 @@ where SIZE: ArrayLength<u8>
|
||||
// maximum expected response length
|
||||
le: parsed.le,
|
||||
// payload
|
||||
data: Bytes::try_from_slice(data_slice).unwrap(),
|
||||
data: Data::from_slice(data_slice)
|
||||
.map_err(|_| Self::Error::TooLong)?,
|
||||
extended: parsed.extended,
|
||||
})
|
||||
}
|
||||
@@ -248,8 +248,7 @@ mod test {
|
||||
0xbe, 0x1e, 0x2c, 0x69, 0x1d, 0xc3, 0x53, 0x4c, 0x89, 0x14, 0xa3, 0x12, 0x30, 0x10, 0x30, 0x0e,
|
||||
0x06, 0x03, 0x55, 0x1d,
|
||||
];
|
||||
// let apdu = b"\x10\xdb?\xff\xff\\\x03_\xc1\x05S\x82\x01Zp\x82\x01Q0\x82\x01M0\x81\xf4\xa0\x03\x02\x01\x02\x02\x10\x19\x185\xd2i\xcb\x0b\xf9\xcc\x07)*\xb5QLq0\n\x06\x08*\x86";
|
||||
|
||||
let command = Command::try_from(apdu).unwrap();
|
||||
let command = Command::<256>::try_from(apdu).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
+5
-12
@@ -1,14 +1,8 @@
|
||||
#![cfg_attr(not(test), no_std)]
|
||||
// #![no_std]
|
||||
|
||||
#[macro_use]
|
||||
extern crate delog;
|
||||
generate_macros!();
|
||||
|
||||
pub use heapless_bytes::Bytes as Bytes;
|
||||
|
||||
// // 7816-4, 8.2.1.2
|
||||
// pub type Aid = Bytes<heapless::consts::U16>;
|
||||
// generate_macros!();
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum Interface {
|
||||
@@ -16,14 +10,13 @@ pub enum Interface {
|
||||
Contactless,
|
||||
}
|
||||
|
||||
pub type Result<T> = core::result::Result<T, Status>;
|
||||
pub type Data<const S: usize> = heapless::Vec<u8, S>;
|
||||
pub type Result<T=()> = core::result::Result<T, Status>;
|
||||
|
||||
pub mod aid;
|
||||
pub mod command;
|
||||
pub mod response;
|
||||
|
||||
pub use aid::{Aid, App};
|
||||
pub use command::Command;
|
||||
pub use command::instruction::Instruction;
|
||||
pub use response::{Data, Response};
|
||||
pub use response::status::Status;
|
||||
pub use command::{Command, Instruction};
|
||||
pub use response::{Response, Status};
|
||||
|
||||
+5
-10
@@ -1,20 +1,15 @@
|
||||
use heapless_bytes::ArrayLength;
|
||||
|
||||
pub mod status;
|
||||
mod status;
|
||||
pub use status::Status;
|
||||
|
||||
pub type Data<T> = crate::Bytes<T>;
|
||||
use crate::Data;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Response<SIZE>
|
||||
where SIZE: ArrayLength<u8>
|
||||
{
|
||||
Data(Data<SIZE>),
|
||||
pub enum Response<const S: usize> {
|
||||
Data(Data<S>),
|
||||
Status(Status),
|
||||
}
|
||||
|
||||
impl<SIZE> Default for Response<SIZE>
|
||||
where SIZE: ArrayLength<u8> {
|
||||
impl<const S: usize> Default for Response<S> {
|
||||
fn default() -> Self {
|
||||
Self::Status(Default::default())
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use core::convert::TryFrom;
|
||||
use crate::Data;
|
||||
|
||||
impl Default for Status {
|
||||
fn default() -> Self {
|
||||
@@ -170,13 +171,12 @@ impl Into<[u8; 2]> for Status {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Into<heapless_bytes::Bytes<S>> for Status
|
||||
where S: heapless_bytes::ArrayLength<u8>
|
||||
impl<const S: usize> Into<Data<S>> for Status
|
||||
{
|
||||
#[inline]
|
||||
fn into(self) -> heapless_bytes::Bytes<S> {
|
||||
fn into(self) -> Data<S> {
|
||||
let arr: [u8; 2] = self.into();
|
||||
heapless_bytes::Bytes::try_from_slice(&arr).unwrap()
|
||||
Data::from_slice(&arr).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user