use std::any::Any;
use std::collections::VecDeque;
use std::io::{self, Read, Write};
use std::net::{TcpListener, TcpStream};
use crate::device::DeviceContext;
use crate::time::VirtualNanos;
pub trait Peer: std::fmt::Debug + Send + Any {
fn receive(&mut self, word: u16, context: &DeviceContext<'_>);
fn transmit(&mut self, now: VirtualNanos) -> Option<u16>;
fn received(&self) -> &[u16] {
&[]
}
}
const CHUNK: usize = 4096;
const LOOK_PERIOD: VirtualNanos = VirtualNanos(1_000_000);
#[derive(Debug)]
pub struct Host {
listener: TcpListener,
stream: Option<TcpStream>,
incoming: VecDeque<u8>,
outgoing: VecDeque<u8>,
looked: Option<VirtualNanos>,
}
impl Host {
pub fn listen(port: u16) -> io::Result<Self> {
let listener = TcpListener::bind(("127.0.0.1", port))?;
listener.set_nonblocking(true)?;
Ok(Self {
listener,
stream: None,
incoming: VecDeque::new(),
outgoing: VecDeque::new(),
looked: None,
})
}
pub fn port(&self) -> io::Result<u16> {
self.listener.local_addr().map(|address| address.port())
}
fn serve(&mut self, now: VirtualNanos) {
let look = self
.looked
.is_none_or(|looked| now.0 >= looked.0.saturating_add(LOOK_PERIOD.0));
if look {
self.looked = Some(now);
if let Ok((stream, _)) = self.listener.accept()
&& stream.set_nonblocking(true).is_ok()
{
self.stream = Some(stream);
self.incoming.clear();
}
}
let Some(stream) = &mut self.stream else {
self.outgoing.clear();
return;
};
if look {
let mut buffer = [0; CHUNK];
loop {
match stream.read(&mut buffer) {
Ok(0) => {
self.stream = None;
self.outgoing.clear();
return;
}
Ok(count) => self.incoming.extend(&buffer[..count]),
Err(error) if error.kind() == io::ErrorKind::WouldBlock => break,
Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
Err(_) => {
self.stream = None;
self.outgoing.clear();
return;
}
}
}
}
while let Some(&byte) = self.outgoing.front() {
match stream.write(&[byte]) {
Ok(_) => {
self.outgoing.pop_front();
}
Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
Err(error) if error.kind() == io::ErrorKind::WouldBlock => break,
Err(_) => {
self.stream = None;
self.outgoing.clear();
return;
}
}
}
}
}
impl Peer for Host {
fn receive(&mut self, word: u16, context: &DeviceContext<'_>) {
self.outgoing
.push_back(u8::try_from(word & 0xFF).expect("masked to a byte"));
self.serve(context.now());
}
fn transmit(&mut self, now: VirtualNanos) -> Option<u16> {
self.serve(now);
self.incoming.pop_front().map(u16::from)
}
}
#[cfg(test)]
mod host_tests {
use std::net::TcpStream;
use super::*;
#[test]
fn the_socket_is_looked_at_once_a_period_and_a_byte_arrives_by_the_next_look() {
let mut host = Host::listen(0).unwrap();
let mut client = TcpStream::connect(("127.0.0.1", host.port().unwrap())).unwrap();
client.write_all(b"a").unwrap();
let mut now = VirtualNanos(0);
let first = loop {
if let Some(word) = host.transmit(now) {
break word;
}
now = VirtualNanos(now.0 + LOOK_PERIOD.0);
assert!(now.0 < 1_000 * LOOK_PERIOD.0, "the byte never arrived");
};
assert_eq!(first, u16::from(b'a'));
let looked_at = now;
client.write_all(b"b").unwrap();
for _ in 0..100 {
assert_eq!(
host.transmit(looked_at),
None,
"within the period the socket is not looked at again"
);
}
let mut now = VirtualNanos(looked_at.0 + LOOK_PERIOD.0);
let second = loop {
if let Some(word) = host.transmit(now) {
break word;
}
now = VirtualNanos(now.0 + LOOK_PERIOD.0);
assert!(
now.0 < looked_at.0 + 1_000 * LOOK_PERIOD.0,
"the byte never arrived"
);
};
assert_eq!(second, u16::from(b'b'));
}
}