* Tips robot virtual node / relayer to different LoRa modes & channels
Note that this commit has details hardcoded for the Wellington (NZ)
mesh, and also requires the following patch to the protobufs:
-----
diff --git a/meshtastic/mesh.proto b/meshtastic/mesh.proto
index 03162d8..ec54c99 100644
--- a/meshtastic/mesh.proto
+++ b/meshtastic/mesh.proto
@@ -1393,6 +1393,21 @@ message MeshPacket {
* Set by the firmware internally, clients are not supposed to set this.
*/
uint32 tx_after = 20;
+
+ /*
+ * The modem preset to use fo rthis packet
+ */
+ uint32 modem_preset = 21;
+
+ /*
+ * The frequency slot to use for this packet
+ */
+ uint32 frequency_slot = 22;
+
+ /*
+ * Whether the packet has a nonstandard radio config
+ */
+ bool nonstandard_radio_config = 23;
}
/*
-----
* fix: repair mesh tips CI build
* feat: add MeshBeacon module (Phase 1 — proto + generated code + initial stub)
* feat(beacon): implement broadcaster + listener (phases 2-5)
* feat(beacon): wire RadioLibInterface hooks + admin validation (phases 6-7)
* fix(beacon): fix LocalModuleConfig flat access (no payload_variant), add localonly proto field
* feat(beacon): fix broadcaster inheritance, add preset/region validation + proto cache
- MeshBeaconBroadcastModule now inherits ProtobufModule<meshtastic_MeshBeacon>
(alongside private MeshBeaconModule + OSThread), giving it allocDataPacket()
and setStartDelay() without extra includes.
- Payload cache: rebuildCache() encodes the MeshBeacon protobuf once and stores
it in payloadCache[]/payloadCacheSize; sendBeacon() only calls rebuildCache()
when payloadCacheDirty==true. AdminModule calls invalidateCache() after saving
new config so the next broadcast picks up changes.
- Region/preset validation in handleSetModuleConfig (mesh_beacon_tag):
broadcast_on_preset is validated against the device's current region via
RadioInterface::validateConfigLora(); broadcast_offer_region is validated via
RadioInterface::validateConfigRegion(). Invalid values are zeroed with a
LOG_WARN before saving.
* feat(beacon): add unit tests for MeshBeaconModule and AdminModule configuration validation
* remove old meshtips
* more validation in NodeDB and AdminModule, and userprefs for baked in goodness
* copilot is my gravity
* mmmmm... beacon
* oops
* Enhance unit tests for MeshBeaconModule with detailed validation checks and output formatting
* new lines. Why not?
* finally
* legacy mode activate!
* Update protobufs (#17)
Co-authored-by: NomDeTom <116762865+NomDeTom@users.noreply.github.com>
* better logic, fixed a test
* updated for packet signing
fixed a test
added guards for licensed/ham mode
* channel numbers
* beacon: encrypt on the beacon channel PSK; fix split note
When broadcast_on_channel overrides the primary channel's name/PSK, the
beacon was encrypted with the PRIMARY PSK: perhapsEncode keys encryption
off the primary slot, but the radio-thread channel switch happens only
after encryption. sendBeaconPacket() now installs the beacon channel into
the primary slot for the synchronous duration of send() (cooperative
threading => no interleaving) so encryption/hash use the beacon channel,
then restores it. A shared beaconChannelSettings() helper builds the
channel for both the encrypt-time swap and the RF-time swap so the
key+hash cannot drift.
Also: correct the legacy-split comments (both packets go out on the same
beacon radio settings, not the normal config) and merge the two
consecutive `if (hasText)` blocks in the listener (cppcheck
duplicateCondition).
Tests: add channelPskOverride_swapsBeaconChannelAndRestores and
noChannelOverride_doesNotSwapPrimary; MockRouter snapshots the primary
channel at send() time.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test/beacon: drain toPhoneQueue in tearDown to fix LSan leak abort
The listener delivers received text via MeshService::sendToPhone(), which
enqueues the packet into toPhoneQueue and takes ownership. Nothing dequeues
it in tests, so the three listener tests carrying message text stranded a
MeshPacket each — 1272 bytes / 3 allocations that LeakSanitizer flagged at
process exit, aborting the coverage run (surfaced by pio as [ERRORED] /
SIGHUP even though all 40 assertions passed).
Drain the phone queue in tearDown (getForPhone()/releaseToPool) so the
packets return to packetPool. Suite is now GREEN with no sanitizer abort.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* legacy hop override for zero-hoppers
* ever more beacons
* beacon: comment out broadcast_send_as_node pending further review
Functionality preserved in comments with full signing/has_bitfield notes
for when it is re-enabled. Proto tag 3 retained on the wire.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test/beacon: fix fromIsCustomNodeWhenSet now that send-as-node is disabled
broadcast_send_as_node is commented out; from is always the local node.
Update the test assertion and doc comment to match current behaviour.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update protobufs (#21)
Co-authored-by: NomDeTom <116762865+NomDeTom@users.noreply.github.com>
* flags for beacons
* beacon: do more with less — slot-index targets + validation
Multi-target beacons embedded a full ChannelSettings in every BroadcastTarget,
blowing ModuleConfig past the 512-byte BLE FromRadio budget so the firmware would
not compile. Targets now reference an existing channel-table slot by channel_index
and the broadcaster resolves it via channels.getByIndex() at TX time. Net effect:
the same multi-target capability for a fraction of the bytes —
FromRadio 609 -> 510 B, MeshBeaconConfig 596 -> 324 B, AdminMessage 615 -> 511 B.
- proto: BroadcastTarget.channel (embedded) -> channel_index (uint32 ref); regen all
generated headers (size constants propagate to admin/localonly/deviceonly/mesh).
- broadcaster: resolve channel_index from the channel table; an out-of-range or blank
slot falls back to the default channel for the target preset rather than borrowing
the primary's name/PSK.
- AdminModule: validate broadcast_targets entries on write (region/preset sanitised
like the single-target fields; channel_index range-checked).
- userPrefs: TARGET_<n>_CHANNEL_{NAME,NUM,PSK} collapse to a single CHANNEL_INDEX.
- docs: two-step (set_channel -> set_module_config) multi-target setup, inline-vs-
reference distinction, and single-/multi-target are equal (not "legacy") options.
- tests: target validation + channel-index resolution incl. blank-slot fallback
(47/47 green on `./bin/run-tests.sh -e native -f test_mesh_beacon`).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRAF5csgsMn6p1zEcFL8Qz
* throttling after reboot
* address copilot review
* simplify
* fix(beacon): use 0x%08x for node/packet IDs in logs; register test suite
The %#08lx log specifiers passed uint32_t (NodeNum/PacketId) to a %lx
length modifier — undefined behaviour on 64-bit (native test) targets and
non-standard width. Switch to the project-standard 0x%08x. Also bump
test/native-suite-count to 25 for the added test_mesh_beacon suite.
clod helped too
* copilot & clarity
clod helped too
* refactor(beacon): use auto for the sanitized config copy
clod helped too
* fix(beacon): guard empty-payload sends; gate has_mesh_beacon on build flag; document ISR_TX pre-switch
clod helped too
---------
Co-authored-by: Steve Gilberd <steve@erayd.net>
Co-authored-by: Darafei Praliaskouski <me@komzpa.net>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
161 lines
6.2 KiB
C++
161 lines
6.2 KiB
C++
#pragma once
|
|
|
|
#include "CryptoEngine.h"
|
|
#include "NodeDB.h"
|
|
#include "mesh-pb-constants.h"
|
|
#include <Arduino.h>
|
|
|
|
/** A channel number (index into the channel table)
|
|
*/
|
|
typedef uint8_t ChannelIndex;
|
|
|
|
/** A low quality hash of the channel PSK and the channel name. created by generateHash(chIndex)
|
|
* Used as a hint to limit which PSKs are considered for packet decoding.
|
|
*/
|
|
typedef uint8_t ChannelHash;
|
|
|
|
/** The container/on device API for working with channels */
|
|
class Channels
|
|
{
|
|
/// The index of the primary channel
|
|
ChannelIndex primaryIndex = 0;
|
|
|
|
/** The channel index that was requested for sending/receiving. Note: if this channel is a secondary
|
|
channel and does not have a PSK, we will use the PSK from the primary channel. If this channel is disabled
|
|
no sending or receiving will be allowed */
|
|
ChannelIndex activeChannelIndex = 0;
|
|
|
|
/// the precomputed hashes for each of our channels, or -1 for invalid
|
|
int16_t hashes[MAX_NUM_CHANNELS] = {};
|
|
|
|
public:
|
|
Channels() {}
|
|
|
|
/// Well known channel names
|
|
static const char *adminChannel, *gpioChannel, *serialChannel, *mqttChannel;
|
|
|
|
const meshtastic_ChannelSettings &getPrimary() { return getByIndex(getPrimaryIndex()).settings; }
|
|
|
|
/** Return the Channel for a specified index */
|
|
meshtastic_Channel &getByIndex(ChannelIndex chIndex);
|
|
|
|
/** Return the Channel for a specified name, return primary if not found. */
|
|
meshtastic_Channel &getByName(const char *chName);
|
|
|
|
/** Using the index inside the channel, update the specified channel's settings and role. If this channel is being promoted
|
|
* to be primary, force all other channels to be secondary.
|
|
*/
|
|
void setChannel(const meshtastic_Channel &c);
|
|
|
|
/** Return a human friendly name for this channel (and expand any short strings as needed)
|
|
*/
|
|
const char *getName(size_t chIndex);
|
|
|
|
/**
|
|
* Return a globally unique channel ID usable with MQTT.
|
|
*/
|
|
const char *getGlobalId(size_t chIndex) { return getName(chIndex); } // FIXME, not correct
|
|
|
|
/** The index of the primary channel */
|
|
ChannelIndex getPrimaryIndex() const { return primaryIndex; }
|
|
|
|
ChannelIndex getNumChannels() { return channelFile.channels_count; }
|
|
|
|
/// Called by NodeDB on initial boot when the radio config settings are unset. Set a default single channel config.
|
|
void initDefaults();
|
|
|
|
/// called when the user has just changed our radio config and we might need to change channel keys
|
|
void onConfigChanged();
|
|
|
|
/** Given a channel hash setup crypto for decoding that channel (or the primary channel if that channel is unsecured)
|
|
*
|
|
* This method is called before decoding inbound packets
|
|
*
|
|
* @return false if the channel hash or channel is invalid
|
|
*/
|
|
bool decryptForHash(ChannelIndex chIndex, ChannelHash channelHash);
|
|
|
|
/** Given a channel index setup crypto for encoding that channel (or the primary channel if that channel is unsecured)
|
|
*
|
|
* This method is called before encoding outbound packets
|
|
*
|
|
* @eturn the (0 to 255) hash for that channel - if no suitable channel could be found, return -1
|
|
*/
|
|
int16_t setActiveByIndex(ChannelIndex channelIndex);
|
|
|
|
// Returns true if the channel has the default name and PSK
|
|
bool isDefaultChannel(ChannelIndex chIndex);
|
|
|
|
// Returns true if this channel's effective key is publicly decryptable (open or well-known/default PSK).
|
|
bool usesPublicKey(ChannelIndex chIndex);
|
|
// Returns true if the channel is "well known": its PSK is absent or a
|
|
// single-byte well-known key index, AND its name is any modem-preset
|
|
// display name (e.g. a channel named "LongFast" counts even while the
|
|
// radio runs MediumFast). Broader than isDefaultChannel, which only
|
|
// matches the current preset's name and PSK byte 1.
|
|
bool isWellKnownChannel(ChannelIndex chIndex);
|
|
|
|
// Returns true if we can be reached via a channel with the default settings given a region and modem preset
|
|
bool hasDefaultChannel();
|
|
|
|
// Returns true if any of our channels have enabled MQTT uplink or downlink
|
|
bool anyMqttEnabled();
|
|
|
|
bool ensureLicensedOperation();
|
|
|
|
bool setDefaultPresetCryptoForHash(ChannelHash channelHash);
|
|
|
|
/**
|
|
* Validate a channel, fixing any errors as needed
|
|
*/
|
|
meshtastic_Channel &fixupChannel(ChannelIndex chIndex);
|
|
|
|
int16_t getHash(ChannelIndex i) { return hashes[i]; }
|
|
|
|
private:
|
|
/** Given a channel index, change to use the crypto key specified by that index
|
|
*
|
|
* @eturn the (0 to 255) hash for that channel - if no suitable channel could be found, return -1
|
|
*/
|
|
int16_t setCrypto(ChannelIndex chIndex);
|
|
|
|
/** Return the channel index for the specified channel hash, or -1 for not found */
|
|
int8_t getIndexByHash(ChannelHash channelHash);
|
|
|
|
/** Given a channel number, return the (0 to 255) hash for that channel
|
|
* If no suitable channel could be found, return -1
|
|
*
|
|
* called by fixupChannel when a new channel is set
|
|
*/
|
|
int16_t generateHash(ChannelIndex channelNum);
|
|
|
|
/**
|
|
* Writes the default lora config
|
|
*/
|
|
void initDefaultLoraConfig();
|
|
|
|
/**
|
|
* Write default channels defined in UserPrefs
|
|
*/
|
|
void initDefaultChannel(ChannelIndex chIndex);
|
|
|
|
/**
|
|
* Return the key used for encrypting this channel (if channel is secondary and no key provided, use the primary channel's
|
|
* PSK)
|
|
*/
|
|
CryptoKey getKey(ChannelIndex chIndex);
|
|
};
|
|
|
|
/// Singleton channel table
|
|
extern Channels channels;
|
|
|
|
/// 16 bytes of random PSK for our _public_ default channel that all devices power up on (AES128)
|
|
static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59,
|
|
0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0x01};
|
|
|
|
/// True if a getKey()-resolved key offers no privacy: length 0 (off) or the public defaultpsk family. Pure; for tests.
|
|
bool cryptoKeyIsPublic(const CryptoKey &key);
|
|
|
|
static const uint8_t eventpsk[] = {0x38, 0x4b, 0xbc, 0xc0, 0x1d, 0xc0, 0x22, 0xd1, 0x81, 0xbf, 0x36,
|
|
0xb8, 0x61, 0x21, 0xe1, 0xfb, 0x96, 0xb7, 0x2e, 0x55, 0xbf, 0x74,
|
|
0x22, 0x7e, 0x9d, 0x6a, 0xfb, 0x48, 0xd6, 0x4c, 0xb1, 0xa1}; |