refactor: split OTA implementation into core and platform-specific fi… #174
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main, "feat/**", "fix/**", "p3/**"] | |
| pull_request: | |
| branches: [main] | |
| env: | |
| CARGO_TERM_COLOR: always | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| firmware-matrix: | |
| name: Firmware matrix | |
| runs-on: ubuntu-latest | |
| outputs: | |
| matrix: ${{ steps.matrix.outputs.matrix }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - id: matrix | |
| name: Load firmware matrix | |
| run: | | |
| MATRIX=$(python3 -c "import json,pathlib; print(json.dumps(json.loads(pathlib.Path('.github/firmware-matrix.json').read_text())))") | |
| echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT" | |
| # ─── Rust: format, lint, test, FFI build ──────────────────────────────────── | |
| rust: | |
| name: Rust checks | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: rustfmt, clippy | |
| - name: Cache cargo registry | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cargo/registry/index | |
| ~/.cargo/registry/cache | |
| ~/.cargo/git/db | |
| target | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: ${{ runner.os }}-cargo- | |
| - name: Check formatting | |
| run: cargo fmt --all -- --check | |
| - name: Clippy (deny warnings) | |
| run: cargo clippy --all-targets -- -D warnings | |
| - name: Run tests | |
| run: cargo test --all | |
| # Validate that the FFI feature compiles (exercises the C-ABI surface) | |
| - name: Build FFI feature | |
| run: cargo build -p rivr_core --features ffi | |
| # ─── C: acceptance + radio-recovery + replay + dutycycle tests ────────────── | |
| c-tests: | |
| name: C tests | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install build tools | |
| run: sudo apt-get install -y gcc make | |
| - name: Build and run all C test suites | |
| run: make -C tests | |
| - name: Build + run policy-engine tests (P2) | |
| run: | | |
| gcc -O2 -Ifirmware_core -DIRAM_ATTR="" \ | |
| firmware_core/policy.c \ | |
| tests/test_policy.c \ | |
| -o /tmp/test_policy && /tmp/test_policy | |
| - name: Build + run OTA gate tests – signed (P2) | |
| run: | | |
| gcc -O2 -Ifirmware_core -DIRAM_ATTR="" -DRIVR_SIGNED_PROG -DRIVR_PUBKEY_PRODUCTION=1 \ | |
| firmware_core/ed25519_verify.c \ | |
| firmware_core/rivr_ota_core.c \ | |
| tests/test_ota.c \ | |
| -o /tmp/test_ota && timeout 60 /tmp/test_ota | |
| - name: Build + run OTA gate tests – unsigned (P2) | |
| run: | | |
| gcc -O2 -Ifirmware_core -DIRAM_ATTR="" \ | |
| firmware_core/ed25519_verify.c \ | |
| firmware_core/rivr_ota_core.c \ | |
| tests/test_ota.c \ | |
| -o /tmp/test_ota_unsigned && /tmp/test_ota_unsigned | |
| # ── ASan + UBSan (P3.2) ────────────────────────────────────────────────── | |
| asan-tests: | |
| name: ASan + UBSan C tests | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install GCC | |
| run: sudo apt-get install -y gcc make | |
| - name: Build + run core suites under ASan/UBSan | |
| working-directory: tests | |
| run: make asan | |
| - name: Build + run policy under ASan/UBSan | |
| run: | | |
| gcc -O1 -g -fsanitize=address,undefined \ | |
| -Ifirmware_core -DIRAM_ATTR="" \ | |
| firmware_core/policy.c \ | |
| tests/test_policy.c \ | |
| -o /tmp/test_policy_asan && /tmp/test_policy_asan | |
| - name: Build + run OTA under ASan/UBSan (signed) | |
| run: | | |
| gcc -O1 -g -fsanitize=address,undefined \ | |
| -Ifirmware_core -DIRAM_ATTR="" -DRIVR_SIGNED_PROG -DRIVR_PUBKEY_PRODUCTION=1 \ | |
| firmware_core/ed25519_verify.c \ | |
| firmware_core/rivr_ota_core.c \ | |
| tests/test_ota.c \ | |
| -o /tmp/test_ota_asan && timeout 60 /tmp/test_ota_asan | |
| # ── Size regression (P3.1) ─────────────────────────────────────────────── | |
| size-check: | |
| name: Size regression (+5% host binaries) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install GCC + make | |
| run: sudo apt-get install -y gcc make | |
| - name: Build host test binaries | |
| working-directory: tests | |
| run: make all | |
| - name: Size regression check | |
| run: bash tests/ci_size_check.sh tests/baselines/host_sizes.txt 5 | |
| # ── Determinism (P3.1) ─────────────────────────────────────────────────── | |
| determinism: | |
| name: Replay determinism hash | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install GCC + make | |
| run: sudo apt-get install -y gcc make | |
| - name: Build replay test | |
| working-directory: tests | |
| run: make replay | |
| - name: Compare against golden snapshot | |
| run: bash tests/ci_determinism_check.sh tests/golden/replay_sha256.txt | |
| # ── Firmware variants on GitHub-hosted runners ─────────────────────────── | |
| firmware: | |
| name: Firmware ${{ matrix.pio_env }} | |
| runs-on: ubuntu-latest | |
| needs: firmware-matrix | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJSON(needs.firmware-matrix.outputs.matrix) }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Restore firmware build cache | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cache/pip | |
| ~/.platformio | |
| key: firmware-${{ matrix.pio_env }}-${{ runner.os }}-${{ hashFiles('.github/firmware-matrix.json', 'platformio.ini', 'variants/**/*.ini', 'Cargo.lock', 'firmware_core/CMakeLists.txt', 'sdkconfig/**', 'sdkconfig*') }} | |
| restore-keys: | | |
| firmware-${{ matrix.pio_env }}-${{ runner.os }}- | |
| firmware-${{ runner.os }}- | |
| - name: Install PlatformIO and esptool | |
| run: | | |
| pip install --upgrade pip | |
| pip install platformio esptool | |
| - name: Install Espressif Rust toolchain via espup | |
| if: ${{ startsWith(matrix.rust_target, 'xtensa-') }} | |
| run: | | |
| cargo install espup --locked | |
| espup install | |
| source "$HOME/export-esp.sh" | |
| [ -n "$LIBCLANG_PATH" ] && echo "LIBCLANG_PATH=$LIBCLANG_PATH" >> "$GITHUB_ENV" | |
| [ -n "$CLANG_PATH" ] && echo "CLANG_PATH=$CLANG_PATH" >> "$GITHUB_ENV" | |
| ESP_TOOLCHAIN=$(rustup toolchain list | awk '/^esp/ {print $1; exit}') | |
| echo "RUSTUP_TOOLCHAIN=$ESP_TOOLCHAIN" >> "$GITHUB_ENV" | |
| echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" | |
| shell: bash | |
| - name: Install ARM Rust target (nRF52 / RP2040) | |
| if: ${{ matrix.rust_target == 'thumbv7em-none-eabihf' || matrix.rust_target == 'thumbv6m-none-eabi' }} | |
| run: rustup target add ${{ matrix.rust_target }} | |
| - name: Build Rust rivr_core (${{ matrix.rust_target }}) | |
| run: | | |
| if [[ "${{ matrix.rust_target }}" == thumbv* ]]; then | |
| cargo +stable build -p rivr_core \ | |
| --target ${{ matrix.rust_target }} \ | |
| --no-default-features \ | |
| --features ffi \ | |
| --release | |
| else | |
| source "$HOME/export-esp.sh" | |
| cargo build -p rivr_core \ | |
| --target ${{ matrix.rust_target }} \ | |
| --no-default-features \ | |
| --features ffi \ | |
| --release \ | |
| -Zbuild-std=core,alloc,panic_abort | |
| fi | |
| shell: bash | |
| - name: Build firmware (${{ matrix.pio_env }}) | |
| run: | | |
| pio run -e ${{ matrix.pio_env }} |