use std::sync::mpsc;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TransportError {
Disconnected,
Timeout,
SerializationError(String),
IoError(String),
}
impl std::fmt::Display for TransportError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TransportError::Disconnected => write!(f, "peer disconnected"),
TransportError::Timeout => write!(f, "operation timed out"),
TransportError::SerializationError(e) => write!(f, "serialization error: {}", e),
TransportError::IoError(e) => write!(f, "I/O error: {}", e),
}
}
}
pub trait NetworkTransport<M> {
fn send(&mut self, msg: M) -> Result<(), TransportError>;
fn recv(&mut self) -> Result<M, TransportError>;
fn try_recv(&mut self) -> Result<Option<M>, TransportError>;
}
pub struct ChannelTransport<M> {
tx: mpsc::Sender<M>,
rx: mpsc::Receiver<M>,
}
impl<M> ChannelTransport<M> {
pub fn new_pair() -> (Self, Self) {
let (tx_a, rx_b) = mpsc::channel();
let (tx_b, rx_a) = mpsc::channel();
(
ChannelTransport { tx: tx_a, rx: rx_a },
ChannelTransport { tx: tx_b, rx: rx_b },
)
}
}
impl<M> NetworkTransport<M> for ChannelTransport<M> {
fn send(&mut self, msg: M) -> Result<(), TransportError> {
self.tx.send(msg).map_err(|_| TransportError::Disconnected)
}
fn recv(&mut self) -> Result<M, TransportError> {
self.rx.recv().map_err(|_| TransportError::Disconnected)
}
fn try_recv(&mut self) -> Result<Option<M>, TransportError> {
match self.rx.try_recv() {
Ok(msg) => Ok(Some(msg)),
Err(mpsc::TryRecvError::Empty) => Ok(None),
Err(mpsc::TryRecvError::Disconnected) => Err(TransportError::Disconnected),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LinkRole {
Host,
Guest,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_send_recv() {
let (mut a, mut b) = ChannelTransport::new_pair();
a.send("hello".to_string()).unwrap();
assert_eq!(b.recv().unwrap(), "hello");
b.send("world".to_string()).unwrap();
assert_eq!(a.recv().unwrap(), "world");
}
#[test]
fn try_recv_empty_then_message() {
let (mut a, mut b) = ChannelTransport::new_pair();
assert_eq!(a.try_recv().unwrap(), None);
b.send(7).unwrap();
assert_eq!(a.try_recv().unwrap(), Some(7));
}
#[test]
fn pair_ends_are_independent() {
let (mut a, mut b) = ChannelTransport::new_pair();
a.send(1).unwrap();
assert_eq!(a.try_recv().unwrap(), None);
assert_eq!(b.try_recv().unwrap(), Some(1));
}
#[test]
fn drop_reports_disconnected_to_peer() {
let (a, mut b) = ChannelTransport::<u32>::new_pair();
drop(a);
assert_eq!(b.try_recv(), Err(TransportError::Disconnected));
assert_eq!(b.recv(), Err(TransportError::Disconnected));
}
#[test]
fn send_after_peer_drop_reports_disconnected() {
let (mut a, b) = ChannelTransport::new_pair();
drop(b);
assert_eq!(a.send(1), Err(TransportError::Disconnected));
}
#[test]
fn transport_error_display() {
assert_eq!(TransportError::Disconnected.to_string(), "peer disconnected");
assert_eq!(TransportError::Timeout.to_string(), "operation timed out");
assert_eq!(
TransportError::SerializationError("bad json".into()).to_string(),
"serialization error: bad json"
);
assert_eq!(
TransportError::IoError("reset".into()).to_string(),
"I/O error: reset"
);
}
}