diff --git a/src/MessageStore.cpp b/src/MessageStore.cpp index c74e16f93..57ae2fecc 100644 --- a/src/MessageStore.cpp +++ b/src/MessageStore.cpp @@ -7,17 +7,7 @@ #include "SafeFile.h" #include "gps/RTC.h" #include "graphics/draw/MessageRenderer.h" - -#include // std::min, std::find_if - -using graphics::MessageRenderer::setThreadMode; -using graphics::MessageRenderer::ThreadMode; - -// Calculate serialized size for a StoredMessage -static inline size_t getMessageSize(const StoredMessage &m) -{ - return 16 + std::min(static_cast(MAX_MESSAGE_SIZE), m.text.size()); -} +#include // memcpy // Helper: assign a timestamp (RTC if available, else boot-relative) static inline void assignTimestamp(StoredMessage &sm) @@ -78,7 +68,8 @@ const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &pa StoredMessage sm; assignTimestamp(sm); // set timestamp (RTC or boot-relative) sm.channelIndex = packet.channel; - sm.text = std::string(reinterpret_cast(packet.decoded.payload.bytes)); + strncpy(sm.text, reinterpret_cast(packet.decoded.payload.bytes), MAX_MESSAGE_SIZE - 1); + sm.text[MAX_MESSAGE_SIZE - 1] = '\0'; if (packet.from == 0) { // Outgoing (phone-originated) @@ -110,7 +101,8 @@ void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const st sm.sender = sender; sm.channelIndex = channelIndex; - sm.text = text; + strncpy(sm.text, text.c_str(), MAX_MESSAGE_SIZE - 1); + sm.text[MAX_MESSAGE_SIZE - 1] = '\0'; // Default manual adds to broadcast sm.dest = NODENUM_BROADCAST; @@ -123,52 +115,84 @@ void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const st } #if ENABLE_MESSAGE_PERSISTENCE -// Save RAM queue to flash (called on shutdown) + +// Use a compile-time constant so the array bound can be used in the struct +static constexpr size_t TEXT_LEN = MAX_MESSAGE_SIZE; + +// Compact, fixed-size on-flash representation +struct __attribute__((packed)) StoredMessageRecord { + uint32_t timestamp; + uint32_t sender; + uint8_t channelIndex; + uint32_t dest; + uint8_t isBootRelative; + uint8_t ackStatus; // static_cast(AckStatus) + char text[TEXT_LEN]; // null-terminated +}; + +// Serialize one StoredMessage to flash +static inline void writeMessageRecord(SafeFile &f, const StoredMessage &m) +{ + StoredMessageRecord rec = {}; + rec.timestamp = m.timestamp; + rec.sender = m.sender; + rec.channelIndex = m.channelIndex; + rec.dest = m.dest; + rec.isBootRelative = m.isBootRelative; + rec.ackStatus = static_cast(m.ackStatus); + + strncpy(rec.text, m.text, TEXT_LEN - 1); + rec.text[TEXT_LEN - 1] = '\0'; + f.write(reinterpret_cast(&rec), sizeof(rec)); +} + +// Deserialize one StoredMessage from flash; returns false on short read +static inline bool readMessageRecord(File &f, StoredMessage &m) +{ + StoredMessageRecord rec = {}; + if (f.readBytes(reinterpret_cast(&rec), sizeof(rec)) != sizeof(rec)) + return false; + + m.timestamp = rec.timestamp; + m.sender = rec.sender; + m.channelIndex = rec.channelIndex; + m.dest = rec.dest; + m.isBootRelative = rec.isBootRelative; + m.ackStatus = static_cast(rec.ackStatus); + strncpy(m.text, rec.text, MAX_MESSAGE_SIZE - 1); + m.text[MAX_MESSAGE_SIZE - 1] = '\0'; + m.type = (m.dest == NODENUM_BROADCAST) ? MessageType::BROADCAST : MessageType::DM_TO_US; + return true; +} + void MessageStore::saveToFlash() { #ifdef FSCom // Copy live RAM buffer into persistence queue messages = liveMessages; + // Ensure root exists spiLock->lock(); - FSCom.mkdir("/"); // ensure root exists + FSCom.mkdir("/"); spiLock->unlock(); SafeFile f(filename.c_str(), false); spiLock->lock(); - uint8_t count = messages.size(); + uint8_t count = static_cast(messages.size()); + if (count > MAX_MESSAGES_SAVED) + count = MAX_MESSAGES_SAVED; f.write(&count, 1); - for (uint8_t i = 0; i < count && i < MAX_MESSAGES_SAVED; i++) { - const StoredMessage &m = messages.at(i); - f.write((uint8_t *)&m.timestamp, sizeof(m.timestamp)); - f.write((uint8_t *)&m.sender, sizeof(m.sender)); - f.write((uint8_t *)&m.channelIndex, sizeof(m.channelIndex)); - f.write((uint8_t *)&m.dest, sizeof(m.dest)); - - // Write text payload (capped at MAX_MESSAGE_SIZE) - const size_t toWrite = std::min(static_cast(MAX_MESSAGE_SIZE), m.text.size()); - if (toWrite) - f.write((const uint8_t *)m.text.data(), toWrite); - f.write('\0'); - - uint8_t bootFlag = m.isBootRelative ? 1 : 0; - f.write(&bootFlag, 1); // persist boot-relative flag - - uint8_t statusByte = static_cast(m.ackStatus); - f.write(&statusByte, 1); // persist ackStatus + for (uint8_t i = 0; i < count; ++i) { + writeMessageRecord(f, messages[i]); } spiLock->unlock(); f.close(); - -#else - // Filesystem not available, skip persistence #endif } -// Load persisted messages into RAM (called at boot) void MessageStore::loadFromFlash() { messages.clear(); @@ -178,73 +202,30 @@ void MessageStore::loadFromFlash() if (!FSCom.exists(filename.c_str())) return; + auto f = FSCom.open(filename.c_str(), FILE_O_READ); if (!f) return; uint8_t count = 0; - f.readBytes((char *)&count, 1); + f.readBytes(reinterpret_cast(&count), 1); + if (count > MAX_MESSAGES_SAVED) + count = MAX_MESSAGES_SAVED; - for (uint8_t i = 0; i < count && i < MAX_MESSAGES_SAVED; i++) { + for (uint8_t i = 0; i < count; ++i) { StoredMessage m; - f.readBytes((char *)&m.timestamp, sizeof(m.timestamp)); - f.readBytes((char *)&m.sender, sizeof(m.sender)); - f.readBytes((char *)&m.channelIndex, sizeof(m.channelIndex)); - f.readBytes((char *)&m.dest, sizeof(m.dest)); - - m.text.clear(); - m.text.reserve(64); - char c; - size_t readCount = 0; - while (readCount < MAX_MESSAGE_SIZE) { - if (f.readBytes(&c, 1) <= 0) - break; - if (c == '\0') - break; - m.text.push_back(c); - ++readCount; - } - - // Try to read boot-relative flag - uint8_t bootFlag = 0; - if (f.available() > 0) { - if (f.readBytes((char *)&bootFlag, 1) == 1) { - m.isBootRelative = (bootFlag != 0); - } else { - m.isBootRelative = (m.timestamp < 60u * 60u * 24u * 7u); - } - } else { - m.isBootRelative = (m.timestamp < 60u * 60u * 24u * 7u); - } - - // Try to read ackStatus - if (f.available() > 0) { - uint8_t statusByte = 0; - if (f.readBytes((char *)&statusByte, 1) == 1) { - m.ackStatus = static_cast(statusByte); - } else { - m.ackStatus = AckStatus::NONE; - } - } else { - m.ackStatus = AckStatus::NONE; - } - - // Recompute type from dest - if (m.dest == NODENUM_BROADCAST) { - m.type = MessageType::BROADCAST; - } else { - m.type = MessageType::DM_TO_US; - } - + if (!readMessageRecord(f, m)) + break; messages.push_back(m); liveMessages.push_back(m); // restore into RAM buffer } - f.close(); + f.close(); #endif } + #else -// Persistence disabled (saves flash space) +// If persistence is disabled, these functions become no-ops void MessageStore::saveToFlash() {} void MessageStore::loadFromFlash() {} #endif @@ -275,8 +256,12 @@ template static void eraseIf(std::deque &deq } } } else { - // Erase the first matching message from the front - auto it = std::find_if(deque.begin(), deque.end(), pred); + // Manual forward search to avoid std::find_if + auto it = deque.begin(); + for (; it != deque.end(); ++it) { + if (pred(*it)) + break; + } if (it != deque.end()) deque.erase(it); } diff --git a/src/MessageStore.h b/src/MessageStore.h index 1ce01fa0d..630d9e580 100644 --- a/src/MessageStore.h +++ b/src/MessageStore.h @@ -44,10 +44,10 @@ enum class AckStatus : uint8_t { // A single stored message in RAM and/or flash struct StoredMessage { - uint32_t timestamp; // When message was created (secs since boot/RTC) - uint32_t sender; // NodeNum of sender - uint8_t channelIndex; // Channel index used - std::string text; // UTF-8 text payload (dynamic now, no fixed size) + uint32_t timestamp; // When message was created (secs since boot/RTC) + uint32_t sender; // NodeNum of sender + uint8_t channelIndex; // Channel index used + char text[MAX_MESSAGE_SIZE]; // fixed buffer for message text // Destination node. uint32_t dest; diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp index 6f2eab485..e982b32ca 100644 --- a/src/graphics/draw/MessageRenderer.cpp +++ b/src/graphics/draw/MessageRenderer.cpp @@ -529,7 +529,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 ackForLine.push_back(m.ackStatus); // Split message text into wrapped lines - std::vector wrapped = generateLines(display, "", m.text.c_str(), textWidth); + std::vector wrapped = generateLines(display, "", m.text, textWidth); for (auto &ln : wrapped) { allLines.push_back(ln); isMine.push_back(mine); diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index a5de8a50f..f88adb66c 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -1062,7 +1062,7 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha sm.sender = nodeDB->getNodeNum(); // us sm.channelIndex = channel; - sm.text = std::string(message).substr(0, MAX_MESSAGE_SIZE - 1); + strncpy(sm.text, message, MAX_MESSAGE_SIZE - 1); sm.text[MAX_MESSAGE_SIZE - 1] = '\0'; // Classify broadcast vs DM