use std::sync::Arc; use crate::api::types::InfoUrls; use crate::config::AppConfig; use crate::dashboard::DecisionLog; use crate::failover::FailoverGuard; use crate::puzzle::challenge::PuzzleEngine; use crate::risk::{RiskScorer, TrustedProxies, VerifyScorer}; use crate::storage::memory::InMemoryStore; pub type SharedState = Arc; pub struct AppState { pub store: Arc, pub engine: PuzzleEngine, pub risk: RiskScorer, pub verify_scorer: VerifyScorer, /// Header name to read for TLS fingerprint (e.g. `x-ja4`). `None` disables /// the signal entirely. pub tls_fingerprint_header: Option, /// Trusted proxies whose TLS fingerprint header we honor (and whose /// `X-Forwarded-For` we walk to resolve the client IP). Empty if no /// proxies are configured — direct connections only. pub trusted_proxies: Arc, /// Validation dashboard log. When `Some`, every puzzle/verify decision is /// also persisted to SQLite for the admin dashboard. pub decision_log: Option, /// Bearer token required to call mutating admin endpoints, including /// `POST /v1/sites`. When `None`, those endpoints are disabled (404). pub admin_token: Option>, /// Operator-overridden info URLs surfaced in the puzzle response and /// the 429 block body. `None` when no `INFO_*_URL` env var is set; the /// widget then uses bundled `/static/*.html` defaults for every field. pub info_urls: Option, /// Bounds how many Argon2id verifications run at once. Each one allocates /// `ARGON2_M_COST` KiB (8 MiB by default) for the duration of the hash, and /// tokio grows its blocking pool to 512 threads on demand — so without a /// bound, ~500 concurrent `/v1/verify` calls peak around 4 GiB and OOM a /// small VPS. Permits cap that at `parallelism * 8 MiB`. Requests queue for /// a permit rather than being refused; the global request timeout is what /// bounds the queue, so overload degrades into timeouts instead of an OOM. pub verify_permits: Arc, /// Outage attestation + the accept/refuse decision for client failover /// claims. Always present; a guard built from a default (disabled) config /// refuses every claim, so the verify path needs no `Option` handling. pub failover: Arc, pub config: AppConfig, } /// Concurrency limit for memory-hard verification, as a multiple of available /// parallelism. Argon2id is CPU- *and* memory-bound, so there's no throughput /// gained past a small multiple of the core count — the extra concurrency buys /// only resident memory. pub const VERIFY_PERMITS_PER_CORE: usize = 2; /// Permit count for [`AppState::verify_permits`], derived from the host's /// parallelism. Falls back to a single core's worth when the platform can't /// report it, which errs toward the safe side. pub fn verify_permits() -> usize { std::thread::available_parallelism() .map(|n| n.get()) .unwrap_or(1) * VERIFY_PERMITS_PER_CORE } /// Build the `info_urls` payload from config. Returns `None` when no /// override is set so we don't serialize an empty object on every puzzle /// response. pub fn info_urls_from_config(config: &AppConfig) -> Option { let urls = InfoUrls { about: config.info_about_url.clone(), privacy: config.info_privacy_url.clone(), terms: config.info_terms_url.clone(), }; if urls.is_empty() { None } else { Some(urls) } }