Move to heapless 7 = const generics

This commit is contained in:
Nicolas Stalder
2021-06-10 22:31:41 +02:00
parent f12c2d59ce
commit 9d6e92fe20
6 changed files with 124 additions and 100 deletions
+5 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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())
}
+4 -4
View File
@@ -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()
}
}