1use std::collections::HashMap;
4use std::net::IpAddr;
5use std::ops::RangeInclusive;
6use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
7use std::time::{Duration, Instant};
8
9use crate::contact::Contact;
10use crate::query::DomainName;
11
12#[derive(Clone, Debug)]
29pub struct Cache {
30 hold: Duration,
31 entries: Arc<Mutex<Entries>>,
32}
33
34impl Cache {
35 pub const DEFAULT_HOLD: Duration = Duration::from_secs(60 * 60);
37
38 pub fn new(hold: Duration) -> Self {
42 Self {
43 hold,
44 entries: Arc::default(),
45 }
46 }
47
48 pub fn len(&self) -> usize {
51 self.lock().len()
52 }
53
54 pub fn is_empty(&self) -> bool {
56 self.len() == 0
57 }
58
59 pub fn clear(&self) {
61 *self.lock() = Entries::default();
62 }
63
64 pub(crate) fn network(&self, ip: IpAddr) -> Option<Vec<Contact>> {
66 self.lock().network(ip, Instant::now())
67 }
68
69 pub(crate) fn put_network(&self, range: RangeInclusive<IpAddr>, contacts: Vec<Contact>) {
71 let now = Instant::now();
72 self.lock()
73 .put_network(range, contacts, now, now + self.hold);
74 }
75
76 pub(crate) fn domain(&self, domain: &DomainName) -> Option<Vec<Contact>> {
78 self.lock().domain(domain, Instant::now())
79 }
80
81 pub(crate) fn put_domain(&self, domain: DomainName, contacts: Vec<Contact>) {
83 let now = Instant::now();
84 self.lock()
85 .put_domain(domain, contacts, now, now + self.hold);
86 }
87
88 fn lock(&self) -> MutexGuard<'_, Entries> {
89 self.entries.lock().unwrap_or_else(PoisonError::into_inner)
92 }
93}
94
95impl Default for Cache {
96 fn default() -> Self {
98 Self::new(Self::DEFAULT_HOLD)
99 }
100}
101
102#[derive(Debug, Default)]
104struct Entries {
105 networks: Vec<(RangeInclusive<IpAddr>, Held)>,
106 domains: HashMap<DomainName, Held>,
107}
108
109#[derive(Debug)]
110struct Held {
111 contacts: Vec<Contact>,
112 until: Instant,
113}
114
115impl Held {
116 fn is_live(&self, now: Instant) -> bool {
117 now < self.until
118 }
119}
120
121impl Entries {
122 fn len(&self) -> usize {
123 self.networks.len() + self.domains.len()
124 }
125
126 fn network(&self, ip: IpAddr, now: Instant) -> Option<Vec<Contact>> {
132 self.networks
133 .iter()
134 .filter(|(range, held)| range.contains(&ip) && held.is_live(now))
135 .min_by_key(|(range, _)| width(range))
136 .map(|(_, held)| held.contacts.clone())
137 }
138
139 fn put_network(
140 &mut self,
141 range: RangeInclusive<IpAddr>,
142 contacts: Vec<Contact>,
143 now: Instant,
144 until: Instant,
145 ) {
146 self.drop_expired(now);
147 self.networks.retain(|(held, _)| *held != range);
148 self.networks.push((range, Held { contacts, until }));
149 }
150
151 fn domain(&self, domain: &DomainName, now: Instant) -> Option<Vec<Contact>> {
152 self.domains
153 .get(domain)
154 .filter(|held| held.is_live(now))
155 .map(|held| held.contacts.clone())
156 }
157
158 fn put_domain(
159 &mut self,
160 domain: DomainName,
161 contacts: Vec<Contact>,
162 now: Instant,
163 until: Instant,
164 ) {
165 self.drop_expired(now);
166 self.domains.insert(domain, Held { contacts, until });
167 }
168
169 fn drop_expired(&mut self, now: Instant) {
170 self.networks.retain(|(_, held)| held.is_live(now));
171 self.domains.retain(|_, held| held.is_live(now));
172 }
173}
174
175fn width(range: &RangeInclusive<IpAddr>) -> u128 {
177 number(*range.end()) - number(*range.start())
178}
179
180fn number(ip: IpAddr) -> u128 {
181 match ip {
182 IpAddr::V4(v4) => u128::from(u32::from(v4)),
183 IpAddr::V6(v6) => u128::from(v6),
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190 use crate::contact::{EmailAddress, Scope, Source};
191
192 const HOUR: Duration = Duration::from_secs(60 * 60);
193
194 fn contacts(email: &str) -> Vec<Contact> {
195 vec![Contact {
196 email: EmailAddress::new(email).unwrap(),
197 scope: Scope::Network,
198 source: Source::Rdap {
199 server: "rdap.example".to_owned(),
200 },
201 }]
202 }
203
204 fn range(start: &str, end: &str) -> RangeInclusive<IpAddr> {
205 start.parse().unwrap()..=end.parse().unwrap()
206 }
207
208 fn emails(found: Option<Vec<Contact>>) -> Option<Vec<String>> {
209 found.map(|contacts| contacts.iter().map(|c| c.email.to_string()).collect())
210 }
211
212 #[test]
213 fn a_range_answers_for_every_address_in_it() {
214 let now = Instant::now();
215 let mut entries = Entries::default();
216 entries.put_network(
217 range("8.8.8.0", "8.8.8.255"),
218 contacts("abuse@example.com"),
219 now,
220 now + HOUR,
221 );
222
223 for ip in ["8.8.8.0", "8.8.8.8", "8.8.8.255"] {
224 assert_eq!(
225 emails(entries.network(ip.parse().unwrap(), now)),
226 Some(vec!["abuse@example.com".to_owned()]),
227 "{ip}"
228 );
229 }
230 for ip in ["8.8.7.255", "8.8.9.0", "::ffff:808:808"] {
231 assert_eq!(entries.network(ip.parse().unwrap(), now), None, "{ip}");
232 }
233 }
234
235 #[test]
236 fn the_narrowest_range_answers_for_an_address_in_two() {
237 let now = Instant::now();
238 let mut entries = Entries::default();
239 entries.put_network(
240 range("8.8.8.0", "8.8.8.7"),
241 contacts("customer@example.com"),
242 now,
243 now + HOUR,
244 );
245 entries.put_network(
246 range("8.8.0.0", "8.8.255.255"),
247 contacts("isp@example.com"),
248 now,
249 now + HOUR,
250 );
251
252 assert_eq!(
253 emails(entries.network("8.8.8.1".parse().unwrap(), now)),
254 Some(vec!["customer@example.com".to_owned()])
255 );
256 assert_eq!(
257 emails(entries.network("8.8.9.1".parse().unwrap(), now)),
258 Some(vec!["isp@example.com".to_owned()])
259 );
260 }
261
262 #[test]
263 fn an_answer_whose_time_is_over_is_not_given() {
264 let now = Instant::now();
265 let mut entries = Entries::default();
266 entries.put_network(
267 range("8.8.8.0", "8.8.8.255"),
268 contacts("abuse@example.com"),
269 now,
270 now + HOUR,
271 );
272 let domain: DomainName = "example.com".parse().unwrap();
273 entries.put_domain(
274 domain.clone(),
275 contacts("abuse@example.com"),
276 now,
277 now + HOUR,
278 );
279
280 let later = now + HOUR;
281 assert_eq!(entries.network("8.8.8.8".parse().unwrap(), later), None);
282 assert_eq!(entries.domain(&domain, later), None);
283 }
284
285 #[test]
286 fn storing_an_answer_drops_the_answers_whose_time_is_over() {
287 let now = Instant::now();
288 let mut entries = Entries::default();
289 entries.put_network(
290 range("8.8.8.0", "8.8.8.255"),
291 contacts("old@example.com"),
292 now,
293 now + HOUR,
294 );
295 entries.put_domain(
296 "old.example".parse().unwrap(),
297 contacts("old@example.com"),
298 now,
299 now + HOUR,
300 );
301
302 let later = now + HOUR;
303 entries.put_domain(
304 "new.example".parse().unwrap(),
305 contacts("new@example.com"),
306 later,
307 later + HOUR,
308 );
309
310 assert_eq!(entries.len(), 1);
311 }
312
313 #[test]
314 fn storing_the_same_range_again_replaces_it() {
315 let now = Instant::now();
316 let mut entries = Entries::default();
317 for email in ["old@example.com", "new@example.com"] {
318 entries.put_network(
319 range("8.8.8.0", "8.8.8.255"),
320 contacts(email),
321 now,
322 now + HOUR,
323 );
324 }
325
326 assert_eq!(entries.len(), 1);
327 assert_eq!(
328 emails(entries.network("8.8.8.8".parse().unwrap(), now)),
329 Some(vec!["new@example.com".to_owned()])
330 );
331 }
332
333 #[test]
334 fn a_hold_of_zero_holds_nothing() {
335 let cache = Cache::new(Duration::ZERO);
336 cache.put_network(range("8.8.8.0", "8.8.8.255"), contacts("abuse@example.com"));
337
338 assert_eq!(cache.network("8.8.8.8".parse().unwrap()), None);
339 }
340
341 #[test]
342 fn a_clone_shares_the_answers_and_clear_drops_them() {
343 let cache = Cache::default();
344 let clone = cache.clone();
345 clone.put_domain(
346 "example.com".parse().unwrap(),
347 contacts("abuse@example.com"),
348 );
349
350 assert_eq!(cache.len(), 1);
351 cache.clear();
352 assert!(clone.is_empty());
353 }
354
355 #[test]
356 fn measures_the_width_of_a_range() {
357 assert_eq!(width(&range("8.8.8.0", "8.8.8.255")), 255);
358 assert_eq!(width(&range("2001:db8::", "2001:db8::ffff")), 0xffff);
359 }
360}