dig_dht/config.rs
1//! [`DhtConfig`] — the Kademlia tuning parameters (replication `k`, lookup parallelism `α`, provider
2//! TTL, the maintenance intervals, and the provider-store admission-control caps).
3
4use std::time::Duration;
5
6use crate::provider_store::ProviderStoreLimits;
7
8/// Kademlia parameters for a [`DhtService`](crate::DhtService).
9///
10/// The defaults follow the canonical Kademlia paper (`k = 20`, `α = 3`) and typical provider-record
11/// lifetimes; every field is documented so a node operator can tune replication vs. traffic.
12#[derive(Debug, Clone)]
13pub struct DhtConfig {
14 /// **Replication parameter `k`** — the bucket size and the number of closest peers a lookup
15 /// converges on. A provider record is announced to (and a `find_node` returns) up to `k` peers.
16 /// Larger `k` = more redundancy against churn, more traffic. Canonical default: 20.
17 pub k: usize,
18
19 /// **Lookup parallelism `α`** — how many peers an iterative lookup queries concurrently per
20 /// round. Larger `α` = faster convergence, more in-flight traffic. Canonical default: 3.
21 pub alpha: usize,
22
23 /// **Provider-record TTL** — how long a PUT provider record is considered valid. A holder
24 /// republishes before this elapses; a finder discards records older than this. Default: 2 hours.
25 ///
26 /// This is also the **clamp ceiling** for inbound `add_provider` records (SPEC §6.2, §14): a
27 /// responder never stores a third-party `expires_at` further in the future than
28 /// `now + provider_ttl`, so a malicious record can never outlive local GC indefinitely.
29 pub provider_ttl: Duration,
30
31 /// **Republish interval** — how often the holder re-announces the content it still holds, so its
32 /// provider records never expire while it is online. MUST be shorter than [`Self::provider_ttl`].
33 /// Default: 1 hour.
34 pub republish_interval: Duration,
35
36 /// **Bucket-refresh interval** — how often a bucket with no recent activity is refreshed by
37 /// looking up a random key that falls in it, keeping the routing table populated. Default: 1 hour.
38 pub refresh_interval: Duration,
39
40 /// **Per-RPC timeout** — how long a single request to one peer may take before that peer is
41 /// treated as unresponsive and the lookup moves on. Default: 5 seconds.
42 pub rpc_timeout: Duration,
43
44 /// **Provider-store admission-control caps** — the per-content-key and global record limits
45 /// enforced on every inbound `add_provider` (SPEC §6.3, §14). Bounds worst-case memory growth
46 /// from a single peer (or a small set of colluding peers) flooding announces. Default:
47 /// [`ProviderStoreLimits::default`].
48 pub provider_store_limits: ProviderStoreLimits,
49}
50
51impl Default for DhtConfig {
52 fn default() -> Self {
53 DhtConfig {
54 k: 20,
55 alpha: 3,
56 provider_ttl: Duration::from_secs(2 * 60 * 60),
57 republish_interval: Duration::from_secs(60 * 60),
58 refresh_interval: Duration::from_secs(60 * 60),
59 rpc_timeout: Duration::from_secs(5),
60 provider_store_limits: ProviderStoreLimits::default(),
61 }
62 }
63}
64
65impl DhtConfig {
66 /// The provider TTL in whole seconds (records store an absolute Unix-seconds expiry).
67 pub fn provider_ttl_secs(&self) -> u64 {
68 self.provider_ttl.as_secs()
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn defaults_follow_kademlia() {
78 let c = DhtConfig::default();
79 assert_eq!(c.k, 20);
80 assert_eq!(c.alpha, 3);
81 assert_eq!(c.provider_ttl_secs(), 7200);
82 }
83
84 #[test]
85 fn republish_is_shorter_than_ttl() {
86 // Invariant: a record must be republished before it expires, or providers vanish while online.
87 let c = DhtConfig::default();
88 assert!(c.republish_interval < c.provider_ttl);
89 }
90
91 #[test]
92 fn default_provider_store_limits_are_bounded() {
93 // The audit's "unbounded provider store" finding: the default config MUST carry a non-zero,
94 // finite cap so a freshly constructed DhtService is never unbounded out of the box.
95 let c = DhtConfig::default();
96 assert!(c.provider_store_limits.max_providers_per_key > 0);
97 assert!(c.provider_store_limits.max_total_records > 0);
98 }
99}