Save RAK repeater flash while retaining fast field arithmetic

Use compact scalar crypto, reviewed GNSS version parsing and shared CLI reply copies for RAK3401/RAK4631 repeaters. Preserve keys, protocols, commands and configured capacities.

Both Reduced images save 11,580 bytes. SWD measurements on the RAK3401 found scalar-only signing still near 50.9 ms and key exchange near 38 ms. Keep compact field arithmetic off: its extra 4.3 KiB saving adds about a quarter second and delays radio servicing.

Validate Full and Reduced production OTA packages on both boards, focused crypto/GPS/CLI tests, and linked ARM crypto outputs. Include the RAM benchmark harness and scoped results. No live RF packet-loss qualification is claimed.
This commit is contained in:
mikecarper committed 2026-10-27 11:41:32 -07:00
1 parent 308fec1162
commit 08b1c8826c
9 files changed
+1256 -10

No files matched your search

+41 -1
View File
@@ -5,6 +5,9 @@ and flags: build.sh can synthesize profiles over another PlatformIO recipe.
Unknown roles fail closed. No shared framework or downloaded library is edited.
CDC, USB host, sensor/GPS support, BLE P-256, and stored Ed25519 keys remain.
Infrastructure uses RAM-built compact Ed25519 and display-font trims.
RAK3401/RAK4631 repeaters also share CLI reply copies and use compact scalar
arithmetic and the reviewed GNSS version parser without changing protocols.
Fast field arithmetic is retained to avoid long radio-service stalls.
Companion uses a 960-byte Ed25519 flash table to keep signing stack bounded,
and retains all UI fonts and the Reader. The nRF52 base keeps an 8 KiB loop
stack for crypto and the existing OTA path.
@@ -70,6 +73,11 @@ COMPANION_TRIM_DEFINES["ED25519_SMALL_BASE"] = 1
COMPANION_TRIM_DEFINES["ED25519_COMPACT_SHA512"] = 1
COMPANION_TRIM_DEFINES["ED25519_COMPACT_SCALAR"] = 1
RAK_REPEATER_TRIM_DEFINES = {
"ED25519_COMPACT_SCALAR": 1,
"MESH_CLI_SHARE_REPLIES": 1,
}
def flag_defines(flags=(), cppdefines=()):
"""Read effective defines without importing PlatformIO/SCons for tests."""
@@ -115,6 +123,15 @@ def source_selected(src_filter, source):
return selected
def is_rak_repeater(mcu, src_filter, values):
"""Qualify hardware and the effective role, including release overlays."""
return (str(mcu).lower() == "nrf52840"
and any(_enabled(values.get(board)) for board in ("RAK_3401", "RAK_4631"))
and source_selected(src_filter, ROLE_SOURCES[0])
and not any(source_selected(src_filter, source)
for source in ROLE_SOURCES[1:] + COMPANION_SOURCES))
def trim_policy(platform, mcu, env_name, src_filter, flags=(), cppdefines=(),
ota_name_mode="front-coded"):
"""Return defaults for a qualified non-Companion role, otherwise nothing.
@@ -141,6 +158,8 @@ def trim_policy(platform, mcu, env_name, src_filter, flags=(), cppdefines=(),
if ota_name_mode != "front-coded":
raise ValueError("unsupported nRF52 OTA name trim policy: " + ota_name_mode)
definitions = dict(TRIM_DEFINES, OTA_TARGET_NAME_FRONT_CODED=1)
if is_rak_repeater(mcu, src_filter, values):
definitions.update(RAK_REPEATER_TRIM_DEFINES)
if _enabled(values.get("MESH_OTA_LOCAL_TARGET_NAME_ONLY")):
# Release overlays can compile a different PIOENV from their deployed
# OTA identity. Keep the logical name/ID in that case; direct builds
@@ -240,6 +259,20 @@ def compile_packed_help(build_env, node):
return build_env.Object(node, **flags)
def compile_shared_cli_replies(build_env, node):
"""Keep constant reply copies out of line in the qualified CLI object only."""
source = Path(node.srcnode().get_abspath()).resolve()
expected = Path(build_env.subst("$PROJECT_DIR/src/helpers/CommonCLI.cpp")).resolve()
if source != expected:
return node
# GCC otherwise expands many constant strcpy replies into repeated stores.
# Use the same libc function and strings, with no parser or output changes.
flags = list(build_env.get("CCFLAGS", ()))
if "-fno-builtin-strcpy" not in flags:
flags.append("-fno-builtin-strcpy")
return build_env.Object(node, CCFLAGS=flags)
def install(build_env):
platform = build_env.PioPlatform()
mcu = build_env.BoardConfig().get("build.mcu", "")
@@ -265,6 +298,12 @@ def install(build_env):
hook = importlib.util.module_from_spec(hook_spec)
hook_spec.loader.exec_module(hook)
hook.install(build_env)
values = flag_defines(flags, cppdefines)
rak_repeater = infrastructure and is_rak_repeater(
mcu, build_env.get("SRC_FILTER", ()), values)
if rak_repeater and _enabled(values.get("MESH_CLI_SHARE_REPLIES",
definitions.get("MESH_CLI_SHARE_REPLIES"))):
build_env.AddBuildMiddleware(compile_shared_cli_replies, "*CommonCLI.cpp")
if infrastructure:
names = ("local OTA target name only" if definitions.get("OTA_TARGET_NAME_TABLE") == 0
or flag_defines(flags, cppdefines).get("OTA_TARGET_NAME_TABLE") == "0"
@@ -272,13 +311,14 @@ def install(build_env):
print("nRF52 infrastructure flash trim: compact crypto, unused USB device classes/GPS cache/vendor splash, " + names)
else:
print("nRF52 Companion flash trim: small crypto table, unused USB device classes/GPS cache/vendor splash; fonts and Reader retained")
if (not infrastructure or rak_repeater) and _enabled(
values.get("MESH_NRF52_GNSS_VERSION_TRIM", "1")):
hook_path = build_env.subst("$PROJECT_DIR/scripts/nrf52_gnss_version_trim.py")
hook_spec = importlib.util.spec_from_file_location("mesh_nrf52_gnss_trim", hook_path)
hook = importlib.util.module_from_spec(hook_spec)
hook_spec.loader.exec_module(hook)
hook.install(build_env)
framework = platform.get_package_dir("framework-arduinoadafruitnrf52")
values = flag_defines(flags, cppdefines)
if "MESH_NRF52_NO_PROGRAM_EXIT" not in values and supported_program_lifetime(framework):
build_env.AppendUnique(CPPDEFINES=[("MESH_NRF52_NO_PROGRAM_EXIT", 1)])
print("nRF52 reviewed firmware lifetime: omit unreachable process-exit registrations")
+10 -5
View File
@@ -1,7 +1,8 @@
"""Keep the reviewed GNSS version parser without linking the scanf engine.
Only the qualified nRF52 Companion trim installs this middleware. Compile a
private copy of the reviewed SparkFun GNSS sources; never change downloaded dependencies.
Qualified nRF52 Companion and RAK3401/RAK4631 Repeater trims install this
middleware. Compile a private copy of the reviewed SparkFun GNSS sources;
never change downloaded dependencies.
Unknown sources or headers retain their ordinary implementation.
"""
@@ -11,6 +12,9 @@ from pathlib import Path
SOURCE_NAME = "SparkFun_u-blox_GNSS_Arduino_Library.cpp"
SOURCE_SHA256 = "8ed1d313177df864b6821b3cb3e4fbcc59d850d09e2460269d571f70cd2b9f1c"
# 2.2.28 has the same version parser and headers as 2.2.29. Its only source
# differences are two file-buffer boundary checks, which this trim preserves.
SOURCE_2_2_28_SHA256 = "4c6c40fe01b15b486791d9ceb045093a394bf157489c2b22d96d7afb92789c49"
HEADERS = {
"SparkFun_u-blox_GNSS_Arduino_Library.h": "5da7a95ddd4b24e26ca474d017a6a5347137a31aee58fb7b450a4ce85bbcc07a",
"u-blox_config_keys.h": "86cae35ec200871f748be21d6d59583c48db82621f4c5504e257e8358a241499",
@@ -38,7 +42,8 @@ def digest(source):
def patched_source(source):
source = source.replace("\r\n", "\n")
if (digest(source) != SOURCE_SHA256 or source.count(ORIGINAL_CONVERSION) != 1
if (digest(source) not in (SOURCE_SHA256, SOURCE_2_2_28_SHA256)
or source.count(ORIGINAL_CONVERSION) != 1
or source.count(INCLUDE) != 1):
raise RuntimeError("changed reviewed SparkFun GNSS source")
return source.replace(INCLUDE, "#include <stdlib.h>\n" + INCLUDE, 1).replace(
@@ -63,7 +68,7 @@ def replace_gnss_source(build_env, node):
raise RuntimeError("changed reviewed SparkFun GNSS header: " + name)
headers[name] = header
except (OSError, UnicodeError, RuntimeError) as error:
print("nRF52 Companion GNSS trim: ordinary source retained (" + str(error) + ")")
print("nRF52 GNSS trim: ordinary source retained (" + str(error) + ")")
return node
destination = Path(build_env.subst("$BUILD_DIR")) / "trimmed-nrf52-gnss" / source.name
destination.parent.mkdir(parents=True, exist_ok=True)
@@ -71,7 +76,7 @@ def replace_gnss_source(build_env, node):
for name, header in headers.items():
write_if_changed(destination.parent / name, header)
write_if_changed(destination, patched)
print("nRF52 Companion GNSS trim: reviewed version conversion uses strtol")
print("nRF52 GNSS trim: reviewed version conversion uses strtol")
return build_env.File(str(destination))