Acquire fresh GPS and validated UTC, check an authenticated owner Companion, and apply saved lost-status cadence with bounded awake windows. Persist request tags, routes, and six-hour flood reservations; preserve the four-day cutoff across sleep and restart. Keep Dispatcher recovery active after a failed radio wake, honor denied flood retries after lost TxDone, and qualify GPS ownership and clock corrections. Add executable regression tests and nRF52/ESP32 Sensor build jobs. Validation: six firmware builds, 1829 native test cases, sanitized tracker/storage/GPS/sleep/recovery tests, and strict documentation build. Bluetooth Find My advertising and automatic Room mailbox polling remain separate work.
141 lines
5.3 KiB
C++
141 lines
5.3 KiB
C++
#pragma once
|
|
// Fault-injecting adapter for the actual tracker-store filesystem calls.
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cerrno>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <limits>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <set>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
struct PowerCut {};
|
|
class MemoryFS;
|
|
static MemoryFS* metadata_filesystem = nullptr;
|
|
class File {
|
|
MemoryFS* fs = nullptr;
|
|
std::shared_ptr<std::vector<uint8_t>> bytes;
|
|
std::string path;
|
|
size_t cursor = 0;
|
|
bool writable = false, directory = false, valid = false;
|
|
public:
|
|
File() = delete;
|
|
explicit File(MemoryFS* owner) : fs(owner) {}
|
|
File(MemoryFS*, std::shared_ptr<std::vector<uint8_t>>, const char*, bool, bool);
|
|
operator bool() const { return valid; }
|
|
bool isDirectory() const { return directory; }
|
|
size_t size();
|
|
int read(uint8_t*, size_t);
|
|
size_t write(const uint8_t*, size_t);
|
|
void flush();
|
|
void close();
|
|
};
|
|
class MemoryFS {
|
|
public:
|
|
void _lockFS() {}
|
|
void _unlockFS() {}
|
|
MemoryFS* _getFS() { return this; }
|
|
std::map<std::string, std::shared_ptr<std::vector<uint8_t>>> files;
|
|
std::set<std::string> faults, directories;
|
|
std::vector<std::string> trace;
|
|
size_t boundary = 0, cut_at = 0;
|
|
bool short_write = false, short_read = false;
|
|
std::vector<uint8_t> replacement_on_close;
|
|
unsigned write_opens = 0;
|
|
void point(const std::string& event) {
|
|
trace.push_back(event);
|
|
if (++boundary == cut_at) throw PowerCut{};
|
|
}
|
|
bool begin(const std::string& op) { point("before:" + op); return !faults.count(op); }
|
|
void end(const std::string& op) { point("after:" + op); }
|
|
bool exists(const char* path) {
|
|
const std::string op = std::string("stat:") + path;
|
|
const bool okay = begin(op);
|
|
const bool present = files.count(path) || directories.count(path);
|
|
end(op);
|
|
return okay && present;
|
|
}
|
|
bool remove(const char* path) {
|
|
const std::string op = std::string("remove:") + path;
|
|
if (!begin(op)) return false;
|
|
const bool removed = files.erase(path) + directories.erase(path) != 0;
|
|
end(op); return removed;
|
|
}
|
|
bool rename(const char* from, const char* to) {
|
|
const std::string op = std::string("rename:") + from + ":" + to;
|
|
if (!begin(op) || !files.count(from) || files.count(to) || directories.count(to)) return false;
|
|
files[to] = files[from]; files.erase(from); end(op); return true;
|
|
}
|
|
File open(const char* path, const char* mode = "r", bool create = false) {
|
|
(void)create;
|
|
const bool write = mode[0] == 'w';
|
|
const std::string op = std::string("open:") + (write ? "w:" : "r:") + path;
|
|
if (!begin(op)) return File(this);
|
|
if (write) {
|
|
++write_opens;
|
|
files[path] = std::make_shared<std::vector<uint8_t>>();
|
|
if (faults.count(std::string("directory:") + path)) directories.insert(path);
|
|
}
|
|
if (!files.count(path) && !directories.count(path)) { end(op); return File(this); }
|
|
if (!files.count(path)) files[path] = std::make_shared<std::vector<uint8_t>>();
|
|
File file(this, files[path], path, write, directories.count(path));
|
|
end(op); return file;
|
|
}
|
|
File open(const char* path, int mode) { assert(mode == 1); return open(path, "w"); }
|
|
void put(const char* path, const std::vector<uint8_t>& bytes) {
|
|
files[path] = std::make_shared<std::vector<uint8_t>>(bytes);
|
|
}
|
|
std::vector<uint8_t> get(const char* path) const { return *files.at(path); }
|
|
void reset() { trace.clear(); boundary = cut_at = 0; faults.clear(); short_write = short_read = false; write_opens = 0; }
|
|
};
|
|
File::File(MemoryFS* owner, std::shared_ptr<std::vector<uint8_t>> data,
|
|
const char* name, bool write, bool dir)
|
|
: fs(owner), bytes(data), path(name), writable(write), directory(dir), valid(true) {}
|
|
size_t File::size() {
|
|
const std::string op = "size:" + path;
|
|
const bool okay = fs->begin(op); const size_t size = okay ? bytes->size() : 0;
|
|
fs->end(op); return size;
|
|
}
|
|
int File::read(uint8_t* output, size_t length) {
|
|
const std::string op = "read:" + path;
|
|
if (!valid || directory || !fs->begin(op)) return -1;
|
|
length = std::min(length, bytes->size() - cursor);
|
|
if (fs->short_read && length) --length;
|
|
if (length) memcpy(output, bytes->data() + cursor, length);
|
|
cursor += length; fs->end(op); return static_cast<int>(length);
|
|
}
|
|
size_t File::write(const uint8_t* data, size_t length) {
|
|
const std::string op = "write:" + path;
|
|
if (!valid || directory || !fs->begin(op)) return 0;
|
|
if (fs->short_write && length) --length;
|
|
bytes->insert(bytes->end(), data, data + length); fs->end(op); return length;
|
|
}
|
|
void File::flush() {
|
|
const std::string op = "flush:" + path;
|
|
if (!fs->begin(op) && !bytes->empty()) bytes->pop_back();
|
|
fs->end(op);
|
|
}
|
|
void File::close() {
|
|
const std::string op = std::string("close:") + (writable ? "w:" : "r:") + path;
|
|
if (!fs->begin(op) && writable && !bytes->empty()) (*bytes)[0] ^= 1;
|
|
if (writable && !fs->replacement_on_close.empty()) *bytes = fs->replacement_on_close;
|
|
valid = false; fs->end(op);
|
|
}
|
|
#define FILE_O_WRITE 1
|
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
|
struct lfs_info {};
|
|
static constexpr int LFS_ERR_NOENT = -2;
|
|
static int lfs_stat(MemoryFS* fs, const char* path, lfs_info*) {
|
|
const std::string op = std::string("stat:") + path;
|
|
const bool okay = fs->begin(op);
|
|
const bool present = fs->files.count(path) || fs->directories.count(path);
|
|
fs->end(op);
|
|
return !okay ? -5 : present ? 0 : LFS_ERR_NOENT;
|
|
}
|
|
#endif
|