-
Notifications
You must be signed in to change notification settings - Fork 902
MCUboot Refactoring Plan #2670
Description
MCUboot Refactoring Plan
Executive Summary
The MCUboot boot/bootutil/ code has grown organically through incremental feature additions using #ifdef conditionals. The result is a codebase where status management, swap algorithms, and upgrade modes are deeply interleaved in ways that make maintenance difficult and future development risky. This plan proposes a phased approach: first strengthen the test harness, then isolate status management, then clean up swap algorithm pluggability, and finally address the remaining ifdef tangle in loader.c.
1. Current State Analysis
1.1 Code Structure Overview
The core bootloader lives in boot/bootutil/src/ (~25 source files). The heaviest files by complexity:
| File | Lines | Role | Complexity |
|---|---|---|---|
loader.c |
~2530 | Main boot flow | Very high |
swap_scratch.c |
~1030 | Scratch swap | High |
swap_move.c |
~580 | Move swap | High |
swap_offset.c |
~800 | Offset swap | High |
bootutil_misc.c |
~600 | Status helpers | High |
bootutil_public.c |
~500 | Public API/trailer I/O | Medium |
image_validate.c |
~800 | Signature verification | Medium |
1.2 The Ifdef Problem
loader.c alone contains conditional blocks for:
- Upgrade modes (mutually exclusive):
MCUBOOT_OVERWRITE_ONLY,MCUBOOT_SWAP_USING_SCRATCH,MCUBOOT_SWAP_USING_MOVE,MCUBOOT_SWAP_USING_OFFSET,MCUBOOT_DIRECT_XIP,MCUBOOT_RAM_LOAD,MCUBOOT_FIRMWARE_LOADER - Features (combinable):
MCUBOOT_ENC_IMAGES,MCUBOOT_BOOTSTRAP,MCUBOOT_VALIDATE_PRIMARY_SLOT,MCUBOOT_DOWNGRADE_PREVENTION,MCUBOOT_HW_ROLLBACK_PROT,BOOT_IMAGE_NUMBER > 1
There are ~13 algorithm-specific #ifdef blocks in loader.c that should be pushed down into the swap algorithm files. The worst offenders:
boot_copy_region()has two different function signatures depending onMCUBOOT_SWAP_USING_OFFSET && MCUBOOT_ENC_IMAGES(lines 785-796), hidden behind a macro wrapper (lines 85-91).boot_validate_slot()(lines 541-700) has nested ifdefs for scratch/move/offset trailer cleanup, offset-specific header checks, overwrite+downgrade logic, and three address verification variants.context_boot_go()has two completely separate implementations forDIRECT_XIP/RAM_LOADvs swap modes, with significant code duplication.
1.3 Status Management
Status management -- how the bootloader tracks swap progress in the flash trailer -- is scattered across 6+ files:
- Trailer layout/offsets:
bootutil_misc.h(inline macros),bootutil_area.c(size calculations) - Read/write primitives:
bootutil_public.c(boot_write_magic,boot_write_trailer,boot_read_swap_state, etc.) - Swap-size/enc-key writes:
bootutil_misc.c - Progress tracking:
loader.c(boot_write_status) - Status initialization:
swap_misc.c(swap_status_init) - Status byte parsing: each swap algorithm file has its own
swap_read_status_bytes()with different interpretations
The trailer format stores (from end of slot): magic (16B), image_ok (1B padded), copy_done (1B padded), swap_info (1B padded), swap_size (4B padded), optionally unprotected TLV sizes (offset only), optionally encryption keys, then per-sector status bytes.
Key problem for new flash devices: boot_write_trailer() hard-codes the assumption that writes must be padded to flash_area_align() with the erased value (0xff). The offset calculation macros in bootutil_misc.h assume a fixed trailer layout. Devices with different write granularity, non-0xff erase values, or write-once semantics would require changes across all these files.
1.4 Swap Algorithm Comparison
The three swap algorithms share a common interface (swap_priv.h) but diverge significantly:
What they share (in swap_misc.c, ~150 lines):
swap_erase_trailer_sectors()swap_scramble_trailer_sectors()swap_status_init()swap_read_status()(calls per-algorithmswap_read_status_bytes())swap_set_copy_done(),swap_set_image_ok()
What each implements differently:
swap_read_status_bytes()-- different status byte layoutsboot_status_internal_off()-- different offset calculationsboot_slots_compatible()-- different slot requirementsswap_status_source()-- different status detection logicswap_run()-- completely different algorithmsapp_max_size()-- different size calculations
Flash layout differences:
- scratch: 3 partitions (primary, secondary, scratch)
- move: 2 partitions (primary has 1 extra sector)
- offset: 2 partitions (secondary has 1 extra sector)
What swap_offset lacks vs swap_scratch:
- No trailer-in-swap-region handling (
bs->use_scratch). swap_scratch preserves the trailer in scratch when the trailer overlaps the sector being swapped; swap_offset does not handle this case. - No multi-image scratch status awareness (swap_scratch checks
image_numin scratch status for multi-image builds). - Simpler
app_max_size()that may be incorrect with certain sector configurations (swap_scratch has 73 lines of complex boundary calculation vs swap_offset's 20 lines).
1.5 Simulator Test Coverage
The Rust simulator (sim/) exercises the C code via FFI with 28 test functions across 10 device configurations, 4 alignment values, and 2 erased-value settings.
Well-tested areas:
- Basic upgrade/revert cycle
- Reset resilience (exhaustive and random interrupt injection)
- Signature verification (RSA, ECDSA, Ed25519)
- Encryption (RSA, KW, EC256, X25519 with AES-128/256)
- Multi-image dependency checking (11 combinations)
- Flash operation failure injection
Gaps identified:
| Area | Gap |
|---|---|
| Swap algorithms | No dedicated test comparing all three under identical conditions. swap_scratch only tested implicitly as default. |
| Error paths | No malformed TLV tests, no decryption error tests, no read-failure injection (only write failures). |
| Multi-image | Only 2-image configs tested; no 3+ image scenarios. |
| Direct-XIP | Only 2 CI combinations, only with sig-rsa. |
| RAM load | No SRAM corruption simulation. |
| Downgrade prevention | Only 1 CI combination (sig-rsa + overwrite-only). |
| HW rollback | Only 1 CI combination, 2 test scenarios. |
| Status corruption | Limited to write failures; no read corruption, partial writes, or status format mismatch. |
| Sector mismatches | Tested via device configs but no dedicated edge-case scenarios for trailer-in-last-sector. |
| PSA crypto | Only 2 CI matrix entries vs many for other backends. |
| boot_record.c | No dedicated tests. |
CI matrix: 31 matrix entries producing ~51 feature combinations. Reasonable breadth but sparse on newer features (PSA, direct-xip, ram-load, hw-rollback).
1.6 Build System Fragility
Each port has its own logic for selecting which bootutil source files to compile. There is no single source of truth.
Simulator (sim/mcuboot-sys/build.rs):
- Includes ALL swap algorithm files unconditionally (scratch, move, offset all compiled; only one is active via
#defines). This means dead code from inactive algorithms is compiled into every test build. - Signature algorithm files (
image_rsa.c,image_ecdsa.c,image_ed25519.c) are conditionally included based on features -- correct but fragile. - Crypto backend selection is a ~350-line cascade of
if/else ifblocks (lines 102-456) mapping feature combinations to specific MbedTLS/TinyCrypt/FIAT source files. Adding a new crypto combination requires understanding the entire cascade. - MbedTLS config header selection (lines 458-474) is another ad-hoc mapping:
config-rsa.h,config-rsa-kw.h,config-ec.h,config-asn1.h,config-kw.h,config-ed25519.h,config-ec-psa.h. A misconfiguration silently produces wrong crypto.
Zephyr (boot/zephyr/CMakeLists.txt):
- Includes ALL signature and encryption algorithm files unconditionally (
image_rsa.c,image_ecdsa.c,image_ed25519.c,encrypted.calways compiled). Relies on#ifdefguards inside the C files to produce empty compilation units for inactive algorithms. - Only swap algorithm files are conditionally selected (one of scratch/move/offset based on Kconfig).
encrypted_psa.cconditionally added for PSA combos.ecc_dh.cfrom TinyCrypt is included twice (lines 273, 282) -- harmless but indicates ad-hoc maintenance.
Shared boot/bootutil/CMakeLists.txt:
- Used by Mbed and as a reference. Includes ALL source files unconditionally (all swap algorithms, all crypto files,
ram_load.c, etc.).
Other ports:
- Cypress uses
$(wildcard ../bootutil/src/*.c)-- picks up every .c file, no selectivity at all. - Espressif explicitly lists bootutil sources (most selective, but must be manually kept in sync).
- Mynewt uses its package system with conditional file ignores.
- NuttX delegates entirely to the NuttX kernel build.
Key pain point: Adding a new feature (e.g., a new swap algorithm or crypto backend) requires updating build logic in 4-6 different places with different build systems. There is no way to verify consistency across ports except by trying to build each one. The simulator's build.rs and Zephyr's CMakeLists.txt are the two most critical and they diverge in approach (conditional include vs. unconditional include with ifdef guards).
A realistic improvement would be to document a canonical file-to-feature mapping table (perhaps in a machine-readable format) that each build system can reference, even if they cannot share actual build logic. This at least provides a single place to update when features change.
1.7 OS Port Crypto Dependencies
The crypto backend situation across ports creates a constraint on deprecation plans:
| Port | TinyCrypt | MbedTLS | PSA | Notes |
|---|---|---|---|---|
| Zephyr | Deprecated | Via PSA | Yes | Fully migrated to PSA API |
| Espressif | Required | RSA only | No | EC256 + Ed25519 use TinyCrypt exclusively |
| Cypress | No | Yes + HW accel | No | Self-contained with MbedTLS |
| Mbed | Blocked | Yes | No | Explicitly rejects TinyCrypt (build conflicts) |
| Mynewt | Unknown | Unknown | Unknown | Delegated to Mynewt OS |
| NuttX | Unknown | Unknown | Unknown | Delegated to NuttX kernel |
The TinyCrypt problem: TinyCrypt has been abandoned upstream and receives no security updates. However:
- Espressif depends on it exclusively for EC256 and Ed25519 signatures. Their
ec256.cmakepulls in TinyCrypt's ECC implementation; theired25519.cmakepulls in TinyCrypt's SHA-256/SHA-512. Even the "no signature" fallback uses TinyCrypt for SHA-256. - The simulator uses TinyCrypt for the default
sig-ecdsafeature, forenc-ec256, and forenc-x25519. - Ed25519 verification uses TinyCrypt's SHA-512 plus the FIAT
curve25519.c-- there is no MbedTLS or PSA alternative path because MbedTLS (and PSA Crypto under it) does not support EdDSA.
Replacement options to investigate:
- For ECDSA/EC256: MbedTLS and PSA both support P-256 ECDSA. Espressif would need to add an MbedTLS or PSA code path (Espressif's IDF includes MbedTLS already, so this is likely feasible).
- For Ed25519: This is harder. Options include:
- Wait for MbedTLS to add EdDSA support (no timeline).
- Integrate a standalone Ed25519 library (e.g.,
ed25519-donna,HACL*, or the FIAT-generated code that MCUboot already uses for curve25519). The current code already uses FIAT for the curve arithmetic; only the hash (SHA-512) comes from TinyCrypt. - Replace only the TinyCrypt SHA-512 dependency with MbedTLS SHA-512 (which exists and is well-supported). This is the smallest change and would eliminate the TinyCrypt dependency for Ed25519.
- For AES/HMAC in encryption: The
enc-ec256andenc-x25519encryption paths use TinyCrypt's AES and HMAC. MbedTLS equivalents exist (and theenc-ec256- mbedtlsandenc-aes256-*features already use them).
Practical path forward: Rather than a single big switch, a port-by-port migration makes sense:
- Replace TinyCrypt SHA-512 with MbedTLS SHA-512 for Ed25519 (small, safe change that helps all ports).
- Add
sig-ecdsa-mbedtlssupport to Espressif (they already have MbedTLS in their IDF). - Add
enc-ec256-mbedtls/enc-x25519-mbedtlspaths for encryption (partially done -- theenc-aes256-*features use MbedTLS already). - Once all ports have a non-TinyCrypt path, mark TinyCrypt as deprecated with a removal timeline.
2. Refactoring Plan
Phase 0: Improve Test Coverage (Do First)
Goal: Establish a safety net before refactoring. Every change in subsequent phases should be validated by existing or new tests.
0.1 Add explicit swap-algorithm comparison tests
Add test scenarios that run the same upgrade/revert/interrupt sequence under each swap algorithm (scratch, move, offset) so regressions in any algorithm are caught. Currently swap_scratch is only implicitly tested as the default.
Files: sim/tests/core.rs, sim/src/image.rs
0.2 Add malformed-image and error-path tests
- Truncated TLV trailers
- Corrupted signature bytes
- Wrong encryption keys / corrupted ciphertext
- Status area read failures (extend
simflashto support read-failure injection, not just write failures)
Files: sim/simflash/src/lib.rs, sim/src/image.rs, sim/tests/core.rs
0.3 Expand direct-xip and ram-load coverage
Add CI combinations testing direct-xip and ram-load with signature algorithms beyond sig-rsa, and with encryption.
Files: .github/workflows/sim.yaml
0.4 Add trailer-boundary stress tests
Create device configurations where the image fills the slot such that the trailer spans the last sector boundary. This exercises the trailer-in-swap-region handling that differs between algorithms.
Files: sim/src/image.rs (new device configs)
0.5 Clean up simulator code quality
- Remove the 6+
.clone(); // TODOinstances insim/src/image.rs - Standardize error handling (currently mix of
assert!andfalsereturns) - Add documentation to complex swap scenario methods
Phase 1: Isolate Status Management
Goal: Create a clean status abstraction layer so that new flash devices can plug in different trailer implementations without touching the swap algorithms or loader.
1.1 Create boot_status_ops abstraction
Define a struct of function pointers for all trailer I/O:
struct boot_status_ops {
/* Read/write trailer fields */
int (*write_magic)(const struct flash_area *fap);
int (*write_swap_info)(const struct flash_area *fap,
const struct boot_swap_state *st);
int (*write_image_ok)(const struct flash_area *fap);
int (*write_copy_done)(const struct flash_area *fap);
int (*write_swap_size)(const struct flash_area *fap,
uint32_t size);
int (*write_enc_key)(const struct flash_area *fap,
uint8_t slot,
const struct boot_status *bs);
int (*read_swap_state)(const struct flash_area *fap,
struct boot_swap_state *st);
/* Progress tracking */
int (*write_status)(const struct boot_loader_state *st,
struct boot_status *bs);
int (*read_status_bytes)(const struct flash_area *fap,
struct boot_status *bs);
/* Layout queries */
uint32_t (*trailer_sz)(const struct flash_area *fap);
uint32_t (*status_off)(const struct flash_area *fap);
};The default implementation wraps the existing functions from bootutil_public.c and bootutil_misc.c. A new flash device can provide an alternate implementation (e.g., storing status in a separate metadata partition, or using a log-structured format for write-once flash).
Files to modify: bootutil_public.c, bootutil_misc.c, bootutil_area.c, new boot_status_ops.h
1.2 Consolidate trailer offset calculations
The offset macros in bootutil_misc.h (boot_magic_off, boot_image_ok_off, boot_swap_info_off, etc.) should become functions on the ops struct or at minimum be consolidated into a single file with clear documentation of the layout.
1.3 Consolidate status-related code
Move all status read/write functions currently in bootutil_public.c and bootutil_misc.c into a dedicated boot_status.c file. This makes it clear where all trailer I/O lives.
Current locations:
bootutil_public.c:boot_write_magic,boot_write_trailer,boot_write_trailer_flag,boot_write_swap_info,boot_write_image_ok,boot_write_copy_done,boot_read_swap_state,boot_read_image_okbootutil_misc.c:boot_write_swap_size,boot_write_enc_key,boot_find_statusloader.c:boot_write_status,boot_status_reset
All of these should live in one file behind the ops struct.
Phase 2: Clean Up Swap Algorithm Pluggability
Goal: Push algorithm-specific code out of loader.c and into the swap algorithm files, behind a clean interface.
2.1 Define swap_algorithm_ops struct
Extend the existing swap_priv.h interface into a formal ops struct:
struct swap_algorithm_ops {
/* Already algorithm-specific */
int (*slots_compatible)(struct boot_loader_state *st);
int (*status_source)(struct boot_loader_state *st);
int (*read_status_bytes)(const struct flash_area *fap,
struct boot_status *bs);
uint32_t (*status_internal_off)(
const struct boot_status *bs,
uint32_t write_sz);
void (*run)(struct boot_loader_state *st,
struct boot_status *bs,
uint32_t copy_sz);
int (*app_max_size)(struct boot_loader_state *st);
/* Currently ifdefs in loader.c */
int (*prepare_for_swap)(struct boot_loader_state *st,
struct boot_status *bs);
int (*validate_slot_post_swap)(
struct boot_loader_state *st,
uint32_t slot);
int (*copy_image)(struct boot_loader_state *st,
uint32_t sz);
};2.2 Move algorithm ifdefs from loader.c
The ~13 algorithm-specific #ifdef blocks in loader.c should be replaced with calls through the ops struct:
| loader.c lines | Current ifdef | Replacement |
|---|---|---|
| 312-330 | SWAP_USING_OFFSET image header read |
ops->prepare_for_swap() |
| 382-410 | SWAP_USING_OFFSET secondary offset cache |
ops->prepare_for_swap() |
| 551-592 | Trailer scrambling with MOVE special case | ops->validate_slot_post_swap() |
| 595-612 | SWAP_USING_OFFSET header validation |
ops->validate_slot_post_swap() |
| 994-1012 | SWAP_USING_MOVE erase sector count |
ops->copy_image() |
| 1046-1050 | SWAP_USING_OFFSET copy with offset |
ops->copy_image() |
Note: This can remain compile-time selection (the ops struct resolved at build time via #if) rather than runtime dispatch. The goal is code organization, not runtime polymorphism. Using designated initializers:
#if defined(MCUBOOT_SWAP_USING_OFFSET)
static const struct swap_algorithm_ops swap_ops =
SWAP_OFFSET_OPS;
#elif defined(MCUBOOT_SWAP_USING_MOVE)
static const struct swap_algorithm_ops swap_ops =
SWAP_MOVE_OPS;
#else
static const struct swap_algorithm_ops swap_ops =
SWAP_SCRATCH_OPS;
#endifThis keeps the single-#if at the top of the file and removes the dozen scattered ones.
2.3 Unify boot_copy_region signature
The two-signature problem (boot_copy_region with 6 vs 7 parameters depending on SWAP_USING_OFFSET && MCUBOOT_ENC_IMAGES) should be resolved by always passing sector_off (defaulting to 0 for non-offset algorithms). This eliminates the BOOT_COPY_REGION macro wrapper.
2.4 Address swap_offset gaps
Before swap_offset can replace swap_scratch for all use cases, it needs:
-
Trailer-in-swap-region handling: Port the
bs->use_scratchlogic from swap_scratch. When the sector being swapped overlaps with the trailer area, swap_offset needs to preserve the trailer. This may require a different approach than scratch (perhaps writing to the secondary's extra sector). -
Multi-image status awareness: Add
image_numchecking inswap_status_source()for multi-image builds. -
Robust app_max_size: Port the sector-boundary calculation logic from swap_scratch's
app_max_size_adjust_to_trailer().
Phase 3: Reduce loader.c Complexity
Goal: Break loader.c into manageable pieces.
3.1 Extract DIRECT_XIP / RAM_LOAD
context_boot_go() currently has two completely separate implementations gated by #if defined(MCUBOOT_DIRECT_XIP) || defined(MCUBOOT_RAM_LOAD). These should be extracted into dedicated files:
boot_direct_xip.c--context_boot_go_xip()boot_ram_load.c--context_boot_go_ram_load()(most ofram_load.calready exists, but the main flow is still inloader.c)
loader.c would have a thin dispatcher:
fih_ret context_boot_go(struct boot_loader_state *state,
struct boot_rsp *rsp)
{
#if defined(MCUBOOT_DIRECT_XIP)
return context_boot_go_xip(state, rsp);
#elif defined(MCUBOOT_RAM_LOAD)
return context_boot_go_ram_load(state, rsp);
#else
return context_boot_go_swap(state, rsp);
#endif
}3.2 Extract image validation
boot_validate_slot() (lines 541-700) mixes validation logic with algorithm-specific trailer handling. Split into:
- Core validation (header check, signature verify, address check) stays in
loader.cor moves toimage_validate.c - Post-swap trailer fixup moves to swap algorithm ops
3.3 Extract boot_prepare_image_for_update
This 160-line function (lines 1442-1601) with 8+ nested conditionals should be decomposed:
- Status reading/resumption logic
- Bootstrap decision logic
- Header validation logic
Each becomes a focused helper function.
3.4 Simplify context_boot_go_swap
The remaining swap-mode context_boot_go() has three sequential IMAGES_ITER loops with implicit state dependencies between them. Document these dependencies explicitly and consider whether the loops can be combined or the inter-loop contracts made explicit via assertions.
Phase 4: Build System and Crypto Backend Cleanup
Goal: Reduce the friction of adding features across ports, and chart a path away from TinyCrypt.
4.1 Create canonical file-to-feature mapping
Create a machine-readable table (e.g., YAML or a simple text file) documenting which source files are needed for each feature combination. Example:
# boot/bootutil/source_map.yaml
swap_algorithms:
MCUBOOT_SWAP_USING_SCRATCH:
- swap_scratch.c
- swap_misc.c
MCUBOOT_SWAP_USING_MOVE:
- swap_move.c
- swap_misc.c
MCUBOOT_SWAP_USING_OFFSET:
- swap_offset.c
- swap_misc.c
signatures:
MCUBOOT_SIGN_RSA:
- image_rsa.c
MCUBOOT_SIGN_EC256:
- image_ecdsa.c
MCUBOOT_SIGN_ED25519:
- image_ed25519.c
# ... etcThis doesn't replace each port's build system, but it provides a single reference when adding features, and could eventually be consumed by build scripts that support it (e.g., build.rs could parse it).
4.2 Clean up build.rs crypto cascade
The ~350-line if/else if cascade in build.rs (lines 102-456) should be restructured into a more table-driven approach:
- Separate signature-backend file selection from encryption-backend file selection.
- Extract the MbedTLS config header mapping (lines 458-474) into a clear lookup table.
- Fix: all three swap files are compiled unconditionally (lines 493-495); only one should be included, matching Zephyr's approach.
4.3 Eliminate TinyCrypt for Ed25519
The Ed25519 code path uses TinyCrypt only for SHA-512. Replace with MbedTLS SHA-512, which is well-supported. The curve arithmetic already uses FIAT's curve25519.c, so only the hash needs changing. This is a small, targeted change that benefits all ports.
Files: image_ed25519.c, build.rs, boot/espressif/include/crypto_config/ed25519.cmake
4.4 Add MbedTLS ECDSA path for Espressif
Espressif currently uses TinyCrypt for EC256. Since Espressif's IDF already includes MbedTLS, adding a sig-ecdsa-mbedtls equivalent for their build is feasible. This would let Espressif drop TinyCrypt for ECDSA signatures.
Files: boot/espressif/include/crypto_config/ec256.cmake, boot/espressif/CMakeLists.txt
4.5 Consolidate encryption backends
The enc-ec256 (TinyCrypt) vs enc-ec256-mbedtls and enc-x25519 (TinyCrypt) vs enc-aes256-x25519 (MbedTLS) splits already exist. Once Espressif and other ports can use MbedTLS, the TinyCrypt encryption paths can be deprecated.
4.6 Mark TinyCrypt deprecated with removal timeline
Once non-TinyCrypt paths exist for all signature and encryption combinations across all active ports, add build-time deprecation warnings (e.g., #warning) to the TinyCrypt code paths with a version-targeted removal date. Document which ports still need migration.
4.7 Document encrypted.c vs encrypted_psa.c
Clarify which ports use the legacy encrypted.c (direct MbedTLS) vs the preferred encrypted_psa.c (PSA Crypto API). Document migration steps for ports still using the legacy path.
3. Suggested Ordering and Dependencies
Phase 0 (Tests)
|
+---> 0.1 Swap comparison tests
+---> 0.2 Error-path tests
+---> 0.3 CI coverage expansion
+---> 0.4 Trailer-boundary tests
+---> 0.5 Sim code cleanup
|
v
Phase 1 (Status Isolation)
|
+---> 1.1 boot_status_ops struct
+---> 1.2 Consolidate offset macros
+---> 1.3 Consolidate status code into boot_status.c
|
v
Phase 2 (Swap Pluggability)
|
+---> 2.1 swap_algorithm_ops struct
+---> 2.2 Move ifdefs from loader.c
+---> 2.3 Unify boot_copy_region signature
+---> 2.4 Fill swap_offset gaps
|
v
Phase 3 (loader.c Cleanup)
|
+---> 3.1 Extract DIRECT_XIP / RAM_LOAD
+---> 3.2 Extract image validation
+---> 3.3 Decompose boot_prepare_image_for_update
+---> 3.4 Simplify context_boot_go_swap
|
v
Phase 4 (Build System & Crypto)
|
+---> 4.1 Canonical file-to-feature mapping
| (can start any time after Phase 0)
+---> 4.2 Clean up build.rs crypto cascade
+---> 4.3 Eliminate TinyCrypt for Ed25519
| (small, independent, high value)
+---> 4.4 MbedTLS ECDSA for Espressif
+---> 4.5 Consolidate encryption backends
+---> 4.6 Mark TinyCrypt deprecated
| (after 4.3-4.5 provide alternatives)
+---> 4.7 Document encrypted.c vs encrypted_psa.c
Phase 0 should come first because every subsequent phase needs test coverage to validate that refactoring preserves behavior.
Phase 1 (status isolation) is the highest-value refactoring because it directly enables the stated goal of supporting different status storage for new flash devices.
Phase 2 depends on Phase 1 because the swap algorithm ops will reference the status ops.
Phase 3 can partially overlap with Phase 2 (especially 3.1, extracting DIRECT_XIP/RAM_LOAD, which is independent of swap algorithm work).
Phase 4 is partially independent. Items 4.1 (file mapping) and 4.3 (Ed25519 TinyCrypt removal) can start any time and don't depend on the other phases. Items 4.4-4.6 (port-by-port TinyCrypt migration) are a longer effort that requires coordination with port maintainers. Item 4.2 (build.rs cleanup) benefits from Phase 2's swap pluggability work.
4. Risk Assessment
Low risk:
- Phase 0 (adding tests doesn't change production code)
- Phase 3.1 (extracting DIRECT_XIP/RAM_LOAD is straightforward code motion)
- Phase 4.1 (documentation only)
- Phase 4.7 (documentation only)
Medium risk:
- Phase 1 (status abstraction touches many files but the operations themselves don't change, just their organization)
- Phase 3.2-3.4 (decomposing loader.c functions requires careful preservation of control flow)
- Phase 4.2 (build.rs restructuring could break feature combinations; CI catches this)
- Phase 4.3 (replacing TinyCrypt SHA-512 with MbedTLS SHA-512 is a targeted crypto change; needs careful testing across platforms)
Higher risk:
- Phase 2.2 (moving ifdefs from loader.c to swap ops changes call sites)
- Phase 2.3 (unifying boot_copy_region signature affects all swap algorithms + encryption)
- Phase 2.4 (filling swap_offset gaps adds new functionality that must be tested under reset scenarios)
- Phase 4.4-4.6 (crypto backend migration for Espressif and other ports requires coordination with port maintainers and testing on actual hardware; risk of breaking ports that MCUboot CI doesn't fully cover)
Each phase should be done as a series of small, independently testable commits. The simulator's reset-resilience testing (run_perm_with_fails exhaustive mode) is the critical validation tool -- it should pass after every commit.
5. Files Inventory
For reference, here is the complete set of files in boot/bootutil/src/ and their current roles:
Core flow:
loader.c-- entry point, main boot logic, validationbootutil_priv.h-- central internal header (boot_loader_state,boot_status)bootutil_loader.c-- OS-agnostic loader helpers
Swap algorithms:
swap_scratch.c-- scratch-based swapswap_move.c-- move-based swapswap_offset.c-- offset-based swap (preferred)swap_misc.c-- shared swap utilitiesswap_priv.h-- swap interface declarations
Status/trailer:
bootutil_public.c-- trailer read/write (magic, flags)bootutil_misc.c-- swap size, enc keys, status helpersbootutil_misc.h-- trailer offset macrosbootutil_area.c-- trailer size calculations
Image validation:
image_validate.c-- hash/signature verificationimage_rsa.c-- RSA signatureimage_ecdsa.c-- ECDSA signatureimage_ed25519.c-- Ed25519 signatureed25519_psa.c-- Ed25519 PSA backendtlv.c-- TLV parsing
Encryption:
encrypted_psa.c-- PSA Crypto (preferred)encrypted.c-- legacy MbedTLS (deprecated)
Other:
boot_record.c-- shared data / boot recordram_load.c-- RAM loading supportbootutil_find_key.c-- signature key lookupcaps.c-- capability detectionfault_injection_hardening.c-- FIH protectionfault_injection_hardening_delay_rng_mbedtls.c-- FIH RNG
6. Build Systems Inventory
Each port has independent build logic for bootutil:
| Port | Build System | File Selection | CI Tested |
|---|---|---|---|
| Simulator | Cargo/build.rs | Conditional (ad-hoc cascade) | Yes |
| Zephyr | CMake/Kconfig | Mixed (always-include + conditional swap) | Yes |
| Espressif | CMake | Explicit file list | Yes |
| Cypress | GNU Make | Wildcard glob (all .c) | No |
| Mbed | CMake | Shared CMakeLists (all files) | No |
| Mynewt | newtmgr/pkg.yml | Conditional ignores | Yes |
| NuttX | NuttX kernel | Delegated (not in MCUboot repo) | No |
7. Crypto Backend Status by Port
| Port | ECDSA P-256 | Ed25519 | AES Enc | SHA | Preferred Direction |
|---|---|---|---|---|---|
| Zephyr | PSA | PSA (no EdDSA yet) | PSA | PSA | Already on PSA |
| Espressif | TinyCrypt | TinyCrypt+FIAT | TinyCrypt | TinyCrypt | Needs MbedTLS paths |
| Cypress | MbedTLS+HW | N/A | MbedTLS | MbedTLS+HW | Already on MbedTLS |
| Mbed | MbedTLS | MbedTLS | MbedTLS | MbedTLS | Already on MbedTLS |
| Mynewt | OS-managed | OS-managed | OS-managed | OS-managed | Depends on Mynewt |
| NuttX | OS-managed | OS-managed | OS-managed | OS-managed | Depends on NuttX |
| Simulator | TinyCrypt or MbedTLS or PSA | TinyCrypt+FIAT | TinyCrypt or MbedTLS | Various | Needs all for testing |
TinyCrypt removal blockers:
- Espressif EC256/Ed25519 (no alternative path exists)
- Simulator default
sig-ecdsafeature (uses TinyCrypt) - Ed25519 SHA-512 (all ports; MbedTLS SHA-512 is the fix)
enc-ec256andenc-x25519encryption (TinyCrypt AES/HMAC; MbedTLS alternatives partially exist)