release: v0.1.0
First cut release. Snapshots ./src/ as ./versions/v0.1.0/ via `just cut-version v0.1.0` (22 files: 21 chapter MDs + SUMMARY.md). Deployed alongside `latest` at: https://swaits.github.io/meshcore-spec/v0.1.0/ Also: - CHANGELOG.md adopts Keep a Changelog-style version sections. Future entries land under [Unreleased] and graduate to a version heading when cut. The existing date-stamped pass entries stay put as a historical record of the work captured in v0.1.0. - README.md fixes a stale "per vX.Y.Z tag" mention left over from the tag-based versioning model, and links the current release explicitly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
24 files changed
+2478
-2
No files matched your search
@@ -0,0 +1,124 @@
|
||||
# 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) and `BaseChatMesh::sendAckTo()` (which falls back
|
||||
to `sendFloodScoped()` when `out_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](16-packet-hash.md) (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 by `ack_crc` is 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)
|
||||
|
||||
1. Set first byte to `(remaining << 4) | PAYLOAD_TYPE_ACK`.
|
||||
2. Copy the 4-byte ACK CRC starting at offset 1.
|
||||
3. Set payload_len to 5.
|
||||
|
||||
### Decoding (Multipart ACK)
|
||||
|
||||
1. Read first byte. Extract remaining = byte >> 4, sub_type = byte & 0x0F.
|
||||
2. If sub_type == 0x03 (ACK) and payload_len >= 5:
|
||||
a. Read 4 bytes at offset 1 as ACK CRC (uint32_le).
|
||||
3. 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](04-payload-ack.md) — ACK CRC format
|
||||
- Test vectors: `corpus/payloads/multipart/`
|
||||
|
||||
### Reference Implementation
|
||||
|
||||
- `Mesh::createMultiAck()` in `src/Mesh.cpp` — Encoding
|
||||
- `Mesh::routeDirectRecvAcks()` in `src/Mesh.cpp` — Direct-only gating and
|
||||
inter-copy spacing
|
||||
- `BaseChatMesh::sendAckTo()` in `src/helpers/BaseChatMesh.cpp` — Flood
|
||||
fallback when no direct return path is known
|
||||
- `Mesh::onRecvPacket()`, case `PAYLOAD_TYPE_MULTIPART` — Decoding
|
||||
Reference in new issue
Block a user