Node events
A mesh node reacts to three kinds of event from its transports.
A link comes up
- Record the peer and give it a fresh reassembly buffer. A link reported twice is counted once.
- Offer it every message in the outbox that it has not been offered before, oldest first.
- Send a hello now and again 1.5 seconds later.
A link goes down
- Forget the peer and its reassembly buffer.
- Forget any mapping from a person to that link. Voice for them falls back to broadcast until they are heard on another link.
Bytes arrive
Feed them to the peer's reassembly buffer and handle each complete packet.
Handling a packet
| Step | Condition | Action |
|---|---|---|
| 1 | Identifier already seen | Drop |
| 2 | Older than 6 hours, or more than 15 minutes in the future | Drop |
| 3 | Voice | Deliver if addressed here. Never relay |
| 4 | Direct or receipt addressed here | Deliver. Do not relay |
| 5 | Broadcast | Deliver, and relay if the hop limit is above 1 |
| 6 | Addressed to someone else | Relay if the hop limit is above 1 |
A relayed packet is sent on every link with its hop limit reduced by one. The identifier is recorded as seen at step 1, so every node forwards a given packet at most once.
Delivering by kind
- Hello. Parse the contact card. Ignore it if it is invalid or is this device's own. Remember the card, map the person to the link it arrived on, and announce the new contact to the interface.
- Direct. Open the sealed message and verify the signature. If either fails, drop it silently. Map the sender to the link, send a sealed receipt back without queuing it, and pass the content to the interface.
- Receipt. Open it, read the acknowledged identifier, remove that message from the outbox and mark it delivered.
- Voice. Hand the sealed frame to the call.
Sending
A message
- Prefix the content type byte, pad, sign and seal to the recipient.
- Set the hop limit from the number of peers in range: 8 below three, 6 from three to five, 5 from six up.
- Record the identifier as seen, so an echo is not treated as news.
- Add the packet to the outbox, unless it is a receipt or a call signal.
- Send it on every link.
A voice frame
- Seal the frame with the call's send key and the next sequence number.
- Wrap it in a voice packet with a hop limit of one.
- Send it to the link the recipient was last heard on, or on every link if that is unknown.
A hello
A plaintext contact card to everyone, with a hop limit of two.
Outbox
| Rule | Value |
|---|---|
| What goes in | Messages this device originated, except receipts and call signals |
| Offered | To each new peer once, oldest first |
| Removed | When a sealed receipt for it arrives, or after 24 hours |
| Effective delivery window | 6 hours, because receivers drop older packets |
| Persistence | Memory only, for as long as the app runs |
Call state machine
call()
idle ─────────────────────────> dialling ──(accept)──────> connected
│ │ │
│ invite │ 60 s, no answer │ end (either side)
v v v
ringing ──(answer)──> connected ended("No answer") ended
│
├──(decline)──────────> ended
└──(no invite for 12 s)──> ended("Missed call")| State | Meaning |
|---|---|
| Idle | No call |
| Dialling | We sent an invite and are repeating it every 2 seconds |
| Ringing | We received an invite and are ringing |
| Connected | Keys agreed and audio running |
| Ended | The call is over, with a reason such as "No answer" or "Missed call" |
Rules
- Microphone first. A call is not placed, and an answer not sent, until microphone permission is granted. Without it the call ends with "No microphone".
- One call at a time. An invite that arrives while busy is declined with the invite's own nonce.
- Nonces. Every signal carries the call nonce, and signals whose nonce does not match the live call are ignored.
- Beacons. While dialling, the same invite is re-sent every 2 seconds. A repeated invite for the call being rung keeps the ring alive. A ring that hears no invite for 12 seconds ends as missed.
- Stale calls. The last eight ended nonces are remembered. A late invite for one of them is declined, never rung.
- Never queued. Invite, accept, decline and end are sent only to devices reachable now.
Media roads
| Event | Mesh road | Direct road |
|---|---|---|
| Call connects | Carries every frame | Socket opening |
| First frame arrives over UDP | Stops carrying frames | Carries every frame |
| No UDP frame for 2.5 seconds | Resumes | Keeps sending when the socket is ready |
Received frames are checked against a 64-frame replay window before decryption, so a frame that arrives by both roads plays once, and a frame more than 64 behind is dropped.
Timers
| Timer | Value |
|---|---|
| Hello repeat after link up | 1.5 s |
| Invite beacon | 2 s |
| Ring without a beacon | 12 s |
| Ring patience at the caller | 60 s |
| Direct road trusted after last frame | 2.5 s |
| Wi-Fi dial timeout | 10 s |
| TCP keepalive | 5 s idle, 5 s interval, 2 probes |
| Duplicate table | 8,192 entries, 30 minutes |
| Maximum packet age | 6 hours, 15 minutes of clock skew allowed |
| Outbox patience | 24 hours |