use std::sync::{Arc, Mutex};
use std::time::Duration;
use crate::engine::codec::Refused;
use crate::engine::codec::raw::{CanFrame, Event, RawCodec};
use super::{DeviceDecoder, DeviceEncoder, DeviceEvent, DeviceHandle, lock};
pub fn run<C: RawCodec + Send + 'static>(
codec: C,
reader: Box<dyn std::io::Read + Send>,
writer: Box<dyn std::io::Write + Send>,
) -> DeviceHandle<CanFrame> {
let codec = Arc::new(Mutex::new(codec));
let decoder = Decoder {
codec: codec.clone(),
events: Vec::with_capacity(8),
};
super::run(decoder, Encoder { codec }, reader, writer)
}
struct Decoder<C> {
codec: Arc<Mutex<C>>,
events: Vec<Event>,
}
impl<C: RawCodec + Send + 'static> DeviceDecoder<CanFrame> for Decoder<C> {
fn decode(&mut self, bytes: &[u8], events: &mut Vec<DeviceEvent<CanFrame>>) {
lock(&self.codec).receive(bytes, &mut self.events);
events.extend(self.events.drain(..).map(|ev| match ev {
Event::Frame(f) => DeviceEvent::Frame(f),
Event::Error(e) => DeviceEvent::Error(e),
}));
}
}
struct Encoder<C> {
codec: Arc<Mutex<C>>,
}
impl<C: RawCodec + Send + 'static> DeviceEncoder<CanFrame> for Encoder<C> {
fn init_bytes(&self) -> Vec<u8> {
lock(&self.codec).open()
}
fn encode_frame(&self, frame: &CanFrame) -> Option<Vec<u8>> {
lock(&self.codec)
.send(frame)
.map_err(|e| log::warn!("not sending CAN frame {:08X}: {e}", frame.id))
.ok()
}
fn shutdown_bytes(&self) -> Vec<u8> {
lock(&self.codec).close()
}
fn write_gap(&self) -> Duration {
lock(&self.codec).write_gap()
}
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
fn socketcan_check(frame: &CanFrame) -> Result<(), Refused> {
if frame.data.len() > 8 {
Err(Refused::TooLarge)
} else if !frame.is_valid() {
Err(Refused::Unsupported)
} else {
Ok(())
}
}
#[cfg(target_os = "linux")]
pub fn open_socketcan(iface: &str) -> std::io::Result<DeviceHandle<CanFrame>> {
linux::open(iface)
}
#[cfg(not(target_os = "linux"))]
pub fn open_socketcan(_iface: &str) -> std::io::Result<DeviceHandle<CanFrame>> {
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"SocketCAN is only available on Linux",
))
}
#[cfg(target_os = "linux")]
mod linux {
use std::io;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, mpsc};
use std::thread;
use std::time::{Duration, Instant};
use smallvec::SmallVec;
use socketcan::{CanSocket, EmbeddedFrame, ExtendedId, Id, Socket, StandardId};
use super::{CanFrame, socketcan_check};
use crate::io::device::{DeviceFailure, DeviceHandle, WriterCmd, from_parts};
const READ_TIMEOUT: Duration = Duration::from_millis(250);
const WRITE_WAIT: Duration = Duration::from_secs(1);
pub(super) fn open(iface: &str) -> io::Result<DeviceHandle<CanFrame>> {
let sock = CanSocket::open(iface)
.map_err(|e| io::Error::new(e.kind(), format!("opening {iface}: {e}")))?;
sock.set_read_timeout(READ_TIMEOUT)?;
let sock = Arc::new(sock);
let (frames_tx, frames_rx) = mpsc::channel::<CanFrame>();
let (cmd_tx, cmd_rx) = mpsc::channel::<WriterCmd<CanFrame>>();
let progress = Arc::new(AtomicU64::new(0));
let lost = DeviceFailure::default();
let stop = Arc::new(AtomicBool::new(false));
let (rsock, rlost, rstop) = (sock.clone(), lost.clone(), stop.clone());
let name = iface.to_string();
let reader = thread::Builder::new()
.name("socketcan-raw-reader".into())
.spawn(move || read(&rsock, &name, &frames_tx, &rlost, &rstop))?;
let (wprogress, wlost, name) = (progress.clone(), lost.clone(), iface.to_string());
let writer = thread::Builder::new()
.name("socketcan-raw-writer".into())
.spawn(move || {
write(&sock, &name, &cmd_rx, &wprogress, &wlost);
stop.store(true, Ordering::Relaxed);
})?;
Ok(from_parts(
frames_rx,
cmd_tx,
vec![reader, writer],
progress,
lost,
))
}
fn read(
sock: &CanSocket,
iface: &str,
frames_tx: &mpsc::Sender<CanFrame>,
lost: &DeviceFailure,
stop: &AtomicBool,
) {
while !stop.load(Ordering::Relaxed) {
let frame = match sock.read_frame() {
Ok(socketcan::CanFrame::Data(d)) => {
let (id, extended) = match d.id() {
Id::Standard(id) => (u32::from(id.as_raw()), false),
Id::Extended(id) => (id.as_raw(), true),
};
CanFrame {
id,
extended,
data: SmallVec::from_slice(d.data()),
}
}
Ok(_) => continue,
Err(e)
if matches!(
e.kind(),
io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut
) =>
{
continue;
}
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => {
let why = format!("CAN {iface}: read error: {e}");
log::error!("{why}");
lost.set(&why);
return;
}
};
if frames_tx.send(frame).is_err() {
return;
}
}
}
fn write(
sock: &CanSocket,
iface: &str,
cmd_rx: &mpsc::Receiver<WriterCmd<CanFrame>>,
progress: &AtomicU64,
lost: &DeviceFailure,
) {
while let Ok(cmd) = cmd_rx.recv() {
let frame = match cmd {
WriterCmd::Frame(f) => f,
WriterCmd::Bytes(_) => continue,
WriterCmd::Shutdown(done) => {
if let Some(done) = done {
let _ = done.send(true);
}
return;
}
};
let Some(can) = socketcan_check(&frame)
.map_err(|e| log::warn!("not sending CAN frame {:08X}: {e}", frame.id))
.ok()
.and_then(|()| to_socketcan(&frame))
else {
continue;
};
let deadline = Instant::now() + WRITE_WAIT;
loop {
match sock.write_frame(&can) {
Ok(()) => {
progress.fetch_add(1, Ordering::Relaxed);
break;
}
Err(e) if e.raw_os_error() == Some(libc::ENOBUFS) => {
if Instant::now() >= deadline {
log::warn!(
"CAN {iface}: transmit queue stays full, dropping frame {:08X}",
frame.id
);
break;
}
thread::sleep(Duration::from_millis(1));
}
Err(e) => {
let why = format!("CAN {iface}: write error: {e}");
log::error!("{why}");
lost.set(&why);
return;
}
}
}
}
}
fn to_socketcan(f: &CanFrame) -> Option<socketcan::CanFrame> {
let id = if f.extended {
Id::Extended(ExtendedId::new(f.id)?)
} else {
Id::Standard(StandardId::new(u16::try_from(f.id).ok()?)?)
};
socketcan::CanFrame::new(id, &f.data)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::codec::raw::{Bst95, UsbCanA, Ydwg};
use crate::engine::format::bst::encode_bst95_can;
use std::io::{self, Read, Write};
use std::time::Instant;
struct Once(Option<Vec<u8>>);
impl Read for Once {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self.0.take() {
Some(b) => {
buf[..b.len()].copy_from_slice(&b);
Ok(b.len())
}
None => {
std::thread::sleep(Duration::from_millis(50));
Err(io::ErrorKind::WouldBlock.into())
}
}
}
}
type Written = (Instant, Vec<u8>);
#[derive(Clone, Default)]
struct Recorder(Arc<Mutex<Vec<Written>>>);
impl Write for Recorder {
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
self.0.lock().unwrap().push((Instant::now(), data.to_vec()));
Ok(data.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[test]
fn frames_go_both_ways_unchanged() {
let id = 0x1F1F_02A7;
let mut wire = Vec::new();
encode_bst95_can(id, &[1, 2], false, &mut wire);
let out = Recorder::default();
let handle = run(
Bst95::new(),
Box::new(Once(Some(wire))),
Box::new(out.clone()),
);
let got = handle
.frames_rx
.recv_timeout(Duration::from_secs(2))
.unwrap();
assert_eq!(got, CanFrame::extended(id, &[1, 2]).unwrap());
handle
.send_frame(CanFrame::extended(id, &[3]).unwrap())
.unwrap();
assert_eq!(handle.close(), crate::io::device::Closed::Confirmed);
let mut want = Vec::new();
encode_bst95_can(id, &[3], true, &mut want);
let written: Vec<Vec<u8>> = out
.0
.lock()
.unwrap()
.iter()
.map(|(_, b)| b.clone())
.collect();
assert_eq!(written, [want]);
}
#[test]
fn a_receive_only_link_writes_nothing() {
let out = Recorder::default();
let handle = run(Ydwg::new(), Box::new(Once(None)), Box::new(out.clone()));
handle
.send_frame(CanFrame::extended(1, &[]).unwrap())
.unwrap();
assert_eq!(handle.close(), crate::io::device::Closed::Confirmed);
assert!(out.0.lock().unwrap().is_empty());
}
#[test]
fn writes_are_spaced_by_the_codecs_gap() {
let out = Recorder::default();
let handle = run(
UsbCanA::new(250_000).unwrap(),
Box::new(Once(None)),
Box::new(out.clone()),
);
for i in 0..5 {
handle
.send_frame(CanFrame::extended(i, &[i as u8]).unwrap())
.unwrap();
}
assert_eq!(handle.close(), crate::io::device::Closed::Confirmed);
let written = out.0.lock().unwrap();
assert_eq!(written.len(), 6);
for w in written[1..].windows(2) {
assert!(w[1].0 - w[0].0 >= Duration::from_micros(700));
}
}
#[test]
fn frames_socketcan_cannot_carry_are_refused() {
assert_eq!(
socketcan_check(&CanFrame {
id: 0x800,
extended: false,
data: Default::default()
}),
Err(Refused::Unsupported)
);
}
}