use crate::Data;
pub mod class;
pub mod instruction;
pub use instruction::Instruction;
pub mod writer;
pub use writer::{BufferFull, Writer};
mod datasource;
pub use datasource::{DataSource, DataStream};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Command<const S: usize> {
class: class::Class,
instruction: Instruction,
pub p1: u8,
pub p2: u8,
data: Data<S>,
le: usize,
pub extended: bool,
}
impl<const S: usize> Command<S> {
pub fn try_from(apdu: &[u8]) -> Result<Self, FromSliceError> {
apdu.try_into()
}
pub fn class(&self) -> class::Class {
self.class
}
pub fn instruction(&self) -> Instruction {
self.instruction
}
pub fn data(&self) -> &Data<S> {
&self.data
}
pub fn data_mut(&mut self) -> &mut Data<S> {
&mut self.data
}
pub fn expected(&self) -> usize {
self.le
}
pub fn as_view(&self) -> CommandView {
CommandView {
class: self.class,
instruction: self.instruction,
p1: self.p1,
p2: self.p2,
data: self.data(),
le: self.le,
extended: self.extended,
}
}
#[allow(clippy::result_unit_err)]
pub fn extend_from_command<const T: usize>(
&mut self,
command: &Command<T>,
) -> core::result::Result<(), ()> {
self.extend_from_command_view(command.as_view())
}
#[allow(clippy::result_unit_err)]
pub fn extend_from_command_view(
&mut self,
command: CommandView,
) -> core::result::Result<(), ()> {
self.class = command.class();
self.instruction = command.instruction();
self.p1 = command.p1;
self.p2 = command.p2;
self.le = command.le;
self.extended = true;
self.data.extend_from_slice(command.data())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CommandView<'a> {
class: class::Class,
instruction: Instruction,
pub p1: u8,
pub p2: u8,
data: &'a [u8],
le: usize,
pub extended: bool,
}
impl<'a> CommandView<'a> {
pub fn class(&self) -> class::Class {
self.class
}
pub fn instruction(&self) -> Instruction {
self.instruction
}
pub fn data(&self) -> &[u8] {
self.data
}
pub fn expected(&self) -> usize {
self.le
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum ExtendedLen {
Unsupported,
Supported,
Forced,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct CommandBuilder<D> {
class: class::Class,
instruction: Instruction,
pub p1: u8,
pub p2: u8,
data: D,
le: ExpectedLen,
extended_length: ExtendedLen,
}
#[derive(Debug)]
pub struct ChainedCommandIterator<'a> {
command: Option<CommandBuilder<&'a [u8]>>,
available_len: usize,
}
impl<'a> Iterator for ChainedCommandIterator<'a> {
type Item = CommandBuilder<&'a [u8]>;
fn next(&mut self) -> Option<CommandBuilder<&'a [u8]>> {
let Some(next) = self.command.take() else {
return None;
};
if let Some((cur, next)) = next.should_split(self.available_len) {
self.command = Some(next);
Some(cur)
} else {
Some(next)
}
}
}
const HEADER_LEN: usize = 4;
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord, Copy)]
pub enum ExpectedLen {
Ne(u16),
Max,
}
impl From<u16> for ExpectedLen {
fn from(value: u16) -> Self {
Self::Ne(value)
}
}
impl From<ExpectedLen> for usize {
fn from(value: ExpectedLen) -> Self {
(match value {
ExpectedLen::Ne(l) => l,
ExpectedLen::Max => u16::MAX,
}) as _
}
}
impl<D: DataSource> CommandBuilder<D> {
pub fn new(
class: class::Class,
instruction: instruction::Instruction,
p1: u8,
p2: u8,
data: D,
le: impl Into<ExpectedLen>,
) -> Self {
assert!(data.len() <= u16::MAX as usize);
Self {
class,
instruction,
p1,
p2,
data,
le: le.into(),
extended_length: ExtendedLen::Supported,
}
}
pub fn force_extended(mut self) -> Self {
assert!(!matches!(self.extended_length, ExtendedLen::Unsupported));
self.extended_length = ExtendedLen::Forced;
self
}
pub fn data(&self) -> D
where
D: Copy,
{
self.data
}
fn header_data(&self) -> BuildingHeaderData {
fn serialize_data_len(
len: u16,
expected_len: ExpectedLen,
extended: ExtendedLen,
) -> (heapless::Vec<u8, 3>, bool) {
let expected_is_extended =
matches!(expected_len, ExpectedLen::Ne(257..) | ExpectedLen::Max);
match (len, expected_is_extended, extended) {
(0, _, _) => (Default::default(), false),
(1..=255, false, ExtendedLen::Unsupported | ExtendedLen::Supported) => {
([len as u8].as_slice().try_into().unwrap(), false)
}
_ => {
let l = len.to_be_bytes();
([0, l[0], l[1]].as_slice().try_into().unwrap(), true)
}
}
}
fn serialize_expected_len(
len: ExpectedLen,
lc_extended: bool,
data_is_empty: bool,
extended: ExtendedLen,
) -> heapless::Vec<u8, 3> {
match (len, lc_extended, data_is_empty, extended) {
(ExpectedLen::Ne(0), _, _, _) => Default::default(),
(
ExpectedLen::Ne(len @ 1..=255),
false,
_,
ExtendedLen::Unsupported | ExtendedLen::Supported,
) => [len as u8].as_slice().try_into().unwrap(),
(
ExpectedLen::Ne(256),
false,
_,
ExtendedLen::Unsupported | ExtendedLen::Supported,
) => [0].as_slice().try_into().unwrap(),
(ExpectedLen::Ne(len), true, false, _) => {
let l = len.to_be_bytes();
[l[0], l[1]].as_slice().try_into().unwrap()
}
(ExpectedLen::Max, true, false, _) => [0, 0].as_slice().try_into().unwrap(),
(ExpectedLen::Ne(len), false, true, _) => {
let l = len.to_be_bytes();
[0, l[0], l[1]].as_slice().try_into().unwrap()
}
(ExpectedLen::Max, false, true, _) => [0, 0, 0].as_slice().try_into().unwrap(),
(ExpectedLen::Ne(257..) | ExpectedLen::Max, false, false, _)
| (_, false, false, ExtendedLen::Forced) => {
unreachable!("Can't have non extended Lc and extended Le")
}
(_, true, true, _) => {
unreachable!("Can't have both no data and data extended length")
}
}
}
let le = if self.extended_length == ExtendedLen::Unsupported {
self.le.min(256.into())
} else {
self.le
};
let (data_len, lc_extended) = serialize_data_len(
self.data.len().try_into().unwrap(),
le,
self.extended_length,
);
let expected_data_len =
serialize_expected_len(le, lc_extended, self.data.is_empty(), self.extended_length);
BuildingHeaderData {
le,
data_len,
expected_data_len,
}
}
pub fn required_len(&self) -> usize {
let header_data = self.header_data();
let header_len = 4;
let length_len = header_data.data_len.len() + header_data.expected_data_len.len();
header_len + length_len + self.data.len()
}
#[cfg(any(feature = "std", test))]
pub fn serialize_to_vec(self) -> Vec<u8>
where
D: DataStream<Vec<u8>>,
{
let required_len = self.required_len();
let mut buffer = Vec::with_capacity(required_len);
self.serialize_into(&mut buffer).unwrap();
debug_assert_eq!(required_len, buffer.len());
buffer
}
pub fn serialize_into<W: Writer>(&self, writer: &mut W) -> Result<(), W::Error>
where
D: DataStream<W>,
{
let BuildingHeaderData {
data_len,
expected_data_len,
..
} = self.header_data();
writer.write_all(&[
self.class.into_inner(),
self.instruction.into(),
self.p1,
self.p2,
])?;
writer.write_all(&data_len)?;
self.data.to_writer(writer)?;
writer.write_all(&expected_data_len)?;
Ok(())
}
}
struct BuildingHeaderData {
le: ExpectedLen,
data_len: heapless::Vec<u8, 3>,
expected_data_len: heapless::Vec<u8, 3>,
}
impl<'a, D: PartialEq<&'a [u8]>> PartialEq<CommandView<'a>> for CommandBuilder<D> {
fn eq(&self, other: &CommandView<'a>) -> bool {
let Self {
class,
instruction,
p1,
p2,
data,
le,
extended_length: _,
} = self;
let le: usize = (*le).into();
class == &other.class
&& instruction == &other.instruction
&& p1 == &other.p1
&& p2 == &other.p2
&& data == &other.data
&& le == other.le
}
}
impl<'a> CommandBuilder<&'a [u8]> {
pub fn new_non_extended(
class: class::Class,
instruction: instruction::Instruction,
p1: u8,
p2: u8,
data: &'a [u8],
le: u16,
buffer_len: Option<usize>,
) -> ChainedCommandIterator<'a> {
assert!(data.len() <= u16::MAX as usize);
ChainedCommandIterator {
command: Some(Self {
class,
instruction,
p1,
p2,
data,
le: le.into(),
extended_length: ExtendedLen::Unsupported,
}),
available_len: buffer_len.unwrap_or(255 + 5 + 1),
}
}
pub fn should_split(&self, available_len: usize) -> Option<(Self, Self)> {
if available_len < HEADER_LEN {
panic!("Commands cannot be encoded to fit in buffers smaller than 9 bytes");
}
let BuildingHeaderData {
le,
data_len,
expected_data_len,
} = self.header_data();
let mut max_data_len = u16::MAX as usize;
if self.extended_length == ExtendedLen::Unsupported {
max_data_len = 255;
}
let available_data_len = (available_len - HEADER_LEN)
.saturating_sub(data_len.len() + expected_data_len.len())
.min(max_data_len);
if available_data_len >= self.data.len() {
return None;
}
if available_data_len == 0 {
panic!("Commands cannot be encoded to fit in buffers smaller than 9 bytes");
}
let (send_now, send_later) = self.data.split_at(available_data_len);
let send_now = Self {
class: self.class.as_chained(),
instruction: self.instruction,
p1: self.p1,
p2: self.p2,
data: send_now,
le: 0.into(),
extended_length: self.extended_length,
};
let send_later = Self {
class: self.class,
instruction: self.instruction,
p1: self.p1,
p2: self.p2,
data: send_later,
le,
extended_length: self.extended_length,
};
Some((send_now, send_later))
}
}
impl<D: DataSource> DataSource for CommandBuilder<D> {
fn len(&self) -> usize {
self.required_len()
}
fn is_empty(&self) -> bool {
false
}
}
impl<W: Writer, D: DataStream<W>> DataStream<W> for CommandBuilder<D> {
fn to_writer(&self, writer: &mut W) -> Result<(), <W as Writer>::Error> {
self.serialize_into(writer)
}
}
impl<'a, D: PartialEq<&'a [u8]>> PartialEq<CommandBuilder<D>> for CommandView<'a> {
fn eq(&self, other: &CommandBuilder<D>) -> bool {
other == self
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum FromSliceError {
TooShort,
TooLong,
InvalidClass,
InvalidFirstBodyByteForExtended,
InvalidSliceLength,
}
impl From<class::InvalidClass> for FromSliceError {
fn from(_: class::InvalidClass) -> Self {
Self::InvalidClass
}
}
impl<'a> TryFrom<&'a [u8]> for CommandView<'a> {
type Error = FromSliceError;
fn try_from(apdu: &'a [u8]) -> core::result::Result<Self, Self::Error> {
if apdu.len() < 4 {
return Err(FromSliceError::TooShort);
}
#[cfg(test)]
println!("{}", apdu.len());
let (header, body) = apdu.split_at(4);
let class = class::Class::try_from(header[0])?;
let instruction = Instruction::from(header[1]);
let p1 = header[2];
let p2 = header[3];
let parsed = parse_lengths(body)?;
let data = &body[parsed.offset..][..parsed.lc];
Ok(Self {
class,
instruction,
p1,
p2,
le: parsed.le,
data,
extended: parsed.extended,
})
}
}
impl<'a> CommandView<'a> {
pub fn to_owned<const S: usize>(&self) -> Result<Command<S>, FromSliceError> {
let &CommandView {
class,
instruction,
p1,
p2,
le,
data: data_slice,
extended,
} = self;
let mut command = Command {
class,
instruction,
p1,
p2,
le,
data: Data::new(),
extended,
};
command
.data
.extend_from_slice(data_slice)
.map_err(|_| FromSliceError::TooLong)?;
Ok(command)
}
}
impl<const S: usize> TryFrom<&[u8]> for Command<S> {
type Error = FromSliceError;
fn try_from(apdu: &[u8]) -> core::result::Result<Self, Self::Error> {
let view: CommandView = apdu.try_into()?;
view.to_owned()
}
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
struct ParsedLengths {
lc: usize,
le: usize,
offset: usize,
extended: bool,
}
#[inline(always)]
fn replace_zero(value: usize, replacement: usize) -> usize {
if value == 0 {
replacement
} else {
value
}
}
#[inline]
fn parse_lengths(body: &[u8]) -> Result<ParsedLengths, FromSliceError> {
let l = body.len();
let mut parsed: ParsedLengths = Default::default();
if l == 0 {
return Ok(parsed);
}
let b1 = body[0] as usize;
#[cfg(test)]
println!("l = {}, b1 = {}", l, b1);
if l == 1 {
parsed.lc = 0;
parsed.le = replace_zero(b1, 256);
return Ok(parsed);
}
if l == 1 + b1 && b1 != 0 {
parsed.lc = b1;
parsed.le = 0;
parsed.offset = 1;
return Ok(parsed);
}
if l == 2 + b1 && b1 != 0 {
parsed.lc = b1;
parsed.le = replace_zero(body[l - 1] as usize, 256);
parsed.offset = 1;
return Ok(parsed);
}
parsed.extended = true;
if b1 != 0 {
return Err(FromSliceError::InvalidFirstBodyByteForExtended);
} else if l < 3 {
return Err(FromSliceError::InvalidSliceLength);
}
if l == 3 && b1 == 0 {
parsed.lc = 0;
parsed.le = replace_zero(u16::from_be_bytes([body[1], body[2]]) as usize, 65_536);
return Ok(parsed);
}
parsed.lc = u16::from_be_bytes([body[1], body[2]]) as usize;
if l == 3 + parsed.lc {
parsed.le = 0;
parsed.offset = 3;
return Ok(parsed);
}
if l == 5 + parsed.lc {
parsed.le = replace_zero(
u16::from_be_bytes([body[l - 2], body[l - 1]]) as usize,
65_536,
);
parsed.offset = 3;
return Ok(parsed);
}
Err(FromSliceError::InvalidSliceLength)
}
#[cfg(test)]
mod test {
use super::*;
use hex_literal::hex;
use quickcheck_macros::quickcheck;
#[quickcheck]
fn parse_no_panic(data: Vec<u8>) {
let _ = parse_lengths(&data);
}
#[quickcheck]
fn lengths(lc: u16, le: Option<u16>) {
let extended =
lc > u16::from(u8::MAX) || le.map(|val| val > u16::from(u8::MAX)).unwrap_or_default();
let nc = usize::from(lc);
let ne = le
.map(usize::from)
.map(|val| {
if val == 0 {
(if extended {
usize::from(u16::MAX)
} else {
usize::from(u8::MAX)
}) + 1
} else {
val
}
})
.unwrap_or_default();
let mut data = Vec::new();
let mut offset = 0;
if lc > 0 {
if extended {
data.push(0);
data.extend_from_slice(&lc.to_be_bytes());
offset = 3;
} else {
data.push(lc as u8);
offset = 1;
}
}
data.resize(data.len() + nc, 0);
if let Some(le) = le {
if extended {
if lc == 0 {
data.push(0);
}
data.extend_from_slice(&le.to_be_bytes());
} else {
data.push(le as u8);
}
}
let lengths = parse_lengths(&data).expect("failed to parse lengths");
assert_eq!(extended, lengths.extended);
assert_eq!(offset, lengths.offset);
assert_eq!(nc, lengths.lc);
assert_eq!(ne, lengths.le);
}
#[test]
fn builder_forced_extended() {
let cla = 0.try_into().unwrap();
let ins = 1.into();
let command = CommandBuilder::new(cla, ins, 2, 3, &[], 0x04).force_extended();
assert_eq!(command.serialize_to_vec(), &hex!("00 01 02 03 00 00 04"));
let command = CommandBuilder::new(cla, ins, 2, 3, &[0x05, 0x06], 0x04).force_extended();
assert_eq!(
command.serialize_to_vec(),
&hex!("00 01 02 03 00 00 02 05 06 00 04")
);
let command = CommandBuilder::new(cla, ins, 2, 3, &[0x01; 0x2AE], 0x100).force_extended();
assert_eq!(
command.serialize_to_vec(),
[
hex!("00 01 02 03 00 02AE").as_slice(),
&[0x01; 0x2AE],
&hex!("01 00"),
]
.into_iter()
.flatten()
.copied()
.collect::<Vec<u8>>()
);
}
#[test]
fn builder() {
let cla = 0.try_into().unwrap();
let ins = 1.into();
let command = CommandBuilder::new(cla, ins, 2, 3, &[], 0x04);
assert_eq!(command.serialize_to_vec(), &hex!("00 01 02 03 04"));
let command = CommandBuilder::new(cla, ins, 2, 3, &[], 0x00);
assert_eq!(command.serialize_to_vec(), &hex!("00 01 02 03"));
let command = CommandBuilder::new(cla, ins, 2, 3, &[], 256);
assert_eq!(command.serialize_to_vec(), &hex!("00 01 02 03 00"));
let command = CommandBuilder::new(cla, ins, 2, 3, &[], 257);
assert_eq!(command.serialize_to_vec(), &hex!("00 01 02 03 00 0101"));
let command = CommandBuilder::new(cla, ins, 2, 3, &[], 0xFFFF);
assert_eq!(command.serialize_to_vec(), &hex!("00 01 02 03 00 FFFF"));
let command = CommandBuilder::new(cla, ins, 2, 3, &[0x05, 0x06], 0x04);
assert_eq!(command.serialize_to_vec(), &hex!("00 01 02 03 02 05 06 04"));
let command = CommandBuilder::new(cla, ins, 2, 3, &[0x05, 0x06], 0x00);
assert_eq!(command.serialize_to_vec(), &hex!("00 01 02 03 02 05 06"));
let command = CommandBuilder::new(cla, ins, 2, 3, &[0x05, 0x06], 0x100);
assert_eq!(
command.serialize_to_vec(),
&hex!("00 01 02 03 02 05 06 00")
);
let command = CommandBuilder::new(cla, ins, 2, 3, &[0x01; 0x2AE], 0x100);
assert_eq!(
command.serialize_to_vec(),
[
hex!("00 01 02 03 00 02AE").as_slice(),
&[0x01; 0x2AE],
&hex!("01 00"),
]
.into_iter()
.flatten()
.copied()
.collect::<Vec<u8>>()
);
let command = CommandBuilder::new(cla, ins, 2, 3, &[0x01; 0x2AE], 0x01);
assert_eq!(
command.serialize_to_vec(),
[
hex!("00 01 02 03 00 02AE").as_slice(),
&[0x01; 0x2AE],
&hex!("00 01"),
]
.into_iter()
.flatten()
.copied()
.collect::<Vec<u8>>()
);
let command = CommandBuilder::new(cla, ins, 2, 3, &[0x01; 0x2AE], 0x00);
assert_eq!(
command.serialize_to_vec(),
[hex!("00 01 02 03 00 02AE").as_slice(), &[0x01; 0x2AE],]
.into_iter()
.flatten()
.copied()
.collect::<Vec<u8>>()
);
let command = CommandBuilder::new(cla, ins, 2, 3, &[0x01; 0x2AE], 0xFF);
assert_eq!(
command.serialize_to_vec(),
[
hex!("00 01 02 03 00 02AE").as_slice(),
&[0x01; 0x2AE],
&[0x00, 0xFF]
]
.into_iter()
.flatten()
.copied()
.collect::<Vec<u8>>()
);
let command = CommandBuilder::new(cla, ins, 2, 3, &[0x01; 0xFFFF], 0xFFFF);
assert_eq!(
command.serialize_to_vec(),
[
hex!("00 01 02 03 00 FFFF").as_slice(),
&[0x01; 0xFFFF],
&[0xFF, 0xFF]
]
.into_iter()
.flatten()
.copied()
.collect::<Vec<u8>>()
);
let command = CommandBuilder::new(cla, ins, 2, 3, &[1, 2, 3, 4], 294);
assert_eq!(
command.serialize_to_vec(),
&hex!("00 01 02 03 00 00 04 01020304 0126")
);
}
#[test]
fn building_chained() {
let cla = 0x00.try_into().unwrap();
let ins = 0x01.into();
let mut buffer = heapless::Vec::<u8, 4096>::new();
let command = CommandBuilder::new(cla, ins, 2, 3, &[], 0xFFFF);
command.clone().serialize_into(&mut buffer).unwrap();
assert_eq!(&*buffer, &command.clone().serialize_to_vec());
buffer.clear();
let command =
CommandBuilder::new_non_extended(cla, ins, 2, 3, &[], 0xFFFF, Some(buffer.capacity()))
.next()
.unwrap();
command.clone().serialize_into(&mut buffer).unwrap();
assert_eq!(
&*buffer,
&CommandBuilder::new(cla, ins, 2, 3, &[], 0x0100).serialize_to_vec()
);
buffer.clear();
let command =
CommandBuilder::new_non_extended(cla, ins, 2, 3, &[], 0, Some(buffer.capacity()))
.next()
.unwrap();
command.serialize_into(&mut buffer).unwrap();
assert_eq!(
&*buffer,
&CommandBuilder::new(cla, ins, 2, 3, &[], 0).serialize_to_vec()
);
buffer.clear();
let mut buffer = heapless::Vec::<u8, 105>::new();
let mut command_iter =
CommandBuilder::new_non_extended(cla, ins, 2, 3, &[5; 200], 0, Some(buffer.capacity()));
let command = command_iter.next().unwrap();
let mut rem = command_iter.next().unwrap();
assert!(command_iter.next().is_none());
command.serialize_into(&mut buffer).unwrap();
assert_eq!(buffer.len(), 105);
rem.extended_length = ExtendedLen::Supported;
assert_eq!(
rem,
CommandBuilder::new(cla, ins, 2, 3, [5; 100].as_slice(), 0)
);
assert_eq!(
&*buffer,
&CommandBuilder::new(cla.as_chained(), ins, 2, 3, &[5; 100], 0).serialize_to_vec()
);
}
#[test]
fn nested_commands() {
let cla = 0x00.try_into().unwrap();
let ins = 0x01.into();
let mut buffer = heapless::Vec::<u8, 4096>::new();
let inner = CommandBuilder::new(cla, ins, 1, 2, &hex!("FFFEFDFCFBFA"), 0x10);
let outer = CommandBuilder::new(cla, 0xAA.into(), 3, 4, &inner, 0x20);
outer.serialize_into(&mut buffer).unwrap();
#[rustfmt::skip]
assert_eq!(
&*buffer,
hex!("
00 AA 0304 0C
00 01 01 02 06 FFFEFDFCFBFA 10
20"
)
);
}
#[test]
fn lengths_4s() {
let data = &[0x02, 0xB6, 0x00, 0x00];
let lengths = parse_lengths(data).expect("failed to parse lengths");
assert_eq!(lengths.lc, 2);
assert_eq!(lengths.le, 256);
assert_eq!(lengths.offset, 1);
}
#[test]
fn command_chaining() {
let apdu = &[
0x10, 0xdb, 0x3f, 0xff, 0xff, 0x5c, 0x03, 0x5f, 0xc1, 0x05, 0x53, 0x82, 0x01, 0x5b,
0x70, 0x82, 0x01, 0x52, 0x30, 0x82, 0x01, 0x4e, 0x30, 0x81, 0xf5, 0xa0, 0x03, 0x02,
0x01, 0x02, 0x02, 0x11, 0x00, 0x8b, 0xab, 0x31, 0xcf, 0x3e, 0xb9, 0xf5, 0x6a, 0x6f,
0x38, 0xf0, 0x5a, 0x4d, 0x7f, 0x55, 0x62, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48,
0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x2a, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55,
0x04, 0x0a, 0x13, 0x0d, 0x79, 0x75, 0x62, 0x69, 0x6b, 0x65, 0x79, 0x2d, 0x61, 0x67,
0x65, 0x6e, 0x74, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07,
0x28, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x29, 0x30, 0x20, 0x17, 0x0d, 0x32, 0x30, 0x30,
0x35, 0x31, 0x36, 0x30, 0x31, 0x31, 0x37, 0x32, 0x36, 0x5a, 0x18, 0x0f, 0x32, 0x30,
0x36, 0x32, 0x30, 0x35, 0x31, 0x36, 0x30, 0x32, 0x31, 0x37, 0x32, 0x36, 0x5a, 0x30,
0x12, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x07, 0x53, 0x53,
0x48, 0x20, 0x6b, 0x65, 0x79, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48,
0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07,
0x03, 0x42, 0x00, 0x04, 0x4f, 0x98, 0x63, 0x2f, 0x53, 0xbd, 0xab, 0xee, 0xbf, 0x69,
0x73, 0x3a, 0x84, 0x0f, 0xfd, 0x9f, 0x9d, 0xb3, 0xce, 0x5c, 0x1e, 0x1b, 0x84, 0x06,
0x63, 0x32, 0xff, 0x9c, 0x44, 0x0b, 0xce, 0x56, 0x13, 0x94, 0x00, 0x98, 0xe3, 0x46,
0xc2, 0xbc, 0x3d, 0xe6, 0x5e, 0xf2, 0x81, 0x4b, 0xbc, 0xea, 0x2b, 0x9d, 0x47, 0xcc,
0x9b, 0x5e, 0xbe, 0x1e, 0x2c, 0x69, 0x1d, 0xc3, 0x53, 0x4c, 0x89, 0x14, 0xa3, 0x12,
0x30, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d,
];
let _command = Command::<256>::try_from(apdu).unwrap();
}
#[test]
fn lc_oob() {
let apdu = &hex!("00C00000 00FF");
let _ = Command::<256>::try_from(apdu);
let apdu = &hex!("00C00000 0000");
let _ = Command::<256>::try_from(apdu);
}
}