mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-20 09:43:03 +00:00
Switch from dynamic std::string storage to fixed-size char[]
This commit is contained in:
@@ -7,17 +7,7 @@
|
|||||||
#include "SafeFile.h"
|
#include "SafeFile.h"
|
||||||
#include "gps/RTC.h"
|
#include "gps/RTC.h"
|
||||||
#include "graphics/draw/MessageRenderer.h"
|
#include "graphics/draw/MessageRenderer.h"
|
||||||
|
#include <cstring> // memcpy
|
||||||
#include <algorithm> // 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<size_t>(MAX_MESSAGE_SIZE), m.text.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: assign a timestamp (RTC if available, else boot-relative)
|
// Helper: assign a timestamp (RTC if available, else boot-relative)
|
||||||
static inline void assignTimestamp(StoredMessage &sm)
|
static inline void assignTimestamp(StoredMessage &sm)
|
||||||
@@ -78,7 +68,8 @@ const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &pa
|
|||||||
StoredMessage sm;
|
StoredMessage sm;
|
||||||
assignTimestamp(sm); // set timestamp (RTC or boot-relative)
|
assignTimestamp(sm); // set timestamp (RTC or boot-relative)
|
||||||
sm.channelIndex = packet.channel;
|
sm.channelIndex = packet.channel;
|
||||||
sm.text = std::string(reinterpret_cast<const char *>(packet.decoded.payload.bytes));
|
strncpy(sm.text, reinterpret_cast<const char *>(packet.decoded.payload.bytes), MAX_MESSAGE_SIZE - 1);
|
||||||
|
sm.text[MAX_MESSAGE_SIZE - 1] = '\0';
|
||||||
|
|
||||||
if (packet.from == 0) {
|
if (packet.from == 0) {
|
||||||
// Outgoing (phone-originated)
|
// Outgoing (phone-originated)
|
||||||
@@ -110,7 +101,8 @@ void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const st
|
|||||||
|
|
||||||
sm.sender = sender;
|
sm.sender = sender;
|
||||||
sm.channelIndex = channelIndex;
|
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
|
// Default manual adds to broadcast
|
||||||
sm.dest = NODENUM_BROADCAST;
|
sm.dest = NODENUM_BROADCAST;
|
||||||
@@ -123,52 +115,84 @@ void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const st
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if ENABLE_MESSAGE_PERSISTENCE
|
#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<uint8_t>(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<uint8_t>(m.ackStatus);
|
||||||
|
|
||||||
|
strncpy(rec.text, m.text, TEXT_LEN - 1);
|
||||||
|
rec.text[TEXT_LEN - 1] = '\0';
|
||||||
|
f.write(reinterpret_cast<const uint8_t *>(&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<char *>(&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<AckStatus>(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()
|
void MessageStore::saveToFlash()
|
||||||
{
|
{
|
||||||
#ifdef FSCom
|
#ifdef FSCom
|
||||||
// Copy live RAM buffer into persistence queue
|
// Copy live RAM buffer into persistence queue
|
||||||
messages = liveMessages;
|
messages = liveMessages;
|
||||||
|
|
||||||
|
// Ensure root exists
|
||||||
spiLock->lock();
|
spiLock->lock();
|
||||||
FSCom.mkdir("/"); // ensure root exists
|
FSCom.mkdir("/");
|
||||||
spiLock->unlock();
|
spiLock->unlock();
|
||||||
|
|
||||||
SafeFile f(filename.c_str(), false);
|
SafeFile f(filename.c_str(), false);
|
||||||
|
|
||||||
spiLock->lock();
|
spiLock->lock();
|
||||||
uint8_t count = messages.size();
|
uint8_t count = static_cast<uint8_t>(messages.size());
|
||||||
|
if (count > MAX_MESSAGES_SAVED)
|
||||||
|
count = MAX_MESSAGES_SAVED;
|
||||||
f.write(&count, 1);
|
f.write(&count, 1);
|
||||||
|
|
||||||
for (uint8_t i = 0; i < count && i < MAX_MESSAGES_SAVED; i++) {
|
for (uint8_t i = 0; i < count; ++i) {
|
||||||
const StoredMessage &m = messages.at(i);
|
writeMessageRecord(f, messages[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<size_t>(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<uint8_t>(m.ackStatus);
|
|
||||||
f.write(&statusByte, 1); // persist ackStatus
|
|
||||||
}
|
}
|
||||||
spiLock->unlock();
|
spiLock->unlock();
|
||||||
|
|
||||||
f.close();
|
f.close();
|
||||||
|
|
||||||
#else
|
|
||||||
// Filesystem not available, skip persistence
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load persisted messages into RAM (called at boot)
|
|
||||||
void MessageStore::loadFromFlash()
|
void MessageStore::loadFromFlash()
|
||||||
{
|
{
|
||||||
messages.clear();
|
messages.clear();
|
||||||
@@ -178,73 +202,30 @@ void MessageStore::loadFromFlash()
|
|||||||
|
|
||||||
if (!FSCom.exists(filename.c_str()))
|
if (!FSCom.exists(filename.c_str()))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto f = FSCom.open(filename.c_str(), FILE_O_READ);
|
auto f = FSCom.open(filename.c_str(), FILE_O_READ);
|
||||||
if (!f)
|
if (!f)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
uint8_t count = 0;
|
uint8_t count = 0;
|
||||||
f.readBytes((char *)&count, 1);
|
f.readBytes(reinterpret_cast<char *>(&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;
|
StoredMessage m;
|
||||||
f.readBytes((char *)&m.timestamp, sizeof(m.timestamp));
|
if (!readMessageRecord(f, m))
|
||||||
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;
|
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<AckStatus>(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
messages.push_back(m);
|
messages.push_back(m);
|
||||||
liveMessages.push_back(m); // restore into RAM buffer
|
liveMessages.push_back(m); // restore into RAM buffer
|
||||||
}
|
}
|
||||||
f.close();
|
|
||||||
|
|
||||||
|
f.close();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
// Persistence disabled (saves flash space)
|
// If persistence is disabled, these functions become no-ops
|
||||||
void MessageStore::saveToFlash() {}
|
void MessageStore::saveToFlash() {}
|
||||||
void MessageStore::loadFromFlash() {}
|
void MessageStore::loadFromFlash() {}
|
||||||
#endif
|
#endif
|
||||||
@@ -275,8 +256,12 @@ template <typename Predicate> static void eraseIf(std::deque<StoredMessage> &deq
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Erase the first matching message from the front
|
// Manual forward search to avoid std::find_if
|
||||||
auto it = std::find_if(deque.begin(), deque.end(), pred);
|
auto it = deque.begin();
|
||||||
|
for (; it != deque.end(); ++it) {
|
||||||
|
if (pred(*it))
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (it != deque.end())
|
if (it != deque.end())
|
||||||
deque.erase(it);
|
deque.erase(it);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ struct StoredMessage {
|
|||||||
uint32_t timestamp; // When message was created (secs since boot/RTC)
|
uint32_t timestamp; // When message was created (secs since boot/RTC)
|
||||||
uint32_t sender; // NodeNum of sender
|
uint32_t sender; // NodeNum of sender
|
||||||
uint8_t channelIndex; // Channel index used
|
uint8_t channelIndex; // Channel index used
|
||||||
std::string text; // UTF-8 text payload (dynamic now, no fixed size)
|
char text[MAX_MESSAGE_SIZE]; // fixed buffer for message text
|
||||||
|
|
||||||
// Destination node.
|
// Destination node.
|
||||||
uint32_t dest;
|
uint32_t dest;
|
||||||
|
|||||||
@@ -529,7 +529,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
ackForLine.push_back(m.ackStatus);
|
ackForLine.push_back(m.ackStatus);
|
||||||
|
|
||||||
// Split message text into wrapped lines
|
// Split message text into wrapped lines
|
||||||
std::vector<std::string> wrapped = generateLines(display, "", m.text.c_str(), textWidth);
|
std::vector<std::string> wrapped = generateLines(display, "", m.text, textWidth);
|
||||||
for (auto &ln : wrapped) {
|
for (auto &ln : wrapped) {
|
||||||
allLines.push_back(ln);
|
allLines.push_back(ln);
|
||||||
isMine.push_back(mine);
|
isMine.push_back(mine);
|
||||||
|
|||||||
@@ -1062,7 +1062,7 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha
|
|||||||
|
|
||||||
sm.sender = nodeDB->getNodeNum(); // us
|
sm.sender = nodeDB->getNodeNum(); // us
|
||||||
sm.channelIndex = channel;
|
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';
|
sm.text[MAX_MESSAGE_SIZE - 1] = '\0';
|
||||||
|
|
||||||
// Classify broadcast vs DM
|
// Classify broadcast vs DM
|
||||||
|
|||||||
Reference in New Issue
Block a user