From cc8701436e9b7750e97b07d4915ce8af66830447 Mon Sep 17 00:00:00 2001 From: DeFiDude <59237470+DeFiDude@users.noreply.github.com> Date: Tue, 24 Mar 2026 16:22:47 -0600 Subject: [PATCH] =?UTF-8?q?Remove=20local=20LXMFMessage=20=E2=80=94=20now?= =?UTF-8?q?=20provided=20by=20microReticulum=20library?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LXMFMessage.h/cpp is now part of the microReticulum library (commit 6f3c08d). This eliminates the duplicate LXMF codec and ensures all platforms (Ratdeck, Ratcom, native tests) use the same shared implementation. The local header is replaced with a forward to so existing #include "LXMFMessage.h" references still resolve. microReticulum library includes 32 tests validating the LXMF codec: - 10 pack/unpack/sign tests (test_lxmf_codec) - 10 cross-platform fixture tests (test_lxmf_interop) - 6 announce interop tests (test_announce_interop) - 6 protocol edge case tests (test_protocol_edge_cases) --- src/reticulum/LXMFMessage.cpp | 184 ---------------------------------- src/reticulum/LXMFMessage.h | 35 +------ 2 files changed, 3 insertions(+), 216 deletions(-) delete mode 100644 src/reticulum/LXMFMessage.cpp diff --git a/src/reticulum/LXMFMessage.cpp b/src/reticulum/LXMFMessage.cpp deleted file mode 100644 index 4460e37..0000000 --- a/src/reticulum/LXMFMessage.cpp +++ /dev/null @@ -1,184 +0,0 @@ -// Direct port from Ratputer — LXMF message format (MsgPack wire, JSON storage) -#include "LXMFMessage.h" -#include - -static void mpPackFloat64(std::vector& buf, double val) { - buf.push_back(0xCB); - uint64_t bits; - memcpy(&bits, &val, 8); - for (int i = 7; i >= 0; i--) { - buf.push_back((bits >> (i * 8)) & 0xFF); - } -} - -static void mpPackString(std::vector& buf, const std::string& str) { - size_t len = str.size(); - if (len < 32) { - buf.push_back(0xA0 | (uint8_t)len); - } else if (len < 256) { - buf.push_back(0xD9); - buf.push_back((uint8_t)len); - } else { - buf.push_back(0xDA); - buf.push_back((len >> 8) & 0xFF); - buf.push_back(len & 0xFF); - } - buf.insert(buf.end(), str.begin(), str.end()); -} - -// MsgPack bin format — Python LXMF expects title/content as bytes, not str -static void mpPackBin(std::vector& buf, const std::string& str) { - size_t len = str.size(); - if (len < 256) { - buf.push_back(0xC4); - buf.push_back((uint8_t)len); - } else { - buf.push_back(0xC5); - buf.push_back((len >> 8) & 0xFF); - buf.push_back(len & 0xFF); - } - buf.insert(buf.end(), str.begin(), str.end()); -} - -static bool mpReadFloat64(const uint8_t* data, size_t len, size_t& pos, double& val) { - if (pos >= len || data[pos] != 0xCB) return false; - pos++; - if (pos + 8 > len) return false; - uint64_t bits = 0; - for (int i = 0; i < 8; i++) { bits = (bits << 8) | data[pos++]; } - memcpy(&val, &bits, 8); - return true; -} - -static bool mpReadString(const uint8_t* data, size_t len, size_t& pos, std::string& str) { - if (pos >= len) return false; - uint8_t b = data[pos]; - size_t slen = 0; - // MsgPack str formats - if ((b & 0xE0) == 0xA0) { slen = b & 0x1F; pos++; } - else if (b == 0xD9) { pos++; if (pos >= len) return false; slen = data[pos++]; } - else if (b == 0xDA) { pos++; if (pos + 2 > len) return false; slen = ((size_t)data[pos] << 8) | data[pos + 1]; pos += 2; } - // MsgPack bin formats (Python LXMF sends title/content as bin) - else if (b == 0xC4) { pos++; if (pos >= len) return false; slen = data[pos++]; } - else if (b == 0xC5) { pos++; if (pos + 2 > len) return false; slen = ((size_t)data[pos] << 8) | data[pos + 1]; pos += 2; } - else return false; - if (pos + slen > len) return false; - str.assign((const char*)&data[pos], slen); - pos += slen; - return true; -} - -static bool mpSkipValue(const uint8_t* data, size_t len, size_t& pos) { - if (pos >= len) return false; - uint8_t b = data[pos]; - if ((b & 0xE0) == 0xA0) { pos += 1 + (b & 0x1F); return pos <= len; } - if ((b & 0xF0) == 0x80) { size_t c = b & 0x0F; pos++; for (size_t i = 0; i < c * 2; i++) { if (!mpSkipValue(data, len, pos)) return false; } return true; } - if ((b & 0xF0) == 0x90) { size_t c = b & 0x0F; pos++; for (size_t i = 0; i < c; i++) { if (!mpSkipValue(data, len, pos)) return false; } return true; } - if (b == 0xCB) { pos += 9; return pos <= len; } - if (b == 0xD9) { if (pos + 2 > len) return false; size_t s = data[pos + 1]; pos += 2 + s; return pos <= len; } - if (b == 0xDA) { if (pos + 3 > len) return false; size_t s = ((size_t)data[pos+1] << 8) | data[pos+2]; pos += 3 + s; return pos <= len; } - if ((b & 0x80) == 0x00 || (b & 0xE0) == 0xE0 || b == 0xC0 || b == 0xC2 || b == 0xC3) { pos++; return true; } - if (b == 0xCC || b == 0xD0) { pos += 2; return pos <= len; } - if (b == 0xCD || b == 0xD1) { pos += 3; return pos <= len; } - if (b == 0xCE || b == 0xD2 || b == 0xCA) { pos += 5; return pos <= len; } - if (b == 0xCF || b == 0xD3) { pos += 9; return pos <= len; } - if (b == 0xC4) { if (pos + 2 > len) return false; pos += 2 + data[pos+1]; return pos <= len; } - if (b == 0xC5) { if (pos + 3 > len) return false; pos += 3 + (((size_t)data[pos+1] << 8) | data[pos+2]); return pos <= len; } - return false; -} - -std::vector LXMFMessage::packContent(double timestamp, const std::string& content, const std::string& title) { - std::vector buf; - buf.reserve(32 + content.size() + title.size()); - buf.push_back(0x94); - mpPackFloat64(buf, timestamp); - mpPackBin(buf, title); // LXMF spec: [ts, title, content, fields] — must be bin, not str - mpPackBin(buf, content); - buf.push_back(0x80); - return buf; -} - -std::vector LXMFMessage::packFull(const RNS::Identity& signingIdentity) { - std::vector packed = packContent(timestamp, content, title); - if (sourceHash.size() < 16 || destHash.size() < 16) return {}; - - // Sign: dest_hash || source_hash || packed_content || message_hash (LXMF spec) - std::vector hashed_part; - hashed_part.reserve(32 + packed.size()); - hashed_part.insert(hashed_part.end(), destHash.data(), destHash.data() + 16); - hashed_part.insert(hashed_part.end(), sourceHash.data(), sourceHash.data() + 16); - hashed_part.insert(hashed_part.end(), packed.begin(), packed.end()); - - RNS::Bytes hashedBytes(hashed_part.data(), hashed_part.size()); - RNS::Bytes msgHash = RNS::Identity::full_hash(hashedBytes); - - // Store messageId = SHA256(dest + src + payload), matching Python LXMessage.pack() - messageId = msgHash; - - std::vector signed_part; - signed_part.reserve(hashed_part.size() + msgHash.size()); - signed_part.insert(signed_part.end(), hashed_part.begin(), hashed_part.end()); - signed_part.insert(signed_part.end(), msgHash.data(), msgHash.data() + msgHash.size()); - - RNS::Bytes signableBytes(signed_part.data(), signed_part.size()); - RNS::Bytes sig = signingIdentity.sign(signableBytes); - if (sig.size() < 64) return {}; - - // Wire (opportunistic): [src_hash:16][signature:64][packed_content] - // dest_hash is carried by the RNS packet header, not the LXMF payload - std::vector payload; - payload.reserve(16 + 64 + packed.size()); - payload.insert(payload.end(), sourceHash.data(), sourceHash.data() + 16); - payload.insert(payload.end(), sig.data(), sig.data() + 64); - payload.insert(payload.end(), packed.begin(), packed.end()); - return payload; -} - -bool LXMFMessage::unpackFull(const uint8_t* data, size_t len, LXMFMessage& msg) { - // Wire: [dest_hash:16][src_hash:16][signature:64][packed_content] - if (len < 97) return false; // 16+16+64+1 minimum - msg.destHash = RNS::Bytes(data, 16); - msg.sourceHash = RNS::Bytes(data + 16, 16); - msg.signature = RNS::Bytes(data + 32, 64); - - const uint8_t* content = data + 96; - size_t contentLen = len - 96; - size_t pos = 0; - - if (pos >= contentLen) return false; - uint8_t arrHeader = content[pos]; - if ((arrHeader & 0xF0) != 0x90) return false; - size_t arrLen = arrHeader & 0x0F; - if (arrLen < 3) return false; - pos++; - - // LXMF spec: [timestamp, title, content, fields] - if (!mpReadFloat64(content, contentLen, pos, msg.timestamp)) return false; - if (!mpReadString(content, contentLen, pos, msg.title)) return false; - if (!mpReadString(content, contentLen, pos, msg.content)) return false; - if (arrLen >= 4 && pos < contentLen) { mpSkipValue(content, contentLen, pos); } - - // messageId = SHA256(dest + src + packed_content), matching Python/Rust - // (skip signature at bytes 32..96) - std::vector hashInput; - hashInput.reserve(32 + (len - 96)); - hashInput.insert(hashInput.end(), data, data + 32); // dest_hash + src_hash - hashInput.insert(hashInput.end(), data + 96, data + len); // packed_content - RNS::Bytes hashable(hashInput.data(), hashInput.size()); - msg.messageId = RNS::Identity::full_hash(hashable); - msg.incoming = true; - msg.status = LXMFStatus::DELIVERED; - return true; -} - -const char* LXMFMessage::statusStr() const { - switch (status) { - case LXMFStatus::DRAFT: return "draft"; - case LXMFStatus::QUEUED: return "queued"; - case LXMFStatus::SENDING: return "sending"; - case LXMFStatus::SENT: return "sent"; - case LXMFStatus::DELIVERED: return "delivered"; - case LXMFStatus::FAILED: return "failed"; - default: return "?"; - } -} diff --git a/src/reticulum/LXMFMessage.h b/src/reticulum/LXMFMessage.h index 596738d..160d870 100644 --- a/src/reticulum/LXMFMessage.h +++ b/src/reticulum/LXMFMessage.h @@ -1,33 +1,4 @@ +// LXMFMessage is now provided by the microReticulum library. +// This forward header ensures existing #include "LXMFMessage.h" still resolves. #pragma once - -#include -#include -#include -#include -#include - -enum class LXMFStatus : uint8_t { - DRAFT = 0, QUEUED, SENDING, SENT, DELIVERED, FAILED -}; - -struct LXMFMessage { - RNS::Bytes sourceHash; - RNS::Bytes destHash; - double timestamp = 0; - std::string content; - std::string title; - RNS::Bytes signature; - - LXMFStatus status = LXMFStatus::DRAFT; - bool incoming = false; - bool read = false; - int retries = 0; - unsigned long lastRetryMs = 0; - uint32_t savedCounter = 0; - RNS::Bytes messageId; - - static std::vector packContent(double timestamp, const std::string& content, const std::string& title); - std::vector packFull(const RNS::Identity& signingIdentity); - static bool unpackFull(const uint8_t* data, size_t len, LXMFMessage& msg); - const char* statusStr() const; -}; +#include