Mesh frames#
Where the infrastructure is gone or was never there, devices carry each other's traffic: every node relays what it hears, so a message crosses ground no single node can reach. The radios that do that cheaply, the connectionless ESP-NOW of an ESP32 swarm or a pennies-per-node nRF24, hand you a payload and nothing else: no addressing, no hop count, no integrity. pamoja supplies that layer as pure logic with no allocation, so the same code runs on a node with a radio and in a test with none.
What the example does#
It builds the frame a river gauge floods into the mesh, then takes the part of the node that hears it: parse the frame, drop the second copy, spend a hop, and stop once the hops run out. Finally it inverts a payload byte and confirms the checksum rejects the frame.
The header layout is fixed and big-endian, and the byte-for-byte frame is pinned
in the conformance vectors every binding checks itself against, so this page
shows what a node does with a frame rather than restating one. Nothing about the
frame is typed out in hex: the destination is checked against the exported
BROADCAST address, the hop limit a new frame starts with comes from the
crate's default, and the size on the air is measured off the frame that was
built.
It proves:
- A broadcast frame parses back out of the bytes that go on the air with its
payload intact and its destination equal to
BROADCAST. - A packet is identified as it floods by its source and sequence id, so the second copy to arrive is dropped instead of relayed again.
- Relaying spends exactly one hop, and the forwarded bytes still parse and carry the same payload, because the checksum covers every byte except the hop limit.
- A packet whose hops have run out is not relayed, which is what keeps a flood finite.
- An inverted payload byte fails the checksum instead of arriving as a plausible reading.
Rust#
From examples/tests/guides/mesh.rs:
use pamoja_mesh::{Frame, SeenCache, BROADCAST};
// A river gauge floods a level reading to every node in range. The header is fixed
// and big-endian: version, source, destination, sequence id, hop limit, then the
// payload and a checksum over everything but the hop limit.
const RIVER_GAUGE: u32 = 305_419_896;
let reading = Frame::broadcast(RIVER_GAUGE, 1, b"level=high").expect("the payload fits");
let on_the_air = reading.as_bytes().len();
let to_everyone = reading.dst() == BROADCAST;
println!("sent {on_the_air} bytes to every node in range");
println!("addressed to broadcast: {to_everyone}");
// A neighbour hears it. Every node in range rebroadcasts, so the same packet arrives
// several times over; the source and sequence id decide which copy is the first.
let received = Frame::parse(reading.as_bytes()).expect("the checksum matches");
println!("payload {}", String::from_utf8_lossy(received.payload()));
let mut seen: SeenCache<64> = SeenCache::new();
let first = seen.record(received.dedup_key());
let again = seen.record(received.dedup_key());
println!("first copy relayed: {first}, second copy relayed: {again}");
// Relaying spends one hop. The checksum skips the hop-limit byte, so a relay forwards
// the frame without recomputing it and the check stays end to end.
let forwarded = received.relayed().expect("hops remain");
println!("relayed hop limit {}", forwarded.hop_limit());
let onward = Frame::parse(forwarded.as_bytes()).expect("the checksum still matches");
println!("onward {}", String::from_utf8_lossy(onward.payload()));
// A frame that has run out of hops is not relayed again, which is what ends the flood.
match received.with_hop_limit(0).relayed() {
Some(_) => println!("a spent frame was relayed, which should never happen"),
None => println!("spent hop limit reached, the flood stops here"),
}
// A payload byte the air mangled fails the checksum rather than reaching the
// application as a plausible reading. The header is a fixed width, so the first
// byte past it is the first byte of the reading itself.
let mut mangled = reading.as_bytes().to_vec();
mangled[Frame::HEADER_LEN] ^= 0xFF;
match Frame::parse(&mangled) {
Ok(_) => println!("a mangled frame was accepted, which should never happen"),
Err(error) => println!("mangled rejected: {error}"),
}TypeScript#
From bindings/node/guides/mesh.ts:
import { BROADCAST, HEADER_LEN, SeenPackets, broadcast, parse, relayed } from '@pamoja/mesh'
// A river gauge floods a level reading to every node in range. The header is fixed and
// big-endian: version, source, destination, sequence id, hop limit, then the payload and
// a checksum over everything but the hop limit.
const RIVER_GAUGE = 305419896
const reading = broadcast(RIVER_GAUGE, 1, Buffer.from('level=high'))
console.log(`sent ${reading.bytes.length} bytes to every node in range`)
console.log(`addressed to broadcast: ${reading.dst === BROADCAST}`)
// A neighbour hears it. Every node in range rebroadcasts, so the same packet arrives
// several times over; the source and sequence id decide which copy is the first.
const received = parse(reading.bytes)
console.log(`payload ${received.payload.toString()}`)
const seen = new SeenPackets(64)
const first = seen.record(received.src, received.id)
const again = seen.record(received.src, received.id)
console.log(`first copy relayed: ${first}, second copy relayed: ${again}`)
// Relaying spends one hop. The checksum skips the hop-limit byte, so a relay forwards the
// frame without recomputing it and the check stays end to end.
const forwarded = relayed(received.bytes)!
console.log(`relayed hop limit ${forwarded.hopLimit}`)
const onward = parse(forwarded.bytes)
console.log(`onward ${onward.payload.toString()}`)
// A frame that has run out of hops is not relayed again, which is what ends the flood.
const spent = relayed(broadcast(RIVER_GAUGE, 1, Buffer.from('level=high'), 0).bytes)
if (spent === null) {
console.log('spent hop limit reached, the flood stops here')
} else {
console.log('a spent frame was relayed, which should never happen')
}
// A payload byte the air mangled fails the checksum rather than reaching the application
// as a plausible reading. The header is a fixed width, so the first byte past it is the
// first byte of the reading itself.
const mangled = Buffer.from(reading.bytes)
mangled[HEADER_LEN] ^= 0xff
try {
parse(mangled)
console.log('a mangled frame was accepted, which should never happen')
} catch (error) {
console.log(`mangled rejected: ${(error as Error).message}`)
}Python#
From bindings/python/guides/mesh.py:
from pamoja.core import PamojaError
from pamoja.mesh import BROADCAST, HEADER_LEN, SeenPackets, broadcast, parse, relayed
# A river gauge floods a level reading to every node in range. The header is fixed and
# big-endian: version, source, destination, sequence id, hop limit, then the payload and a
# checksum over everything but the hop limit.
RIVER_GAUGE = 305419896
reading = broadcast(RIVER_GAUGE, 1, b"level=high")
print(f"sent {len(reading.bytes)} bytes to every node in range")
print(f"addressed to broadcast: {reading.dst == BROADCAST}")
# A neighbour hears it. Every node in range rebroadcasts, so the same packet arrives
# several times over; the source and sequence id decide which copy is the first.
received = parse(reading.bytes)
print(f"payload {received.payload.decode()}")
seen = SeenPackets(64)
first = seen.record(received.src, received.id)
again = seen.record(received.src, received.id)
print(f"first copy relayed: {first}, second copy relayed: {again}")
# Relaying spends one hop. The checksum skips the hop-limit byte, so a relay forwards the
# frame without recomputing it and the check stays end to end.
forwarded = relayed(received.bytes)
print(f"relayed hop limit {forwarded.hop_limit}")
onward = parse(forwarded.bytes)
print(f"onward {onward.payload.decode()}")
# A frame that has run out of hops is not relayed again, which is what ends the flood.
spent = relayed(broadcast(RIVER_GAUGE, 1, b"level=high", 0).bytes)
if spent is None:
print("spent hop limit reached, the flood stops here")
else:
print("a spent frame was relayed, which should never happen")
# A payload byte the air mangled fails the checksum rather than reaching the application
# as a plausible reading. The header is a fixed width, so the first byte past it is the
# first byte of the reading itself.
mangled = bytearray(reading.bytes)
mangled[HEADER_LEN] ^= 0xFF
try:
parse(bytes(mangled))
print("a mangled frame was accepted, which should never happen")
except PamojaError as error:
print(f"mangled rejected: {error}")C##
From bindings/dotnet/samples/Pamoja.Guides/MeshGuide.cs:
// A river gauge floods a level reading to every node in range. The header is fixed
// and big-endian: version, source, destination, sequence id, hop limit, then the
// payload and a checksum over everything but the hop limit.
const uint RiverGauge = 305_419_896;
MeshFrame reading = Mesh.BroadcastFrame(RiverGauge, 1, "level=high"u8);
Console.WriteLine($"sent {reading.Bytes.Length} bytes to every node in range");
Console.WriteLine($"addressed to broadcast: {reading.Dst == Mesh.Broadcast}");
// A neighbour hears it. Every node in range rebroadcasts, so the same packet
// arrives several times over; the source and sequence id decide which copy is
// the first.
MeshFrame received = Mesh.Parse(reading.Bytes);
Console.WriteLine($"payload {System.Text.Encoding.UTF8.GetString(received.Payload)}");
using SeenPackets seen = new(64);
bool first = seen.Record(received.Src, received.Id);
bool again = seen.Record(received.Src, received.Id);
Console.WriteLine($"first copy relayed: {first}, second copy relayed: {again}");
// Relaying spends one hop. The checksum skips the hop-limit byte, so a relay
// forwards the frame without recomputing it and the check stays end to end.
MeshFrame forwarded = Mesh.Relayed(received.Bytes)!;
Console.WriteLine($"relayed hop limit {forwarded.HopLimit}");
MeshFrame onward = Mesh.Parse(forwarded.Bytes);
Console.WriteLine($"onward {System.Text.Encoding.UTF8.GetString(onward.Payload)}");
// A frame that has run out of hops is not relayed again, which ends the flood.
MeshFrame? spent = Mesh.Relayed(Mesh.BroadcastFrame(RiverGauge, 1, "level=high"u8, 0).Bytes);
Console.WriteLine(spent is null
? "spent hop limit reached, the flood stops here"
: "a spent frame was relayed, which should never happen");
// A payload byte the air mangled fails the checksum rather than reaching the
// application as a plausible reading. The header is a fixed width, so the first
// byte past it is the first byte of the reading itself.
byte[] mangled = [.. reading.Bytes];
mangled[Mesh.HeaderLen] ^= 0xFF;
try
{
Mesh.Parse(mangled);
Console.WriteLine("a mangled frame was accepted, which should never happen");
}
catch (PamojaException error)
{
Console.WriteLine($"mangled rejected: {error.Message}");
}Reference#
- Rust:
pamoja-mesh, install - TypeScript:
@pamoja/mesh, install - Python:
pamoja.mesh, install - C#:
Pamoja.Mesh, install - Hardware: ESP-NOW