Fix TCP messaging, LoRa status reversion, and message ordering (v1.5.8)

- TCPClientInterface overrides needs_transport_headers() so hops==1
  packets get HEADER_2 wrapping through the hub
- Track savedCounter in LXMFMessage and use direct filename lookup
  for status updates, fixing LoRa uptime-timestamp precision loss
  that caused SENT status to revert to QUEUED on reload
- Remove timestamp-based re-sort in loadConversation() which broke
  chronological order when mixing LoRa (uptime) and TCP (epoch)
  timestamps; counter-based filenames already provide correct order
This commit is contained in:
DeFiDude
2026-03-10 00:49:59 -06:00
parent 6f272596c8
commit fb9eb4a4c3
5 changed files with 50 additions and 13 deletions

View File

@@ -30,7 +30,9 @@ void LXMFManager::loop() {
// Persist updated status to disk so reloads don't revert to QUEUED
std::string peerHex = msg.destHash.toHex();
if (_store) {
if (_store && msg.savedCounter > 0) {
_store->updateMessageStatusByCounter(peerHex, msg.savedCounter, false, msg.status);
} else if (_store) {
_store->updateMessageStatus(peerHex, msg.timestamp, false, msg.status);
}
@@ -60,7 +62,8 @@ bool LXMFManager::sendMessage(const RNS::Bytes& destHash, const std::string& con
if ((int)_outQueue.size() >= RATDECK_MAX_OUTQUEUE) { _outQueue.pop_front(); }
_outQueue.push_back(msg);
// Immediately save with QUEUED status so it appears in getMessages() right away
if (_store) { _store->saveMessage(msg); }
// Save the queue copy so savedCounter propagates back to the queued message
if (_store) { _store->saveMessage(_outQueue.back()); }
return true;
}

View File

@@ -22,6 +22,7 @@ struct LXMFMessage {
bool incoming = false;
bool read = false;
int retries = 0;
uint32_t savedCounter = 0;
RNS::Bytes messageId;
static std::vector<uint8_t> packContent(double timestamp, const std::string& content, const std::string& title);

View File

@@ -246,7 +246,7 @@ void MessageStore::refreshConversations() {
}
}
bool MessageStore::saveMessage(const LXMFMessage& msg) {
bool MessageStore::saveMessage(LXMFMessage& msg) {
if (!_flash) return false;
std::string peerHex = msg.incoming ?
@@ -270,6 +270,7 @@ bool MessageStore::saveMessage(const LXMFMessage& msg) {
// Counter-based filename: unique, monotonic, sorts correctly
uint32_t counter = _nextReceiveCounter++;
msg.savedCounter = counter;
char filename[64];
snprintf(filename, sizeof(filename), "%013lu_%c.json",
(unsigned long)counter, msg.incoming ? 'i' : 'o');
@@ -393,15 +394,6 @@ std::vector<LXMFMessage> MessageStore::loadConversation(const std::string& peerH
}
}
// Sort chronologically; non-epoch timestamps (uptime-based) sort before epoch
std::sort(messages.begin(), messages.end(),
[](const LXMFMessage& a, const LXMFMessage& b) {
bool aEpoch = a.timestamp > 1700000000;
bool bEpoch = b.timestamp > 1700000000;
if (aEpoch != bEpoch) return !aEpoch; // non-epoch sorts before epoch
return a.timestamp < b.timestamp;
});
return messages;
}
@@ -598,6 +590,44 @@ bool MessageStore::updateMessageStatus(const std::string& peerHex, double timest
return updated;
}
bool MessageStore::updateMessageStatusByCounter(const std::string& peerHex, uint32_t counter, bool incoming, LXMFStatus newStatus) {
if (counter == 0) return false; // Not saved yet
char filename[64];
snprintf(filename, sizeof(filename), "%013lu_%c.json",
(unsigned long)counter, incoming ? 'i' : 'o');
auto readModifyWrite = [&](auto readFn, auto writeFn, const String& dir) -> bool {
String path = dir + "/" + filename;
String json = readFn(path.c_str());
if (json.length() == 0) return false;
JsonDocument doc;
if (deserializeJson(doc, json)) return false;
doc["status"] = (int)newStatus;
String updated;
serializeJson(doc, updated);
writeFn(path.c_str(), updated);
return true;
};
bool updated = false;
if (_sd && _sd->isReady()) {
String sdDir = sdConversationDir(peerHex);
updated = readModifyWrite(
[&](const char* p) { return _sd->readString(p); },
[&](const char* p, const String& d) { _sd->writeString(p, d); },
sdDir);
}
if (_flash) {
String dir = conversationDir(peerHex);
bool flashUpdated = readModifyWrite(
[this](const char* p) { return _flash->readString(p); },
[this](const char* p, const String& d) { _flash->writeString(p, d); },
dir);
updated = updated || flashUpdated;
}
return updated;
}
void MessageStore::buildSummaries() {
_summaries.clear();

View File

@@ -20,7 +20,7 @@ class MessageStore {
public:
bool begin(FlashStore* flash, SDStore* sd = nullptr);
bool saveMessage(const LXMFMessage& msg);
bool saveMessage(LXMFMessage& msg);
std::vector<LXMFMessage> loadConversation(const std::string& peerHex) const;
const std::vector<std::string>& conversations() const { return _conversations; }
void refreshConversations();
@@ -28,6 +28,7 @@ public:
bool deleteConversation(const std::string& peerHex);
void markConversationRead(const std::string& peerHex);
bool updateMessageStatus(const std::string& peerHex, double timestamp, bool incoming, LXMFStatus newStatus);
bool updateMessageStatusByCounter(const std::string& peerHex, uint32_t counter, bool incoming, LXMFStatus newStatus);
const ConversationSummary* getSummary(const std::string& peerHex) const;
int totalUnreadCount() const;

View File

@@ -13,6 +13,8 @@ public:
void stop() override;
void loop() override;
bool needs_transport_headers() const override { return true; }
virtual inline std::string toString() const override {
return "TCPClient[" + _name + "]";
}