tuwunel_service/federation/mod.rs
1//! Sends federation requests and tracks per-peer reachability.
2//!
3//! The service resolves and signs outbound requests, records selected outcomes,
4//! ranks fallback candidates, and exposes bounded multi-destination fanout.
5
6mod execute;
7pub mod feds;
8mod format;
9mod peer;
10mod rank;
11pub mod scheme;
12#[cfg(test)]
13mod tests;
14
15use std::{sync::Arc, time::Duration};
16
17use tuwunel_core::{Result, utils::exponential_backoff_streak_cap};
18use tuwunel_database::Map;
19
20use self::peer::MAX_BACKOFF;
21/// Re-exports peer reachability verdicts and candidate-ranking types.
22///
23/// These items classify failures, expose retry eligibility, and preserve the
24/// ranking policy shared by federation request paths.
25pub use self::{
26 peer::{Classification, PeerBackoff, ShouldAttempt, is_content_rejection},
27 rank::{Candidates, WhenAllBackedOff},
28};
29use crate::services::OnceServices;
30
31/// Executes outbound federation traffic and maintains peer status.
32///
33/// Request entry points choose whether to consult or update reachability state.
34/// Fanout and candidate-ranking helpers build on the same transport policy.
35pub struct Service {
36 services: Arc<OnceServices>,
37 statuses: Arc<Map>,
38
39 /// Width of one peer-status bucket in seconds, aligned with
40 /// `sender_timeout` so the streak (the window span between a peer's oldest
41 /// and newest recorded failure) tracks the sender's `consecutive_failures`
42 /// notion at the cutover.
43 window_secs: u64,
44
45 /// Streak cap = `ceil(sqrt(MAX_BACKOFF / window_secs))`. Past this span the
46 /// quadratic curve `window * n²` saturates at [`MAX_BACKOFF`], so a longer
47 /// streak cannot change the verdict.
48 n_max: u32,
49
50 /// Grace before the first retry of a once-failed peer, snapshot from
51 /// `sender_retry_grace`. Zero disables the grace tier so the plain bucket
52 /// curve governs from the first failure.
53 grace: Duration,
54}
55
56impl crate::Service for Service {
57 fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
58 let window_secs = args.server.config.sender_timeout.max(1);
59 let n_max = exponential_backoff_streak_cap(Duration::from_secs(window_secs), MAX_BACKOFF);
60 let grace = Duration::from_secs(args.server.config.sender_retry_grace);
61
62 Ok(Arc::new(Self {
63 services: args.services.clone(),
64 statuses: args.db["servername_status"].clone(),
65 window_secs,
66 n_max,
67 grace,
68 }))
69 }
70
71 fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
72}