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
This commit is contained in:
@@ -176,13 +176,31 @@ impl MeshEnvelope {
|
||||
copy
|
||||
}
|
||||
|
||||
/// Serialize to bytes (JSON).
|
||||
/// Serialize to compact CBOR binary format (for wire transmission).
|
||||
pub fn to_wire(&self) -> Vec<u8> {
|
||||
let mut buf = Vec::new();
|
||||
ciborium::into_writer(self, &mut buf).expect("CBOR serialization should not fail");
|
||||
buf
|
||||
}
|
||||
|
||||
/// Deserialize from CBOR binary format.
|
||||
pub fn from_wire(bytes: &[u8]) -> anyhow::Result<Self> {
|
||||
let env: Self = ciborium::from_reader(bytes)?;
|
||||
Ok(env)
|
||||
}
|
||||
|
||||
/// Deserialize from wire format, trying CBOR first then JSON fallback.
|
||||
pub fn from_wire_or_json(bytes: &[u8]) -> anyhow::Result<Self> {
|
||||
Self::from_wire(bytes).or_else(|_| Self::from_bytes(bytes))
|
||||
}
|
||||
|
||||
/// Serialize to bytes (JSON). Kept for backward compatibility and debugging.
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
// serde_json::to_vec should not fail on a well-formed envelope.
|
||||
serde_json::to_vec(self).expect("envelope serialization should not fail")
|
||||
}
|
||||
|
||||
/// Deserialize from bytes (JSON).
|
||||
/// Deserialize from bytes (JSON). Kept for backward compatibility and debugging.
|
||||
pub fn from_bytes(bytes: &[u8]) -> anyhow::Result<Self> {
|
||||
let env: Self = serde_json::from_slice(bytes)?;
|
||||
Ok(env)
|
||||
@@ -293,4 +311,68 @@ mod tests {
|
||||
assert!(env.recipient_key.is_empty());
|
||||
assert!(env.verify());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cbor_roundtrip() {
|
||||
let id = test_identity();
|
||||
let recipient = [0xABu8; 32];
|
||||
let env = MeshEnvelope::new(&id, &recipient, b"cbor roundtrip".to_vec(), 3600, 5);
|
||||
|
||||
let wire = env.to_wire();
|
||||
let restored = MeshEnvelope::from_wire(&wire).expect("CBOR deserialize");
|
||||
|
||||
assert_eq!(env.id, restored.id);
|
||||
assert_eq!(env.sender_key, restored.sender_key);
|
||||
assert_eq!(env.recipient_key, restored.recipient_key);
|
||||
assert_eq!(env.payload, restored.payload);
|
||||
assert_eq!(env.ttl_secs, restored.ttl_secs);
|
||||
assert_eq!(env.hop_count, restored.hop_count);
|
||||
assert_eq!(env.max_hops, restored.max_hops);
|
||||
assert_eq!(env.timestamp, restored.timestamp);
|
||||
assert_eq!(env.signature, restored.signature);
|
||||
assert!(restored.verify());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cbor_smaller_than_json() {
|
||||
let id = test_identity();
|
||||
let recipient = [0xCCu8; 32];
|
||||
let payload = b"a typical chat message for size comparison testing".to_vec();
|
||||
let env = MeshEnvelope::new(&id, &recipient, payload, 3600, 5);
|
||||
|
||||
let wire_len = env.to_wire().len();
|
||||
let json_len = env.to_bytes().len();
|
||||
|
||||
println!("CBOR wire size: {wire_len} bytes");
|
||||
println!("JSON size: {json_len} bytes");
|
||||
println!("Ratio: {:.1}x smaller", json_len as f64 / wire_len as f64);
|
||||
|
||||
assert!(
|
||||
json_len * 2 > wire_len * 3,
|
||||
"CBOR ({wire_len}B) should be materially smaller than JSON ({json_len}B)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cbor_backward_compat() {
|
||||
let id = test_identity();
|
||||
let env = MeshEnvelope::new(&id, &[0xDD; 32], b"json compat".to_vec(), 60, 3);
|
||||
|
||||
// Serialize as JSON (old format).
|
||||
let json_bytes = env.to_bytes();
|
||||
|
||||
// from_wire_or_json should fall back to JSON parsing.
|
||||
let restored = MeshEnvelope::from_wire_or_json(&json_bytes)
|
||||
.expect("from_wire_or_json should handle JSON");
|
||||
assert_eq!(env.id, restored.id);
|
||||
assert_eq!(env.payload, restored.payload);
|
||||
assert!(restored.verify());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cbor_from_wire_rejects_garbage() {
|
||||
let garbage = [0xFF, 0xFE, 0x00, 0x42, 0x99, 0x01, 0x02, 0x03];
|
||||
let result = MeshEnvelope::from_wire(&garbage);
|
||||
assert!(result.is_err(), "garbage input must return Err, not panic");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user