diff --git a/tests/regress.py b/tests/regress.py index e253982..be0938a 100755 --- a/tests/regress.py +++ b/tests/regress.py @@ -806,6 +806,7 @@ def run_cell( kh: KernelHost, encoding: Optional[dict] = None, sniffer_iface: Optional[str] = None, + cell_tag: str = "", ) -> CellResult: """Run one matrix cell. State contract: always restore DUTs to a clean baseline (host kernel-bound) on exit via try/finally. @@ -819,7 +820,14 @@ def run_cell( via sniff_air's radiotap decoder and a one-line summary appended to the CellResult.notes. Lets the matrix prove what encoding actually flew, vs what inject_beacon.py / txdemo *requested*.""" + # Per-cell filename stem. `cell_tag` is appended by callers that run + # multiple cells with the same (tx_side, rx_side) but different + # encodings (--encoding-matrix) — without it the pcap from cell N + # gets overwritten by cell N+1, so --keep-logs only preserves the + # last encoding combo per driver-mode. cell_id = f"tx-{tx_side}_rx-{rx_side}" + if cell_tag: + cell_id = f"{cell_id}_{cell_tag}" tx_log = tmpdir / f"{cell_id}.tx.log" rx_log = tmpdir / f"{cell_id}.rx.log" sniffer_pcap = tmpdir / f"{cell_id}.sniffer.pcap" if sniffer_iface else None @@ -1152,11 +1160,13 @@ def run_encoding_matrix( f"RX={rx_dut.chipset} ({rx_side}) enc=[{enc_label}]" ) print(cell_hdr + " ...", flush=True) + # Sanitize encoding label for use in a filename. + tag = enc_label.lower().replace("+", "-").replace("=", "") try: r = run_cell( devourer_root, tx_dut, rx_dut, tx_side, rx_side, channel, duration, tmpdir, kh, encoding=enc, - sniffer_iface=sniffer_iface, + sniffer_iface=sniffer_iface, cell_tag=tag, ) except Exception as e: print(f" ✗ cell crashed: {e}", flush=True) diff --git a/tests/sniff_air.py b/tests/sniff_air.py index 27020d2..3a6d9e1 100644 --- a/tests/sniff_air.py +++ b/tests/sniff_air.py @@ -84,7 +84,12 @@ def _aligned(offset: int, align: int) -> int: def _parse_radiotap(frame: bytes): """Decode radiotap MCS (bit 19) / VHT (bit 21) info from one captured frame. Returns dict with keys: kind ('HT' | 'VHT' | 'legacy'), mcs, - ldpc, stbc, bw, nss (where applicable).""" + ldpc, stbc, bw, nss (where applicable), or None on malformed header. + + Handles multi-word it_present chains and the radiotap control bits + (29 RADIOTAP_NS, 30 VENDOR_NS, 31 EXT). Real mac80211 captures from + ath9k_htc routinely use multiple presence words + bit 29, so a parser + that only walks word0 fails on every frame.""" if len(frame) < 8: return None version, _pad, it_len = struct.unpack_from(" it_len: - return None - if bit == 19: # MCS info - mcs_known, mcs_flags, mcs_idx = struct.unpack_from( - " it_len: + return None + cur += 6 + in_vendor_ns = True + # Continue iterating bits, but any further field bits in + # this word are in vendor namespace — we can't size them. + continue + if in_vendor_ns: + # Vendor field, unknown size — bail out of further parsing + # but keep what we already extracted. + return parsed_to_result(parsed, it_len) + if bit not in _RT_FIELDS: + # Unknown radiotap field — can't safely advance cur. Return + # what we parsed so far rather than discarding. + return parsed_to_result(parsed, it_len) + size, align = _RT_FIELDS[bit] + cur = _aligned(cur, align) + if cur + size > it_len: + return parsed_to_result(parsed, it_len) + if bit == 19: # MCS info + mcs_known, mcs_flags, mcs_idx = struct.unpack_from( + "