fips_core/tree/coordinate.rs
1//! Tree coordinates and distance calculations.
2
3use std::fmt;
4
5use super::TreeError;
6use crate::NodeAddr;
7
8/// Metadata for a single node in a tree coordinate path.
9///
10/// Carries the node address and its declaration metadata (sequence number
11/// and timestamp). Used in TreeCoordinate entries and TreeAnnounce wire
12/// format.
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct CoordEntry {
15 /// The node's routing address.
16 pub node_addr: NodeAddr,
17 /// The node's declaration sequence number.
18 pub sequence: u64,
19 /// The node's declaration timestamp (Unix seconds).
20 pub timestamp: u64,
21}
22
23impl CoordEntry {
24 /// Wire size of a serialized entry: node_addr(16) + sequence(8) + timestamp(8).
25 pub const WIRE_SIZE: usize = 32;
26
27 /// Create a new coordinate entry.
28 pub fn new(node_addr: NodeAddr, sequence: u64, timestamp: u64) -> Self {
29 Self {
30 node_addr,
31 sequence,
32 timestamp,
33 }
34 }
35
36 /// Create an entry with default metadata (sequence=0, timestamp=0).
37 ///
38 /// Useful for constructing coordinates when only routing (not wire format)
39 /// is needed, e.g., in tests or distance calculations.
40 pub fn addr_only(node_addr: NodeAddr) -> Self {
41 Self {
42 node_addr,
43 sequence: 0,
44 timestamp: 0,
45 }
46 }
47}
48
49/// A node's coordinates in the spanning tree.
50///
51/// Coordinates are the path from the node to the root:
52/// `[self, parent, grandparent, ..., root]`
53///
54/// Each entry carries the node address plus declaration metadata (sequence
55/// and timestamp) for the wire protocol. Routing operations (distance,
56/// LCA) use only the node addresses.
57///
58/// The coordinate enables greedy routing via tree distance calculation.
59/// Two nodes can compute the hops between them by finding their lowest
60/// common ancestor (LCA) in the tree.
61#[derive(Clone, PartialEq, Eq)]
62pub struct TreeCoordinate(Vec<CoordEntry>);
63
64impl TreeCoordinate {
65 /// Create a coordinate from a path of entries (self to root).
66 ///
67 /// The path must be non-empty and ordered from the node to the root.
68 pub fn new(path: Vec<CoordEntry>) -> Result<Self, TreeError> {
69 if path.is_empty() {
70 return Err(TreeError::EmptyCoordinate);
71 }
72 Ok(Self(path))
73 }
74
75 /// Create a coordinate from node addresses only (no metadata).
76 ///
77 /// Convenience constructor for cases where only routing is needed.
78 /// Each entry gets sequence=0, timestamp=0.
79 pub fn from_addrs(addrs: Vec<NodeAddr>) -> Result<Self, TreeError> {
80 if addrs.is_empty() {
81 return Err(TreeError::EmptyCoordinate);
82 }
83 Ok(Self(addrs.into_iter().map(CoordEntry::addr_only).collect()))
84 }
85
86 /// Create a coordinate for a root node.
87 pub fn root(node_addr: NodeAddr) -> Self {
88 Self(vec![CoordEntry::addr_only(node_addr)])
89 }
90
91 /// Create a root coordinate with metadata.
92 pub fn root_with_meta(node_addr: NodeAddr, sequence: u64, timestamp: u64) -> Self {
93 Self(vec![CoordEntry::new(node_addr, sequence, timestamp)])
94 }
95
96 /// The node this coordinate belongs to (first element).
97 pub fn node_addr(&self) -> &NodeAddr {
98 &self.0[0].node_addr
99 }
100
101 /// The root of the tree (last element).
102 pub fn root_id(&self) -> &NodeAddr {
103 &self.0.last().expect("coordinate never empty").node_addr
104 }
105
106 /// The immediate parent (second element, or self if root).
107 pub fn parent_id(&self) -> &NodeAddr {
108 self.0
109 .get(1)
110 .map(|e| &e.node_addr)
111 .unwrap_or(&self.0[0].node_addr)
112 }
113
114 /// Depth in the tree (0 = root).
115 pub fn depth(&self) -> usize {
116 self.0.len() - 1
117 }
118
119 /// The full path of entries with metadata.
120 pub fn entries(&self) -> &[CoordEntry] {
121 &self.0
122 }
123
124 /// Iterator over node addresses in the path (self to root).
125 ///
126 /// Use this for routing operations (distance, LCA, ancestor checks)
127 /// that only need the address path.
128 pub fn node_addrs(&self) -> impl DoubleEndedIterator<Item = &NodeAddr> {
129 self.0.iter().map(|e| &e.node_addr)
130 }
131
132 /// Check if this coordinate is a root (length 1).
133 pub fn is_root(&self) -> bool {
134 self.0.len() == 1
135 }
136
137 /// Calculate tree distance to another coordinate.
138 ///
139 /// Distance is hops through the lowest common ancestor (LCA).
140 /// If the coordinates have different roots, returns usize::MAX.
141 pub fn distance_to(&self, other: &TreeCoordinate) -> usize {
142 // Different trees have infinite distance
143 if self.root_id() != other.root_id() {
144 return usize::MAX;
145 }
146
147 let lca_depth = self.lca_depth(other);
148 let self_to_lca = self.depth() - lca_depth;
149 let other_to_lca = other.depth() - lca_depth;
150 self_to_lca + other_to_lca
151 }
152
153 /// Find the depth of the lowest common ancestor.
154 ///
155 /// Since coordinates are self-to-root, common ancestry is a suffix match.
156 /// Returns the depth (from root) of the LCA.
157 pub fn lca_depth(&self, other: &TreeCoordinate) -> usize {
158 let mut common: usize = 0;
159 let self_rev = self.node_addrs().rev();
160 let other_rev = other.node_addrs().rev();
161
162 for (a, b) in self_rev.zip(other_rev) {
163 if a == b {
164 common += 1;
165 } else {
166 break;
167 }
168 }
169
170 // LCA depth is counted from root (depth 0)
171 common.saturating_sub(1)
172 }
173
174 /// Get the lowest common ancestor node ID.
175 pub fn lca(&self, other: &TreeCoordinate) -> Option<&NodeAddr> {
176 let self_rev: Vec<_> = self.node_addrs().rev().collect();
177 let other_rev: Vec<_> = other.node_addrs().rev().collect();
178
179 let mut lca = None;
180 for (a, b) in self_rev.iter().zip(other_rev.iter()) {
181 if a == b {
182 lca = Some(*a);
183 } else {
184 break;
185 }
186 }
187 lca
188 }
189
190 /// Check if `other` is an ancestor (appears in our path after self).
191 pub fn has_ancestor(&self, other: &NodeAddr) -> bool {
192 self.node_addrs().skip(1).any(|id| id == other)
193 }
194
195 /// Check if `other` is in our ancestry (including self).
196 pub fn contains(&self, other: &NodeAddr) -> bool {
197 self.node_addrs().any(|id| id == other)
198 }
199
200 /// Get the ancestor at a specific depth from self.
201 ///
202 /// `ancestor_at(0)` returns self, `ancestor_at(1)` returns parent, etc.
203 pub fn ancestor_at(&self, depth: usize) -> Option<&NodeAddr> {
204 self.0.get(depth).map(|e| &e.node_addr)
205 }
206}
207
208impl fmt::Debug for TreeCoordinate {
209 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210 write!(f, "TreeCoordinate(depth={}, path=[", self.depth())?;
211 for (i, entry) in self.0.iter().enumerate() {
212 if i > 0 {
213 write!(f, " → ")?;
214 }
215 // Show first 4 bytes of each node ID
216 write!(
217 f,
218 "{:02x}{:02x}",
219 entry.node_addr.as_bytes()[0],
220 entry.node_addr.as_bytes()[1]
221 )?;
222 }
223 write!(f, "])")
224 }
225}