Repository navigation
cell M6: UE re-attachment after a DU restart - #536
Conversation
A restarted DU's grant maps begin again at a low epoch on a new superframe count; the UE's receiver refused them as old and the UE was never heard in the new grid. GrantMapReceiver::offer_after_restart: a map at a strictly lower epoch but due within 16 superframes of the beacon carrying it starts the receiver over — asked only for CELL_UE_REATTACH_SF (200) after the UE's clock fit confirmed a step of the DU's TSF (#535). The UE then resets what belonged to the old DU instance, as on a switch: DL HARQ space, MR generation, neighbour list, gap parking, pending handover IEs (cell.ue.reattach). Also: the UE's cell.dl.rx in_slot now judges against its grant's DL slot, not the position matching its id (a CU move showed as DL out of slot); the doubled DAPS heading in docs/cell-m6.md. Rig (testrig scripts/m6_fitstep.sh, restart case, 3/3): one re-attach per run, the restarted DU hears the UE 99.8-100 % of superframes from service start, in slot 99.8 %, first in-slot uplink at service start; UE DL in its granted slot 100 %. Regression: handover 2/2 + fallback 1/1, measgap 1/1, phase planning 1/1; no spurious re-attach. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PR Summary by QodoRe-attach static UEs after a DU restart
AI Description
Diagram
High-Level Assessment
Files changed (4)
|
Code Review by Qodo
1.
|
…based on the new count; ungranted DL still logged; forged-map exposure documented - next_gap_sf and no_step_until_sf return to their sentinels on a re-attach (they were on the old DU's count). - A handover awaiting a target grant when the target restarted keeps its way back, its deadline re-based on the restarted target's count. - cell.dl.rx is logged without a grant in force too (in_slot 0, granted 0). - docs: the forged-map exposure the restart path adds, and grant-map authentication as an open item. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Qodo review addressed in d40aed3:
ctest 95/95. Rig re-run ( |
|
CI green; all four Qodo findings resolved. The remaining item is the high-level alternative (a DU instance identifier in grant maps), for which Qodo's own recommendation is the clock-step-gated epoch rule used here for the static-UE scope, without a wire change. Map authentication and RACH re-join are listed as open in docs/cell-m6.md. Merging with skip-qodo-gate. |
M6 (#260, EPIC #254): the grant map IE is now authenticated. This closes the open item from #536. ## Why The grant map was the only scheduling IE without a tag. A forged map could already move a UE's slots, and since #536 it could also reset the UE's grant state through the DU-restart path. ## What - **GM IE v2** (`src/cell/GrantMap.h`) has the layout `… crc16 tag(8)`. The tag is the first 8 bytes of `mac('G' || body..crc)` under the cell key (`CELL_PSK`), the same scheme as the NL/MR/handover IEs. - `encode_grant_ie`, `decode_grant_ie` and `find_grant_map` take the `Mac`. It is mandatory, so every caller decides. - With a key, a forged map or an untagged one (zeros) is refused, using a constant-time compare. - Without a key (an unauthenticated development cell, or a passive witness), only the structure is checked. - v1 maps are refused. A failing Mac airs nothing. - `Mac` moves from `Rach.h` to `GrantMap.h`, which `Rach.h` includes. - **Callers:** - the DU's `build_du_beacon(map, mac, …)`; - the UE (serving maps, the handover target's maps, the restart path), with `auth`; - the witness, with `CELL_PSK` if it has one. - **The UE** counts serving maps taken and refused: `gm_ok` / `gm_refused` in `cell.ue.done`, and the first refusal logs `cell.ue.gm_refused`. - **Selftests (cell, cell_node):** round trip with a key, another key, tag bit flip, zero tag at a keyed receiver, structure-only without a key, an edited map re-CRC'd under another key, v1 refused, a failing Mac, and the 24-grant map fitting 255 bytes (226). ## A correction this PR makes The M5/M6 rig scripts never set `CELL_PSK`. So the handover, DAPS, inter-frequency, measurement-gap, phase-planning and clock-fit rig results ran as an **unauthenticated development cell**: zero tags, not checked. F1 was keyed throughout. `docs/cell-m6.md` now says so. The testrig scripts give every cell role one key, and the suite was re-run keyed (below). ## Rig **Wrong-key test** (testrig `scripts/m6_gmauth.sh`, 2/2). Every role holds the cell key except UE2: | | maps taken | maps refused | heard in slot by its DU | |---|---|---|---| | UE1 (cell key) | 586–587 | 0 | 99.8 % | | UE2 (another key) | 0 | 586–587 | never | **The M6 suite keyed, all pass.** No keyed UE refused a map (36 UE runs), apart from UE2 above. - same-channel handover: 2, plus fallback 1; - inter-frequency handover: 2; - DAPS: 2 + 2 (missing uplinks 0/0 vs 11/16 without); - measurement-gap handover: 2; - phase planning: 2; - clock fit, transient jump and DU restart with re-attach: 1 + 1. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…he TBTT protection (#538) M6 (#260, EPIC #254): a UE that joined through the RACH re-joins after its DU restarts. Closes the open item from #536. It also includes a Kestrel driver fix that this work turned up. ## Why A RACH-joined UE holds an id that a restarted DU instance doesn't know. After re-attaching (#536), the UE was in no map and was never heard again. ## What - **UE (`examples/cell`):** a joined UE that the authentic map in force has not granted for `CELL_UE_REJOIN_SF` (30) superframes drops its id and joins again, logged as `cell.ue.rejoin`. That covers both a DU that restarted and one that released it. - Its uplink beacon goes off, its ACK responder is cleared, and the old id's DL HARQ space goes. - It gets a new RACH client with a **fresh nonce**. A DU answers a repeated (MAC, nonce) request from its cache, which would be the old id. - It keeps the stable joiner MAC. - The rule is skipped mid-handover. Static UEs never re-join. - **Kestrel driver fix (`HalKestrel::stop_beacon`):** `StopBeacon` cleared the beacon-TX enable but left the TBTT protection that `StartBeacon` turns on (`TBTT_PROHIB_EN`, `BRK_SETUP`). The port kept holding injected frames to its old beacon grid. - On the rig, every re-join msgA arrived at a fixed ~29.9 ms into the superframe, past the RACH slot. It drifted with the UE's own crystal, whatever offset the UE drew. - An A/B with the beacon left running landed in slot. - `stop_beacon` now clears both bits. The value of the ~13 ms hold, presumably `BCN_HOLD`, is not separately established. - **Docs:** `docs/cell-m6.md` gains a section on re-joining through the RACH, and the `StopBeacon` contract note in `docs/scheduled-mac.md` covers Kestrel. ## Rig (testrig `scripts/m6_rejoin.sh`, keyed, 3/3 pass) UE1 joins cell 1 through the RACH, and DU1 is killed and restarted under it 35 s in. | KPI | value | |---|---| | re-attach, then one re-join | every run | | second join after the restarted DU's service start | −4 to +28 sf (median 11) | | its RACH attempts | 4–8 (the first fall in the restarted DU's acquisition) | | new id's uplink at the restarted DU, from 10 sf after the join | heard 100 %, in slot 100 % | **Counterfactual (`CELL_UE_REJOIN_SF=0`):** the UE keeps a dead id and is never heard. **Regression for the `StopBeacon` change:** - inter-frequency handover 2, plus fallback 1 (each a beacon stop and restart); - measurement-gap handover 2; - clock-fit restart and transient 1 + 1; - same-channel handover 1. All pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
M6 (#260, EPIC #254): a UE re-attaches to its DU after the DU restarts. This closes the open item from #535.
Why
#535 made the UE's clock follow a restarted DU. But the restarted DU's grant maps begin again at a low epoch on a new superframe count, and the UE's
GrantMapReceiverrefused them as old. The UE kept its old map and was never heard in the new grid (0 uplinks on the rig).What
GrantMapReceiver::offer_after_restart(m, beacon_sf, window = 16): a map at a strictly lower epoch than the newest accepted, but due within 16 superframes of the beacon carrying it, starts the receiver over.newest_epoch()getter.CELL_UE_REATTACH_SF(200) superframes after the UE's clock fit confirms a step of the DU's TSF (cell M6: the UE's clock fit survives a jump of its DU's TSF (TsfSync step detection) #535);cell.ue.reattach.cell.dl.rxin_slotnow judges against the DL slot of its grant in force, not the position matching its id. Any CU move showed as DL out of slot (0.05), although the frames were right.docs/cell-m6.mdgains a "Re-attachment after a DU restart" section and a fix for the doubled DAPS heading left by cell M6: the UE's clock fit survives a jump of its DU's TSF (TsfSync step detection) #535. RACH-joined UEs across a DU restart are listed as open.Rig (testrig
scripts/m6_fitstep.sh: DU1 killed and restarted under its UE, 3/3 pass)Residual: grant maps carry no tag (they never did), so the epoch rule orders maps; it doesn't authenticate them.
🤖 Generated with Claude Code