From 19e048f8dd9346ca6cad8625ca4cebc47b957ae3 Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Sun, 7 Jun 2026 21:59:03 +0300 Subject: [PATCH] Honour HT-MCS in send_packet (gated by DEVOURER_TX_HT_MCS) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The IEEE80211_RADIOTAP_MCS branch in RtlJaguarDevice::send_packet parsed the bandwidth and SGI fields out of the radiotap MCS field but never set `fixed_rate`. The result was that an HT-MCS-only radiotap header (no legacy RATE field) fell through to the function-top default of MGN_1M, so the chip transmitted 1 Mbps CCK regardless of the MCS index — PR #80's README documented this as the "HT MCS 0 doesn't reach the air" decisive correction that locked the precoder PoC to legacy 6 Mbps OFDM. The VHT branch already does the right thing (line 119: `fixed_rate = MGN_VHT1SS_MCS0 + ((nss-1)*10 + mcs)`); the HT case just needed the equivalent mapping (`fixed_rate = MGN_MCS0 + radiotap_byte_2` when HAVE_MCS is set). MGN_MCS0..MGN_MCS31 = 0x80..0x9F map cleanly through MRateToHwRate to DESC_RATEMCS0..MCS31. No translation layer needed. Gated behind DEVOURER_TX_HT_MCS=1 — flipping this unconditionally would silently change the regression matrix's rate sweeps (currently expecting 1 Mbps CCK on HT-MCS frames) and would break PR #80's PoC plan that explicitly locks to legacy 6M OFDM as the precoder's on-air carrier. Defaults are unchanged. Verification * Unit / regression: the existing test suite is byte-identical without the env var set (no code path change). * Hardware (next-step): set DEVOURER_TX_HT_MCS=1, transmit a probe request with an HT-MCS radiotap header, expect the RX-side data_rate field to surface in the HT range (0x0c..0x2b for MCS0..MCS31) rather than 0x00 (1M CCK). The radiotap-builder PR (F3 follow-up) will provide the CLI to drive this end-to-end. Co-Authored-By: Claude Opus 4.7 --- src/RtlJaguarDevice.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/RtlJaguarDevice.cpp b/src/RtlJaguarDevice.cpp index 88d22d5..25b7738 100644 --- a/src/RtlJaguarDevice.cpp +++ b/src/RtlJaguarDevice.cpp @@ -71,6 +71,7 @@ bool RtlJaguarDevice::send_packet(const uint8_t *packet, size_t length) { break; case IEEE80211_RADIOTAP_MCS: { + u8 mcs_known = iterator.this_arg[0]; u8 mcs_flags = iterator.this_arg[1]; uint8_t mcs_bw_field = mcs_flags & IEEE80211_RADIOTAP_MCS_BW_MASK; @@ -87,6 +88,23 @@ bool RtlJaguarDevice::send_packet(const uint8_t *packet, size_t length) { } else { sgi = 0; } + + /* DEVOURER_TX_HT_MCS=1: honour the HT MCS index from radiotap byte 2 + * and set fixed_rate accordingly. Without this knob the historic + * behaviour kicks in — fixed_rate stays at the MGN_1M default and the + * chip transmits 1 Mbps CCK regardless of the HT-MCS field (see PR + * #80's README: "send_packet only wires fixed_rate from the radiotap + * RATE/VHT fields — never the HT MCS index"). Gated because flipping + * this unconditionally would silently change the regression matrix's + * rate sweeps and the precoder PoC's locked-in 6 Mbps OFDM carrier. */ + static const bool ht_mcs_enabled = + std::getenv("DEVOURER_TX_HT_MCS") != nullptr; + if (ht_mcs_enabled && (mcs_known & IEEE80211_RADIOTAP_MCS_HAVE_MCS)) { + uint8_t mcs_index = iterator.this_arg[2]; + if (mcs_index <= 31) { + fixed_rate = MGN_MCS0 + mcs_index; + } + } } break; case IEEE80211_RADIOTAP_VHT: {