mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-23 07:32:27 +00:00
fix(build): clear matrix failures while preserving features
This commit is contained in:
1 parent
7b8ca33386
commit
e6b002759d
14 files changed
+170
-12
No files matched your search
@@ -233,6 +233,8 @@ jobs:
|
||||
run: |
|
||||
bash test/test_build_size_detection.sh
|
||||
python3 -B test/test_firmware_ram.py
|
||||
python3 -B test/test_neighbor_scope_storage.py
|
||||
python3 -B test/test_ed25519_compact.py
|
||||
python3 -B test/test_cascade_release_package.py
|
||||
python3 -B test/test_t096_full_memory.py
|
||||
python3 -B test/test_nrf52_ble_startup.py
|
||||
|
||||
@@ -4384,7 +4384,7 @@ run_pio_with_size_detection() {
|
||||
build_status=1
|
||||
elif [ "$build_status" -ne 0 ] \
|
||||
&& grep -Eiq \
|
||||
'will not fit in region|region .+ overflowed by|section .+ will not fit|sketch too big|program size is greater than maximum|exceed(s|ing).*(flash|partition|app)' \
|
||||
'will not fit in region|region .+ overflowed by|section .+ will not fit|sketch too big|program size.*is greater than maximum|exceed(s|ing).*(flash|partition|app)' \
|
||||
"$build_output_log"; then
|
||||
build_status=42
|
||||
fi
|
||||
@@ -5066,6 +5066,13 @@ is_runtime_setting_alias_target() {
|
||||
}
|
||||
|
||||
is_redundant_bulk_build_target() {
|
||||
# Bench fixtures and migration utilities use their dedicated PlatformIO
|
||||
# recipes; they are not node firmware for the release/OTA packaging matrix.
|
||||
case "${1,,}" in
|
||||
profile_switch_*|*_partition_migrator|*_partition_migrator_test_*|*_partition_legacy_seed|*_sim)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
# Keep every legacy name available to `build-firmware` and
|
||||
# `build-matching-firmwares`, but do not republish binaries that differ only
|
||||
# by a saved/default setting, or roles already supplied by Full Companion.
|
||||
|
||||
@@ -2285,7 +2285,11 @@ void halt() {
|
||||
clear_bonds, stealth_pair_once,
|
||||
bonded_only_peer_ptr)) {
|
||||
strncpy(companion_bluetooth_start_failure,
|
||||
#if defined(NRF52_PLATFORM)
|
||||
bluetooth_interface.beginFailure(),
|
||||
#else
|
||||
"Bluetooth initialization failed",
|
||||
#endif
|
||||
sizeof(companion_bluetooth_start_failure) - 1);
|
||||
companion_bluetooth_start_failure[
|
||||
sizeof(companion_bluetooth_start_failure) - 1] = 0;
|
||||
|
||||
@@ -3662,7 +3662,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
loadTelemetryHistoryTxPrefs();
|
||||
#endif
|
||||
|
||||
#ifdef SIM_WIFI_SSID
|
||||
#if defined(SIM_WIFI_SSID) && defined(WITH_MQTT_BRIDGE)
|
||||
// Emulator builds (Wokwi) boot with fresh NVS every run. Seed WiFi so the
|
||||
// observer auto-joins the simulator's network and brings the MQTT bridge up
|
||||
// (WiFi is driven by the bridge task), instead of raising the setup AP that
|
||||
@@ -12842,7 +12842,7 @@ bool MyMesh::completeNeighborDiscoverEntry() {
|
||||
pubkey_hex,
|
||||
entry.snr / 4.0f,
|
||||
UINT32_MAX,
|
||||
entry.scopes,
|
||||
neighbor_discover_scopes[neighbor_discover_next],
|
||||
entry.status == ND_RESPONDED ? "responded"
|
||||
: (entry.status == ND_SEND_FAILED ? "send_failed" : "timeout")
|
||||
};
|
||||
@@ -12868,7 +12868,8 @@ bool MyMesh::completeNeighborDiscoverEntry() {
|
||||
bool MyMesh::handleNeighborDiscoverResponse(int overlay_idx,
|
||||
const uint8_t* data, size_t len,
|
||||
float snr, int16_t rssi) {
|
||||
if (overlay_idx < 0 || overlay_idx >= neighbor_discover_count) return false;
|
||||
if (overlay_idx < 0 || overlay_idx >= neighbor_discover_count
|
||||
|| (size_t)overlay_idx >= NEIGHBOR_SCOPE_RESULTS) return false;
|
||||
NeighborDiscoverEntry& entry = neighbor_discover[overlay_idx];
|
||||
if (entry.status != ND_PENDING || len < 8) return false;
|
||||
|
||||
@@ -12877,11 +12878,12 @@ bool MyMesh::handleNeighborDiscoverResponse(int overlay_idx,
|
||||
if (tag != entry.tag) return false;
|
||||
|
||||
size_t scope_len = len - 8;
|
||||
if (scope_len >= sizeof(entry.scopes)) {
|
||||
scope_len = sizeof(entry.scopes) - 1;
|
||||
auto& scopes = neighbor_discover_scopes[overlay_idx];
|
||||
if (scope_len >= sizeof(scopes)) {
|
||||
scope_len = sizeof(scopes) - 1;
|
||||
}
|
||||
memcpy(entry.scopes, &data[8], scope_len);
|
||||
entry.scopes[scope_len] = 0;
|
||||
memcpy(scopes, &data[8], scope_len);
|
||||
scopes[scope_len] = 0;
|
||||
entry.status = ND_RESPONDED;
|
||||
// A zero-hop reply is proof we heard this neighbour now, so re-stamp both the
|
||||
// snapshot and live table with this packet's measurements. A stamp taken
|
||||
@@ -13028,7 +13030,7 @@ void MyMesh::finishNeighborDiscover() {
|
||||
bool heard_known = neighborHeardAgeUsable(entry.heard_timestamp, now_secs);
|
||||
entries[i].heard_unknown = !heard_known;
|
||||
entries[i].heard_secs_ago = heard_known ? (now_secs - entry.heard_timestamp) : 0;
|
||||
entries[i].scopes = entry.scopes;
|
||||
entries[i].scopes = neighbor_discover_scopes[i];
|
||||
switch (entry.status) {
|
||||
case ND_RESPONDED: entries[i].status = "responded"; break;
|
||||
case ND_SEND_FAILED: entries[i].status = "send_failed"; break;
|
||||
@@ -13169,7 +13171,6 @@ bool MyMesh::startNeighborDiscover(char* reply) {
|
||||
entry.heard_timestamp = neighbours[i].heard_timestamp;
|
||||
entry.snr = neighbours[i].snr;
|
||||
entry.rssi = neighbours[i].rssi;
|
||||
entry.scopes[0] = 0;
|
||||
entry.tag = 0;
|
||||
entry.status = ND_UNSENT;
|
||||
neighbor_discover_count++;
|
||||
@@ -13193,6 +13194,7 @@ bool MyMesh::startNeighborDiscover(char* reply) {
|
||||
neighbor_discover[j] = entry;
|
||||
}
|
||||
|
||||
memset(neighbor_discover_scopes, 0, sizeof(neighbor_discover_scopes));
|
||||
neighbor_discover_next = 0;
|
||||
resetNeighborDiscoverJsonBudget();
|
||||
neighbor_discover_active = true;
|
||||
|
||||
@@ -644,10 +644,16 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks
|
||||
int8_t snr; // multiplied by 4
|
||||
int16_t rssi; // dBm from the last packet heard from this neighbour
|
||||
uint32_t tag; // anon-regions request tag we're waiting on
|
||||
char scopes[96]; // scope names from the response
|
||||
uint8_t status; // NeighborDiscoverStatus
|
||||
};
|
||||
NeighborDiscoverEntry neighbor_discover[MAX_NEIGHBOURS];
|
||||
// Only the publishable prefix (plus the result that detects truncation)
|
||||
// receives scope queries. Keep every neighbor snapshot, without reserving
|
||||
// 96 response bytes for each entry that will never be queried.
|
||||
static constexpr size_t NEIGHBOR_SCOPE_RESULTS =
|
||||
MAX_NEIGHBOURS < MQTTBridge::NEIGHBORS_MAX_PUBLISH_ENTRIES + 1
|
||||
? MAX_NEIGHBOURS : MQTTBridge::NEIGHBORS_MAX_PUBLISH_ENTRIES + 1;
|
||||
char neighbor_discover_scopes[NEIGHBOR_SCOPE_RESULTS][96];
|
||||
uint8_t neighbor_discover_count;
|
||||
uint8_t neighbor_discover_next; // newest-first entry currently being queried
|
||||
uint8_t neighbor_discover_publish_count; // completed prefix that fits the JSON buffer
|
||||
|
||||
@@ -115,6 +115,19 @@ static int sha512_compress(sha512_context *md, unsigned char *buf)
|
||||
d += t0; \
|
||||
h = t0 + t1;
|
||||
|
||||
#if defined(ED25519_COMPACT_SHA512)
|
||||
/* Same 80 rounds, with a fixed state rotation instead of eight copies of
|
||||
the round body. Small STM32 images trade loop overhead for flash space;
|
||||
neither the loop bounds nor memory indexes depend on secret data. */
|
||||
for (i = 0; i < 80; ++i) {
|
||||
int j;
|
||||
uint64_t next;
|
||||
RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],i);
|
||||
next = S[7];
|
||||
for (j = 7; j > 0; --j) S[j] = S[j - 1];
|
||||
S[0] = next;
|
||||
}
|
||||
#else
|
||||
for (i = 0; i < 80; i += 8) {
|
||||
RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],i+0);
|
||||
RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],i+1);
|
||||
@@ -125,6 +138,7 @@ static int sha512_compress(sha512_context *md, unsigned char *buf)
|
||||
RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],i+6);
|
||||
RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],i+7);
|
||||
}
|
||||
#endif
|
||||
|
||||
#undef RND
|
||||
|
||||
|
||||
@@ -12,6 +12,13 @@ fail() {
|
||||
[ "$OPTION3_BUILD_WORKERS" -eq 1 ] \
|
||||
|| fail "logging matrix permits concurrent PlatformIO target builds"
|
||||
|
||||
for utility in profile_switch_t096_sx1262 xiao_s3_partition_migrator \
|
||||
heltec_v4_partition_migrator_test_hold xiao_s3_partition_legacy_seed \
|
||||
Heltec_v3_repeater_observer_mqtt_sim; do
|
||||
is_redundant_bulk_build_target "$utility" \
|
||||
|| fail "release matrix includes development utility $utility"
|
||||
done
|
||||
|
||||
# Reduced RAK profiles all retain the compact INA set, but GPS depends on
|
||||
# whether the bridge already owns Serial1. Keep those independent contracts so
|
||||
# a valid Serial1 image cannot fail release qualification for an absent GPS
|
||||
|
||||
@@ -38,7 +38,8 @@ pio_result=1
|
||||
for pio_message in \
|
||||
"firmware.elf section .text will not fit in region FLASH" \
|
||||
"region FLASH overflowed by 43080 bytes" \
|
||||
"Error: The program size is greater than maximum allowed"; do
|
||||
"Error: The program size is greater than maximum allowed" \
|
||||
"Error: The program size (1314113 bytes) is greater than maximum allowed (1310720 bytes)"; do
|
||||
check_status 42
|
||||
grep -Fq "$pio_message" "$test_root/output" || fail "compiler output was lost"
|
||||
done
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import ctypes
|
||||
import hashlib
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
@@ -22,6 +24,22 @@ SOURCES = [
|
||||
|
||||
|
||||
class Ed25519CompactTest(unittest.TestCase):
|
||||
def test_compact_sha512_matches_hashlib_at_block_boundaries(self):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
library = Path(temp_dir) / "sha512.so"
|
||||
subprocess.run(["cc", "-std=c99", "-O2", "-shared", "-fPIC",
|
||||
"-DED25519_COMPACT_SHA512=1", str(ED25519 / "sha512.c"),
|
||||
"-o", str(library)], check=True)
|
||||
sha = ctypes.CDLL(str(library)).sha512
|
||||
sha.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p]
|
||||
sha.restype = ctypes.c_int
|
||||
for length in (0, 1, 63, 64, 111, 112, 127, 128, 129, 255, 256, 1024, 4097):
|
||||
data = bytes((i * 73 + 19) % 256 for i in range(length))
|
||||
output = ctypes.create_string_buffer(64)
|
||||
with self.subTest(length=length):
|
||||
self.assertEqual(sha(data, len(data), output), 0)
|
||||
self.assertEqual(output.raw, hashlib.sha512(data).digest())
|
||||
|
||||
def test_stm32_builds_enable_compact_base_table(self):
|
||||
platformio = (ROOT / "platformio.ini").read_text()
|
||||
stm32_base = platformio.split("[stm32_base]", 1)[1].split("\n[", 1)[0]
|
||||
@@ -32,6 +50,8 @@ class Ed25519CompactTest(unittest.TestCase):
|
||||
for label, extra_flags in (
|
||||
("standard", []),
|
||||
("compact", ["-DED25519_COMPACT_BASE=1"]),
|
||||
("compact_sha512", ["-DED25519_COMPACT_BASE=1",
|
||||
"-DED25519_COMPACT_SHA512=1"]),
|
||||
):
|
||||
executable = Path(temp_dir) / label
|
||||
subprocess.run(
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
"""Exercise production scope storage and truncation with 254 neighbors."""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from test_replay_reset_integration import extract_braced
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class NeighborScopeStorageTest(unittest.TestCase):
|
||||
def test_response_bounds_and_publish_prefix(self):
|
||||
header = (ROOT / "examples/simple_repeater/MyMesh.h").read_text()
|
||||
source = (ROOT / "examples/simple_repeater/MyMesh.cpp").read_text()
|
||||
layout = header[header.index(" struct NeighborDiscoverEntry {"):
|
||||
header.index(" uint8_t neighbor_discover_count;")]
|
||||
methods = "\n".join(extract_braced(source, signature) for signature in (
|
||||
"bool MyMesh::completeNeighborDiscoverEntry()",
|
||||
"bool MyMesh::handleNeighborDiscoverResponse("))
|
||||
code = r'''
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <cstddef>
|
||||
#define MAX_NEIGHBOURS 254
|
||||
#define PUB_KEY_SIZE 32
|
||||
namespace mesh {
|
||||
struct Identity { uint8_t pub_key[32]; };
|
||||
struct Utils { static void toHex(char* out, const uint8_t*, size_t) { out[0] = 0; } };
|
||||
}
|
||||
struct MQTTBridge {
|
||||
static constexpr int NEIGHBORS_MAX_PUBLISH_ENTRIES = 20;
|
||||
static constexpr size_t NEIGHBORS_JSON_BUFFER_SIZE = 8192;
|
||||
};
|
||||
struct MQTTMessageBuilder {
|
||||
struct NeighborsMessageEntry {
|
||||
const char* key; float snr; uint32_t age; const char* scopes;
|
||||
const char* status; int16_t rssi;
|
||||
};
|
||||
static size_t measureNeighborsMessageEntry(const NeighborsMessageEntry&) { return 100; }
|
||||
};
|
||||
struct Clock { uint32_t getCurrentTime() { return 100; } };
|
||||
struct MyMesh {
|
||||
enum { ND_UNSENT, ND_QUEUED, ND_PENDING, ND_RESPONDED, ND_TIMEOUT, ND_SEND_FAILED };
|
||||
''' + layout + r'''
|
||||
unsigned neighbor_discover_next = 0, neighbor_discover_count = 254;
|
||||
unsigned neighbor_discover_publish_count = 0;
|
||||
size_t neighbor_discover_json_size = 0;
|
||||
bool neighbor_discover_truncated = false, finished = false;
|
||||
Clock clock;
|
||||
Clock* getRTCClock() { return &clock; }
|
||||
void touchNeighbourHeard(const mesh::Identity&, uint32_t, float, int16_t) {}
|
||||
void finishNeighborDiscover() { finished = true; }
|
||||
bool completeNeighborDiscoverEntry();
|
||||
bool handleNeighborDiscoverResponse(int, const uint8_t*, size_t, float, int16_t);
|
||||
};
|
||||
''' + methods + r'''
|
||||
int main() {
|
||||
MyMesh m = {};
|
||||
static_assert(sizeof(m.neighbor_discover_scopes) == 21 * 96, "bounded scope storage");
|
||||
static_assert(sizeof(m.neighbor_discover) / sizeof(m.neighbor_discover[0]) == 254,
|
||||
"retain every neighbor snapshot");
|
||||
uint8_t response[200] = {};
|
||||
uint32_t tag = 123;
|
||||
memcpy(response, &tag, 4);
|
||||
memset(response + 8, 'x', sizeof(response) - 8);
|
||||
for (int i = 0; i <= 20; ++i) {
|
||||
auto& entry = m.neighbor_discover[i];
|
||||
entry.status = MyMesh::ND_PENDING;
|
||||
entry.tag = tag;
|
||||
assert(m.handleNeighborDiscoverResponse(i, response, sizeof(response), 5, -100));
|
||||
assert(strlen(m.neighbor_discover_scopes[i]) == 95);
|
||||
assert(m.completeNeighborDiscoverEntry() == (i < 20));
|
||||
}
|
||||
assert(m.finished && m.neighbor_discover_truncated);
|
||||
assert(m.neighbor_discover_publish_count == 20);
|
||||
assert(!m.handleNeighborDiscoverResponse(21, response, sizeof(response), 5, -100));
|
||||
assert(!m.handleNeighborDiscoverResponse(-1, response, sizeof(response), 5, -100));
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = Path(directory)
|
||||
(path / "test.cpp").write_text(code)
|
||||
subprocess.run(["g++", "-std=c++17", "-fsanitize=address,undefined",
|
||||
str(path / "test.cpp"), "-o", str(path / "test")], check=True)
|
||||
subprocess.run([str(path / "test")], check=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -66,6 +66,7 @@ extends = rak3x72
|
||||
platform_packages = platformio/toolchain-gccarmnoneeabi@^1.140201.0
|
||||
build_unflags = -Os
|
||||
build_flags = ${rak3x72.build_flags}
|
||||
-D ED25519_COMPACT_SHA512=1 ; same hash rounds, compact loop preserves the filesystem boundary
|
||||
-Oz
|
||||
-flto
|
||||
-fno-inline-small-functions
|
||||
|
||||
@@ -72,6 +72,7 @@ extends = Tiny_Relay
|
||||
platform_packages = platformio/toolchain-gccarmnoneeabi@^1.140201.0
|
||||
build_unflags = -Os
|
||||
build_flags = ${Tiny_Relay.build_flags}
|
||||
-D ED25519_COMPACT_SHA512=1 ; same hash rounds, compact loop preserves the filesystem boundary
|
||||
-Oz
|
||||
-flto
|
||||
-fno-inline-small-functions
|
||||
|
||||
@@ -90,6 +90,7 @@ extends = lora_e5
|
||||
platform_packages = platformio/toolchain-gccarmnoneeabi@^1.140201.0
|
||||
build_unflags = -Os
|
||||
build_flags = ${lora_e5.build_flags}
|
||||
-D ED25519_COMPACT_SHA512=1 ; same hash rounds, compact loop preserves the filesystem boundary
|
||||
-Oz
|
||||
-flto
|
||||
-fno-inline-small-functions
|
||||
|
||||
@@ -79,6 +79,7 @@ platform_packages = platformio/toolchain-gccarmnoneeabi@^1.140201.0
|
||||
build_unflags =
|
||||
-Os
|
||||
build_flags = ${lora_e5_mini.build_flags}
|
||||
-D ED25519_COMPACT_SHA512=1 ; same hash rounds, compact loop preserves the filesystem boundary
|
||||
-Oz
|
||||
-flto
|
||||
-fno-inline-small-functions
|
||||
|
||||
Reference in new issue
Block a user