273 lines
12 KiB
Python
273 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""Execute production contact indexing at compact/wide and PSRAM boundaries."""
|
|
from pathlib import Path
|
|
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
from test_replay_reset_integration import extract_braced
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
HARNESS = r'''
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <new>
|
|
#include <set>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include <helpers/ContactListOrder.h>
|
|
#include <helpers/SmallTableSort.h>
|
|
#define MESH_DEBUG_PRINTLN(...) ((void)0)
|
|
@INDEX_TYPES@
|
|
struct ContactInfo {
|
|
struct Identity {
|
|
uint8_t prefix = 0;
|
|
uint32_t ordinal = 0;
|
|
bool isHashMatch(const uint8_t* hash) const { return prefix == *hash; }
|
|
} id;
|
|
uint8_t flags = 0;
|
|
uint32_t last_advert_timestamp = 0;
|
|
};
|
|
@VISITOR@
|
|
static unsigned allocations = 0, fail_allocation = 0;
|
|
static std::set<void*> outstanding;
|
|
static std::vector<std::pair<size_t, size_t>> allocation_sizes;
|
|
static constexpr int MALLOC_CAP_SPIRAM = 1, MALLOC_CAP_8BIT = 2;
|
|
static void* heap_caps_calloc(size_t count, size_t size, int) {
|
|
allocation_sizes.push_back({count, size});
|
|
if (++allocations == fail_allocation) return nullptr;
|
|
void* pointer = calloc(count, size);
|
|
assert(pointer != nullptr);
|
|
outstanding.insert(pointer);
|
|
return pointer;
|
|
}
|
|
static void heap_caps_free(void* pointer) {
|
|
assert(outstanding.erase(pointer) == 1);
|
|
free(pointer);
|
|
}
|
|
class BaseChatMesh {
|
|
public:
|
|
@STORAGE@
|
|
unsigned relocations = 0;
|
|
BaseChatMesh() {
|
|
#if defined(ESP32_PLATFORM) && defined(BOARD_HAS_PSRAM)
|
|
contacts = contacts_fallback;
|
|
sort_array = sort_array_fallback;
|
|
contact_capacity = CONTACT_PSRAM_FALLBACK_TOTAL_SLOTS;
|
|
#endif
|
|
}
|
|
~BaseChatMesh() {
|
|
#if defined(ESP32_PLATFORM) && defined(BOARD_HAS_PSRAM)
|
|
if (contacts != contacts_fallback) {
|
|
heap_caps_free(contacts);
|
|
heap_caps_free(sort_array);
|
|
}
|
|
#endif
|
|
}
|
|
static void resetContactValue(ContactInfo& contact) { contact = ContactInfo(); }
|
|
void onContactReferenceChanged(const ContactInfo*, ContactInfo*) { ++relocations; }
|
|
bool initializeContactStorage();
|
|
int searchPeersByHash(const uint8_t* hash);
|
|
void scanRecentContacts(int last_n, ContactVisitor* visitor);
|
|
};
|
|
@METHODS@
|
|
struct Visitor : ContactVisitor {
|
|
std::vector<ContactInfo> seen;
|
|
void onContactVisit(const ContactInfo& contact) override { seen.push_back(contact); }
|
|
};
|
|
static void checkSort(BaseChatMesh& mesh, int requested, size_t expected) {
|
|
Visitor visitor;
|
|
mesh.scanRecentContacts(requested, &visitor);
|
|
assert(visitor.seen.size() == expected);
|
|
std::set<uint32_t> ordinals;
|
|
for (size_t i = 0; i < visitor.seen.size(); ++i) {
|
|
const auto& contact = visitor.seen[i];
|
|
assert(contact.id.ordinal < static_cast<uint32_t>(mesh.num_contacts));
|
|
assert(ordinals.insert(contact.id.ordinal).second);
|
|
if (i > 0) {
|
|
const auto& previous = visitor.seen[i - 1];
|
|
const bool previous_favorite = (previous.flags & 1) != 0;
|
|
const bool favorite = (contact.flags & 1) != 0;
|
|
assert(previous_favorite || !favorite);
|
|
if (previous_favorite == favorite)
|
|
assert(previous.last_advert_timestamp >= contact.last_advert_timestamp);
|
|
}
|
|
}
|
|
}
|
|
static void checkFullTable() {
|
|
auto mesh = std::make_unique<BaseChatMesh>();
|
|
assert(mesh->initializeContactStorage());
|
|
mesh->num_contacts = MAX_CONTACTS + MAX_ANON_CONTACTS;
|
|
for (int i = 0; i < mesh->num_contacts; ++i) {
|
|
mesh->contacts[i].id.ordinal = i;
|
|
mesh->contacts[i].flags = (i % 7 == 0 ? 1 : 0) | 0x20;
|
|
mesh->contacts[i].last_advert_timestamp = (static_cast<uint32_t>(i) * 7919) % 997;
|
|
}
|
|
checkSort(*mesh, 0, mesh->num_contacts);
|
|
checkSort(*mesh, 11, 11);
|
|
checkSort(*mesh, mesh->num_contacts + 1, mesh->num_contacts);
|
|
|
|
// Twenty late persistent matches precede the eight transient matches. The
|
|
// 24-result limit must preserve the highest index and then four transients.
|
|
const uint8_t hash = 0x72;
|
|
for (int i = 0; i < MAX_ANON_CONTACTS; ++i) mesh->contacts[i].id.prefix = hash;
|
|
for (int i = mesh->num_contacts - 20; i < mesh->num_contacts; ++i)
|
|
mesh->contacts[i].id.prefix = hash;
|
|
assert(mesh->searchPeersByHash(&hash) == MAX_SEARCH_RESULTS);
|
|
for (int i = 0; i < 20; ++i)
|
|
assert(mesh->matching_peer_indexes[i] == mesh->num_contacts - 20 + i);
|
|
for (int i = 20; i < MAX_SEARCH_RESULTS; ++i)
|
|
assert(mesh->matching_peer_indexes[i] == i - 20);
|
|
for (int i = mesh->num_contacts - 24; i < mesh->num_contacts - 20; ++i)
|
|
mesh->contacts[i].id.prefix = hash;
|
|
assert(mesh->searchPeersByHash(&hash) == MAX_SEARCH_RESULTS);
|
|
for (int i = 0; i < MAX_SEARCH_RESULTS; ++i)
|
|
assert(mesh->matching_peer_indexes[i] == mesh->num_contacts - 24 + i);
|
|
const uint8_t missing = 0x91;
|
|
assert(mesh->searchPeersByHash(&missing) == 0);
|
|
}
|
|
#if defined(ESP32_PLATFORM) && defined(BOARD_HAS_PSRAM)
|
|
static void checkPsram() {
|
|
for (unsigned fail : {1U, 2U, 0U}) {
|
|
allocations = 0;
|
|
fail_allocation = fail;
|
|
allocation_sizes.clear();
|
|
{
|
|
BaseChatMesh mesh;
|
|
mesh.num_contacts = MAX_ANON_CONTACTS + 16;
|
|
for (int i = 0; i < mesh.num_contacts; ++i) mesh.contacts[i].id.ordinal = i + 100;
|
|
assert(mesh.initializeContactStorage() == (fail == 0));
|
|
assert(allocations == 2);
|
|
assert(allocation_sizes[1].first == MAX_CONTACTS + MAX_ANON_CONTACTS);
|
|
assert(allocation_sizes[1].second == sizeof(ContactIndex));
|
|
assert(mesh.contact_capacity == (fail ? MAX_ANON_CONTACTS + 16
|
|
: MAX_CONTACTS + MAX_ANON_CONTACTS));
|
|
assert((mesh.contacts == mesh.contacts_fallback) == (fail != 0));
|
|
assert((mesh.sort_array == mesh.sort_array_fallback) == (fail != 0));
|
|
for (int i = 0; i < mesh.num_contacts; ++i) assert(mesh.contacts[i].id.ordinal == i + 100);
|
|
if (!fail) {
|
|
const unsigned before = allocations;
|
|
assert(mesh.initializeContactStorage());
|
|
assert(allocations == before);
|
|
}
|
|
}
|
|
assert(outstanding.empty());
|
|
}
|
|
fail_allocation = 0;
|
|
}
|
|
#endif
|
|
int main() {
|
|
#if MAX_CONTACTS + MAX_ANON_CONTACTS <= 65535
|
|
static_assert(std::is_same<ContactIndex, uint16_t>::value, "compact contact indices");
|
|
#else
|
|
static_assert(std::is_same<ContactIndex, int>::value, "retain wide contact indices");
|
|
#endif
|
|
static_assert(sizeof(((BaseChatMesh*)nullptr)->matching_peer_indexes)
|
|
== MAX_SEARCH_RESULTS * sizeof(ContactIndex), "matching index storage");
|
|
#if defined(ESP32_PLATFORM) && defined(BOARD_HAS_PSRAM)
|
|
static_assert(std::is_same<decltype(BaseChatMesh::sort_array), ContactIndex*>::value,
|
|
"PSRAM sort allocation type");
|
|
static_assert(sizeof(((BaseChatMesh*)nullptr)->sort_array_fallback)
|
|
== (MAX_ANON_CONTACTS + 16) * sizeof(ContactIndex), "fallback index storage");
|
|
checkPsram();
|
|
#else
|
|
static_assert(sizeof(((BaseChatMesh*)nullptr)->sort_array)
|
|
== (MAX_CONTACTS + MAX_ANON_CONTACTS) * sizeof(ContactIndex), "sort index storage");
|
|
#endif
|
|
checkFullTable();
|
|
assert(outstanding.empty());
|
|
puts("PASS: contact sorting, peer collisions, widths and allocation ownership");
|
|
}
|
|
'''
|
|
|
|
|
|
class ContactIndexTests(unittest.TestCase):
|
|
def test_contact_layout_on_all_platforms(self):
|
|
# Compile the real record rather than the indexing harness's small
|
|
# stand-in. Only cached nRF52 records have the extra storage-slot field
|
|
# whose placement can cost four bytes per contact.
|
|
compiler = os.environ.get("CXX") or shutil.which("g++") or shutil.which("clang++")
|
|
self.assertIsNotNone(compiler, "a host C++17 compiler is required")
|
|
for platform in ("NRF52_PLATFORM", "ESP32_PLATFORM", "RP2040_PLATFORM", "STM32_PLATFORM"):
|
|
for cache in (0, 1):
|
|
expected = 92 if cache else (188 if platform == "NRF52_PLATFORM" else 184)
|
|
with self.subTest(platform=platform, cache=cache):
|
|
source = ("#include <helpers/ContactInfo.h>\n"
|
|
f'static_assert(sizeof(ContactInfo) == {expected}, "contact RAM budget");\n')
|
|
compiled = subprocess.run([
|
|
compiler, "-std=c++17", "-fsyntax-only", "-x", "c++", "-",
|
|
f"-D{platform}=1", f"-DMESH_CONTACT_CACHE={cache}",
|
|
f"-I{ROOT / 'test/fixtures/contact_cache/mocks'}",
|
|
f"-I{ROOT / 'lib/ed25519'}", f"-I{ROOT / 'src'}",
|
|
], input=source, capture_output=True, text=True, timeout=30)
|
|
self.assertEqual(compiled.returncode, 0, compiled.stdout + compiled.stderr)
|
|
|
|
def test_production_indices(self):
|
|
compiler = os.environ.get("CXX") or shutil.which("g++") or shutil.which("clang++")
|
|
self.assertIsNotNone(compiler, "a host C++17 compiler is required")
|
|
header = (ROOT / "src/helpers/BaseChatMesh.h").read_text()
|
|
source = (ROOT / "src/helpers/BaseChatMesh.cpp").read_text()
|
|
index_types = "\n".join(re.findall(
|
|
r"^#define MAX_(?:ANON_CONTACTS|SEARCH_RESULTS)\s+.+$", header, re.MULTILINE))
|
|
index_types += "\n" + header[header.index("#if MAX_CONTACTS + MAX_ANON_CONTACTS <= 65535"):
|
|
header.index("#ifndef MAX_CONNECTIONS")]
|
|
storage_start = header.index("#if defined(ESP32_PLATFORM)",
|
|
header.index("friend class ContactsIterator;"))
|
|
storage = header[storage_start:header.index(" unsigned long txt_send_timeout;", storage_start)]
|
|
methods = "\n".join(extract_braced(source, signature) for signature in (
|
|
"bool BaseChatMesh::initializeContactStorage(",
|
|
"int BaseChatMesh::searchPeersByHash(",
|
|
"static int cmp_contact_list_order(",
|
|
"void BaseChatMesh::scanRecentContacts("))
|
|
methods = "static ContactInfo* table;\n" + methods
|
|
harness = (HARNESS.replace("@INDEX_TYPES@", index_types)
|
|
.replace("@VISITOR@", extract_braced(header, "class ContactVisitor") + ";")
|
|
.replace("@STORAGE@", storage).replace("@METHODS@", methods))
|
|
variants = ((350, None, False, False),
|
|
(350, "NRF52_PLATFORM", False, True),
|
|
(350, "ESP32_PLATFORM", False, True),
|
|
(350, "ESP32_PLATFORM", True, False),
|
|
(350, "ESP32_PLATFORM", True, True),
|
|
(350, "RP2040_PLATFORM", False, False),
|
|
(32, "STM32_PLATFORM", False, False),
|
|
(65527, None, False, False), (65528, None, False, False),
|
|
(65529, None, False, False), (65529, "ESP32_PLATFORM", True, True))
|
|
with tempfile.TemporaryDirectory(prefix="meshcore-contact-indices-") as directory:
|
|
work = Path(directory)
|
|
test_source = work / "test.cpp"
|
|
test_source.write_text(harness, encoding="ascii")
|
|
for contacts, platform, psram, cache in variants:
|
|
with self.subTest(contacts=contacts, platform=platform, psram=psram, cache=cache):
|
|
binary = work / f"indices-{contacts}-{platform}-{psram}-{cache}"
|
|
command = [compiler, "-std=c++17", "-Wall", "-Wextra", "-Werror",
|
|
"-Wno-unused-function", "-Wno-sign-compare",
|
|
f"-DMAX_CONTACTS={contacts}", f"-DMESH_CONTACT_CACHE={int(cache)}",
|
|
f"-I{ROOT / 'src'}", str(test_source), "-o", str(binary)]
|
|
if platform:
|
|
command.append(f"-D{platform}=1")
|
|
if psram:
|
|
command.append("-DBOARD_HAS_PSRAM=1")
|
|
if sys.platform.startswith("linux"):
|
|
command += ["-fsanitize=address,undefined", "-fno-sanitize-recover=all",
|
|
"-fno-pie", "-no-pie"]
|
|
built = subprocess.run(command, capture_output=True, text=True, timeout=60)
|
|
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
|
|
checked = subprocess.run([str(binary)], capture_output=True, text=True, timeout=20)
|
|
self.assertEqual(checked.returncode, 0, checked.stdout + checked.stderr)
|
|
self.assertIn("PASS: contact sorting", checked.stdout)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|