emdashes begone (#10847)

This commit is contained in:
Tom
2026-07-01 19:01:27 -05:00
committed by GitHub
co-authored by GitHub
parent dee94e0758
commit 3becaf2d95
276 changed files with 1795 additions and 1793 deletions
+2 -2
View File
@@ -443,7 +443,7 @@ bool Channels::usesPublicKey(ChannelIndex chIndex)
bool Channels::isWellKnownChannel(ChannelIndex chIndex)
{
const auto &ch = getByIndex(chIndex);
// Absent (unencrypted) or single-byte PSK all the well-known key indexes
// Absent (unencrypted) or single-byte PSK - all the well-known key indexes
if (ch.settings.psk.size > 1)
return false;
@@ -451,7 +451,7 @@ bool Channels::isWellKnownChannel(ChannelIndex chIndex)
for (int p = _meshtastic_Config_LoRaConfig_ModemPreset_MIN; p <= _meshtastic_Config_LoRaConfig_ModemPreset_MAX; p++) {
const char *presetName =
DisplayFormatters::getModemPresetDisplayName(static_cast<meshtastic_Config_LoRaConfig_ModemPreset>(p), false, true);
// Presets without a display name fall through to "Invalid" never a match
// Presets without a display name fall through to "Invalid" - never a match
if (strcmp(presetName, "Invalid") != 0 && strcmp(name, presetName) == 0)
return true;
}
+1 -1
View File
@@ -71,7 +71,7 @@ uint32_t Default::getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t d
int8_t throttle =
(type == TrafficType::POSITION) ? myRegion->profile->positionThrottle : myRegion->profile->telemetryThrottle;
// throttle <= 0 means unset; 1 is the neutral multiplier skip the multiply for performance
// throttle <= 0 means unset; 1 is the neutral multiplier - skip the multiply for performance
if (throttle <= 1)
return baseMs;
+1 -1
View File
@@ -129,7 +129,7 @@ bool fill(uint8_t *buffer, size_t length, bool useRadioEntropy)
filled = true;
}
#elif defined(__EMSCRIPTEN__)
// Browser/wasm: no getrandom/arc4random fall through to std::random_device,
// Browser/wasm: no getrandom/arc4random - fall through to std::random_device,
// which emscripten backs with crypto.getRandomValues().
#else
// arc4random_buf is available on Darwin/BSD and cannot fail.
+2 -2
View File
@@ -205,7 +205,7 @@ template <typename T> bool LR11x0Interface<T>::reconfigure()
err = lora.setOutputPower(power);
assert(err == RADIOLIB_ERR_NONE);
// Apply RX gain mode valid in STDBY, matches resetAGC() pattern
// Apply RX gain mode - valid in STDBY, matches resetAGC() pattern
err = lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain);
if (err != RADIOLIB_ERR_NONE)
LOG_WARN("LR11x0 setRxBoostedGainMode %s%d", radioLibErr, err);
@@ -326,7 +326,7 @@ template <typename T> void LR11x0Interface<T>::resetAGC()
LOG_DEBUG("LR11x0 AGC reset: warm sleep + Calibrate(0x3F)");
// 1. Warm sleep powers down the analog frontend, resetting AGC state
// 1. Warm sleep - powers down the analog frontend, resetting AGC state
lora.sleep(true, 0);
// 2. Wake to RC standby for stable calibration
+2 -2
View File
@@ -211,7 +211,7 @@ template <typename T> bool LR20x0Interface<T>::reconfigure()
err = lora.setOutputPower(power);
assert(err == RADIOLIB_ERR_NONE);
// Apply RX gain mode valid in STDBY, matches resetAGC() pattern
// Apply RX gain mode - valid in STDBY, matches resetAGC() pattern
err = lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain);
if (err != RADIOLIB_ERR_NONE)
LOG_WARN("LR20x0 setRxBoostedGainMode %s%d", radioLibErr, err);
@@ -332,7 +332,7 @@ template <typename T> void LR20x0Interface<T>::resetAGC()
LOG_DEBUG("LR20x0 AGC reset: warm sleep + Calibrate(0x3F)");
// 1. Warm sleep powers down the analog frontend, resetting AGC state
// 1. Warm sleep - powers down the analog frontend, resetting AGC state
lora.sleep(true, 0);
// 2. Wake to RC standby for stable calibration
+5 -5
View File
@@ -100,7 +100,7 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
meshtastic_NodeInfoLite *origTx = nodeDB->getMeshNode(p->from);
// Either relayer of ACK was also a relayer of the packet, or we were the *only* relayer and the ACK came
// directly from the destination. checkRelayers is read-only on PacketHistory and O(1), so we run it even
// when origTx is absent that lets us still capture the confirmed hop into the TMM overflow cache below.
// when origTx is absent - that lets us still capture the confirmed hop into the TMM overflow cache below.
// Single lookup for both relayer checks on the same (request_id, to) pair
bool wasAlreadyRelayer = false;
bool weWereSoleRelayer = false;
@@ -110,7 +110,7 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
if ((weWereRelayer && wasAlreadyRelayer) || (getHopsAway(*p) == 0 && weWereSoleRelayer)) {
// M1/M2: only learn a next hop whose last byte maps to a single plausible relay. On a dense
// mesh the byte may be ambiguous; storing it would aim future DMs at the wrong node. This gate
// now protects BOTH the hot-store route (NodeInfoLite.next_hop) AND the TMM overflow cache
// now protects BOTH the hot-store route (NodeInfoLite.next_hop) AND the TMM overflow cache -
// the overflow cache deliberately holds many more next-hop bytes (long-tail nodes), so it is
// even more collision-prone and must never store an ambiguous byte either. Ambiguous/unknown
// -> store nothing and keep flooding (safe).
@@ -162,7 +162,7 @@ bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
if (p->id != 0) {
if (isRebroadcaster()) {
// NOTE: this is a self-identity match (is the addressed next_hop OUR last byte?), so it
// cannot be hardened with resolveLastByte() a remote node that legitimately shares our
// cannot be hardened with resolveLastByte() - a remote node that legitimately shares our
// last byte will also match here and rebroadcast. That residual collision needs a wider
// on-wire field to fix. M1/M2 instead shrink the blast radius by reducing how often an
// ambiguous next_hop byte is ever learned (sniffReceived) or originated (getNextHop).
@@ -382,7 +382,7 @@ int32_t NextHopRouter::doRetransmissions()
if (!isBroadcast(p.packet->to)) {
if (p.numRetransmissions == 1) {
// Last retransmission: this directed delivery went un-ACKed. Record the failure
// (M3 accumulates across DMs to age out a flapping/dead route) and reset
// (M3 - accumulates across DMs to age out a flapping/dead route) and reset
// next_hop so the final try falls back to FloodingRouter.
noteRouteFailure(p.packet->to);
p.packet->next_hop = NO_NEXT_HOP_PREFERENCE;
@@ -401,7 +401,7 @@ int32_t NextHopRouter::doRetransmissions()
} else {
#if NEXTHOP_EARLY_FLOOD_ON_UNVERIFIED
// M4 (gated): if the route isn't proven healthy, don't spend a second directed
// attempt start flooding one retry sooner to cut recovery latency. A verified
// attempt - start flooding one retry sooner to cut recovery latency. A verified
// route (fresh, zero recent failures) keeps the unchanged directed-retry path so
// the sparse-mesh happy path is untouched.
RouteHealth *h = findRouteHealth(p.packet->to);
+25 -25
View File
@@ -723,7 +723,7 @@ template <typename Map> std::vector<NodeNum> snapshotSatelliteNodeNums(const Map
}
// Drop the stalest entry of `map` (staleness proxied via the owner's
// last_heard; 0 = owner evicted, i.e. an orphan first out). Never evicts our
// last_heard; 0 = owner evicted, i.e. an orphan - first out). Never evicts our
// own node's entry. Caller holds satelliteMutex. Returns false if nothing
// could be evicted.
template <typename Map> bool evictStalestSatellite(NodeDB &db, Map &map)
@@ -1797,7 +1797,7 @@ bool NodeDB::enforceSatelliteCaps()
#if WARM_NODE_COUNT > 0
// Classify an evicted node's hop-protected category for the warm tier. Favorite/ignored/
// verified are local flags (rarely reach warm they're eviction-protected but classify
// verified are local flags (rarely reach warm - they're eviction-protected - but classify
// them if they do); otherwise tracker/sensor/tak_tracker are role-protected.
static uint8_t warmProtectedCategory(const meshtastic_NodeInfoLite &n)
{
@@ -1940,7 +1940,7 @@ LoadFileResult NodeDB::loadProto(const char *filename, size_t protoSize, size_t
// check if the file is encrypted and decrypt before protobuf decode
if (EncryptedStorage::isEncrypted(filename)) {
// ZeroizingArrayPtr wipes the decrypted plaintext (which contains config
// secrets channel PSKs, security private_key, etc.) before delete[],
// secrets - channel PSKs, security private_key, etc.) before delete[],
// so it isn't recoverable from the heap after this function returns.
auto decBuf = meshtastic_security::make_zeroizing_array(protoSize);
if (!decBuf) {
@@ -2028,7 +2028,7 @@ void NodeDB::demoteOldestHotNodesToWarm()
warmStore.absorb(n.num, n.last_heard, n.public_key.size > 0 ? n.public_key.bytes : nullptr, n.role,
warmProtectedCategory(n));
// Demotion drops the node from the header table, so drop its satellites
// too (the eviction chokepoint) they'd otherwise orphan until the next
// too (the eviction chokepoint) - they'd otherwise orphan until the next
// enforceSatelliteCaps pass.
eraseNodeSatellites(n.num);
demoted++;
@@ -2079,7 +2079,7 @@ void NodeDB::nodeDBSelfCare()
const bool satsTrimmed = enforceSatelliteCaps();
// Ensure self exists, sits at index 0, and carries current owner info after
// Ensure self exists, sits at index 0, and carries current owner info - after
// any demotion has freed a slot. Covers the foreign/fixture case where the
// loaded file did not contain us at all.
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(self);
@@ -2090,7 +2090,7 @@ void NodeDB::nodeDBSelfCare()
}
// One-shot rewrite: only when we healed something, and never while storage
// is locked a locked boot loads placeholder defaults that must not be written
// is locked - a locked boot loads placeholder defaults that must not be written
// over the encrypted store; reloadFromDisk() re-runs self-care once unlocked.
#ifdef MESHTASTIC_ENCRYPTED_STORAGE
const bool storageLocked = EncryptedStorage::isLockdownActive() && !EncryptedStorage::isUnlocked();
@@ -2171,7 +2171,7 @@ void NodeDB::loadFromDisk()
#ifdef MESHTASTIC_ENCRYPTED_STORAGE
// Only take the locked-boot defaults path when lockdown is ACTIVE (the
// device is provisioned) AND storage is still locked. A lockdown-capable
// build that has never been provisioned or that was disabled falls
// build that has never been provisioned - or that was disabled - falls
// through to the normal plaintext load below and behaves like stock.
if (EncryptedStorage::isLockdownActive() && !EncryptedStorage::isUnlocked()) {
// Encrypted storage is locked. Install defaults and wait for the
@@ -2188,7 +2188,7 @@ void NodeDB::loadFromDisk()
// would otherwise honour USERPREFS_CONFIG_LORA_REGION (the common shape
// for managed deployments) and the LongFast default channel synthesised
// by installDefaultChannels, so the device would beacon nodeinfo /
// telemetry on the public default PSK before any unlock and process
// telemetry on the public default PSK before any unlock - and process
// incoming default-channel packets the same way. Forcing region=UNSET
// gates both TX and RX in RadioLibInterface (see the region==UNSET
// checks in startSend and readData); tx_enabled=false is belt-and-
@@ -2262,7 +2262,7 @@ void NodeDB::loadFromDisk()
// Left UNTRIMMED on purpose: trim/demote/satellite-cap/self-pin/rewrite all
// run in nodeDBSelfCare() once getNodeNum() is valid (still 0 here on a cold
// boot, so we could only assume index 0 == self the very bug being fixed).
// boot, so we could only assume index 0 == self - the very bug being fixed).
#if WARM_NODE_COUNT > 0
// Load the warm tier so its on-disk snapshot is available before the node DB
// is exercised (and before nodeDBSelfCare() demotes any overflow into it).
@@ -2445,7 +2445,7 @@ void NodeDB::loadFromDisk()
}
// Always-on traffic management: a device that has NEVER configured TMM
// (has_traffic_management false AdminModule always sets the has_ flag on
// (has_traffic_management false - AdminModule always sets the has_ flag on
// write, even when disabling) gets the fork defaults. Explicitly configured
// devices keep their exact settings.
if (!moduleConfig.has_traffic_management) {
@@ -2479,7 +2479,7 @@ void NodeDB::loadFromDisk()
// without saving to disk, so we force a save here to ensure encrypted files exist.
//
// Only when lockdown is ACTIVE. A capable-but-off device must leave its
// files as plaintext encryptAndWrite would fail anyway (no DEK), but
// files as plaintext - encryptAndWrite would fail anyway (no DEK), but
// skipping the whole block avoids the wasted attempts and error logs.
if (EncryptedStorage::isLockdownActive()) {
const char *filesToCheck[] = {configFileName, moduleConfigFileName, channelFileName, deviceStateFileName,
@@ -2548,7 +2548,7 @@ void NodeDB::loadFromDisk()
// Serializes reloadFromDisk against itself. Other readers of config /
// channelFile / nodeDatabase don't take this lock today, so this only
// prevents reload-vs-reload races (e.g. fast successive unlocks). It is
// not a full data-race fix for those structs that would require
// not a full data-race fix for those structs - that would require
// thread-shared locking discipline across the whole codebase, beyond
// the audit's M7 scope. The radio standby+reconfigure below keeps the
// radio out of the window where SX12xx registers are mid-swap.
@@ -2561,7 +2561,7 @@ static concurrency::Lock g_reloadFromDiskMutex;
* reconfigure() to push the now-real settings to the chip.
*
* Returns true iff every encrypted file decrypted and decoded cleanly.
* On false the caller MUST treat storage as corrupt see header.
* On false the caller MUST treat storage as corrupt - see header.
*/
bool NodeDB::reloadFromDisk()
{
@@ -2580,7 +2580,7 @@ bool NodeDB::reloadFromDisk()
loadFromDisk();
if (storageCorruptThisLoad) {
LOG_ERROR("NodeDB: storage decrypt/decode failed during reload surfacing as corrupt");
LOG_ERROR("NodeDB: storage decrypt/decode failed during reload - surfacing as corrupt");
// Leave the radio sleeping. Caller will lock storage and emit
// a LOCKED(storage_corrupt) status; we must not reconfigure
// the chip with the locked-default placeholder values still
@@ -2629,7 +2629,7 @@ bool NodeDB::disableLockdownToPlaintext()
}
}
// All files are plaintext now remove the lockdown artifacts. Deleting
// All files are plaintext now - remove the lockdown artifacts. Deleting
// /prefs/.dek is the atomic commit: after it, isLockdownActive() is false.
EncryptedStorage::removeLockdownArtifacts();
return true;
@@ -2652,11 +2652,11 @@ bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_
// Encrypt all files except uiconfig (no secrets) and the DEK file (self-encrypted).
// Only when lockdown is ACTIVE (provisioned). A lockdown-capable but DISABLED
// device has no DEK, so encryptAndWrite would fail and config would never
// persist it must save plaintext exactly like stock firmware. Once enabled,
// persist - it must save plaintext exactly like stock firmware. Once enabled,
// the reloadFromDisk migrate pass re-saves these plaintext files encrypted.
if (EncryptedStorage::isLockdownActive() && strcmp(filename, uiconfigFileName) != 0) {
// ZeroizingArrayPtr wipes the unencrypted protobuf encoding (which contains
// config secrets channel PSKs, security private_key, etc.) before delete[],
// config secrets - channel PSKs, security private_key, etc.) before delete[],
// so plaintext copies aren't left in heap memory after encryption completes.
auto pbBuf = meshtastic_security::make_zeroizing_array(protoSize);
if (!pbBuf) {
@@ -2853,7 +2853,7 @@ bool NodeDB::saveNodeDatabaseToDisk()
// reset the 8s HW watchdog so the second write gets a full budget (issue #10746).
watchdog_update();
#endif
// Same cadence as the node DB; failure is logged but must not propagate
// Same cadence as the node DB; failure is logged but must not propagate -
// a false return from here would trigger saveToDisk()'s fsFormat() path.
warmStore.saveIfDirty();
#endif
@@ -2874,14 +2874,14 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat)
// When lockdown is ACTIVE but storage is still locked, encryptAndWrite()
// returns false for every file. That would cause saveToDisk()'s nRF52 retry
// path to call FSCom.format(), wiping all encrypted proto files from flash.
// Return true here "nothing to save, not an error."
// Return true here - "nothing to save, not an error."
//
// Gate on isLockdownActive(): a lockdown-capable but DISABLED device (never
// provisioned) also has isUnlocked()==false, but it must persist plaintext
// normally skipping here would silently drop every config write (e.g. the
// normally - skipping here would silently drop every config write (e.g. the
// LoRa region) until the device is provisioned.
if (EncryptedStorage::isLockdownActive() && !EncryptedStorage::isUnlocked()) {
LOG_WARN("NodeDB: saveToDisk skipped encrypted storage locked");
LOG_WARN("NodeDB: saveToDisk skipped - encrypted storage locked");
return true;
}
#endif
@@ -3009,7 +3009,7 @@ HopStartStatus classifyHopStart(const meshtastic_MeshPacket &p)
return HopStartStatus::INVALID;
if (p.hop_start == 0) {
// hop_start == hop_limit == 0: intentional zero-hop broadcast (e.g. beacon). Valid by definition
// hop_start == hop_limit == 0: intentional zero-hop broadcast (e.g. beacon). Valid by definition -
// the packet was never meant to travel any hops, so no hop_start ambiguity applies.
if (p.hop_limit == 0)
return HopStartStatus::VALID;
@@ -3204,7 +3204,7 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
TypeConversions::CopyUserToNodeInfoLite(info, contact.user);
if (contact.should_ignore) {
// Block the contact and drop its rich satellite data, but keep the
// public key copied above an ignored peer keeps a usable identity
// public key copied above - an ignored peer keeps a usable identity
// (a verifiable target) rather than a bare node number.
if (!setProtectedFlag(info, NODEINFO_BITFIELD_IS_IGNORED_MASK, true))
LOG_WARN(PROTECTED_CAP_WARN_FMT, "ignore", contact.node_num, MAX_NUM_NODES - 2);
@@ -3355,7 +3355,7 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
#if HAS_VARIABLE_HOPS
// Only sample genuine RF-origin packets. The transport check excludes packets received
// directly from the broker (TRANSPORT_MQTT), but an MQTT-origin packet rebroadcast onto
// LoRa by a gateway arrives as TRANSPORT_LORA with via_mqtt set count those would
// LoRa by a gateway arrives as TRANSPORT_LORA with via_mqtt set - count those would
// inflate the local mesh-size estimate with non-RF nodes (and they usually carry
// hop_start==0, landing in the hop-0 bucket that pulls the recommendation lowest), so
// exclude via_mqtt too.
@@ -3706,7 +3706,7 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
// Don't append past the end of the vector. The protected-node cap
// (numProtectedNodes() <= MAX_NUM_NODES-2) means the eviction above frees
// a slot in normal operation; this guards the legacy case of a pre-cap
// database that is full of protected nodes refuse rather than overrun.
// database that is full of protected nodes - refuse rather than overrun.
if (numMeshNodes >= MAX_NUM_NODES)
return NULL;
// Pre-size before append when run before nodeDBSelfCare() (boot keygen); else at() aborts on nRF52.
+6 -6
View File
@@ -337,11 +337,11 @@ class NodeDB
WarmNodeStore warmStore;
#endif
/// Copy the 32-byte public key for node n hot store first, then the warm
/// Copy the 32-byte public key for node n - hot store first, then the warm
/// tier. Returns false if we don't know a key for n.
bool copyPublicKey(NodeNum n, meshtastic_NodeInfoLite_public_key_t &out);
/// Resolve a node's device role hot store (with user) first, then the role
/// Resolve a node's device role - hot store (with user) first, then the role
/// cached in the warm tier, else CLIENT. Lets role-aware policy keep firing for
/// nodes that have aged out of the hot store.
meshtastic_Config_DeviceConfig_Role getNodeRole(NodeNum n);
@@ -412,7 +412,7 @@ class NodeDB
emptyNodeDatabase.version = DEVICESTATE_CUR_VER;
size_t nodeDatabaseSize;
pb_get_encoded_size(&nodeDatabaseSize, meshtastic_NodeDatabase_fields, &emptyNodeDatabase);
// Decode-stream size ceiling only no buffer this big is allocated (load
// Decode-stream size ceiling only - no buffer this big is allocated (load
// streams from the file). Sized for the largest file any prior firmware
// could write (250-node ESP32-S3, satellites uncapped) so capacity
// downgrades / peer backups still decode; excess is trimmed after load.
@@ -480,7 +480,7 @@ class NodeDB
/// Returns true iff every encrypted file decrypted and decoded cleanly.
/// On false the caller MUST treat the storage as corrupt: leave the
/// connection unauthenticated, emit a LOCKED(storage_corrupt) status,
/// and refuse to call setAdminAuthorized otherwise a subsequent
/// and refuse to call setAdminAuthorized - otherwise a subsequent
/// set_config would re-encrypt a wrong baseline (the locked-default
/// values still resident in `config` / `channelFile` / `nodeDatabase`)
/// and overwrite the operator's persisted state.
@@ -489,7 +489,7 @@ class NodeDB
/// Disable lockdown: decrypt every encrypted pref file back to plaintext,
/// then remove the DEK / token / counter / backoff artifacts. Requires
/// EncryptedStorage to be unlocked (DEK in RAM). Returns false if any
/// file failed to revert in which case the DEK is still present and the
/// file failed to revert - in which case the DEK is still present and the
/// device remains in lockdown so the operator can retry. APPROTECT is not
/// reversed. Called from the main loop via lockdownDisablePending.
bool disableLockdownToPlaintext();
@@ -571,7 +571,7 @@ class NodeDB
bool migrateLegacyNodeDatabase();
// Route satellite-store decode entries straight into our maps instead of
// temp vectors. Must be paired disarm before any other NodeDatabase decode.
// temp vectors. Must be paired - disarm before any other NodeDatabase decode.
void armNodeDatabaseDecodeTargets();
void disarmNodeDatabaseDecodeTargets();
};
+1 -1
View File
@@ -92,7 +92,7 @@ bool NodeDB::migrateLegacyNodeDatabase()
sanitizeUtf8(slim.long_name, sizeof(slim.long_name)); // replace bad bytes so nanopb encode never fails
strncpy(slim.short_name, legacy.user.short_name, sizeof(slim.short_name));
slim.short_name[sizeof(slim.short_name) - 1] = '\0';
sanitizeUtf8(slim.short_name, sizeof(slim.short_name)); // same v24 names may contain non-UTF-8 bytes
sanitizeUtf8(slim.short_name, sizeof(slim.short_name)); // same - v24 names may contain non-UTF-8 bytes
slim.hw_model = legacy.user.hw_model;
slim.role = legacy.user.role;
if (legacy.user.is_licensed)
+2 -2
View File
@@ -248,7 +248,7 @@ void PacketHistory::hashRemove(NodeNum sender, PacketId id)
return;
uint16_t idx = hashIndex[bucket];
if (idx < recentPacketsCapacity && recentPackets[idx].sender == sender && recentPackets[idx].id == id) {
// Found it delete and re-insert subsequent entries to maintain probe chain integrity
// Found it - delete and re-insert subsequent entries to maintain probe chain integrity
hashIndex[bucket] = HASH_EMPTY;
uint32_t next = (bucket + 1) & hashMask;
for (uint32_t j = 0; j < hashCapacity; j++) {
@@ -488,7 +488,7 @@ bool PacketHistory::wasRelayer(const uint8_t relayer, const uint32_t id, const N
/* Check if a certain node was a relayer of a packet in the history given iterator
* @return true if node was indeed a relayer, false if not
* NOTE: intentionally byte-domain. Both `relayer` and relayed_by[] are on-wire last bytes, so this
* answers "did a relayer with this byte touch the packet" correct without resolving to a NodeNum.
* answers "did a relayer with this byte touch the packet" - correct without resolving to a NodeNum.
* The collision risk is neutralized where the result is consumed (route learning in
* NextHopRouter::sniffReceived now gates the write through NodeDB::resolveUniqueLastByte). */
bool PacketHistory::wasRelayer(const uint8_t relayer, const PacketRecord &r, bool *wasSole)
+52 -52
View File
@@ -64,12 +64,12 @@ static constexpr size_t MAX_AUTH_SLOTS = 6;
// status produced for connection A (e.g. UNLOCKED with the active TTL,
// or UNLOCK_FAILED with a backoff) cannot be drained by connection B,
// which would otherwise learn that A just authenticated or just failed
// a real information leak across local clients.
// - a real information leak across local clients.
//
// File-scope rather than a per-PhoneAPI member because adding any
// non-trivial state directly to PhoneAPI broke USB-CDC enumeration on
// the current nRF52 framework; the auth-slot table next door uses the
// same workaround. Lifecycle is tied to the auth slot table both are
// same workaround. Lifecycle is tied to the auth slot table - both are
// keyed by PhoneAPI*, both are cleared together in clearAuthSlot_LH,
// and both share g_authSlotsMutex.
struct PendingStatusSlot {
@@ -78,7 +78,7 @@ struct PendingStatusSlot {
bool hasPending = false;
// True between a successful passphrase verify and the main-loop
// reloadFromDisk that follows. While set, the connection is NOT
// yet authorized and no UNLOCKED status has been emitted the
// yet authorized and no UNLOCKED status has been emitted - the
// client still sees LOCKED, and any admin op it tries is dropped
// by the existing unauth gates. Cleared either way by
// completePendingUnlocks once reload finishes.
@@ -107,7 +107,7 @@ static PendingStatusSlot *findOrAllocStatusSlot_LH(PhoneAPI *p)
// Mirror the auth-slot eviction policy: stale slots can be reused.
// A connection that lost its auth slot has nothing meaningful to be
// told via a pending status anyway. Never evict a slot mid-unlock
// (pendingUnlockAfterReload set) completing that flow on the
// (pendingUnlockAfterReload set) - completing that flow on the
// wrong PhoneAPI would authorize the wrong connection.
for (auto &s : g_statusSlots) {
if (!s.hasPending && !s.pendingUnlockAfterReload) {
@@ -143,7 +143,7 @@ static void buildStatus_LH(meshtastic_LockdownStatus &out, meshtastic_LockdownSt
memset(&out, 0, sizeof(out));
out.state = state;
// Collapse the specific token_* reasons to a generic "locked" over
// the wire full detail still goes to local logs. An unauth client
// the wire - full detail still goes to local logs. An unauth client
// does not need to know whether HMAC failed vs the boot count
// hit zero vs the file was the wrong size; all of those mean the
// same thing to the client ("locked, ask for passphrase") but
@@ -171,7 +171,7 @@ struct PhoneAuthSlot {
static PhoneAuthSlot g_authSlots[MAX_AUTH_SLOTS];
// Global auth epoch. Lock Now bumps it; per-slot `epoch` compared against
// this. Wraps at 2^32 revocations practically unreachable; on wrap the
// this. Wraps at 2^32 revocations - practically unreachable; on wrap the
// only behavioral effect is that any slot whose epoch happens to match the
// new low value would be treated as authorized again, which requires a
// pre-existing authorized slot to survive 2^32 lockNow events on the same
@@ -179,7 +179,7 @@ static PhoneAuthSlot g_authSlots[MAX_AUTH_SLOTS];
static uint32_t g_authEpoch = 1;
// Single mutex guarding g_authSlots and g_authEpoch. All readers and
// writers including const getters like getAdminAuthorized must take
// writers - including const getters like getAdminAuthorized - must take
// it. Granularity is fine because the critical sections are short (a
// fixed-size linear scan over 6 entries) and contention is dominated by
// getFromRadio's per-call redaction checks, which tolerate brief
@@ -191,7 +191,7 @@ static concurrency::Lock g_authSlotsMutex;
// evicts the first unauthorized slot found. Refuses to evict an authorized
// slot (those represent a live operator session and must outlive the table
// pressure of reconnect churn). Returns nullptr only if every slot is
// occupied by a different live, authorized PhoneAPI practically only
// occupied by a different live, authorized PhoneAPI - practically only
// reachable as a DoS via 7+ simultaneous authed connections, in which
// case fail-closed and log.
static PhoneAuthSlot *findOrAllocSlot_LH(PhoneAPI *p)
@@ -211,7 +211,7 @@ static PhoneAuthSlot *findOrAllocSlot_LH(PhoneAPI *p)
}
}
// Second pass: evict an unauthorized stale slot. Don't touch authorized
// ones those still represent an operator-authenticated session.
// ones - those still represent an operator-authenticated session.
for (auto &s : g_authSlots) {
if (!s.authorized) {
s.who = p;
@@ -312,7 +312,7 @@ void PhoneAPI::handleStartConfig()
#if defined(MESHTASTIC_EXCLUDE_FILES_MANIFEST)
// Skip the recursive FS walk. Used by platforms whose Zephyr LittleFS
// backend can't safely traverse a deep tree (e.g. nRF54L15) and platforms
// that don't support OTA browsing the manifest is only consumed by
// that don't support OTA browsing - the manifest is only consumed by
// companion apps for those flows.
releaseFilesManifest(filesManifest);
#else
@@ -444,12 +444,12 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
case meshtastic_ToRadio_packet_tag:
#ifdef MESHTASTIC_PHONEAPI_ACCESS_CONTROL
if (!getAdminAuthorized()) {
// Allow admin messages addressed to this device passphrase delivery must get through.
// Allow admin messages addressed to this device - passphrase delivery must get through.
// AdminModule handles its own is_managed gate for those.
// Block everything else unauthorized clients cannot inject mesh traffic.
// Block everything else - unauthorized clients cannot inject mesh traffic.
// Require the packet to carry a decoded (not encrypted) payload so portnum is valid.
// Refuse to match when our own node number is still 0 (NodeDB
// not yet loaded happens during the locked-default boot path
// not yet loaded - happens during the locked-default boot path
// before reloadFromDisk). Otherwise a packet with to==0 would
// satisfy the equality and bypass the gate.
NodeNum ourNum = nodeDB->getNodeNum();
@@ -584,12 +584,12 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
fromRadioScratch.my_info = myNodeInfo;
#ifdef MESHTASTIC_PHONEAPI_ACCESS_CONTROL
if (!getAdminAuthorized()) {
// device_id is a stable hardware identifier useful for an attacker
// device_id is a stable hardware identifier - useful for an attacker
// to fingerprint / correlate the device across observations. Strip it
// for unauthenticated clients. my_node_num is kept (it's broadcast
// on the mesh anyway). pio_env / min_app_version reveal the exact
// build flavour, useful only for picking which known-CVE to try.
// nodedb_count stays clients need it to decide whether to pull
// nodedb_count stays - clients need it to decide whether to pull
// the node DB after unlocking.
fromRadioScratch.my_info.device_id.size = 0;
memset(fromRadioScratch.my_info.device_id.bytes, 0, sizeof(fromRadioScratch.my_info.device_id.bytes));
@@ -657,7 +657,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
// position_flags, excluded_modules, optionsCount. None of it
// is needed to drive lockdown_auth, and most of it tells an
// attacker which CVE / behavior quirks to probe. Wipe the
// whole struct clients re-fetch once authenticated.
// whole struct - clients re-fetch once authenticated.
memset(&fromRadioScratch.metadata, 0, sizeof(fromRadioScratch.metadata));
}
#endif
@@ -668,7 +668,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
// Tell the client which modem presets are legal in each region so its UI
// can block illegal region+preset combinations. This is public RF /
// regulatory information (region and modem_preset are already in the
// unauthenticated LoRa whitelist below), so it is sent unconditionally
// unauthenticated LoRa whitelist below), so it is sent unconditionally -
// even an unauthorized/locked-down client can render a correct picker.
LOG_DEBUG("Send region preset map");
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_region_presets_tag;
@@ -683,7 +683,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
if (!getAdminAuthorized()) {
// Unauthenticated: emit a zero-initialized Channel. fromRadioScratch
// was memset(0) at the top of getFromRadio(), so leaving .channel
// untouched gives the client an empty entry no name, no PSK, no
// untouched gives the client an empty entry - no name, no PSK, no
// role. Advances the state machine normally so config_complete_id
// still fires.
} else
@@ -748,7 +748,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
// private knobs (ignore_incoming list, override_duty_cycle,
// override_frequency, sx126x_rx_boosted_gain, tx_power,
// ignore_mqtt, fem_lna_mode, config_ok_to_mqtt, ...) stay
// hidden they tell an attacker how the operator has tuned
// hidden - they tell an attacker how the operator has tuned
// the device but are not needed by an unauth client.
meshtastic_Config_LoRaConfig whitelist = {};
whitelist.use_preset = config.lora.use_preset;
@@ -775,7 +775,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
if (!getAdminAuthorized()) {
// Unauthenticated: emit an empty SecurityConfig (zero-init from
// the top-of-loop memset). No private_key, no admin_keys, no
// public_key nothing for an attacker to inspect.
// public_key - nothing for an attacker to inspect.
//
// Provisioning state (NEEDS_PROVISION vs LOCKED) is conveyed via
// the FromRadio.lockdown_status proto sent post-config; clients
@@ -906,7 +906,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
if (config_state > (_meshtastic_AdminMessage_ModuleConfigType_MAX + 1)) {
#ifdef MESHTASTIC_PHONEAPI_ACCESS_CONTROL
if (!getAdminAuthorized()) {
// Unauthorized client: skip node DB and file manifest only send config complete
// Unauthorized client: skip node DB and file manifest - only send config complete
state = STATE_SEND_COMPLETE_ID;
} else
#endif
@@ -993,7 +993,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
} else if (mqttClientProxyMessageForPhone) {
#ifdef MESHTASTIC_PHONEAPI_ACCESS_CONTROL
if (!getAdminAuthorized()) {
releaseMqttClientProxyPhonePacket(); // Discard unauthorized client
releaseMqttClientProxyPhonePacket(); // Discard - unauthorized client
} else
#endif
{
@@ -1004,7 +1004,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
} else if (xmodemPacketForPhone.control != meshtastic_XModem_Control_NUL) {
#ifdef MESHTASTIC_PHONEAPI_ACCESS_CONTROL
if (!getAdminAuthorized()) {
xmodemPacketForPhone = meshtastic_XModem_init_zero; // Discard unauthorized client
xmodemPacketForPhone = meshtastic_XModem_init_zero; // Discard - unauthorized client
} else
#endif
{
@@ -1015,7 +1015,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
#ifdef MESHTASTIC_PHONEAPI_ACCESS_CONTROL
} else if (hasPendingLockdownStatus()) {
concurrency::LockGuard guard(&g_authSlotsMutex);
// Look up our own slot only never another connection's. Re-check
// Look up our own slot only - never another connection's. Re-check
// hasPending under the lock since a concurrent drain on the same
// connection (unlikely but possible if multiple transport
// callbacks race against one PhoneAPI) may have grabbed it.
@@ -1033,7 +1033,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
} else if (packetForPhone) {
#ifdef MESHTASTIC_PHONEAPI_ACCESS_CONTROL
if (!getAdminAuthorized()) {
releasePhonePacket(); // Discard mesh traffic unauthorized client
releasePhonePacket(); // Discard mesh traffic - unauthorized client
} else
#endif
{
@@ -1044,7 +1044,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
releasePhonePacket();
}
} else if (replayPending()) {
// No live packet pending feed the phone one cached satellite-DB packet.
// No live packet pending - feed the phone one cached satellite-DB packet.
// popReplayPacket advances through positions->telemetry->environment->status,
// and flips replayPhase back to IDLE when everything has been drained.
meshtastic_MeshPacket replayPkt;
@@ -1222,7 +1222,7 @@ meshtastic_MeshPacket PhoneAPI::makeReplayTelemetryPacket(NodeNum num, const mes
pkt.hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
pkt.hop_start = pkt.hop_limit;
pkt.priority = meshtastic_MeshPacket_Priority_BACKGROUND;
// Mark as if heard over the air, not internally generated iOS client filters
// Mark as if heard over the air, not internally generated - iOS client filters
// TRANSPORT_INTERNAL packets out of broadcast peer state updates.
pkt.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA;
pkt.which_payload_variant = meshtastic_MeshPacket_decoded_tag;
@@ -1327,7 +1327,7 @@ meshtastic_MeshPacket PhoneAPI::makeReplayEnvironmentPacket(uint32_t num, const
pkt.hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
pkt.hop_start = pkt.hop_limit;
pkt.priority = meshtastic_MeshPacket_Priority_BACKGROUND;
// Mark as if heard over the air, not internally generated iOS client filters
// Mark as if heard over the air, not internally generated - iOS client filters
// TRANSPORT_INTERNAL packets out of broadcast peer state updates.
pkt.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA;
pkt.which_payload_variant = meshtastic_MeshPacket_decoded_tag;
@@ -1391,7 +1391,7 @@ meshtastic_MeshPacket PhoneAPI::makeReplayStatusPacket(uint32_t num, const mesht
pkt.hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
pkt.hop_start = pkt.hop_limit;
pkt.priority = meshtastic_MeshPacket_Priority_BACKGROUND;
// Mark as if heard over the air, not internally generated client filters
// Mark as if heard over the air, not internally generated - client filters
pkt.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA;
pkt.which_payload_variant = meshtastic_MeshPacket_decoded_tag;
pkt.decoded.portnum = meshtastic_PortNum_NODE_STATUS_APP;
@@ -1483,7 +1483,7 @@ bool PhoneAPI::popReplayPacket(meshtastic_MeshPacket &out)
}
}
// Queue empty AND no more entries to feed it phase is exhausted.
// Queue empty AND no more entries to feed it - phase is exhausted.
advanceReplayPhase();
}
return false;
@@ -1609,7 +1609,7 @@ bool PhoneAPI::available()
hasPacket = !!packetForPhone;
if (hasPacket)
return true;
// Trailing replay drain feeds cached satellite-DB packets alongside
// Trailing replay drain - feeds cached satellite-DB packets alongside
// (lower priority than) live traffic.
return replayPending();
}
@@ -1666,7 +1666,7 @@ bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p)
// (b) Any other admin payload from an unauthorized connection:
// dropped here. The previous design relied on AdminModule
// to apply isLocalAdminAuthorized() during dispatch, but
// AdminModule runs on the Router task by then the
// AdminModule runs on the Router task - by then the
// PhoneAPI dispatching task has already exited and the
// per-connection auth context is unrecoverable. Putting
// the gate here closes that race and covers H6/H7 from the
@@ -1678,7 +1678,7 @@ bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p)
if (pb_decode_from_bytes(p.decoded.payload.bytes, p.decoded.payload.size, &meshtastic_AdminMessage_msg, &admin)) {
if (admin.which_payload_variant == meshtastic_AdminMessage_lockdown_auth_tag) {
handleLockdownAuthInline(admin.lockdown_auth);
// Wipe the decoded passphrase scratch the byte array in
// Wipe the decoded passphrase scratch - the byte array in
// p.decoded.payload.bytes is wiped by handleLockdownAuthInline.
volatile uint8_t *adminVol = const_cast<volatile uint8_t *>(admin.lockdown_auth.passphrase.bytes);
for (size_t i = 0; i < sizeof(admin.lockdown_auth.passphrase.bytes); i++)
@@ -1769,7 +1769,7 @@ bool PhoneAPI::getAdminAuthorized() const
{
// Runtime-toggle model: when lockdown is NOT active (a lockdown-capable
// build that hasn't been provisioned, or that was disabled), there is
// nothing to protect every connection is implicitly authorized, so
// nothing to protect - every connection is implicitly authorized, so
// all the `if (!getAdminAuthorized())` redaction gates throughout
// getFromRadio() / handleToRadio() become no-ops and the device serves
// config exactly like stock firmware. Only once provisioned (lockdown
@@ -1779,7 +1779,7 @@ bool PhoneAPI::getAdminAuthorized() const
return true;
#endif
concurrency::LockGuard g(&g_authSlotsMutex);
// const_cast is safe findOrAllocSlot_LH only mutates the slot table,
// const_cast is safe - findOrAllocSlot_LH only mutates the slot table,
// not the PhoneAPI itself, and the table key is just the pointer.
const auto *slot = findOrAllocSlot_LH(const_cast<PhoneAPI *>(this));
return slot && slot->authorized && slot->epoch == g_authEpoch;
@@ -1790,7 +1790,7 @@ void PhoneAPI::setAdminAuthorized(bool authorized)
concurrency::LockGuard g(&g_authSlotsMutex);
auto *slot = findOrAllocSlot_LH(this);
if (!slot)
return; // slot table full fail-closed
return; // slot table full - fail-closed
if (authorized) {
slot->epoch = g_authEpoch;
slot->authorized = true;
@@ -1813,7 +1813,7 @@ void PhoneAPI::completePendingUnlocks(bool reloadOk)
{
// Snapshot fields that we'll need outside the lock (we cannot call
// EncryptedStorage / setAdminAuthorized / unlockScreen while holding
// g_authSlotsMutex without risking re-entry setAdminAuthorized
// g_authSlotsMutex without risking re-entry - setAdminAuthorized
// itself takes the same lock).
constexpr size_t kMaxSnapshots = MAX_AUTH_SLOTS;
PhoneAPI *targets[kMaxSnapshots] = {};
@@ -1825,7 +1825,7 @@ void PhoneAPI::completePendingUnlocks(bool reloadOk)
continue;
if (targetCount < kMaxSnapshots)
targets[targetCount++] = s.who;
// Clear the pending flag either way failure path must not
// Clear the pending flag either way - failure path must not
// leave it set so a subsequent successful reload retries
// against the wrong PhoneAPI.
s.pendingUnlockAfterReload = false;
@@ -1841,13 +1841,13 @@ void PhoneAPI::completePendingUnlocks(bool reloadOk)
p->queueLockdownStatus(meshtastic_LockdownStatus_State_UNLOCKED, "", boots, until, 0);
}
// Screen-lock latch is cleared once any client successfully
// unlocks the operator has proven the passphrase. Matches the
// unlocks - the operator has proven the passphrase. Matches the
// re-verify path's behavior.
if (targetCount > 0)
meshtastic_security::unlockScreen();
LOG_INFO("Lockdown: post-reload completion: authorized %u connection(s)", (unsigned)targetCount);
} else {
// Storage corrupt emit LOCKED(storage_corrupt) to every slot
// Storage corrupt - emit LOCKED(storage_corrupt) to every slot
// that was awaiting the unlock. setAdminAuthorized is NOT called
// so the connection stays redacted and any set_config it sends
// is dropped at the existing unauth gates. Caller (main.cpp) has
@@ -1866,7 +1866,7 @@ void PhoneAPI::queueLockdownStatus(meshtastic_LockdownStatus_State state, const
concurrency::LockGuard guard(&g_authSlotsMutex);
auto *slot = findOrAllocStatusSlot_LH(this);
if (!slot)
return; // slot table exhausted fail-closed, no status delivered
return; // slot table exhausted - fail-closed, no status delivered
buildStatus_LH(slot->status, state, lock_reason, boots_remaining, valid_until_epoch, backoff_seconds);
slot->hasPending = true;
}
@@ -1914,14 +1914,14 @@ bool PhoneAPI::handleLockdownAuthInline(const meshtastic_LockdownAuth &la)
ppVol[zi] = 0;
};
// Lock Now only honored from a connection that has already proven
// Lock Now - only honored from a connection that has already proven
// the passphrase. Unauthenticated clients used to be able to trigger
// a reboot, which was a trivial local-presence DoS (any BLE/USB
// attacker could brick-loop the device). Now lock_now requires
// prior auth on this connection.
if (la.lock_now) {
if (!getAdminAuthorized()) {
LOG_WARN("Lockdown: LOCK NOW from unauthorized connection denied");
LOG_WARN("Lockdown: LOCK NOW from unauthorized connection - denied");
queueLockdownStatus(meshtastic_LockdownStatus_State_UNLOCK_FAILED, "", 0, 0, 0);
zeroPassphrase();
return true;
@@ -1938,18 +1938,18 @@ bool PhoneAPI::handleLockdownAuthInline(const meshtastic_LockdownAuth &la)
// Disable lockdown entirely. Requires the passphrase (must prove
// ownership before reverting at-rest encryption). We verify it here to
// load the DEK, then hand the heavy decrypt-revert work to the main
// loop via lockdownDisablePending exactly like the unlock reload
// loop via lockdownDisablePending - exactly like the unlock reload
// path, because decrypting + rewriting nodes.proto is too heavy for
// this transport-callback stack. APPROTECT is NOT reversed.
if (la.disable) {
if (la.passphrase.size < 1) {
LOG_WARN("Lockdown: disable with empty passphrase rejecting");
LOG_WARN("Lockdown: disable with empty passphrase - rejecting");
queueLockdownStatus(meshtastic_LockdownStatus_State_UNLOCK_FAILED, "", 0, 0, 0);
zeroPassphrase();
return true;
}
if (!EncryptedStorage::isLockdownActive()) {
// Already off nothing to do; report DISABLED so the client UI settles.
// Already off - nothing to do; report DISABLED so the client UI settles.
LOG_INFO("Lockdown: disable requested but lockdown is not active");
queueLockdownStatus(meshtastic_LockdownStatus_State_DISABLED, "", 0, 0, 0);
zeroPassphrase();
@@ -1972,13 +1972,13 @@ bool PhoneAPI::handleLockdownAuthInline(const meshtastic_LockdownAuth &la)
return true;
}
// Empty-passphrase auth was previously a silent success clients
// Empty-passphrase auth was previously a silent success - clients
// got no feedback and the device looked the same as it would after
// an actual no-op. Emit UNLOCK_FAILED with no backoff so honest
// clients can detect their own bug and an attacker still learns
// nothing they wouldn't from any other failed attempt.
if (la.passphrase.size < 1) {
LOG_WARN("Lockdown: lockdown_auth with empty passphrase and lock_now=false rejecting");
LOG_WARN("Lockdown: lockdown_auth with empty passphrase and lock_now=false - rejecting");
queueLockdownStatus(meshtastic_LockdownStatus_State_UNLOCK_FAILED, "", 0, 0, 0);
zeroPassphrase();
return true;
@@ -2017,7 +2017,7 @@ bool PhoneAPI::handleLockdownAuthInline(const meshtastic_LockdownAuth &la)
if (ok) {
needsReload = true;
// Mark this slot for the main-loop completion handler. Don't
// authorize or emit UNLOCKED yet `config` / `channelFile`
// authorize or emit UNLOCKED yet - `config` / `channelFile`
// / `nodeDatabase` still hold the locked-default placeholders
// installed by loadFromDisk()'s !isUnlocked() branch. If we
// flipped the connection to authorized here, the client could
@@ -2040,7 +2040,7 @@ bool PhoneAPI::handleLockdownAuthInline(const meshtastic_LockdownAuth &la)
ok = EncryptedStorage::unlockWithPassphrase(la.passphrase.bytes, la.passphrase.size, boots, validUntilEpoch,
sessionMaxSeconds);
if (ok) {
// Storage was already unlocked no reload needed. Authorize
// Storage was already unlocked - no reload needed. Authorize
// and surface UNLOCKED to the client immediately.
setAdminAuthorized(true);
LOG_INFO("Lockdown: passphrase verified, this connection authorized");
@@ -2055,13 +2055,13 @@ bool PhoneAPI::handleLockdownAuthInline(const meshtastic_LockdownAuth &la)
queueLockdownStatus(meshtastic_LockdownStatus_State_UNLOCKED, "", EncryptedStorage::getBootsRemaining(),
EncryptedStorage::getValidUntilEpoch(), 0);
} else if (ok && needsReload) {
// Cold-unlock path: deliberately no status emission yet the
// Cold-unlock path: deliberately no status emission yet - the
// client keeps seeing LOCKED until completePendingUnlocks()
// runs after a successful reload.
} else {
uint32_t backoff = EncryptedStorage::getBackoffSecondsRemaining();
queueLockdownStatus(meshtastic_LockdownStatus_State_UNLOCK_FAILED, "", 0, 0, backoff);
// Don't log backoff seconds the client receives it in the
// Don't log backoff seconds - the client receives it in the
// UNLOCK_FAILED status anyway, and in non-DEBUG_MUTE builds the
// numeric value would otherwise spill onto a USB-attached
// attacker's serial terminal alongside other diagnostic noise.
+4 -4
View File
@@ -175,7 +175,7 @@ class PhoneAPI
#ifdef MESHTASTIC_PHONEAPI_ACCESS_CONTROL
/// Per-connection auth: tracked in a small file-scope slot table keyed
/// by PhoneAPI*. Adding state members directly to PhoneAPI broke
/// USB-CDC enumeration on current nRF52 framework even one extra
/// USB-CDC enumeration on current nRF52 framework - even one extra
/// per-instance uint32_t was enough. Keeping all state out-of-line
/// avoids the issue.
void setAdminAuthorized(bool authorized);
@@ -257,10 +257,10 @@ class PhoneAPI
APIType api_type = TYPE_NONE;
#ifdef MESHTASTIC_PHONEAPI_ACCESS_CONTROL
// No per-instance auth members see method-level note. All state lives
// No per-instance auth members - see method-level note. All state lives
// in a file-scope slot table in PhoneAPI.cpp keyed by `this` pointer.
// Pending LockdownStatus storage is NOT a class member having a
// Pending LockdownStatus storage is NOT a class member - having a
// meshtastic_LockdownStatus (~50 bytes with the char[33] lock_reason)
// as a PhoneAPI member broke USB-CDC enumeration on the nRF52 Adafruit
// framework. The exact mechanism wasn't pinned down, but moving the
@@ -310,7 +310,7 @@ class PhoneAPI
#if defined(MESHTASTIC_ENCRYPTED_STORAGE) && defined(MESHTASTIC_PHONEAPI_ACCESS_CONTROL)
/// Synchronously handle a lockdown_auth AdminMessage from the local
/// client. Runs inside handleToRadioPacket so the originating
/// connection is reachable via `this` avoids the async context
/// connection is reachable via `this` - avoids the async context
/// loss that broke the previous AdminModule path. Always consumes the
/// packet (returns true): lockdown_auth is local-only and must not be
/// forwarded to the mesh router.
+1 -1
View File
@@ -6,7 +6,7 @@
// Max precision on a publicly-decryptable channel. CCPA "precise geolocation" = within a ~564m (1,850ft) radius.
// Precision is bit-truncation of latitude_i/longitude_i: the latitude cell stays ~constant in meters worldwide
// (~700m at 15 bits), while only the longitude cell varies widest at the equator, narrowing toward the poles.
// (~700m at 15 bits), while only the longitude cell varies - widest at the equator, narrowing toward the poles.
// 15 also matches the MQTT map-report public precision ceiling.
#define MAX_POSITION_PRECISION_PUBLIC_KEY 15
+2 -2
View File
@@ -174,7 +174,7 @@ const RegionInfo regions[] = {
/*
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
https://standard.nbtc.go.th/getattachment/Standards/%E0%B8%A1%E0%B8%B2%E0%B8%95%E0%B8%A3%E0%B8%90%E0%B8%B2%E0%B8%99%E0%B8%97%E0%B8%B2%E0%B8%87%E0%B9%80%E0%B8%97%E0%B8%84%E0%B8%99%E0%B8%B4%E0%B8%84%E0%B8%82%E0%B8%AD%E0%B8%87%E0%B9%80%E0%B8%84%E0%B8%A3%E0%B8%B7%E0%B9%88%E0%B8%AD%E0%B8%87%E0%B9%82%E0%B8%97%E0%B8%A3%E0%B8%84%E0%B8%A1%E0%B8%99%E0%B8%B2%E0%B8%84%E0%B8%A1/1033-2565.pdf.aspx?lang=th-TH
Thailand 920925 MHz set max TX power to 27 dBm and enforce 10% duty cycle, aligned with NBTC regulations.
Thailand 920-925 MHz set max TX power to 27 dBm and enforce 10% duty cycle, aligned with NBTC regulations.
*/
RDEF(TH, 920.0f, 925.0f, 10, 27, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
@@ -246,7 +246,7 @@ const RegionInfo regions[] = {
/*
ITU Region 1 (Europe, Africa, Middle East, former USSR) amateur 2m allocation: 144.000 - 146.000 MHz.
Power limit is the regulatory ceiling (1 W / 30 dBm) individual hardware will cap below this
Power limit is the regulatory ceiling (1 W / 30 dBm) - individual hardware will cap below this
via its own PA curve; the field here is just the legal upper bound.
Default slot: 26 (144.510 MHz)
+4 -4
View File
@@ -371,10 +371,10 @@ void RadioLibInterface::onNotify(uint32_t notification)
handleTransmitInterrupt(); // completeSending() already restored the radio to the home config
#if !MESHTASTIC_EXCLUDE_BEACON
// Pre-switch the radio to the NEXT queued packet's beacon config (no-op for normal traffic).
// Not required for correctness TRANSMIT_DELAY_COMPLETED would switch before CAD anyway but
// Not required for correctness - TRANSMIT_DELAY_COMPLETED would switch before CAD anyway - but
// doing it here lets the next beacon skip the switch-only delay cycle and, more importantly,
// keeps the post-TX listen window (and the CAD/LBT that follows) on the channel we're about to
// transmit on. Only engages when the next packet is itself a beacon exactly when we want it.
// transmit on. Only engages when the next packet is itself a beacon - exactly when we want it.
MeshBeaconModule::reconfigureForBeaconTX(this, txQueue.getFront());
#endif
startReceive();
@@ -402,8 +402,8 @@ void RadioLibInterface::onNotify(uint32_t notification)
#if !MESHTASTIC_EXCLUDE_BEACON
} else if (MeshBeaconModule::beaconTxConfigInvalid(txp)) {
// The beacon's target radio config is invalid (bad preset/region, or an
// unlicensed node keying up on a ham-only region). Drop the packet never
// transmit it on the current (home) config and move on to the next queued packet.
// unlicensed node keying up on a ham-only region). Drop the packet - never
// transmit it on the current (home) config - and move on to the next queued packet.
LOG_DEBUG("Beacon: invalid TX radio config, dropping packet 0x%08x", txp->id);
meshtastic_MeshPacket *bad = txQueue.dequeue();
MeshBeaconModule::clearTargetRadioSettings(bad);
+1 -1
View File
@@ -160,7 +160,7 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
/**
* Reset AGC by power-cycling the analog frontend.
* Subclasses override with chip-specific calibration sequences.
* Safe to call periodically skips if currently sending or receiving.
* Safe to call periodically - skips if currently sending or receiving.
*/
virtual void resetAGC();
+2 -2
View File
@@ -100,7 +100,7 @@ bool Router::shouldDecrementHopLimit(const meshtastic_MeshPacket *p)
return true;
}
// router_preserve_hops: not suitable right now removed from config until
// router_preserve_hops: not suitable right now - removed from config until
// the right heuristics for when to preserve vs. exhaust hops are established.
// #if HAS_TRAFFIC_MANAGEMENT
// if (moduleConfig.has_traffic_management &&
@@ -560,7 +560,7 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
LOG_DEBUG("No public key for 0x%08x, cannot verify XEdDSA signature", p->from);
}
} else {
// Unsigned packet only reject the class of packet a signing node always signs:
// Unsigned packet - only reject the class of packet a signing node always signs:
// an unencrypted broadcast small enough to also carry a signature (see perhapsEncode()).
// Unicast packets and oversized broadcasts are never signed, so they must not be
// hard-failed here even if this node has signed before.
+1 -1
View File
@@ -36,7 +36,7 @@ class Router : protected concurrency::OSThread, protected PacketHistory
void addInterface(std::unique_ptr<RadioInterface> _iface) { iface = std::move(_iface); }
/**
* Borrowed (non-owning) access to the radio interface used by NodeDB
* Borrowed (non-owning) access to the radio interface - used by NodeDB
* after a lockdown unlock so it can push the freshly-loaded config to
* the SX12xx via reconfigure(). Returns nullptr when no radio has been
* attached (e.g. ARCH_PORTDUINO simulator before SimRadio bind).
+1 -1
View File
@@ -215,7 +215,7 @@ void StreamAPI::emitRebooted()
void StreamAPI::emitLogRecord(meshtastic_LogRecord_Level level, const char *src, const char *format, va_list arg)
{
// IMPORTANT: do NOT touch `fromRadioScratch` or `txBuf` here those
// IMPORTANT: do NOT touch `fromRadioScratch` or `txBuf` here - those
// belong to the main packet-emission path and a LOG_ firing during
// `writeStream()` would corrupt an in-flight encode. We keep a
// dedicated `fromRadioScratchLog` + `txBufLog` for log records and
+1 -1
View File
@@ -107,7 +107,7 @@ class StreamAPI : public PhoneAPI
/// re-used `fromRadioScratch` / `txBuf` and corrupted whatever the main
/// path had already encoded. Symptoms on the host were
/// `google.protobuf.message.DecodeError: Error parsing message with type
/// 'meshtastic.protobuf.FromRadio'` any tool with
/// 'meshtastic.protobuf.FromRadio'` - any tool with
/// `config.security.debug_log_api_enabled=true` under traffic would see
/// torn frames every few messages.
///
+3 -3
View File
@@ -87,7 +87,7 @@ void TransmitHistory::setLastSentToMesh(uint16_t key)
const uint8_t flags = (getRTCQuality() == RTCQualityNone) ? ENTRY_FLAG_BOOT_RELATIVE : ENTRY_FLAG_NONE;
history[key] = makeStoredTimestamp(now, flags);
dirty = true;
// Don't flush to disk on every transmit flash has limited write endurance.
// Don't flush to disk on every transmit - flash has limited write endurance.
// The in-memory lastMillis map handles throttle during normal operation.
// Disk is flushed: before deep sleep (sleep.cpp) and periodically here,
// throttled to at most once per 5 minutes. Always save the first time
@@ -189,7 +189,7 @@ uint32_t TransmitHistory::getLastSentToMeshMillis(uint16_t key) const
// Fall back to epoch conversion (loaded from disk after reboot)
auto it = history.find(key);
if (it == history.end() || it->second.seconds == 0) {
return 0; // No stored time module has never sent
return 0; // No stored time - module has never sent
}
// Convert to a millis()-relative timestamp: millis() - msAgo.
@@ -271,7 +271,7 @@ void TransmitHistory::clear()
}
#else
// No filesystem available provide stub with in-memory tracking
// No filesystem available - provide stub with in-memory tracking
TransmitHistory *transmitHistory = nullptr;
TransmitHistory *TransmitHistory::getInstance()
+1 -1
View File
@@ -78,7 +78,7 @@ class TransmitHistory
/**
* Wipe in-memory throttle state + remove the on-disk file. Required
* alongside rmDir("/prefs") in factoryReset otherwise the 5-min
* alongside rmDir("/prefs") in factoryReset - otherwise the 5-min
* auto-flush resurrects the file from the still-populated maps.
*/
void clear();
+1 -1
View File
@@ -94,7 +94,7 @@ meshtastic_Position TypeConversions::ConvertToPosition(meshtastic_PositionLite l
position.time = lite.time;
// Preserve the peer's broadcast precision; falls back to 0 for entries cached
// before the precision_bits field existed in PositionLite (pre-migration data).
// iOS treats 0 as "unspecified precision" and won't render the pin so for
// iOS treats 0 as "unspecified precision" and won't render the pin - so for
// unset values, declare full precision so the stored lat/lon renders as a point.
position.precision_bits = lite.precision_bits == 0 ? 32 : lite.precision_bits;
+11 -11
View File
@@ -12,13 +12,13 @@
#if defined(NRF52840_XXAA)
#include "flash/flash_nrf5x.h"
#define WARM_RING_MAGIC 0x324E5257u // "WRN2" v2: last_heard low bits carry role + protected category
#define WARM_RING_MAGIC_V1 0x474E5257u // "WRNG" v1: last_heard was a plain timestamp.
#define WARM_RING_MAGIC 0x324E5257u // "WRN2" - v2: last_heard low bits carry role + protected category
#define WARM_RING_MAGIC_V1 0x474E5257u // "WRNG" - v1: last_heard was a plain timestamp.
// v1 pages are still read on upgrade: we keep each record's identity + public key but
// DISCARD its last_heard (the old timestamp would be misread as role/protected bits).
// Records re-rank and re-learn their role on the next contact. Legacy pages convert to
// v2 naturally as the ring rotates.
// A tombstone is an entry record whose last_heard is all-ones getTime()
// A tombstone is an entry record whose last_heard is all-ones - getTime()
// (unix seconds) cannot reach 0xFFFFFFFF until 2106, and erased flash is
// detected via num == 0xFFFFFFFF before last_heard is ever inspected.
#define WARM_RING_TOMBSTONE 0xFFFFFFFFu
@@ -33,9 +33,9 @@ struct WarmStoreHeader {
};
static_assert(sizeof(WarmStoreHeader) == 16, "header layout is part of the persistence format");
#define WARM_STORE_MAGIC 0x324D5257u // "WRM2" v2: last_heard low bits carry role + protected category
#define WARM_STORE_MAGIC 0x324D5257u // "WRM2" - v2: last_heard low bits carry role + protected category
#define WARM_STORE_MAGIC_V1 \
0x314D5257u // "WRM1" v1: last_heard was a plain timestamp. On upgrade we keep
0x314D5257u // "WRM1" - v1: last_heard was a plain timestamp. On upgrade we keep
// identity + key but discard last_heard, then rewrite as v2.
#ifdef FSCom
@@ -104,7 +104,7 @@ WarmNodeEntry *WarmNodeStore::place(NodeNum num, uint32_t lastHeard, const uint8
slot = &e;
break;
}
// Compare on the time bits only the low metadata bits (role/protected) must
// Compare on the time bits only - the low metadata bits (role/protected) must
// not perturb LRU victim selection.
if (keyIsSet(e.public_key)) {
if (!oldestKeyed || warmTimeOf(e) < warmTimeOf(*oldestKeyed))
@@ -252,7 +252,7 @@ bool WarmNodeStore::saveIfDirty()
// 3 × 4 KB pages below LittleFS. Mutations append 40 B records (entry snapshot,
// or tombstone with last_heard == 0xFFFFFFFF) via the shared flash_nrf5x page
// cache; saveIfDirty() is the durability point. A full page reclaims the oldest
// (stranded live entries re-appended, then erased). Flash access holds spiLock
// (stranded live entries re-appended, then erased). Flash access holds spiLock -
// the page cache is shared with InternalFS/LittleFS.
bool WarmNodeStore::ringReadHeader(uint8_t page, WarmPageHeader &h, bool *legacy) const
@@ -289,7 +289,7 @@ void WarmNodeStore::ringOpenPage(uint8_t page)
}
// Caller holds spiLock. May recurse once via ringAppend if the stranded set
// fills the fresh page exactly bounded by WARM_NODE_COUNT <= 2*kRecordsPerPage.
// fills the fresh page exactly - bounded by WARM_NODE_COUNT <= 2*kRecordsPerPage.
void WarmNodeStore::ringRotate()
{
uint8_t target = 0;
@@ -441,7 +441,7 @@ void WarmNodeStore::load()
memset(e, 0, sizeof(*e));
}
} else {
// v1 (legacy) record: keep identity + key, but discard the old timestamp
// v1 (legacy) record: keep identity + key, but discard the old timestamp -
// its low bits would otherwise be misread as role/protected metadata.
uint32_t lh = rec.last_heard;
if (legacy) {
@@ -459,7 +459,7 @@ void WarmNodeStore::load()
nextSeq = seqs[k] + 1;
// If the head is a v1 page, force the next append to rotate into a fresh v2 page,
// so new (v2) records never land in a page whose header says v1 (which would make
// a later load discard their last_heard including the role/protected we just set).
// a later load discard their last_heard - including the role/protected we just set).
if (legacy)
writeSlot = kRecordsPerPage;
}
@@ -521,7 +521,7 @@ void WarmNodeStore::load()
{
if (!entries)
return;
// Clear first all failure paths below then correctly represent "empty",
// Clear first - all failure paths below then correctly represent "empty",
// even if load() is called on an already-used instance.
memset(entries, 0, WARM_NODE_COUNT * sizeof(WarmNodeEntry));
concurrency::LockGuard g(spiLock);
+5 -5
View File
@@ -23,21 +23,21 @@
* Warm ("long-tail") node tier.
*
* Minimal identity record (NodeNum, last_heard, Curve25519 public key) for nodes
* evicted from the hot NodeInfoLite store, so DMs to/from them keep encrypting
* evicted from the hot NodeInfoLite store, so DMs to/from them keep encrypting -
* the key is expensive to re-learn, the rest rebuilds from traffic in seconds.
* Flat fixed array, linear scan (only on hot-store misses), LRU by last_heard
* with keyed entries outranking keyless.
*
* Persistence: nRF52840 uses a 12 KB raw-flash record-ring below LittleFS
* (append + replay + compact-on-rotate see the backend in WarmNodeStore.cpp,
* (append + replay + compact-on-rotate - see the backend in WarmNodeStore.cpp,
* link-guarded by nrf52840_s140_v7.ld). Everywhere else: /prefs/warm.dat.
*/
struct WarmNodeEntry {
NodeNum num; // 0 = empty slot
uint32_t last_heard; // recency for LRU ordering see the metadata steal below
uint32_t last_heard; // recency for LRU ordering - see the metadata steal below
uint8_t public_key[32]; // all-zero = no key (a real key is never all-zero)
};
static_assert(sizeof(WarmNodeEntry) == 40, "WarmNodeEntry must stay 40 B persistence format depends on it");
static_assert(sizeof(WarmNodeEntry) == 40, "WarmNodeEntry must stay 40 B - persistence format depends on it");
// Metadata packed into the low bits of last_heard.
//
@@ -49,7 +49,7 @@ static_assert(sizeof(WarmNodeEntry) == 40, "WarmNodeEntry must stay 40 B — per
//
// Safe because: a real timestamp can never be all-ones (the tombstone sentinel) before
// 2106, and tombstones/erased flash are detected via num before last_heard is read. Only
// the LOW bits are stolen the high (era) bits are untouched, so the time range is intact.
// the LOW bits are stolen - the high (era) bits are untouched, so the time range is intact.
static constexpr uint32_t WARM_META_BITS = 6; // role(4) + protected(2)
static constexpr uint32_t WARM_META_MASK = (1u << WARM_META_BITS) - 1; // 0x3F → 64 s quantum
static constexpr uint32_t WARM_TIME_MASK = ~WARM_META_MASK; // 0xFFFFFFC0
+4 -4
View File
@@ -26,7 +26,7 @@ static constexpr const char *PROTOBUF_SCHEMA =
// PhoneAPI subclass for the Ethernet HTTP transport. Mirrors mesh/http/HttpAPI
// but lives outside the MESHTASTIC_EXCLUDE_WEBSERVER gate (which is ESP32-only).
// A single instance is shared between the HTTP and HTTPS servers since they
// represent the same logical "phone" same state machine, same packet queue.
// represent the same logical "phone" - same state machine, same packet queue.
class EthHttpAPI : public PhoneAPI
{
public:
@@ -192,7 +192,7 @@ static bool handleFromRadio(IStreamReadWrite &client, const Request &req)
// Buffer all packets first so we can emit an accurate Content-Length and
// keep the connection alive. Phase 2 used Connection: close framing, which
// forced clients to redo the TLS handshake (~625 ms) for every single
// /fromradio poll client.meshtastic.org needs dozens of those during
// /fromradio poll - client.meshtastic.org needs dozens of those during
// initial sync, so the user-visible load time was 15-30 s of pure
// handshakes. With Content-Length + keep-alive a whole sync rides one
// handshake. Buffer is dynamic (std::vector) so the common 1-packet case
@@ -290,7 +290,7 @@ void handleApiClient(IStreamReadWrite &client)
// the parent OSThread is not returning to mainController, and the
// RP2350 hardware watchdog (8 s default in arduino-pico) only gets
// pet by the main loop. A client.meshtastic.org sync produces ~80
// back-to-back requests over a single TLS session well past the
// back-to-back requests over a single TLS session - well past the
// watchdog deadline. yield() between requests lets the rest of core0
// (Periodic ticks, NTP, MQTT, LoRa packet pump) run + pets the
// watchdog; the cap puts a hard ceiling so a chatty client can never
@@ -320,7 +320,7 @@ void handleApiClient(IStreamReadWrite &client)
keepAlive = handleToRadio(client, req);
} else {
sendError(client, 404, "Not Found", "unknown endpoint");
return; // errors are terminal Connection: close framing
return; // errors are terminal - Connection: close framing
}
// A handler that emitted an error advertised Connection: close. Stop the
// keep-alive loop so any unread/leftover body bytes (e.g. after a 408
+3 -3
View File
@@ -13,14 +13,14 @@
// transports without recompiling the handlers.
//
// Inherits Print so all `print(int)`, `print(const char *)`, `print(char)`
// helpers are available for free the only thing implementations have to
// helpers are available for free - the only thing implementations have to
// supply on the write side is `write(uint8_t)` + the bulk `write(buf, len)`.
class IStreamReadWrite : public Print
{
public:
virtual ~IStreamReadWrite() = default;
// Write side Print pure virtual + bulk override
// Write side - Print pure virtual + bulk override
size_t write(uint8_t b) override = 0;
size_t write(const uint8_t *buf, size_t len) override = 0;
using Print::write; // bring in write(const char *str) and friends
@@ -34,7 +34,7 @@ class IStreamReadWrite : public Print
virtual bool connected() = 0;
void flush() override = 0; // Print::flush is virtual void with empty default
// Logging helper used by request log line
// Logging helper - used by request log line
virtual IPAddress remoteIP() = 0;
};
+2 -2
View File
@@ -83,7 +83,7 @@ static EthApiServerThread *apiThread = nullptr;
void initEthApiServer()
{
// Bind the listener (idempotent deInitEthApiServer() drops apiServer on a
// Bind the listener (idempotent - deInitEthApiServer() drops apiServer on a
// W5500 reset, and this rebinds it on the restart path).
if (!apiServer) {
apiServer = new EthernetServer(ETH_API_PORT);
@@ -92,7 +92,7 @@ void initEthApiServer()
}
// The worker is created once and kept for the lifetime of the process. It
// idles harmlessly while apiServer is null (runOnce guards on it), so we
// never delete it from another thread's runOnce that would corrupt the
// never delete it from another thread's runOnce - that would corrupt the
// scheduler's thread list mid-iteration.
if (!apiThread)
apiThread = new EthApiServerThread(); // OSThread base auto-registers with the scheduler
+1 -1
View File
@@ -8,7 +8,7 @@
/// Initialize the Ethernet HTTP API server (call after Ethernet is connected).
/// Spawns an internal OSThread that polls accept() on a sub-second cadence,
/// independent of the 5s Ethernet client periodic needed because the web
/// independent of the 5s Ethernet client periodic - needed because the web
/// client makes many small back-to-back requests.
void initEthApiServer();
+6 -6
View File
@@ -30,7 +30,7 @@ static constexpr const char *CERT_PATH = "/eth_cert_v2.der";
static constexpr const char *KEY_PATH = "/eth_key_v2.der";
static constexpr const char *IP_PATH = "/eth_cert_ip_v2.txt";
// Random callback for mbedtls sources entropy from the RP2350 ROSC TRNG via
// Random callback for mbedtls - sources entropy from the RP2350 ROSC TRNG via
// pico-sdk get_rand_64(). Used directly as f_rng in mbedtls calls so we don't
// have to plumb a full mbedtls_entropy_context + ctr_drbg. The hardware TRNG
// is cryptographically suitable per pico-sdk docs (ROSC + whitening).
@@ -175,7 +175,7 @@ static bool generateCert(IPAddress ip, EthCertMaterial &out)
}
// ExtendedKeyUsage: serverAuth. NSS / Firefox refuse to treat a cert
// as a TLS server cert without this extension since 2023 the error
// as a TLS server cert without this extension since 2023 - the error
// surfaces as a non-overridable "Secure Connection Failed" with no
// "Accept the Risk" path.
mbedtls_asn1_sequence ekuSeq;
@@ -293,13 +293,13 @@ bool ensureCertForIp(IPAddress ip, EthCertMaterial &out)
// current process still has the in-memory cert ready for use.
//
// Clear the IP commit-marker FIRST so a reset mid-write can't leave the marker
// pointing at a half-written (or stale-paired) cert/key the load path only
// pointing at a half-written (or stale-paired) cert/key - the load path only
// trusts the cache when the marker matches. Writing the marker LAST commits the
// new pair atomically w.r.t. the loader.
writeText(IP_PATH, "");
if (!writeBinary(CERT_PATH, out.certDer.data(), out.certDer.size()) ||
!writeBinary(KEY_PATH, out.keyDer.data(), out.keyDer.size()) || !writeText(IP_PATH, ipStr)) {
LOG_WARN("ETH CERT: persist failed will regenerate next boot");
LOG_WARN("ETH CERT: persist failed - will regenerate next boot");
} else {
LOG_INFO("ETH CERT: persisted to LittleFS");
}
@@ -310,7 +310,7 @@ bool ensureCertForIp(IPAddress ip, EthCertMaterial &out)
// Worker that defers cert gen off the Periodic thread (which has a tight stack
// and ticks every 5s alongside reconnect / NTP / MQTT). Waits for a non-zero IP,
// generates/loads the cert for it, then keeps polling at CERT_RECHECK_MS so a
// DHCP lease change to a new IP regenerates the cert the SAN must track the
// DHCP lease change to a new IP regenerates the cert - the SAN must track the
// current address or browsers reject the new one. The steady-state poll is just
// localIP() + compare; ECDSA keygen only reruns when the IP actually changes,
// and it runs on this thread's own stack (not the Periodic's).
@@ -337,7 +337,7 @@ class EthCertThread : public concurrency::OSThread
// regenerates whenever its saved IP != ip, so the cert SAN follows.
bool ok = ensureCertForIp(ip, material_);
if (!ok) {
LOG_ERROR("ETH CERT: pipeline FAILED TLS server will not start");
LOG_ERROR("ETH CERT: pipeline FAILED - TLS server will not start");
// Don't leave isReady() reporting true with empty material: a later TLS
// teardown (e.g. a W5500 reset) would then fail initTlsContext() and stay
// disabled. Clear readiness so the TLS worker waits and the next poll
+1 -1
View File
@@ -35,7 +35,7 @@ bool isEthCertReady();
// Snapshot of the generated material once isEthCertReady(). Empty otherwise.
const EthCertMaterial &getEthCert();
// Monotonic counter bumped each time the cert is (re)generated e.g. when a DHCP
// Monotonic counter bumped each time the cert is (re)generated - e.g. when a DHCP
// lease change moves us to a new IP. The TLS server reloads when this changes.
uint32_t getEthCertGeneration();
+4 -4
View File
@@ -17,7 +17,7 @@
#include "mesh/eth/ethTlsApiServer.h"
#endif
#ifdef USE_ARDUINO_ETHERNET
#include <Ethernet.h> // arduino-libraries/Ethernet supports W5100/W5200/W5500
#include <Ethernet.h> // arduino-libraries/Ethernet - supports W5100/W5200/W5500
// Shorter DHCP timeout so LoRa startup isn't blocked when no DHCP server is present.
#define ETH_DHCP_TIMEOUT_MS 10000
#else
@@ -181,12 +181,12 @@ static int32_t reconnectETH()
initEthApiServer();
#endif
#if HAS_ETHERNET && defined(HAS_ETHERNET_TLS_API) && defined(ARCH_RP2040)
// Phase 2.1-bis cert gen runs on its own OSThread so ECDSA keygen
// Phase 2.1-bis - cert gen runs on its own OSThread so ECDSA keygen
// + DER encoding + LittleFS write don't share the Periodic stack
// (which overflowed in the original inline attempt). The thread
// polls for a non-zero IP itself and runs once.
initEthCertThread();
// Phase 2.2 TLS server skeleton on TCP/443. The worker waits
// Phase 2.2 - TLS server skeleton on TCP/443. The worker waits
// until the cert thread signals isEthCertReady() before binding.
initEthTlsApiServer();
#endif
@@ -220,7 +220,7 @@ static int32_t reconnectETH()
#if HAS_ETHERNET && defined(HAS_ETHERNET_OTA)
ethOTALoop();
#endif
// ethApiServer runs on its own OSThread (20ms ticks) not polled here.
// ethApiServer runs on its own OSThread (20ms ticks) - not polled here.
return 5000; // every 5 seconds
}
+7 -7
View File
@@ -39,7 +39,7 @@ static const uint32_t OTA_AUTH_COOLDOWN_MS = 5000; // 5s cooldown after failed a
static const size_t OTA_NONCE_SIZE = 32;
static const size_t OTA_HASH_SIZE = 32;
// OTA PSK override via USERPREFS_OTA_PSK in userPrefs.jsonc
// OTA PSK - override via USERPREFS_OTA_PSK in userPrefs.jsonc
// USERPREFS_OTA_PSK is stringified by PlatformIO (wrapped in quotes), so we
// use a char[] and sizeof-1 to exclude the trailing NUL byte from the hash.
#ifdef USERPREFS_OTA_PSK
@@ -96,7 +96,7 @@ static void computeAuthHash(const uint8_t *nonce, size_t nonceLen, const uint8_t
/// Challenge-response authentication. Returns true if client is authenticated.
static bool authenticateClient(EthernetClient &client)
{
// Rate-limit after failed auth close silently so the error byte is not
// Rate-limit after failed auth - close silently so the error byte is not
// misinterpreted as part of the nonce by a re-trying client.
if (lastAuthFailure != 0 && (millis() - lastAuthFailure) < OTA_AUTH_COOLDOWN_MS) {
LOG_WARN("ETH OTA: Auth cooldown active, rejecting connection");
@@ -140,7 +140,7 @@ static bool authenticateClient(EthernetClient &client)
return false;
}
// Auth success send ACK
// Auth success - send ACK
client.write(OTA_ACK);
LOG_INFO("ETH OTA: Authentication successful");
return true;
@@ -179,14 +179,14 @@ static void handleOTAClient(EthernetClient &client)
return;
}
// Begin the update this opens firmware.bin on LittleFS
// Begin the update - this opens firmware.bin on LittleFS
if (!Update.begin(hdr.firmwareSize)) {
LOG_ERROR("ETH OTA: Update.begin() failed, error=%u", Update.getError());
client.write(OTA_ERR_BEGIN);
return;
}
// ACK the header client can start sending firmware data
// ACK the header - client can start sending firmware data
client.write(OTA_ACK);
// Receive firmware in chunks
@@ -252,7 +252,7 @@ static void handleOTAClient(EthernetClient &client)
return;
}
// Finalize this calls picoOTA.commit() which stages the update for the
// Finalize - this calls picoOTA.commit() which stages the update for the
// bootloader
if (!Update.end(true)) {
LOG_ERROR("ETH OTA: Update.end() failed, error=%u", Update.getError());
@@ -265,7 +265,7 @@ static void handleOTAClient(EthernetClient &client)
client.flush();
delay(500);
// Reboot the built-in bootloader will apply the update from LittleFS
// Reboot - the built-in bootloader will apply the update from LittleFS
rp2040.reboot();
}
+8 -8
View File
@@ -32,7 +32,7 @@ static constexpr uint32_t MEDIUM_THRESHOLD_MS = 30000;
static constexpr int32_t ACTIVE_INTERVAL_MS = 20;
static constexpr int32_t MEDIUM_INTERVAL_MS = 100;
static constexpr int32_t IDLE_INTERVAL_MS = 500;
// Matches the keep-alive idle window in ethApiHandlers if the handler
// Matches the keep-alive idle window in ethApiHandlers - if the handler
// loop calls read() and netRecv blocked for 10 s, the 3 s idle deadline
// inside parseRequest would be irrelevant and the OSThread would stay stuck
// long after a quiet browser closed its end of the TCP socket.
@@ -52,7 +52,7 @@ static int picoRand(void * /*ctx*/, unsigned char *out, size_t len)
return 0;
}
// One-shot TLS context lives in BSS keeps mbedtls allocations off the
// One-shot TLS context lives in BSS - keeps mbedtls allocations off the
// OSThread stack (lesson from Phase 2.1-bis: stack budget is tight on M33).
static EthernetServer *tlsServer = nullptr;
static mbedtls_x509_crt certChain;
@@ -73,7 +73,7 @@ static int netSend(void *ctx, const unsigned char *buf, size_t len)
// Block-with-yield until the W5500 TX buffer can absorb the chunk.
// Returning WANT_WRITE without delay made mbedtls_ssl_handshake() spin
// at ~180k iter/s when Chrome was slow to drain the socket during the
// ECDHE-ECDSA ServerKeyExchange the original code logged exactly that
// ECDHE-ECDSA ServerKeyExchange - the original code logged exactly that
// signature (ret=-0x6880 / WANT_WRITE) tight-looping forever. Firefox
// happened to read fast enough that the buffer never filled.
uint32_t t0 = millis();
@@ -96,12 +96,12 @@ static int netRecv(void *ctx, unsigned char *buf, size_t len)
// Block-with-timeout: spin until bytes arrive, the peer closes, or we
// exceed the per-recv budget. Pure non-blocking (return WANT_READ) would
// require mbedtls_ssl_handshake to be driven from the runOnce dispatcher
// overkill for the Phase 2.2 skeleton with a single in-flight session.
// - overkill for the Phase 2.2 skeleton with a single in-flight session.
//
// Pet the 8 s hardware watchdog from inside the poll loop. We sit here
// for up to RECV_TIMEOUT_MS waiting for the next keep-alive request, and
// a quiet client can string two such waits back-to-back (6 s) plus the
// earlier handshake/handler time easily past the watchdog deadline.
// earlier handshake/handler time - easily past the watchdog deadline.
// The main loop()'s watchdog_update() never runs while the OSThread is
// inside serveClient(), so it has to be done here.
uint32_t t0 = millis();
@@ -152,7 +152,7 @@ class MbedTlsStream : public IStreamReadWrite
size_t pending = mbedtls_ssl_get_bytes_avail(ssl_);
if (pending > 0)
return (int)pending;
// Best-effort: report network bytes (rough proxy handlers usually
// Best-effort: report network bytes (rough proxy - handlers usually
// call read() in a loop and tolerate slow streams).
return client_->available();
}
@@ -197,7 +197,7 @@ class EthTlsApiServerThread : public concurrency::OSThread
if (!isEthCertReady())
return 500;
if (!initTlsContext())
return INT32_MAX; // hard fail TLS server stays disabled
return INT32_MAX; // hard fail - TLS server stays disabled
loadedCertGen_ = getEthCertGeneration();
tlsReady = true;
}
@@ -330,7 +330,7 @@ void deInitEthTlsApiServer()
{
// A W5500 chip reset leaves tlsServer bound to a dead socket and the cached
// mbedTLS context stale. Reset the worker back to Phase A (free the context,
// drop the listener, clear tlsReady) WITHOUT deleting the OSThread its next
// drop the listener, clear tlsReady) WITHOUT deleting the OSThread - its next
// runOnce re-waits for isEthCertReady() and rebuilds the context + rebinds
// TCP/443. Safe to free here: this runs in reconnectETH (ethConnect thread),
// and the cooperative scheduler guarantees tlsThread is not mid-runOnce, so
+3 -3
View File
@@ -184,7 +184,7 @@ typedef struct _meshtastic_LockdownAuth {
token at unlock time: the client-supplied boots_remaining when
non-zero, otherwise the firmware default (TOKEN_DEFAULT_BOOTS).
Note that boots_remaining == 0 in this message means "use firmware
default", NOT "zero boots" a client computing the ceiling for
default", NOT "zero boots" - a client computing the ceiling for
display should mirror that resolution rather than multiplying the
raw request value.
@@ -194,7 +194,7 @@ typedef struct _meshtastic_LockdownAuth {
Uses millis() (CPU uptime), not wall-clock time, so the cap is
immune to GPS spoofing, RTC backup-battery removal, and Faraday
cage isolation none of those move the uptime counter. The only
cage isolation - none of those move the uptime counter. The only
way to reset the session clock is a reboot, which costs a boot
from the on-flash, HMAC-bound counter. */
uint32_t max_session_seconds;
@@ -211,7 +211,7 @@ typedef struct _meshtastic_LockdownAuth {
NOT reversed by this operation: APPROTECT. Once the debug port
lockout has been burned (on silicon where it is effective) it is
permanent disabling lockdown decrypts your data and removes the
permanent - disabling lockdown decrypts your data and removes the
access gates, but the SWD/JTAG port stays locked for the life of
the device (recoverable only via a full chip erase over a debug
probe, which destroys all data). Clients should make this
+23 -23
View File
@@ -332,7 +332,7 @@ typedef enum _meshtastic_CotType {
/* y-: TAKTALK room/membership broadcast. Payload carried via the
TakTalkRoomData typed variant (sender_callsign, room_id, room_name,
participants). The CoT type literally has a trailing dash and no
second atom not a typo. */
second atom - not a typo. */
meshtastic_CotType_CotType_y = 126
} meshtastic_CotType;
@@ -380,7 +380,7 @@ typedef enum _meshtastic_DrawnShape_Kind {
/* u-r-b-bullseye: Bullseye ring with range rings and bearing reference */
meshtastic_DrawnShape_Kind_Kind_Bullseye = 7,
/* u-d-c-e: Ellipse with distinct major/minor axes (same storage as
Kind_Circle uses major_cm/minor_cm/angle_deg but receivers
Kind_Circle - uses major_cm/minor_cm/angle_deg - but receivers
render it as a non-circular ellipse rather than a round circle). */
meshtastic_DrawnShape_Kind_Kind_Ellipse = 8,
/* u-d-v: 2D vehicle outline drawn on the map. Vertices carry the
@@ -400,7 +400,7 @@ typedef enum _meshtastic_DrawnShape_Kind {
end of parse; builder uses it to decide which of <strokeColor> /
<fillColor> to emit in the reconstructed XML. */
typedef enum _meshtastic_DrawnShape_StyleMode {
/* Unspecified receiver infers from which color fields are non-zero. */
/* Unspecified - receiver infers from which color fields are non-zero. */
meshtastic_DrawnShape_StyleMode_StyleMode_Unspecified = 0,
/* Stroke only. No <fillColor> in the source XML. Used for polylines,
ranging lines, bullseye rings. */
@@ -417,7 +417,7 @@ typedef enum _meshtastic_DrawnShape_StyleMode {
alone is ambiguous (e.g. a-u-G could be a 2525 symbol or a custom icon
depending on the iconset path). */
typedef enum _meshtastic_Marker_Kind {
/* Unspecified fall back to TAKPacketV2.cot_type_id */
/* Unspecified - fall back to TAKPacketV2.cot_type_id */
meshtastic_Marker_Kind_Kind_Unspecified = 0,
/* b-m-p-s-m: Spot map marker */
meshtastic_Marker_Kind_Kind_Spot = 1,
@@ -680,10 +680,10 @@ typedef struct _meshtastic_AircraftTrack {
hundred meters of the anchor has per-vertex deltas in the ±10^4 range.
Under sint32+zigzag those encode as 2 bytes each (tag+varint), versus the
4 bytes that sfixed32 would always require. At 32 vertices that is ~128
bytes of savings the difference between fitting under the LoRa MTU or
bytes of savings - the difference between fitting under the LoRa MTU or
not. Absolute coordinates (values ~10^9) would cost sint32 varint 5 bytes
per field, which is why TAKPacketV2's top-level latitude_i / longitude_i
stay sfixed32 only small values win with sint32. */
stay sfixed32 - only small values win with sint32. */
typedef struct _meshtastic_CotGeoPoint {
/* Latitude delta from TAKPacketV2.latitude_i, in 1e-7 degree units.
Add to the enclosing event's latitude_i to recover the absolute latitude. */
@@ -791,7 +791,7 @@ typedef struct _meshtastic_Marker {
Covers CoT type u-rb-a. The anchor position is on
TAKPacketV2.latitude_i/longitude_i; the target endpoint is carried as a
CotGeoPoint same delta-from-anchor encoding used by DrawnShape.vertices
CotGeoPoint - same delta-from-anchor encoding used by DrawnShape.vertices
so a self-anchored RAB (common case) encodes in zero bytes. */
typedef struct _meshtastic_RangeAndBearing {
/* Target/anchor endpoint (delta-encoded from TAKPacketV2.latitude_i/longitude_i). */
@@ -899,12 +899,12 @@ typedef struct _meshtastic_CasevacReport {
same as the envelope callsign but ATAK sometimes carries a distinct
ops-number here. */
pb_callback_t title;
/* Primary medline free-text the single most clinically important line
/* Primary medline free-text - the single most clinically important line
on a MEDLINE form (e.g. "2 urgent litter patients, smoke on approach").
MUST be preserved under MTU pressure as long as any casevac is sent. */
pb_callback_t medline_remarks;
/* Line 3 (newer ATAK format): patient counts by precedence level.
Coexists with the enum-style `precedence` field (tag 1) older ATAK
Coexists with the enum-style `precedence` field (tag 1) - older ATAK
emits a single enum, newer ATAK emits these counts, and both can be
set simultaneously. Senders populate whichever style(s) the source
XML had; receivers prefer counts when non-zero. */
@@ -946,19 +946,19 @@ typedef struct _meshtastic_CasevacReport {
(e.g. "Primary HLZ is soccer field"). */
pb_callback_t hlz_remarks;
/* Per-patient clinical records. Each entry is one patient's ZMIST card
(Zap number / Mechanism / Injuries / Signs / Treatment). Repeatable
(Zap number / Mechanism / Injuries / Signs / Treatment). Repeatable -
a mass-casualty event can carry 1-6 entries in practice, limited by
the 237 B LoRa MTU. */
pb_callback_t zmist;
} meshtastic_CasevacReport;
/* Per-patient clinical summary record one entry per patient in a CASEVAC.
/* Per-patient clinical summary record - one entry per patient in a CASEVAC.
Maps directly to ATAK's <zMist> child element inside <zMistsMap>.
All fields are optional free-text; senders populate what they have. */
typedef struct _meshtastic_ZMistEntry {
/* Patient identifier / sequence label (e.g. "ZMIST-1", "ZMIST-2"). */
pb_callback_t title;
/* Zap number unique patient tracking ID (often a terse code like
/* Zap number - unique patient tracking ID (often a terse code like
"Gunshot" or a serial). */
pb_callback_t z;
/* Mechanism of injury (e.g. "Penetrating trauma", "Blast injury"). */
@@ -997,7 +997,7 @@ typedef struct _meshtastic_EmergencyAlert {
creation time; the fields below carry structured metadata the raw-detail
fallback currently loses.
Fields are deliberately lean this variant is closer to the MTU ceiling
Fields are deliberately lean - this variant is closer to the MTU ceiling
than the others, so every string is capped in options. */
typedef struct _meshtastic_TaskRequest {
/* Short tag for the task category (e.g. "engage", "observe", "recon",
@@ -1017,7 +1017,7 @@ typedef struct _meshtastic_TaskRequest {
/* Weather annotation from <environment> CoT detail element.
Attaches to any TAKPacketV2 regardless of payload_variant an Aircraft,
Attaches to any TAKPacketV2 regardless of payload_variant - an Aircraft,
PLI, or Marker can all carry observed conditions at the emitting station.
ATAK-CIV ships an XSD for <environment> but no dedicated handler, so the
element round-trips through the generic detail pipeline; this message
@@ -1026,7 +1026,7 @@ typedef struct _meshtastic_TaskRequest {
Target wire cost: ~6-8 bytes compressed with a fully populated instance.
Named `TAKEnvironment` (not just `Environment`) because the bare name
collides with `SwiftUI.Environment` every SwiftUI view in a consuming
collides with `SwiftUI.Environment` - every SwiftUI view in a consuming
iOS app uses the `@Environment` property wrapper, and importing the
generated proto module would make `Environment` ambiguous in every one
of those files. The `TAK` prefix matches the convention used by the
@@ -1055,7 +1055,7 @@ typedef struct _meshtastic_TAKEnvironment {
The receiving ATAK client restores those from its own defaults, same as
every other CoT carried over Meshtastic today.
Attaches to any TAKPacketV2 a PLI with a sensor on the operator's head,
Attaches to any TAKPacketV2 - a PLI with a sensor on the operator's head,
an Aircraft with a FLIR turret, a Marker dropped on a UAV.
Target wire cost: ~7-14 bytes compressed (dominated by model string). */
typedef struct _meshtastic_SensorFov {
@@ -1065,30 +1065,30 @@ typedef struct _meshtastic_SensorFov {
SensorDetailHandler default (270°) and save varint bytes over centi-deg. */
uint32_t azimuth_deg;
/* Maximum range of the cone in meters.
Optional if unset, receivers should use the ATAK-CIV default of 100m. */
Optional - if unset, receivers should use the ATAK-CIV default of 100m. */
bool has_range_m;
uint32_t range_m;
/* Horizontal field of view in whole degrees (cone's angular width).
ATAK-CIV default is 45°. */
uint32_t fov_horizontal_deg;
/* Vertical field of view in whole degrees. ATAK-CIV default is 45°.
Optional a value of 0 means "not set / use horizontal FOV". */
Optional - a value of 0 means "not set / use horizontal FOV". */
uint32_t fov_vertical_deg;
/* Elevation angle in whole degrees. Positive = up, negative = down.
Range -90 to +90. sint32 for varint efficiency on small negatives. */
int32_t elevation_deg;
/* Roll (camera tilt) in whole degrees, -180 to +180.
Optional use 0 if the sensor doesn't track roll. */
Optional - use 0 if the sensor doesn't track roll. */
int32_t roll_deg;
/* Free-form device model identifier, e.g. "FLIR-Boson-640", "SEEK".
Optional empty string means "unknown model" (ATAK-CIV default). */
Optional - empty string means "unknown model" (ATAK-CIV default). */
pb_callback_t model;
} meshtastic_SensorFov;
/* TAKTALK chat message payload (CoT type m-t-t).
TAKTALK is an ATAK plugin for voice + text team messaging. The voice
audio stream goes over UDP/RTP and is NOT carried by the mesh only
audio stream goes over UDP/RTP and is NOT carried by the mesh - only
the text envelope (this message) is. `from_voice` marks messages sent
via push-to-talk speech-to-text so receivers can render a mic icon
next to the text.
@@ -1122,7 +1122,7 @@ typedef struct _meshtastic_TakTalkMessage {
Announces a TAKTALK chatroom's friendly name and roster so peers can
resolve room UUIDs (used in TakTalkMessage.chatroom_id and
GeoChat.room_id) to a display name and participant list. Not a chat
message itself these events are emitted by TAKTALK when rooms are
message itself - these events are emitted by TAKTALK when rooms are
created or memberships change. */
typedef struct _meshtastic_TakTalkRoomData {
/* Callsign of the device broadcasting the room state (typically the
@@ -1161,7 +1161,7 @@ typedef struct _meshtastic_Marti {
primary-vs-cc distinction the same way ATAK does.
If dest_callsign is [TAKPacketV2.callsign] (self-addressed, unusual but
legal e.g. ATAK echoing back to its own room), the builder still emits
legal - e.g. ATAK echoing back to its own room), the builder still emits
the element so loopback shapes round-trip cleanly. */
pb_callback_t dest_callsign;
} meshtastic_Marti;
+9 -9
View File
@@ -1244,15 +1244,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];
@@ -15,7 +15,7 @@
/* Payload for MESH_BEACON_APP packets.
Periodically broadcast by nodes in beacon mode.
Listeners deliver the text message to the local inbox and cache any offered
channel/preset for the client app to act on the firmware never auto-applies them. */
channel/preset for the client app to act on - the firmware never auto-applies them. */
typedef struct _meshtastic_MeshBeacon {
/* Human-readable beacon message. Max 100 bytes enforced by firmware on send. */
char message[101];
@@ -497,7 +497,7 @@ typedef struct _meshtastic_ModuleConfig_MeshBeaconConfig {
/* 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
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;
@@ -514,7 +514,7 @@ typedef struct _meshtastic_ModuleConfig_MeshBeaconConfig {
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
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). */
+3 -3
View File
@@ -125,7 +125,7 @@ static inline int get_max_num_nodes()
#elif defined(NRF52840_XXAA)
// Keyed on the NRF52840_XXAA build flag, not ARCH_NRF52: the latter (from
// architecture.h via configuration.h) isn't defined this early in every include
// chain. Backed by the raw-flash ring below LittleFS see WarmNodeStore.h.
// chain. Backed by the raw-flash ring below LittleFS - see WarmNodeStore.h.
#define WARM_NODE_COUNT 200
#elif (defined(CONFIG_IDF_TARGET_ESP32S3) && defined(BOARD_HAS_PSRAM)) || defined(ARCH_PORTDUINO)
#define WARM_NODE_COUNT 2000 // PSRAM-equipped ESP32-S3 / native host; warm cache in PSRAM (~80 KB)
@@ -137,7 +137,7 @@ static inline int get_max_num_nodes()
#define WARM_NODE_COUNT 150 // RP2040 (264 KB) / RP2350 (520 KB): bounded so warm.dat write fits the 8s watchdog (#10746)
#else
// nRF52840 is handled explicitly above (200, raw-flash ring). Any other nRF52 (non-XXAA) and any
// future non-ESP32/non-RP LittleFS part fall through to this 320 default flag for review if such a
// future non-ESP32/non-RP LittleFS part fall through to this 320 default - flag for review if such a
// RAM-constrained nRF52 target is ever added.
#define WARM_NODE_COUNT 320 // other LittleFS-backed parts (e.g. non-nRF52840 nRF52)
#endif // platform
@@ -178,7 +178,7 @@ static inline int get_max_num_nodes()
#elif defined(ARCH_ESP32)
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 400 // classic ESP32 / S2 / C3: tightest free heap, ~4 KB (#10705)
#else
// nRF52 (incl. nRF52840) and RP2040/RP2350 fall through here there is no nRF/RP branch above,
// nRF52 (incl. nRF52840) and RP2040/RP2350 fall through here - there is no nRF/RP branch above,
// by design. These parts have no ESP32-style WiFi+BLE coexistence eating the heap, so the larger
// 1000-entry (~10 KB) cache fits: nRF52840 is BLE-only on 256 KB RAM; RP2040/RP2350 have 264/520 KB.
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 1000 // nRF52 / RP2040 / RP2350 / other non-ESP32
+1 -1
View File
@@ -46,7 +46,7 @@ Author: Marc Philipp Hammermann
mail: marchammermann@googlemail.com
*/
// Mirrors the guard in PiWebServer.h see comment there. macOS Homebrew
// Mirrors the guard in PiWebServer.h - see comment there. macOS Homebrew
// provides ulfius + deps; Linux pulls them via apt. Either way, this
// translation unit only compiles when the headers are present.
#ifdef ARCH_PORTDUINO
+1 -1
View File
@@ -72,7 +72,7 @@ class UdpMulticastHandler final
LOG_DEBUG("Decoding MeshPacket from UDP len=%u", packetLength);
bool isPacketDecoded = pb_decode_from_bytes(packet.data(), packetLength, &meshtastic_MeshPacket_msg, &mp);
if (isPacketDecoded && router && mp.which_payload_variant == meshtastic_MeshPacket_encrypted_tag) {
// Drop packets with spoofed local origin no legitimate LAN node should send from=0 or our own nodeNum
// Drop packets with spoofed local origin - no legitimate LAN node should send from=0 or our own nodeNum
if (isFromUs(&mp)) {
LOG_WARN("UDP packet with spoofed local from=0x%08x, dropping", mp.from);
return;