mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-26 17:38:16 +00:00
Distinguish OTA read failures from missing blocks and select unique raw-flash checkpoints by MID and target. Keep deferred CLI requests and replies bound to immutable command identity and full authenticated keys. Gate radio mutations on their exact reply delivery, recover coding-rate restoration, and roll back failed Companion preference writes. Preserve accepted low duty cycles across reboot. Retain partial network writes, cancel displaced client operations, and reject malformed or stalled framed input. Add regression and CI coverage, and size-optimize the RAK4631 Ethernet build without dropping features.
137 lines
4.3 KiB
C++
137 lines
4.3 KiB
C++
#pragma once
|
|
|
|
#include <MeshCore.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
namespace mesh {
|
|
|
|
// Keeps a small bounded history of completed remote CLI replies so a delayed
|
|
// exact retry can recover a lost response without executing the command twice.
|
|
class RemoteCliReplyCache {
|
|
public:
|
|
static constexpr size_t MAX_REPLY_TEXT =
|
|
MAX_PACKET_PAYLOAD - CIPHER_MAC_SIZE - (CIPHER_BLOCK_SIZE - 1) - 5;
|
|
|
|
RemoteCliReplyCache() { clear(); }
|
|
|
|
#if defined(STM32_PLATFORM)
|
|
static constexpr size_t ENTRY_COUNT = 2;
|
|
#else
|
|
static constexpr size_t ENTRY_COUNT = 4;
|
|
#endif
|
|
|
|
static uint32_t fingerprint(const char* command, size_t command_len) {
|
|
if (command == NULL) return 0;
|
|
|
|
// FNV-1a is only a compact request-mismatch guard. Authentication and the
|
|
// sender/timestamp tuple remain the replay-protection boundary.
|
|
uint32_t value = 2166136261UL;
|
|
for (size_t i = 0; i < command_len; i++) {
|
|
value ^= (uint8_t)command[i];
|
|
value *= 16777619UL;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
bool remember(const uint8_t* sender_pub_key, uint32_t request_timestamp,
|
|
uint32_t command_fingerprint, const char* response,
|
|
bool authoritative_reply = false) {
|
|
if (sender_pub_key == NULL || response == NULL) return false;
|
|
|
|
Entry* target = NULL;
|
|
for (size_t i = 0; i < ENTRY_COUNT; ++i) {
|
|
if (entryMatches(entries_[i], sender_pub_key, request_timestamp,
|
|
command_fingerprint)) {
|
|
target = &entries_[i];
|
|
latest_entry_ = (uint8_t)i;
|
|
break;
|
|
}
|
|
}
|
|
if (target == NULL) {
|
|
target = &entries_[next_entry_];
|
|
latest_entry_ = next_entry_;
|
|
next_entry_ = (uint8_t)((next_entry_ + 1) % ENTRY_COUNT);
|
|
}
|
|
|
|
memcpy(target->sender_pub_key, sender_pub_key,
|
|
sizeof(target->sender_pub_key));
|
|
target->request_timestamp = request_timestamp;
|
|
target->command_fingerprint = command_fingerprint;
|
|
|
|
size_t response_len = 0;
|
|
while (response_len < MAX_REPLY_TEXT && response[response_len] != 0) {
|
|
response_len++;
|
|
}
|
|
memcpy(target->response, response, response_len);
|
|
target->response[response_len] = 0;
|
|
target->valid = true;
|
|
target->authoritative_reply = authoritative_reply;
|
|
return true;
|
|
}
|
|
|
|
bool lookup(const uint8_t* sender_pub_key, uint32_t request_timestamp,
|
|
uint32_t command_fingerprint,
|
|
const char** response = NULL, bool* authoritative_reply = NULL) const {
|
|
if (authoritative_reply) *authoritative_reply = false;
|
|
for (size_t i = 0; i < ENTRY_COUNT; ++i) {
|
|
if (!entryMatches(entries_[i], sender_pub_key, request_timestamp,
|
|
command_fingerprint)) {
|
|
continue;
|
|
}
|
|
if (response != NULL) *response = entries_[i].response;
|
|
if (authoritative_reply) *authoritative_reply = entries_[i].authoritative_reply;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool matches(const uint8_t* sender_pub_key, uint32_t request_timestamp,
|
|
uint32_t command_fingerprint) const {
|
|
return lookup(sender_pub_key, request_timestamp, command_fingerprint);
|
|
}
|
|
|
|
const char* response() const {
|
|
return latest_entry_ < ENTRY_COUNT && entries_[latest_entry_].valid
|
|
? entries_[latest_entry_].response : "";
|
|
}
|
|
bool hasResponse() const { return isValid() && response()[0] != 0; }
|
|
bool isValid() const {
|
|
return latest_entry_ < ENTRY_COUNT && entries_[latest_entry_].valid;
|
|
}
|
|
|
|
void clear() {
|
|
memset(entries_, 0, sizeof(entries_));
|
|
next_entry_ = 0;
|
|
latest_entry_ = (uint8_t)ENTRY_COUNT;
|
|
}
|
|
|
|
private:
|
|
struct Entry {
|
|
bool valid;
|
|
bool authoritative_reply;
|
|
uint8_t sender_pub_key[PUB_KEY_SIZE];
|
|
uint32_t request_timestamp;
|
|
uint32_t command_fingerprint;
|
|
char response[MAX_REPLY_TEXT + 1];
|
|
};
|
|
|
|
static bool entryMatches(const Entry& entry,
|
|
const uint8_t* sender_pub_key,
|
|
uint32_t request_timestamp,
|
|
uint32_t command_fingerprint) {
|
|
return entry.valid && sender_pub_key != NULL
|
|
&& entry.request_timestamp == request_timestamp
|
|
&& entry.command_fingerprint == command_fingerprint
|
|
&& memcmp(entry.sender_pub_key, sender_pub_key,
|
|
sizeof(entry.sender_pub_key)) == 0;
|
|
}
|
|
|
|
Entry entries_[ENTRY_COUNT];
|
|
uint8_t next_entry_;
|
|
uint8_t latest_entry_;
|
|
};
|
|
|
|
} // namespace mesh
|