Cache Full Companion paths and shared secrets to recover RAM
This commit is contained in:
1 parent
5013c8ff34
commit
6db10c3986
27 files changed
+2360
-58
No files matched your search
@@ -0,0 +1,267 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
// Paths are immutable values shared by ContactInfo copies. Editing a copy uses
|
||||
// a new handle, so a pending app response or a rollback keeps its original path.
|
||||
// Only the 16 most recently used path byte arrays are resident. Small handles
|
||||
// retain the location of the other paths in the existing contact files.
|
||||
class ContactPathBackend {
|
||||
public:
|
||||
virtual bool readStoredPath(uint16_t source, uint8_t path[64]) = 0;
|
||||
virtual bool flushCachedPaths() = 0;
|
||||
};
|
||||
|
||||
class ContactPathStorage {
|
||||
public:
|
||||
static const uint16_t NONE = 0xffff;
|
||||
static const uint16_t PAGED = 0x8000;
|
||||
virtual void attach(ContactPathBackend* backend) = 0;
|
||||
virtual uint16_t bind(uint16_t source, const uint8_t path[64]) = 0;
|
||||
virtual void retain(uint16_t handle) = 0;
|
||||
virtual void release(uint16_t handle) = 0;
|
||||
virtual bool read(uint16_t handle, uint8_t path[64]) = 0;
|
||||
virtual const uint8_t* view(uint16_t handle) = 0;
|
||||
virtual bool replace(uint16_t& handle, const uint8_t path[64]) = 0;
|
||||
virtual uint16_t source(uint16_t handle) const = 0;
|
||||
virtual void beginCommit() = 0;
|
||||
virtual void mark(uint16_t handle) = 0;
|
||||
virtual bool preserveSnapshots(uint16_t first, uint16_t count) = 0;
|
||||
virtual void publish(uint16_t handle, uint16_t source) = 0;
|
||||
virtual void endCommit(bool success) = 0;
|
||||
};
|
||||
|
||||
ContactPathStorage& contactPathStorage();
|
||||
|
||||
class ContactPathRef {
|
||||
uint16_t _handle = 0;
|
||||
public:
|
||||
ContactPathRef() = default;
|
||||
ContactPathRef(const ContactPathRef& other) : _handle(other._handle) {
|
||||
if (_handle) contactPathStorage().retain(_handle);
|
||||
}
|
||||
ContactPathRef& operator=(const ContactPathRef& other) {
|
||||
if (this != &other) {
|
||||
if (other._handle) contactPathStorage().retain(other._handle);
|
||||
if (_handle) contactPathStorage().release(_handle);
|
||||
_handle = other._handle;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
~ContactPathRef() { if (_handle) contactPathStorage().release(_handle); }
|
||||
bool bind(uint16_t source, const uint8_t path[64]) {
|
||||
uint16_t next = contactPathStorage().bind(source, path);
|
||||
if (next == ContactPathStorage::NONE) return false;
|
||||
if (_handle) contactPathStorage().release(_handle);
|
||||
_handle = next;
|
||||
return true;
|
||||
}
|
||||
bool set(const uint8_t path[64]) {
|
||||
return contactPathStorage().replace(_handle, path);
|
||||
}
|
||||
bool read(uint8_t path[64]) const {
|
||||
return contactPathStorage().read(_handle, path);
|
||||
}
|
||||
const uint8_t* view() const { return contactPathStorage().view(_handle); }
|
||||
uint16_t handle() const { return _handle; }
|
||||
};
|
||||
|
||||
template<size_t Handles, size_t Resident = 16>
|
||||
class ContactPathPool : public ContactPathStorage {
|
||||
static_assert(Handles > Resident && Handles < 0xffff, "Invalid path handle capacity");
|
||||
static_assert(Resident > 0 && Resident < 255, "Invalid path cache capacity");
|
||||
enum : uint8_t { DIRTY = 1, MARKED = 2, DETACH = 4 };
|
||||
struct Slot {
|
||||
uint32_t checksum = 0;
|
||||
uint16_t refs = 0;
|
||||
uint16_t location = NONE;
|
||||
uint8_t resident = 255;
|
||||
uint8_t flags = 0;
|
||||
} _slots[Handles];
|
||||
struct Entry {
|
||||
uint8_t path[64] = {};
|
||||
uint16_t handle = 0;
|
||||
uint8_t age = 0;
|
||||
} _entries[Resident];
|
||||
ContactPathBackend* _backend = nullptr;
|
||||
bool _committing = false;
|
||||
bool _flushing = false;
|
||||
|
||||
static uint32_t checksum(const uint8_t path[64]) {
|
||||
uint32_t crc = 0xffffffff;
|
||||
for (size_t i = 0; i < 64; ++i) {
|
||||
crc ^= path[i];
|
||||
for (unsigned bit = 0; bit < 8; ++bit)
|
||||
crc = (crc >> 1) ^ ((crc & 1) ? 0xedb88320UL : 0);
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
Slot* slot(uint16_t h) {
|
||||
return h > 0 && h <= Handles && _slots[h - 1].refs ? &_slots[h - 1] : nullptr;
|
||||
}
|
||||
void touch(size_t index) {
|
||||
// Bounded ranks avoid millis/counter wrap affecting LRU ordering.
|
||||
for (size_t i = 0; i < Resident; ++i) {
|
||||
if (_entries[i].handle && _entries[i].age < Resident) ++_entries[i].age;
|
||||
}
|
||||
_entries[index].age = 0;
|
||||
}
|
||||
int availableEntry() {
|
||||
int oldest = -1;
|
||||
for (size_t i = 0; i < Resident; ++i) {
|
||||
Slot* s = slot(_entries[i].handle);
|
||||
if (!s) return static_cast<int>(i);
|
||||
if (s->location != NONE && !(s->flags & (DIRTY | DETACH)) &&
|
||||
(oldest < 0 || _entries[i].age > _entries[oldest].age)) oldest = i;
|
||||
}
|
||||
return oldest;
|
||||
}
|
||||
int makeRoom() {
|
||||
int index = availableEntry();
|
||||
if (index < 0 && _backend && !_committing && !_flushing) {
|
||||
_flushing = true;
|
||||
_backend->flushCachedPaths();
|
||||
_flushing = false;
|
||||
index = availableEntry();
|
||||
}
|
||||
if (index >= 0) {
|
||||
Slot* old = slot(_entries[index].handle);
|
||||
if (old) old->resident = 255;
|
||||
_entries[index].handle = 0;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
uint16_t allocate() {
|
||||
for (size_t i = 0; i < Handles; ++i) {
|
||||
if (!_slots[i].refs) {
|
||||
_slots[i] = Slot();
|
||||
_slots[i].refs = 1;
|
||||
return static_cast<uint16_t>(i + 1);
|
||||
}
|
||||
}
|
||||
return NONE;
|
||||
}
|
||||
int load(uint16_t h) {
|
||||
Slot* s = slot(h);
|
||||
if (!s) return -1;
|
||||
if (s->resident != 255) { touch(s->resident); return s->resident; }
|
||||
// A flush in makeRoom() may move this source. Read its current location
|
||||
// afterward. read() itself never allocates or starts a persistence write.
|
||||
if (!_backend || s->location == NONE) return -1;
|
||||
int index = makeRoom();
|
||||
if (index < 0) return -1;
|
||||
if (!_backend->readStoredPath(s->location, _entries[index].path) ||
|
||||
checksum(_entries[index].path) != s->checksum) return -1;
|
||||
_entries[index].handle = h;
|
||||
s->resident = index;
|
||||
touch(index);
|
||||
return index;
|
||||
}
|
||||
|
||||
public:
|
||||
void attach(ContactPathBackend* backend) override { _backend = backend; }
|
||||
uint16_t bind(uint16_t location, const uint8_t path[64]) override {
|
||||
if (location == NONE) return NONE;
|
||||
const uint16_t h = allocate();
|
||||
if (h != NONE) {
|
||||
_slots[h - 1].location = location;
|
||||
_slots[h - 1].checksum = checksum(path);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
void retain(uint16_t h) override { Slot* s = slot(h); if (s) ++s->refs; }
|
||||
void release(uint16_t h) override {
|
||||
Slot* s = slot(h);
|
||||
if (!s || --s->refs) return;
|
||||
if (s->resident != 255) _entries[s->resident].handle = 0;
|
||||
*s = Slot();
|
||||
}
|
||||
bool read(uint16_t h, uint8_t path[64]) override {
|
||||
if (!h) { memset(path, 0, 64); return true; }
|
||||
Slot* s = slot(h);
|
||||
if (!s) return false;
|
||||
if (s->resident != 255) {
|
||||
memcpy(path, _entries[s->resident].path, 64);
|
||||
touch(s->resident);
|
||||
return true;
|
||||
}
|
||||
// Serializing a whole contact table must not evict a dirty entry and
|
||||
// recursively rewrite the table it is currently reading.
|
||||
return _backend && s->location != NONE &&
|
||||
_backend->readStoredPath(s->location, path) && checksum(path) == s->checksum;
|
||||
}
|
||||
const uint8_t* view(uint16_t h) override {
|
||||
static const uint8_t empty[64] = {};
|
||||
if (!h) return empty;
|
||||
const int index = load(h);
|
||||
return index < 0 ? nullptr : _entries[index].path;
|
||||
}
|
||||
bool replace(uint16_t& h, const uint8_t path[64]) override {
|
||||
uint8_t previous[64];
|
||||
if (read(h, previous) && memcmp(previous, path, 64) == 0) return true;
|
||||
bool empty = true;
|
||||
for (size_t i = 0; i < 64; ++i) empty = empty && path[i] == 0;
|
||||
if (empty) { release(h); h = 0; return true; }
|
||||
Slot* old = slot(h);
|
||||
// In-place mutation is safe only when there is no ContactInfo snapshot.
|
||||
if (old && old->refs == 1 && old->resident != 255) {
|
||||
memcpy(_entries[old->resident].path, path, 64);
|
||||
old->flags |= DIRTY;
|
||||
old->checksum = checksum(path);
|
||||
touch(old->resident);
|
||||
return true;
|
||||
}
|
||||
const uint16_t next = allocate();
|
||||
if (next == NONE) return false;
|
||||
const int index = makeRoom();
|
||||
if (index < 0) { release(next); return false; }
|
||||
Slot& s = _slots[next - 1];
|
||||
s.location = old ? old->location : NONE;
|
||||
s.flags = DIRTY;
|
||||
s.checksum = checksum(path);
|
||||
s.resident = index;
|
||||
memcpy(_entries[index].path, path, 64);
|
||||
_entries[index].handle = next;
|
||||
touch(index);
|
||||
release(h);
|
||||
h = next;
|
||||
return true;
|
||||
}
|
||||
uint16_t source(uint16_t h) const override {
|
||||
return h && h <= Handles && _slots[h - 1].refs ? _slots[h - 1].location : NONE;
|
||||
}
|
||||
void beginCommit() override {
|
||||
_committing = true;
|
||||
for (auto& s : _slots) s.flags &= ~(MARKED | DETACH);
|
||||
}
|
||||
void mark(uint16_t h) override { Slot* s = slot(h); if (s) s->flags |= MARKED; }
|
||||
bool preserveSnapshots(uint16_t first, uint16_t count) override {
|
||||
for (size_t i = 0; i < Handles; ++i) {
|
||||
Slot& s = _slots[i];
|
||||
if (!s.refs || s.location == NONE || (s.flags & MARKED) ||
|
||||
s.location < first || static_cast<uint32_t>(s.location) >=
|
||||
static_cast<uint32_t>(first) + count) continue;
|
||||
if (load(i + 1) < 0) return false;
|
||||
s.flags |= DETACH;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void publish(uint16_t h, uint16_t location) override {
|
||||
Slot* s = slot(h);
|
||||
if (s) { s->location = location; s->flags &= ~DIRTY; }
|
||||
}
|
||||
void endCommit(bool success) override {
|
||||
for (auto& s : _slots) {
|
||||
if (success && (s.flags & DETACH)) s.location = NONE;
|
||||
s.flags &= ~(MARKED | DETACH);
|
||||
}
|
||||
_committing = false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mesh
|
||||
Reference in new issue
Block a user