IoT and Smart Systems Engineer · Projects
Project 3: Safe over-the-air updates
Marking guide version 1 · Lessons: IO9 lessons 1–5, IO10 lesson 4
The problem and who it's for
Once tanks are installed, nobody wants to climb up to each one to fix a bug. In the starter, the update code doesn't check signatures, accepts any different version (even an older one), writes over the firmware that's running, starts again from the beginning after a dropped download, ignores the battery, has no trial or rollback, and picks rollout devices at random. A signing seed is written in the code, and the rollout sends every update to every device at once. The committee needs updates that only come from the project, can't leave a tank monitor dead, and reach a few devices before all of them.
Scope
- Check the manifest's signature before downloading anything, and the image's hash before using it.
- Refuse versions that aren't newer than the running one, or older than the last confirmed one.
- Download into the other slot, not over the running firmware, in chunks that resume after a drop.
- Wait until the battery is above the manifest's minimum.
- Run new firmware on trial, confirm it only when healthy, roll back after three failed boots, and don't reinstall that version.
- Keep the signing key out of the firmware and the repository, and list
keys/and*.pemin.gitignore. - Write
ota/rollout.jsonwith stages and halt rules, choose a device's stage from its ID, and writedocs/ota-design.md.
Not included
- A real bootloader or flash partitions: the kit simulates the slots.
- A real update server or fleet: the kit's server and devices are simulated.
- Encrypting firmware images (IO9 explains when it's needed).
Before you start
-
Core: Module 71 (CI/CD), Module 73 (Security Introduction), Module 76 (API and Network Security), Module 77 (Encryption and Secrets), Module 96 (Reliability Engineering).
-
Path: IO9 complete, and Projects 1 and 2 done.
- The kit installed, with
pytest tests/test_ota.pyrunning (all 8 of its tests fail at first).
Starter files
-
jalsetu/firmware/ota.py: updates with no signature check, no anti-rollback, writing over the running slot, no resume, no battery check, no trial or rollback, random stage choice, and a signing seed in the code. -
jalsetu/ota/rollout.json: one stage, 100% at once, with no halt rules. jalsetu/.gitignore: lists only__pycache__/.jalsetu/tools/make_keys.py: makes an Ed25519 key pair.jalsetu/tools/sign_firmware.py: signs a release manifest with a key passed by--keyorJALSETU_SIGNING_KEY.jalsetu/sim/factory.py: provisions a simulated device with its firmware, boot record and public key.jalsetu/tests/test_ota.py: the 8 acceptance tests for this project.
Download everything at once: starter/iot-starter.zip.
Milestones
- Read
docs/FORMATS.mdandsim/factory.py, make a key pair outside the project, and sign a test release. - Check signatures and hashes, and add anti-rollback (tests 1 and 2).
- Download into the other slot with resume and the battery check (tests 3, 4 and 5).
- Add trial boots and rollback without reinstalling (test 6).
- Remove the seed from the code and fix
.gitignore(test 7). - Write the rollout plan and
docs/ota-design.md(test 8), then run all 8 tests twice in a row.
Expected outputs
- 8 of 8 update tests passing, twice in a row.
- The update code,
ota/rollout.json,.gitignoreanddocs/ota-design.md. - A note on what you'd do if the signing key were exposed.
Acceptance tests
| # | Input | Expected result |
|---|---|---|
| 1 | pytest tests/test_ota.py |
8 of 8 pass, twice in a row |
| 2 | A changed image; a manifest signed with another key; an older version | Each is refused, and nothing is downloaded for a bad signature (tests 1 and 2) |
| 3 | An update; a dropped download; a low battery | The running slot is untouched, the download resumes from the last chunk, and no update starts below the battery minimum (tests 3, 4 and 5) |
| 4 | Healthy and unhealthy new firmware | Healthy firmware is confirmed; unhealthy firmware rolls back after three boots and isn't reinstalled (test 6) |
| 5 | The project's files; ota/rollout.json; docs/ota-design.md |
No key in the code or repository, the tools work with a key kept outside, stages with halt rules and a fixed choice of devices, and the design written down (tests 7 and 8) |
Failure and recovery exercises
- Turn off the signature check and run test 1. Explain what an attacker on the same network could do, then restore it.
- Write the update over the running slot and run test 3. Explain what a power cut mid-update would do, then restore the second slot.
- Pick stage devices at random and run test 8. Explain why the stages stop meaning anything, then restore the hash of the device ID.
Evidence to submit
- Link to your repository or a shared folder with the kit and your changes: the deliverable link.
- The output of
pytest tests/test_ota.pyshowing 8 passed, pasted into test evidence. - Your rollout plan, update design and the note on an exposed key.
- Your explanation: what happens, step by step, when a faulty update reaches one device.
Marking guide and self-check
Pass rule: every Safety-critical (S) and Mandatory (M) item must be met. A single unmet safety item means the project is not complete, whatever else is done. Quality items are for your own improvement.
Your answers are saved in this browser on this device. Self-checked means you checked your own work against this guide; it is not a reviewer's approval. Never paste passwords, keys, tokens or real customer data here.
