Skip to main content

fips_core/node/
identity_cache.rs

1use super::*;
2
3/// Source-attributed packet delivered by a node running without a system TUN.
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct NodeDeliveredPacket {
6    /// FIPS node address that originated the packet.
7    pub source_node_addr: NodeAddr,
8    /// Source Nostr public key when the node has learned it.
9    pub source_npub: Option<String>,
10    /// Destination FIPS address from the IPv6 packet.
11    pub destination: FipsAddress,
12    /// Full IPv6 packet after FIPS session decapsulation.
13    pub packet: Vec<u8>,
14}
15
16#[derive(Debug, Clone)]
17struct IdentityCacheEntry {
18    node_addr: NodeAddr,
19    pubkey: secp256k1::PublicKey,
20    npub: String,
21    last_seen_ms: u64,
22}
23
24impl IdentityCacheEntry {
25    fn new(
26        node_addr: NodeAddr,
27        pubkey: secp256k1::PublicKey,
28        npub: String,
29        last_seen_ms: u64,
30    ) -> Self {
31        Self {
32            node_addr,
33            pubkey,
34            npub,
35            last_seen_ms,
36        }
37    }
38}
39
40/// Prefix-indexed identity cache for FipsAddress/NodeAddr lookup.
41#[derive(Debug, Default)]
42pub(in crate::node) struct IdentityCache {
43    entries: HashMap<[u8; 15], IdentityCacheEntry>,
44}
45
46impl IdentityCache {
47    pub(in crate::node) fn prefix_for(node_addr: &NodeAddr) -> [u8; 15] {
48        let mut prefix = [0u8; 15];
49        prefix.copy_from_slice(&node_addr.as_bytes()[0..15]);
50        prefix
51    }
52
53    pub(in crate::node) fn register(
54        &mut self,
55        node_addr: NodeAddr,
56        pubkey: secp256k1::PublicKey,
57        now_ms: u64,
58        max_entries: usize,
59    ) -> bool {
60        let prefix = Self::prefix_for(&node_addr);
61        if let Some(entry) = self.entries.get(&prefix)
62            && entry.node_addr == node_addr
63            && entry.pubkey == pubkey
64        {
65            return true;
66        }
67
68        let (xonly, _) = pubkey.x_only_public_key();
69        let derived_node_addr = NodeAddr::from_pubkey(&xonly);
70        if derived_node_addr != node_addr {
71            debug!(
72                claimed_node_addr = %node_addr,
73                derived_node_addr = %derived_node_addr,
74                "Rejected identity cache entry with mismatched public key"
75            );
76            return false;
77        }
78
79        if let Some(entry) = self.entries.get_mut(&prefix)
80            && entry.node_addr == node_addr
81        {
82            entry.pubkey = pubkey;
83            entry.last_seen_ms = now_ms;
84            return true;
85        }
86
87        let npub = encode_npub(&xonly);
88        self.entries.insert(
89            prefix,
90            IdentityCacheEntry::new(node_addr, pubkey, npub, now_ms),
91        );
92        self.evict_lru(max_entries);
93        true
94    }
95
96    pub(in crate::node) fn lookup_by_prefix(
97        &mut self,
98        prefix: &[u8; 15],
99        now_ms: u64,
100    ) -> Option<(NodeAddr, secp256k1::PublicKey)> {
101        let entry = self.entries.get_mut(prefix)?;
102        entry.last_seen_ms = now_ms;
103        Some((entry.node_addr, entry.pubkey))
104    }
105
106    pub(in crate::node) fn has_prefix_for(&self, node_addr: &NodeAddr) -> bool {
107        self.entries.contains_key(&Self::prefix_for(node_addr))
108    }
109
110    pub(in crate::node) fn len(&self) -> usize {
111        self.entries.len()
112    }
113
114    pub(in crate::node) fn iter(
115        &self,
116    ) -> impl Iterator<Item = (&NodeAddr, &secp256k1::PublicKey, u64)> {
117        self.entries
118            .values()
119            .map(|entry| (&entry.node_addr, &entry.pubkey, entry.last_seen_ms))
120    }
121
122    pub(in crate::node) fn pubkey_for_node_addr(
123        &self,
124        addr: &NodeAddr,
125    ) -> Option<secp256k1::PublicKey> {
126        self.entries
127            .get(&Self::prefix_for(addr))
128            .filter(|entry| &entry.node_addr == addr)
129            .map(|entry| entry.pubkey)
130    }
131
132    pub(in crate::node) fn npub_for_node_addr(&self, addr: &NodeAddr) -> Option<String> {
133        self.entries
134            .get(&Self::prefix_for(addr))
135            .filter(|entry| &entry.node_addr == addr)
136            .map(|entry| entry.npub.clone())
137    }
138
139    fn evict_lru(&mut self, max_entries: usize) {
140        if self.entries.len() > max_entries
141            && let Some(oldest_key) = self
142                .entries
143                .iter()
144                .min_by_key(|(_, entry)| entry.last_seen_ms)
145                .map(|(key, _)| *key)
146        {
147            self.entries.remove(&oldest_key);
148        }
149    }
150}