Repository navigation
Route the non-LoRa egress taps through a transport registry - #11945
Draft
jamesarich wants to merge 2 commits into
Draft
jamesarich wants to merge 2 commits into
jamesarich wants to merge 2 commits into
Conversation
Contributor
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Contributor
⚡ Try this PR in the Web FlasherNote Building this pull request… the flash button, badges and supported-board |
This was referenced Sep 29, 2026
jamesarich
force-pushed
the
feat/transport-registry
branch
from
September 29, 2026 14:30
32bb24e to
2769ba4
Compare
Contributor
|
jamesarich
force-pushed
the
feat/transport-registry
branch
from
October 1, 2026 21:58
2769ba4 to
fc1fd7b
Compare
jamesarich
force-pushed
the
feat/transport-registry
branch
3 times, most recently
from
October 7, 2026 18:04
2f3a8f9 to
efb1c0d
Compare
Router::send() named its non-LoRa outputs one by one: a hardcoded mqtt->onSend() inside the decoded-tag block and a udpHandler->onSend() at the end, each behind its own null check and enabled_protocols gate. A new bearer meant editing the funnel again. MeshTransportBase is a self-registering base in MeshModule's idiom: a transport constructs into one of two static lists and Router::send() fans out to whichever list matches the packet state. PostEncode gets the final encrypted packet and gates on each transport's own isEnabled(); UdpMulticastHandler moves there unchanged in behaviour. PreEncode fires inside the decoded-tag block with the decoded copy and the channel index, which is what MQTT needs; a stateless MQTTTransport adapter forwards to the singleton and keeps every MQTT-side gate where it was, including the old null check. The two lists never cross: a PreEncode transport is invisible to the post-encode fan-out, so MQTT can never be handed relayed traffic. FloodingRouter's overhear cancel keeps LoRa on its own queue and asks the registry for any other medium, strictly same-medium: an overhear on one medium says nothing about who heard this node on another. A transport that emits inline has no queue and declines by default. Native tests in test_transport_registry: every enabled transport is called in order, a return value never suppresses the next, a disabled one is skipped, an empty registry is a no-op, PreEncode receives both copies, and the two hook points are disjoint.
…ncode on isEnabled() Each bearer carried its own copy of the ingress guards, and the copies drifted: UDP never cleared tx_after or priority, and never zeroed the public_key bytes. MeshTransportBase::sanitizeIngress() now holds the one rule set - drop a local origin or an out-of-range hop count, stamp the medium, clear what the LoRa header does not carry (tx_after, priority, pki_encrypted, public_key, rx_*). The key bytes are zeroed as well as the size because Router::send reads them without consulting the size. UDP and MQTT ingress call it; MQTT keeps its gateway-echo implicit ack ahead of it. stripForTransmit() is the egress half, and UDP now uses it, so a UDP frame no longer carries this node's rx_rssi/rx_snr, priority, tx_after or transport_mechanism. A third-party multicast listener reading those from our frames will see defaults. callTransportsPreEncode() now consults isEnabled() like callTransports(), so a new PreEncode transport needs no gate added to Router::send. MQTT's adapter reports moduleConfig.mqtt.enabled and forwards only our own originations, which drops its two PostEncode stubs and the Router-side condition; onSend() is a default no-op. isEnabled() is public, so the three UDP start sites ask the handler instead of re-reading enabled_protocols. test_transport_ingress runs every ingress rule once per bearer (UDP through UdpMulticastHandler::decodeIngress, MQTT directly; test_mqtt covers its envelope path) and pins the strip. test_transport_registry gains the disabled PreEncode case.
jamesarich
force-pushed
the
feat/transport-registry
branch
from
October 11, 2026 16:05
efb1c0d to
77e3361
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Router::send()names its non-LoRa outputs one by one: a hardcodedmqtt->onSend()inside the decoded-tag block and audpHandler->onSend()at the end, each behind its own null check andenabled_protocolsgate. Every new bearer means editing that funnel again, and two BLE bearers are on their way.What changed
MeshTransportBaseis a self-registering base inMeshModule's idiom: a transport constructs into one of two static lists andRouter::send()fans out to whichever list matches the packet state. PostEncode gets the final encrypted packet and gates on each transport's ownisEnabled();UdpMulticastHandlermoves there. UDP and MQTT ingress now share one rule set,sanitizeIngress(): drop a local origin or an out-of-range hop count, stamp the medium, and clear what the LoRa header does not carry, which UDP had never cleared fortx_after,priorityor thepublic_keybytes. UDP egress goes throughstripForTransmit(), so a UDP frame no longer carries this node'srx_rssi/rx_snr,priority,tx_afterortransport_mechanism. PreEncode fires inside the decoded-tag block with the decoded copy and the channel index, which is what MQTT needs; a statelessMQTTTransportadapter forwards to the singleton and keeps every MQTT-side gate where it was, including the old null check. The two lists never cross, so MQTT can never be handed relayed traffic.FloodingRouter's overhear cancel keeps LoRa on its own queue and asks the registry for any other medium, strictly same-medium. A transport that emits inline has no queue and declines by default.No new bearer is in this PR; nothing is enabled that was not enabled before.
Testing
test_transport_registry(new, 8 tests): enabled transports are called in order, a return value never suppresses the next, a disabled one is skipped, an empty registry is a no-op, PreEncode receives both copies, and the two hook points are disjoint.test_transport_ingress(new, 8 tests) pins the ingress rule set andstripForTransmit(). Both pass, 16/16 in Docker at77e33615a.heltec-v3builds. No device testing: the MQTT path is the same calls behind a different dispatch, and the UDP ingress and egress changes are covered by native tests only.Merge order
meshtastic/protobufs#1111, then #11945, #11946, #12010 and #12011, each based on the one before it. #12010 also waits for #1111 so its
protobufspointer can move fromfeat/ble-mesh-protocol-flagsto protobufsmaster.