Make command serialization writer-based

This commit is contained in:
Sosthène Guédon
2023-09-22 11:27:49 +02:00
committed by sosthene-nitrokey
parent 2513de3757
commit 03445b9cb1
4 changed files with 192 additions and 59 deletions
+1
View File
@@ -13,6 +13,7 @@ documentation = "https://docs.rs/iso7816"
[dependencies]
delog = "0.1.2"
heapless = "0.7"
heapless-bytes = { version = "0.3.0", optional = true }
[features]
std = []
+58 -15
View File
@@ -5,7 +5,37 @@ use libfuzzer_sys::fuzz_target;
use arbitrary::Arbitrary;
use iso7816::command::{class, Command, CommandBuilder, CommandView};
use std::convert::Infallible;
use std::iter::repeat;
use std::ops::Deref;
struct WriteMock {
buffer: [u8; 4096],
written: usize,
capacity: usize,
}
impl Deref for WriteMock {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.buffer[..self.written]
}
}
impl iso7816::command::Writer for WriteMock {
type Error = Infallible;
fn write(&mut self, data: &[u8]) -> Result<usize, Infallible> {
let available = self.capacity - self.written;
let written = available.min(data.len());
self.buffer[self.written..][..written].copy_from_slice(data);
self.written += written;
Ok(written)
}
fn remaining_len(&self) -> usize {
self.capacity - self.written
}
}
#[derive(Debug, Arbitrary)]
struct Input<'a> {
@@ -47,12 +77,20 @@ fuzz_target!(|data: Input| {
command.clone().serialize_to_vec();
}
let buffer = &mut [0; 4096][..buf_len.min(4096).max(128)];
let mut buffer = WriteMock {
buffer: [0; 4096],
written: 0,
capacity: buf_len.min(4096).max(128),
};
match command.clone().serialize_into(buffer, supports_extended) {
Ok(len) => {
// dbg!(&buffer[..len][..len]);
let view = CommandView::try_from(&buffer[..len]).unwrap();
match command
.clone()
.serialize_into(&mut buffer, supports_extended)
.unwrap()
{
Ok(()) => {
// dbg!(&*buffer, buffer.len());
let view = CommandView::try_from(&*buffer).unwrap();
if !supports_extended {
assert!(view.data().len() <= 256);
assert!(!view.extended());
@@ -63,8 +101,10 @@ fuzz_target!(|data: Input| {
assert_eq!(view, command);
}
}
Err((len, mut rem)) => {
// dbg!(&buffer[..len]);
Err(mut rem) => {
let len = buffer.len();
// dbg!(&*buffer, buffer.len());
let mut parsed = Command::<4096>::try_from(&buffer[..len]).unwrap();
if !supports_extended {
assert!(parsed.data().len() <= 255);
@@ -72,11 +112,15 @@ fuzz_target!(|data: Input| {
}
// Loop with arbitrary buflens forever
for buflen in repeat(buf_lens.iter().chain([&128])).flatten() {
let buffer = &mut [0; 4096][..(*buflen).min(4096).max(128)];
match rem.serialize_into(buffer, supports_extended) {
Ok(len) => {
// dbg!(&buffer[..len]);
let view = CommandView::try_from(&buffer[..len]).unwrap();
let mut buffer = WriteMock {
buffer: [0; 4096],
written: 0,
capacity: (*buflen).min(4096).max(128),
};
match rem.serialize_into(&mut buffer, supports_extended).unwrap() {
Ok(()) => {
// dbg!(&*buffer, buffer.len());
let view = CommandView::try_from(&*buffer).unwrap();
if !supports_extended {
assert!(view.data().len() <= 255);
assert!(!view.extended());
@@ -92,11 +136,10 @@ fuzz_target!(|data: Input| {
}
return;
}
Err((len, new_rem)) => {
// dbg!(&buffer[..len]);
Err(new_rem) => {
rem = new_rem;
let view = CommandView::try_from(&buffer[..len]).unwrap();
let view = CommandView::try_from(&*buffer).unwrap();
if !supports_extended {
assert!(view.data().len() <= 255);
assert!(!view.extended());
+57 -44
View File
@@ -4,6 +4,9 @@ pub mod class;
pub mod instruction;
pub use instruction::Instruction;
mod writer;
pub use writer::Writer;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Command<const S: usize> {
class: class::Class,
@@ -247,27 +250,22 @@ impl<'a> CommandBuilder<'a> {
#[cfg(any(feature = "std", test))]
pub fn serialize_to_vec(self) -> Vec<u8> {
let required_len = self.required_len();
let mut buffer = vec![0; required_len];
assert_eq!(
self.serialize_into(&mut buffer, true).unwrap(),
required_len,
"internal error, serialization should fill the buffer"
);
let mut buffer = Vec::with_capacity(required_len);
self.serialize_into(&mut buffer, true).unwrap().unwrap();
buffer
}
/// Serialize the command into the given buffer and return the length of data returned to the buffer.
///
/// If the command does not fit in the buffer, fill the buffer with data and return another command
/// to be send containing the remaining data through command chaining
pub fn serialize_into(
/// - `Ok(Ok(()))` means that the command was successfully written
/// - `Ok(Err(command))` means that the writer ran out of space and that the command needed
/// - `Err(err)` means that there was an error from the writer
pub fn serialize_into<W: Writer>(
self,
buf: &mut [u8],
writer: &mut W,
supports_extended_length: bool,
) -> Result<usize, (usize, Self)> {
) -> Result<Result<(), Self>, W::Error> {
const HEADER_LEN: usize = 4;
if buf.len() < HEADER_LEN {
return Err((0, self));
if writer.remaining_len() < HEADER_LEN {
return Ok(Err(self));
}
let BuildingHeaderData {
@@ -280,15 +278,14 @@ impl<'a> CommandBuilder<'a> {
if !supports_extended_length {
max_data_len = 255;
}
let rem = &buf[HEADER_LEN..];
let available_data_len = rem
.len()
let available_data_len = (writer.remaining_len() - HEADER_LEN)
.saturating_sub(data_len.len() + expected_data_len.len())
.min(max_data_len);
if available_data_len < self.data.len() {
if available_data_len == 0 {
// Let's not support this case
return Err((0, self));
return Ok(Err(self));
}
let (send_now, send_later) = self.data.split_at(available_data_len);
@@ -310,22 +307,22 @@ impl<'a> CommandBuilder<'a> {
le,
};
// We know that the comman has enough space to be properly serialized
let sent = send_now
.serialize_into(buf, supports_extended_length)
send_now
.serialize_into(writer, supports_extended_length)?
.unwrap();
return Err((sent, send_later));
return Ok(Err(send_later));
}
buf[0] = self.class.into_inner();
buf[1] = self.instruction.into();
buf[2] = self.p1;
buf[3] = self.p2;
writer.write_all(&[
self.class.into_inner(),
self.instruction.into(),
self.p1,
self.p2,
])?;
let rem = &mut buf[HEADER_LEN..];
rem[..data_len.len()].copy_from_slice(&data_len);
rem[data_len.len()..][..self.data.len()].copy_from_slice(self.data);
rem[data_len.len() + self.data.len()..][..expected_data_len.len()]
.copy_from_slice(&expected_data_len);
Ok(HEADER_LEN + data_len.len() + self.data.len() + expected_data_len.len())
writer.write_all(&data_len)?;
writer.write_all(self.data)?;
writer.write_all(&expected_data_len)?;
Ok(Ok(()))
}
}
@@ -718,36 +715,52 @@ mod test {
fn building_chained() {
let cla = 0x00.try_into().unwrap();
let ins = 0x01.into();
let mut buffer = [0; 4096];
let mut buffer = heapless::Vec::<u8, 4096>::new();
let command = CommandBuilder::new(cla, ins, 2, 3, &[], 0xFFFF);
let len = command.clone().serialize_into(&mut buffer, true).unwrap();
assert_eq!(&buffer[..len], &command.clone().serialize_to_vec());
command
.clone()
.serialize_into(&mut buffer, true)
.unwrap()
.unwrap();
assert_eq!(&*buffer, &command.clone().serialize_to_vec());
buffer.clear();
// without extended length
let len = command.clone().serialize_into(&mut buffer, false).unwrap();
command
.clone()
.serialize_into(&mut buffer, false)
.unwrap()
.unwrap();
assert_eq!(
&buffer[..len],
&*buffer,
&CommandBuilder::new(cla, ins, 2, 3, &[], 0x0100).serialize_to_vec()
);
buffer.clear();
// without extended length
let command = CommandBuilder::new(cla, ins, 2, 3, &[], 0);
let len = command.clone().serialize_into(&mut buffer, false).unwrap();
command
.clone()
.serialize_into(&mut buffer, false)
.unwrap()
.unwrap();
assert_eq!(
&buffer[..len],
&*buffer,
&CommandBuilder::new(cla, ins, 2, 3, &[], 0).serialize_to_vec()
);
buffer.clear();
buffer = [0; 4096];
let mut buffer = heapless::Vec::<u8, 105>::new();
let command = CommandBuilder::new(cla, ins, 2, 3, &[5; 200], 0);
let (len, rem) = command
.serialize_into(&mut buffer[..105], false)
let rem = command
.serialize_into(&mut buffer, false)
.unwrap()
.unwrap_err();
assert_eq!(len, 105);
assert_eq!(buffer.len(), 105);
assert_eq!(rem, CommandBuilder::new(cla, ins, 2, 3, &[5; 100], 0));
assert_eq!(
&buffer[..len],
&*buffer,
&CommandBuilder::new(cla.as_chained(), ins, 2, 3, &[5; 100], 0).serialize_to_vec()
);
}
+76
View File
@@ -0,0 +1,76 @@
use core::convert::Infallible;
use core::fmt::{Debug, Display};
use core::mem::replace;
pub trait Writer {
type Error: Debug + Display;
fn write(&mut self, data: &[u8]) -> Result<usize, Self::Error>;
fn remaining_len(&self) -> usize;
/// data must be smaller than [`remaining_len`](Writer::remaining_len)
fn write_all(&mut self, data: &[u8]) -> Result<(), Self::Error> {
debug_assert!(data.len() <= self.remaining_len());
let mut offset = 0;
while offset < data.len() {
offset += self.write(data)?;
}
Ok(())
}
}
impl<'a> Writer for &'a mut [u8] {
type Error = Infallible;
fn write(&mut self, data: &[u8]) -> Result<usize, Infallible> {
let amt = data.len().min(self.len());
let (a, b) = replace(self, &mut []).split_at_mut(amt);
a.copy_from_slice(&data[..amt]);
*self = b;
Ok(amt)
}
fn remaining_len(&self) -> usize {
self.len()
}
}
impl<const N: usize> Writer for heapless::Vec<u8, N> {
type Error = Infallible;
fn write(&mut self, data: &[u8]) -> Result<usize, Infallible> {
let written_len = data.len().min(self.capacity() - self.len());
self.extend_from_slice(&data[..written_len]).unwrap();
Ok(written_len)
}
fn remaining_len(&self) -> usize {
self.capacity() - self.len()
}
}
#[cfg(feature = "heapless_bytes")]
impl<const N: usize> Writer for heapless_bytes::Bytes<N> {
type Error = Infallible;
fn write(&mut self, data: &[u8]) -> Result<usize, Infallible> {
let written_len = data.len().min(self.capacity() - self.len());
self.extend_from_slice(&data[..written_len]).unwrap();
Ok(written_len)
}
fn remaining_len(&self) -> usize {
self.capacity() - self.len()
}
}
#[cfg(any(feature = "std", test))]
impl Writer for Vec<u8> {
type Error = Infallible;
fn write(&mut self, data: &[u8]) -> Result<usize, Infallible> {
self.extend_from_slice(data);
Ok(data.len())
}
fn remaining_len(&self) -> usize {
usize::MAX
}
}