use std::time::Duration;
use smallvec::SmallVec;
use crate::engine::format::bst::{BdtpDecoder, bst95_can_message, encode_bst95_can};
use crate::engine::format::iso11783_compose;
use crate::engine::format::mxpgn::{self, DataOrder, MINIPLEX_MXPGN_ALL};
use crate::engine::format::ydwg02::YDNU_RAW_MODE;
use super::Refused;
use super::canalyst::{Message, PACKET_LEN, Packets, encode_packets, messages};
use super::usb_can_a::{self, Frames, bitrates, config_packet, encode_frame};
pub use super::raw_can::CanFrame;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Event {
Frame(CanFrame),
Error(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct Capabilities {
pub sends: bool,
pub standard_frames: bool,
}
pub trait RawCodec {
fn capabilities(&self) -> Capabilities;
fn open(&mut self) -> Vec<u8> {
Vec::new()
}
fn receive(&mut self, bytes: &[u8], events: &mut Vec<Event>);
fn send(&mut self, frame: &CanFrame) -> Result<Vec<u8>, Refused>;
fn write_gap(&self) -> Duration {
Duration::ZERO
}
fn close(&mut self) -> Vec<u8> {
Vec::new()
}
}
impl<C: RawCodec + ?Sized> RawCodec for Box<C> {
fn capabilities(&self) -> Capabilities {
(**self).capabilities()
}
fn open(&mut self) -> Vec<u8> {
(**self).open()
}
fn receive(&mut self, bytes: &[u8], events: &mut Vec<Event>) {
(**self).receive(bytes, events)
}
fn send(&mut self, frame: &CanFrame) -> Result<Vec<u8>, Refused> {
(**self).send(frame)
}
fn write_gap(&self) -> Duration {
(**self).write_gap()
}
fn close(&mut self) -> Vec<u8> {
(**self).close()
}
}
fn check(frame: &CanFrame, standard: bool) -> Result<(), Refused> {
if frame.data.len() > 8 {
return Err(Refused::TooLarge);
}
if !frame.is_valid() || (!frame.extended && !standard) {
return Err(Refused::Unsupported);
}
Ok(())
}
#[derive(Debug, Default)]
pub struct Bst95 {
bdtp: BdtpDecoder,
}
impl Bst95 {
pub fn new() -> Self {
Self::default()
}
}
impl RawCodec for Bst95 {
fn capabilities(&self) -> Capabilities {
Capabilities {
sends: true,
standard_frames: false,
}
}
fn receive(&mut self, bytes: &[u8], events: &mut Vec<Event>) {
let dropped = self.bdtp.dropped;
for &b in bytes {
let Some(m) = self.bdtp.push_byte(b) else {
continue;
};
if let Some((id, data, false)) = bst95_can_message(&m) {
events.push(Event::Frame(CanFrame {
id,
extended: true,
data: SmallVec::from_slice(data),
}));
}
}
let dropped = self.bdtp.dropped - dropped;
if dropped > 0 {
events.push(Event::Error(format!(
"dropped {dropped} damaged BDTP frame(s)"
)));
}
}
fn send(&mut self, frame: &CanFrame) -> Result<Vec<u8>, Refused> {
check(frame, false)?;
let mut out = Vec::new();
encode_bst95_can(frame.id, &frame.data, true, &mut out);
Ok(out)
}
}
#[derive(Debug, Default)]
pub struct Canalyst {
packets: Packets,
}
impl Canalyst {
pub fn new() -> Self {
Self::default()
}
pub const PACKET_LEN: usize = PACKET_LEN;
}
impl RawCodec for Canalyst {
fn capabilities(&self) -> Capabilities {
Capabilities {
sends: true,
standard_frames: true,
}
}
fn receive(&mut self, bytes: &[u8], events: &mut Vec<Event>) {
self.packets.push(bytes, |packet| match messages(packet) {
Ok(ms) => events.extend(ms.iter().map(|m| {
Event::Frame(CanFrame {
id: m.id,
extended: m.extended,
data: SmallVec::from_slice(m.data),
})
})),
Err(count) => events.push(Event::Error(format!(
"CANalyst packet claims {count} messages"
))),
});
}
fn send(&mut self, frame: &CanFrame) -> Result<Vec<u8>, Refused> {
check(frame, true)?;
let mut out = Vec::new();
encode_packets(
&[Message {
id: frame.id,
extended: frame.extended,
data: &frame.data,
}],
&mut out,
);
Ok(out)
}
}
#[derive(Debug)]
pub struct UsbCanA {
config: [u8; usb_can_a::FRAME_LEN],
bitrate: u32,
frames: Frames,
}
impl UsbCanA {
pub fn new(bitrate: u32) -> Option<Self> {
Some(Self {
config: config_packet(bitrate, crate::engine::BusProtocol::Nmea2000)?,
bitrate,
frames: Frames::default(),
})
}
pub fn bitrates() -> impl Iterator<Item = u32> {
bitrates()
}
}
impl RawCodec for UsbCanA {
fn capabilities(&self) -> Capabilities {
Capabilities {
sends: true,
standard_frames: true,
}
}
fn open(&mut self) -> Vec<u8> {
self.config.to_vec()
}
fn receive(&mut self, bytes: &[u8], events: &mut Vec<Event>) {
let skipped = self.frames.skipped;
self.frames.push(bytes, |f| events.push(Event::Frame(f)));
if self.frames.skipped != skipped {
events.push(Event::Error(format!(
"skipped {} bytes to find a USB-CAN-A frame",
self.frames.skipped - skipped
)));
}
}
fn send(&mut self, frame: &CanFrame) -> Result<Vec<u8>, Refused> {
check(frame, true)?;
let mut out = Vec::new();
encode_frame(frame, &mut out);
Ok(out)
}
fn write_gap(&self) -> Duration {
usb_can_a::write_gap(self.bitrate)
}
}
const MAX_LINE: usize = 4096;
#[derive(Debug, Default)]
struct Lines {
acc: Vec<u8>,
dropping: bool,
}
impl Lines {
fn push(&mut self, mut bytes: &[u8], mut each: impl FnMut(&str)) -> usize {
let mut dropped = 0;
loop {
let end = bytes.iter().position(|&b| b == b'\n');
let part = &bytes[..end.unwrap_or(bytes.len())];
if !self.dropping {
if self.acc.len() + part.len() > MAX_LINE {
self.acc.clear();
self.dropping = true;
dropped += 1;
} else {
self.acc.extend_from_slice(part);
}
}
let Some(end) = end else {
return dropped;
};
if !self.dropping {
let line = String::from_utf8_lossy(&self.acc);
let line = line.trim();
if !line.is_empty() {
each(line);
}
}
self.acc.clear();
self.dropping = false;
bytes = &bytes[end + 1..];
}
}
fn overflow(dropped: usize) -> Option<Event> {
(dropped > 0).then(|| {
Event::Error(format!(
"dropped {dropped} line(s) longer than {MAX_LINE} bytes"
))
})
}
}
#[derive(Debug, Default)]
pub struct Ydwg {
lines: Lines,
init: &'static [u8],
}
impl Ydwg {
pub fn new() -> Self {
Self::default()
}
pub fn ydnu() -> Self {
Self {
lines: Lines::default(),
init: YDNU_RAW_MODE,
}
}
}
fn ydwg_line(line: &str) -> Result<CanFrame, String> {
let mut toks = line.split_whitespace().skip(1);
if !matches!(toks.next(), Some("R" | "T")) {
return Err("no direction".into());
}
let id = toks
.next()
.and_then(|t| u32::from_str_radix(t, 16).ok())
.ok_or("no CAN identifier")?;
let data = toks
.map(|t| u8::from_str_radix(t, 16).map_err(|_| format!("bad data byte {t:?}")))
.collect::<Result<SmallVec<[u8; 8]>, _>>()?;
let frame = CanFrame {
id,
extended: true,
data,
};
if frame.is_valid() {
Ok(frame)
} else {
Err("not a CAN frame".into())
}
}
impl RawCodec for Ydwg {
fn capabilities(&self) -> Capabilities {
Capabilities {
sends: false,
standard_frames: false,
}
}
fn open(&mut self) -> Vec<u8> {
self.init.to_vec()
}
fn receive(&mut self, bytes: &[u8], events: &mut Vec<Event>) {
let dropped = self.lines.push(bytes, |line| {
events.push(match ydwg_line(line) {
Ok(f) => Event::Frame(f),
Err(e) => Event::Error(format!("{line}: {e}")),
})
});
events.extend(Lines::overflow(dropped));
}
fn send(&mut self, _frame: &CanFrame) -> Result<Vec<u8>, Refused> {
Err(Refused::ReceiveOnly)
}
}
#[derive(Debug)]
pub struct Candump {
lines: Lines,
clock: fn() -> u64,
}
impl Default for Candump {
fn default() -> Self {
Self::new()
}
}
impl Candump {
pub fn new() -> Self {
Self {
lines: Lines::default(),
clock: || 0,
}
}
pub fn with_clock(clock: fn() -> u64) -> Self {
Self {
lines: Lines::default(),
clock,
}
}
}
impl RawCodec for Candump {
fn capabilities(&self) -> Capabilities {
Capabilities {
sends: true,
standard_frames: true,
}
}
fn receive(&mut self, bytes: &[u8], events: &mut Vec<Event>) {
let dropped = self.lines.push(
bytes,
|line| match crate::engine::format::candump::parse_can(line) {
Ok(Some(frame)) => events.push(Event::Frame(frame)),
Ok(None) => {}
Err(e) => events.push(Event::Error(format!("{line}: {e}"))),
},
);
events.extend(Lines::overflow(dropped));
}
fn send(&mut self, frame: &CanFrame) -> Result<Vec<u8>, Refused> {
check(frame, true)?;
let mut out = String::with_capacity(48);
crate::engine::format::candump::write_log(
&mut out,
frame.id,
frame.extended,
&frame.data,
(self.clock)(),
);
Ok(out.into_bytes())
}
}
#[derive(Debug)]
pub struct Mxpgn {
lines: Lines,
order: DataOrder,
init: &'static [u8],
}
impl Mxpgn {
pub fn miniplex() -> Self {
Self {
lines: Lines::default(),
order: DataOrder::Reversed,
init: MINIPLEX_MXPGN_ALL,
}
}
pub fn actisense() -> Self {
Self {
lines: Lines::default(),
order: DataOrder::Bus,
init: b"",
}
}
}
impl RawCodec for Mxpgn {
fn capabilities(&self) -> Capabilities {
Capabilities {
sends: false,
standard_frames: false,
}
}
fn open(&mut self) -> Vec<u8> {
self.init.to_vec()
}
fn receive(&mut self, bytes: &[u8], events: &mut Vec<Event>) {
let order = self.order;
let dropped = self.lines.push(bytes, |line| {
match mxpgn::parse_ordered(line, true, order) {
Ok(Some(f)) => events.push(Event::Frame(CanFrame {
id: iso11783_compose(f.prio, f.pgn, f.src, f.dst),
extended: true,
data: f.data,
})),
Ok(None) => {}
Err(e) => events.push(Event::Error(format!("{line}: {e}"))),
}
});
events.extend(Lines::overflow(dropped));
}
fn send(&mut self, _frame: &CanFrame) -> Result<Vec<u8>, Refused> {
Err(Refused::ReceiveOnly)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::format::bst::encode_bst95_can as bst95;
const MASTERBUS_ID: u32 = 0x1F_1F_02_A7;
fn frames(events: Vec<Event>) -> Vec<CanFrame> {
events
.into_iter()
.map(|e| match e {
Event::Frame(f) => f,
Event::Error(e) => panic!("{e}"),
})
.collect()
}
fn receive(codec: &mut impl RawCodec, bytes: &[u8]) -> Vec<CanFrame> {
let mut events = Vec::new();
codec.receive(bytes, &mut events);
frames(events)
}
fn ext(id: u32, data: &[u8]) -> CanFrame {
CanFrame::extended(id, data).unwrap()
}
#[test]
fn frames_are_checked_as_they_are_made() {
assert!(CanFrame::extended(0x1fff_ffff, &[0; 8]).is_some());
assert!(CanFrame::extended(0x2000_0000, &[]).is_none());
assert!(CanFrame::extended(1, &[0; 9]).is_none());
assert!(CanFrame::standard(0x7ff, &[]).is_some());
assert!(CanFrame::standard(0x800, &[]).is_none());
}
#[test]
fn bst95_passes_frames_unchanged_and_skips_its_echoes() {
let mut codec = Bst95::new();
let sent = ext(MASTERBUS_ID, &[1, 2, 3]);
let wire = codec.send(&sent).unwrap();
assert!(receive(&mut codec, &wire).is_empty(), "the echo");
let mut from_bus = Vec::new();
bst95(MASTERBUS_ID, &[1, 2, 3], false, &mut from_bus);
bst95(MASTERBUS_ID, &[4], false, &mut from_bus);
assert_eq!(
receive(&mut codec, &from_bus),
[sent, ext(MASTERBUS_ID, &[4])]
);
}
#[test]
fn candump_frames_go_both_ways() {
let mut codec = Candump::with_clock(|| 1_502_979_132_106);
let mut events = Vec::new();
codec.receive(
b"(1502979132.106111) can0 09F50374#000A00FF\n can0 123 [2] 01 02\n",
&mut events,
);
assert_eq!(
events,
[
Event::Frame(ext(0x09F5_0374, &[0x00, 0x0A, 0x00, 0xFF])),
Event::Frame(CanFrame::standard(0x123, &[1, 2]).unwrap()),
]
);
assert_eq!(
codec.send(&ext(0x09F5_0374, &[0xAB])).unwrap(),
b"(1502979132.106000) can0 09F50374#AB\n"
);
assert_eq!(
codec
.send(&CanFrame::standard(0x7FF, &[]).unwrap())
.unwrap(),
b"(1502979132.106000) can0 7FF#\n"
);
}
#[test]
fn what_a_codec_refuses_matches_its_capabilities() {
let standard = CanFrame::standard(0x123, &[1]).unwrap();
let extended = ext(MASTERBUS_ID, &[1]);
let codecs: [Box<dyn RawCodec>; 7] = [
Box::new(Candump::new()),
Box::new(Bst95::new()),
Box::new(Canalyst::new()),
Box::new(UsbCanA::new(250_000).unwrap()),
Box::new(Ydwg::new()),
Box::new(Mxpgn::miniplex()),
Box::new(Mxpgn::actisense()),
];
for mut codec in codecs {
let caps = codec.capabilities();
assert_eq!(codec.send(&extended).is_ok(), caps.sends, "{caps:?}");
assert_eq!(
codec.send(&standard).is_ok(),
caps.sends && caps.standard_frames,
"{caps:?}"
);
}
}
#[test]
fn bst95_refuses_standard_frames() {
let frame = CanFrame::standard(0x123, &[1]).unwrap();
assert_eq!(Bst95::new().send(&frame), Err(Refused::Unsupported));
}
#[test]
fn canalyst_round_trips_both_kinds() {
let mut codec = Canalyst::new();
let ours = [
ext(MASTERBUS_ID, &[0; 8]),
CanFrame::standard(0x7e6, &[9]).unwrap(),
];
for f in &ours {
let wire = codec.send(f).unwrap();
assert_eq!(wire.len(), Canalyst::PACKET_LEN);
assert_eq!(receive(&mut codec, &wire), std::slice::from_ref(f));
}
}
#[test]
fn usb_can_a_round_trips_and_spaces_its_writes() {
assert!(UsbCanA::new(123_456).is_none());
let mut codec = UsbCanA::new(250_000).unwrap();
assert_eq!(codec.open().len(), usb_can_a::FRAME_LEN);
assert_eq!(codec.write_gap(), Duration::from_micros(700));
let frame = ext(MASTERBUS_ID, &[1, 2, 3, 4, 5, 6, 7, 8]);
let wire = codec.send(&frame).unwrap();
assert_eq!(receive(&mut codec, &wire), [frame]);
}
#[test]
fn ydwg_reads_both_directions_and_sends_nothing() {
let mut codec = Ydwg::new();
assert!(codec.open().is_empty());
let got = receive(
&mut codec,
b"00:17:55.475 R 1F1F02A7 01 02\r\n00:17:55.476 T 0DF50B23 FF\r\n\r\n00:17",
);
assert_eq!(got, [ext(MASTERBUS_ID, &[1, 2]), ext(0x0DF50B23, &[0xff])]);
assert_eq!(
receive(&mut codec, b":55.477 R 1F1F02A7\n"),
[ext(MASTERBUS_ID, &[])]
);
assert_eq!(
codec.send(&ext(MASTERBUS_ID, &[])),
Err(Refused::ReceiveOnly)
);
assert_eq!(Ydwg::ydnu().open(), b"0\n");
}
#[test]
fn ydwg_reports_a_line_that_is_no_frame() {
let mut events = Vec::new();
Ydwg::new().receive(
b"00:17:55.475 R 1F1F02A7 01 02 03 04 05 06 07 08 09\n",
&mut events,
);
assert!(matches!(&events[..], [Event::Error(_)]), "{events:?}");
}
#[test]
fn a_line_too_long_is_dropped_up_to_its_end() {
let mut codec = Ydwg::new();
let mut events = Vec::new();
for _ in 0..10 {
codec.receive(&[b'A'; 1000], &mut events);
}
assert!(codec.lines.acc.len() <= MAX_LINE);
codec.receive(b"AAA\n00:17:55.475 R 1F1F02A7 01\n", &mut events);
let [Event::Error(e), Event::Frame(f)] = &events[..] else {
panic!("{events:?}");
};
assert!(e.contains("longer than 4096"), "{e}");
assert_eq!(f, &ext(MASTERBUS_ID, &[1]));
let mut lines = Lines::default();
let mut got = 0;
let long = [b'x'; MAX_LINE];
assert_eq!(lines.push(&long, |_| got += 1), 0);
assert_eq!(lines.push(b"\n", |l| got += l.len()), 0);
assert_eq!(got, MAX_LINE);
}
#[test]
fn mxpgn_gives_back_the_whole_identifier() {
let mut codec = Mxpgn::miniplex();
assert_eq!(codec.open(), MINIPLEX_MXPGN_ALL);
let mut line = String::new();
let raw = crate::engine::RawFrame::new(None, 7, 0x31F00, 0xA7, 0x02, [1, 2, 3]);
mxpgn::write_ordered(&mut line, &raw, false, DataOrder::Reversed).unwrap();
assert!(line.starts_with("$MXPGN,031F02,73A7,030201*"), "{line}");
line.push_str("\r\n$GPGLL,5300.97,N,00517.86,E*7F\r\n");
assert_eq!(
receive(&mut codec, line.as_bytes()),
[ext(MASTERBUS_ID, &[1, 2, 3])]
);
assert_eq!(
codec.send(&ext(MASTERBUS_ID, &[])),
Err(Refused::ReceiveOnly)
);
assert!(Mxpgn::actisense().open().is_empty());
}
}