Files
MeshCore/test/test_companion_fleet_control.py
mikecarper 0be4c7488c Add bounded Morse receive and negotiated RS232 autosleep
Add native narrow-band CW capture, ACK-first remote sessions, retained LoRa-readable pages, checked restoration and transport-safe progress. Add stock-compatible negotiated heartbeat/autosleep and recovery. Include host regressions and identity-bound HIL runners; keep unverified faster tuning HIL-only and document outstanding hardware validation.
2026-10-13 01:24:39 -07:00

1487 lines
77 KiB
Python

#!/usr/bin/env python3
"""Execute the real Companion fleet-send CLI, preserving its client boundary.
Only channel storage and packet admission are mocked here. Protocol encoding,
target hashing and permission checks come from FleetCommand.cpp; OpenSSL
supplies real Ed25519 signing and verification through the Identity interface.
"""
from pathlib import Path
import os
import shutil
import subprocess
import tempfile
import unittest
from cpp_source import body
ROOT = Path(__file__).resolve().parents[1]
SOURCE = ROOT / "examples/companion_radio/MyMesh.cpp"
HARNESS = r'''
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <helpers/FleetCommand.h>
#include <helpers/CompanionFrameLimits.h>
#include <openssl/evp.h>
struct RescueStream : Stream {
std::string input, output;
size_t position = 0, reads = 0;
int available() override { return position < input.size(); }
int read() override { ++reads; return input[position++]; }
size_t write(uint8_t value) override { output += char(value); return 1; }
};
static RescueStream rescue_input, rescue_output;
namespace mesh {
Stream& usbTerminalPort() { return rescue_output; }
Stream& usbCompanionPort() { return rescue_input; }
Identity::Identity() { memset(pub_key, 0, sizeof(pub_key)); }
LocalIdentity::LocalIdentity() : Identity() {
memset(prv_key, 0xAA, 32);
EVP_PKEY* key = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, nullptr, prv_key, 32);
assert(key);
size_t length = sizeof(pub_key);
assert(EVP_PKEY_get_raw_public_key(key, pub_key, &length) == 1);
assert(length == sizeof(pub_key));
memcpy(prv_key + 32, pub_key, sizeof(pub_key));
EVP_PKEY_free(key);
}
void LocalIdentity::sign(uint8_t* signature, const uint8_t* data, int length) const {
EVP_PKEY* key = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, nullptr, prv_key, 32);
EVP_MD_CTX* context = EVP_MD_CTX_new();
assert(key && context);
assert(EVP_DigestSignInit(context, nullptr, nullptr, nullptr, key) == 1);
size_t size = SIGNATURE_SIZE;
assert(EVP_DigestSign(context, signature, &size, data, length) == 1);
assert(size == SIGNATURE_SIZE);
EVP_MD_CTX_free(context); EVP_PKEY_free(key);
}
bool Identity::verify(const uint8_t* signature, const uint8_t* data, int length) const {
EVP_PKEY* key = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, nullptr, pub_key, sizeof(pub_key));
EVP_MD_CTX* context = EVP_MD_CTX_new();
assert(key && context);
assert(EVP_DigestVerifyInit(context, nullptr, nullptr, nullptr, key) == 1);
const bool valid = EVP_DigestVerify(context, signature, SIGNATURE_SIZE, data, length) == 1;
EVP_MD_CTX_free(context); EVP_PKEY_free(key);
return valid;
}
}
#define MAX_GROUP_CHANNELS 4
#define OUT_PATH_UNKNOWN 255
namespace mesh {
struct GroupChannel {
uint8_t secret[32] = {};
uint8_t tx_radio = 0;
};
struct Packet {
uint8_t payload[168] = {};
size_t payload_len = 0;
uint8_t tx_radio = 0;
uint16_t scope = 0;
bool isRouteDirect() const { return false; }
uint8_t getPayloadType() const { return 6; }
};
struct Mesh {
unsigned base_copy_hooks = 0;
void onRadioProfileCopyQueued(Packet*, const Packet*, uint8_t) { ++base_copy_hooks; }
};
}
using GroupChannel = mesh::GroupChannel;
#define PAYLOAD_TYPE_GRP_DATA 6
#define PAYLOAD_TYPE_TRACE 9
struct PacketManager {
mesh::Packet pool[64];
bool used[64] = {};
std::vector<mesh::Packet*> outbound;
unsigned total = 0, allocations = 0, released = 0;
unsigned fail_allocation = 0;
mesh::Packet* allocNew() {
if (++allocations == fail_allocation) return nullptr;
for (unsigned i = 0; i < 64; ++i) if (!used[i]) {
used[i] = true; pool[i] = {}; return &pool[i];
}
return nullptr;
}
void free(mesh::Packet* packet) {
const ptrdiff_t index = packet - pool;
assert(index >= 0 && index < 64 && used[index]);
used[index] = false; ++released;
}
unsigned freeCount() const { unsigned count = 0; for (bool value : used) count += !value; return count; }
int getOutboundTotal() const { return int(total); }
mesh::Packet* getOutboundByIdx(int index) { return outbound.at(index); }
mesh::Packet* removeOutboundByIdx(int index) {
auto* packet = outbound.at(index); outbound.erase(outbound.begin() + index); --total;
return packet;
}
void queue(mesh::Packet* packet) { outbound.push_back(packet); ++total; }
};
struct ChannelDetails { GroupChannel channel; char name[32] = {}; };
struct Clock { uint32_t now = mesh::FleetCommand::MinEpoch + 1000;
uint32_t getCurrentTime() const { return now; } };
class MyMesh : public mesh::Mesh {
public:
struct NoMorseReply { void copy(const void*, const void*) {} } _morse_reply;
mesh::LocalIdentity self_id;
Clock clock;
ChannelDetails channels[MAX_GROUP_CHANNELS];
bool queue_ok = true;
PacketManager manager;
PacketManager* _mgr = &manager;
unsigned& queued = manager.total;
unsigned queue_calls = 0, fail_queue = 0, failed_hooks = 0, radio_cancellations = 0;
bool radio_copy = false;
mesh::Packet* _fleet_admission_original = nullptr;
mesh::Packet* _fleet_admission_copy = nullptr;
char cli_command[80] = {};
bool _cli_line_overflow = false;
bool _cli_rescue = false;
std::vector<std::string> rescue_commands;
uint8_t sent_policy = 0;
uint16_t sent_type = 0;
uint8_t sent[mesh::FleetCommand::MaxEnvelopeLength] = {};
size_t sent_length = 0;
MyMesh() {
strcpy(channels[1].name, "private fleet");
memset(channels[1].channel.secret, 0x21, mesh::FleetCommand::KeySize);
channels[1].channel.tx_radio = 2;
}
bool getChannel(int index, ChannelDetails& out) {
if (index < 0 || index >= MAX_GROUP_CHANNELS) return false;
out = channels[index]; return true;
}
Clock* getRTCClock() { return &clock; }
mesh::Packet* createGroupDatagram(uint8_t type, const GroupChannel& channel,
const uint8_t* data, size_t length) {
assert(type == PAYLOAD_TYPE_GRP_DATA && length <= sizeof(mesh::Packet::payload));
auto* packet = manager.allocNew();
if (!packet) return nullptr;
packet->tx_radio = channel.tx_radio; packet->payload_len = length;
memcpy(packet->payload, data, length); return packet;
}
void releasePacket(mesh::Packet* packet) { manager.free(packet); }
void onSendFail(mesh::Packet*) { ++failed_hooks; }
void cancelOutboundRadioRetry(const mesh::Packet*) { ++radio_cancellations; }
void onTracePacketQueuedForSend(mesh::Packet*) { assert(false); }
bool queueOutboundPacket(mesh::Packet* packet, uint8_t, uint32_t) {
if (++queue_calls == fail_queue || !queue_ok) return false;
manager.queue(packet);
sent_policy = packet->tx_radio;
sent_type = uint16_t(packet->payload[0]) | uint16_t(packet->payload[1]) << 8;
const uint8_t* blob = packet->payload + 3;
const size_t length = packet->payload[2];
assert(length + 3 == packet->payload_len && length <= mesh::FleetCommand::MaxPayloadLength);
mesh::FleetCommand::Fragment part;
if (mesh::FleetCommand::parseFragment(blob, length, part)) {
memcpy(sent + part.index * mesh::FleetCommand::FragmentDataLength, part.data, part.length);
sent_length = part.total_length;
} else {
memcpy(sent, blob, length); sent_length = length;
}
if (radio_copy) {
auto* copy = manager.allocNew(); assert(copy); *copy = *packet;
manager.queue(copy); onRadioProfileCopyQueued(copy, packet, 1);
}
return true;
}
bool sendPacket(mesh::Packet*, uint8_t, uint32_t = 0);
bool sendFloodScoped(const GroupChannel& channel, mesh::Packet* packet, uint32_t = 0) {
assert(packet->tx_radio == channel.tx_radio);
packet->scope = 0x4321; // Scope adapter; production helper preserves this channel route.
return sendPacket(packet, 1);
}
bool sendGroupData(GroupChannel& channel, uint8_t* path, uint8_t path_length,
uint16_t type, const uint8_t* data, int length) {
assert(path == nullptr && path_length == OUT_PATH_UNKNOWN);
assert(length > 0 && size_t(length) <= mesh::FleetCommand::MaxPayloadLength);
uint8_t group[168] = {uint8_t(type), uint8_t(type >> 8), uint8_t(length)};
memcpy(group + 3, data, length);
auto* packet = createGroupDatagram(PAYLOAD_TYPE_GRP_DATA, channel, group, 3 + length);
return packet && sendFloodScoped(channel, packet);
}
bool sendFleetCommandData(GroupChannel&, const uint8_t*, size_t);
void onRadioProfileCopyQueued(mesh::Packet*, const mesh::Packet*, uint8_t);
bool handleCommand(const char* command, uint32_t sender_timestamp, char* reply);
void checkCLIRescueCmd();
void enterCLIRescue();
void resetUsbHostSessionInput();
};
@METHOD@
@SEND_HELPERS@
@RESCUE_READER@
static void rejected(MyMesh& node, const char* text, uint32_t remote = 0) {
char reply[160] = {};
const unsigned before = node.queued;
assert(node.handleCommand(text, remote, reply));
const char* result = strlen(reply) > 3 && reply[2] == '|' ? reply + 3 : reply;
assert(!strncmp(result, "Error:", 6));
assert(before == node.queued);
}
static std::string fullKey(uint8_t value) {
char hex[65];
for (unsigned i = 0; i < 32; ++i) sprintf(hex + i * 2, "%02x", value);
return hex;
}
struct RegionMembership {
std::vector<std::string> configured;
std::vector<std::string> home_ancestors;
};
// Receiver membership is a role-owned read-only callback. These explicit
// memberships isolate sender serialization from actual RegionMap behavior.
static bool regionMatches(void* context, mesh::FleetCommand::RegionTarget target,
const char* name, size_t length) {
assert(context && length > 0 && length <= mesh::FleetCommand::MaxRegionNameLength);
const auto& membership = *static_cast<const RegionMembership*>(context);
const auto& names = target == mesh::FleetCommand::RegionTarget::Configured
? membership.configured : membership.home_ancestors;
for (const auto& candidate : names)
if (candidate.size() == length && !memcmp(candidate.data(), name, length)) return true;
return false;
}
static bool decodedRegion(MyMesh& node, RegionMembership& membership,
mesh::FleetCommand::Decoded& decoded, uint8_t node_prefix = 0x77) {
uint8_t recipient[32]; memset(recipient, node_prefix, sizeof(recipient));
return mesh::FleetCommand::decode(node.self_id, node.channels[1].channel.secret,
node.sent, node.sent_length, node.clock.now, recipient, decoded,
regionMatches, &membership);
}
struct GeoPosition {
int32_t latitude_e6 = 47606200, longitude_e6 = -122332100;
bool available = true;
};
static bool geoMatches(void* context, int32_t latitude_e6, int32_t longitude_e6,
uint32_t radius_meters) {
assert(context);
const auto& position = *static_cast<const GeoPosition*>(context);
return position.available && mesh::FleetCommand::withinRadius(
latitude_e6, longitude_e6, radius_meters, position.latitude_e6, position.longitude_e6);
}
static bool decodedGeo(MyMesh& node, GeoPosition& position,
mesh::FleetCommand::Decoded& decoded,
RegionMembership* regions = nullptr, uint8_t node_prefix = 0x77) {
uint8_t recipient[32]; memset(recipient, node_prefix, sizeof(recipient));
return mesh::FleetCommand::decode(node.self_id, node.channels[1].channel.secret,
node.sent, node.sent_length, node.clock.now, recipient, decoded,
regions ? regionMatches : nullptr, regions, geoMatches, &position);
}
static uint32_t encoded32(const uint8_t* bytes) {
return uint32_t(bytes[0]) | uint32_t(bytes[1]) << 8
| uint32_t(bytes[2]) << 16 | uint32_t(bytes[3]) << 24;
}
static void checkedFragments(MyMesh& node, size_t first_index,
mesh::FleetCommand::RegionMatcher matcher = nullptr,
void* context = nullptr,
mesh::FleetCommand::GeoMatcher geo_matcher = nullptr,
void* geo_context = nullptr) {
assert(node.sent_length > mesh::FleetCommand::MaxPayloadLength);
uint8_t assembled[mesh::FleetCommand::MaxEnvelopeLength] = {};
// Read in reverse order; the immutable part index determines placement.
for (uint8_t index : {1, 0}) {
auto* packet = node.manager.outbound.at(first_index + index);
assert(packet->scope == 0x4321 && packet->tx_radio == 2);
assert(packet->payload[0] == 1 && packet->payload[1] == 0xff);
const size_t bytes = packet->payload[2];
mesh::FleetCommand::Fragment part;
assert(mesh::FleetCommand::parseFragment(packet->payload + 3, bytes, part));
assert(part.index == index && part.total_length == node.sent_length);
assert(part.sequence == node.clock.now && bytes <= mesh::FleetCommand::MaxPayloadLength);
assert(part.length == (index == 0 ? 154 : node.sent_length - 154));
assert(bytes == mesh::FleetCommand::FragmentHeaderSize + part.length);
memcpy(assembled + index * 154, part.data, part.length);
uint8_t recipient[32]; memset(recipient, 0x42, sizeof(recipient));
mesh::FleetCommand::Decoded decoded;
assert(!mesh::FleetCommand::decode(node.self_id, node.channels[1].channel.secret,
packet->payload + 3, bytes, node.clock.now, recipient, decoded));
}
assert(!memcmp(assembled, node.sent, node.sent_length));
uint8_t recipient[32]; memset(recipient, 0x42, sizeof(recipient));
mesh::FleetCommand::Decoded decoded;
assert(mesh::FleetCommand::decode(node.self_id, node.channels[1].channel.secret,
assembled, node.sent_length, node.clock.now, recipient, decoded,
matcher, context, geo_matcher, geo_context));
assembled[node.sent_length - 1] ^= 1;
assert(!mesh::FleetCommand::decode(node.self_id, node.channels[1].channel.secret,
assembled, node.sent_length, node.clock.now, recipient, decoded,
matcher, context, geo_matcher, geo_context));
}
static void compactBroadcast(MyMesh& node, const char* command) {
assert(!memcmp(node.sent, "FMC2", 4));
assert(node.sent[12] == 0); // No target records means every authorized receiver.
assert(node.sent[13] == strlen(command));
assert(mesh::FleetCommand::MinHeaderSize == 14);
assert(node.sent_length == 14 + strlen(command) + mesh::FleetCommand::SignatureSize);
assert(!memcmp(node.sent + 14, command, strlen(command)));
if (node.sent_length > mesh::FleetCommand::MaxPayloadLength)
checkedFragments(node, node.manager.outbound.size() - 2);
for (uint8_t value : {0x42, 0x77}) {
uint8_t recipient[32]; memset(recipient, value, sizeof(recipient));
mesh::FleetCommand::Decoded decoded;
assert(mesh::FleetCommand::decode(node.self_id, node.channels[1].channel.secret,
node.sent, node.sent_length, node.clock.now, recipient, decoded));
assert(decoded.broadcast && !strcmp(decoded.command, command));
assert(decoded.sequence == node.clock.now);
assert(decoded.expires == node.clock.now + mesh::FleetCommand::MaxLifetime);
}
}
int main(int argc, char** argv) {
assert(argc == 2);
const std::string scenario = argv[1];
MyMesh node; char reply[160] = {};
if (scenario == "local_boundary") {
assert(!node.handleCommand(nullptr, 0, reply));
assert(!node.handleCommand("fleet send 1 all set radio2 off", 0, nullptr));
assert(!node.handleCommand("fleetish", 0, reply));
rejected(node, "fleet send 1 all set radio2 off", 1);
rejected(node, "fleet send 1 00112233;445566778899 set radio2 off", 1);
rejected(node, "fleet", 1);
rejected(node, "fleet send 1 all reboot");
rejected(node, "fleet send 1 all get prv.key");
rejected(node, "fleet send 1 all fleet send 1 all set radio2 off");
assert(node.handleCommand(" \tAB| fleet send 1 all set radio2 off", 0, reply));
assert(!strncmp(reply, "AB|OK - fleet command queued; seq=", 32));
assert(node.queued == 1 && node.sent_type == mesh::FleetCommand::DataType);
assert(node.sent_policy == 2); // The configured radio2 channel route survives.
compactBroadcast(node, "set radio2 off");
} else if (scenario == "implicit_broadcast") {
for (const char* command : {"set radio2 off", "get radio2.status", "del flood.filter all",
"clock", "clock sync", "time 1800000000",
"set tempradioat2 910.5,500,5,5,rxtx,+1,+2,auto"}) {
const std::string implicit = std::string("fleet send 1 ") + command;
assert(node.handleCommand(implicit.c_str(), 0, reply));
assert(!strncmp(reply, "OK - fleet command queued", 25));
assert(node.sent_policy == 2 && node.sent_type == mesh::FleetCommand::DataType);
compactBroadcast(node, command);
++node.clock.now;
const std::string explicit_all = std::string("fleet send 1 all ") + command;
assert(node.handleCommand(explicit_all.c_str(), 0, reply));
compactBroadcast(node, command);
++node.clock.now;
}
assert(node.queued == 14);
} else if (scenario == "implicit_local_prefix_tabs") {
rejected(node, "fleet send 1 set radio2 off", 1);
rejected(node, "AB|fleet send 1 set radio2 off", 100);
rejected(node, "AB|fleet\tsend\t1\tset radio2 off", 1);
assert(node.handleCommand(" \tAB| fleet\tsend\t1\tset radio2 off", 0, reply));
assert(!strncmp(reply, "AB|OK - fleet command queued; seq=", 32));
compactBroadcast(node, "set radio2 off");
assert(node.queued == 1 && node.sent_policy == 2);
++node.clock.now;
assert(node.handleCommand("CD|fleet\tsend\t1\tall\tclock sync", 0, reply));
assert(!strncmp(reply, "CD|OK - fleet command queued; seq=", 32));
compactBroadcast(node, "clock sync");
assert(node.queued == 2);
} else if (scenario == "implicit_retry_sequence") {
node.queue_ok = false;
rejected(node, "fleet send 1 set radio2 off");
node.queue_ok = true;
assert(node.handleCommand("fleet send 1 all set radio2 off", 0, reply));
compactBroadcast(node, "set radio2 off");
rejected(node, "fleet send 1 set radio2 off");
++node.clock.now;
assert(node.handleCommand("EF|fleet send 1 set radio2 off", 0, reply));
assert(!strncmp(reply, "EF|OK - fleet command queued; seq=", 32));
compactBroadcast(node, "set radio2 off");
rejected(node, "fleet send 1 all set radio2 off");
--node.clock.now;
rejected(node, "fleet send 1 clock sync");
node.clock.now += 2;
rejected(node, "fleet send 1 nonsense set radio2 off");
assert(node.handleCommand("fleet send 1 clock sync", 0, reply));
compactBroadcast(node, "clock sync");
assert(node.queued == 3);
} else if (scenario == "implicit_target_ambiguity") {
for (const char* targets : {"nonsense", "all;42424242", "42424242;all",
"4242424", "424242424", "4242424242424", "GG424242",
"42424242;;51515151", "42424242;", ";42424242", "region:", "home:",
"get", "set", "del", "time", "clock"}) {
rejected(node, (std::string("fleet send 1 ") + targets + " set radio2 off").c_str());
}
rejected(node, ("fleet send 1 " + fullKey(0) + " set radio2 off").c_str());
rejected(node, ("fleet send 1 " + std::string(65, '4') + " set radio2 off").c_str());
assert(node.handleCommand("fleet send 1 get radio2", 0, reply));
compactBroadcast(node, "get radio2");
assert(node.queued == 1); // No typo became a broadcast or consumed a sequence.
} else if (scenario == "implicit_command_controls") {
for (const char* command : {"reboot", "get prv.key", "get password", "get clock",
"set radio2 of", "clock sync extra", "set radio2 off\nreboot", "clock\r",
"set\tradio2 off", "set radio2 off", "clock sync ", "clock\x7f",
"set radio2 off;reboot", "set radio2 off|reboot", "set radio2 off&reboot",
"set flood.filter `reboot`", "set flood.filter \\reboot"}) {
rejected(node, (std::string("fleet send 1 ") + command).c_str());
rejected(node, (std::string("fleet send 1 all ") + command).c_str());
}
const std::string prefix = "set flood.filter ";
const std::string maximum = prefix + std::string(mesh::FleetCommand::MaxCommandLength - prefix.size(), '1');
assert(maximum.size() == 230 && mesh::FleetCommand::commandAllowed(maximum.c_str()));
rejected(node, ("fleet send 1 " + maximum + "1").c_str());
rejected(node, ("fleet send 1 all " + maximum + "1").c_str());
assert(node.handleCommand(("fleet send 1 " + maximum).c_str(), 0, reply));
compactBroadcast(node, maximum.c_str());
assert(node.sent_length == 308 && node.queued == 2);
++node.clock.now;
assert(node.handleCommand(("fleet send 1 all " + maximum).c_str(), 0, reply));
compactBroadcast(node, maximum.c_str());
assert(node.sent_length == 308 && node.queued == 4);
} else if (scenario == "target_command_capacity") {
struct Boundary { std::string targets; size_t command_length; };
const Boundary boundaries[] = {
{"42424242", 225}, {"424242424242", 223}, {fullKey(0x42), 215},
{"42424242;51515151", 220}, {"42424242;515151515151", 218},
{fullKey(0x42) + ";" + fullKey(0x51), 196},
};
const std::string prefix = "set flood.filter ";
for (const Boundary& boundary : boundaries) {
const std::string fitting = prefix + std::string(boundary.command_length - prefix.size(), '1');
const std::string oversize = fitting + '1';
assert(mesh::FleetCommand::commandAllowed(oversize.c_str()));
const std::string cli_prefix = "fleet send 1 " + boundary.targets + " ";
const unsigned before = node.queued;
rejected(node, (cli_prefix + oversize).c_str());
// A valid target plus a long command must not become a broadcast, a
// partial send, or a consumed sequence. A queue refusal is retryable too.
assert(node.handleCommand((cli_prefix + oversize).c_str(), 0, reply));
assert(strstr(reply, "exceed two packets") && node.queued == before);
node.queue_ok = false;
rejected(node, (cli_prefix + fitting).c_str());
node.queue_ok = true;
assert(node.handleCommand((cli_prefix + fitting).c_str(), 0, reply));
assert(node.queued == before + 2 && node.sent_length == 308);
assert(node.sent_policy == 2);
uint8_t recipient[32]; memset(recipient, 0x42, sizeof(recipient));
mesh::FleetCommand::Decoded decoded;
assert(mesh::FleetCommand::decode(node.self_id, node.channels[1].channel.secret,
node.sent, node.sent_length, node.clock.now, recipient, decoded));
assert(decoded.sequence == node.clock.now && !strcmp(decoded.command, fitting.c_str()));
assert(!memcmp(node.sent, boundary.targets.size() == 64 ? "FMC1" : "FMC2", 4));
memset(recipient, 0x77, sizeof(recipient));
assert(!mesh::FleetCommand::decode(node.self_id, node.channels[1].channel.secret,
node.sent, node.sent_length, node.clock.now, recipient, decoded));
rejected(node, (cli_prefix + fitting).c_str()); // Successful send consumes its second.
++node.clock.now;
}
assert(node.queued == 12);
} else if (scenario == "maximum_correlated_app_command") {
const std::string prefix = "set flood.filter ";
const std::string maximum = prefix + std::string(158 - prefix.size(), '1');
const std::string local_cli = "AB|fleet send 1 " + maximum;
// The app's local CLI frame adds opcode 66 and a trailing NUL. Its request
// correlation prefix is already inside local_cli; all are ASCII bytes.
assert(local_cli.size() + 2 == MAX_FRAME_SIZE);
assert((local_cli + '1').size() + 2 > MAX_FRAME_SIZE);
rejected(node, local_cli.c_str(), 1);
node.queue_ok = false;
rejected(node, local_cli.c_str());
node.queue_ok = true;
assert(node.handleCommand(local_cli.c_str(), 0, reply));
assert(!strncmp(reply, "AB|OK - fleet command queued; seq=", 32));
compactBroadcast(node, maximum.c_str());
assert(node.sent_length == 236 && node.queued == 2);
assert(node.sent_policy == 2);
rejected(node, local_cli.c_str());
++node.clock.now;
const std::string unprefixed = prefix + std::string(161 - prefix.size(), '1');
const std::string implicit = "fleet send 1 " + unprefixed;
assert(implicit.size() + 2 == MAX_FRAME_SIZE);
assert((implicit + '1').size() + 2 > MAX_FRAME_SIZE);
assert(node.handleCommand(implicit.c_str(), 0, reply));
compactBroadcast(node, unprefixed.c_str());
assert(node.sent_length == 239 && node.queued == 4);
} else if (scenario == "fragment_boundaries") {
struct Boundary { std::string targets; size_t single_length; };
const Boundary boundaries[] = {
{"", 87}, {"42424242 ", 82}, {"424242424242 ", 80}, {fullKey(0x42) + " ", 72},
};
const std::string prefix = "set flood.filter ";
for (const Boundary& boundary : boundaries) {
std::string command = prefix + std::string(boundary.single_length - prefix.size(), '1');
const std::string cli_prefix = "fleet send 1 " + boundary.targets;
const size_t before = node.manager.outbound.size();
assert(node.handleCommand((cli_prefix + command).c_str(), 0, reply));
assert(node.queued == before + 1 && node.sent_length == 165);
assert(!memcmp(node.manager.outbound.back()->payload + 3,
boundary.targets.size() == 65 ? "FMC1" : "FMC2", 4));
++node.clock.now; command += '1';
assert(node.handleCommand((cli_prefix + command).c_str(), 0, reply));
assert(node.queued == before + 3 && node.sent_length == 166);
checkedFragments(node, before + 1);
++node.clock.now;
}
assert(node.queued == 12);
} else if (scenario == "fragment_atomic_admission") {
unsigned scenario_index = 0;
for (const char* failure : {"pool_first", "pool_second", "queue_first", "queue_second", "queue_second_copy"}) {
MyMesh trial; trial.clock.now += ++scenario_index * 10;
const std::string prefix = "set flood.filter ";
const std::string command = prefix + std::string(161 - prefix.size(), '1');
const std::string cli = "fleet send 1 " + command;
auto* unrelated = trial.manager.allocNew(); assert(unrelated);
unrelated->payload[0] = 99; trial.manager.queue(unrelated);
const unsigned free_before = trial.manager.freeCount();
if (!strcmp(failure, "pool_first")) trial.manager.fail_allocation = trial.manager.allocations + 1;
if (!strcmp(failure, "pool_second")) trial.manager.fail_allocation = trial.manager.allocations + 2;
if (!strcmp(failure, "queue_first")) trial.fail_queue = trial.queue_calls + 1;
if (!strcmp(failure, "queue_second")) trial.fail_queue = trial.queue_calls + 2;
if (!strcmp(failure, "queue_second_copy")) {
trial.fail_queue = trial.queue_calls + 2; trial.radio_copy = true;
}
rejected(trial, cli.c_str());
assert(trial.queued == 1 && trial.manager.outbound[0] == unrelated);
assert(trial.manager.freeCount() == free_before);
assert(!trial._fleet_admission_original && !trial._fleet_admission_copy);
if (!strcmp(failure, "queue_first")) assert(trial.failed_hooks == 1);
if (!strcmp(failure, "queue_second"))
assert(trial.failed_hooks == 2 && trial.radio_cancellations == 1);
if (!strcmp(failure, "queue_second_copy")) {
assert(trial.failed_hooks == 3 && trial.radio_cancellations == 2);
assert(trial.base_copy_hooks == 1); // The production override chains Mesh.
}
trial.manager.fail_allocation = 0; trial.fail_queue = 0; trial.radio_copy = false;
assert(trial.handleCommand(cli.c_str(), 0, reply));
assert(trial.queued == 3 && trial.manager.outbound[0] == unrelated);
compactBroadcast(trial, command.c_str());
assert(trial.manager.freeCount() == free_before - 2);
}
} else if (scenario == "rescue_overflow") {
const std::string unsafe = "fleet send 1 set flood.rule.1 type=grp_txt drop hops=all in=any priority=00001 suspend=tempradio";
assert(unsafe.size() > sizeof(node.cli_command));
// The old forced-completion behavior would execute this valid prefix,
// silently omitting the requested suspend=tempradio condition.
const std::string truncated = unsafe.substr(0, 78);
assert(mesh::FleetCommand::commandAllowed(truncated.c_str() + 13));
rescue_input.input = unsafe + "\r\nfleet send 1 clock\r\n";
while (rescue_input.available()) {
const size_t before = rescue_input.reads;
node.checkCLIRescueCmd();
assert(rescue_input.reads - before <= sizeof(node.cli_command));
}
assert(node.rescue_commands.size() == 1 && node.rescue_commands[0] == "fleet send 1 clock");
assert(node.queued == 1 && !node._cli_line_overflow && node.cli_command[0] == 0);
assert(rescue_output.output.find("ERR: too long") != std::string::npos);
compactBroadcast(node, "clock");
} else if (scenario == "rescue_split_overflow") {
rescue_input.input = std::string(400, 'x');
node.checkCLIRescueCmd();
assert(rescue_input.reads == sizeof(node.cli_command) && node._cli_line_overflow);
assert(node.rescue_commands.empty() && node.queued == 0);
while (rescue_input.available()) node.checkCLIRescueCmd();
assert(node._cli_line_overflow && node.rescue_commands.empty());
// Even a valid fleet suffix is discarded until the actual line ending.
rescue_input.input += "fleet send 1 set radio2 off\nfleet send 1 clock\n";
while (rescue_input.available()) node.checkCLIRescueCmd();
assert(node.rescue_commands.size() == 1 && node.rescue_commands[0] == "fleet send 1 clock");
assert(node.queued == 1 && !node._cli_line_overflow);
} else if (scenario == "rescue_boundaries") {
const std::string exact(79, 'x');
rescue_input.input = exact;
node.checkCLIRescueCmd();
assert(node.rescue_commands.empty() && strlen(node.cli_command) == 79);
rescue_input.input += "\r\nfirst\r\nsecond\nthird\r";
while (rescue_input.available()) node.checkCLIRescueCmd();
const std::vector<std::string> expected = {exact, "first", "second", "third"};
assert(node.rescue_commands == expected && !node._cli_line_overflow);
assert(node.cli_command[0] == 0);
} else if (scenario == "rescue_unterminated") {
memset(node.cli_command, 'x', sizeof(node.cli_command));
rescue_input.input = "fleet send 1 set radio2 off\r\nfleet send 1 clock\r";
while (rescue_input.available()) node.checkCLIRescueCmd();
assert(node.rescue_commands.size() == 1 && node.rescue_commands[0] == "fleet send 1 clock");
assert(node.queued == 1 && !node._cli_line_overflow);
} else if (scenario == "rescue_session_reset") {
node._cli_line_overflow = true; strcpy(node.cli_command, "old host prefix");
node.enterCLIRescue();
assert(node._cli_rescue && !node._cli_line_overflow && node.cli_command[0] == 0);
rescue_input.input = "fleet send 1 clock\r";
while (rescue_input.available()) node.checkCLIRescueCmd();
assert(node.queued == 1);
++node.clock.now;
node._cli_line_overflow = true; strcpy(node.cli_command, "old host prefix");
node.resetUsbHostSessionInput();
assert(!node._cli_line_overflow);
for (char value : node.cli_command) assert(value == 0);
rescue_input.input += "fleet send 1 clock\n";
while (rescue_input.available()) node.checkCLIRescueCmd();
assert(node.queued == 2 && node.rescue_commands.size() == 2);
} else if (scenario == "stm32_rescue") {
rescue_input.input = std::string(400, 'x') + "fleet send 1 clock\r\nnext\r\n";
while (rescue_input.available()) {
const size_t before = rescue_input.reads;
node.checkCLIRescueCmd();
assert(rescue_input.reads - before <= sizeof(node.cli_command));
}
assert(node.rescue_commands == std::vector<std::string>{"next"});
assert(rescue_output.output.find(" Error: unknown command") != std::string::npos);
assert(rescue_output.output.find("ERR: too long") == std::string::npos);
assert(node.queued == 0 && !node._cli_line_overflow);
memset(node.cli_command, 'x', sizeof(node.cli_command));
rescue_input.input += "suffix\nlast\n";
while (rescue_input.available()) node.checkCLIRescueCmd();
assert(node.rescue_commands == (std::vector<std::string>{"next", "last"}));
} else if (scenario == "region_target_modes") {
struct Selection { const char* token; const char* name; uint8_t type; };
const Selection selections[] = {
{"sea", "sea", 0x20}, {"pdx", "pdx", 0x20}, {"region:sea", "sea", 0x20},
{"#sea", "sea", 0x20}, {"region:#sea", "sea", 0x20},
{"home:sea", "sea", 0x21}, {"home:#sea", "sea", 0x21},
{"$sea", "$sea", 0x20}, {"home:$sea", "$sea", 0x21},
// Bare command verbs are reserved. Explicit selectors and # aliases
// can still select identically named regions without command ambiguity.
{"region:get", "get", 0x20}, {"home:get", "get", 0x21}, {"#get", "get", 0x20},
{"region:clock", "clock", 0x20}, {"home:time", "time", 0x21}, {"ALL", "ALL", 0x20},
{"region:00112233", "00112233", 0x20},
};
for (const auto& selection : selections) {
const std::string cli = std::string("fleet send 1 ") + selection.token + " get radio2";
rejected(node, cli.c_str(), 1);
const unsigned before = node.queued;
assert(node.handleCommand(cli.c_str(), 0, reply));
assert(node.queued == before + 1 && node.sent_policy == 2);
assert(!memcmp(node.sent, "FMC2", 4) && node.sent[12] == 1);
assert(node.sent[13] == selection.type && node.sent[14] == strlen(selection.name));
assert(!memcmp(node.sent + 15, selection.name, strlen(selection.name)));
assert(node.sent_length == 78 + 2 + strlen(selection.name) + strlen("get radio2"));
RegionMembership selected, wrong;
auto& names = selection.type == 0x20 ? selected.configured : selected.home_ancestors;
names.push_back(selection.name);
mesh::FleetCommand::Decoded decoded;
assert(decodedRegion(node, selected, decoded));
assert(decoded.broadcast && !strcmp(decoded.command, "get radio2"));
assert(!decodedRegion(node, wrong, decoded));
// Configured-list and home modes cannot borrow each other's membership.
auto& opposite = selection.type == 0x20 ? wrong.home_ancestors : wrong.configured;
opposite.push_back(selection.name);
assert(!decodedRegion(node, wrong, decoded));
uint8_t recipient[32]; memset(recipient, 0x77, sizeof(recipient));
assert(!mesh::FleetCommand::decode(node.self_id, node.channels[1].channel.secret,
node.sent, node.sent_length, node.clock.now, recipient, decoded));
rejected(node, cli.c_str()); // A successful target consumes this second.
++node.clock.now;
}
// Omitting a target still depends on a complete allowlisted command.
assert(node.handleCommand("fleet send 1 get radio2", 0, reply));
compactBroadcast(node, "get radio2");
++node.clock.now;
rejected(node, "fleet send 1 get clock");
assert(node.handleCommand("fleet send 1 region:get clock", 0, reply));
RegionMembership membership{{"get"}, {}};
mesh::FleetCommand::Decoded decoded;
assert(decodedRegion(node, membership, decoded) && !strcmp(decoded.command, "clock"));
membership.configured.clear();
assert(!decodedRegion(node, membership, decoded)); // An explicitly named verb remains targeted.
rejected(node, "fleet send 1 all get clock"); // It never becomes a new command alias.
} else if (scenario == "region_mixed_targets") {
const std::string cli = "AB|fleet\tsend\t1\tsea;pdx;home:sea;42424242\tset radio2 off";
rejected(node, cli.c_str(), 123);
node.queue_ok = false;
rejected(node, cli.c_str());
node.queue_ok = true;
assert(node.handleCommand(cli.c_str(), 0, reply));
assert(!strncmp(reply, "AB|OK - fleet command queued; seq=", 32));
assert(node.queued == 1 && node.sent_policy == 2 && node.sent[12] == 4);
assert(node.sent_length == 78 + 20 + strlen("set radio2 off"));
mesh::FleetCommand::Decoded decoded;
for (auto membership : {RegionMembership{{"sea"}, {}}, RegionMembership{{"pdx"}, {}},
RegionMembership{{}, {"bel", "sea", "w-wa"}}}) {
assert(decodedRegion(node, membership, decoded));
assert(decoded.broadcast && !strcmp(decoded.command, "set radio2 off"));
}
RegionMembership none{{"Sea", "seattle"}, {"pdx"}};
assert(!decodedRegion(node, none, decoded));
assert(decodedRegion(node, none, decoded, 0x42)); // Selected node prefix is independent.
rejected(node, cli.c_str());
++node.clock.now;
assert(node.handleCommand("fleet send 1 unknown-place clock", 0, reply));
assert(!decodedRegion(node, none, decoded)); // Unknown names never mean all.
} else if (scenario == "region_packet_budgets") {
const std::string name30(30, 'z');
struct Boundary { std::string token; size_t records; RegionMembership membership; };
const Boundary boundaries[] = {
{"sea", 5, {{"sea"}, {}}}, {"home:sea", 5, {{}, {"bel", "sea"}}},
{"region:" + name30, 32, {{name30}, {}}},
{"home:" + name30, 32, {{}, {name30}}},
{"sea;pdx;home:sea;42424242", 20, {{"pdx"}, {}}},
};
const std::string prefix = "set flood.filter ";
for (const auto& boundary : boundaries) {
const size_t maximum = mesh::FleetCommand::MaxCommandLength - boundary.records;
std::string command = prefix + std::string(maximum - prefix.size(), '1');
const std::string cli = "fleet send 1 " + boundary.token + " ";
const size_t before = node.manager.outbound.size();
rejected(node, (cli + command + '1').c_str());
assert(node.handleCommand((cli + command + '1').c_str(), 0, reply));
assert(strstr(reply, "exceed two packets") && node.queued == before);
node.queue_ok = false;
rejected(node, (cli + command).c_str());
node.queue_ok = true;
assert(node.handleCommand((cli + command).c_str(), 0, reply));
assert(node.sent_length == 308 && node.queued == before + 2 && node.sent_policy == 2);
auto membership = boundary.membership;
checkedFragments(node, before, regionMatches, &membership);
mesh::FleetCommand::Decoded decoded;
assert(decodedRegion(node, membership, decoded));
assert(!strcmp(decoded.command, command.c_str()));
++node.clock.now;
// The exact single-packet boundary changes admission to two packets
// when one additional command byte is supplied.
const size_t single = mesh::FleetCommand::SinglePacketMaxCommandLength - boundary.records;
command = prefix + std::string(single - prefix.size(), '1');
const size_t single_before = node.manager.outbound.size();
assert(node.handleCommand((cli + command).c_str(), 0, reply));
assert(node.sent_length == 165 && node.queued == single_before + 1);
assert(decodedRegion(node, membership, decoded));
++node.clock.now;
command += '1';
assert(node.handleCommand((cli + command).c_str(), 0, reply));
assert(node.sent_length == 166 && node.queued == single_before + 3);
checkedFragments(node, single_before + 1, regionMatches, &membership);
++node.clock.now;
}
} else if (scenario == "region_signature_binding") {
const std::string prefix = "set flood.filter ";
for (size_t length : {size_t(20), size_t(83)}) {
const std::string command = prefix + std::string(length - prefix.size(), '1');
const size_t before = node.manager.outbound.size();
assert(node.handleCommand(("fleet send 1 sea " + command).c_str(), 0, reply));
RegionMembership membership{{"sea", "tea"}, {"sea", "tea"}};
mesh::FleetCommand::Decoded decoded;
assert(decodedRegion(node, membership, decoded));
if (length == 83) checkedFragments(node, before, regionMatches, &membership);
// Both the readable name and its configured-vs-home selector are signed.
node.sent[15] = 't';
assert(!decodedRegion(node, membership, decoded));
node.sent[15] = 's';
node.sent[13] = mesh::FleetCommand::HomeRecordType;
assert(!decodedRegion(node, membership, decoded));
node.sent[13] = mesh::FleetCommand::RegionRecordType;
assert(decodedRegion(node, membership, decoded));
++node.clock.now;
}
} else if (scenario == "region_invalid_targets") {
for (const std::string& targets : {std::string("all;sea"), std::string("sea;all"),
std::string("sea;;pdx"), std::string("sea;"), std::string(",sea"),
std::string("region:"), std::string("home:"), std::string("region:#"),
std::string("home:*"), std::string("region:sea:pdx"), std::string("unknown:sea"),
std::string("region:") + std::string(31, 'z'), std::string("home:") + std::string(31, 'z'),
std::string("sea;GG112233"), std::string("sea;0011223"), std::string("sea;001122334"),
std::string("home:sea;0011223344556"), std::string("sea;00112233;44556677889Z"),
std::string("sea;region:\x7f"), std::string("sea;region:\xc3\xa9"),
std::string("sea;") + fullKey(0), std::string("sea;") + std::string(65, '1')}) {
rejected(node, ("fleet send 1 " + targets + " set radio2 off").c_str());
assert(node.queued == 0);
}
assert(node.handleCommand("fleet send 1 sea clock", 0, reply));
assert(node.queued == 1); // No malformed target consumed the successful second.
RegionMembership membership{{"sea"}, {}};
mesh::FleetCommand::Decoded decoded;
assert(decodedRegion(node, membership, decoded) && !strcmp(decoded.command, "clock"));
} else if (scenario == "rescue_region_overflow") {
const std::string oversized = "fleet send 1 sea;pdx;home:sea;" + fullKey(0x42)
+ " set radio2 off";
assert(oversized.size() > sizeof(node.cli_command));
rescue_input.input = oversized + "\r\nfleet send 1 sea clock\r\n";
while (rescue_input.available()) node.checkCLIRescueCmd();
assert(node.rescue_commands == std::vector<std::string>{"fleet send 1 sea clock"});
assert(node.queued == 1 && !node._cli_line_overflow);
assert(rescue_output.output.find("ERR: too long") != std::string::npos);
RegionMembership membership{{"sea"}, {}};
mesh::FleetCommand::Decoded decoded;
assert(decodedRegion(node, membership, decoded));
assert(!strcmp(decoded.command, "clock") && node.sent[12] == 1);
} else if (scenario == "gps_target") {
struct Circle { const char* text; int32_t lat, lon; uint32_t radius; };
const Circle circles[] = {
{"gps:47.6062,-122.3321:25", 47606200, -122332100, 25000},
{"gps:+47.606200,-122.332100:25.125", 47606200, -122332100, 25125},
{"gps:47.6062,-122.3321:0.001", 47606200, -122332100, 1},
};
GeoPosition position;
for (const auto& circle : circles) {
const std::string cli = std::string("fleet send 1 ") + circle.text + " get radio2";
rejected(node, cli.c_str(), 1);
assert(node.handleCommand(cli.c_str(), 0, reply));
assert(node.sent_policy == 2 && node.sent[12] == 1 && node.sent[13] == 0x22);
assert(int32_t(encoded32(node.sent + 14)) == circle.lat);
assert(int32_t(encoded32(node.sent + 18)) == circle.lon);
assert(encoded32(node.sent + 22) == circle.radius);
assert(node.sent_length == 78 + 13 + strlen("get radio2"));
mesh::FleetCommand::Decoded decoded;
assert(decodedGeo(node, position, decoded));
assert(decoded.broadcast && !strcmp(decoded.command, "get radio2"));
position.available = false;
assert(!decodedGeo(node, position, decoded));
position.available = true;
uint8_t recipient[32]; memset(recipient, 0x77, sizeof(recipient));
assert(!mesh::FleetCommand::decode(node.self_id, node.channels[1].channel.secret,
node.sent, node.sent_length, node.clock.now, recipient, decoded));
rejected(node, cli.c_str());
++node.clock.now;
}
} else if (scenario == "gps_mixed_targets") {
const std::string cli = "CD|fleet\tsend\t1\tsea;gps:47.6062,-122.3321:25;home:pdx;42424242\tclock sync";
rejected(node, cli.c_str(), 99);
assert(node.handleCommand(cli.c_str(), 0, reply));
assert(!strncmp(reply, "CD|OK - fleet command queued; seq=", 32));
assert(node.sent_policy == 2 && node.queued == 1 && node.sent[12] == 4);
assert(node.sent_length == 78 + 28 + strlen("clock sync"));
GeoPosition position; RegionMembership regions;
mesh::FleetCommand::Decoded decoded;
assert(decodedGeo(node, position, decoded, &regions)); // Seattle circle.
position.latitude_e6 = 47610100; position.longitude_e6 = -122201500;
assert(decodedGeo(node, position, decoded, &regions)); // Bellevue is inside25km.
position.latitude_e6 = 45523000; position.longitude_e6 = -122676000;
assert(!decodedGeo(node, position, decoded, &regions)); // Portland is outside.
regions.configured = {"sea"};
assert(decodedGeo(node, position, decoded, &regions)); // Configured-region OR.
regions.configured.clear(); regions.home_ancestors = {"suburb", "pdx"};
assert(decodedGeo(node, position, decoded, &regions)); // Home-ancestor OR.
regions.home_ancestors.clear(); position.available = false;
assert(decodedGeo(node, position, decoded, &regions, 0x42)); // Node-prefix OR.
assert(!decodedGeo(node, position, decoded, &regions));
rejected(node, cli.c_str());
} else if (scenario == "gps_packet_budgets") {
struct Boundary { const char* targets; size_t records; };
const Boundary boundaries[] = {
{"gps:47.6062,-122.3321:25", 13},
{"sea;gps:47.6062,-122.3321:25;home:pdx;42424242", 28},
};
const std::string prefix = "set flood.filter ";
GeoPosition position;
for (const auto& boundary : boundaries) {
const std::string cli = std::string("fleet send 1 ") + boundary.targets + " ";
const size_t maximum = mesh::FleetCommand::MaxCommandLength - boundary.records;
std::string command = prefix + std::string(maximum - prefix.size(), '1');
const size_t before = node.manager.outbound.size();
rejected(node, (cli + command + '1').c_str());
assert(node.handleCommand((cli + command + '1').c_str(), 0, reply));
assert(strstr(reply, "exceed two packets") && node.queued == before);
node.fail_queue = node.queue_calls + 2;
rejected(node, (cli + command).c_str());
assert(node.queued == before); // Neither half remains after second admission fails.
node.fail_queue = 0;
assert(node.handleCommand((cli + command).c_str(), 0, reply));
assert(node.sent_length == 308 && node.queued == before + 2 && node.sent_policy == 2);
checkedFragments(node, before, nullptr, nullptr, geoMatches, &position);
mesh::FleetCommand::Decoded decoded;
assert(decodedGeo(node, position, decoded) && !strcmp(decoded.command, command.c_str()));
++node.clock.now;
const size_t single = mesh::FleetCommand::SinglePacketMaxCommandLength - boundary.records;
command = prefix + std::string(single - prefix.size(), '1');
const size_t single_before = node.manager.outbound.size();
assert(node.handleCommand((cli + command).c_str(), 0, reply));
assert(node.sent_length == 165 && node.queued == single_before + 1);
assert(decodedGeo(node, position, decoded));
++node.clock.now; command += '1';
assert(node.handleCommand((cli + command).c_str(), 0, reply));
assert(node.sent_length == 166 && node.queued == single_before + 3);
checkedFragments(node, single_before + 1, nullptr, nullptr, geoMatches, &position);
++node.clock.now;
}
} else if (scenario == "gps_signature_binding") {
const std::string prefix = "set flood.filter "; GeoPosition position;
for (size_t length : {size_t(20), size_t(75)}) {
const std::string command = prefix + std::string(length - prefix.size(), '1');
const size_t before = node.manager.outbound.size();
assert(node.handleCommand(("fleet send 1 gps:47.6062,-122.3321:25 " + command).c_str(), 0, reply));
mesh::FleetCommand::Decoded decoded;
assert(decodedGeo(node, position, decoded));
if (length == 75) checkedFragments(node, before, nullptr, nullptr, geoMatches, &position);
for (size_t field : {size_t(14), size_t(18), size_t(22)}) {
node.sent[field] ^= 1; // Still valid and near the selected receiver.
assert(!decodedGeo(node, position, decoded));
node.sent[field] ^= 1;
assert(decodedGeo(node, position, decoded));
}
++node.clock.now;
}
} else if (scenario == "gps_invalid_targets") {
for (const char* target : {"gps:", "gps:47,-122", "gps:47,-122:", "gps:::-1",
"gps:47:-122:25", "gps:47,,122:25", "gps:47,-122:25;", ";gps:47,-122:25",
"sea;;gps:47,-122:25", "gps:47,-122:25;all", "all;gps:47,-122:25",
"gps:47,-122:25;clock", "gps:47,-122:25;sea;", "sea;gps:47;-122:25",
"gps:47,-122:25:1", "gps:91,-122:25", "gps:-91,-122:25", "gps:47,181:25",
"gps:47,-181:25", "gps:47,-122:0", "gps:47,-122:-1", "gps:47,-122:+1",
"gps:47,-122:0.0001", "gps:47,-122:20050.001", "gps:NaN,-122:25",
"gps:47,inf:25", "gps:47,-122:1e3", "gps:.5,-122:25", "gps:47.,-122:25",
"gps:47.6062001,-122:25", "gps:47,-122.3321001:25", "gps:047,-122:25",
"gps:47,-122:025", "gps:47,-122:429496729600000000000", "gps:47,-122:25,,sea",
"all,gps:47,-122:25", "gps:47,-122:25,all", "gps:47,-122:25,GG112233"}) {
rejected(node, (std::string("fleet send 1 ") + target + " clock").c_str());
assert(node.queued == 0);
}
std::string circles;
for (unsigned i = 0; i < 6; ++i) {
if (i) circles += ';';
circles += "gps:47.6062,-122.3321:25";
}
mesh::FleetCommand::Targets parsed;
assert(mesh::FleetCommand::parseTargets(circles.c_str(), circles.size(), parsed));
assert(parsed.count == 6 && parsed.length == 78);
rejected(node, ("fleet send 1 " + circles + ";gps:47,-122:25 clock").c_str());
assert(node.queued == 0); // Target-record overflow never signs a partial list.
assert(node.handleCommand(("fleet send 1 " + circles + " clock").c_str(), 0, reply));
assert(node.sent_length == 161 && node.queued == 1);
GeoPosition position; mesh::FleetCommand::Decoded decoded;
assert(decodedGeo(node, position, decoded) && !strcmp(decoded.command, "clock"));
} else if (scenario == "comma_target_lists_rejected") {
const std::string comma_lists[] = {
"42424242,515151515151", fullKey(0x42) + "," + fullKey(0x63),
"sea,pdx", "home:sea,home:pdx", "sea;home:pdx,region:sea",
"sea,gps:47.6062,-122.3321:25", "gps:47.6062,-122.3321:25,sea",
"gps:47.6062,-122.3321:25;sea,pdx",
"gps:47.6062,-122.3321:25,gps:45.5,-122.6:25",
};
for (const auto& targets : comma_lists) {
rejected(node, ("fleet send 1 " + targets + " clock").c_str());
assert(node.queued == 0);
}
assert(node.handleCommand("fleet send 1 sea;gps:47.6062,-122.3321:25;home:pdx;42424242 clock", 0, reply));
assert(node.queued == 1 && node.sent[12] == 4);
GeoPosition position; mesh::FleetCommand::Decoded decoded;
assert(decodedGeo(node, position, decoded) && !strcmp(decoded.command, "clock"));
} else if (scenario == "explicit_target_forms") {
for (const std::string& targets : {std::string("42424242"), std::string("424242424242"),
fullKey(0x42), std::string("42424242;515151515151;") + fullKey(0x63)}) {
assert(node.handleCommand(("fleet send 1 " + targets + " set radio2 off").c_str(), 0, reply));
uint8_t recipient[32]; memset(recipient, 0x42, sizeof(recipient));
mesh::FleetCommand::Decoded decoded;
assert(mesh::FleetCommand::decode(node.self_id, node.channels[1].channel.secret,
node.sent, node.sent_length, node.clock.now, recipient, decoded));
assert(!strcmp(decoded.command, "set radio2 off"));
// Prefixes and lists spread replies like broadcasts because they may
// select several receivers, but a nonmatching node still cannot act.
assert(decoded.broadcast == (targets.size() != 64));
memset(recipient, 0x77, sizeof(recipient));
assert(!mesh::FleetCommand::decode(node.self_id, node.channels[1].channel.secret,
node.sent, node.sent_length, node.clock.now, recipient, decoded));
assert(node.sent_policy == 2);
++node.clock.now;
}
assert(node.queued == 4);
} else if (scenario == "syntax") {
for (const char* command : {"fleet", "fleet send", "fleet sender 1 all set radio2 off",
"fleet send -1 all set radio2 off", "fleet send 4 all set radio2 off",
"fleet send 4294967296 all set radio2 off", "fleet send 1x all set radio2 off",
"fleet send 1 all", "fleet send 1 a set radio2 off",
"fleet send 1 11111111111111 set radio2 off", "fleet send 1 all get password",
"fleet send 1 all set radio2 off\nreboot"}) rejected(node, command);
rejected(node, ("fleet send 1 " + std::string(65, '1') + " set radio2 off").c_str());
rejected(node, ("fleet send 1 all set flood.filter " + std::string(mesh::FleetCommand::MaxCommandLength, '1')).c_str());
assert(node.handleCommand("fleet\tsend\t1\tall\tset radio2 off", 0, reply));
assert(!strncmp(reply, "OK - fleet command queued", 25));
} else if (scenario == "private_channel") {
rejected(node, "fleet send 0 all set radio2 off");
memset(node.channels[1].channel.secret, 0, 32);
rejected(node, "fleet send 1 all set radio2 off");
const uint8_t public_key[16] = {0x8b,0x33,0x87,0xe9,0xc5,0xcd,0xea,0x6a,
0xc9,0xe5,0xed,0xba,0xa1,0x15,0xcd,0x72};
memcpy(node.channels[1].channel.secret, public_key, sizeof(public_key));
strcpy(node.channels[1].name, "renamed public");
rejected(node, "fleet send 1 all set radio2 off");
memset(node.channels[1].channel.secret, 0x21, 32);
rejected(node, "fleet send 1 all set radio2 off");
memset(node.channels[1].channel.secret + 16, 0, 16);
strcpy(node.channels[1].name, "Public"); // Names are not key authorization.
assert(node.handleCommand("fleet send 1 all set radio2 off", 0, reply));
assert(node.queued == 1);
} else if (scenario == "queue_sequence") {
node.queue_ok = false;
rejected(node, "fleet send 1 all set radio2 off");
node.queue_ok = true;
assert(node.handleCommand("fleet send 1 all set radio2 off", 0, reply));
assert(node.queued == 1);
rejected(node, "fleet send 1 all set radio2 off");
++node.clock.now;
assert(node.handleCommand("fleet send 1 all set radio2 off", 0, reply));
assert(node.queued == 2);
--node.clock.now;
rejected(node, "fleet send 1 all set radio2 off");
} else if (scenario == "clock") {
node.clock.now = 0;
rejected(node, "fleet send 1 all set radio2 off");
node.clock.now = mesh::FleetCommand::MinEpoch - 1;
rejected(node, "fleet send 1 all set radio2 off");
node.clock.now = UINT32_MAX;
rejected(node, "fleet send 1 all set radio2 off");
node.clock.now = mesh::FleetCommand::MinEpoch;
assert(node.handleCommand("fleet send 1 all set radio2 off", 0, reply));
assert(node.queued == 1);
} else if (scenario == "signed_target") {
const std::string command = "fleet send 1 " + fullKey(0x42)
+ " set tempradio2 910.5,500,5,5,rxtx,1";
assert(node.handleCommand(command.c_str(), 0, reply));
assert(node.queued == 1);
assert(!memcmp(node.sent, "FMC1", 4)); // One full key keeps the original envelope.
uint8_t recipient[32]; memset(recipient, 0x42, sizeof(recipient));
mesh::FleetCommand::Decoded decoded;
assert(mesh::FleetCommand::decode(node.self_id, node.channels[1].channel.secret,
node.sent, node.sent_length, node.clock.now, recipient, decoded));
assert(decoded.sequence == node.clock.now);
assert(decoded.expires == node.clock.now + mesh::FleetCommand::MaxLifetime);
assert(!strcmp(decoded.command, "set tempradio2 910.5,500,5,5,rxtx,1"));
recipient[31] ^= 1;
assert(!mesh::FleetCommand::decode(node.self_id, node.channels[1].channel.secret,
node.sent, node.sent_length, node.clock.now, recipient, decoded));
} else if (scenario == "mixed_targets") {
const std::string command = "fleet send 1 42424242;515151515151;" + fullKey(0x63)
+ " set radio2 off";
assert(node.handleCommand(command.c_str(), 0, reply));
assert(node.queued == 1 && node.sent_policy == 2);
assert(!memcmp(node.sent, "FMC2", 4));
mesh::FleetCommand::Decoded decoded;
uint8_t recipient[32];
auto matched = [&]() {
return mesh::FleetCommand::decode(node.self_id, node.channels[1].channel.secret,
node.sent, node.sent_length, node.clock.now, recipient, decoded);
};
memset(recipient, 0x42, sizeof(recipient));
assert(matched());
assert(!strcmp(decoded.command, "set radio2 off"));
recipient[31] ^= 1;
assert(matched()); // An eight-hex target selects the four-byte prefix.
recipient[3] ^= 1;
assert(!matched());
memset(recipient, 0x51, sizeof(recipient));
assert(matched());
recipient[31] ^= 1;
assert(matched()); // Twelve hex selects all six specified prefix bytes.
recipient[5] ^= 1;
assert(!matched());
memset(recipient, 0x63, sizeof(recipient));
assert(matched());
recipient[31] ^= 1;
assert(!matched()); // Full keys retain the complete-key fingerprint.
memset(recipient, 0x77, sizeof(recipient));
assert(!matched());
} else if (scenario == "multiple_full_targets") {
const std::string command = "fleet send 1 " + fullKey(0x42) + ";" + fullKey(0x63)
+ " set radio2 off";
assert(node.handleCommand(command.c_str(), 0, reply));
assert(node.queued == 1);
assert(!memcmp(node.sent, "FMC2", 4));
mesh::FleetCommand::Decoded decoded;
uint8_t recipient[32];
for (uint8_t value : {0x42, 0x63}) {
memset(recipient, value, sizeof(recipient));
assert(mesh::FleetCommand::decode(node.self_id, node.channels[1].channel.secret,
node.sent, node.sent_length, node.clock.now, recipient, decoded));
recipient[31] ^= 1;
assert(!mesh::FleetCommand::decode(node.self_id, node.channels[1].channel.secret,
node.sent, node.sent_length, node.clock.now, recipient, decoded));
}
} else if (scenario == "invalid_target_lists") {
for (const char* targets : {"all;00112233", "00112233;all", "00112233;;44556677",
";00112233", "00112233;", "0011223", "001122334", "0011223344556",
"GG112233", "00112233;44556677889Z"}) {
rejected(node, (std::string("fleet send 1 ") + targets + " set radio2 off").c_str());
}
assert(node.handleCommand("fleet send 1 00112233 set radio2 off", 0, reply));
assert(node.queued == 1); // Invalid lists consumed no sequence or partial send.
} else if (scenario == "target_capacity") {
std::string targets;
for (unsigned i = 1; i <= 17; ++i) {
char prefix[9]; snprintf(prefix, sizeof(prefix), "%08x", i);
if (!targets.empty()) targets += ';';
targets += prefix;
}
mesh::FleetCommand::Targets parsed;
assert(mesh::FleetCommand::parseTargets(targets.c_str(), targets.size(), parsed));
const std::string prefix = "set flood.filter ";
const std::string too_long = prefix + std::string(mesh::FleetCommand::MaxCommandLength - prefix.size(), '1');
assert(node.handleCommand(("fleet send 1 " + targets + " " + too_long).c_str(), 0, reply));
assert(strstr(reply, "exceed two packets") && node.queued == 0);
assert(node.handleCommand(("fleet send 1 " + targets + " set radio2 off").c_str(), 0, reply));
assert(node.queued == 2); // The valid list now fits one atomic two-packet envelope.
++node.clock.now;
targets += ";44556677"; // More than the entire target-record capacity.
rejected(node, ("fleet send 1 " + targets + " set radio2 off").c_str());
assert(node.queued == 2);
} else if (scenario == "scheduled_sets") {
const char* relative[] = {
"set radioat 910.5,500,5,5,+1,auto",
"set tempradioat 910.5,500,5,5,+1,+2,32",
"set radioat2 910.5,500,5,5,rxtx,+1,auto",
"set tempradioat2 910.5,500,5,5,rx,+1,+2,32",
};
uint8_t recipient[32]; memset(recipient, 0x42, sizeof(recipient));
auto sendAndDecode = [&](const std::string& scheduled) {
const std::string command = "fleet send 1 all " + scheduled;
rejected(node, command.c_str(), 1);
const unsigned before = node.queued;
assert(node.handleCommand(command.c_str(), 0, reply));
assert(!strncmp(reply, "OK - fleet command queued", 25));
assert(node.queued == before + 1 && node.sent_policy == 2);
mesh::FleetCommand::Decoded decoded;
assert(mesh::FleetCommand::decode(node.self_id, node.channels[1].channel.secret,
node.sent, node.sent_length, node.clock.now, recipient, decoded));
assert(!strcmp(decoded.command, scheduled.c_str()));
assert(decoded.sequence == node.clock.now);
++node.clock.now;
};
for (const char* scheduled : relative) sendAndDecode(scheduled);
const std::string start = std::to_string(node.clock.now + 60);
const std::string end = std::to_string(node.clock.now + 120);
sendAndDecode("set radioat 910.5,500,5,5," + start);
sendAndDecode("set tempradioat 910.5,500,5,5," + start + "," + end);
sendAndDecode("set radioat2 910.5,500,5,5,rx," + start);
sendAndDecode("set tempradioat2 910.5,500,5,5,rxtx," + start + "," + end);
assert(node.queued == 8);
} else if (scenario == "schedule_get_delete") {
uint8_t recipient[32]; memset(recipient, 0x42, sizeof(recipient));
for (const char* scheduled : {
"get radioat", "get radioat 1", "get radioat 255", "get radioat all",
"get tempradioat", "get tempradioat 2", "get tempradioat all",
"get radioat2", "get radioat2 4", "get radioat2 all",
"get tempradioat2", "get tempradioat2 1", "get tempradioat2 all",
"del radioat", "del radioat 1", "del radioat all",
"del tempradioat", "del tempradioat 2", "del tempradioat all",
"del radioat2", "del radioat2 4", "del radioat2 all",
"del tempradioat2", "del tempradioat2 1", "del tempradioat2 all"}) {
const std::string command = "fleet send 1 42424242 " + std::string(scheduled);
rejected(node, command.c_str(), 100);
const unsigned before = node.queued;
assert(node.handleCommand(command.c_str(), 0, reply));
assert(node.queued == before + 1 && node.sent_policy == 2);
mesh::FleetCommand::Decoded decoded;
assert(mesh::FleetCommand::decode(node.self_id, node.channels[1].channel.secret,
node.sent, node.sent_length, node.clock.now, recipient, decoded));
assert(!strcmp(decoded.command, scheduled));
++node.clock.now;
}
} else if (scenario == "malformed_schedules") {
for (const char* scheduled : {
"set radioat 910.5,500,5,5", "set tempradioat 910.5,500,5,5,+1",
"set radioat2 910.5,500,5,5,rx", "set tempradioat2 910.5,500,5,5,rxtx,+1",
"set radioat 910.5,500,5,5,+0", "set radioat 910.5,500,5,5,-1",
"set radioat 910.5,500,5,5,+1.5", "set radioat 910.5,500,5,5,4294967296",
"set radioat2 910.5,500,5,5,bogus,+1",
"set tempradioat2 910.5,500,5,5,rx,+1,+2,auto,extra",
"set tempradioat 910.5,500,5,5,+1,+2,7",
"set radioat 910.5,500,5,5,+1,65529",
"get radioat 256", "get radioat2 5", "get radioat 01", "get radioat 1x",
"del tempradioat2 0", "del radioat2 all extra", "radioat 910.5,500,5,5,+1"}) {
rejected(node, ("fleet send 1 all " + std::string(scheduled)).c_str());
}
assert(node.handleCommand("fleet send 1 all set radioat 910.5,500,5,5,+1", 0, reply));
assert(node.queued == 1);
} else if (scenario == "schedule_packet_budget") {
const std::string scheduled = "set tempradioat2 910.5,500,5,5,rxtx,+1,+2,auto";
std::string targets = fullKey(0x42) + ";" + fullKey(0x51) + ";"
+ fullKey(0x63) + ";" + fullKey(0x74);
mesh::FleetCommand::Targets parsed;
assert(mesh::FleetCommand::parseTargets(targets.c_str(), targets.size(), parsed));
assert(mesh::FleetCommand::commandAllowed(scheduled.c_str()));
assert(node.handleCommand(("fleet send 1 " + targets + " " + scheduled).c_str(), 0, reply));
assert(!strncmp(reply, "OK - fleet command queued", 25) && node.queued == 2);
uint8_t recipient[32]; memset(recipient, 0x42, sizeof(recipient));
mesh::FleetCommand::Decoded decoded;
assert(mesh::FleetCommand::decode(node.self_id, node.channels[1].channel.secret,
node.sent, node.sent_length, node.clock.now, recipient, decoded));
assert(!strcmp(decoded.command, scheduled.c_str()));
++node.clock.now;
assert(node.handleCommand(("fleet send 1 42424242 " + scheduled).c_str(), 0, reply));
assert(node.queued == 3 && node.sent_policy == 2);
} else if (scenario == "clock_controls") {
uint8_t recipient[32]; memset(recipient, 0x42, sizeof(recipient));
for (const char* clock_command : {"clock", "clock sync", "time 1800000000"}) {
const std::string command = "fleet send 1 all " + std::string(clock_command);
rejected(node, command.c_str(), 1);
const unsigned before = node.queued;
assert(node.handleCommand(command.c_str(), 0, reply));
assert(node.queued == before + 1 && node.sent_policy == 2);
mesh::FleetCommand::Decoded decoded;
assert(mesh::FleetCommand::decode(node.self_id, node.channels[1].channel.secret,
node.sent, node.sent_length, node.clock.now, recipient, decoded));
assert(!strcmp(decoded.command, clock_command));
++node.clock.now;
}
for (const char* clock_command : {"get clock", "set clock", "clock synctime",
"clock sync extra", "time", "time -1", "time +1800000000", "time NaN",
"time 4294967296", "time 1735689599", "time 1800000000 extra"})
rejected(node, ("fleet send 1 all " + std::string(clock_command)).c_str());
} else assert(false);
printf("Companion fleet scenario %s passed\n", argv[1]);
}
'''
class CompanionFleetControlTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
compiler = shutil.which("g++") or shutil.which("clang++")
if compiler is None:
raise unittest.SkipTest("host C++ compiler required")
cls.compiler = compiler
source = SOURCE.read_text()
function = body(source, "bool MyMesh::handleCommand(const char* command,")
# Retain the real NULL, whitespace and request-prefix handling too.
prefix = function[:function.index("#if COMPANION_FEATURE_READER")]
fleet_start = function.rindex("#if MESH_ENABLE_FLEET_CONTROL", 0,
function.index('if (!strncmp(command, "fleet", 5)'))
fleet_end = function.index(" if (mesh::handleMorseReplyCommand", fleet_start)
# Keep the actual compile guard as well as its complete branch so
# compact profiles prove that none of the fleet dependencies survive.
cls.production = prefix + function[fleet_start:fleet_end] + "\n return false;\n}\n"
cls.work = tempfile.TemporaryDirectory(prefix="meshcore-fleet-companion-")
work = Path(cls.work.name)
cls.binary = work / "companion-fleet"
fixture = work / "fixture.cpp"
helpers = "\n".join(body(source, signature) for signature in (
"void MyMesh::onRadioProfileCopyQueued(", "bool MyMesh::sendFleetCommandData(",
))
send_packet = body((ROOT / "src/Dispatcher.cpp").read_text(), "bool Dispatcher::sendPacket(")
send_packet = send_packet.replace("bool Dispatcher::sendPacket(Packet*", "bool MyMesh::sendPacket(mesh::Packet*", 1)
rescue = body(source, "void MyMesh::checkCLIRescueCmd()")
# Keep the entire production reader/line-admission logic. Replace only
# downstream rescue commands (filesystem/UI peripherals) with a record
# and the real Companion fleet dispatcher used throughout this suite.
rescue = rescue[:rescue.index(" reply_buf[0] = 0;")]
# Print::println(text) is absent from the shared host Stream adapter.
# Preserve STM32's actual shared error text and adapt only printing.
rescue = rescue.replace('output.println(" Error: unknown command");',
'output.print(" Error: unknown command"); output.println();')
rescue += '''
rescue_commands.emplace_back(cli_command);
char reply[160] = {};
handleCommand(cli_command, 0, reply);
cli_command[0] = 0;
}
}
'''
reset = body(source, "void MyMesh::resetUsbHostSessionInput()")
enter = body(source, "void MyMesh::enterCLIRescue()")
# The shared Stream mock lacks Print::println(text); preserve the
# actual entry-state reset and adapt only this banner's formatting.
banner = 'mesh::usbTerminalPort().println("========= CLI Rescue =========");'
assert enter.count(banner) == 1
enter = enter.replace(banner, 'mesh::usbTerminalPort().print("========= CLI Rescue =========\\n");')
cls.fixture = fixture
fixture.write_text(HARNESS.replace("@METHOD@", cls.production)
.replace("@SEND_HELPERS@", send_packet + "\n" + helpers)
.replace("@RESCUE_READER@", rescue + "\n" + reset + "\n" + enter))
sanitizer_flags = (["-fsanitize=address,undefined", "-fno-omit-frame-pointer", "-no-pie"]
if os.environ.get("MESHCORE_FLEET_SANITIZERS") == "1" else [])
cls.sanitizer_flags = sanitizer_flags
result = subprocess.run([
compiler, "-std=c++17", "-Wall", "-Wextra", "-Werror",
"-Wno-unused-parameter", "-O2", *sanitizer_flags,
"-fstack-usage", "-isystem", str(ROOT / "test/mocks"),
"-I", str(ROOT / "src"), str(fixture),
str(ROOT / "src/helpers/FleetCommand.cpp"), "-lcrypto", "-o", str(cls.binary),
], capture_output=True, text=True, timeout=60)
if result.returncode:
cls.work.cleanup()
raise AssertionError(result.stdout + result.stderr)
@classmethod
def tearDownClass(cls):
cls.work.cleanup()
def scenario(self, name):
result = subprocess.run([str(self.binary), name], capture_output=True,
text=True, timeout=10)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
def test_local_client_boundary_and_channel_tx_route(self):
self.scenario("local_boundary")
def test_omitted_target_and_explicit_all_emit_compact_signed_broadcasts(self):
self.scenario("implicit_broadcast")
def test_implicit_broadcast_requires_local_client_and_preserves_prefix_and_tabs(self):
self.scenario("implicit_local_prefix_tabs")
def test_implicit_and_explicit_all_share_queue_retry_and_same_second_replay_guard(self):
self.scenario("implicit_retry_sequence")
def test_unknown_or_malformed_explicit_targets_never_fall_back_to_broadcast(self):
self.scenario("implicit_target_ambiguity")
def test_omitted_target_requires_canonical_allowlisted_command_and_230_byte_cap(self):
self.scenario("implicit_command_controls")
def test_target_specific_exact_packet_limits_reject_atomically_and_allow_retry(self):
self.scenario("target_command_capacity")
def test_maximum_correlated_implicit_command_fits_app_frame_and_remains_local_only(self):
self.scenario("maximum_correlated_app_command")
def test_one_packet_boundary_automatically_becomes_two_signed_fragments(self):
self.scenario("fragment_boundaries")
def test_two_part_admission_reserves_pool_and_rolls_back_exact_packets_and_radio_copy(self):
self.scenario("fragment_atomic_admission")
def test_rescue_overflow_discards_full_fleet_rule_and_preserves_next_crlf_command(self):
self.scenario("rescue_overflow")
def test_rescue_overflow_drains_bounded_chunks_and_never_executes_valid_suffix(self):
self.scenario("rescue_split_overflow")
def test_rescue_exact_capacity_waits_for_real_terminator_and_keeps_lines_separate(self):
self.scenario("rescue_boundaries")
def test_rescue_unterminated_buffer_is_bounded_and_discards_remainder(self):
self.scenario("rescue_unterminated")
def test_rescue_entry_and_usb_host_recovery_reset_discard_state(self):
self.scenario("rescue_session_reset")
def test_stm32_rescue_shared_error_still_drains_and_rejects_entire_overflow(self):
binary = Path(self.work.name) / "companion-stm32-rescue"
built = subprocess.run([
self.compiler, "-std=c++17", "-Wall", "-Wextra", "-Werror",
"-Wno-unused-parameter", "-DSTM32_PLATFORM=1", "-O2", *self.sanitizer_flags,
"-isystem", str(ROOT / "test/mocks"), "-I", str(ROOT / "src"),
str(self.fixture), str(ROOT / "src/helpers/FleetCommand.cpp"),
"-lcrypto", "-o", str(binary),
], capture_output=True, text=True, timeout=60)
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
result = subprocess.run([str(binary), "stm32_rescue"],
capture_output=True, text=True, timeout=10)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
def test_bare_and_explicit_region_targets_preserve_distinct_configured_and_home_modes(self):
self.scenario("region_target_modes")
def test_mixed_regions_home_and_node_prefix_are_signed_without_broadcast_fallback(self):
self.scenario("region_mixed_targets")
def test_region_records_share_exact_one_and_two_packet_budgets_and_allow_safe_retry(self):
self.scenario("region_packet_budgets")
def test_region_name_and_configured_or_home_selector_are_signature_bound(self):
self.scenario("region_signature_binding")
def test_invalid_region_and_keylike_lists_reject_atomically_without_consuming_sequence(self):
self.scenario("region_invalid_targets")
def test_rescue_overflow_never_executes_truncated_region_target_list(self):
self.scenario("rescue_region_overflow")
def test_signed_gps_circle_uses_exact_fixed_point_coordinates_and_requires_location(self):
self.scenario("gps_target")
def test_gps_region_home_and_node_targets_form_one_authorized_or_list(self):
self.scenario("gps_mixed_targets")
def test_gps_exact_packet_budgets_reject_and_roll_back_without_consuming_sequence(self):
self.scenario("gps_packet_budgets")
def test_gps_latitude_longitude_and_radius_are_bound_by_real_signature(self):
self.scenario("gps_signature_binding")
def test_malformed_gps_and_overcapacity_lists_never_sign_or_queue_partial_requests(self):
self.scenario("gps_invalid_targets")
def test_only_semicolons_separate_targets_and_gps_coordinate_comma_is_not_a_list(self):
self.scenario("comma_target_lists_rejected")
def test_explicit_8_12_64_hex_and_list_targets_remain_restricted(self):
self.scenario("explicit_target_forms")
def test_invalid_syntax_targets_and_forbidden_commands(self):
self.scenario("syntax")
def test_private_128_bit_key_required_even_if_channel_is_renamed(self):
self.scenario("private_channel")
def test_queue_failure_does_not_consume_sequence_and_success_does(self):
self.scenario("queue_sequence")
def test_invalid_clock_and_timestamp_overflow_never_queue(self):
self.scenario("clock")
def test_production_encode_targets_full_key_and_sets_bounded_expiry(self):
self.scenario("signed_target")
def test_mixed_prefix_and_full_key_list_matches_only_selected_nodes(self):
self.scenario("mixed_targets")
def test_multiple_full_keys_share_one_signed_packet(self):
self.scenario("multiple_full_targets")
def test_invalid_lists_reject_atomically_without_consuming_sequence(self):
self.scenario("invalid_target_lists")
def test_valid_list_exceeding_two_packet_capacity_never_partially_sends(self):
self.scenario("target_capacity")
def test_four_schedule_set_forms_are_signed_without_rewriting(self):
self.scenario("scheduled_sets")
def test_schedule_get_and_delete_forms_preserve_access_and_tx_route(self):
self.scenario("schedule_get_delete")
def test_malformed_and_truncated_schedules_never_queue_or_consume_sequence(self):
self.scenario("malformed_schedules")
def test_schedule_and_target_list_share_one_packet_budget(self):
self.scenario("schedule_packet_budget")
def test_exact_clock_controls_are_signed_and_remote_origin_is_denied(self):
self.scenario("clock_controls")
def test_disabled_profiles_require_no_sender_peripherals_or_crypto_link(self):
harness = r'''
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <helpers/FleetCommand.h>
static_assert(MESH_ENABLE_FLEET_CONTROL == 0, "compact profile must disable fleet");
// No channels, clock, signing identity, codec definitions or sendGroupData.
class MyMesh {
public:
bool handleCommand(const char*, uint32_t, char*);
};
@METHOD@
int main() {
MyMesh node;
char reply[160] = {};
assert(!node.handleCommand("fleet send 1 all set radio2 off", 0, reply));
assert(!node.handleCommand("fleet send 1 all set radio2 off", 1, reply));
assert(!node.handleCommand("AB|fleet send 1 all set radio2 off", 0, reply));
}
'''
work = Path(self.work.name)
source = work / "disabled.cpp"
source.write_text(harness.replace("@METHOD@", self.production))
for profile, flags in (("stm32-default", ["-DSTM32_PLATFORM=1"]),
("explicit-disable", ["-DMESH_ENABLE_FLEET_CONTROL=0"])):
with self.subTest(profile=profile):
executable = work / profile
built = subprocess.run([
self.compiler, "-std=c++17", "-Wall", "-Wextra", "-Werror",
"-Wno-unused-parameter", "-isystem", str(ROOT / "test/mocks"),
"-I", str(ROOT / "src"), *flags, str(source), "-o", str(executable),
], capture_output=True, text=True, timeout=60)
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
result = subprocess.run([str(executable)], capture_output=True,
text=True, timeout=10)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
if __name__ == "__main__":
unittest.main()