ndn_discovery/strategy/
swim.rs1use std::time::{Duration, Instant};
13
14use crate::config::DiscoveryConfig;
15use crate::strategy::{NeighborProbeStrategy, ProbeRequest, TriggerEvent};
16
17pub struct SwimScheduler {
28 interval: Duration,
29 next_probe_at: Instant,
30 pending_immediate: bool,
31}
32
33impl SwimScheduler {
34 pub fn new(interval: Duration) -> Self {
39 let now = Instant::now();
40 Self {
41 interval,
42 next_probe_at: now + interval,
43 pending_immediate: true,
44 }
45 }
46
47 pub fn from_discovery_config(cfg: &DiscoveryConfig) -> Self {
50 Self::new(cfg.hello_interval_base)
51 }
52}
53
54impl NeighborProbeStrategy for SwimScheduler {
55 fn on_tick(&mut self, now: Instant) -> Vec<ProbeRequest> {
56 if self.pending_immediate || now >= self.next_probe_at {
57 self.pending_immediate = false;
58 self.next_probe_at = now + self.interval;
59 vec![ProbeRequest::Broadcast]
60 } else {
61 vec![]
62 }
63 }
64
65 fn on_probe_success(&mut self, _rtt: Duration) {
66 }
69
70 fn on_probe_timeout(&mut self) {
71 }
74
75 fn trigger(&mut self, event: TriggerEvent) {
76 match event {
77 TriggerEvent::FaceUp
78 | TriggerEvent::ForwardingFailure
79 | TriggerEvent::NeighborStale => {
80 self.pending_immediate = true;
81 }
82 TriggerEvent::PassiveDetection => {
83 }
85 }
86 }
87}
88
89#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn fires_immediately_on_first_tick() {
97 let mut s = SwimScheduler::new(Duration::from_secs(5));
98 let now = Instant::now();
99 let probes = s.on_tick(now);
100 assert_eq!(probes, vec![ProbeRequest::Broadcast]);
101 }
102
103 #[test]
104 fn no_second_fire_before_interval() {
105 let mut s = SwimScheduler::new(Duration::from_secs(5));
106 let now = Instant::now();
107 s.on_tick(now);
108 let probes = s.on_tick(now + Duration::from_millis(100));
109 assert!(probes.is_empty());
110 }
111
112 #[test]
113 fn fires_after_interval() {
114 let mut s = SwimScheduler::new(Duration::from_secs(5));
115 let now = Instant::now();
116 s.on_tick(now); let probes = s.on_tick(now + Duration::from_secs(6));
118 assert_eq!(probes, vec![ProbeRequest::Broadcast]);
119 }
120
121 #[test]
122 fn timeout_does_not_change_interval() {
123 let interval = Duration::from_secs(5);
124 let mut s = SwimScheduler::new(interval);
125 let now = Instant::now();
126 s.on_tick(now); s.on_probe_timeout();
128 assert!(s.on_tick(now + Duration::from_millis(100)).is_empty());
130 assert_eq!(
132 s.on_tick(now + interval + Duration::from_millis(100)),
133 vec![ProbeRequest::Broadcast]
134 );
135 }
136
137 #[test]
138 fn trigger_schedules_immediate_probe() {
139 let mut s = SwimScheduler::new(Duration::from_secs(60));
140 let now = Instant::now();
141 s.on_tick(now); s.trigger(TriggerEvent::NeighborStale);
143 let probes = s.on_tick(now + Duration::from_millis(10));
144 assert_eq!(probes, vec![ProbeRequest::Broadcast]);
145 }
146
147 #[test]
148 fn passive_detection_ignored() {
149 let mut s = SwimScheduler::new(Duration::from_secs(60));
150 let now = Instant::now();
151 s.on_tick(now); s.trigger(TriggerEvent::PassiveDetection);
153 assert!(s.on_tick(now + Duration::from_millis(1)).is_empty());
155 }
156}