ndn_discovery/strategy/
composite.rs1use std::time::{Duration, Instant};
21
22use ndn_transport::FaceId;
23
24use crate::strategy::{NeighborProbeStrategy, ProbeRequest, TriggerEvent};
25
26pub struct CompositeStrategy {
31 members: Vec<Box<dyn NeighborProbeStrategy>>,
32}
33
34impl CompositeStrategy {
35 pub fn new() -> Self {
38 Self {
39 members: Vec::new(),
40 }
41 }
42
43 pub fn with(mut self, strategy: Box<dyn NeighborProbeStrategy>) -> Self {
45 self.members.push(strategy);
46 self
47 }
48
49 pub fn push(&mut self, strategy: Box<dyn NeighborProbeStrategy>) {
51 self.members.push(strategy);
52 }
53}
54
55impl Default for CompositeStrategy {
56 fn default() -> Self {
57 Self::new()
58 }
59}
60
61impl NeighborProbeStrategy for CompositeStrategy {
62 fn on_tick(&mut self, now: Instant) -> Vec<ProbeRequest> {
63 let mut broadcast = false;
64 let mut unicasts: Vec<FaceId> = Vec::new();
65
66 for s in &mut self.members {
67 for req in s.on_tick(now) {
68 match req {
69 ProbeRequest::Broadcast => {
70 broadcast = true;
71 }
72 ProbeRequest::Unicast(fid) => {
73 if !unicasts.contains(&fid) {
74 unicasts.push(fid);
75 }
76 }
77 }
78 }
79 }
80
81 let mut result: Vec<ProbeRequest> =
82 unicasts.into_iter().map(ProbeRequest::Unicast).collect();
83 if broadcast {
84 result.push(ProbeRequest::Broadcast);
85 }
86 result
87 }
88
89 fn on_probe_success(&mut self, rtt: Duration) {
90 for s in &mut self.members {
91 s.on_probe_success(rtt);
92 }
93 }
94
95 fn on_probe_timeout(&mut self) {
96 for s in &mut self.members {
97 s.on_probe_timeout();
98 }
99 }
100
101 fn trigger(&mut self, event: TriggerEvent) {
102 for s in &mut self.members {
103 s.trigger(event.clone());
104 }
105 }
106}
107
108#[cfg(test)]
111mod tests {
112 use std::time::{Duration, Instant};
113
114 use super::*;
115 use crate::config::{DiscoveryConfig, DiscoveryProfile};
116 use crate::strategy::{BackoffScheduler, ReactiveScheduler};
117
118 #[test]
119 fn deduplicates_broadcast() {
120 let mut composite = CompositeStrategy::new()
122 .with(Box::new(BackoffScheduler::from_discovery_config(
123 &DiscoveryConfig::for_profile(&DiscoveryProfile::Lan),
124 )))
125 .with(Box::new(ReactiveScheduler::from_discovery_config(
126 &DiscoveryConfig::for_profile(&DiscoveryProfile::Mobile),
127 )));
128
129 let reqs = composite.on_tick(Instant::now());
130 let broadcasts = reqs
131 .iter()
132 .filter(|r| **r == ProbeRequest::Broadcast)
133 .count();
134 assert_eq!(broadcasts, 1, "broadcasts should be deduplicated: {reqs:?}");
135 }
136
137 #[test]
138 fn forwards_success_to_all() {
139 let mut composite = CompositeStrategy::new()
140 .with(Box::new(BackoffScheduler::from_discovery_config(
141 &DiscoveryConfig::for_profile(&DiscoveryProfile::Lan),
142 )))
143 .with(Box::new(ReactiveScheduler::from_discovery_config(
144 &DiscoveryConfig::for_profile(&DiscoveryProfile::Mobile),
145 )));
146 let now = Instant::now();
147 composite.on_tick(now);
148 composite.on_probe_success(Duration::from_millis(12));
150 }
151
152 #[test]
153 fn trigger_forwarded_to_all() {
154 let mut composite = CompositeStrategy::new()
155 .with(Box::new(BackoffScheduler::from_discovery_config(
156 &DiscoveryConfig::for_profile(&DiscoveryProfile::Lan),
157 )))
158 .with(Box::new(ReactiveScheduler::from_discovery_config(
159 &DiscoveryConfig::for_profile(&DiscoveryProfile::Mobile),
160 )));
161 let now = Instant::now();
162 composite.on_tick(now); composite.trigger(TriggerEvent::FaceUp);
165 let reqs = composite.on_tick(now + Duration::from_secs(1));
166 let broadcasts = reqs
167 .iter()
168 .filter(|r| **r == ProbeRequest::Broadcast)
169 .count();
170 assert!(broadcasts >= 1);
172 }
173
174 #[test]
175 fn empty_composite_returns_no_probes() {
176 let mut composite = CompositeStrategy::new();
177 let reqs = composite.on_tick(Instant::now());
178 assert!(reqs.is_empty());
179 }
180}