use std::io;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SlotInfo {
pub num: u8,
pub module_ready: bool,
}
pub trait CaDevice {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>;
fn write(&mut self, buf: &[u8]) -> io::Result<()>;
fn reset(&mut self) -> io::Result<()>;
fn slot_info(&mut self) -> io::Result<SlotInfo>;
fn poll(&mut self, timeout: std::time::Duration) -> io::Result<bool>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DeviceOp {
Write(Vec<u8>),
Reset,
SlotInfo,
}
#[derive(Debug, Default)]
pub struct MockCaDevice {
pub inbound: std::collections::VecDeque<Vec<u8>>,
pub ops: Vec<DeviceOp>,
pub slot: SlotInfo,
}
impl MockCaDevice {
#[must_use]
pub fn new(inbound: impl IntoIterator<Item = Vec<u8>>) -> Self {
Self {
inbound: inbound.into_iter().collect(),
ops: Vec::new(),
slot: SlotInfo {
num: 0,
module_ready: true,
},
}
}
#[must_use]
pub fn written(&self) -> Vec<u8> {
self.ops
.iter()
.filter_map(|o| match o {
DeviceOp::Write(b) => Some(b.clone()),
_ => None,
})
.flatten()
.collect()
}
}
impl CaDevice for MockCaDevice {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self.inbound.pop_front() {
Some(frame) => {
let n = frame.len().min(buf.len());
buf[..n].copy_from_slice(&frame[..n]);
Ok(n)
}
None => Ok(0),
}
}
fn write(&mut self, buf: &[u8]) -> io::Result<()> {
self.ops.push(DeviceOp::Write(buf.to_vec()));
Ok(())
}
fn reset(&mut self) -> io::Result<()> {
self.ops.push(DeviceOp::Reset);
Ok(())
}
fn slot_info(&mut self) -> io::Result<SlotInfo> {
self.ops.push(DeviceOp::SlotInfo);
Ok(self.slot)
}
fn poll(&mut self, _timeout: std::time::Duration) -> io::Result<bool> {
Ok(!self.inbound.is_empty())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LinkEvent {
Tx(Vec<u8>),
Rx(Vec<u8>),
Reset,
SlotInfo(SlotInfo),
}
#[derive(Debug, Default)]
pub struct RecordingCaDevice<D> {
inner: D,
pub log: Vec<LinkEvent>,
}
impl<D: CaDevice> RecordingCaDevice<D> {
pub fn new(inner: D) -> Self {
Self {
inner,
log: Vec::new(),
}
}
#[must_use]
pub fn log(&self) -> &[LinkEvent] {
&self.log
}
pub fn inner(&self) -> &D {
&self.inner
}
}
impl<D: CaDevice> CaDevice for RecordingCaDevice<D> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let n = self.inner.read(buf)?;
if n > 0 {
self.log.push(LinkEvent::Rx(buf[..n].to_vec()));
}
Ok(n)
}
fn write(&mut self, buf: &[u8]) -> io::Result<()> {
self.log.push(LinkEvent::Tx(buf.to_vec()));
self.inner.write(buf)
}
fn reset(&mut self) -> io::Result<()> {
self.log.push(LinkEvent::Reset);
self.inner.reset()
}
fn slot_info(&mut self) -> io::Result<SlotInfo> {
let si = self.inner.slot_info()?;
self.log.push(LinkEvent::SlotInfo(si));
Ok(si)
}
fn poll(&mut self, timeout: std::time::Duration) -> io::Result<bool> {
self.inner.poll(timeout)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn recording_device_captures_both_directions() {
let inner = MockCaDevice::new([vec![0x83, 0x01, 0x01]]);
let mut dev = RecordingCaDevice::new(inner);
dev.reset().unwrap();
dev.write(&[0x82, 0x01, 0x01]).unwrap();
let mut buf = [0u8; 16];
dev.read(&mut buf).unwrap();
assert_eq!(
dev.log(),
&[
LinkEvent::Reset,
LinkEvent::Tx(vec![0x82, 0x01, 0x01]),
LinkEvent::Rx(vec![0x83, 0x01, 0x01]),
]
);
}
#[test]
fn mock_records_writes_and_replays_inbound() {
let mut dev = MockCaDevice::new([vec![0x01, 0x02], vec![0x03]]);
dev.write(&[0xAA, 0xBB]).unwrap();
dev.reset().unwrap();
let mut buf = [0u8; 16];
assert_eq!(dev.read(&mut buf).unwrap(), 2);
assert_eq!(&buf[..2], &[0x01, 0x02]);
assert_eq!(dev.read(&mut buf).unwrap(), 1);
assert_eq!(dev.read(&mut buf).unwrap(), 0); assert_eq!(dev.written(), vec![0xAA, 0xBB]);
assert_eq!(dev.ops[1], DeviceOp::Reset);
}
}