From 7afd270f3982a232a5fc8cdf5c2a7eae066ab0a5 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 28 Aug 2026 19:51:43 +0000 Subject: [PATCH] Gut beacon send-as-node and consolidate TX onto broadcast_targets (#11646) * Gut beacon send-as-node and consolidate TX onto broadcast_targets Two MeshBeaconConfig changes, both against fields that never reached a tagged release, so there is no migration for existing nodes. broadcast_send_as_node let a client name a node ID to send beacons AS, rewriting the packet's `from`. Firmware never applied it - the assignment was commented out, so `from` was always the local node and the field was a settable, persisted no-op. It was also unsound as designed: rewriting `from` forges no signature, it only makes isFromUs() false, so perhapsEncode() skips XEdDSA signing and receivers get an unsigned packet attributed to another node. broadcast_on_channel / broadcast_on_region / broadcast_on_preset were a second way to name a beacon destination alongside broadcast_targets, chosen silently on whether broadcast_targets was empty. The comments claimed the two were equivalent; they were not. An inline ChannelSettings carries name and PSK, so broadcast_on_channel could transmit on a channel absent from the node's channel table, which channel_index cannot express. That is dropped deliberately - the channel must exist on the node. Empty broadcast_targets now synthesises one target on the running preset and region over the primary channel, matching what the scalar path produced when left unset, so an otherwise unconfigured node still beacons. The USERPREFS_MESH_BEACON_ON_* keys go with the fields. A preconfigured build that still defines one now fails at compile time with a pointer to the USERPREFS_MESH_BEACON_TARGET_0_* equivalents, rather than silently losing its beacon channel. The replacement names a channel-table slot, so such a build must also provision that channel. MeshBeaconConfig shrinks 324 -> 240 bytes and ModuleConfig 328 -> 244, against the 512-byte MAX_TO_FROM_RADIO_SIZE ceiling that FromRadio sits 2 bytes under. The protobufs submodule points at a branch carrying both proto changes; it needs re-pointing to master once meshtastic/protobufs#1047 and #1048 merge. * Point protobufs submodule at master now that the beacon protos are merged meshtastic/protobufs#1047 and #1048 are in master, so drop the temporary beacon-proto-integration pin. MeshBeaconConfig stays 240 bytes and ModuleConfig 244, unchanged from the integration branch. The bump also picks up master's unrelated additions: the MESHNOLOGY_W12 and MESHPAGER_X2 hardware models, and a ground-speed unit correction in Position. --- protobufs | 2 +- src/mesh/NodeDB.cpp | 32 +--- src/mesh/PhoneAPI.cpp | 6 +- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 24 +-- .../generated/meshtastic/module_config.pb.h | 58 ++----- src/modules/AdminModule.cpp | 28 +--- src/modules/MeshBeaconModule.cpp | 45 ++---- src/modules/MeshBeaconModule.h | 4 +- test/test_mesh_beacon/test_main.cpp | 144 ++++++------------ userPrefs.jsonc | 12 +- 12 files changed, 106 insertions(+), 253 deletions(-) diff --git a/protobufs b/protobufs index aca181b97b..7b2464c9b8 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit aca181b97b7db047d76e9f000220a11a234cd389 +Subproject commit 7b2464c9b8c1521f93852261e4123826e5b25e11 diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index cf1f19cb99..f6bf9829d9 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1514,30 +1514,14 @@ void NodeDB::installDefaultModuleConfig() memcpy(moduleConfig.mesh_beacon.broadcast_offer_channel.psk.bytes, beaconOfferPsk, sizeof(beaconOfferPsk)); moduleConfig.mesh_beacon.broadcast_offer_channel.psk.size = sizeof(beaconOfferPsk); #endif -#ifdef USERPREFS_MESH_BEACON_ON_PRESET - moduleConfig.mesh_beacon.has_broadcast_on_preset = true; - moduleConfig.mesh_beacon.broadcast_on_preset = USERPREFS_MESH_BEACON_ON_PRESET; -#endif -#ifdef USERPREFS_MESH_BEACON_ON_REGION - moduleConfig.mesh_beacon.broadcast_on_region = USERPREFS_MESH_BEACON_ON_REGION; -#endif -#ifdef USERPREFS_MESH_BEACON_ON_CHANNEL_NAME - moduleConfig.mesh_beacon.has_broadcast_on_channel = true; - strncpy(moduleConfig.mesh_beacon.broadcast_on_channel.name, USERPREFS_MESH_BEACON_ON_CHANNEL_NAME, - sizeof(moduleConfig.mesh_beacon.broadcast_on_channel.name) - 1); - moduleConfig.mesh_beacon.broadcast_on_channel.name[sizeof(moduleConfig.mesh_beacon.broadcast_on_channel.name) - 1] = '\0'; -#endif -#ifdef USERPREFS_MESH_BEACON_ON_CHANNEL_PSK - moduleConfig.mesh_beacon.has_broadcast_on_channel = true; - static const uint8_t beaconOnPsk[] = USERPREFS_MESH_BEACON_ON_CHANNEL_PSK; - static_assert(sizeof(beaconOnPsk) <= sizeof(moduleConfig.mesh_beacon.broadcast_on_channel.psk.bytes), - "USERPREFS_MESH_BEACON_ON_CHANNEL_PSK exceeds the 32-byte channel PSK buffer"); - memcpy(moduleConfig.mesh_beacon.broadcast_on_channel.psk.bytes, beaconOnPsk, sizeof(beaconOnPsk)); - moduleConfig.mesh_beacon.broadcast_on_channel.psk.size = sizeof(beaconOnPsk); -#endif -#ifdef USERPREFS_MESH_BEACON_ON_CHANNEL_NUM - moduleConfig.mesh_beacon.has_broadcast_on_channel = true; - moduleConfig.mesh_beacon.broadcast_on_channel.channel_num = USERPREFS_MESH_BEACON_ON_CHANNEL_NUM; +// The USERPREFS_MESH_BEACON_ON_* keys were removed with the broadcast_on_* config fields. Fail the +// build rather than silently dropping a preconfigured beacon channel: define the equivalent +// USERPREFS_MESH_BEACON_TARGET_0_{PRESET,REGION,CHANNEL_INDEX} keys instead. CHANNEL_INDEX names a +// slot in the device's channel table, so the channel must also be provisioned on the node. +#if defined(USERPREFS_MESH_BEACON_ON_PRESET) || defined(USERPREFS_MESH_BEACON_ON_REGION) || \ + defined(USERPREFS_MESH_BEACON_ON_CHANNEL_NAME) || defined(USERPREFS_MESH_BEACON_ON_CHANNEL_PSK) || \ + defined(USERPREFS_MESH_BEACON_ON_CHANNEL_NUM) +#error "USERPREFS_MESH_BEACON_ON_* removed; use USERPREFS_MESH_BEACON_TARGET_0_* (channel must be in the channel table)" #endif #ifdef USERPREFS_MESH_BEACON_LEGACY_SPLIT BEACON_APPLY_FLAG(USERPREFS_MESH_BEACON_LEGACY_SPLIT, meshtastic_ModuleConfig_MeshBeaconConfig_Flags_FLAG_LEGACY_SPLIT); diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index fdffd0c260..f6757206c5 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -920,9 +920,9 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) #ifdef MESHTASTIC_PHONEAPI_ACCESS_CONTROL if (!getAdminAuthorized()) { // Unauthenticated: emit an empty MeshBeaconConfig (zero-init from - // the top-of-loop memset). The embedded ChannelSettings - // (broadcast_offer_channel / broadcast_on_channel) carry PSKs that - // must not be visible to an unauth client. + // the top-of-loop memset). The embedded broadcast_offer_channel + // ChannelSettings carries a PSK that must not be visible to an + // unauth client. } else #endif { diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 51e43526e0..ea6286fac5 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -455,7 +455,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg; /* Maximum encoded size of messages (where known) */ /* meshtastic_NodeDatabase_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size -#define meshtastic_BackupPreferences_size 2740 +#define meshtastic_BackupPreferences_size 2656 #define meshtastic_ChannelFile_size 718 #define meshtastic_DeviceState_size 1944 #define meshtastic_NodeEnvironmentEntry_size 231 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index c560d5447e..35a0d43d55 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -212,7 +212,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size #define meshtastic_LocalConfig_size 759 -#define meshtastic_LocalModuleConfig_size 1126 +#define meshtastic_LocalModuleConfig_size 1042 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index c59001f105..375ff4861b 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -341,6 +341,10 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_HELTEC_RCC6 = 143, /* Seeed Wio Tracker L1 Pro 1W, nRF52840 + SX1262 with 1 W external PA */ meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_PRO_1W = 144, + /* Meshnology W12 */ + meshtastic_HardwareModel_MESHNOLOGY_W12 = 145, + /* Seeed Studio MeshPager X2 */ + meshtastic_HardwareModel_MESHPAGER_X2 = 146, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ @@ -738,7 +742,7 @@ typedef struct _meshtastic_Position { multiplied with DOP to calculate positional accuracy Default: "'bout three meters-ish" :) */ uint32_t gps_accuracy; - /* Ground speed in m/s and True North TRACK in 1/100 degrees + /* Ground speed in km/h and True North TRACK in 1/100 degrees Clarification of terms: - "track" is the direction of motion (measured in horizontal plane) - "heading" is where the fuselage points (measured in horizontal plane) @@ -1273,15 +1277,15 @@ typedef struct _meshtastic_LockdownStatus { /* Current lockdown state being reported. */ meshtastic_LockdownStatus_State state; /* For LOCKED: machine-readable reason. Known values: - "needs_auth" — storage already unlocked, client must auth - "token_missing" — no boot token on flash - "token_expired" — boot token wall-clock TTL elapsed - "token_boots_zero" — boot token boot-count TTL exhausted - "token_hmac_fail" — token tampered or wrong device - "token_dek_fail" — token DEK decrypt failed - "token_wrong_size" — token file corrupted - "token_bad_magic" — token file corrupted - "not_provisioned" — should generally use NEEDS_PROVISION state instead + "needs_auth" - storage already unlocked, client must auth + "token_missing" - no boot token on flash + "token_expired" - boot token wall-clock TTL elapsed + "token_boots_zero" - boot token boot-count TTL exhausted + "token_hmac_fail" - token tampered or wrong device + "token_dek_fail" - token DEK decrypt failed + "token_wrong_size" - token file corrupted + "token_bad_magic" - token file corrupted + "not_provisioned" - should generally use NEEDS_PROVISION state instead Other values may be added; clients should treat unknown values as "locked, ask for passphrase". */ char lock_reason[32]; diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index b04c358fc4..5d6585f036 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -457,8 +457,8 @@ typedef struct _meshtastic_ModuleConfig_StatusMessageConfig { char node_status[80]; } meshtastic_ModuleConfig_StatusMessageConfig; -/* One entry in the multi-target broadcast list. - The broadcaster transmits one beacon copy per entry, each on its own radio settings. */ +/* One entry in the broadcast destination list. + Each entry names one set of radio settings to send a beacon copy on. */ typedef struct _meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget { /* Modem preset to use for this target. Falls back to the running config preset if unset. */ @@ -478,12 +478,6 @@ typedef struct _meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget { typedef struct _meshtastic_ModuleConfig_MeshBeaconConfig { /* Bitwise-OR of Flags values (listen / broadcast / legacy-split toggles). */ uint32_t flags; - /* Optional: node ID to send beacon messages AS. - When set, the `from` field of outgoing beacon packets is set to this node ID, - making beacons appear to originate from that node. - When unset (0), beacons are sent as the local node. - A remote admin can only set this field to their own node ID. */ - uint32_t broadcast_send_as_node; /* Message to include in each beacon broadcast. Max 100 bytes enforced by firmware. */ char broadcast_message[101]; /* Optional channel (name + PSK) to advertise in the MeshBeacon offer_channel field. */ @@ -494,30 +488,15 @@ typedef struct _meshtastic_ModuleConfig_MeshBeaconConfig { /* Optional modem preset to advertise in the MeshBeacon offer_preset field. */ bool has_broadcast_offer_preset; meshtastic_Config_LoRaConfig_ModemPreset broadcast_offer_preset; - /* Single-target TX channel: channel settings (name + PSK) to send beacons on. - If unset, beacons go out on the primary channel. Used only when broadcast_targets is empty. - NOTE: the single-target path embeds the ChannelSettings inline here, whereas a - broadcast_targets entry references a channel-table slot by channel_index instead — see - BroadcastTarget. The two paths are equal, first-class options; only this representation differs. */ - bool has_broadcast_on_channel; - meshtastic_ChannelSettings broadcast_on_channel; - /* Region to use when sending beacons on broadcast_on_preset. */ - meshtastic_Config_LoRaConfig_RegionCode broadcast_on_region; - /* Modem preset to use when sending beacons. - If different from current config, the radio is temporarily switched for TX. */ - bool has_broadcast_on_preset; - meshtastic_Config_LoRaConfig_ModemPreset broadcast_on_preset; /* How often to broadcast, in seconds. Min 3600 (1 h), default 3600. */ uint32_t broadcast_interval_secs; - /* Multi-target broadcast list. - When non-empty the broadcaster transmits one beacon copy per entry in sequence, - each temporarily switching the radio to that entry's preset/region/channel. - When empty, the broadcaster uses the scalar broadcast_on_preset / broadcast_on_region / - broadcast_on_channel fields instead (the single-target path). - Single- and multi-target are equal, first-class options — neither is preferred or - deprecated. They differ only in how the TX channel is named: broadcast_on_channel embeds a - ChannelSettings inline, while a target references an existing channel-table slot by - channel_index (see BroadcastTarget). */ + /* Broadcast destination list. + The broadcaster sends one beacon copy per distinct destination, in sequence, temporarily + switching the radio to that entry's preset/region/channel for each. + When empty, a single beacon is sent on the node's running preset and region over the + primary channel. + Entries that resolve to the same effective preset, region and channel are deduplicated, so + a duplicate entry does not produce a second transmission. */ pb_size_t broadcast_targets_count; meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget broadcast_targets[4]; } meshtastic_ModuleConfig_MeshBeaconConfig; @@ -654,8 +633,6 @@ extern "C" { #define meshtastic_ModuleConfig_MeshBeaconConfig_broadcast_offer_region_ENUMTYPE meshtastic_Config_LoRaConfig_RegionCode #define meshtastic_ModuleConfig_MeshBeaconConfig_broadcast_offer_preset_ENUMTYPE meshtastic_Config_LoRaConfig_ModemPreset -#define meshtastic_ModuleConfig_MeshBeaconConfig_broadcast_on_region_ENUMTYPE meshtastic_Config_LoRaConfig_RegionCode -#define meshtastic_ModuleConfig_MeshBeaconConfig_broadcast_on_preset_ENUMTYPE meshtastic_Config_LoRaConfig_ModemPreset #define meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_preset_ENUMTYPE meshtastic_Config_LoRaConfig_ModemPreset #define meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_region_ENUMTYPE meshtastic_Config_LoRaConfig_RegionCode @@ -684,7 +661,7 @@ extern "C" { #define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StatusMessageConfig_init_default {""} -#define meshtastic_ModuleConfig_MeshBeaconConfig_init_default {0, 0, "", false, meshtastic_ChannelSettings_init_default, _meshtastic_Config_LoRaConfig_RegionCode_MIN, false, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, false, meshtastic_ChannelSettings_init_default, _meshtastic_Config_LoRaConfig_RegionCode_MIN, false, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, {meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_init_default, meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_init_default, meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_init_default, meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_init_default}} +#define meshtastic_ModuleConfig_MeshBeaconConfig_init_default {0, "", false, meshtastic_ChannelSettings_init_default, _meshtastic_Config_LoRaConfig_RegionCode_MIN, false, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, {meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_init_default, meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_init_default, meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_init_default, meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_init_default}} #define meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_init_default {false, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_RegionCode_MIN, false, 0} #define meshtastic_ModuleConfig_TAKConfig_init_default {_meshtastic_Team_MIN, _meshtastic_MemberRole_MIN} #define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN} @@ -705,7 +682,7 @@ extern "C" { #define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define meshtastic_ModuleConfig_AmbientLightingConfig_init_zero {0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StatusMessageConfig_init_zero {""} -#define meshtastic_ModuleConfig_MeshBeaconConfig_init_zero {0, 0, "", false, meshtastic_ChannelSettings_init_zero, _meshtastic_Config_LoRaConfig_RegionCode_MIN, false, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, false, meshtastic_ChannelSettings_init_zero, _meshtastic_Config_LoRaConfig_RegionCode_MIN, false, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, {meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_init_zero, meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_init_zero, meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_init_zero, meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_init_zero}} +#define meshtastic_ModuleConfig_MeshBeaconConfig_init_zero {0, "", false, meshtastic_ChannelSettings_init_zero, _meshtastic_Config_LoRaConfig_RegionCode_MIN, false, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, {meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_init_zero, meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_init_zero, meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_init_zero, meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_init_zero}} #define meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_init_zero {false, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_RegionCode_MIN, false, 0} #define meshtastic_ModuleConfig_TAKConfig_init_zero {_meshtastic_Team_MIN, _meshtastic_MemberRole_MIN} #define meshtastic_RemoteHardwarePin_init_zero {0, "", _meshtastic_RemoteHardwarePinType_MIN} @@ -821,14 +798,10 @@ extern "C" { #define meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_region_tag 2 #define meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_channel_index_tag 4 #define meshtastic_ModuleConfig_MeshBeaconConfig_flags_tag 1 -#define meshtastic_ModuleConfig_MeshBeaconConfig_broadcast_send_as_node_tag 3 #define meshtastic_ModuleConfig_MeshBeaconConfig_broadcast_message_tag 4 #define meshtastic_ModuleConfig_MeshBeaconConfig_broadcast_offer_channel_tag 5 #define meshtastic_ModuleConfig_MeshBeaconConfig_broadcast_offer_region_tag 6 #define meshtastic_ModuleConfig_MeshBeaconConfig_broadcast_offer_preset_tag 7 -#define meshtastic_ModuleConfig_MeshBeaconConfig_broadcast_on_channel_tag 8 -#define meshtastic_ModuleConfig_MeshBeaconConfig_broadcast_on_region_tag 9 -#define meshtastic_ModuleConfig_MeshBeaconConfig_broadcast_on_preset_tag 10 #define meshtastic_ModuleConfig_MeshBeaconConfig_broadcast_interval_secs_tag 11 #define meshtastic_ModuleConfig_MeshBeaconConfig_broadcast_targets_tag 13 #define meshtastic_ModuleConfig_TAKConfig_team_tag 1 @@ -1073,20 +1046,15 @@ X(a, STATIC, SINGULAR, STRING, node_status, 1) #define meshtastic_ModuleConfig_MeshBeaconConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, flags, 1) \ -X(a, STATIC, SINGULAR, UINT32, broadcast_send_as_node, 3) \ X(a, STATIC, SINGULAR, STRING, broadcast_message, 4) \ X(a, STATIC, OPTIONAL, MESSAGE, broadcast_offer_channel, 5) \ X(a, STATIC, SINGULAR, UENUM, broadcast_offer_region, 6) \ X(a, STATIC, OPTIONAL, UENUM, broadcast_offer_preset, 7) \ -X(a, STATIC, OPTIONAL, MESSAGE, broadcast_on_channel, 8) \ -X(a, STATIC, SINGULAR, UENUM, broadcast_on_region, 9) \ -X(a, STATIC, OPTIONAL, UENUM, broadcast_on_preset, 10) \ X(a, STATIC, SINGULAR, UINT32, broadcast_interval_secs, 11) \ X(a, STATIC, REPEATED, MESSAGE, broadcast_targets, 13) #define meshtastic_ModuleConfig_MeshBeaconConfig_CALLBACK NULL #define meshtastic_ModuleConfig_MeshBeaconConfig_DEFAULT NULL #define meshtastic_ModuleConfig_MeshBeaconConfig_broadcast_offer_channel_MSGTYPE meshtastic_ChannelSettings -#define meshtastic_ModuleConfig_MeshBeaconConfig_broadcast_on_channel_MSGTYPE meshtastic_ChannelSettings #define meshtastic_ModuleConfig_MeshBeaconConfig_broadcast_targets_MSGTYPE meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget #define meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_FIELDLIST(X, a) \ @@ -1164,7 +1132,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_MQTTConfig_size 224 #define meshtastic_ModuleConfig_MapReportSettings_size 14 #define meshtastic_ModuleConfig_MeshBeaconConfig_BroadcastTarget_size 10 -#define meshtastic_ModuleConfig_MeshBeaconConfig_size 324 +#define meshtastic_ModuleConfig_MeshBeaconConfig_size 240 #define meshtastic_ModuleConfig_NeighborInfoConfig_size 10 #define meshtastic_ModuleConfig_PaxcounterConfig_size 30 #define meshtastic_ModuleConfig_RangeTestConfig_size 12 @@ -1175,7 +1143,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_TAKConfig_size 4 #define meshtastic_ModuleConfig_TelemetryConfig_size 50 #define meshtastic_ModuleConfig_TrafficManagementConfig_size 30 -#define meshtastic_ModuleConfig_size 328 +#define meshtastic_ModuleConfig_size 244 #define meshtastic_RemoteHardwarePin_size 21 #ifdef __cplusplus diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 0af7ed9e05..07d55b7483 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -343,17 +343,6 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta case meshtastic_AdminMessage_set_module_config_tag: LOG_DEBUG("Client set module config"); -#if !MESHTASTIC_EXCLUDE_BEACON - // broadcast_send_as_node: remote admins may only set this to their own node ID. - if (mp.from != 0 && r->set_module_config.which_payload_variant == meshtastic_ModuleConfig_mesh_beacon_tag) { - auto &b = r->set_module_config.payload_variant.mesh_beacon; - if (b.broadcast_send_as_node != 0 && b.broadcast_send_as_node != mp.from) { - LOG_WARN("Beacon: rejecting broadcast_send_as_node 0x%08x from node 0x%08x (must match sender)", - b.broadcast_send_as_node, mp.from); - b.broadcast_send_as_node = moduleConfig.mesh_beacon.broadcast_send_as_node; - } - } -#endif if (!handleSetModuleConfig(r->set_module_config)) { myReply = allocErrorResponse(meshtastic_Routing_Error_BAD_REQUEST, &mp); } @@ -1371,19 +1360,6 @@ bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) if (beaconCfg.broadcast_interval_secs != 0 && beaconCfg.broadcast_interval_secs < default_mesh_beacon_min_broadcast_interval_secs) beaconCfg.broadcast_interval_secs = default_mesh_beacon_min_broadcast_interval_secs; - // Validate broadcast_on_preset against broadcast_on_region (or current region if unset). - if (beaconCfg.has_broadcast_on_preset) { - meshtastic_Config_LoRaConfig probe = config.lora; - probe.use_preset = true; - probe.modem_preset = beaconCfg.broadcast_on_preset; - if (beaconCfg.broadcast_on_region != meshtastic_Config_LoRaConfig_RegionCode_UNSET) - probe.region = beaconCfg.broadcast_on_region; - if (!RadioInterface::validateConfigLora(probe)) { - LOG_WARN("Beacon: broadcast_on_preset %d invalid for region, clearing", beaconCfg.broadcast_on_preset); - beaconCfg.has_broadcast_on_preset = false; - beaconCfg.has_broadcast_on_channel = false; - } - } // Validate broadcast_offer_preset against broadcast_offer_region (or current region if unset). if (beaconCfg.has_broadcast_offer_preset) { meshtastic_Config_LoRaConfig probe = config.lora; @@ -1404,8 +1380,8 @@ bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) beaconCfg.broadcast_offer_region = meshtastic_Config_LoRaConfig_RegionCode_UNSET; } } - // Validate each multi-target entry the same way as the single-target broadcast_on_* fields, - // so a bad preset/region is cleared on write rather than relying on the runtime TX drop. + // Validate each broadcast target so a bad preset/region is cleared on write rather than + // relying on the runtime TX drop. for (pb_size_t i = 0; i < beaconCfg.broadcast_targets_count; i++) { auto &t = beaconCfg.broadcast_targets[i]; // Region must be a known region code (UNSET = use running config at TX time). diff --git a/src/modules/MeshBeaconModule.cpp b/src/modules/MeshBeaconModule.cpp index 1976f054d7..9842de2549 100644 --- a/src/modules/MeshBeaconModule.cpp +++ b/src/modules/MeshBeaconModule.cpp @@ -417,22 +417,6 @@ void MeshBeaconBroadcastModule::sendBeacon() const auto stampPacket = [&](meshtastic_MeshPacket *p) { p->to = NODENUM_BROADCAST; p->from = nodeDB->getNodeNum(); - // broadcast_send_as_node: commented out pending further review. - // Spoof notes preserved for when this is re-enabled: - // broadcast_send_as_node overrides the source NodeNum. NOTE: this is a *node-ID* spoof - // only - it rewrites the 'from' field but does NOT forge any signature. Once 'from' is - // not us, the packet is no longer isFromUs(), so Router::perhapsEncode() skips XEdDSA - // signing and receivers get an unsigned packet attributed to another node. - // When broadcast_send_as_node == 0 the beacon is genuinely from us and Router::perhapsEncode() - // signs it under the same XEdDSA broadcast policy as normal channel messages. - // When broadcast_send_as_node rewrites p->from, perhapsEncode() sees isFromUs()=false and - // skips setting has_bitfield - must be set explicitly so receivers can classify hop_start - // correctly and so ok_to_mqtt is honoured on the spoofed packet. - // if (bcfg.broadcast_send_as_node != 0) { - // p->from = bcfg.broadcast_send_as_node; - // p->decoded.has_bitfield = true; - // p->decoded.bitfield |= (config.lora.config_ok_to_mqtt << BITFIELD_OK_TO_MQTT_SHIFT); - // } p->hop_limit = 0; // all beacon packets are zero hopped to limit spamming. p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; p->want_ack = false; @@ -476,10 +460,9 @@ void MeshBeaconBroadcastModule::sendBeacon() // ── Per-target loop ────────────────────────────────────────────────────── // - // If broadcast_targets is populated, iterate over those. Otherwise use the single-target - // broadcast_on_preset / broadcast_on_region / broadcast_on_channel fields. The two paths are - // equal options; they differ only in how the TX channel is named (single-target embeds a - // ChannelSettings inline; a target references a channel-table slot by channel_index). + // Every destination comes from broadcast_targets. An entry names its TX channel by + // channel_index, a slot in the device's channel table, so the channel must already be + // configured on the node - its key is needed to encrypt. struct EffTarget { meshtastic_Config_LoRaConfig_ModemPreset preset; uint16_t slot; @@ -488,8 +471,9 @@ void MeshBeaconBroadcastModule::sendBeacon() meshtastic_ChannelSettings channel; }; - const bool useTargetList = bcfg.broadcast_targets_count > 0; - const int targetCount = useTargetList ? (int)bcfg.broadcast_targets_count : 1; + // An empty list still beacons once, on the node's running preset and region over the primary + // channel. Each entry below overrides only what it sets. + const int targetCount = bcfg.broadcast_targets_count > 0 ? (int)bcfg.broadcast_targets_count : 1; // Dedup state: the beacon payload is identical across targets, so two targets that resolve to // the same effective radio config (preset + resolved region + channel) would just re-broadcast @@ -510,17 +494,19 @@ void MeshBeaconBroadcastModule::sendBeacon() }; for (int ti = 0; ti < targetCount; ti++) { + // Defaults: running radio config, primary channel. A target entry overrides from here. EffTarget tgt = {}; - if (useTargetList) { + tgt.preset = config.lora.modem_preset; + tgt.slot = config.lora.channel_num; + if (ti < (int)bcfg.broadcast_targets_count) { const auto &bt = bcfg.broadcast_targets[ti]; - tgt.preset = bt.has_preset ? bt.preset : config.lora.modem_preset; + if (bt.has_preset) + tgt.preset = bt.preset; tgt.region = bt.region; // Resolve the channel from the device's channel table by index. A slot is only usable // if it is actually configured (has a name or PSK - its key is needed to encrypt). An // out-of-range index, or a blank slot, falls back to the default channel for the target // preset (see beaconChannelSettings), exactly as an unset channel_index would. - tgt.has_channel = false; - tgt.slot = config.lora.channel_num; if (bt.has_channel_index) { if (bt.channel_index >= (uint32_t)channels.getNumChannels()) { LOG_WARN("Beacon: target %d channel_index %u out of range, use preset default", ti, bt.channel_index); @@ -535,13 +521,6 @@ void MeshBeaconBroadcastModule::sendBeacon() } } } - } else { - tgt.preset = bcfg.has_broadcast_on_preset ? bcfg.broadcast_on_preset : config.lora.modem_preset; - tgt.region = bcfg.broadcast_on_region; - tgt.has_channel = bcfg.has_broadcast_on_channel; - if (tgt.has_channel) - tgt.channel = bcfg.broadcast_on_channel; - tgt.slot = tgt.has_channel ? bcfg.broadcast_on_channel.channel_num : config.lora.channel_num; } // Skip a target whose effective radio config duplicates one already sent this cycle. diff --git a/src/modules/MeshBeaconModule.h b/src/modules/MeshBeaconModule.h index bed9e882c6..9ae6d4ddda 100644 --- a/src/modules/MeshBeaconModule.h +++ b/src/modules/MeshBeaconModule.h @@ -40,7 +40,7 @@ class MeshBeaconModule /** * Reconfigure the radio for beacon TX, or restore to original config if p is NULL. * Returns true if the radio was reconfigured (caller must re-run transmit delay for CCA). - * Driven by broadcast_on_preset / broadcast_on_channel from MeshBeaconConfig. + * Driven by the broadcast_targets entry associated with the packet. */ static bool reconfigureForBeaconTX(RadioInterface *iface, meshtastic_MeshPacket *p); @@ -77,7 +77,7 @@ class MeshBeaconModule protected: /** * Build the ChannelSettings the beacon transmits on: the base (primary) channel overlaid with - * any broadcast_on_channel overrides, defaulting an empty name to the target preset's display + * the target's channel-table slot, defaulting an empty name to the target preset's display * name. Shared by the encrypt-time channel swap and the radio-thread RF swap so the channel * key + hash are identical at both points. */ diff --git a/test/test_mesh_beacon/test_main.cpp b/test/test_mesh_beacon/test_main.cpp index 4c2ddad954..a7b5dbe202 100644 --- a/test/test_mesh_beacon/test_main.cpp +++ b/test/test_mesh_beacon/test_main.cpp @@ -205,13 +205,14 @@ static void test_adminValidation_turboPresetOnEU868_isCleared(void) meshtastic_ModuleConfig_MeshBeaconConfig bcfg = meshtastic_ModuleConfig_MeshBeaconConfig_init_zero; bcfg.flags |= MESH_BEACON_FLAG_BROADCAST_ENABLED; - bcfg.has_broadcast_on_preset = true; - bcfg.broadcast_on_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO; + bcfg.broadcast_targets_count = 1; + bcfg.broadcast_targets[0].has_preset = true; + bcfg.broadcast_targets[0].preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO; testAdmin->handleSetModuleConfig(makeBeaconModuleConfig(bcfg)); TEST_ASSERT_TRUE(moduleConfig.has_mesh_beacon); - TEST_ASSERT_FALSE_MESSAGE(moduleConfig.mesh_beacon.has_broadcast_on_preset, "SHORT_TURBO must be cleared for EU_868"); + TEST_ASSERT_FALSE_MESSAGE(moduleConfig.mesh_beacon.broadcast_targets[0].has_preset, "SHORT_TURBO must be cleared for EU_868"); } /** @@ -223,12 +224,13 @@ static void test_adminValidation_longTurboPresetOnEU868_isCleared(void) resetConfig(); meshtastic_ModuleConfig_MeshBeaconConfig bcfg = meshtastic_ModuleConfig_MeshBeaconConfig_init_zero; - bcfg.has_broadcast_on_preset = true; - bcfg.broadcast_on_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO; + bcfg.broadcast_targets_count = 1; + bcfg.broadcast_targets[0].has_preset = true; + bcfg.broadcast_targets[0].preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO; testAdmin->handleSetModuleConfig(makeBeaconModuleConfig(bcfg)); - TEST_ASSERT_FALSE(moduleConfig.mesh_beacon.has_broadcast_on_preset); + TEST_ASSERT_FALSE(moduleConfig.mesh_beacon.broadcast_targets[0].has_preset); } /** @@ -242,13 +244,14 @@ static void test_adminValidation_turboPresetOnUS_isAccepted(void) initRegion(); meshtastic_ModuleConfig_MeshBeaconConfig bcfg = meshtastic_ModuleConfig_MeshBeaconConfig_init_zero; - bcfg.has_broadcast_on_preset = true; - bcfg.broadcast_on_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO; + bcfg.broadcast_targets_count = 1; + bcfg.broadcast_targets[0].has_preset = true; + bcfg.broadcast_targets[0].preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO; testAdmin->handleSetModuleConfig(makeBeaconModuleConfig(bcfg)); - TEST_ASSERT_TRUE(moduleConfig.mesh_beacon.has_broadcast_on_preset); - TEST_ASSERT_EQUAL(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO, moduleConfig.mesh_beacon.broadcast_on_preset); + TEST_ASSERT_TRUE(moduleConfig.mesh_beacon.broadcast_targets[0].has_preset); + TEST_ASSERT_EQUAL(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO, moduleConfig.mesh_beacon.broadcast_targets[0].preset); } /** @@ -260,13 +263,14 @@ static void test_adminValidation_mediumTurboPresetOnEU868_isCleared(void) resetConfig(); meshtastic_ModuleConfig_MeshBeaconConfig bcfg = meshtastic_ModuleConfig_MeshBeaconConfig_init_zero; - bcfg.has_broadcast_on_preset = true; - bcfg.broadcast_on_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_TURBO; + bcfg.broadcast_targets_count = 1; + bcfg.broadcast_targets[0].has_preset = true; + bcfg.broadcast_targets[0].preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_TURBO; testAdmin->handleSetModuleConfig(makeBeaconModuleConfig(bcfg)); TEST_ASSERT_TRUE(moduleConfig.has_mesh_beacon); - TEST_ASSERT_FALSE(moduleConfig.mesh_beacon.has_broadcast_on_preset); + TEST_ASSERT_FALSE(moduleConfig.mesh_beacon.broadcast_targets[0].has_preset); } /** @@ -280,13 +284,15 @@ static void test_adminValidation_mediumTurboPresetOnUS_isAccepted(void) initRegion(); meshtastic_ModuleConfig_MeshBeaconConfig bcfg = meshtastic_ModuleConfig_MeshBeaconConfig_init_zero; - bcfg.has_broadcast_on_preset = true; - bcfg.broadcast_on_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_TURBO; + bcfg.broadcast_targets_count = 1; + bcfg.broadcast_targets[0].has_preset = true; + bcfg.broadcast_targets[0].preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_TURBO; testAdmin->handleSetModuleConfig(makeBeaconModuleConfig(bcfg)); - TEST_ASSERT_TRUE(moduleConfig.mesh_beacon.has_broadcast_on_preset); - TEST_ASSERT_EQUAL(meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_TURBO, moduleConfig.mesh_beacon.broadcast_on_preset); + TEST_ASSERT_TRUE(moduleConfig.mesh_beacon.broadcast_targets[0].has_preset); + TEST_ASSERT_EQUAL(meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_TURBO, + moduleConfig.mesh_beacon.broadcast_targets[0].preset); } /** @@ -324,8 +330,7 @@ static void test_adminValidation_validOfferRegion_isPreserved(void) /** * Verify an out-of-range region in a multi-target entry is sanitised to UNSET on write. - * Important because broadcast_targets entries are validated independently of the single-target - * broadcast_on_* fields, and an invalid enum must never reach the radio-switch path. + * Important because an invalid enum must never reach the radio-switch path. */ static void test_adminValidation_targetUnknownRegion_isCleared(void) { @@ -342,8 +347,8 @@ static void test_adminValidation_targetUnknownRegion_isCleared(void) } /** - * Verify a preset that is illegal for a multi-target entry's region clears that entry's preset - * (and its channel), matching the single-target broadcast_on_preset rule. + * Verify a preset that is illegal for a broadcast target's region clears that entry's preset + * and its channel. */ static void test_adminValidation_targetInvalidPresetForRegion_isCleared(void) { @@ -647,7 +652,7 @@ static void test_broadcaster_rebuildCache_idempotent(void) // =========================================================================== /** - * Verify the 'from' field defaults to the local node number when broadcast_send_as_node is 0. + * Verify the 'from' field is the local node number. * Important for correct source attribution in peer node tables that receive the beacon. */ static void test_broadcaster_sendBeacon_fromIsLocalNodeWhenUnset(void) @@ -655,7 +660,6 @@ static void test_broadcaster_sendBeacon_fromIsLocalNodeWhenUnset(void) resetConfig(); moduleConfig.has_mesh_beacon = true; moduleConfig.mesh_beacon.flags |= MESH_BEACON_FLAG_BROADCAST_ENABLED; - moduleConfig.mesh_beacon.broadcast_send_as_node = 0; strncpy(moduleConfig.mesh_beacon.broadcast_message, "from-local", sizeof(moduleConfig.mesh_beacon.broadcast_message) - 1); MeshBeaconBroadcastModuleTestShim bcast; @@ -665,27 +669,6 @@ static void test_broadcaster_sendBeacon_fromIsLocalNodeWhenUnset(void) TEST_ASSERT_EQUAL_UINT32(kLocalNode, mockRouter->sentPackets[0].from); } -/** - * Verify broadcast_send_as_node is currently disabled: 'from' is always the local node - * even when broadcast_send_as_node is set to a remote node number. - * (broadcast_send_as_node is commented out as "not suitable right now".) - */ -static void test_broadcaster_sendBeacon_fromIsCustomNodeWhenSet(void) -{ - resetConfig(); - moduleConfig.has_mesh_beacon = true; - moduleConfig.mesh_beacon.flags |= MESH_BEACON_FLAG_BROADCAST_ENABLED; - moduleConfig.mesh_beacon.broadcast_send_as_node = kRemoteNode; - strncpy(moduleConfig.mesh_beacon.broadcast_message, "from-remote", sizeof(moduleConfig.mesh_beacon.broadcast_message) - 1); - - MeshBeaconBroadcastModuleTestShim bcast; - bcast.sendBeacon(); - - TEST_ASSERT_EQUAL_UINT32(1, mockRouter->sentPackets.size()); - // broadcast_send_as_node is disabled; from is always the local node - TEST_ASSERT_EQUAL_UINT32(kLocalNode, mockRouter->sentPackets[0].from); -} - /** * Verify the 'to' field is always NODENUM_BROADCAST regardless of other settings. * Important because beacons are mesh-wide announcements and must never be addressed to a single peer. @@ -723,8 +706,8 @@ static void test_broadcaster_sendBeacon_usesBeaconPortnum(void) } /** - * Verify TEXT_MESSAGE_APP portnum is used when no offer content is present, even if - * broadcast_on_preset is set (that field governs which radio config to use for TX, not portnum). + * Verify TEXT_MESSAGE_APP portnum is used when no offer content is present, even if a + * broadcast target preset is set (that governs which radio config to use for TX, not portnum). * Important so standard clients display plain-text beacons without needing a MESH_BEACON_APP decoder. */ static void test_broadcaster_sendBeacon_fallsBackToTextMessagePortnum(void) @@ -733,9 +716,10 @@ static void test_broadcaster_sendBeacon_fallsBackToTextMessagePortnum(void) moduleConfig.has_mesh_beacon = true; const char *msg = "plain-text-beacon"; strncpy(moduleConfig.mesh_beacon.broadcast_message, msg, sizeof(moduleConfig.mesh_beacon.broadcast_message) - 1); - // broadcast_on_preset set, but no offer - should still be TEXT_MESSAGE_APP - moduleConfig.mesh_beacon.has_broadcast_on_preset = true; - moduleConfig.mesh_beacon.broadcast_on_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW; + // Target preset set, but no offer - should still be TEXT_MESSAGE_APP + moduleConfig.mesh_beacon.broadcast_targets_count = 1; + moduleConfig.mesh_beacon.broadcast_targets[0].has_preset = true; + moduleConfig.mesh_beacon.broadcast_targets[0].preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW; MeshBeaconBroadcastModuleTestShim bcast; bcast.sendBeacon(); @@ -1155,52 +1139,11 @@ static void test_broadcaster_legacySplit_secondPacketIsTextMessage(void) } // =========================================================================== -// Group 7: Beacon-channel PSK swap (broadcast_on_channel override) +// Group 7: Beacon-channel PSK swap (target channel-table slot) // =========================================================================== /** - * When broadcast_on_channel overrides the primary channel's name/PSK, the packet must be encrypted - * on the BEACON channel, not the primary. perhapsEncode keys off the primary slot, so sendBeaconPacket - * temporarily installs the beacon channel there for the send and restores it after. Verify both: the - * primary slot IS the beacon channel during send(), and it is restored afterwards (no leak). - */ -static void test_broadcaster_channelPskOverride_swapsBeaconChannelAndRestores(void) -{ - resetConfig(); - static const uint8_t homePsk[16] = {0xAA, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; - installTestPrimaryChannel("Home", homePsk, sizeof(homePsk)); - - moduleConfig.has_mesh_beacon = true; - moduleConfig.mesh_beacon.has_broadcast_offer_preset = true; // gives the beacon radio content to send - moduleConfig.mesh_beacon.broadcast_offer_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW; - moduleConfig.mesh_beacon.has_broadcast_on_channel = true; - strncpy(moduleConfig.mesh_beacon.broadcast_on_channel.name, "BeaconCh", - sizeof(moduleConfig.mesh_beacon.broadcast_on_channel.name) - 1); - static const uint8_t beaconPsk[16] = {0xBB, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, - 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}; - moduleConfig.mesh_beacon.broadcast_on_channel.psk.size = sizeof(beaconPsk); - memcpy(moduleConfig.mesh_beacon.broadcast_on_channel.psk.bytes, beaconPsk, sizeof(beaconPsk)); - - MeshBeaconBroadcastModuleTestShim bcast; - bcast.sendBeacon(); - - // During send(), the primary slot must hold the BEACON channel (so encryption uses its PSK). - TEST_ASSERT_TRUE_MESSAGE(mockRouter->primaryAtSend.size() >= 1, "expected at least one send"); - const meshtastic_ChannelSettings &atSend = mockRouter->primaryAtSend[0]; - TEST_ASSERT_EQUAL_STRING_MESSAGE("BeaconCh", atSend.name, "primary must be the beacon channel during send"); - TEST_ASSERT_EQUAL_UINT(sizeof(beaconPsk), atSend.psk.size); - TEST_ASSERT_EQUAL_UINT8_MESSAGE(0xBB, atSend.psk.bytes[0], "encryption must use the beacon channel PSK"); - - // After send(), the primary channel must be restored to the original (no leak into normal traffic). - const meshtastic_ChannelSettings &after = channels.getByIndex(channels.getPrimaryIndex()).settings; - TEST_ASSERT_EQUAL_STRING_MESSAGE("Home", after.name, "primary channel must be restored after send"); - TEST_ASSERT_EQUAL_UINT(sizeof(homePsk), after.psk.size); - TEST_ASSERT_EQUAL_UINT8(0xAA, after.psk.bytes[0]); -} - -/** - * Without a broadcast_on_channel override, the beacon must transmit on the primary channel unchanged + * With no target channel_index, the beacon must transmit on the primary channel unchanged * (no swap). Guards against the swap firing - and churning the channel table - when it isn't needed. */ static void test_broadcaster_noChannelOverride_doesNotSwapPrimary(void) @@ -1213,7 +1156,7 @@ static void test_broadcaster_noChannelOverride_doesNotSwapPrimary(void) moduleConfig.has_mesh_beacon = true; moduleConfig.mesh_beacon.has_broadcast_offer_preset = true; moduleConfig.mesh_beacon.broadcast_offer_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW; - // No broadcast_on_channel override. + // No target channel_index. MeshBeaconBroadcastModuleTestShim bcast; bcast.sendBeacon(); @@ -1249,10 +1192,15 @@ static void test_broadcaster_targetChannelIndex_usesTableSlot(void) bcast.sendBeacon(); TEST_ASSERT_TRUE_MESSAGE(mockRouter->primaryAtSend.size() >= 1, "expected at least one send"); - TEST_ASSERT_EQUAL_STRING_MESSAGE("BeaconNet", mockRouter->primaryAtSend[0].name, - "beacon must be encrypted on the referenced slot's channel"); - // Primary slot restored to home after send (no leak). - TEST_ASSERT_EQUAL_STRING("Home", channels.getByIndex(channels.getPrimaryIndex()).settings.name); + const meshtastic_ChannelSettings &atSend = mockRouter->primaryAtSend[0]; + TEST_ASSERT_EQUAL_STRING_MESSAGE("BeaconNet", atSend.name, "beacon must be encrypted on the referenced slot's channel"); + TEST_ASSERT_EQUAL_UINT(sizeof(beaconPsk), atSend.psk.size); + TEST_ASSERT_EQUAL_UINT8_MESSAGE(0xBB, atSend.psk.bytes[0], "encryption must use the slot's PSK"); + // Primary slot restored to home after send (no leak into normal traffic). + const meshtastic_ChannelSettings &after = channels.getByIndex(channels.getPrimaryIndex()).settings; + TEST_ASSERT_EQUAL_STRING_MESSAGE("Home", after.name, "primary channel must be restored after send"); + TEST_ASSERT_EQUAL_UINT(sizeof(homePsk), after.psk.size); + TEST_ASSERT_EQUAL_UINT8(0xAA, after.psk.bytes[0]); } /** @@ -1753,7 +1701,6 @@ BEACON_TEST_ENTRY void setup() printf("\n=== Broadcaster sendBeacon ===\n"); RUN_TEST(test_broadcaster_sendBeacon_fromIsLocalNodeWhenUnset); - RUN_TEST(test_broadcaster_sendBeacon_fromIsCustomNodeWhenSet); RUN_TEST(test_broadcaster_sendBeacon_addressedToBroadcast); RUN_TEST(test_broadcaster_sendBeacon_usesBeaconPortnum); RUN_TEST(test_broadcaster_sendBeacon_fallsBackToTextMessagePortnum); @@ -1783,7 +1730,6 @@ BEACON_TEST_ENTRY void setup() printf("\n=== Beacon-channel PSK swap ===\n"); - RUN_TEST(test_broadcaster_channelPskOverride_swapsBeaconChannelAndRestores); RUN_TEST(test_broadcaster_noChannelOverride_doesNotSwapPrimary); RUN_TEST(test_broadcaster_targetChannelIndex_usesTableSlot); RUN_TEST(test_broadcaster_targetChannelIndex_blankSlotFallsBackToPreset); diff --git a/userPrefs.jsonc b/userPrefs.jsonc index 8daddd8f21..f50bbaf0f8 100644 --- a/userPrefs.jsonc +++ b/userPrefs.jsonc @@ -96,15 +96,11 @@ // "USERPREFS_MESH_BEACON_OFFER_REGION": "meshtastic_Config_LoRaConfig_RegionCode_EU_N_868", // Region advertised in the beacon payload // "USERPREFS_MESH_BEACON_OFFER_CHANNEL_NAME": "'MyChannel'", // Channel name advertised in the beacon payload // "USERPREFS_MESH_BEACON_OFFER_CHANNEL_PSK": "{ 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 }", // PSK for the offered channel (32-byte AES-256) - // "USERPREFS_MESH_BEACON_ON_PRESET": "meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST", // Modem preset to use when transmitting beacons (radio temporarily switched for TX) - // "USERPREFS_MESH_BEACON_ON_REGION": "meshtastic_Config_LoRaConfig_RegionCode_EU_868", // Region to use when transmitting beacons - // "USERPREFS_MESH_BEACON_ON_CHANNEL_NAME": "'LongFast'", // Channel name to use on the TX radio config - uses default if unset. - // "USERPREFS_MESH_BEACON_ON_CHANNEL_PSK": "{ 0x01 }", // PSK for the TX channel (0x01 = Meshtastic default PSK) - // "USERPREFS_MESH_BEACON_ON_CHANNEL_NUM": "0", // LoRa channel/frequency slot to use on the TX radio config - zero is default, 20 is standard for US LongFast, etc. // "USERPREFS_MESH_BEACON_LEGACY_SPLIT": "true", // When both text and offer are present, split into a separate MESH_BEACON_APP (offer) and TEXT_MESSAGE_APP (text) for legacy client compatibility - // Multi-target broadcast: when any TARGET__* key is set, broadcast_targets overrides the - // single-target broadcast_on_* fields above. Each target transmits its own copy of the beacon. - // Up to 4 targets (0-3). Only TARGET_0 is used here; uncomment TARGET_1 to add a second preset. + // Broadcast targets: every beacon destination is a TARGET__* entry. With none set, the node + // beacons once on its running preset and region over the primary channel. Up to 4 targets (0-3); + // only TARGET_0 is used here, uncomment TARGET_1 to add a second preset. Targets that resolve to + // the same preset, region and channel are deduplicated. // CHANNEL_INDEX references a slot in the device's channel table (0..MAX_NUM_CHANNELS-1); that // channel must already be configured on the node (its key is needed to encrypt the beacon). // "USERPREFS_MESH_BEACON_TARGET_0_PRESET": "meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST",