Skip to content

Routing: TTL, flood, strict on-wire routes, duplicate suppression

Where path details live on the wire (header vs routing block vs encrypted tail): Routing on wire.

LowMesh repeaters are stateless dumb-pipes at the mesh layer: they do not maintain distance-vector tables, predictive nextHop RAM, or MeshCore-style learned routes for third-party traffic. Forwarding is driven by:

  1. Flood routing — Controlled duplication when FLOOD is set; path state is carried in the packet (routing block), not in repeater RAM.
  2. Strict source routing — Optional explicit path hashes in the routing block for unicast legs where the originator supplies the path.
  3. RAM duplicate suppression — Recent (channelHash, srcId, sessionId, messageId, fragmentWord) signatures discard echoes and loops.

Routing modes

Hop limit (hopControl / ttlRaw)

  • Each forwarded frame carries hopControl (1 byte on wire; firmware name ttlRaw). Normative layout: bit 7 = HAS_ROUTE_BLOCK (0x80); bit 6 reserved (MUST be 0); bits 5–0 = HOP_LIMIT (hopLimit = hopControl & 0x3F).
  • Each relay decrements only the hop field while preserving bits 7–6, e.g. hopControl = (hopControl & 0xC0) | ((hopLimit - 1) & 0x3F) after reading hopLimit; when hopLimit reaches 0, the frame is not forwarded.

TTL ceilings vs HASH_SIZE (normative policy)

Breadcrumbs in the routing block consume pathCount × hashSize bytes, where hashSize = ((verFlags >> 9) & 3) + 1 (1–4). To keep worst-case growth within the 255-byte LoRa PHY payload and to stabilize application tail budgets, originators and gateways MUST clamp the initial hop limit to the maximum for the selected HASH_SIZE:

HASH_SIZE encoding (verFlags bits 10–9)hashSizeMAX TTL (hop limit)
00163
01232
10321
11416

Note: The on-wire hop field is 6 bits (absolute max 63). Originators MUST clamp to the table above for the selected HASH_SIZE.

Repeaters: if a received frame’s hop limit exceeds the ceiling for its embedded HASH_SIZE, the frame is invaliddrop (do not append breadcrumbs, do not retransmit).

HASH_SIZE and collision risk

HASH_SIZE is flexible (1–4 bytes per path entry). The spec does not mandate a minimum hash width.

Smaller hashSize values reduce airtime but increase collision risk among path fingerprints. Product profiles MAY choose hashSize based on network size, payload budget, and acceptable ambiguity. The Base profile recommends 2 bytes while requiring parsers to accept 1–4.

Route fingerprint (normative)

Each relay’s breadcrumb entry is a truncated digest of the local nodeId32:

digest[0..31] = SHA256( UTF8("LowMesh/route-fingerprint/1") || uint32_le(nodeId32) )
pathHash      = digest[0 .. hashSize-1]

pathHash is appended to pathHashes in the routing block. hashSize comes from verFlags bits 10–9 — see Routing on wire.

Flood routing

Flood mode is used when FLOOD is set (application policy) and/or when discovering neighbors or delivering group traffic.

When FLOOD is set and a relay forwards a frame with (ttlRaw & 0x80) == 0, no routing block exists on the wire — the relay MUST insert a new routing block (pathCount = 1 + local fingerprint) before the encrypted application tail, set ttlRaw |= 0x80, decrement hops, and verify ≤ 255 bytes (see Routing on wire). Subsequent relays with ttlRaw bit 7 set append their fingerprint (hashSize bytes) to pathHashes, increment pathCount, and grow the PDU only if the result still fits PHY policy.

Flood rebroadcast timing (randomized backoff)

When a node rebroadcasts an eligible flood frame (hop budget still valid, not suppressed by duplicate cache, role policy, or strong-signal suppression), it MUST NOT retransmit immediately. It MUST schedule using a randomized backoff window to reduce collisions.

Normative default (recommended for interop):

  • Compute a deterministic per-relay jitter seed from channelHash, srcId, sessionId, messageId, and fragment word.
  • Pick delayMs uniformly in a role-specific window, then apply listen-before-talk (see LoRa parameters and hardware).

Duplicate suppression (normative)

Each relay maintains a recently seen cache keyed by:

(channelHash, srcId, sessionId, messageId, fragmentWord)
  • channelHash: 2 bytes (LE).
  • srcId: 4 bytes (LE).
  • sessionId: 4 bytes (LE) — from the base header.
  • messageId: 2 bytes (LE).
  • fragmentWord: 2 bytes — packed mesh fragment block when HAS_FRAGMENT is set; otherwise 0.

Entries expire after tDup (e.g. 2–5 minutes, scaled by airtime class).

Strong-signal suppression (flood relays)

A relay MAY suppress a flood rebroadcast when local RF heuristics indicate that sufficient nearby peers likely received the original transmission. Thresholds and scoring are radio-profile / product policy and are not part of Base Profile interoperability.

Normative (interop): duplicate suppression, TTL/hop limits, and randomized flood backoff remain required. Strong-signal suppression is not required for interoperability. Different vendors MAY choose different RSSI/SNR/margin/peer-density thresholds.

Informative example inputs (not universal dBm thresholds): inbound RSSI, SNR, calculated link margin, recently heard peer density, CAD/LBT state.

Interaction with device roles

RoleStrict source route relayFlood relay
GatewayYes (policy)Yes
RepeaterYes (policy)Yes
Client (not in temporary relay)No third-party relayNo
Client (Standard; TempRelay)Policy-definedYes — Dynamic Repeater Mode fallback only
Client (Sensor Profile)No third-party relayNo

Clients in temporary relay state MUST respect tempRelayFloodTtlCap / tempRelayMaxForwardsPerMin — see Device roles — Dynamic Repeater Mode (Standard Client Profile only by default).

Strict source routes and authorization

The routing block is outside CCM AAD so relays can insert or extend flood breadcrumbs without re-encrypting. That is intentional for flood forwarding.

Strict source-route instructions that affect security, authorization, or delivery guarantees MUST be authenticated inside the encrypted application body or by a separate routeAuth mechanism. Strict source routes carried only in the plaintext routing block are advisory unless authenticated by a higher layer — MUST NOT be treated as trusted authorization by themselves.

See Routing on wire and Channels & security.

Receiver drop reasons (implementation / logging)

These names are for firmware logs, USB/BLE diagnostics, and test harnesses. They are not normative on-wire status codes (except where an application payload like ACK_BASIC defines its own status byte).

ReasonTypical cause
DROP_BAD_VERSIONverFlags VERSION not supported
DROP_RESERVED_BITSReserved verFlags bits 1–0 non-zero, or MIC_MODE reserved values 10/11
DROP_UNSUPPORTED_MIC_MODEMIC_MODE = 01 (CCM-8) not supported locally
DROP_TOO_SHORTPDU shorter than minimum for declared flags
DROP_TOO_LARGEMesh PDU > 255 bytes
DROP_BAD_REGION_BLOCKHAS_REGION set but region bytes invalid
DROP_BAD_FRAGMENT_BLOCKHAS_FRAGMENT set but fragment word invalid
DROP_BAD_TOTAL_COUNTtotalCount == 0 or totalCount > 1024 (or local nFragMax)
DROP_FRAGMENT_INDEX_MISMATCHHeader fragmentIndexcurrentIndex
DROP_BAD_MORE_FRAGMENTSHeader moreFragments disagrees with (currentIndex + 1 < totalCount)
DROP_IDENTITY_COLLISIONSame nodeId32 with a different pkEd on the active domain (contact/identity handling; not an on-wire NAK)
DROP_BAD_ROUTE_BLOCKHAS_ROUTE_BLOCK set but pathCount / length inconsistent
DROP_ROUTE_TOO_LARGEGrowing routing block would exceed PHY limit
DROP_TTL_ZEROHop limit exhausted
DROP_TTL_EXCEEDS_HASH_POLICYHop limit above ceiling for HASH_SIZE
DROP_UNKNOWN_KEYNo local psk32 candidate for channelHash
DROP_MIC_FAILAES-CCM verification failed for all candidates
DROP_ADMIN_BAD_FORMATADMIN_CMD inner layout invalid
DROP_ADMIN_REPLAYadminCounter replay
DROP_ADMIN_SIGNATURE_REQUIREDPolicy requires signature; none present
DROP_ADMIN_SIGNATURE_BADEd25519 signature verify failed
DROP_ADMIN_POLICY_REQUIRES_CCM8Policy requires CCM-8; frame used CCM-4 or peer lacks CCM-8
DROP_MIC_AMBIGUOUSMore than one candidate key verified the same frame
DROP_REPLAYCCM nonce / replay window rejection
DROP_DUPLICATE(channelHash, srcId, sessionId, messageId, fragmentWord) cache hit
DROP_REGION_POLICYFORCE_REGION or spatial policy drop
DROP_QUEUE_FULLTX queue congested; policy drop
DROP_UNSUPPORTED_PAYLOADUnknown payloadType after decrypt (or policy reject before decrypt)
DROP_ADMIN_AUTH_REQUIREDAdmin frame failed operator / key policy
DROP_UNAUTHORIZED_GPS_SCHEDULEGPS_SCHEDULE not from authorized source — see GPS

Security note

AES-CCM-4 on the inner tail prevents forgery of payloads by outsiders who lack the psk32. Metadata in the plaintext prefix (base, region, fragment, routing) is still visible on the air; deployments MUST NOT treat admin traffic as authenticated unless decrypted with adminChannelPsk — see Mesh admin.

Spatial Region policy (HAS_REGION) and FORCE_REGION

The Spatial Region block (when verFlags & HAS_REGION) is a plaintext routing hint that may be used by repeaters to restrict forwarding scope (deployment policy).

  • Packets without HAS_REGION: repeaters default to forwarding behavior based on ttlRaw hop semantics and FLOOD rules (normal operation).
  • FORCE_REGION enforcement: if a repeater has allowedRegionCount > 0 and forceRegionEnforcement == 1 (NVM DeviceRole), it MUST drop any packet that does not have HAS_REGION set (no implicit “global” fallback in this mode).

Transmit queue priority (congestion)

When the outbound queue is congested, implementations SHOULD preserve control traffic and defer bulk transfers. Recommended priority order (highest first):

  1. Emergency / critical admin
  2. ACK_BASIC / FRAG_NACK
  3. Direct user messages (unicast / high-priority chat)
  4. Telemetry / position
  5. Bulletin sync
  6. File / voice bulk transfer (PAYLOAD_BODY_V2)

When queues are full, implementations SHOULD drop or defer lower-priority bulk traffic first while keeping ACK/control slots available.

Flood rebroadcasts MUST use randomized backoff and SHOULD use listen-before-talk where supported — see LoRa parameters.

LowMeshOS — always open-source mesh protocol documentation