use std::io;
use byteorder;
use self::byteorder::{ByteOrder, BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt};
#[cfg(test)]
extern crate proptest;
#[cfg(test)]
#[macro_use]
extern crate assert_matches;
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct DltHeader {
pub is_big_endian: bool,
pub version: u8,
pub message_counter: u8,
pub length: u16,
pub ecu_id: Option<u32>,
pub session_id: Option<u32>,
pub timestamp: Option<u32>,
pub extended_header: Option<DltExtendedHeader>
}
#[derive(Debug)]
pub enum ReadError {
UnexpectedEndOfSlice { minimum_size: usize, actual_size: usize},
LengthSmallerThenMinimum { required_length: usize, length: usize },
IoError(io::Error)
}
impl From<io::Error> for ReadError {
fn from(err: io::Error) -> ReadError {
ReadError::IoError(err)
}
}
#[derive(Debug)]
pub enum WriteError {
VersionTooLarge(u8),
IoError(io::Error)
}
impl From<io::Error> for WriteError {
fn from(err: io::Error) -> WriteError {
WriteError::IoError(err)
}
}
const MAX_VERSION: u8 = 0b111;
const EXTDENDED_HEADER_FLAG: u8 = 0b1;
const BIG_ENDIAN_FLAG: u8 = 0b10;
const ECU_ID_FLAG: u8 = 0b100;
const SESSION_ID_FLAG: u8 = 0b1000;
const TIMESTAMP_FLAG: u8 = 0b10000;
impl DltHeader {
pub fn read<T: io::Read + Sized>(reader: &mut T) -> Result<DltHeader, ReadError> {
let header_type = reader.read_u8()?;
Ok(DltHeader{
is_big_endian: 0 != header_type & BIG_ENDIAN_FLAG,
version: (header_type >> 5) & MAX_VERSION,
message_counter: reader.read_u8()?,
length: reader.read_u16::<BigEndian>()?,
ecu_id: if 0 != header_type & ECU_ID_FLAG {
Some(reader.read_u32::<BigEndian>()?)
} else {
None
},
session_id: if 0 != header_type & SESSION_ID_FLAG {
Some(reader.read_u32::<BigEndian>()?)
} else {
None
},
timestamp: if 0 != header_type & TIMESTAMP_FLAG {
Some(reader.read_u32::<BigEndian>()?)
} else {
None
},
extended_header: if 0 != header_type & EXTDENDED_HEADER_FLAG {
Some(DltExtendedHeader{
message_info: reader.read_u8()?,
number_of_arguments: reader.read_u8()?,
application_id: reader.read_u32::<BigEndian>()?,
context_id: reader.read_u32::<BigEndian>()?
})
} else {
None
}
})
}
pub fn write<T: io::Write + Sized>(&self, writer: &mut T) -> Result<(), WriteError> {
if self.version > MAX_VERSION {
return Err(WriteError::VersionTooLarge(self.version))
}
writer.write_u8({
let mut result = 0;
if self.extended_header.is_some() {
result |= EXTDENDED_HEADER_FLAG;
}
if self.is_big_endian {
result |= BIG_ENDIAN_FLAG;
}
if self.ecu_id.is_some() {
result |= ECU_ID_FLAG;
}
if self.session_id.is_some() {
result |= SESSION_ID_FLAG;
}
if self.timestamp.is_some() {
result |= TIMESTAMP_FLAG;
}
result |= (self.version << 5) & 0b1110_0000;
result
})?;
writer.write_u8(self.message_counter)?;
writer.write_u16::<BigEndian>(self.length)?;
if let Some(value) = self.ecu_id {
writer.write_u32::<BigEndian>(value)?;
}
if let Some(value) = self.session_id {
writer.write_u32::<BigEndian>(value)?;
}
if let Some(value) = self.timestamp {
writer.write_u32::<BigEndian>(value)?;
}
match &self.extended_header {
Some(value) => {
writer.write_u8(value.message_info)?;
writer.write_u8(value.number_of_arguments)?;
writer.write_u32::<BigEndian>(value.application_id)?;
writer.write_u32::<BigEndian>(value.context_id)?;
},
None => {}
}
Ok(())
}
pub fn is_verbose(&self) -> bool {
match &self.extended_header {
None => false,
Some(ext) => ext.is_verbose()
}
}
pub fn header_len(&self) -> u16 {
4 + match self.ecu_id {
Some(_) => 4,
None => 0
} + match self.session_id {
Some(_) => 4,
None => 0
} + match self.timestamp {
Some(_) => 4,
None => 0
} + match self.extended_header {
Some(_) => 10,
None => 0
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct DltExtendedHeader {
pub message_info: u8,
pub number_of_arguments: u8,
pub application_id: u32,
pub context_id: u32
}
impl DltExtendedHeader {
pub fn new_non_verbose(application_id: u32, context_id: u32) -> DltExtendedHeader {
DltExtendedHeader {
message_info: 0,
number_of_arguments: 0,
application_id: application_id,
context_id: context_id
}
}
pub fn is_verbose(&self) -> bool {
0 != self.message_info & 0b1
}
pub fn set_is_verbose(&mut self, is_verbose: bool) {
if is_verbose {
self.message_info |= 0b1;
} else {
self.message_info &= 0b1111_1110;
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DltPacketSlice<'a> {
slice: &'a [u8],
header_len: usize
}
impl<'a> DltPacketSlice<'a> {
pub fn from_slice(slice: &'a [u8]) -> Result<DltPacketSlice<'_>, ReadError> {
if slice.len() < 4 {
return Err(ReadError::UnexpectedEndOfSlice{ minimum_size: 4, actual_size: slice.len()})
}
let length = BigEndian::read_u16(&slice[2..4]) as usize;
if slice.len() < length {
return Err(ReadError::UnexpectedEndOfSlice { minimum_size: length, actual_size: slice.len() });
}
let header_type = slice[0];
let header_len = if 0 != header_type & ECU_ID_FLAG {
4 + 4
} else {
4
};
let header_len = if 0 != header_type & SESSION_ID_FLAG {
header_len + 4
} else {
header_len
};
let header_len = if 0 != header_type & TIMESTAMP_FLAG {
header_len + 4
} else {
header_len
};
let header_len = if 0 != header_type & EXTDENDED_HEADER_FLAG {
header_len + 10
} else {
header_len
};
if length < header_len + 4 {
return Err(ReadError::LengthSmallerThenMinimum {
required_length: header_len + 4,
length
});
}
Ok(DltPacketSlice {
slice: &slice[..length],
header_len
})
}
pub fn has_extended_header(&self) -> bool {
0 != self.slice[0] & 0b1
}
pub fn is_big_endian(&self) -> bool {
0 != self.slice[0] & 0b10
}
pub fn is_verbose(&self) -> bool {
if self.has_extended_header() {
0 != self.slice[self.header_len - 10] & 0b1
} else {
false
}
}
pub fn message_id(&self) -> Option<u32> {
if self.is_verbose() {
None
} else {
let id_slice = &self.slice[self.header_len .. self.header_len + 4];
if self.is_big_endian() {
Some(BigEndian::read_u32(id_slice))
} else {
Some(LittleEndian::read_u32(id_slice))
}
}
}
pub fn slice(&self) -> &'a [u8] {
self.slice
}
pub fn payload(&self) -> &'a [u8] {
&self.slice[self.header_len..]
}
pub fn non_verbose_payload(&self) -> &'a [u8] {
&self.slice[usize::from(self.header_len) + 4..]
}
pub fn header(&self) -> DltHeader {
let header_type = self.slice[0];
let (is_big_endian, version) = {
let header_type = self.slice[0];
(0 != header_type & BIG_ENDIAN_FLAG,
(header_type >> 5) & MAX_VERSION)
};
let message_counter = self.slice[1];
let length = BigEndian::read_u16(&self.slice[2..4]);
let (ecu_id, slice) = if 0 != header_type & ECU_ID_FLAG {
(Some(BigEndian::read_u32(&self.slice[4..8])), &self.slice[8..])
} else {
(None, &self.slice[4..])
};
let (session_id, slice) = if 0 != header_type & SESSION_ID_FLAG {
(Some(BigEndian::read_u32(&slice[..4])), &slice[4..])
} else {
(None, slice)
};
let (timestamp, slice) = if 0 != header_type & TIMESTAMP_FLAG {
(Some(BigEndian::read_u32(&slice[..4])), &slice[4..])
} else {
(None, slice)
};
let extended_header = if 0 != header_type & EXTDENDED_HEADER_FLAG {
Some(DltExtendedHeader {
message_info: slice[0],
number_of_arguments: slice[1],
application_id: BigEndian::read_u32(&slice[2..6]),
context_id: BigEndian::read_u32(&slice[6..10])
})
} else {
None
};
DltHeader {
is_big_endian,
version,
message_counter,
length,
ecu_id,
session_id,
timestamp,
extended_header
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SliceIterator<'a> {
slice: &'a [u8]
}
impl<'a> SliceIterator<'a> {
pub fn new(slice: &'a [u8]) -> SliceIterator<'a> {
SliceIterator {
slice
}
}
}
impl<'a> Iterator for SliceIterator<'a> {
type Item = Result<DltPacketSlice<'a>, ReadError>;
fn next(&mut self) -> Option<Result<DltPacketSlice<'a>, ReadError>> {
if !self.slice.is_empty() {
let result = DltPacketSlice::from_slice(self.slice);
match &result {
Err(_) => {
let len = self.slice.len();
self.slice = &self.slice[len..];
}
Ok(ref value) => {
self.slice = &self.slice[value.slice().len()..];
}
}
Some(result)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::*;
use proptest::prelude::*;
use std::io::Cursor;
use std::io::Write;
prop_compose! {
fn extended_dlt_header_any()(message_info in any::<u8>(),
number_of_arguments in any::<u8>(),
application_id in any::<u32>(),
context_id in any::<u32>()) -> DltExtendedHeader
{
DltExtendedHeader {
message_info: message_info,
number_of_arguments: number_of_arguments,
application_id: application_id,
context_id: context_id
}
}
}
prop_compose! {
fn dlt_header_with_payload_any()(
payload_length in 4u32..1234
)(
is_big_endian in any::<bool>(),
version in prop::bits::u8::between(0,3),
message_counter in any::<u8>(),
ecu_id in any::<Option<u32>>(),
session_id in any::<Option<u32>>(),
timestamp in any::<Option<u32>>(),
extended_header in option::of(extended_dlt_header_any()),
payload in proptest::collection::vec(any::<u8>(), payload_length as usize)
) -> (DltHeader, Vec<u8>)
{
(
{
let mut header = DltHeader {
is_big_endian,
version,
message_counter,
length: payload.len() as u16,
ecu_id,
session_id,
timestamp,
extended_header
};
let header_size = header.header_len();
header.length = header_size + (payload.len() as u16);
header
},
payload
)
}
}
prop_compose! {
fn dlt_header_any()(is_big_endian in any::<bool>(),
version in prop::bits::u8::between(0,3),
message_counter in any::<u8>(),
length in any::<u16>(),
ecu_id in any::<Option<u32>>(),
session_id in any::<Option<u32>>(),
timestamp in any::<Option<u32>>(),
extended_header in option::of(extended_dlt_header_any())) -> DltHeader
{
DltHeader {
is_big_endian,
version,
message_counter,
length,
ecu_id,
session_id,
timestamp,
extended_header
}
}
}
proptest! {
#[test]
fn write_read(ref dlt_header in dlt_header_any()) {
let mut buffer = Vec::new();
dlt_header.write(&mut buffer).unwrap();
let mut reader = Cursor::new(&buffer[..]);
let result = DltHeader::read(&mut reader).unwrap();
assert_eq!(dlt_header, &result);
}
}
proptest! {
#[test]
fn read_length_error(ref dlt_header in dlt_header_any()) {
let mut buffer = Vec::new();
dlt_header.write(&mut buffer).unwrap();
let reduced_len = buffer.len() - 1;
let mut reader = Cursor::new(&buffer[..reduced_len]);
assert_matches!(DltHeader::read(&mut reader), Err(ReadError::IoError(_)));
}
}
proptest! {
#[test]
fn write_version_error(ref dlt_header in dlt_header_any(),
version in MAX_VERSION+1..std::u8::MAX) {
let mut input = dlt_header.clone();
input.version = version;
let mut buffer = Vec::new();
assert_matches!(input.write(&mut buffer), Err(WriteError::VersionTooLarge(_)));
}
}
proptest! {
#[test]
fn write_io_error(ref dlt_header in dlt_header_any()) {
let mut buffer: [u8;1] = [0];
let mut writer = Cursor::new(&mut buffer[..]);
assert_matches!(dlt_header.write(&mut writer), Err(WriteError::IoError(_)));
}
}
proptest! {
#[test]
fn packet_from_slice(ref packet in dlt_header_with_payload_any()) {
let mut buffer = Vec::new();
packet.0.write(&mut buffer).unwrap();
buffer.write(&packet.1[..]).unwrap();
let slice = DltPacketSlice::from_slice(&buffer[..]).unwrap();
assert_eq!(slice.header(), packet.0);
assert_eq!(slice.has_extended_header(), packet.0.extended_header.is_some());
assert_eq!(slice.is_big_endian(), packet.0.is_big_endian);
assert_eq!(slice.is_verbose(), packet.0.is_verbose());
assert_eq!(slice.payload(), &packet.1[..]);
{
let len = buffer.len();
assert_matches!(DltPacketSlice::from_slice(&buffer[..len-1]), Err(ReadError::UnexpectedEndOfSlice{
minimum_size: _,
actual_size: _
}));
}
}
}
proptest! {
#[test]
fn iterator(ref packets in prop::collection::vec(dlt_header_with_payload_any(), 1..5)) {
let mut buffer = Vec::with_capacity(
(*packets).iter().fold(0, |acc, x| acc + usize::from(x.0.header_len()) + x.1.len())
);
let mut offsets: Vec<(usize, usize)> = Vec::with_capacity(packets.len());
for packet in packets {
let start = buffer.len();
packet.0.write(&mut buffer).unwrap();
buffer.write_all(&packet.1).unwrap();
offsets.push((start, buffer.len()));
}
let mut expected: Vec<DltPacketSlice<'_>> = Vec::with_capacity(packets.len());
for offset in &offsets {
let slice = &buffer[offset.0..offset.1];
let e = DltPacketSlice::from_slice(slice).unwrap();
assert_eq!(e.slice(), slice);
expected.push(e);
}
assert_eq!(expected, SliceIterator::new(&buffer).map(|x| x.unwrap()).collect::<Vec<DltPacketSlice<'_>>>());
{
let o = offsets.first().unwrap();
let mut it = SliceIterator::new(&buffer[..(o.1 - 1)]);
assert_matches!(it.next(), Some(Err(ReadError::UnexpectedEndOfSlice{minimum_size: _, actual_size: _})));
assert_matches!(it.next(), None);
}
{
let o = offsets.last().unwrap();
let it = SliceIterator::new(&buffer[..(o.1 - 1)]);
let mut it = it.skip(offsets.len()-1);
assert_matches!(it.next(), Some(Err(ReadError::UnexpectedEndOfSlice{minimum_size: _, actual_size: _})));
assert_matches!(it.next(), None);
}
}
}
#[test]
fn packet_from_slice_header_len_eof_errors() {
{
let buffer = [1,2,3];
assert_matches!(DltPacketSlice::from_slice(&buffer[..]), Err(ReadError::UnexpectedEndOfSlice{
minimum_size: 4,
actual_size: 3
}));
}
{
let mut header: DltHeader = Default::default();
header.length = 5;
let mut buffer = Vec::new();
header.write(&mut buffer).unwrap();
assert_matches!(DltPacketSlice::from_slice(&buffer[..]), Err(ReadError::UnexpectedEndOfSlice{
minimum_size: 5,
actual_size: 4
}));
}
}
proptest! {
#[test]
fn packet_from_slice_header_variable_len_eof_errors(ref input in dlt_header_any()) {
let mut header = input.clone();
header.length = header.header_len() + 3;
let mut buffer = Vec::new();
header.write(&mut buffer).unwrap();
buffer.write(&[1,2,3]).unwrap();
assert_matches!(DltPacketSlice::from_slice(&buffer[..]), Err(ReadError::LengthSmallerThenMinimum{required_length: _, length: _}));
}
}
#[test]
fn test_debug() {
{
use crate::ReadError::*;
for value in [
UnexpectedEndOfSlice { minimum_size: 1, actual_size: 2},
LengthSmallerThenMinimum { required_length: 3, length: 4 },
IoError(std::io::Error::new(std::io::ErrorKind::Other, "oh no!"))
].iter() {
println!("{:?}", value);
}
}
{
use crate::WriteError::*;
for value in [
VersionTooLarge(123),
IoError(std::io::Error::new(std::io::ErrorKind::Other, "oh no!"))].iter()
{
println!("{:?}", value);
}
}
{
let header: DltHeader = Default::default();
let mut buffer = Vec::new();
header.write(&mut buffer).unwrap();
let slice = DltPacketSlice::from_slice(&buffer);
println!("{:?}", slice);
}
}
proptest! {
#[test]
fn new_non_verbose(application_id in any::<u32>(),
context_id in any::<u32>())
{
let header = DltExtendedHeader::new_non_verbose(application_id, context_id);
assert_eq!(0, header.message_info);
assert_eq!(0, header.number_of_arguments);
assert_eq!(application_id, header.application_id);
assert_eq!(context_id, header.context_id);
}
}
#[test]
fn ext_set_is_verbose() {
let mut header: DltExtendedHeader = Default::default();
let original = header.clone();
header.set_is_verbose(true);
assert_eq!(true, header.is_verbose());
header.set_is_verbose(false);
assert_eq!(false, header.is_verbose());
assert_eq!(original, header);
}
#[test]
fn is_verbose() {
let mut header: DltHeader = Default::default();
assert_eq!(false, header.is_verbose());
header.extended_header = Some(Default::default());
assert_eq!(false, header.is_verbose());
header.extended_header.as_mut().unwrap().set_is_verbose(true);
assert_eq!(true, header.is_verbose());
}
#[test]
fn message_id() {
let tests = [
(
{
let mut header: DltHeader = Default::default();
header.extended_header = Some(
{
let mut ext: DltExtendedHeader = Default::default();
ext.set_is_verbose(true);
ext
}
);
header
},
false
),
(
{
let mut header: DltHeader = Default::default();
header.extended_header = Some(
{
let mut ext: DltExtendedHeader = Default::default();
ext.set_is_verbose(false);
ext
}
);
header
},
true
),
(
{
let mut header: DltHeader = Default::default();
header.extended_header = None;
header
},
true
),
];
for t in tests.iter() {
{
let header = {
let mut header = t.0.clone();
header.is_big_endian = true;
header.length = header.header_len() + 4;
header
};
let mut buffer = Vec::<u8>::new();
header.write(&mut buffer).unwrap();
buffer.write_u32::<BigEndian>(0x1234_5678).unwrap();
let slice = DltPacketSlice::from_slice(&buffer).unwrap();
assert_eq!(
slice.message_id(),
if t.1 {
Some(0x1234_5678)
} else {
None
}
);
}
{
let header = {
let mut header = t.0.clone();
header.is_big_endian = false;
header.length = header.header_len() + 4;
header
};
let mut buffer = Vec::<u8>::new();
header.write(&mut buffer).unwrap();
buffer.write_u32::<LittleEndian>(0x1234_5678).unwrap();
let slice = DltPacketSlice::from_slice(&buffer).unwrap();
assert_eq!(
slice.message_id(),
if t.1 {
Some(0x1234_5678)
} else {
None
}
);
}
}
}
}