diff options
| author | tv <tv@krebsco.de> | 2026-11-06 04:31:39 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-06 04:31:39 +0100 |
| commit | 5491b76da400ee52a473c0297fa3488bb54f39da (patch) | |
| tree | b9c853efd94ef753318caaf78c818f0f2da9413f | |
| parent | 217c14071a8aa9858f511d8737e4d727c5c6afd5 (diff) | |
identity evicts the silent
| -rw-r--r-- | src/transport/identity.rs | 32 |
1 files changed, 30 insertions, 2 deletions
diff --git a/src/transport/identity.rs b/src/transport/identity.rs index 1131a11..b61fb0f 100644 --- a/src/transport/identity.rs +++ b/src/transport/identity.rs @@ -121,7 +121,8 @@ struct Shared { /// Who is where, by address. #[derive(Default)] struct Book { - /// The node that proved itself at an address, and when. + /// The node that proved itself at an address, and when it was last heard from there + /// (its proof, or a message since: `MAX_ENTRIES` evicts the longest silent). at: HashMap<String, (String, Instant)>, /// The addresses each node last proved itself at, one per scheme. proven: HashMap<String, Vec<String>>, @@ -173,6 +174,14 @@ impl Book { news } + /// The node proven at `address`, which was heard from there `now`. + fn heard(&mut self, address: &str, now: Instant) -> Option<String> { + self.at.get_mut(address).map(|(node, heard)| { + *heard = now; + node.clone() + }) + } + fn forget_proof(&mut self, node: &str, address: &str) { if let Some(addresses) = self.proven.get_mut(node) { addresses.retain(|at| at != address); @@ -466,7 +475,9 @@ impl Shared { Ok(()) } message => { - let node = self.lock().at.get(&address).map(|(node, _)| node.clone()); + // Heard from: a node that talks isn't the one evicted when strangers' + // proofs fill the book (`Book::bind`). + let node = self.lock().heard(&address, Instant::now()); match node { Some(node) => handler(node, message, payload_len).await, None => { @@ -801,6 +812,23 @@ mod tests { assert_ne!(signed(WHO_LABEL, "a", "b"), signed(WHO_LABEL, "b", "a")); } + /// When the book is full, the address longest silent goes, not the one proven + /// first: a member that talks stays however many strangers prove themselves. + #[test] + fn a_full_book_evicts_the_longest_silent() { + let start = Instant::now(); + let mut book = Book::default(); + book.bind("s:member", "member", start); + for i in 0..MAX_ENTRIES { + let now = start + Duration::from_millis(i as u64 + 1); + assert_eq!(book.heard("s:member", now).as_deref(), Some("member")); + book.bind(&format!("s:{i}"), &format!("stranger{i}"), now); + } + assert!(book.at.contains_key("s:member")); + assert!(!book.at.contains_key("s:0")); + assert_eq!(book.at.len(), MAX_ENTRIES); + } + #[test] fn one_address_per_node_and_scheme_and_one_node_per_address() { let now = Instant::now(); |
