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    dns_resolved: bool,
23}
24
25impl IdentityCacheEntry {
26    fn new(
27        node_addr: NodeAddr,
28        pubkey: secp256k1::PublicKey,
29        npub: String,
30        last_seen_ms: u64,
31        dns_resolved: bool,
32    ) -> Self {
33        Self {
34            node_addr,
35            pubkey,
36            npub,
37            last_seen_ms,
38            dns_resolved,
39        }
40    }
41}
42
43/// Prefix-indexed identity cache for FipsAddress/NodeAddr lookup.
44#[derive(Debug, Default)]
45pub(in crate::node) struct IdentityCache {
46    entries: HashMap<[u8; 15], IdentityCacheEntry>,
47}
48
49impl IdentityCache {
50    pub(in crate::node) fn prefix_for(node_addr: &NodeAddr) -> [u8; 15] {
51        let mut prefix = [0u8; 15];
52        prefix.copy_from_slice(&node_addr.as_bytes()[0..15]);
53        prefix
54    }
55
56    pub(in crate::node) fn register(
57        &mut self,
58        node_addr: NodeAddr,
59        pubkey: secp256k1::PublicKey,
60        now_ms: u64,
61        max_entries: usize,
62    ) -> bool {
63        self.register_inner(node_addr, pubkey, now_ms, max_entries, false)
64    }
65
66    pub(in crate::node) fn register_dns_resolved(
67        &mut self,
68        node_addr: NodeAddr,
69        pubkey: secp256k1::PublicKey,
70        now_ms: u64,
71        max_entries: usize,
72    ) -> bool {
73        self.register_inner(node_addr, pubkey, now_ms, max_entries, true)
74    }
75
76    fn register_inner(
77        &mut self,
78        node_addr: NodeAddr,
79        pubkey: secp256k1::PublicKey,
80        now_ms: u64,
81        max_entries: usize,
82        dns_resolved: bool,
83    ) -> bool {
84        let prefix = Self::prefix_for(&node_addr);
85        if let Some(entry) = self.entries.get_mut(&prefix)
86            && entry.node_addr == node_addr
87            && entry.pubkey == pubkey
88        {
89            entry.last_seen_ms = now_ms;
90            entry.dns_resolved |= dns_resolved;
91            return true;
92        }
93
94        let (xonly, _) = pubkey.x_only_public_key();
95        let derived_node_addr = NodeAddr::from_pubkey(&xonly);
96        if derived_node_addr != node_addr {
97            debug!(
98                claimed_node_addr = %node_addr,
99                derived_node_addr = %derived_node_addr,
100                "Rejected identity cache entry with mismatched public key"
101            );
102            return false;
103        }
104
105        if let Some(entry) = self.entries.get_mut(&prefix)
106            && entry.node_addr == node_addr
107        {
108            entry.pubkey = pubkey;
109            entry.last_seen_ms = now_ms;
110            entry.dns_resolved |= dns_resolved;
111            return true;
112        }
113
114        let npub = encode_npub(&xonly);
115        self.entries.insert(
116            prefix,
117            IdentityCacheEntry::new(node_addr, pubkey, npub, now_ms, dns_resolved),
118        );
119        self.evict_lru(max_entries);
120        true
121    }
122
123    pub(in crate::node) fn lookup_by_prefix(
124        &mut self,
125        prefix: &[u8; 15],
126        now_ms: u64,
127    ) -> Option<(NodeAddr, secp256k1::PublicKey)> {
128        let entry = self.entries.get_mut(prefix)?;
129        entry.last_seen_ms = now_ms;
130        Some((entry.node_addr, entry.pubkey))
131    }
132
133    pub(in crate::node) fn has_prefix_for(&self, node_addr: &NodeAddr) -> bool {
134        self.entries.contains_key(&Self::prefix_for(node_addr))
135    }
136
137    pub(in crate::node) fn is_dns_resolved(&self, node_addr: &NodeAddr) -> bool {
138        self.entries
139            .get(&Self::prefix_for(node_addr))
140            .is_some_and(|entry| entry.node_addr == *node_addr && entry.dns_resolved)
141    }
142
143    pub(in crate::node) fn len(&self) -> usize {
144        self.entries.len()
145    }
146
147    pub(in crate::node) fn iter(
148        &self,
149    ) -> impl Iterator<Item = (&NodeAddr, &secp256k1::PublicKey, u64)> {
150        self.entries
151            .values()
152            .map(|entry| (&entry.node_addr, &entry.pubkey, entry.last_seen_ms))
153    }
154
155    pub(in crate::node) fn pubkey_for_node_addr(
156        &self,
157        addr: &NodeAddr,
158    ) -> Option<secp256k1::PublicKey> {
159        self.entries
160            .get(&Self::prefix_for(addr))
161            .filter(|entry| &entry.node_addr == addr)
162            .map(|entry| entry.pubkey)
163    }
164
165    pub(in crate::node) fn npub_for_node_addr(&self, addr: &NodeAddr) -> Option<String> {
166        self.entries
167            .get(&Self::prefix_for(addr))
168            .filter(|entry| &entry.node_addr == addr)
169            .map(|entry| entry.npub.clone())
170    }
171
172    fn evict_lru(&mut self, max_entries: usize) {
173        if self.entries.len() > max_entries
174            && let Some(oldest_key) = self
175                .entries
176                .iter()
177                .min_by_key(|(_, entry)| entry.last_seen_ms)
178                .map(|(key, _)| *key)
179        {
180            self.entries.remove(&oldest_key);
181        }
182    }
183}