Migrate session-layer addresses from FipsAddress to NodeAddr

Session packet headers use 32-byte NodeAddr (SHA-256 of pubkey) for
routing, not 16-byte FipsAddress (IPv6 ULA). Update all session-layer
structs (SessionDatagram, SessionSetup, SessionAck, DataPacket,
CoordsRequired, PathBroken) and CoordCache to use NodeAddr.

- DATA_HEADER_SIZE: 36 → 68 (two 32-byte addresses + 4 bytes overhead)
- CoordCache: HashMap key FipsAddress → NodeAddr
- Docs: fix header sizes in session-protocol and routing docs
- All 265 tests updated and passing
This commit is contained in:
Johnathan Corgan
2026-02-06 13:48:36 +00:00
parent 5e7342c57c
commit dc12b4a82e
4 changed files with 75 additions and 86 deletions
+2 -2
View File
@@ -584,7 +584,7 @@ enum RouteState {
| LookupResponse | Return coordinates | ~400 bytes | Reply to discovery |
| SessionSetup | Warm router caches + crypto init | ~400-700 bytes | Before data transfer |
| SessionAck | Confirm session + crypto response | ~300-500 bytes | Session confirmation |
| DataPacket | Application data | 36 bytes + payload (minimal) | Bulk of traffic |
| DataPacket | Application data | 68 bytes + payload (minimal) | Bulk of traffic |
| DataPacket | With coordinates | ~300-500 bytes + payload | After CoordsRequired |
| CoordsRequired | Request coords in next packet | ~50 bytes | Cache miss recovery |
@@ -601,7 +601,7 @@ enum RouteState {
- **Bloom filter traffic**: Near zero (event-driven, no changes)
- **Discovery traffic**: Rare (warm caches)
- **Session traffic**: Rare (established sessions)
- **Data traffic**: Minimal overhead (36-byte header)
- **Data traffic**: Minimal overhead (68-byte header)
### Network Churn
+2 -2
View File
@@ -288,7 +288,7 @@ The crypto session handshake (SessionSetup/SessionAck) warms route caches at
intermediate routers as it transits. Each message carries the sender's
coordinates; routers extract and cache `(src_addr, dest_addr) → next_hop` for
both directions. After the handshake completes, data packets use minimal
36-byte headers and routers forward based on cached routes.
68-byte headers and routers forward based on cached routes.
### 5.2 Cache Miss Recovery
@@ -715,7 +715,7 @@ A DataPacket from source S to destination D, transiting router R:
│ │ SESSION LAYER (S↔D encrypted) │ │
│ ├───────────┬───────┬──────────┬──────────┬─────────────────────────────┤ │
│ │ 0x10 │ flags │ hop_limit│ pay_len │ src_addr │ dest_addr │ │
│ │ DataPacket│ 0x00 │ 64 │ 1400 │ 16 bytes │ 16 bytes │ │
│ │ DataPacket│ 0x00 │ 64 │ 1400 │ 32 bytes │ 32 bytes │ │
│ ├───────────┴───────┴──────────┴──────────┴──────────────┴──────────────┤ │
│ │ │ │
│ │ ENCRYPTED PAYLOAD (S↔D session keys) │ │
+30 -36
View File
@@ -5,7 +5,7 @@
//! RouteCache stores coordinates learned from discovery queries.
use crate::tree::TreeCoordinate;
use crate::{FipsAddress, NodeAddr};
use crate::NodeAddr;
use std::collections::HashMap;
use thiserror::Error;
@@ -116,13 +116,13 @@ impl CacheEntry {
/// Coordinate cache for routing decisions.
///
/// Maps FIPS addresses to their tree coordinates, enabling data packets
/// Maps node addresses to their tree coordinates, enabling data packets
/// to be routed without carrying coordinates in every packet. Populated
/// by SessionSetup packets.
#[derive(Clone, Debug)]
pub struct CoordCache {
/// Address -> coordinates mapping.
entries: HashMap<FipsAddress, CacheEntry>,
/// NodeAddr -> coordinates mapping.
entries: HashMap<NodeAddr, CacheEntry>,
/// Maximum number of entries.
max_entries: usize,
/// Default TTL for entries (milliseconds).
@@ -160,7 +160,7 @@ impl CoordCache {
}
/// Insert or update a cache entry.
pub fn insert(&mut self, addr: FipsAddress, coords: TreeCoordinate, current_time_ms: u64) {
pub fn insert(&mut self, addr: NodeAddr, coords: TreeCoordinate, current_time_ms: u64) {
// Update existing entry if present
if let Some(entry) = self.entries.get_mut(&addr) {
entry.update(coords, current_time_ms, self.default_ttl_ms);
@@ -179,7 +179,7 @@ impl CoordCache {
/// Insert with a custom TTL.
pub fn insert_with_ttl(
&mut self,
addr: FipsAddress,
addr: NodeAddr,
coords: TreeCoordinate,
current_time_ms: u64,
ttl_ms: u64,
@@ -198,7 +198,7 @@ impl CoordCache {
}
/// Look up coordinates for an address (without touching).
pub fn get(&self, addr: &FipsAddress, current_time_ms: u64) -> Option<&TreeCoordinate> {
pub fn get(&self, addr: &NodeAddr, current_time_ms: u64) -> Option<&TreeCoordinate> {
self.entries.get(addr).and_then(|entry| {
if entry.is_expired(current_time_ms) {
None
@@ -211,7 +211,7 @@ impl CoordCache {
/// Look up coordinates and touch (update last_used).
pub fn get_and_touch(
&mut self,
addr: &FipsAddress,
addr: &NodeAddr,
current_time_ms: u64,
) -> Option<&TreeCoordinate> {
// Check and remove if expired
@@ -232,17 +232,17 @@ impl CoordCache {
}
/// Get the full cache entry.
pub fn get_entry(&self, addr: &FipsAddress) -> Option<&CacheEntry> {
pub fn get_entry(&self, addr: &NodeAddr) -> Option<&CacheEntry> {
self.entries.get(addr)
}
/// Remove an entry.
pub fn remove(&mut self, addr: &FipsAddress) -> Option<CacheEntry> {
pub fn remove(&mut self, addr: &NodeAddr) -> Option<CacheEntry> {
self.entries.remove(addr)
}
/// Check if an address is cached (and not expired).
pub fn contains(&self, addr: &FipsAddress, current_time_ms: u64) -> bool {
pub fn contains(&self, addr: &NodeAddr, current_time_ms: u64) -> bool {
self.get(addr, current_time_ms).is_some()
}
@@ -413,7 +413,7 @@ impl CachedCoords {
///
/// Separate from CoordCache, this stores routes learned from the discovery
/// protocol (LookupRequest/LookupResponse) rather than session establishment.
/// Keyed by NodeAddr rather than FipsAddress.
/// Keyed by NodeAddr.
#[derive(Clone, Debug)]
pub struct RouteCache {
/// NodeAddr -> discovered coordinates.
@@ -539,12 +539,6 @@ mod tests {
NodeAddr::from_bytes(bytes)
}
fn make_address(val: u8) -> FipsAddress {
let mut bytes = [0xfdu8; 16];
bytes[1] = val;
FipsAddress::from_bytes(bytes).unwrap()
}
fn make_coords(ids: &[u8]) -> TreeCoordinate {
TreeCoordinate::new(ids.iter().map(|&v| make_node_addr(v)).collect()).unwrap()
}
@@ -595,7 +589,7 @@ mod tests {
#[test]
fn test_coord_cache_basic() {
let mut cache = CoordCache::new(100, 1000);
let addr = make_address(1);
let addr = make_node_addr(1);
let coords = make_coords(&[1, 0]);
cache.insert(addr, coords.clone(), 0);
@@ -608,7 +602,7 @@ mod tests {
#[test]
fn test_coord_cache_expiry() {
let mut cache = CoordCache::new(100, 1000);
let addr = make_address(1);
let addr = make_node_addr(1);
let coords = make_coords(&[1, 0]);
cache.insert(addr, coords, 0);
@@ -620,7 +614,7 @@ mod tests {
#[test]
fn test_coord_cache_update() {
let mut cache = CoordCache::new(100, 1000);
let addr = make_address(1);
let addr = make_node_addr(1);
cache.insert(addr, make_coords(&[1, 0]), 0);
cache.insert(addr, make_coords(&[1, 2, 0]), 500);
@@ -634,9 +628,9 @@ mod tests {
fn test_coord_cache_eviction() {
let mut cache = CoordCache::new(2, 10000);
let addr1 = make_address(1);
let addr2 = make_address(2);
let addr3 = make_address(3);
let addr1 = make_node_addr(1);
let addr2 = make_node_addr(2);
let addr3 = make_node_addr(3);
cache.insert(addr1, make_coords(&[1, 0]), 0);
cache.insert(addr2, make_coords(&[2, 0]), 100);
@@ -656,25 +650,25 @@ mod tests {
fn test_coord_cache_evict_expired_first() {
let mut cache = CoordCache::new(2, 100);
cache.insert(make_address(1), make_coords(&[1, 0]), 0);
cache.insert(make_address(2), make_coords(&[2, 0]), 50);
cache.insert(make_node_addr(1), make_coords(&[1, 0]), 0);
cache.insert(make_node_addr(2), make_coords(&[2, 0]), 50);
// At time 150, addr1 is expired, addr2 is not
cache.insert(make_address(3), make_coords(&[3, 0]), 150);
cache.insert(make_node_addr(3), make_coords(&[3, 0]), 150);
// addr1 should be evicted (expired), not addr2 (LRU but not expired)
assert!(!cache.contains(&make_address(1), 150));
assert!(cache.contains(&make_address(2), 150));
assert!(cache.contains(&make_address(3), 150));
assert!(!cache.contains(&make_node_addr(1), 150));
assert!(cache.contains(&make_node_addr(2), 150));
assert!(cache.contains(&make_node_addr(3), 150));
}
#[test]
fn test_coord_cache_purge_expired() {
let mut cache = CoordCache::new(100, 100);
cache.insert(make_address(1), make_coords(&[1, 0]), 0); // expires at 100
cache.insert(make_address(2), make_coords(&[2, 0]), 50); // expires at 150
cache.insert(make_address(3), make_coords(&[3, 0]), 200); // expires at 300
cache.insert(make_node_addr(1), make_coords(&[1, 0]), 0); // expires at 100
cache.insert(make_node_addr(2), make_coords(&[2, 0]), 50); // expires at 150
cache.insert(make_node_addr(3), make_coords(&[3, 0]), 200); // expires at 300
assert_eq!(cache.len(), 3);
@@ -683,15 +677,15 @@ mod tests {
// Entry 1 and 2 expired, entry 3 still valid
assert_eq!(purged, 2);
assert_eq!(cache.len(), 1);
assert!(cache.contains(&make_address(3), 151));
assert!(cache.contains(&make_node_addr(3), 151));
}
#[test]
fn test_coord_cache_stats() {
let mut cache = CoordCache::new(100, 100);
cache.insert(make_address(1), make_coords(&[1, 0]), 0);
cache.insert(make_address(2), make_coords(&[2, 0]), 50);
cache.insert(make_node_addr(1), make_coords(&[1, 0]), 0);
cache.insert(make_node_addr(2), make_coords(&[2, 0]), 50);
let stats = cache.stats(150);
+41 -46
View File
@@ -22,7 +22,7 @@
use crate::bloom::BloomFilter;
use crate::tree::{ParentDeclaration, TreeCoordinate};
use crate::{FipsAddress, NodeAddr};
use crate::NodeAddr;
use secp256k1::schnorr::Signature;
use std::fmt;
use thiserror::Error;
@@ -31,7 +31,8 @@ use thiserror::Error;
pub const PROTOCOL_VERSION: u8 = 1;
/// Data packet header size in bytes (excluding payload).
pub const DATA_HEADER_SIZE: usize = 36;
/// flags(1) + hop_limit(1) + payload_length(2) + src_addr(32) + dest_addr(32) = 68
pub const DATA_HEADER_SIZE: usize = 68;
// ============================================================================
// Link Layer Message Types (peer-to-peer, hop-by-hop)
@@ -496,8 +497,8 @@ impl LookupResponse {
/// address but cannot decrypt the payload.
#[derive(Clone, Debug)]
pub struct SessionDatagram {
/// Destination FIPS address (for routing decisions).
pub dest_addr: FipsAddress,
/// Destination node address (for routing decisions).
pub dest_addr: NodeAddr,
/// Hop limit (decremented at each hop).
pub hop_limit: u8,
/// Encrypted session-layer payload (opaque to intermediate nodes).
@@ -506,7 +507,7 @@ pub struct SessionDatagram {
impl SessionDatagram {
/// Create a new session datagram.
pub fn new(dest_addr: FipsAddress, payload: Vec<u8>) -> Self {
pub fn new(dest_addr: NodeAddr, payload: Vec<u8>) -> Self {
Self {
dest_addr,
hop_limit: 64,
@@ -594,10 +595,10 @@ impl SessionFlags {
/// information. Routers along the path cache the mappings.
#[derive(Clone, Debug)]
pub struct SessionSetup {
/// Source FIPS address.
pub src_addr: FipsAddress,
/// Destination FIPS address.
pub dest_addr: FipsAddress,
/// Source node address.
pub src_addr: NodeAddr,
/// Destination node address.
pub dest_addr: NodeAddr,
/// Source coordinates (for return path caching).
pub src_coords: TreeCoordinate,
/// Destination coordinates (for forward routing).
@@ -609,8 +610,8 @@ pub struct SessionSetup {
impl SessionSetup {
/// Create a new session setup message.
pub fn new(
src_addr: FipsAddress,
dest_addr: FipsAddress,
src_addr: NodeAddr,
dest_addr: NodeAddr,
src_coords: TreeCoordinate,
dest_coords: TreeCoordinate,
) -> Self {
@@ -635,17 +636,17 @@ impl SessionSetup {
/// Sent in response to SessionSetup when request_ack is set.
#[derive(Clone, Debug)]
pub struct SessionAck {
/// Source address (the acknowledger).
pub src_addr: FipsAddress,
/// Destination address (original session initiator).
pub dest_addr: FipsAddress,
/// Source node address (the acknowledger).
pub src_addr: NodeAddr,
/// Destination node address (original session initiator).
pub dest_addr: NodeAddr,
/// Acknowledger's coordinates.
pub src_coords: TreeCoordinate,
}
impl SessionAck {
/// Create a new session acknowledgement.
pub fn new(src_addr: FipsAddress, dest_addr: FipsAddress, src_coords: TreeCoordinate) -> Self {
pub fn new(src_addr: NodeAddr, dest_addr: NodeAddr, src_coords: TreeCoordinate) -> Self {
Self {
src_addr,
dest_addr,
@@ -715,12 +716,12 @@ impl DataFlags {
/// Minimal data packet with addresses only (no coordinates).
///
/// The 36-byte header contains:
/// The 68-byte header contains:
/// - flags (1 byte)
/// - hop_limit (1 byte)
/// - payload_length (2 bytes)
/// - src_addr (16 bytes)
/// - dest_addr (16 bytes)
/// - src_addr (32 bytes)
/// - dest_addr (32 bytes)
///
/// Routers use cached coordinates for routing decisions.
#[derive(Clone, Debug)]
@@ -729,17 +730,17 @@ pub struct DataPacket {
pub flags: DataFlags,
/// Hop limit (TTL).
pub hop_limit: u8,
/// Source FIPS address.
pub src_addr: FipsAddress,
/// Destination FIPS address.
pub dest_addr: FipsAddress,
/// Source node address.
pub src_addr: NodeAddr,
/// Destination node address.
pub dest_addr: NodeAddr,
/// Payload data.
pub payload: Vec<u8>,
}
impl DataPacket {
/// Create a new data packet.
pub fn new(src_addr: FipsAddress, dest_addr: FipsAddress, payload: Vec<u8>) -> Self {
pub fn new(src_addr: NodeAddr, dest_addr: NodeAddr, payload: Vec<u8>) -> Self {
Self {
flags: DataFlags::new(),
hop_limit: 64,
@@ -801,14 +802,14 @@ impl DataPacket {
#[derive(Clone, Debug)]
pub struct CoordsRequired {
/// Destination that couldn't be routed.
pub dest_addr: FipsAddress,
pub dest_addr: NodeAddr,
/// Router reporting the miss.
pub reporter: NodeAddr,
}
impl CoordsRequired {
/// Create a new CoordsRequired error.
pub fn new(dest_addr: FipsAddress, reporter: NodeAddr) -> Self {
pub fn new(dest_addr: NodeAddr, reporter: NodeAddr) -> Self {
Self { dest_addr, reporter }
}
}
@@ -819,9 +820,9 @@ impl CoordsRequired {
#[derive(Clone, Debug)]
pub struct PathBroken {
/// Original source of the failed packet.
pub original_src: FipsAddress,
pub original_src: NodeAddr,
/// Destination that couldn't be reached.
pub dest_addr: FipsAddress,
pub dest_addr: NodeAddr,
/// Node that detected the failure.
pub reporter: NodeAddr,
/// Optional: last known coordinates of destination.
@@ -830,7 +831,7 @@ pub struct PathBroken {
impl PathBroken {
/// Create a new PathBroken error.
pub fn new(original_src: FipsAddress, dest_addr: FipsAddress, reporter: NodeAddr) -> Self {
pub fn new(original_src: NodeAddr, dest_addr: NodeAddr, reporter: NodeAddr) -> Self {
Self {
original_src,
dest_addr,
@@ -856,12 +857,6 @@ mod tests {
NodeAddr::from_bytes(bytes)
}
fn make_address(val: u8) -> FipsAddress {
let mut bytes = [0xfdu8; 16];
bytes[1] = val;
FipsAddress::from_bytes(bytes).unwrap()
}
fn make_coords(ids: &[u8]) -> TreeCoordinate {
TreeCoordinate::new(ids.iter().map(|&v| make_node_addr(v)).collect()).unwrap()
}
@@ -974,17 +969,17 @@ mod tests {
#[test]
fn test_data_packet_size() {
let packet = DataPacket::new(make_address(1), make_address(2), vec![0u8; 100]);
let packet = DataPacket::new(make_node_addr(1), make_node_addr(2), vec![0u8; 100]);
// 36 byte header + 100 byte payload
assert_eq!(packet.total_size(), 136);
assert_eq!(packet.header_size(), 36);
// 68 byte header + 100 byte payload
assert_eq!(packet.total_size(), 168);
assert_eq!(packet.header_size(), 68);
assert_eq!(packet.payload_len(), 100);
}
#[test]
fn test_data_packet_hop_limit() {
let mut packet = DataPacket::new(make_address(1), make_address(2), vec![]);
let mut packet = DataPacket::new(make_node_addr(1), make_node_addr(2), vec![]);
packet.hop_limit = 2;
assert!(packet.can_forward());
@@ -1002,7 +997,7 @@ mod tests {
#[test]
fn test_data_packet_builder() {
let packet = DataPacket::new(make_address(1), make_address(2), vec![1, 2, 3])
let packet = DataPacket::new(make_node_addr(1), make_node_addr(2), vec![1, 2, 3])
.with_hop_limit(32)
.with_flags(DataFlags::from_byte(0x80));
@@ -1151,8 +1146,8 @@ mod tests {
#[test]
fn test_session_setup() {
let setup = SessionSetup::new(
make_address(1),
make_address(2),
make_node_addr(1),
make_node_addr(2),
make_coords(&[1, 0]),
make_coords(&[2, 0]),
)
@@ -1166,9 +1161,9 @@ mod tests {
#[test]
fn test_coords_required() {
let err = CoordsRequired::new(make_address(1), make_node_addr(2));
let err = CoordsRequired::new(make_node_addr(1), make_node_addr(2));
assert_eq!(err.dest_addr, make_address(1));
assert_eq!(err.dest_addr, make_node_addr(1));
assert_eq!(err.reporter, make_node_addr(2));
}
@@ -1176,7 +1171,7 @@ mod tests {
#[test]
fn test_path_broken() {
let err = PathBroken::new(make_address(1), make_address(2), make_node_addr(3))
let err = PathBroken::new(make_node_addr(1), make_node_addr(2), make_node_addr(3))
.with_last_coords(make_coords(&[2, 0]));
assert!(err.last_known_coords.is_some());