mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-20 02:11:28 +10:00
Merge develop into packet authentication policy
This commit is contained in:
@@ -516,7 +516,7 @@ bool Channels::hasDefaultChannel()
|
||||
*/
|
||||
bool Channels::decryptForHash(ChannelIndex chIndex, ChannelHash channelHash)
|
||||
{
|
||||
if (chIndex > getNumChannels() || getHash(chIndex) != channelHash) {
|
||||
if (chIndex >= getNumChannels() || getHash(chIndex) != channelHash) {
|
||||
// LOG_DEBUG("Skip channel %d (hash %x) due to invalid hash/index, want=%x", chIndex, getHash(chIndex),
|
||||
// channelHash);
|
||||
return false;
|
||||
|
||||
@@ -224,7 +224,11 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtas
|
||||
uint64_t packetNum, size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut)
|
||||
{
|
||||
uint8_t *auth;
|
||||
long extraNonceTmp = random();
|
||||
// The extra nonce must be unpredictable: use the hardware RNG, falling back to the
|
||||
// seeded CSPRNG only when no hardware source is available.
|
||||
uint32_t extraNonceTmp;
|
||||
if (!HardwareRNG::fill((uint8_t *)&extraNonceTmp, sizeof(extraNonceTmp)))
|
||||
CryptRNG.rand((uint8_t *)&extraNonceTmp, sizeof(extraNonceTmp));
|
||||
auth = bytesOut + numBytes;
|
||||
memcpy((uint8_t *)(auth + 8), &extraNonceTmp,
|
||||
sizeof(uint32_t)); // do not use dereference on potential non aligned pointers : *extraNonce = extraNonceTmp;
|
||||
@@ -341,6 +345,32 @@ bool CryptoEngine::setDHPublicKey(uint8_t *pubKey)
|
||||
return true;
|
||||
}
|
||||
|
||||
void CryptoEngine::setPendingPublicKey(uint32_t node, const uint8_t *key)
|
||||
{
|
||||
concurrency::LockGuard g(&pendingKeyLock);
|
||||
pendingKeyVerificationNode = node;
|
||||
memcpy(pendingKeyVerificationPublicKey, key, 32);
|
||||
hasPendingKeyVerificationKey = true;
|
||||
}
|
||||
|
||||
void CryptoEngine::clearPendingPublicKey()
|
||||
{
|
||||
concurrency::LockGuard g(&pendingKeyLock);
|
||||
pendingKeyVerificationNode = 0;
|
||||
memset(pendingKeyVerificationPublicKey, 0, 32);
|
||||
hasPendingKeyVerificationKey = false;
|
||||
}
|
||||
|
||||
bool CryptoEngine::getPendingPublicKey(uint32_t node, meshtastic_NodeInfoLite_public_key_t &out)
|
||||
{
|
||||
concurrency::LockGuard g(&pendingKeyLock);
|
||||
if (!hasPendingKeyVerificationKey || node == 0 || node != pendingKeyVerificationNode)
|
||||
return false;
|
||||
out.size = 32;
|
||||
memcpy(out.bytes, pendingKeyVerificationPublicKey, 32);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
concurrency::Lock *cryptLock;
|
||||
|
||||
|
||||
@@ -59,6 +59,17 @@ class CryptoEngine
|
||||
virtual bool setDHPublicKey(uint8_t *publicKey);
|
||||
virtual void hash(uint8_t *bytes, size_t numBytes);
|
||||
|
||||
// Temporary holder for a peer's not-yet-verified public key, learned in-band during an
|
||||
// in-progress key-verification handshake before it is committed to NodeDB. Lets the Router
|
||||
// run the DH handshake to encode/decode the follow-on PKI packet. Single slot is enough:
|
||||
// only one verification runs at a time. Discarded when the handshake ends (resetToIdle).
|
||||
// Internally guarded by pendingKeyLock, not cryptLock: the Router calls the getter while
|
||||
// already holding the non-recursive cryptLock; KeyVerificationModule writes from elsewhere.
|
||||
void setPendingPublicKey(uint32_t node, const uint8_t *key);
|
||||
void clearPendingPublicKey();
|
||||
// Fills `out` (size set to 32) and returns true iff a pending key is held for `node`.
|
||||
bool getPendingPublicKey(uint32_t node, meshtastic_NodeInfoLite_public_key_t &out);
|
||||
|
||||
virtual void aesSetKey(const uint8_t *key, size_t key_len);
|
||||
|
||||
virtual void aesEncrypt(uint8_t *in, uint8_t *out);
|
||||
@@ -94,6 +105,10 @@ class CryptoEngine
|
||||
#if !(MESHTASTIC_EXCLUDE_PKI)
|
||||
uint8_t shared_key[32] = {0};
|
||||
uint8_t private_key[32] = {0};
|
||||
uint32_t pendingKeyVerificationNode = 0;
|
||||
uint8_t pendingKeyVerificationPublicKey[32] = {0};
|
||||
bool hasPendingKeyVerificationKey = false;
|
||||
concurrency::Lock pendingKeyLock;
|
||||
#if !(MESHTASTIC_EXCLUDE_XEDDSA)
|
||||
uint8_t xeddsa_public_key[32] = {0};
|
||||
uint8_t xeddsa_private_key[32] = {0};
|
||||
|
||||
@@ -22,6 +22,12 @@ extern Adafruit_nRFCrypto nRFCrypto;
|
||||
#include <unistd.h>
|
||||
#ifdef __linux__
|
||||
#include <sys/random.h> // getrandom()
|
||||
#elif defined(_WIN32)
|
||||
// Order is load-bearing, hence the blank line: bcrypt.h uses LONG/ULONG from
|
||||
// windows.h and does not include it itself.
|
||||
#include <windows.h>
|
||||
|
||||
#include <bcrypt.h> // BCryptGenRandom()
|
||||
#else
|
||||
#include <stdlib.h> // arc4random_buf() on Darwin/BSD
|
||||
#endif
|
||||
@@ -128,6 +134,12 @@ bool fill(uint8_t *buffer, size_t length, bool useRadioEntropy)
|
||||
if (generated == static_cast<ssize_t>(length)) {
|
||||
filled = true;
|
||||
}
|
||||
#elif defined(_WIN32)
|
||||
// No getrandom/arc4random on Windows; BCryptGenRandom is the documented CSPRNG.
|
||||
// Preferred over std::random_device, whose libstdc++ Windows backend reports entropy() == 0.
|
||||
if (BCryptGenRandom(NULL, buffer, static_cast<ULONG>(length), BCRYPT_USE_SYSTEM_PREFERRED_RNG) == 0) { // STATUS_SUCCESS
|
||||
filled = true;
|
||||
}
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
// Browser/wasm: no getrandom/arc4random - fall through to std::random_device,
|
||||
// which emscripten backs with crypto.getRandomValues().
|
||||
|
||||
@@ -229,6 +229,11 @@ static inline void modemPresetToParams(meshtastic_Config_LoRaConfig_ModemPreset
|
||||
cr = 5;
|
||||
sf = 10;
|
||||
break;
|
||||
case PRESET(MEDIUM_TURBO):
|
||||
bwKHz = wideLora ? 1625.0f : 500.0f;
|
||||
cr = 5;
|
||||
sf = 9;
|
||||
break;
|
||||
case PRESET(LONG_TURBO):
|
||||
bwKHz = wideLora ? 1625.0f : 500.0f;
|
||||
cr = 8;
|
||||
|
||||
@@ -11,12 +11,13 @@
|
||||
#include "NodeDB.h"
|
||||
#include "Power.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "RTC.h"
|
||||
#include "TypeConversions.h"
|
||||
#include "gps/RTC.h"
|
||||
#include "graphics/draw/MessageRenderer.h"
|
||||
#include "main.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "meshUtils.h"
|
||||
#include "modules/AdminModule.h"
|
||||
#include "modules/NodeInfoModule.h"
|
||||
#include "modules/PositionModule.h"
|
||||
#include "modules/RoutingModule.h"
|
||||
@@ -63,6 +64,7 @@ Allocator<meshtastic_ClientNotification> &clientNotificationPool = staticClientN
|
||||
|
||||
Allocator<meshtastic_QueueStatus> &queueStatusPool = staticQueueStatusPool;
|
||||
|
||||
#include "PositionPrecision.h"
|
||||
#include "Router.h"
|
||||
|
||||
MeshService::MeshService()
|
||||
@@ -173,6 +175,51 @@ NodeNum MeshService::getNodenumFromRequestId(uint32_t request_id)
|
||||
return nodenum;
|
||||
}
|
||||
|
||||
#if MESHTASTIC_ENABLE_FRAME_INJECTION
|
||||
// Deliver a client-supplied frame into the receive pipeline as if it arrived off the LoRa chip. Mirrors
|
||||
// the portduino SimRadio SIMULATOR_APP unwrap so the same host wire format works on real hardware: the
|
||||
// frame rides inside a Compressed envelope wrapped in a MeshPacket that carries from/to/id/channel.
|
||||
// Compressed.portnum == UNKNOWN_APP -> Compressed.data is verbatim ciphertext, decrypted as if off-air
|
||||
// otherwise -> Compressed.data is the plaintext payload for Compressed.portnum
|
||||
void MeshService::injectAsReceived(meshtastic_MeshPacket &p)
|
||||
{
|
||||
meshtastic_Compressed scratch;
|
||||
if (p.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
||||
memset(&scratch, 0, sizeof(scratch));
|
||||
if (pb_decode_from_bytes(p.decoded.payload.bytes, p.decoded.payload.size, &meshtastic_Compressed_msg, &scratch)) {
|
||||
if (scratch.portnum == meshtastic_PortNum_UNKNOWN_APP) {
|
||||
p.which_payload_variant = meshtastic_MeshPacket_encrypted_tag;
|
||||
memcpy(p.encrypted.bytes, scratch.data.bytes, scratch.data.size);
|
||||
p.encrypted.size = scratch.data.size;
|
||||
} else {
|
||||
memcpy(&p.decoded.payload, &scratch.data, sizeof(scratch.data));
|
||||
p.decoded.portnum = scratch.portnum;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR("inject: could not decode Compressed envelope, dropping");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// The real RX path (RadioLibInterface::handleReceiveInterrupt) drops sender==0; mirror it so injection
|
||||
// behaves identically to an over-the-air frame.
|
||||
if (p.from == 0) {
|
||||
LOG_WARN("inject: dropping frame with from==0 (matches real LoRa RX)");
|
||||
return;
|
||||
}
|
||||
meshtastic_MeshPacket *mp = packetPool.allocCopy(p);
|
||||
if (!mp)
|
||||
return;
|
||||
if (mp->rx_snr == 0) // plausible synthetic link metadata unless the caller set it
|
||||
mp->rx_snr = 8;
|
||||
if (mp->rx_rssi == 0)
|
||||
mp->rx_rssi = -40;
|
||||
mp->rx_time = getValidTime(RTCQualityFromNet);
|
||||
LOG_INFO("inject: RX from=0x%08x to=0x%08x id=0x%08x ch=%d %s", mp->from, mp->to, mp->id, mp->channel,
|
||||
mp->which_payload_variant == meshtastic_MeshPacket_encrypted_tag ? "encrypted" : "decoded");
|
||||
router->enqueueReceivedMessage(mp);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Given a ToRadio buffer parse it and properly handle it (setup radio, owner or send packet into the mesh)
|
||||
* Called by PhoneAPI.handleToRadio. Note: p is a scratch buffer, this function is allowed to write to it but it can not keep a
|
||||
@@ -186,6 +233,16 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
|
||||
SimRadio::instance->unpackAndReceive(p);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if MESHTASTIC_ENABLE_FRAME_INJECTION
|
||||
// Real-hardware analog of the SimRadio path above: deliver a client-supplied frame into the RX
|
||||
// pipeline exactly as if it had arrived off the LoRa chip. Reached before the p.from=0 line below,
|
||||
// so an injected sender is preserved. Build-flag gated (off by default) - it lets anything with a
|
||||
// wired connection forge over-the-air traffic, so it must never ship enabled.
|
||||
if (p.which_payload_variant == meshtastic_MeshPacket_decoded_tag && p.decoded.portnum == meshtastic_PortNum_SIMULATOR_APP) {
|
||||
injectAsReceived(p);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
p.from = 0; // We don't let clients assign nodenums to their sent messages
|
||||
p.next_hop = NO_NEXT_HOP_PREFERENCE; // We don't let clients assign next_hop to their sent messages
|
||||
@@ -200,9 +257,17 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
|
||||
p.to != NODENUM_BROADCAST && p.to != 0) // DM only
|
||||
{
|
||||
perhapsDecode(&p);
|
||||
const StoredMessage &sm = messageStore.addFromPacket(p);
|
||||
graphics::MessageRenderer::handleNewMessage(nullptr, sm, p); // notify UI
|
||||
if (const StoredMessage *sm = messageStore.tryAddFromPacket(p))
|
||||
graphics::MessageRenderer::handleNewMessage(nullptr, *sm, p); // notify UI
|
||||
})
|
||||
#if !MESHTASTIC_EXCLUDE_ADMIN
|
||||
// Note admin requests on their way out: AdminModule only accepts a response from a remote we
|
||||
// actually asked. Runs before encryption, while the payload is still readable.
|
||||
if (adminModule && p.which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
|
||||
p.decoded.portnum == meshtastic_PortNum_ADMIN_APP)
|
||||
adminModule->noteOutgoingAdminRequest(p);
|
||||
#endif
|
||||
|
||||
// Send the packet into the mesh
|
||||
DEBUG_HEAP_BEFORE;
|
||||
auto a = packetPool.allocCopy(p);
|
||||
@@ -293,8 +358,31 @@ bool MeshService::trySendPosition(NodeNum dest, bool wantReplies)
|
||||
LOG_DEBUG("Skip position ping; no fresh position since boot");
|
||||
return false;
|
||||
}
|
||||
LOG_INFO("Send position ping to 0x%08x, wantReplies=%d, channel=%d", dest, wantReplies, node->channel);
|
||||
positionModule->sendOurPosition(dest, wantReplies, node->channel);
|
||||
// Prefer the node's current channel, but fall back to the first channel with
|
||||
// position enabled (matching PositionModule::sendOurPosition() behavior).
|
||||
uint8_t sendChan = node->channel;
|
||||
if (getPositionPrecisionForChannel(sendChan) == 0) {
|
||||
bool found = false;
|
||||
for (uint8_t ch = 0; ch < 8; ++ch) {
|
||||
if (getPositionPrecisionForChannel(ch) != 0) {
|
||||
sendChan = ch;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// No channel with position enabled: fall back to sending nodeinfo, as before.
|
||||
if (nodeInfoModule) {
|
||||
LOG_INFO(
|
||||
"No channel with position enabled; sending nodeinfo instead to 0x%08x, wantReplies=%d, channel=%d",
|
||||
dest, wantReplies, node->channel);
|
||||
nodeInfoModule->sendOurNodeInfo(dest, wantReplies, node->channel);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
LOG_INFO("Send position ping to 0x%08x, wantReplies=%d, channel=%d", dest, wantReplies, sendChan);
|
||||
positionModule->sendOurPosition(dest, wantReplies, sendChan);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -159,6 +159,12 @@ class MeshService
|
||||
*/
|
||||
void handleToRadio(meshtastic_MeshPacket &p);
|
||||
|
||||
#if MESHTASTIC_ENABLE_FRAME_INJECTION
|
||||
/// Test/debug seam (build-flag gated, off by default): deliver a client-supplied frame into the
|
||||
/// receive pipeline as if it had arrived off the LoRa chip. See the definition for the wire format.
|
||||
void injectAsReceived(meshtastic_MeshPacket &p);
|
||||
#endif
|
||||
|
||||
/** The radioConfig object just changed, call this to force the hw to change to the new settings
|
||||
* @return true if client devices should be sent a new set of radio configs
|
||||
*/
|
||||
|
||||
+62
-71
@@ -13,7 +13,6 @@
|
||||
#include "NodeDB.h"
|
||||
#include "PacketHistory.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "RTC.h"
|
||||
#include "RadioInterface.h"
|
||||
#include "Router.h"
|
||||
#include "SPILock.h"
|
||||
@@ -21,12 +20,14 @@
|
||||
#include "TransmitHistory.h"
|
||||
#include "TypeConversions.h"
|
||||
#include "error.h"
|
||||
#include "gps/RTC.h"
|
||||
#include "main.h"
|
||||
#include "memory/MemAudit.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "mesh/generated/meshtastic/deviceonly_legacy.pb.h"
|
||||
#include "meshUtils.h"
|
||||
#include "modules/NeighborInfoModule.h"
|
||||
#include "target_specific.h"
|
||||
#if HAS_VARIABLE_HOPS
|
||||
#include "modules/HopScalingModule.h"
|
||||
#endif
|
||||
@@ -50,11 +51,7 @@
|
||||
#include "SPILock.h"
|
||||
#include "modules/StoreForwardModule.h"
|
||||
#include <Preferences.h>
|
||||
#include <esp_efuse.h>
|
||||
#include <esp_efuse_table.h>
|
||||
#include <nvs_flash.h>
|
||||
#include <soc/efuse_reg.h>
|
||||
#include <soc/soc.h>
|
||||
#endif
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
@@ -372,8 +369,7 @@ void NodeDB::disarmNodeDatabaseDecodeTargets()
|
||||
*/
|
||||
uint32_t radioGeneration;
|
||||
|
||||
// FIXME - move this somewhere else
|
||||
extern void getMacAddr(uint8_t *dmac);
|
||||
// getMacAddr() and getDeviceId() are the per-architecture hooks declared in target_specific.h.
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -410,53 +406,13 @@ NodeDB::NodeDB()
|
||||
uint32_t channelFileCRC = crc32Buffer(&channelFile, sizeof(channelFile));
|
||||
|
||||
int saveWhat = 0;
|
||||
// Get device unique id
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)
|
||||
uint32_t unique_id[4];
|
||||
// ESP32 factory burns a unique id in efuse for S2+ series and evidently C3+ series
|
||||
// This is used for HMACs in the esp-rainmaker AIOT platform and seems to be a good choice for us
|
||||
esp_err_t err = esp_efuse_read_field_blob(ESP_EFUSE_OPTIONAL_UNIQUE_ID, unique_id, sizeof(unique_id) * 8);
|
||||
if (err == ESP_OK) {
|
||||
memcpy(myNodeInfo.device_id.bytes, unique_id, sizeof(unique_id));
|
||||
myNodeInfo.device_id.size = 16;
|
||||
} else {
|
||||
LOG_WARN("Failed to read unique id from efuse");
|
||||
// Re-read the device id from silicon each boot via the per-arch getDeviceId(); clear the
|
||||
// disk-loaded value first so a failed/empty derivation leaves it unset rather than stale.
|
||||
myNodeInfo.device_id.size = 0;
|
||||
memset(myNodeInfo.device_id.bytes, 0, sizeof(myNodeInfo.device_id.bytes));
|
||||
if (getDeviceId(myNodeInfo.device_id.bytes)) {
|
||||
myNodeInfo.device_id.size = sizeof(myNodeInfo.device_id.bytes);
|
||||
}
|
||||
#elif defined(ARCH_NRF54L15)
|
||||
// nRF54L15: DEVICEID is under FICR->INFO sub-struct (not top-level as on nRF52)
|
||||
uint64_t device_id_start = ((uint64_t)NRF_FICR->INFO.DEVICEID[1] << 32) | NRF_FICR->INFO.DEVICEID[0];
|
||||
uint64_t device_id_end = ((uint64_t)NRF_FICR->DEVICEADDR[1] << 32) | NRF_FICR->DEVICEADDR[0];
|
||||
memcpy(myNodeInfo.device_id.bytes, &device_id_start, sizeof(device_id_start));
|
||||
memcpy(myNodeInfo.device_id.bytes + sizeof(device_id_start), &device_id_end, sizeof(device_id_end));
|
||||
myNodeInfo.device_id.size = 16;
|
||||
#elif defined(ARCH_NRF52)
|
||||
// Nordic applies a FIPS compliant Random ID to each chip at the factory
|
||||
// We concatenate the device address to the Random ID to create a unique ID for now
|
||||
// This will likely utilize a crypto module in the future
|
||||
uint64_t device_id_start = ((uint64_t)NRF_FICR->DEVICEID[1] << 32) | NRF_FICR->DEVICEID[0];
|
||||
uint64_t device_id_end = ((uint64_t)NRF_FICR->DEVICEADDR[1] << 32) | NRF_FICR->DEVICEADDR[0];
|
||||
memcpy(myNodeInfo.device_id.bytes, &device_id_start, sizeof(device_id_start));
|
||||
memcpy(myNodeInfo.device_id.bytes + sizeof(device_id_start), &device_id_end, sizeof(device_id_end));
|
||||
myNodeInfo.device_id.size = 16;
|
||||
// Uncomment below to print the device id
|
||||
#elif ARCH_PORTDUINO
|
||||
if (portduino_config.has_device_id) {
|
||||
memcpy(myNodeInfo.device_id.bytes, portduino_config.device_id, 16);
|
||||
myNodeInfo.device_id.size = 16;
|
||||
}
|
||||
#else
|
||||
// FIXME - implement for other platforms
|
||||
#endif
|
||||
|
||||
// if (myNodeInfo.device_id.size == 16) {
|
||||
// std::string deviceIdHex;
|
||||
// for (size_t i = 0; i < myNodeInfo.device_id.size; ++i) {
|
||||
// char buf[3];
|
||||
// snprintf(buf, sizeof(buf), "%02X", myNodeInfo.device_id.bytes[i]);
|
||||
// deviceIdHex += buf;
|
||||
// }
|
||||
// LOG_DEBUG("Device ID (HEX): %s", deviceIdHex.c_str());
|
||||
// }
|
||||
|
||||
// likewise - we always want the app requirements to come from the running appload
|
||||
myNodeInfo.min_app_version = 30200; // format is Mmmss (where M is 1+the numeric major number. i.e. 30200 means 2.2.00
|
||||
@@ -522,6 +478,12 @@ NodeDB::NodeDB()
|
||||
LOG_DEBUG("Number of Device Reboots: %d", myNodeInfo.reboot_count);
|
||||
#endif
|
||||
|
||||
// UA_868 is obsolete; migrate to EU_868 before resetRadioConfig() below validates the region.
|
||||
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UA_868) {
|
||||
LOG_INFO("UA_868 region is obsolete, migrating saved config to EU_868");
|
||||
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_EU_868;
|
||||
}
|
||||
|
||||
resetRadioConfig(); // If bogus settings got saved, then fix them
|
||||
// nodeDB->LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d", config.lora.region, myNodeInfo.my_node_num, numMeshNodes);
|
||||
|
||||
@@ -1564,16 +1526,34 @@ void NodeDB::initModuleConfigIntervals()
|
||||
moduleConfig.telemetry.device_update_interval = MAX_INTERVAL;
|
||||
#endif
|
||||
|
||||
#ifdef USERPREFS_CONFIG_ENVIRONMENT_MEASUREMENT_ENABLED
|
||||
moduleConfig.telemetry.environment_measurement_enabled = USERPREFS_CONFIG_ENVIRONMENT_MEASUREMENT_ENABLED;
|
||||
#endif
|
||||
|
||||
#ifdef USERPREFS_CONFIG_ENV_TELEM_UPDATE_INTERVAL
|
||||
moduleConfig.telemetry.environment_update_interval = USERPREFS_CONFIG_ENV_TELEM_UPDATE_INTERVAL;
|
||||
#else
|
||||
moduleConfig.telemetry.environment_update_interval = 0;
|
||||
#endif
|
||||
|
||||
#ifdef USERPREFS_CONFIG_ENVIRONMENT_MEASUREMENT_ENABLED
|
||||
moduleConfig.telemetry.environment_measurement_enabled = USERPREFS_CONFIG_ENVIRONMENT_MEASUREMENT_ENABLED;
|
||||
#ifdef USERPREFS_CONFIG_ENV_SCREEN_SCREEN_ENABLED
|
||||
moduleConfig.telemetry.environment_screen_enabled = USERPREFS_CONFIG_ENV_SCREEN_SCREEN_ENABLED;
|
||||
#endif
|
||||
|
||||
#ifdef USERPREFS_CONFIG_AQ_TELEM_UPDATE_INTERVAL
|
||||
moduleConfig.telemetry.air_quality_interval = USERPREFS_CONFIG_AQ_TELEM_UPDATE_INTERVAL;
|
||||
#else
|
||||
moduleConfig.telemetry.air_quality_interval = 0;
|
||||
#endif
|
||||
|
||||
#ifdef USERPREFS_CONFIG_AQ_MEASUREMENT_ENABLED
|
||||
moduleConfig.telemetry.air_quality_enabled = USERPREFS_CONFIG_AQ_MEASUREMENT_ENABLED;
|
||||
#endif
|
||||
|
||||
#ifdef USERPREFS_CONFIG_AQ_SCREEN_ENABLED
|
||||
moduleConfig.telemetry.air_quality_screen_enabled = USERPREFS_CONFIG_AQ_SCREEN_ENABLED;
|
||||
#endif
|
||||
|
||||
moduleConfig.telemetry.power_update_interval = 0;
|
||||
moduleConfig.telemetry.health_update_interval = 0;
|
||||
moduleConfig.neighbor_info.update_interval = 0;
|
||||
@@ -1635,10 +1615,16 @@ void NodeDB::removeNodeByNum(NodeNum nodeNum)
|
||||
removed++;
|
||||
}
|
||||
numMeshNodes -= removed;
|
||||
std::fill(nodeDatabase.nodes.begin() + numMeshNodes, nodeDatabase.nodes.begin() + numMeshNodes + 1,
|
||||
meshtastic_NodeInfoLite());
|
||||
if (removed)
|
||||
eraseNodeSatellites(nodeNum);
|
||||
if (removed) {
|
||||
// Clear exactly the slots compaction vacated. Sizing this from `removed` (rather than a
|
||||
// fixed one) keeps it inside the vector when nothing matched and the store is full.
|
||||
const size_t first = numMeshNodes;
|
||||
const size_t last = std::min(first + removed, nodeDatabase.nodes.size());
|
||||
std::fill(nodeDatabase.nodes.begin() + first, nodeDatabase.nodes.begin() + last, meshtastic_NodeInfoLite());
|
||||
}
|
||||
// Drop the node's satellite stores and warm-tier copy regardless of which tier it lived in, so
|
||||
// an explicit removal fully forgets it.
|
||||
eraseNodeSatellites(nodeNum);
|
||||
#if WARM_NODE_COUNT > 0
|
||||
// Explicit user removal: don't let the warm tier resurrect the node
|
||||
warmStore.remove(nodeNum);
|
||||
@@ -1898,7 +1884,8 @@ void NodeDB::cleanupMeshDB()
|
||||
// Keep any key we learned (e.g. via a DM before the NodeInfo
|
||||
// exchange completed) rather than losing it with the purge.
|
||||
if (n.public_key.size == 32)
|
||||
warmStore.absorb(gone, n.last_heard, n.public_key.bytes, n.role, warmProtectedCategory(n));
|
||||
warmStore.absorb(gone, n.last_heard, n.public_key.bytes, n.role, warmProtectedCategory(n),
|
||||
nodeInfoLiteHasXeddsaSigned(&n));
|
||||
#endif
|
||||
|
||||
eraseNodeSatellites(gone);
|
||||
@@ -2082,7 +2069,7 @@ void NodeDB::demoteOldestHotNodesToWarm()
|
||||
// Keep the public key if we have one (40 B warm record); keyless nodes
|
||||
// still get a placeholder so re-admission restores last_heard.
|
||||
warmStore.absorb(n.num, n.last_heard, n.public_key.size > 0 ? n.public_key.bytes : nullptr, n.role,
|
||||
warmProtectedCategory(n));
|
||||
warmProtectedCategory(n), nodeInfoLiteHasXeddsaSigned(&n));
|
||||
// Demotion drops the node from the header table, so drop its satellites
|
||||
// too (the eviction chokepoint) - they'd otherwise orphan until the next
|
||||
// enforceSatelliteCaps pass.
|
||||
@@ -3084,14 +3071,12 @@ 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 -
|
||||
// the packet was never meant to travel any hops, so no hop_start ambiguity applies.
|
||||
if (p.hop_limit == 0)
|
||||
return HopStartStatus::VALID;
|
||||
// Firmware prior to 2.3.0 (585805c) lacked a hop_start field. Firmware version 2.5.0 (bf34329) introduced a
|
||||
// bitfield that is always present. Use the presence of the bitfield to determine if the origin's firmware
|
||||
// version is guaranteed to have hop_start populated. Note that this can only be done for decoded packets as
|
||||
// the bitfield is encrypted under the channel encryption key.
|
||||
// hop_start == 0 is either a modern zero-hop broadcast (e.g. beacon) or pre-2.3.0 firmware (585805c)
|
||||
// that never populated hop_start. Firmware 2.5.0 (bf34329) introduced a bitfield that is always
|
||||
// present; use it to tell the two apart. The bitfield is encrypted under the channel key, so this can
|
||||
// only be resolved for decoded packets - until then the status stays MISSING_OR_UNKNOWN. Callers
|
||||
// acting before decode must therefore not treat UNKNOWN as a drop (the bitfield isn't readable yet);
|
||||
// the verdict is re-checked post-decode in Router::handleReceived.
|
||||
if (p.which_payload_variant == meshtastic_MeshPacket_decoded_tag && p.decoded.has_bitfield)
|
||||
return HopStartStatus::VALID;
|
||||
return HopStartStatus::MISSING_OR_UNKNOWN;
|
||||
@@ -3285,6 +3270,9 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
|
||||
LOG_WARN(PROTECTED_CAP_WARN_FMT, "ignore", contact.node_num, MAX_NUM_NODES - 2);
|
||||
nodeInfoLiteSetBit(info, NODEINFO_BITFIELD_IS_FAVORITE_MASK, false);
|
||||
eraseNodeSatellites(contact.node_num);
|
||||
#if HAS_SCREEN || defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS)
|
||||
messageStore.deleteAllMessagesFromNode(contact.node_num);
|
||||
#endif
|
||||
} else {
|
||||
/* Clients are sending add_contact before every text message DM (because clients may hold a larger node database with
|
||||
* public keys than the radio holds). However, we don't want to update last_heard just because we sent someone a DM!
|
||||
@@ -3788,7 +3776,7 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
|
||||
// Demote to the warm tier so the identity (and crucially the
|
||||
// PKI key) outlives the hot-store slot.
|
||||
warmStore.absorb(evicted.num, evicted.last_heard, evicted.public_key.size == 32 ? evicted.public_key.bytes : NULL,
|
||||
evicted.role, warmProtectedCategory(evicted));
|
||||
evicted.role, warmProtectedCategory(evicted), nodeInfoLiteHasXeddsaSigned(&evicted));
|
||||
#endif
|
||||
eraseNodeSatellites(evicted.num);
|
||||
// Shove the remaining nodes down the chain
|
||||
@@ -3817,10 +3805,13 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
|
||||
// Re-admission: restore what the warm tier kept for this node
|
||||
WarmNodeEntry warm;
|
||||
if (warmStore.take(n, warm)) {
|
||||
lite->last_heard = warmTimeOf(warm); // mask off the stolen role/protected metadata bits
|
||||
lite->last_heard = warmTimeOf(warm); // mask off the stolen metadata bits
|
||||
// Restore the role the warm tier cached, so re-admission isn't stuck at CLIENT
|
||||
// until the next NodeInfo arrives.
|
||||
lite->role = static_cast<meshtastic_Config_DeviceConfig_Role>(warmRoleOf(warm));
|
||||
// Restore the signer bit too: it is learned from verified traffic, not from
|
||||
// NodeInfo, so a round trip through the warm tier must not relearn it from zero.
|
||||
nodeInfoLiteSetBit(lite, NODEINFO_BITFIELD_HAS_XEDDSA_SIGNED_MASK, warmSignerOf(warm));
|
||||
if (!memfll(warm.public_key, 0, sizeof(warm.public_key))) {
|
||||
lite->public_key.size = 32;
|
||||
memcpy(lite->public_key.bytes, warm.public_key, 32);
|
||||
|
||||
+4
-1
@@ -156,7 +156,10 @@ inline bool shouldDropPacketForPreHop(const meshtastic_MeshPacket &p)
|
||||
if (isFromUs(&p)) {
|
||||
return false; // local-originated packets should never be dropped by pre-hop drop policy
|
||||
}
|
||||
return classifyHopStart(p) != HopStartStatus::VALID;
|
||||
// Pre-decode: the channel-encrypted bitfield isn't readable yet, so a missing/unknown hop_start can't be
|
||||
// distinguished from a modern packet. Only drop the provably-corrupt case here; the bitfield-dependent
|
||||
// verdict is re-checked post-decode in Router::handleReceived.
|
||||
return classifyHopStart(p) == HopStartStatus::INVALID;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -1,253 +0,0 @@
|
||||
#include "PacketCache.h"
|
||||
#include "Router.h"
|
||||
|
||||
PacketCache packetCache{};
|
||||
|
||||
/**
|
||||
* Allocate a new cache entry and copy the packet header and payload into it
|
||||
*/
|
||||
PacketCacheEntry *PacketCache::cache(const meshtastic_MeshPacket *p, bool preserveMetadata)
|
||||
{
|
||||
size_t payload_size =
|
||||
(p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) ? p->encrypted.size : p->decoded.payload.size;
|
||||
PacketCacheEntry *e = (PacketCacheEntry *)malloc(sizeof(PacketCacheEntry) + payload_size +
|
||||
(preserveMetadata ? sizeof(PacketCacheMetadata) : 0));
|
||||
if (!e) {
|
||||
LOG_ERROR("Unable to allocate memory for packet cache entry");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*e = {};
|
||||
e->header.from = p->from;
|
||||
e->header.to = p->to;
|
||||
e->header.id = p->id;
|
||||
e->header.channel = p->channel;
|
||||
e->header.next_hop = p->next_hop;
|
||||
e->header.relay_node = p->relay_node;
|
||||
e->header.flags = (p->hop_limit & PACKET_FLAGS_HOP_LIMIT_MASK) | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0) |
|
||||
(p->via_mqtt ? PACKET_FLAGS_VIA_MQTT_MASK : 0) |
|
||||
((p->hop_start << PACKET_FLAGS_HOP_START_SHIFT) & PACKET_FLAGS_HOP_START_MASK);
|
||||
|
||||
PacketCacheMetadata m{};
|
||||
if (preserveMetadata) {
|
||||
e->has_metadata = true;
|
||||
m.rx_rssi = (uint8_t)(p->rx_rssi + 200);
|
||||
m.rx_snr = (uint8_t)((p->rx_snr + 30.0f) / 0.25f);
|
||||
m.rx_time = p->rx_time;
|
||||
m.transport_mechanism = p->transport_mechanism;
|
||||
m.priority = p->priority;
|
||||
}
|
||||
if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) {
|
||||
e->encrypted = true;
|
||||
e->payload_len = p->encrypted.size;
|
||||
memcpy(((unsigned char *)e) + sizeof(PacketCacheEntry), p->encrypted.bytes, p->encrypted.size);
|
||||
} else if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
||||
e->encrypted = false;
|
||||
if (preserveMetadata) {
|
||||
m.portnum = p->decoded.portnum;
|
||||
m.want_response = p->decoded.want_response;
|
||||
m.emoji = p->decoded.emoji;
|
||||
m.bitfield = p->decoded.bitfield;
|
||||
if (p->decoded.reply_id)
|
||||
m.reply_id = p->decoded.reply_id;
|
||||
else if (p->decoded.request_id)
|
||||
m.request_id = p->decoded.request_id;
|
||||
}
|
||||
e->payload_len = p->decoded.payload.size;
|
||||
memcpy(((unsigned char *)e) + sizeof(PacketCacheEntry), p->decoded.payload.bytes, p->decoded.payload.size);
|
||||
} else {
|
||||
LOG_ERROR("Unable to cache packet with unknown payload type %d", p->which_payload_variant);
|
||||
free(e);
|
||||
return NULL;
|
||||
}
|
||||
if (preserveMetadata)
|
||||
memcpy(((unsigned char *)e) + sizeof(PacketCacheEntry) + e->payload_len, &m, sizeof(m));
|
||||
|
||||
size += sizeof(PacketCacheEntry) + e->payload_len + (e->has_metadata ? sizeof(PacketCacheMetadata) : 0);
|
||||
insert(e);
|
||||
return e;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dump a list of packets into the provided buffer
|
||||
*/
|
||||
void PacketCache::dump(void *dest, const PacketCacheEntry **entries, size_t num_entries)
|
||||
{
|
||||
unsigned char *pos = (unsigned char *)dest;
|
||||
for (size_t i = 0; i < num_entries; i++) {
|
||||
size_t entry_len =
|
||||
sizeof(PacketCacheEntry) + entries[i]->payload_len + (entries[i]->has_metadata ? sizeof(PacketCacheMetadata) : 0);
|
||||
memcpy(pos, entries[i], entry_len);
|
||||
pos += entry_len;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the length of buffer needed to dump the specified entries
|
||||
*/
|
||||
size_t PacketCache::dumpSize(const PacketCacheEntry **entries, size_t num_entries)
|
||||
{
|
||||
size_t total_size = 0;
|
||||
for (size_t i = 0; i < num_entries; i++) {
|
||||
total_size += sizeof(PacketCacheEntry) + entries[i]->payload_len;
|
||||
if (entries[i]->has_metadata)
|
||||
total_size += sizeof(PacketCacheMetadata);
|
||||
}
|
||||
return total_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a packet in the cache
|
||||
*/
|
||||
PacketCacheEntry *PacketCache::find(NodeNum from, PacketId id)
|
||||
{
|
||||
uint16_t h = PACKET_HASH(from, id);
|
||||
PacketCacheEntry *e = buckets[PACKET_CACHE_BUCKET(h)];
|
||||
while (e) {
|
||||
if (e->header.from == from && e->header.id == id)
|
||||
return e;
|
||||
e = e->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a packet in the cache by its hash
|
||||
*/
|
||||
PacketCacheEntry *PacketCache::find(PacketHash h)
|
||||
{
|
||||
PacketCacheEntry *e = buckets[PACKET_CACHE_BUCKET(h)];
|
||||
while (e) {
|
||||
if (PACKET_HASH(e->header.from, e->header.id) == h)
|
||||
return e;
|
||||
e = e->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a list of packets from the provided buffer
|
||||
*/
|
||||
bool PacketCache::load(void *src, PacketCacheEntry **entries, size_t num_entries)
|
||||
{
|
||||
memset(entries, 0, sizeof(PacketCacheEntry *) * num_entries);
|
||||
unsigned char *pos = (unsigned char *)src;
|
||||
for (size_t i = 0; i < num_entries; i++) {
|
||||
PacketCacheEntry e{};
|
||||
memcpy(&e, pos, sizeof(PacketCacheEntry));
|
||||
size_t entry_len = sizeof(PacketCacheEntry) + e.payload_len + (e.has_metadata ? sizeof(PacketCacheMetadata) : 0);
|
||||
entries[i] = (PacketCacheEntry *)malloc(entry_len);
|
||||
size += entry_len;
|
||||
if (!entries[i]) {
|
||||
LOG_ERROR("Unable to allocate memory for packet cache entry");
|
||||
for (size_t j = 0; j < i; j++) {
|
||||
size -= sizeof(PacketCacheEntry) + entries[j]->payload_len +
|
||||
(entries[j]->has_metadata ? sizeof(PacketCacheMetadata) : 0);
|
||||
free(entries[j]);
|
||||
entries[j] = NULL;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
memcpy(entries[i], pos, entry_len);
|
||||
pos += entry_len;
|
||||
}
|
||||
for (size_t i = 0; i < num_entries; i++)
|
||||
insert(entries[i]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the cached packet into the provided MeshPacket structure
|
||||
*/
|
||||
void PacketCache::rehydrate(const PacketCacheEntry *e, meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (!e || !p)
|
||||
return;
|
||||
|
||||
*p = {};
|
||||
p->from = e->header.from;
|
||||
p->to = e->header.to;
|
||||
p->id = e->header.id;
|
||||
p->channel = e->header.channel;
|
||||
p->next_hop = e->header.next_hop;
|
||||
p->relay_node = e->header.relay_node;
|
||||
p->hop_limit = e->header.flags & PACKET_FLAGS_HOP_LIMIT_MASK;
|
||||
p->want_ack = !!(e->header.flags & PACKET_FLAGS_WANT_ACK_MASK);
|
||||
p->via_mqtt = !!(e->header.flags & PACKET_FLAGS_VIA_MQTT_MASK);
|
||||
p->hop_start = (e->header.flags & PACKET_FLAGS_HOP_START_MASK) >> PACKET_FLAGS_HOP_START_SHIFT;
|
||||
p->which_payload_variant = e->encrypted ? meshtastic_MeshPacket_encrypted_tag : meshtastic_MeshPacket_decoded_tag;
|
||||
|
||||
unsigned char *payload = ((unsigned char *)e) + sizeof(PacketCacheEntry);
|
||||
PacketCacheMetadata m{};
|
||||
if (e->has_metadata) {
|
||||
memcpy(&m, (payload + e->payload_len), sizeof(m));
|
||||
p->rx_rssi = ((int)m.rx_rssi) - 200;
|
||||
p->rx_snr = ((float)m.rx_snr * 0.25f) - 30.0f;
|
||||
p->rx_time = m.rx_time;
|
||||
p->transport_mechanism = (meshtastic_MeshPacket_TransportMechanism)m.transport_mechanism;
|
||||
p->priority = (meshtastic_MeshPacket_Priority)m.priority;
|
||||
}
|
||||
if (e->encrypted) {
|
||||
memcpy(p->encrypted.bytes, payload, e->payload_len);
|
||||
p->encrypted.size = e->payload_len;
|
||||
} else {
|
||||
memcpy(p->decoded.payload.bytes, payload, e->payload_len);
|
||||
p->decoded.payload.size = e->payload_len;
|
||||
if (e->has_metadata) {
|
||||
// Decrypted-only metadata
|
||||
p->decoded.portnum = (meshtastic_PortNum)m.portnum;
|
||||
p->decoded.want_response = m.want_response;
|
||||
p->decoded.emoji = m.emoji;
|
||||
p->decoded.bitfield = m.bitfield;
|
||||
if (m.reply_id)
|
||||
p->decoded.reply_id = m.reply_id;
|
||||
else if (m.request_id)
|
||||
p->decoded.request_id = m.request_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release a cache entry
|
||||
*/
|
||||
void PacketCache::release(PacketCacheEntry *e)
|
||||
{
|
||||
if (!e)
|
||||
return;
|
||||
remove(e);
|
||||
size -= sizeof(PacketCacheEntry) + e->payload_len + (e->has_metadata ? sizeof(PacketCacheMetadata) : 0);
|
||||
free(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new entry into the hash table
|
||||
*/
|
||||
void PacketCache::insert(PacketCacheEntry *e)
|
||||
{
|
||||
assert(e);
|
||||
PacketHash h = PACKET_HASH(e->header.from, e->header.id);
|
||||
PacketCacheEntry **target = &buckets[PACKET_CACHE_BUCKET(h)];
|
||||
e->next = *target;
|
||||
*target = e;
|
||||
num_entries++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an entry from the hash table
|
||||
*/
|
||||
void PacketCache::remove(PacketCacheEntry *e)
|
||||
{
|
||||
assert(e);
|
||||
PacketHash h = PACKET_HASH(e->header.from, e->header.id);
|
||||
PacketCacheEntry **target = &buckets[PACKET_CACHE_BUCKET(h)];
|
||||
while (*target) {
|
||||
if (*target == e) {
|
||||
*target = e->next;
|
||||
e->next = NULL;
|
||||
num_entries--;
|
||||
break;
|
||||
} else {
|
||||
target = &(*target)->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
#pragma once
|
||||
#include "RadioInterface.h"
|
||||
|
||||
#define PACKET_HASH(a, b) ((((a ^ b) >> 16) ^ (a ^ b)) & 0xFFFF) // 16 bit fold of packet (from, id) tuple
|
||||
typedef uint16_t PacketHash;
|
||||
|
||||
#define PACKET_CACHE_BUCKETS 64 // Number of hash table buckets
|
||||
#define PACKET_CACHE_BUCKET(h) (((h >> 12) ^ (h >> 6) ^ h) & 0x3F) // Fold hash down to 6-bit bucket index
|
||||
|
||||
typedef struct PacketCacheEntry {
|
||||
PacketCacheEntry *next;
|
||||
PacketHeader header;
|
||||
uint16_t payload_len = 0;
|
||||
union {
|
||||
uint16_t bitfield;
|
||||
struct {
|
||||
uint8_t encrypted : 1; // Payload is encrypted
|
||||
uint8_t has_metadata : 1; // Payload includes PacketCacheMetadata
|
||||
uint8_t : 6; // Reserved for future use
|
||||
uint8_t : 8; // Reserved for future use
|
||||
};
|
||||
};
|
||||
} PacketCacheEntry;
|
||||
|
||||
typedef struct PacketCacheMetadata {
|
||||
PacketCacheMetadata() : _bitfield(0), reply_id(0), _bitfield2(0) {}
|
||||
union {
|
||||
uint32_t _bitfield;
|
||||
struct {
|
||||
uint16_t portnum : 9; // meshtastic_MeshPacket::decoded::portnum
|
||||
uint16_t want_response : 1; // meshtastic_MeshPacket::decoded::want_response
|
||||
uint16_t emoji : 1; // meshtastic_MeshPacket::decoded::emoji
|
||||
uint16_t bitfield : 5; // meshtastic_MeshPacket::decoded::bitfield (truncated)
|
||||
uint8_t rx_rssi : 8; // meshtastic_MeshPacket::rx_rssi (map via actual RSSI + 200)
|
||||
uint8_t rx_snr : 8; // meshtastic_MeshPacket::rx_snr (map via (p->rx_snr + 30.0f) / 0.25f)
|
||||
};
|
||||
};
|
||||
union {
|
||||
uint32_t reply_id; // meshtastic_MeshPacket::decoded.reply_id
|
||||
uint32_t request_id; // meshtastic_MeshPacket::decoded.request_id
|
||||
};
|
||||
uint32_t rx_time = 0; // meshtastic_MeshPacket::rx_time
|
||||
uint8_t transport_mechanism = 0; // meshtastic_MeshPacket::transport_mechanism
|
||||
struct {
|
||||
uint8_t _bitfield2;
|
||||
union {
|
||||
uint8_t priority : 7; // meshtastic_MeshPacket::priority
|
||||
uint8_t reserved : 1; // Reserved for future use
|
||||
};
|
||||
};
|
||||
} PacketCacheMetadata;
|
||||
|
||||
class PacketCache
|
||||
{
|
||||
public:
|
||||
PacketCacheEntry *cache(const meshtastic_MeshPacket *p, bool preserveMetadata);
|
||||
static void dump(void *dest, const PacketCacheEntry **entries, size_t num_entries);
|
||||
size_t dumpSize(const PacketCacheEntry **entries, size_t num_entries);
|
||||
PacketCacheEntry *find(NodeNum from, PacketId id);
|
||||
PacketCacheEntry *find(PacketHash h);
|
||||
bool load(void *src, PacketCacheEntry **entries, size_t num_entries);
|
||||
size_t getNumEntries() { return num_entries; }
|
||||
size_t getSize() { return size; }
|
||||
void rehydrate(const PacketCacheEntry *e, meshtastic_MeshPacket *p);
|
||||
void release(PacketCacheEntry *e);
|
||||
|
||||
private:
|
||||
PacketCacheEntry *buckets[PACKET_CACHE_BUCKETS]{};
|
||||
size_t num_entries = 0;
|
||||
size_t size = 0;
|
||||
void insert(PacketCacheEntry *e);
|
||||
void remove(PacketCacheEntry *e);
|
||||
};
|
||||
|
||||
extern PacketCache packetCache;
|
||||
+81
-31
@@ -38,7 +38,7 @@
|
||||
#include "mqtt/MQTT.h"
|
||||
#endif
|
||||
#include "Throttle.h"
|
||||
#include <RTC.h>
|
||||
#include "gps/RTC.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -1201,19 +1201,49 @@ void PhoneAPI::prefetchNodeInfos()
|
||||
onNowHasData(0);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
/// Derive a stable id for a replayed satellite-DB record. Unchanged history replayed on
|
||||
/// every reconnect must not look like a new packet to the phone's history/dedup - the id
|
||||
/// only changes when the underlying data (its timestamp) actually changes.
|
||||
// `kind` only needs to distinguish the record types that can otherwise collide (e.g. device
|
||||
// vs. environment metrics both replay as TELEMETRY_APP with the same node/last_heard) - pass
|
||||
// the payload's own variant/port constant.
|
||||
uint32_t makeReplayPacketId(NodeNum num, uint32_t timestamp, uint32_t kind)
|
||||
{
|
||||
uint32_t h = num;
|
||||
h = h * 2654435761u + timestamp;
|
||||
h = h * 2654435761u + kind;
|
||||
return h ? h : 1; // some clients treat id 0 as "unset"
|
||||
}
|
||||
|
||||
/// Populate hop_start/hop_limit from the node's real last-known hop count (if any) so a
|
||||
/// replayed packet doesn't read as "heard directly" when it wasn't.
|
||||
void setReplayHopFields(meshtastic_MeshPacket &pkt, const meshtastic_NodeInfoLite *header)
|
||||
{
|
||||
uint8_t hopLimit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
|
||||
uint8_t hopsAway = (header && header->has_hops_away) ? header->hops_away : 0;
|
||||
pkt.hop_start = hopLimit;
|
||||
pkt.hop_limit = hopsAway < hopLimit ? (uint8_t)(hopLimit - hopsAway) : 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
meshtastic_MeshPacket PhoneAPI::makeReplayPositionPacket(NodeNum num, const meshtastic_PositionLite &pos)
|
||||
{
|
||||
// Shape this exactly like a fresh live broadcast Position from the peer so the
|
||||
// phone runs it through its normal "live position broadcast" handler path.
|
||||
// to=ourNum would read as a DM-from-peer and never lands in node detail UI.
|
||||
meshtastic_MeshPacket pkt = meshtastic_MeshPacket_init_default;
|
||||
const meshtastic_NodeInfoLite *header = nodeDB->getMeshNode(num);
|
||||
pkt.from = num;
|
||||
pkt.to = NODENUM_BROADCAST;
|
||||
pkt.id = generatePacketId();
|
||||
pkt.rx_time = pos.time;
|
||||
// Stable per-node/per-fix id: replaying the same unchanged history on every
|
||||
// reconnect must not look like a brand new packet to the phone's history/dedup.
|
||||
pkt.id = makeReplayPacketId(num, pkt.rx_time, meshtastic_PortNum_POSITION_APP);
|
||||
pkt.channel = 0;
|
||||
pkt.hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
|
||||
pkt.hop_start = pkt.hop_limit;
|
||||
pkt.rx_snr = header ? header->snr : 0;
|
||||
setReplayHopFields(pkt, header);
|
||||
pkt.priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||
// Mark as if heard over the air, not internally generated
|
||||
pkt.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA;
|
||||
@@ -1231,13 +1261,13 @@ meshtastic_MeshPacket PhoneAPI::makeReplayTelemetryPacket(NodeNum num, const mes
|
||||
meshtastic_MeshPacket pkt = meshtastic_MeshPacket_init_default;
|
||||
pkt.from = num;
|
||||
pkt.to = NODENUM_BROADCAST;
|
||||
pkt.id = generatePacketId();
|
||||
// No native timestamp on telemetry packets here; use last_heard.
|
||||
const meshtastic_NodeInfoLite *header = nodeDB->getMeshNode(num);
|
||||
pkt.rx_time = header ? header->last_heard : 0;
|
||||
pkt.id = makeReplayPacketId(num, pkt.rx_time, meshtastic_Telemetry_device_metrics_tag);
|
||||
pkt.channel = 0;
|
||||
pkt.hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
|
||||
pkt.hop_start = pkt.hop_limit;
|
||||
pkt.rx_snr = header ? header->snr : 0;
|
||||
setReplayHopFields(pkt, header);
|
||||
pkt.priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||
// Mark as if heard over the air, not internally generated - iOS client filters
|
||||
// TRANSPORT_INTERNAL packets out of broadcast peer state updates.
|
||||
@@ -1337,12 +1367,12 @@ meshtastic_MeshPacket PhoneAPI::makeReplayEnvironmentPacket(uint32_t num, const
|
||||
meshtastic_MeshPacket pkt = meshtastic_MeshPacket_init_default;
|
||||
pkt.from = num;
|
||||
pkt.to = NODENUM_BROADCAST;
|
||||
pkt.id = generatePacketId();
|
||||
const meshtastic_NodeInfoLite *header = nodeDB->getMeshNode(num);
|
||||
pkt.rx_time = header ? header->last_heard : 0;
|
||||
pkt.id = makeReplayPacketId(num, pkt.rx_time, meshtastic_Telemetry_environment_metrics_tag);
|
||||
pkt.channel = 0;
|
||||
pkt.hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
|
||||
pkt.hop_start = pkt.hop_limit;
|
||||
pkt.rx_snr = header ? header->snr : 0;
|
||||
setReplayHopFields(pkt, header);
|
||||
pkt.priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||
// Mark as if heard over the air, not internally generated - iOS client filters
|
||||
// TRANSPORT_INTERNAL packets out of broadcast peer state updates.
|
||||
@@ -1400,13 +1430,13 @@ meshtastic_MeshPacket PhoneAPI::makeReplayStatusPacket(uint32_t num, const mesht
|
||||
meshtastic_MeshPacket pkt = meshtastic_MeshPacket_init_default;
|
||||
pkt.from = num;
|
||||
pkt.to = NODENUM_BROADCAST;
|
||||
pkt.id = generatePacketId();
|
||||
// StatusMessage has no native timestamp; use last_heard.
|
||||
const meshtastic_NodeInfoLite *header = nodeDB->getMeshNode(num);
|
||||
pkt.rx_time = header ? header->last_heard : 0;
|
||||
pkt.id = makeReplayPacketId(num, pkt.rx_time, meshtastic_PortNum_NODE_STATUS_APP);
|
||||
pkt.channel = 0;
|
||||
pkt.hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
|
||||
pkt.hop_start = pkt.hop_limit;
|
||||
pkt.rx_snr = header ? header->snr : 0;
|
||||
setReplayHopFields(pkt, header);
|
||||
pkt.priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||
// Mark as if heard over the air, not internally generated - client filters
|
||||
pkt.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA;
|
||||
@@ -1667,6 +1697,19 @@ bool PhoneAPI::wasSeenRecently(uint32_t id)
|
||||
return false;
|
||||
}
|
||||
|
||||
PhoneAPI::LocalAdminGate PhoneAPI::classifyLocalAdminPacket(const meshtastic_MeshPacket &p, bool adminAuthorized,
|
||||
meshtastic_AdminMessage &outAdmin)
|
||||
{
|
||||
if (p.which_payload_variant != meshtastic_MeshPacket_decoded_tag || p.decoded.portnum != meshtastic_PortNum_ADMIN_APP)
|
||||
return LocalAdminGate::NotAdmin;
|
||||
outAdmin = meshtastic_AdminMessage_init_zero;
|
||||
if (!pb_decode_from_bytes(p.decoded.payload.bytes, p.decoded.payload.size, &meshtastic_AdminMessage_msg, &outAdmin))
|
||||
return LocalAdminGate::NotAdmin; // undecodable: let the normal reject path respond
|
||||
if (outAdmin.which_payload_variant == meshtastic_AdminMessage_lockdown_auth_tag)
|
||||
return LocalAdminGate::LockdownAuth; // the passphrase itself; authenticate regardless of prior state
|
||||
return adminAuthorized ? LocalAdminGate::AuthorizedPassThrough : LocalAdminGate::DropUnauthorized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a packet that the phone wants us to send. It is our responsibility to free the packet to the pool
|
||||
*/
|
||||
@@ -1691,26 +1734,33 @@ bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p)
|
||||
// the gate here closes that race and covers H6/H7 from the
|
||||
// audit: get_config_request and set_config from unauthed
|
||||
// clients no longer reach AdminModule at all.
|
||||
if (p.from == 0 && p.which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
|
||||
p.decoded.portnum == meshtastic_PortNum_ADMIN_APP) {
|
||||
// Gate on the connection, not the wire `from`, which a client can forge to a non-zero value to
|
||||
// bypass the check before MeshService normalizes it back to a local identity.
|
||||
// Scope the decoded admin message: it holds the plaintext passphrase, so bound its lifetime.
|
||||
{
|
||||
meshtastic_AdminMessage admin = meshtastic_AdminMessage_init_zero;
|
||||
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
|
||||
// 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++)
|
||||
adminVol[i] = 0;
|
||||
return true;
|
||||
}
|
||||
if (!getAdminAuthorized()) {
|
||||
LOG_WARN("Lockdown: dropping admin payload variant=%d from unauthorized connection", admin.which_payload_variant);
|
||||
return false;
|
||||
}
|
||||
switch (classifyLocalAdminPacket(p, getAdminAuthorized(), admin)) {
|
||||
case LocalAdminGate::LockdownAuth: {
|
||||
handleLockdownAuthInline(admin.lockdown_auth);
|
||||
// The encoded wipe is the security-critical one: nothing else clears the passphrase from
|
||||
// the packet buffer. handleLockdownAuthInline already zeroes the decoded copy up to its
|
||||
// size; re-zero the full scratch capacity here as defense in depth.
|
||||
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++)
|
||||
adminVol[i] = 0;
|
||||
volatile uint8_t *encodedVol = const_cast<volatile uint8_t *>(p.decoded.payload.bytes);
|
||||
for (size_t i = 0; i < sizeof(p.decoded.payload.bytes); i++)
|
||||
encodedVol[i] = 0;
|
||||
p.decoded.payload.size = 0; // keep the length consistent with the wiped buffer
|
||||
return true;
|
||||
}
|
||||
case LocalAdminGate::DropUnauthorized:
|
||||
LOG_WARN("Lockdown: dropping admin payload variant=%d from unauthorized connection", admin.which_payload_variant);
|
||||
return false;
|
||||
case LocalAdminGate::NotAdmin:
|
||||
case LocalAdminGate::AuthorizedPassThrough:
|
||||
break; // normal handling
|
||||
}
|
||||
// pb_decode failure: fall through to normal handling so the
|
||||
// regular Router/AdminModule reject path can respond.
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -319,4 +319,18 @@ class PhoneAPI
|
||||
|
||||
/// If the mesh service tells us fromNum has changed, tell the phone
|
||||
virtual int onNotify(uint32_t newValue) override;
|
||||
|
||||
public:
|
||||
/// How the lockdown admin gate should treat a phone->radio packet.
|
||||
enum class LocalAdminGate {
|
||||
NotAdmin, ///< Not a decodable ADMIN_APP payload; normal handling.
|
||||
LockdownAuth, ///< A lockdown_auth payload; authenticate the connection inline.
|
||||
DropUnauthorized, ///< Admin payload from a connection that has not authenticated; drop.
|
||||
AuthorizedPassThrough, ///< Admin payload from an authorized connection; normal handling.
|
||||
};
|
||||
|
||||
/// Classify a phone->radio packet for the lockdown admin gate, ignoring the wire `from` (which a
|
||||
/// client can forge) and deciding on the connection's authorization. Fills outAdmin for lockdown.
|
||||
static LocalAdminGate classifyLocalAdminPacket(const meshtastic_MeshPacket &p, bool adminAuthorized,
|
||||
meshtastic_AdminMessage &outAdmin);
|
||||
};
|
||||
|
||||
+43
-21
@@ -39,8 +39,8 @@
|
||||
#endif
|
||||
|
||||
static const meshtastic_Config_LoRaConfig_ModemPreset PRESETS_STD[] = {
|
||||
PRESET(LONG_FAST), PRESET(LONG_SLOW), PRESET(MEDIUM_SLOW), PRESET(MEDIUM_FAST), PRESET(SHORT_SLOW),
|
||||
PRESET(SHORT_FAST), PRESET(LONG_MODERATE), PRESET(SHORT_TURBO), PRESET(LONG_TURBO), MODEM_PRESET_END};
|
||||
PRESET(LONG_FAST), PRESET(LONG_SLOW), PRESET(MEDIUM_SLOW), PRESET(MEDIUM_FAST), PRESET(SHORT_SLOW), PRESET(SHORT_FAST),
|
||||
PRESET(LONG_MODERATE), PRESET(SHORT_TURBO), PRESET(LONG_TURBO), PRESET(MEDIUM_TURBO), MODEM_PRESET_END};
|
||||
|
||||
static const meshtastic_Config_LoRaConfig_ModemPreset PRESETS_EU_868[] = {
|
||||
PRESET(LONG_FAST), PRESET(LONG_SLOW), PRESET(MEDIUM_SLOW), PRESET(MEDIUM_FAST),
|
||||
@@ -55,6 +55,34 @@ static const meshtastic_Config_LoRaConfig_ModemPreset PRESETS_NARROW[] = {PRESET
|
||||
|
||||
static const meshtastic_Config_LoRaConfig_ModemPreset PRESETS_TINY[] = {PRESET(TINY_FAST), PRESET(TINY_SLOW), MODEM_PRESET_END};
|
||||
|
||||
// The EU_868/EU_866/EU_N_868 trio share the 868 band but own mutually exclusive preset
|
||||
// profiles. Selecting a preset locked to a sibling swaps the region to that sibling (see
|
||||
// regionSwapForPreset), so from any region in the trio every one of these presets is
|
||||
// selectable. This union is what we advertise to clients as the trio's legal list. It is a
|
||||
// display-only superset: on-device enforcement still uses each region's own disjoint
|
||||
// profile->presets, so this must never be assigned to a RegionProfile (that would make
|
||||
// supportsPreset() accept the preset in place and defeat the swap). Keep in sync with the
|
||||
// EU_868/EU_866/EU_N_868 profile lists below. Sized to the 11-preset wire cap.
|
||||
static const meshtastic_Config_LoRaConfig_ModemPreset PRESETS_EU_SUPERSET[] = {
|
||||
PRESET(LONG_FAST), PRESET(LONG_SLOW), PRESET(MEDIUM_SLOW), PRESET(MEDIUM_FAST), PRESET(SHORT_SLOW), PRESET(SHORT_FAST),
|
||||
PRESET(LONG_MODERATE), PRESET(LITE_FAST), PRESET(LITE_SLOW), PRESET(NARROW_FAST), PRESET(NARROW_SLOW), MODEM_PRESET_END};
|
||||
|
||||
// The EU_868/EU_866/EU_N_868 trio own mutually exclusive preset lists. Selecting a preset
|
||||
// locked to a sibling means the user wants that sibling region, not the default preset.
|
||||
static const meshtastic_Config_LoRaConfig_RegionCode SWAPPABLE_EU_REGIONS[] = {
|
||||
meshtastic_Config_LoRaConfig_RegionCode_EU_868,
|
||||
meshtastic_Config_LoRaConfig_RegionCode_EU_866,
|
||||
meshtastic_Config_LoRaConfig_RegionCode_EU_N_868,
|
||||
};
|
||||
|
||||
static bool isSwappableEuRegion(meshtastic_Config_LoRaConfig_RegionCode code)
|
||||
{
|
||||
for (auto c : SWAPPABLE_EU_REGIONS)
|
||||
if (c == code)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Region profiles: bundle preset list + regulatory parameters shared across regions
|
||||
// presets, spacing, padding, audio, licensed, text throttle, position throttle, telemetry throttle
|
||||
const RegionProfile PROFILE_STD = {PRESETS_STD, 0, 0, true, false, 0, 1, 1};
|
||||
@@ -184,11 +212,9 @@ const RegionInfo regions[] = {
|
||||
|
||||
/*
|
||||
433,05-434,7 Mhz 10 mW
|
||||
868,0-868,6 Mhz 25 mW
|
||||
https://nkrzi.gov.ua/images/upload/256/5810/PDF_UUZ_19_01_2016.pdf
|
||||
https://zakon.rada.gov.ua/laws/show/262-2026-п
|
||||
*/
|
||||
RDEF(UA_433, 433.0f, 434.7f, 10, 10, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
|
||||
RDEF(UA_868, 868.0f, 868.6f, 1, 14, false, false, PROFILE_STD, PRESET(LONG_FAST), 0),
|
||||
|
||||
/*
|
||||
Malaysia
|
||||
@@ -337,8 +363,12 @@ LoRaRadioType radioType = NO_RADIO;
|
||||
|
||||
extern RadioLibHal *RadioLibHAL;
|
||||
#if defined(HW_SPI1_DEVICE) && defined(ARCH_ESP32)
|
||||
#if defined(HAS_SDCARD) && defined(SDCARD_USE_SPI1)
|
||||
extern SPIClass &SPI1; // alias for SPI_HSPI; both on SPI2_HOST
|
||||
#else
|
||||
extern SPIClass SPI1;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
std::unique_ptr<RadioInterface> initLoRa()
|
||||
{
|
||||
@@ -685,8 +715,13 @@ void getRegionPresetMap(meshtastic_LoRaRegionPresetMap &map)
|
||||
grp.default_preset = r->getDefaultPreset();
|
||||
grp.licensed_only = r->profile->licensedOnly;
|
||||
grp.presets_count = 0;
|
||||
for (size_t i = 0; r->profile->presets[i] != MODEM_PRESET_END && grp.presets_count < maxPresets; i++)
|
||||
grp.presets[grp.presets_count++] = r->profile->presets[i];
|
||||
// EU 86x siblings advertise the trio's superset - any of those presets is
|
||||
// reachable from here via an automatic region swap. Every other region
|
||||
// advertises exactly its own enforced profile list.
|
||||
const meshtastic_Config_LoRaConfig_ModemPreset *advertised =
|
||||
isSwappableEuRegion(r->code) ? PRESETS_EU_SUPERSET : r->profile->presets;
|
||||
for (size_t i = 0; advertised[i] != MODEM_PRESET_END && grp.presets_count < maxPresets; i++)
|
||||
grp.presets[grp.presets_count++] = advertised[i];
|
||||
}
|
||||
|
||||
// Map this region to its group (capacity checked at the top of the loop).
|
||||
@@ -961,14 +996,6 @@ static void sendErrorNotification(const char *msg, meshtastic_LogRecord_Level le
|
||||
service->sendClientNotification(cn);
|
||||
}
|
||||
|
||||
// The EU_868/EU_866/EU_N_868 trio own mutually exclusive preset lists. Selecting a preset
|
||||
// locked to a sibling means the user wants that sibling region, not the default preset.
|
||||
static const meshtastic_Config_LoRaConfig_RegionCode SWAPPABLE_EU_REGIONS[] = {
|
||||
meshtastic_Config_LoRaConfig_RegionCode_EU_868,
|
||||
meshtastic_Config_LoRaConfig_RegionCode_EU_866,
|
||||
meshtastic_Config_LoRaConfig_RegionCode_EU_N_868,
|
||||
};
|
||||
|
||||
/**
|
||||
* If currentRegion is one of the swappable EU regions and preset belongs to a sibling in
|
||||
* that trio, return the sibling region that owns the preset. Returns nullptr otherwise.
|
||||
@@ -976,12 +1003,7 @@ static const meshtastic_Config_LoRaConfig_RegionCode SWAPPABLE_EU_REGIONS[] = {
|
||||
const RegionInfo *RadioInterface::regionSwapForPreset(meshtastic_Config_LoRaConfig_RegionCode currentRegion,
|
||||
meshtastic_Config_LoRaConfig_ModemPreset preset)
|
||||
{
|
||||
bool currentIsSwappable = false;
|
||||
for (auto code : SWAPPABLE_EU_REGIONS) {
|
||||
if (code == currentRegion)
|
||||
currentIsSwappable = true;
|
||||
}
|
||||
if (!currentIsSwappable)
|
||||
if (!isSwappableEuRegion(currentRegion))
|
||||
return nullptr;
|
||||
|
||||
for (auto code : SWAPPABLE_EU_REGIONS) {
|
||||
|
||||
+74
-25
@@ -5,7 +5,7 @@
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "PositionPrecision.h"
|
||||
#include "RTC.h"
|
||||
#include "gps/RTC.h"
|
||||
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
@@ -711,37 +711,62 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
|
||||
bool matchedChannel = false;
|
||||
ChannelIndex chIndex = 0;
|
||||
#if !(MESHTASTIC_EXCLUDE_PKI)
|
||||
// Attempt PKI decryption first. The sender's key may come from the hot
|
||||
// store or the warm tier (nodes evicted from the hot store keep their key
|
||||
// there), so DMs from long-tail nodes still decrypt.
|
||||
meshtastic_NodeInfoLite_public_key_t fromKey = {0, {0}};
|
||||
if (p->channel == 0 && isToUs(p) && p->to > 0 && !isBroadcast(p->to) && nodeDB->copyPublicKey(p->from, fromKey) &&
|
||||
nodeDB->getMeshNode(p->to) != nullptr && nodeDB->getMeshNode(p->to)->public_key.size > 0 &&
|
||||
rawSize > MESHTASTIC_PKC_OVERHEAD) {
|
||||
// Resolve the sender's public key: prefer the one stored in NodeDB (hot store or warm tier), else
|
||||
// fall back to a not-yet-committed key held during an in-progress key-verification handshake.
|
||||
meshtastic_NodeInfoLite_public_key_t remotePublic = {0, {0}};
|
||||
bool haveRemoteKey = nodeDB->copyPublicKey(p->from, remotePublic) || crypto->getPendingPublicKey(p->from, remotePublic);
|
||||
|
||||
meshtastic_NodeInfoLite *ourNode = nullptr;
|
||||
if (p->channel == 0 && isToUs(p) && p->to > 0 && !isBroadcast(p->to) && rawSize > MESHTASTIC_PKC_OVERHEAD &&
|
||||
(ourNode = nodeDB->getMeshNode(p->to)) != nullptr && ourNode->public_key.size > 0) {
|
||||
pkiAttempted = true;
|
||||
LOG_DEBUG("Attempt PKI decryption");
|
||||
// Try the sender's known key first, then each configured admin key so an authorized admin can
|
||||
// reach a node that has not yet learned their key. AES-CCM AEAD rejects wrong candidates.
|
||||
bool viaAdminKey = false;
|
||||
if (haveRemoteKey && crypto->decryptCurve25519(p->from, remotePublic, p->id, rawSize, p->encrypted.bytes, bytes)) {
|
||||
decrypted = true;
|
||||
}
|
||||
for (int i = 0; i < 3 && !decrypted; i++) {
|
||||
if (config.security.admin_key[i].size != 32)
|
||||
continue;
|
||||
remotePublic.size = 32;
|
||||
memcpy(remotePublic.bytes, config.security.admin_key[i].bytes, 32);
|
||||
|
||||
if (crypto->decryptCurve25519(p->from, fromKey, p->id, rawSize, p->encrypted.bytes, bytes)) {
|
||||
if (crypto->decryptCurve25519(p->from, remotePublic, p->id, rawSize, p->encrypted.bytes, bytes)) {
|
||||
decrypted = true;
|
||||
viaAdminKey = true;
|
||||
break; // stop after first successful decryption
|
||||
}
|
||||
}
|
||||
if (decrypted) {
|
||||
LOG_INFO("PKI Decryption worked!");
|
||||
|
||||
meshtastic_Data decodedtmp;
|
||||
memset(&decodedtmp, 0, sizeof(decodedtmp));
|
||||
rawSize -= MESHTASTIC_PKC_OVERHEAD;
|
||||
if (pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &decodedtmp) &&
|
||||
size_t payloadSize = rawSize - MESHTASTIC_PKC_OVERHEAD;
|
||||
if (pb_decode_from_bytes(bytes, payloadSize, &meshtastic_Data_msg, &decodedtmp) &&
|
||||
decodedtmp.portnum != meshtastic_PortNum_UNKNOWN_APP) {
|
||||
decrypted = true;
|
||||
rawSize = payloadSize; // commit the overhead subtraction only on full success
|
||||
LOG_INFO("Packet decrypted using PKI!");
|
||||
p->pki_encrypted = true;
|
||||
memcpy(p->public_key.bytes, fromKey.bytes, 32);
|
||||
memcpy(p->public_key.bytes, remotePublic.bytes, 32);
|
||||
p->public_key.size = 32;
|
||||
p->decoded = decodedtmp;
|
||||
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded
|
||||
if (viaAdminKey) {
|
||||
// Persist the admin key for the sender so future packets take the fast path and we can
|
||||
// PKI-reply; p->from is bound into the AEAD nonce, so the trusted admin authenticated it.
|
||||
meshtastic_NodeInfoLite *fromNode = nodeDB->getOrCreateMeshNode(p->from);
|
||||
if (fromNode != nullptr)
|
||||
fromNode->public_key = remotePublic;
|
||||
}
|
||||
} else {
|
||||
// AEAD already authenticated this ciphertext, so no other candidate could decode it -
|
||||
// the payload is simply malformed.
|
||||
LOG_ERROR("PKC Decrypted, but pb_decode failed!");
|
||||
return DecodeState::DECODE_FAILURE;
|
||||
}
|
||||
} else {
|
||||
LOG_WARN("PKC decrypt attempted but failed!");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -786,17 +811,16 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
|
||||
if (decrypted) {
|
||||
// parsing was successful
|
||||
p->channel = chIndex; // change to store the index instead of the hash
|
||||
if (p->decoded.has_bitfield)
|
||||
p->decoded.want_response |= p->decoded.bitfield & BITFIELD_WANT_RESPONSE_MASK;
|
||||
|
||||
#if !(MESHTASTIC_EXCLUDE_PKI) && !(MESHTASTIC_EXCLUDE_XEDDSA)
|
||||
// rawSize is the size of the encoded Data exactly as the sender built it (the PKI branch's
|
||||
// MESHTASTIC_PKC_OVERHEAD subtraction preserves that, and PKI packets are unicast so the
|
||||
// downgrade predicate ignores them anyway).
|
||||
// Use the encoded Data size before merging local-only bitfield state.
|
||||
if (!checkXeddsaReceivePolicy(p, rawSize))
|
||||
return DecodeState::DECODE_POLICY_REJECT;
|
||||
#endif
|
||||
|
||||
if (p->decoded.has_bitfield)
|
||||
p->decoded.want_response |= p->decoded.bitfield & BITFIELD_WANT_RESPONSE_MASK;
|
||||
|
||||
/* Not actually ever used.
|
||||
// Decompress if needed. jm
|
||||
if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP) {
|
||||
@@ -918,7 +942,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
|
||||
|
||||
LOG_DEBUG("Original length - %d ", p->decoded.payload.size);
|
||||
LOG_DEBUG("Compressed length - %d ", compressed_len);
|
||||
LOG_DEBUG("Original message - %s ", p->decoded.payload.bytes);
|
||||
LOG_DEBUG("Original message - %.*s ", (int)p->decoded.payload.size, p->decoded.payload.bytes);
|
||||
|
||||
// If the compressed length is greater than or equal to the original size, don't use the compressed form
|
||||
if (compressed_len >= p->decoded.payload.size) {
|
||||
@@ -947,10 +971,17 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
|
||||
ChannelIndex chIndex = p->channel; // keep as a local because we are about to change it
|
||||
|
||||
#if !(MESHTASTIC_EXCLUDE_PKI)
|
||||
// Destination key from the hot store or the warm tier (evicted
|
||||
// long-tail nodes keep their key there)
|
||||
// Resolve the destination's public key: prefer NodeDB (hot store or warm tier - evicted
|
||||
// long-tail nodes keep their key there), otherwise (for a key-verification follow-on packet
|
||||
// that explicitly requested PKI) fall back to the not-yet-verified key held during an
|
||||
// in-progress handshake. This lets us DH-encode the follow-on packet before the peer's key
|
||||
// has been committed to NodeDB.
|
||||
meshtastic_NodeInfoLite_public_key_t destKey = {0, {0}};
|
||||
bool haveDestKey = nodeDB->copyPublicKey(p->to, destKey);
|
||||
if (!haveDestKey && p->pki_encrypted && p->decoded.portnum == meshtastic_PortNum_KEY_VERIFICATION_APP &&
|
||||
crypto->getPendingPublicKey(p->to, destKey)) {
|
||||
haveDestKey = true;
|
||||
}
|
||||
// We may want to retool things so we can send a PKC packet when the client specifies a key and nodenum, even if the node
|
||||
// is not in the local nodedb
|
||||
// First, only PKC encrypt packets we are originating
|
||||
@@ -968,11 +999,17 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
|
||||
config.security.private_key.size == 32 && !isBroadcast(p->to) &&
|
||||
// Some portnums either make no sense to send with PKC
|
||||
p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP &&
|
||||
p->decoded.portnum != meshtastic_PortNum_ROUTING_APP && p->decoded.portnum != meshtastic_PortNum_POSITION_APP) {
|
||||
p->decoded.portnum != meshtastic_PortNum_ROUTING_APP && p->decoded.portnum != meshtastic_PortNum_POSITION_APP &&
|
||||
// We allow Key Verification messages to be sent without a known destination key, since the point of those messages is
|
||||
// to exchange keys. The first exchange (no usable key yet) falls through to channel encryption; the follow-on packet
|
||||
// uses the pending key resolved into haveDestKey/destKey above.
|
||||
// Though possible the first packet each direction should go non-pkc
|
||||
// to handle the case where the remote node has our key, but we don't have theirs.
|
||||
!(p->decoded.portnum == meshtastic_PortNum_KEY_VERIFICATION_APP && !haveDestKey)) {
|
||||
LOG_DEBUG("Use PKI!");
|
||||
if (numbytes + MESHTASTIC_HEADER_LENGTH + MESHTASTIC_PKC_OVERHEAD > MAX_LORA_PAYLOAD_LEN)
|
||||
return meshtastic_Routing_Error_TOO_LARGE;
|
||||
// Check for a known public key for the destination
|
||||
// Check for a usable public key for the destination (NodeDB or a pending key-verification key)
|
||||
if (!haveDestKey) {
|
||||
LOG_WARN("Unknown public key for destination node 0x%08x (portnum %d), refusing to send legacy DM", p->to,
|
||||
p->decoded.portnum);
|
||||
@@ -1085,6 +1122,18 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
|
||||
else
|
||||
printPacket("handleReceived(REMOTE)", p);
|
||||
|
||||
#if MESHTASTIC_PREHOP_DROP
|
||||
// Pre-hop firmware drop, post-decode half: the bitfield that proves the origin populated hop_start is
|
||||
// encrypted under the channel key, so it can only be evaluated now that the packet is decoded. A packet
|
||||
// whose hop_start is still missing/unknown comes from pre-hop firmware - keep it out of module
|
||||
// processing, admin handling, phone delivery, MQTT and rebroadcast. Local-origin packets are exempt.
|
||||
if (!isFromUs(p) && classifyHopStart(*p) != HopStartStatus::VALID) {
|
||||
logHopStartDrop(*p, "post-decode pre-hop drop");
|
||||
cancelSending(p->from, p->id);
|
||||
skipHandle = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Neighbor info module is disabled, ignore expensive neighbor info packets
|
||||
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
|
||||
p->decoded.portnum == meshtastic_PortNum_NEIGHBORINFO_APP &&
|
||||
|
||||
@@ -173,30 +173,6 @@ template <typename T> bool SX126xInterface<T>::init()
|
||||
LOG_WARN("Failed to apply SX1262 register 0x8B5 patch for RX improvement");
|
||||
}
|
||||
|
||||
#if 0
|
||||
// Read/write a register we are not using (only used for FSK mode) to test SPI comms
|
||||
uint8_t crcLSB = 0;
|
||||
int err = lora.readRegister(SX126X_REG_CRC_POLYNOMIAL_LSB, &crcLSB, 1);
|
||||
if(err != RADIOLIB_ERR_NONE)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
|
||||
|
||||
//if(crcLSB != 0x0f)
|
||||
// RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
|
||||
|
||||
crcLSB = 0x5a;
|
||||
err = lora.writeRegister(SX126X_REG_CRC_POLYNOMIAL_LSB, &crcLSB, 1);
|
||||
if(err != RADIOLIB_ERR_NONE)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
|
||||
|
||||
err = lora.readRegister(SX126X_REG_CRC_POLYNOMIAL_LSB, &crcLSB, 1);
|
||||
if(err != RADIOLIB_ERR_NONE)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
|
||||
|
||||
if(crcLSB != 0x5a)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
|
||||
// If we got this far register accesses (and therefore SPI comms) are good
|
||||
#endif
|
||||
|
||||
if (res == RADIOLIB_ERR_NONE)
|
||||
res = lora.setCRC(RADIOLIB_SX126X_LORA_CRC_ON);
|
||||
|
||||
|
||||
+32
-10
@@ -1,14 +1,15 @@
|
||||
#include "StreamAPI.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "RTC.h"
|
||||
#include "Throttle.h"
|
||||
#include "concurrency/LockGuard.h"
|
||||
#include "configuration.h"
|
||||
#include "gps/RTC.h"
|
||||
|
||||
#define START1 0x94
|
||||
#define START2 0xc3
|
||||
#define HEADER_LEN 4
|
||||
|
||||
/// Poll the underlying stream, drain output, and update connection state.
|
||||
int32_t StreamAPI::runOncePart()
|
||||
{
|
||||
auto result = readStream();
|
||||
@@ -17,6 +18,7 @@ int32_t StreamAPI::runOncePart()
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Consume supplied input bytes, drain output, and update connection state.
|
||||
int32_t StreamAPI::runOncePart(char *buf, uint16_t bufLen)
|
||||
{
|
||||
auto result = readStream(buf, bufLen);
|
||||
@@ -48,6 +50,11 @@ int32_t StreamAPI::readStream(const char *buf, uint16_t bufLen)
|
||||
void StreamAPI::writeStream()
|
||||
{
|
||||
if (canWrite) {
|
||||
// A transport that retained a short frame must complete it before
|
||||
// getFromRadio() advances the PhoneAPI state to the next packet.
|
||||
if (!finishPendingFrame())
|
||||
return;
|
||||
|
||||
uint32_t len;
|
||||
do {
|
||||
// Send every packet we can
|
||||
@@ -58,6 +65,7 @@ void StreamAPI::writeStream()
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse supplied bytes through the framed ToRadio receive state machine.
|
||||
int32_t StreamAPI::handleRecStream(const char *buf, uint16_t bufLen)
|
||||
{
|
||||
uint16_t index = 0;
|
||||
@@ -167,20 +175,27 @@ int32_t StreamAPI::readStream()
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode the stream marker and big-endian payload length.
|
||||
size_t StreamAPI::buildFrameHeader(uint8_t *buf, size_t payloadLen)
|
||||
{
|
||||
buf[0] = START1;
|
||||
buf[1] = START2;
|
||||
buf[2] = (payloadLen >> 8) & 0xff;
|
||||
buf[3] = payloadLen & 0xff;
|
||||
return payloadLen + HEADER_LEN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the current txBuffer over our stream
|
||||
*/
|
||||
bool StreamAPI::writeFrame(uint8_t *buf, size_t len)
|
||||
/// Write one framed payload using the transport's failure semantics.
|
||||
bool StreamAPI::writeFrame(uint8_t *buf, size_t len, bool bestEffort)
|
||||
{
|
||||
(void)bestEffort;
|
||||
if (len == 0 || !canWrite)
|
||||
return false;
|
||||
|
||||
buf[0] = START1;
|
||||
buf[1] = START2;
|
||||
buf[2] = (len >> 8) & 0xff;
|
||||
buf[3] = len & 0xff;
|
||||
|
||||
auto totalLen = len + HEADER_LEN;
|
||||
const size_t totalLen = buildFrameHeader(buf, len);
|
||||
// Serialize write-readiness checks, writes and write-failure handling
|
||||
// against concurrent stream writes/close.
|
||||
concurrency::LockGuard guard(&streamLock);
|
||||
@@ -197,11 +212,13 @@ bool StreamAPI::writeFrame(uint8_t *buf, size_t len)
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Emit the prepared main PhoneAPI payload as required output.
|
||||
bool StreamAPI::emitTxBuffer(size_t len)
|
||||
{
|
||||
return writeFrame(txBuf, len);
|
||||
return writeFrame(txBuf, len, false);
|
||||
}
|
||||
|
||||
/// Emit the initial reboot notification as a framed FromRadio payload.
|
||||
void StreamAPI::emitRebooted()
|
||||
{
|
||||
// In case we send a FromRadio packet
|
||||
@@ -213,8 +230,13 @@ void StreamAPI::emitRebooted()
|
||||
emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch));
|
||||
}
|
||||
|
||||
/// Encode and emit one protobuf LogRecord using the dedicated log buffers.
|
||||
void StreamAPI::emitLogRecord(meshtastic_LogRecord_Level level, const char *src, const char *format, va_list arg)
|
||||
{
|
||||
// A retained short log frame still points into txBufLog, so do not overwrite it.
|
||||
if (!canEncodeLogRecord())
|
||||
return;
|
||||
|
||||
// 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
|
||||
@@ -237,7 +259,7 @@ void StreamAPI::emitLogRecord(meshtastic_LogRecord_Level level, const char *src,
|
||||
|
||||
size_t len =
|
||||
pb_encode_to_bytes(txBufLog + HEADER_LEN, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratchLog);
|
||||
writeFrame(txBufLog, len);
|
||||
writeFrame(txBufLog, len, true);
|
||||
}
|
||||
|
||||
/// Hookable to find out when connection changes
|
||||
|
||||
+14
-3
@@ -91,12 +91,24 @@ class StreamAPI : public PhoneAPI
|
||||
/// Low level function to emit a protobuf encapsulated log record
|
||||
void emitLogRecord(meshtastic_LogRecord_Level level, const char *src, const char *format, va_list arg);
|
||||
|
||||
/// Return whether the transport can accept a frame of the requested size.
|
||||
virtual bool canWriteFrame(size_t frameLen) { return true; }
|
||||
/// Let transports recover from or close after an incomplete write.
|
||||
virtual void onFrameWriteFailed(size_t frameLen, size_t writtenLen) {}
|
||||
|
||||
private:
|
||||
bool writeFrame(uint8_t *buf, size_t len);
|
||||
/// Fill in the 4-byte 0x94C3 length header; returns the total frame length.
|
||||
static size_t buildFrameHeader(uint8_t *buf, size_t payloadLen);
|
||||
|
||||
/// Complete retained transport output before dequeuing another PhoneAPI packet.
|
||||
virtual bool finishPendingFrame() { return true; }
|
||||
/// Return whether the dedicated log buffer is available for encoding.
|
||||
virtual bool canEncodeLogRecord() { return true; }
|
||||
/// Frame and write a payload, optionally using best-effort admission.
|
||||
virtual bool writeFrame(uint8_t *buf, size_t len, bool bestEffort);
|
||||
|
||||
concurrency::Lock streamLock;
|
||||
|
||||
private:
|
||||
/// Dedicated scratch + tx buffer for LogRecord emission.
|
||||
///
|
||||
/// The main packet emission path (`writeStream` -> `getFromRadio` ->
|
||||
@@ -117,5 +129,4 @@ class StreamAPI : public PhoneAPI
|
||||
/// interleave on the wire.
|
||||
meshtastic_FromRadio fromRadioScratchLog = {};
|
||||
uint8_t txBufLog[MAX_STREAM_BUF_SIZE] = {0};
|
||||
concurrency::Lock streamLock;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#include "StreamFrameWriter.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
/// Start a frame or retain required output behind an incomplete frame.
|
||||
bool StreamFrameWriter::writeFrame(Stream &stream, uint8_t *frame, size_t frameLen, bool bestEffort)
|
||||
{
|
||||
if (!finishPendingFrame(stream)) {
|
||||
// Single required-frame producer invariant: writeStream() is gated on
|
||||
// finishPendingFrame(), so a second required frame can never arrive here.
|
||||
assert(bestEffort || !deferredFrame);
|
||||
|
||||
// Preserve required output that was encoded while another frame tail
|
||||
// was pending. Best-effort output is dropped instead.
|
||||
if (!bestEffort && !deferredFrame) {
|
||||
deferredFrame = frame;
|
||||
deferredFrameLen = frameLen;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Never start best-effort output unless its complete frame fits.
|
||||
if (bestEffort && stream.availableForWrite() < (int)frameLen)
|
||||
return false;
|
||||
|
||||
size_t written = stream.write(frame, frameLen);
|
||||
if (written == frameLen)
|
||||
return true;
|
||||
|
||||
pendingFrame = frame;
|
||||
pendingFrameLen = frameLen;
|
||||
pendingFrameOffset = written;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Continue the pending tail with at most one Stream::write() call.
|
||||
bool StreamFrameWriter::finishPendingFrame(Stream &stream)
|
||||
{
|
||||
if (!pendingFrame)
|
||||
return true;
|
||||
|
||||
size_t remaining = pendingFrameLen - pendingFrameOffset;
|
||||
size_t written = stream.write(pendingFrame + pendingFrameOffset, remaining);
|
||||
if (written > remaining)
|
||||
written = remaining;
|
||||
pendingFrameOffset += written;
|
||||
|
||||
if (pendingFrameOffset < pendingFrameLen)
|
||||
return false;
|
||||
|
||||
pendingFrame = nullptr;
|
||||
pendingFrameLen = 0;
|
||||
pendingFrameOffset = 0;
|
||||
|
||||
// Promote required output without starting another write in this call.
|
||||
if (deferredFrame) {
|
||||
pendingFrame = deferredFrame;
|
||||
pendingFrameLen = deferredFrameLen;
|
||||
deferredFrame = nullptr;
|
||||
deferredFrameLen = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "Stream.h"
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
/** Caller-owned frame storage must remain valid and unchanged until isIdle(). */
|
||||
class StreamFrameWriter
|
||||
{
|
||||
public:
|
||||
/// Start a complete frame, or retain a required frame behind pending output.
|
||||
bool writeFrame(Stream &stream, uint8_t *frame, size_t frameLen, bool bestEffort);
|
||||
|
||||
/// Make one bounded attempt to finish pending output.
|
||||
bool finishPendingFrame(Stream &stream);
|
||||
|
||||
/// Return true when no frame buffer is retained.
|
||||
bool isIdle() const { return pendingFrame == nullptr && deferredFrame == nullptr; }
|
||||
|
||||
private:
|
||||
uint8_t *pendingFrame = nullptr;
|
||||
size_t pendingFrameLen = 0;
|
||||
size_t pendingFrameOffset = 0;
|
||||
uint8_t *deferredFrame = nullptr;
|
||||
size_t deferredFrameLen = 0;
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "TransmitHistory.h"
|
||||
#include "FSCommon.h"
|
||||
#include "RTC.h"
|
||||
#include "SPILock.h"
|
||||
#include "gps/RTC.h"
|
||||
#include <Throttle.h>
|
||||
|
||||
#ifdef FSCom
|
||||
|
||||
+57
-39
@@ -13,12 +13,11 @@
|
||||
|
||||
#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 0x334E5257u // "WRN3" - v3: last_heard low bits carry role + protected + signer
|
||||
#define WARM_RING_MAGIC_V2 0x324E5257u // "WRN2" - v2: role + protected only; bit 6 was still timestamp.
|
||||
#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.
|
||||
// Older pages are still read on upgrade: v1 keeps identity + key but discards last_heard,
|
||||
// v2 keeps the word but clears bit 6, which was still part of the timestamp then.
|
||||
// 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.
|
||||
@@ -34,10 +33,13 @@ 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 0x334D5257u // "WRM3" - v3: last_heard low bits carry role + protected + signer
|
||||
#define WARM_STORE_MAGIC_V2 \
|
||||
0x324D5257u // "WRM2" - v2: role + protected only; bit 6 was still timestamp. On upgrade
|
||||
// we clear the signer bit, then rewrite as v3.
|
||||
#define WARM_STORE_MAGIC_V1 \
|
||||
0x314D5257u // "WRM1" - v1: last_heard was a plain timestamp. On upgrade we keep
|
||||
// identity + key but discard last_heard, then rewrite as v2.
|
||||
// identity + key but discard last_heard, then rewrite as v3.
|
||||
|
||||
#ifdef FSCom
|
||||
static const char *warmFileName = "/prefs/warm.dat";
|
||||
@@ -134,18 +136,18 @@ WarmNodeEntry *WarmNodeStore::place(NodeNum num, uint32_t lastHeard, const uint8
|
||||
return slot;
|
||||
}
|
||||
|
||||
bool WarmNodeStore::absorb(NodeNum num, uint32_t lastHeard, const uint8_t *key32, uint8_t role, uint8_t protectedCat)
|
||||
bool WarmNodeStore::absorb(NodeNum num, uint32_t lastHeard, const uint8_t *key32, uint8_t role, uint8_t protectedCat, bool signer)
|
||||
{
|
||||
// Pack role + protected category into the low bits of last_heard. place() and ring
|
||||
// replay store the raw word verbatim, so the metadata round-trips through flash.
|
||||
const uint32_t packed = warmPackLastHeard(lastHeard, role, protectedCat);
|
||||
// Pack role + protected category + signer into the low bits of last_heard. place() and
|
||||
// ring replay store the raw word verbatim, so the metadata round-trips through flash.
|
||||
const uint32_t packed = warmPackLastHeard(lastHeard, role, protectedCat, signer);
|
||||
const WarmNodeEntry *slot = place(num, packed, key32);
|
||||
if (!slot)
|
||||
return false;
|
||||
persistEntry(*slot);
|
||||
LOG_MIGRATION("WarmStore absorb 0x%08x key=%d last_heard=%u role=%u prot=%u (now %u/%u)", (unsigned)num,
|
||||
LOG_MIGRATION("WarmStore absorb 0x%08x key=%d last_heard=%u role=%u prot=%u signer=%u (now %u/%u)", (unsigned)num,
|
||||
keyIsSet(slot->public_key) ? 1 : 0, (unsigned)warmTimeOf(*slot), (unsigned)role, (unsigned)protectedCat,
|
||||
(unsigned)count(), (unsigned)capacity());
|
||||
signer ? 1u : 0u, (unsigned)count(), (unsigned)capacity());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -258,19 +260,24 @@ bool WarmNodeStore::saveIfDirty()
|
||||
// (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
|
||||
bool WarmNodeStore::ringReadHeader(uint8_t page, WarmPageHeader &h, WarmFormat *fmt) const
|
||||
{
|
||||
flash_nrf5x_read(&h, WARM_FLASH_PAGE_ADDR(page), sizeof(h));
|
||||
if (h.seq == 0xFFFFFFFFu)
|
||||
return false; // erased page
|
||||
if (h.magic == WARM_RING_MAGIC) {
|
||||
if (legacy)
|
||||
*legacy = false;
|
||||
if (fmt)
|
||||
*fmt = WarmFormat::Current;
|
||||
return true;
|
||||
}
|
||||
if (h.magic == WARM_RING_MAGIC_V2) {
|
||||
if (fmt)
|
||||
*fmt = WarmFormat::V2; // replay it, but clear the signer bit (see WARM_RING_MAGIC_V2)
|
||||
return true;
|
||||
}
|
||||
if (h.magic == WARM_RING_MAGIC_V1) {
|
||||
if (legacy)
|
||||
*legacy = true; // v1 page: replay it, but discard last_heard (see WARM_RING_MAGIC_V1)
|
||||
if (fmt)
|
||||
*fmt = WarmFormat::V1; // replay it, but discard last_heard (see WARM_RING_MAGIC_V1)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -386,13 +393,13 @@ void WarmNodeStore::load()
|
||||
// Order valid pages by ascending seq so replay applies oldest first
|
||||
uint8_t order[WARM_FLASH_PAGES] = {};
|
||||
uint32_t seqs[WARM_FLASH_PAGES] = {};
|
||||
bool legacyOf[WARM_FLASH_PAGES] = {}; // per-page: v1 (WRNG) → discard last_heard on replay
|
||||
WarmFormat fmtOf[WARM_FLASH_PAGES] = {}; // per-page on-flash format; older ones are normalised on replay
|
||||
uint8_t nValid = 0;
|
||||
uint8_t nCorrupt = 0;
|
||||
for (uint8_t p = 0; p < WARM_FLASH_PAGES; p++) {
|
||||
WarmPageHeader h;
|
||||
bool legacy = false;
|
||||
if (!ringReadHeader(p, h, &legacy)) {
|
||||
WarmFormat fmt = WarmFormat::Current;
|
||||
if (!ringReadHeader(p, h, &fmt)) {
|
||||
// An erased page reads back all-ones; any other magic is a
|
||||
// partially-written or bit-rotted header we're dropping, so flag it
|
||||
// rather than silently treating the loss as a clean empty ring.
|
||||
@@ -400,7 +407,7 @@ void WarmNodeStore::load()
|
||||
nCorrupt++;
|
||||
continue;
|
||||
}
|
||||
legacyOf[p] = legacy;
|
||||
fmtOf[p] = fmt;
|
||||
uint8_t pos = nValid;
|
||||
while (pos > 0 && static_cast<int32_t>(h.seq - seqs[pos - 1]) < 0) {
|
||||
order[pos] = order[pos - 1];
|
||||
@@ -427,7 +434,7 @@ void WarmNodeStore::load()
|
||||
uint32_t migrated = 0;
|
||||
for (uint8_t k = 0; k < nValid; k++) {
|
||||
const uint8_t p = order[k];
|
||||
const bool legacy = legacyOf[p];
|
||||
const WarmFormat fmt = fmtOf[p];
|
||||
uint16_t slot = 0;
|
||||
for (; slot < kRecordsPerPage; slot++) {
|
||||
WarmNodeEntry rec;
|
||||
@@ -444,12 +451,15 @@ void WarmNodeStore::load()
|
||||
memset(e, 0, sizeof(*e));
|
||||
}
|
||||
} else {
|
||||
// v1 (legacy) record: keep identity + key, but discard the old timestamp -
|
||||
// its low bits would otherwise be misread as role/protected metadata.
|
||||
// Normalise older records: v1's timestamp would be misread as role/protected,
|
||||
// and v2's bit 6 as a signer we never verified.
|
||||
uint32_t lh = rec.last_heard;
|
||||
if (legacy) {
|
||||
if (fmt == WarmFormat::V1) {
|
||||
lh = 0;
|
||||
migrated++;
|
||||
} else if (fmt == WarmFormat::V2) {
|
||||
lh &= ~(WARM_SIGNER_MASK << WARM_SIGNER_SHIFT);
|
||||
migrated++;
|
||||
}
|
||||
const WarmNodeEntry *e = place(rec.num, lh, rec.public_key);
|
||||
if (e)
|
||||
@@ -460,17 +470,16 @@ void WarmNodeStore::load()
|
||||
activePage = p;
|
||||
writeSlot = slot;
|
||||
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).
|
||||
if (legacy)
|
||||
// Rotate rather than append into an older-format page: a later load would
|
||||
// normalise the new records to that page's format and drop metadata.
|
||||
if (fmt != WarmFormat::Current)
|
||||
writeSlot = kRecordsPerPage;
|
||||
}
|
||||
}
|
||||
if (nCorrupt)
|
||||
LOG_WARN("WarmStore: dropped %u corrupt ring page(s), some nodes lost", nCorrupt);
|
||||
if (migrated)
|
||||
LOG_INFO("WarmStore: migrated %u v1 record(s) (kept key, discarded last_heard)", (unsigned)migrated);
|
||||
LOG_INFO("WarmStore: migrated %u legacy record(s) to the current format", (unsigned)migrated);
|
||||
LOG_INFO("WarmStore: replayed %u ring records -> %u live nodes (page %u, slot %u)", (unsigned)replayed, (unsigned)count(),
|
||||
activePage, writeSlot);
|
||||
}
|
||||
@@ -537,10 +546,14 @@ void WarmNodeStore::load()
|
||||
LOG_WARN("WarmStore: %s header read failed, starting empty", warmFileName);
|
||||
return;
|
||||
}
|
||||
// v1 (WRM1) is still accepted: same record size, but its last_heard was a plain
|
||||
// timestamp. We keep identity + key and discard last_heard on load (see below).
|
||||
const bool legacy = (h.magic == WARM_STORE_MAGIC_V1);
|
||||
if ((h.magic != WARM_STORE_MAGIC && !legacy) || h.entrySize != sizeof(WarmNodeEntry) || h.count > WARM_NODE_COUNT) {
|
||||
// Older snapshots are still accepted: same record size, fewer metadata bits in
|
||||
// last_heard. Both are normalised to the current format below.
|
||||
const bool known = h.magic == WARM_STORE_MAGIC || h.magic == WARM_STORE_MAGIC_V2 || h.magic == WARM_STORE_MAGIC_V1;
|
||||
const WarmFormat fmt = h.magic == WARM_STORE_MAGIC_V1 ? WarmFormat::V1
|
||||
: h.magic == WARM_STORE_MAGIC_V2 ? WarmFormat::V2
|
||||
: WarmFormat::Current;
|
||||
const bool legacy = fmt != WarmFormat::Current;
|
||||
if (!known || h.entrySize != sizeof(WarmNodeEntry) || h.count > WARM_NODE_COUNT) {
|
||||
f.close();
|
||||
LOG_WARN("WarmStore: %s header invalid (magic=0x%08x entrySize=%u count=%u), starting empty", warmFileName, h.magic,
|
||||
h.entrySize, h.count);
|
||||
@@ -560,19 +573,24 @@ void WarmNodeStore::load()
|
||||
memset(entries, 0, WARM_NODE_COUNT * sizeof(WarmNodeEntry));
|
||||
return;
|
||||
}
|
||||
if (legacy) {
|
||||
// Migrate v1 → v2: discard the old last_heard (its low bits would be misread as
|
||||
// role/protected); keep num + public_key. Mark dirty so save() rewrites as v2.
|
||||
// Normalise older records, then mark dirty so save() rewrites in the current format:
|
||||
// v1's timestamp would be misread as role/protected, v2's bit 6 as an unverified signer.
|
||||
if (fmt == WarmFormat::V1) {
|
||||
for (size_t i = 0; i < WARM_NODE_COUNT; i++)
|
||||
if (entries[i].num)
|
||||
entries[i].last_heard = 0;
|
||||
dirty = true;
|
||||
} else if (fmt == WarmFormat::V2) {
|
||||
for (size_t i = 0; i < WARM_NODE_COUNT; i++)
|
||||
if (entries[i].num)
|
||||
entries[i].last_heard &= ~(WARM_SIGNER_MASK << WARM_SIGNER_SHIFT);
|
||||
dirty = true;
|
||||
}
|
||||
} else {
|
||||
f.close();
|
||||
}
|
||||
LOG_INFO("WarmStore: loaded %u warm nodes from %s%s", h.count, warmFileName,
|
||||
legacy ? " (v1 migrated: discarded last_heard)" : "");
|
||||
legacy ? " (migrated from an older format)" : "");
|
||||
}
|
||||
|
||||
bool WarmNodeStore::save()
|
||||
|
||||
+22
-10
@@ -43,27 +43,33 @@ static_assert(sizeof(WarmNodeEntry) == 40, "WarmNodeEntry must stay 40 B - persi
|
||||
//
|
||||
// The warm tier only uses last_heard to LRU-rank evicted (long-tail) nodes, so ~minute
|
||||
// recency resolution is plenty. We reclaim the low WARM_META_BITS of that field to carry
|
||||
// the evicted node's device role + a protected category, at zero cost to record size
|
||||
// (entry stays 40 B; no RAM/flash growth). The high bits remain a real unix-seconds
|
||||
// timestamp quantised to (1 << WARM_META_BITS) seconds.
|
||||
// the evicted node's device role, a protected category + a signer flag, at zero cost to
|
||||
// record size (entry stays 40 B; no RAM/flash growth). The high bits remain a real
|
||||
// unix-seconds timestamp quantised to (1 << WARM_META_BITS) seconds.
|
||||
//
|
||||
// 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.
|
||||
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
|
||||
static constexpr uint32_t WARM_META_BITS = 7; // role(4) + protected(2) + signer(1)
|
||||
static constexpr uint32_t WARM_META_MASK = (1u << WARM_META_BITS) - 1; // 0x7F → 128 s quantum
|
||||
static constexpr uint32_t WARM_TIME_MASK = ~WARM_META_MASK; // 0xFFFFFF80
|
||||
static constexpr uint32_t WARM_ROLE_MASK = 0x0Fu; // bits [3:0] device role (0..12)
|
||||
static constexpr uint32_t WARM_PROT_SHIFT = 4; // bits [5:4] protected category
|
||||
static constexpr uint32_t WARM_PROT_MASK = 0x03u;
|
||||
static constexpr uint32_t WARM_SIGNER_SHIFT = 6; // bit [6] we verified an XEdDSA signature from this node
|
||||
static constexpr uint32_t WARM_SIGNER_MASK = 0x01u;
|
||||
|
||||
// On-disk record format, from the page/file magic; older ones are normalised by load().
|
||||
enum class WarmFormat : uint8_t { Current, V2, V1 };
|
||||
|
||||
// Protected category cached alongside role so consumers needn't re-derive the mapping.
|
||||
enum class WarmProtected : uint8_t { None = 0, Role = 1, Flag = 2, XeddsaSigner = 3 };
|
||||
|
||||
inline uint32_t warmPackLastHeard(uint32_t lastHeard, uint8_t role, uint8_t prot)
|
||||
inline uint32_t warmPackLastHeard(uint32_t lastHeard, uint8_t role, uint8_t prot, bool signer)
|
||||
{
|
||||
return (lastHeard & WARM_TIME_MASK) | (static_cast<uint32_t>(role) & WARM_ROLE_MASK) |
|
||||
((static_cast<uint32_t>(prot) & WARM_PROT_MASK) << WARM_PROT_SHIFT);
|
||||
((static_cast<uint32_t>(prot) & WARM_PROT_MASK) << WARM_PROT_SHIFT) |
|
||||
((signer ? WARM_SIGNER_MASK : 0u) << WARM_SIGNER_SHIFT);
|
||||
}
|
||||
inline uint32_t warmTimeOf(const WarmNodeEntry &e)
|
||||
{
|
||||
@@ -77,6 +83,10 @@ inline uint8_t warmProtOf(const WarmNodeEntry &e)
|
||||
{
|
||||
return static_cast<uint8_t>((e.last_heard >> WARM_PROT_SHIFT) & WARM_PROT_MASK);
|
||||
}
|
||||
inline bool warmSignerOf(const WarmNodeEntry &e)
|
||||
{
|
||||
return ((e.last_heard >> WARM_SIGNER_SHIFT) & WARM_SIGNER_MASK) != 0;
|
||||
}
|
||||
|
||||
// Gated on NRF52840_XXAA: the ring sits at 0xEA000
|
||||
// valid only on the 1 MB-flash nRF52840.
|
||||
@@ -99,9 +109,11 @@ class WarmNodeStore
|
||||
/// entries; otherwise the oldest (keyless-first) entry is replaced.
|
||||
/// @param role the node's device role (meshtastic_Config_DeviceConfig_Role, 0..12)
|
||||
/// @param protectedCat WarmProtected category cached for the hop-trim path
|
||||
/// @param signer true if we ever verified an XEdDSA signature from this node, so
|
||||
/// re-admission restores the bit rather than relearning it
|
||||
/// @return true if the node was stored or updated
|
||||
bool absorb(NodeNum num, uint32_t lastHeard, const uint8_t *key32 /* may be NULL */, uint8_t role = 0,
|
||||
uint8_t protectedCat = 0);
|
||||
uint8_t protectedCat = 0, bool signer = false);
|
||||
|
||||
/// Look up the cached device role + protected category for a warm node.
|
||||
/// @return false if the node is not in the warm tier.
|
||||
@@ -167,7 +179,7 @@ class WarmNodeStore
|
||||
void ringAppend(const WarmNodeEntry &rec, int storeSlot /* -1 for tombstones */);
|
||||
void ringRotate(); // reclaim oldest page, compacting stranded live entries
|
||||
void ringOpenPage(uint8_t page); // erase + write header (seq = nextSeq++)
|
||||
bool ringReadHeader(uint8_t page, WarmPageHeader &h, bool *legacy = nullptr) const;
|
||||
bool ringReadHeader(uint8_t page, WarmPageHeader &h, WarmFormat *fmt = nullptr) const;
|
||||
#endif
|
||||
|
||||
bool save();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "mesh/eth/ethClient.h"
|
||||
#include "NodeDB.h"
|
||||
#include "RTC.h"
|
||||
#include "concurrency/Periodic.h"
|
||||
#include "configuration.h"
|
||||
#include "gps/RTC.h"
|
||||
#include "main.h"
|
||||
#include "mesh/api/ethServerAPI.h"
|
||||
#include "target_specific.h"
|
||||
|
||||
@@ -69,6 +69,8 @@ PB_BIND(meshtastic_Config_SessionkeyConfig, meshtastic_Config_SessionkeyConfig,
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -374,7 +374,11 @@ typedef enum _meshtastic_Config_LoRaConfig_ModemPreset {
|
||||
Note: TCXO with tight tolerances (±5 ppm or better) is *absolutely required* at these narrow bandwidths.
|
||||
Only compatible with SX127x and SX126x chipsets.
|
||||
Comparable link budget and data rate to LONG_MODERATE. */
|
||||
meshtastic_Config_LoRaConfig_ModemPreset_TINY_SLOW = 15
|
||||
meshtastic_Config_LoRaConfig_ModemPreset_TINY_SLOW = 15,
|
||||
/* Medium Range - Turbo
|
||||
This preset performs similarly to MEDIUM_FAST, but with 500kHz bandwidth.
|
||||
It is not legal to use in all regions due to this wider bandwidth. */
|
||||
meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_TURBO = 16
|
||||
} meshtastic_Config_LoRaConfig_ModemPreset;
|
||||
|
||||
typedef enum _meshtastic_Config_LoRaConfig_FEM_LNA_Mode {
|
||||
@@ -395,6 +399,19 @@ typedef enum _meshtastic_Config_BluetoothConfig_PairingMode {
|
||||
meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN = 2
|
||||
} meshtastic_Config_BluetoothConfig_PairingMode;
|
||||
|
||||
/* Controls how the device authenticates remotely received mesh packets. */
|
||||
typedef enum _meshtastic_Config_SecurityConfig_PacketSignaturePolicy {
|
||||
/* Accept unsigned packets for maximum compatibility while still rejecting malformed or invalid signatures.
|
||||
This is the default to avoid legacy nodes dropping signed packets during rebroadcast. */
|
||||
meshtastic_Config_SecurityConfig_PacketSignaturePolicy_PACKET_SIGNATURE_POLICY_COMPATIBLE = 0,
|
||||
/* Prefer authenticated packets while retaining compatibility with unsigned packets from nodes not known to sign.
|
||||
Rejects unsigned, signable broadcasts from nodes that have previously signed. */
|
||||
meshtastic_Config_SecurityConfig_PacketSignaturePolicy_PACKET_SIGNATURE_POLICY_BALANCED = 1,
|
||||
/* Accept only packets authenticated by a verified XEdDSA signature or successful PKI decryption.
|
||||
Unsigned, malformed, invalid, or unverifiable packets are ignored. */
|
||||
meshtastic_Config_SecurityConfig_PacketSignaturePolicy_PACKET_SIGNATURE_POLICY_STRICT = 2
|
||||
} meshtastic_Config_SecurityConfig_PacketSignaturePolicy;
|
||||
|
||||
/* Struct definitions */
|
||||
/* Configuration */
|
||||
typedef struct _meshtastic_Config_DeviceConfig {
|
||||
@@ -685,6 +702,8 @@ typedef struct _meshtastic_Config_SecurityConfig {
|
||||
bool debug_log_api_enabled;
|
||||
/* Allow incoming device control over the insecure legacy admin channel. */
|
||||
bool admin_channel_enabled;
|
||||
/* Determines the packet signature policy applied to remotely received mesh packets. */
|
||||
meshtastic_Config_SecurityConfig_PacketSignaturePolicy packet_signature_policy;
|
||||
} meshtastic_Config_SecurityConfig;
|
||||
|
||||
/* Blank config request, strictly for getting the session key */
|
||||
@@ -767,8 +786,8 @@ extern "C" {
|
||||
#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_ITU2_125CM+1))
|
||||
|
||||
#define _meshtastic_Config_LoRaConfig_ModemPreset_MIN meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST
|
||||
#define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_TINY_SLOW
|
||||
#define _meshtastic_Config_LoRaConfig_ModemPreset_ARRAYSIZE ((meshtastic_Config_LoRaConfig_ModemPreset)(meshtastic_Config_LoRaConfig_ModemPreset_TINY_SLOW+1))
|
||||
#define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_TURBO
|
||||
#define _meshtastic_Config_LoRaConfig_ModemPreset_ARRAYSIZE ((meshtastic_Config_LoRaConfig_ModemPreset)(meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_TURBO+1))
|
||||
|
||||
#define _meshtastic_Config_LoRaConfig_FEM_LNA_Mode_MIN meshtastic_Config_LoRaConfig_FEM_LNA_Mode_DISABLED
|
||||
#define _meshtastic_Config_LoRaConfig_FEM_LNA_Mode_MAX meshtastic_Config_LoRaConfig_FEM_LNA_Mode_NOT_PRESENT
|
||||
@@ -778,6 +797,10 @@ extern "C" {
|
||||
#define _meshtastic_Config_BluetoothConfig_PairingMode_MAX meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN
|
||||
#define _meshtastic_Config_BluetoothConfig_PairingMode_ARRAYSIZE ((meshtastic_Config_BluetoothConfig_PairingMode)(meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN+1))
|
||||
|
||||
#define _meshtastic_Config_SecurityConfig_PacketSignaturePolicy_MIN meshtastic_Config_SecurityConfig_PacketSignaturePolicy_PACKET_SIGNATURE_POLICY_COMPATIBLE
|
||||
#define _meshtastic_Config_SecurityConfig_PacketSignaturePolicy_MAX meshtastic_Config_SecurityConfig_PacketSignaturePolicy_PACKET_SIGNATURE_POLICY_STRICT
|
||||
#define _meshtastic_Config_SecurityConfig_PacketSignaturePolicy_ARRAYSIZE ((meshtastic_Config_SecurityConfig_PacketSignaturePolicy)(meshtastic_Config_SecurityConfig_PacketSignaturePolicy_PACKET_SIGNATURE_POLICY_STRICT+1))
|
||||
|
||||
|
||||
#define meshtastic_Config_DeviceConfig_role_ENUMTYPE meshtastic_Config_DeviceConfig_Role
|
||||
#define meshtastic_Config_DeviceConfig_rebroadcast_mode_ENUMTYPE meshtastic_Config_DeviceConfig_RebroadcastMode
|
||||
@@ -801,6 +824,7 @@ extern "C" {
|
||||
|
||||
#define meshtastic_Config_BluetoothConfig_mode_ENUMTYPE meshtastic_Config_BluetoothConfig_PairingMode
|
||||
|
||||
#define meshtastic_Config_SecurityConfig_packet_signature_policy_ENUMTYPE meshtastic_Config_SecurityConfig_PacketSignaturePolicy
|
||||
|
||||
|
||||
|
||||
@@ -814,7 +838,7 @@ extern "C" {
|
||||
#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0, 0}
|
||||
#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, _meshtastic_Config_LoRaConfig_FEM_LNA_Mode_MIN, 0}
|
||||
#define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
|
||||
#define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
|
||||
#define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0, _meshtastic_Config_SecurityConfig_PacketSignaturePolicy_MIN}
|
||||
#define meshtastic_Config_SessionkeyConfig_init_default {0}
|
||||
#define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}}
|
||||
#define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0, _meshtastic_Config_DeviceConfig_BuzzerMode_MIN}
|
||||
@@ -825,7 +849,7 @@ extern "C" {
|
||||
#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0, 0}
|
||||
#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, _meshtastic_Config_LoRaConfig_FEM_LNA_Mode_MIN, 0}
|
||||
#define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
|
||||
#define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
|
||||
#define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0, _meshtastic_Config_SecurityConfig_PacketSignaturePolicy_MIN}
|
||||
#define meshtastic_Config_SessionkeyConfig_init_zero {0}
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
@@ -921,6 +945,7 @@ extern "C" {
|
||||
#define meshtastic_Config_SecurityConfig_serial_enabled_tag 5
|
||||
#define meshtastic_Config_SecurityConfig_debug_log_api_enabled_tag 6
|
||||
#define meshtastic_Config_SecurityConfig_admin_channel_enabled_tag 8
|
||||
#define meshtastic_Config_SecurityConfig_packet_signature_policy_tag 9
|
||||
#define meshtastic_Config_device_tag 1
|
||||
#define meshtastic_Config_position_tag 2
|
||||
#define meshtastic_Config_power_tag 3
|
||||
@@ -1082,7 +1107,8 @@ X(a, STATIC, REPEATED, BYTES, admin_key, 3) \
|
||||
X(a, STATIC, SINGULAR, BOOL, is_managed, 4) \
|
||||
X(a, STATIC, SINGULAR, BOOL, serial_enabled, 5) \
|
||||
X(a, STATIC, SINGULAR, BOOL, debug_log_api_enabled, 6) \
|
||||
X(a, STATIC, SINGULAR, BOOL, admin_channel_enabled, 8)
|
||||
X(a, STATIC, SINGULAR, BOOL, admin_channel_enabled, 8) \
|
||||
X(a, STATIC, SINGULAR, UENUM, packet_signature_policy, 9)
|
||||
#define meshtastic_Config_SecurityConfig_CALLBACK NULL
|
||||
#define meshtastic_Config_SecurityConfig_DEFAULT NULL
|
||||
|
||||
@@ -1126,7 +1152,7 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg;
|
||||
#define meshtastic_Config_NetworkConfig_size 204
|
||||
#define meshtastic_Config_PositionConfig_size 62
|
||||
#define meshtastic_Config_PowerConfig_size 52
|
||||
#define meshtastic_Config_SecurityConfig_size 178
|
||||
#define meshtastic_Config_SecurityConfig_size 180
|
||||
#define meshtastic_Config_SessionkeyConfig_size 0
|
||||
#define meshtastic_Config_size 207
|
||||
|
||||
|
||||
@@ -452,7 +452,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
/* meshtastic_NodeDatabase_size depends on runtime parameters */
|
||||
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size
|
||||
#define meshtastic_BackupPreferences_size 2738
|
||||
#define meshtastic_BackupPreferences_size 2740
|
||||
#define meshtastic_ChannelFile_size 718
|
||||
#define meshtastic_DeviceState_size 1944
|
||||
#define meshtastic_NodeEnvironmentEntry_size 170
|
||||
|
||||
@@ -6,10 +6,34 @@
|
||||
#error Regenerate this file with the current version of nanopb generator.
|
||||
#endif
|
||||
|
||||
PB_BIND(meshtastic_SensorData, meshtastic_SensorData, AUTO)
|
||||
PB_BIND(meshtastic_FileTransfer, meshtastic_FileTransfer, 4)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_DirectoryListing, meshtastic_DirectoryListing, 4)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_I2CTransaction, meshtastic_I2CTransaction, 2)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_SdCardInfo, meshtastic_SdCardInfo, AUTO)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_I2CResult, meshtastic_I2CResult, 2)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_InterdeviceMessage, meshtastic_InterdeviceMessage, 4)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
PB_BIND(meshtastic_InterdeviceMessage, meshtastic_InterdeviceMessage, 2)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -10,38 +10,194 @@
|
||||
#endif
|
||||
|
||||
/* Enum definitions */
|
||||
typedef enum _meshtastic_MessageType {
|
||||
meshtastic_MessageType_ACK = 0,
|
||||
meshtastic_MessageType_COLLECT_INTERVAL = 160, /* in ms */
|
||||
meshtastic_MessageType_BEEP_ON = 161, /* duration ms */
|
||||
meshtastic_MessageType_BEEP_OFF = 162, /* cancel prematurely */
|
||||
meshtastic_MessageType_SHUTDOWN = 163,
|
||||
meshtastic_MessageType_POWER_ON = 164,
|
||||
meshtastic_MessageType_SCD41_TEMP = 176,
|
||||
meshtastic_MessageType_SCD41_HUMIDITY = 177,
|
||||
meshtastic_MessageType_SCD41_CO2 = 178,
|
||||
meshtastic_MessageType_AHT20_TEMP = 179,
|
||||
meshtastic_MessageType_AHT20_HUMIDITY = 180,
|
||||
meshtastic_MessageType_TVOC_INDEX = 181
|
||||
} meshtastic_MessageType;
|
||||
/* Version of the interdevice protocol spoken on the link. Both sides send
|
||||
theirs in the ping/pong handshake; a peer reporting a different one runs
|
||||
firmware that does not match and is not talked to.
|
||||
|
||||
On a change that breaks the other side (renumbered fields, changed
|
||||
semantics, removed messages), raise the value of CURRENT. Do not add
|
||||
another entry: this enum carries a single constant, not a history. */
|
||||
typedef enum _meshtastic_InterdeviceVersion {
|
||||
meshtastic_InterdeviceVersion_INTERDEVICE_VERSION_UNSPECIFIED = 0,
|
||||
/* Never use 1: ping/pong were bools before the handshake existed, and a
|
||||
bool true is the same varint on the wire as the number 1, so firmware
|
||||
predating the handshake would pass it. */
|
||||
meshtastic_InterdeviceVersion_INTERDEVICE_VERSION_CURRENT = 2
|
||||
} meshtastic_InterdeviceVersion;
|
||||
|
||||
/* Defines the supported file operations */
|
||||
typedef enum _meshtastic_FileOperation {
|
||||
meshtastic_FileOperation_GET = 0,
|
||||
meshtastic_FileOperation_POST = 1,
|
||||
meshtastic_FileOperation_PUT = 2,
|
||||
meshtastic_FileOperation_DELETE = 3
|
||||
} meshtastic_FileOperation;
|
||||
|
||||
/* Outcome of a file or directory operation. The requester must be able to
|
||||
tell a transient condition from a definitive one: BUSY is worth another
|
||||
try, NOT_FOUND is not. */
|
||||
typedef enum _meshtastic_FileStatus {
|
||||
meshtastic_FileStatus_FILE_UNSPECIFIED = 0,
|
||||
meshtastic_FileStatus_FILE_OK = 1,
|
||||
/* Retry later: the co-processor is doing card maintenance (mount,
|
||||
free space scan) and cannot serve the request right now */
|
||||
meshtastic_FileStatus_FILE_BUSY = 2,
|
||||
meshtastic_FileStatus_FILE_NO_CARD = 3,
|
||||
meshtastic_FileStatus_FILE_NOT_FOUND = 4,
|
||||
/* PUT only: offset did not match the current end of the file. file_size
|
||||
carries the size the file actually has, so the writer can resync (or
|
||||
recognize its own chunk as already written after a lost response). */
|
||||
meshtastic_FileStatus_FILE_OFFSET_CONFLICT = 5,
|
||||
meshtastic_FileStatus_FILE_IO_ERROR = 6,
|
||||
meshtastic_FileStatus_FILE_NOT_A_FILE = 7 /* path is a directory (GET) or not one (listing) */
|
||||
} meshtastic_FileStatus;
|
||||
|
||||
/* What to do with the SD card of the co-processor */
|
||||
typedef enum _meshtastic_SdCommand {
|
||||
meshtastic_SdCommand_SD_COMMAND_UNSPECIFIED = 0,
|
||||
meshtastic_SdCommand_SD_MOUNT = 1, /* mount a card that is in the slot, also after an eject */
|
||||
meshtastic_SdCommand_SD_EJECT = 2, /* flush and release the card so it can be pulled safely */
|
||||
meshtastic_SdCommand_SD_FORMAT = 3 /* wipe the card and put a fresh FAT on it, then mount it */
|
||||
} meshtastic_SdCommand;
|
||||
|
||||
typedef enum _meshtastic_SdCardInfo_CardType {
|
||||
meshtastic_SdCardInfo_CardType_NONE = 0,
|
||||
meshtastic_SdCardInfo_CardType_MMC = 1,
|
||||
meshtastic_SdCardInfo_CardType_SD = 2,
|
||||
meshtastic_SdCardInfo_CardType_SDHC = 3,
|
||||
meshtastic_SdCardInfo_CardType_SDXC = 4,
|
||||
meshtastic_SdCardInfo_CardType_UNKNOWN_CARD = 5
|
||||
} meshtastic_SdCardInfo_CardType;
|
||||
|
||||
typedef enum _meshtastic_SdCardInfo_FatType {
|
||||
meshtastic_SdCardInfo_FatType_UNKNOWN_FAT = 0,
|
||||
meshtastic_SdCardInfo_FatType_FAT16 = 1,
|
||||
meshtastic_SdCardInfo_FatType_FAT32 = 2,
|
||||
meshtastic_SdCardInfo_FatType_EXFAT = 3
|
||||
} meshtastic_SdCardInfo_FatType;
|
||||
|
||||
typedef enum _meshtastic_I2CResult_Status {
|
||||
/* Never sent: an all-defaults (e.g. accidentally empty) message must
|
||||
not decode as a successful transaction */
|
||||
meshtastic_I2CResult_Status_UNSPECIFIED = 0,
|
||||
meshtastic_I2CResult_Status_OK = 1,
|
||||
meshtastic_I2CResult_Status_NACK_ADDRESS = 2,
|
||||
meshtastic_I2CResult_Status_NACK_DATA = 3,
|
||||
meshtastic_I2CResult_Status_ERROR = 4
|
||||
} meshtastic_I2CResult_Status;
|
||||
|
||||
/* Struct definitions */
|
||||
typedef struct _meshtastic_SensorData {
|
||||
/* The message type */
|
||||
meshtastic_MessageType type;
|
||||
pb_size_t which_data;
|
||||
union {
|
||||
float float_value;
|
||||
uint32_t uint32_value;
|
||||
} data;
|
||||
} meshtastic_SensorData;
|
||||
typedef PB_BYTES_ARRAY_T(4096) meshtastic_FileTransfer_filedata_t;
|
||||
/* Message for file operations */
|
||||
typedef struct _meshtastic_FileTransfer {
|
||||
meshtastic_FileOperation operation; /* File operation (GET, POST, PUT, DELETE) */
|
||||
char filepath[256]; /* Path of the file on the SD card */
|
||||
meshtastic_FileTransfer_filedata_t filedata; /* Chunk content (POST/PUT request, GET response) */
|
||||
meshtastic_FileStatus status; /* Response: outcome of the operation */
|
||||
char message[255]; /* Response: human readable detail, may be empty */
|
||||
uint64_t offset; /* Byte offset of this chunk within the file (ranged GET/PUT) */
|
||||
/* GET request: number of bytes to read, 0 = max chunk size. A response
|
||||
carries at most the filedata max_size (see interdevice.options) per
|
||||
chunk; larger requests are truncated, visible in the filedata length. */
|
||||
uint32_t length;
|
||||
uint64_t file_size; /* GET response: total size of the file */
|
||||
} meshtastic_FileTransfer;
|
||||
|
||||
/* Message for structured directory listing */
|
||||
typedef struct _meshtastic_DirectoryListing {
|
||||
char directory[256]; /* Path of the directory */
|
||||
/* One page of entry names, full FAT LFN length. Subdirectories carry a
|
||||
trailing slash. Note that a name whose directory prefix pushes the
|
||||
combined path past the FileTransfer.filepath limit cannot round-trip.
|
||||
Page size is the max_count in interdevice.options; page through with
|
||||
offset and total_count. */
|
||||
pb_size_t filenames_count;
|
||||
char filenames[16][256];
|
||||
meshtastic_FileStatus status; /* Response: outcome of the operation */
|
||||
char message[255]; /* Response: human readable detail, may be empty */
|
||||
uint32_t offset; /* Request: skip this many entries (paging) */
|
||||
uint32_t total_count; /* Response: total number of entries in the directory */
|
||||
} meshtastic_DirectoryListing;
|
||||
|
||||
typedef PB_BYTES_ARRAY_T(256) meshtastic_I2CTransaction_write_data_t;
|
||||
/* A single I2C transaction: an optional write followed by an optional
|
||||
read with repeated start, matching the TwoWire usage of sensor drivers
|
||||
(beginTransmission/write.../endTransmission(false)/requestFrom) */
|
||||
typedef struct _meshtastic_I2CTransaction {
|
||||
uint32_t address; /* 7-bit device address */
|
||||
meshtastic_I2CTransaction_write_data_t write_data; /* Bytes to write, may be empty */
|
||||
/* Number of bytes to read after the write, 0 = write-only. Bounded by
|
||||
the read_data max_size of I2CResult (see interdevice.options); larger
|
||||
requests are truncated, visible in the returned byte count. */
|
||||
uint32_t read_len;
|
||||
} meshtastic_I2CTransaction;
|
||||
|
||||
/* SD card statistics */
|
||||
typedef struct _meshtastic_SdCardInfo {
|
||||
/* Card initialized and usable. False while `busy` is set does not mean
|
||||
there is no card: the co-processor does not know yet. */
|
||||
bool present;
|
||||
meshtastic_SdCardInfo_CardType card_type;
|
||||
meshtastic_SdCardInfo_FatType fat_type;
|
||||
uint64_t card_size; /* Filesystem size in bytes */
|
||||
uint64_t used_bytes; /* Used bytes (may be expensive to compute on FAT32) */
|
||||
uint64_t free_bytes; /* Free bytes */
|
||||
/* used_bytes/free_bytes are only meaningful when true: the scan behind
|
||||
them runs in the background after mount and can take a while, and a
|
||||
full card is otherwise indistinguishable from a scan in progress */
|
||||
bool stats_valid;
|
||||
/* The co-processor is mounting a card right now, so whether one is
|
||||
present is not decided yet. Ask again rather than concluding the slot
|
||||
is empty. */
|
||||
bool busy;
|
||||
/* A card answers in the slot but carries no filesystem that could be
|
||||
mounted (present is false then). Formatting it makes it usable. */
|
||||
bool unformatted;
|
||||
} meshtastic_SdCardInfo;
|
||||
|
||||
typedef PB_BYTES_ARRAY_T(256) meshtastic_I2CResult_read_data_t;
|
||||
/* Result of an I2CTransaction */
|
||||
typedef struct _meshtastic_I2CResult {
|
||||
meshtastic_I2CResult_Status status;
|
||||
meshtastic_I2CResult_read_data_t read_data; /* Data read from the device, empty for write-only transactions */
|
||||
} meshtastic_I2CResult;
|
||||
|
||||
typedef PB_BYTES_ARRAY_T(128) meshtastic_InterdeviceMessage_i2c_scan_result_t;
|
||||
/* Main message for interdevice communication */
|
||||
typedef struct _meshtastic_InterdeviceMessage {
|
||||
pb_size_t which_data;
|
||||
union {
|
||||
char nmea[1024];
|
||||
meshtastic_SensorData sensor;
|
||||
uint16_t beep;
|
||||
meshtastic_I2CTransaction i2c_transaction;
|
||||
meshtastic_I2CResult i2c_result;
|
||||
bool i2c_scan; /* Request: scan the secondary I2C bus */
|
||||
meshtastic_InterdeviceMessage_i2c_scan_result_t i2c_scan_result; /* Response: 7-bit addresses of discovered devices */
|
||||
meshtastic_FileTransfer file_transfer;
|
||||
meshtastic_DirectoryListing directory_listing;
|
||||
bool get_sd_info; /* Request: SD card statistics */
|
||||
meshtastic_SdCardInfo sd_info; /* Response */
|
||||
/* Link liveness probe and version handshake. The receiver answers ping
|
||||
with pong, echoing the id. Touches no peripherals, so it works with
|
||||
nothing attached. Both carry the version the sender speaks; a peer
|
||||
that answers with a different one speaks another protocol and must
|
||||
not be used. */
|
||||
meshtastic_InterdeviceVersion ping;
|
||||
meshtastic_InterdeviceVersion pong;
|
||||
/* Response: the request could not be decoded or is of an unhandled
|
||||
type, so the requester fails fast instead of burning its timeout.
|
||||
Echoes the id when known, 0 when the frame was undecodable. Never
|
||||
sent in reaction to a nack. */
|
||||
bool nack;
|
||||
/* Request: mount the card, or release it so it can be pulled safely. The
|
||||
co-processor answers with sd_info. Without an eject the card is mounted
|
||||
on its own and kept mounted; after one it stays released until a mount
|
||||
is asked for. */
|
||||
meshtastic_SdCommand sd_command;
|
||||
} data;
|
||||
/* Correlates a response with its request: responses echo the id of the
|
||||
request they answer. 0 for unsolicited messages (e.g. the nmea stream). */
|
||||
uint32_t id;
|
||||
} meshtastic_InterdeviceMessage;
|
||||
|
||||
|
||||
@@ -50,53 +206,205 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
/* Helper constants for enums */
|
||||
#define _meshtastic_MessageType_MIN meshtastic_MessageType_ACK
|
||||
#define _meshtastic_MessageType_MAX meshtastic_MessageType_TVOC_INDEX
|
||||
#define _meshtastic_MessageType_ARRAYSIZE ((meshtastic_MessageType)(meshtastic_MessageType_TVOC_INDEX+1))
|
||||
#define _meshtastic_InterdeviceVersion_MIN meshtastic_InterdeviceVersion_INTERDEVICE_VERSION_UNSPECIFIED
|
||||
#define _meshtastic_InterdeviceVersion_MAX meshtastic_InterdeviceVersion_INTERDEVICE_VERSION_CURRENT
|
||||
#define _meshtastic_InterdeviceVersion_ARRAYSIZE ((meshtastic_InterdeviceVersion)(meshtastic_InterdeviceVersion_INTERDEVICE_VERSION_CURRENT+1))
|
||||
|
||||
#define meshtastic_SensorData_type_ENUMTYPE meshtastic_MessageType
|
||||
#define _meshtastic_FileOperation_MIN meshtastic_FileOperation_GET
|
||||
#define _meshtastic_FileOperation_MAX meshtastic_FileOperation_DELETE
|
||||
#define _meshtastic_FileOperation_ARRAYSIZE ((meshtastic_FileOperation)(meshtastic_FileOperation_DELETE+1))
|
||||
|
||||
#define _meshtastic_FileStatus_MIN meshtastic_FileStatus_FILE_UNSPECIFIED
|
||||
#define _meshtastic_FileStatus_MAX meshtastic_FileStatus_FILE_NOT_A_FILE
|
||||
#define _meshtastic_FileStatus_ARRAYSIZE ((meshtastic_FileStatus)(meshtastic_FileStatus_FILE_NOT_A_FILE+1))
|
||||
|
||||
#define _meshtastic_SdCommand_MIN meshtastic_SdCommand_SD_COMMAND_UNSPECIFIED
|
||||
#define _meshtastic_SdCommand_MAX meshtastic_SdCommand_SD_FORMAT
|
||||
#define _meshtastic_SdCommand_ARRAYSIZE ((meshtastic_SdCommand)(meshtastic_SdCommand_SD_FORMAT+1))
|
||||
|
||||
#define _meshtastic_SdCardInfo_CardType_MIN meshtastic_SdCardInfo_CardType_NONE
|
||||
#define _meshtastic_SdCardInfo_CardType_MAX meshtastic_SdCardInfo_CardType_UNKNOWN_CARD
|
||||
#define _meshtastic_SdCardInfo_CardType_ARRAYSIZE ((meshtastic_SdCardInfo_CardType)(meshtastic_SdCardInfo_CardType_UNKNOWN_CARD+1))
|
||||
|
||||
#define _meshtastic_SdCardInfo_FatType_MIN meshtastic_SdCardInfo_FatType_UNKNOWN_FAT
|
||||
#define _meshtastic_SdCardInfo_FatType_MAX meshtastic_SdCardInfo_FatType_EXFAT
|
||||
#define _meshtastic_SdCardInfo_FatType_ARRAYSIZE ((meshtastic_SdCardInfo_FatType)(meshtastic_SdCardInfo_FatType_EXFAT+1))
|
||||
|
||||
#define _meshtastic_I2CResult_Status_MIN meshtastic_I2CResult_Status_UNSPECIFIED
|
||||
#define _meshtastic_I2CResult_Status_MAX meshtastic_I2CResult_Status_ERROR
|
||||
#define _meshtastic_I2CResult_Status_ARRAYSIZE ((meshtastic_I2CResult_Status)(meshtastic_I2CResult_Status_ERROR+1))
|
||||
|
||||
#define meshtastic_FileTransfer_operation_ENUMTYPE meshtastic_FileOperation
|
||||
#define meshtastic_FileTransfer_status_ENUMTYPE meshtastic_FileStatus
|
||||
|
||||
#define meshtastic_DirectoryListing_status_ENUMTYPE meshtastic_FileStatus
|
||||
|
||||
|
||||
#define meshtastic_SdCardInfo_card_type_ENUMTYPE meshtastic_SdCardInfo_CardType
|
||||
#define meshtastic_SdCardInfo_fat_type_ENUMTYPE meshtastic_SdCardInfo_FatType
|
||||
|
||||
#define meshtastic_I2CResult_status_ENUMTYPE meshtastic_I2CResult_Status
|
||||
|
||||
#define meshtastic_InterdeviceMessage_data_ping_ENUMTYPE meshtastic_InterdeviceVersion
|
||||
#define meshtastic_InterdeviceMessage_data_pong_ENUMTYPE meshtastic_InterdeviceVersion
|
||||
#define meshtastic_InterdeviceMessage_data_sd_command_ENUMTYPE meshtastic_SdCommand
|
||||
|
||||
|
||||
/* Initializer values for message structs */
|
||||
#define meshtastic_SensorData_init_default {_meshtastic_MessageType_MIN, 0, {0}}
|
||||
#define meshtastic_InterdeviceMessage_init_default {0, {""}}
|
||||
#define meshtastic_SensorData_init_zero {_meshtastic_MessageType_MIN, 0, {0}}
|
||||
#define meshtastic_InterdeviceMessage_init_zero {0, {""}}
|
||||
#define meshtastic_FileTransfer_init_default {_meshtastic_FileOperation_MIN, "", {0, {0}}, _meshtastic_FileStatus_MIN, "", 0, 0, 0}
|
||||
#define meshtastic_DirectoryListing_init_default {"", 0, {"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""}, _meshtastic_FileStatus_MIN, "", 0, 0}
|
||||
#define meshtastic_I2CTransaction_init_default {0, {0, {0}}, 0}
|
||||
#define meshtastic_SdCardInfo_init_default {0, _meshtastic_SdCardInfo_CardType_MIN, _meshtastic_SdCardInfo_FatType_MIN, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_I2CResult_init_default {_meshtastic_I2CResult_Status_MIN, {0, {0}}}
|
||||
#define meshtastic_InterdeviceMessage_init_default {0, {""}, 0}
|
||||
#define meshtastic_FileTransfer_init_zero {_meshtastic_FileOperation_MIN, "", {0, {0}}, _meshtastic_FileStatus_MIN, "", 0, 0, 0}
|
||||
#define meshtastic_DirectoryListing_init_zero {"", 0, {"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""}, _meshtastic_FileStatus_MIN, "", 0, 0}
|
||||
#define meshtastic_I2CTransaction_init_zero {0, {0, {0}}, 0}
|
||||
#define meshtastic_SdCardInfo_init_zero {0, _meshtastic_SdCardInfo_CardType_MIN, _meshtastic_SdCardInfo_FatType_MIN, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_I2CResult_init_zero {_meshtastic_I2CResult_Status_MIN, {0, {0}}}
|
||||
#define meshtastic_InterdeviceMessage_init_zero {0, {""}, 0}
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
#define meshtastic_SensorData_type_tag 1
|
||||
#define meshtastic_SensorData_float_value_tag 2
|
||||
#define meshtastic_SensorData_uint32_value_tag 3
|
||||
#define meshtastic_FileTransfer_operation_tag 1
|
||||
#define meshtastic_FileTransfer_filepath_tag 2
|
||||
#define meshtastic_FileTransfer_filedata_tag 3
|
||||
#define meshtastic_FileTransfer_status_tag 4
|
||||
#define meshtastic_FileTransfer_message_tag 5
|
||||
#define meshtastic_FileTransfer_offset_tag 6
|
||||
#define meshtastic_FileTransfer_length_tag 7
|
||||
#define meshtastic_FileTransfer_file_size_tag 8
|
||||
#define meshtastic_DirectoryListing_directory_tag 1
|
||||
#define meshtastic_DirectoryListing_filenames_tag 2
|
||||
#define meshtastic_DirectoryListing_status_tag 3
|
||||
#define meshtastic_DirectoryListing_message_tag 4
|
||||
#define meshtastic_DirectoryListing_offset_tag 5
|
||||
#define meshtastic_DirectoryListing_total_count_tag 6
|
||||
#define meshtastic_I2CTransaction_address_tag 1
|
||||
#define meshtastic_I2CTransaction_write_data_tag 2
|
||||
#define meshtastic_I2CTransaction_read_len_tag 3
|
||||
#define meshtastic_SdCardInfo_present_tag 1
|
||||
#define meshtastic_SdCardInfo_card_type_tag 2
|
||||
#define meshtastic_SdCardInfo_fat_type_tag 3
|
||||
#define meshtastic_SdCardInfo_card_size_tag 4
|
||||
#define meshtastic_SdCardInfo_used_bytes_tag 5
|
||||
#define meshtastic_SdCardInfo_free_bytes_tag 6
|
||||
#define meshtastic_SdCardInfo_stats_valid_tag 7
|
||||
#define meshtastic_SdCardInfo_busy_tag 8
|
||||
#define meshtastic_SdCardInfo_unformatted_tag 9
|
||||
#define meshtastic_I2CResult_status_tag 1
|
||||
#define meshtastic_I2CResult_read_data_tag 2
|
||||
#define meshtastic_InterdeviceMessage_nmea_tag 1
|
||||
#define meshtastic_InterdeviceMessage_sensor_tag 2
|
||||
#define meshtastic_InterdeviceMessage_beep_tag 2
|
||||
#define meshtastic_InterdeviceMessage_i2c_transaction_tag 3
|
||||
#define meshtastic_InterdeviceMessage_i2c_result_tag 4
|
||||
#define meshtastic_InterdeviceMessage_i2c_scan_tag 5
|
||||
#define meshtastic_InterdeviceMessage_i2c_scan_result_tag 6
|
||||
#define meshtastic_InterdeviceMessage_file_transfer_tag 7
|
||||
#define meshtastic_InterdeviceMessage_directory_listing_tag 8
|
||||
#define meshtastic_InterdeviceMessage_get_sd_info_tag 9
|
||||
#define meshtastic_InterdeviceMessage_sd_info_tag 10
|
||||
#define meshtastic_InterdeviceMessage_ping_tag 11
|
||||
#define meshtastic_InterdeviceMessage_pong_tag 12
|
||||
#define meshtastic_InterdeviceMessage_nack_tag 13
|
||||
#define meshtastic_InterdeviceMessage_sd_command_tag 14
|
||||
#define meshtastic_InterdeviceMessage_id_tag 15
|
||||
|
||||
/* Struct field encoding specification for nanopb */
|
||||
#define meshtastic_SensorData_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, UENUM, type, 1) \
|
||||
X(a, STATIC, ONEOF, FLOAT, (data,float_value,data.float_value), 2) \
|
||||
X(a, STATIC, ONEOF, UINT32, (data,uint32_value,data.uint32_value), 3)
|
||||
#define meshtastic_SensorData_CALLBACK NULL
|
||||
#define meshtastic_SensorData_DEFAULT NULL
|
||||
#define meshtastic_FileTransfer_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, UENUM, operation, 1) \
|
||||
X(a, STATIC, SINGULAR, STRING, filepath, 2) \
|
||||
X(a, STATIC, SINGULAR, BYTES, filedata, 3) \
|
||||
X(a, STATIC, SINGULAR, UENUM, status, 4) \
|
||||
X(a, STATIC, SINGULAR, STRING, message, 5) \
|
||||
X(a, STATIC, SINGULAR, UINT64, offset, 6) \
|
||||
X(a, STATIC, SINGULAR, UINT32, length, 7) \
|
||||
X(a, STATIC, SINGULAR, UINT64, file_size, 8)
|
||||
#define meshtastic_FileTransfer_CALLBACK NULL
|
||||
#define meshtastic_FileTransfer_DEFAULT NULL
|
||||
|
||||
#define meshtastic_DirectoryListing_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, STRING, directory, 1) \
|
||||
X(a, STATIC, REPEATED, STRING, filenames, 2) \
|
||||
X(a, STATIC, SINGULAR, UENUM, status, 3) \
|
||||
X(a, STATIC, SINGULAR, STRING, message, 4) \
|
||||
X(a, STATIC, SINGULAR, UINT32, offset, 5) \
|
||||
X(a, STATIC, SINGULAR, UINT32, total_count, 6)
|
||||
#define meshtastic_DirectoryListing_CALLBACK NULL
|
||||
#define meshtastic_DirectoryListing_DEFAULT NULL
|
||||
|
||||
#define meshtastic_I2CTransaction_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, UINT32, address, 1) \
|
||||
X(a, STATIC, SINGULAR, BYTES, write_data, 2) \
|
||||
X(a, STATIC, SINGULAR, UINT32, read_len, 3)
|
||||
#define meshtastic_I2CTransaction_CALLBACK NULL
|
||||
#define meshtastic_I2CTransaction_DEFAULT NULL
|
||||
|
||||
#define meshtastic_SdCardInfo_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, BOOL, present, 1) \
|
||||
X(a, STATIC, SINGULAR, UENUM, card_type, 2) \
|
||||
X(a, STATIC, SINGULAR, UENUM, fat_type, 3) \
|
||||
X(a, STATIC, SINGULAR, UINT64, card_size, 4) \
|
||||
X(a, STATIC, SINGULAR, UINT64, used_bytes, 5) \
|
||||
X(a, STATIC, SINGULAR, UINT64, free_bytes, 6) \
|
||||
X(a, STATIC, SINGULAR, BOOL, stats_valid, 7) \
|
||||
X(a, STATIC, SINGULAR, BOOL, busy, 8) \
|
||||
X(a, STATIC, SINGULAR, BOOL, unformatted, 9)
|
||||
#define meshtastic_SdCardInfo_CALLBACK NULL
|
||||
#define meshtastic_SdCardInfo_DEFAULT NULL
|
||||
|
||||
#define meshtastic_I2CResult_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, UENUM, status, 1) \
|
||||
X(a, STATIC, SINGULAR, BYTES, read_data, 2)
|
||||
#define meshtastic_I2CResult_CALLBACK NULL
|
||||
#define meshtastic_I2CResult_DEFAULT NULL
|
||||
|
||||
#define meshtastic_InterdeviceMessage_FIELDLIST(X, a) \
|
||||
X(a, STATIC, ONEOF, STRING, (data,nmea,data.nmea), 1) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (data,sensor,data.sensor), 2)
|
||||
X(a, STATIC, ONEOF, UINT32, (data,beep,data.beep), 2) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (data,i2c_transaction,data.i2c_transaction), 3) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (data,i2c_result,data.i2c_result), 4) \
|
||||
X(a, STATIC, ONEOF, BOOL, (data,i2c_scan,data.i2c_scan), 5) \
|
||||
X(a, STATIC, ONEOF, BYTES, (data,i2c_scan_result,data.i2c_scan_result), 6) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (data,file_transfer,data.file_transfer), 7) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (data,directory_listing,data.directory_listing), 8) \
|
||||
X(a, STATIC, ONEOF, BOOL, (data,get_sd_info,data.get_sd_info), 9) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (data,sd_info,data.sd_info), 10) \
|
||||
X(a, STATIC, ONEOF, UENUM, (data,ping,data.ping), 11) \
|
||||
X(a, STATIC, ONEOF, UENUM, (data,pong,data.pong), 12) \
|
||||
X(a, STATIC, ONEOF, BOOL, (data,nack,data.nack), 13) \
|
||||
X(a, STATIC, ONEOF, UENUM, (data,sd_command,data.sd_command), 14) \
|
||||
X(a, STATIC, SINGULAR, UINT32, id, 15)
|
||||
#define meshtastic_InterdeviceMessage_CALLBACK NULL
|
||||
#define meshtastic_InterdeviceMessage_DEFAULT NULL
|
||||
#define meshtastic_InterdeviceMessage_data_sensor_MSGTYPE meshtastic_SensorData
|
||||
#define meshtastic_InterdeviceMessage_data_i2c_transaction_MSGTYPE meshtastic_I2CTransaction
|
||||
#define meshtastic_InterdeviceMessage_data_i2c_result_MSGTYPE meshtastic_I2CResult
|
||||
#define meshtastic_InterdeviceMessage_data_file_transfer_MSGTYPE meshtastic_FileTransfer
|
||||
#define meshtastic_InterdeviceMessage_data_directory_listing_MSGTYPE meshtastic_DirectoryListing
|
||||
#define meshtastic_InterdeviceMessage_data_sd_info_MSGTYPE meshtastic_SdCardInfo
|
||||
|
||||
extern const pb_msgdesc_t meshtastic_SensorData_msg;
|
||||
extern const pb_msgdesc_t meshtastic_FileTransfer_msg;
|
||||
extern const pb_msgdesc_t meshtastic_DirectoryListing_msg;
|
||||
extern const pb_msgdesc_t meshtastic_I2CTransaction_msg;
|
||||
extern const pb_msgdesc_t meshtastic_SdCardInfo_msg;
|
||||
extern const pb_msgdesc_t meshtastic_I2CResult_msg;
|
||||
extern const pb_msgdesc_t meshtastic_InterdeviceMessage_msg;
|
||||
|
||||
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
|
||||
#define meshtastic_SensorData_fields &meshtastic_SensorData_msg
|
||||
#define meshtastic_FileTransfer_fields &meshtastic_FileTransfer_msg
|
||||
#define meshtastic_DirectoryListing_fields &meshtastic_DirectoryListing_msg
|
||||
#define meshtastic_I2CTransaction_fields &meshtastic_I2CTransaction_msg
|
||||
#define meshtastic_SdCardInfo_fields &meshtastic_SdCardInfo_msg
|
||||
#define meshtastic_I2CResult_fields &meshtastic_I2CResult_msg
|
||||
#define meshtastic_InterdeviceMessage_fields &meshtastic_InterdeviceMessage_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define MESHTASTIC_MESHTASTIC_INTERDEVICE_PB_H_MAX_SIZE meshtastic_InterdeviceMessage_size
|
||||
#define meshtastic_InterdeviceMessage_size 1026
|
||||
#define meshtastic_SensorData_size 9
|
||||
#define meshtastic_DirectoryListing_size 4657
|
||||
#define meshtastic_FileTransfer_size 4646
|
||||
#define meshtastic_I2CResult_size 261
|
||||
#define meshtastic_I2CTransaction_size 271
|
||||
#define meshtastic_InterdeviceMessage_size 4666
|
||||
#define meshtastic_SdCardInfo_size 45
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -211,7 +211,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size
|
||||
#define meshtastic_LocalConfig_size 757
|
||||
#define meshtastic_LocalConfig_size 759
|
||||
#define meshtastic_LocalModuleConfig_size 1126
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -412,6 +412,8 @@ typedef enum _meshtastic_FirmwareEdition {
|
||||
meshtastic_FirmwareEdition_BURNING_MAN = 18,
|
||||
/* Hamvention, the Dayton amateur radio convention */
|
||||
meshtastic_FirmwareEdition_HAMVENTION = 19,
|
||||
/* FAB, the international Fab Lab digital fabrication conference */
|
||||
meshtastic_FirmwareEdition_FAB = 20,
|
||||
/* Placeholder for DIY and unofficial events */
|
||||
meshtastic_FirmwareEdition_DIY_EDITION = 127
|
||||
} meshtastic_FirmwareEdition;
|
||||
@@ -1395,6 +1397,9 @@ typedef struct _meshtastic_DeviceMetadata {
|
||||
/* Bit field of boolean for excluded modules
|
||||
(bitwise OR of ExcludedModules) */
|
||||
uint32_t excluded_modules;
|
||||
/* Indicates whether this firmware build includes XEdDSA packet signature verification.
|
||||
This is a read-only capability and must be false when XEdDSA is not compiled in. */
|
||||
bool has_xeddsa;
|
||||
} meshtastic_DeviceMetadata;
|
||||
|
||||
/* A distinct set of legal modem presets shared by one or more LoRa regions.
|
||||
@@ -1743,7 +1748,7 @@ extern "C" {
|
||||
#define meshtastic_Compressed_init_default {_meshtastic_PortNum_MIN, {0, {0}}}
|
||||
#define meshtastic_NeighborInfo_init_default {0, 0, 0, 0, {meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default, meshtastic_Neighbor_init_default}}
|
||||
#define meshtastic_Neighbor_init_default {0, 0, 0, 0}
|
||||
#define meshtastic_DeviceMetadata_init_default {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0, 0, 0}
|
||||
#define meshtastic_DeviceMetadata_init_default {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0, 0, 0, 0}
|
||||
#define meshtastic_LoRaPresetGroup_init_default {0, {_meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN}, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0}
|
||||
#define meshtastic_LoRaRegionPresets_init_default {_meshtastic_Config_LoRaConfig_RegionCode_MIN, 0}
|
||||
#define meshtastic_LoRaRegionPresetMap_init_default {0, {meshtastic_LoRaPresetGroup_init_default, meshtastic_LoRaPresetGroup_init_default, meshtastic_LoRaPresetGroup_init_default, meshtastic_LoRaPresetGroup_init_default, meshtastic_LoRaPresetGroup_init_default, meshtastic_LoRaPresetGroup_init_default, meshtastic_LoRaPresetGroup_init_default, meshtastic_LoRaPresetGroup_init_default}, 0, {meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default, meshtastic_LoRaRegionPresets_init_default}}
|
||||
@@ -1782,7 +1787,7 @@ extern "C" {
|
||||
#define meshtastic_Compressed_init_zero {_meshtastic_PortNum_MIN, {0, {0}}}
|
||||
#define meshtastic_NeighborInfo_init_zero {0, 0, 0, 0, {meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero, meshtastic_Neighbor_init_zero}}
|
||||
#define meshtastic_Neighbor_init_zero {0, 0, 0, 0}
|
||||
#define meshtastic_DeviceMetadata_init_zero {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0, 0, 0}
|
||||
#define meshtastic_DeviceMetadata_init_zero {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0, 0, 0, 0}
|
||||
#define meshtastic_LoRaPresetGroup_init_zero {0, {_meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, _meshtastic_Config_LoRaConfig_ModemPreset_MIN}, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0}
|
||||
#define meshtastic_LoRaRegionPresets_init_zero {_meshtastic_Config_LoRaConfig_RegionCode_MIN, 0}
|
||||
#define meshtastic_LoRaRegionPresetMap_init_zero {0, {meshtastic_LoRaPresetGroup_init_zero, meshtastic_LoRaPresetGroup_init_zero, meshtastic_LoRaPresetGroup_init_zero, meshtastic_LoRaPresetGroup_init_zero, meshtastic_LoRaPresetGroup_init_zero, meshtastic_LoRaPresetGroup_init_zero, meshtastic_LoRaPresetGroup_init_zero, meshtastic_LoRaPresetGroup_init_zero}, 0, {meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero, meshtastic_LoRaRegionPresets_init_zero}}
|
||||
@@ -1985,6 +1990,7 @@ extern "C" {
|
||||
#define meshtastic_DeviceMetadata_hasRemoteHardware_tag 10
|
||||
#define meshtastic_DeviceMetadata_hasPKC_tag 11
|
||||
#define meshtastic_DeviceMetadata_excluded_modules_tag 12
|
||||
#define meshtastic_DeviceMetadata_has_xeddsa_tag 14
|
||||
#define meshtastic_LoRaPresetGroup_presets_tag 1
|
||||
#define meshtastic_LoRaPresetGroup_default_preset_tag 2
|
||||
#define meshtastic_LoRaPresetGroup_licensed_only_tag 3
|
||||
@@ -2403,7 +2409,8 @@ X(a, STATIC, SINGULAR, UINT32, position_flags, 8) \
|
||||
X(a, STATIC, SINGULAR, UENUM, hw_model, 9) \
|
||||
X(a, STATIC, SINGULAR, BOOL, hasRemoteHardware, 10) \
|
||||
X(a, STATIC, SINGULAR, BOOL, hasPKC, 11) \
|
||||
X(a, STATIC, SINGULAR, UINT32, excluded_modules, 12)
|
||||
X(a, STATIC, SINGULAR, UINT32, excluded_modules, 12) \
|
||||
X(a, STATIC, SINGULAR, BOOL, has_xeddsa, 14)
|
||||
#define meshtastic_DeviceMetadata_CALLBACK NULL
|
||||
#define meshtastic_DeviceMetadata_DEFAULT NULL
|
||||
|
||||
@@ -2552,7 +2559,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
|
||||
#define meshtastic_ClientNotification_size 482
|
||||
#define meshtastic_Compressed_size 239
|
||||
#define meshtastic_Data_size 335
|
||||
#define meshtastic_DeviceMetadata_size 54
|
||||
#define meshtastic_DeviceMetadata_size 56
|
||||
#define meshtastic_DuplicatedPublicKey_size 0
|
||||
#define meshtastic_FileInfo_size 236
|
||||
#define meshtastic_FromRadio_size 510
|
||||
|
||||
@@ -121,7 +121,9 @@ typedef enum _meshtastic_TelemetrySensorType {
|
||||
/* ICM-42607-P 6‑Axis IMU */
|
||||
meshtastic_TelemetrySensorType_ICM42607P = 53,
|
||||
/* SPA06 pressure and temperature */
|
||||
meshtastic_TelemetrySensorType_SPA06 = 54
|
||||
meshtastic_TelemetrySensorType_SPA06 = 54,
|
||||
/* HM330X PM SENSOR */
|
||||
meshtastic_TelemetrySensorType_HM330X = 55
|
||||
} meshtastic_TelemetrySensorType;
|
||||
|
||||
/* Struct definitions */
|
||||
@@ -502,8 +504,8 @@ extern "C" {
|
||||
|
||||
/* Helper constants for enums */
|
||||
#define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
|
||||
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SPA06
|
||||
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SPA06+1))
|
||||
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_HM330X
|
||||
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_HM330X+1))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -96,14 +96,6 @@ static void taskCreateCert(void *parameter)
|
||||
{
|
||||
prefs.begin("MeshtasticHTTPS", false);
|
||||
|
||||
#if 0
|
||||
// Delete the saved certs (used in debugging)
|
||||
LOG_DEBUG("Delete any saved SSL keys");
|
||||
// prefs.clear();
|
||||
prefs.remove("PK");
|
||||
prefs.remove("cert");
|
||||
#endif
|
||||
|
||||
LOG_INFO("Checking if we have a saved SSL Certificate");
|
||||
|
||||
size_t pkLen = prefs.getBytesLength("PK");
|
||||
|
||||
@@ -231,8 +231,6 @@ int callback_static_file(const struct _u_request *request, struct _u_response *r
|
||||
}
|
||||
}
|
||||
|
||||
static void handleWebResponse() {}
|
||||
|
||||
/*
|
||||
* Adapt the radioapi to the Webservice handleAPIv1ToRadio
|
||||
* Trigger : WebGui(SAVE)->WebServcice->phoneApi
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "configuration.h"
|
||||
#if HAS_WIFI
|
||||
#include "NodeDB.h"
|
||||
#include "RTC.h"
|
||||
#include "concurrency/Periodic.h"
|
||||
#include "gps/RTC.h"
|
||||
#include "mesh/wifi/WiFiAPClient.h"
|
||||
|
||||
#include "main.h"
|
||||
|
||||
Reference in New Issue
Block a user