From a96a99bbdfd9c998c28f54a254aea107a99d402c Mon Sep 17 00:00:00 2001 From: nightjoker7 Date: Wed, 22 Apr 2026 22:06:05 -0500 Subject: [PATCH 1/3] Null-check clientNotificationPool / packetPool / mqtt pool allocations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fifteen sites across nine files called pool.allocZeroed() and immediately dereferenced the result without null-checking. On static MemoryPool-backed pools, allocZeroed() returns nullptr when slots are exhausted (with a LOG_WARN) — every dereference crashes. Sites fixed: - main.cpp:911 — low-entropy key warning on boot - mesh/NodeDB.cpp:1883 — duplicate-public-key warning - mesh/PhoneAPI.cpp:767 — sendNotification helper - mesh/Router.cpp:327 — duty-cycle-exceeded notification - modules/PositionModule.cpp:399 — tracker power-save notify - modules/SerialModule.cpp:98 — invalid serial config notify - modules/Telemetry/AirQualityTelemetry.cpp:418,432 — sensor sleep notify (2 sites) - modules/Telemetry/EnvironmentTelemetry.cpp:646 — sensor sleep notify - mqtt/MQTT.cpp:484,505 — MQTT publish proxy message (2 sites) - mqtt/MQTT.cpp:687,703 — invalid-config client notifications (2 sites) - mqtt/MQTT.cpp:888 — map report MeshPacket alloc Sister PRs in flight (#10261 covers Router::allocForSending) harden the same allocation class on different call sites. Matching the existing style: sites that already null-check (AdminModule::sendWarning, RadioInterface::sendErrorNotification, MQTT.cpp:671) use the same `if (cn) { ... }` or `if (!cn) return;` pattern now applied uniformly. --- src/main.cpp | 12 ++++--- src/mesh/NodeDB.cpp | 10 +++--- src/mesh/PhoneAPI.cpp | 2 ++ src/mesh/Router.cpp | 14 ++++---- src/modules/PositionModule.cpp | 14 ++++---- src/modules/SerialModule.cpp | 10 +++--- src/modules/Telemetry/AirQualityTelemetry.cpp | 32 +++++++++++-------- .../Telemetry/EnvironmentTelemetry.cpp | 16 ++++++---- src/mqtt/MQTT.cpp | 32 +++++++++++++------ 9 files changed, 86 insertions(+), 56 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 6f78c0b960b..5b3b1cfece5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -909,11 +909,13 @@ void setup() if (nodeDB->keyIsLowEntropy && !nodeDB->hasWarned) { LOG_WARN(LOW_ENTROPY_WARNING); meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); - cn->level = meshtastic_LogRecord_Level_WARNING; - cn->time = getValidTime(RTCQualityFromNet); - sprintf(cn->message, LOW_ENTROPY_WARNING); - service->sendClientNotification(cn); - nodeDB->hasWarned = true; + if (cn) { + cn->level = meshtastic_LogRecord_Level_WARNING; + cn->time = getValidTime(RTCQualityFromNet); + sprintf(cn->message, LOW_ENTROPY_WARNING); + service->sendClientNotification(cn); + nodeDB->hasWarned = true; + } } #endif #if !MESHTASTIC_EXCLUDE_INPUTBROKER diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 6e57e89f6e1..67e8b2d98d4 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1881,10 +1881,12 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde "to regenerate your public keys."; LOG_WARN(warning, p.long_name); meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); - cn->level = meshtastic_LogRecord_Level_WARNING; - cn->time = getValidTime(RTCQualityFromNet); - sprintf(cn->message, warning, p.long_name); - service->sendClientNotification(cn); + if (cn) { + cn->level = meshtastic_LogRecord_Level_WARNING; + cn->time = getValidTime(RTCQualityFromNet); + sprintf(cn->message, warning, p.long_name); + service->sendClientNotification(cn); + } } return false; } diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 714e6110865..30751df46e8 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -765,6 +765,8 @@ bool PhoneAPI::available() void PhoneAPI::sendNotification(meshtastic_LogRecord_Level level, uint32_t replyId, const char *message) { meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); + if (!cn) + return; cn->has_reply_id = true; cn->reply_id = replyId; cn->level = meshtastic_LogRecord_Level_WARNING; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 836cd1a2291..537c758c1ad 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -325,12 +325,14 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) LOG_WARN("Duty cycle limit exceeded. Aborting send for now, you can send again in %d mins", silentMinutes); meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); - cn->has_reply_id = true; - cn->reply_id = p->id; - cn->level = meshtastic_LogRecord_Level_WARNING; - cn->time = getValidTime(RTCQualityFromNet); - sprintf(cn->message, "Duty cycle limit exceeded. You can send again in %d mins", silentMinutes); - service->sendClientNotification(cn); + if (cn) { + cn->has_reply_id = true; + cn->reply_id = p->id; + cn->level = meshtastic_LogRecord_Level_WARNING; + cn->time = getValidTime(RTCQualityFromNet); + sprintf(cn->message, "Duty cycle limit exceeded. You can send again in %d mins", silentMinutes); + service->sendClientNotification(cn); + } meshtastic_Routing_Error err = meshtastic_Routing_Error_DUTY_CYCLE_LIMIT; if (isFromUs(p)) { // only send NAK to API, not to the mesh diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index 0378d01e74b..67c84f4a99c 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -397,12 +397,14 @@ void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t cha meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) && config.power.is_power_saving) { meshtastic_ClientNotification *notification = clientNotificationPool.allocZeroed(); - notification->level = meshtastic_LogRecord_Level_INFO; - notification->time = getValidTime(RTCQualityFromNet); - sprintf(notification->message, "Sending position and sleeping for %us interval in a moment", - Default::getConfiguredOrDefaultMs(config.position.position_broadcast_secs, default_broadcast_interval_secs) / - 1000U); - service->sendClientNotification(notification); + if (notification) { + notification->level = meshtastic_LogRecord_Level_INFO; + notification->time = getValidTime(RTCQualityFromNet); + sprintf(notification->message, "Sending position and sleeping for %us interval in a moment", + Default::getConfiguredOrDefaultMs(config.position.position_broadcast_secs, default_broadcast_interval_secs) / + 1000U); + service->sendClientNotification(notification); + } sleepOnNextExecution = true; LOG_DEBUG("Start next execution in 5s, then sleep"); setIntervalFromNow(FIVE_SECONDS_MS); diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 20d4d7d8c7e..ac57e71a8d4 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -96,10 +96,12 @@ bool SerialModule::isValidConfig(const meshtastic_ModuleConfig_SerialConfig &con LOG_ERROR(warning); #if !IS_RUNNING_TESTS meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); - cn->level = meshtastic_LogRecord_Level_ERROR; - cn->time = getValidTime(RTCQualityFromNet); - snprintf(cn->message, sizeof(cn->message), "%s", warning); - service->sendClientNotification(cn); + if (cn) { + cn->level = meshtastic_LogRecord_Level_ERROR; + cn->time = getValidTime(RTCQualityFromNet); + snprintf(cn->message, sizeof(cn->message), "%s", warning); + service->sendClientNotification(cn); + } #endif return false; } diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index ca853d0510e..42091fce6d4 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -416,13 +416,15 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR && config.power.is_power_saving) { meshtastic_ClientNotification *notification = clientNotificationPool.allocZeroed(); - notification->level = meshtastic_LogRecord_Level_INFO; - notification->time = getValidTime(RTCQualityFromNet); - sprintf(notification->message, "Sending telemetry and sleeping for %us interval in a moment", - Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval, - default_telemetry_broadcast_interval_secs) / - 1000U); - service->sendClientNotification(notification); + if (notification) { + notification->level = meshtastic_LogRecord_Level_INFO; + notification->time = getValidTime(RTCQualityFromNet); + sprintf(notification->message, "Sending telemetry and sleeping for %us interval in a moment", + Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval, + default_telemetry_broadcast_interval_secs) / + 1000U); + service->sendClientNotification(notification); + } sleepOnNextExecution = true; LOG_DEBUG("Start next execution in 5s, then sleep"); setIntervalFromNow(FIVE_SECONDS_MS); @@ -430,13 +432,15 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR && config.power.is_power_saving) { meshtastic_ClientNotification *notification = clientNotificationPool.allocZeroed(); - notification->level = meshtastic_LogRecord_Level_INFO; - notification->time = getValidTime(RTCQualityFromNet); - sprintf(notification->message, "Sending telemetry and sleeping for %us interval in a moment", - Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval, - default_telemetry_broadcast_interval_secs) / - 1000U); - service->sendClientNotification(notification); + if (notification) { + notification->level = meshtastic_LogRecord_Level_INFO; + notification->time = getValidTime(RTCQualityFromNet); + sprintf(notification->message, "Sending telemetry and sleeping for %us interval in a moment", + Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval, + default_telemetry_broadcast_interval_secs) / + 1000U); + service->sendClientNotification(notification); + } sleepOnNextExecution = true; LOG_DEBUG("Start next execution in 5s, then sleep"); setIntervalFromNow(FIVE_SECONDS_MS); diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 684d408a1cc..143786c5efe 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -644,13 +644,15 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR && config.power.is_power_saving) { meshtastic_ClientNotification *notification = clientNotificationPool.allocZeroed(); - notification->level = meshtastic_LogRecord_Level_INFO; - notification->time = getValidTime(RTCQualityFromNet); - sprintf(notification->message, "Sending telemetry and sleeping for %us interval in a moment", - Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval, - default_telemetry_broadcast_interval_secs) / - 1000U); - service->sendClientNotification(notification); + if (notification) { + notification->level = meshtastic_LogRecord_Level_INFO; + notification->time = getValidTime(RTCQualityFromNet); + sprintf(notification->message, "Sending telemetry and sleeping for %us interval in a moment", + Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval, + default_telemetry_broadcast_interval_secs) / + 1000U); + service->sendClientNotification(notification); + } sleepOnNextExecution = true; LOG_DEBUG("Start next execution in 5s, then sleep"); setIntervalFromNow(FIVE_SECONDS_MS); diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index aba06c21005..cb63b7d34fa 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -482,6 +482,8 @@ bool MQTT::publish(const char *topic, const char *payload, bool retained) { if (moduleConfig.mqtt.proxy_to_client_enabled) { meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); + if (!msg) + return false; msg->which_payload_variant = meshtastic_MqttClientProxyMessage_text_tag; strncpy(msg->topic, topic, sizeof(msg->topic)); msg->topic[sizeof(msg->topic) - 1] = '\0'; @@ -503,6 +505,8 @@ bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, boo { if (moduleConfig.mqtt.proxy_to_client_enabled) { meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); + if (!msg) + return false; msg->which_payload_variant = meshtastic_MqttClientProxyMessage_data_tag; strncpy(msg->topic, topic, sizeof(msg->topic)); msg->topic[sizeof(msg->topic) - 1] = '\0'; // Ensure null termination @@ -685,11 +689,13 @@ bool MQTT::isValidConfig(const meshtastic_ModuleConfig_MQTTConfig &config, MQTTC LOG_ERROR(warning); #if !IS_RUNNING_TESTS meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); - cn->level = meshtastic_LogRecord_Level_ERROR; - cn->time = getValidTime(RTCQualityFromNet); - strncpy(cn->message, warning, sizeof(cn->message) - 1); - cn->message[sizeof(cn->message) - 1] = '\0'; // Ensure null termination - service->sendClientNotification(cn); + if (cn) { + cn->level = meshtastic_LogRecord_Level_ERROR; + cn->time = getValidTime(RTCQualityFromNet); + strncpy(cn->message, warning, sizeof(cn->message) - 1); + cn->message[sizeof(cn->message) - 1] = '\0'; // Ensure null termination + service->sendClientNotification(cn); + } #endif return false; #endif @@ -701,11 +707,13 @@ bool MQTT::isValidConfig(const meshtastic_ModuleConfig_MQTTConfig &config, MQTTC LOG_ERROR(warning); #if !IS_RUNNING_TESTS meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); - cn->level = meshtastic_LogRecord_Level_ERROR; - cn->time = getValidTime(RTCQualityFromNet); - strncpy(cn->message, warning, sizeof(cn->message) - 1); - cn->message[sizeof(cn->message) - 1] = '\0'; // Ensure null termination - service->sendClientNotification(cn); + if (cn) { + cn->level = meshtastic_LogRecord_Level_ERROR; + cn->time = getValidTime(RTCQualityFromNet); + strncpy(cn->message, warning, sizeof(cn->message) - 1); + cn->message[sizeof(cn->message) - 1] = '\0'; // Ensure null termination + service->sendClientNotification(cn); + } #endif return false; } @@ -878,6 +886,10 @@ void MQTT::perhapsReportToMap() // Allocate MeshPacket and fill it meshtastic_MeshPacket *mp = packetPool.allocZeroed(); + if (!mp) { + LOG_WARN("MQTT Map report: packet pool exhausted"); + return; + } mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag; mp->from = nodeDB->getNodeNum(); mp->to = NODENUM_BROADCAST; From 055bf7b52833ec88a8d0f219e85998e9c1c4a92c Mon Sep 17 00:00:00 2001 From: nightjoker7 Date: Thu, 23 Apr 2026 16:01:03 -0500 Subject: [PATCH 2/3] Address review feedback on #10262 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five issues raised by @thebentern and @Copilot: 1. **MQTT map report alloc failure (thebentern, MQTT.cpp:890)**: changed LOG_WARN to LOG_ERROR — map reports aren't retried, so losing one on alloc failure is worth error-level visibility. 2. **MQTT proxy publish silently drops (Copilot, MQTT.cpp:486+509)**: added LOG_WARN on pool exhaustion so operators can see why a publish failed instead of just getting a silent false return. 3. **Low-entropy warning spin (Copilot, main.cpp:918)**: nodeDB->hasWarned was only set on allocZeroed() success, so if the pool was full we'd retry every tick and re-LOG_WARN forever. Set unconditionally — the LOG_WARN is what matters for visibility, the notification is best-effort. 4. **Level parameter ignored (Copilot, PhoneAPI.cpp:772)**: sendNotification took a level parameter but hardcoded cn->level = WARNING. Honor the caller's level. Also switched strncpy to NUL-terminating form because meshtastic_ClientNotification.message may not be NUL-terminated if the source string fills the buffer. 5. **sprintf overflow (Copilot, NodeDB.cpp:1887)**: the public-key-duplicate warning used sprintf with a remote-controlled p.long_name (up to 40 bytes) formatted into a ~200-byte warning template. Switched to snprintf against sizeof(cn->message). --- src/main.cpp | 5 ++++- src/mesh/NodeDB.cpp | 4 +++- src/mesh/PhoneAPI.cpp | 5 +++-- src/mqtt/MQTT.cpp | 10 +++++++--- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 5b3b1cfece5..5b94e744149 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -908,13 +908,16 @@ void setup() // warn the user about a low entropy key if (nodeDB->keyIsLowEntropy && !nodeDB->hasWarned) { LOG_WARN(LOW_ENTROPY_WARNING); + // Mark as warned unconditionally — the LOG_WARN above has already fired the user-visible + // notice; if the ClientNotification pool is exhausted we'd otherwise spin every tick + // retrying + spamming LOG_WARN. + nodeDB->hasWarned = true; meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); if (cn) { cn->level = meshtastic_LogRecord_Level_WARNING; cn->time = getValidTime(RTCQualityFromNet); sprintf(cn->message, LOW_ENTROPY_WARNING); service->sendClientNotification(cn); - nodeDB->hasWarned = true; } } #endif diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 67e8b2d98d4..ae7d675ce6f 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1884,7 +1884,9 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde if (cn) { cn->level = meshtastic_LogRecord_Level_WARNING; cn->time = getValidTime(RTCQualityFromNet); - sprintf(cn->message, warning, p.long_name); + // snprintf because p.long_name is remote-controlled up to 40 bytes and the + // warning format itself is ~150 bytes — sprintf could overflow cn->message. + snprintf(cn->message, sizeof(cn->message), warning, p.long_name); service->sendClientNotification(cn); } } diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 3d8f675ed84..da331993c9b 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -774,9 +774,10 @@ void PhoneAPI::sendNotification(meshtastic_LogRecord_Level level, uint32_t reply return; cn->has_reply_id = true; cn->reply_id = replyId; - cn->level = meshtastic_LogRecord_Level_WARNING; + cn->level = level; cn->time = getValidTime(RTCQualityFromNet); - strncpy(cn->message, message, sizeof(cn->message)); + strncpy(cn->message, message, sizeof(cn->message) - 1); + cn->message[sizeof(cn->message) - 1] = '\0'; service->sendClientNotification(cn); } diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index cb63b7d34fa..121b7548c89 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -482,8 +482,10 @@ bool MQTT::publish(const char *topic, const char *payload, bool retained) { if (moduleConfig.mqtt.proxy_to_client_enabled) { meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); - if (!msg) + if (!msg) { + LOG_WARN("MQTT proxy publish skipped: message pool exhausted"); return false; + } msg->which_payload_variant = meshtastic_MqttClientProxyMessage_text_tag; strncpy(msg->topic, topic, sizeof(msg->topic)); msg->topic[sizeof(msg->topic) - 1] = '\0'; @@ -505,8 +507,10 @@ bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, boo { if (moduleConfig.mqtt.proxy_to_client_enabled) { meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); - if (!msg) + if (!msg) { + LOG_WARN("MQTT proxy publish skipped: message pool exhausted"); return false; + } msg->which_payload_variant = meshtastic_MqttClientProxyMessage_data_tag; strncpy(msg->topic, topic, sizeof(msg->topic)); msg->topic[sizeof(msg->topic) - 1] = '\0'; // Ensure null termination @@ -887,7 +891,7 @@ void MQTT::perhapsReportToMap() // Allocate MeshPacket and fill it meshtastic_MeshPacket *mp = packetPool.allocZeroed(); if (!mp) { - LOG_WARN("MQTT Map report: packet pool exhausted"); + LOG_ERROR("MQTT Map report: packet pool exhausted"); return; } mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag; From 293cf2cf86389b77cc7aa2eea514d08b9896ba24 Mon Sep 17 00:00:00 2001 From: nightjoker7 Date: Fri, 24 Apr 2026 17:15:34 -0500 Subject: [PATCH 3/3] Address Copilot follow-ups: map-report throttle + dedupe proxy WARNs + reword setup comment Three line-item nits from Copilot's post-fixup pass on #10262: 1. perhapsReportToMap() pool-exhaustion path: didn't update last_report_to_map, so the caller in runOnce() would retry every 20-200 ms while the pool stayed exhausted, turning sustained pool pressure into a log-flood. Back off to the configured publish interval by stamping last_report_to_map = millis() on allocation failure. 2. MQTT::publish() proxy overloads: MemoryPool::alloc already LOG_WARNs on exhaustion, so the caller-side LOG_WARN("MQTT proxy publish skipped: message pool exhausted") was double-logging under the same stress that triggers the allocator's own warn. Drop both (char* and uint8_t* overloads) and rely on the allocator log. 3. main.cpp low-entropy-key warn block: the comment said the unconditional hasWarned assignment prevented spinning 'every tick', but setup() is single-shot. Reword to reflect the actual behavior: hasWarned persists in NodeDB so the warning is suppressed across subsequent boots. --- src/main.cpp | 4 ++-- src/mqtt/MQTT.cpp | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 5b94e744149..95577a4d049 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -909,8 +909,8 @@ void setup() if (nodeDB->keyIsLowEntropy && !nodeDB->hasWarned) { LOG_WARN(LOW_ENTROPY_WARNING); // Mark as warned unconditionally — the LOG_WARN above has already fired the user-visible - // notice; if the ClientNotification pool is exhausted we'd otherwise spin every tick - // retrying + spamming LOG_WARN. + // notice. `hasWarned` persists in NodeDB, so this also suppresses the same warning on + // subsequent boots regardless of whether the ClientNotification pool had room this time. nodeDB->hasWarned = true; meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); if (cn) { diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 121b7548c89..83a4503003b 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -483,7 +483,7 @@ bool MQTT::publish(const char *topic, const char *payload, bool retained) if (moduleConfig.mqtt.proxy_to_client_enabled) { meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); if (!msg) { - LOG_WARN("MQTT proxy publish skipped: message pool exhausted"); + // MemoryPool::alloc already LOG_WARNs on exhaustion; don't double-log here. return false; } msg->which_payload_variant = meshtastic_MqttClientProxyMessage_text_tag; @@ -508,7 +508,7 @@ bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, boo if (moduleConfig.mqtt.proxy_to_client_enabled) { meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); if (!msg) { - LOG_WARN("MQTT proxy publish skipped: message pool exhausted"); + // MemoryPool::alloc already LOG_WARNs on exhaustion; don't double-log here. return false; } msg->which_payload_variant = meshtastic_MqttClientProxyMessage_data_tag; @@ -891,7 +891,12 @@ void MQTT::perhapsReportToMap() // Allocate MeshPacket and fill it meshtastic_MeshPacket *mp = packetPool.allocZeroed(); if (!mp) { + // Back off to the configured publish interval on pool exhaustion. Without this, + // perhapsReportToMap() would be called on every runOnce() (20–200 ms) until the + // pool recovers, and each call would retry the log + early return — turning + // sustained pool pressure into a log-flood instead of a throttled failure. LOG_ERROR("MQTT Map report: packet pool exhausted"); + last_report_to_map = millis(); return; } mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag;