Skip to content

MCUboot Refactoring Plan #2670

@d3zd3z

Description

@d3zd3z

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 on MCUBOOT_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 for DIRECT_XIP/RAM_LOAD vs 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-algorithm swap_read_status_bytes())
  • swap_set_copy_done(), swap_set_image_ok()

What each implements differently:

  • swap_read_status_bytes() -- different status byte layouts
  • boot_status_internal_off() -- different offset calculations
  • boot_slots_compatible() -- different slot requirements
  • swap_status_source() -- different status detection logic
  • swap_run() -- completely different algorithms
  • app_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:

  1. 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.
  2. No multi-image scratch status awareness (swap_scratch checks image_num in scratch status for multi-image builds).
  3. 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 if blocks (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.c always compiled). Relies on #ifdef guards 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.c conditionally added for PSA combos.
  • ecc_dh.c from 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:

  1. Espressif depends on it exclusively for EC256 and Ed25519 signatures. Their ec256.cmake pulls in TinyCrypt's ECC implementation; their ed25519.cmake pulls in TinyCrypt's SHA-256/SHA-512. Even the "no signature" fallback uses TinyCrypt for SHA-256.
  2. The simulator uses TinyCrypt for the default sig-ecdsa feature, for enc-ec256, and for enc-x25519.
  3. 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-ec256 and enc-x25519 encryption paths use TinyCrypt's AES and HMAC. MbedTLS equivalents exist (and the enc-ec256- mbedtls and enc-aes256-* features already use them).

Practical path forward: Rather than a single big switch, a port-by-port migration makes sense:

  1. Replace TinyCrypt SHA-512 with MbedTLS SHA-512 for Ed25519 (small, safe change that helps all ports).
  2. Add sig-ecdsa-mbedtls support to Espressif (they already have MbedTLS in their IDF).
  3. Add enc-ec256-mbedtls / enc-x25519-mbedtls paths for encryption (partially done -- the enc-aes256-* features use MbedTLS already).
  4. 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 simflash to 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(); // TODO instances in sim/src/image.rs
  • Standardize error handling (currently mix of assert! and false returns)
  • 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_ok
  • bootutil_misc.c: boot_write_swap_size, boot_write_enc_key, boot_find_status
  • loader.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;
#endif

This 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:

  1. Trailer-in-swap-region handling: Port the bs->use_scratch logic 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).

  2. Multi-image status awareness: Add image_num checking in swap_status_source() for multi-image builds.

  3. 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 of ram_load.c already exists, but the main flow is still in loader.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.c or moves to image_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
# ... etc

This 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, validation
  • bootutil_priv.h -- central internal header (boot_loader_state, boot_status)
  • bootutil_loader.c -- OS-agnostic loader helpers

Swap algorithms:

  • swap_scratch.c -- scratch-based swap
  • swap_move.c -- move-based swap
  • swap_offset.c -- offset-based swap (preferred)
  • swap_misc.c -- shared swap utilities
  • swap_priv.h -- swap interface declarations

Status/trailer:

  • bootutil_public.c -- trailer read/write (magic, flags)
  • bootutil_misc.c -- swap size, enc keys, status helpers
  • bootutil_misc.h -- trailer offset macros
  • bootutil_area.c -- trailer size calculations

Image validation:

  • image_validate.c -- hash/signature verification
  • image_rsa.c -- RSA signature
  • image_ecdsa.c -- ECDSA signature
  • image_ed25519.c -- Ed25519 signature
  • ed25519_psa.c -- Ed25519 PSA backend
  • tlv.c -- TLV parsing

Encryption:

  • encrypted_psa.c -- PSA Crypto (preferred)
  • encrypted.c -- legacy MbedTLS (deprecated)

Other:

  • boot_record.c -- shared data / boot record
  • ram_load.c -- RAM loading support
  • bootutil_find_key.c -- signature key lookup
  • caps.c -- capability detection
  • fault_injection_hardening.c -- FIH protection
  • fault_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:

  1. Espressif EC256/Ed25519 (no alternative path exists)
  2. Simulator default sig-ecdsa feature (uses TinyCrypt)
  3. Ed25519 SHA-512 (all ports; MbedTLS SHA-512 is the fix)
  4. enc-ec256 and enc-x25519 encryption (TinyCrypt AES/HMAC; MbedTLS alternatives partially exist)

Metadata

Metadata

Assignees

No one assigned

    Labels

    RFCRequest for CommentsWIPWork In Progressenhancement

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions