Version: 1.6 (draft)
Status: Approved for implementation
Scope: Track A — OTA with atomic rollback and delta updates
1. Goals
| # | Goal |
|---|---|
| G1 | Atomic slot-swap: only boot new firmware after explicit confirmation |
| G2 | Automatic rollback after N consecutive boot failures (configurable) |
| G3 | Delta (binary-patch) update mode to reduce download size |
| G4 | WASM app can trigger / monitor OTA without elevated native access |
| G5 | Single docs/feature_overhead.md entry tracking flash/RAM cost |
2. Current State (v1.5.4)
src/connectivity/ota/ota_manager.c/h already provides:
- MCUboot
boot_request_upgrade()/boot_write_img_confirmed()wrappers ota_start_update(),ota_write_chunk(),ota_finalize_update()ota_confirm_firmware(),ota_request_rollback()- Pluggable transport interface (
ota_transport_t)
Missing for v1.6:
akira_boot_guard— persistent boot counter stored in NVS, triggers rollback if the counter exceedsCONFIG_AKIRA_OTA_MAX_BOOT_FAILURESwithout being cleared.- Manifest-based HTTP fetch — JSON manifest → URL → streaming download into MCUboot secondary slot.
- Delta patching — streaming bspatch applied between the running slot and the incoming binary.
- WASM native API —
wasm_ota_check(),wasm_ota_fetch_and_apply(),wasm_ota_get_state().
3. MCUboot Slot Architecture
Flash map (typical):
┌──────────────────────────────────────────┐
│ boot partition (MCUboot loader) │
├──────────────────────────────────────────┤
│ slot0_partition (primary — running)│
├──────────────────────────────────────────┤
│ slot1_partition (secondary — OTA) │
├──────────────────────────────────────────┤
│ panic_store (NVS — crash log) │
├──────────────────────────────────────────┤
│ boot_counter (NVS — boot count) │ ← NEW
└──────────────────────────────────────────┘
MCUboot performs the swap atomically: if the new image does not call boot_write_img_confirmed() within CONFIG_MCUBOOT_WATCHDOG_FEED_TIMEOUT_MS, it reverts to the previous primary slot on next reset.
AkiraOS adds a software-level boot guard on top of MCUboot to handle the case where the device crashes during application startup before the RTOS watchdog feeds MCUboot.
4. Boot Guard Design (akira_boot_guard)
4.1 Data Layout
A small NVS partition (boot_counter) holds a single record:
typedef struct {
uint32_t magic; // 0xB007C0DE when valid
uint8_t boot_count; // incremented on every unconfirmed boot
uint8_t confirmed; // set to 1 after akira_boot_guard_confirm()
uint8_t _pad[2];
} akira_boot_counter_t;
4.2 Boot Sequence
akira_boot_guard_init()
│
├─ Read NVS record
│ ├─ Not found → write {magic, boot_count=1, confirmed=0}
│ └─ Found
│ ├─ confirmed=1 → reset to {confirmed=0, boot_count=1} → OK
│ └─ confirmed=0
│ ├─ boot_count < MAX → increment, write back → continue
│ └─ boot_count >= MAX → LOG_ERR, call ota_request_rollback() → reboot
│
└─ Return 0 (continue boot) or -EAGAIN (rollback triggered)
4.3 Confirmation
The application must call akira_boot_guard_confirm() once it is healthy (e.g., after successful network join + first sensor read). This sets confirmed=1 and calls ota_confirm_firmware() to also confirm at the MCUboot level.
4.4 Kconfig
config AKIRA_BOOT_GUARD
bool "AkiraOS software boot guard"
default n
select AKIRA_OTA
select NVS
config AKIRA_OTA_MAX_BOOT_FAILURES
int "Max consecutive unconfirmed boots before rollback"
default 3
range 1 10
depends on AKIRA_BOOT_GUARD
5. Delta Update Design (akira_delta)
5.1 Motivation
Typical ESP32-S3 firmware: ~400 KB. A delta patch between two minor releases is typically 20–60 KB — reducing OTA download by 80–90%.
5.2 Algorithm
bsdiff/bspatch (BSD licence, well-tested, widely deployed):
- Produces compressed patches using bzip2.
- Patch is applied in-place streaming: read old slot, apply patch blocks, write to secondary slot, never hold the full binary in RAM.
Streaming patch flow:
HTTP GET /firmware.patch
│
│ (chunk by chunk, CONFIG_AKIRA_OTA_DELTA_CHUNK_SIZE)
▼
akira_delta_open(ctx, secondary_slot_fd, primary_slot_fd)
akira_delta_feed(ctx, patch_chunk, len) // called per HTTP chunk
akira_delta_close(ctx) // finalises secondary slot
│
▼
ota_finalize_update() // SHA-256 verify + boot_request_upgrade()
5.3 Memory Budget
akira_delta_ctx_theap usage: ~4 KB (bzip2 decompressor state).- Stack delta: ≤ 512 B added to the OTA thread stack.
- No extra thread.
5.4 Kconfig
config AKIRA_OTA_DELTA
bool "Delta (binary-patch) OTA updates"
default n
depends on AKIRA_OTA
select CBPRINTF_FP_SUPPORT # not needed but kept for reference
config AKIRA_OTA_DELTA_CHUNK_SIZE
int "Delta patch input chunk size (bytes)"
default 4096
range 512 65536
depends on AKIRA_OTA_DELTA
6. Manifest Format
{
"version": "1.6.0",
"board": "esp32s3_devkitm",
"url": "https://cdn.akirasystems.io/fw/akiraos-1.6.0.bin",
"size": 425984,
"sha256": "a1b2c3...",
"delta": {
"from_version": "1.5.4",
"url": "https://cdn.akirasystems.io/fw/akiraos-1.5.4-to-1.6.0.patch",
"size": 38912,
"sha256": "d4e5f6..."
}
}
deltakey is optional. If present andfrom_versionmatches the running image, the delta path is preferred.- SHA-256 verified client-side before calling
ota_finalize_update(). - JSON parsed with the existing
simple_json.chelper.
7. WASM Native API (akira_ota_api)
7.1 Exports
| WASM function | Signature (type string) | Description |
|---|---|---|
wasm_ota_check | "($)i" | Fetch manifest from URL, return 1 if update available, 0 if up-to-date |
wasm_ota_fetch_and_apply | "($)i" | Download and stage firmware (blocking, progress via telemetry) |
wasm_ota_get_state | "()i" | Return ota_state enum value |
wasm_ota_confirm | "()i" | Confirm running firmware (clears boot guard) |
wasm_ota_rollback | "()i" | Request immediate rollback |
7.2 Capability
AKIRA_CAP_OTA_TRIGGER (bit 31) — must be declared in app manifest.
7.3 Security Considerations
- URL validation: only HTTPS allowed. Reject
http://at the API boundary. - SHA-256 verified before any
boot_request_upgrade()call. - WASM sandbox cannot write directly to flash slots — only via the native OTA API.
- No URL or manifest content reaches WASM memory; only status codes.
8. Acceptance Criteria
| # | Criterion | Test |
|---|---|---|
| AC1 | Three consecutive unconfirmed boots trigger rollback | tests/ota/test_boot_guard.c |
| AC2 | Delta patch produces identical binary to full-image download | tests/ota/test_delta.c |
| AC3 | wasm_ota_get_state() returns correct enum | tests/wasm_api/test_ota_api.c |
| AC4 | OTA without AKIRA_CAP_OTA_TRIGGER returns -EACCES | tests/wasm_api/test_ota_api.c |
| AC5 | Native sim build with CONFIG_AKIRA_BOOT_GUARD=y compiles and runs AC1 | CI |
9. Flash / RAM Cost Estimate
| Module | Flash (text) | RAM (bss/data) |
|---|---|---|
akira_boot_guard | ~1.2 KB | ~128 B (NVS fs struct) |
akira_delta | ~8.0 KB (bzip2 subset) | ~4.1 KB (ctx, heap-allocated) |
akira_ota_api | ~1.0 KB | ~0 B |
| Total (all enabled) | ~10.2 KB | ~4.2 KB |
Delta module is disabled by default (CONFIG_AKIRA_OTA_DELTA=n) so the baseline cost is ~2.2 KB flash / ~128 B RAM for boot guard + API.
10. Out of Scope (v1.6)
- Code-signing key rotation
- Multi-image updates (MCUboot multi-image is possible but adds complexity)
- BLE OTA transport (already exists via
ota_ble_transportin v1.5.x) - A/B app bundle updates (WASM
.wasmfile updates handled separately)