use std::net::{Ipv4Addr, SocketAddr, TcpListener};
use crate::config::FrameSection;
use crate::error::HostError;
const FETCH_BAD_PORTS: &[u16] = &[
0, 1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 69, 77, 79, 87, 95, 101,
102, 103, 104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 137, 139, 143, 161, 179, 389, 427,
465, 512, 513, 514, 515, 526, 530, 531, 532, 540, 548, 554, 556, 563, 587, 601, 636, 989, 990,
993, 995, 1719, 1720, 1723, 2049, 3659, 4045, 4_190, 5060, 5061, 6000, 6566, 6665, 6666, 6667,
6668, 6669, 6679, 6697, 10080,
];
pub const PREFERRED_PAGE_PORT: u16 = 6010;
#[derive(Debug)]
pub struct PageServer {
listener: TcpListener,
addr: SocketAddr,
}
impl PageServer {
pub fn resolve(frame: &FrameSection) -> Result<Self, HostError> {
match frame.bind {
Some(addr) => Self::bind_exact(addr),
None => Self::walk_forward(),
}
}
fn bind_exact(addr: SocketAddr) -> Result<Self, HostError> {
match TcpListener::bind(addr) {
Ok(listener) => Self::from_listener(listener),
Err(source) => Err(HostError::PageServerUnavailable { addr, source }),
}
}
fn walk_forward() -> Result<Self, HostError> {
Self::walk_forward_from(PREFERRED_PAGE_PORT)
}
fn walk_forward_from(start_port: u16) -> Result<Self, HostError> {
for port in start_port..=u16::MAX {
if FETCH_BAD_PORTS.contains(&port) {
continue;
}
let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, port));
match TcpListener::bind(addr) {
Ok(listener) => return Self::from_listener(listener),
Err(source) if source.kind() == std::io::ErrorKind::AddrInUse => {}
Err(source) => return Err(HostError::PageServerUnavailable { addr, source }),
}
}
Err(HostError::NoFreePagePort { from: start_port })
}
fn from_listener(listener: TcpListener) -> Result<Self, HostError> {
let addr = listener.local_addr().map_err(|source| HostError::Bind {
addr: SocketAddr::from((Ipv4Addr::LOCALHOST, 0)),
source,
})?;
Ok(Self { listener, addr })
}
#[must_use]
pub const fn local_addr(&self) -> SocketAddr {
self.addr
}
#[must_use]
pub fn into_listener(self) -> TcpListener {
self.listener
}
}
#[cfg(test)]
mod tests {
use super::{FETCH_BAD_PORTS, PREFERRED_PAGE_PORT, PageServer};
use crate::config::FrameSection;
use crate::error::HostError;
use std::net::{Ipv4Addr, SocketAddr, TcpListener};
use std::path::PathBuf;
fn frame_section(bind: Option<SocketAddr>) -> FrameSection {
FrameSection {
bind,
assets: PathBuf::from("page/dist"),
auth_token: String::new(),
channel: Some("demo.events".to_owned()),
}
}
#[test]
fn preferred_page_port_is_not_fetch_blocked() {
assert!(
!FETCH_BAD_PORTS.contains(&PREFERRED_PAGE_PORT),
"preferred page port {PREFERRED_PAGE_PORT} is blocked by the WHATWG Fetch Standard"
);
}
#[test]
fn walk_forward_prefers_6010_when_free() -> Result<(), Box<dyn std::error::Error>> {
let page = PageServer::resolve(&frame_section(None))?;
assert!(
page.local_addr().port() >= PREFERRED_PAGE_PORT,
"walk-forward must land at or after the preferred port"
);
Ok(())
}
#[test]
fn walk_forward_skips_a_fetch_blocked_start() -> Result<(), Box<dyn std::error::Error>> {
let blocked_start = 4_190;
assert!(FETCH_BAD_PORTS.contains(&blocked_start));
let page = PageServer::walk_forward_from(blocked_start)?;
assert_ne!(page.local_addr().port(), blocked_start);
assert!(!FETCH_BAD_PORTS.contains(&page.local_addr().port()));
Ok(())
}
#[test]
fn walk_forward_skips_a_squatted_preferred_port() -> Result<(), Box<dyn std::error::Error>> {
let squatter =
TcpListener::bind(SocketAddr::from((Ipv4Addr::LOCALHOST, PREFERRED_PAGE_PORT)));
let Ok(squatter) = squatter else {
return Ok(());
};
let page = PageServer::resolve(&frame_section(None))?;
assert!(
page.local_addr().port() > PREFERRED_PAGE_PORT,
"the walk must skip the squatted preferred port"
);
drop(squatter);
Ok(())
}
#[test]
fn explicit_bind_that_is_taken_fails_loudly() -> Result<(), Box<dyn std::error::Error>> {
let held = TcpListener::bind(SocketAddr::from((Ipv4Addr::LOCALHOST, 0)))?;
let taken = held.local_addr()?;
let result = PageServer::resolve(&frame_section(Some(taken)));
let Err(HostError::PageServerUnavailable { addr, .. }) = result else {
return Err(format!("expected PageServerUnavailable, got {result:?}").into());
};
assert_eq!(addr, taken, "the refusal must name the taken address");
Ok(())
}
#[test]
fn explicit_bind_that_is_free_binds_exactly() -> Result<(), Box<dyn std::error::Error>> {
let scratch = TcpListener::bind(SocketAddr::from((Ipv4Addr::LOCALHOST, 0)))?;
let free = scratch.local_addr()?;
drop(scratch);
let page = PageServer::resolve(&frame_section(Some(free)))?;
assert_eq!(
page.local_addr(),
free,
"an explicit free bind must bind exactly, never move"
);
Ok(())
}
}