ctf-ad-mirror-tool-rust/docs/engine-contract.md

39 lines
3.4 KiB
Markdown

# Engine implementation contract
Implement `src/engine.rs` and `tests/forwarding.rs`. Root owns config.rs and other modules. Export from lib.rs by adding `pub mod engine;` only; do not edit config or Cargo dependencies without coordinating.
Public types:
```rust
pub struct Engine; // cloneable handle, internally shared
impl Engine {
pub fn new(settings: crate::config::NetworkConfig) -> Self;
pub async fn upsert(&self, service: &crate::config::ServiceConfig, teams: &[crate::config::Team]) -> anyhow::Result<()>;
pub async fn remove(&self, id: &str) -> anyhow::Result<()>;
pub fn snapshots(&self) -> std::collections::BTreeMap<String, EngineStatus>;
pub async fn shutdown(&self);
}
#[derive(Clone, Debug, serde::Serialize)]
pub struct EngineStatus {
pub running: bool,
pub stats: TrafficStats,
pub last_error: Option<String>,
}
#[derive(Clone, Debug, Default, serde::Serialize)]
pub struct TrafficStats {
pub accepted: u64, pub active: u64, pub completed: u64,
pub connect_errors: u64, pub io_errors: u64, pub rejected: u64, pub accept_errors: u64,
pub bytes_up: u64, pub bytes_down: u64,
pub replica_disconnects: u64, pub replica_errors: u64, pub replica_bytes: u64,
}
```
`upsert` binds before success, returns bind errors to caller, updates targets without restarting an unchanged listener, keeps existing sessions on their original route, supports enabled=false (stop accepting, let old sessions drain; bound drain/cancel on whole engine shutdown). Re-enable must be reliable. Explicit remove should retire metrics only after no leaked tasks; stop accepting and bound existing drain is acceptable, document semantics. Only forward new streams to the configured primary, no midstream retry/replay.
Tokio only, no subprocess/thread-per-connection. Process-wide connection semaphore before accept/spawn to bound tasks (or shed promptly with `rejected` counter). `TcpSocket`/TcpListener, TCP_NODELAY; connect timeout only, no short socket I/O timeout. Preserve both directions until EOF/half-close; shutdown opposite write half, do not abort reply on request EOF. Isolate requests and replica responses: always drain replica replies concurrently, ignore content. Memory bound by configured queue and chunk sizes, `Bytes` shared across sinks, no per-replica payload clones if feasible.
Replica policies:
- `Isolate` (default): a bounded non-blocking queue per replica. A full queue or replica error explicitly disconnects that replica for THIS session and increments a counter. Never drop an arbitrary chunk and continue sending an invalid stream. Primary proceeds independently. No replica reconnect/replay midstream.
- `Strict`: connect all required replicas before sending client bytes; bounded queues apply backpressure; replica failure ends the session explicitly (error metrics) instead of silently proceeding. Strict mode cannot promise availability/primary throughput when a replica is slower or absent.
Connection/task shutdown must be cancellation-safe, close owned sockets and release permits. Active metrics increment/decrement using RAII or equivalently reliable path. Increments on hot path relaxed atomics, no locked logging of payloads. No unsafe code needed. Include normal bulk bidirectional delivery, half-close, >3s idle, live retarget old/new streams, slow/failing replica behavior, bind conflict/error, connection limits, clean shutdown tests. Meaningful integration tests using loopback.