Gossip mesh topology + source-based routing #445
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
gossip + source-based routing
Summary
In a basic mesh network, when a device wants to send a message to everyone, it broadcasts the message to all its direct neighbors. Each of those neighbors then broadcasts
it to their neighbors, and so on. This is known as "flooding." While simple, it has major drawbacks:
Source-based routing solves this by having the sender of a message determine the entire path the message should take to its destination. This is like giving a package to a
mail carrier with a specific list of post offices it must visit to reach its final address.
To make this possible, two key components were introduced:
Step 1: Building a Map of the Network (Gossip)
Announcing Neighbors: When a device (let's call it Node A) sends out its regular "hello, I'm here" message (an IdentityAnnouncement), it now includes extra information: a
list of its direct neighbors. This is done using a custom GossipTLV (Type-Length-Value) format. So, Node A tells the world, "I'm here, and I'm directly connected to Node B
and Node C."
Collecting Information: Other devices on the network receive this announcement. When Node D hears from Node A, it updates its internal map of the network. It now knows
that A, B, and C are all connected.
The
MeshGraphService: This new service is the "keeper of the map." It runs on every device and continuously builds and updates a graph of the entire mesh network basedon the gossip it hears. It stores:
Step 2: Planning the Route
Finding the Shortest Path: When Node A wants to send a message to Node Z, it consults its MeshGraphService. It asks the new RoutePlanner utility, "What's the shortest
path from me to Node Z?"
Dijkstra's Algorithm: The RoutePlanner uses a classic and efficient algorithm (Dijkstra's) to find the shortest path across the graph. It might determine the best route
is A -> C -> F -> Z.
Attaching the Route: Node A then creates the message packet and attaches this route to it. The BitchatPacket format was updated to include a new route field to carry this
list of "hops."
Step 3: Forwarding the Message Along the Route
Targeted Sending: Node A doesn't broadcast the message to everyone. It looks at the first hop in the route (Node C) and sends the message directly to Node C.
Relaying: When Node C receives the message, it inspects the packet. It sees the route field and notices that it is part of the route. It then does two things:
Final Delivery: This process repeats. Node F receives the message and forwards it directly to the final destination, Node Z.
Fallback to Broadcasting: If at any point a node in the chain can't find the next hop (perhaps that device went offline), it can fall back to the old method of
broadcasting the message to all its neighbors to ensure it still has a chance of arriving.
UI and Debugging
A new Mesh Topology view was added to the app's debug screen. This provides a live, visual graph of the network as the device sees it, showing all the nodes and the
connections between them. This is an invaluable tool for developers to see if the gossip protocol is working correctly and to understand the current shape of the network.
Summary of Changes
This feature makes the Bitchat network significantly more efficient and scalable, especially as more devices join the mesh.
Announcement Gossip TLV (Direct Neighbors)
This document specifies an optional TLV extension to the BitChat
ANNOUNCEmessage that allows peers to gossip which other peers they are currently connected to directly over Bluetooth. Implementations can use this to build a mesh topology view (nodes = peers, edges = direct connections).Status: optional and backward-compatible.
Layering Overview
type = 0x01(ANNOUNCE). Header is unchanged.0x03. The gossip TLV (if present) is part of the payload and therefore covered by the signature.TLV Format
Each TLV uses a compact layout:
type: 1 bytelength: 1 byte (0..255)value:lengthbytesExisting TLVs (unchanged):
0x01NICKNAME: UTF‑8 string (≤ 255 bytes)0x02NOISE_PUBLIC_KEY: Noise static public key bytes (typically 32 bytes for X25519)0x03SIGNING_PUBLIC_KEY: Ed25519 public key bytes (typically 32 bytes)New TLV (optional):
0x04DIRECT_NEIGHBORS: Concatenation of up to 10 peer IDs, each encoded as exactly 8 bytes. There is no inner count; the number of neighbors islength / 8. Iflengthis not a multiple of 8, trailing partial bytes MUST be ignored.Peer ID Binary Encoding (8 bytes)
Peer IDs are represented as 8 raw bytes (16 hex chars) in “network order” (left‑to‑right):
0x00at the end to reach 8 bytes.This matches the on‑wire 8‑byte
senderID/recipientIDencoding used in the BitChat packet header.Sender Behavior
0x01..0x03as usual.0x04with up to 10 unique, directly connected peer IDs.0x03.0. This allows TTL to change during relays without invalidating the signature.Receiver Behavior
0x01..0x03as usual; ignore any unknown TLVs.0x04TLV is present:N = length / 8peer IDs (ignore trailing non‑aligned bytes).0x04.timestampin the BitChat packet header), replace A’s previously recorded neighbor list with the new one. If older or equal, ignore the neighbor update.{A, B}in your topology visualization; i.e., if A reports direct peers[B, C], add edges A–B and A–C.Limits and Compatibility
N ≥ 0implied bylength / 8up to the receivedlength.0x04is omitted, the announce remains valid. Peers can still chat and interoperate normally; the topology graph will just not include edges reported by that peer (other peers that include A in their neighbor lists can still introduce edges to A).Minimal Example (conceptual)
ANNOUNCE payload TLVs (concatenated):
01 [len=N] [UTF‑8 nickname]02 [len=32] [32 bytes X25519 pubkey]03 [len=32] [32 bytes Ed25519 pubkey]04 [len=8*M] [peerID1(8) || peerID2(8) || ... || peerIDM(8)](optional)Where each
peerIDk(8)is the 8‑byte binary form of the peer ID as specified above.That’s the entire change; the outer packet header, message type, and relay/TTL behavior are unchanged.
Source-Based Routing for BitChat Packets
This document specifies an optional source-based routing extension to the BitChat packet format. A sender may attach a hop-by-hop route (list of peer IDs) to instruct relays on the intended path. Relays that support this feature will try to forward to the next hop directly; otherwise, they fall back to regular broadcast relaying.
Status: optional and backward-compatible.
Layering Overview
HAS_ROUTE (0x08).SenderID(8 bytes)RecipientID(8 bytes) ifHAS_RECIPIENTRoute(ifHAS_ROUTE):count(1 byte) +count * 8bytes hop IDsPayload(with optional compression preamble)Signature(64 bytes) ifHAS_SIGNATUREUnknown flags are ignored by older implementations (they will simply not see a route and continue broadcasting as before).
Route Field Encoding
HAS_ROUTE (0x08)bit inflags.RecipientID):count: 1 byte (0..255)hops: concatenation ofcountpeer IDs, each encoded as exactly 8 bytes0x00if shorter). This matches the on‑wiresenderID/recipientIDencoding.1 + 8*Nbytes, whereN = count.HAS_ROUTEwithcount = 0is treated as no route (relays ignore it).Sender Behavior
recipientIDis set and is not the broadcast ID). For broadcast packets, omit the route.BitchatPacketalready contains dedicatedsenderIDandrecipientIDfields. TheRoutefield'shopslist SHOULD contain the sequence of intermediate peer IDs that the packet should traverse. It SHOULD NOT duplicate thesenderIDorrecipientIDif they are already present in theBitchatPacket's dedicated fields. Instead, thehopslist represents the explicit path between the sender and recipient, starting from the first relay and ending with the last relay before the recipient.HAS_ROUTE, writecount = path.length, then the 8‑byte hop IDs in order. Keepcount <= 255.signatureomitted andttl = 0(TTL excluded to allow relay decrement) — same rule as base protocol.Relay Behavior
When receiving a packet that is not addressed to you:
HAS_ROUTEis not set, or the route is empty, relay using your normal broadcast logic (subject to TTL/probability policies).HAS_ROUTEis set:iin the hop list:i+1, attempt a targeted unicast to that next hop if you have a direct connection to it.i+1), the packet has reached the end of its explicit route. The relay should then attempt to deliver it to the finalrecipientIDif directly connected, but SHOULD NOT relay it further as a broadcast.TTL handling remains unchanged: relays decrement TTL by 1 before forwarding (whether targeted or broadcast). If TTL reaches 0, do not relay.
Receiver Behavior (Destination)
recipientID == myPeerID), process it normally (e.g., decrypt Noise payload, verify signatures, etc.).Compatibility
HAS_ROUTEis omitted, legacy behavior applies. Relays that don’t implement this feature will ignore the route entirely, because they won’t set or checkHAS_ROUTE.Minimal Example (conceptual)
SenderID(8)RecipientID(8)(if present)HAS_ROUTEset →count=1,hops = [H1]whereH1is 8 bytesIn this example,
SENDER_IDis the sender,RECIPIENT_IDis the final recipient, andH1is the single intermediate relay. Thehopslist explicitly defines the path between the sender and recipient. The receiver verifies the signature over the packet encoding (withttl = 0andsignatureomitted), which includes thehopswhenHAS_ROUTEis set.Operational Notes