mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 00:04:54 +00:00
Migrate the FMP discovery decision logic out of the async handlers into synchronous, runtime-agnostic sans-IO state machines owned by the protocol structs, with I/O pushed to the edges. Pulls the full decision surface into a pure core (backoff, rate-limit, planners, response routing), consolidates the tests into a per-module tree with a shared crate testutil, and injects a u64 wall-clock so the core is free of Instant and std time. Also brings the module toward no_std+alloc: the four discovery maps use alloc::collections::BTreeMap (HashMap's RandomState is std-only), Arc is spelled alloc::sync::Arc, the backoff-reset log lives in the shell (the core returns the cleared count so observability stays out of the pure core), and the crate root names alloc directly. The one remaining tether is ProtocolError's std::error::Error coupling in the wire codec. First subsystem of the broader sans-IO refactor; establishes the extraction patterns and conventions carried forward to the remaining protocols.
111 lines
3.9 KiB
Rust
111 lines
3.9 KiB
Rust
//! Discovery-subsystem state owned by [`Node`](crate::node::Node).
|
|
//!
|
|
//! Groups the four discovery-related state fields (recent-request dedup
|
|
//! cache, in-flight lookups, originator-side backoff, transit-side forward
|
|
//! rate limiter) behind a single struct so the discovery handlers can
|
|
//! evolve toward a sans-IO core without threading four fields through
|
|
//! `Node`.
|
|
|
|
use alloc::collections::BTreeMap;
|
|
|
|
use super::limits::{DiscoveryBackoff, DiscoveryForwardRateLimiter};
|
|
use crate::NodeAddr;
|
|
|
|
/// Recent request tracking for dedup and reverse-path forwarding.
|
|
///
|
|
/// When a LookupRequest is forwarded through a node, the node stores the
|
|
/// request_id and which peer sent it. When the corresponding LookupResponse
|
|
/// arrives, it's forwarded back to that peer (reverse-path forwarding).
|
|
/// The `response_forwarded` flag prevents response routing loops.
|
|
#[derive(Clone, Debug)]
|
|
pub(crate) struct RecentRequest {
|
|
/// The peer who sent this request to us.
|
|
pub(crate) from_peer: NodeAddr,
|
|
/// When we received this request (Unix milliseconds).
|
|
pub(crate) timestamp_ms: u64,
|
|
/// Whether we've already forwarded a response for this request.
|
|
/// Prevents response routing loops when convergent request paths
|
|
/// create bidirectional entries in recent_requests.
|
|
pub(crate) response_forwarded: bool,
|
|
}
|
|
|
|
impl RecentRequest {
|
|
pub(crate) fn new(from_peer: NodeAddr, timestamp_ms: u64) -> Self {
|
|
Self {
|
|
from_peer,
|
|
timestamp_ms,
|
|
response_forwarded: false,
|
|
}
|
|
}
|
|
|
|
/// Check if this entry has expired (older than expiry_ms).
|
|
pub(crate) fn is_expired(&self, current_time_ms: u64, expiry_ms: u64) -> bool {
|
|
current_time_ms.saturating_sub(self.timestamp_ms) > expiry_ms
|
|
}
|
|
}
|
|
|
|
/// Tracks a pending discovery lookup with retry state.
|
|
pub struct PendingLookup {
|
|
/// When the lookup was first initiated.
|
|
pub initiated_ms: u64,
|
|
/// When the last attempt was sent.
|
|
pub last_sent_ms: u64,
|
|
/// Current attempt number (1 = initial, 2 = first retry, ...).
|
|
pub attempt: u8,
|
|
}
|
|
|
|
impl PendingLookup {
|
|
pub fn new(now_ms: u64) -> Self {
|
|
Self {
|
|
initiated_ms: now_ms,
|
|
last_sent_ms: now_ms,
|
|
attempt: 1,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Discovery-subsystem state.
|
|
pub(crate) struct Discovery {
|
|
/// Recent discovery requests (dedup + reverse-path forwarding).
|
|
/// Maps request_id → RecentRequest.
|
|
pub(crate) recent_requests: BTreeMap<u64, RecentRequest>,
|
|
/// Tracks in-flight discovery lookups. Maps target NodeAddr to the
|
|
/// initiation timestamp (Unix ms). Prevents duplicate flood queries.
|
|
pub(crate) pending_lookups: BTreeMap<NodeAddr, PendingLookup>,
|
|
/// Backoff for failed discovery lookups (originator-side).
|
|
pub(crate) backoff: DiscoveryBackoff,
|
|
/// Rate limiter for forwarded discovery requests (transit-side).
|
|
pub(crate) forward_limiter: DiscoveryForwardRateLimiter,
|
|
}
|
|
|
|
impl Discovery {
|
|
/// Create discovery state with the given backoff and forward limiter.
|
|
///
|
|
/// The two limiters are constructed by the caller so each `Node`
|
|
/// constructor can supply its own configured/default variant, matching
|
|
/// the pre-refactor initialization exactly.
|
|
pub(crate) fn new(
|
|
backoff: DiscoveryBackoff,
|
|
forward_limiter: DiscoveryForwardRateLimiter,
|
|
) -> Self {
|
|
Self {
|
|
recent_requests: BTreeMap::new(),
|
|
pending_lookups: BTreeMap::new(),
|
|
backoff,
|
|
forward_limiter,
|
|
}
|
|
}
|
|
|
|
/// Reset discovery backoff on topology changes. Returns the number of
|
|
/// entries cleared (0 if already empty) so the shell can log the reset —
|
|
/// observability stays out of the pure core.
|
|
pub(crate) fn reset_backoff(&mut self) -> usize {
|
|
if self.backoff.is_empty() {
|
|
return 0;
|
|
}
|
|
let cleared = self.backoff.entry_count();
|
|
self.backoff.reset_all();
|
|
cleared
|
|
}
|
|
}
|