mod filter;
mod http;
mod listen;
pub(super) use filter::filter;
pub use listen::{serve, ServeIo};
use core::net::SocketAddr;
use std::io::{self, Read, Write};
use crate::canonical::{CanonicalError, CanonicalRequest, Event, ExitClass};
use crate::config::partial::{LossyMode, PartialIngress};
use crate::config::{PartialConfig, ResolvedConfig};
use crate::ingress::{
decode_request, encode_response, reinject, IngressId, IngressState, THINKING_REPLAY,
};
use crate::store::{Clock, ModelCache, ReplayStash};
use super::{generate, Host};
pub trait ServeConn: Read + Write + Send {}
impl<T: Read + Write + Send> ServeConn for T {}
pub trait Listener {
fn accept(&self) -> Option<Box<dyn ServeConn>>;
}
pub trait Bind {
fn bind(&self, addr: SocketAddr) -> io::Result<Box<dyn Listener>>;
}
pub(super) trait Respond {
fn begin(&mut self, status: u16, sse: bool) -> io::Result<()>;
fn chunk(&mut self, bytes: &[u8]) -> io::Result<()>;
fn end(&mut self) -> io::Result<()>;
}
pub(super) struct MasqIn<'a> {
pub dialect: IngressId,
pub reject: bool,
pub merged: PartialConfig,
pub stash: &'a ReplayStash,
pub cache: &'a dyn ModelCache,
}
pub(super) fn prepare(
cx: MasqIn,
body: &[u8],
) -> Result<(CanonicalRequest, ResolvedConfig, Vec<String>), CanonicalError> {
if let Some(table) = &cx.merged.ingress {
table.validate_lossy_overrides()?;
}
let mut req = decode_request(cx.dialect, body)?;
let adaptations = reinject(&mut req, cx.stash, cx.reject)?;
let req_model = (!req.model.is_empty()).then(|| req.model.clone());
let cfg = cx
.merged
.into_resolved(req_model.as_deref(), Some(cx.cache))?;
Ok((req, cfg, adaptations))
}
pub(super) fn turn(cx: MasqIn, body: &[u8], host: &Host, out: &mut dyn Respond) -> u8 {
let dialect = cx.dialect;
let stash = cx.stash;
let (req, cfg, adaptations) = match prepare(cx, body) {
Ok(p) => p,
Err(e) => return edge(dialect, e, host.clock, out),
};
let mut state = IngressState::for_request(&req, adaptations, host.clock);
let sse = state.stream;
let mut exit = ExitClass::Ok.code();
let mut begun = false;
let outcome: Result<(), io::Error> = (|| {
for ev in generate(req, cfg, host) {
if let Event::Error(e) = &ev {
exit = e.exit_code();
}
let bytes = encode_response(dialect, &ev, &mut state);
if bytes.is_empty() {
continue; }
if !begun {
out.begin(state.status(), sse)?;
begun = true;
}
out.chunk(&bytes)?;
}
for (key, payload) in state.take_stash() {
let _ = stash.stash(&key, &payload, host.clock);
}
out.end()
})();
match outcome {
Ok(()) => exit,
Err(io) => ExitClass::from_io(&io).code(),
}
}
pub(super) fn edge(
dialect: IngressId,
err: CanonicalError,
clock: &dyn Clock,
out: &mut dyn Respond,
) -> u8 {
let exit = err.exit_code();
let mut state = IngressState::for_request(&CanonicalRequest::default(), Vec::new(), clock);
let mut body = encode_response(dialect, &Event::Error(err), &mut state);
body.extend(encode_response(dialect, &Event::End, &mut state));
let _ = out
.begin(state.status(), false)
.and_then(|()| out.chunk(&body))
.and_then(|()| out.end());
exit
}
pub(super) fn reject_replay(table: Option<&PartialIngress>) -> bool {
table.is_some_and(|t| {
t.lossy_overrides
.get(THINKING_REPLAY)
.copied()
.unwrap_or_else(|| t.lossy.unwrap_or_default())
== LossyMode::Reject
})
}