* NodeDB: 3-tier node store with persistent warm tier (long-tail identity retention)
Introduces a tiered NodeDB so the device retains identity (public key,
last_heard) for far more nodes than fit in the full-record hot store,
without growing heap or the persisted nodes.proto unboundedly.
- Hot store: full NodeInfoLite, MAX_NUM_NODES (120 on nRF52).
- Satellite maps: position/telemetry/environment/status capped at
MAX_SATELLITE_NODES (40 freshest); eviction via enforceSatelliteCaps /
evictSatelliteOverCap.
- Warm tier (WarmNodeStore): 40 B {num,last_heard,public_key} records for
evicted nodes so DMs to/from long-tail nodes keep encrypting/decrypting.
Persisted to /prefs/warm.dat, or on nRF52840 a dedicated 12 KB raw-flash
record-ring below LittleFS (3x4 KB pages; see linker scripts + the
nrf52_warm_region.py post-link guard).
NodeDB::getOrCreateMeshNode now demotes evicted nodes into the warm tier and
re-admits them (restoring key/last_heard). Router PKI decrypt/encode resolve
the peer key via NodeDB::copyPublicKey (hot store, then warm tier).
NodeInfoLite gains snr_q4 (sint32, Q4-encoded dB); the float snr is zeroed on
disk. NodeInfoLite grows 105 -> 112 B; backup 2432 -> 2468 B.
Note: the snr_q4 .proto change still needs to land in the protobufs submodule
(generated header is updated here; submodule pointer left at upstream).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* NodeDB: robust receive + retention for blocked (ignored) nodes
Hardens how ignored/favourite nodes are received over admin and retained,
closing paths where a block could be lost or accidentally cleared.
- Blocking keeps the node's public key (admin set_ignored_node and
addFromContact no longer zero it / drop the warm-tier key), so a blocked
peer stays a verifiable identity.
- set_ignored_node creates the node if absent, so a block by node ID sticks
even for a node we've never heard from (e.g. pushed by a remote admin) with
no NodeInfo or key.
- Eviction protection (favourite/ignored/manually-verified) now also applies to
the load-time hot-store migration and is never undone by cleanupMeshDB, which
previously purged ignored nodes that lacked user info.
- The hot-store migration leaves our own node (index 0) in place and prefers to
demote non-protected nodes, like the runtime eviction scan.
Caps the protected set (favourite + ignored + verified) at MAX_NUM_NODES-2 via
NodeDB::setProtectedFlag(), so at least two evictable slots always remain and
getOrCreateMeshNode can always make room — replacing the previous unconditional
append that could run off the end of the node vector when every node was
protected. A locally-set favourite/ignore that hits the cap reports back to the
phone via a ClientNotification.
Adds test_nodedb_blocked covering the migration, favourite/ignored eviction
protection, ignored-survives-cleanup, and the protected-node cap. The
maintenance methods stay private in production; the test reaches them through a
PIO_UNIT_TESTING-guarded friend shim.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts:
# src/mesh/NodeDB.h
* fix copilot comments
* once again
* WarmNodeStore: fix cppcheck warnings (uninitvar, constVariablePointer)
Zero-initialise `stranded[]` and `seqs[]/order[]` VLAs so cppcheck can
verify there are no unguarded reads of uninitialised memory (the guards
exist but are not visible to static analysis). Mark two local pointers
`const` where the pointed-to entry is never mutated after assignment.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* self-care added to assist 2.7 and 2.8 nodedb migration
* Tidy warm-store/self-care: comments, guards, log + flash cleanup
Style/cleanup pass over the branch (no behavior change except the noted
preprocessor simplifications, which are semantically identical):
- Comments: move function descriptions to the headers, cap in-function
comments at ~3-4 lines, drop leading-number step markers, label stacked
#endif blocks, de-decorate banner comments.
- dumpToLog: fully gate decl + definition + AdminModule call site behind
MESHTASTIC_NODEDB_MIGRATION_VERBOSE so it compiles out when disabled
(~1.2 KB when off).
- mesh-pb-constants: drop the dead nRF52832 WARM_NODE_COUNT branch and trim
the macro docs.
- WarmNodeStore: simplify the redundant `ARCH_NRF52 && NRF52840_XXAA` guards
to `NRF52840_XXAA`, add a kNoPage sentinel for the ring page state.
- Shorten the always-on LOG_WARN strings (~120 B flash).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* more tidying up, aligning with docs and undoing other-arch regressions
* Update protobufs (#19)
Co-authored-by: NomDeTom <116762865+NomDeTom@users.noreply.github.com>
* made the migration pathway cleareer
* address copilot review
* fixed a copilot review on a downstream PR.
* Address Copilot review comments for PR #10705 (warmstore/nodedb)
- WarmNodeStore.h: default MIGRATION_VERBOSE to 0 (suppress info-level
chatter on production builds; opt in with =1)
- WarmNodeStore.cpp load(): move memset to top of function so all
failure paths (header-read fail, invalid header) leave entries clear
- WarmNodeStore.cpp save(): replace manual spiLock lock/unlock around
mkdir with LockGuard covering the full SafeFile sequence, matching
the lock discipline in load()
- Router.cpp: memcpy(&p->public_key.bytes, ...) -> memcpy(p->public_key.bytes,
...) — pass decayed uint8_t* rather than pointer-to-array
- AdminModule.cpp: check setProtectedFlag return for PKC auto-favorite;
log cap-refusal warning instead of unconditional "auto-favoriting"
- nrf52_warm_region.py: error message references both v6.ld and v7.ld
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* NodeDB: formatting cleanup (blank lines after preprocessor blocks)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Lukewarm store
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
658 lines
30 KiB
C++
658 lines
30 KiB
C++
#pragma once
|
|
|
|
#include "Observer.h"
|
|
#include <Arduino.h>
|
|
#include <algorithm>
|
|
#include <assert.h>
|
|
#include <map>
|
|
#include <pb_encode.h>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "MeshTypes.h"
|
|
#include "NodeStatus.h"
|
|
#include "WarmNodeStore.h"
|
|
#include "concurrency/Lock.h"
|
|
#include "configuration.h"
|
|
#include "mesh-pb-constants.h"
|
|
#include "mesh/generated/meshtastic/mesh.pb.h" // For CriticalErrorCode
|
|
|
|
#if ARCH_PORTDUINO
|
|
#include "PortduinoGlue.h"
|
|
#endif
|
|
|
|
#if !defined(MESHTASTIC_EXCLUDE_PKI)
|
|
// E3B0C442 is the blank hash
|
|
static const uint8_t LOW_ENTROPY_HASHES[][32] = {
|
|
{0xf4, 0x7e, 0xcc, 0x17, 0xe6, 0xb4, 0xa3, 0x22, 0xec, 0xee, 0xd9, 0x08, 0x4f, 0x39, 0x63, 0xea,
|
|
0x80, 0x75, 0xe1, 0x24, 0xce, 0x05, 0x36, 0x69, 0x63, 0xb2, 0xcb, 0xc0, 0x28, 0xd3, 0x34, 0x8b},
|
|
{0x5a, 0x9e, 0xa2, 0xa6, 0x8a, 0xa6, 0x66, 0xc1, 0x5f, 0x55, 0x00, 0x64, 0xa3, 0xa6, 0xfe, 0x71,
|
|
0xc0, 0xbb, 0x82, 0xc3, 0x32, 0x3d, 0x7a, 0x7a, 0xe3, 0x6e, 0xfd, 0xdd, 0xad, 0x3a, 0x66, 0xb9},
|
|
{0xb3, 0xdf, 0x3b, 0x2e, 0x67, 0xb6, 0xd5, 0xf8, 0xdf, 0x76, 0x2c, 0x45, 0x5e, 0x2e, 0xbd, 0x16,
|
|
0xc5, 0xf8, 0x67, 0xaa, 0x15, 0xf8, 0x92, 0x0b, 0xdf, 0x5a, 0x66, 0x50, 0xac, 0x0d, 0xbb, 0x2f},
|
|
{0x3b, 0x8f, 0x86, 0x3a, 0x38, 0x1f, 0x77, 0x39, 0xa9, 0x4e, 0xef, 0x91, 0x18, 0x5a, 0x62, 0xe1,
|
|
0xaa, 0x9d, 0x36, 0xea, 0xce, 0x60, 0x35, 0x8d, 0x9d, 0x1f, 0xf4, 0xb8, 0xc9, 0x13, 0x6a, 0x5d},
|
|
{0x36, 0x7e, 0x2d, 0xe1, 0x84, 0x5f, 0x42, 0x52, 0x29, 0x11, 0x0a, 0x25, 0x64, 0x54, 0x6a, 0x6b,
|
|
0xfd, 0xb6, 0x65, 0xff, 0x15, 0x1a, 0x51, 0x71, 0x22, 0x40, 0x57, 0xf6, 0x91, 0x9b, 0x64, 0x58},
|
|
{0x16, 0x77, 0xeb, 0xa4, 0x52, 0x91, 0xfb, 0x26, 0xcf, 0x8f, 0xd7, 0xd9, 0xd1, 0x5d, 0xc4, 0x68,
|
|
0x73, 0x75, 0xed, 0xc5, 0x95, 0x58, 0xee, 0x90, 0x56, 0xd4, 0x2f, 0x31, 0x29, 0xf7, 0x8c, 0x1f},
|
|
{0x31, 0x8c, 0xa9, 0x5e, 0xed, 0x3c, 0x12, 0xbf, 0x97, 0x9c, 0x47, 0x8e, 0x98, 0x9d, 0xc2, 0x3e,
|
|
0x86, 0x23, 0x90, 0x29, 0xc8, 0xb0, 0x20, 0xf8, 0xb1, 0xb0, 0xaa, 0x19, 0x2a, 0xcf, 0x0a, 0x54},
|
|
{0xa4, 0x8a, 0x99, 0x0e, 0x51, 0xdc, 0x12, 0x20, 0xf3, 0x13, 0xf5, 0x2b, 0x3a, 0xe2, 0x43, 0x42,
|
|
0xc6, 0x52, 0x98, 0xcd, 0xbb, 0xca, 0xb1, 0x31, 0xa0, 0xd4, 0xd6, 0x30, 0xf3, 0x27, 0xfb, 0x49},
|
|
{0xd2, 0x3f, 0x13, 0x8d, 0x22, 0x04, 0x8d, 0x07, 0x59, 0x58, 0xa0, 0xf9, 0x55, 0xcf, 0x30, 0xa0,
|
|
0x2e, 0x2f, 0xca, 0x80, 0x20, 0xe4, 0xde, 0xa1, 0xad, 0xd9, 0x58, 0xb3, 0x43, 0x2b, 0x22, 0x70},
|
|
{0x40, 0x41, 0xec, 0x6a, 0xd2, 0xd6, 0x03, 0xe4, 0x9a, 0x9e, 0xbd, 0x6c, 0x0a, 0x9b, 0x75, 0xa4,
|
|
0xbc, 0xab, 0x6f, 0xa7, 0x95, 0xff, 0x2d, 0xf6, 0xe9, 0xb9, 0xab, 0x4c, 0x0c, 0x1c, 0xd0, 0x3b},
|
|
{0x22, 0x49, 0x32, 0x2b, 0x00, 0xf9, 0x22, 0xfa, 0x17, 0x02, 0xe9, 0x64, 0x82, 0xf0, 0x4d, 0x1b,
|
|
0xc7, 0x04, 0xfc, 0xdc, 0x8c, 0x5e, 0xb6, 0xd9, 0x16, 0xd6, 0x37, 0xce, 0x59, 0xaa, 0x09, 0x49},
|
|
{0x48, 0x6f, 0x1e, 0x48, 0x97, 0x88, 0x64, 0xac, 0xe8, 0xeb, 0x30, 0xa3, 0xc3, 0xe1, 0xcf, 0x97,
|
|
0x39, 0xa6, 0x55, 0x5b, 0x5f, 0xbf, 0x18, 0xb7, 0x3a, 0xdf, 0xa8, 0x75, 0xe7, 0x9d, 0xe0, 0x1e},
|
|
{0x09, 0xb4, 0xe2, 0x6d, 0x28, 0x98, 0xc9, 0x47, 0x66, 0x46, 0xbf, 0xff, 0x58, 0x17, 0x91, 0xaa,
|
|
0xc3, 0xbf, 0x4a, 0x9d, 0x0b, 0x88, 0xb1, 0xf1, 0x03, 0xdd, 0x61, 0xd7, 0xba, 0x9e, 0x64, 0x98},
|
|
{0x39, 0x39, 0x84, 0xe0, 0x22, 0x2f, 0x7d, 0x78, 0x45, 0x18, 0x72, 0xb4, 0x13, 0xd2, 0x01, 0x2f,
|
|
0x3c, 0xa1, 0xb0, 0xfe, 0x39, 0xd0, 0xf1, 0x3c, 0x72, 0xd6, 0xef, 0x54, 0xd5, 0x77, 0x22, 0xa0},
|
|
{0x0a, 0xda, 0x5f, 0xec, 0xff, 0x5c, 0xc0, 0x2e, 0x5f, 0xc4, 0x8d, 0x03, 0xe5, 0x80, 0x59, 0xd3,
|
|
0x5d, 0x49, 0x86, 0xe9, 0x8d, 0xf6, 0xf6, 0x16, 0x35, 0x3d, 0xf9, 0x9b, 0x29, 0x55, 0x9e, 0x64},
|
|
{0x08, 0x56, 0xF0, 0xD7, 0xEF, 0x77, 0xD6, 0x11, 0x1C, 0x8F, 0x95, 0x2D, 0x3C, 0xDF, 0xB1, 0x22,
|
|
0xBF, 0x60, 0x9B, 0xE5, 0xA9, 0xC0, 0x6E, 0x4B, 0x01, 0xDC, 0xD1, 0x57, 0x44, 0xB2, 0xA5, 0xCF},
|
|
{0x2C, 0xB2, 0x77, 0x85, 0xD6, 0xB7, 0x48, 0x9C, 0xFE, 0xBC, 0x80, 0x26, 0x60, 0xF4, 0x6D, 0xCE,
|
|
0x11, 0x31, 0xA2, 0x1E, 0x33, 0x0A, 0x6D, 0x2B, 0x00, 0xFA, 0x0C, 0x90, 0x95, 0x8F, 0x5C, 0x6B},
|
|
{0xFA, 0x59, 0xC8, 0x6E, 0x94, 0xEE, 0x75, 0xC9, 0x9A, 0xB0, 0xFE, 0x89, 0x36, 0x40, 0xC9, 0x99,
|
|
0x4A, 0x3B, 0xF4, 0xAA, 0x12, 0x24, 0xA2, 0x0F, 0xF9, 0xD1, 0x08, 0xCB, 0x78, 0x19, 0xAA, 0xE5},
|
|
{0x6E, 0x42, 0x7A, 0x4A, 0x8C, 0x61, 0x62, 0x22, 0xA1, 0x89, 0xD3, 0xA4, 0xC2, 0x19, 0xA3, 0x83,
|
|
0x53, 0xA7, 0x7A, 0x0A, 0x89, 0xE2, 0x54, 0x52, 0x62, 0x3D, 0xE7, 0xCA, 0x8C, 0xF6, 0x6A, 0x60},
|
|
{0x20, 0x27, 0x2F, 0xBA, 0x0C, 0x99, 0xD7, 0x29, 0xF3, 0x11, 0x35, 0x89, 0x9D, 0x0E, 0x24, 0xA1,
|
|
0xC3, 0xCB, 0xDF, 0x8A, 0xF1, 0xC6, 0xFE, 0xD0, 0xD7, 0x9F, 0x92, 0xD6, 0x8F, 0x59, 0xBF, 0xE4},
|
|
{0x91, 0x70, 0xb4, 0x7c, 0xfb, 0xff, 0xa0, 0x59, 0x6a, 0x25, 0x1c, 0xa9, 0x9e, 0xe9, 0x43, 0x81,
|
|
0x5d, 0x74, 0xb1, 0xb1, 0x09, 0x28, 0x00, 0x4a, 0xaf, 0xe3, 0xfc, 0xa9, 0x4e, 0x27, 0x76, 0x4c},
|
|
{0x85, 0xfe, 0x7c, 0xec, 0xb6, 0x78, 0x74, 0xc3, 0xec, 0xe1, 0x32, 0x7f, 0xb0, 0xb7, 0x02, 0x74,
|
|
0xf9, 0x23, 0xd8, 0xe7, 0xfa, 0x14, 0xe6, 0xee, 0x66, 0x44, 0xb1, 0x8c, 0xa5, 0x2f, 0x7e, 0xd2},
|
|
{0x8e, 0x66, 0x65, 0x7b, 0x3b, 0x6f, 0x7e, 0xcc, 0x57, 0xb4, 0x57, 0xea, 0xcc, 0x83, 0xf5, 0xaa,
|
|
0xf7, 0x65, 0xa3, 0xce, 0x93, 0x72, 0x13, 0xc1, 0xb6, 0x46, 0x7b, 0x29, 0x45, 0xb5, 0xc8, 0x93},
|
|
{0xcc, 0x11, 0xfb, 0x1a, 0xab, 0xa1, 0x31, 0x87, 0x6a, 0xc6, 0xde, 0x88, 0x87, 0xa9, 0xb9, 0x59,
|
|
0x37, 0x82, 0x8d, 0xb2, 0xcc, 0xd8, 0x97, 0x40, 0x9a, 0x5c, 0x8f, 0x40, 0x55, 0xcb, 0x4c, 0x3e}};
|
|
static const char LOW_ENTROPY_WARNING[] = "Compromised keys were detected and regenerated.";
|
|
#endif
|
|
/*
|
|
DeviceState versions used to be defined in the .proto file but really only this function cares. So changed to a
|
|
#define here.
|
|
*/
|
|
|
|
#define SEGMENT_CONFIG 1
|
|
#define SEGMENT_MODULECONFIG 2
|
|
#define SEGMENT_DEVICESTATE 4
|
|
#define SEGMENT_CHANNELS 8
|
|
#define SEGMENT_NODEDATABASE 16
|
|
|
|
#define DEVICESTATE_CUR_VER 25
|
|
// Lowest on-disk version we still know how to load. v24 saves are migrated
|
|
// at boot via the parallel deviceonly_legacy descriptor and re-saved as v25.
|
|
#define DEVICESTATE_MIN_VER 24
|
|
|
|
extern meshtastic_DeviceState devicestate;
|
|
extern meshtastic_NodeDatabase nodeDatabase;
|
|
extern meshtastic_ChannelFile channelFile;
|
|
extern meshtastic_MyNodeInfo &myNodeInfo;
|
|
extern meshtastic_LocalConfig config;
|
|
extern meshtastic_DeviceUIConfig uiconfig;
|
|
extern meshtastic_LocalModuleConfig moduleConfig;
|
|
extern meshtastic_User &owner;
|
|
extern meshtastic_Position localPosition;
|
|
|
|
static constexpr const char *deviceStateFileName = "/prefs/device.proto";
|
|
static constexpr const char *legacyPrefFileName = "/prefs/db.proto";
|
|
static constexpr const char *nodeDatabaseFileName = "/prefs/nodes.proto";
|
|
static constexpr const char *configFileName = "/prefs/config.proto";
|
|
static constexpr const char *uiconfigFileName = "/prefs/uiconfig.proto";
|
|
static constexpr const char *moduleConfigFileName = "/prefs/module.proto";
|
|
static constexpr const char *channelFileName = "/prefs/channels.proto";
|
|
static constexpr const char *backupFileName = "/backups/backup.proto";
|
|
|
|
/// Given a node, return how many seconds in the past (vs now) that we last heard from it
|
|
uint32_t sinceLastSeen(const meshtastic_NodeInfoLite *n);
|
|
|
|
/// Given a packet, return how many seconds in the past (vs now) it was received
|
|
uint32_t sinceReceived(const meshtastic_MeshPacket *p);
|
|
|
|
/// Given a packet, return the number of hops used to reach this node.
|
|
/// Returns defaultIfUnknown if the number of hops couldn't be determined.
|
|
int8_t getHopsAway(const meshtastic_MeshPacket &p, int8_t defaultIfUnknown = -1);
|
|
|
|
enum class HopStartStatus : uint8_t { VALID = 0, MISSING_OR_UNKNOWN, INVALID };
|
|
|
|
/// Classify hop_start validity for forwarding decisions.
|
|
HopStartStatus classifyHopStart(const meshtastic_MeshPacket &p);
|
|
|
|
inline bool shouldDropPacketForPreHop(const meshtastic_MeshPacket &p)
|
|
{
|
|
#if !MESHTASTIC_PREHOP_DROP
|
|
(void)p;
|
|
return false;
|
|
#else
|
|
if (isFromUs(&p)) {
|
|
return false; // local-originated packets should never be dropped by pre-hop drop policy
|
|
}
|
|
return classifyHopStart(p) != HopStartStatus::VALID;
|
|
#endif
|
|
}
|
|
|
|
/// Rate-limited debug log when hop_start is invalid/missing and packet is dropped.
|
|
void logHopStartDrop(const meshtastic_MeshPacket &p, const char *context);
|
|
|
|
enum LoadFileResult {
|
|
// Successfully opened the file
|
|
LOAD_SUCCESS = 1,
|
|
// File does not exist
|
|
NOT_FOUND = 2,
|
|
// Device does not have a filesystem
|
|
NO_FILESYSTEM = 3,
|
|
// File exists, but could not decode protobufs
|
|
DECODE_FAILED = 4,
|
|
// File exists, but open failed for some reason
|
|
OTHER_FAILURE = 5
|
|
};
|
|
|
|
enum UserLicenseStatus { NotKnown, NotLicensed, Licensed };
|
|
|
|
class NodeDB
|
|
{
|
|
// NodeNum provisionalNodeNum; // if we are trying to find a node num this is our current attempt
|
|
|
|
// A NodeInfo for every node we've seen
|
|
// Eventually use a smarter datastructure
|
|
// HashMap<NodeNum, NodeInfo> nodes;
|
|
// Note: these two references just point into our static array we serialize to/from disk
|
|
|
|
public:
|
|
std::vector<meshtastic_NodeInfoLite> *meshNodes;
|
|
bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled
|
|
meshtastic_NodeInfoLite *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI
|
|
Observable<const meshtastic::NodeStatus *> newStatus;
|
|
pb_size_t numMeshNodes;
|
|
|
|
// Satellite per-NodeNum maps. std::map avoids unordered_map's bucket-array
|
|
// preallocation; O(log N) lookup is fine at these sizes.
|
|
#if !MESHTASTIC_EXCLUDE_POSITIONDB
|
|
std::map<NodeNum, meshtastic_PositionLite> nodePositions;
|
|
#endif
|
|
#if !MESHTASTIC_EXCLUDE_TELEMETRYDB
|
|
std::map<NodeNum, meshtastic_DeviceMetrics> nodeTelemetry;
|
|
#endif
|
|
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTDB
|
|
std::map<NodeNum, meshtastic_EnvironmentMetrics> nodeEnvironment;
|
|
#endif
|
|
#if !MESHTASTIC_EXCLUDE_STATUSDB
|
|
std::map<NodeNum, meshtastic_StatusMessage> nodeStatus;
|
|
#endif
|
|
|
|
bool keyIsLowEntropy = false;
|
|
bool hasWarned = false;
|
|
|
|
/// don't do mesh based algorithm for node id assignment (initially)
|
|
/// instead just store in flash - possibly even in the initial alpha release do this hack
|
|
NodeDB();
|
|
|
|
/// write to flash
|
|
/// @return true if the save was successful
|
|
bool saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS |
|
|
SEGMENT_NODEDATABASE);
|
|
|
|
/** Reinit radio config if needed, because either:
|
|
* a) sometimes a buggy android app might send us bogus settings or
|
|
* b) the client set factory_reset
|
|
*
|
|
* @param factory_reset if true, reset all settings to factory defaults
|
|
* @param is_fresh_install set to true after a fresh install, to trigger NodeInfo/Position requests
|
|
* @return true if the config was completely reset, in that case, we should send it back to the client
|
|
*/
|
|
void resetRadioConfig(bool is_fresh_install = false);
|
|
|
|
/// given a subpacket sniffed from the network, update our DB state
|
|
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
|
|
void updateFrom(const meshtastic_MeshPacket &p);
|
|
|
|
void addFromContact(const meshtastic_SharedContact);
|
|
|
|
/** Update position info for this node based on received position data
|
|
*/
|
|
void updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSource src = RX_SRC_RADIO);
|
|
|
|
/** Update telemetry info for this node based on received metrics
|
|
*/
|
|
void updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxSource src = RX_SRC_RADIO);
|
|
|
|
/** Update user info and channel for this node based on received user data
|
|
*/
|
|
bool updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelIndex = 0);
|
|
|
|
/*
|
|
* Sets a node either favorite or unfavorite. Returns true if the node ends
|
|
* up in the requested state; false if the node is unknown or favouriting
|
|
* was refused by the protected-node cap (MAX_NUM_NODES - 2).
|
|
*/
|
|
bool set_favorite(bool is_favorite, uint32_t nodeId);
|
|
|
|
/// Count of eviction-protected (favourite/ignored/manually-verified) nodes.
|
|
int numProtectedNodes() const;
|
|
|
|
/// printf-style warning emitted when setProtectedFlag() refuses a node at
|
|
/// the cap. %s = verb (favorite/ignore), 0x%08x = node, %d = cap. Shared by
|
|
/// LOG_WARN here and AdminModule::sendWarning so the wording stays in sync.
|
|
static constexpr const char *PROTECTED_CAP_WARN_FMT = "Can't %s 0x%08x: protected-node limit (%d) reached";
|
|
|
|
/// Turn an eviction-protection flag (favourite/ignored/verified) on/off. Off
|
|
/// always succeeds; on returns false (no change) once the protected set hits
|
|
/// the cap (MAX_NUM_NODES-2), keeping >=2 always-evictable slots. Callers
|
|
/// surface the refusal to the user.
|
|
bool setProtectedFlag(meshtastic_NodeInfoLite *node, uint32_t mask, bool on);
|
|
|
|
/*
|
|
* Returns true if the node is in the NodeDB and marked as favorite
|
|
*/
|
|
bool isFavorite(uint32_t nodeId);
|
|
|
|
/*
|
|
* Returns true if p->from or p->to is a favorited node
|
|
*/
|
|
bool isFromOrToFavoritedNode(const meshtastic_MeshPacket &p);
|
|
|
|
/**
|
|
* Other functions like the node picker can request a pause in the node sorting
|
|
*/
|
|
void pause_sort(bool paused);
|
|
|
|
/// @return our node number
|
|
NodeNum getNodeNum() { return myNodeInfo.my_node_num; }
|
|
|
|
/// @return our node ID as a string in the format "!xxxxxxxx"
|
|
std::string getNodeId() const;
|
|
|
|
// @return last byte of a NodeNum, 0xFF if it ended at 0x00
|
|
uint8_t getLastByteOfNodeNum(NodeNum num) { return (uint8_t)((num & 0xFF) ? (num & 0xFF) : 0xFF); }
|
|
|
|
/// if returns false, that means our node should send a DenyNodeNum response. If true, we think the number is okay for use
|
|
// bool handleWantNodeNum(NodeNum n);
|
|
|
|
/* void handleDenyNodeNum(NodeNum FIXME read mesh proto docs, perhaps picking a random node num is not a great idea
|
|
and instead we should use a special 'im unconfigured node number' and include our desired node number in the wantnum message.
|
|
the unconfigured node num would only be used while initially joining the mesh so low odds of conflicting (especially if we
|
|
randomly select from a small number of nodenums which can be used temporarily for this operation). figure out what the lower
|
|
level mesh sw does if it does conflict? would it be better for people who are replying with denynode num to just broadcast
|
|
their denial?)
|
|
*/
|
|
|
|
// get channel channel index we heard a nodeNum on, defaults to 0 if not found
|
|
uint8_t getMeshNodeChannel(NodeNum n);
|
|
|
|
/* Return the number of nodes we've heard from recently (within the last 2 hrs?)
|
|
* @param localOnly if true, ignore nodes heard via MQTT
|
|
*/
|
|
size_t getNumOnlineMeshNodes(bool localOnly = false);
|
|
|
|
void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(bool keepFavorites = false),
|
|
removeNodeByNum(NodeNum nodeNum);
|
|
|
|
bool factoryReset(bool eraseBleBonds = false);
|
|
|
|
LoadFileResult loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields,
|
|
void *dest_struct);
|
|
bool saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct,
|
|
bool fullAtomic = true);
|
|
|
|
void installRoleDefaults(meshtastic_Config_DeviceConfig_Role role);
|
|
|
|
const meshtastic_NodeInfoLite *readNextMeshNode(uint32_t &readIndex);
|
|
|
|
meshtastic_NodeInfoLite *getMeshNodeByIndex(size_t x)
|
|
{
|
|
assert(x < numMeshNodes);
|
|
return &meshNodes->at(x);
|
|
}
|
|
|
|
virtual meshtastic_NodeInfoLite *getMeshNode(NodeNum n);
|
|
size_t getNumMeshNodes() { return numMeshNodes; }
|
|
/// Find a node in our DB, create an empty NodeInfoLite if missing (evicting
|
|
/// the oldest non-protected node when full). Public so admin handlers can
|
|
/// register a node we have not heard from yet (e.g. to block it by ID).
|
|
meshtastic_NodeInfoLite *getOrCreateMeshNode(NodeNum n);
|
|
|
|
#if WARM_NODE_COUNT > 0
|
|
// Warm ("long-tail") tier: minimal {num, last_heard, public_key} records
|
|
// for nodes evicted from the hot store. See WarmNodeStore.h.
|
|
WarmNodeStore warmStore;
|
|
#endif
|
|
|
|
/// 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);
|
|
|
|
/// last_heard of a hot-store node, or 0 if absent. Plain scan of meshNodes
|
|
/// with no allocation side effects (unlike getOrCreateMeshNode).
|
|
uint32_t hotNodeLastHeard(NodeNum n) const;
|
|
|
|
// Thread-safe satellite-map accessors. Return false if absent or the
|
|
// corresponding DB is compiled out.
|
|
bool copyNodePosition(NodeNum n, meshtastic_PositionLite &out) const;
|
|
bool copyNodeTelemetry(NodeNum n, meshtastic_DeviceMetrics &out) const;
|
|
bool copyNodeEnvironment(NodeNum n, meshtastic_EnvironmentMetrics &out) const;
|
|
bool copyNodeStatus(NodeNum n, meshtastic_StatusMessage &out) const;
|
|
std::vector<NodeNum> snapshotPositionNodeNums(NodeNum exclude) const;
|
|
std::vector<NodeNum> snapshotTelemetryNodeNums(NodeNum exclude) const;
|
|
std::vector<NodeNum> snapshotEnvironmentNodeNums(NodeNum exclude) const;
|
|
std::vector<NodeNum> snapshotStatusNodeNums(NodeNum exclude) const;
|
|
|
|
void setNodeStatus(NodeNum n, const meshtastic_StatusMessage &status);
|
|
void touchNodePositionTime(NodeNum n, uint32_t time);
|
|
|
|
bool hasNodePosition(NodeNum n) const
|
|
{
|
|
meshtastic_PositionLite scratch;
|
|
return copyNodePosition(n, scratch);
|
|
}
|
|
bool hasNodeTelemetry(NodeNum n) const
|
|
{
|
|
meshtastic_DeviceMetrics scratch;
|
|
return copyNodeTelemetry(n, scratch);
|
|
}
|
|
bool hasNodeEnvironment(NodeNum n) const
|
|
{
|
|
meshtastic_EnvironmentMetrics scratch;
|
|
return copyNodeEnvironment(n, scratch);
|
|
}
|
|
bool hasNodeStatus(NodeNum n) const
|
|
{
|
|
meshtastic_StatusMessage scratch;
|
|
return copyNodeStatus(n, scratch);
|
|
}
|
|
|
|
void eraseNodeSatellites(NodeNum n);
|
|
|
|
UserLicenseStatus getLicenseStatus(uint32_t nodeNum);
|
|
|
|
size_t getMaxNodesAllocatedSize()
|
|
{
|
|
meshtastic_NodeDatabase emptyNodeDatabase;
|
|
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
|
|
// 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.
|
|
// (not constexpr: portduino resolves MAX_NUM_NODES from runtime config)
|
|
const size_t loadCeiling = ((size_t)MAX_NUM_NODES > 250) ? (size_t)MAX_NUM_NODES : 250;
|
|
return nodeDatabaseSize + (loadCeiling * meshtastic_NodeInfoLite_size) +
|
|
(loadCeiling * meshtastic_NodePositionEntry_size) + (loadCeiling * meshtastic_NodeTelemetryEntry_size) +
|
|
(loadCeiling * meshtastic_NodeEnvironmentEntry_size) + (loadCeiling * meshtastic_NodeStatusEntry_size);
|
|
}
|
|
|
|
// returns true if the maximum number of nodes is reached or we are running low on memory
|
|
bool isFull();
|
|
|
|
void clearLocalPosition();
|
|
|
|
void setLocalPosition(meshtastic_Position position, bool timeOnly = false)
|
|
{
|
|
if (timeOnly) {
|
|
LOG_DEBUG("Set local position time only: time=%u timestamp=%u", position.time, position.timestamp);
|
|
localPosition.time = position.time;
|
|
localPosition.timestamp = position.timestamp > 0 ? position.timestamp : position.time;
|
|
return;
|
|
}
|
|
LOG_DEBUG("Set local position: lat=%i lon=%i time=%u timestamp=%u", position.latitude_i, position.longitude_i,
|
|
position.time, position.timestamp);
|
|
localPosition = position;
|
|
if (position.latitude_i != 0 || position.longitude_i != 0) {
|
|
localPositionUpdatedSinceBoot = true;
|
|
}
|
|
}
|
|
|
|
bool hasValidPosition(const meshtastic_NodeInfoLite *n);
|
|
bool hasLocalPositionSinceBoot() const { return localPositionUpdatedSinceBoot; }
|
|
|
|
#if !defined(MESHTASTIC_EXCLUDE_PKI)
|
|
bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest);
|
|
#endif
|
|
|
|
/// Consolidate crypto key generation logic used across multiple modules
|
|
/// @param privateKey Optional 32-byte private key to use. If nullptr, generates new random keys.
|
|
bool generateCryptoKeyPair(const uint8_t *privateKey = nullptr);
|
|
|
|
bool createNewIdentity();
|
|
|
|
bool backupPreferences(meshtastic_AdminMessage_BackupLocation location);
|
|
bool restorePreferences(meshtastic_AdminMessage_BackupLocation location,
|
|
int restoreWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS);
|
|
|
|
/// Notify observers of changes to the DB
|
|
void notifyObservers(bool forceUpdate = false)
|
|
{
|
|
// Notify observers of the current node state
|
|
const meshtastic::NodeStatus status = meshtastic::NodeStatus(getNumOnlineMeshNodes(), getNumMeshNodes(), forceUpdate);
|
|
newStatus.notifyObservers(&status);
|
|
}
|
|
|
|
#ifdef MESHTASTIC_ENCRYPTED_STORAGE
|
|
/// Re-run loadFromDisk() after the encrypted storage is unlocked at runtime.
|
|
/// Trigger: PhoneAPI::handleLockdownAuthInline sets lockdownReloadPending
|
|
/// on a successful provisionPassphrase / unlockWithPassphrase; the main
|
|
/// loop in main.cpp services the flag and calls this method on the main
|
|
/// thread. The transport callback stack (BLE/USB) is too small for the
|
|
/// file IO + MAX_NUM_NODES vector reserve + proto decode this triggers.
|
|
///
|
|
/// 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
|
|
/// 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.
|
|
bool reloadFromDisk();
|
|
|
|
/// 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
|
|
/// device remains in lockdown so the operator can retry. APPROTECT is not
|
|
/// reversed. Called from the main loop via lockdownDisablePending.
|
|
bool disableLockdownToPlaintext();
|
|
|
|
/// Set by loadProto when any encrypted file fails to decrypt or decode.
|
|
/// Tracked across an entire loadFromDisk pass so reloadFromDisk can
|
|
/// surface the condition without callers re-walking each loadProto
|
|
/// result. Cleared at the top of every loadFromDisk run.
|
|
bool storageCorruptThisLoad = false;
|
|
#endif
|
|
|
|
private:
|
|
mutable concurrency::Lock satelliteMutex;
|
|
bool duplicateWarned = false;
|
|
bool localPositionUpdatedSinceBoot = false;
|
|
bool migrationSavePending = false;
|
|
uint32_t lastNodeDbSave = 0; // when we last saved our db to flash
|
|
uint32_t lastBackupAttempt = 0; // when we last tried a backup automatically or manually
|
|
uint32_t lastSort = 0; // When last sorted the nodeDB
|
|
|
|
/*
|
|
* Internal boolean to track sorting paused
|
|
*/
|
|
bool sortingIsPaused = false;
|
|
|
|
/// pick a provisional nodenum we hope no one is using
|
|
void pickNewNodeNum();
|
|
|
|
/// read our db from flash
|
|
void loadFromDisk();
|
|
|
|
#ifdef PIO_UNIT_TESTING
|
|
// Grant the unit-test shim access to the private maintenance paths below
|
|
// (migration / cleanup / eviction) without relaxing production access.
|
|
friend class NodeDBTestShim;
|
|
#endif
|
|
|
|
/// purge db entries without user info
|
|
void cleanupMeshDB();
|
|
|
|
/// Trim each satellite map down to MAX_SATELLITE_NODES, dropping the
|
|
/// stalest entries (used after loading files written before the cap, or by
|
|
/// a build with a larger cap). Returns true iff anything was trimmed.
|
|
bool enforceSatelliteCaps();
|
|
|
|
/// Node-DB self-care; call only once identity is established (getNodeNum()
|
|
/// valid). Confirms self is present, trims/demotes only NON-self overflow, and
|
|
/// rewrites the store once when something changed (never while storage locked).
|
|
void nodeDBSelfCare();
|
|
|
|
#if WARM_NODE_COUNT > 0
|
|
/// A database from a larger-cap build (e.g. the pre-fork 150-node nRF52 store)
|
|
/// can exceed MAX_NUM_NODES on load. Rank the hot store, demote the oldest
|
|
/// overflow into the warm tier preserving {num, last_heard, public_key} so PKI
|
|
/// DMs survive instead of dropping on truncation.
|
|
void demoteOldestHotNodesToWarm();
|
|
#endif
|
|
|
|
/// Reinit device state from scratch (not loading from disk)
|
|
void installDefaultDeviceState(), installDefaultNodeDatabase(), installDefaultChannels(),
|
|
installDefaultConfig(bool preserveKey), installDefaultModuleConfig();
|
|
|
|
/// write to flash
|
|
/// @return true if the save was successful
|
|
bool saveToDiskNoRetry(int saveWhat);
|
|
|
|
bool saveChannelsToDisk();
|
|
bool saveDeviceStateToDisk();
|
|
bool saveNodeDatabaseToDisk();
|
|
void sortMeshDB();
|
|
|
|
// Defined in NodeDBLegacyMigration.cpp. Decodes /prefs/nodes.proto via
|
|
// the legacy descriptor and copies entries into the v25 layout. Caller
|
|
// is responsible for save / install-default on the result.
|
|
bool migrateLegacyNodeDatabase();
|
|
|
|
// Route satellite-store decode entries straight into our maps instead of
|
|
// temp vectors. Must be paired — disarm before any other NodeDatabase decode.
|
|
void armNodeDatabaseDecodeTargets();
|
|
void disarmNodeDatabaseDecodeTargets();
|
|
};
|
|
|
|
extern NodeDB *nodeDB;
|
|
|
|
/*
|
|
If is_router is set, we use a number of different default values
|
|
|
|
# FIXME - after tuning, move these params into the on-device defaults based on is_router and is_power_saving
|
|
|
|
# prefs.position_broadcast_secs = FIXME possibly broadcast only once an hr
|
|
prefs.wait_bluetooth_secs = 1 # Don't stay in bluetooth mode
|
|
# try to stay in light sleep one full day, then briefly wake and sleep again
|
|
|
|
prefs.ls_secs = oneday
|
|
|
|
prefs.position_broadcast_secs = 12 hours # send either position or owner every 12hrs
|
|
|
|
# get a new GPS position once per day
|
|
prefs.gps_update_interval = oneday
|
|
|
|
prefs.is_power_saving = True
|
|
*/
|
|
|
|
/** The current change # for radio settings. Starts at 0 on boot and any time the radio settings
|
|
* might have changed is incremented. Allows others to detect they might now be on a new channel.
|
|
*/
|
|
extern uint32_t radioGeneration;
|
|
|
|
extern meshtastic_CriticalErrorCode error_code;
|
|
|
|
/*
|
|
* A numeric error address (nonzero if available)
|
|
*/
|
|
extern uint32_t error_address;
|
|
// Bit assignments for meshtastic_NodeInfoLite.bitfield.
|
|
#define NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_SHIFT 0
|
|
#define NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK (1u << NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_SHIFT)
|
|
#define NODEINFO_BITFIELD_IS_MUTED_SHIFT 1
|
|
#define NODEINFO_BITFIELD_IS_MUTED_MASK (1u << NODEINFO_BITFIELD_IS_MUTED_SHIFT)
|
|
#define NODEINFO_BITFIELD_VIA_MQTT_SHIFT 2
|
|
#define NODEINFO_BITFIELD_VIA_MQTT_MASK (1u << NODEINFO_BITFIELD_VIA_MQTT_SHIFT)
|
|
#define NODEINFO_BITFIELD_IS_FAVORITE_SHIFT 3
|
|
#define NODEINFO_BITFIELD_IS_FAVORITE_MASK (1u << NODEINFO_BITFIELD_IS_FAVORITE_SHIFT)
|
|
#define NODEINFO_BITFIELD_IS_IGNORED_SHIFT 4
|
|
#define NODEINFO_BITFIELD_IS_IGNORED_MASK (1u << NODEINFO_BITFIELD_IS_IGNORED_SHIFT)
|
|
#define NODEINFO_BITFIELD_HAS_USER_SHIFT 5
|
|
#define NODEINFO_BITFIELD_HAS_USER_MASK (1u << NODEINFO_BITFIELD_HAS_USER_SHIFT)
|
|
#define NODEINFO_BITFIELD_IS_LICENSED_SHIFT 6
|
|
#define NODEINFO_BITFIELD_IS_LICENSED_MASK (1u << NODEINFO_BITFIELD_IS_LICENSED_SHIFT)
|
|
#define NODEINFO_BITFIELD_IS_UNMESSAGABLE_SHIFT 7
|
|
#define NODEINFO_BITFIELD_IS_UNMESSAGABLE_MASK (1u << NODEINFO_BITFIELD_IS_UNMESSAGABLE_SHIFT)
|
|
#define NODEINFO_BITFIELD_HAS_IS_UNMESSAGABLE_SHIFT 8
|
|
#define NODEINFO_BITFIELD_HAS_IS_UNMESSAGABLE_MASK (1u << NODEINFO_BITFIELD_HAS_IS_UNMESSAGABLE_SHIFT)
|
|
#define NODEINFO_BITFIELD_HAS_XEDDSA_SIGNED_SHIFT 9
|
|
#define NODEINFO_BITFIELD_HAS_XEDDSA_SIGNED_MASK (1u << NODEINFO_BITFIELD_HAS_XEDDSA_SIGNED_SHIFT)
|
|
// Bits 10..31 reserved for future single-bit flags.
|
|
|
|
// Convenience accessors so call sites read like the old struct fields.
|
|
inline bool nodeInfoLiteHasUser(const meshtastic_NodeInfoLite *n)
|
|
{
|
|
return n && (n->bitfield & NODEINFO_BITFIELD_HAS_USER_MASK);
|
|
}
|
|
inline bool nodeInfoLiteViaMqtt(const meshtastic_NodeInfoLite *n)
|
|
{
|
|
return n && (n->bitfield & NODEINFO_BITFIELD_VIA_MQTT_MASK);
|
|
}
|
|
inline bool nodeInfoLiteIsFavorite(const meshtastic_NodeInfoLite *n)
|
|
{
|
|
return n && (n->bitfield & NODEINFO_BITFIELD_IS_FAVORITE_MASK);
|
|
}
|
|
inline bool nodeInfoLiteIsIgnored(const meshtastic_NodeInfoLite *n)
|
|
{
|
|
return n && (n->bitfield & NODEINFO_BITFIELD_IS_IGNORED_MASK);
|
|
}
|
|
inline bool nodeInfoLiteIsLicensed(const meshtastic_NodeInfoLite *n)
|
|
{
|
|
return n && (n->bitfield & NODEINFO_BITFIELD_IS_LICENSED_MASK);
|
|
}
|
|
inline bool nodeInfoLiteHasIsUnmessagable(const meshtastic_NodeInfoLite *n)
|
|
{
|
|
return n && (n->bitfield & NODEINFO_BITFIELD_HAS_IS_UNMESSAGABLE_MASK);
|
|
}
|
|
inline bool nodeInfoLiteIsUnmessagable(const meshtastic_NodeInfoLite *n)
|
|
{
|
|
return n && (n->bitfield & NODEINFO_BITFIELD_IS_UNMESSAGABLE_MASK);
|
|
}
|
|
inline bool nodeInfoLiteIsMuted(const meshtastic_NodeInfoLite *n)
|
|
{
|
|
return n && (n->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK);
|
|
}
|
|
inline bool nodeInfoLiteIsKeyManuallyVerified(const meshtastic_NodeInfoLite *n)
|
|
{
|
|
return n && (n->bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK);
|
|
}
|
|
inline bool nodeInfoLiteHasXeddsaSigned(const meshtastic_NodeInfoLite *n)
|
|
{
|
|
return n && (n->bitfield & NODEINFO_BITFIELD_HAS_XEDDSA_SIGNED_MASK);
|
|
}
|
|
/// A node that the eviction/migration paths must not drop: a favourite, an
|
|
/// ignored (blocked) node, or a manually-verified key.
|
|
inline bool nodeInfoLiteIsProtected(const meshtastic_NodeInfoLite *n)
|
|
{
|
|
return nodeInfoLiteIsFavorite(n) || nodeInfoLiteIsIgnored(n) || nodeInfoLiteIsKeyManuallyVerified(n);
|
|
}
|
|
|
|
inline void nodeInfoLiteSetBit(meshtastic_NodeInfoLite *n, uint32_t mask, bool value)
|
|
{
|
|
if (!n)
|
|
return;
|
|
if (value)
|
|
n->bitfield |= mask;
|
|
else
|
|
n->bitfield &= ~mask;
|
|
}
|
|
|
|
#define Module_Config_size \
|
|
(ModuleConfig_CannedMessageConfig_size + ModuleConfig_ExternalNotificationConfig_size + ModuleConfig_MQTTConfig_size + \
|
|
ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \
|
|
ModuleConfig_TelemetryConfig_size + ModuleConfig_size)
|
|
|
|
// Please do not remove this comment, it makes trunk and compiler happy at the same time.
|