Implement transport abstraction (TCP/iroh), announce and routing table, multi-hop mesh router, truncated-address link layer, and LoRa mock medium with fragmentation plus EU868-style duty-cycle accounting. Add mesh_lora_relay_demo and scripts/mesh-demo.sh. Relax CBOR vs JSON size assertion to match fixed-size cryptographic overhead. Extend .gitignore for nested targets and node_modules. Made-with: Cursor
136 lines
3.9 KiB
Rust
136 lines
3.9 KiB
Rust
//! Truncated mesh addresses for bandwidth-efficient routing.
|
|
//!
|
|
//! A [`MeshAddress`] is derived from an Ed25519 public key by taking the first
|
|
//! 16 bytes of its SHA-256 hash. This provides globally unique addressing
|
|
//! (birthday collision at ~2^64) while saving 16 bytes per packet compared to
|
|
//! full 32-byte public keys.
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use sha2::{Digest, Sha256};
|
|
use std::fmt;
|
|
|
|
/// 16-byte truncated mesh address.
|
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
pub struct MeshAddress([u8; 16]);
|
|
|
|
impl MeshAddress {
|
|
/// Derive from a 32-byte Ed25519 public key.
|
|
pub fn from_public_key(key: &[u8; 32]) -> Self {
|
|
let hash = Sha256::digest(key);
|
|
let mut addr = [0u8; 16];
|
|
addr.copy_from_slice(&hash[..16]);
|
|
Self(addr)
|
|
}
|
|
|
|
/// Create from raw 16-byte array.
|
|
pub fn from_bytes(bytes: [u8; 16]) -> Self {
|
|
Self(bytes)
|
|
}
|
|
|
|
/// Get the raw 16-byte address.
|
|
pub fn as_bytes(&self) -> &[u8; 16] {
|
|
&self.0
|
|
}
|
|
|
|
/// Check if a 32-byte public key matches this address.
|
|
pub fn matches_key(&self, key: &[u8; 32]) -> bool {
|
|
Self::from_public_key(key) == *self
|
|
}
|
|
|
|
/// The broadcast address (all zeros).
|
|
pub const BROADCAST: Self = Self([0u8; 16]);
|
|
|
|
/// Check if this is the broadcast address.
|
|
pub fn is_broadcast(&self) -> bool {
|
|
self.0 == [0u8; 16]
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for MeshAddress {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "MeshAddress({})", hex::encode(self.0))
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for MeshAddress {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}", hex::encode(&self.0[..8]))
|
|
}
|
|
}
|
|
|
|
impl From<[u8; 16]> for MeshAddress {
|
|
fn from(bytes: [u8; 16]) -> Self {
|
|
Self(bytes)
|
|
}
|
|
}
|
|
|
|
impl AsRef<[u8; 16]> for MeshAddress {
|
|
fn as_ref(&self) -> &[u8; 16] {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn from_key_deterministic() {
|
|
let key = [42u8; 32];
|
|
let addr1 = MeshAddress::from_public_key(&key);
|
|
let addr2 = MeshAddress::from_public_key(&key);
|
|
assert_eq!(addr1, addr2, "same key must produce same address");
|
|
}
|
|
|
|
#[test]
|
|
fn different_keys_different_addresses() {
|
|
let key_a = [1u8; 32];
|
|
let key_b = [2u8; 32];
|
|
let addr_a = MeshAddress::from_public_key(&key_a);
|
|
let addr_b = MeshAddress::from_public_key(&key_b);
|
|
assert_ne!(addr_a, addr_b, "different keys must produce different addresses");
|
|
}
|
|
|
|
#[test]
|
|
fn matches_key_works() {
|
|
let key = [99u8; 32];
|
|
let addr = MeshAddress::from_public_key(&key);
|
|
assert!(addr.matches_key(&key), "correct key must match");
|
|
|
|
let wrong_key = [100u8; 32];
|
|
assert!(!addr.matches_key(&wrong_key), "wrong key must not match");
|
|
}
|
|
|
|
#[test]
|
|
fn broadcast_address() {
|
|
assert_eq!(*MeshAddress::BROADCAST.as_bytes(), [0u8; 16]);
|
|
assert!(MeshAddress::BROADCAST.is_broadcast());
|
|
|
|
let non_broadcast = MeshAddress::from_bytes([1u8; 16]);
|
|
assert!(!non_broadcast.is_broadcast());
|
|
}
|
|
|
|
#[test]
|
|
fn display_formatting() {
|
|
let key = [0xAB; 32];
|
|
let addr = MeshAddress::from_public_key(&key);
|
|
let display = format!("{addr}");
|
|
// Display shows first 8 bytes as hex = 16 hex chars.
|
|
assert_eq!(display.len(), 16, "display should show 8 bytes = 16 hex chars");
|
|
|
|
let debug = format!("{addr:?}");
|
|
// Debug shows all 16 bytes as hex = 32 hex chars, plus wrapper.
|
|
assert!(debug.starts_with("MeshAddress("));
|
|
assert!(debug.ends_with(')'));
|
|
}
|
|
|
|
#[test]
|
|
fn serde_roundtrip() {
|
|
let key = [77u8; 32];
|
|
let addr = MeshAddress::from_public_key(&key);
|
|
let json = serde_json::to_string(&addr).expect("serialize");
|
|
let restored: MeshAddress = serde_json::from_str(&json).expect("deserialize");
|
|
assert_eq!(addr, restored);
|
|
}
|
|
}
|