diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index abd01a281d..b69a58b046 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -972,11 +972,6 @@ void MyMesh::begin(FILESYSTEM *fs) { _fs = fs; // load persisted prefs _cli.loadPrefs(_fs); - - // Set MQTT origin to actual device name (not build-time ADVERT_NAME) - StrHelper::strncpy(_prefs.mqtt_origin, _prefs.node_name, sizeof(_prefs.mqtt_origin)); - MESH_DEBUG_PRINTLN("MQTT origin set to device name: %s", _prefs.mqtt_origin); - acl.load(_fs, self_id); // TODO: key_store.begin(); region_map.load(_fs); diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index ea9a052785..9174c11b58 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -712,10 +712,6 @@ void MyMesh::begin(FILESYSTEM *fs) { applyGpsPrefs(); #endif #ifdef WITH_MQTT_BRIDGE - // Set MQTT origin to actual device name (not build-time ADVERT_NAME) - same as repeater - StrHelper::strncpy(_prefs.mqtt_origin, _prefs.node_name, sizeof(_prefs.mqtt_origin)); - MESH_DEBUG_PRINTLN("MQTT origin set to device name: %s", _prefs.mqtt_origin); - if (_prefs.bridge_enabled) { // Set device public key for MQTT topics (same as repeater) char device_id[65]; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 54ab076693..46969a7a14 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -69,6 +69,13 @@ void CommonCLI::loadPrefs(FILESYSTEM* fs) { loadMQTTPrefs(fs); // Sync MQTT prefs to NodePrefs so existing code (like MQTTBridge) can access them syncMQTTPrefsToNodePrefs(); + // Fresh-install fallback: default mqtt.origin to node_name when mqtt_prefs has no origin yet. + // This preserves explicit user-configured mqtt.origin values. + if (_prefs->mqtt_origin[0] == '\0' && _prefs->node_name[0] != '\0') { + StrHelper::strncpy(_prefs->mqtt_origin, _prefs->node_name, sizeof(_prefs->mqtt_origin)); + syncNodePrefsToMQTTPrefs(); + saveMQTTPrefs(fs); + } // For MQTT bridge, migrate bridge.source to RX (logRx) only on fresh installs or upgrades // This ensures new users get the correct default, but respects existing user choices @@ -392,7 +399,11 @@ void CommonCLI::saveMQTTPrefs(FILESYSTEM* fs) { File file = fs->open("/mqtt_prefs", "w", true); #endif if (file) { - file.write((uint8_t *)&_mqtt_prefs, sizeof(_mqtt_prefs)); + size_t bytes_written = file.write((uint8_t *)&_mqtt_prefs, sizeof(_mqtt_prefs)); + if (bytes_written != sizeof(_mqtt_prefs)) { + MESH_DEBUG_PRINTLN("Failed to write /mqtt_prefs completely (wrote %u/%u bytes)", + (unsigned)bytes_written, (unsigned)sizeof(_mqtt_prefs)); + } file.close(); } } @@ -503,9 +514,13 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "ERR: clock cannot go backwards"); } } else if (memcmp(command, "memory", 6) == 0) { +#ifdef ESP_PLATFORM sprintf(reply, "Free: %d, Min: %d, Max: %d, Queue: %d", ESP.getFreeHeap(), ESP.getMinFreeHeap(), ESP.getMaxAllocHeap(), _callbacks->getQueueSize()); +#else + sprintf(reply, "Queue: %d", _callbacks->getQueueSize()); +#endif } else if (memcmp(command, "start ota", 9) == 0) { if (!_board->startOTAUpdate(_prefs->node_name, reply)) { strcpy(reply, "Error"); @@ -741,10 +756,10 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch uint8_t ps = _prefs->wifi_power_save; const char* ps_name = (ps == 1) ? "none" : (ps == 2) ? "max" : "min"; sprintf(reply, "> %s", ps_name); - } else if (memcmp(config, "timezone", 8) == 0) { - sprintf(reply, "> %s", _prefs->timezone_string); } else if (memcmp(config, "timezone.offset", 15) == 0) { sprintf(reply, "> %d", _prefs->timezone_offset); + } else if (memcmp(config, "timezone", 8) == 0) { + sprintf(reply, "> %s", _prefs->timezone_string); } else if (memcmp(config, "mqtt.analyzer.us", 17) == 0) { sprintf(reply, "> %s", _prefs->mqtt_analyzer_us_enabled ? "on" : "off"); } else if (memcmp(config, "mqtt.analyzer.eu", 17) == 0) { @@ -1149,9 +1164,13 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch savePrefs(); strcpy(reply, "OK"); } else if (memcmp(config, "timezone.offset ", 16) == 0) { - int8_t offset = _atoi(&config[16]); - if (offset >= -12 && offset <= 14) { - _prefs->timezone_offset = offset; + const char* offset_str = &config[16]; + char* endptr = nullptr; + long parsed = strtol(offset_str, &endptr, 10); + if (endptr == offset_str || (endptr != nullptr && *endptr != '\0')) { + strcpy(reply, "Error: timezone offset must be an integer"); + } else if (parsed >= -12 && parsed <= 14) { + _prefs->timezone_offset = (int8_t)parsed; savePrefs(); strcpy(reply, "OK"); } else { diff --git a/src/helpers/JWTHelper.cpp b/src/helpers/JWTHelper.cpp index dc4154ba30..7284afa2da 100644 --- a/src/helpers/JWTHelper.cpp +++ b/src/helpers/JWTHelper.cpp @@ -138,9 +138,12 @@ size_t JWTHelper::base64UrlEncode(const uint8_t* input, size_t inputLen, char* o size_t JWTHelper::createHeader(char* output, size_t outputSize) { // Create JWT header: {"alg":"Ed25519","typ":"JWT"} - DynamicJsonDocument doc(256); + StaticJsonDocument<128> doc; doc["alg"] = "Ed25519"; doc["typ"] = "JWT"; + if (doc.overflowed()) { + return 0; + } char jsonBuffer[256]; size_t len = serializeJson(doc, jsonBuffer, sizeof(jsonBuffer)); @@ -163,7 +166,7 @@ size_t JWTHelper::createPayload( const char* email ) { // Create JWT payload - DynamicJsonDocument doc(512); + StaticJsonDocument<512> doc; doc["publicKey"] = publicKey; doc["aud"] = audience; doc["iat"] = issuedAt; @@ -186,6 +189,9 @@ size_t JWTHelper::createPayload( if (email && strlen(email) > 0) { doc["email"] = email; } + if (doc.overflowed()) { + return 0; + } char jsonBuffer[512]; size_t len = serializeJson(doc, jsonBuffer, sizeof(jsonBuffer)); @@ -195,4 +201,3 @@ size_t JWTHelper::createPayload( return base64UrlEncode((uint8_t*)jsonBuffer, len, output, outputSize); } - diff --git a/src/helpers/bridges/MQTTBridge.cpp b/src/helpers/bridges/MQTTBridge.cpp index 287bc5f0b6..467a133be4 100644 --- a/src/helpers/bridges/MQTTBridge.cpp +++ b/src/helpers/bridges/MQTTBridge.cpp @@ -38,6 +38,10 @@ static bool isWiFiConfigValid(const NodePrefs* prefs) { if (strlen(prefs->wifi_ssid) == 0) { return false; } + // Reject placeholder values used in defaults/examples + if (strcmp(prefs->wifi_ssid, "ssid_here") == 0 || strcmp(prefs->wifi_ssid, "ssid") == 0) { + return false; + } // WiFi password can be empty for open networks, so we don't check it @@ -137,16 +141,19 @@ MQTTBridge::MQTTBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCCloc _ntp_client(_ntp_udp, "pool.ntp.org", 0, 60000), _last_ntp_sync(0), _ntp_synced(false), _ntp_sync_pending(false), _timezone(nullptr), _last_raw_len(0), _last_snr(0), _last_rssi(0), _last_raw_timestamp(0), _analyzer_us_enabled(false), _analyzer_eu_enabled(false), _identity(identity), + _token_us_expires_at(0), _token_eu_expires_at(0), _analyzer_us_client(nullptr), _analyzer_eu_client(nullptr), _config_valid(false), _cached_has_brokers(false), _cached_has_analyzer_servers(false), _last_memory_check(0), _skipped_publishes(0), _last_fragmentation_recovery(0), _fragmentation_pressure_since(0), _last_critical_check_run(0), + _last_token_renewal_attempt_us(0), _last_token_renewal_attempt_eu(0), + _last_reconnect_attempt_us(0), _last_reconnect_attempt_eu(0), _last_no_broker_log(0), _last_config_warning(0), _dispatcher(nullptr), _radio(nullptr), _board(nullptr), _ms(nullptr), _last_wifi_check(0), _last_wifi_status(WL_DISCONNECTED), _wifi_status_initialized(false), _wifi_disconnected_time(0), _last_wifi_reconnect_attempt(0), _wifi_reconnect_backoff_attempt(0), _main_broker_reconnect_backoff_attempt(0), _analyzer_us_reconnect_backoff_attempt(0), _analyzer_eu_reconnect_backoff_attempt(0) #ifdef ESP_PLATFORM - , _packet_queue_handle(nullptr), _mqtt_task_handle(nullptr), _raw_data_mutex(nullptr), _mqtt_task_stack(nullptr), _packet_queue_storage(nullptr) + , _packet_queue_handle(nullptr), _mqtt_task_handle(nullptr), _raw_data_mutex(nullptr), _wifi_event_id(0), _wifi_event_registered(false), _mqtt_task_stack(nullptr), _packet_queue_storage(nullptr) #else , _queue_head(0), _queue_tail(0) #endif @@ -173,15 +180,18 @@ MQTTBridge::MQTTBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCCloc // Override with build flags if defined #ifdef MQTT_SERVER strncpy(_prefs->mqtt_server, MQTT_SERVER, sizeof(_prefs->mqtt_server) - 1); + _prefs->mqtt_server[sizeof(_prefs->mqtt_server) - 1] = '\0'; #endif #ifdef MQTT_PORT _prefs->mqtt_port = MQTT_PORT; #endif #ifdef MQTT_USERNAME strncpy(_prefs->mqtt_username, MQTT_USERNAME, sizeof(_prefs->mqtt_username) - 1); + _prefs->mqtt_username[sizeof(_prefs->mqtt_username) - 1] = '\0'; #endif #ifdef MQTT_PASSWORD strncpy(_prefs->mqtt_password, MQTT_PASSWORD, sizeof(_prefs->mqtt_password) - 1); + _prefs->mqtt_password[sizeof(_prefs->mqtt_password) - 1] = '\0'; #endif // Initialize packet queue (FreeRTOS queue will be created in begin()) @@ -200,14 +210,9 @@ MQTTBridge::MQTTBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCCloc _last_analyzer_us_log = 0; _last_analyzer_eu_log = 0; - // JWT token buffers: allocate in PSRAM when available (plan §2) - _auth_token_us = (char*)psram_malloc(AUTH_TOKEN_SIZE); - _auth_token_eu = (char*)psram_malloc(AUTH_TOKEN_SIZE); - if (_auth_token_us) _auth_token_us[0] = '\0'; - if (_auth_token_eu) _auth_token_eu[0] = '\0'; - - // Raw radio buffer in PSRAM when available (plan §6) - _last_raw_data = (uint8_t*)psram_malloc(LAST_RAW_DATA_SIZE); + _auth_token_us = nullptr; + _auth_token_eu = nullptr; + _last_raw_data = nullptr; // Set default broker configuration setBrokerDefaults(); @@ -253,6 +258,26 @@ void MQTTBridge::begin() { MQTT_DEBUG_PRINTLN("MQTT Bridge initialization skipped - WiFi credentials not configured"); return; } + + // Allocate long-lived buffers on each begin() so restartBridge() is safe. + if (_auth_token_us == nullptr) { + _auth_token_us = (char*)psram_malloc(AUTH_TOKEN_SIZE); + } + if (_auth_token_eu == nullptr) { + _auth_token_eu = (char*)psram_malloc(AUTH_TOKEN_SIZE); + } + if (_last_raw_data == nullptr) { + _last_raw_data = (uint8_t*)psram_malloc(LAST_RAW_DATA_SIZE); + } + if (_auth_token_us) _auth_token_us[0] = '\0'; + if (_auth_token_eu) _auth_token_eu[0] = '\0'; + _last_raw_len = 0; + _token_us_expires_at = 0; + _token_eu_expires_at = 0; + _last_token_renewal_attempt_us = 0; + _last_token_renewal_attempt_eu = 0; + _last_reconnect_attempt_us = 0; + _last_reconnect_attempt_eu = 0; // Validate custom MQTT broker configuration (optional) _config_valid = isMQTTConfigValid(); @@ -330,6 +355,8 @@ void MQTTBridge::begin() { MQTT_DEBUG_PRINTLN("Failed to create raw data mutex!"); vQueueDelete(_packet_queue_handle); _packet_queue_handle = nullptr; + psram_free(_packet_queue_storage); + _packet_queue_storage = nullptr; return; } @@ -475,17 +502,18 @@ void MQTTBridge::end() { // Give task time to clean up vTaskDelay(pdMS_TO_TICKS(100)); } + if (_wifi_event_registered) { + WiFi.removeEvent(_wifi_event_id); + _wifi_event_registered = false; + } // Free PSRAM task stack (plan §3) psram_free(_mqtt_task_stack); _mqtt_task_stack = nullptr; - // Clean up queued packets from FreeRTOS queue - // NOTE: Do NOT free queued.packet - the Dispatcher owns those packets. - // We just discard our references to them. + // Clean up queued packet snapshots from FreeRTOS queue. if (_packet_queue_handle != nullptr) { QueuedPacket queued; while (xQueueReceive(_packet_queue_handle, &queued, 0) == pdTRUE) { - queued.packet = nullptr; _queue_count--; } vQueueDelete(_packet_queue_handle); @@ -499,6 +527,18 @@ void MQTTBridge::end() { vSemaphoreDelete(_raw_data_mutex); _raw_data_mutex = nullptr; } + + // Disconnect analyzer clients + if (_analyzer_us_client) { + _analyzer_us_client->disconnect(); + delete _analyzer_us_client; + _analyzer_us_client = nullptr; + } + if (_analyzer_eu_client) { + _analyzer_eu_client->disconnect(); + delete _analyzer_eu_client; + _analyzer_eu_client = nullptr; + } #else // Disconnect from all brokers (main client only exists when _config_valid) if (_mqtt_client) { @@ -522,12 +562,9 @@ void MQTTBridge::end() { _analyzer_eu_client = nullptr; } - // Clean up queued packet references - // NOTE: Do NOT free the packets - the Dispatcher owns those packets. - // We just discard our references to them. + // Clean up queued packet snapshots for (int i = 0; i < _queue_count; i++) { int index = (_queue_head + i) % MAX_QUEUE_SIZE; - _packet_queue[index].packet = nullptr; memset(&_packet_queue[index], 0, sizeof(QueuedPacket)); } @@ -582,7 +619,7 @@ void MQTTBridge::initializeWiFiInTask() { WiFi.setAutoConnect(true); // Set up WiFi event handlers for better diagnostics and immediate disconnection detection - WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { + _wifi_event_id = WiFi.onEvent([this](WiFiEvent_t event, WiFiEventInfo_t info) { switch(event) { case ARDUINO_EVENT_WIFI_STA_GOT_IP: MQTT_DEBUG_PRINTLN("WiFi connected: %s", IPAddress(info.got_ip.ip_info.ip.addr).toString().c_str()); @@ -595,6 +632,7 @@ void MQTTBridge::initializeWiFiInTask() { break; } }); + _wifi_event_registered = true; WiFi.begin(_prefs->wifi_ssid, _prefs->wifi_password); @@ -1370,22 +1408,21 @@ void MQTTBridge::processPacketQueue() { break; // No more packets } - // Publish packet (use stored raw data if available) - publishPacket(queued.packet, queued.is_tx, - queued.has_raw_data ? queued.raw_data : nullptr, - queued.has_raw_data ? queued.raw_len : 0, - queued.has_raw_data ? queued.snr : 0.0f, - queued.has_raw_data ? queued.rssi : 0.0f); - - // Publish raw if enabled - if (_raw_enabled) { - publishRaw(queued.packet); + mesh::Packet snapshot; + if (queued.packet_len > 0 && snapshot.readFrom(queued.packet_data, queued.packet_len)) { + // Publish packet (use stored raw data if available) + publishPacket(&snapshot, queued.is_tx, + queued.has_raw_data ? queued.raw_data : nullptr, + queued.has_raw_data ? queued.raw_len : 0, + queued.has_raw_data ? queued.snr : 0.0f, + queued.has_raw_data ? queued.rssi : 0.0f); + + // Publish raw if enabled + if (_raw_enabled) { + publishRaw(&snapshot); + } } - // NOTE: Do NOT free the packet here - the Dispatcher owns and frees it after logRx() returns. - // The MQTT bridge only stores a pointer to read from; it does not own the packet. - queued.packet = nullptr; - _queue_count--; processed++; @@ -1426,19 +1463,19 @@ void MQTTBridge::processPacketQueue() { QueuedPacket& queued = _packet_queue[_queue_head]; - publishPacket(queued.packet, queued.is_tx, - queued.has_raw_data ? queued.raw_data : nullptr, - queued.has_raw_data ? queued.raw_len : 0, - queued.has_raw_data ? queued.snr : 0.0f, - queued.has_raw_data ? queued.rssi : 0.0f); - - if (_raw_enabled) { - publishRaw(queued.packet); + mesh::Packet snapshot; + if (queued.packet_len > 0 && snapshot.readFrom(queued.packet_data, queued.packet_len)) { + publishPacket(&snapshot, queued.is_tx, + queued.has_raw_data ? queued.raw_data : nullptr, + queued.has_raw_data ? queued.raw_len : 0, + queued.has_raw_data ? queued.snr : 0.0f, + queued.has_raw_data ? queued.rssi : 0.0f); + + if (_raw_enabled) { + publishRaw(&snapshot); + } } - // NOTE: Do NOT free the packet here - the Dispatcher owns and frees it after logRx() returns. - queued.packet = nullptr; - dequeuePacket(); processed++; } @@ -1694,21 +1731,16 @@ void MQTTBridge::publishPacket(mesh::Packet* packet, bool is_tx, } #endif - // JSON buffer: prefer PSRAM to reduce stack (plan §4); fallback to stack if allocation fails + // JSON buffer: use PSRAM to avoid large stack allocations on constrained targets. + // If allocation fails, skip publish to preserve system stability. static const size_t PUBLISH_JSON_BUFFER_SIZE = 2048; char* json_buffer_psram = (char*)psram_malloc(PUBLISH_JSON_BUFFER_SIZE); - char json_buffer_stack[1024]; - char json_buffer_large_stack[2048]; - int packet_size = packet->getRawLength(); - char* active_buffer; - size_t active_buffer_size; - if (json_buffer_psram != nullptr) { - active_buffer = json_buffer_psram; - active_buffer_size = PUBLISH_JSON_BUFFER_SIZE; - } else { - active_buffer = (packet_size > 200) ? json_buffer_large_stack : json_buffer_stack; - active_buffer_size = (packet_size > 200) ? 2048 : 1024; + if (json_buffer_psram == nullptr) { + _skipped_publishes++; + return; } + char* active_buffer = json_buffer_psram; + size_t active_buffer_size = PUBLISH_JSON_BUFFER_SIZE; char origin_id[65]; // Use actual device ID @@ -1837,20 +1869,15 @@ void MQTTBridge::publishRaw(mesh::Packet* packet) { return; } - // JSON buffer: prefer PSRAM (plan §4); fallback to stack if allocation fails + // JSON buffer: use PSRAM to avoid large stack allocations on constrained targets. + // If allocation fails, skip publish to preserve system stability. char* json_buffer_psram = (char*)psram_malloc(2048); - char json_buffer_stack[1024]; - char json_buffer_large_stack[2048]; - int packet_size = packet->getRawLength(); - char* active_buffer; - size_t active_buffer_size; - if (json_buffer_psram != nullptr) { - active_buffer = json_buffer_psram; - active_buffer_size = 2048; - } else { - active_buffer = (packet_size > 200) ? json_buffer_large_stack : json_buffer_stack; - active_buffer_size = (packet_size > 200) ? 2048 : 1024; + if (json_buffer_psram == nullptr) { + _skipped_publishes++; + return; } + char* active_buffer = json_buffer_psram; + size_t active_buffer_size = 2048; char origin_id[65]; // Use actual device ID @@ -1933,6 +1960,9 @@ void MQTTBridge::publishRaw(mesh::Packet* packet) { } void MQTTBridge::queuePacket(mesh::Packet* packet, bool is_tx) { + if (!packet) { + return; + } #ifdef ESP_PLATFORM // Use FreeRTOS queue for thread-safe operation if (_packet_queue_handle == nullptr) { @@ -1942,8 +1972,10 @@ void MQTTBridge::queuePacket(mesh::Packet* packet, bool is_tx) { QueuedPacket queued; memset(&queued, 0, sizeof(QueuedPacket)); - queued.packet = packet; - queued.timestamp = millis(); + queued.packet_len = packet->writeTo(queued.packet_data); + if (queued.packet_len == 0 || queued.packet_len > sizeof(queued.packet_data)) { + return; + } queued.is_tx = is_tx; queued.has_raw_data = false; @@ -1971,9 +2003,7 @@ void MQTTBridge::queuePacket(mesh::Packet* packet, bool is_tx) { // Queue full - try to remove oldest packet QueuedPacket oldest; if (xQueueReceive(_packet_queue_handle, &oldest, 0) == pdTRUE) { - // NOTE: Do NOT free oldest.packet - the Dispatcher owns and frees it. - // We just drop our reference to it. - MQTT_DEBUG_PRINTLN("Queue full, dropping oldest packet reference"); + MQTT_DEBUG_PRINTLN("Queue full, dropping oldest packet snapshot"); // Now try to send again if (xQueueSend(_packet_queue_handle, &queued, 0) != pdTRUE) { MQTT_DEBUG_PRINTLN("Failed to queue packet after dropping oldest"); @@ -1992,18 +2022,17 @@ void MQTTBridge::queuePacket(mesh::Packet* packet, bool is_tx) { // Non-ESP32: Use circular buffer if (_queue_count >= MAX_QUEUE_SIZE) { QueuedPacket& oldest = _packet_queue[_queue_head]; - // NOTE: Do NOT free oldest.packet - the Dispatcher owns and frees it. - // We just drop our reference to it. - MQTT_DEBUG_PRINTLN("Queue full, dropping oldest packet reference (queue size: %d)", _queue_count); - oldest.packet = nullptr; + MQTT_DEBUG_PRINTLN("Queue full, dropping oldest packet snapshot (queue size: %d)", _queue_count); dequeuePacket(); } QueuedPacket& queued = _packet_queue[_queue_tail]; memset(&queued, 0, sizeof(QueuedPacket)); - queued.packet = packet; - queued.timestamp = millis(); + queued.packet_len = packet->writeTo(queued.packet_data); + if (queued.packet_len == 0 || queued.packet_len > sizeof(queued.packet_data)) { + return; + } queued.is_tx = is_tx; queued.has_raw_data = false; @@ -2067,10 +2096,16 @@ void MQTTBridge::setBroker(int broker_index, const char* host, uint16_t port, if (broker_index < 0 || broker_index >= MAX_MQTT_BROKERS_COUNT) return; MQTTBroker& broker = _brokers[broker_index]; + if (host == nullptr) host = ""; + if (username == nullptr) username = ""; + if (password == nullptr) password = ""; strncpy(broker.host, host, sizeof(broker.host) - 1); + broker.host[sizeof(broker.host) - 1] = '\0'; broker.port = port; strncpy(broker.username, username, sizeof(broker.username) - 1); + broker.username[sizeof(broker.username) - 1] = '\0'; strncpy(broker.password, password, sizeof(broker.password) - 1); + broker.password[sizeof(broker.password) - 1] = '\0'; broker.enabled = enabled; broker.connected = false; broker.reconnect_interval = 5000; @@ -3173,4 +3208,3 @@ void MQTTBridge::logMemoryStatus() { } #endif - diff --git a/src/helpers/bridges/MQTTBridge.h b/src/helpers/bridges/MQTTBridge.h index 0afc2217b3..d7cf58f12e 100644 --- a/src/helpers/bridges/MQTTBridge.h +++ b/src/helpers/bridges/MQTTBridge.h @@ -92,12 +92,12 @@ class MQTTBridge : public BridgeBase { // Packet queue for offline scenarios struct QueuedPacket { - mesh::Packet* packet; - unsigned long timestamp; + uint8_t packet_data[MAX_TRANS_UNIT + 1]; + uint8_t packet_len; bool is_tx; // Store raw radio data with each packet to avoid it being overwritten uint8_t raw_data[256]; - int raw_len; + uint16_t raw_len; float snr; float rssi; bool has_raw_data; @@ -110,6 +110,8 @@ class MQTTBridge : public BridgeBase { QueueHandle_t _packet_queue_handle; TaskHandle_t _mqtt_task_handle; SemaphoreHandle_t _raw_data_mutex; // Mutex for raw radio data + wifi_event_id_t _wifi_event_id; + bool _wifi_event_registered; // PSRAM-backed task stack (plan §3); TCB kept in internal RAM StackType_t* _mqtt_task_stack; // nullptr if using dynamic task creation StaticTask_t _mqtt_task_tcb;