Skip to main content

xds_core/
node.rs

1//! Node identification and hashing for xDS.
2//!
3//! This module provides [`NodeHash`], an efficient node identifier using
4//! FNV-1a hashing. This is used to identify Envoy nodes in the cache
5//! for per-node configuration snapshots.
6
7use std::fmt;
8use std::hash::{Hash, Hasher};
9
10use fnv::FnvHasher;
11
12/// Hash-based node identifier for efficient lookup.
13///
14/// `NodeHash` uses FNV-1a hashing to convert node IDs into fixed-size
15/// hash values for efficient cache lookups. It also supports a wildcard
16/// value for broadcast scenarios.
17///
18/// # Example
19///
20/// ```rust
21/// use xds_core::NodeHash;
22///
23/// let node1 = NodeHash::from_id("envoy-node-1");
24/// let node2 = NodeHash::from_id("envoy-node-2");
25/// let wildcard = NodeHash::wildcard();
26///
27/// assert_ne!(node1, node2);
28/// assert!(wildcard.is_wildcard());
29/// ```
30#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
31pub struct NodeHash {
32    hash: u64,
33    is_wildcard: bool,
34}
35
36impl NodeHash {
37    /// The wildcard hash value (all zeros).
38    const WILDCARD_HASH: u64 = 0;
39
40    /// Create a node hash from a node ID string.
41    ///
42    /// Uses FNV-1a hashing for fast, well-distributed hashes.
43    #[must_use]
44    pub fn from_id(node_id: &str) -> Self {
45        let mut hasher = FnvHasher::default();
46        node_id.hash(&mut hasher);
47        let hash = hasher.finish();
48
49        // Ensure we don't accidentally create a wildcard
50        let hash = if hash == Self::WILDCARD_HASH {
51            hash.wrapping_add(1)
52        } else {
53            hash
54        };
55
56        Self {
57            hash,
58            is_wildcard: false,
59        }
60    }
61
62    /// Create a wildcard node hash that matches all nodes.
63    ///
64    /// This is used when you want to set a snapshot that applies
65    /// to all nodes that don't have a specific snapshot.
66    #[must_use]
67    pub fn wildcard() -> Self {
68        Self {
69            hash: Self::WILDCARD_HASH,
70            is_wildcard: true,
71        }
72    }
73
74    /// Check if this is a wildcard hash.
75    #[must_use]
76    pub fn is_wildcard(&self) -> bool {
77        self.is_wildcard
78    }
79
80    /// Get the raw hash value.
81    #[must_use]
82    pub fn as_u64(&self) -> u64 {
83        self.hash
84    }
85}
86
87impl fmt::Display for NodeHash {
88    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89        if self.is_wildcard {
90            write!(f, "<wildcard>")
91        } else {
92            write!(f, "{:016x}", self.hash)
93        }
94    }
95}
96
97impl Default for NodeHash {
98    fn default() -> Self {
99        Self::wildcard()
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn test_node_hash_creation() {
109        let node = NodeHash::from_id("test-node");
110        assert!(!node.is_wildcard());
111        assert_ne!(node.as_u64(), 0);
112    }
113
114    #[test]
115    fn test_node_hash_deterministic() {
116        let node1 = NodeHash::from_id("test-node");
117        let node2 = NodeHash::from_id("test-node");
118        assert_eq!(node1, node2);
119        assert_eq!(node1.as_u64(), node2.as_u64());
120    }
121
122    #[test]
123    fn test_different_nodes_different_hashes() {
124        let node1 = NodeHash::from_id("node-1");
125        let node2 = NodeHash::from_id("node-2");
126        assert_ne!(node1, node2);
127    }
128
129    #[test]
130    fn test_wildcard() {
131        let wildcard = NodeHash::wildcard();
132        assert!(wildcard.is_wildcard());
133        assert_eq!(wildcard.as_u64(), 0);
134    }
135
136    #[test]
137    fn test_display() {
138        let node = NodeHash::from_id("test");
139        let display = format!("{node}");
140        assert_eq!(display.len(), 16); // 16 hex chars
141
142        let wildcard = NodeHash::wildcard();
143        assert_eq!(format!("{wildcard}"), "<wildcard>");
144    }
145
146    #[test]
147    fn test_default_is_wildcard() {
148        let default = NodeHash::default();
149        assert!(default.is_wildcard());
150    }
151}