Skip to content

F3: Selectable stream-carrier rate (Legacy / HT-MCS / VHT)#90

Merged
josephnef merged 2 commits into
masterfrom
stream-carrier-rate
Jun 7, 2026
Merged

F3: Selectable stream-carrier rate (Legacy / HT-MCS / VHT)#90
josephnef merged 2 commits into
masterfrom
stream-carrier-rate

Conversation

@josephnef
Copy link
Copy Markdown
Collaborator

@josephnef josephnef commented Jun 7, 2026

Summary

Replaces the hard-coded 13-byte 6-Mbps legacy-OFDM radiotap header in StreamDuplexDemo and StreamTxDemo with a DEVOURER_STREAM_RATE-controlled builder that emits Legacy / HT-MCS / VHT carriers under one env var.

Env var grammar

DEVOURER_STREAM_RATE=6M|9M|12M|18M|24M|36M|48M|54M           # Legacy
DEVOURER_STREAM_RATE=MCS0..MCS31                              # HT
DEVOURER_STREAM_RATE=VHT1SS_MCS0..VHT4SS_MCS9                 # VHT

DEVOURER_STREAM_BW=20|40|80|160      # bw (Legacy ignores; HT honours 20/40; VHT honours 20/40/80/160)
DEVOURER_STREAM_SGI=1                # short GI
DEVOURER_STREAM_LDPC=1               # FEC = LDPC
DEVOURER_STREAM_STBC=1               # STBC

Default (env var unset) = 6M legacy, bit-identical to the historic kRadiotapLegacy6M[13].

Wire-format facts the chip relies on

send_packet (RtlJaguarDevice.cpp:42):

radiotap length vht rate_id builder mode
13 false 8 Legacy or HT-MCS
> 13 true 9 VHT

The radiotap iterator parses all fields regardless of vht — so a 13-byte HT-MCS radiotap (it_present = MCS \| TX_FLAGS, no RATE) is correctly parsed via the IEEE80211_RADIOTAP_MCS case and stays on the HT path. Cross-checked against txdemo/main.cpp's own 13-byte HT beacon_frame[] layout (line 300).

VHT uses the same 22-byte layout as txdemo/main.cpp's DEVOURER_TX_VHT=1 path (lines 374-409): it_present = VHT \| TX_FLAGS, known = STBC \| GI \| BW, MCS/NSS in user-0 nibbles.

F1 dependency for HT mode

HT-MCS in send_packet is gated by DEVOURER_TX_HT_MCS=1 (F1, PR #88, merged). parse_stream_rate_env() emits a stderr warning if an HT rate is parsed without that gate so users don't silently fall back to 1M CCK:

<stream-radiotap>warning: DEVOURER_STREAM_RATE=MCS5 requires DEVOURER_TX_HT_MCS=1 to actually fly at the requested rate (otherwise send_packet falls back to 1M CCK)

VHT has no such gate — send_packet's VHT branch always honours the VHT info field.

Hardware verification (8/8 PASS)

TX = RTL8812AU (0bda:8812), RX = TP-Link RTL8821AU (2357:0120, DEVOURER_VID=0x2357 DEVOURER_PID=0x0120). ~200 stream frames per case, RX-side <devourer-stream>rate=N index parsed.

Case Env Channel Expected rate index Frames RX Observed
baseline (none) 6 4 (DESC_RATE6M) 183 4
Legacy 6M STREAM_RATE=6M 6 4 197 4
Legacy 24M STREAM_RATE=24M 6 8 (DESC_RATE24M) 192 8
Legacy 54M STREAM_RATE=54M 6 11 (DESC_RATE54M) 193 11
HT MCS5 STREAM_RATE=MCS5 TX_HT_MCS=1 6 17 (DESC_RATEMCS5) 175 17
VHT 1SS MCS3 BW20 STREAM_RATE=VHT1SS_MCS3 STREAM_BW=20 6 47 (DESC_RATEVHTSS1MCS3) 185 47
VHT 1SS MCS3 BW20 STREAM_RATE=VHT1SS_MCS3 STREAM_BW=20 36 47 192 47
VHT 1SS MCS7 BW80 STREAM_RATE=VHT1SS_MCS7 STREAM_BW=80 36 51 (DESC_RATEVHTSS1MCS7) 192 51

Every case: 100 % of received frames carry the expected rate index. Default behaviour (no env var) is byte-identical to master.

What ships

  • src/RadiotapBuilder.{h,cpp}StreamRateCfg struct + build_stream_radiotap(cfg) -> std::vector<uint8_t> + parse_stream_rate_env() -> StreamRateCfg. Three internal builders (legacy 13B, HT 13B, VHT 22B) keyed off cfg.mode.
  • StreamDuplexDemo and StreamTxDemo switched from each demo's private kRadiotapLegacy6M[13] constant to a kStreamRadiotap vector built once at static-init from the env var.
  • PrecoderDemo intentionally untouched — its PoC plan explicitly locks the carrier to 6M.
  • CMakeLists.txt updated to compile the helper into WiFiDriver.

🤖 Generated with Claude Code

josephnef and others added 2 commits June 7, 2026 22:11
The two stream demos (StreamDuplexDemo, StreamTxDemo) hard-coded a 13-byte
6-Mbps legacy-OFDM radiotap header. With the precoder stream link's FEC
layers landed (#86 RaptorQ, #87 RLC), the carrier rate is now a useful
robustness-vs-throughput knob.

This v1 ships legacy-OFDM rate switching only:

  DEVOURER_STREAM_RATE=6M|9M|12M|18M|24M|36M|48M|54M   (default 6M)
  DEVOURER_STREAM_RATE=12|18|24|36|48|72|96|108        (500 kbps units)

Higher-rate HT-MCS / VHT paths deliberately stay out of this PR — they
need a non-13-byte radiotap and would switch send_packet onto its VHT
branch via the existing `radiotap_length != 0x0d` heuristic. F1 (#88)
unlocks the HT-MCS path; a follow-up can extend RadiotapBuilder to emit
HT and VHT headers behind the same env var.

  - New src/RadiotapBuilder.{h,cpp}: small helper exposing
      build_legacy_radiotap(rate_500kbps) -> std::array<uint8_t, 13>
      parse_stream_rate_env() -> uint8_t
  - Wired into StreamDuplexDemo and StreamTxDemo, replacing each
    binary's private kRadiotapLegacy6M[13] constant. The on-air length
    stays at 13 bytes so send_packet keeps these on the legacy path.
  - PrecoderDemo is intentionally NOT touched — its PoC plan locks the
    carrier to 6M.

Defaults are unchanged on every code path, so the existing regression
matrix and tools/precoder/precoder_stream_roundtrip.py stay byte-identical
without setting the new env var.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the legacy-only v1 API with a single StreamRateCfg / build_stream_radiotap
pair that emits three radiotap variants under the same DEVOURER_STREAM_RATE
env var:

  Legacy:  6M | 9M | 12M | 18M | 24M | 36M | 48M | 54M   (13-byte radiotap)
  HT:      MCS0 .. MCS31                                  (13-byte radiotap)
  VHT:     VHT1SS_MCS0 .. VHT4SS_MCS9                     (22-byte radiotap)

Plus DEVOURER_STREAM_BW (20|40|80|160), _SGI, _LDPC, _STBC.

Wire-format facts the chip-side send_packet (RtlJaguarDevice.cpp:42) relies
on:
  - radiotap_length == 13  -> vht=false, rate_id=8 (legacy or HT path)
  - radiotap_length  > 13  -> vht=true,  rate_id=9 (VHT path)
  - the iterator parses *all* fields regardless of vht — so a 13-byte
    HT-MCS radiotap (presence = MCS|TX_FLAGS, no RATE) is correctly
    parsed via the IEEE80211_RADIOTAP_MCS case and stays on rate_id=8.
    Verified against txdemo/main.cpp's own 13-byte HT beacon_frame[].

HT-MCS requires DEVOURER_TX_HT_MCS=1 (F1 gate in send_packet) to actually
fly at the requested rate; parse_stream_rate_env() emits a stderr warning
when an HT rate is parsed without that gate so users don't silently fall
back to 1M CCK. VHT has no such gate — send_packet's VHT branch
unconditionally honours the VHT info field.

Builders mirror exact field layouts already in use:
  - HT 13-byte MCS layout cross-checked against txdemo/main.cpp's
    beacon_frame[] (line 300) — same it_present, same MCS field positions
  - VHT 22-byte layout cross-checked against txdemo/main.cpp's
    DEVOURER_TX_VHT path (lines 374-409) — same known mask, same flag
    bits, same coding/mcs_nss positions

Default behaviour (env var unset) is unchanged: 6M legacy OFDM,
bit-identical to the historic kRadiotapLegacy6M[13] constant.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@josephnef josephnef changed the title F3: Selectable stream-carrier legacy-OFDM rate (DEVOURER_STREAM_RATE) F3: Selectable stream-carrier rate (Legacy / HT-MCS / VHT) Jun 7, 2026
@josephnef josephnef merged commit 08e1ce4 into master Jun 7, 2026
5 checks passed
@josephnef josephnef deleted the stream-carrier-rate branch June 7, 2026 19:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant