Qualify MeshPocket and support pinned flasher USB helpers

This commit is contained in:
mikecarper committed 2026-10-21 08:28:42 -07:00
1 parent d3661f66f0
commit 497138fad3
5 files changed
+485 -5

No files matched your search

@@ -4,12 +4,19 @@
# Mercerwood bench qualification: 12 active radios, 20 October 2026
**21 October follow-up:** the user lifted MeshPocket's bootloader-work
exclusion. Its fresh Full Companion USB, BLE, contact persistence/sync and
three two-way LoRa rounds passed in the
[MeshPocket qualification](mesh_pocket_qualification_1_18_0_1_2026-10-21.md).
The exclusion and source-bound measurements below describe the earlier
20 October campaign; the other twelve boards were not retested for this update.
Status: **PASS**. 12/12 active boards have complete
accepted bench coverage; 0 have a latest required failure and
0 remain incomplete. The original inventory has thirteen
radios: eleven Full Companions and two Full OTA repeaters. The active target
is twelve radios: ten Companions and two repeaters. MeshPocket is
operator-excluded from all further testing for separate bootloader work.
is twelve radios: ten Companions and two repeaters. MeshPocket was
operator-excluded at this campaign's close for separate bootloader work.
All thirteen 0865 candidate builds passed. Companion updates target
`08650f644b680a5dfaacb95142e3cef8529da9d2`; the table records each board's
@@ -51,8 +58,8 @@ records each axis's actual tested source, artifact, measurement interval,
conditions and evidence digest. V3, Tag and T096 retain valid ABF USB/BLE/RF measurements; V4 retains ABF RF.
Pocket's earlier ABF and 0865 results appear only in its excluded historical
record; its current installed application and bootloader are unverified
outside this report for operator bootloader work, and no further Pocket tests
are planned. ABF is
outside this report for operator bootloader work. The later Pocket tests are
recorded separately in the follow-up linked above. ABF is
`abf7ba5d5c3958580d52d6954d816c08a035a552`. For active boards, changed applications, current identities and new 64/32
contact trials are separate measurements.
Reviewed source assurance `577aee5162c7553f58bf13b550560da50bbf73860ab37febe3674a5ae73c01aa`
@@ -0,0 +1,70 @@
<!-- meshcore-hosted-doc-link:start -->
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/test-results/mesh_pocket_qualification_1_18_0_1_2026-10-21/">View this page on MeshCore Docs</a>.</p>
<!-- meshcore-hosted-doc-link:end -->
# MeshPocket Companion qualification, 21 October 2026
Status: **PASS for the automated Companion bench checks below**. The user's
request to test MeshPocket lifted its earlier bootloader-work exclusion.
The board now runs Full Companion built from
`21a0f318da26bdb1fa5257471c3d7a68a0684337`, version
`v1.18.0.1-halo-keymind-cascade-dev-21a0f318`, protocol 14.
Its measured application body is `54411895235BE9C4`; application ZIP SHA256 is
`7841c4e7e8bbb8695ba0e796fa811efa41112367641c2790f22cc9085ba00da6`.
No firmware change was needed for these checks.
| Check | Measured result |
| --- | --- |
| Build and application update | Full capability/memory checks passed; identity-bound USB application-only DFU succeeded; exact version and EndF body verified after boot |
| USB | 100 query cycles, 10 reopens, owned contact CRUD and persistence through one verified warm restart passed; no framing errors or core error flags |
| Bluetooth | Three authenticated sessions held for 30.269 / 30.271 / 30.271 seconds; saved bond, exact USB identity/version and three clean controller lifecycles; no bond deletion |
| Full contact refresh | All 64 owned records verified in a 65-record stream, including the original contact; request to END: 2.491665 seconds |
| Incremental refresh | Exact 32 owned timestamp-selected records in a 33-record stream; request to END: 1.305048 seconds |
| Contact cleanup | All 64 synthetic contacts removed; zero remained after reopen; original contact fingerprint unchanged |
| Two-way LoRa | Three rounds, six exact direct nonce-bound datagrams, six send commands; every round passed |
| Handback | Owned channels removed; both radios' original modem settings, TX power and saved routes restored; ports closed; final Pocket core errors and outbound queue both zero |
Bluetooth passed while the USB data cable remained connected. Each contact
refresh used the existing bond and a separately verified clean controller
lifecycle. USB persistence covers one owned contact across a warm restart;
the 64-record fixture separately covers a seven-second save allowance and
exact reopen readback. Neither is a cold-power persistence measurement.
The RF partner was the already restored XIAO ESP32-S3 Full Companion, retained
at `ebb59d48292de4a573250cf4c6a2ed7cf5f7ef69`, body `4EEB12667E5E554B`.
Both identities and actual versions were verified before the three rounds.
The temporary test tuple was 910.525 MHz / BW62.5 / SF7 / CR5, preamble 32.
Exact source, destination, nonce, zero-hop route and physical packet counters
were checked. This is an explicitly mixed-source pair, not a claim that every
radio runs the new Pocket artifact. The RAK3401 gateway was not touched.
The existing bootloader and SoftDevice were preserved: the verified DFU ZIP
contained only application BIN/DAT components. Its exact installed bootloader
revision remains unverified because this Full Companion returns an unknown
command for `get bootloader.ver`. Successful application DFU and subsequent
runtime checks do not establish the independent bootloader's revision.
The first updater attempt failed before its DFU touch because the newer
flasher's pinned USB helpers depend on startup globals absent from the
definitions-only bench adapter. The compatible, previously reviewed updater
then installed the app successfully. The adapter now validates and privately
stages both bundled helpers and resolves them locally without running flasher
startup. Fourteen adapter tests and five application-ZIP verifier tests passed;
shell syntax and diff checks passed. This tooling-only fix did not trigger
another firmware installation or repeated hardware campaign. The preinstall
0865 application's core flag 8 and the failed updater attempt remain in the
private evidence; no causal firmware fix is inferred from the clean new boot.
The [safe receipt](mesh_pocket_qualification_1_18_0_1_2026-10-21.safe.json)
binds every measured axis to its source, artifact, interval and evidence
digest. Earlier Pocket results and the temporary exclusion remain historical
in the [20 October fleet report](hardware_validation_mercerwood_1_18_0_1_2026-10-20.md).
The other twelve boards retain their accepted source-bound measurements;
unchanged hardware tests were not repeated.
These finite checks do not establish RF range, long-duration reliability,
phone interoperability, battery current or physical cold starts. Screen/page
rendering awaits operator observation. MeshPocket has no native WiFi.
Bluetooth DFU is build-verified but a physical wireless update was not run in
this task. This Companion can seed LoRa firmware transfers; local LoRa firmware
installation is disabled and was not claimed tested.
@@ -0,0 +1,217 @@
{
"schema": 1,
"report_date": "2026-10-21",
"status": "PASS",
"scope": "Fresh automated Full Companion USB/BLE/contact-persistence/direct-LoRa qualification after the user lifted MeshPocket bootloader-work exclusion. Screen, cold-power, range, phone interoperability and physical wireless DFU are not claimed.",
"board": "Heltec MeshPocket",
"target": "Mesh_pocket_companion_radio_full",
"source_commit": "21a0f318da26bdb1fa5257471c3d7a68a0684337",
"version": "v1.18.0.1-halo-keymind-cascade-dev-21a0f318 (Build: 21-Oct-2026)",
"application_sha256": "7841c4e7e8bbb8695ba0e796fa811efa41112367641c2790f22cc9085ba00da6",
"body_hash": "54411895235BE9C4",
"firmware_modified_in_this_task": false,
"bootloader": {
"action": "preserved_by_application_only_DFU",
"bootloader_or_softdevice_components_transferred": false,
"current_exact_version": null,
"version_query": "get bootloader.ver is unsupported by the measured Full Companion",
"independent_bootloader_image_assumed": false
},
"axes": {
"build": {
"status": "PASS",
"memory_available_internal_bytes": 85352,
"memory_required_heap_bytes": 53056,
"memory_required_contiguous_bytes": 8192
},
"application_install": {
"status": "PASS",
"method": "identity_bound_USB_serial_application_only_DFU",
"application_return_verified_by_exact_version_and_EndF_body": true,
"backups_created": false,
"full_flash_erased": false
},
"usb": {
"status": "PASS",
"started_at": "2026-10-21T15:13:55.339146+00:00",
"finished_at": "2026-10-21T15:14:50.564843+00:00",
"query_cycles": 100,
"reopens": 10,
"owned_crud": true,
"owned_record_survives_verified_warm_reboot": true,
"warm_reboot_requests": 1,
"stable_binary_transport": {
"discarded_prefix_bytes": 0,
"ignored_push_frames": 0,
"invalid_length_candidates": 0,
"ports_closed": 18,
"ports_opened": 18,
"request_bytes_sent": 5419,
"request_frames_sent": 662,
"response_frames_received": 667,
"response_payload_bytes": 24927
},
"channel_state_unchanged": true,
"initial_contacts": 1,
"final_contacts": 1
},
"ble": {
"status": "PASS",
"started_at": "2026-10-21T15:14:58.890522+00:00",
"finished_at": "2026-10-21T15:17:17.349636+00:00",
"sessions": 3,
"hold_seconds": [
30.269,
30.271,
30.271
],
"preexisting_bond": true,
"target_bond_deleted": false,
"usb_identity_and_version_verified": true,
"USB_data_cable_remained_connected": true,
"hci_lifecycle": {
"capture_ended_before_timeout": true,
"classification_complete": true,
"clean": true,
"ignored_foreign_connection_events": 0,
"lifecycle_parse_errors": 0,
"minimum_required_sessions": 3,
"target_command_failures": 0,
"target_disconnect_reasons": [
"0x16",
"0x16",
"0x16"
],
"target_disconnect_statuses": [
"0x00",
"0x00",
"0x00"
],
"target_establishment_0x3e_disconnects": 0,
"target_failed_establishments": 0,
"target_ll_response_timeout_0x22_disconnects": 0,
"target_open_handles_at_end": 0,
"target_pending_attempts_at_end": 0,
"target_successful_connection_events": 3,
"target_timeout_0x08_disconnects": 0,
"unattributed_controller_events": 0
}
},
"contact_refresh": {
"status": "PASS",
"started_at": "2026-10-21T15:17:31.314046+00:00",
"finished_at": "2026-10-21T15:18:41.651834+00:00",
"owned_full_records": 64,
"full_total_records": 65,
"full_request_to_end_seconds": 2.491665,
"owned_incremental_records": 32,
"incremental_total_records": 33,
"incremental_request_to_end_seconds": 1.305048,
"owned_deleted": 64,
"owned_remaining": 0,
"unrelated_records_preserved": true,
"existing_bond_and_clean_HCI_verified": true,
"persist_allowance_seconds": 7,
"cold_persistence_claimed": false
},
"lora": {
"status": "PASS",
"started_at": "2026-10-21T15:21:14.086947+00:00",
"finished_at": "2026-10-21T15:22:40.088960+00:00",
"rounds": 3,
"directions_per_round": 2,
"exact_direct_nonce_datagrams_received": 6,
"send_commands": 6,
"temporary_frequency_MHz": 910.525,
"bandwidth_kHz": 62.5,
"spreading_factor": 7,
"coding_rate": 5,
"preamble": 32,
"mixed_source_pair": true,
"partner": {
"expected_version": "v1.18.0.1-halo-keymind-cascade-dev-ebb59d48",
"source_commit": "ebb59d48292de4a573250cf4c6a2ed7cf5f7ef69",
"target": "Xiao_S3_WIO_companion_radio_full"
},
"partner_body_hash": "4EEB12667E5E554B",
"owned_channels_removed": true,
"original_radio_TX_settings_and_routes_restored": true,
"final_handback_passed": true,
"ports_unowned_after_close": true,
"final_core": {
"battery_mv": 4130,
"error_flags": 0,
"outbound_queue_length": 0,
"uptime_seconds": 480
}
},
"screen": {
"status": "NOT_OBSERVED",
"operator_check_requested": true
},
"wifi": {
"status": "NOT_APPLICABLE",
"reason": "No native WiFi radio on this nRF52840 board"
},
"wireless_OTA": {
"status": "NOT_MEASURED",
"build_verified_bluetooth_DFU_capability": true,
"USB_DFU_measured": true,
"LoRa_role": "source-only; local install disabled"
}
},
"evidence_sha256": {
"initial-inventory.private.json": "faf876cf4835a7b6711595b9f3258f90de785a8edf1251c5856910523576971a",
"bootloader-before.private.json": "185291ff70854e38cf94c0c61e06de522f46789597b0bb6523282e6381fe4678",
"install.safe.json": "0c57088884a6ea50c28dc3a9cd3c07f81164f115075b99cff0f3d35a4aa7bccf",
"install-retry.safe.json": "3b83c7097203bbaf42862b0e1c44fc5131d83f1b6cf626c3d76e0d2cefb6508d",
"post-install-runtime.private.json": "a46998359e606591e1c3e4f2ac7e859445f5aa77a73d66e9c76598cdaf81e220",
"build-verified.safe.json": "da2b471f951052ba69ddd13e935d7f127717f2f45f134bc41b01e7f289f9a70c",
"usb/summary.safe.json": "58a52ce6d4c42450fa812ed09b3c44d229a21b8b3bc3188e1ef1647feda9451d",
"usb-setup.safe.json": "ec43c8098837506110e9fec0248c2a3d85565b85165502def264fd2301e7020c",
"ble/summary.safe.json": "5d4aef872afbac18a214074e063cbed1d10f6489a1540866675433e01e14b13a",
"contacts/summary.safe.json": "7bdd680e899ca0ba633b0f8b346c4485928ebc86b581f52bb991254f6da6c94a",
"contacts/sync/summary.safe.json": "4b8e2f0eb919c3f452812f721dabe9a62d5e58df2af9472494563950900bff45",
"contacts/cleanup/summary.safe.json": "6f3e136bef3e3bad9fcedf85feecbb207286bcdc5ec5cc4030be3950a8715623",
"rf/summary.safe.json": "e0d9e93f834e1758aaa7b59d1c5915883ec0f656146249e1269e5dad91af6f60"
},
"drivers": {
"campaign_sha256": "863706b02cf88eb13c5e1ea0f1018663b6c3ecf16989c5cc24f490c1bdb404f0",
"rf_driver_sha256": "83ed1d30e1c25371709d39c48e3a4bb4a9a44e659093ab4cd75737b639cb5880",
"reviewed_harness_manifest_sha256": "580f758c2a70333205b3eaa41e4f45b6a7c85058477c959bc885b04c215cfcab"
},
"history": {
"preinstall_source": "08650f644b680a5dfaacb95142e3cef8529da9d2",
"preinstall_core_error_flags": 8,
"postinstall_measured_core_error_flags": 0,
"causal_firmware_fix_claimed": false,
"first_updater_attempt": "Failed before touch/programming due to omitted hardened-helper startup globals; preserved separately",
"successful_install_flasher_sha256": "4da05ba37c2cb8b4a7e455a4aa3ee24f6d8020912d71874c38afd099e98e75b2"
},
"test_tool_fix": {
"files": [
"tools/hil/mercer_nrf_app_flash.sh",
"test/test_mercer_nrf_app_flash.py"
],
"description": "Definitions-only adapter validates and privately stages pinned bundled USB helpers; local-only resolution without sourcing flasher startup",
"focused_adapter_tests_passed": 14,
"application_zip_verifier_tests_passed": 5,
"hardware_retest_after_test_tool_only_change": false
},
"fleet_scope": {
"pocket_exclusion_lifted_by_user": true,
"newly_measured_boards": 1,
"previously_accepted_boards": 12,
"previous_measurements_repeated": false,
"all_thirteen_same_source_claimed": false,
"gateway_service_touched": false
},
"limits": [
"Finite bench operation, not RF range or long-duration reliability",
"Screen and physical button rendering await operator observation",
"No physical cold-power or battery-current measurement",
"No physical Bluetooth DFU or LoRa firmware-source transfer in this task",
"Exact installed bootloader revision is unavailable through this Companion getter",
"Other boards retain their own source-bound reports"
]
}
+110 -1
View File
@@ -81,8 +81,14 @@ class MercerNrfAppShellTest(unittest.TestCase):
self.inventory_file.write_text(json.dumps(inventory()), encoding="utf-8")
self.startup_marker = self.work / "STARTUP-WAS-SOURCED"
self.dfu_marker = self.work / "MOCK-DFU-ONLY"
self.resolver_marker = self.work / "REMOTE-RESOLVER-WAS-CALLED"
self.helper_marker = self.work / "LOCAL-HELPER-PATHS"
self.port_check_marker = self.work / "SERIAL-PREFLIGHT-WAS-CALLED"
self.env = dict(os.environ, SIM_STARTUP_MARKER=str(self.startup_marker),
SIM_DFU_MARKER=str(self.dfu_marker))
SIM_DFU_MARKER=str(self.dfu_marker),
SIM_RESOLVER_MARKER=str(self.resolver_marker),
SIM_HELPER_MARKER=str(self.helper_marker),
SIM_PORT_CHECK_MARKER=str(self.port_check_marker))
def executable(name, text):
path = self.bin / name
@@ -129,6 +135,47 @@ class MercerNrfAppShellTest(unittest.TestCase):
text += f"unused_mock_function_{index}() {{\n return 0\n}}\n"
self.flasher.write_text(text, encoding="utf-8")
def newer_flasher(self):
"""Model the bundled-helper contract added by meshfirmware 41a21fa.
The real helper entry points are never executed. Resolver stubs would
mark an unwanted dependency fetch if the adapter did not replace them.
"""
directory = self.work / "tools"
directory.mkdir()
helpers = {
"meshcore_serial_touch.py": b"# SIMULATED TOUCH ONLY\r\n",
"meshcore_usb_reset.py": b"# SIMULATED IDENTITY ONLY\n",
}
declarations = ""
for name, content in helpers.items():
(directory / name).write_bytes(content)
variable = ("MESHCORE_SERIAL_TOUCH_TOOL_SHA256" if "serial_touch" in name
else "MESHCORE_USB_RESET_TOOL_SHA256")
digest = hashlib.sha256(content.replace(b"\r\n", b"\n")).hexdigest()
declarations += f'{variable}="{digest}"\n'
text = self.flasher.read_text(encoding="utf-8") + declarations
for name in ("resolve_meshcore_serial_touch_tool", "resolve_meshcore_usb_reset_tool"):
text += (f"{name}() {{\n"
' printf \'UNEXPECTED DOWNLOAD\\n\' > "$SIM_RESOLVER_MARKER"\n'
" return 89\n}\n")
text += (
"preferred_flash_serial_port() {\n"
' printf \'PREFLIGHT\\n\' > "$SIM_PORT_CHECK_MARKER"\n'
' printf \'%s\\n\' "$1"\n}\n'
"run_nrf52_dfu_package_buttonless() {\n"
' [[ "$PWD_SCRIPT" == "$FIRMWARE_ROOT" ]] || return 90\n'
' [[ -z "$MESHCORE_SERIAL_TOUCH_TOOL_URL" && -z "$MESHCORE_USB_RESET_TOOL_URL" ]] || return 91\n'
' local touch identity\n'
' touch=$(resolve_meshcore_serial_touch_tool) || return 92\n'
' identity=$(resolve_meshcore_usb_reset_tool) || return 92\n'
' printf \'%s\\n%s\\n\' "$touch" "$identity" > "$SIM_HELPER_MARKER"\n'
' printf \'%s|%s|%s|%s\\n\' "$1" "$2" "$3" "$4" > "$SIM_DFU_MARKER"\n'
"}\n"
)
self.flasher.write_text(text, encoding="utf-8")
return helpers
def invoke(self, overrides=None, *, consent=True):
arguments = {
"--flasher": str(self.flasher), "--port": PORT, "--workdir": str(self.work),
@@ -181,6 +228,68 @@ class MercerNrfAppShellTest(unittest.TestCase):
self.assertEqual(Path(call[0]).name, self.package.name)
self.assertIn("Application-only DFU completed", result.stdout)
def test_new_flasher_uses_only_verified_private_bundled_helpers(self):
helpers = self.newer_flasher()
# Ambient values cannot select different helper bytes or locations.
self.env.update(PWD_SCRIPT="/unreviewed", FIRMWARE_ROOT="/unreviewed",
MESHCORE_SERIAL_TOUCH_TOOL_SHA256="0" * 64,
MESHCORE_USB_RESET_TOOL_SHA256="0" * 64)
result = self.invoke()
self.assertEqual(result.returncode, 0, result.stderr)
self.assertTrue(self.dfu_marker.exists())
self.assertFalse(self.resolver_marker.exists(), "a remote-capable resolver was called")
paths = [Path(value) for value in self.helper_marker.read_text().splitlines()]
self.assertEqual(len(paths), 2)
self.assertEqual(paths[0].parent, paths[1].parent)
for path in paths:
self.assertEqual(path.parent.name, "tools")
self.assertTrue(path.parent.parent.name.startswith(".mcfirmware-nrf-app."))
self.assertNotEqual(path, self.work / "tools" / path.name)
self.assertEqual(path.read_bytes(), helpers[path.name])
self.assertEqual(path.stat().st_mode & 0o777, 0o400)
def test_missing_bundled_helper_fails_before_serial_preflight(self):
self.newer_flasher()
(self.work / "tools/meshcore_usb_reset.py").unlink()
self.assert_refused(self.invoke(), "Bundled helper unavailable")
self.assertFalse(self.port_check_marker.exists())
self.assertFalse(self.resolver_marker.exists())
def test_mismatched_bundled_helper_fails_before_serial_preflight(self):
self.newer_flasher()
(self.work / "tools/meshcore_serial_touch.py").write_bytes(b"UNREVIEWED HELPER")
self.assert_refused(self.invoke(), "Bundled helper hash mismatch")
self.assertFalse(self.port_check_marker.exists())
self.assertFalse(self.resolver_marker.exists())
def test_symlink_bundled_helper_fails_before_serial_preflight(self):
self.newer_flasher()
source = self.work / "tools/meshcore_serial_touch.py"
source.rename(self.work / "external-helper.py")
source.symlink_to(self.work / "external-helper.py")
self.assert_refused(self.invoke(), "Bundled helper unavailable")
self.assertFalse(self.port_check_marker.exists())
def test_helper_hash_declaration_is_data_and_must_be_unique(self):
self.newer_flasher()
original = self.flasher.read_text(encoding="utf-8")
for declaration, reason in (
('MESHCORE_USB_RESET_TOOL_SHA256="' + "0" * 64 + '"\n',
"Expected one literal helper hash declaration"),
('MESHCORE_SERIAL_TOUCH_TOOL_SHA256="$(touch $SIM_STARTUP_MARKER)"\n',
"Unsupported helper hash declaration"),
):
with self.subTest(declaration=declaration):
if declaration.startswith("MESHCORE_SERIAL_TOUCH"):
text = "\n".join(line for line in original.splitlines()
if not line.startswith("MESHCORE_SERIAL_TOUCH_TOOL_SHA256=")) + "\n"
else:
text = original
self.flasher.write_text(text + declaration, encoding="utf-8")
self.assert_refused(self.invoke(), reason)
self.assertFalse(self.port_check_marker.exists())
self.assertFalse(self.resolver_marker.exists())
if __name__ == "__main__":
unittest.main()
+77
View File
@@ -14,6 +14,7 @@ nrf_adapter_usage() {
' --artifact APPLICATION.zip --sha256 HEX --allow-write' \
'Only an inventory-matched application-only DFU ZIP is accepted.' \
'The sibling verified_nrf_flash.py helper and installed dependencies are required.' \
'Newer flashers also require their matching bundled tools/ serial-touch helpers.' \
'Successful DFU still requires a separate application-level boot/version check.'
}
@@ -104,6 +105,82 @@ for nrf_adapter_function in preferred_flash_serial_port save_selected_serial_por
declare -F "$nrf_adapter_function" >/dev/null || nrf_adapter_die "Flasher lacks $nrf_adapter_function."
done
# Newer flashers use a pinned Python helper for the single 1200-baud touch.
# Their normal startup initializes paths and hashes, but sourcing startup here
# would also install traps and run unrelated device/service operations. Read
# only the two literal hash declarations and freeze their bundled helpers.
# Never let a definitions-only adapter fall back to downloading dependencies.
if declare -F resolve_meshcore_serial_touch_tool >/dev/null; then
nrf_adapter_bundled_hashes=$(python3 - "$nrf_adapter_flasher" "$nrf_adapter_state_dir" <<'PY'
import hashlib
import os
from pathlib import Path
import re
import stat
import sys
flasher, state = map(Path, sys.argv[1:])
text = flasher.read_text(encoding="utf-8")
helpers = (
("meshcore_serial_touch.py", "MESHCORE_SERIAL_TOUCH_TOOL_SHA256"),
("meshcore_usb_reset.py", "MESHCORE_USB_RESET_TOOL_SHA256"),
)
verified = []
for name, variable in helpers:
declarations = re.findall(r"^" + variable + r"=.*$", text, re.MULTILINE)
if len(declarations) != 1:
raise SystemExit("Expected one literal helper hash declaration: " + variable)
match = re.fullmatch(variable + r'="([0-9a-fA-F]{64})"', declarations[0])
if match is None:
raise SystemExit("Unsupported helper hash declaration: " + variable)
expected = match[1].lower()
source = flasher.parent / "tools" / name
try:
descriptor = os.open(source, os.O_RDONLY | os.O_NOFOLLOW)
with os.fdopen(descriptor, "rb") as handle:
if not stat.S_ISREG(os.fstat(handle.fileno()).st_mode):
raise ValueError("Helper is not a regular file")
content = handle.read(1024 * 1024 + 1)
except (OSError, ValueError) as exc:
raise SystemExit("Bundled helper unavailable: " + name) from exc
if not content or len(content) > 1024 * 1024:
raise SystemExit("Bundled helper is empty or oversized: " + name)
if hashlib.sha256(content.replace(b"\r\n", b"\n")).hexdigest() != expected:
raise SystemExit("Bundled helper hash mismatch: " + name)
verified.append((name, content, expected))
directory = state / "tools"
directory.mkdir(mode=0o700)
for name, content, expected in verified:
descriptor = os.open(directory / name,
os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o400)
with os.fdopen(descriptor, "wb") as handle:
handle.write(content)
print(" ".join(expected for _, _, expected in verified))
PY
) || nrf_adapter_die 'Bundled serial-touch helpers failed verification; no DFU touch was requested.'
read -r MESHCORE_SERIAL_TOUCH_TOOL_SHA256 MESHCORE_USB_RESET_TOOL_SHA256 \
<<< "$nrf_adapter_bundled_hashes"
PWD_SCRIPT=$nrf_adapter_state_dir
FIRMWARE_ROOT=$nrf_adapter_state_dir
MESHCORE_SERIAL_TOUCH_TOOL_URL=''
MESHCORE_USB_RESET_TOOL_URL=''
nrf_adapter_local_helper() {
local helper="$nrf_adapter_state_dir/tools/$1" expected=$2 observed
[[ -f "$helper" && ! -L "$helper" && -r "$helper" ]] || return 1
observed=$(python3 -c 'import hashlib,sys; print(hashlib.sha256(open(sys.argv[1], "rb").read().replace(b"\r\n", b"\n")).hexdigest())' "$helper") || return 1
[[ "$observed" == "$expected" ]] || return 1
printf '%s\n' "$helper"
}
resolve_meshcore_usb_reset_tool() {
nrf_adapter_local_helper meshcore_usb_reset.py "$MESHCORE_USB_RESET_TOOL_SHA256"
}
resolve_meshcore_serial_touch_tool() {
resolve_meshcore_usb_reset_tool >/dev/null || return 1
nrf_adapter_local_helper meshcore_serial_touch.py "$MESHCORE_SERIAL_TOUCH_TOOL_SHA256"
}
fi
# Stage a private, hash-verified snapshot, so the build output may not change
# beneath a DFU operation. Keep the original board-qualified artifact basename.
nrf_adapter_staged="$nrf_adapter_state_dir/$(basename -- "$nrf_adapter_artifact")"