#include "FakeFilesystem.h" #include #include #define FILESYSTEM FakeFilesystem namespace mesh { struct Utils { static void toHex(char* dest, const uint8_t* src, int length) { for (int i=0; iopen(path);} void cancelContactWrite(){} void begin(); bool formatFileSystem(); bool queueAdvertByKey(const uint8_t[],int,const uint8_t[],uint8_t); bool serviceAdvertWrites(uint32_t); bool flushAdvertWrites(uint32_t now=0); bool hasPendingAdvertWrites() const; bool isAdvertWriteDue(uint32_t) const; bool consumeSynchronousAdvertIO(); uint8_t getBlobByKey(const uint8_t[],int,uint8_t[]); bool putBlobByKey(const uint8_t[],int,const uint8_t[],uint8_t); bool deleteBlobByKey(const uint8_t[],int); bool hasPendingContactWrites() const{return contact_pending;} bool serviceContactWrites(DataStoreHost*,bool(*)(const ContactInfo&)) { ++_fs->operations; ++contact_passes; return true; } bool flushContactWrites(DataStoreHost*,bool(*)(const ContactInfo&)) { ++_fs->operations; ++contact_passes; contact_pending=false; return true; } }; #include "presence.h" #include "store.h" constexpr uint32_t CONTACT_PAGE_WRITE_GAP=1; struct Clock { uint32_t now=10000; uint32_t getMillis() const{return now;} }; struct RadioDriver { bool isWatchdogObserving() const{return false;} bool isCalibratingNoiseFloor() const{return false;} } radio_driver; struct SerialInterface { bool hasPendingIO() const{return false;} bool isReplyRouteAvailable(const SerialInterface*) const{return false;} }; struct DelayedReplies { bool hasBinaryTrace() const{return false;} }; class MyMesh { public: DataStore* _store; Clock* _ms; bool _advert_write_next=true; unsigned long dirty_contacts_expiry=0; uint8_t dirty_contacts_failures=0; bool(*save_filter)(const ContactInfo&)=nullptr; bool _radio_available=false, _iter_started=false; bool command_radio_apply_pending=false, saved_radio_apply_pending=false; uint32_t _scheduled_reboot_at=0, radio_apply_retry_at=0; uint32_t emergency_client_repeat_send_at=0; void* sign_data=nullptr; void* emergency_client_repeat_packet=nullptr; SerialInterface* _serial=nullptr; SerialInterface* _iter_reply_route=nullptr; DelayedReplies _delayed_replies; bool putBlobByKey(const uint8_t[],int,const uint8_t[],int); void servicePersistence(); bool flushContactsBeforeReboot(); bool hasPendingWork() const; bool canRecoverUsbLogging() const; bool isDualRadioActive() const{return false;} bool hasOutbound() const{return false;} bool isAnyTempRadioActive() const{return false;} bool hasPendingOtaApply() const{return false;} bool hasPendingReqs() const{return false;} bool hasQueuedWorkDue() const{return false;} bool hasRetryWorkDue() const{return false;} bool millisHasNowPassed(uint32_t when) const{return _ms->now>=when;} bool isContactWriteDue() const{return _store->contact_pending;} void scheduleContactWriteRetry(){++dirty_contacts_failures;} unsigned long futureMillis(uint32_t delay) const{return _ms->now+delay;} }; #include "mesh.h" using Key=std::array; using Blob=std::vector; using Stage=DataStore::AdvertWriteState::Stage; Key key(unsigned n){Key k{};k[0]=n;return k;} Blob blob(unsigned n, unsigned len=255){return Blob(len,n);} std::string path(const Key& key){char path[64];makeBlobPath(key.data(),32,path,sizeof(path));return path;} Blob get(DataStore& store,const Key& k){uint8_t bytes[255];auto n=store.getBlobByKey(k.data(),32,bytes);return Blob(bytes,bytes+n);} void enqueue(DataStore& store,const Key& k,const Blob& b){assert(store.queueAdvertByKey(k.data(),32,b.data(),b.size()));} void step(DataStore& store,FakeFilesystem& fs,uint32_t now=10000) { auto before=fs.operations, backend=fs.backend_writes; fs.max_backend_write=0; store.serviceAdvertWrites(now); assert(fs.operations-before<=1 && "one storage operation per cooperative pass"); assert(fs.backend_writes-backend<=1); assert(fs.max_backend_write<=64); } void drain(DataStore& store,FakeFilesystem& fs) { unsigned n=0; while(store.hasPendingAdvertWrites()){assert(++n<1000);step(store,fs,10000+n*1000);} assert(fs.handles==0); } void until(DataStore& store,FakeFilesystem& fs,Stage target) { unsigned n=0; do {assert(++n<1000);step(store,fs,10000+n*1000);} while(store._advert_write.stage!=target); } void rxRouteAndScheduling() { FakeFilesystem fs;DataStore store(fs);Clock clock;MyMesh mesh{&store,&clock}; auto k=key(1);auto b=blob(1); const auto before=fs.operations; assert(mesh.putBlobByKey(k.data(),32,b.data(),b.size())); assert(fs.operations==before && "advert_rx_no_fs"); assert(get(store,k)==b && fs.operations==before); store.contact_pending=true; unsigned blob_passes=0, serial_services=0; while(store.hasPendingAdvertWrites()) { ++serial_services; auto old=fs.operations, contacts=store.contact_passes; mesh.servicePersistence(); assert(fs.operations-old<=1); if(store.contact_passes==contacts)++blob_passes; ++clock.now; } assert(serial_services>20 && blob_passes>10 && store.contact_passes>10); assert(fs.files.at(path(k))==b && !fs.exists("/advert.tmp")); // Explicit durability work must not be followed by another automatic job. enqueue(store,k,blob(2));assert(store.putBlobByKey(k.data(),32,b.data(),b.size())); auto old=fs.operations;mesh.servicePersistence();assert(old==fs.operations); // Disk share/export work consumes the same pass; a RAM share does not. store.consumeSynchronousAdvertIO(); assert(get(store,k)==b);enqueue(store,key(2),blob(2)); old=fs.operations;mesh.servicePersistence();assert(old==fs.operations); assert(get(store,key(2))==blob(2)); old=fs.operations;mesh.servicePersistence();assert(fs.operations==old+1); store.contact_pending=false;drain(store,fs); } void coalesceOverflowAndDeleteEveryStage() { for(unsigned stage=0;stage<24;++stage) { FakeFilesystem fs;DataStore store(fs);auto k=key(1), other=key(2);auto old=blob(1), fresh=blob(2); fs.files[path(k)]=old;enqueue(store,k,fresh); for(unsigned i=0;i