Skip to main content

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}