#!/usr/bin/env python3 """Execute production fleet receiver/storage/crypto boundaries with host mocks. FleetChannel.cpp and FleetCommand.cpp are compiled unchanged. Only hardware, packet admission, clock and filesystem I/O are mocked. Ed25519 signatures and SHA-256 use real implementations; AES encryption/MAC are supplied by OpenSSL. The real RP2040 ContactFileTransaction and nRF52 AtomicFileWriter validate and publish persisted state using their actual platform-specific implementations. """ 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] UTILS = r''' #pragma once #include #include #include #include #include #include #include #include #define PUB_KEY_SIZE 32 #define PRV_KEY_SIZE 64 #define SIGNATURE_SIZE 64 #define PATH_HASH_SIZE 1 #define CIPHER_MAC_SIZE 2 #define CIPHER_BLOCK_SIZE 16 inline unsigned hash_calls = 0, decrypt_calls = 0, verify_calls = 0, last_jitter_max = 0; inline int jitter_pick_boundary = 0; namespace mesh { class RNG { public: uint32_t nextInt(uint32_t low, uint32_t high) { assert(low == 500 && (high == 10000 || high == 1500)); last_jitter_max=high; return jitter_pick_boundary < 0 ? low : jitter_pick_boundary > 0 ? high : 1000; }}; class Utils { public: static void sha256(uint8_t* out, size_t count, const uint8_t* data, int length) { ++hash_calls; SHA256 hash; hash.update(data, length); hash.finalize(out, count); } static void toHex(char* out, const uint8_t* data, size_t length) { for (size_t i = 0; i < length; ++i) sprintf(out + 2*i, "%02x", data[i]); out[2*length] = 0; } static bool fromHex(uint8_t* out, int length, const char* hex) { if (strlen(hex) != size_t(2*length)) return false; for (int i=0;i='0'&&c<='9'?c-'0':c>='a'&&c<='f'?c-'a'+10: c>='A'&&c<='F'?c-'A'+10:-1; }; int a=digit(hex[2*i]), b=digit(hex[2*i+1]); if(a<0||b<0) return false; out[i]=uint8_t(a*16+b); } return true; } static int encryptThenMAC(const uint8_t* key, uint8_t* out, const uint8_t* data, int size) { uint8_t padded[256] = {}; const int rounded = (size+15)/16*16; assert(rounded <= int(sizeof(padded))); memcpy(padded,data,size); EVP_CIPHER_CTX* ctx=EVP_CIPHER_CTX_new(); assert(ctx); assert(EVP_EncryptInit_ex(ctx,EVP_aes_128_ecb(),nullptr,key,nullptr)); EVP_CIPHER_CTX_set_padding(ctx,0); int produced=0, tail=0; assert(EVP_EncryptUpdate(ctx,out+CIPHER_MAC_SIZE,&produced,padded,rounded)); assert(EVP_EncryptFinal_ex(ctx,out+CIPHER_MAC_SIZE+produced,&tail)); EVP_CIPHER_CTX_free(ctx); assert(produced+tail==rounded); uint8_t mac[32]; unsigned mac_len=0; assert(HMAC(EVP_sha256(),key,32,out+CIPHER_MAC_SIZE,rounded,mac,&mac_len)); memcpy(out,mac,CIPHER_MAC_SIZE); return rounded+CIPHER_MAC_SIZE; } static int MACThenDecrypt(const uint8_t* key,uint8_t* out,const uint8_t* data,int size) { ++decrypt_calls; int cipher_size=size-CIPHER_MAC_SIZE; if(cipher_size<=0 || cipher_size%16) return 0; uint8_t mac[32]; unsigned mac_len=0; assert(HMAC(EVP_sha256(),key,32,data+CIPHER_MAC_SIZE,cipher_size,mac,&mac_len)); if(memcmp(mac,data,CIPHER_MAC_SIZE)) return 0; EVP_CIPHER_CTX* ctx=EVP_CIPHER_CTX_new(); assert(ctx); assert(EVP_DecryptInit_ex(ctx,EVP_aes_128_ecb(),nullptr,key,nullptr)); EVP_CIPHER_CTX_set_padding(ctx,0); int produced=0,tail=0; assert(EVP_DecryptUpdate(ctx,out,&produced,data+CIPHER_MAC_SIZE,cipher_size)); assert(EVP_DecryptFinal_ex(ctx,out+produced,&tail)); EVP_CIPHER_CTX_free(ctx); return produced+tail; } }; } ''' FILESYSTEM = r''' #pragma once #include #include #include #include #include #include namespace fs { class FS; } class File { fs::FS* owner_=nullptr; std::string path_; size_t cursor_=0; bool live_=false; public: File() = default; File(fs::FS* owner,const char* path,bool live):owner_(owner),path_(path),live_(live){} explicit File(fs::FS& owner):owner_(&owner){} bool open(const char* path,int flags); explicit operator bool() const { return live_; } bool isDirectory() const { return false; } size_t size() const; size_t read(uint8_t* data,size_t length); size_t write(const uint8_t* data,size_t length); void flush() {} void close() {live_=false;} }; namespace fs { class FS { public: std::map> files; bool fail_open=false, fail_read=false, fail_write=false, fail_rename=false; unsigned reads=0,writes=0,renames=0; std::string fail_rename_from; void _lockFS() {} void _unlockFS() {} FS* _getFS(){return this;} bool exists(const char* path) {return files.count(path)!=0;} bool remove(const char* path) {return files.erase(path)!=0;} bool mkdir(const char*) {return true;} bool rename(const char* old,const char* next) { ++renames; if(fail_rename || fail_rename_from==old || !exists(old)) return false; files[next]=std::move(files[old]);files.erase(old);return true; } File open(const char* path,const char* mode="r") { if(!path||fail_open) return {}; if(mode[0]=='w') {files[path].clear();return File(this,path,true);} return File(this,path,exists(path)); } }; } inline bool File::open(const char* path,int flags){ if(!owner_)return false;*this=owner_->open(path,flags==2?"w":"r");return live_; } inline size_t File::size() const {return live_?owner_->files[path_].size():0;} inline size_t File::read(uint8_t* data,size_t length) { ++owner_->reads; if(!live_||owner_->fail_read) return 0; auto& bytes=owner_->files[path_];size_t n=std::min(length,bytes.size()-cursor_); memcpy(data,bytes.data()+cursor_,n);cursor_+=n;return n; } inline size_t File::write(const uint8_t* data,size_t length) { ++owner_->writes; if(!live_||owner_->fail_write) return 0; auto& bytes=owner_->files[path_];bytes.insert(bytes.end(),data,data+length);return length; } ''' MESH = r''' #pragma once #include #include #include #include #define MAX_PACKET_PAYLOAD 184 #define PAYLOAD_TYPE_GRP_TXT 5 #define PAYLOAD_TYPE_GRP_DATA 6 #define FLOOD_RETRY_POLICY_DENY 1 inline uint32_t fake_ms = 1000; inline uint32_t millis() {return fake_ms;} namespace mesh { struct Packet { uint8_t type=PAYLOAD_TYPE_GRP_DATA,payload[MAX_PACKET_PAYLOAD]={},flood_retry_policy=0; uint16_t payload_len=0,transport_codes[2]={}; uint32_t radio_generation=0;uint8_t radio_profile=0,path_hash_size=1; bool scoped=false,radio_reply=false,radio_bound=false; uint8_t getPayloadType() const{return type;} uint8_t getPathHashSize() const{return path_hash_size;} bool hasTransportCodes() const{return scoped;} }; struct GroupChannel {uint8_t hash[PATH_HASH_SIZE]={},secret[32]={};}; struct Clock {uint32_t now=1735689600UL+1000; uint32_t getCurrentTime() const{return now;}}; class Mesh; class Dispatcher {public: class ReceiveProfileScope {Mesh& mesh_;uint8_t old_;uint32_t generation_; public:ReceiveProfileScope(Mesh&,uint8_t,uint32_t);~ReceiveProfileScope();}; virtual void onSendFail(Packet*)=0; }; class PacketManager {public: std::vector outbound; int getOutboundTotal() const{return int(outbound.size());} Packet* getOutboundByIdx(int i){return outbound.at(i);} Packet* removeOutboundByIdx(int i){Packet* p=outbound.at(i);outbound.erase(outbound.begin()+i);return p;} }; class Mesh:public Dispatcher {public: LocalIdentity self_id;Clock clock;RNG rng;PacketManager manager;PacketManager* _mgr=&manager; std::function copying; std::function failure; std::vector allocated; Packet* in_flight=nullptr;bool queue_ok=true,allocation_ok=true,fanout=false; unsigned queued=0,cancelled=0,released=0; uint8_t scoped_profile=0,ack_path_size=0;uint32_t scoped_generation=0,ack_delay=0; bool ack_scoped=false;uint16_t ack_codes[2]={}; ~Mesh(){for(Packet* p:allocated)delete p;} Clock* getRTCClock(){return &clock;}RNG* getRNG(){return &rng;} Packet* createGroupDatagram(uint8_t type,const GroupChannel& channel,const uint8_t* data,size_t length){ assert(length<=168);if(!allocation_ok)return nullptr;Packet* p=new Packet;allocated.push_back(p);p->type=type; p->payload[0]=channel.hash[0];p->payload_len=1+Utils::encryptThenMAC(channel.secret,p->payload+1,data,int(length));return p; } bool sendFlood(Packet* p,uint32_t delay,uint8_t hashes){ ack_delay=delay;ack_path_size=hashes; if(!queue_ok)return false;manager.outbound.push_back(p);++queued; if(fanout){Packet* second=new Packet(*p);allocated.push_back(second);manager.outbound.push_back(second); if(copying)copying(second,p);} return true; } bool sendFlood(Packet* p,const uint16_t* codes,uint32_t delay,uint8_t hashes){ ack_scoped=true;memcpy(ack_codes,codes,sizeof(ack_codes));return sendFlood(p,delay,hashes); } const Packet* getOutboundInFlight()const{return in_flight;} void cancelOutboundRadioRetry(const Packet* p){assert(p==in_flight);++cancelled;} void onSendFail(Packet* p)override{if(failure)failure(p);} void releasePacket(Packet*){++released;} }; inline Dispatcher::ReceiveProfileScope::ReceiveProfileScope(Mesh& m,uint8_t profile,uint32_t generation) :mesh_(m),old_(m.scoped_profile),generation_(m.scoped_generation){m.scoped_profile=profile;m.scoped_generation=generation;} inline Dispatcher::ReceiveProfileScope::~ReceiveProfileScope(){mesh_.scoped_profile=old_;mesh_.scoped_generation=generation_;} } ''' REGION_MAP = r''' #pragma once #include #include #include #define MAX_REGION_ENTRIES 32 #define REGION_DENY_FLOOD 0x01 struct RegionEntry { uint16_t id=0,parent=0;uint8_t flags=0;char name[31]={}; bool isWildcard()const{return id==0;} }; class RegionMap {public: RegionEntry entries[MAX_REGION_ENTRIES]{},wildcard{}; unsigned count=0;uint16_t home_id=0,default_id=0; RegionEntry* put(const char* name,uint16_t parent=0){ assert(countid:0;} void setDefaultRegion(const RegionEntry* row){default_id=row?row->id:0;} }; ''' PROFILE = r''' #pragma once namespace mesh { class RadioProfileCLI {public: bool mutation=false,command=false;uint32_t generation=0; unsigned finishes=0,commits=0,rollbacks=0; void beginReplyCommand(){command=true;}void endReplyCommand(){command=false;} bool hasReplyMutation()const{return mutation;} uint32_t replyMutationGeneration()const{return generation;} void stage(){assert(command);mutation=true;++generation;} bool finishReplyMutation(bool delivered){assert(mutation);mutation=false;++finishes; if(delivered)++commits;else ++rollbacks;return true;} }; } ''' HARNESS = r''' #include #include #include #include #include #include @TRANSPORT_METHODS@ inline unsigned identity_counter=0; namespace mesh { Identity::Identity(){memset(pub_key,0,sizeof(pub_key));} LocalIdentity::LocalIdentity():Identity(){ memset(prv_key,0xAA+identity_counter++,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); memcpy(prv_key+32,pub_key,32);EVP_PKEY_free(key); } void LocalIdentity::sign(uint8_t* sig,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* ctx=EVP_MD_CTX_new();assert(key&&ctx);assert(EVP_DigestSignInit(ctx,nullptr,nullptr,nullptr,key)==1); size_t size=64;assert(EVP_DigestSign(ctx,sig,&size,data,length)==1);assert(size==64); EVP_MD_CTX_free(ctx);EVP_PKEY_free(key); } bool Identity::verify(const uint8_t* sig,const uint8_t* data,int length)const{ ++verify_calls;EVP_PKEY* key=EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519,nullptr,pub_key,32); EVP_MD_CTX* ctx=EVP_MD_CTX_new();assert(key&&ctx);assert(EVP_DigestVerifyInit(ctx,nullptr,nullptr,nullptr,key)==1); bool valid=EVP_DigestVerify(ctx,sig,64,data,length)==1;EVP_MD_CTX_free(ctx);EVP_PKEY_free(key);return valid; } } using namespace mesh; static constexpr const char* Path="/fleet_channel"; static constexpr const char* Key="21212121212121212121212121212121"; static uint8_t channel_key[32]={0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21, 0x21,0x21,0x21,0x21,0x21,0x21,0x21,0x21}; static std::string config(FleetChannel& fleet,const std::string& command){ char reply[160]={};assert(fleet.handleConfig(command.c_str(),reply,sizeof(reply)));return reply; } static void enroll(FleetChannel& fleet,const LocalIdentity& publisher){ assert(config(fleet,std::string("set fleet.channel ")+Key).rfind("OK",0)==0); char hex[65];Utils::toHex(hex,publisher.pub_key,32); assert(config(fleet,std::string("set fleet.controller ")+hex).rfind("OK",0)==0); } static Packet raw(const uint8_t* bytes,size_t length){ Packet packet; assert(1+CIPHER_MAC_SIZE+(length+15)/16*16<=sizeof(packet.payload)); Utils::sha256(packet.payload,1,channel_key,16); packet.payload_len=1+Utils::encryptThenMAC(channel_key,packet.payload+1,bytes,int(length)); packet.radio_profile=1;packet.radio_generation=17;packet.path_hash_size=2; return packet; } static Packet command(const LocalIdentity& publisher,uint32_t sequence,const char* text="set radio2 off", const char* target="all"){ assert(strlen(text)<=FleetCommand::MaxPayloadLength-FleetCommand::HeaderSize-FleetCommand::SignatureSize); uint8_t payload[184]={},destination[16];assert(FleetCommand::parseTarget(target,destination)); size_t length=FleetCommand::encode(publisher,channel_key,sequence,sequence+300,destination,text,payload+3,sizeof(payload)-3); assert(length);payload[0]=uint8_t(FleetCommand::DataType);payload[1]=uint8_t(FleetCommand::DataType>>8);payload[2]=uint8_t(length); return raw(payload,3+length); } static Packet targetsCommand(const LocalIdentity& publisher,uint32_t sequence,const std::string& list, const char* text="get radio2"){ FleetCommand::Targets targets;assert(FleetCommand::parseTargets(list.c_str(),list.size(),targets)); uint8_t payload[184]={}; const size_t length=FleetCommand::encode(publisher,channel_key,sequence,sequence+300,targets,text, payload+3,sizeof(payload)-3); assert(length);payload[0]=uint8_t(FleetCommand::DataType);payload[1]=uint8_t(FleetCommand::DataType>>8); payload[2]=uint8_t(length); return raw(payload,3+length); } static Packet shortNewAll(const LocalIdentity& publisher,uint32_t sequence){ constexpr const char* text="get radio2"; uint8_t data[184]={},message[160]={};uint8_t* payload=data+3; memcpy(payload,"FMC2",4);storage::writeLE32(payload+4,sequence); storage::writeLE32(payload+8,sequence+300);payload[12]=0; const size_t text_size=strlen(text);payload[13]=uint8_t(text_size);memcpy(payload+14,text,text_size); constexpr const char* domain="MeshCoreFleet1";const size_t domain_size=strlen(domain); memcpy(message,domain,domain_size);memcpy(message+domain_size,channel_key,16); memcpy(message+domain_size+16,payload,14+text_size); publisher.sign(payload+14+text_size,message,int(domain_size+16+14+text_size)); data[0]=uint8_t(FleetCommand::DataType);data[1]=uint8_t(FleetCommand::DataType>>8); data[2]=uint8_t(14+text_size+64);return raw(data,3+data[2]); } static std::string publicHex(const Identity& identity){ char text[65];Utils::toHex(text,identity.pub_key,32);return text; } static Packet signedUnchecked(const LocalIdentity& publisher,uint32_t sequence,uint32_t expires, const char* text){ uint8_t data[184]={},message[160]={};size_t text_size=strlen(text); assert(text_size<=FleetCommand::MaxPayloadLength-FleetCommand::HeaderSize-FleetCommand::SignatureSize); uint8_t* payload=data+3;memcpy(payload,"FMC1",4); storage::writeLE32(payload+4,sequence);storage::writeLE32(payload+8,expires); payload[28]=uint8_t(text_size);memcpy(payload+29,text,text_size); constexpr const char* domain="MeshCoreFleet1";const size_t domain_size=strlen(domain); memcpy(message,domain,domain_size);memcpy(message+domain_size,channel_key,16); memcpy(message+domain_size+16,payload,29+text_size); publisher.sign(payload+29+text_size,message,int(domain_size+16+29+text_size)); data[0]=uint8_t(FleetCommand::DataType);data[1]=uint8_t(FleetCommand::DataType>>8); data[2]=uint8_t(29+text_size+64);return raw(data,3+data[2]); } static Packet compactUnchecked(const LocalIdentity& publisher,uint32_t sequence,const char* text){ uint8_t data[184]={},message[160]={};const size_t text_size=strlen(text); // One byte over the valid limit still fits the cipher's final padded block. assert(text_size<=FleetCommand::SinglePacketMaxCommandLength+1); uint8_t* payload=data+3;memcpy(payload,"FMC2",4); storage::writeLE32(payload+4,sequence);storage::writeLE32(payload+8,sequence+300); payload[12]=0;payload[13]=uint8_t(text_size);memcpy(payload+14,text,text_size); constexpr const char* domain="MeshCoreFleet1";const size_t domain_size=strlen(domain); memcpy(message,domain,domain_size);memcpy(message+domain_size,channel_key,16); memcpy(message+domain_size+16,payload,14+text_size); publisher.sign(payload+14+text_size,message,int(domain_size+16+14+text_size)); data[0]=uint8_t(FleetCommand::DataType);data[1]=uint8_t(FleetCommand::DataType>>8); data[2]=uint8_t(14+text_size+64);return raw(data,3+data[2]); } static void bind(FleetChannel& fleet,Mesh& mesh,RadioProfileCLI& profiles){ mesh.copying=[&](Packet* second,const Packet* first){fleet.copy(second,first);}; mesh.failure=[&](Packet* p){fleet.fail(p,profiles);}; } static unsigned apply(FleetChannel& fleet,fs::FS& fs,Mesh& mesh,RadioProfileCLI& profiles,Packet& packet, bool mutation=false,bool fail_handler=false,const TransportKey* scope=nullptr){ unsigned calls=0;fleet.receive(&packet,mesh,scope); fleet.service(mesh,profiles,"test:node",[&](uint32_t seq,const char* text,char* reply){ ++calls;assert(storage::readLE32(fs.files.at(Path).data()+56)==seq); assert(mesh.scoped_profile==1&&mesh.scoped_generation==17);assert(profiles.command); assert(!strcmp(text,"set radio2 off")||!strcmp(text,"get radio2.status")||!strcmp(text,"get radio2")); if(mutation)profiles.stage();strcpy(reply,fail_handler?"Err - rejected":"OK"); }); assert(mesh.scoped_profile==0&&mesh.scoped_generation==0);assert(!profiles.command);return calls; } static void reseal(std::vector& image){ storage::writeLE32(image.data()+60,storage::updateCRC32(0xffffffff,image.data(),60)); } static std::string longRule(){ const std::string prefix="set flood.rule.1 type=grp_txt channel="+std::string(64,'1') +" prefix=AABBCC,DDEEFF,112233 hops=6+ priority=255 suspend=tempradio mode=radio hashbytes=3 in=any"; const std::string action=" action=drop"; assert(prefix.size()+action.size()<=FleetCommand::MaxCommandLength); return prefix+std::string(FleetCommand::MaxCommandLength-prefix.size()-action.size(),' ')+action; } static std::vector envelope(const LocalIdentity& publisher,uint32_t sequence, const std::string& text,const std::string& target="all"){ FleetCommand::Targets targets;assert(FleetCommand::parseTargets(target.c_str(),target.size(),targets)); std::vector bytes(FleetCommand::MaxEnvelopeLength); size_t length=FleetCommand::encode(publisher,channel_key,sequence,sequence+300,targets, text.c_str(),bytes.data(),bytes.size()); assert(length);bytes.resize(length);return bytes; } static Packet part(const std::vector& bytes,uint8_t index){ uint8_t data[168]={};size_t length=FleetCommand::fragment(bytes.data(),bytes.size(),index, data+3,sizeof(data)-3); assert(length&&length<=FleetCommand::MaxPayloadLength);data[0]=uint8_t(FleetCommand::DataType); data[1]=uint8_t(FleetCommand::DataType>>8);data[2]=uint8_t(length); Packet result=raw(data,3+length);assert(result.payload_len<=MAX_PACKET_PAYLOAD);return result; } template static Packet changePart(const Packet& packet,Change change){ uint8_t data[184]={};int length=Utils::MACThenDecrypt(channel_key,data,packet.payload+1,packet.payload_len-1); assert(length>0);change(data+3);return raw(data,size_t(length)); } struct FragmentFixture { fs::FS disk;Mesh mesh;RadioProfileCLI profiles;FleetChannel fleet{&disk};LocalIdentity publisher; std::string expected=longRule();unsigned calls=0;bool mutation=false; FragmentFixture(){enroll(fleet,publisher);bind(fleet,mesh,profiles);} uint32_t reserved()const{return storage::readLE32(disk.files.at(Path).data()+56);} std::string stats(){return config(fleet,"get fleet.stats");} unsigned feed(Packet& packet,const TransportKey* scope=nullptr,uint32_t advance_ms=1000){ fake_ms+=advance_ms;const unsigned before=calls; const auto image=disk.files.at(Path);const unsigned writes=disk.writes,verifies=verify_calls; fleet.receive(&packet,mesh,scope); assert(disk.files.at(Path)==image&&disk.writes==writes&&verify_calls==verifies); fleet.service(mesh,profiles,"long:rule",[&](uint32_t seq,const char* text,char* reply){ ++calls;assert(reserved()==seq&&profiles.command); assert(mesh.scoped_profile==packet.radio_profile&&mesh.scoped_generation==packet.radio_generation); assert(strlen(text)==expected.size()&&!strcmp(text,expected.c_str())); assert(text[expected.size()]==0);if(mutation)profiles.stage();strcpy(reply,"OK"); }); assert(!profiles.command&&mesh.scoped_profile==0&&mesh.scoped_generation==0);return calls-before; } void tick(uint32_t advance_ms){ fake_ms+=advance_ms;fleet.service(mesh,profiles,"node",[](uint32_t,const char*,char*){assert(false);}); } void partial(){assert(calls==0&&reserved()==0&&mesh.queued==0&&!profiles.mutation); assert(stats().find("accepted=0")!=std::string::npos&&stats().find("parts=1")!=std::string::npos);} }; static int fragmentScenario(const std::string& scenario){ FragmentFixture f;const uint32_t seq=f.mesh.clock.now; auto bytes=envelope(f.publisher,seq,f.expected);assert(bytes.size()==308&&f.expected.size()==230); Packet a=part(bytes,0),b=part(bytes,1);assert(a.payload_len==179&&b.payload_len==179); if(scenario=="fragments_orders"){ for(unsigned reverse=0;reverse<2;++reverse){ FragmentFixture x;auto encoded=envelope(x.publisher,seq,x.expected); Packet first=part(encoded,reverse?1:0),second=part(encoded,reverse?0:1); const auto old=x.disk.files.at(Path);const unsigned verifies=verify_calls,writes=x.disk.writes; assert(x.feed(first)==0);x.partial();assert(x.disk.files.at(Path)==old&&x.disk.writes==writes); assert(verify_calls==verifies&&x.stats().find("rejected=0")!=std::string::npos); assert(x.feed(second)==1&&verify_calls==verifies+1&&x.reserved()==seq&&x.mesh.queued==1); assert(x.stats().find("parts=0")!=std::string::npos&&last_jitter_max==10000); assert(x.feed(first)==0&&x.feed(second)==0&&x.calls==1); FleetChannel rebooted(&x.disk);unsigned calls=0; for(Packet* packet:{&first,&second}){fake_ms+=1000;rebooted.receive(packet,x.mesh); rebooted.service(x.mesh,x.profiles,"node",[&](uint32_t,const char*,char*){++calls;});} assert(calls==0&&x.reserved()==seq); } }else if(scenario=="fragments_duplicates"){ const unsigned verifies=verify_calls,writes=f.disk.writes; assert(f.feed(b)==0&&f.feed(b)==0&&f.feed(b)==0);f.partial(); assert(verify_calls==verifies&&f.disk.writes==writes&&f.stats().find("rejected=0")!=std::string::npos); assert(f.feed(a)==1&&f.feed(a)==0&&f.feed(b)==0&&f.calls==1&&f.mesh.queued==1); }else if(scenario=="fragments_minimum_size"){ f.expected="set flood.rule.1 05 c="+std::string(64,'1')+" d"; assert(f.expected.size()==88);auto small=envelope(f.publisher,seq,f.expected); assert(small.size()==166);Packet first=part(small,0),second=part(small,1); assert(first.payload_len==179&&second.payload_len==35); assert(f.feed(second)==0);f.partial();assert(f.feed(first)==1&&f.reserved()==seq); FleetCommand::Targets all;assert(FleetCommand::parseTargets("all",3,all)); uint8_t output[308];memset(output,0xA5,sizeof(output)); const std::string too_long=longRule()+" ";assert(too_long.size()==231); assert(!FleetCommand::encode(f.publisher,channel_key,seq+1,seq+301,all,too_long.c_str(),output,sizeof(output))); for(uint8_t byte:output)assert(byte==0xA5); }else if(scenario=="fragments_expired_during_collection"){ assert(f.feed(a)==0);f.partial();f.mesh.clock.now+=301; assert(f.feed(b)==0&&f.reserved()==0&&f.mesh.queued==0&&f.calls==0); assert(f.stats().find("parts=0")!=std::string::npos); auto retry=envelope(f.publisher,f.mesh.clock.now,f.expected); Packet first=part(retry,0),second=part(retry,1); assert(f.feed(first)==0&&f.feed(second)==1&&f.reserved()==f.mesh.clock.now); }else if(scenario=="fragments_conflicts"){ assert(f.feed(a)==0);f.partial(); Packet changed=changePart(a,[](uint8_t* frame){frame[11+90]^=1;}); assert(f.feed(changed)==0);f.partial(); Packet length=changePart(a,[](uint8_t* frame){storage::writeLE16(frame+8,307);}); assert(f.feed(length)==0);f.partial(); Packet index=changePart(a,[](uint8_t* frame){frame[10]=2;}); assert(f.feed(index)==0);f.partial(); assert(f.feed(b)==1&&f.calls==1&&f.reserved()==seq); }else if(scenario=="fragments_fresh_retry"){ assert(f.feed(a)==0);f.partial();auto newer=envelope(f.publisher,seq+1,f.expected); Packet fresh_first=part(newer,1),fresh_second=part(newer,0); assert(f.feed(fresh_first)==0&&f.stats().find("parts=2")!=std::string::npos); // The unsigned newer metadata does not invalidate an authentic older // envelope. Its signed sequence still passes the durable monotonic gate. assert(f.feed(b)==1&&f.reserved()==seq&&f.stats().find("parts=0")!=std::string::npos); assert(f.feed(fresh_second)==0&&f.feed(fresh_first)==1&&f.reserved()==seq+1); assert(f.feed(a)==0&&f.feed(b)==0&&f.calls==2&&f.mesh.queued==2); }else if(scenario=="fragments_future_poison"){ for(unsigned reverse=0;reverse<2;++reverse){ FragmentFixture x;auto encoded=envelope(x.publisher,seq,x.expected); Packet first=part(encoded,reverse?1:0),second=part(encoded,reverse?0:1); Packet poison=changePart(first,[&](uint8_t* frame){storage::writeLE32(frame+4,seq+60);}); const auto saved=x.disk.files.at(Path);const unsigned verifies=verify_calls; assert(x.feed(poison,nullptr,0)==0&&x.feed(first,nullptr,0)==0); assert(x.stats().find("parts=2")!=std::string::npos&&x.disk.files.at(Path)==saved); assert(x.feed(poison,nullptr,0)==0&&verify_calls==verifies); assert(x.feed(second,nullptr,0)==1&&x.reserved()==seq&&verify_calls==verifies+1); assert(x.stats().find("parts=0")!=std::string::npos&&x.feed(poison)==0); assert(x.feed(first)==0&&x.feed(second)==0&&x.calls==1); FleetChannel rebooted(&x.disk);unsigned calls=0; for(Packet* packet:{&first,&second}){rebooted.receive(packet,x.mesh); rebooted.service(x.mesh,x.profiles,"node",[&](uint32_t,const char*,char*){++calls;});} assert(calls==0&&x.reserved()==seq); } }else if(scenario=="fragments_oldest_eviction"){ Packet poison_a=changePart(a,[&](uint8_t* frame){storage::writeLE32(frame+4,seq+60);}); Packet poison_b=changePart(a,[&](uint8_t* frame){storage::writeLE32(frame+4,seq+59);}); assert(f.feed(poison_a)==0&&f.feed(poison_b)==0); assert(f.stats().find("parts=2")!=std::string::npos); // A third transfer replaces the oldest slot despite its higher sequence. assert(f.feed(a)==0&&f.feed(b)==1&&f.reserved()==seq&&f.calls==1); assert(f.stats().find("parts=0")!=std::string::npos); }else if(scenario=="fragments_independent_failure"){ Packet poison_a=changePart(a,[&](uint8_t* frame){storage::writeLE32(frame+4,seq+60);}); Packet poison_b=changePart(b,[&](uint8_t* frame){storage::writeLE32(frame+4,seq+60);}); assert(f.feed(a)==0&&f.feed(poison_a)==0&&f.feed(poison_b)==0); f.partial();assert(f.feed(b)==1&&f.reserved()==seq&&f.calls==1); }else if(scenario=="fragments_budget_partial"){ const unsigned verifies=verify_calls; for(unsigned i=0;i<20;++i)assert(f.feed(a,nullptr,0)==0); assert(verify_calls==verifies&&f.feed(b,nullptr,0)==1); assert(verify_calls==verifies+1&&f.reserved()==seq); }else if(scenario=="fragments_tamper"){ Packet mac=a;mac.payload[1]^=1;assert(f.feed(mac)==0&&f.stats().find("parts=0")!=std::string::npos); assert(f.feed(a)==0);f.partial();const auto saved=f.disk.files.at(Path); Packet signature=changePart(b,[](uint8_t* frame){frame[11+153]^=1;}); assert(f.feed(signature)==0&&f.calls==0&&f.disk.files.at(Path)==saved&&f.mesh.queued==0); assert(f.stats().find("parts=0")!=std::string::npos); assert(f.feed(b)==0&&f.feed(a)==1&&f.calls==1); }else if(scenario=="fragments_wrong_signer"){ LocalIdentity stranger;auto forged=envelope(stranger,seq,f.expected); Packet first=part(forged,0),second=part(forged,1); assert(f.feed(first)==0);f.partial();assert(f.feed(second)==0&&f.reserved()==0&&f.mesh.queued==0); assert(f.stats().find("parts=0")!=std::string::npos&&f.feed(a)==0&&f.feed(b)==1); }else if(scenario=="fragments_fixed_timeout"){ assert(f.feed(a)==0);f.partial();assert(f.feed(a,nullptr,299999)==0);f.partial(); f.tick(1);assert(f.stats().find("parts=0")!=std::string::npos&&f.reserved()==0&&f.mesh.queued==0); assert(f.feed(b)==0);f.partial();assert(f.feed(a)==1); }else if(scenario=="fragments_timeout_wrap"){ fake_ms=UINT32_MAX-200000;assert(f.feed(b,nullptr,0)==0);f.partial();f.tick(299999);f.partial(); f.tick(1);assert(f.stats().find("parts=0")!=std::string::npos&&f.reserved()==0); assert(f.feed(a)==0&&f.feed(b)==1); }else if(scenario=="fragments_sequence_bounds"){ for(uint32_t invalid:{FleetCommand::MinEpoch-1,seq+61,seq-601}){ Packet changed=changePart(a,[&](uint8_t* frame){storage::writeLE32(frame+4,invalid);}); assert(f.feed(changed)==0&&f.stats().find("parts=0")!=std::string::npos&&f.reserved()==0); } Packet mismatch_a=changePart(a,[&](uint8_t* frame){storage::writeLE32(frame+4,seq+1);}); Packet mismatch_b=changePart(b,[&](uint8_t* frame){storage::writeLE32(frame+4,seq+1);}); const unsigned verifies=verify_calls;assert(f.feed(mismatch_a)==0&&f.feed(mismatch_b)==0); assert(f.reserved()==0&&verify_calls==verifies&&f.stats().find("parts=0")!=std::string::npos); assert(f.feed(a)==0&&f.feed(b)==1); }else if(scenario=="fragments_target"){ LocalIdentity stranger;f.expected="set flood.rule.1 any "+std::string(150,' ')+"drop"; auto other=envelope(f.publisher,seq,f.expected,publicHex(stranger)); Packet other_a=part(other,0),other_b=part(other,1);assert(f.feed(other_a)==0);f.partial(); assert(f.feed(other_b)==0&&f.reserved()==0&&f.mesh.queued==0); auto mine=envelope(f.publisher,seq,f.expected,publicHex(f.mesh.self_id)); Packet mine_a=part(mine,0),mine_b=part(mine,1); assert(f.feed(mine_b)==0&&f.feed(mine_a)==1&&last_jitter_max==1500); }else if(scenario=="fragments_config_reboot"){ assert(f.feed(a)==0);f.partial();assert(config(f.fleet,std::string("set fleet.channel ")+Key).rfind("OK",0)==0); assert(f.stats().find("parts=0")!=std::string::npos&&f.feed(b)==0);f.partial(); assert(config(f.fleet,"set fleet.controller "+publicHex(f.publisher)).rfind("OK",0)==0); assert(f.stats().find("parts=0")!=std::string::npos&&f.feed(a)==0);f.partial(); FleetChannel rebooted(&f.disk);unsigned calls=0;fake_ms+=1000;rebooted.receive(&b,f.mesh); rebooted.service(f.mesh,f.profiles,"node",[&](uint32_t,const char*,char*){++calls;}); assert(calls==0&&f.reserved()==0&&f.mesh.queued==0); fake_ms+=1000;rebooted.receive(&a,f.mesh); rebooted.service(f.mesh,f.profiles,"node",[&](uint32_t sequence,const char* text,char* reply){ ++calls;assert(sequence==seq&&!strcmp(text,f.expected.c_str()));strcpy(reply,"OK");}); assert(calls==1&&f.reserved()==seq); }else if(scenario=="fragments_reserve_failure"){ const auto saved=f.disk.files.at(Path);f.disk.fail_write=true;const unsigned writes=f.disk.writes; assert(f.feed(b)==0);f.partial();assert(f.disk.writes==writes&&f.disk.files.at(Path)==saved); assert(f.feed(a)==0&&f.disk.files.at(Path)==saved&&f.mesh.queued==0&&f.calls==0); assert(config(f.fleet,"get fleet.channel").find("store=error")!=std::string::npos); f.disk.fail_write=false;assert(f.feed(a)==0&&f.feed(b)==0); FleetChannel rebooted(&f.disk);unsigned calls=0; for(Packet* packet:{&a,&b}){fake_ms+=1000;rebooted.receive(packet,f.mesh); rebooted.service(f.mesh,f.profiles,"node",[&](uint32_t,const char*,char* reply){++calls;strcpy(reply,"OK");});} assert(calls==1&&f.reserved()==seq); }else if(scenario=="fragments_scoped_radio_ack"){ f.mutation=true;f.mesh.fanout=true;TransportKey scope;memset(scope.key,0x12,sizeof(scope.key)); a.radio_profile=0;a.radio_generation=9;b.radio_profile=1;b.radio_generation=27;b.path_hash_size=3; b.scoped=true;b.transport_codes[0]=scope.calcTransportCode(&b); assert(f.feed(a)==0);f.partial();assert(f.feed(b,&scope)==1&&f.fleet.waiting()&&f.profiles.mutation); assert(f.mesh.manager.outbound.size()==2&&f.mesh.ack_scoped&&f.mesh.ack_path_size==3); assert(f.mesh.ack_codes[0]==scope.calcTransportCode(f.mesh.manager.outbound[0])); assert(f.mesh.ack_codes[0]!=b.transport_codes[0]&&last_jitter_max==10000); f.fleet.complete(f.mesh.manager.removeOutboundByIdx(0),f.profiles); assert(f.fleet.waiting()&&f.profiles.mutation&&f.profiles.commits==0); f.fleet.fail(f.mesh.manager.removeOutboundByIdx(0),f.profiles); assert(!f.fleet.waiting()&&!f.profiles.mutation&&f.profiles.commits==1); assert(f.feed(a)==0&&f.feed(b,&scope)==0&&f.calls==1); }else if(scenario=="fragments_unknown_scope"){ f.mutation=true;a.scoped=true;a.transport_codes[0]=123; assert(f.feed(b)==0&&f.feed(a)==1&&f.mesh.queued==0&&f.profiles.rollbacks==1); assert(f.reserved()==seq&&!f.fleet.waiting()&&!f.profiles.mutation); }else if(scenario=="fragments_single_interrupt"){ assert(f.feed(a)==0);f.partial();f.expected="get radio2"; Packet single=targetsCommand(f.publisher,seq+1,"all",f.expected.c_str()); assert(f.feed(single)==1&&f.reserved()==seq+1&&f.stats().find("parts=0")!=std::string::npos); assert(f.feed(b)==0&&f.feed(a)==0&&f.calls==1&&f.mesh.queued==1); }else if(scenario=="fragments_malformed_frame"){ for(unsigned damage=0;damage<4;++damage){ Packet malformed=changePart(a,[&](uint8_t* frame){ if(damage==0)storage::writeLE16(frame+8,309); else if(damage==1)storage::writeLE16(frame+8,165); else if(damage==2)frame[10]=2; else frame[-1]=uint8_t(FleetCommand::MaxPayloadLength-1); });assert(f.feed(malformed)==0&&f.stats().find("parts=0")!=std::string::npos); } assert(f.reserved()==0&&f.mesh.queued==0&&f.feed(a)==0&&f.feed(b)==1); }else assert(false); printf("fleet runtime %s passed\n",scenario.c_str());return 0; } static Packet whole(const std::vector& bytes){ assert(bytes.size()<=FleetCommand::MaxPayloadLength);uint8_t data[168]={}; data[0]=uint8_t(FleetCommand::DataType);data[1]=uint8_t(FleetCommand::DataType>>8); data[2]=uint8_t(bytes.size());memcpy(data+3,bytes.data(),bytes.size());return raw(data,3+bytes.size()); } static void resign(const LocalIdentity& publisher,std::vector& bytes){ constexpr const char* domain="MeshCoreFleet1";const size_t domain_size=strlen(domain); assert(bytes.size()>=FleetCommand::SignatureSize); std::vector message(domain_size+16+bytes.size()-FleetCommand::SignatureSize); memcpy(message.data(),domain,domain_size);memcpy(message.data()+domain_size,channel_key,16); memcpy(message.data()+domain_size+16,bytes.data(),bytes.size()-FleetCommand::SignatureSize); publisher.sign(bytes.data()+bytes.size()-FleetCommand::SignatureSize,message.data(),int(message.size())); } struct LocationHooks :FleetReplyHooks { bool valid=false;int32_t latitude=0,longitude=0;mutable unsigned reads=0; bool getFleetLocation(int32_t& lat_e6,int32_t& lon_e6)const override { ++reads;if(!valid)return false;lat_e6=latitude;lon_e6=longitude;return true; } }; struct RegionFixture { fs::FS disk;Mesh mesh;RadioProfileCLI profiles;RegionMap regions;LocationHooks location; FleetChannel fleet{&disk,&location,®ions};LocalIdentity publisher; std::string expected="get radio2";unsigned calls=0,issued=0; RegionEntry* usa=nullptr;RegionEntry* sea=nullptr;RegionEntry* pdx=nullptr;RegionEntry* north=nullptr; RegionFixture(){ usa=regions.put("#usa");sea=regions.put("#sea",usa->id);pdx=regions.put("#pdx",usa->id); north=regions.put("#north",sea->id);regions.put("seaside");regions.put("$private",usa->id); regions.setHomeRegion(north);regions.setDefaultRegion(pdx); enroll(fleet,publisher);bind(fleet,mesh,profiles); } uint32_t reserved()const{return storage::readLE32(disk.files.at(Path).data()+56);} unsigned feed(FleetChannel& receiver,Packet& packet,const TransportKey* scope=nullptr){ fake_ms+=1000;const unsigned before=calls,verifies=verify_calls,writes=disk.writes,reads=location.reads; const auto saved=disk.files.at(Path);receiver.receive(&packet,mesh,scope); assert(location.reads==reads); assert(verify_calls==verifies&&disk.writes==writes&&disk.files.at(Path)==saved); receiver.service(mesh,profiles,"region:node",[&](uint32_t sequence,const char* text,char* reply){ ++calls;assert(reserved()==sequence&&!strcmp(text,expected.c_str())); assert(mesh.scoped_profile==packet.radio_profile&&mesh.scoped_generation==packet.radio_generation); assert(profiles.command);strcpy(reply,"OK"); }); assert(!profiles.command&&mesh.scoped_profile==0&&mesh.scoped_generation==0);return calls-before; } unsigned feed(Packet& packet,const TransportKey* scope=nullptr){return feed(fleet,packet,scope);} unsigned send(const std::string& target){ Packet packet=targetsCommand(publisher,mesh.clock.now+issued++,target,expected.c_str());return feed(packet); } }; static int regionScenario(const std::string& scenario){ RegionFixture f; if(scenario=="regions_target_delimiter"){ FleetCommand::Targets parsed; const auto saved=f.disk.files.at(Path); for(const char* invalid:{"sea,pdx","sea,,pdx","sea;",";sea","sea;;pdx", "gps:47.6062:-122.3321:25","gps:47.6062,-122.3321:25,sea"}){ assert(!FleetCommand::parseTargets(invalid,strlen(invalid),parsed)); assert(parsed.count==0&&parsed.length==0); } assert(f.disk.files.at(Path)==saved&&f.calls==0&&f.mesh.queued==0); assert(f.send("sea;pdx;home:sea")==1&&f.calls==1); }else if(scenario=="regions_list_home"){ f.sea->flags=REGION_DENY_FLOOD;f.pdx->flags=REGION_DENY_FLOOD; assert(f.send("sea")==1&&f.send("region:pdx")==1); // Configured membership ignores routing flags. assert(f.send("home:sea")==1&&f.send("home:north")==1&&f.send("home:usa")==1); assert(f.send("home:pdx")==0&&f.send("home:seaside")==0); assert(f.send("SEA")==0&&f.send("region:se")==0&&f.send("home:NORTH")==0); assert(f.send("#sea")==1&&f.send("region:#pdx")==1&&f.send("home:#sea")==1); assert(last_jitter_max==10000); // Region requests may have multiple matching radios. }else if(scenario=="regions_private_names"){ assert(f.send("region:$private")==1&&f.send("private")==0); RegionEntry* private_row=f.regions.findById(6);f.regions.setHomeRegion(private_row); assert(f.send("home:$private")==1&&f.send("home:private")==0&&f.send("home:usa")==1); }else if(scenario=="regions_home_not_default_or_scope"){ f.regions.setHomeRegion(f.pdx);f.regions.setDefaultRegion(f.sea); Packet packet=targetsCommand(f.publisher,f.mesh.clock.now,"home:sea"); TransportKey incoming;memset(incoming.key,0x12,sizeof(incoming.key)); packet.scoped=true;packet.transport_codes[0]=incoming.calcTransportCode(&packet); const auto before=f.disk.files.at(Path);assert(f.feed(packet,&incoming)==0&&f.disk.files.at(Path)==before); assert(f.send("home:pdx")==1); // Only home and ancestors qualify, not the default. f.regions.setHomeRegion(nullptr);f.regions.setDefaultRegion(f.sea); assert(f.send("home:sea")==0&&f.send("sea")==1); f.regions.setHomeRegion(&f.regions.getWildcard());strcpy(f.regions.wildcard.name,"#sea"); assert(f.send("home:sea")==0); f.regions.home_id=99;assert(f.send("home:sea")==0); // Missing home IDs fail closed. }else if(scenario=="regions_wildcard_and_cycles"){ RegionEntry* wildcard=f.regions.put("wildcard");wildcard->id=0; assert(f.send("region:wildcard")==0&&f.send("home:wildcard")==0); f.north->parent=f.sea->id;f.sea->parent=f.north->id; assert(f.send("home:pdx")==0); // Bounded parent walk cannot hang on a damaged cycle. }else if(scenario=="regions_damaged_map_bounds"){ const unsigned count=f.regions.count;const auto saved=f.disk.files.at(Path); f.regions.count=MAX_REGION_ENTRIES+1; assert(f.send("sea")==0&&f.send("home:sea")==0&&f.disk.files.at(Path)==saved); f.regions.count=UINT32_MAX; assert(f.send("sea")==0&&f.send("home:sea")==0&&f.disk.files.at(Path)==saved); f.regions.count=count;memset(f.sea->name,'G',sizeof(f.sea->name)); assert(f.send(std::string(30,'G'))==0); // An unterminated row cannot alias a truncated name. f.regions.setHomeRegion(f.sea);assert(f.send("home:"+std::string(30,'G'))==0); f.sea->parent=99;assert(f.send("home:usa")==0&&f.disk.files.at(Path)==saved); }else if(scenario=="regions_missing_map"){ FleetChannel no_map(&f.disk);const auto before=f.disk.files.at(Path); Packet list=targetsCommand(f.publisher,f.mesh.clock.now,"sea"); assert(f.feed(no_map,list)==0&&f.disk.files.at(Path)==before); Packet home=targetsCommand(f.publisher,f.mesh.clock.now,"home:sea"); assert(f.feed(no_map,home)==0&&f.disk.files.at(Path)==before); Packet individual=targetsCommand(f.publisher,f.mesh.clock.now,"sea;"+publicHex(f.mesh.self_id)); assert(f.feed(no_map,individual)==1&&f.reserved()==f.mesh.clock.now); }else if(scenario=="regions_overlap_once"){ const std::string key=publicHex(f.mesh.self_id); assert(f.send("sea;home:sea;"+key.substr(0,8)+";"+key)==1); assert(f.calls==1&&f.mesh.queued==1&&last_jitter_max==10000); assert(config(f.fleet,"get fleet.stats").find("accepted=1")!=std::string::npos); LocalIdentity another;const auto before=f.disk.files.at(Path); assert(f.send("home:pdx;"+publicHex(another))==0&&f.disk.files.at(Path)==before); }else if(scenario=="regions_signature_tamper"){ auto good=envelope(f.publisher,f.mesh.clock.now,"get radio2","sea"); assert(!memcmp(good.data(),"FMC2",4)&&good[12]==1&&good[13]==0x20&&good[14]==3); const auto saved=f.disk.files.at(Path);auto changed=good;changed[13]=0x21; // Also matches, but signature differs. Packet bad_type=whole(changed);assert(f.feed(bad_type)==0&&f.disk.files.at(Path)==saved); changed=good;changed[17]='x';Packet bad_name=whole(changed); assert(f.feed(bad_name)==0&&f.disk.files.at(Path)==saved&&f.mesh.queued==0); Packet valid=whole(good);assert(f.feed(valid)==1&&f.calls==1); }else if(scenario=="regions_malformed_after_match"){ auto good=envelope(f.publisher,f.mesh.clock.now,"get radio2","sea;pdx"); assert(good[12]==2&&good[13]==0x20&&good[18]==0x20&&good[19]==3); const auto saved=f.disk.files.at(Path); for(unsigned damage=0;damage<4;++damage){ auto broken=good; if(damage==0)broken[18]=0x23; // Unknown type after an already-matching record. else if(damage==1)broken[19]=0; else if(damage==2)broken[19]=31; else broken[20]=','; resign(f.publisher,broken);Packet packet=whole(broken); assert(f.feed(packet)==0&&f.disk.files.at(Path)==saved&&f.mesh.queued==0); } Packet valid=whole(good);assert(f.feed(valid)==1&&f.calls==1); }else if(scenario=="regions_durable_replay"){ Packet packet=targetsCommand(f.publisher,f.mesh.clock.now,"home:sea"); assert(f.feed(packet)==1);const auto persisted=f.disk.files.at(Path); assert(persisted.size()==64&&!memcmp(persisted.data(),"FCS1",4)); assert(f.feed(packet)==0&&f.disk.files.at(Path)==persisted); FleetChannel rebooted(&f.disk,nullptr,&f.regions);assert(f.feed(rebooted,packet)==0); assert(f.disk.files.at(Path)==persisted&&f.calls==1&&f.mesh.queued==1); }else if(scenario=="regions_fragments_membership_changes"){ f.expected="set flood.rule.1 any "+std::string(150,' ')+"drop"; auto bytes=envelope(f.publisher,f.mesh.clock.now,f.expected,"home:sea"); assert(bytes.size()>FleetCommand::MaxPayloadLength);Packet first=part(bytes,0),second=part(bytes,1); const auto saved=f.disk.files.at(Path);unsigned verifies=verify_calls; assert(f.feed(first)==0&&f.disk.files.at(Path)==saved&&verify_calls==verifies&&f.mesh.queued==0); f.regions.setHomeRegion(f.pdx); assert(f.feed(second)==0&&f.disk.files.at(Path)==saved&&f.calls==0&&f.mesh.queued==0); f.regions.setHomeRegion(f.north); // Rejected nonmember command did not consume its sequence. assert(f.feed(second)==0&&f.feed(first)==1&&f.reserved()==f.mesh.clock.now); assert(f.feed(first)==0&&f.feed(second)==0&&f.calls==1); }else if(scenario=="regions_fragments_tamper"){ f.expected="set flood.rule.1 any "+std::string(150,' ')+"drop"; auto bytes=envelope(f.publisher,f.mesh.clock.now,f.expected,"sea"); Packet first=part(bytes,0),second=part(bytes,1); const auto saved=f.disk.files.at(Path);assert(f.feed(first)==0); Packet tampered=changePart(second,[](uint8_t* frame){frame[11+1]^=1;}); assert(f.feed(tampered)==0&&f.calls==0&&f.disk.files.at(Path)==saved&&f.mesh.queued==0); assert(f.feed(second)==0&&f.feed(first)==1&&f.calls==1); FleetChannel rebooted(&f.disk,nullptr,&f.regions); assert(f.feed(rebooted,first)==0&&f.feed(rebooted,second)==0&&f.calls==1); }else assert(false); printf("fleet runtime %s passed\n",scenario.c_str());return 0; } static constexpr const char* Seattle="gps:47.6062,-122.3321:25"; static int gpsScenario(const std::string& scenario){ RegionFixture f;f.location.valid=true;f.location.latitude=47606200;f.location.longitude=-122332100; if(scenario=="gps_inside_outside"){ assert(f.send(Seattle)==1&&f.location.reads==1&&last_jitter_max==10000); f.location.latitude=47700000;f.location.longitude=-122300000;assert(f.send(Seattle)==1); f.location.latitude=45515200;f.location.longitude=-122678400; const auto saved=f.disk.files.at(Path);assert(f.send(Seattle)==0&&f.disk.files.at(Path)==saved); assert(f.send("gps:45.5152,-122.6784:1")==1); }else if(scenario=="gps_unknown_and_bad_fixes"){ f.location.valid=false;f.location.latitude=0;f.location.longitude=0; const auto saved=f.disk.files.at(Path);assert(f.send("gps:0,0:10")==0&&f.disk.files.at(Path)==saved); f.location.valid=true;assert(f.send("gps:0,0:10")==0&&f.disk.files.at(Path)==saved); f.location.valid=false; f.location.latitude=47606200;f.location.longitude=-122332100; assert(f.send(Seattle)==0); // A stale/invalid provider value is never substituted for a known fix. f.location.valid=true; for(const auto& coords:{std::pair{90000001,0},{-90000001,0}, {0,180000001},{0,-180000001},{INT32_MIN,INT32_MAX}}){ f.location.latitude=coords.first;f.location.longitude=coords.second; assert(f.send(Seattle)==0&&f.disk.files.at(Path)==saved); } FleetChannel no_provider(&f.disk,nullptr,&f.regions);Packet packet=targetsCommand(f.publisher,f.mesh.clock.now,Seattle); assert(f.feed(no_provider,packet)==0&&f.disk.files.at(Path)==saved&&f.mesh.queued==0); }else if(scenario=="gps_boundaries_and_globe"){ assert(FleetCommand::withinRadius(0,0,1,0,8)); assert(!FleetCommand::withinRadius(0,0,1,0,10)); assert(FleetCommand::withinRadius(0,0,1000,0,8990)); assert(!FleetCommand::withinRadius(0,0,1000,0,9000)); assert(FleetCommand::withinRadius(90000000,0,1,90000000,180000000)); f.location.latitude=0;f.location.longitude=-179900000; assert(f.send("gps:0,179.9:25")==1&&f.send("gps:0,179.9:22")==0); f.location.latitude=89900000;f.location.longitude=180000000; assert(f.send("gps:89.9,0:25")==1&&f.send("gps:89.9,0:22")==0); f.location.latitude=90000000;f.location.longitude=180000000; assert(f.send("gps:90,0:0.001")==1); }else if(scenario=="gps_mixed_targets_once"){ const std::string key=publicHex(f.mesh.self_id); assert(f.send(std::string(Seattle)+";sea;home:sea;"+key)==1); assert(f.calls==1&&f.mesh.queued==1&&last_jitter_max==10000); assert(f.send(std::string("gps:0,0:1;")+Seattle+";home:pdx")==1); assert(f.send(key+";"+Seattle)==1); // Internal GPS comma remains distinct from target-list separators. f.location.valid=false;assert(f.send(std::string(Seattle)+";sea")==1); // OR semantics retain valid region matches. const auto saved=f.disk.files.at(Path);LocalIdentity stranger; assert(f.send(std::string(Seattle)+";home:pdx;"+publicHex(stranger))==0&&f.disk.files.at(Path)==saved); }else if(scenario=="gps_signed_metadata_tamper"){ auto good=envelope(f.publisher,f.mesh.clock.now,f.expected,Seattle); assert(good[12]==1&&good[13]==FleetCommand::GeoRecordType); const auto saved=f.disk.files.at(Path); for(unsigned field:{14U,18U,22U,13U}){ auto altered=good;altered[field]^=1;Packet changed=whole(altered); assert(f.feed(changed)==0&&f.disk.files.at(Path)==saved&&f.mesh.queued==0); } Packet valid=whole(good);assert(f.feed(valid)==1&&f.calls==1); }else if(scenario=="gps_malformed_after_matching_region"){ auto good=envelope(f.publisher,f.mesh.clock.now,f.expected,std::string("sea;")+Seattle); assert(good[12]==2&&good[13]==0x20&&good[18]==FleetCommand::GeoRecordType); const auto saved=f.disk.files.at(Path); for(unsigned damage=0;damage<5;++damage){ auto broken=good; if(damage==0)storage::writeLE32(broken.data()+19,90000001); else if(damage==1)storage::writeLE32(broken.data()+23,180000001); else if(damage==2)storage::writeLE32(broken.data()+27,0); else if(damage==3)storage::writeLE32(broken.data()+27,FleetCommand::MaxRadiusMeters+1); else broken[18]=0x23; resign(f.publisher,broken);Packet packet=whole(broken); assert(f.feed(packet)==0&&f.disk.files.at(Path)==saved&&f.mesh.queued==0); } Packet valid=whole(good);assert(f.feed(valid)==1&&f.calls==1); }else if(scenario=="gps_durable_replay"){ Packet packet=targetsCommand(f.publisher,f.mesh.clock.now,Seattle); assert(f.feed(packet)==1);const auto persisted=f.disk.files.at(Path); assert(persisted.size()==64&&!memcmp(persisted.data(),"FCS1",4)&&f.reserved()==f.mesh.clock.now); assert(f.feed(packet)==0&&f.disk.files.at(Path)==persisted); FleetChannel rebooted(&f.disk,&f.location,&f.regions); assert(f.feed(rebooted,packet)==0&&f.disk.files.at(Path)==persisted&&f.calls==1&&f.mesh.queued==1); }else if(scenario=="gps_fragments_fix_changes"){ f.expected="set flood.rule.1 any "+std::string(150,' ')+"drop"; auto bytes=envelope(f.publisher,f.mesh.clock.now,f.expected,Seattle); assert(bytes.size()>FleetCommand::MaxPayloadLength);Packet first=part(bytes,0),second=part(bytes,1); const auto saved=f.disk.files.at(Path);const unsigned reads=f.location.reads; assert(f.feed(first)==0&&f.disk.files.at(Path)==saved&&f.location.reads==reads&&f.mesh.queued==0); f.location.latitude=45515200;f.location.longitude=-122678400; assert(f.feed(second)==0&&f.disk.files.at(Path)==saved&&f.calls==0&&f.mesh.queued==0); f.location.latitude=47606200;f.location.longitude=-122332100; assert(f.feed(second)==0&&f.feed(first)==1&&f.reserved()==f.mesh.clock.now); assert(f.feed(first)==0&&f.feed(second)==0&&f.calls==1); }else if(scenario=="gps_fragments_invalid_final_fix"){ f.expected="set flood.rule.1 any "+std::string(150,' ')+"drop"; auto bytes=envelope(f.publisher,f.mesh.clock.now,f.expected,Seattle);Packet first=part(bytes,0),second=part(bytes,1); const auto saved=f.disk.files.at(Path);assert(f.feed(second)==0);f.location.valid=false; assert(f.feed(first)==0&&f.disk.files.at(Path)==saved&&f.calls==0&&f.mesh.queued==0); f.location.valid=true; Packet tampered=changePart(second,[](uint8_t* frame){frame[11+1]^=1;}); assert(f.feed(first)==0&&f.feed(tampered)==0&&f.calls==0&&f.disk.files.at(Path)==saved); assert(f.feed(second)==0&&f.feed(first)==1&&f.calls==1); FleetChannel rebooted(&f.disk,&f.location,&f.regions); assert(f.feed(rebooted,first)==0&&f.feed(rebooted,second)==0&&f.calls==1); }else assert(false); printf("fleet runtime %s passed\n",scenario.c_str());return 0; } static int ackDelayScenario(){ for(int boundary:{-1,1}){ jitter_pick_boundary=boundary; for(const std::string mode:{"all","radio","prefix","list","region","home","gps","individual"}){ RegionFixture f;f.location.valid=true;f.location.latitude=47606200;f.location.longitude=-122332100; LocalIdentity other;std::string target="all"; if(mode=="prefix")target=publicHex(f.mesh.self_id).substr(0,8); else if(mode=="list")target=publicHex(other)+";"+publicHex(f.mesh.self_id); else if(mode=="region")target="sea"; else if(mode=="home")target="home:sea"; else if(mode=="gps")target=Seattle; else if(mode=="individual")target=publicHex(f.mesh.self_id); const bool mutation=mode=="radio";f.mesh.fanout=mutation; const char* text=mutation?"set radio2 off":"get radio2"; Packet packet=targetsCommand(f.publisher,f.mesh.clock.now,target,text);unsigned calls=0; fake_ms+=1000;f.fleet.receive(&packet,f.mesh); f.fleet.service(f.mesh,f.profiles,"bounded-delay",[&](uint32_t sequence,const char* command,char* reply){ ++calls;assert(sequence==f.reserved()&&!strcmp(command,text)); if(mutation)f.profiles.stage();strcpy(reply,"OK"); }); const uint32_t upper=mode=="individual"?1500:10000; const uint32_t expected=boundary<0?500:upper; assert(calls==1&&last_jitter_max==upper&&f.mesh.ack_delay==expected&&f.mesh.ack_delay<=10000); assert(f.mesh.queued==1&&f.mesh.manager.outbound.size()==(mutation?2U:1U)); if(mutation){ assert(f.fleet.waiting()&&f.profiles.mutation&&f.profiles.commits==0); f.fleet.complete(f.mesh.manager.removeOutboundByIdx(0),f.profiles); assert(f.fleet.waiting()&&f.profiles.mutation&&f.profiles.commits==0); f.fleet.complete(f.mesh.manager.removeOutboundByIdx(0),f.profiles); assert(!f.fleet.waiting()&&!f.profiles.mutation&&f.profiles.commits==1); } } } jitter_pick_boundary=0;puts("fleet runtime ack_delay_ceiling passed");return 0; } int main(int argc,char** argv){ assert(argc==2);const std::string scenario=argv[1]; if(scenario=="ack_delay_ceiling")return ackDelayScenario(); if(scenario.rfind("gps_",0)==0)return gpsScenario(scenario); if(scenario.rfind("regions_",0)==0)return regionScenario(scenario); if(scenario.rfind("fragments_",0)==0)return fragmentScenario(scenario); fs::FS fs;Mesh mesh;RadioProfileCLI profiles; FleetChannel fleet(&fs);LocalIdentity publisher;bind(fleet,mesh,profiles); if(scenario=="enrollment"){ Packet packet=command(publisher,mesh.clock.now);assert(apply(fleet,fs,mesh,profiles,packet)==0); assert(config(fleet,"get fleet.channel").find("off,controller=unset,store=ok")!=std::string::npos); assert(config(fleet,"set fleet.channel 00000000000000000000000000000000").rfind("Err",0)==0); assert(config(fleet,"set fleet.channel 8b3387e9c5cdea6ac9e5edbaa115cd72").rfind("Err",0)==0); assert(config(fleet,"set fleet.controller 0000000000000000000000000000000000000000000000000000000000000000").rfind("Err",0)==0); assert(config(fleet,std::string("set fleet.channel ")+Key).rfind("OK",0)==0); assert(apply(fleet,fs,mesh,profiles,packet)==0);enroll(fleet,publisher); const auto& image=fs.files.at(Path);assert(image.size()==64); assert(!memcmp(image.data()+24,publisher.pub_key,32)); const std::vector private_seed(32,0xAB); assert(std::search(image.begin(),image.end(),private_seed.begin(),private_seed.end())==image.end()); assert(apply(fleet,fs,mesh,profiles,packet)==1); assert(config(fleet,"set fleet.controller off").rfind("OK",0)==0); ++mesh.clock.now;packet=command(publisher,mesh.clock.now);assert(apply(fleet,fs,mesh,profiles,packet)==0); }else if(scenario=="deferred_single_mailbox"){ enroll(fleet,publisher);Packet a=command(publisher,mesh.clock.now),b=command(publisher,mesh.clock.now+1); unsigned decrypt_before=decrypt_calls,verify_before=verify_calls,writes_before=fs.writes; fleet.receive(&a,mesh);fleet.receive(&b,mesh); assert(decrypt_calls==decrypt_before&&verify_calls==verify_before&&fs.writes==writes_before); assert(config(fleet,"get fleet.stats").find("busy=1 pending=1")!=std::string::npos); assert(config(fleet,"set fleet.channel off").rfind("Err",0)==0); unsigned calls=0;fleet.service(mesh,profiles,"node",[&](uint32_t seq,const char*,char* reply){ ++calls;assert(seq==mesh.clock.now);strcpy(reply,"OK");});assert(calls==1); assert(config(fleet,"get fleet.stats").find("accepted=1")!=std::string::npos); }else if(scenario=="durable_replay"){ enroll(fleet,publisher);Packet packet=command(publisher,mesh.clock.now); assert(apply(fleet,fs,mesh,profiles,packet,false,true)==1); assert(apply(fleet,fs,mesh,profiles,packet)==0);FleetChannel rebooted(&fs); assert(apply(rebooted,fs,mesh,profiles,packet)==0); ++mesh.clock.now;packet=command(publisher,mesh.clock.now);assert(apply(rebooted,fs,mesh,profiles,packet)==1); }else if(scenario=="reserve_fault"){ enroll(fleet,publisher);const auto previous=fs.files.at(Path);fs.fail_write=true; Packet packet=command(publisher,mesh.clock.now);assert(apply(fleet,fs,mesh,profiles,packet)==0); assert(fs.files.at(Path)==previous);fs.fail_write=false; assert(config(fleet,"get fleet.channel").find("store=error")!=std::string::npos); assert(apply(fleet,fs,mesh,profiles,packet)==0);FleetChannel rebooted(&fs); assert(apply(rebooted,fs,mesh,profiles,packet)==1); }else if(scenario=="config_fault"){ enroll(fleet,publisher);const auto previous=fs.files.at(Path);fs.fail_write=true; assert(config(fleet,"set fleet.channel off").rfind("Err",0)==0); assert(fs.files.at(Path)==previous);fs.fail_write=false; assert(config(fleet,"get fleet.channel").find("on,controller=set")!=std::string::npos); FleetChannel rebooted(&fs);Packet packet=command(publisher,mesh.clock.now); assert(apply(rebooted,fs,mesh,profiles,packet)==1); }else if(scenario=="images"){ enroll(fleet,publisher);const auto original=fs.files.at(Path); for(unsigned damage=0;damage<7;++damage){ fs.files[Path]=original;auto& image=fs.files[Path]; if(damage==0)image.resize(63);else if(damage==1)image.push_back(0); else if(damage==2)image[0]='X';else if(damage==3)image[4]=2; else if(damage==4)image[5]=1;else if(damage==5)image[60]^=1; else memset(image.data()+8,0,16); if(damage>=3&&damage!=5)reseal(image); FleetChannel bad(&fs);assert(config(bad,"get fleet.channel").find("store=error")!=std::string::npos); Packet packet=command(publisher,mesh.clock.now);assert(apply(bad,fs,mesh,profiles,packet)==0); assert(config(bad,std::string("set fleet.channel ")+Key).rfind("Err",0)==0); } fs.files[Path]=original;fs.fail_read=true;FleetChannel unreadable(&fs);fs.fail_read=false; assert(config(unreadable,"get fleet.channel").find("store=error")!=std::string::npos); #if defined(RP2040_PLATFORM) fs.files.erase(Path);fs.files[std::string(Path)+".bak"]=original; FleetChannel recovered(&fs);assert(config(recovered,"get fleet.channel").find("store=ok")!=std::string::npos); assert(fs.files[Path]==original);Packet packet=command(publisher,mesh.clock.now); assert(apply(recovered,fs,mesh,profiles,packet)==1); #endif }else if(scenario=="ack_both_copies"){ enroll(fleet,publisher);mesh.fanout=true;Packet packet=command(publisher,mesh.clock.now); TransportKey scope;memset(scope.key,0x12,sizeof(scope.key)); packet.scoped=true;packet.transport_codes[0]=scope.calcTransportCode(&packet);packet.transport_codes[1]=456; assert(apply(fleet,fs,mesh,profiles,packet,true,false,&scope)==1);assert(fleet.waiting()); assert(mesh.manager.outbound.size()==2);assert(mesh.ack_scoped&&mesh.ack_codes[1]==0); assert(mesh.ack_codes[0]==scope.calcTransportCode(mesh.manager.outbound[0])); assert(mesh.ack_codes[0]!=packet.transport_codes[0]); assert(mesh.ack_delay==1000&&mesh.ack_path_size==2&&last_jitter_max==10000); assert(mesh.manager.outbound[0]->radio_reply&&!mesh.manager.outbound[0]->radio_bound); assert(mesh.manager.outbound[0]->flood_retry_policy==FLOOD_RETRY_POLICY_DENY); fleet.receive(&packet,mesh);assert(config(fleet,"get fleet.stats").find("busy=1")!=std::string::npos); fleet.complete(mesh.manager.removeOutboundByIdx(0),profiles);assert(fleet.waiting());assert(profiles.commits==0); fleet.fail(mesh.manager.removeOutboundByIdx(0),profiles);assert(!fleet.waiting());assert(profiles.commits==1&&profiles.rollbacks==0); }else if(scenario=="ack_all_fail"){ enroll(fleet,publisher);mesh.fanout=true;Packet packet=command(publisher,mesh.clock.now); assert(apply(fleet,fs,mesh,profiles,packet,true)==1); fleet.fail(mesh.manager.removeOutboundByIdx(0),profiles);assert(fleet.waiting()&&profiles.rollbacks==0); fleet.fail(mesh.manager.removeOutboundByIdx(0),profiles);assert(!fleet.waiting());assert(profiles.rollbacks==1); }else if(scenario=="ack_queue_or_allocation"){ enroll(fleet,publisher);Packet packet=command(publisher,mesh.clock.now);mesh.queue_ok=false; assert(apply(fleet,fs,mesh,profiles,packet,true)==1);assert(!fleet.waiting()&&profiles.rollbacks==1); assert(apply(fleet,fs,mesh,profiles,packet,true)==0);mesh.queue_ok=true;mesh.allocation_ok=false; ++mesh.clock.now;packet=command(publisher,mesh.clock.now); assert(apply(fleet,fs,mesh,profiles,packet,true)==1);assert(profiles.rollbacks==2); }else if(scenario=="ack_timeout"){ enroll(fleet,publisher);mesh.fanout=true;Packet packet=command(publisher,mesh.clock.now); assert(apply(fleet,fs,mesh,profiles,packet,true)==1);fake_ms+=300000; fleet.service(mesh,profiles,"node",[](uint32_t,const char*,char*){assert(false);}); assert(mesh.manager.outbound.empty()&&mesh.released==2);assert(profiles.rollbacks==1&&!fleet.waiting()); ++mesh.clock.now;packet=command(publisher,mesh.clock.now);assert(apply(fleet,fs,mesh,profiles,packet,true)==1); mesh.in_flight=mesh.manager.removeOutboundByIdx(0);fake_ms+=300000; fleet.service(mesh,profiles,"node",[](uint32_t,const char*,char*){assert(false);}); assert(mesh.cancelled==1&&fleet.waiting());fleet.fail(mesh.in_flight,profiles);mesh.in_flight=nullptr; assert(!fleet.waiting()&&profiles.rollbacks==2); }else if(scenario=="tamper_padding_target"){ enroll(fleet,publisher);Packet good=command(publisher,mesh.clock.now); uint8_t data[184];int size=Utils::MACThenDecrypt(channel_key,data,good.payload+1,good.payload_len-1); data[3+29]^=1;Packet forged=raw(data,size);assert(apply(fleet,fs,mesh,profiles,forged)==0); size=Utils::MACThenDecrypt(channel_key,data,good.payload+1,good.payload_len-1); data[size-1]=1;Packet padded=raw(data,size);assert(apply(fleet,fs,mesh,profiles,padded)==0); Packet mac=good;mac.payload[1]^=1;assert(apply(fleet,fs,mesh,profiles,mac)==0); char target[65];uint8_t other[32];memset(other,0x23,sizeof(other));Utils::toHex(target,other,sizeof(other)); Packet wrong=command(publisher,mesh.clock.now,"set radio2 off",target);assert(apply(fleet,fs,mesh,profiles,wrong)==0); fake_ms+=1000;Utils::toHex(target,mesh.self_id.pub_key,32); Packet mine=command(publisher,mesh.clock.now,"set radio2 off",target);assert(apply(fleet,fs,mesh,profiles,mine)==1); assert(last_jitter_max==1500); }else if(scenario=="verify_budget"){ enroll(fleet,publisher);LocalIdentity stranger; Packet forged=command(stranger,mesh.clock.now); const unsigned before=verify_calls; for(unsigned i=0;i<20;++i)assert(apply(fleet,fs,mesh,profiles,forged)==0); assert(verify_calls-before==4);fake_ms+=1000; Packet valid=command(publisher,mesh.clock.now); assert(apply(fleet,fs,mesh,profiles,valid)==1); }else if(scenario=="invalid_mac_cannot_starve"){ enroll(fleet,publisher);Packet valid=command(publisher,mesh.clock.now); Packet garbage=valid;garbage.payload[1]^=1; const unsigned before=verify_calls; for(unsigned i=0;i<20;++i)assert(apply(fleet,fs,mesh,profiles,garbage)==0); assert(verify_calls==before); assert(apply(fleet,fs,mesh,profiles,valid)==1); assert(verify_calls==before+1&&mesh.queued==1); }else if(scenario=="replay_cannot_starve"){ enroll(fleet,publisher);const uint32_t seq=mesh.clock.now; Packet previous=command(publisher,seq); assert(apply(fleet,fs,mesh,profiles,previous)==1); fake_ms+=1000;const unsigned before=verify_calls; for(unsigned i=0;i<20;++i)assert(apply(fleet,fs,mesh,profiles,previous)==0); assert(verify_calls==before); Packet next=command(publisher,seq+1); assert(apply(fleet,fs,mesh,profiles,next)==1&&verify_calls==before+1); }else if(scenario=="wrong_target_cannot_starve"){ enroll(fleet,publisher);LocalIdentity other; Packet not_mine=command(publisher,mesh.clock.now,"get radio2",publicHex(other).c_str()); const unsigned before=verify_calls; for(unsigned i=0;i<20;++i)assert(apply(fleet,fs,mesh,profiles,not_mine)==0); assert(verify_calls==before); Packet valid=command(publisher,mesh.clock.now); assert(apply(fleet,fs,mesh,profiles,valid)==1&&verify_calls==before+1); }else if(scenario=="primary_denied"){ assert(!FleetCommand::commandAllowed("set radio 915,500,5,5")); assert(!FleetCommand::commandAllowed("set tempradio 915,500,5,5,10")); assert(!FleetCommand::commandAllowed("set password pass")); assert(!FleetCommand::commandAllowed("reboot")); assert(!FleetCommand::commandAllowed("set fleet.controller off")); }else if(scenario=="signed_forbidden"){ enroll(fleet,publisher); for(const char* text:{"set radio 915,500,5,5","set password pass","reboot", "set fleet.channel off","get prv.key","set flood.max 5;reboot"}){ fake_ms+=1000;Packet packet=signedUnchecked(publisher,mesh.clock.now,mesh.clock.now+300,text); assert(apply(fleet,fs,mesh,profiles,packet)==0); } assert(storage::readLE32(fs.files[Path].data()+56)==0);assert(mesh.queued==0); }else if(scenario=="clock_limits"){ enroll(fleet,publisher);const uint32_t now=mesh.clock.now; const std::vector> invalid={{now-301,now-1},{now+61,now+100}, {FleetCommand::MinEpoch-1,now+100},{now,now+FleetCommand::MaxLifetime+1},{now,now-1}}; for(const auto& times:invalid){fake_ms+=1000; Packet packet=signedUnchecked(publisher,times.first,times.second,"set radio2 off"); assert(apply(fleet,fs,mesh,profiles,packet)==0); } mesh.clock.now=0;fake_ms+=1000;Packet packet=command(publisher,now); assert(apply(fleet,fs,mesh,profiles,packet)==0);mesh.clock.now=now; fake_ms+=1000;packet=signedUnchecked(publisher,now+60,now+100,"set radio2 off"); assert(apply(fleet,fs,mesh,profiles,packet)==1); }else if(scenario=="transaction_faults"){ for(unsigned fault=0;fault<4;++fault){ fs::FS disk;Mesh transport;RadioProfileCLI radio;FleetChannel receiver(&disk);enroll(receiver,publisher); auto previous=disk.files.at(Path);Packet packet=command(publisher,transport.clock.now); if(fault==0)disk.fail_open=true; else if(fault==1)disk.fail_read=true; else if(fault==2)disk.fail_rename=true; else disk.fail_rename_from=std::string(Path)+".tmp"; assert(apply(receiver,disk,transport,radio,packet)==0);assert(disk.files.at(Path)==previous); assert(config(receiver,"get fleet.channel").find("store=error")!=std::string::npos); } }else if(scenario=="backup_replay"){ enroll(fleet,publisher);Packet packet=command(publisher,mesh.clock.now); assert(apply(fleet,fs,mesh,profiles,packet)==1);const auto persisted=fs.files.at(Path); #if defined(RP2040_PLATFORM) fs.files.erase(Path);fs.files[std::string(Path)+".bak"]=persisted; #else // Atomic LittleFS replacement never has a live-name gap. Abandoned .tmp // bytes are ignored; the committed live sequence remains authoritative. fs.files[std::string(Path)+".tmp"]=persisted; #endif FleetChannel rebooted(&fs);assert(apply(rebooted,fs,mesh,profiles,packet)==0); assert(storage::readLE32(fs.files.at(Path).data()+56)==mesh.clock.now); }else if(scenario=="wrong_publisher"){ enroll(fleet,publisher);LocalIdentity stranger; assert(memcmp(stranger.pub_key,publisher.pub_key,32)); Packet packet=command(stranger,mesh.clock.now);assert(apply(fleet,fs,mesh,profiles,packet)==0); assert(verify_calls==1&&mesh.queued==0&&storage::readLE32(fs.files[Path].data()+56)==0); }else if(scenario=="bounded_ack"){ enroll(fleet,publisher);Packet packet=command(publisher,mesh.clock.now); fleet.receive(&packet,mesh);fleet.service(mesh,profiles,"sixteen:character:very long name", [&](uint32_t,const char*,char* reply){memset(reply,'x',159);reply[159]=0;}); assert(mesh.queued==1);uint8_t data[184]={};Packet* ack=mesh.manager.outbound[0]; assert(ack->payload_len<=MAX_PACKET_PAYLOAD); assert(Utils::MACThenDecrypt(channel_key,data,ack->payload+1,ack->payload_len-1)==176); assert(!strncmp((char*)data+5,"sixteen;characte",15)); }else if(scenario=="unknown_scope"){ enroll(fleet,publisher);Packet packet=command(publisher,mesh.clock.now);packet.scoped=true; packet.transport_codes[0]=123;assert(apply(fleet,fs,mesh,profiles,packet,true)==1); assert(mesh.queued==0&&mesh.released==1&&profiles.rollbacks==1&&!fleet.waiting()); }else if(scenario=="rotated_scope"){ struct Region {bool wildcard=false;bool isWildcard()const{return wildcard;}}; struct Regions {TransportKey keys[2];int getTransportKeysFor(const Region&,TransportKey* out,int max){ assert(max==MAX_TKS_ENTRIES);memcpy(out,keys,sizeof(keys));return 2;}} regions; memset(regions.keys[0].key,0x11,16);memset(regions.keys[1].key,0x22,16); Region region;TransportKey captured{};Packet packet=command(publisher,mesh.clock.now); packet.scoped=true;packet.transport_codes[0]=regions.keys[1].calcTransportCode(&packet); assert(packet.transport_codes[0]!=regions.keys[0].calcTransportCode(&packet)); assert(captureFleetReplyScope(regions,®ion,&packet,captured)); assert(!memcmp(captured.key,regions.keys[1].key,16)); enroll(fleet,publisher);assert(apply(fleet,fs,mesh,profiles,packet,true,false,&captured)==1); assert(mesh.ack_codes[0]==regions.keys[1].calcTransportCode(mesh.manager.outbound[0])); assert(mesh.ack_codes[0]!=packet.transport_codes[0]); region.wildcard=true;assert(!captureFleetReplyScope(regions,®ion,&packet,captured)); assert(!captureFleetReplyScope(regions,static_cast(nullptr),&packet,captured)); region.wildcard=false;packet.transport_codes[0]^=1; assert(!captureFleetReplyScope(regions,®ion,&packet,captured)); }else if(scenario=="fmc2_short_all"){ enroll(fleet,publisher);Packet packet=shortNewAll(publisher,mesh.clock.now); uint8_t plaintext[184]={};assert(Utils::MACThenDecrypt(channel_key,plaintext,packet.payload+1,packet.payload_len-1)>0); assert(plaintext[2]0); assert(!memcmp(plaintext+3,"FMC2",4)&&plaintext[3+12]==0); assert(plaintext[2]==FleetCommand::MinHeaderSize+strlen("get radio2")+FleetCommand::SignatureSize); assert(plaintext[2]0); assert(plaintext[2]==FleetCommand::HeaderSize+FleetCommand::SignatureSize); assert(!memcmp(plaintext+3,"FMC2",4)); assert(apply(fleet,fs,mesh,profiles,packet)==1);assert(last_jitter_max==10000); FleetChannel rebooted(&fs);assert(apply(rebooted,fs,mesh,profiles,packet)==0); }else if(scenario=="fmc2_overlap_matches_once"){ enroll(fleet,publisher);const std::string mine=publicHex(mesh.self_id); Packet packet=targetsCommand(publisher,mesh.clock.now,mine.substr(0,8)+";"+mine); assert(apply(fleet,fs,mesh,profiles,packet)==1);assert(mesh.queued==1&&last_jitter_max==10000); assert(config(fleet,"get fleet.stats").find("accepted=1")!=std::string::npos); }else if(scenario=="fmc2_nonmatching_no_reserve"){ enroll(fleet,publisher);const auto previous=fs.files.at(Path);LocalIdentity stranger; char different_prefix[9];uint8_t changed[4];memcpy(changed,mesh.self_id.pub_key,4);changed[0]^=1; Utils::toHex(different_prefix,changed,4); Packet packet=targetsCommand(publisher,mesh.clock.now,std::string(different_prefix)+";"+publicHex(stranger)); assert(apply(fleet,fs,mesh,profiles,packet)==0);assert(mesh.queued==0&&fs.files.at(Path)==previous); packet=targetsCommand(publisher,mesh.clock.now,publicHex(stranger)+";"+publicHex(mesh.self_id)); assert(apply(fleet,fs,mesh,profiles,packet)==1);assert(last_jitter_max==10000); }else if(scenario=="fmc2_target_tamper"){ enroll(fleet,publisher);const auto previous=fs.files.at(Path); Packet good=targetsCommand(publisher,mesh.clock.now,publicHex(mesh.self_id).substr(0,12)); uint8_t plaintext[184]={};const int length=Utils::MACThenDecrypt(channel_key,plaintext,good.payload+1,good.payload_len-1); assert(length>0);plaintext[3+13]^=1;Packet changed=raw(plaintext,length); assert(apply(fleet,fs,mesh,profiles,changed)==0);assert(fs.files.at(Path)==previous); assert(apply(fleet,fs,mesh,profiles,good)==1); }else if(scenario=="existing_mutation"){ enroll(fleet,publisher);Packet packet=command(publisher,mesh.clock.now); profiles.mutation=true;assert(apply(fleet,fs,mesh,profiles,packet)==0); assert(storage::readLE32(fs.files[Path].data()+56)==0); profiles.mutation=false;assert(apply(fleet,fs,mesh,profiles,packet)==1); }else assert(false); printf("fleet runtime %s passed\n",argv[1]); } ''' class FleetChannelRuntimeTests(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.work = tempfile.TemporaryDirectory(prefix="meshcore-fleet-runtime-") work = Path(cls.work.name) (work / "helpers").mkdir() for name, content in {"Utils.h": UTILS, "FS.h": FILESYSTEM, "Mesh.h": MESH, "helpers/RadioProfileCLI.h": PROFILE, "helpers/RegionMap.h": REGION_MAP, "Packet.h": '#pragma once\n#include \n', "Adafruit_LittleFS.h": ''' #pragma once #include using Adafruit_LittleFS = fs::FS; namespace Adafruit_LittleFS_Namespace {} #define FILE_O_READ 1 #define FILE_O_WRITE 2 #define LFS_ERR_NOENT -2 struct lfs_info {}; inline int lfs_stat(fs::FS* fs,const char* path,lfs_info*) {return fs->exists(path)?0:LFS_ERR_NOENT;} ''', "InternalFileSystem.h": '#pragma once\n#include \ninline fs::FS InternalFS;\n', "Arduino.h": '#pragma once\n#include \n'}.items(): (work / name).write_text(content) fixture = work / "fixture.cpp" transport_source = (ROOT / "src/helpers/TransportKeyStore.cpp").read_text() methods = "\n".join(body(transport_source, signature) for signature in [ "uint16_t TransportKey::calcTransportCode(", "bool TransportKey::isNull("]) fixture.write_text(HARNESS.replace("@TRANSPORT_METHODS@", methods)) sha_header = ROOT / "test/mocks/SHA256.h" (work / "SHA256.h").write_text(f''' #pragma once #define SHA256 SHA256Base #include "{sha_header}" #undef SHA256 class SHA256 : public SHA256Base {{ public: void finalize(void* out,size_t length) {{ SHA256Base::finalize(static_cast(out),length); }} void finalizeHMAC(const uint8_t* key,size_t size,void* out,size_t length) {{ SHA256Base::finalizeHMAC(key,size,static_cast(out),length); }} }}; ''') cls.binaries = {} sanitizer_flags = (["-fsanitize=address,undefined", "-fno-omit-frame-pointer", "-no-pie"] if os.environ.get("MESHCORE_FLEET_SANITIZERS") == "1" else []) for backend in ["RP2040", "NRF52"]: binary = work / f"fleet-runtime-{backend.lower()}" sources = [str(ROOT / "src/helpers/FleetChannel.cpp"), str(ROOT / "src/helpers/FleetCommand.cpp")] if backend == "NRF52": sources.append(str(ROOT / "src/helpers/AtomicFileWriter.cpp")) result = subprocess.run([ compiler, *sanitizer_flags, "-std=c++17", f"-D{backend}_PLATFORM", "-Wall", "-Wextra", "-Werror", "-Wno-unused-parameter", "-Wno-misleading-indentation", "-Wno-sign-compare", "-O2", "-g", "-I", str(work), "-isystem", str(ROOT / "test/mocks"), "-I", str(ROOT / "src"), str(fixture), *sources, "-lcrypto", "-o", str(binary), ], capture_output=True, text=True, timeout=60) if result.returncode: cls.work.cleanup() raise AssertionError(f"{backend}:\n" + result.stdout + result.stderr) cls.binaries[backend] = binary @classmethod def tearDownClass(cls): cls.work.cleanup() def scenario(self, name): for backend, binary in self.binaries.items(): with self.subTest(backend=backend): result = subprocess.run([str(binary), name], capture_output=True, text=True, timeout=10) self.assertEqual(result.returncode, 0, result.stdout + result.stderr) def test_gps_center_radius_targets_inside_seattle_and_excludes_portland(self): self.scenario("gps_inside_outside") def test_gps_unknown_invalid_out_of_range_and_absent_location_providers_fail_closed(self): self.scenario("gps_unknown_and_bad_fixes") def test_gps_small_distance_boundary_antimeridian_and_poles_use_spherical_distance(self): self.scenario("gps_boundaries_and_globe") def test_gps_region_home_and_node_target_matches_use_or_semantics_and_execute_once(self): self.scenario("gps_mixed_targets_once") def test_gps_latitude_longitude_radius_and_record_type_are_signed(self): self.scenario("gps_signed_metadata_tamper") def test_signed_malformed_gps_record_after_matching_region_cannot_authorize_execution(self): self.scenario("gps_malformed_after_matching_region") def test_gps_command_replay_survives_reboot_with_the_existing_64_byte_storage_format(self): self.scenario("gps_durable_replay") def test_two_packet_gps_target_uses_location_at_completion_before_reserving_sequence(self): self.scenario("gps_fragments_fix_changes") def test_two_packet_gps_rejects_invalid_final_fix_tampering_and_reboot_replays(self): self.scenario("gps_fragments_invalid_final_fix") def test_every_group_ack_uses_at_most_ten_seconds_and_single_identity_retains_1500ms(self): self.scenario("ack_delay_ceiling") def test_semicolon_is_the_only_target_separator_with_comma_reserved_for_gps_coordinates(self): self.scenario("regions_target_delimiter") def test_existing_configured_regions_and_home_ancestors_are_distinct_exact_targets(self): self.scenario("regions_list_home") def test_private_region_dollar_identity_is_preserved_in_list_and_home_targets(self): self.scenario("regions_private_names") def test_default_received_scope_and_missing_or_wildcard_home_never_grant_membership(self): self.scenario("regions_home_not_default_or_scope") def test_wildcard_rows_and_damaged_parent_cycles_fail_closed_without_hanging(self): self.scenario("regions_wildcard_and_cycles") def test_invalid_map_counts_unterminated_names_and_missing_parent_ids_fail_closed(self): self.scenario("regions_damaged_map_bounds") def test_receiver_without_region_map_refuses_regions_but_can_still_match_node_keys(self): self.scenario("regions_missing_map") def test_mixed_list_home_and_node_targets_execute_once_with_nonmembers_unreserved(self): self.scenario("regions_overlap_once") def test_region_name_and_mode_are_bound_to_the_real_publisher_signature(self): self.scenario("regions_signature_tamper") def test_signed_malformed_trailing_region_record_rejects_despite_prior_matching_region(self): self.scenario("regions_malformed_after_match") def test_region_target_replay_survives_reboot_without_changing_existing_storage_format(self): self.scenario("regions_durable_replay") def test_region_membership_is_rechecked_when_second_fragment_arrives_before_reservation(self): self.scenario("regions_fragments_membership_changes") def test_region_fragment_tampering_and_reboot_replay_never_repeat_execution(self): self.scenario("regions_fragments_tamper") def test_private_channel_and_public_only_publisher_enrollment(self): self.scenario("enrollment") def test_receive_defers_crypto_writes_and_accepts_one_pending_command(self): self.scenario("deferred_single_mailbox") def test_sequence_reserved_before_handler_including_error_survives_reboot(self): self.scenario("durable_replay") def test_failed_sequence_save_grants_no_control_and_disables_receiver(self): self.scenario("reserve_fault") def test_failed_configuration_write_preserves_durable_enrollment(self): self.scenario("config_fault") def test_corrupt_short_long_unreadable_images_fail_closed_and_backup_recovers(self): self.scenario("images") def test_ack_waits_for_both_profile_copies_with_one_success_and_retains_rx_scope(self): self.scenario("ack_both_copies") def test_both_ack_transmissions_failing_roll_back_radio_mutation(self): self.scenario("ack_all_fail") def test_ack_allocation_queue_failure_rolls_back_but_keeps_reserved_sequence(self): self.scenario("ack_queue_or_allocation") def test_ack_timeout_cancels_queued_and_inflight_packets_before_rollback(self): self.scenario("ack_timeout") def test_real_signature_mac_padding_and_full_identity_target_checks(self): self.scenario("tamper_padding_target") def test_forged_mac_authenticated_packets_have_bounded_signature_verification_cost(self): self.scenario("verify_budget") def test_invalid_mac_packets_cannot_exhaust_the_signature_verification_budget(self): self.scenario("invalid_mac_cannot_starve") def test_recorded_used_command_replays_cannot_spend_signature_budget_before_next_command(self): self.scenario("replay_cannot_starve") def test_recorded_commands_for_another_target_cannot_spend_signature_budget(self): self.scenario("wrong_target_cannot_starve") def test_fleet_permission_never_allows_primary_radio_or_administrator_changes(self): self.scenario("primary_denied") def test_publisher_signed_commands_still_cannot_escape_fleet_capability(self): self.scenario("signed_forbidden") def test_expiry_future_clock_bounds_and_invalid_rtc_prevent_dispatch(self): self.scenario("clock_limits") def test_open_read_rename_and_publish_faults_preserve_state_and_fail_closed(self): self.scenario("transaction_faults") def test_recovered_backup_preserves_durable_anti_replay_sequence(self): self.scenario("backup_replay") def test_another_valid_ed25519_identity_cannot_authorize_fleet_commands(self): self.scenario("wrong_publisher") def test_long_cli_reply_and_sender_name_fit_one_encrypted_group_packet(self): self.scenario("bounded_ack") def test_unknown_scoped_route_refuses_ack_and_rolls_back_radio_mutation(self): self.scenario("unknown_scope") def test_scope_selects_matching_rotated_region_key_and_authenticates_new_ack_payload(self): self.scenario("rotated_scope") def test_new_short_broadcast_envelope_dispatches_and_preserves_replay_protection(self): self.scenario("fmc2_short_all") def test_actual_zero_target_sender_reaches_receiver_and_keeps_ack_and_reboot_replay_safety(self): self.scenario("fmc2_encoded_all") def test_single_packet_87_byte_body_is_intact_signed_and_88_bytes_require_fragmentation(self): self.scenario("fmc2_maximum_command") def test_new_public_key_prefix_envelope_dispatches_and_staggers_possible_multiple_acks(self): self.scenario("fmc2_short_prefix") def test_overlapping_prefix_and_full_key_matches_execute_once(self): self.scenario("fmc2_overlap_matches_once") def test_nonmatching_target_list_never_reserves_sequence_and_matching_list_can_reuse_it(self): self.scenario("fmc2_nonmatching_no_reserve") def test_modified_new_target_header_never_dispatches_without_valid_signature(self): self.scenario("fmc2_target_tamper") def test_existing_remote_radio_mutation_blocks_fleet_without_reserving_sequence(self): self.scenario("existing_mutation") def test_two_packets_deliver_exact_230_byte_command_in_either_order_once_across_reboot(self): self.scenario("fragments_orders") def test_identical_fragments_never_repeat_execution_or_reserve_partial_requests(self): self.scenario("fragments_duplicates") def test_smallest_fragmented_command_uses_a_short_second_packet_and_231_bytes_are_rejected(self): self.scenario("fragments_minimum_size") def test_command_expiring_between_parts_never_dispatches_and_fresh_retry_can_succeed(self): self.scenario("fragments_expired_during_collection") def test_conflicting_parts_cannot_destroy_a_valid_incomplete_transfer(self): self.scenario("fragments_conflicts") def test_unsigned_newer_fragment_cannot_invalidate_an_older_authenticated_envelope(self): self.scenario("fragments_fresh_retry") def test_forged_future_sequence_interleaved_with_both_legitimate_fragment_orders_cannot_lock_out(self): self.scenario("fragments_future_poison") def test_third_transfer_evicts_oldest_slot_instead_of_highest_unsigned_sequence(self): self.scenario("fragments_oldest_eviction") def test_failed_complete_poisoned_assembly_preserves_the_other_legitimate_slot(self): self.scenario("fragments_independent_failure") def test_duplicate_partial_fragments_cannot_spend_signature_verification_budget(self): self.scenario("fragments_budget_partial") def test_mac_and_last_signature_byte_tampering_never_authorize_partial_commands(self): self.scenario("fragments_tamper") def test_fragmented_request_from_another_real_signing_identity_is_rejected(self): self.scenario("fragments_wrong_signer") def test_fragment_deadline_is_fixed_despite_duplicates_and_missing_part(self): self.scenario("fragments_fixed_timeout") def test_fragment_deadline_handles_millisecond_timer_wrap(self): self.scenario("fragments_timeout_wrap") def test_fragment_sequence_plausibility_and_signed_sequence_match_precede_reservation(self): self.scenario("fragments_sequence_bounds") def test_fragmented_nonmatching_target_preserves_sequence_for_matching_retry(self): self.scenario("fragments_target") def test_channel_controller_changes_and_reboot_clear_partial_transfers(self): self.scenario("fragments_config_reboot") def test_fragment_completion_storage_failure_has_no_dispatch_or_reply(self): self.scenario("fragments_reserve_failure") def test_final_fragment_scope_and_radio_profile_feed_both_ack_copy_commit_barrier(self): self.scenario("fragments_scoped_radio_ack") def test_fragmented_radio_mutation_with_unknown_final_scope_rolls_back(self): self.scenario("fragments_unknown_scope") def test_new_valid_single_packet_can_interrupt_an_incomplete_two_packet_transfer(self): self.scenario("fragments_single_interrupt") def test_malformed_fragment_size_index_and_padding_never_allocate_partial_intent(self): self.scenario("fragments_malformed_frame") if __name__ == "__main__": unittest.main()