Skip to content

Module Injection

Injecting a module is the core operator workflow: C3PO compiles a C module for the target chip, signs it with that device's key, and streams it onto the live agent. Nothing is written to the device's flash. This page documents what happens under the hood; you drive it from the Modules view.

The flow

flowchart LR
    SEL["Select module + target device"] --> CC["compile_module()<br/>chip toolchain"]
    CC --> ELF["relocatable ELF"]
    ELF --> SG["sign_module()<br/>HKDF + HMAC-SHA256"]
    SG --> BLOB["[32B HMAC][ELF]"]
    BLOB --> INJ["ModuleInjector"]
    INJ -->|"mod_chunk x N (1 KB)"| DEV["agent"]
    INJ -->|"mod_load"| DEV
    DEV --> RUN["verify -> relocate -> run"]

1. Compile

c3po/modules/compiler.py:compile_module() selects the toolchain from the target chip and compiles the module's entry .c to a relocatable object:

Chip Toolchain
esp32 xtensa-esp32-elf-gcc
esp32s2 xtensa-esp32s2-elf-gcc
esp32s3 xtensa-esp32s3-elf-gcc
esp32c3 / esp32c6 / esp32h2 riscv32-esp-elf-gcc

Flags are -c -Os -fno-common plus -mlongcalls -mtext-section-literals (Xtensa) or -march=rv32imc -mabi=ilp32 (RISC-V). The ESPM include/ directory is on the include path, and -DCONFIG_DEVICE_ID is defined. Compilation runs off the UI thread (asyncio.to_thread).

2. Sign

c3po/modules/signer.py:sign_module() derives a signing key from the device's master_key and prepends an HMAC tag:

sign_key = HKDF(master_key, key_len=32, salt=None, hashmod=SHA256, context=b"espm-sign")
blob     = HMAC_SHA256(sign_key, elf) + elf      # [32B HMAC][ELF]

The blob is bound to the device whose key signed it.

3. Inject

c3po/modules/injector.py:ModuleInjector transfers the blob:

  1. Split it into 1024-byte chunks.
  2. Send each as mod_chunk <name> with binary_data, binary_offset, and binary_total.
  3. Send mod_load <name> <core_id> 30000 [persist].
  4. Wait up to 30 seconds for the load to complete (tracked by CommandTracker).

On the device, mod_load verifies the HMAC, relocates the ELF for the chip, allocates IRAM (or PSRAM on the S3), and starts the module task. Progress and any errors stream into the C3PO log.

Toolchain requirements

Install the cross-compiler for each chip you target. For RISC-V chips:

sudo apt install gcc-riscv32-esp-elf

Xtensa chips use the xtensa-esp32[-s2|-s3]-elf-gcc compilers from an ESP-IDF install. If a toolchain is missing, the compile step fails with a clear error.

See also