Files
MeshCore/test/fixtures/morse_reply_loop.cpp
T
mikecarper 73c5f572eb Apply busy radio settings and recover service starts automatically
Save valid busy RX, fast-switch and Companion tuple changes once, then
apply the latest accepted intent at a safe radio gap. Preserve checked-save
rollback, runtime RXEN intent, TempRadio ownership and exact remote reply
delivery. Repair failed preamble compensation without hiding uncertain saves.

Retry WebConfig, MQTT and NimBLE startup failures automatically. Preserve
explicit off choices, OTA ownership and the latest WiFi credentials through
cooperative shutdown. Add regression coverage and update command guides.

Validation: focused host transaction, rollback, ownership and lifecycle suites;
Full RAK4631, Heltec V4 R8 TFT and RC32 packages; representative room, sensor,
LR1110, LR2021, SX1276, STM32WL and ESP-NOW builds. Linked RAK crypto and OTA
workspace checks pass; its complete OTA image uses 712644/712704 bytes.
2026-10-25 15:20:55 -07:00

65 lines
2.6 KiB
C++

#include <helpers/MorseLoopPolicy.h>
static uint32_t now_ms = 100;
uint32_t millis() { return now_ms; }
#define PUB_KEY_SIZE 32
struct ClientInfo {
struct { uint8_t pub_key[32] = {}; } id;
bool admin = true;
bool isAdmin() const { return admin; }
};
struct ACL {
std::vector<ClientInfo> clients;
size_t requested_key_size = 0;
const ClientInfo* getClient(const uint8_t* key, size_t size) {
requested_key_size = size;
assert(size == 32);
for (const auto& client : clients) if (!memcmp(client.id.pub_key, key, size)) return &client;
return nullptr;
}
};
struct Callbacks { TestRadio* radio; mesh::Radio* getProfileRadio() { return radio; } };
struct CommonCLI {
mesh::MorseReplyHandoff _morse_reply;
ACL* _acl;
Callbacks* _callbacks;
struct Profiles { unsigned loops = 0; void loop() { ++loops; } } _radio_profiles;
unsigned management_loops = 0, tx_services = 0;
CommonCLI(ACL& acl, Callbacks& callbacks) : _acl(&acl), _callbacks(&callbacks) {}
void loopBatteryForwarding(bool = false) {}
void loopManagement() { ++management_loops; }
void servicePendingTxPower() { ++tx_services; }
void servicePendingRxSettings() {}
void loop();
};
@LOOP@
int main() {
const uint8_t key[32] = {1, 2, 3, 4};
for (int scenario = 0; scenario < 5; ++scenario) {
TestRadio radio; radio.rx_supported = true;
ACL acl; ClientInfo actor; memcpy(actor.id.pub_key, key, 32); acl.clients.push_back(actor);
Callbacks callbacks{&radio}; CommonCLI cli(acl, callbacks);
int packet;
cli._morse_reply.beginCommand(key);
assert(cli._morse_reply.stage(902100, 25, 60000, now_ms));
cli._morse_reply.endCommand(); cli._morse_reply.prepareReply(&packet);
cli._morse_reply.armReply(&packet, true);
cli.loop(); assert(radio.rx_starts == 0); // shared loop must still wait for PHY completion
cli._morse_reply.complete(&packet);
if (scenario == 0) acl.clients[0].admin = false;
if (scenario == 1) { acl.clients[0].id.pub_key[31] = 1; } // reused slot, same short key
if (scenario == 2) {
acl.clients[0].id.pub_key[31] = 1; acl.clients.push_back(actor); // original full identity moved
}
if (scenario == 3) mesh::usable = false;
if (scenario == 4) mesh::current.frequency_khz = 902101;
cli.loop();
const bool allowed = scenario == 2;
assert(radio.rx_starts == unsigned(allowed) && !cli._morse_reply.pending());
assert(acl.requested_key_size == 32 && cli._radio_profiles.loops == unsigned(allowed ? 1 : 2));
assert(cli.management_loops == unsigned(allowed ? 1 : 2));
assert(cli.tx_services == cli.management_loops);
mesh::usable = true; mesh::current = mesh::MorseSettings();
}
}