Appearance
Payloads (payloadType + inner tail sizing)
LowMesh’s mesh header is documented in Packet layout.
payloadType(1 byte, 0–255) lives in the base header and is visible on the air before decryption — see Packet layout — visible payloadType.- After the parsed prefix blocks (base, optional region, fragment, routing), the application tail is AES-256-CCM-4 on the wire:
ciphertext ||4-byte MIC for allchannelHashvalues (including0x0000). After decrypt, that tail is the inner body whose layout is selected bypayloadType(tables below).
There is no payloadLen field. The on-air application tail length is:
[ \textbf{appTailLen} = \textbf{rxPhyLen} - \textbf{prefixBytes} ]
where prefixBytes = base + region + fragment + routing blocks. After decrypt, innerBodyLen = cipherLen = appTailLen − micLen.
All multi-byte integers below are little-endian unless noted.
Wire status labels
See Base profile — wire status labels and the compatibility matrix.
| Label | Meaning |
|---|---|
| Base Profile Required | Required for the Base Interoperability Profile |
| Normative Optional | Stable wire format; optional for the Base Profile |
| Design Target | Not stable for interop yet |
| Vendor/Private | Private extension |
| Informative | Not a mesh wire requirement |
Inner body size summary (Base Profile)
Bounds are for the plaintext inner body (before CCM). On-air add 4-byte MIC. innerBodyLen = plaintext length (cipherLen). PHY budget: Payload limits.
payloadType | Wire status | Min inner | Max inner | Length | Fragmentation | Base Profile |
|---|---|---|---|---|---|---|
0x01 TEXT_UTF8 | Base Profile Required | 1 | 256 | variable (1 + utf8Len) | optional header fragment block | Yes |
0x02 POSITION_CORE | Base Profile Required | 13 | 13 | fixed | no | Yes |
0x03 TELEMETRY_BASIC | Base Profile Required | 8 | 8 | fixed | no | Yes |
0x04 PAYLOAD_BODY_V2 | Normative Optional | 8 | PHY-derived | variable | per-chunk + optional HAS_FRAGMENT | Optional |
0x06 ACK_BASIC | Base Profile Required | 17 | 17 | fixed | no | Yes |
0x07 FRAG_NACK | Normative Optional | 13 | 13 + 4×ranges | variable | n/a (recovery) | Optional |
0x0A NODE_ADVERT | Base Profile Required | 20 | ~120+ext | variable | no | Yes |
| NODE_INFO_* | Normative Optional | — | — | variable | no | Optional |
0x51 GPS_FIX | Normative Optional | 19 | 19 | fixed | no | Optional |
0x52 GPS_SCHEDULE | Normative Optional | 8 | 8 | fixed | no | Optional (authorized sources only) |
0x71–0x75 Bulletin | Normative Optional | per op | PHY-derived | variable | query rsp MAY fragment | Optional |
0x91/0x92 Admin | Normative Optional | 15+ | PHY-derived | variable | no | Optional |
| IOT_UPLINK / IOT_ACK | Design Target | — | — | TBD | TBD | No |
Dispatch rule (normative)
Implementations MUST branch on payloadType first:
payloadType | Name | Inner tail interpretation |
|---|---|---|
0x01 | TEXT_UTF8 — Base Profile Required | Direct UTF-8 chat/status string (see Text encoding). |
0x02 | POSITION_CORE — Base Profile Required | Direct compact WGS84 fix (maps/dashboards). |
0x03 | TELEMETRY_BASIC — Base Profile Required | Direct small device health / RF snapshot for dashboards. |
0x04 | PAYLOAD_BODY_V2 — Normative Optional | Stateless file/data chunk — every packet carries currentIndex, totalCount, chunkLen, and chunkBytes (see below). |
0x06 | ACK_BASIC — Base Profile Required | Acknowledgement for ACK_REQ frames — see below. |
0x07 | FRAG_NACK — Normative Optional | Fragment negative ACK — missing chunk ranges for PAYLOAD_BODY_V2 — see Fragmentation. |
0x75 | BULLETIN_NOTIFY — Normative Optional | Bulletin host push-to-notify beacon: **channelHash (2 LE) ` |
0x00, 0x05, 0x08–0x74, 0x76–0xFF | reserved / vendor / extensions | Interpret per linked pages (admin 0x91 / 0x92, bulletin 0x71–0x74 and related flows in Bulletin board, traceroute 0x0D / 0x0E, NODE_ADVERT 0x0A, etc.) or treat as opaque if unknown. |
Namespaces: Mesh
payloadTypeis not the same as USB/BLE frame opcodes (see USB & BLE hex API); keep parsers separate.
Note:
PAYLOAD_BODY_V1is removed from the normative spec. Flood / source-route path data lives in the routing block whenttlRaw & 0x80(HAS_ROUTE_BLOCK) is set — see Routing on wire. UsePAYLOAD_BODY_V2(0x04) — a payload type name, not a public API version — for chunked binary/text transfers.
TEXT_UTF8 (payloadType = 0x01)
| Field | Size | Description |
|---|---|---|
utf8Len | 1 | 0–255 |
utf8 | utf8Len | UTF‑8 text without BOM |
Rules:
innerBodyLenMUST equal1 + utf8Len.utf8Len = 0is allowed (empty message).
POSITION_CORE (payloadType = 0x02)
A compact position record (same numeric semantics as the core of GPS_FIX in GPS, but fixed size for early tooling).
| Field | Size | Description |
|---|---|---|
fixQuality | 1 | 0 = none/unknown, 1 = 2D, 2 = 3D, 3 = RTK (implementation-defined beyond 3) |
timestampUtc | 4 | Unix epoch seconds (claimed event time; not automatic clock input — see time-source trust) |
latitudeE7 | 4 | signed, degrees × 10⁷ |
longitudeE7 | 4 | signed, degrees × 10⁷ |
Rules:
innerBodyLenMUST be13.0x80000000MAY be used in either coordinate field to mean “invalid / withheld” if a fix is partial (product policy).
TELEMETRY_BASIC (payloadType = 0x03)
| Field | Size | Description |
|---|---|---|
batteryMv | 2 | uint16 LE; 0xFFFF = unknown / not provided |
rssiDbm | 1 | int8 last measured RSSI for this hop’s RX context (implementation defines which interface); 0x80 = unknown |
uptimeSec | 4 | uint32 LE seconds since boot |
flags | 1 | Bit 0: charging; bits 1–7: reserved (0 on TX unless defined later) |
Rules:
innerBodyLenMUST be8.
ACK_BASIC (payloadType = 0x06)
Emitted when ACK_REQ is set in verFlags (receiver policy) or to report receive status for a prior frame.
| Field | Size | Description |
|---|---|---|
ackedSrcId | 4 | Original sender srcId (LE nodeId32) |
ackedSessionId | 4 | Original sessionId (LE uint32) from the base header |
ackedMessageId | 2 | Original messageId (LE uint16) |
ackedFragment | 2 | Packed fragment word; 0 if not fragmented |
status | 1 | Status code (table below) |
rxRssiDbm | 1 | Signed dBm; 0x80 = unknown |
rxSnrQ4 | 1 | int8 SNR in quarter-dB units (decodedSnrDb = rxSnrQ4 / 4.0); 0x80 = unknown |
ackDelayMs | 2 | Delay from RX to ACK TX (LE uint16); always present — use 0xFFFF if unknown or unused |
Status codes:
| Value | Name | Meaning |
|---|---|---|
0x00 | OK | Accepted |
0x01 | duplicate | Already seen (suppressed) |
0x02 | rejected_no_key | Could not resolve channel key / decrypt failed (diagnostic) |
0x03 | rejected_ttl | Hop budget exhausted / not forwarded |
0x04 | busy | Receiver congested |
0x05 | fragment_missing | Missing fragment(s) — peer MAY send FRAG_NACK |
0x06 | rejected_policy | Role / region / relay policy drop |
0x07 | rejected_replay | Replay / duplicate-window rejection |
Rules:
innerBodyLenMUST be17(fixed layout above).rxSnrQ4:Q4means quarter-dB units, not a product-defined scale. EncoderxSnrQ4 = round(snrDb × 4)as a signedint8. DecodedecodedSnrDb = rxSnrQ4 / 4.0.0x80(-128 asint8) is unknown, not -32.00 dB. Representable measurements are −31.75 dB … +31.75 dB (0x81…0x7F). Examples: +10.00 dB → 40, +7.25 dB → 29, 0 dB → 0, −5.00 dB → −20, unknown →0x80.ACK_BASICMUST be encrypted/authenticated under the same channel/domain as the message being acknowledged, unless a product profile explicitly defines a management response channel.- Works with
PAYLOAD_BODY_V2and legacy multi-frameTEXT_UTF8whenHAS_FRAGMENTis used.
rejected_no_key note: If a receiver cannot resolve the channel key, it usually cannot emit an encrypted ACK_BASIC on that same channel. rejected_no_key is primarily for local diagnostics, default-community fallback policy, or management contexts (USB/BLE, companion) where a valid response channel exists — do not assume such ACKs are always observable on-air.
FRAG_NACK (payloadType = 0x07)
Normative layout for requesting missing PAYLOAD_BODY_V2 chunks. Full field table: Fragmentation.
PAYLOAD_BODY_V2 (payloadType = 0x04)
Stateless chunk envelope for files and large blobs: each radio frame that uses this type carries its own fragment context.
| Field | Size | Description |
|---|---|---|
bodyVersion | 1 | 0x02 for this layout |
bodyFlags | 1 | MUST be 0 on TX; reserved for future use |
fileType | 1 | 0x01 = Text (UTF-8 chunks), 0x02 = JPEG, 0x03 = Firmware binary. 0x00 = reserved / unknown (SHOULD NOT use on TX unless private). |
currentIndex | 2 | 0-based chunk index (LE uint16) |
totalCount | 2 | Total number of chunks (LE uint16, 1–1024) |
chunkLen | 1 | Number of pure file bytes in this packet (chunkBytes) |
chunkBytes | chunkLen | Raw payload bytes |
Rules:
innerBodyLenMUST equal8 + chunkLen(fixed 8-byte prefix above).totalCountMUST be 1–1024;currentIndexMUST be0..totalCount-1(therefore0..1023).- Every multi-frame
PAYLOAD_BODY_V2transfer MUST setHAS_FRAGMENT. HeaderfragmentIndexMUST equalcurrentIndex. HeadermoreFragmentsMUST equal(currentIndex + 1 < totalCount). Disagreeing values → reject the fragment/session. - Supporting more than 1024 chunks requires a future wire-format/nonce revision and MUST NOT be implemented by wrapping or reusing the 10-bit fragment index.
chunkLen = 0is allowed only if your application defines empty-chunk semantics; otherwise avoid.
Relationship to routing
- Repeaters are stateless dumb-pipes; forwarding uses
ttlRaw,FLOOD, optional routing block path hashes, and duplicate suppression — see Routing. - Strict source routes and flood breadcrumbs are carried in the routing block, not inside the AES-CCM ciphertext — see Routing on wire.
See also
- Packet layout — dynamic Mesh PDU layout
- Maximum payload lengths — PHY budget without
payloadLen - Fragmentation — mesh fragment block + stateless
PAYLOAD_BODY_V2chunk indices - Routing — drop reasons — implementation logging names
- Compatibility matrix — Base Profile requirements