oxedyne/fe2o3/fe2o3_o3db_sync/src/kademlia/contact.rs
2.7 KiB, 23 runs
created by r1870400018:11186, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | //! Peer contact records stored in the routing table. |
| 2 | //! |
| 3 | //! A [`Contact`] is the minimum information needed to route to a peer: its |
| 4 | //! identifier, one or more network addresses, a liveness timestamp, a smoothed |
| 5 | //! round-trip time, and an opaque capability bitfield. Public keys and key |
| 6 | //! rotation live one layer up -- in fe2o3_crypto and the distributed Ozone |
| 7 | //! layer on top of this crate -- because routing itself does not authenticate |
| 8 | //! anything. |
| 9 | //! |
| 10 | //! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\ |
| 11 | //! Anthropic Claude |
| 12 | |
| 13 | use super::id::NodeId; |
| 14 | |
| 15 | use std::{ |
| 16 | net::SocketAddr, |
| 17 | time::Duration, |
| 18 | }; |
| 19 | |
| 20 | |
| 21 | /// Opaque peer capability flags. |
| 22 | /// |
| 23 | /// Concrete bit assignments are made by the layer above (distributed Ozone, |
| 24 | /// Oxegen peer type). This crate treats capabilities as an opaque bitfield |
| 25 | /// with set, test and clear operations. |
| 26 | #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] |
| 27 | pub struct Capabilities(pub u64); |
| 28 | |
| 29 | impl Capabilities { |
| 30 | pub const NONE: Self = Self(0); |
| 31 | |
| 32 | /// Are all of the mask's bits set? |
| 33 | pub fn has(&self, mask: Capabilities) -> bool { |
| 34 | self.0 & mask.0 == mask.0 |
| 35 | } |
| 36 | |
| 37 | pub fn set(&mut self, mask: Capabilities) { |
| 38 | self.0 |= mask.0; |
| 39 | } |
| 40 | |
| 41 | pub fn clear(&mut self, mask: Capabilities) { |
| 42 | self.0 &= !mask.0; |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | |
| 47 | /// A routing-table entry for a single peer. |
| 48 | /// |
| 49 | /// Cheap to clone -- `addresses` is a short `Vec` and the other fields are |
| 50 | /// `Copy`. The routing table keeps `Contact`s directly rather than behind an |
| 51 | /// `Arc` because they are small and mutated only through the table's own API. |
| 52 | #[derive(Clone, Debug)] |
| 53 | pub struct Contact { |
| 54 | pub node_id: NodeId, |
| 55 | pub addresses: Vec<SocketAddr>, // the first is the primary |
| 56 | pub last_seen: u64, // caller's own epoch, opaque here |
| 57 | pub rtt: Option<Duration>, // None until a probe succeeds |
| 58 | pub capabilities: Capabilities, |
| 59 | } |
| 60 | |
| 61 | impl Contact { |
| 62 | /// `last_seen` starts at 0, with no RTT sample and no capabilities. The |
| 63 | /// caller is expected to set `last_seen` at once, through |
| 64 | /// [`Contact::touch`] or the routing table's insert path. |
| 65 | pub fn new(node_id: NodeId, addresses: Vec<SocketAddr>) -> Self { |
| 66 | Self { |
| 67 | node_id, |
| 68 | addresses, |
| 69 | last_seen: 0, |
| 70 | rtt: None, |
| 71 | capabilities: Capabilities::NONE, |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | pub fn touch(&mut self, now: u64) { |
| 76 | self.last_seen = now; |
| 77 | } |
| 78 | |
| 79 | /// An exponentially weighted moving average with α = 1/8, the |
| 80 | /// TCP Jacobson smoothing constant. The first sample becomes the initial |
| 81 | /// estimate unchanged. |
| 82 | pub fn record_rtt(&mut self, sample: Duration) { |
| 83 | self.rtt = Some(match self.rtt { |
| 84 | None => sample, |
| 85 | Some(prev) => { |
| 86 | let prev_ns = prev.as_nanos() as u64; |
| 87 | let samp_ns = sample.as_nanos() as u64; |
| 88 | let next_ns = prev_ns - (prev_ns / 8) + (samp_ns / 8); |
| 89 | Duration::from_nanos(next_ns) |
| 90 | }, |
| 91 | }); |
| 92 | } |
| 93 | } |