Files
fips/src/cache/entry.rs
T
Johnathan Corgan 4ff1762434 Add MTU fields to lookup packets for path MTU discovery
Add min_mtu (u16) to LookupRequest and path_mtu (u16) to
LookupResponse, enabling the discovery system to report transport
MTU capability along the lookup path.

LookupRequest carries min_mtu (origin's minimum MTU requirement,
default 0 = no requirement). LookupResponse carries path_mtu
(initialized to u16::MAX by the target, reduced by transit nodes
via min(path_mtu, outgoing_link_mtu) on the reverse path).

path_mtu is a transit annotation like SessionDatagram.path_mtu and
is NOT included in the proof signature. The originator stores the
discovered path_mtu in CacheEntry alongside cached coordinates.

Wire format: +2 bytes each for LookupRequest and LookupResponse.
2026-02-22 21:52:08 +00:00

194 lines
5.8 KiB
Rust

//! Cache entry with TTL and LRU tracking.
use crate::tree::TreeCoordinate;
/// A cached coordinate entry.
#[derive(Clone, Debug)]
pub struct CacheEntry {
/// The cached coordinates.
coords: TreeCoordinate,
/// When this entry was created (Unix milliseconds).
created_at: u64,
/// When this entry was last used (Unix milliseconds).
last_used: u64,
/// When this entry expires (Unix milliseconds).
expires_at: u64,
/// Path MTU discovered during lookup (if available).
///
/// Set from the `LookupResponse.path_mtu` field when a discovery
/// response is cached. `None` when populated from SessionSetup or
/// other sources that don't carry path MTU information.
path_mtu: Option<u16>,
}
impl CacheEntry {
/// Create a new cache entry.
pub fn new(coords: TreeCoordinate, current_time_ms: u64, ttl_ms: u64) -> Self {
Self {
coords,
created_at: current_time_ms,
last_used: current_time_ms,
expires_at: current_time_ms.saturating_add(ttl_ms),
path_mtu: None,
}
}
/// Get the cached coordinates.
pub fn coords(&self) -> &TreeCoordinate {
&self.coords
}
/// Get the creation timestamp.
pub fn created_at(&self) -> u64 {
self.created_at
}
/// Get the last used timestamp.
pub fn last_used(&self) -> u64 {
self.last_used
}
/// Get the expiry timestamp.
pub fn expires_at(&self) -> u64 {
self.expires_at
}
/// Get the path MTU discovered during lookup, if available.
pub fn path_mtu(&self) -> Option<u16> {
self.path_mtu
}
/// Set the path MTU discovered during lookup.
pub fn set_path_mtu(&mut self, mtu: u16) {
self.path_mtu = Some(mtu);
}
/// Check if this entry has expired.
pub fn is_expired(&self, current_time_ms: u64) -> bool {
current_time_ms > self.expires_at
}
/// Touch the entry to update last_used time.
pub fn touch(&mut self, current_time_ms: u64) {
self.last_used = current_time_ms;
}
/// Refresh the expiry time.
pub fn refresh(&mut self, current_time_ms: u64, ttl_ms: u64) {
self.expires_at = current_time_ms.saturating_add(ttl_ms);
self.last_used = current_time_ms;
}
/// Update the coordinates and refresh timestamps.
pub fn update(&mut self, coords: TreeCoordinate, current_time_ms: u64, ttl_ms: u64) {
self.coords = coords;
self.last_used = current_time_ms;
self.expires_at = current_time_ms.saturating_add(ttl_ms);
}
/// Time since last use (for LRU eviction).
pub fn idle_time(&self, current_time_ms: u64) -> u64 {
current_time_ms.saturating_sub(self.last_used)
}
/// Age of the entry.
pub fn age(&self, current_time_ms: u64) -> u64 {
current_time_ms.saturating_sub(self.created_at)
}
/// Time until expiry (0 if already expired).
pub fn time_to_expiry(&self, current_time_ms: u64) -> u64 {
self.expires_at.saturating_sub(current_time_ms)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::NodeAddr;
fn make_node_addr(val: u8) -> NodeAddr {
let mut bytes = [0u8; 16];
bytes[0] = val;
NodeAddr::from_bytes(bytes)
}
fn make_coords(ids: &[u8]) -> TreeCoordinate {
TreeCoordinate::from_addrs(ids.iter().map(|&v| make_node_addr(v)).collect()).unwrap()
}
#[test]
fn test_cache_entry_expiry() {
let coords = make_coords(&[1, 0]);
let entry = CacheEntry::new(coords, 1000, 500);
assert!(!entry.is_expired(1000));
assert!(!entry.is_expired(1500)); // expires_at = 1500, not yet expired
assert!(entry.is_expired(1501)); // one ms after expiry
assert!(entry.is_expired(2000));
}
#[test]
fn test_cache_entry_refresh() {
let coords = make_coords(&[1, 0]);
let mut entry = CacheEntry::new(coords, 1000, 500);
assert!(entry.is_expired(1501)); // expires_at = 1500
entry.refresh(1400, 500); // new expires_at = 1900
assert!(!entry.is_expired(1600));
assert!(!entry.is_expired(1900)); // at exactly expiry, not expired
assert!(entry.is_expired(1901)); // one ms after expiry
}
#[test]
fn test_cache_entry_times() {
let coords = make_coords(&[1, 0]);
let entry = CacheEntry::new(coords, 1000, 500);
assert_eq!(entry.created_at(), 1000);
assert_eq!(entry.last_used(), 1000);
assert_eq!(entry.expires_at(), 1500);
assert_eq!(entry.age(1200), 200);
assert_eq!(entry.idle_time(1200), 200);
assert_eq!(entry.time_to_expiry(1200), 300);
assert_eq!(entry.time_to_expiry(1600), 0);
}
#[test]
fn test_cache_entry_touch() {
let coords = make_coords(&[1, 0]);
let mut entry = CacheEntry::new(coords, 1000, 500);
assert_eq!(entry.last_used(), 1000);
entry.touch(1300);
assert_eq!(entry.last_used(), 1300);
// Touch doesn't affect expiry
assert_eq!(entry.expires_at(), 1500);
}
#[test]
fn test_cache_entry_update() {
let mut entry = CacheEntry::new(make_coords(&[1, 0]), 1000, 500);
let new_coords = make_coords(&[1, 2, 0]);
entry.update(new_coords.clone(), 2000, 600);
assert_eq!(entry.coords(), &new_coords);
assert_eq!(entry.last_used(), 2000);
assert_eq!(entry.expires_at(), 2600);
// created_at is unchanged
assert_eq!(entry.created_at(), 1000);
}
#[test]
fn test_cache_entry_saturating_add() {
let coords = make_coords(&[1, 0]);
let entry = CacheEntry::new(coords, u64::MAX - 10, 100);
// Should saturate to u64::MAX rather than overflow
assert_eq!(entry.expires_at(), u64::MAX);
}
}