use std::io;
use std::time::Duration;
use serialport as sp;
#[cfg(windows)]
use sp::COMPort as ExternSerial;
use sp::SerialPort;
#[cfg(unix)]
use sp::TTYPort as ExternSerial;
pub(crate) const UNKNOWN_BACKEND_NAME: &str = "<unknown backend>";
pub trait Backend: io::Read + io::Write {
fn set_read_timeout(&mut self, timeout: Option<Duration>) -> Result<(), io::Error>;
fn read_timeout(&self) -> Result<Option<Duration>, io::Error>;
fn name(&self) -> Option<String>;
}
impl<C: Backend + ?Sized> Backend for Box<C> {
fn set_read_timeout(&mut self, timeout: Option<Duration>) -> Result<(), io::Error> {
(**self).set_read_timeout(timeout)
}
fn read_timeout(&self) -> Result<Option<Duration>, io::Error> {
(**self).read_timeout()
}
fn name(&self) -> Option<String> {
(**self).name()
}
}
impl<C: Backend + ?Sized> Backend for &mut C {
fn set_read_timeout(&mut self, timeout: Option<Duration>) -> Result<(), io::Error> {
(**self).set_read_timeout(timeout)
}
fn read_timeout(&self) -> Result<Option<Duration>, io::Error> {
(**self).read_timeout()
}
fn name(&self) -> Option<String> {
(**self).name()
}
}
impl Backend for std::net::TcpStream {
fn set_read_timeout(&mut self, timeout: Option<Duration>) -> Result<(), io::Error> {
std::net::TcpStream::set_read_timeout(self, timeout)
}
fn read_timeout(&self) -> Result<Option<Duration>, io::Error> {
std::net::TcpStream::read_timeout(self)
}
fn name(&self) -> Option<String> {
self.local_addr().map(|addr| format!("{addr}")).ok()
}
}
#[derive(Debug)]
pub struct Serial(pub(crate) ExternSerial);
impl io::Read for Serial {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0.read(buf)
}
}
impl io::Write for Serial {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.0.flush()
}
}
impl Backend for Serial {
fn set_read_timeout(&mut self, timeout: Option<Duration>) -> Result<(), io::Error> {
Ok(self.0.set_timeout(timeout.unwrap_or(Duration::MAX))?)
}
fn read_timeout(&self) -> Result<Option<Duration>, io::Error> {
Ok(Some(self.0.timeout()))
}
fn name(&self) -> Option<String> {
self.0.name()
}
}
#[cfg(any(test, doc, feature = "mock"))]
#[cfg_attr(all(doc, feature = "doc_cfg"), doc(cfg(feature = "mock")))]
pub struct Mock {
buffer: io::Cursor<Vec<u8>>,
read_error: Option<io::Error>,
write_error: Option<io::Error>,
flush_error: Option<io::Error>,
set_read_timeout_error: Option<io::Error>,
ignored_read_timeout: Option<Duration>,
#[allow(clippy::type_complexity)]
write_callback: Box<dyn FnMut(&[u8], &mut dyn io::Write)>,
}
#[cfg(any(test, doc, feature = "mock"))]
impl Mock {
pub(crate) fn new() -> Self {
Self {
buffer: io::Cursor::new(Vec::new()),
read_error: None,
write_error: None,
flush_error: None,
set_read_timeout_error: None,
ignored_read_timeout: Some(Duration::ZERO),
write_callback: Box::new(|_, _| ()),
}
}
pub fn push<T: AsRef<[u8]>>(&mut self, bytes: T) {
self.buffer.get_mut().extend_from_slice(bytes.as_ref());
}
pub fn clear(&mut self) {
self.buffer.get_mut().clear();
self.buffer.set_position(0);
}
pub fn is_empty(&self) -> bool {
self.buffer.position() >= self.buffer.get_ref().len() as u64
}
pub fn set_read_error(&mut self, err: Option<io::Error>) {
self.read_error = err;
}
pub fn set_write_error(&mut self, err: Option<io::Error>) {
self.write_error = err;
}
pub fn set_flush_error(&mut self, err: Option<io::Error>) {
self.flush_error = err;
}
pub fn set_read_timeout_error(&mut self, err: Option<io::Error>) {
self.set_read_timeout_error = err;
}
pub fn set_write_callback(
&mut self,
callback: impl FnMut(&[u8], &mut dyn io::Write) + 'static,
) {
self.write_callback = Box::new(callback);
}
}
#[cfg(any(test, doc, feature = "mock"))]
impl std::fmt::Debug for Mock {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Mock").finish_non_exhaustive()
}
}
#[cfg(any(test, doc, feature = "mock"))]
impl Backend for Mock {
fn set_read_timeout(&mut self, timeout: Option<Duration>) -> Result<(), io::Error> {
if let Some(err) = self.set_read_timeout_error.take() {
Err(err)
} else {
self.ignored_read_timeout = timeout;
Ok(())
}
}
fn read_timeout(&self) -> Result<Option<Duration>, io::Error> {
Ok(self.ignored_read_timeout)
}
fn name(&self) -> Option<String> {
Some(format!("<mock 0x{:x}>", std::ptr::from_ref(self) as usize))
}
}
#[cfg(any(test, doc, feature = "mock"))]
impl io::Read for Mock {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if let Some(err) = self.read_error.take() {
Err(err)
} else if self.is_empty() {
Err(io::Error::new(
io::ErrorKind::TimedOut,
"Simulated timeout error",
))
} else {
self.buffer.read(buf)
}
}
}
#[cfg(any(test, doc, feature = "mock"))]
impl io::Write for Mock {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if let Some(err) = self.write_error.take() {
Err(err)
} else {
(self.write_callback)(buf, self.buffer.get_mut());
Ok(buf.len())
}
}
fn flush(&mut self) -> io::Result<()> {
if let Some(err) = self.flush_error.take() {
Err(err)
} else {
Ok(())
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn mock_set_write_callback() {
let mut mock = Mock::new();
fn fn_pointer_callback(_message: &[u8], _buffer: &mut dyn io::Write) {}
mock.set_write_callback(fn_pointer_callback);
mock.set_write_callback(|_: &[u8], _: &mut dyn io::Write| {});
let mut state = std::collections::HashSet::new();
mock.set_write_callback(move |message: &[u8], _buffer: &mut dyn io::Write| {
state.insert(message.to_vec());
});
}
}