mirror of
https://github.com/markqvist/RNode_Firmware.git
synced 2026-04-27 14:30:33 +00:00
Implements OPPORTUNISTIC LXMF delivery receive — single-packet encrypted messages without link establishment. BeaconCrypto.h: beacon_crypto_decrypt() — reverse of encrypt. ECDH + HKDF + HMAC verify + AES-256-CBC decrypt + PKCS7 unpad. LxmfBeacon.h: MsgpackReader for unpacking received messages. lxmf_parse_received() extracts source_hash, timestamp, title, content. lxmf_verify_signature() verifies Ed25519 against cached announce key. MessageLog.h: Extended to detect DATA packets addressed to our lxmf_source_hash, decrypt, parse, and store content. Announce entries now cache sender's ed25519 public key for verification. Gui.h: Messages screen shows LXMF content (green if verified, white if unverified) with sender name and time ago.
207 lines
8.3 KiB
C
207 lines
8.3 KiB
C
// MessageLog.h — Received packet log and RNS announce parser
|
|
// Stores recent LoRa packets for the Messages screen viewer
|
|
|
|
#ifndef MESSAGELOG_H
|
|
#define MESSAGELOG_H
|
|
|
|
#if BOARD_MODEL == BOARD_TWATCH_ULT
|
|
|
|
#include "mbedtls/sha256.h"
|
|
|
|
// RNS packet type bits (flags byte, bits 1-0)
|
|
#define RNS_TYPE_DATA 0x00
|
|
#define RNS_TYPE_ANNOUNCE 0x01
|
|
// RNS header type (flags byte, bit 6)
|
|
#define RNS_HEADER_1 0x00
|
|
#define RNS_HEADER_2 0x40
|
|
// IFAC flag (flags byte, bit 7)
|
|
#define RNS_IFAC_FLAG 0x80
|
|
|
|
// msg_entry_t struct defined in Gui.h (included earlier)
|
|
// MSG_LOG_SIZE and MSG_NAME_LEN also defined there
|
|
#define MSG_DEDUP_MS 60000 // ignore same sender within 60s
|
|
|
|
msg_entry_t msg_log[MSG_LOG_SIZE];
|
|
uint8_t msg_log_head = 0;
|
|
uint8_t msg_log_count = 0;
|
|
uint32_t msg_log_last_change = 0;
|
|
|
|
// Find existing entry for this sender (for de-dup / update)
|
|
static int msg_log_find_sender(const uint8_t *hash) {
|
|
for (int i = 0; i < msg_log_count; i++) {
|
|
int idx = (msg_log_head - 1 - i + MSG_LOG_SIZE) % MSG_LOG_SIZE;
|
|
if (memcmp(msg_log[idx].sender_hash, hash, 16) == 0) return idx;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// Add or update a message entry
|
|
static void msg_log_add(const uint8_t *sender_hash, const char *name,
|
|
int16_t rssi, int8_t snr, uint8_t pkt_type,
|
|
uint16_t pkt_len, bool is_announce) {
|
|
// De-dup: update existing entry for same sender
|
|
int existing = msg_log_find_sender(sender_hash);
|
|
if (existing >= 0) {
|
|
msg_entry_t &e = msg_log[existing];
|
|
if (millis() - e.timestamp < MSG_DEDUP_MS && !is_announce) return;
|
|
// Update existing
|
|
e.timestamp = millis();
|
|
e.rssi = rssi;
|
|
e.snr = snr;
|
|
e.pkt_type = pkt_type;
|
|
e.pkt_len = pkt_len;
|
|
if (is_announce && name[0]) {
|
|
strncpy(e.display_name, name, MSG_NAME_LEN - 1);
|
|
e.display_name[MSG_NAME_LEN - 1] = '\0';
|
|
e.is_announce = true;
|
|
}
|
|
msg_log_last_change = millis();
|
|
return;
|
|
}
|
|
|
|
// New entry
|
|
msg_entry_t &e = msg_log[msg_log_head];
|
|
e.timestamp = millis();
|
|
e.rssi = rssi;
|
|
e.snr = snr;
|
|
e.pkt_type = pkt_type;
|
|
e.pkt_len = pkt_len;
|
|
e.is_announce = is_announce;
|
|
memcpy(e.sender_hash, sender_hash, 16);
|
|
if (name && name[0]) {
|
|
strncpy(e.display_name, name, MSG_NAME_LEN - 1);
|
|
e.display_name[MSG_NAME_LEN - 1] = '\0';
|
|
} else {
|
|
// Short hex of sender hash
|
|
snprintf(e.display_name, MSG_NAME_LEN, "%02x%02x%02x..%02x%02x",
|
|
sender_hash[0], sender_hash[1], sender_hash[2],
|
|
sender_hash[14], sender_hash[15]);
|
|
}
|
|
msg_log_head = (msg_log_head + 1) % MSG_LOG_SIZE;
|
|
if (msg_log_count < MSG_LOG_SIZE) msg_log_count++;
|
|
msg_log_last_change = millis();
|
|
}
|
|
|
|
// Parse an RNS packet and log it
|
|
// Called from main loop after dequeue, before kiss_write_packet
|
|
// pkt points to raw LoRa payload (after split reassembly), len is total length
|
|
static void msg_log_parse_packet(const uint8_t *pkt, uint16_t len,
|
|
int16_t rssi, int8_t snr) {
|
|
if (len < 19) return; // too short for RNS header
|
|
|
|
uint8_t flags = pkt[0];
|
|
bool has_ifac = (flags & RNS_IFAC_FLAG) != 0;
|
|
uint8_t pkt_type = flags & 0x03;
|
|
|
|
// IFAC packets: the real header starts after unmasking, but we can
|
|
// still extract the dest_hash from bytes 2-17 (masked but deterministic position)
|
|
// For announces, the IFAC is stripped by the receiver's IFAC handler.
|
|
// For our purposes, we parse what we can.
|
|
|
|
int hdr_offset = 0; // where the header starts after IFAC
|
|
if (has_ifac) {
|
|
// IFAC-wrapped: bytes 0-1 are masked header, bytes 2-9 are IFAC signature,
|
|
// rest is masked payload. We can't parse without unmasking.
|
|
// But we still log the packet with the dest_hash from bytes 2-17
|
|
// (these are the IFAC sig bytes, not the actual dest_hash)
|
|
// For now, just log as generic packet with flags info
|
|
uint8_t generic_hash[16];
|
|
memcpy(generic_hash, pkt + 2, 16);
|
|
msg_log_add(generic_hash, NULL, rssi, snr, pkt_type, len, false);
|
|
return;
|
|
}
|
|
|
|
// Non-IFAC packet — parse RNS header directly
|
|
const uint8_t *dest_hash = pkt + 2; // bytes 2-17
|
|
|
|
if (pkt_type == RNS_TYPE_ANNOUNCE && len > 167) {
|
|
// ANNOUNCE: extract identity, display name, and cache public keys
|
|
const uint8_t *pub_keys = pkt + 19; // x25519(32) + ed25519(32)
|
|
uint8_t identity_hash[32];
|
|
mbedtls_sha256(pub_keys, 64, identity_hash, 0);
|
|
|
|
char name[MSG_NAME_LEN] = {0};
|
|
int app_data_offset = 19 + 64 + 10 + 10 + 64; // = 167
|
|
int app_data_len = len - app_data_offset;
|
|
if (app_data_len > 0 && app_data_len < MSG_NAME_LEN) {
|
|
memcpy(name, pkt + app_data_offset, app_data_len);
|
|
name[app_data_len] = '\0';
|
|
}
|
|
|
|
msg_log_add(identity_hash, name, rssi, snr, pkt_type, len, true);
|
|
|
|
// Cache sender's ed25519 public key for signature verification
|
|
int idx = msg_log_find_sender(identity_hash);
|
|
if (idx >= 0) {
|
|
memcpy(msg_log[idx].sender_ed25519_pub, pub_keys + 32, 32);
|
|
msg_log[idx].has_pubkey = true;
|
|
}
|
|
|
|
} else if (pkt_type == RNS_TYPE_DATA && len > 19 + 96) {
|
|
// DATA packet — check if addressed to us (LXMF delivery)
|
|
extern uint8_t lxmf_source_hash[16];
|
|
extern uint8_t lxmf_x25519_sk[32];
|
|
extern bool lxmf_identity_configured;
|
|
|
|
if (lxmf_identity_configured && memcmp(dest_hash, lxmf_source_hash, 16) == 0) {
|
|
// Addressed to our LXMF delivery destination — try to decrypt
|
|
const uint8_t *encrypted = pkt + 19; // after RNS header
|
|
size_t enc_len = len - 19;
|
|
|
|
// We need the sender's identity hash for HKDF salt.
|
|
// For OPPORTUNISTIC messages, we use our OWN identity hash as salt
|
|
// (the sender encrypted TO us, using our identity hash as salt)
|
|
extern uint8_t lxmf_identity_hash[16];
|
|
|
|
static uint8_t plaintext[512];
|
|
int pt_len = beacon_crypto_decrypt(encrypted, enc_len,
|
|
lxmf_x25519_sk,
|
|
lxmf_identity_hash,
|
|
plaintext, sizeof(plaintext));
|
|
|
|
if (pt_len > 0) {
|
|
// Decryption succeeded — parse LXMF message
|
|
LxmfParsed parsed;
|
|
if (lxmf_parse_received(plaintext, pt_len, &parsed)) {
|
|
// Look up sender's name and pubkey from announce cache
|
|
char *sender_name = NULL;
|
|
uint8_t *sender_pub = NULL;
|
|
int sender_idx = msg_log_find_sender(parsed.source_hash);
|
|
if (sender_idx >= 0) {
|
|
sender_name = msg_log[sender_idx].display_name;
|
|
if (msg_log[sender_idx].has_pubkey)
|
|
sender_pub = msg_log[sender_idx].sender_ed25519_pub;
|
|
}
|
|
|
|
// Add as LXMF message entry
|
|
msg_log_add(parsed.source_hash, sender_name, rssi, snr,
|
|
pkt_type, len, false);
|
|
|
|
// Store content in the new entry
|
|
int new_idx = msg_log_find_sender(parsed.source_hash);
|
|
if (new_idx >= 0) {
|
|
strncpy(msg_log[new_idx].content, parsed.content, 63);
|
|
msg_log[new_idx].content[63] = '\0';
|
|
msg_log[new_idx].is_lxmf = true;
|
|
if (sender_pub) {
|
|
msg_log[new_idx].verified =
|
|
lxmf_verify_signature(plaintext, pt_len, sender_pub);
|
|
}
|
|
}
|
|
|
|
Serial.printf("[lxmf_rx] from %02x%02x: \"%s\"\n",
|
|
parsed.source_hash[0], parsed.source_hash[1], parsed.content);
|
|
}
|
|
}
|
|
} else {
|
|
// DATA not for us — log generically
|
|
msg_log_add(dest_hash, NULL, rssi, snr, pkt_type, len, false);
|
|
}
|
|
} else {
|
|
// Other packet — log with dest_hash
|
|
msg_log_add(dest_hash, NULL, rssi, snr, pkt_type, len, false);
|
|
}
|
|
}
|
|
|
|
#endif // BOARD_MODEL == BOARD_TWATCH_ULT
|
|
#endif // MESSAGELOG_H
|