diff --git a/src/main.cpp b/src/main.cpp index 6f78c0b960b..95577a4d049 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -908,12 +908,17 @@ void setup() // warn the user about a low entropy key 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); + // Mark as warned unconditionally — the LOG_WARN above has already fired the user-visible + // 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) { + cn->level = meshtastic_LogRecord_Level_WARNING; + cn->time = getValidTime(RTCQualityFromNet); + sprintf(cn->message, LOW_ENTROPY_WARNING); + service->sendClientNotification(cn); + } } #endif #if !MESHTASTIC_EXCLUDE_INPUTBROKER diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 6e57e89f6e1..ae7d675ce6f 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1881,10 +1881,14 @@ 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); + // 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); + } } return false; } diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index cb25efb770f..da331993c9b 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -770,11 +770,14 @@ 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; + 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/mesh/Router.cpp b/src/mesh/Router.cpp index e0473a14e14..0734c153990 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..83a4503003b 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -482,6 +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) { + // MemoryPool::alloc already LOG_WARNs on exhaustion; don't double-log here. + 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 +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) { + // MemoryPool::alloc already LOG_WARNs on exhaustion; don't double-log here. + 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 +693,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 +711,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 +890,15 @@ 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; mp->from = nodeDB->getNodeNum(); mp->to = NODENUM_BROADCAST;