use std::io::{self, ErrorKind, Read};
use std::net::{Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4, TcpStream};
use std::time::{Duration, Instant};
use crate::Error as ImageryError;
use crate::imagery::{PREFIX_LEN, record_len};
pub const MAX_RECORD_LEN: usize = 64 << 20;
#[derive(Debug)]
enum TerminalState {
CleanEof,
Failed {
kind: ErrorKind,
message: String,
wire: Option<ImageryError>,
raw_os_error: Option<i32>,
},
}
impl TerminalState {
fn replay(&self) -> io::Result<bool> {
match self {
Self::CleanEof => Ok(false),
Self::Failed {
kind,
message,
wire,
raw_os_error,
} => {
let error = if let Some(wire) = wire {
io::Error::new(*kind, wire.clone())
} else if let Some(code) = raw_os_error {
io::Error::from_raw_os_error(*code)
} else {
io::Error::new(*kind, message.clone())
};
Err(error)
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Step {
Read(usize),
Eof,
Empty,
}
const POLL_MAX: Duration = Duration::from_millis(1);
const POLL_MIN: Duration = Duration::from_micros(50);
const GROW_MIN: usize = 64 * 1024;
const LATE_READ: usize = 1 << 20;
fn read_end(len: usize, filled: usize, late: bool) -> usize {
if late {
len.min(filled.saturating_add(LATE_READ))
} else {
len
}
}
#[derive(Debug)]
pub struct ImageClient {
stream: TcpStream,
terminal: Option<TerminalState>,
}
impl ImageClient {
pub fn connect(
interface: Ipv4Addr,
server: SocketAddrV4,
connect_timeout: Duration,
) -> io::Result<Self> {
validate_timeout(connect_timeout, "connect timeout")?;
let stream = TcpStream::connect_timeout(&SocketAddr::V4(server), connect_timeout)?;
if !interface.is_unspecified() {
let chosen = stream.local_addr()?.ip();
if chosen != interface {
return Err(io::Error::new(
ErrorKind::AddrNotAvailable,
format!(
"route to {server} left via {chosen}, not the requested interface \
{interface} — fix the host route or pass the interface actually in use"
),
));
}
}
Self::from_stream(stream, connect_timeout)
}
pub fn from_stream(stream: TcpStream, record_timeout: Duration) -> io::Result<Self> {
validate_timeout(record_timeout, "record timeout")?;
stream.set_nonblocking(true)?;
Ok(Self {
stream,
terminal: None,
})
}
pub fn peer_addr(&self) -> io::Result<SocketAddr> {
self.stream.peer_addr()
}
#[must_use]
pub fn is_terminal(&self) -> bool {
self.terminal.is_some()
}
pub fn local_addr(&self) -> io::Result<SocketAddr> {
self.stream.local_addr()
}
pub fn recv_record(&mut self, buf: &mut Vec<u8>, record_timeout: Duration) -> io::Result<bool> {
validate_timeout(record_timeout, "record timeout")?;
if let Some(terminal) = &self.terminal {
return terminal.replay();
}
let deadline = Instant::now().checked_add(record_timeout).ok_or_else(|| {
io::Error::new(
ErrorKind::InvalidInput,
"record timeout cannot be represented as an absolute deadline",
)
})?;
let mut prefix = [0u8; PREFIX_LEN];
let mut prefix_read = 0;
let mut poll = Poll::new();
while prefix_read < PREFIX_LEN {
let late = Instant::now() >= deadline;
match self.read_step(&mut prefix[prefix_read..]) {
Ok(Step::Read(count)) => {
prefix_read += count;
if prefix_read < PREFIX_LEN && late {
buf.clear();
return self.fail(deadline_expired());
}
poll.reset();
}
Ok(Step::Eof) if prefix_read == 0 => {
self.terminal = Some(TerminalState::CleanEof);
return Ok(false);
}
Ok(Step::Eof) => {
buf.clear();
return self.fail(truncated(PREFIX_LEN, prefix_read));
}
Ok(Step::Empty) if late && prefix_read == 0 => {
return Err(io::Error::new(
ErrorKind::TimedOut,
"no record arrived before the deadline",
));
}
Ok(Step::Empty) if late => {
buf.clear();
return self.fail(deadline_expired());
}
Ok(Step::Empty) => poll.wait(deadline),
Err(error) => {
buf.clear();
return self.fail(error);
}
}
}
let total = match record_len(&prefix) {
Ok(Some(total)) => total,
Ok(None) => {
buf.clear();
return self.fail(io::Error::new(
ErrorKind::InvalidData,
ImageryError::UnexpectedEof {
offset: 0,
needed: PREFIX_LEN,
available: prefix_read,
},
));
}
Err(error) => {
buf.clear();
return self.fail(io::Error::new(ErrorKind::InvalidData, error));
}
};
if total > MAX_RECORD_LEN {
buf.clear();
return self.fail(io::Error::new(
ErrorKind::InvalidData,
format!("record of {total} bytes exceeds MAX_RECORD_LEN ({MAX_RECORD_LEN})"),
));
}
buf.clear();
buf.extend_from_slice(&prefix);
self.read_body(buf, total, deadline, &mut poll)?;
debug_assert_eq!(buf.len(), total);
Ok(true)
}
fn read_body(
&mut self,
buf: &mut Vec<u8>,
total: usize,
deadline: Instant,
poll: &mut Poll,
) -> io::Result<()> {
let mut filled = buf.len();
while filled < total {
let late = Instant::now() >= deadline;
let room = if late {
LATE_READ
} else {
GROW_MIN.max(filled)
};
if buf.len() == filled || late {
let want = filled.saturating_add(room).min(total);
if buf.len() < want {
buf.resize(want, 0);
}
}
let end = read_end(buf.len(), filled, late);
match self.read_step(&mut buf[filled..end]) {
Ok(Step::Read(count)) => {
filled += count;
if filled < total && late {
buf.clear();
return self.fail(deadline_expired());
}
poll.reset();
}
Ok(Step::Eof) => {
buf.clear();
return self.fail(truncated(total, filled));
}
Ok(Step::Empty) if late => {
buf.clear();
return self.fail(deadline_expired());
}
Ok(Step::Empty) => poll.wait(deadline),
Err(error) => {
buf.clear();
return self.fail(error);
}
}
}
Ok(())
}
fn read_step(&mut self, destination: &mut [u8]) -> io::Result<Step> {
debug_assert_ne!(destination, []);
loop {
match self.stream.read(destination) {
Ok(0) => return Ok(Step::Eof),
Ok(count) => return Ok(Step::Read(count)),
Err(error) if error.kind() == ErrorKind::Interrupted => {}
Err(error) if error.kind() == ErrorKind::WouldBlock => return Ok(Step::Empty),
Err(error) => return Err(error),
}
}
}
fn fail<T>(&mut self, error: io::Error) -> io::Result<T> {
self.terminal = Some(TerminalState::Failed {
kind: error.kind(),
message: error.to_string(),
wire: error
.get_ref()
.and_then(|source| source.downcast_ref::<ImageryError>())
.cloned(),
raw_os_error: error.raw_os_error(),
});
let _ = self.stream.shutdown(Shutdown::Both);
Err(error)
}
}
fn validate_timeout(timeout: Duration, label: &str) -> io::Result<()> {
if timeout.is_zero() {
return Err(io::Error::new(
ErrorKind::InvalidInput,
format!("{label} must be finite and greater than zero"),
));
}
Ok(())
}
struct Poll {
next: Duration,
}
impl Poll {
const fn new() -> Self {
Self { next: POLL_MIN }
}
fn reset(&mut self) {
self.next = POLL_MIN;
}
fn wait(&mut self, deadline: Instant) {
let left = deadline.saturating_duration_since(Instant::now());
std::thread::sleep(self.next.min(left));
self.next = (self.next * 2).min(POLL_MAX);
}
}
fn deadline_expired() -> io::Error {
io::Error::new(ErrorKind::TimedOut, "record deadline expired")
}
fn truncated(needed: usize, available: usize) -> io::Error {
io::Error::new(
ErrorKind::UnexpectedEof,
ImageryError::UnexpectedEof {
offset: 0,
needed,
available,
},
)
}
#[cfg(test)]
mod tests {
use super::{LATE_READ, read_end};
#[test]
fn the_final_read_is_capped_at_late_read_even_with_more_room() {
let filled = 3 * LATE_READ;
let len = 2 * filled;
assert_eq!(read_end(len, filled, true), filled + LATE_READ);
assert_eq!(read_end(len, filled, false), len);
}
#[test]
fn the_final_read_never_reaches_past_the_buffer() {
assert_eq!(read_end(10, 4, true), 10);
assert_eq!(read_end(usize::MAX, usize::MAX - 1, true), usize::MAX);
}
}