emdashes begone (#10847)
This commit is contained in:
+10
-10
@@ -89,7 +89,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
assert(r);
|
||||
|
||||
#ifdef MESHTASTIC_ENCRYPTED_STORAGE
|
||||
// While storage is locked, drop every admin payload — both local and
|
||||
// While storage is locked, drop every admin payload - both local and
|
||||
// remote (PKC, mesh-relayed). Lockdown unlock is the prerequisite for
|
||||
// any admin operation: operators must authenticate via lockdown_auth
|
||||
// first. The lockdown_auth path itself is handled synchronously in
|
||||
@@ -100,9 +100,9 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
// operator has even unlocked it.
|
||||
// Only gate when lockdown is ACTIVE. A lockdown-capable build that hasn't
|
||||
// been provisioned (or was disabled) is not unlocked either, but must
|
||||
// still serve admin normally — so check isLockdownActive() first.
|
||||
// still serve admin normally - so check isLockdownActive() first.
|
||||
if (EncryptedStorage::isLockdownActive() && !EncryptedStorage::isUnlocked()) {
|
||||
LOG_WARN("AdminModule: dropping admin payload — storage locked");
|
||||
LOG_WARN("AdminModule: dropping admin payload - storage locked");
|
||||
return handled;
|
||||
}
|
||||
#endif
|
||||
@@ -136,7 +136,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
// local packets into the remote-PKC key check.
|
||||
//
|
||||
// Under MESHTASTIC_PHONEAPI_ACCESS_CONTROL, the per-connection auth
|
||||
// gate lives in PhoneAPI::handleToRadioPacket — any local admin
|
||||
// gate lives in PhoneAPI::handleToRadioPacket - any local admin
|
||||
// payload other than lockdown_auth is dropped there if the
|
||||
// originating connection is unauthorized. By the time we reach
|
||||
// this branch the connection has already proven the passphrase,
|
||||
@@ -168,7 +168,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
// Note: PKC admin does NOT automatically authorize the
|
||||
// originating local PhoneAPI connection for content
|
||||
// redaction purposes. PKC and the per-connection lockdown
|
||||
// auth slot are independent gates — operators using PKC
|
||||
// auth slot are independent gates - operators using PKC
|
||||
// admin from a local app should still send lockdown_auth
|
||||
// separately to unlock the redacted FromRadio stream.
|
||||
// (The previous auto-authorize path read a shared
|
||||
@@ -217,7 +217,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
|
||||
#ifdef MESHTASTIC_ENCRYPTED_STORAGE
|
||||
// lockdown_auth is handled synchronously in
|
||||
// PhoneAPI::handleToRadioPacket — see handleLockdownAuthInline. A
|
||||
// PhoneAPI::handleToRadioPacket - see handleLockdownAuthInline. A
|
||||
// packet should not normally reach AdminModule under that flag set,
|
||||
// but if it ever does (e.g. injected via a non-PhoneAPI path), drop
|
||||
// it silently rather than leaking a partial response.
|
||||
@@ -301,7 +301,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
break;
|
||||
}
|
||||
|
||||
// Hardware supports 2.4 GHz — apply the config.
|
||||
// Hardware supports 2.4 GHz - apply the config.
|
||||
// Fail closed: null instance is treated as incapable.
|
||||
if (RadioLibInterface::instance && RadioLibInterface::instance->wideLora()) {
|
||||
LOG_DEBUG("LORA_24 requested, radio hardware supports 2.4 GHz, applying");
|
||||
@@ -493,7 +493,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
saveChanges(SEGMENT_NODEDATABASE, false);
|
||||
if (screen)
|
||||
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); // <-- Rebuild screens
|
||||
} else if (mp.from == 0) { // local request from the phone — tell the user why it didn't take
|
||||
} else if (mp.from == 0) { // local request from the phone - tell the user why it didn't take
|
||||
sendWarning(NodeDB::PROTECTED_CAP_WARN_FMT, "favorite", r->set_favorite_node, MAX_NUM_NODES - 2);
|
||||
} else {
|
||||
LOG_WARN("Remote set_favorite_node for 0x%08x refused: protected-node cap", r->set_favorite_node);
|
||||
@@ -522,7 +522,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
if (nodeDB->setProtectedFlag(node, NODEINFO_BITFIELD_IS_IGNORED_MASK, true)) {
|
||||
nodeDB->eraseNodeSatellites(node->num);
|
||||
saveChanges(SEGMENT_NODEDATABASE, false);
|
||||
} else if (mp.from == 0) { // local request from the phone — tell the user why it didn't take
|
||||
} else if (mp.from == 0) { // local request from the phone - tell the user why it didn't take
|
||||
sendWarning(NodeDB::PROTECTED_CAP_WARN_FMT, "ignore", r->set_ignored_node, MAX_NUM_NODES - 2);
|
||||
} else {
|
||||
LOG_WARN("Remote set_ignored_node for 0x%08x refused: protected-node cap", r->set_ignored_node);
|
||||
@@ -1271,7 +1271,7 @@ bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
|
||||
moduleConfig.has_mesh_beacon = true;
|
||||
moduleConfig.mesh_beacon = beaconCfg;
|
||||
shouldReboot = false;
|
||||
// Payload content changed — invalidate the broadcaster's cache.
|
||||
// Payload content changed - invalidate the broadcaster's cache.
|
||||
if (meshBeaconBroadcastModule)
|
||||
meshBeaconBroadcastModule->invalidateCache();
|
||||
break;
|
||||
|
||||
@@ -6,6 +6,6 @@ AtakPluginModule::AtakPluginModule() : SinglePortModule("atak", meshtastic_PortN
|
||||
|
||||
ProcessMessage AtakPluginModule::handleReceived(const meshtastic_MeshPacket &mp)
|
||||
{
|
||||
(void)mp; // Passthrough — no processing needed, apps handle compression/decompression
|
||||
(void)mp; // Passthrough - no processing needed, apps handle compression/decompression
|
||||
return ProcessMessage::CONTINUE;
|
||||
}
|
||||
|
||||
@@ -1369,7 +1369,7 @@ int32_t CannedMessageModule::runOnce()
|
||||
case INPUT_BROKER_RIGHT:
|
||||
break;
|
||||
default:
|
||||
// Only insert ASCII printable characters (32–126)
|
||||
// Only insert ASCII printable characters (32-126)
|
||||
if (this->payload >= 32 && this->payload <= 126) {
|
||||
requestFocus();
|
||||
if (this->cursor == this->freetext.length()) {
|
||||
@@ -2128,7 +2128,7 @@ static float getSnrLimit(meshtastic_Config_LoRaConfig_ModemPreset preset)
|
||||
}
|
||||
}
|
||||
|
||||
// Return Good/Fair/Bad label and set 1–5 bars based on SNR and RSSI
|
||||
// Return Good/Fair/Bad label and set 1-5 bars based on SNR and RSSI
|
||||
static const char *getSignalGrade(float snr, int32_t rssi, float snrLimit, int &bars)
|
||||
{
|
||||
// 5-bar logic: strength inside Good/Fair/Bad category
|
||||
|
||||
@@ -23,7 +23,7 @@ constexpr uint32_t RUN_INTERVAL_MS = 5 * 60 * 1000UL; // Emit micro-summary ever
|
||||
// Persistence
|
||||
// Note: this only needs incrementing if the published arrangement changes. For testing purposes, or prior to widespread release,
|
||||
// it can stay the same even if the internal layout changes.
|
||||
constexpr uint32_t HISTOGRAM_STATE_MAGIC = 0x48535432; // 'HST2' — layout v2
|
||||
constexpr uint32_t HISTOGRAM_STATE_MAGIC = 0x48535432; // 'HST2' - layout v2
|
||||
constexpr uint8_t HISTOGRAM_STATE_VERSION = 1;
|
||||
constexpr const char *HISTOGRAM_STATE_FILE = "/prefs/hopScalingState.bin";
|
||||
|
||||
@@ -282,7 +282,7 @@ void HopScalingModule::rollHour()
|
||||
}
|
||||
|
||||
// 2. Walk scaled hop buckets to produce a hop-limit recommendation.
|
||||
// effectiveMin: walk threshold — first hop whose cumulative count reaches this.
|
||||
// effectiveMin: walk threshold - first hop whose cumulative count reaches this.
|
||||
// effectiveMax: ceiling on the one-hop extension check with GENEROUS politeness.
|
||||
const uint16_t effectiveMin = TARGET_AFFECTED_NODES;
|
||||
const uint16_t effectiveMax = MAX_TARGET_NODES;
|
||||
@@ -351,7 +351,7 @@ void HopScalingModule::rollHour()
|
||||
// 5. Tick down the hold counter; once it reaches zero, halve filteringDenominator toward
|
||||
// samplingDenominator once per rollHour() (= once per hour) rather than a single jump:
|
||||
// avoids a sudden large change in the hop-walk count when samplingDenominator cascaded
|
||||
// down significantly during the hold period. No new hold is placed on each step — the
|
||||
// down significantly during the hold period. No new hold is placed on each step - the
|
||||
// 13-roll hold already guaranteed that re-admitted nodes have full seenHoursAgo history;
|
||||
// further pacing is provided naturally by the 1-step-per-hour rate. denominatorHistory
|
||||
// is updated automatically by the shift at the top of rollHour(), so no backfill here.
|
||||
@@ -405,7 +405,7 @@ void HopScalingModule::trimIfNeeded()
|
||||
// down yet) are left untouched: eviction at samplingDenominator retains exactly those
|
||||
// entries, so the old higher gate remains accurate for those historical hours.
|
||||
// Slots below the new value must be raised because the eviction removed entries that
|
||||
// had been admitted at the looser old gate — the remaining entries represent a 1/N
|
||||
// had been admitted at the looser old gate - the remaining entries represent a 1/N
|
||||
// subsample where N is the new filteringDenominator, not the old smaller value.
|
||||
for (uint8_t h = 0; h < 13; h++)
|
||||
denominatorHistory[h] = std::max(denominatorHistory[h], filteringDenominator);
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
*
|
||||
* Memory layout: 512 bytes total (128 entries × 4 bytes/entry, no padding)
|
||||
* - 16-bit XOR-fold hash of node ID
|
||||
* - 3-bit hops away (0–7)
|
||||
* - 3-bit hops away (0-7)
|
||||
* - 13-bit hourly seen bitmap
|
||||
* All three fields are packed into a single 32-bit Record; sizeof(Record) == 4.
|
||||
*
|
||||
* Sampling:
|
||||
* - A node is added only when passesFilter(hashNodeId(nodeId), samplingDenominator),
|
||||
* i.e. (hash16(nodeId) & (samplingDenominator – 1)) == 0 (hash-space subsample, not raw ID)
|
||||
* i.e. (hash16(nodeId) & (samplingDenominator - 1)) == 0 (hash-space subsample, not raw ID)
|
||||
* - samplingDenominator starts at 1 (sample all), doubles when the list exceeds FILL_HIGH_PCT
|
||||
* - filteringDenominator tracks samplingDenominator upward immediately but does not drop back
|
||||
* down until FILTER_DENOM_HOLD_MS (13 h) have elapsed since the last scale-up
|
||||
@@ -72,18 +72,18 @@ class HopScalingModule : private concurrency::OSThread
|
||||
// This value is deliberately equal to the seenHoursAgo window (13 hours / 13 bits).
|
||||
// Invariant: every entry that existed when a scale-up fired had seenHoursAgo != 0 at
|
||||
// that moment (trimIfNeeded() evicts stale entries before doubling the denominator),
|
||||
// so it remains seenInLast13h for at most 13 more rollHour() calls — exactly the
|
||||
// so it remains seenInLast13h for at most 13 more rollHour() calls - exactly the
|
||||
// hold duration. That means entries from the scale-up event keep counts.total above
|
||||
// the scale-down threshold for the entire hold period under normal (active) mesh
|
||||
// conditions. On a genuinely quieting mesh the scale-down CAN fire before the hold
|
||||
// expires — each firing halves samplingDenominator but filteringDenominator stays
|
||||
// expires - each firing halves samplingDenominator but filteringDenominator stays
|
||||
// elevated, so the hop recommendation correctly stays conservative (MAX_HOP) while
|
||||
// the cascade runs. The cascade is bounded at DENOM_MIN (7 halvings from DENOM_MAX);
|
||||
// when the hold finally expires, step 5 of rollHour() halves filteringDenominator
|
||||
// once per hour (rather than jumping directly to samplingDenominator) until the two
|
||||
// converge, giving the hop-walk a gradual, 1-step-per-hour descent.
|
||||
static constexpr uint32_t FILTER_DENOM_HOLD_MS = 13UL * 60UL * 60UL * 1000UL; // 13 h (documentation only)
|
||||
// Number of rollHour() calls the hold spans — equals the seenHoursAgo window width.
|
||||
// Number of rollHour() calls the hold spans - equals the seenHoursAgo window width.
|
||||
// filteringDenomHoldRollsRemaining is initialised to this value on scale-up and
|
||||
// decremented once per rollHour(); step-down begins when it reaches zero.
|
||||
static constexpr uint8_t FILTER_DENOM_HOLD_ROLLS = 13u;
|
||||
@@ -132,15 +132,15 @@ class HopScalingModule : private concurrency::OSThread
|
||||
struct MeshTrendStats {
|
||||
/// Estimated node count per hour slot (h=0 is the just-completed hour, h=12 is 12 h ago).
|
||||
uint16_t scaledPerHour[13] = {};
|
||||
/// Nodes heard only this hour with no prior bitmap history — indicates new arrivals.
|
||||
/// Nodes heard only this hour with no prior bitmap history - indicates new arrivals.
|
||||
uint16_t newThisHour = 0;
|
||||
/// Nodes heard this hour that also appeared in at least one older hour — stable regulars.
|
||||
/// Nodes heard this hour that also appeared in at least one older hour - stable regulars.
|
||||
uint16_t returningThisHour = 0;
|
||||
/// Nodes heard last hour but silent this hour — potential departures.
|
||||
/// Nodes heard last hour but silent this hour - potential departures.
|
||||
uint16_t lapsedSinceLastHour = 0;
|
||||
/// Nodes absent from the last 4 hours but still present in some older hour (5–13 h) — quieting down.
|
||||
/// Nodes absent from the last 4 hours but still present in some older hour (5-13 h) - quieting down.
|
||||
uint16_t olderThan4h = 0;
|
||||
/// Nodes whose only remaining history is the 13th hour (bit 12 only) — about to age out entirely.
|
||||
/// Nodes whose only remaining history is the 13th hour (bit 12 only) - about to age out entirely.
|
||||
uint16_t agingOut = 0;
|
||||
};
|
||||
|
||||
@@ -257,11 +257,11 @@ class HopScalingModule : private concurrency::OSThread
|
||||
//
|
||||
// Two separate denominators control two distinct gates:
|
||||
//
|
||||
// samplingDenominator — admission gate. A node is added/updated only when
|
||||
// samplingDenominator - admission gate. A node is added/updated only when
|
||||
// passesFilter(hash, samplingDenominator). Lower value = more permissive =
|
||||
// more nodes enter = represents recent mesh state.
|
||||
//
|
||||
// filteringDenominator — counting gate. The hop-walk tally in rollHour() only
|
||||
// filteringDenominator - counting gate. The hop-walk tally in rollHour() only
|
||||
// counts entries that pass passesFilter(hash, filteringDenominator). It moves
|
||||
// up with samplingDenominator immediately (scale-up) but is held at the
|
||||
// elevated value for FILTER_DENOM_HOLD_MS (13 h) after any scale-up before it
|
||||
@@ -272,20 +272,20 @@ class HopScalingModule : private concurrency::OSThread
|
||||
// that also pass F is exactly D/F. Therefore:
|
||||
// raw_count × F = (total × D/F) × F = total × D
|
||||
// The population estimate is the same whether we count with D or with F.
|
||||
// The hold period is not about accuracy — it is about stability: it prevents the
|
||||
// The hold period is not about accuracy - it is about stability: it prevents the
|
||||
// hop recommendation from reacting to recently-admitted nodes that have not yet
|
||||
// accumulated enough seenHoursAgo history to be statistically reliable.
|
||||
//
|
||||
// denominatorHistory[h] — the filteringDenominator used to both gate and scale
|
||||
// denominatorHistory[h] - the filteringDenominator used to both gate and scale
|
||||
// hourlyRaw[h]. Invariant: denominatorHistory[h] always equals the
|
||||
// filteringDenominator that was active when seenHoursAgo bit h was set.
|
||||
// rollHour() advances the array at the very start (before the tally loop), then
|
||||
// gates hourlyRaw[h] per-slot by denominatorHistory[h] — each slot's raw count
|
||||
// gates hourlyRaw[h] per-slot by denominatorHistory[h] - each slot's raw count
|
||||
// and multiplier are therefore always consistent, even when filteringDenominator
|
||||
// changes between rolls (e.g. hold expiry). On scale-up (trimIfNeeded()), the
|
||||
// entire array is backfilled uniformly with the new filteringDenominator to
|
||||
// preserve the invariant retroactively for all 13 slots. Initialised to
|
||||
// DENOM_MIN (1); scaledPerHour slots that draw from a 1 entry are unscaled —
|
||||
// DENOM_MIN (1); scaledPerHour slots that draw from a 1 entry are unscaled -
|
||||
// correct for a fresh instance with no prior history.
|
||||
// -----------------------------------------------------------------------
|
||||
uint8_t samplingDenominator = DENOM_MIN;
|
||||
@@ -321,7 +321,7 @@ class HopScalingModule : private concurrency::OSThread
|
||||
|
||||
// Record field semantics:
|
||||
// nodeHash → XOR-fold of full 32-bit node ID to 16 bits
|
||||
// hops_away → hop distance (0–7)
|
||||
// hops_away → hop distance (0-7)
|
||||
// seenHoursAgo → 13-bit per-hour seen bitmap
|
||||
// bit 0 = seen in the current / most-recent hour
|
||||
// bit 12 = seen 12 hours ago
|
||||
@@ -338,7 +338,7 @@ class HopScalingModule : private concurrency::OSThread
|
||||
static bool passesFilter(uint16_t nodeHash, uint8_t denom) { return (nodeHash & static_cast<uint16_t>(denom - 1u)) == 0u; }
|
||||
|
||||
public:
|
||||
// Clock — public so tests can share the same timebase via HopScalingModule::s_testNowMs
|
||||
// Clock - public so tests can share the same timebase via HopScalingModule::s_testNowMs
|
||||
#ifdef PIO_UNIT_TESTING
|
||||
static uint32_t nowMs() { return s_testNowMs; }
|
||||
#else
|
||||
|
||||
@@ -109,13 +109,13 @@ bool MeshBeaconModule::beaconTxConfigInvalid(const meshtastic_MeshPacket *p)
|
||||
meshtastic_Config_LoRaConfig_ModemPreset preset;
|
||||
meshtastic_Config_LoRaConfig_RegionCode sidecarRegion = meshtastic_Config_LoRaConfig_RegionCode_UNSET;
|
||||
if (!getTargetRadioSettings(p, &preset, nullptr, nullptr, &sidecarRegion))
|
||||
return false; // not a beacon-switch packet — nothing to validate, normal traffic unaffected
|
||||
return false; // not a beacon-switch packet - nothing to validate, normal traffic unaffected
|
||||
|
||||
const meshtastic_Config_LoRaConfig_RegionCode region =
|
||||
(sidecarRegion != meshtastic_Config_LoRaConfig_RegionCode_UNSET) ? sidecarRegion : config.lora.region;
|
||||
|
||||
// An unlicensed node must never key up on a ham-only (licensed-only) region. The reverse is
|
||||
// allowed: a licensed (ham) node may operate in a non-ham region — and the switch only touches
|
||||
// allowed: a licensed (ham) node may operate in a non-ham region - and the switch only touches
|
||||
// preset/region/channel, never owner.is_licensed, so it cannot deactivate licensed mode.
|
||||
const RegionInfo *r = getRegion(region);
|
||||
if (r && r->profile->licensedOnly && !owner.is_licensed)
|
||||
@@ -156,7 +156,7 @@ bool MeshBeaconModule::reconfigureForBeaconTX(RadioInterface *iface, meshtastic_
|
||||
// heuristic both missed cases (a channel name/PSK swap that left preset/slot/region unchanged would
|
||||
// never be restored) and fired falsely (a legitimate non-beacon channel edit would be reverted on
|
||||
// the next TX). With the flag the restore fires for ANY field we changed and only when we changed
|
||||
// it — including on TX-failure paths, which route through this same restore call.
|
||||
// it - including on TX-failure paths, which route through this same restore call.
|
||||
static bool radioSwitched = false;
|
||||
|
||||
meshtastic_ChannelSettings *primaryCh = &channels.getByIndex(channels.getPrimaryIndex()).settings;
|
||||
@@ -193,7 +193,7 @@ bool MeshBeaconModule::reconfigureForBeaconTX(RadioInterface *iface, meshtastic_
|
||||
targetRegion == config.lora.region && !channelDiffers(targetChannel))
|
||||
return false;
|
||||
|
||||
// Guard: never key up on an invalid target config — bad preset for the region, or an
|
||||
// Guard: never key up on an invalid target config - bad preset for the region, or an
|
||||
// unlicensed node keying up on a ham-only region. Refuse the switch here so we never
|
||||
// transmit on it; the radio driver drops the packet outright (see RadioLibInterface,
|
||||
// beaconTxConfigInvalid) rather than letting it fall through onto the current config.
|
||||
@@ -265,7 +265,7 @@ void MeshBeaconBroadcastModule::rebuildCache()
|
||||
beacon.has_offer_channel = true;
|
||||
beacon.offer_channel = bcfg.broadcast_offer_channel;
|
||||
// PSK is included intentionally: this beacon is a public join-invitation.
|
||||
// The offered channel is not secret — the PSK here is a convenience token,
|
||||
// The offered channel is not secret - the PSK here is a convenience token,
|
||||
// not a security boundary. Operators who want a private channel must
|
||||
// distribute the PSK out-of-band and leave offer_channel unset.
|
||||
}
|
||||
@@ -273,7 +273,7 @@ void MeshBeaconBroadcastModule::rebuildCache()
|
||||
beacon.offer_preset = bcfg.broadcast_offer_preset;
|
||||
beacon.offer_region = bcfg.broadcast_offer_region;
|
||||
// Note: an empty config legitimately encodes to 0 bytes, and pb_encode_to_bytes can't distinguish
|
||||
// that from a (here effectively impossible — buffer is max-sized) failure, so we always clear the
|
||||
// that from a (here effectively impossible - buffer is max-sized) failure, so we always clear the
|
||||
// dirty flag. The combined send is gated on payloadCacheSize > 0, so an empty payload is never TX'd.
|
||||
payloadCacheSize = (pb_size_t)pb_encode_to_bytes(payloadCache, sizeof(payloadCache), &meshtastic_MeshBeacon_msg, &beacon);
|
||||
payloadCacheDirty = false;
|
||||
@@ -291,7 +291,7 @@ void MeshBeaconBroadcastModule::sendBeaconPacket(meshtastic_MeshPacket *p, mesht
|
||||
}
|
||||
|
||||
// perhapsEncode() keys encryption (and the channel-hash hint) off the PRIMARY channel slot, and
|
||||
// the radio-thread channel switch only happens AFTER encryption — so a beacon on an override
|
||||
// the radio-thread channel switch only happens AFTER encryption - so a beacon on an override
|
||||
// channel would otherwise be encrypted with the PRIMARY PSK, not the beacon channel's. Install the
|
||||
// beacon channel into the primary slot for the synchronous duration of send(), then restore.
|
||||
// Meshtastic threading is cooperative (no preemption between the swap and restore).
|
||||
@@ -326,13 +326,13 @@ void MeshBeaconBroadcastModule::sendBeacon()
|
||||
// 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
|
||||
// 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
|
||||
// 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;
|
||||
@@ -347,8 +347,8 @@ void MeshBeaconBroadcastModule::sendBeacon()
|
||||
|
||||
// ── Packet type decisions ────────────────────────────────────────────────
|
||||
//
|
||||
// FLAG_LEGACY_SPLIT: when both text and offer are present, send TWO packets — A)
|
||||
// MESH_BEACON_APP (offer only) and B) TEXT_MESSAGE_APP (text only) — both on the SAME beacon
|
||||
// FLAG_LEGACY_SPLIT: when both text and offer are present, send TWO packets - A)
|
||||
// MESH_BEACON_APP (offer only) and B) TEXT_MESSAGE_APP (text only) - both on the SAME beacon
|
||||
// radio settings, so nodes that only decode TEXT_MESSAGE_APP still receive the text. Otherwise a
|
||||
// single packet is sent (offer-only, text-only, or the combined offer+text path).
|
||||
//
|
||||
@@ -361,7 +361,7 @@ void MeshBeaconBroadcastModule::sendBeacon()
|
||||
const bool sendTextOnly = splitBoth || (!hasRadioContent && hasText);
|
||||
const bool sendCombined = !legacySplit && hasRadioContent && hasText;
|
||||
|
||||
// Build offer payload once — shared across all targets.
|
||||
// Build offer payload once - shared across all targets.
|
||||
uint8_t offerBuf[meshtastic_MeshBeacon_size] = {};
|
||||
pb_size_t offerSize = 0;
|
||||
if (sendOfferOnly) {
|
||||
@@ -399,7 +399,7 @@ void MeshBeaconBroadcastModule::sendBeacon()
|
||||
|
||||
// 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
|
||||
// the same packet — wasted airtime and a redundant radio switch each. We skip the later one.
|
||||
// the same packet - wasted airtime and a redundant radio switch each. We skip the later one.
|
||||
// Keyed on the *resolved* values so an explicit "current region" dedups against an UNSET one.
|
||||
EffTarget sent[4];
|
||||
meshtastic_Config_LoRaConfig_RegionCode sentRegion[4];
|
||||
@@ -422,7 +422,7 @@ void MeshBeaconBroadcastModule::sendBeacon()
|
||||
tgt.preset = bt.has_preset ? bt.preset : config.lora.modem_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
|
||||
// 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;
|
||||
@@ -585,14 +585,14 @@ bool MeshBeaconListenerModule::handleReceivedProtobuf(const meshtastic_MeshPacke
|
||||
|
||||
// NOTE: we deliberately do NOT unwrap the text into a synthesized TEXT_MESSAGE_APP for the
|
||||
// phone. The original MESH_BEACON_APP packet already flows to the client (we return CONTINUE),
|
||||
// so a beacon-aware client renders `message` directly — injecting a copy would only duplicate
|
||||
// so a beacon-aware client renders `message` directly - injecting a copy would only duplicate
|
||||
// it. Broadcasters that need non-beacon-aware clients to see the text use FLAG_LEGACY_SPLIT,
|
||||
// which sends a real TEXT_MESSAGE_APP over RF. We also do not fire EVENT_RECEIVED_MSG: a beacon
|
||||
// is an advisory broadcast, not a personal message, and must not wake the device from sleep.
|
||||
if (hasText)
|
||||
LOG_INFO("Beacon: received from 0x%08x: '%.40s'", mp.from, b->message);
|
||||
|
||||
// Cache any offer for the client app — never auto-applied.
|
||||
// Cache any offer for the client app - never auto-applied.
|
||||
if (hasOfferContent) {
|
||||
lastReceivedOffer.valid = true;
|
||||
lastReceivedOffer.sender = mp.from;
|
||||
@@ -602,7 +602,7 @@ bool MeshBeaconListenerModule::handleReceivedProtobuf(const meshtastic_MeshPacke
|
||||
lastReceivedOffer.region = b->offer_region;
|
||||
lastReceivedOffer.preset = b->offer_preset;
|
||||
lastReceivedOffer.received_at =
|
||||
getValidTime(RTCQualityFromNet); // 0 if no RTC fix yet — consumers must not treat 0 as valid
|
||||
getValidTime(RTCQualityFromNet); // 0 if no RTC fix yet - consumers must not treat 0 as valid
|
||||
LOG_INFO("Beacon: stored offer from 0x%08x (preset=%d)", mp.from, b->offer_preset);
|
||||
}
|
||||
|
||||
|
||||
@@ -133,9 +133,9 @@ extern MeshBeaconBroadcastModule *meshBeaconBroadcastModule;
|
||||
|
||||
/**
|
||||
* Listener: receives MESH_BEACON_APP packets and caches any offered channel/preset for the client
|
||||
* app to retrieve. It does NOT unwrap the text into a separate message — the original beacon packet
|
||||
* app to retrieve. It does NOT unwrap the text into a separate message - the original beacon packet
|
||||
* already reaches the client (handler returns CONTINUE), which reads `message` from it directly.
|
||||
* Does NOT auto-apply offered settings — client app must do so explicitly.
|
||||
* Does NOT auto-apply offered settings - client app must do so explicitly.
|
||||
* Active only when the FLAG_LISTEN_ENABLED bit is set in moduleConfig.mesh_beacon.flags.
|
||||
*/
|
||||
class MeshBeaconListenerModule : public ProtobufModule<meshtastic_MeshBeacon>, public Observable<const meshtastic_MeshPacket *>
|
||||
@@ -153,7 +153,7 @@ class MeshBeaconListenerModule : public ProtobufModule<meshtastic_MeshBeacon>, p
|
||||
uint32_t received_at;
|
||||
};
|
||||
|
||||
// Last received offer — accessible to admin/API for client app retrieval.
|
||||
// Last received offer - accessible to admin/API for client app retrieval.
|
||||
static BeaconOffer lastReceivedOffer;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -339,7 +339,7 @@ meshtastic_MeshPacket *PositionModule::allocAtakPli()
|
||||
strncpy(takPacket.device_callsign, owner.long_name, sizeof(takPacket.device_callsign) - 1);
|
||||
takPacket.device_callsign[sizeof(takPacket.device_callsign) - 1] = '\0';
|
||||
|
||||
// CoT uid — ATAK drops PLI entities with empty uid; derive stable "!<nodenum>" id.
|
||||
// CoT uid - ATAK drops PLI entities with empty uid; derive stable "!<nodenum>" id.
|
||||
snprintf(takPacket.uid, sizeof(takPacket.uid), "!%08x", nodeDB->getNodeNum());
|
||||
|
||||
// Encode TAKPacketV2 protobuf, leaving room for flags byte prefix
|
||||
@@ -577,7 +577,7 @@ void PositionModule::sendLostAndFoundText()
|
||||
p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
|
||||
p->want_ack = false;
|
||||
if (written < 0) {
|
||||
// snprintf encoding error — send an empty payload rather than uninitialized bytes.
|
||||
// snprintf encoding error - send an empty payload rather than uninitialized bytes.
|
||||
p->decoded.payload.size = 0;
|
||||
} else {
|
||||
// Clamp to buffer capacity (snprintf returns "would-have-written" which can exceed the buffer).
|
||||
@@ -598,7 +598,7 @@ static inline void computeImpreciseLatLon(int32_t inLat, int32_t inLon, uint8_t
|
||||
// Build mask for top 'precisionBits' bits of a 32-bit unsigned field
|
||||
const uint32_t mask = (precisionBits == 32) ? UINT32_MAX : (UINT32_MAX << (32 - precisionBits));
|
||||
// Note: latitude_i/longitude_i are stored as signed 32-bit in meshtastic code but
|
||||
// the bitmask logic used previously operated as unsigned—preserve that behavior by
|
||||
// the bitmask logic used previously operated as unsigned-preserve that behavior by
|
||||
// casting to uint32_t for masking, then back to int32_t.
|
||||
uint32_t lat_u = static_cast<uint32_t>(inLat) & mask;
|
||||
uint32_t lon_u = static_cast<uint32_t>(inLon) & mask;
|
||||
|
||||
@@ -69,7 +69,7 @@ class PositionModule : public ProtobufModule<meshtastic_Position>, private concu
|
||||
// precision grid cell, so re-sending would be a duplicate that traffic management dedups
|
||||
// downstream anyway. Used to hold stationary broadcasts to a 12h floor. useConfiguredPrecision
|
||||
// gauges movement at our own configured (unclamped) precision rather than the on-wire
|
||||
// (public-clamped) precision — trackers report finer movement.
|
||||
// (public-clamped) precision - trackers report finer movement.
|
||||
bool positionUnchangedSinceLastSend(const meshtastic_PositionLite &selfPos, bool useConfiguredPrecision);
|
||||
meshtastic_MeshPacket *allocAtakPli();
|
||||
void trySetRtc(meshtastic_Position p, bool isLocal, bool forceUpdate = false);
|
||||
|
||||
@@ -59,14 +59,14 @@ static bool replybotRateLimited(uint32_t from, uint32_t cooldownMs)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// No entry found – insert new sender into the ring
|
||||
// No entry found - insert new sender into the ring
|
||||
replybotCooldown[replybotCooldownIdx].from = from;
|
||||
replybotCooldown[replybotCooldownIdx].lastMs = now;
|
||||
replybotCooldownIdx = (replybotCooldownIdx + 1) % REPLYBOT_COOLDOWN_SLOTS;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Constructor – registers a single text port and marks the module promiscuous
|
||||
// Constructor - registers a single text port and marks the module promiscuous
|
||||
// so that broadcast messages on the primary channel are visible.
|
||||
ReplyBotModule::ReplyBotModule() : SinglePortModule("replybot", meshtastic_PortNum_TEXT_MESSAGE_APP)
|
||||
{
|
||||
@@ -122,7 +122,7 @@ ProcessMessage ReplyBotModule::handleReceived(const meshtastic_MeshPacket &mp)
|
||||
return ProcessMessage::CONTINUE;
|
||||
}
|
||||
|
||||
// Compute hop count indicator – if the relay_node is non‑zero we know
|
||||
// Compute hop count indicator - if the relay_node is non‑zero we know
|
||||
// there was at least one relay. Some firmware builds support a hop_start
|
||||
// field which could be used for more accurate counts, but here we use
|
||||
// the available relay_node flag only.
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// NEOPIXEL_STATUS_POWER_PIN (required to enable the power/charge pixel)
|
||||
// NEOPIXEL_STATUS_POWER_COLOR (optional, default red 0xFF0000)
|
||||
// NEOPIXEL_STATUS_PAIRING_PIN / _COLOR (default blue 0x0000FF)
|
||||
// Each pixel is a standalone 1-LED strand on its own GPIO — this mirrors how
|
||||
// Each pixel is a standalone 1-LED strand on its own GPIO - this mirrors how
|
||||
// boards like the LilyGo T-Echo-Card expose three independent WS2812s.
|
||||
#if defined(NEOPIXEL_STATUS_POWER_PIN) || defined(NEOPIXEL_STATUS_PAIRING_PIN)
|
||||
#include <Adafruit_NeoPixel.h>
|
||||
|
||||
@@ -436,7 +436,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt
|
||||
bool isCooldownOver = (now - lastAlertTime > 60000);
|
||||
|
||||
if (isOwnTelemetry && bannerMsg && isCooldownOver) {
|
||||
LOG_INFO("drawFrame: IAQ %d (own) — showing banner: %s", m.iaq, bannerMsg);
|
||||
LOG_INFO("drawFrame: IAQ %d (own) - showing banner: %s", m.iaq, bannerMsg);
|
||||
screen->showSimpleBanner(bannerMsg, 3000);
|
||||
|
||||
// Only buzz if IAQ is over 200
|
||||
|
||||
@@ -331,7 +331,7 @@ void TraceRouteModule::maybeSetNextHop(NodeNum target, uint8_t nextHopByte)
|
||||
#if HAS_TRAFFIC_MANAGEMENT
|
||||
// Mirror into the TMM overflow cache. Traceroute is the highest-confidence
|
||||
// source (full known route), and this captures the target even when it isn't
|
||||
// in the hot NodeDB — same rationale as the ACK-confirmed path in NextHopRouter.
|
||||
// in the hot NodeDB - same rationale as the ACK-confirmed path in NextHopRouter.
|
||||
if (trafficManagementModule)
|
||||
trafficManagementModule->setNextHop(target, nextHopByte);
|
||||
#endif
|
||||
|
||||
@@ -227,7 +227,7 @@ void TrafficManagementModule::resetStats()
|
||||
|
||||
void TrafficManagementModule::recordRouterHopPreserved()
|
||||
{
|
||||
// router_preserve_hops: not suitable right now — removed from config until
|
||||
// router_preserve_hops: not suitable right now - removed from config until
|
||||
// the right heuristic for when to preserve vs. exhaust is clearer.
|
||||
(void)stats.router_hops_preserved;
|
||||
}
|
||||
@@ -280,7 +280,7 @@ int TrafficManagementModule::peekCachedRole(NodeNum node)
|
||||
* One linear pass tracks the match, the first empty slot, and the eviction
|
||||
* victim. When the cache is full, the victim is the stalest entry (largest
|
||||
* of its three relative timestamps is smallest), preferring entries without
|
||||
* a next_hop hint — those hints are the long-tail routing state the cache
|
||||
* a next_hop hint - those hints are the long-tail routing state the cache
|
||||
* exists to keep, and the maintenance sweep never ages them out.
|
||||
*
|
||||
* @param node NodeNum to find or create
|
||||
@@ -288,7 +288,7 @@ int TrafficManagementModule::peekCachedRole(NodeNum node)
|
||||
* @return Pointer to entry, or nullptr if the cache is unavailable
|
||||
*/
|
||||
// Sender-role resolution for the position hot path. The tier-3 cache is authoritative
|
||||
// here and is kept fresh by updateCachedRoleFromNodeInfo() — i.e. updated at the same
|
||||
// here and is kept fresh by updateCachedRoleFromNodeInfo() - i.e. updated at the same
|
||||
// time NodeDB learns a role, not re-derived on every packet. We only fall back to a
|
||||
// NodeDB scan (tiers 1+2) the first time we start tracking a node, to seed the cache so
|
||||
// a resident special-role node is correct from its very first position. Thereafter the
|
||||
@@ -308,8 +308,8 @@ meshtastic_Config_DeviceConfig_Role TrafficManagementModule::resolveSenderRole(N
|
||||
return static_cast<meshtastic_Config_DeviceConfig_Role>(entry->getCachedRole());
|
||||
}
|
||||
|
||||
// Refresh the tier-3 role cache from an observed NodeInfo — the same event that updates
|
||||
// NodeDB's role — so role changes (including demotion back to CLIENT) are picked up
|
||||
// Refresh the tier-3 role cache from an observed NodeInfo - the same event that updates
|
||||
// NodeDB's role - so role changes (including demotion back to CLIENT) are picked up
|
||||
// without scanning NodeDB on the position hot path. Role is read straight from the
|
||||
// packet's User payload (authoritative regardless of module ordering). Only updates nodes
|
||||
// we already track (findEntry, no create) so NodeInfo from non-position nodes can't pollute
|
||||
@@ -537,7 +537,7 @@ void TrafficManagementModule::cacheNodeInfoPacket(const meshtastic_MeshPacket &m
|
||||
//
|
||||
// A routing hint store. The byte is the last byte of the NodeNum to use as next
|
||||
// hop to reach `dest`. It is written ONLY from NextHopRouter's ACK-confirmed
|
||||
// decision (a bidirectionally-verified relay) — never inferred one-way from
|
||||
// decision (a bidirectionally-verified relay) - never inferred one-way from
|
||||
// relayed traffic. The TMM cache holds confirmed next-hops that have aged out of
|
||||
// the hot NodeDB (NodeInfoLite), and NextHopRouter::getNextHop() consults it as a
|
||||
// fallback after the hot store.
|
||||
@@ -593,7 +593,7 @@ bool TrafficManagementModule::preloadNextHopsFromNodeDB()
|
||||
{
|
||||
#if TRAFFIC_MANAGEMENT_CACHE_SIZE > 0
|
||||
if (!cache || !nodeDB)
|
||||
return false; // prerequisites not ready yet — caller should retry on a later pass
|
||||
return false; // prerequisites not ready yet - caller should retry on a later pass
|
||||
|
||||
uint16_t seeded = 0;
|
||||
concurrency::LockGuard guard(&cacheLock);
|
||||
@@ -675,7 +675,7 @@ uint8_t TrafficManagementModule::computePositionFingerprint(int32_t lat_truncate
|
||||
// 0 is the "no position seen" sentinel for pos_fingerprint, so a real position that happens to
|
||||
// hash to 0 must not collide with it (otherwise its duplicates would never dedup). Remap 0 -> 0xFF,
|
||||
// mirroring NodeDB::getLastByteOfNodeNum()'s 0 -> 0xFF idiom. Cost: the 0x00 bucket merges into
|
||||
// 0xFF (one extra collision in 256 — negligible; the fingerprint already collides every 16 cells).
|
||||
// 0xFF (one extra collision in 256 - negligible; the fingerprint already collides every 16 cells).
|
||||
return fp ? fp : 0xFF;
|
||||
}
|
||||
|
||||
@@ -715,7 +715,7 @@ ProcessMessage TrafficManagementModule::handleReceived(const meshtastic_MeshPack
|
||||
logAction("drop", &mp, "unknown");
|
||||
incrementStat(&stats.unknown_packet_drops);
|
||||
ignoreRequest = true; // Suppress NAK for want_response packets
|
||||
return ProcessMessage::STOP; // Consumed — will not be rebroadcast
|
||||
return ProcessMessage::STOP; // Consumed - will not be rebroadcast
|
||||
}
|
||||
}
|
||||
return ProcessMessage::CONTINUE;
|
||||
@@ -746,7 +746,7 @@ ProcessMessage TrafficManagementModule::handleReceived(const meshtastic_MeshPack
|
||||
logAction("respond", &mp, "nodeinfo-cache");
|
||||
incrementStat(&stats.nodeinfo_cache_hits);
|
||||
ignoreRequest = true; // We responded; suppress default NAK
|
||||
return ProcessMessage::STOP; // Consumed — request will not be forwarded
|
||||
return ProcessMessage::STOP; // Consumed - request will not be forwarded
|
||||
}
|
||||
}
|
||||
|
||||
@@ -765,7 +765,7 @@ ProcessMessage TrafficManagementModule::handleReceived(const meshtastic_MeshPack
|
||||
logAction("drop", &mp, "position-dedup");
|
||||
incrementStat(&stats.position_dedup_drops);
|
||||
ignoreRequest = true; // Suppress NAK
|
||||
return ProcessMessage::STOP; // Consumed — duplicate will not be rebroadcast
|
||||
return ProcessMessage::STOP; // Consumed - duplicate will not be rebroadcast
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -782,7 +782,7 @@ ProcessMessage TrafficManagementModule::handleReceived(const meshtastic_MeshPack
|
||||
logAction("drop", &mp, "rate-limit");
|
||||
incrementStat(&stats.rate_limit_drops);
|
||||
ignoreRequest = true; // Suppress NAK
|
||||
return ProcessMessage::STOP; // Consumed — throttled packet will not be rebroadcast
|
||||
return ProcessMessage::STOP; // Consumed - throttled packet will not be rebroadcast
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -803,7 +803,7 @@ void TrafficManagementModule::alterReceived(meshtastic_MeshPacket &mp)
|
||||
return;
|
||||
|
||||
// exhaust_hop_telemetry / exhaust_hop_position / router_preserve_hops:
|
||||
// not suitable right now — the right heuristics for when to exhaust or
|
||||
// not suitable right now - the right heuristics for when to exhaust or
|
||||
// preserve hops need more field data before we expose them as config knobs.
|
||||
// exhaustRequested stays false; perhapsRebroadcast() behaves normally.
|
||||
|
||||
@@ -816,8 +816,8 @@ void TrafficManagementModule::alterReceived(meshtastic_MeshPacket &mp)
|
||||
// ceiling. Guards against forwarding more-precise coordinates than the
|
||||
// channel is intended to carry (e.g. a LongFast channel set to 13-bit /
|
||||
// ~1.5 km). chanPrec==0 means position sharing is disabled on the channel;
|
||||
// skip — not our job to zero positions on relay.
|
||||
// Ham mode (owner.is_licensed) is exempt. Lost-and-found is NOT exempt — its relayed
|
||||
// skip - not our job to zero positions on relay.
|
||||
// Ham mode (owner.is_licensed) is exempt. Lost-and-found is NOT exempt - its relayed
|
||||
// positions get the same precision clamp as any node.
|
||||
// Compile USERPREFS_TMM_APPLY_TO_PRIVATE_CHANNELS to extend to private channels.
|
||||
if (!owner.is_licensed && isPosition && isBroadcast(mp.to)) {
|
||||
@@ -878,7 +878,7 @@ int32_t TrafficManagementModule::runOnce()
|
||||
const uint8_t rateTtlTicks = static_cast<uint8_t>(
|
||||
std::min(static_cast<uint32_t>(15), (rateWindowMs > 0 ? rateWindowMs * 2 : 24 * kRateTimeTickMs) / kRateTimeTickMs));
|
||||
|
||||
// unknown: fixed 12-tick TTL (12 min — 4 ticks past the 5-min default window)
|
||||
// unknown: fixed 12-tick TTL (12 min - 4 ticks past the 5-min default window)
|
||||
const uint8_t unknownTtlTicks = 12;
|
||||
|
||||
const uint8_t nowPosTick = currentPosTick();
|
||||
@@ -978,7 +978,7 @@ bool TrafficManagementModule::shouldDropPosition(const meshtastic_MeshPacket *p,
|
||||
if (!pos->has_latitude_i || !pos->has_longitude_i)
|
||||
return false;
|
||||
|
||||
// Precision is driven by the channel's own position_precision ceiling — the same
|
||||
// Precision is driven by the channel's own position_precision ceiling - the same
|
||||
// grid the channel uses for broadcast. Falls back to the firmware default (19-bit,
|
||||
// ~90m cells) when the channel has no precision configured (chanPrec == 0).
|
||||
const uint32_t chanPrec = getPositionPrecisionForChannel(p->channel);
|
||||
@@ -990,7 +990,7 @@ bool TrafficManagementModule::shouldDropPosition(const meshtastic_MeshPacket *p,
|
||||
const uint8_t fingerprint = computePositionFingerprint(lat_truncated, lon_truncated, precision);
|
||||
// Drop gate uses the RAW configured interval: 0 means "dedup disabled" (the
|
||||
// contract documented below). The 12h default is only for resolution/TTL
|
||||
// sizing (constructor / runOnce), not for deciding whether to drop — feeding
|
||||
// sizing (constructor / runOnce), not for deciding whether to drop - feeding
|
||||
// the default here would silently turn the 0-disables-dedup contract off.
|
||||
uint32_t minIntervalMs = secsToMs(moduleConfig.traffic_management.position_min_interval_secs);
|
||||
|
||||
@@ -1003,12 +1003,12 @@ bool TrafficManagementModule::shouldDropPosition(const meshtastic_MeshPacket *p,
|
||||
// Role exceptions keyed on the originating node's advertised role, resolved across
|
||||
// all three tiers (hot store → warm store → TMM live cache). The position path is
|
||||
// the one place that needs sender-role, and it also keeps tier 3 warm so the
|
||||
// exception survives the node aging out of both NodeDB stores — important in the
|
||||
// exception survives the node aging out of both NodeDB stores - important in the
|
||||
// common dedup-only config, where isRateLimited()'s role write never runs.
|
||||
const meshtastic_Config_DeviceConfig_Role role = resolveSenderRole(p->from, entry, isNew);
|
||||
if (role == meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND) {
|
||||
// Lost-and-found may refresh a duplicate position at most every ~15 min (cap, never
|
||||
// lengthens; quantised to ~2 dedup ticks). Only when dedup is active — never tighten
|
||||
// lengthens; quantised to ~2 dedup ticks). Only when dedup is active - never tighten
|
||||
// past an operator who disabled it (0).
|
||||
const uint32_t lostFoundCapMs = secsToMs(default_traffic_mgmt_lost_and_found_position_min_interval_secs);
|
||||
if (minIntervalMs != 0 && minIntervalMs > lostFoundCapMs)
|
||||
@@ -1253,7 +1253,7 @@ bool TrafficManagementModule::shouldDropUnknown(const meshtastic_MeshPacket *p,
|
||||
}
|
||||
|
||||
// Increment counter, saturating at 63 (6-bit field max). With threshold
|
||||
// capped at 60, a saturated reading always exceeds the limit — no special
|
||||
// capped at 60, a saturated reading always exceeds the limit - no special
|
||||
// already-saturated edge case needed.
|
||||
const uint8_t cur = entry->getUnknownCount();
|
||||
if (cur < 0x3F)
|
||||
|
||||
@@ -33,7 +33,7 @@ class TrafficManagementModule : public MeshModule, private concurrency::OSThread
|
||||
TrafficManagementModule();
|
||||
~TrafficManagementModule();
|
||||
|
||||
// Singleton — no copying or moving
|
||||
// Singleton - no copying or moving
|
||||
TrafficManagementModule(const TrafficManagementModule &) = delete;
|
||||
TrafficManagementModule &operator=(const TrafficManagementModule &) = delete;
|
||||
|
||||
@@ -116,10 +116,10 @@ class TrafficManagementModule : public MeshModule, private concurrency::OSThread
|
||||
// unknown active: getUnknownCount() != 0 (low 6 bits only)
|
||||
//
|
||||
// next_hop: routing hint written only from ACK-confirmed NextHopRouter decisions.
|
||||
// No TTL — keeps the slot alive across maintenance sweeps.
|
||||
// No TTL - keeps the slot alive across maintenance sweeps.
|
||||
//
|
||||
#if _meshtastic_Config_DeviceConfig_Role_MAX > 15
|
||||
#warning "Device role enum max exceeds 15 — TMM 4-bit role cache (rate_count[7:6]/unknown_count[7:6]) will truncate new values"
|
||||
#warning "Device role enum max exceeds 15 - TMM 4-bit role cache (rate_count[7:6]/unknown_count[7:6]) will truncate new values"
|
||||
#endif
|
||||
struct __attribute__((packed)) UnifiedCacheEntry {
|
||||
NodeNum node;
|
||||
@@ -152,7 +152,7 @@ class TrafficManagementModule : public MeshModule, private concurrency::OSThread
|
||||
// =========================================================================
|
||||
//
|
||||
// Plain array, linear scan (same idiom as WarmNodeStore). A lookup walks at
|
||||
// most cacheSize() × 10 B — microseconds at LoRa packet rates, not worth a
|
||||
// most cacheSize() × 10 B - microseconds at LoRa packet rates, not worth a
|
||||
// hash table. Insertion on a full cache evicts the stalest entry,
|
||||
// preferring entries without a next_hop hint (those are the long-tail
|
||||
// routing state this cache exists to keep).
|
||||
@@ -270,7 +270,7 @@ class TrafficManagementModule : public MeshModule, private concurrency::OSThread
|
||||
|
||||
// Resolve a sender's advertised device role for the position hot path. The tier-3
|
||||
// cache (this entry's getCachedRole) is authoritative and is kept fresh by
|
||||
// updateCachedRoleFromNodeInfo() — updated when NodeDB learns a role, not re-derived
|
||||
// updateCachedRoleFromNodeInfo() - updated when NodeDB learns a role, not re-derived
|
||||
// per packet. Only on first tracking (isNew) do we scan NodeDB (hot store → warm
|
||||
// store, via getNodeRole) to seed the cache, so a resident special-role node is
|
||||
// correct from its first position; after that the read is O(1) and survives the node
|
||||
|
||||
Reference in New Issue
Block a user