This commit is contained in:
2026-09-13 13:30:21 +08:00
commit a6bbd520cf
2744 changed files with 1598795 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
#pragma once
/* TriggerParse — decode a received 802.11ax Trigger frame.
*
* A Trigger is an 802.11 CONTROL frame (FC type 1, subtype 2) aired in a
* legacy/non-HT PPDU, so ANY monitor — including an 11ac witness that cannot
* decode HE data — captures the bytes and this parser reads them. That is the
* validation lever for the F2P/UL_FIXINFO scheduled-UL path: point rxdemo at
* the AP and confirm the aired trigger's fields match what was commanded, which
* also empirically resolves the fw's RU/mode encoding.
*
* Symmetric with build_basic_trigger() (TriggerTwt.h); the round-trip is a
* headless selftest (tests/trigger_parse_selftest.cpp). Header-only, no deps,
* neutral namespace — same shape as RxPacket.h / RadiotapPeek.h. */
#include <cstddef>
#include <cstdint>
namespace devourer {
struct TriggerUserInfo {
uint16_t aid12 = 0;
uint8_t ru_alloc = 0;
bool ldpc = false;
uint8_t mcs = 0;
bool dcm = false;
uint8_t ss = 0;
int8_t tgt_rssi_dbm = 0;
};
struct TriggerInfo {
uint8_t trigger_type = 0; /* 0 = Basic, 1 = Beamforming Report Poll, ... */
uint16_t ul_length = 0;
uint8_t ul_bw = 0; /* 0 = 20, 1 = 40, 2 = 80, 3 = 160 */
uint8_t gi_ltf = 0;
uint8_t num_he_ltf = 0;
uint8_t ap_tx_power = 0;
uint8_t ta[6] = {0}; /* transmitter (AP) address */
uint8_t ra[6] = {0}; /* receiver address */
uint8_t n_users = 0; /* User Info fields decoded (capped at cap below) */
TriggerUserInfo users[8];
};
/* True iff `frame` (an 802.11 frame body, no radiotap, no FCS) is a Trigger
* control frame. Cheap FC check for an RX-loop fast path. */
inline bool is_trigger_frame(const uint8_t *frame, size_t len) {
/* FC byte0: version[1:0]=0, type[3:2]=01(control), subtype[7:4]=0010 -> 0x24. */
return frame != nullptr && len >= 2 && frame[0] == 0x24;
}
/* Parse a Basic-Trigger-style frame into `out`. Returns false if the frame is
* not a trigger or is too short for the fixed header + one User Info. The FCS
* (last 4 bytes on a monitor capture that includes it) is ignored; pass either
* with or without it. Up to 8 User Info fields are decoded. */
inline bool parse_trigger(const uint8_t *frame, size_t len, TriggerInfo &out) {
/* FC(2) + Duration(2) + RA(6) + TA(6) + Common(8) = 24, then >=1 user (5). */
if (!is_trigger_frame(frame, len) || len < 24 + 5)
return false;
out = TriggerInfo{};
for (int i = 0; i < 6; ++i) {
out.ra[i] = frame[4 + i];
out.ta[i] = frame[10 + i];
}
/* Common Info: little-endian 8-byte bitfield (see build_basic_trigger). */
uint64_t common = 0;
for (int i = 0; i < 8; ++i)
common |= static_cast<uint64_t>(frame[16 + i]) << (8 * i);
out.trigger_type = static_cast<uint8_t>(common & 0xf);
out.ul_length = static_cast<uint16_t>((common >> 4) & 0xfff);
out.ul_bw = static_cast<uint8_t>((common >> 18) & 0x3);
out.gi_ltf = static_cast<uint8_t>((common >> 20) & 0x3);
out.num_he_ltf = static_cast<uint8_t>((common >> 23) & 0x7);
out.ap_tx_power = static_cast<uint8_t>((common >> 28) & 0x3f);
/* User Info fields until the frame ends. Each is 5 common bytes + a
* Trigger-Dependent User Info whose size is set by the trigger type (Basic=1
* byte, per 802.11ax). A Padding field (AID12 == 4095) or a sub-6-byte tail
* (a capture's trailing FCS) ends the list. */
const size_t tdep = out.trigger_type == 0 ? 1 : 0; /* Basic Trigger: 1 byte */
const size_t ustride = 5 + tdep;
size_t o = 24;
out.n_users = 0;
while (o + ustride <= len && out.n_users < 8) {
uint64_t ui = 0;
for (int i = 0; i < 5; ++i)
ui |= static_cast<uint64_t>(frame[o + i]) << (8 * i);
const uint16_t aid = static_cast<uint16_t>(ui & 0xfff);
/* AID12 == 0xFFF marks a Padding User Info — end of the user list. */
if (aid == 0xfff)
break;
TriggerUserInfo &u = out.users[out.n_users];
u.aid12 = aid;
u.ru_alloc = static_cast<uint8_t>((ui >> 12) & 0xff);
u.ldpc = ((ui >> 20) & 0x1) != 0;
u.mcs = static_cast<uint8_t>((ui >> 21) & 0xf);
u.dcm = ((ui >> 25) & 0x1) != 0;
u.ss = static_cast<uint8_t>((ui >> 26) & 0x3f);
/* Target RSSI subfield [38:32]: 0..90 = -110..-20 dBm; 127 = max power (0). */
uint8_t rssi7 = static_cast<uint8_t>((ui >> 32) & 0x7f);
u.tgt_rssi_dbm =
(rssi7 >= 91) ? 0 : static_cast<int8_t>(static_cast<int>(rssi7) - 110);
out.n_users++;
o += ustride;
}
return out.n_users > 0;
}
} /* namespace devourer */