The Internet is unavailable, GPS is jammed, and only a sparse network of BLE gateways remains operational. Your task is to complete the firmware protocol for battery-powered rescue tags that must remain discoverable without broadcasting a permanent identity.
The protocol must survive unstable power, reject forged and replayed packets, limit flash wear, and remain within a strict energy budget. The final system is tested as a multi-device nRF52840 swarm in Renode.
This is an approximately 8-hour firmware challenge. You will work only in:
firmware/ghost_protocol.c
The public API, application code, gateway firmware, simulation scenario, and validation rules are fixed contracts.
problem/problem.md – the complete protocol, journal, recovery, energy,and acceptance contract.
firmware/include/ghost_protocol.h – all public types, constants,callbacks, and function signatures.
firmware/ghost_protocol.c – the starter implementation and the sixTODOs you must complete.
firmware/tests/test_protocol.c – the fast public tests and the expectedstorage behavior.
renode/README.md – the files exposed for the simulation and thereset/attacker sequence.
renode/generate_swarm_resc.py and renode/run_test.sh – how the swarmis generated, executed, and passed to validation.
docs/renode_primer.md – the nRF52840 machines, BLE medium, provisioningregion, persistent journal region, and UART evidence.
docs/exercise_guide.md – the recommended solution order and the threefeedback layers.
The values and byte layouts below are a summary. If this page and the fixed C
header differ, follow the header and problem/problem.md.
Complete the six TODOs in firmware/ghost_protocol.c:
in-memory epoch advance.
Keep the existing function signatures. Do not change the packet size, constants, storage callbacks, or public structures to work around the contract.
Every BLE manufacturer payload is exactly 28 bytes:
| Bytes | Field |
|---|---|
0..1 | company ID 0xF00D, little-endian |
2 | protocol version 3 |
3 | flags |
4..7 | persistent ratchet epoch, little-endian |
8..11 | public city sector, little-endian |
12..19 | keyed ephemeral identifier |
20..27 | keyed MAC over bytes 0..19 |
A stable tag identifier must never appear on air in either byte order.
The provided seed_to_key function derives the epoch-0 key. To derive the
key for next_epoch, compute two SipHash outputs using the current 16-byte
key:
0x52 || next_epoch_le32 || lane
Use lane 0 for new key bytes 0..7 and lane 1 for bytes 8..15.
Compute both outputs before replacing the current key.
For a packet at epoch e:
e to obtain the epoch key;
0x45 || epoch_le32 || sector_le32;
0, 7, 8, and 15 with
0x4d, 0x41, 0x43, and 0xa7 to obtain the MAC key;
0..19 with the MAC key;
1,000,000;
The tag has two 4096-byte journal pages. Erased bytes are 0xff; a write may
only change bits from 1 to 0; an erase operates on one complete page.
Each journal slot is a 40-byte record:
| Bytes | Field |
|---|---|
0..3 | magic 0x47535452 |
4..7 | generation |
8..11 | future resume epoch |
12..15 | cumulative page-erase count |
16..31 | ratcheted key for the resume epoch |
32..35 | IEEE CRC32 over bytes 0..31 |
36..39 | commit word 0xC01117ED |
Write the 36-byte body first. Write the commit word in a separate final storage operation. A missing commit, bad CRC, bad magic, or partially written body is not a valid record.
The journal reserves leases of 16 epochs:
0..15 by storing
the resume state for epoch 16;
lease before transmitting;
leases conservatively before reserving again;
committed.
These rules must prevent epoch reuse after a torn body write, torn commit, corrupted CRC, page rollover, or power loss at any storage operation.
Runtime energy is calculated as:
energy = flash_writes * 8 + page_erases * 40 + advertisements
One committed record requires two writes: body, then commit. The power-cut simulation must use no more than 80 energy units.
The endurance tests generate 2000 sequential payloads. A passing solution uses at most 252 flash writes and exactly one page erase. Persisting every packet cannot pass these limits; lease reservation is required.
The complete run starts:
At virtual second 3, Renode resets tag 1 while preserving its nonvolatile journal. The replay attacker then transmits an old epoch-0 packet from a different BLE address.
A correct system must:
| Goal |
|---|
| Read every contract and pass all SipHash vectors. |
| Implement ratchet, packet generation, EID, MAC, and verification. |
| Implement record encoding, CRC32, scanning, append, and leases. |
| Handle torn writes, corruption, reboot, and page rollover. |
| Verify flash-wear and energy bounds. |
| Run Zephyr and Renode; diagnose the complete swarm. |
Use the fastest feedback first. Do not repeatedly run the full swarm while the native protocol suite still fails.
The Run action has three gates:
corruption handling, and endurance;
The complete success output includes lines equivalent to:
PROTOCOL_CONTRACT_TESTS failures=0 Renode exit status: 0 GHOST_VALIDATION passed=1 ... tags=6/6 rotated=6/6 ... recovered=1 energy=.../80 >>> GHOSTTAG FLEET SURVIVED THE APOCALYPSE <<<
After the run, refresh the Results panel and inspect output/report.html.
When a check fails, read the first failing gate and its log before changing
code.
Before considering the challenge complete, confirm that:
all pass.