Support atomic two-packet signed fleet commands

Automatically split longer signed envelopes into two bounded group-data packets, reserve both before queueing, and roll back exact packet copies and retries on admission failure. Collect fragments in either order with a fixed deadline and verify the complete command before durable replay reservation and dispatch.

Raise whole-fleet protocol capacity to 230 bytes, preserve target-specific budgets and radio acknowledgement barriers, and reject rule-token and USB rescue-line truncation. Document existing app input limits and add real-signature, sanitizer, queue failure, timeout, replay, and compact-board regressions.
This commit is contained in:
mikecarper committed 2026-10-09 00:46:50 -07:00
1 parent 90783fbd50
commit 8b3655d76a
18 files changed
+1649 -143

No files matched your search

+47 -7
View File
@@ -7,11 +7,16 @@ namespace {
constexpr char Domain[] = "MeshCoreFleet1";
constexpr size_t SignedMessageCapacity = sizeof(Domain) - 1
+ FleetCommand::KeySize + FleetCommand::MaxPayloadLength
+ FleetCommand::KeySize + FleetCommand::MaxEnvelopeLength
- FleetCommand::SignatureSize;
static_assert(FleetCommand::HeaderSize + FleetCommand::MaxCommandLength
+ FleetCommand::SignatureSize <= FleetCommand::MaxPayloadLength,
"Legacy fleet envelope must fit the shared packet budget");
static_assert(FleetCommand::HeaderSize + FleetCommand::FixedTargetMaxCommandLength
+ FleetCommand::SignatureSize <= FleetCommand::MaxEnvelopeLength,
"Fixed-target fleet envelope must fit the two-packet budget");
static_assert(FleetCommand::MinHeaderSize + FleetCommand::MaxCommandLength
+ FleetCommand::SignatureSize <= FleetCommand::MaxEnvelopeLength,
"Compact fleet envelope must fit the two-packet budget");
static_assert(FleetCommand::MaxCommandLength <= UINT8_MAX,
"Fleet command length must fit the signed length byte");
constexpr uint8_t PublicKey[FleetCommand::KeySize] = {
0x8b, 0x33, 0x87, 0xe9, 0xc5, 0xcd, 0xea, 0x6a,
0xc9, 0xe5, 0xed, 0xba, 0xa1, 0x15, 0xcd, 0x72
@@ -355,7 +360,7 @@ size_t FleetCommand::encode(const LocalIdentity& publisher, const uint8_t* key,
const size_t command_length = strlen(command);
const size_t unsigned_length = HeaderSize + command_length;
const size_t length = unsigned_length + SignatureSize;
if (capacity < length) return 0;
if (length > MaxEnvelopeLength || capacity < length) return 0;
memcpy(output, "FMC1", 4);
write32(output + 4, sequence);
write32(output + 8, expires);
@@ -389,7 +394,7 @@ size_t FleetCommand::encode(const LocalIdentity& publisher, const uint8_t* key,
const size_t command_length = strlen(command);
const size_t unsigned_length = MinHeaderSize + targets.length + command_length;
const size_t length = unsigned_length + SignatureSize;
if (length > MaxPayloadLength || capacity < length) return 0;
if (length > MaxEnvelopeLength || capacity < length) return 0;
memcpy(output, "FMC2", 4);
write32(output + 4, sequence);
write32(output + 8, expires);
@@ -411,7 +416,7 @@ bool FleetCommand::decode(const Identity& publisher, const uint8_t* key,
if (!privateKeyAllowed(key) || !payload || !self_public_key
|| zero(publisher.pub_key, PUB_KEY_SIZE) || zero(self_public_key, PUB_KEY_SIZE)
|| now < MinEpoch || length < MinHeaderSize + SignatureSize
|| length > MaxPayloadLength) return false;
|| length > MaxEnvelopeLength) return false;
const size_t unsigned_length = length - SignatureSize;
size_t command_offset;
bool broadcast;
@@ -458,4 +463,39 @@ bool FleetCommand::decode(const Identity& publisher, const uint8_t* key,
return true;
}
size_t FleetCommand::fragment(const uint8_t* envelope, size_t length, uint8_t index,
uint8_t* output, size_t capacity) {
if (!envelope || !output || length <= MaxPayloadLength
|| length > MaxEnvelopeLength || index > 1
|| (memcmp(envelope, "FMC1", 4) && memcmp(envelope, "FMC2", 4))) return 0;
const size_t offset = size_t(index) * FragmentDataLength;
const size_t chunk_length = index ? length - offset : FragmentDataLength;
const size_t fragment_length = FragmentHeaderSize + chunk_length;
if (capacity < fragment_length) return 0;
memcpy(output, "FMP1", 4);
memcpy(output + 4, envelope + 4, 4); // The receiver checks this against signed UTC sequence.
output[8] = uint8_t(length);
output[9] = uint8_t(length >> 8);
output[10] = index;
memcpy(output + FragmentHeaderSize, envelope + offset, chunk_length);
return fragment_length;
}
bool FleetCommand::parseFragment(const uint8_t* payload, size_t length, Fragment& output) {
output = {};
if (!payload || length < FragmentHeaderSize || length > MaxPayloadLength
|| memcmp(payload, "FMP1", 4)) return false;
const uint16_t total = uint16_t(payload[8]) | (uint16_t(payload[9]) << 8);
const uint8_t index = payload[10];
if (total <= MaxPayloadLength || total > MaxEnvelopeLength || index > 1) return false;
const size_t chunk_length = index ? total - FragmentDataLength : FragmentDataLength;
if (length != FragmentHeaderSize + chunk_length) return false;
output.sequence = read32(payload + 4);
output.total_length = total;
output.index = index;
output.data = payload + FragmentHeaderSize;
output.length = chunk_length;
return true;
}
} // namespace mesh