use crate::{
CanAnyFrame, CanDataFrame, CanErrorFrame, CanFdFrame, CanFrame, CanId, CanRemoteFrame,
ConstructionError,
id::{CAN_ERR_FLAG, CAN_ERR_MASK, FdFlags},
};
use embedded_can::{Frame as EmbeddedFrame, Id};
use hex::FromHex;
use libc::canid_t;
use std::{
fmt,
fs::File,
io::{self, BufRead, BufReader},
path::Path,
str::FromStr,
};
use thiserror::Error;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Error, Debug)]
#[cfg_attr(feature = "serde", derive(Deserialize), serde(from = "ParseErrorRepr"))]
pub enum ParseError {
#[error(transparent)]
Io(#[from] io::Error),
#[error("Unexpected end of line")]
UnexpectedEndOfLine,
#[error("Invalid timestamp")]
InvalidTimestamp,
#[error("Invalid device name")]
InvalidDeviceName,
#[error("Invalid CAN frame")]
InvalidCanFrame,
#[error("Invalid frame direction")]
InvalidFrameDirection,
#[error(transparent)]
ConstructionError(#[from] ConstructionError),
}
#[cfg(feature = "serde")]
#[derive(Debug, Serialize, Deserialize)]
pub enum ParseErrorRepr {
Io {
kind: String,
message: String,
},
UnexpectedEndOfLine,
InvalidTimestamp,
InvalidDeviceName,
InvalidCanFrame,
InvalidFrameDirection,
ConstructionError(ConstructionError),
}
#[cfg(feature = "serde")]
impl From<&ParseError> for ParseErrorRepr {
fn from(err: &ParseError) -> Self {
use crate::errors::io_kind_name;
use ParseError::*;
match err {
Io(e) => Self::Io {
kind: io_kind_name(e.kind()).to_string(),
message: e.to_string(),
},
UnexpectedEndOfLine => Self::UnexpectedEndOfLine,
InvalidTimestamp => Self::InvalidTimestamp,
InvalidDeviceName => Self::InvalidDeviceName,
InvalidCanFrame => Self::InvalidCanFrame,
InvalidFrameDirection => Self::InvalidFrameDirection,
ConstructionError(e) => Self::ConstructionError(*e),
}
}
}
#[cfg(feature = "serde")]
impl Serialize for ParseError {
fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
ParseErrorRepr::from(self).serialize(ser)
}
}
#[cfg(feature = "serde")]
impl From<ParseErrorRepr> for ParseError {
fn from(repr: ParseErrorRepr) -> Self {
use crate::errors::io_kind_from_name;
match repr {
ParseErrorRepr::Io { kind, message } => {
Self::Io(io::Error::new(io_kind_from_name(&kind), message))
}
ParseErrorRepr::UnexpectedEndOfLine => Self::UnexpectedEndOfLine,
ParseErrorRepr::InvalidTimestamp => Self::InvalidTimestamp,
ParseErrorRepr::InvalidDeviceName => Self::InvalidDeviceName,
ParseErrorRepr::InvalidCanFrame => Self::InvalidCanFrame,
ParseErrorRepr::InvalidFrameDirection => Self::InvalidFrameDirection,
ParseErrorRepr::ConstructionError(e) => Self::ConstructionError(e),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Direction {
Received,
Transmitted,
}
impl FromStr for Direction {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"R" | "r" => Ok(Self::Received),
"T" | "t" => Ok(Self::Transmitted),
_ => Err(ParseError::InvalidFrameDirection),
}
}
}
impl fmt::Display for Direction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Received => "R",
Self::Transmitted => "T",
})
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CanDumpRecord {
pub t_us: u64,
pub device: String,
pub frame: CanAnyFrame,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub direction: Option<Direction>,
}
impl fmt::Display for CanDumpRecord {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"({}.{:06}) {} ",
self.t_us / 1_000_000,
self.t_us % 1_000_000,
self.device
)?;
fmt::UpperHex::fmt(&self.frame, f)?;
match self.direction {
Some(dir) => write!(f, " {dir}"),
None => Ok(()),
}
}
}
#[derive(Debug)]
pub struct Reader<R> {
rdr: R,
buf: String,
}
impl<R: io::Read> Reader<R> {
pub fn from_reader(rdr: R) -> Reader<BufReader<R>> {
Reader {
rdr: BufReader::new(rdr),
buf: String::with_capacity(256),
}
}
}
impl Reader<File> {
pub fn from_file<P: AsRef<Path>>(path: P) -> io::Result<Reader<BufReader<File>>> {
Ok(Reader::from_reader(File::open(path)?))
}
}
impl<R: BufRead> Reader<R> {
pub fn next_record(&mut self) -> Result<Option<CanDumpRecord>, ParseError> {
const MAX_LINE: u64 = 64 * 1024;
self.buf.clear();
let mut handle = io::Read::take(&mut self.rdr, MAX_LINE);
let nread = handle.read_line(&mut self.buf)?;
if nread == 0 {
return Ok(None);
}
if nread as u64 == MAX_LINE && !self.buf.ends_with('\n') {
return Err(ParseError::InvalidCanFrame);
}
let line = self.buf[..nread].trim();
let mut field_iter = line.split(' ');
let ts = field_iter.next().ok_or(ParseError::UnexpectedEndOfLine)?;
if ts.len() < 3 || !ts.starts_with('(') || !ts.ends_with(')') {
return Err(ParseError::InvalidTimestamp);
}
let ts = &ts[1..ts.len() - 1];
let t_us = match ts.split_once('.') {
Some((num, mant)) => {
if mant.len() != 6 {
return Err(ParseError::InvalidTimestamp);
}
let num = num
.parse::<u64>()
.map_err(|_| ParseError::InvalidTimestamp)?;
let mant = mant
.parse::<u64>()
.map_err(|_| ParseError::InvalidTimestamp)?;
num.checked_mul(1_000_000)
.and_then(|v| v.checked_add(mant))
.ok_or(ParseError::InvalidTimestamp)?
}
_ => return Err(ParseError::InvalidTimestamp),
};
let device = field_iter
.next()
.ok_or(ParseError::UnexpectedEndOfLine)?
.to_string();
let can_raw = field_iter.next().ok_or(ParseError::UnexpectedEndOfLine)?;
let (can_id_str, mut can_data) = match can_raw.split_once('#') {
Some((id, data)) => (id, data),
_ => return Err(ParseError::InvalidCanFrame),
};
let is_extended = match can_id_str.len() {
3 => false,
8 => true,
_ => return Err(ParseError::InvalidCanFrame),
};
let raw_id =
canid_t::from_str_radix(can_id_str, 16).map_err(|_| ParseError::InvalidCanFrame)?;
let frame: CanAnyFrame = if raw_id & CAN_ERR_FLAG != 0 {
Vec::from_hex(can_data)
.ok()
.and_then(|data| CanErrorFrame::new_error(raw_id & CAN_ERR_MASK, &data).ok())
.map(CanAnyFrame::Error)
} else {
let can_id: Id = if is_extended {
CanId::extended(raw_id)
} else {
CanId::standard(raw_id as u16)
}
.ok_or(ParseError::InvalidCanFrame)?
.into();
if can_data.starts_with('#') {
let fd_flags = can_data
.get(1..2)
.and_then(|s| u8::from_str_radix(s, 16).ok())
.map(FdFlags::from_bits_retain)
.ok_or(ParseError::InvalidCanFrame)?;
Vec::from_hex(&can_data[2..])
.ok()
.and_then(|data| CanFdFrame::with_flags(can_id, &data, fd_flags))
.map(CanAnyFrame::Fd)
} else if can_data.starts_with('R') {
can_data = &can_data[1..];
let rlen = if can_data.is_empty() {
0
} else {
usize::from_str_radix(can_data, 16).map_err(|_| ParseError::InvalidCanFrame)?
};
CanRemoteFrame::new_remote(can_id, rlen)
.map(CanFrame::Remote)
.map(CanAnyFrame::from)
} else {
Vec::from_hex(can_data)
.ok()
.and_then(|data| CanDataFrame::new(can_id, &data))
.map(CanFrame::Data)
.map(CanAnyFrame::from)
}
}
.ok_or(ParseError::InvalidCanFrame)?;
let direction = field_iter.next().map(Direction::from_str).transpose()?;
Ok(Some(CanDumpRecord {
t_us,
device,
frame,
direction,
}))
}
}
impl<R: BufRead> Iterator for Reader<R> {
type Item = Result<CanDumpRecord, ParseError>;
fn next(&mut self) -> Option<Self::Item> {
match self.next_record() {
Ok(Some(rec)) => Some(Ok(rec)),
Ok(None) => None,
Err(e) => Some(Err(e)),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{CanAnyFrame, Frame};
use embedded_can::Frame as EmbeddedFrame;
#[test]
fn test_simple_example() {
let input: &[u8] = b"(1469439874.299591) can1 080#\n\
(1469439874.299654) can1 701#7F";
let mut reader = Reader::from_reader(input);
let rec1 = reader.next_record().unwrap().unwrap();
assert_eq!(rec1.t_us, 1469439874299591);
assert_eq!(rec1.device, "can1");
if let CanAnyFrame::Normal(frame) = rec1.frame {
assert_eq!(frame.raw_id(), 0x080);
assert!(!frame.is_remote_frame());
assert!(!frame.is_error_frame());
assert!(!frame.is_extended());
assert_eq!(frame.data(), &[]);
} else {
panic!("Expected Normal frame, got FD");
}
let rec2 = reader.next_record().unwrap().unwrap();
assert_eq!(rec2.t_us, 1469439874299654);
assert_eq!(rec2.device, "can1");
if let CanAnyFrame::Normal(frame) = rec2.frame {
assert_eq!(frame.raw_id(), 0x701);
assert!(!frame.is_remote_frame());
assert!(!frame.is_error_frame());
assert!(!frame.is_extended());
assert_eq!(frame.data(), &[0x7F]);
} else {
panic!("Expected Normal frame, got FD");
}
assert!(reader.next_record().unwrap().is_none());
}
#[test]
fn test_extended_example() {
let input: &[u8] = b"(1469439874.299591) can1 00080080#\n\
(1469439874.299654) can1 00053701#7F";
let mut reader = Reader::from_reader(input);
let rec1 = reader.next_record().unwrap().unwrap();
assert_eq!(rec1.t_us, 1469439874299591);
assert_eq!(rec1.device, "can1");
if let CanAnyFrame::Normal(frame) = rec1.frame {
assert_eq!(frame.raw_id(), 0x080080);
assert!(!frame.is_remote_frame());
assert!(!frame.is_error_frame());
assert!(frame.is_extended());
assert_eq!(frame.data(), &[]);
} else {
panic!("Expected Normal frame, got FD");
}
let rec2 = reader.next_record().unwrap().unwrap();
assert_eq!(rec2.t_us, 1469439874299654);
assert_eq!(rec2.device, "can1");
if let CanAnyFrame::Normal(frame) = rec2.frame {
assert_eq!(frame.raw_id(), 0x053701);
assert!(!frame.is_remote_frame());
assert!(!frame.is_error_frame());
assert!(frame.is_extended());
assert_eq!(frame.data(), &[0x7F]);
} else {
panic!("Expected Normal frame, got FD");
}
assert!(reader.next_record().unwrap().is_none());
}
#[test]
fn test_remote() {
let input: &[u8] = b"(1469439874.299591) can0 00080080#R\n\
(1469439874.299654) can0 00053701#R4";
let mut reader = Reader::from_reader(input);
let rec1 = reader.next_record().unwrap().unwrap();
assert_eq!(rec1.t_us, 1469439874299591);
assert_eq!(rec1.device, "can0");
if let CanAnyFrame::Remote(frame) = rec1.frame {
assert_eq!(frame.raw_id(), 0x080080);
assert!(!frame.is_data_frame());
assert!(frame.is_remote_frame());
assert!(!frame.is_error_frame());
assert!(frame.is_extended());
assert_eq!(frame.len(), 0);
assert_eq!(frame.data(), &[]);
} else {
panic!("Expected Remote frame");
}
let rec2 = reader.next_record().unwrap().unwrap();
assert_eq!(rec2.t_us, 1469439874299654);
assert_eq!(rec2.device, "can0");
if let CanAnyFrame::Remote(frame) = rec2.frame {
assert_eq!(frame.raw_id(), 0x053701);
assert!(!frame.is_data_frame());
assert!(frame.is_remote_frame());
assert!(!frame.is_error_frame());
assert!(frame.is_extended());
assert_eq!(frame.len(), 4);
} else {
panic!("Expected Remote frame");
}
assert!(reader.next_record().unwrap().is_none());
}
#[test]
fn test_direction_round_trip() {
const LINES: &[&str] = &[
"(1788557985.236417) vcan0 123#DEADBEEF T",
"(1788557985.239414) vcan0 456#R T",
"(1788557985.242030) vcan0 789#R5 T",
"(1788557985.244919) vcan0 1F334455##5112233 T",
"(1788557985.247474) vcan0 20000004#000C000000000000 T",
"(1788557985.250307) vcan0 321# T",
];
for line in LINES {
let mut reader = Reader::from_reader(line.as_bytes());
let rec = reader.next_record().unwrap().unwrap();
assert_eq!(rec.direction, Some(Direction::Transmitted), "{line}");
assert_eq!(rec.to_string(), *line, "round-trip changed the line");
}
for (line, rendered) in [
("(1.000000) can0 123#01 R", "(1.000000) can0 123#01 R"),
("(1.000000) can0 123#01 r", "(1.000000) can0 123#01 R"),
] {
let mut reader = Reader::from_reader(line.as_bytes());
let rec = reader.next_record().unwrap().unwrap();
assert_eq!(rec.direction, Some(Direction::Received), "{line}");
assert_eq!(rec.to_string(), rendered);
}
}
#[test]
fn test_direction_absent() {
let line = "(1469439874.299591) can1 080#";
let mut reader = Reader::from_reader(line.as_bytes());
let rec = reader.next_record().unwrap().unwrap();
assert_eq!(rec.direction, None);
assert_eq!(rec.to_string(), line);
}
#[test]
fn test_invalid_direction_is_rejected() {
for line in ["(1.000000) can0 123#01 X", "(1.000000) can0 123#01 RX"] {
let mut reader = Reader::from_reader(line.as_bytes());
assert!(
matches!(reader.next_record(), Err(ParseError::InvalidFrameDirection)),
"{line:?} should be rejected"
);
}
let mut reader = Reader::from_reader(&b"(1.000000) can0 123#01 "[..]);
let rec = reader.next_record().unwrap().unwrap();
assert_eq!(rec.direction, None);
}
#[test]
fn test_remote_dlc_range() {
for dlc in 0..=8usize {
let line = if dlc == 0 {
"(1469439874.299591) can0 123#R".to_string()
} else {
format!("(1469439874.299591) can0 123#R{:X}", dlc)
};
let mut reader = Reader::from_reader(line.as_bytes());
let rec = reader.next_record().unwrap().unwrap();
match rec.frame {
CanAnyFrame::Remote(frame) => assert_eq!(frame.dlc(), dlc, "{line}"),
other => panic!("expected a remote frame, got {other:?}"),
}
assert_eq!(rec.to_string(), line, "round-trip changed the line");
}
for line in [
"(1469439874.299591) can0 123#R9",
"(1469439874.299591) can0 123#RF",
] {
let mut reader = Reader::from_reader(line.as_bytes());
assert!(
matches!(reader.next_record(), Err(ParseError::InvalidCanFrame)),
"{line} should be rejected"
);
}
assert!(CanRemoteFrame::remote_from_raw_id(0x123, 8).is_some());
assert!(CanRemoteFrame::remote_from_raw_id(0x123, 9).is_none());
}
#[test]
fn test_extended_id_fd() {
let input: &[u8] = b"(1234.567890) can0 12345678##500112233445566778899AABB";
let mut reader = Reader::from_reader(input);
let rec = reader.next_record().unwrap().unwrap();
let frame = CanFdFrame::try_from(rec.frame).unwrap();
assert!(frame.is_extended());
assert_eq!(0x12345678, frame.raw_id());
assert_eq!(5, frame.flags().bits());
assert_eq!(frame.dlc(), 0x09);
assert_eq!(frame.len(), 12);
assert_eq!(frame.data().len(), 12);
assert_eq!(
frame.data(),
&[
0x0, 0x011, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB
]
);
assert_eq!(
rec.to_string(),
"(1234.567890) can0 12345678##500112233445566778899AABB"
);
}
#[test]
fn test_error_frames() {
let input: &[u8] = b"(1785099856.242430) vcan0 20000004#000C000000000000\n\
(1785099856.243595) vcan0 200000A8#00009E0800000000";
let mut reader = Reader::from_reader(input);
let rec1 = reader.next_record().unwrap().unwrap();
assert_eq!(rec1.t_us, 1785099856242430);
assert_eq!(rec1.device, "vcan0");
if let CanAnyFrame::Error(frame) = rec1.frame {
assert!(frame.is_error_frame());
assert!(!frame.is_data_frame());
assert!(!frame.is_remote_frame());
assert_eq!(frame.error_bits(), 0x004);
assert_eq!(frame.data(), &[0, 0x0C, 0, 0, 0, 0, 0, 0]);
let err = frame.into_error();
assert_eq!(err.len(), 1);
} else {
panic!("Expected Error frame, got {:?}", rec1.frame);
}
let rec2 = reader.next_record().unwrap().unwrap();
assert_eq!(rec2.t_us, 1785099856243595);
if let CanAnyFrame::Error(frame) = rec2.frame {
assert_eq!(frame.error_bits(), 0x0A8);
assert_eq!(frame.data(), &[0, 0, 0x9E, 0x08, 0, 0, 0, 0]);
assert_eq!(frame.into_error().len(), 3);
} else {
panic!("Expected Error frame, got {:?}", rec2.frame);
}
assert!(reader.next_record().unwrap().is_none());
}
#[test]
fn test_error_frame_round_trip() {
const LINES: &[&str] = &[
"(1785099856.242430) vcan0 20000004#000C000000000000",
"(1785099856.243595) vcan0 200000A8#00009E0800000000",
"(1785099856.244721) vcan0 20000010#0000000044000000",
"(1785099856.245800) vcan0 20000040#0000000000000000",
"(1785099856.246900) vcan0 200003FF#070C9E08440A7060",
];
for line in LINES {
let mut reader = Reader::from_reader(line.as_bytes());
let rec = reader.next_record().unwrap().unwrap();
assert!(matches!(rec.frame, CanAnyFrame::Error(_)), "{line}");
assert_eq!(rec.to_string(), *line, "round-trip changed the line");
}
}
#[test]
fn test_extended_id_is_not_mistaken_for_error() {
let input: &[u8] = b"(1785099856.241425) vcan0 1FFFFFFF#0102";
let mut reader = Reader::from_reader(input);
let rec = reader.next_record().unwrap().unwrap();
if let CanAnyFrame::Normal(frame) = rec.frame {
assert!(frame.is_extended());
assert!(!frame.is_error_frame());
assert_eq!(frame.raw_id(), 0x1FFFFFFF);
assert_eq!(frame.data(), &[0x01, 0x02]);
} else {
panic!("Expected Normal frame, got {:?}", rec.frame);
}
assert_eq!(rec.to_string(), "(1785099856.241425) vcan0 1FFFFFFF#0102");
}
#[test]
fn test_timestamp_round_trip_is_exact() {
const CASES: &[u64] = &[
0,
1,
999_999,
1_000_000,
1_785_099_856_242_430, 4_294_967_295_004_142, 8_014_677_457_392_536, u64::MAX / 2,
];
for &t_us in CASES {
let line = format!("({}.{:06}) can0 123#01", t_us / 1_000_000, t_us % 1_000_000);
let mut reader = Reader::from_reader(line.as_bytes());
let rec = reader
.next_record()
.unwrap_or_else(|e| panic!("{line}: {e}"))
.expect("a record");
assert_eq!(rec.t_us, t_us, "{line}");
assert_eq!(rec.to_string(), line, "round-trip changed the line");
}
}
#[test]
fn test_record_body_is_frame_upper_hex() {
const BODIES: &[&str] = &[
"123#",
"123#01",
"7FF#1122334455667788",
"00000123#01",
"1FFFFFFF#0102",
"123#R",
"123#R4",
"00000123#R8",
"123##4",
"123##500112233",
"00000123##F00",
"20000004#000C000000000000",
"200003FF#070C9E08440A7060",
];
for body in BODIES {
let line = format!("(1469439874.299591) can0 {body}");
let mut reader = Reader::from_reader(line.as_bytes());
let rec = reader
.next_record()
.unwrap_or_else(|e| panic!("{line}: {e}"))
.expect("a record");
assert_eq!(
format!("{:X}", rec.frame),
*body,
"{line}: frame's own rendering"
);
assert_eq!(rec.to_string(), line, "{line}: record rendering");
}
}
#[test]
fn test_fd_flag_nibble_is_preserved() {
const FDF: u8 = 0x4;
for nibble in 0..=0xFu8 {
let line = format!("(1469439874.299591) can0 123##{nibble:X}00");
let mut reader = Reader::from_reader(line.as_bytes());
let rec = reader.next_record().unwrap().expect("a record");
match &rec.frame {
CanAnyFrame::Fd(frame) => {
assert_eq!(
frame.as_ref().flags,
nibble | FDF,
"{line}: every bit but the forced FDF must survive"
);
}
other => panic!("{line}: expected an FD frame, got {other:?}"),
}
let expected = format!("(1469439874.299591) can0 123##{:X}00", nibble | FDF);
assert_eq!(rec.to_string(), expected, "{line}");
}
}
#[test]
fn test_id_width_selects_the_format() {
let cases: &[(&str, bool)] = &[
("123#01", false),
("7FF#01", false),
("000#01", false),
("00000123#01", true), ("00000000#01", true),
("000007FF#01", true), ("1FFFFFFF#0102", true),
("00000123#R4", true), ("123#R4", false),
("00000123##500112233", true), ("123##500112233", false),
];
for (body, extended) in cases {
let line = format!("(1469439874.299591) can0 {body}");
let mut reader = Reader::from_reader(line.as_bytes());
let rec = reader
.next_record()
.unwrap_or_else(|e| panic!("{line}: {e}"))
.expect("a record");
let got = match &rec.frame {
CanAnyFrame::Normal(f) => f.is_extended(),
CanAnyFrame::Remote(f) => f.is_extended(),
CanAnyFrame::Fd(f) => f.is_extended(),
other => panic!("{line}: unexpected {other:?}"),
};
assert_eq!(got, *extended, "{line}: wrong format");
assert_eq!(rec.to_string(), line, "round-trip changed the line");
}
for body in ["800#AA", "FFF#AA", "1FFFFFFF0#01"] {
let line = format!("(1469439874.299591) can0 {body}");
let mut reader = Reader::from_reader(line.as_bytes());
assert!(
matches!(reader.next_record(), Err(ParseError::InvalidCanFrame)),
"{line} should be rejected"
);
}
for width in [1usize, 2, 4, 5, 6, 7, 9] {
let line = format!("(1469439874.299591) can0 {}#01", "0".repeat(width));
let mut reader = Reader::from_reader(line.as_bytes());
assert!(
matches!(reader.next_record(), Err(ParseError::InvalidCanFrame)),
"{width}-digit identifier should be rejected"
);
}
}
#[test]
fn test_len8_dlc_suffix_is_rejected() {
let input: &[u8] = b"(1469439874.299591) can1 123#1122334455667788_E";
let mut reader = Reader::from_reader(input);
assert!(matches!(
reader.next_record(),
Err(ParseError::InvalidCanFrame)
));
}
#[test]
fn test_fd() {
let input: &[u8] = b"(1469439874.299591) can1 080##0\n\
(1469439874.299654) can1 701##17F";
let mut reader = Reader::from_reader(input);
let rec1 = reader.next_record().unwrap().unwrap();
assert_eq!(rec1.t_us, 1469439874299591);
assert_eq!(rec1.device, "can1");
if let CanAnyFrame::Fd(frame) = rec1.frame {
assert_eq!(frame.raw_id(), 0x080);
assert!(!frame.is_remote_frame());
assert!(!frame.is_error_frame());
assert!(!frame.is_extended());
assert!(!frame.is_brs());
assert!(!frame.is_esi());
assert_eq!(0x04, frame.flags().bits());
assert_eq!(frame.dlc(), 0);
assert_eq!(frame.len(), 0);
assert_eq!(frame.data().len(), 0);
assert_eq!(frame.data(), &[]);
} else {
panic!("Expected FD frame, got Normal");
}
let rec2 = reader.next_record().unwrap().unwrap();
assert_eq!(rec2.t_us, 1469439874299654);
assert_eq!(rec2.device, "can1");
if let CanAnyFrame::Fd(frame) = rec2.frame {
assert_eq!(frame.raw_id(), 0x701);
assert!(!frame.is_remote_frame());
assert!(!frame.is_error_frame());
assert!(!frame.is_extended());
assert!(frame.is_brs());
assert!(!frame.is_esi());
assert_eq!(0x05, frame.flags().bits());
assert_eq!(frame.dlc(), 1);
assert_eq!(frame.len(), 1);
assert_eq!(frame.data().len(), 1);
assert_eq!(frame.data(), &[0x7F]);
} else {
panic!("Expected FD frame, got Normal");
}
assert!(reader.next_record().unwrap().is_none());
}
}