How the Relay Policy Dictates Message Forwarding in the Bitchat Mesh
Bitchat’s deterministic relay policy uses the RelayController.decide method to evaluate packet types, TTL constraints, and mesh topology, returning a structured decision that dictates whether to forward, the new TTL, and the precise delay milliseconds for each hop.
Bitchat is an open-source, permissionless messaging mesh built by permissionlesstech. Understanding how its relay policy dictates message forwarding within the mesh is essential for optimizing message propagation and preventing network congestion in decentralized environments.
Core Decision Logic in RelayController
The central authority for flood control is the RelayController type defined in bitchat/Services/RelayController.swift. Its static method RelayController.decide implements a deterministic algorithm that evaluates high-level packet flags against current mesh topology to produce a RelayDecision.
The Five-Step Evaluation Algorithm
The method processes incoming packets through a strict priority hierarchy:
| Step | Condition | Action |
|---|---|---|
| 1 | isRequestSync (link-local) |
Never relay – return shouldRelay = false |
| 2 | ttlCap ≤ 1 or senderIsSelf or recipientIsSelf |
Suppress obvious non-relays |
| 3 | Session-critical traffic (isHandshake, isDirectedFragment, isDirectedEncrypted) |
Always relay with single-hop TTL decrement and modest jitter (10-35 ms for handshakes, 20-60 ms otherwise) |
| 4 | Live media (isFragment or isVoiceFrame) |
Apply fragment policy: determine TTL cap based on node degree (bleFragmentRelayTtlCapDense vs bleFragmentRelayTtlCap); relay if capped TTL > 1 with random delay within BLE fragment window |
| 5 | Broadcast / announce traffic | Choose TTL limit by topology: dense graphs ≤ 5, thin chains allow full TTL, urgent posts get boost to 7; delay chosen from tiered range growing with degree |
The output is a RelayDecision struct containing shouldRelay (Boolean), newTTL (UInt8), and delayMs (UInt64). This structure limits flood-overload while guaranteeing timely delivery for critical traffic.
Integration with Source Routing
When a node receives a packet not addressed to itself, the mesh first checks for a source-route (v2) flag. According to docs/SOURCE_ROUTING.md, if a route exists:
- The node finds its own peer ID in the route list.
- It forwards the packet to the next hop (
i + 1). - If the next hop is unreachable, it falls back to broadcast/flood.
If no route is present, the packet follows the classic flood path subject to the TTL and probability limits defined by RelayController. The BLESourceRouteOriginationPolicy determines whether to attach a route at send-time, requiring the packet to be locally authored, directed, have TTL headroom, and guarantee a viable v2 path.
Where the Policy Is Applied
The relay policy operates at critical junctions in the message pipeline:
- BLE receive pipeline (
bitchat/Services/BLE/BLEReceivePipeline.swift): InvokesRelayController.decidefor each incoming fragment. - MessageRouter: Uses the decision to schedule delayed sends or drop packets.
- Transport configuration (
bitchat/Services/TransportConfig.swift): Stores TTL caps and jitter ranges referenced by the controller.
Code Examples
The following Swift code demonstrates how to evaluate an incoming fragment:
// Example: deciding whether to forward an incoming fragment
let decision = RelayController.decide(
ttl: incomingPacket.ttl,
senderIsSelf: incomingPacket.sender == myPeerID,
recipientIsSelf: false,
isEncrypted: true,
isDirectedEncrypted: false,
isFragment: true,
isDirectedFragment: false,
isHandshake: false,
isAnnounce: false,
isRequestSync: false,
degree: meshDegree,
highDegreeThreshold: 5)
// Schedule the forward if permitted
if decision.shouldRelay {
scheduler.schedule(after: .milliseconds(decision.delayMs)) {
sendPacket(packet, ttl: decision.newTTL)
}
}
When originating packets, the source-route policy works alongside the relay controller:
// Example: attaching a source-route only when the origination policy permits
if BLESourceRouteOriginationPolicy.shouldAttachRoute(
packet: outgoingPacket,
myPeerID: myPeerID,
mesh: meshTopology) {
outgoingPacket.attachRoute(...)
}
else {
// Fall back to normal flood; RelayController will handle TTL/delay
}
Summary
RelayController.decideinbitchat/Services/RelayController.swiftserves as the central authority for all forwarding decisions.- The policy uses a five-step hierarchy that prioritizes session-critical traffic over bulk media and suppresses link-local sync requests.
- TTL caps and jitter windows are dynamically adjusted based on node degree to prevent exponential fan-out in dense mesh topologies.
- Source routing provides deterministic paths when available, falling back to the relay-controlled flood mechanism when routes are absent or invalid.
- Configuration constants for delays and caps reside in
TransportConfig.swift, allowing protocol tuning without modifying core logic.
Frequently Asked Questions
How does the relay policy handle high-traffic voice frames?
Live media such as voice frames (isVoiceFrame) and fragments (isFragment) trigger the fragment policy within RelayController.decide. The system checks node degree against highDegreeThreshold to select between bleFragmentRelayTtlCapDense and bleFragmentRelayTtlCap. High-degree nodes receive stricter TTL limits to prevent broadcast storms, while all fragment relays include random delays within bleFragmentRelayMinDelayMs…MaxDelayMs to desynchronize transmissions.
What happens when a packet has a source route attached?
When a packet carries a source-route list, the receiving node bypasses the standard flood logic and forwards directly to the next hop in the route list, as documented in docs/SOURCE_ROUTING.md. If that next hop is unreachable, the node falls back to the standard relay policy for broadcast distribution. This hybrid approach optimizes for efficiency when paths are known while maintaining reliability through flood fallback.
Why does the policy suppress packets with ttlCap ≤ 1?
Packets with a TTL (Time To Live) of 1 or less have no remaining propagation budget. The RelayController suppresses these immediately to prevent unnecessary airtime consumption and processing overhead. This check occurs early in the decision pipeline (Step 2) alongside self-origination and self-destination checks to quickly eliminate non-viable candidates before evaluating more complex policy rules.
Where are the delay and TTL configuration values defined?
The numerical parameters for the relay policy—including bleFragmentRelayTtlCap, bleFragmentRelayMinDelayMs, and tiered broadcast delay ranges—are defined in bitchat/Services/TransportConfig.swift. This separation of configuration from logic allows developers to tune mesh behavior for different deployment scenarios (dense urban vs. sparse rural) without modifying the core decision algorithm in RelayController.swift.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →