/ abzu-router / src / table.rs
table.rs
  1  //! Routing Table
  2  //!
  3  //! Greedy routing based on tree coordinates and DHT-style key distance.
  4  //!
  5  //! This is **Pure Logic** - no IO, no networking.
  6  //! Takes State → Returns Decisions.
  7  
  8  use std::collections::HashMap;
  9  
 10  use ed25519_dalek::VerifyingKey;
 11  use serde::{Deserialize, Serialize};
 12  use thiserror::Error;
 13  
 14  use crate::address::{address_for_key, Address};
 15  use crate::coords::{RouteDirection, TreeCoords};
 16  
 17  /// Errors from routing operations
 18  #[derive(Error, Debug, Clone)]
 19  pub enum RoutingError {
 20      #[error("No route to destination")]
 21      NoRoute,
 22      
 23      #[error("Peer not found: {0}")]
 24      PeerNotFound(String),
 25      
 26      #[error("Invalid address")]
 27      InvalidAddress,
 28  
 29      #[error("Routing table full (max {0} peers)")]
 30      TableFull(usize),
 31  
 32      #[error("Too many peers from same prefix (max {0})")]
 33      PrefixLimitReached(usize),
 34  }
 35  
 36  /// A peer's identity (32-byte Ed25519 public key)
 37  pub type PeerKey = [u8; 32];
 38  
 39  /// Information about a known peer
 40  #[derive(Debug, Clone, Serialize, Deserialize)]
 41  pub struct PeerInfo {
 42      /// The peer's public key
 43      pub key: PeerKey,
 44      /// The peer's tree coordinates  
 45      pub coords: TreeCoords,
 46      /// The peer's derived IPv6 address
 47      pub address: Address,
 48      /// Port number on our side (which child slot they occupy)
 49      pub port: u64,
 50      /// Is this our parent in the tree?
 51      pub is_parent: bool,
 52      /// Link quality metric (lower is better)
 53      pub cost: u64,
 54  }
 55  
 56  impl PeerInfo {
 57      /// Create a new peer info from a verifying key
 58      pub fn new(key: &VerifyingKey, coords: TreeCoords, port: u64, is_parent: bool, cost: u64) -> Self {
 59          let address = address_for_key(key);
 60          Self {
 61              key: *key.as_bytes(),
 62              coords,
 63              address,
 64              port,
 65              is_parent,
 66              cost,
 67          }
 68      }
 69  
 70      /// Create from raw key bytes (for testing)
 71      pub fn from_key_bytes(key: PeerKey, coords: TreeCoords, port: u64, is_parent: bool, cost: u64) -> Self {
 72          // Derive address from key bytes
 73          // Note: This creates a dummy key for address derivation
 74          let mut addr_bytes = [0u8; 16];
 75          addr_bytes[0] = 0x02;
 76          // Use first byte of key as ones count, rest as address bits
 77          addr_bytes[1] = key[0] & 0x7F; // max 127 ones
 78          addr_bytes[2..].copy_from_slice(&key[..14]);
 79          
 80          Self {
 81              key,
 82              coords,
 83              address: Address::from_bytes(addr_bytes),
 84              port,
 85              is_parent,
 86              cost,
 87          }
 88      }
 89  }
 90  
 91  /// Routing table configuration for Sybil resistance
 92  #[derive(Debug, Clone)]
 93  pub struct TableConfig {
 94      /// Maximum total peers (0 = unlimited)
 95      pub max_peers: usize,
 96      /// Maximum peers sharing the same first byte of pubkey (0 = unlimited)
 97      pub max_peers_per_prefix: usize,
 98  }
 99  
100  impl Default for TableConfig {
101      fn default() -> Self {
102          Self {
103              max_peers: 256,           // Reasonable limit for mesh connectivity
104              max_peers_per_prefix: 16, // Limits clustering in key space
105          }
106      }
107  }
108  
109  /// The routing table for a node
110  #[derive(Debug, Clone)]
111  pub struct RoutingTable {
112      /// Our own key (reserved for future direct routing)
113      #[allow(dead_code)]
114      self_key: PeerKey,
115      /// Our own coordinates
116      self_coords: TreeCoords,
117      /// Our own address
118      self_address: Address,
119      /// Known peers indexed by their key
120      peers: HashMap<PeerKey, PeerInfo>,
121      /// Parent peer key (if we have one)
122      parent: Option<PeerKey>,
123      /// Children indexed by port number
124      children: HashMap<u64, PeerKey>,
125      /// Configuration for Sybil resistance
126      config: TableConfig,
127  }
128  
129  impl RoutingTable {
130      /// Create a new routing table for a node
131      pub fn new(self_key: &VerifyingKey, self_coords: TreeCoords) -> Self {
132          Self::with_config(self_key, self_coords, TableConfig::default())
133      }
134  
135      /// Create a new routing table with custom configuration
136      pub fn with_config(self_key: &VerifyingKey, self_coords: TreeCoords, config: TableConfig) -> Self {
137          let self_address = address_for_key(self_key);
138          Self {
139              self_key: *self_key.as_bytes(),
140              self_coords,
141              self_address,
142              peers: HashMap::new(),
143              parent: None,
144              children: HashMap::new(),
145              config,
146          }
147      }
148  
149      /// Create from raw key bytes (for testing)
150      pub fn from_key_bytes(self_key: PeerKey, self_coords: TreeCoords) -> Self {
151          Self::from_key_bytes_with_config(self_key, self_coords, TableConfig::default())
152      }
153  
154      /// Create from raw key bytes with custom config (for testing)
155      pub fn from_key_bytes_with_config(self_key: PeerKey, self_coords: TreeCoords, config: TableConfig) -> Self {
156          let mut addr_bytes = [0u8; 16];
157          addr_bytes[0] = 0x02;
158          addr_bytes[1] = self_key[0] & 0x7F;
159          addr_bytes[2..].copy_from_slice(&self_key[..14]);
160          
161          Self {
162              self_key,
163              self_coords,
164              self_address: Address::from_bytes(addr_bytes),
165              peers: HashMap::new(),
166              parent: None,
167              children: HashMap::new(),
168              config,
169          }
170      }
171  
172      /// Get our own address
173      pub fn self_address(&self) -> &Address {
174          &self.self_address
175      }
176  
177      /// Get our own coordinates
178      pub fn self_coords(&self) -> &TreeCoords {
179          &self.self_coords
180      }
181  
182      /// Add or update a peer. Returns error if limits would be exceeded.
183      pub fn add_peer(&mut self, peer: PeerInfo) -> Result<(), RoutingError> {
184          let key = peer.key;
185          
186          // If updating existing peer, skip limit checks
187          if !self.peers.contains_key(&key) {
188              // Check total peer limit
189              if self.config.max_peers > 0 && self.peers.len() >= self.config.max_peers {
190                  return Err(RoutingError::TableFull(self.config.max_peers));
191              }
192              
193              // Check per-prefix limit (first byte of pubkey)
194              if self.config.max_peers_per_prefix > 0 {
195                  let prefix = key[0];
196                  let same_prefix = self.peers.keys().filter(|k| k[0] == prefix).count();
197                  if same_prefix >= self.config.max_peers_per_prefix {
198                      return Err(RoutingError::PrefixLimitReached(self.config.max_peers_per_prefix));
199                  }
200              }
201          }
202          
203          if peer.is_parent {
204              self.parent = Some(key);
205          } else {
206              self.children.insert(peer.port, key);
207          }
208          
209          self.peers.insert(key, peer);
210          Ok(())
211      }
212  
213      /// Remove a peer
214      pub fn remove_peer(&mut self, key: &PeerKey) -> Option<PeerInfo> {
215          if let Some(peer) = self.peers.remove(key) {
216              if peer.is_parent {
217                  self.parent = None;
218              } else {
219                  self.children.remove(&peer.port);
220              }
221              Some(peer)
222          } else {
223              None
224          }
225      }
226  
227      /// Get a peer by key
228      pub fn get_peer(&self, key: &PeerKey) -> Option<&PeerInfo> {
229          self.peers.get(key)
230      }
231  
232      /// Get all peers
233      pub fn peers(&self) -> impl Iterator<Item = &PeerInfo> {
234          self.peers.values()
235      }
236  
237      /// Get parent peer
238      pub fn parent(&self) -> Option<&PeerInfo> {
239          self.parent.as_ref().and_then(|k| self.peers.get(k))
240      }
241  
242      /// Get children
243      pub fn children(&self) -> impl Iterator<Item = &PeerInfo> {
244          self.children.values().filter_map(|k| self.peers.get(k))
245      }
246  
247      /// Find the next hop for a destination address.
248      ///
249      /// Implements greedy routing:
250      /// 1. If destination is us, return None (we're the destination)
251      /// 2. If destination is in our tree (descendant), route to child
252      /// 3. Otherwise, route toward parent OR use DHT-style closest peer
253      pub fn find_next_hop(&self, destination: &Address) -> Result<Option<PeerKey>, RoutingError> {
254          // Check if destination is us
255          if destination == &self.self_address {
256              return Ok(None);
257          }
258  
259          // First, try tree-based routing if we know the destination's coordinates
260          if let Some(dest_coords) = self.find_coords_for_address(destination) {
261              return self.route_by_coords(&dest_coords);
262          }
263  
264          // Fall back to DHT-style routing (closest key to destination)
265          self.route_by_key_distance(destination)
266      }
267  
268      /// Route using tree coordinates (greedy tree routing)
269      fn route_by_coords(&self, dest_coords: &TreeCoords) -> Result<Option<PeerKey>, RoutingError> {
270          match self.self_coords.route_direction(dest_coords) {
271              RouteDirection::Self_ => Ok(None),
272              
273              RouteDirection::Up => {
274                  // Send to parent
275                  self.parent.ok_or(RoutingError::NoRoute).map(Some)
276              }
277              
278              RouteDirection::Down(port) => {
279                  // Send to child on that port
280                  self.children
281                      .get(&port)
282                      .copied()
283                      .ok_or(RoutingError::NoRoute)
284                      .map(Some)
285              }
286          }
287      }
288  
289      /// Route using DHT-style key distance (XOR metric)
290      fn route_by_key_distance(&self, destination: &Address) -> Result<Option<PeerKey>, RoutingError> {
291          let dest_bytes = destination.as_bytes();
292          
293          // Find peer with smallest XOR distance to destination
294          let mut best_peer: Option<&PeerInfo> = None;
295          let mut best_distance = u128::MAX;
296          
297          for peer in self.peers.values() {
298              let peer_addr = peer.address.as_bytes();
299              let distance = xor_distance(peer_addr, dest_bytes);
300              
301              if distance < best_distance {
302                  best_distance = distance;
303                  best_peer = Some(peer);
304              }
305          }
306  
307          // Check if any peer is closer than we are
308          let self_distance = xor_distance(self.self_address.as_bytes(), dest_bytes);
309          
310          if let Some(peer) = best_peer
311              && best_distance < self_distance
312          {
313              return Ok(Some(peer.key));
314          }
315  
316          // No peer is closer - we might be the closest, but we're not the destination
317          // This could mean we need to wait for more routing info
318          Err(RoutingError::NoRoute)
319      }
320  
321      /// Find coordinates for an address (if we know them)
322      fn find_coords_for_address(&self, address: &Address) -> Option<TreeCoords> {
323          // Check if it's a known peer
324          for peer in self.peers.values() {
325              if &peer.address == address {
326                  return Some(peer.coords.clone());
327              }
328          }
329          None
330      }
331  
332      /// Get routing table statistics
333      pub fn stats(&self) -> RoutingStats {
334          RoutingStats {
335              peer_count: self.peers.len(),
336              has_parent: self.parent.is_some(),
337              child_count: self.children.len(),
338              tree_depth: self.self_coords.depth(),
339          }
340      }
341  }
342  
343  /// Routing table statistics
344  #[derive(Debug, Clone)]
345  pub struct RoutingStats {
346      pub peer_count: usize,
347      pub has_parent: bool,
348      pub child_count: usize,
349      pub tree_depth: usize,
350  }
351  
352  /// Calculate XOR distance between two addresses (for DHT routing)
353  fn xor_distance(a: &[u8; 16], b: &[u8; 16]) -> u128 {
354      let a_val = u128::from_be_bytes(*a);
355      let b_val = u128::from_be_bytes(*b);
356      a_val ^ b_val
357  }
358  
359  #[cfg(test)]
360  mod tests {
361      use super::*;
362  
363      fn make_key(seed: u8) -> PeerKey {
364          let mut key = [0u8; 32];
365          key[0] = seed;
366          key[1] = seed.wrapping_add(1);
367          for i in 2..32 {
368              key[i] = seed.wrapping_add(i as u8);
369          }
370          key
371      }
372  
373      #[test]
374      fn test_add_remove_peer() {
375          let self_key = make_key(0);
376          let mut table = RoutingTable::from_key_bytes(self_key, TreeCoords::root());
377          
378          let peer_key = make_key(1);
379          let peer = PeerInfo::from_key_bytes(
380              peer_key,
381              TreeCoords::from_path(vec![0]),
382              0,
383              false,
384              100,
385          );
386          
387          let _ = table.add_peer(peer);
388          assert!(table.get_peer(&peer_key).is_some());
389          
390          table.remove_peer(&peer_key);
391          assert!(table.get_peer(&peer_key).is_none());
392      }
393  
394      #[test]
395      fn test_tree_routing() {
396          let self_key = make_key(0);
397          let mut table = RoutingTable::from_key_bytes(
398              self_key,
399              TreeCoords::from_path(vec![1, 2]),
400          );
401          
402          // Add parent
403          let parent_key = make_key(1);
404          let parent = PeerInfo::from_key_bytes(
405              parent_key,
406              TreeCoords::from_path(vec![1]),
407              0,
408              true,
409              50,
410          );
411          let _ = table.add_peer(parent);
412          
413          // Add child
414          let child_key = make_key(2);
415          let child = PeerInfo::from_key_bytes(
416              child_key,
417              TreeCoords::from_path(vec![1, 2, 5]),
418              5,
419              false,
420              50,
421          );
422          let _ = table.add_peer(child);
423          
424          // Route to child's subtree should go to child
425          let child_dest = table.get_peer(&child_key).unwrap().address;
426          let next_hop = table.find_next_hop(&child_dest).unwrap();
427          assert_eq!(next_hop, Some(child_key));
428          
429          // Route to parent should go to parent  
430          let parent_dest = table.get_peer(&parent_key).unwrap().address;
431          let next_hop = table.find_next_hop(&parent_dest).unwrap();
432          assert_eq!(next_hop, Some(parent_key));
433      }
434  
435      #[test]
436      fn test_self_destination() {
437          let self_key = make_key(0);
438          let table = RoutingTable::from_key_bytes(self_key, TreeCoords::root());
439          
440          let result = table.find_next_hop(table.self_address());
441          assert_eq!(result.unwrap(), None);
442      }
443  
444      #[test]
445      fn test_stats() {
446          let self_key = make_key(0);
447          let mut table = RoutingTable::from_key_bytes(
448              self_key,
449              TreeCoords::from_path(vec![1, 2, 3]),
450          );
451          
452          let stats = table.stats();
453          assert_eq!(stats.peer_count, 0);
454          assert!(!stats.has_parent);
455          assert_eq!(stats.tree_depth, 3);
456          
457          // Add a parent
458          let parent = PeerInfo::from_key_bytes(
459              make_key(1),
460              TreeCoords::from_path(vec![1, 2]),
461              0,
462              true,
463              50,
464          );
465          let _ = table.add_peer(parent);
466          
467          let stats = table.stats();
468          assert_eq!(stats.peer_count, 1);
469          assert!(stats.has_parent);
470      }
471  
472      #[test]
473      fn test_sybil_max_peers() {
474          let config = TableConfig {
475              max_peers: 3,
476              max_peers_per_prefix: 0, // Disabled
477          };
478          let self_key = make_key(0);
479          let mut table = RoutingTable::from_key_bytes_with_config(
480              self_key,
481              TreeCoords::root(),
482              config,
483          );
484          
485          // Add 3 peers - should succeed
486          for i in 1..=3 {
487              let peer = PeerInfo::from_key_bytes(make_key(i), TreeCoords::root(), i as u64, false, 50);
488              assert!(table.add_peer(peer).is_ok());
489          }
490          
491          // 4th peer should fail
492          let peer = PeerInfo::from_key_bytes(make_key(4), TreeCoords::root(), 4, false, 50);
493          let err = table.add_peer(peer).unwrap_err();
494          assert!(matches!(err, RoutingError::TableFull(3)));
495          
496          // Updating existing peer should still work
497          let peer = PeerInfo::from_key_bytes(make_key(1), TreeCoords::root(), 1, false, 100);
498          assert!(table.add_peer(peer).is_ok());
499      }
500  
501      #[test]
502      fn test_sybil_prefix_limit() {
503          let config = TableConfig {
504              max_peers: 256,
505              max_peers_per_prefix: 2, // Only 2 peers per prefix
506          };
507          let self_key = make_key(0);
508          let mut table = RoutingTable::from_key_bytes_with_config(
509              self_key,
510              TreeCoords::root(),
511              config,
512          );
513          
514          // make_key puts the index in the first byte, so keys 1,2 share prefix 0x01, 0x02
515          // Create peers with same prefix (we'll force same first byte)
516          let mut key1 = [0u8; 32];
517          key1[0] = 0xAA;
518          key1[1] = 0x01;
519          let mut key2 = [0u8; 32];
520          key2[0] = 0xAA;
521          key2[1] = 0x02;
522          let mut key3 = [0u8; 32];
523          key3[0] = 0xAA;
524          key3[1] = 0x03;
525          let mut key4 = [0u8; 32];
526          key4[0] = 0xBB; // Different prefix
527          
528          // Add 2 peers with prefix 0xAA - should succeed
529          let peer1 = PeerInfo::from_key_bytes(key1, TreeCoords::root(), 1, false, 50);
530          let peer2 = PeerInfo::from_key_bytes(key2, TreeCoords::root(), 2, false, 50);
531          assert!(table.add_peer(peer1).is_ok());
532          assert!(table.add_peer(peer2).is_ok());
533          
534          // 3rd peer with same prefix should fail
535          let peer3 = PeerInfo::from_key_bytes(key3, TreeCoords::root(), 3, false, 50);
536          let err = table.add_peer(peer3).unwrap_err();
537          assert!(matches!(err, RoutingError::PrefixLimitReached(2)));
538          
539          // Peer with different prefix should succeed
540          let peer4 = PeerInfo::from_key_bytes(key4, TreeCoords::root(), 4, false, 50);
541          assert!(table.add_peer(peer4).is_ok());
542      }
543  }