Status: Accepted — 2026-04-17 Phase: 2 (Anti-replay) Commit: 942abce
The command dispatcher gained a 32-bit monotonic counter + 64-bit sliding-window bitmap to reject replayed uplink commands. Two design choices around counter semantics bit us during test:
g_high_counter is 0 and the bitmap is empty.
A prerecorded frame with counter = 0 would walk the “first of
epoch” branch and be accepted — replayable indefinitely across
reboots.Counter value 0 is reserved. The firmware rejects every frame
with counter == 0 regardless of HMAC validity; the ground library
(ground-station/utils/hmac_auth.py) raises
ReplayCounterError when a caller tries to build a frame with
counter <= 0.
Senders start at counter = 1 and increment monotonically.
g_high_counter is 0. If 0 were a legitimate counter value,
an attacker with a captured counter=0 frame could replay it
and pass the “first frame of epoch” branch — indistinguishable
from a fresh boot.Positive:
Negative:
Firmware (firmware/stm32/Core/Src/command_dispatcher.c):
if (counter == 0U) {
return false; /* reserved sentinel */
}
Ground (ground-station/utils/hmac_auth.py):
if counter <= 0 or counter > 0xFFFFFFFF:
raise ReplayCounterError(...)
Both paths are exercised by dedicated tests:
test_command_dispatcher.c::test_counter_zero_rejectedtest_hmac_auth.py::test_counter_zero_or_out_of_range_rejected