#[cfg(feature = "io")]
pub mod bst95;
#[cfg(feature = "io")]
pub mod bst_d0;
#[cfg(feature = "io")]
mod can_link;
#[cfg(feature = "io")]
pub mod canalyst;
#[cfg(feature = "io")]
pub mod canboat_csv;
#[cfg(feature = "io")]
pub mod candump;
#[cfg(feature = "io")]
pub mod ikonvert;
#[cfg(feature = "io")]
mod iso11783_node;
pub mod kind;
#[cfg(feature = "io")]
pub mod line_gateway;
#[cfg(feature = "io")]
pub mod maretron;
#[cfg(feature = "io")]
pub mod ngt1;
pub mod open;
pub mod raw;
#[cfg(feature = "io")]
pub mod replay;
#[cfg(feature = "io")]
pub mod socketcan;
pub mod supervisor;
#[cfg(feature = "io")]
pub mod usb_can_a;
pub use supervisor::DeviceFactory;
use std::io::{self, Read, Write};
use std::sync::atomic::{AtomicBool, AtomicU8, 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<F = RawFrame> {
Frame(F),
SendBytes(Vec<u8>),
Error(String),
Fatal(String),
}
pub trait DeviceDecoder<F = RawFrame>: Send + 'static {
fn decode(&mut self, bytes: &[u8], events: &mut Vec<DeviceEvent<F>>);
fn tick(&mut self, _events: &mut Vec<DeviceEvent<F>>) {}
}
pub trait DeviceEncoder<F = RawFrame>: Send + Sync + 'static {
fn init_bytes(&self) -> Vec<u8> {
Vec::new()
}
fn keepalive(&self) -> Option<(Duration, Vec<u8>)> {
None
}
fn encode_frame(&self, frame: &F) -> Option<Vec<u8>>;
fn shutdown_bytes(&self) -> Vec<u8> {
Vec::new()
}
fn write_gap(&self) -> Duration {
Duration::ZERO
}
fn next_deadline(&self) -> Option<u64> {
None
}
fn poll(&self, _now_ms: u64) -> Vec<u8> {
Vec::new()
}
}
pub(crate) enum WriterCmd<F = RawFrame> {
Bytes(Vec<u8>),
Frame(F),
Shutdown(Option<mpsc::Sender<bool>>),
}
pub struct DeviceHandle<F = RawFrame> {
pub frames_rx: mpsc::Receiver<F>,
cmd_tx: mpsc::Sender<WriterCmd<F>>,
joins: Vec<JoinHandle<()>>,
writer: Option<JoinHandle<()>>,
progress: Arc<AtomicU64>,
failure: DeviceFailure,
lost: DeviceFailure,
stop: Option<StopOnDrop>,
claim: Option<Arc<AtomicU8>>,
}
pub(crate) const CLAIM_UNCLAIMED: u8 = 254;
struct StopOnDrop(Arc<AtomicBool>);
impl Drop for StopOnDrop {
fn drop(&mut self) {
self.0.store(true, Ordering::Relaxed);
}
}
#[derive(Debug, Clone, Default)]
pub struct DeviceFailure(Arc<std::sync::OnceLock<String>>);
impl DeviceFailure {
pub fn get(&self) -> Option<&str> {
self.0.get().map(String::as_str)
}
pub(crate) fn set(&self, why: &str) {
let _ = self.0.set(why.to_owned());
}
}
#[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<F: Send + 'static> DeviceHandle<F> {
pub fn failure(&self) -> DeviceFailure {
self.failure.clone()
}
pub fn lost(&self) -> DeviceFailure {
self.lost.clone()
}
pub fn claimed_address(&self) -> Option<u8> {
self.claim
.as_ref()
.map(|claim| claim.load(Ordering::Relaxed))
.filter(|&address| address < CLAIM_UNCLAIMED)
}
#[cfg_attr(not(feature = "bridge"), allow(dead_code))]
pub(crate) fn claim_atom(&self) -> Option<Arc<AtomicU8>> {
self.claim.clone()
}
#[cfg_attr(not(feature = "io"), allow(dead_code))]
pub(crate) fn with_claim(mut self, claim: Arc<AtomicU8>) -> Self {
self.claim = Some(claim);
self
}
pub fn send_frame(&self, frame: F) -> Result<(), DeviceWriterGone> {
self.cmd_tx
.send(WriterCmd::Frame(frame))
.map_err(|_| DeviceWriterGone)
}
pub fn frame_sender(&self) -> FrameSender<F> {
FrameSender {
cmd_tx: self.cmd_tx.clone(),
}
}
pub fn join(self) {
drop(self.stop);
drop(self.frames_rx);
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<F> {
DeviceCloser {
cmd_tx: self.cmd_tx.clone(),
progress: self.progress.clone(),
stop: self.stop.as_ref().map(|stop| stop.0.clone()),
}
}
}
pub const CLOSE_TIMEOUT: Duration = Duration::from_secs(2);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Closed {
Confirmed,
Gone,
Unconfirmed,
}
pub struct DeviceCloser<F = RawFrame> {
cmd_tx: mpsc::Sender<WriterCmd<F>>,
progress: Arc<AtomicU64>,
stop: Option<Arc<AtomicBool>>,
}
impl<F> Clone for DeviceCloser<F> {
fn clone(&self) -> Self {
Self {
cmd_tx: self.cmd_tx.clone(),
progress: self.progress.clone(),
stop: self.stop.clone(),
}
}
}
impl<F> DeviceCloser<F> {
pub fn close(&self, timeout: Duration) -> Closed {
if let Some(stop) = &self.stop {
stop.store(true, Ordering::Relaxed);
}
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,
}
}
}
}
pub struct FrameSender<F = RawFrame> {
cmd_tx: mpsc::Sender<WriterCmd<F>>,
}
impl<F> Clone for FrameSender<F> {
fn clone(&self) -> Self {
Self {
cmd_tx: self.cmd_tx.clone(),
}
}
}
impl<F> FrameSender<F> {
pub fn send_frame(&self, frame: F) -> Result<(), DeviceWriterGone> {
self.cmd_tx
.send(WriterCmd::Frame(frame))
.map_err(|_| DeviceWriterGone)
}
}
#[cfg(test)]
impl<F> FrameSender<F> {
pub(crate) fn for_test() -> (Self, mpsc::Receiver<WriterCmd<F>>) {
let (cmd_tx, cmd_rx) = mpsc::channel();
(Self { cmd_tx }, cmd_rx)
}
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub(crate) fn from_parts<F>(
frames_rx: mpsc::Receiver<F>,
cmd_tx: mpsc::Sender<WriterCmd<F>>,
joins: Vec<JoinHandle<()>>,
progress: Arc<AtomicU64>,
lost: DeviceFailure,
) -> DeviceHandle<F> {
DeviceHandle {
frames_rx,
cmd_tx,
joins,
writer: None,
progress,
failure: DeviceFailure::default(),
lost,
stop: None,
claim: None,
}
}
pub fn run<F: Send + 'static, D: DeviceDecoder<F>, E: DeviceEncoder<F>>(
decoder: D,
encoder: E,
mut reader: Box<dyn Read + Send>,
mut writer: Box<dyn Write + Send>,
) -> DeviceHandle<F> {
let (frames_tx, frames_rx) = mpsc::channel::<F>();
let (cmd_tx, cmd_rx) = mpsc::channel::<WriterCmd<F>>();
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 failure = DeviceFailure::default();
let reader_failure = failure.clone();
let lost = DeviceFailure::default();
let reader_lost = lost.clone();
let reader_join = thread::Builder::new()
.name("device-reader".into())
.spawn(move || {
reader_thread(
&mut *reader,
decoder,
frames_tx,
cmd_tx_for_reader,
&reader_failure,
&reader_lost,
)
})
.expect("spawn device reader");
DeviceHandle {
frames_rx,
cmd_tx,
joins: vec![reader_join],
writer: Some(writer_join),
progress,
failure,
lost,
stop: None,
claim: None,
}
}
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 stray = Arc::new(Mutex::new(Vec::new()));
let decoder = CodecDecoder {
codec: codec.clone(),
fixtime,
events: Vec::with_capacity(8),
stray: stray.clone(),
};
run(decoder, CodecEncoder { codec, stray }, reader, writer)
}
pub(crate) 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>,
stray: Arc<Mutex<Vec<Event>>>,
}
impl<C> CodecDecoder<C> {
fn deliver(&mut self, out: &mut Vec<DeviceEvent>) {
let mut stray = std::mem::take(&mut *lock(&self.stray));
stray.append(&mut self.events);
out.extend(stray.into_iter().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>) {
let failure = {
let mut codec = lock(&self.codec);
codec.receive(bytes, now_ms(), &mut self.events);
codec.failure().map(str::to_owned)
};
self.deliver(events);
events.extend(failure.map(DeviceEvent::Fatal));
}
fn tick(&mut self, events: &mut Vec<DeviceEvent>) {
let failure = {
let mut codec = lock(&self.codec);
codec.tick(now_ms(), &mut self.events);
codec.failure().map(str::to_owned)
};
self.deliver(events);
events.extend(failure.map(DeviceEvent::Fatal));
}
}
struct CodecEncoder<C> {
codec: Arc<Mutex<C>>,
stray: Arc<Mutex<Vec<Event>>>,
}
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>> {
match lock(&self.codec).send_at(frame, now_ms()) {
Ok(bytes) => Some(bytes),
Err(why) => {
log::debug!("not sending PGN {}: {why}", frame.pgn);
None
}
}
}
fn shutdown_bytes(&self) -> Vec<u8> {
lock(&self.codec).close()
}
fn next_deadline(&self) -> Option<u64> {
lock(&self.codec).next_deadline()
}
fn poll(&self, now_ms: u64) -> Vec<u8> {
let mut events = Vec::new();
lock(&self.codec).tick(now_ms, &mut events);
let mut bytes = Vec::new();
let mut stray = lock(&self.stray);
for ev in events {
match ev {
Event::Send(b) => bytes.extend(b),
ev => stray.push(ev),
}
}
bytes
}
}
fn reader_thread<F, D: DeviceDecoder<F>>(
reader: &mut dyn Read,
mut decoder: D,
frames_tx: mpsc::Sender<F>,
cmd_tx: mpsc::Sender<WriterCmd<F>>,
failure: &DeviceFailure,
lost: &DeviceFailure,
) {
let mut buf = [0u8; 4096];
let mut events: Vec<DeviceEvent<F>> = Vec::with_capacity(8);
let deliver = |events: &mut Vec<DeviceEvent<F>>| -> 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}");
}
DeviceEvent::Fatal(e) => {
log::error!("{e}");
failure.set(&e);
return false;
}
}
}
true
};
loop {
match reader.read(&mut buf) {
Ok(0) => {
lost.set("the device ended the connection");
return;
}
Ok(n) => {
decoder.decode(&buf[..n], &mut events);
if !deliver(&mut events) {
return;
}
}
Err(e) if is_read_timeout(&e) => {
decoder.tick(&mut events);
if !deliver(&mut events) {
return;
}
}
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => {
let why = format!("device read error: {e}");
log::error!("{why}");
lost.set(&why);
return;
}
}
}
}
fn is_read_timeout(e: &io::Error) -> bool {
match e.kind() {
io::ErrorKind::WouldBlock => true,
io::ErrorKind::TimedOut => cfg!(windows) || e.raw_os_error().is_none(),
_ => false,
}
}
fn writer_thread<F, E: DeviceEncoder<F>>(
writer: &mut dyn Write,
encoder: E,
init: Vec<u8>,
keepalive: Option<(Duration, Vec<u8>)>,
rx: mpsc::Receiver<WriterCmd<F>>,
progress: &AtomicU64,
) {
let gap = encoder.write_gap();
let mut last_frame: Option<Instant> = None;
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 due = encoder
.next_deadline()
.map(|at| now + Duration::from_millis(at.saturating_sub(now_ms())));
let wake = match (due, next_keepalive) {
(Some(d), Some(k)) => Some(d.min(k)),
(d, k) => d.or(k),
};
let cmd = match wake {
Some(wake) => match rx.recv_timeout(wake.saturating_duration_since(now)) {
Ok(cmd) => cmd,
Err(mpsc::RecvTimeoutError::Timeout) if due == Some(wake) => {
match encoder.poll(now_ms()) {
b if b.is_empty() => continue,
b => WriterCmd::Bytes(b),
}
}
Err(mpsc::RecvTimeoutError::Timeout) => {
let Some((interval, payload)) = &keepalive else {
continue;
};
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,
},
None => 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) => {
if let Some(last) = last_frame {
let wait = (last + gap).saturating_duration_since(Instant::now());
if !wait.is_zero() {
thread::sleep(wait);
}
}
if !gap.is_zero() {
last_frame = Some(Instant::now());
}
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;
if self.0.is_multiple_of(2) {
Err(io::ErrorKind::WouldBlock.into())
} else {
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 the_writer_sends_at_the_codecs_deadlines() {
use crate::engine::BusProtocol;
use crate::engine::codec::bst95::Bst95;
use crate::engine::format::bst::{BdtpDecoder, bst95_message};
struct Quiet(u32);
impl Read for Quiet {
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
if self.0 == 0 {
return Ok(0);
}
self.0 -= 1;
thread::sleep(Duration::from_millis(250));
Err(io::ErrorKind::WouldBlock.into())
}
}
let sink = Sink::default();
let handle = run_codec(
Bst95::new(BusProtocol::J1939),
None,
Box::new(Quiet(4)),
Box::new(sink.clone()),
);
let started = Instant::now();
let bam = RawFrame::new(None, 6, 65260, 0x21, 255, [7u8; 20]);
handle.send_frame(bam).unwrap();
let packets = |sink: &Sink| {
let mut d = BdtpDecoder::default();
sink.0
.lock()
.unwrap()
.iter()
.filter_map(|&b| d.push_byte(b))
.filter_map(|m| bst95_message(&m))
.count()
};
while packets(&sink) < 4 && started.elapsed() < Duration::from_secs(2) {
thread::sleep(Duration::from_millis(5));
}
let took = started.elapsed();
assert_eq!(packets(&sink), 4, "the announcement and three packets");
assert!(took >= Duration::from_millis(150), "{took:?}");
assert!(
took < Duration::from_millis(600),
"not paced by the tick: {took:?}"
);
handle.close();
}
#[test]
fn a_codec_failure_ends_the_device() {
struct Picky(bool);
impl Codec for Picky {
fn receive(&mut self, bytes: &[u8], _now_ms: u64, events: &mut Vec<Event>) {
for &b in bytes {
if b == b'!' {
self.0 = true;
} else if !self.0 {
events.push(Event::Frame(RawFrame::new(None, 6, 127508, 1, 255, [b])));
}
}
}
fn send(
&mut self,
_frame: &RawFrame,
) -> Result<Vec<u8>, crate::engine::codec::Refused> {
Ok(Vec::new())
}
fn failure(&self) -> Option<&str> {
self.0.then_some("not the device")
}
}
let handle = run_codec(
Picky(false),
None,
Box::new(io::Cursor::new(b"a!".to_vec())),
Box::new(Sink::default()),
);
let failure = handle.failure();
let frames: Vec<RawFrame> = handle.frames_rx.iter().collect();
assert_eq!(frames.len(), 1);
assert_eq!(failure.get(), Some("not the device"));
}
#[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);
}
#[test]
fn a_read_error_loses_the_device() {
struct Reset;
impl Read for Reset {
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
Err(io::ErrorKind::ConnectionReset.into())
}
}
let handle = run(Ticker, NoEncoder, Box::new(Reset), Box::new(io::sink()));
let (failure, lost) = (handle.failure(), handle.lost());
assert_eq!(handle.frames_rx.iter().count(), 0);
assert_eq!(failure.get(), None);
assert_eq!(lost.get(), Some("device read error: connection reset"));
}
#[cfg(all(unix, feature = "io"))]
#[test]
fn a_connection_timing_out_loses_the_device() {
struct KeepaliveGaveUp;
impl Read for KeepaliveGaveUp {
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
Err(io::Error::from_raw_os_error(libc::ETIMEDOUT))
}
}
let handle = run(
Ticker,
NoEncoder,
Box::new(KeepaliveGaveUp),
Box::new(io::sink()),
);
let lost = handle.lost();
assert_eq!(handle.frames_rx.iter().count(), 0);
let why = lost.get().expect("the device is lost");
assert!(why.starts_with("device read error: "), "{why}");
assert_ne!(why, "the device ended the connection");
}
#[test]
fn only_a_read_timeout_is_read_on_after() {
assert!(is_read_timeout(&io::ErrorKind::WouldBlock.into()));
assert!(is_read_timeout(&io::Error::new(
io::ErrorKind::TimedOut,
"Operation timed out"
)));
assert!(!is_read_timeout(&io::ErrorKind::ConnectionReset.into()));
#[cfg(all(unix, feature = "io"))]
assert!(!is_read_timeout(&io::Error::from_raw_os_error(
libc::ETIMEDOUT
)));
}
#[test]
fn the_end_of_the_stream_loses_the_device() {
let handle = run(
Ticker,
NoEncoder,
Box::new(QuietThenEof(0)),
Box::new(io::sink()),
);
let lost = handle.lost();
assert_eq!(handle.frames_rx.iter().count(), 0);
assert_eq!(lost.get(), Some("the device ended the connection"));
}
}