openaliro
Aliro reader: UWB/CCC core and ESP32-S3/C5/C6 port
Loading...
Searching...
No Matches
ccc_shim_rx.c File Reference

— responder-RX CCC STS substitution: woz_uwb_arm_rx() programs the CCC STS on each RX-arm; target only. More...

#include <stddef.h>
#include <stdint.h>
#include <errno.h>
#include "woz_port.h"
#include "woz_bytes.h"
#include <deca_device_api.h>
#include "ccc_shim.h"
#include "aliro_round_config.h"
#include "ccc_kdf.h"
#include "ccc_mac.h"
#include "fira_session.h"
#include "uwb_min.h"
#include "uwb_seam.h"
#include "woz_diag.h"
#include "uwb_rxdiag.h"
#include "flight_recorder.h"
Include dependency graph for ccc_shim_rx.c:

Macros

#define CCC_RX_LOG_ARMS   16
 Log the first N intercepted RX-arms — confirms slot advance + register load.
 
#define STS_KEY0_REG   0x2000CUL
 STS_KEY/STS_IV registers (dw3000_deca_regs.h) — read back to prove the load lands.
 
#define CHAN_CTRL_REG   0x10014UL
 CHAN_CTRL (dw3000_deca_regs.h): RX preamble code [12:8], SFD type [2:1]; read at the POLL arm to prove the active PHY.
 
#define SYS_CFG_REG   0x10UL
 SYS_CFG [13:12] = CP_SPC (STS packet config / SP mode: 3 = SP3/ND).
 
#define STS_CFG0_REG   0x20000UL
 STS_CFG0 [7:0] = CPS_LEN (STS length code: 7 = STS64 = 4096 DRBG bits).
 
#define CCC_RX_PIN_ROUND0_POLL   1
 ISOLATION PROBE (bench) — pin the RX STS index to the deterministic round-0 POLL (STS_Index0 + 1) instead of the local-time slot.
 
#define CCC_RX_FORCE_SP3   1 /* 1 for the seeded Δ sweep: engage the STS engine on the SP3 POLL. */
 ISOLATION PROBE (bench) — force SP3/ND at each RX arm so the STS engine is engaged when Apple's SP3 POLL lands.
 
#define CCC_RX_LOCK_SWEEP
 EMPIRICAL STS-INDEX LOCK (bench) — track Apple's ranging clock on-air by searching the constant slot offset Δ between our time0 and Apple's UWB_Time0.
 
#define CCC_RX_SLOTS_PER_BLOCK    96u
 RESIDUE-COMPLETE alignment-offset sweep (slot = current_slot() + Δ): the STS index is linear at one per slot, so sweep the constant offset Δ.
 
#define CCC_RX_DELTA_SEED   1299
 Sweep CENTER = Apple's UWB_Time0 / slot-duration; the offset is the constant slot gap between our time0 and Apple's UWB_Time0, searched not derived.
 
#define CCC_RX_DELTA_HALF    768u
 ± window around the seed: covers offsets [531,2067] (two prior estimates \ ≈1250–1300).
 
#define CCC_RX_N_CAND
 1536 one-slot Δ candidates, alternating outward from the seed.
 
#define CCC_RX_DWELL    1u
 catches held per Δ candidate (1 = fast full-range sweep; bump if it cycles past).
 
#define CCC_RX_PREPOLL_LOG   16u
 BENCH: decode a live SP0 Pre-POLL (CCM*-decrypt with mUPSK1) to read Apple's exact POLL STS index, learn the block stride, and warm the next block's STS.
 
#define CCC_RX_PACK_SELFTEST   0 /* answered 2026-07-09: pack_iv needed the reverse (applied). */
 BENCH PROBE (CCC_RX_PACK_SELFTEST) — dump the STS register lanes a known V lands in, to pin the pack_iv byte order.
 
#define CCC_RX_DIAG_N    110u
 Re-arm the SP0 receiver after each RX event; the woz_uwb_set_callbacks shim runs ccc_shim_rx_try_prepoll first, so here we keep the plain SP0 receive listening.
 
#define CCC_RX_SLEEP_HI32   45900000u
 Sleep between wakes, hi32 (~4 ns) units: one 192 ms block MINUS ~5 slots, waking just ahead of the Pre-POLL so it is caught fresh each block.
 
#define CCC_RX_BLOCK_HI32   47920000u
 One 192 ms ranging block, hi32 (~4 ns) units (measured on air, dsys ≈ 47.92 M).
 
#define CCC_RX_SLOT_HI32   499000u
 One 2 ms slot, hi32 units (measured, dsys ≈ 499 000).
 
#define CCC_RX_WIN_TO   1950u
 Anchored SP0 RX window length, dwt_setrxtimeout units (1.0256 µs): ~1 slot, so a miss times out before the POLL slot.
 
#define CCC_RX_SWEEP_N   5u
 Sweep the armed offset ±2 slots (0..4 -> -2..+2), so an off-by-one gap does not read as "no Pre-POLL".
 
#define CCC_RX_POLL_LEAD   40000u
 Open the SP3 POLL window this many hi32 units (~160 µs) BEFORE the POLL RMARKER (Pre-POLL.
 
#define CCC_RX_POLL_WIN_TO   1350u
 SP3 POLL RX window (dwt_setrxtimeout units, 1.0256 µs): ~1.4 ms, delayed to the POLL slot, opening ~600 µs early.
 
#define CCC_RX_PREPOLL_CODE   9u
 Sync (preamble) code to listen on for the SP0 Pre-POLL: 9, read from the phone's plaintext M4 (07 01 09 = SYNC_Code_Index=9).
 

Functions

void ccc_shim_rx_log_reset (void)
 Reset all Per-POLL state: arm count, index tracking, STS warm cache, and optional lock-sweep diagnostic counters; called on entry to a new Pre-POLL listen.
 
bool ccc_shim_rx_awaiting_poll (void)
 Returns true if the responder is awaiting the POLL frame after a successful Pre-POLL decode.
 
bool ccc_shim_rx_deadline_pending (void)
 Return true if CCC UWB reception is awaiting either POLL or FINAL frame from the responder, false otherwise.
 
bool ccc_shim_rx_awaiting_final (void)
 Return true if CCC UWB reception is awaiting a FINAL frame from the responder, false otherwise.
 
void ccc_shim_rx_notify_rx (uint32_t status)
 Log one RX event for the optional lock-sweep diagnostic (CONFIG_CCC_RX_LOCK_SWEEP); tracks CPER (STS correlation fail flag) and dwells candidate indices until lock achieved or full cycle exhausted.
 
static void prepoll_decode (const uint8_t *frame, uint16_t datalength)
 Decode a received Pre-POLL frame: verify MHR and message ID, decrypt SP0 payload, cache UAD-derived keys, extract Poll_STS_Index and stride, and pre-warm the next block's STS triplet (POLL, Response_0, Final) to eliminate KDF latency from the critical path.
 
static uint64_t ts5_to_u64 (const uint8_t t[5])
 Assemble a 5-byte DW3000 (40-bit) timestamp into a uint64 (DTU ticks).
 
static void final_data_decode (const uint8_t *frame, uint16_t datalength)
 Decode a received Final_Data (SP0, msg_id=02): dUDSK-decrypt and parse the initiator's ranging timestamps; not time-critical.
 
void ccc_shim_rx_try_prepoll (uint16_t datalength)
 Pre-POLL RX entry (from the RX-good shim): stash the frame and DEFER its ~2 ms decrypt+derive off the Pre-POLL->POLL critical path.
 
static void pack_key (dwt_sts_cp_key_t *out, const uint8_t dursk[CCC_DURSK_LEN])
 Pack a 16-byte dURSK into the DW3000 STS-key image (whole-16 reverse).
 
static void pack_iv (dwt_sts_cp_iv_t *out, const uint8_t sts_v[CCC_STS_V_LEN])
 Pack a 16-byte STS-V into the DW3000 STS-IV image (whole-16 reverse then per-word LE, same as pack_key).
 
int32_t woz_uwb_arm_rx (int32_t mode)
 Program the CCC STS for the current ranging slot, then arm RX.
 
static int32_t gated_rxenable (int32_t mode)
 Gate-checked RX arm for every self-rearm site below; refuses once the listen-gate is closed.
 
static int arm_poll_sp3 (uint32_t prepoll_ip)
 Flip to SP3/ND, load the pre-warmed CCC STS (g_warm_index), and arm a delayed RX to catch the POLL that follows the Pre-POLL.
 
static void revert_to_sp0_listen (void)
 Revert SP3/ND -> SP0 and re-arm the permanent Pre-POLL listen (no timeout).
 
static int tx_response_sp3 (uint32_t poll_ip, uint32_t resp_idx)
 Delayed-TX the responder's Response_0 (SP3-ND) one slot after the POLL, at STS index Poll_STS_Index + 1 (same dURSK, STS-V advances).
 
static int arm_final_sp3 (uint32_t poll_ip)
 Arm the delayed SP3-ND RX for the phone's Final at STS index Poll_STS_Index+ALIRO_FINAL_SLOT_OFFSET, packing the g_armed_final_* STS (no KDF).
 
static void resp_tx_done (const dwt_cb_data_t *cb)
 TX-done (TXFRS) callback: our Response_0 left the antenna, so arm the Final RX one slot later, then run the block's deferred Pre-POLL decode in the idle.
 
static void prepoll_rx_rearm (const dwt_cb_data_t *cb)
 RX callback for Pre-POLL listen and POLL/Final results.
 
int ccc_prepoll_prewarm (uint8_t channel, uint8_t preamble_code)
 Pre-apply the expected session PHY (radio configured, TRX off, RX not armed) so a following ccc_prepoll_listen() with the same params skips the dwt_configure long pole.
 
int ccc_prepoll_listen (uint8_t channel, uint8_t preamble_code)
 BENCH: bring up a raw continuous SP0 receiver for the CCC Pre-POLL (target only).
 
void ccc_prepoll_stop (void)
 Stop the Pre-POLL listener: close the self-rearm listen-gate, then force the radio off (target only).
 

Variables

static uint32_t g_rx_arms
 Count of intercepted RX-arms; the first CCC_RX_LOG_ARMS are logged.
 
static uint32_t g_poll_sts_index
 Apple's Poll_STS_Index from the most recent decoded Pre-POLL.
 
static bool g_have_poll_index
 True once a Pre-POLL has handed us a fresh g_poll_sts_index.
 
static bool g_await_poll
 True while the SP3 RX is armed for the POLL (next RX event = its result).
 
static uint32_t g_armed_index
 Poll_STS_Index already armed once — a re-detected Pre-POLL (same index) must not re-arm late in the block.
 
static uint32_t g_prepoll_ip
 Pre-POLL Ipatov timestamp of the in-flight POLL arm (for the gap d= log).
 
static uint32_t g_poll_stride
 Poll_STS_Index delta between consecutive Pre-POLLs (the block stride, ~96); primes the next block's dURSK.
 
static bool g_warm_valid
 STS pre-derived in the idle for the predicted next POLL index, so the SP3 arm packs it directly with no KDF on the 2 ms path.
 
static uint32_t g_pp_logged
 Verbose Pre-POLL trace budget; file-scope so a session start can reset it (below), else the cap latches after the first ranging attempt and later sessions log nothing.
 
static uint8_t g_warm_resp_dursk [CCC_DURSK_LEN]
 Response_0 STS (Poll_STS_Index+1): same-round dURSK plus index+1 STS-V, pre-derived in the idle so the TX path runs no KDF.
 
static uint8_t g_warm_final_dursk [CCC_DURSK_LEN]
 Final STS (Poll_STS_Index+2): the phone's Final RFRAME one slot after our Response, pre-derived and snapshotted at the POLL arm so no KDF runs.
 
static bool g_await_final
 True while the SP3 RX is armed for the Final (next RX event = its result).
 
static uint32_t g_poll_ip_for_final
 POLL RMARKER of the in-flight round, so the TXDONE can anchor the Final window.
 
static uint64_t g_t_poll_rx
 The three responder DS-TWR timestamps, full 40-bit DTU: POLL RX (t2), Response TX (t3), Final RX (t6).
 
static int32_t g_final_sts_verdict = -1
 Responder-side DS-TWR intervals (reply1 = t3-t2, round2 = t6-t3) SNAPSHOTTED at the Final RX capture, when t2/t3/t6 are all from the same round.
 
static bool g_uad_cached
 Per-session Pre-POLL decrypt constants (mUPSK1 + UAD-derived src/dest/keysource): depend only on URSK + STS_Index0, derived once and reused.
 
static uint8_t g_pp_stash [64]
 Pre-POLL frame stashed at RX for a DEFERRED decode: the ~2 ms decrypt+derive must not run between the Pre-POLL and the POLL.
 
uint32_t g_ccc_dbg_decode
 try_prepoll() decode duration (hi32 ~4 ns units), reported on the ARM-FAIL line to attribute the pre-arm latency.
 
static volatile bool g_listen_gate
 Listen-gate: true only while the Pre-POLL listener is up.
 
static uint8_t g_resp_payload [4]
 Dummy Response_0 body — NOT radiated (SP3/ND sends STS only), but the TX sequence writes a frame body before dwt_starttx.
 

Detailed Description

— responder-RX CCC STS substitution: woz_uwb_arm_rx() programs the CCC STS on each RX-arm; target only.

Macro Definition Documentation

◆ CCC_RX_DELTA_SEED

#define CCC_RX_DELTA_SEED   1299

Sweep CENTER = Apple's UWB_Time0 / slot-duration; the offset is the constant slot gap between our time0 and Apple's UWB_Time0, searched not derived.

center: 0x0027a4d4 µs / 2 ms (heuristic; retunable).

◆ CCC_RX_DIAG_N

#define CCC_RX_DIAG_N    110u

Re-arm the SP0 receiver after each RX event; the woz_uwb_set_callbacks shim runs ccc_shim_rx_try_prepoll first, so here we keep the plain SP0 receive listening.

per-catch lines to emit (~1 catch/block now — spans a full sweep).

◆ CCC_RX_DWELL

#define CCC_RX_DWELL    1u

catches held per Δ candidate (1 = fast full-range sweep; bump if it cycles past).

\

◆ CCC_RX_PACK_SELFTEST

#define CCC_RX_PACK_SELFTEST   0 /* answered 2026-07-09: pack_iv needed the reverse (applied). */

BENCH PROBE (CCC_RX_PACK_SELFTEST) — dump the STS register lanes a known V lands in, to pin the pack_iv byte order.

Not shippable.

◆ CCC_RX_POLL_LEAD

#define CCC_RX_POLL_LEAD   40000u

Open the SP3 POLL window this many hi32 units (~160 µs) BEFORE the POLL RMARKER (Pre-POLL.

  • 1 slot), leaving settle time so a too-late window doesn't preamble-miss.

◆ CCC_RX_SLOTS_PER_BLOCK

#define CCC_RX_SLOTS_PER_BLOCK    96u

RESIDUE-COMPLETE alignment-offset sweep (slot = current_slot() + Δ): the STS index is linear at one per slot, so sweep the constant offset Δ.

192 ms / 2 ms — phase-log modulus only, NOT the index stride.

Function Documentation

◆ arm_final_sp3()

static int arm_final_sp3 ( uint32_t  poll_ip)
static

Arm the delayed SP3-ND RX for the phone's Final at STS index Poll_STS_Index+ALIRO_FINAL_SLOT_OFFSET, packing the g_armed_final_* STS (no KDF).

EXPERIMENT-2RESP: the 1:1 baseline uses POLL + 2 slots / index+2; a 2-responder round puts the Final at POLL + 3 (responder 1's silent slot sits at POLL + 2).

◆ ccc_shim_rx_awaiting_final()

bool ccc_shim_rx_awaiting_final ( void  )

Return true if CCC UWB reception is awaiting a FINAL frame from the responder, false otherwise.

True while an SP3 Final RX is armed.

◆ ccc_shim_rx_awaiting_poll()

bool ccc_shim_rx_awaiting_poll ( void  )

Returns true if the responder is awaiting the POLL frame after a successful Pre-POLL decode.

True while an SP3 POLL RX is armed (the next RX-good event is its result).

Returns
true if awaiting POLL, false otherwise.

◆ ccc_shim_rx_deadline_pending()

bool ccc_shim_rx_deadline_pending ( void  )

Return true if CCC UWB reception is awaiting either POLL or FINAL frame from the responder, false otherwise.

True while the responder still owes the current ranging block a radio event: an armed POLL RX (a few slots out) or an armed Final RX (~2 ms out).

◆ ccc_shim_rx_log_reset()

void ccc_shim_rx_log_reset ( void  )

Reset all Per-POLL state: arm count, index tracking, STS warm cache, and optional lock-sweep diagnostic counters; called on entry to a new Pre-POLL listen.

Reset the responder-RX wrap's first-N-arm log budget (target only).

◆ ccc_shim_rx_notify_rx()

void ccc_shim_rx_notify_rx ( uint32_t  status)

Log one RX event for the optional lock-sweep diagnostic (CONFIG_CCC_RX_LOCK_SWEEP); tracks CPER (STS correlation fail flag) and dwells candidate indices until lock achieved or full cycle exhausted.

Feed each RX event's status word to the empirical STS-index tracker (target only).

Parameters
statusDW3000 status register value.

◆ ccc_shim_rx_try_prepoll()

void ccc_shim_rx_try_prepoll ( uint16_t  datalength)

Pre-POLL RX entry (from the RX-good shim): stash the frame and DEFER its ~2 ms decrypt+derive off the Pre-POLL->POLL critical path.

Decode a received SP0 Pre-POLL frame to read Apple's exact POLL STS index (target only).

The decode only feeds the NEXT block's warm (the SP3 arm keys on the pre-warmed prediction, not this block's fresh index), so it has the ~190 ms idle to run in. Running it inline — on the single DW3000 workqueue, ahead of the queued POLL RX-OK callback — pushed the Response TX arm past its slot (dx-now < 0 => HPDWARN). So read the bytes now (cheap SPI), then:

  • bootstrap (no warm yet): decode inline to seed the first arm's STS — this block is not armed, so blocking its POLL is harmless;
  • steady state: mark pending; prepoll_rx_rearm runs prepoll_decode after the Response TX is armed. A pending decode orphaned by a missed POLL is flushed on the next Pre-POLL so the warm never goes more than one block stale.

◆ final_data_decode()

static void final_data_decode ( const uint8_t *  frame,
uint16_t  datalength 
)
static

Decode a received Final_Data (SP0, msg_id=02): dUDSK-decrypt and parse the initiator's ranging timestamps; not time-critical.

Parameters
framePointer to the received frame buffer.
datalengthLength of the frame in bytes.

◆ gated_rxenable()

static int32_t gated_rxenable ( int32_t  mode)
static

Gate-checked RX arm for every self-rearm site below; refuses once the listen-gate is closed.

Arms the radio directly: each caller has just programmed the STS for the window it is opening, so re-running woz_uwb_arm_rx would overwrite it with the current slot's.

◆ pack_iv()

static void pack_iv ( dwt_sts_cp_iv_t *  out,
const uint8_t  sts_v[CCC_STS_V_LEN] 
)
static

Pack a 16-byte STS-V into the DW3000 STS-IV image (whole-16 reverse then per-word LE, same as pack_key).

Parameters
outPointer to the DW3000 STS-IV register image.
sts_v16-byte STS vector to pack.

◆ prepoll_decode()

static void prepoll_decode ( const uint8_t *  frame,
uint16_t  datalength 
)
static

Decode a received Pre-POLL frame: verify MHR and message ID, decrypt SP0 payload, cache UAD-derived keys, extract Poll_STS_Index and stride, and pre-warm the next block's STS triplet (POLL, Response_0, Final) to eliminate KDF latency from the critical path.

Parameters
framePointer to the received frame buffer.
datalengthLength of the frame in bytes.

◆ prepoll_rx_rearm()

static void prepoll_rx_rearm ( const dwt_cb_data_t *  cb)
static

RX callback for Pre-POLL listen and POLL/Final results.

Re-arms SP0 by default, or arms SP3/ND for POLL if a warmed index is ready, or fires the delayed-TX Response_0 and Final RX arm on valid POLL CPER; logs free-running timing and optionally defers Pre-POLL decode to warm the next block.

Variable Documentation

◆ g_ccc_dbg_decode

uint32_t g_ccc_dbg_decode
extern

try_prepoll() decode duration (hi32 ~4 ns units), reported on the ARM-FAIL line to attribute the pre-arm latency.

try_prepoll() decode duration (hi32 ~4 ns units), reported on the ARM-FAIL line to attribute the pre-arm latency.

Decode-cost probe: last try_prepoll() duration, hi32 (~4 ns) units.

◆ g_final_sts_verdict

int32_t g_final_sts_verdict = -1
static

Responder-side DS-TWR intervals (reply1 = t3-t2, round2 = t6-t3) SNAPSHOTTED at the Final RX capture, when t2/t3/t6 are all from the same round.

final_data_decode consumes these instead of recomputing from the live globals: the Final_Data lands only after the NEXT round's POLL/ Response have overwritten t2/t3, so recomputing there mixes this round's t6 with the next round's t3 and corrupts round2 (observed as km-scale distances). g_final_round_valid gates the compute (consume-once): a Final_Data with no fresh Final capture is dropped, not turned into garbage.

Cache of the STS key (dURSK) currently loaded in the STS_KEY registers, so the per-arm sts_key_load() skips the ~258 us of redundant key writes when the per-cycle-constant dURSK is unchanged. Defined here (ahead of ccc_shim_rx_log_reset) so a session start can invalidate it.

◆ g_listen_gate

volatile bool g_listen_gate
static

Listen-gate: true only while the Pre-POLL listener is up.

ccc_prepoll_stop() closes it so no callback rearm can re-enable RX after a session stop; ccc_prepoll_listen() reopens it before its arm.