Avoid flash load image for the empty contact-path cache
This commit is contained in:
1 parent
dcabcb8cb2
commit
53835c0793
4 files changed
+73
No files matched your search
@@ -401,6 +401,40 @@ App CLI replies remain bounded and supply a continuation command.
|
||||
The physical RAK passed all 21 chapters, ranges, app-style continuation replies,
|
||||
a paused USB reader exceeding the 4 KiB queue, and disconnect cancellation.
|
||||
|
||||
### Empty contact-path cache initialization
|
||||
|
||||
The contact-path pool now has an explicit runtime constructor. Its existing
|
||||
member defaults still initialize every empty handle and resident entry, and
|
||||
`Slot()` still resets released handles. This moves the local static pool from
|
||||
initialized `.data` to zero-initialized `.bss`: the vtable pointer and repeated
|
||||
`NONE`/255 slot markers no longer require a 5,780-byte flash load image. The
|
||||
pool is constructed on first use by the existing main-loop contact owner.
|
||||
Its layout, 390 handle slots, 16 resident paths, reference/snapshot behavior
|
||||
and persistent contact records are unchanged.
|
||||
|
||||
The RAK4631 Full release recipe at `c0672b90`, with the full
|
||||
`v1.17.1.10-halo-keymind-cascade-dev` version and commit suffix, measured:
|
||||
|
||||
| Measurement | Before | Runtime pool construction |
|
||||
| --- | ---: | ---: |
|
||||
| Linked initialized flash bytes | 716,448 | 710,908 |
|
||||
| Application image including EndF | 716,528 | 710,988 |
|
||||
| Linked available heap bytes | 79,388 | 79,388 |
|
||||
| Required runtime heap bytes | 74,576 | 74,576 |
|
||||
|
||||
The net flash saving is 5,540 bytes after constructor/guard code. The image
|
||||
passes the existing 712,704-byte application ceiling, capability checks, RAM
|
||||
budget and DFU handoff checks. Its padded UF2 ends at `0xD3A00`, below the
|
||||
unchanged `0xD4000` boundary. Sensors, BLE/USB/Ethernet, SD, logging, Meshtext,
|
||||
OTA, Reader data and the 8 KiB loop stack remain enabled.
|
||||
|
||||
The contact-cache test compiles a real static pool and checks its zero-initialized
|
||||
storage, alongside the existing sanitized eviction, snapshot, migration and
|
||||
transaction tests. The compiled ARM Companion check also rejects a pool in an
|
||||
initialized ELF section, so compiler promotion cannot silently restore the
|
||||
flash image. Its normal linked crypto vectors and Reader retention checks
|
||||
remain required.
|
||||
|
||||
## Release evidence and regression tests
|
||||
|
||||
Full Companions without PSRAM also use
|
||||
|
||||
@@ -45,6 +45,9 @@ def check(path):
|
||||
reader = "_ZN4mesh5bible9generatedL10readerDataE"
|
||||
if reader not in elf.symbols:
|
||||
raise ValueError("Companion Reader corpus is missing")
|
||||
path_cache = elf.symbols.get("_ZZN4mesh18contactPathStorageEvE4pool")
|
||||
if path_cache and elf.sections[path_cache[2]][1] != 8: # SHT_NOBITS
|
||||
raise ValueError("Contact path cache has a flash load image; expected zero-initialized storage")
|
||||
uc = Uc(UC_ARCH_ARM, UC_MODE_THUMB | UC_MODE_MCLASS)
|
||||
uc.mem_map(0, 0x100000)
|
||||
uc.mem_map(0x20000000, 0x40000)
|
||||
@@ -100,6 +103,7 @@ def check(path):
|
||||
"passed": True, "elf_sha256": hashlib.sha256(elf.data).hexdigest(),
|
||||
"base_table_bytes": elf.symbols["base"][1],
|
||||
"reader_data_bytes": elf.symbols[reader][1], "vectors": rows,
|
||||
"zero_initialized_path_cache_bytes": path_cache[1] if path_cache else 0,
|
||||
"stack_limit_bytes": 2048,
|
||||
"scope": "Crypto function frames only; excludes caller/ISR/task stacks and physical timing.",
|
||||
}
|
||||
|
||||
@@ -170,6 +170,11 @@ class ContactPathPool : public ContactPathStorage {
|
||||
}
|
||||
|
||||
public:
|
||||
// Construct the empty cache on first use instead of storing a flash load
|
||||
// image of every slot's NONE/255 sentinels and the vtable pointer. Keep the
|
||||
// member defaults for both initial construction and Slot() resets.
|
||||
ContactPathPool() {}
|
||||
|
||||
void attach(ContactPathBackend* backend) override { _backend = backend; }
|
||||
uint16_t bind(uint16_t location, const uint8_t path[64]) override {
|
||||
if (location == NONE) return NONE;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Exercise real contact handles, cache policy and persistence under sanitizers."""
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
@@ -22,6 +23,35 @@ def crypto_objects(temp):
|
||||
|
||||
|
||||
class ContactCacheTest(unittest.TestCase):
|
||||
def test_empty_path_pool_has_no_flash_load_image(self):
|
||||
# The 390-handle pool must occupy zero-initialized storage, not a
|
||||
# multi-KiB initialized-data image containing repeated empty markers.
|
||||
# Compile a real static instance; source-shape checks cannot establish
|
||||
# whether the compiler emits a flash initializer for it.
|
||||
with tempfile.TemporaryDirectory(prefix="mesh-path-pool-image-") as directory:
|
||||
source = Path(directory) / "pool.cpp"
|
||||
obj = Path(directory) / "pool.o"
|
||||
source.write_text('''#include <helpers/ContactPathCache.h>
|
||||
mesh::ContactPathStorage& testPathStorage() {
|
||||
static mesh::ContactPathPool<390> pool;
|
||||
return pool;
|
||||
}
|
||||
''', encoding="ascii")
|
||||
compiled = subprocess.run([
|
||||
"c++", "-std=c++17", "-Os", "-fno-threadsafe-statics",
|
||||
"-fno-rtti", "-fno-exceptions", "-I", str(ROOT / "src"),
|
||||
"-c", str(source), "-o", str(obj),
|
||||
], capture_output=True, text=True)
|
||||
self.assertEqual(compiled.returncode, 0, compiled.stdout + compiled.stderr)
|
||||
symbols = subprocess.run(["nm", "-S", "-C", "--defined-only", str(obj)],
|
||||
capture_output=True, text=True)
|
||||
self.assertEqual(symbols.returncode, 0, symbols.stdout + symbols.stderr)
|
||||
match = re.search(r"^\S+\s+([0-9a-fA-F]+)\s+(\w) testPathStorage\(\)::pool$",
|
||||
symbols.stdout, re.MULTILINE)
|
||||
self.assertIsNotNone(match, symbols.stdout)
|
||||
self.assertGreater(int(match[1], 16), 5000)
|
||||
self.assertEqual(match[2].lower(), "b", symbols.stdout)
|
||||
|
||||
def test_default_policy_covers_high_contact_nrf52_without_full(self):
|
||||
cases = (
|
||||
({"NRF52_PLATFORM": 1, "MAX_CONTACTS": 350}, 1),
|
||||
|
||||
Reference in new issue
Block a user