How ESP32 OTA Updates Work: The Technical Story

How ESP32 OTA Updates Work: The Technical Story

OTA - Over-The-Air - firmware updates let an embedded device download and install new firmware without being physically connected to a programmer. On the BLEShark Nano, this means connecting to a WiFi network, pulling the latest firmware from InfiShark's update server, and installing it - no USB cable, no flashing tool, no technical knowledge required beyond pressing a button.

That simplicity hides a surprisingly robust pipeline. This article covers the full OTA process at the technical level: the partition scheme, the download and verification flow, the boot pointer swap, and the rollback mechanism that ensures a bad firmware update doesn't brick the device.

Table of Contents

Why OTA Matters for Embedded Devices

Before OTA was standard practice for embedded devices, updating firmware meant physically connecting the device to a computer, installing a specific toolchain, running a flash utility, and hoping nothing went wrong. For consumer products, this was a support nightmare. Most users never updated firmware at all.

The consequences of unupdated firmware are well documented. The Mirai botnet compromised hundreds of thousands of IoT devices running firmware with known vulnerabilities because those devices had no update mechanism, or because users had no reason to think they needed to update a lightbulb. Security researchers regularly find vulnerabilities in embedded devices that are difficult or impossible to patch because the update mechanism either doesn't exist or requires professional tools.

OTA changes this. An OTA update is as easy as connecting to WiFi and pressing a button. When security fixes or new features are available, users actually get them. For a device whose feature set is expanding (new apps, protocol support, performance improvements), OTA also means the device you buy today can do more next month without any hardware changes.

graph TD
    subgraph "ESP32 Flash Partition Layout"
        FLASH["4MB Flash Memory"]
        FLASH --> BOOT["Bootloader
(0x1000, 28KB)"] FLASH --> PTABLE["Partition Table
(0x8000, 4KB)"] FLASH --> NVS["NVS Storage
(0x9000, 20KB)
WiFi creds, config"] FLASH --> OTA_DATA["OTA Data
(0xD000, 8KB)
Which partition to boot"] FLASH --> OTA0["ota_0 Partition
(0x10000, ~1.5MB)
Factory / Current FW"] FLASH --> OTA1["ota_1 Partition
(~0x190000, ~1.5MB)
OTA Update Target"] FLASH --> SPIFFS["SPIFFS / LittleFS
(remaining space)
Web UI, config files"] end subgraph "Boot Decision Flow" POWER["Power On"] --> BOOTL["Bootloader runs"] BOOTL --> READ_OTA["Read OTA Data
partition"] READ_OTA --> DECIDE{"Which partition
is active?"} DECIDE -->|"ota_0 valid"| BOOT0["Boot ota_0"] DECIDE -->|"ota_1 valid"| BOOT1["Boot ota_1"] DECIDE -->|"Neither valid"| FACTORY["Boot factory
recovery"] end

ESP32 flash partition layout showing dual OTA partitions for safe updates with automatic rollback capability

The Dual-Partition Scheme

The foundation of safe OTA on ESP32 is the dual-partition flash layout. The 4MB flash on the BLEShark's ESP32-C3 is divided into two identically-sized firmware partitions: app0 and app1. At any time, exactly one of these contains the running firmware (the "active" partition), and the other is available for the next update (the "inactive" partition).

A small metadata partition called "otadata" records which app partition is currently active and what state each partition is in. The possible states are:

  • VALID: Firmware is installed and has confirmed it's working correctly. Normal operating state.
  • NEW: Firmware has been written to this partition but hasn't booted yet.
  • PENDING_VERIFY: Firmware has booted for the first time and is waiting for the application to confirm it's working.
  • INVALID: Firmware reported an error or failed validation. Bootloader will not boot from this partition.
  • ABORTED: OTA write was interrupted before completion. Partition content is incomplete and will not be booted.

This state machine is what makes rollback possible. The critical rule: a new firmware image only reaches VALID state after the application successfully boots and explicitly confirms that everything works. Until that confirmation happens, the bootloader will fall back to the previous VALID partition on the next reboot.

Step 1: Downloading the Binary

When you trigger an OTA update on the BLEShark Nano, the first thing it does is fetch a manifest file from the server. The manifest contains:

  • The latest firmware version number
  • The URL of the firmware binary
  • The expected SHA256 hash of the binary
  • The binary file size

The firmware compares the version in the manifest against the currently running version. If the manifest version isn't newer, the update is skipped. This prevents unnecessary downloads and avoids downgrade attacks (deliberately installing older, vulnerable firmware).

The download begins. Firmware data arrives in chunks (typically 4KB). Each chunk is written directly to flash as it arrives, rather than buffering the entire firmware in RAM first. This is the only practical approach given that the ESP32-C3 has 400KB of RAM and the firmware binary is over 1MB.

Step 2: SHA256 Verification

SHA256 verification happens in two ways during an OTA update:

Streaming hash: As each chunk of firmware data arrives and is written to flash, the ESP32 maintains a running SHA256 hash computation. This is a streaming approach - it doesn't require the entire file to be present before hashing begins. By the time the last byte is written, the SHA256 computation is complete.

Post-write verification: When the OTA update has finished, the computed SHA256 hash is compared against the expected hash from the manifest. If they match, the firmware image is verified intact. If they don't match, the OTA fails: the partition is left in ABORTED state, the currently running firmware continues operating, and the failed update is discarded. The device is never left in a corrupt state.

The SHA256 hash is also embedded in the firmware binary's image header by Espressif's build toolchain. The bootloader verifies this embedded hash independently on every boot, not just after OTA. This provides an additional layer of integrity checking - even if somehow the flash contents were corrupted after a successful OTA, the bootloader would detect the mismatch and refuse to boot the corrupted image.

Step 3: Writing to the Inactive Partition

Firmware data is written to the inactive app partition - never to the partition that's currently running. This is fundamental: overwriting the running firmware while it's executing would be catastrophic. The inactive partition is the safe workspace for the update.

Before writing begins, the inactive partition is erased sector by sector (4KB at a time). Each sector erase takes 30-100ms. The full 1920KB partition has 480 sectors, so a full erase takes roughly 30-48 seconds. In practice, the erase and write operations are interleaved with the download - as data arrives, old sectors are erased and new data is written. This pipelining keeps total OTA time reasonable.

During the write process, the device is in a vulnerable state: the active partition is running the old firmware, the inactive partition is partially written with new firmware. A power failure or crash at this moment is handled gracefully - the inactive partition will be in ABORTED or partially-written state, which the bootloader ignores. The next boot returns to the active partition with the old firmware, no worse off than before the update began.

Step 4: Boot Pointer Swap

After successful SHA256 verification, the firmware calls esp_ota_set_boot_partition(). This writes to the otadata partition, updating the active partition pointer to indicate the newly written partition, and setting that partition's state to NEW.

At this moment, the device is still running the old firmware. The partition swap happens on the next reboot. Until then, the running system is unchanged.

Step 5: Reboot and Self-Validation

The device reboots. The bootloader reads otadata, finds the newly written partition flagged as boot target, verifies its SHA256 hash (once more - bootloader verifies every boot), and jumps to the new firmware's entry point.

The new firmware's first task on startup is a brief self-check: does the firmware appear to be working? Can it access the flash correctly? Does the basic hardware initialization succeed? If these checks pass, the firmware calls esp_ota_mark_app_valid_cancel_rollback(), which writes VALID to the otadata for the current partition. The update is now complete.

If the new firmware crashes before reaching the self-validation call - say it has a bug that prevents it from completing initialization - the otadata still shows the partition in PENDING_VERIFY state. On the next reboot, the bootloader sees PENDING_VERIFY and falls back to the previous VALID partition. The device recovers automatically.

Rollback: The Safety Net

Rollback is the property that distinguishes a professionally designed OTA system from a naive one. The naive approach: download firmware, overwrite current firmware, reboot. Problem: if the new firmware is broken, you've overwritten the only working copy and the device is bricked.

The ESP32's dual-partition scheme with PENDING_VERIFY state prevents this. The old firmware is preserved in the other partition until the new firmware explicitly confirms it's working. If confirmation never comes (due to a crash, boot loop, or hardware fault detected during startup), the bootloader automatically reverts.

This means a firmware update to BLEShark Nano with a critical bug would result in: download succeeds, write succeeds, reboot, new firmware fails to complete startup, bootloader detects PENDING_VERIFY without confirmation, rolls back to previous firmware, device boots successfully on old firmware. You might see an error message or notice the firmware version hasn't changed, but the device continues working.

Multi-Network OTA on BLEShark Nano

The BLEShark Nano OTA system supports multiple saved WiFi networks for updates. Through the settings, you can save multiple WiFi network credentials. When an OTA update is triggered, the device tries each saved network in sequence until one connects successfully, then proceeds with the update.

This solves a practical problem: a security researcher who uses the BLEShark Nano at home, at the office, and at a lab has different WiFi networks at each location. Without multi-network support, they'd need to reconfigure the OTA WiFi each time they changed locations. With multiple saved networks, the update works wherever they are.

The saved networks are stored in the SPIFFS filesystem partition (separate from the firmware partitions), so they survive OTA updates. After updating to new firmware, the saved network list is still there.

Security of the OTA Process

The BLEShark OTA pipeline has multiple security layers:

ESP32 OTA update sequence - from version check through download, flash, verification, and automatic rollback on failure

SHA256 integrity check: Any modification to the firmware binary (in transit or on the server) produces a different hash and causes the OTA to abort.

Version enforcement: The manifest version check prevents downgrade attacks. You can't OTA to an older (potentially more vulnerable) version.

Secure boot (optional): Espressif's secure boot V2 feature cryptographically signs firmware images and has the bootloader verify the signature on every boot. If enabled, only firmware signed with the manufacturer's private key can run. This prevents loading unauthorized firmware even if someone gains physical flash access. This is a hardware-level protection beyond what standard OTA provides.

Diagram of the OTA Process

sequenceDiagram
    participant DEV as ESP32 Device
    participant SRV as OTA Server
    participant FLASH as Flash Memory

    Note over DEV,SRV: OTA Update Process
    DEV->>SRV: Check for update (HTTP GET /version)
    SRV->>DEV: New version available (v2.1.0)
    DEV->>DEV: Compare with running version

    Note over DEV,FLASH: Download Phase
    DEV->>SRV: Request firmware binary
    SRV->>DEV: Stream firmware chunks
    DEV->>FLASH: Write to OTA partition (ota_1)
    DEV->>DEV: Verify SHA-256 checksum

    Note over DEV,FLASH: Activation Phase
    DEV->>FLASH: Set ota_1 as boot partition
    DEV->>DEV: Reboot into new firmware

    Note over DEV: Rollback Safety
    DEV->>DEV: Run self-diagnostics
    alt Diagnostics pass
        DEV->>FLASH: Mark ota_1 as valid
        DEV->>SRV: Report success
    else Diagnostics fail
        DEV->>FLASH: Rollback to ota_0
        DEV->>DEV: Reboot into previous firmware
    end

The combination of hash verification, dual-partition rollback, and version enforcement makes the BLEShark OTA update process substantially more robust than the USB-flash approach used by similar devices - and safer than IoT devices with no update mechanism at all.

Get BLEShark Nano

Back to blog

Leave a comment