Skip to content

Short paths waste ~64 bytes of payload headroom — MAX_PACKET_PAYLOAD partitions a dynamic budget statically #2509

@broglep

Description

@broglep

I was reading through the dispatcher and noticed something that might be worth a discussion before anyone touches it.

The on-air frame layout is header + transport_codes? + path_len + path + payload, with the PHY ceiling at MAX_TRANS_UNIT = 255. MAX_PACKET_PAYLOAD = 184 is sized so that worst case 64 + 184 + 2 = 250 fits 255. That works, but it assumes every frame uses a 64-byte path, which almost no frame does in practice — most paths I see in the wild are 0 to maybe 8 hops.

The thing is, payload_len isn't even on the wire. The receiver computes it as phy_len - header_offset (Dispatcher.cpp:179). The 184 number lives only in the parser, as a buffer-size check (Dispatcher.cpp:180) and a TX guard (Dispatcher.cpp:371). The wire format itself is fine with anything that fits the LoRa explicit-header length field.

So if you send a frame with path_len = 0, you've got room for ~250 bytes of payload before hitting the PHY, but the parser hard-rejects anything past 184. That headroom just goes unused.

Why I think this is worth raising

The 167-character limit on text messages comes up a lot — see #2481 where someone is proposing compression to work around it. Compression is a real fix and I'm not arguing against it, but it's solving a downstream symptom. The underlying thing is that the per-frame budget is being statically partitioned at the parser level when the wire format already supports a dynamic split.

If the cap were lifted, a PAYLOAD_TYPE_TXT_MSG on a short path could carry roughly 230 characters in the same encryption envelope, no compression dependency. Same applies to PAYLOAD_TYPE_GRP_DATA and PAYLOAD_TYPE_MULTIPART — fewer parts per logical message.

There's also a nice side-effect: if the forwarder gate becomes "wire-after-append ≤ 255" instead of separate path ≤ 64 and payload ≤ 184 caps, then the sender choosing a bigger payload automatically reduces the room for path growth on flood. A long message self-limits to fewer hops by physics, without any new field. Roughly:

payload_len implicit max hops (1-byte hashes)
184 (today) 64 (MAX_PATH_SIZE)
220 33
240 13
248 5

Whether that's a feature or a footgun depends on how you look at it, but it's at least an interesting property.

What would actually need to change

The buffer in Packet::payload and the cap constant. The forwarder gate in Mesh.cpp:347-358 would need to learn about the PHY budget rather than just the path-size cap. And of course old firmware would drop frames over 184 silently, so any sender emitting larger frames needs to know the path is upgraded — the existing reserved ADV_FEAT1_MASK / ADV_FEAT2_MASK slots in AdvertDataHelpers.h:14-17 look like they were left there for exactly this kind of capability flag.

There's also the BLE/serial buffer concern that came up on #2022 (the 172→176 bump). That's the same buffer family and a 184→248 change has a bigger blast radius — I'd want to see the same BLE re-test matrix before merging anything.

For low-RAM boards (nRF52832 etc.) the bump probably needs to be compile-gated. Each pool slot grows by 64 bytes; with a typical pool of 8–16 packets that's 0.5–1 KB. Fine on ESP32, tight on the smaller nRFs.

Question

Is there appetite for relaxing MAX_PACKET_PAYLOAD toward the actual PHY budget, either by default or gated by a capability flag and memory tier? Or is the position that compression (#2481) is the preferred lever and the cap should stay where it is? Either answer is useful to know before anyone writes a PR.

Happy to write up a more detailed proposal if there's interest.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions