Files
quicproquo/crates/quicprochat-p2p/src/address.rs
Christian Nennemann f9ac921a0c feat(p2p): mesh stack, LoRa mock transport, and relay demo
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
2026-03-30 21:19:12 +02:00

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);
}
}