436 lines
23 KiB
Python
436 lines
23 KiB
Python
#!/usr/bin/env python3
|
|
"""Exercise the nRF52 HEX post-build hook with the installed build dependencies."""
|
|
|
|
from contextlib import redirect_stdout
|
|
import importlib.util
|
|
import io
|
|
import os
|
|
from pathlib import Path
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
from intelhex import IntelHex
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT / "tools" / "mota"))
|
|
import motalib as ml # noqa: E402
|
|
|
|
|
|
class BuildEnvironment(dict):
|
|
def __init__(self, app_base):
|
|
super().__init__(
|
|
PROJECT_DIR=str(ROOT),
|
|
PIOENV="nrf52_sensor_hex_test",
|
|
CPPDEFINES=[
|
|
"ENABLE_OTA", "NRF52_PLATFORM",
|
|
("FIRMWARE_VERSION", "v1.17.1.9"),
|
|
("MOTA_TARGET_ID", "0x12345678"),
|
|
("MOTA_HW_ID", "NRF52_TEST"),
|
|
],
|
|
)
|
|
self.app_base = app_base
|
|
self.maximum_size = ml.NRF52_APP_END - app_base
|
|
self.source_filter = "+<examples/simple_sensor>"
|
|
self.linker = ""
|
|
self.post_actions = {}
|
|
|
|
def AddPostAction(self, target, action):
|
|
self.post_actions[target] = action
|
|
|
|
def BoardConfig(self):
|
|
return {"upload.maximum_size": self.maximum_size, "build.ldscript": self.linker}
|
|
|
|
def GetProjectOption(self, name, default=""):
|
|
if name in ("build_src_filter", "src_filter"):
|
|
return self.source_filter
|
|
return default
|
|
|
|
def Replace(self, **values):
|
|
self.update(values)
|
|
|
|
|
|
def load_post_script(env):
|
|
spec = importlib.util.spec_from_file_location(
|
|
"nrf52_endf_hex_test", ROOT / "tools" / "mota" / "pio_endf.py")
|
|
module = importlib.util.module_from_spec(spec)
|
|
module.Import = lambda _name: None
|
|
module.env = env
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def read_application(path):
|
|
image = IntelHex(str(path))
|
|
start, end = image.segments()[0]
|
|
return bytes(image.tobinarray(start=start, size=end - start))
|
|
|
|
|
|
def stamp(action, path, env):
|
|
with redirect_stdout(io.StringIO()):
|
|
action([], [path], env)
|
|
|
|
|
|
class Nrf52EndfHexTests(unittest.TestCase):
|
|
def test_explicit_legacy_rak_sd_stamps_actual_flash_only_adaptive_capability(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
env = BuildEnvironment(ml.NRF52_APP_BASE_S140_V6)
|
|
env["CPPDEFINES"].extend(["RAK_4631", "OTA_SD_STORE", "OTA_RAK15002_SD_STORE",
|
|
"OTA_RAK_AUTO_STORE", "OTA_QSPI_STORE", "OTA_FLASH_STORE"])
|
|
env.linker = str(ROOT / "boards/nrf52840_s140_v6_sd_ota.ld")
|
|
env["LDSCRIPT_PATH"] = env.linker
|
|
load_post_script(env)
|
|
path = Path(directory) / "legacy.hex"
|
|
image = IntelHex()
|
|
image.frombytes(bytes(range(256)), offset=env.app_base)
|
|
image.write_hex_file(str(path))
|
|
with mock.patch.dict(os.environ, {"MESHCORE_NRF52_LEGACY_FLASH_OTA": "1"}):
|
|
stamp(env.post_actions["$BUILD_DIR/${PROGNAME}.hex"], path, env)
|
|
layout = ml.parse_nrf52_layout(read_application(path))
|
|
self.assertEqual(layout, ml.Nrf52Layout(
|
|
env.app_base, ml.NRF52_APP_END, ml.NRF52_APP_END, ml.NRF52_LAYOUT_FLAG_AUTO_STORE))
|
|
self.assertTrue(layout.auto_store)
|
|
self.assertFalse(layout.hybrid_ram)
|
|
self.assertFalse(layout.external_backed)
|
|
|
|
def test_legacy_adaptive_exception_rejects_wrong_flags_maps_and_flash_geometry(self):
|
|
base_flags = ["RAK_4631", "OTA_SD_STORE", "OTA_RAK15002_SD_STORE",
|
|
"OTA_RAK_AUTO_STORE", "OTA_QSPI_STORE", "OTA_FLASH_STORE"]
|
|
cases = (
|
|
("not_explicit", {}, {}, "RAK adaptive OTA requires"),
|
|
("missing_qspi", {"remove": "OTA_QSPI_STORE"}, {"MESHCORE_NRF52_LEGACY_FLASH_OTA": "1"}, "both stores"),
|
|
("missing_flash", {"remove": "OTA_FLASH_STORE"}, {"MESHCORE_NRF52_LEGACY_FLASH_OTA": "1"}, "both stores"),
|
|
("seeder", {"add": ["OTA_SEEDER_ONLY"]}, {"MESHCORE_NRF52_LEGACY_FLASH_OTA": "1"}, "requires a receiver"),
|
|
("wrong_active", {"active": "boards/nrf52840_s140_v6.ld"}, {"MESHCORE_NRF52_LEGACY_FLASH_OTA": "1"}, "active linker"),
|
|
("wrong_configured", {"configured": "boards/nrf52840_s140_v6.ld"}, {"MESHCORE_NRF52_LEGACY_FLASH_OTA": "1"}, "configured linker"),
|
|
("wrong_softdevice", {"base": ml.NRF52_APP_BASE_S140_V7}, {"MESHCORE_NRF52_LEGACY_FLASH_OTA": "1"}, "exact S140 v6"),
|
|
("wrong_flash_end", {"end": ml.NRF52_EXTRAFS_START}, {"MESHCORE_NRF52_LEGACY_FLASH_OTA": "1"}, "exact S140 v6"),
|
|
("hybrid_only", {"add": ["OTA_HYBRID_RAM_STORE"]}, {"MESHCORE_NRF52_LEGACY_FLASH_OTA": "1"}, "enabled together"),
|
|
("hybrid_pair", {"add": ["OTA_HYBRID_RAM_STORE", "OTA_INTERNAL_BOOTLOADER_UPDATE"],
|
|
"configured": str(ROOT / "boards/nrf52840_s140_v6_mota64.ld"),
|
|
"active": str(ROOT / "boards/nrf52840_s140_v6_mota64.ld")},
|
|
{"MESHCORE_NRF52_LEGACY_FLASH_OTA": "1"}, "without hybrid RAM"),
|
|
("unmarked_sd_role", {"remove": "OTA_RAK15002_SD_STORE"}, {"MESHCORE_NRF52_LEGACY_FLASH_OTA": "1"}, "both SD and QSPI"),
|
|
)
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
path = Path(directory) / "legacy.hex"
|
|
for name, changes, process_env, error in cases:
|
|
with self.subTest(case=name):
|
|
env = BuildEnvironment(changes.get("base", ml.NRF52_APP_BASE_S140_V6))
|
|
env["CPPDEFINES"].extend(base_flags)
|
|
if "remove" in changes:
|
|
env["CPPDEFINES"].remove(changes["remove"])
|
|
env["CPPDEFINES"].extend(changes.get("add", []))
|
|
env.maximum_size = changes.get("end", ml.NRF52_APP_END) - env.app_base
|
|
env.linker = changes.get("configured", str(ROOT / "boards/nrf52840_s140_v6_sd_ota.ld"))
|
|
env["LDSCRIPT_PATH"] = changes.get("active", str(ROOT / "boards/nrf52840_s140_v6_sd_ota.ld"))
|
|
load_post_script(env)
|
|
image = IntelHex()
|
|
image.frombytes(bytes(range(256)), offset=env.app_base)
|
|
image.write_hex_file(str(path))
|
|
before = path.read_bytes()
|
|
with mock.patch.dict(os.environ, process_env, clear=True):
|
|
with self.assertRaisesRegex(RuntimeError, error):
|
|
stamp(env.post_actions["$BUILD_DIR/${PROGNAME}.hex"], path, env)
|
|
self.assertEqual(path.read_bytes(), before)
|
|
|
|
def test_hex_hook_appends_valid_identity_and_preserves_other_segments(self):
|
|
for app_base in (ml.NRF52_APP_BASE_S140_V6, ml.NRF52_APP_BASE_S140_V7):
|
|
with self.subTest(app_base=hex(app_base)), tempfile.TemporaryDirectory() as tmp:
|
|
env = BuildEnvironment(app_base)
|
|
load_post_script(env)
|
|
action = env.post_actions["$BUILD_DIR/${PROGNAME}.hex"]
|
|
|
|
path = Path(tmp) / "firmware.hex"
|
|
firmware = bytes(range(256))
|
|
uicr_base = 0x10001080
|
|
uicr = b"\x12\x34\x56\x78"
|
|
original = IntelHex()
|
|
original.frombytes(firmware, offset=app_base)
|
|
original.frombytes(uicr, offset=uicr_base)
|
|
original.write_hex_file(str(path))
|
|
|
|
with redirect_stdout(io.StringIO()):
|
|
action([], [path], env)
|
|
result = IntelHex(str(path))
|
|
segments = result.segments()
|
|
self.assertEqual(len(segments), 2)
|
|
self.assertEqual(segments[0], (
|
|
app_base, app_base + len(firmware) + ml.NRF52_LAYOUT_LEN + ml.ENDF_LEN))
|
|
image = bytes(result.tobinarray(start=app_base, end=segments[0][1] - 1))
|
|
self.assertEqual(image[:len(firmware)], firmware)
|
|
self.assertEqual(bytes(result.tobinarray(
|
|
start=uicr_base, size=len(uicr))), uicr)
|
|
self.assertTrue(ml.has_endf(image))
|
|
self.assertEqual(ml.parse_endf_ident(image), ml.FwIdent(
|
|
ml.pack_version("1.17.1.9"), 0x12345678, "NRF52_TEST"))
|
|
self.assertEqual(ml.parse_nrf52_layout(image), ml.Nrf52Layout(
|
|
app_base, ml.NRF52_APP_END, ml.NRF52_APP_END))
|
|
|
|
first_pass = path.read_bytes()
|
|
with redirect_stdout(io.StringIO()):
|
|
action([], [path], env)
|
|
self.assertEqual(path.read_bytes(), first_pass)
|
|
|
|
def test_restamps_last_identity_overrides_without_growing_or_rewriting_unchanged_hex(self):
|
|
for app_base in (ml.NRF52_APP_BASE_S140_V6, ml.NRF52_APP_BASE_S140_V7):
|
|
with self.subTest(app_base=app_base), tempfile.TemporaryDirectory() as directory:
|
|
env = BuildEnvironment(app_base)
|
|
load_post_script(env)
|
|
action = env.post_actions["$BUILD_DIR/${PROGNAME}.hex"]
|
|
path = Path(directory) / "firmware.hex"
|
|
body = bytes(range(256))
|
|
original = IntelHex()
|
|
original.frombytes(body, offset=app_base)
|
|
original.write_hex_file(str(path))
|
|
stamp(action, path, env)
|
|
expected_length = len(body) + ml.NRF52_LAYOUT_LEN + ml.ENDF_LEN
|
|
for index in range(3):
|
|
env["CPPDEFINES"].extend([
|
|
("FIRMWARE_VERSION", f"v1.17.1.{10 + index}"),
|
|
("MOTA_TARGET_ID", hex(0x87654321 + index)),
|
|
("MOTA_HW_ID", f"NRF52_CHANGED_{index}"),
|
|
])
|
|
stamp(action, path, env)
|
|
application = read_application(path)
|
|
self.assertEqual(len(application), expected_length)
|
|
self.assertEqual(application[:len(body)], body)
|
|
self.assertEqual(application.count(ml.ENDF_MAGIC), 1)
|
|
self.assertEqual(application.count(ml.NRF52_LAYOUT_MAGIC), 1)
|
|
self.assertEqual(ml.parse_endf_ident(application), ml.FwIdent(
|
|
ml.pack_version(f"1.17.1.{10 + index}"),
|
|
0x87654321 + index, f"NRF52_CHANGED_{index}"))
|
|
unchanged = path.read_bytes()
|
|
os.utime(path, ns=(1_000_000_000, 1_000_000_000))
|
|
stamp(action, path, env)
|
|
self.assertEqual(path.read_bytes(), unchanged)
|
|
self.assertEqual(path.stat().st_mtime_ns, 1_000_000_000)
|
|
|
|
def test_restamps_layout_flags_without_duplicate_record_and_keeps_other_segments(self):
|
|
app_base = ml.NRF52_APP_BASE_S140_V7
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
env = BuildEnvironment(app_base)
|
|
load_post_script(env)
|
|
action = env.post_actions["$BUILD_DIR/${PROGNAME}.hex"]
|
|
path = Path(directory) / "firmware.hex"
|
|
body = bytes(range(256))
|
|
original = IntelHex()
|
|
original.frombytes(body, offset=app_base)
|
|
original.frombytes(b"separate-flash-data", offset=0x40000)
|
|
original.frombytes(b"\x12\x34\x56\x78", offset=0x10001080)
|
|
original.start_addr = {"EIP": app_base + 4}
|
|
original.write_hex_file(str(path))
|
|
base_defines = list(env["CPPDEFINES"])
|
|
for extra, expected_flags in (([], 0), (["OTA_SD_STORE"], ml.NRF52_LAYOUT_FLAG_SD),
|
|
(["OTA_QSPI_STORE"], ml.NRF52_LAYOUT_FLAG_QSPI), ([], 0)):
|
|
env["CPPDEFINES"] = base_defines + extra
|
|
stamp(action, path, env)
|
|
application = read_application(path)
|
|
self.assertEqual(ml.parse_nrf52_layout(application).flags, expected_flags)
|
|
self.assertEqual(len(application), len(body) + ml.NRF52_LAYOUT_LEN + ml.ENDF_LEN)
|
|
self.assertEqual(application.count(ml.NRF52_LAYOUT_MAGIC), 1)
|
|
image = IntelHex(str(path))
|
|
self.assertEqual(bytes(image.tobinarray(start=0x40000, size=19)), b"separate-flash-data")
|
|
self.assertEqual(bytes(image.tobinarray(start=0x10001080, size=4)), b"\x12\x34\x56\x78")
|
|
self.assertEqual(image.start_addr, original.start_addr)
|
|
|
|
def test_existing_trailer_without_layout_is_upgraded_in_place(self):
|
|
app_base = ml.NRF52_APP_BASE_S140_V7
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
env = BuildEnvironment(app_base)
|
|
load_post_script(env)
|
|
path = Path(directory) / "old.hex"
|
|
body = bytes(range(256))
|
|
old, _ = ml.ensure_endf(body, ml.FwIdent(1, 2, "OLD"))
|
|
original = IntelHex()
|
|
original.frombytes(old, offset=app_base)
|
|
original.write_hex_file(str(path))
|
|
stamp(env.post_actions["$BUILD_DIR/${PROGNAME}.hex"], path, env)
|
|
application = read_application(path)
|
|
self.assertEqual(len(application), len(old) + ml.NRF52_LAYOUT_LEN)
|
|
self.assertTrue(ml.has_endf(application))
|
|
self.assertEqual(application.count(ml.ENDF_MAGIC), 1)
|
|
self.assertEqual(application[:len(body)], body)
|
|
|
|
def test_stamped_hex_still_rejects_changed_unsafe_profiles_without_writes(self):
|
|
app_base = ml.NRF52_APP_BASE_S140_V7
|
|
unsafe_profiles = (
|
|
["OTA_SD_STORE", "OTA_QSPI_STORE"],
|
|
["OTA_QSPI_STORE", "QSPIFLASH"],
|
|
["OTA_HYBRID_RAM_STORE"],
|
|
["OTA_INTERNAL_BOOTLOADER_UPDATE"],
|
|
["OTA_QSPI_BOOTLOADER_UPDATE"],
|
|
["OTA_SD_BOOTLOADER_UPDATE"],
|
|
)
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
env = BuildEnvironment(app_base)
|
|
load_post_script(env)
|
|
action = env.post_actions["$BUILD_DIR/${PROGNAME}.hex"]
|
|
path = Path(directory) / "firmware.hex"
|
|
original = IntelHex()
|
|
original.frombytes(bytes(range(256)), offset=app_base)
|
|
original.write_hex_file(str(path))
|
|
stamp(action, path, env)
|
|
before = path.read_bytes()
|
|
base_defines = list(env["CPPDEFINES"])
|
|
for flags in unsafe_profiles:
|
|
with self.subTest(flags=flags):
|
|
env["CPPDEFINES"] = base_defines + flags
|
|
with self.assertRaises((RuntimeError, ValueError)):
|
|
stamp(action, path, env)
|
|
self.assertEqual(path.read_bytes(), before)
|
|
env["CPPDEFINES"] = base_defines
|
|
env.maximum_size = None
|
|
with self.assertRaisesRegex(RuntimeError, "region is unavailable"):
|
|
stamp(action, path, env)
|
|
self.assertEqual(path.read_bytes(), before)
|
|
|
|
def test_optional_rak_sd_preserves_adaptive_hybrid_and_companion_extrafs_layouts(self):
|
|
app_base = ml.NRF52_APP_BASE_S140_V6
|
|
for companion in (False, True):
|
|
with self.subTest(companion=companion), tempfile.TemporaryDirectory() as directory:
|
|
env = BuildEnvironment(app_base)
|
|
env["CPPDEFINES"].extend(["RAK_4631", "OTA_SD_STORE", "OTA_RAK15002_SD_STORE"])
|
|
if companion:
|
|
env.source_filter = "+<examples/companion_radio>"
|
|
env.maximum_size = ml.NRF52_EXTRAFS_START - app_base
|
|
env["CPPDEFINES"].extend(["OTA_SEEDER_ONLY", "EXTRAFS"])
|
|
expected_flags = ml.NRF52_LAYOUT_FLAG_INTERNAL_EXTRAFS
|
|
expected_ceiling = ml.NRF52_EXTRAFS_START
|
|
else:
|
|
env["CPPDEFINES"].extend(["OTA_RAK_AUTO_STORE", "OTA_QSPI_STORE", "OTA_FLASH_STORE",
|
|
"OTA_HYBRID_RAM_STORE", "OTA_INTERNAL_BOOTLOADER_UPDATE"])
|
|
env.linker = str(ROOT / "boards/nrf52840_s140_v6_mota64.ld")
|
|
env["LDSCRIPT_PATH"] = env.linker
|
|
expected_flags = ml.NRF52_LAYOUT_FLAG_AUTO_STORE | ml.NRF52_LAYOUT_FLAG_HYBRID_RAM
|
|
expected_ceiling = ml.NRF52_APP_END
|
|
load_post_script(env)
|
|
path = Path(directory) / "firmware.hex"
|
|
original = IntelHex()
|
|
original.frombytes(bytes(range(256)), offset=app_base)
|
|
original.write_hex_file(str(path))
|
|
action = env.post_actions["$BUILD_DIR/${PROGNAME}.hex"]
|
|
stamp(action, path, env)
|
|
layout = ml.parse_nrf52_layout(read_application(path))
|
|
self.assertEqual(layout.flags, expected_flags)
|
|
self.assertEqual(layout.stage_ceiling, expected_ceiling)
|
|
before = path.read_bytes()
|
|
for missing in ("RAK_4631", "OTA_SD_STORE"):
|
|
env["CPPDEFINES"].remove(missing)
|
|
with self.assertRaisesRegex(RuntimeError, "optional RAK15002"):
|
|
stamp(action, path, env)
|
|
self.assertEqual(path.read_bytes(), before)
|
|
env["CPPDEFINES"].append(missing)
|
|
env["CPPDEFINES"].remove("OTA_SEEDER_ONLY" if companion else "OTA_RAK_AUTO_STORE")
|
|
with self.assertRaisesRegex(RuntimeError, "optional RAK15002"):
|
|
stamp(action, path, env)
|
|
self.assertEqual(path.read_bytes(), before)
|
|
|
|
def test_hex_exact_size_limit_and_smaller_profile_revalidated_after_stamp(self):
|
|
app_base = ml.NRF52_APP_BASE_S140_V7
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
env = BuildEnvironment(app_base)
|
|
load_post_script(env)
|
|
action = env.post_actions["$BUILD_DIR/${PROGNAME}.hex"]
|
|
path = Path(directory) / "firmware.hex"
|
|
body = b"\x55" * (env.maximum_size - ml.NRF52_LAYOUT_LEN - ml.ENDF_LEN)
|
|
original = IntelHex()
|
|
original.frombytes(body, offset=app_base)
|
|
original.write_hex_file(str(path))
|
|
stamp(action, path, env)
|
|
self.assertEqual(len(read_application(path)), env.maximum_size)
|
|
before = path.read_bytes()
|
|
env["CPPDEFINES"].append(("FIRMWARE_VERSION", "v1.17.1.10"))
|
|
stamp(action, path, env)
|
|
self.assertEqual(len(read_application(path)), env.maximum_size)
|
|
before = path.read_bytes()
|
|
env.maximum_size = ml.NRF52_EXTRAFS_START - app_base
|
|
with self.assertRaisesRegex(RuntimeError, "limit is"):
|
|
stamp(action, path, env)
|
|
self.assertEqual(path.read_bytes(), before)
|
|
|
|
def test_source_only_and_filesystem_staging_limits_remain_distinct(self):
|
|
app_base = ml.NRF52_APP_BASE_S140_V7
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
env = BuildEnvironment(app_base)
|
|
env.source_filter = "+<examples/companion_radio>"
|
|
env["CPPDEFINES"].extend(["EXTRAFS", "OTA_SEEDER_ONLY"])
|
|
load_post_script(env)
|
|
action = env.post_actions["$BUILD_DIR/${PROGNAME}.hex"]
|
|
path = Path(directory) / "firmware.hex"
|
|
original = IntelHex()
|
|
original.frombytes(b"\x55" * (ml.NRF52_EXTRAFS_START - app_base), offset=app_base)
|
|
original.write_hex_file(str(path))
|
|
stamp(action, path, env)
|
|
layout = ml.parse_nrf52_layout(read_application(path))
|
|
self.assertEqual(layout.stage_ceiling, ml.NRF52_EXTRAFS_START)
|
|
self.assertEqual(layout.flags, ml.NRF52_LAYOUT_FLAG_INTERNAL_EXTRAFS)
|
|
before = path.read_bytes()
|
|
env["CPPDEFINES"].remove("OTA_SEEDER_ONLY")
|
|
with self.assertRaisesRegex(RuntimeError, "nRF52 OTA application limit"):
|
|
stamp(action, path, env)
|
|
self.assertEqual(path.read_bytes(), before)
|
|
|
|
def test_trailer_growth_cannot_overwrite_another_hex_segment(self):
|
|
app_base = ml.NRF52_APP_BASE_S140_V7
|
|
for following in (257, 256 + ml.NRF52_LAYOUT_LEN + ml.ENDF_LEN):
|
|
with self.subTest(following=following), tempfile.TemporaryDirectory() as directory:
|
|
env = BuildEnvironment(app_base)
|
|
load_post_script(env)
|
|
path = Path(directory) / "firmware.hex"
|
|
original = IntelHex()
|
|
original.frombytes(bytes(range(256)), offset=app_base)
|
|
original.frombytes(b"untouched", offset=app_base + following)
|
|
original.write_hex_file(str(path))
|
|
before = path.read_bytes()
|
|
with self.assertRaisesRegex(RuntimeError, "separate HEX segment"):
|
|
stamp(env.post_actions["$BUILD_DIR/${PROGNAME}.hex"], path, env)
|
|
self.assertEqual(path.read_bytes(), before)
|
|
|
|
def test_uicr_added_after_first_stamp_survives_an_identity_restamp(self):
|
|
app_base = ml.NRF52_APP_BASE_S140_V7
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
env = BuildEnvironment(app_base)
|
|
load_post_script(env)
|
|
action = env.post_actions["$BUILD_DIR/${PROGNAME}.hex"]
|
|
path = Path(directory) / "firmware.hex"
|
|
original = IntelHex()
|
|
original.frombytes(bytes(range(256)), offset=app_base)
|
|
original.write_hex_file(str(path))
|
|
stamp(action, path, env)
|
|
previous = read_application(path)
|
|
augmented = IntelHex(str(path))
|
|
augmented.frombytes(b"new-uicr", offset=0x10001080)
|
|
augmented.write_hex_file(str(path))
|
|
env["CPPDEFINES"].append(("FIRMWARE_VERSION", "v1.17.1.10"))
|
|
stamp(action, path, env)
|
|
application = read_application(path)
|
|
self.assertEqual(len(application), len(previous))
|
|
self.assertEqual(application[:-ml.ENDF_LEN], previous[:-ml.ENDF_LEN])
|
|
self.assertEqual(ml.parse_endf_ident(application).fw_version, ml.pack_version("1.17.1.10"))
|
|
self.assertEqual(bytes(IntelHex(str(path)).tobinarray(start=0x10001080, size=8)), b"new-uicr")
|
|
|
|
def test_new_hex_over_linked_application_bound_is_rejected_even_for_source_only(self):
|
|
for source_only in (False, True):
|
|
with self.subTest(source_only=source_only), tempfile.TemporaryDirectory() as directory:
|
|
env = BuildEnvironment(ml.NRF52_APP_BASE_S140_V7)
|
|
if source_only:
|
|
env["CPPDEFINES"].append("OTA_SEEDER_ONLY")
|
|
load_post_script(env)
|
|
path = Path(directory) / "firmware.hex"
|
|
# Just one byte too large after adding layout and EndF.
|
|
body = b"\x55" * (env.maximum_size - ml.NRF52_LAYOUT_LEN - ml.ENDF_LEN + 1)
|
|
original = IntelHex()
|
|
original.frombytes(body, offset=env.app_base)
|
|
original.write_hex_file(str(path))
|
|
before = path.read_bytes()
|
|
with self.assertRaisesRegex(RuntimeError, "limit is"):
|
|
stamp(env.post_actions["$BUILD_DIR/${PROGNAME}.hex"], path, env)
|
|
self.assertEqual(path.read_bytes(), before)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|