From e49b07ac8ca1e71936f44c0a16f46ca90568b8d8 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 12 Sep 2025 17:12:18 -0500 Subject: [PATCH 01/18] Merge pull request #7965 from compumike/compumike/fix-nrf52-bluetooth-memory-leak Fix memory leak in `NRF52Bluetooth`: allocate `BluetoothStatus` on stack, not heap --- src/platform/nrf52/NRF52Bluetooth.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 6f0e7250f..f8366ae32 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -59,7 +59,8 @@ void onConnect(uint16_t conn_handle) LOG_INFO("BLE Connected to %s", central_name); // Notify UI (or any other interested firmware components) - bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED)); + meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED); + bluetoothStatus->updateStatus(&newStatus); } /** * Callback invoked when a connection is dropped @@ -74,7 +75,8 @@ void onDisconnect(uint16_t conn_handle, uint8_t reason) } // Notify UI (or any other interested firmware components) - bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED)); + meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); + bluetoothStatus->updateStatus(&newStatus); } void onCccd(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value) { @@ -326,7 +328,8 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke textkey += (char)passkey[i]; // Notify UI (or other components) of pairing event and passkey - bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(textkey)); + meshtastic::BluetoothStatus newStatus(textkey); + bluetoothStatus->updateStatus(&newStatus); #if !defined(MESHTASTIC_EXCLUDE_SCREEN) // Todo: migrate this display code back into Screen class, and observe bluetoothStatus if (screen) { @@ -398,12 +401,13 @@ void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, uint8_t auth_statu { if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) { LOG_INFO("BLE pair success"); - bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED)); + meshtastic::BluetoothStatus newConnectedStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED); + bluetoothStatus->updateStatus(&newConnectedStatus); } else { LOG_INFO("BLE pair failed"); // Notify UI (or any other interested firmware components) - bluetoothStatus->updateStatus( - new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED)); + meshtastic::BluetoothStatus newDisconnectedStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); + bluetoothStatus->updateStatus(&newDisconnectedStatus); } // Todo: migrate this display code back into Screen class, and observe bluetoothStatus From d00b2afe1d5a7ffb9fc571fc60efefcd872bc349 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 12 Sep 2025 17:12:27 -0500 Subject: [PATCH 02/18] Merge pull request #7964 from compumike/compumike/fix-nimble-bluetooth-memory-leak Fix memory leak in `NimbleBluetooth`: allocate `BluetoothStatus` on stack, not heap --- src/nimble/NimbleBluetooth.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 95e191c8e..ee95168c3 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -133,7 +133,8 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks LOG_INFO("*** Enter passkey %d on the peer side ***", passkey); powerFSM.trigger(EVENT_BLUETOOTH_PAIR); - bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(std::to_string(passkey))); + meshtastic::BluetoothStatus newStatus(std::to_string(passkey)); + bluetoothStatus->updateStatus(&newStatus); #if HAS_SCREEN // Todo: migrate this display code back into Screen class, and observe bluetoothStatus if (screen) { @@ -173,7 +174,8 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks { LOG_INFO("BLE authentication complete"); - bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED)); + meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED); + bluetoothStatus->updateStatus(&newStatus); // Todo: migrate this display code back into Screen class, and observe bluetoothStatus if (passkeyShowing) { @@ -187,8 +189,8 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks { LOG_INFO("BLE disconnect"); - bluetoothStatus->updateStatus( - new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED)); + meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); + bluetoothStatus->updateStatus(&newStatus); if (bluetoothPhoneAPI) { std::lock_guard guard(bluetoothPhoneAPI->nimble_mutex); From b6dd99917d365872a94138c74d04658180265afd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 13 Sep 2025 06:37:58 -0500 Subject: [PATCH 03/18] Update protobufs (#7973) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/device_ui.pb.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index a84657c22..8caf42396 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit a84657c220421536f18d11fc5edf680efadbceeb +Subproject commit 8caf42396438f0d8a0305143485fd671c1fc7126 diff --git a/src/mesh/generated/meshtastic/device_ui.pb.h b/src/mesh/generated/meshtastic/device_ui.pb.h index 8313438f8..8f693e570 100644 --- a/src/mesh/generated/meshtastic/device_ui.pb.h +++ b/src/mesh/generated/meshtastic/device_ui.pb.h @@ -66,6 +66,8 @@ typedef enum _meshtastic_Language { meshtastic_Language_UKRAINIAN = 16, /* Bulgarian */ meshtastic_Language_BULGARIAN = 17, + /* Czech */ + meshtastic_Language_CZECH = 18, /* Simplified Chinese (experimental) */ meshtastic_Language_SIMPLIFIED_CHINESE = 30, /* Traditional Chinese (experimental) */ From 6d2093650ad16b4c3a8fe8c71d9cc96350710f3f Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 13 Sep 2025 13:50:02 +0200 Subject: [PATCH 04/18] T-Lora Pager: Support LR1121 and SX1280 models (#7956) * T-Lora Pager: Support LR1121 and SX1280 models * Remove ifdefs --- variants/esp32s3/tlora-pager/rfswitch.h | 18 ++++++++++++++++++ variants/esp32s3/tlora-pager/variant.h | 19 ++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 variants/esp32s3/tlora-pager/rfswitch.h diff --git a/variants/esp32s3/tlora-pager/rfswitch.h b/variants/esp32s3/tlora-pager/rfswitch.h new file mode 100644 index 000000000..1e5eb7a9e --- /dev/null +++ b/variants/esp32s3/tlora-pager/rfswitch.h @@ -0,0 +1,18 @@ +#include "RadioLib.h" + +static const uint32_t rfswitch_dio_pins[] = { + RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, + RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC +}; + +static const Module::RfSwitchMode_t rfswitch_table[] = { + // mode DIO5 DIO6 + { LR11x0::MODE_STBY, { LOW, LOW } }, + { LR11x0::MODE_RX, { LOW, HIGH } }, + { LR11x0::MODE_TX, { HIGH, LOW } }, + { LR11x0::MODE_TX_HP, { HIGH, LOW } }, + { LR11x0::MODE_TX_HF, { LOW, LOW } }, + { LR11x0::MODE_GNSS, { LOW, LOW } }, + { LR11x0::MODE_WIFI, { LOW, LOW } }, + END_OF_MODE_TABLE, +}; \ No newline at end of file diff --git a/variants/esp32s3/tlora-pager/variant.h b/variants/esp32s3/tlora-pager/variant.h index ee48088c8..2875f6804 100644 --- a/variants/esp32s3/tlora-pager/variant.h +++ b/variants/esp32s3/tlora-pager/variant.h @@ -105,14 +105,16 @@ // LoRa #define USE_SX1262 #define USE_SX1268 +#define USE_SX1280 +#define USE_LR1121 #define LORA_SCK 35 #define LORA_MISO 33 #define LORA_MOSI 34 #define LORA_CS 36 +#define LORA_RESET 47 #define LORA_DIO0 -1 // a No connect on the SX1262 module -#define LORA_RESET 47 #define LORA_DIO1 14 // SX1262 IRQ #define LORA_DIO2 48 // SX1262 BUSY #define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled @@ -123,3 +125,18 @@ #define SX126X_RESET LORA_RESET #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 3.0 + +#define SX128X_CS LORA_CS +#define SX128X_DIO1 LORA_DIO1 +#define SX128X_BUSY LORA_DIO2 +#define SX128X_RESET LORA_RESET + +#define LR1121_IRQ_PIN LORA_DIO1 +#define LR1121_NRESET_PIN LORA_RESET +#define LR1121_BUSY_PIN LORA_DIO2 +#define LR1121_SPI_NSS_PIN LORA_CS +#define LR1121_SPI_SCK_PIN LORA_SCK +#define LR1121_SPI_MOSI_PIN LORA_MOSI +#define LR1121_SPI_MISO_PIN LORA_MISO +#define LR11X0_DIO3_TCXO_VOLTAGE 3.0 +#define LR11X0_DIO_AS_RF_SWITCH From 51acd92a37e1dd760fdcc0af1007f584a0de590c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 13 Sep 2025 06:51:18 -0500 Subject: [PATCH 05/18] Trunk --- variants/esp32s3/tlora-pager/rfswitch.h | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/variants/esp32s3/tlora-pager/rfswitch.h b/variants/esp32s3/tlora-pager/rfswitch.h index 1e5eb7a9e..0fba5a305 100644 --- a/variants/esp32s3/tlora-pager/rfswitch.h +++ b/variants/esp32s3/tlora-pager/rfswitch.h @@ -1,18 +1,11 @@ #include "RadioLib.h" -static const uint32_t rfswitch_dio_pins[] = { - RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, - RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC -}; +static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; static const Module::RfSwitchMode_t rfswitch_table[] = { // mode DIO5 DIO6 - { LR11x0::MODE_STBY, { LOW, LOW } }, - { LR11x0::MODE_RX, { LOW, HIGH } }, - { LR11x0::MODE_TX, { HIGH, LOW } }, - { LR11x0::MODE_TX_HP, { HIGH, LOW } }, - { LR11x0::MODE_TX_HF, { LOW, LOW } }, - { LR11x0::MODE_GNSS, { LOW, LOW } }, - { LR11x0::MODE_WIFI, { LOW, LOW } }, - END_OF_MODE_TABLE, + {LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {LOW, HIGH}}, + {LR11x0::MODE_TX, {HIGH, LOW}}, {LR11x0::MODE_TX_HP, {HIGH, LOW}}, + {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}}, + {LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE, }; \ No newline at end of file From 70ac3601b04ef3f9243777fde545befbe254c08e Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 13 Sep 2025 06:57:12 -0500 Subject: [PATCH 06/18] Trunk From 9211b1bb4b0cf9fcfa6a889cbab49938743f7414 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 13 Sep 2025 07:01:07 -0500 Subject: [PATCH 07/18] Static memory pool allocation (#7966) * Static memory pool * Initializer * T-Lora Pager: Support LR1121 and SX1280 models (#7956) * T-Lora Pager: Support LR1121 and SX1280 models * Remove ifdefs --------- Co-authored-by: WillyJL --- src/mesh/MemoryPool.h | 9 ++++----- src/mesh/MeshService.cpp | 9 ++++++--- src/mesh/Router.cpp | 3 +-- variants/esp32s3/tlora-pager/rfswitch.h | 12 ++++++++---- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/mesh/MemoryPool.h b/src/mesh/MemoryPool.h index 0c5ba6c71..eb5ac5109 100644 --- a/src/mesh/MemoryPool.h +++ b/src/mesh/MemoryPool.h @@ -115,12 +115,11 @@ template class MemoryPool : public Allocator bool used[MaxSize]; public: - MemoryPool() + MemoryPool() : pool{}, used{} { - // Initialize the used array to false (all slots free) - for (int i = 0; i < MaxSize; i++) { - used[i] = false; - } + // Arrays are now zero-initialized by member initializer list + // pool array: all elements are default-constructed (zero for POD types) + // used array: all elements are false (zero-initialized) } /// Return a buffer for use by others diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 607766ab6..96782cda5 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -46,11 +46,14 @@ the new node can build its node db) MeshService *service; -static MemoryDynamic staticMqttClientProxyMessagePool; +#define MAX_MQTT_PROXY_MESSAGES 16 +static MemoryPool staticMqttClientProxyMessagePool; -static MemoryDynamic staticQueueStatusPool; +#define MAX_QUEUE_STATUS 4 +static MemoryPool staticQueueStatusPool; -static MemoryDynamic staticClientNotificationPool; +#define MAX_CLIENT_NOTIFICATIONS 4 +static MemoryPool staticClientNotificationPool; Allocator &mqttClientProxyMessagePool = staticMqttClientProxyMessagePool; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 44d09637f..b5ae1ec0c 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -31,8 +31,7 @@ (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \ 2) // max number of packets which can be in flight (either queued from reception or queued for sending) -// static MemoryPool staticPool(MAX_PACKETS); -static MemoryDynamic staticPool; +static MemoryPool staticPool; Allocator &packetPool = staticPool; diff --git a/variants/esp32s3/tlora-pager/rfswitch.h b/variants/esp32s3/tlora-pager/rfswitch.h index 0fba5a305..337346ec5 100644 --- a/variants/esp32s3/tlora-pager/rfswitch.h +++ b/variants/esp32s3/tlora-pager/rfswitch.h @@ -4,8 +4,12 @@ static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11 static const Module::RfSwitchMode_t rfswitch_table[] = { // mode DIO5 DIO6 - {LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {LOW, HIGH}}, - {LR11x0::MODE_TX, {HIGH, LOW}}, {LR11x0::MODE_TX_HP, {HIGH, LOW}}, - {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}}, - {LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE, + {LR11x0::MODE_STBY, {LOW, LOW}}, + {LR11x0::MODE_RX, {LOW, HIGH}}, + {LR11x0::MODE_TX, {HIGH, LOW}}, + {LR11x0::MODE_TX_HP, {HIGH, LOW}}, + {LR11x0::MODE_TX_HF, {LOW, LOW}}, + {LR11x0::MODE_GNSS, {LOW, LOW}}, + {LR11x0::MODE_WIFI, {LOW, LOW}}, + END_OF_MODE_TABLE, }; \ No newline at end of file From 78dfb05eeb475af7dfff1903f7d2923e5bba74ca Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 13 Sep 2025 11:59:50 -0500 Subject: [PATCH 08/18] Portduino dynamic alloc --- src/mesh/Router.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index b5ae1ec0c..c5eed5180 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -5,6 +5,7 @@ #include "MeshService.h" #include "NodeDB.h" #include "RTC.h" + #include "configuration.h" #include "detect/LoRaRadioType.h" #include "main.h" @@ -27,13 +28,24 @@ // I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX // And every TX packet might have a retransmission packet or an ack alive at any moment + +#ifdef ARCH_PORTDUINO +// Portduino (native) targets can use dynamic memory pools with runtime-configurable sizes #define MAX_PACKETS \ (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \ 2) // max number of packets which can be in flight (either queued from reception or queued for sending) -static MemoryPool staticPool; +static MemoryDynamic dynamicPool(MAX_PACKETS); +Allocator &packetPool = dynamicPool; +#else +// Embedded targets use static memory pools with compile-time constants +#define MAX_PACKETS_STATIC \ + (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \ + 2) // max number of packets which can be in flight (either queued from reception or queued for sending) +static MemoryPool staticPool; Allocator &packetPool = staticPool; +#endif static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__)); From 4ee07226e4574b35fb864f337ff3753fee40cae1 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 13 Sep 2025 11:59:58 -0500 Subject: [PATCH 09/18] Missed From ae814b54630c470d023b00b5b53146692208f394 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 13 Sep 2025 12:07:14 -0500 Subject: [PATCH 10/18] Drop the limit --- src/mesh/Router.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index c5eed5180..6c5d08a93 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -35,7 +35,7 @@ (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \ 2) // max number of packets which can be in flight (either queued from reception or queued for sending) -static MemoryDynamic dynamicPool(MAX_PACKETS); +static MemoryDynamic dynamicPool; Allocator &packetPool = dynamicPool; #else // Embedded targets use static memory pools with compile-time constants From 6165b4f7a9b7ad43936c338cd3d43002d0cbaae0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 13 Sep 2025 16:31:56 -0500 Subject: [PATCH 11/18] Update meshtastic-esp8266-oled-ssd1306 digest to 0cbc26b (#7977) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index e2e5e1a18..47b5f823d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -60,7 +60,7 @@ monitor_speed = 115200 monitor_filters = direct lib_deps = # renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master - https://github.com/meshtastic/esp8266-oled-ssd1306/archive/9573abb64dc9c94f3051348f2bf4fc5cedf03c22.zip + https://github.com/meshtastic/esp8266-oled-ssd1306/archive/0cbc26b1f8f61957af0475f486b362eafe7cc4e2.zip # renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip # renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master From 760471d62021b0a02f1481e9b1cdce267c430f19 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 13 Sep 2025 18:52:46 -0500 Subject: [PATCH 12/18] Fix json report crashes on esp32 (#7978) --- src/mesh/http/ContentHandler.cpp | 39 ++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 74953d8fc..fb66dae7c 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -292,11 +292,14 @@ JSONArray htmlListDir(const char *dirname, uint8_t levels) JSONObject thisFileMap; thisFileMap["size"] = new JSONValue((int)file.size()); #ifdef ARCH_ESP32 - thisFileMap["name"] = new JSONValue(String(file.path()).substring(1).c_str()); + String fileName = String(file.path()).substring(1); + thisFileMap["name"] = new JSONValue(fileName.c_str()); #else - thisFileMap["name"] = new JSONValue(String(file.name()).substring(1).c_str()); + String fileName = String(file.name()).substring(1); + thisFileMap["name"] = new JSONValue(fileName.c_str()); #endif - if (String(file.name()).substring(1).endsWith(".gz")) { + String tempName = String(file.name()).substring(1); + if (tempName.endsWith(".gz")) { #ifdef ARCH_ESP32 String modifiedFile = String(file.path()).substring(1); #else @@ -339,7 +342,8 @@ void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res) JSONValue *value = new JSONValue(jsonObjOuter); - res->print(value->Stringify().c_str()); + std::string jsonString = value->Stringify(); + res->print(jsonString.c_str()); delete value; @@ -367,7 +371,8 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res) JSONObject jsonObjOuter; jsonObjOuter["status"] = new JSONValue("ok"); JSONValue *value = new JSONValue(jsonObjOuter); - res->print(value->Stringify().c_str()); + std::string jsonString = value->Stringify(); + res->print(jsonString.c_str()); delete value; return; } else { @@ -376,7 +381,8 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res) JSONObject jsonObjOuter; jsonObjOuter["status"] = new JSONValue("Error"); JSONValue *value = new JSONValue(jsonObjOuter); - res->print(value->Stringify().c_str()); + std::string jsonString = value->Stringify(); + res->print(jsonString.c_str()); delete value; return; } @@ -622,10 +628,7 @@ void handleReport(HTTPRequest *req, HTTPResponse *res) tempArray.push_back(new JSONValue((int)logArray[i])); } JSONValue *result = new JSONValue(tempArray); - // Clean up original array to prevent memory leak - for (auto *val : tempArray) { - delete val; - } + // Note: Don't delete tempArray elements here - JSONValue now owns them return result; }; @@ -656,7 +659,9 @@ void handleReport(HTTPRequest *req, HTTPResponse *res) // data->wifi JSONObject jsonObjWifi; jsonObjWifi["rssi"] = new JSONValue(WiFi.RSSI()); - jsonObjWifi["ip"] = new JSONValue(WiFi.localIP().toString().c_str()); + String wifiIPString = WiFi.localIP().toString(); + std::string wifiIP = wifiIPString.c_str(); + jsonObjWifi["ip"] = new JSONValue(wifiIP.c_str()); // data->memory JSONObject jsonObjMemory; @@ -702,7 +707,8 @@ void handleReport(HTTPRequest *req, HTTPResponse *res) jsonObjOuter["status"] = new JSONValue("ok"); // serialize and write it to the stream JSONValue *value = new JSONValue(jsonObjOuter); - res->print(value->Stringify().c_str()); + std::string jsonString = value->Stringify(); + res->print(jsonString.c_str()); delete value; } @@ -773,7 +779,8 @@ void handleNodes(HTTPRequest *req, HTTPResponse *res) jsonObjOuter["status"] = new JSONValue("ok"); // serialize and write it to the stream JSONValue *value = new JSONValue(jsonObjOuter); - res->print(value->Stringify().c_str()); + std::string jsonString = value->Stringify(); + res->print(jsonString.c_str()); delete value; // Clean up the nodesArray to prevent memory leak @@ -926,7 +933,8 @@ void handleBlinkLED(HTTPRequest *req, HTTPResponse *res) JSONObject jsonObjOuter; jsonObjOuter["status"] = new JSONValue("ok"); JSONValue *value = new JSONValue(jsonObjOuter); - res->print(value->Stringify().c_str()); + std::string jsonString = value->Stringify(); + res->print(jsonString.c_str()); delete value; } @@ -968,7 +976,8 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res) // serialize and write it to the stream JSONValue *value = new JSONValue(jsonObjOuter); - res->print(value->Stringify().c_str()); + std::string jsonString = value->Stringify(); + res->print(jsonString.c_str()); delete value; // Clean up the networkObjs to prevent memory leak From 096afa07f8bda37629a9ba1eafc51cde5890c2ea Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 13 Sep 2025 18:57:00 -0500 Subject: [PATCH 13/18] Tweak maximums --- src/mesh/mesh-pb-constants.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/mesh-pb-constants.h b/src/mesh/mesh-pb-constants.h index 12aec98cd..868670f42 100644 --- a/src/mesh/mesh-pb-constants.h +++ b/src/mesh/mesh-pb-constants.h @@ -20,12 +20,12 @@ /// max number of QueueStatus packets which can be waiting for delivery to phone #ifndef MAX_RX_QUEUESTATUS_TOPHONE -#define MAX_RX_QUEUESTATUS_TOPHONE 4 +#define MAX_RX_QUEUESTATUS_TOPHONE 2 #endif /// max number of MqttClientProxyMessage packets which can be waiting for delivery to phone #ifndef MAX_RX_MQTTPROXY_TOPHONE -#define MAX_RX_MQTTPROXY_TOPHONE 32 +#define MAX_RX_MQTTPROXY_TOPHONE 16 #endif /// max number of ClientNotification packets which can be waiting for delivery to phone From 99770354995c9b463fbf570993b7e67289f65dc9 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 13 Sep 2025 20:14:10 -0500 Subject: [PATCH 14/18] Fix DRAM overflow on old esp32 targets --- src/mesh/mesh-pb-constants.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/mesh/mesh-pb-constants.h b/src/mesh/mesh-pb-constants.h index 868670f42..e4f65aa28 100644 --- a/src/mesh/mesh-pb-constants.h +++ b/src/mesh/mesh-pb-constants.h @@ -15,8 +15,12 @@ // FIXME - max_count is actually 32 but we save/load this as one long string of preencoded MeshPacket bytes - not a big array in // RAM #define MAX_RX_TOPHONE (member_size(DeviceState, receive_queue) / member_size(DeviceState, receive_queue[0])) #ifndef MAX_RX_TOPHONE +#if defined(ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3)) +#define MAX_RX_TOPHONE 8 +#else #define MAX_RX_TOPHONE 32 #endif +#endif /// max number of QueueStatus packets which can be waiting for delivery to phone #ifndef MAX_RX_QUEUESTATUS_TOPHONE @@ -25,7 +29,7 @@ /// max number of MqttClientProxyMessage packets which can be waiting for delivery to phone #ifndef MAX_RX_MQTTPROXY_TOPHONE -#define MAX_RX_MQTTPROXY_TOPHONE 16 +#define MAX_RX_MQTTPROXY_TOPHONE 8 #endif /// max number of ClientNotification packets which can be waiting for delivery to phone From d201f6a1ed07bf3b159cfdfdc29a230c7f0c10dc Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Mon, 8 Sep 2025 10:29:26 +1000 Subject: [PATCH 15/18] Guard bad time warning logs using GPS_DEBUG (#7897) In 2.7.7 / 2.7.8 we introduced some new checks for time accuracy. In combination, these result in a spamming of the logs when a bad time is found When the GPS is active, we're calling the GPS thread every 0.2secs. So this log could be printed 4,500 times in a no-lock scenario :) Reserve this experience for developers using GPS_DEBUG. Fixes https://github.com/meshtastic/firmware/issues/7896 --- src/gps/RTC.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index e208e2df9..39b633e47 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -130,11 +130,15 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd uint32_t printableEpoch = tv->tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms #ifdef BUILD_EPOCH if (tv->tv_sec < BUILD_EPOCH) { +#ifdef GPS_DEBUG LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); +#endif return RTCSetResultInvalidTime; } else if (tv->tv_sec > (BUILD_EPOCH + FORTY_YEARS)) { +#ifdef GPS_DEBUG LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH, BUILD_EPOCH + FORTY_YEARS); +#endif return RTCSetResultInvalidTime; } #endif @@ -252,11 +256,15 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t) uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms #ifdef BUILD_EPOCH if (tv.tv_sec < BUILD_EPOCH) { +#ifdef GPS_DEBUG LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); +#endif return RTCSetResultInvalidTime; } else if (tv.tv_sec > (BUILD_EPOCH + FORTY_YEARS)) { +#ifdef GPS_DEBUG LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH, BUILD_EPOCH + FORTY_YEARS); +#endif return RTCSetResultInvalidTime; } #endif From 2dc7760508360732be6605d708b5f046530f2be7 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 14 Sep 2025 06:31:17 -0500 Subject: [PATCH 16/18] Scale probe buffer size based on current baud rate (#7975) * Scale probe buffer size based on current baud rate * Throttle bad time validation logging and fix time comparison logic * Remove comment * Missed the other instances * Copy pasta --- src/gps/GPS.cpp | 19 +++++++++++++---- src/gps/GPS.h | 2 +- src/gps/RTC.cpp | 54 ++++++++++++++++++++++++++++++++----------------- src/gps/RTC.h | 2 +- 4 files changed, 53 insertions(+), 24 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index d4e9076d9..a663f46c4 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1205,7 +1205,7 @@ static const char *DETECTED_MESSAGE = "%s detected"; LOG_DEBUG(PROBE_MESSAGE, COMMAND, FAMILY_NAME); \ clearBuffer(); \ _serial_gps->write(COMMAND "\r\n"); \ - GnssModel_t detectedDriver = getProbeResponse(TIMEOUT, RESPONSE_MAP); \ + GnssModel_t detectedDriver = getProbeResponse(TIMEOUT, RESPONSE_MAP, serialSpeed); \ if (detectedDriver != GNSS_MODEL_UNKNOWN) { \ return detectedDriver; \ } \ @@ -1367,9 +1367,18 @@ GnssModel_t GPS::probe(int serialSpeed) return GNSS_MODEL_UNKNOWN; } -GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector &responseMap) +GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector &responseMap, int serialSpeed) { - char response[256] = {0}; // Fixed buffer instead of String + // Calculate buffer size based on baud rate - 256 bytes for 9600 baud as baseline + // Higher baud rates get proportionally larger buffers to handle more data + int bufferSize = (serialSpeed * 256) / 9600; + // Clamp buffer size between reasonable limits + if (bufferSize < 128) + bufferSize = 128; + if (bufferSize > 2048) + bufferSize = 2048; + + char *response = new char[bufferSize](); // Dynamically allocate based on baud rate uint16_t responseLen = 0; unsigned long start = millis(); while (millis() - start < timeout) { @@ -1377,7 +1386,7 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vectorread(); // Add char to buffer if there's space - if (responseLen < sizeof(response) - 1) { + if (responseLen < bufferSize - 1) { response[responseLen++] = c; response[responseLen] = '\0'; } @@ -1390,6 +1399,7 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector &responseMap); + GnssModel_t getProbeResponse(unsigned long timeout, const std::vector &responseMap, int serialSpeed); // Get GNSS model GnssModel_t probe(int serialSpeed); diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 39b633e47..3e410d236 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -9,6 +9,9 @@ static RTCQuality currentQuality = RTCQualityNone; uint32_t lastSetFromPhoneNtpOrGps = 0; +static uint32_t lastTimeValidationWarning = 0; +static const uint32_t TIME_VALIDATION_WARNING_INTERVAL_MS = 15000; // 15 seconds + RTCQuality getRTCQuality() { return currentQuality; @@ -48,7 +51,9 @@ RTCSetResult readFromRTC() #ifdef BUILD_EPOCH if (tv.tv_sec < BUILD_EPOCH) { - LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); + if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) { + LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); + } return RTCSetResultInvalidTime; } #endif @@ -87,7 +92,10 @@ RTCSetResult readFromRTC() #ifdef BUILD_EPOCH if (tv.tv_sec < BUILD_EPOCH) { - LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); + if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) { + LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); + lastTimeValidationWarning = millis(); + } return RTCSetResultInvalidTime; } #endif @@ -130,15 +138,20 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd uint32_t printableEpoch = tv->tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms #ifdef BUILD_EPOCH if (tv->tv_sec < BUILD_EPOCH) { -#ifdef GPS_DEBUG - LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); -#endif + if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) { + LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); + lastTimeValidationWarning = millis(); + } return RTCSetResultInvalidTime; - } else if (tv->tv_sec > (BUILD_EPOCH + FORTY_YEARS)) { -#ifdef GPS_DEBUG - LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH, - BUILD_EPOCH + FORTY_YEARS); -#endif + } else if (tv->tv_sec > (time_t)(BUILD_EPOCH + FORTY_YEARS)) { + if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) { + // Calculate max allowed time safely to avoid overflow in logging + uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS; + uint32_t maxAllowedPrintable = (maxAllowedTime > UINT32_MAX) ? UINT32_MAX : (uint32_t)maxAllowedTime; + LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, + (uint32_t)BUILD_EPOCH, maxAllowedPrintable); + lastTimeValidationWarning = millis(); + } return RTCSetResultInvalidTime; } #endif @@ -256,15 +269,20 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t) uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms #ifdef BUILD_EPOCH if (tv.tv_sec < BUILD_EPOCH) { -#ifdef GPS_DEBUG - LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); -#endif + if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) { + LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); + lastTimeValidationWarning = millis(); + } return RTCSetResultInvalidTime; - } else if (tv.tv_sec > (BUILD_EPOCH + FORTY_YEARS)) { -#ifdef GPS_DEBUG - LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH, - BUILD_EPOCH + FORTY_YEARS); -#endif + } else if (tv.tv_sec > (time_t)(BUILD_EPOCH + FORTY_YEARS)) { + if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) { + // Calculate max allowed time safely to avoid overflow in logging + uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS; + uint32_t maxAllowedPrintable = (maxAllowedTime > UINT32_MAX) ? UINT32_MAX : (uint32_t)maxAllowedTime; + LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, + (uint32_t)BUILD_EPOCH, maxAllowedPrintable); + lastTimeValidationWarning = millis(); + } return RTCSetResultInvalidTime; } #endif diff --git a/src/gps/RTC.h b/src/gps/RTC.h index 03350823c..1ecde79ae 100644 --- a/src/gps/RTC.h +++ b/src/gps/RTC.h @@ -56,5 +56,5 @@ time_t gm_mktime(struct tm *tm); #define SEC_PER_HOUR 3600 #define SEC_PER_MIN 60 #ifdef BUILD_EPOCH -#define FORTY_YEARS (40UL * 365 * SEC_PER_DAY) // probably time to update your firmware +#define FORTY_YEARS (40ULL * 365 * SEC_PER_DAY) // Use 64-bit arithmetic to prevent overflow #endif From bf4e2e8e866c2f522f2e8f24ad14bb76f356fd7f Mon Sep 17 00:00:00 2001 From: Mike Robbins Date: Sun, 14 Sep 2025 03:05:06 -0700 Subject: [PATCH 17/18] Fix GPS gm_mktime memory leak (#7981) --- src/gps/RTC.cpp | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 3e410d236..4a629d755 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -342,14 +342,40 @@ uint32_t getValidTime(RTCQuality minQuality, bool local) time_t gm_mktime(struct tm *tm) { #if !MESHTASTIC_EXCLUDE_TZ - setenv("TZ", "GMT0", 1); - time_t res = mktime(tm); - if (*config.device.tzdef) { - setenv("TZ", config.device.tzdef, 1); - } else { - setenv("TZ", "UTC0", 1); + time_t result = 0; + + // First, get us to the start of tm->year, by calcuating the number of days since the Unix epoch. + int year = 1900 + tm->tm_year; // tm_year is years since 1900 + int year_minus_one = year - 1; + int days_before_this_year = 0; + days_before_this_year += year_minus_one * 365; + // leap days: every 4 years, except 100s, but including 400s. + days_before_this_year += year_minus_one / 4 - year_minus_one / 100 + year_minus_one / 400; + // subtract from 1970-01-01 to get days since epoch + days_before_this_year -= 719162; // (1969 * 365 + 1969 / 4 - 1969 / 100 + 1969 / 400); + + // Now, within this tm->year, compute the days *before* this tm->month starts. + int days_before_month[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // non-leap year + int days_this_year_before_this_month = days_before_month[tm->tm_mon]; // tm->tm_mon is 0..11 + + // If this is a leap year, and we're past February, add a day: + if (tm->tm_mon >= 2 && (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) { + days_this_year_before_this_month += 1; } - return res; + + // And within this month: + int days_this_month_before_today = tm->tm_mday - 1; // tm->tm_mday is 1..31 + + // Now combine them all together, and convert days to seconds: + result += (days_before_this_year + days_this_year_before_this_month + days_this_month_before_today); + result *= 86400L; + + // Finally, add in the hours, minutes, and seconds of today: + result += tm->tm_hour * 3600; + result += tm->tm_min * 60; + result += tm->tm_sec; + + return result; #else return mktime(tm); #endif From 70724bef72684c96f8a6d2972d80d37648eb8de4 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 14 Sep 2025 08:12:38 -0500 Subject: [PATCH 18/18] Fix overflow of time value (#7984) * Fix overflow of time value * Revert "Fix overflow of time value" This reverts commit 084796920179e80a7500d36c25fd4d82b3ef4214. * That got boogered up --- src/gps/RTC.cpp | 4 ++-- src/gps/RTC.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 4a629d755..da20e28eb 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -143,7 +143,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd lastTimeValidationWarning = millis(); } return RTCSetResultInvalidTime; - } else if (tv->tv_sec > (time_t)(BUILD_EPOCH + FORTY_YEARS)) { + } else if ((uint64_t)tv->tv_sec > ((uint64_t)BUILD_EPOCH + FORTY_YEARS)) { if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) { // Calculate max allowed time safely to avoid overflow in logging uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS; @@ -274,7 +274,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t) lastTimeValidationWarning = millis(); } return RTCSetResultInvalidTime; - } else if (tv.tv_sec > (time_t)(BUILD_EPOCH + FORTY_YEARS)) { + } else if ((uint64_t)tv.tv_sec > ((uint64_t)BUILD_EPOCH + FORTY_YEARS)) { if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) { // Calculate max allowed time safely to avoid overflow in logging uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS; diff --git a/src/gps/RTC.h b/src/gps/RTC.h index 1ecde79ae..eca17bf35 100644 --- a/src/gps/RTC.h +++ b/src/gps/RTC.h @@ -56,5 +56,5 @@ time_t gm_mktime(struct tm *tm); #define SEC_PER_HOUR 3600 #define SEC_PER_MIN 60 #ifdef BUILD_EPOCH -#define FORTY_YEARS (40ULL * 365 * SEC_PER_DAY) // Use 64-bit arithmetic to prevent overflow +static constexpr uint64_t FORTY_YEARS = (40ULL * 365 * SEC_PER_DAY); // Use 64-bit arithmetic to prevent overflow #endif