1use std::collections::HashMap;
4use std::net::SocketAddr;
5use std::time::{Duration, Instant};
6
7pub struct FabricDns {
31 entries: HashMap<String, DnsEntry>,
32 ttl: Duration,
33}
34
35struct DnsEntry {
36 addrs: Vec<SocketAddr>,
37 inserted: Instant,
38}
39
40impl FabricDns {
41 pub fn new(ttl: Duration) -> Self {
43 FabricDns {
44 entries: HashMap::new(),
45 ttl,
46 }
47 }
48
49 pub fn register(&mut self, name: &str, addr: SocketAddr) {
53 let entry = self
54 .entries
55 .entry(name.to_string())
56 .or_insert_with(|| DnsEntry {
57 addrs: Vec::new(),
58 inserted: Instant::now(),
59 });
60 if !entry.addrs.contains(&addr) {
61 entry.addrs.push(addr);
62 }
63 entry.inserted = Instant::now();
64 }
65
66 pub fn resolve(&self, name: &str) -> Option<Vec<SocketAddr>> {
70 let entry = self.entries.get(name)?;
71 if entry.inserted.elapsed() > self.ttl {
72 return None;
73 }
74 Some(entry.addrs.clone())
75 }
76
77 pub fn evict_expired(&mut self) {
79 self.entries.retain(|_, e| e.inserted.elapsed() <= self.ttl);
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use std::net::{Ipv4Addr, SocketAddrV4};
87
88 fn addr(ip: [u8; 4], port: u16) -> SocketAddr {
89 SocketAddr::V4(SocketAddrV4::new(
90 Ipv4Addr::new(ip[0], ip[1], ip[2], ip[3]),
91 port,
92 ))
93 }
94
95 #[test]
96 fn register_and_resolve() {
97 let mut dns = FabricDns::new(Duration::from_secs(60));
98 let a = addr([10, 10, 0, 11], 5701);
99
100 dns.register("node-b.fabric", a);
101
102 let addrs = dns.resolve("node-b.fabric").expect("resolve");
103 assert_eq!(addrs, vec![a]);
104 }
105
106 #[test]
107 fn resolve_unknown_returns_none() {
108 let dns = FabricDns::new(Duration::from_secs(60));
109 assert!(dns.resolve("unknown.fabric").is_none());
110 }
111
112 #[test]
113 fn multiple_addrs_for_same_name() {
114 let mut dns = FabricDns::new(Duration::from_secs(60));
115 let a1 = addr([10, 10, 0, 11], 5701);
116 let a2 = addr([10, 10, 0, 11], 5702);
117
118 dns.register("node-b.fabric", a1);
119 dns.register("node-b.fabric", a2);
120
121 let addrs = dns.resolve("node-b.fabric").expect("resolve");
122 assert_eq!(addrs.len(), 2);
123 assert!(addrs.contains(&a1));
124 assert!(addrs.contains(&a2));
125 }
126
127 #[test]
128 fn duplicate_register_is_idempotent() {
129 let mut dns = FabricDns::new(Duration::from_secs(60));
130 let a = addr([10, 10, 0, 11], 5701);
131
132 dns.register("node-b.fabric", a);
133 dns.register("node-b.fabric", a);
134
135 let addrs = dns.resolve("node-b.fabric").expect("resolve");
136 assert_eq!(addrs.len(), 1);
137 }
138
139 #[test]
140 fn ttl_expiry() {
141 let mut dns = FabricDns::new(Duration::from_millis(0));
142 let a = addr([10, 10, 0, 11], 5701);
143
144 dns.register("node-b.fabric", a);
145 std::thread::sleep(Duration::from_millis(1));
147
148 assert!(dns.resolve("node-b.fabric").is_none());
149 }
150
151 #[test]
152 fn evict_expired_removes_stale_entries() {
153 let mut dns = FabricDns::new(Duration::from_millis(0));
154 let a = addr([10, 10, 0, 11], 5701);
155
156 dns.register("node-b.fabric", a);
157 std::thread::sleep(Duration::from_millis(1));
158
159 dns.evict_expired();
160 assert!(dns.resolve("node-b.fabric").is_none());
161 assert!(dns.entries.is_empty());
162 }
163}