MeshCore Protocol Specification
Section 11: Payload — Multipart
Overview
The MULTIPART payload wraps another payload type to indicate it is part of a multi-packet sequence. The first byte encodes the number of remaining packets in the sequence and the sub-payload type. Currently, only multipart ACK is defined in the reference implementation.
Payload Type
Header payload type field: 0x0A (MULTIPART)
Wire Format
0 1
0 1 2 3 4 5 6 7 8 9 0 ...
+-+-+-+-+-+-+-+-+-+-+-+-+
|Rem| SubType | Sub-payload |
+-+-+-+-+-+-+-+-+-+-+-+-+
First byte encoding:
Bit: 7 6 5 4 3 2 1 0
+---+---+---+---+---+---+---+---+
| Remaining (4) | Sub-Type (4) |
+---+---+---+---+---+---+---+---+
Fields
| Field | Offset | Size | Type | Description |
|---|---|---|---|---|
| remaining | 0 (bits 4-7) | 4 bits | uint | Number of remaining packets in sequence (0-15) |
| sub_type | 0 (bits 0-3) | 4 bits | uint | Payload type of the wrapped content |
| sub_payload | 1 | variable | raw | The wrapped payload data |
first_byte = (remaining << 4) | (sub_type & 0x0F)
Multipart ACK
The only currently defined multipart sub-type is ACK (sub_type = 0x03):
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| (rem<<4)|0x03| ACK CRC (uint32_le) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Total payload: 5 bytes (1 byte header + 4 byte CRC).
Multipart ACKs are used to send additional ACK retransmissions for reliability.
The remaining field indicates how many more ACK packets follow this one,
counting down: a chain of K extra copies is emitted as MULTIPART with
remaining = K, K-1, …, 1, followed by exactly one final plain
PAYLOAD_TYPE_ACK packet (NOT a MULTIPART with remaining = 0). All copies
in the chain carry the same ack_crc. Receivers MUST treat the first copy
received as the authoritative ACK and dedup subsequent copies by ack_crc
at the application layer.
Multipart ACK Usage Constraints
- A sender MUST only emit MULTIPART-wrapped ACKs on direct-routed return
paths (i.e., when a direct path to the original sender is known). When the
return path is unknown, the sender MUST fall back to emitting a plain
PAYLOAD_TYPE_ACK via flood routing. This reflects the firmware behavior in
Mesh::routeDirectRecvAcks()(which emits MULTIPART copies only when a direct path is available) andBaseChatMesh::sendAckTo()(which falls back tosendFloodScoped()whenout_path_len == OUT_PATH_UNKNOWN). - All MULTIPART-ACK copies and the final plain ACK for the same acknowledged
message carry an identical 4-byte
ack_crc. Receivers MUST treat them as the same logical ACK: the first one received satisfies the acknowledgment, and subsequent duplicates MUST be deduplicated per Section 16 (each copy has a distinct packet hash because the wrapper byte differs, so ordinary dedup does not suppress them on the wire — application-level dedup byack_crcis required). - The typical inter-copy delay in the reference implementation is
approximately 300 ms plus the direct retransmit delay
(
Mesh::routeDirectRecvAcks()); exact timing is implementation-defined. - The number of extra copies is configured via
getExtraAckTransmitCount()and is implementation-defined. The firmware default is 0 (plain ACK only).
A receiver MUST NOT infer delivery reliability from the remaining counter
alone: the counter is a hint about how many additional copies the sender
intends to emit, not a guarantee that they will arrive.
Encoding (Multipart ACK)
- Set first byte to
(remaining << 4) | PAYLOAD_TYPE_ACK. - Copy the 4-byte ACK CRC starting at offset 1.
- Set payload_len to 5.
Decoding (Multipart ACK)
- Read first byte. Extract remaining = byte >> 4, sub_type = byte & 0x0F.
- If sub_type == 0x03 (ACK) and payload_len >= 5: a. Read 4 bytes at offset 1 as ACK CRC (uint32_le).
- For other sub_types: reserved for future use.
Constraints
- Payload MUST be at least 2 bytes (1 header + 1 sub-payload minimum).
- For multipart ACK, payload MUST be at least 5 bytes.
Cross-References
- Section 4: ACK — ACK CRC format
- Test vectors:
corpus/payloads/multipart/
Reference Implementation
Mesh::createMultiAck()insrc/Mesh.cpp— EncodingMesh::routeDirectRecvAcks()insrc/Mesh.cpp— Direct-only gating and inter-copy spacingBaseChatMesh::sendAckTo()insrc/helpers/BaseChatMesh.cpp— Flood fallback when no direct return path is knownMesh::onRecvPacket(), casePAYLOAD_TYPE_MULTIPART— Decoding