pub mod canboat_csv;
pub mod ikonvert;
pub mod line_gateway;
pub mod maretron;
pub mod ngt1;
pub mod socketcan;
pub mod supervisor;
pub use supervisor::{DeviceFactory, Supervisor};
use std::io::{self, Read, Write};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, mpsc};
use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use crate::engine::RawFrame;
use crate::engine::codec::{Codec, Event};
#[derive(Debug)]
pub enum DeviceEvent {
Frame(RawFrame),
SendBytes(Vec<u8>),
Error(String),
}
pub trait DeviceDecoder: Send + 'static {
fn decode(&mut self, bytes: &[u8], events: &mut Vec<DeviceEvent>);
fn tick(&mut self, _events: &mut Vec<DeviceEvent>) {}
}
pub trait DeviceEncoder: Send + Sync + 'static {
fn init_bytes(&self) -> Vec<u8> {
Vec::new()
}
fn keepalive(&self) -> Option<(Duration, Vec<u8>)> {
None
}
fn encode_frame(&self, frame: &RawFrame) -> Option<Vec<u8>>;
fn shutdown_bytes(&self) -> Vec<u8> {
Vec::new()
}
}
pub(crate) enum WriterCmd {
Bytes(Vec<u8>),
Frame(RawFrame),
Shutdown(Option<mpsc::Sender<bool>>),
}
pub struct DeviceHandle {
pub frames_rx: mpsc::Receiver<RawFrame>,
cmd_tx: mpsc::Sender<WriterCmd>,
joins: Vec<JoinHandle<()>>,
writer: Option<JoinHandle<()>>,
progress: Arc<AtomicU64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DeviceWriterGone;
impl std::fmt::Display for DeviceWriterGone {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("device writer is no longer accepting frames")
}
}
impl std::error::Error for DeviceWriterGone {}
impl DeviceHandle {
pub fn send_frame(&self, frame: RawFrame) -> Result<(), DeviceWriterGone> {
self.cmd_tx
.send(WriterCmd::Frame(frame))
.map_err(|_| DeviceWriterGone)
}
pub fn frame_sender(&self) -> FrameSender {
FrameSender {
cmd_tx: self.cmd_tx.clone(),
}
}
pub fn join(self) {
drop(self.cmd_tx);
for j in self.joins.into_iter().chain(self.writer) {
let _ = j.join();
}
}
pub fn close(self) -> Closed {
let closed = self.closer().close(CLOSE_TIMEOUT);
match closed {
Closed::Confirmed | Closed::Gone => {
if let Some(writer) = self.writer {
let _ = writer.join();
}
}
Closed::Unconfirmed => {
log::warn!("the device did not confirm closing; leaving it");
}
}
closed
}
pub fn closer(&self) -> DeviceCloser {
DeviceCloser {
cmd_tx: self.cmd_tx.clone(),
progress: self.progress.clone(),
}
}
}
pub const CLOSE_TIMEOUT: Duration = Duration::from_secs(2);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Closed {
Confirmed,
Gone,
Unconfirmed,
}
#[derive(Clone)]
pub struct DeviceCloser {
cmd_tx: mpsc::Sender<WriterCmd>,
progress: Arc<AtomicU64>,
}
impl DeviceCloser {
pub fn close(&self, timeout: Duration) -> Closed {
let (done_tx, done_rx) = mpsc::channel();
if self
.cmd_tx
.send(WriterCmd::Shutdown(Some(done_tx)))
.is_err()
{
return Closed::Gone;
}
loop {
let before = self.progress.load(Ordering::Relaxed);
match done_rx.recv_timeout(timeout) {
Ok(true) => return Closed::Confirmed,
Ok(false) => return Closed::Unconfirmed,
Err(mpsc::RecvTimeoutError::Disconnected) => return Closed::Gone,
Err(mpsc::RecvTimeoutError::Timeout)
if self.progress.load(Ordering::Relaxed) != before => {}
Err(mpsc::RecvTimeoutError::Timeout) => return Closed::Unconfirmed,
}
}
}
}
#[derive(Clone)]
pub struct FrameSender {
cmd_tx: mpsc::Sender<WriterCmd>,
}
impl FrameSender {
pub fn send_frame(&self, frame: RawFrame) -> Result<(), DeviceWriterGone> {
self.cmd_tx
.send(WriterCmd::Frame(frame))
.map_err(|_| DeviceWriterGone)
}
pub(crate) fn from_cmd_tx(cmd_tx: mpsc::Sender<WriterCmd>) -> Self {
Self { cmd_tx }
}
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub(crate) fn from_parts(
frames_rx: mpsc::Receiver<RawFrame>,
cmd_tx: mpsc::Sender<WriterCmd>,
joins: Vec<JoinHandle<()>>,
progress: Arc<AtomicU64>,
) -> DeviceHandle {
DeviceHandle {
frames_rx,
cmd_tx,
joins,
writer: None,
progress,
}
}
pub fn run<D: DeviceDecoder, E: DeviceEncoder>(
decoder: D,
encoder: E,
mut reader: Box<dyn Read + Send>,
mut writer: Box<dyn Write + Send>,
) -> DeviceHandle {
let (frames_tx, frames_rx) = mpsc::channel::<RawFrame>();
let (cmd_tx, cmd_rx) = mpsc::channel::<WriterCmd>();
let cmd_tx_for_reader = cmd_tx.clone();
let init = encoder.init_bytes();
let keepalive = encoder.keepalive();
let progress = Arc::new(AtomicU64::new(0));
let writer_progress = progress.clone();
let writer_join = thread::Builder::new()
.name("device-writer".into())
.spawn(move || {
writer_thread(
&mut *writer,
encoder,
init,
keepalive,
cmd_rx,
&writer_progress,
)
})
.expect("spawn device writer");
let reader_join = thread::Builder::new()
.name("device-reader".into())
.spawn(move || reader_thread(&mut *reader, decoder, frames_tx, cmd_tx_for_reader))
.expect("spawn device reader");
DeviceHandle {
frames_rx,
cmd_tx,
joins: vec![reader_join],
writer: Some(writer_join),
progress,
}
}
pub fn run_codec<C: Codec + Send + 'static>(
codec: C,
fixtime: Option<String>,
reader: Box<dyn Read + Send>,
writer: Box<dyn Write + Send>,
) -> DeviceHandle {
let codec = Arc::new(Mutex::new(codec));
let decoder = CodecDecoder {
codec: codec.clone(),
fixtime,
events: Vec::with_capacity(8),
};
run(decoder, CodecEncoder { codec }, reader, writer)
}
fn now_ms() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}
fn lock<C>(codec: &Mutex<C>) -> std::sync::MutexGuard<'_, C> {
codec
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
struct CodecDecoder<C> {
codec: Arc<Mutex<C>>,
fixtime: Option<String>,
events: Vec<Event>,
}
impl<C> CodecDecoder<C> {
fn deliver(&mut self, out: &mut Vec<DeviceEvent>) {
out.extend(self.events.drain(..).map(|ev| match ev {
Event::Frame(mut f) => {
if let Some(ts) = &self.fixtime {
f.timestamp = Some(ts.clone());
}
DeviceEvent::Frame(f)
}
Event::Send(b) => DeviceEvent::SendBytes(b),
Event::Error(e) => DeviceEvent::Error(e),
}));
}
}
impl<C: Codec + Send + 'static> DeviceDecoder for CodecDecoder<C> {
fn decode(&mut self, bytes: &[u8], events: &mut Vec<DeviceEvent>) {
lock(&self.codec).receive(bytes, now_ms(), &mut self.events);
self.deliver(events);
}
fn tick(&mut self, events: &mut Vec<DeviceEvent>) {
lock(&self.codec).tick(now_ms(), &mut self.events);
self.deliver(events);
}
}
struct CodecEncoder<C> {
codec: Arc<Mutex<C>>,
}
impl<C: Codec + Send + 'static> DeviceEncoder for CodecEncoder<C> {
fn init_bytes(&self) -> Vec<u8> {
lock(&self.codec).open()
}
fn keepalive(&self) -> Option<(Duration, Vec<u8>)> {
lock(&self.codec).keepalive()
}
fn encode_frame(&self, frame: &RawFrame) -> Option<Vec<u8>> {
lock(&self.codec).send(frame).ok()
}
fn shutdown_bytes(&self) -> Vec<u8> {
lock(&self.codec).close()
}
}
fn reader_thread<D: DeviceDecoder>(
reader: &mut dyn Read,
mut decoder: D,
frames_tx: mpsc::Sender<RawFrame>,
cmd_tx: mpsc::Sender<WriterCmd>,
) {
let mut buf = [0u8; 4096];
let mut events: Vec<DeviceEvent> = Vec::with_capacity(8);
let deliver = |events: &mut Vec<DeviceEvent>| -> bool {
for ev in events.drain(..) {
match ev {
DeviceEvent::Frame(f) => {
if frames_tx.send(f).is_err() {
return false;
}
}
DeviceEvent::SendBytes(b) => {
if cmd_tx.send(WriterCmd::Bytes(b)).is_err() {
return false;
}
}
DeviceEvent::Error(e) => {
log::warn!("device decode error: {e}");
}
}
}
true
};
loop {
match reader.read(&mut buf) {
Ok(0) => return,
Ok(n) => {
decoder.decode(&buf[..n], &mut events);
if !deliver(&mut events) {
return;
}
}
Err(e) if e.kind() == io::ErrorKind::TimedOut => {
decoder.tick(&mut events);
if !deliver(&mut events) {
return;
}
}
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => {
log::error!("device read error: {e}");
return;
}
}
}
}
fn writer_thread<E: DeviceEncoder>(
writer: &mut dyn Write,
encoder: E,
init: Vec<u8>,
keepalive: Option<(Duration, Vec<u8>)>,
rx: mpsc::Receiver<WriterCmd>,
progress: &AtomicU64,
) {
if !init.is_empty() {
if let Err(e) = writer.write_all(&init) {
log::error!("device init write failed: {e}");
return;
}
let _ = writer.flush();
}
let mut next_keepalive = keepalive.as_ref().map(|(d, _)| Instant::now() + *d);
loop {
let now = Instant::now();
let cmd = match (next_keepalive, &keepalive) {
(Some(deadline), Some((interval, payload))) => {
let timeout = deadline.saturating_duration_since(now);
match rx.recv_timeout(timeout) {
Ok(cmd) => cmd,
Err(mpsc::RecvTimeoutError::Timeout) => {
if let Err(e) = writer.write_all(payload) {
log::warn!("device keepalive failed: {e}");
return;
}
let _ = writer.flush();
next_keepalive = Some(Instant::now() + *interval);
continue;
}
Err(mpsc::RecvTimeoutError::Disconnected) => return,
}
}
_ => match rx.recv() {
Ok(cmd) => cmd,
Err(_) => return,
},
};
let bytes = match cmd {
WriterCmd::Bytes(b) => b,
WriterCmd::Frame(f) => match encoder.encode_frame(&f) {
Some(b) => b,
None => continue,
},
WriterCmd::Shutdown(done) => {
let bytes = encoder.shutdown_bytes();
let sent = bytes.is_empty()
|| match writer.write_all(&bytes).and_then(|()| writer.flush()) {
Ok(()) => true,
Err(e) => {
log::warn!("device shutdown write failed: {e}");
false
}
};
if let Some(done) = done {
let _ = done.send(sent);
}
return;
}
};
if let Err(e) = writer.write_all(&bytes) {
log::error!("device write failed: {e}");
return;
}
let _ = writer.flush();
progress.fetch_add(1, Ordering::Relaxed);
if let Some((interval, _)) = keepalive.as_ref() {
next_keepalive = Some(Instant::now() + *interval);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct QuietThenEof(u32);
impl Read for QuietThenEof {
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
if self.0 == 0 {
return Ok(0);
}
self.0 -= 1;
Err(io::ErrorKind::TimedOut.into())
}
}
struct Ticker;
impl DeviceDecoder for Ticker {
fn decode(&mut self, _bytes: &[u8], _events: &mut Vec<DeviceEvent>) {}
fn tick(&mut self, events: &mut Vec<DeviceEvent>) {
events.push(DeviceEvent::Frame(RawFrame::new(
None,
0,
1,
0,
0,
std::iter::empty(),
)));
}
}
struct NoEncoder;
impl DeviceEncoder for NoEncoder {
fn encode_frame(&self, _frame: &RawFrame) -> Option<Vec<u8>> {
None
}
}
#[derive(Clone, Default)]
struct Sink(Arc<Mutex<Vec<u8>>>);
impl Write for Sink {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.lock().unwrap().extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[test]
fn run_codec_drives_a_codec_on_threads() {
use crate::engine::codec::maretron::{Config, Maretron};
use crate::engine::format::maretron_ipg::{
F1_SYNC_BIT, FRAME_SYNC, build_connect, build_frame, build_set_mode_binary,
};
let mut input = b"CONNECTED\t1234567\0".to_vec();
input.extend([
FRAME_SYNC,
F1_SYNC_BIT | (6 << 4) | (1 << 1),
0xF1,
0x01,
0x17,
1,
0x42,
]);
let sink = Sink::default();
let handle = run_codec(
Maretron::new(Config {
password: "pw".into(),
}),
Some("fixed".into()),
Box::new(io::Cursor::new(input)),
Box::new(sink.clone()),
);
let frame = handle.frames_rx.recv().expect("a frame");
assert_eq!(frame.pgn, 0xF101);
assert_eq!(frame.timestamp.as_deref(), Some("fixed"));
let out = RawFrame::new(None, 3, 127508, 17, 255, [1u8, 2, 3, 4, 5, 6, 7, 8]);
handle.send_frame(out.clone()).unwrap();
assert_eq!(handle.close(), Closed::Confirmed);
let mut expected = build_connect("pw");
expected.extend(build_set_mode_binary());
expected.extend(build_frame(out.pgn, out.prio, out.dst, &out.data).unwrap());
assert_eq!(*sink.0.lock().unwrap(), expected);
}
#[test]
fn read_timeouts_tick_the_decoder() {
let handle = run(
Ticker,
NoEncoder,
Box::new(QuietThenEof(3)),
Box::new(io::sink()),
);
let ticks = handle.frames_rx.iter().count();
assert_eq!(ticks, 3);
}
}