use std::os::fd::RawFd;
use std::sync::Mutex;
use std::time::Duration;
use thiserror::Error;
pub const MAX_FRAME_LEN: usize = 1522;
#[derive(Debug, Error)]
pub enum TransportError {
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("receive buffer shorter than {MAX_FRAME_LEN} bytes")]
BufferTooSmall,
#[error("frame of {len} bytes does not fit the receive buffer")]
FrameTooLong {
len: usize,
},
}
pub trait EthTransport: Send + Sync {
fn send(&self, frame: &[u8]) -> Result<(), TransportError>;
fn recv_into(
&self,
buf: &mut [u8],
timeout: Option<Duration>,
) -> Result<Option<usize>, TransportError>;
fn recv(&self, timeout: Option<Duration>) -> Result<Option<Vec<u8>>, TransportError> {
let mut buf = vec![0u8; MAX_FRAME_LEN];
match self.recv_into(&mut buf, timeout)? {
Some(n) => {
buf.truncate(n);
Ok(Some(buf))
}
None => Ok(None),
}
}
fn raw_fd(&self) -> Option<RawFd> {
None
}
}
#[derive(Default)]
pub struct MockTransport {
tx: Mutex<Vec<Vec<u8>>>,
rx: Mutex<std::collections::VecDeque<Vec<u8>>>,
}
impl MockTransport {
pub fn new() -> Self {
Self::default()
}
pub fn push_rx(&self, frame: Vec<u8>) {
self.rx.lock().unwrap().push_back(frame);
}
pub fn sent(&self) -> Vec<Vec<u8>> {
self.tx.lock().unwrap().clone()
}
}
impl EthTransport for MockTransport {
fn send(&self, frame: &[u8]) -> Result<(), TransportError> {
self.tx.lock().unwrap().push(frame.to_vec());
Ok(())
}
fn recv_into(
&self,
buf: &mut [u8],
_timeout: Option<Duration>,
) -> Result<Option<usize>, TransportError> {
if buf.len() < MAX_FRAME_LEN {
return Err(TransportError::BufferTooSmall);
}
let mut rx = self.rx.lock().unwrap();
let Some(frame) = rx.front() else {
return Ok(None);
};
if frame.len() > buf.len() {
let len = frame.len();
rx.pop_front();
return Err(TransportError::FrameTooLong { len });
}
let frame = rx.pop_front().expect("front() was Some");
buf[..frame.len()].copy_from_slice(&frame);
Ok(Some(frame.len()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mock_records_sent_frames() {
let t = MockTransport::new();
t.send(&[1, 2, 3]).unwrap();
t.send(&[4, 5]).unwrap();
assert_eq!(t.sent(), vec![vec![1, 2, 3], vec![4, 5]]);
}
#[test]
fn mock_returns_pushed_rx_in_order_then_none() {
let t = MockTransport::new();
t.push_rx(vec![9, 9]);
assert_eq!(t.recv(None).unwrap(), Some(vec![9, 9]));
assert_eq!(t.recv(None).unwrap(), None);
}
#[test]
fn mock_recv_into_copies_the_frame_and_returns_its_length() {
let t = MockTransport::new();
t.push_rx(vec![1, 2, 3]);
let mut buf = [0u8; MAX_FRAME_LEN];
assert_eq!(t.recv_into(&mut buf, None).unwrap(), Some(3));
assert_eq!(&buf[..3], &[1, 2, 3]);
assert_eq!(t.recv_into(&mut buf, None).unwrap(), None);
}
#[test]
fn mock_recv_into_rejects_a_short_buffer_without_consuming() {
let t = MockTransport::new();
t.push_rx(vec![1, 2, 3]);
let mut small = [0u8; 16];
assert!(matches!(
t.recv_into(&mut small, None),
Err(TransportError::BufferTooSmall)
));
let mut buf = [0u8; MAX_FRAME_LEN];
assert_eq!(t.recv_into(&mut buf, None).unwrap(), Some(3));
}
#[test]
fn mock_recv_into_reports_a_frame_longer_than_the_buffer() {
let t = MockTransport::new();
t.push_rx(vec![0u8; MAX_FRAME_LEN + 1]);
let mut buf = [0u8; MAX_FRAME_LEN];
assert!(matches!(
t.recv_into(&mut buf, None),
Err(TransportError::FrameTooLong { len }) if len == MAX_FRAME_LEN + 1
));
}
#[test]
fn default_recv_returns_the_same_bytes_as_recv_into() {
let t = MockTransport::new();
t.push_rx(vec![7, 8, 9]);
assert_eq!(t.recv(None).unwrap(), Some(vec![7, 8, 9]));
}
}