Skip to content

Test data

Only recordings from own devices, with anonymized MAC addresses, also in the IEs (tools/anonymize, before the commit; the mapping TSV stays outside the repo). Per session this file lists the location, the channel and width, the AP settings, the client, the contents and the expected outcome; each session.json records the tshark version.

Opening a recording in the app writes its outcome and SSID into its session.json, for the start's cards (SessionStore.keepOutcome), and rewrites the file's layout: to look at one of sessions/ in the app, open a copy, or the checkout and the app's next build read as changed.

The app writes into the session.json of a recording it opens, as it does into its own recordings (the app, the start): on 2026-10-29 opening testlab-client-leaves-eduroam in the app added the engine configuration's newer fields to it in the main checkout. Before a commit there, git status shows such a file, and git checkout on it restores the recording.

The MAC addresses in the synthetic files are locally administered test addresses (02:00:00:00:…). Checked with tshark 4.6.8.

synthetic/frames.pcap

51 synthetic frames, not a recording. They cover: - management, control (RTS, CTS, ACK, BAR, BA, trigger) and data; - QoS, +HTC, four addresses, FCS, Null and A-MSDU; - EAPOL-Key, EAP, EAPOL-Start; - an extension frame; - radiotap of 8, 23 and 320 bytes.

The client is …:01, the AP …:a0. Same frames as the test of linkside-cap. To regenerate:

python3 sensor/linkside-cap/test/test_linkside_cap.py --write-pcap testdata/synthetic/frames.pcap

tshark calls two frames malformed: - the EAPOL-Key with a 24-byte MIC, because without a (re)assoc tshark does not know the AKM; - the data frame with a body shorter than LLC/SNAP.

synthetic/rates.pcap

13 frames with every kind of rate radiotap, for the rate decoding and linkside-cli radiotap: legacy, HT, VHT, HE (also without known bits), and U-SIG and EHT as TLVs after the chain namespaces, as mac80211 writes them. EHT on 80, 160 and 320 MHz, uplink and downlink, U-SIG without EHT, EHT values without known bits, and a data frame without any rate field (tshark calls it OFDM). Same addresses as ies.pcap.

python3 testdata/synthetic/make_rates.py

synthetic/events.pcap

9 management frames on 5 GHz ch 52 (DFS) for the AP-side events the hints read: the client …:01 associates with …:a0 (WPA2-PSK), the AP announces a switch to ch 100 with mode 1 in two beacons (a Channel Switch Announcement and an Extended CSA with operating class 121), sends a broadcast deauthentication with reason 3, and refuses the next association with status 17.

python3 testdata/synthetic/make_events.py

synthetic/ofdma.pcap

36 frames of UL OFDMA on 5 GHz ch 36 from an AP that answers with all-acks, for the uplink's Block Ack success, and a roam, for the AP an AID belongs to; no recording has an all-ack, and in none does the AP of an AID change what counts. The client …:01 associates with …:a0 and gets AID 5, and sends 0–7 in an SU PPDU, acknowledged with a compressed Block Ack. Then six trigger frames, each followed by the AP's Multi-STA Block Ack; the TB PPDUs are not in the file, as the sensor cannot decode them:

  • an all-ack (Ack Type 1, TID 14) for 8–17, next to a bitmap for AID 7;
  • an all-ack for 18–29;
  • a bitmap for 30–39 with 34 lost;
  • a single ack (Ack Type 1, TID 0) for 40;
  • the ack of a management frame (TID 15);
  • an all-ack for 41–50.

Last 51 in an SU PPDU with its compressed Block Ack.

At 1 s the client roams to …:b0 on the same channel and gets AID 3 there. The old AP triggers AID 5, now another station's, and acknowledges it with a bitmap; the new AP triggers the client next to a station of its own with AID 5 and acknowledges both, 0–3 and 4–9 with 6 lost for the client; then it deauthenticates the client and triggers AID 3, now another station's.

python3 testdata/synthetic/make_ofdma.py

synthetic/ies.pcap

23 management frames with many elements, in one setup: - AP MLD …:0a:00: link 0 on 5 GHz (…:a0, ch 36, 80 MHz), link 1 on 6 GHz (…:c0, ch 37, 160 MHz, duplicate beacons, LPI); - on 6 GHz an MBSSID set: …:c0 transmits, …:c1 (guest, OWE, own MLD) and …:c2 (iot, with Non-Inheritance) are nontransmitted, with one profile per beacon (EMA); - the same AP on 2.4 GHz (…:d0), not in the MLD; - a neighbor with the same SSID (…:b1, ch 100, 80 MHz, Wi-Fi 6), 14 dB stronger; - the client MLD …:01:00 with link 0 …:01 and link 1 …:11, eMLSR.

Frames: beacons, an unsolicited probe response, FILS Discovery, a probe response with TPE and QBSS, assoc request and response with Basic ML element, BTM request (candidate list) and response, 11k neighbor report request and response, SA Query, FT action, FT authentication, reassociation to the neighbor (the client's frames to it on the neighbor's channel, 5500), and an encrypted action frame (PMF).

python3 sensor/linkside-cap/test/test_linkside_cap.py --write-ies-pcap testdata/synthetic/ies.pcap

On the assoc request tshark reports that AKM 25 would not be an FT AKM; that is a limitation of tshark 4.6, not an error in the frame.

The reference capture of the decode check

81 frames that the app gives the installed tshark as a live session does, its input open until each pass has put out every frame, and whose decode it compares, value by value, with what tshark 4.6.8 gave for them (DecodeCheck, getting started). They sit in LinksideKit's resources, app/LinksideKit/Sources/LinksideKit/Decode/Reference/: reference.pcap, the field list's output and the management JSON of tshark 4.6.8 as it printed them (reference-fields.txt, reference-management.json), and its version line (reference-tshark.txt).

The frames come from the anonymized recordings, for the mt7925's own radiotap, and from the synthetic pcaps, where no recording has the kind: beacons on 2.4, 5 and 6 GHz with an RNR, with MBSSID, Non-Inheritance and an Extended CSA, FILS Discovery, Multi-Link probe responses, a probe request; SAE, open authentication, 802.1X with EAP Identity, EAP-TLS and PEAP, a 4-way with a 16-byte MIC, a Multi-Link association, a 4-way with a 24-byte MIC after its association with AKM 25 (no recording or synthetic file has AKM 24), an FT reassociation; ADDBA, the VHT, HE and EHT action categories, a neighbor report, BTM, a protected action frame; deauthentications in the clear, protected and to the broadcast address, a disassociation; RTS, CTS, ACK, BAR, a compressed and a Multi-STA Block Ack, an all-ack, an HE and an EHT trigger; data at HR/DSSS, ERP, HT, OFDM on 5 and 6 GHz, VHT, HE SU on 2.4 and 5 GHz, EHT SU on 5 GHz and at 160 MHz on 6 GHz, an A-MSDU, +HTC, a retry, the PM bit, four addresses; after those, frames 76 to 81, HT on 2.4 GHz in PPDUs of their own with one and two streams, with and without the short GI, for the airtime (AirtimeTests). The script makes four from the frame before them: a beacon with Bad FCS, an RTS with bandwidth signaling (the group bit in its TA, which the anonymizer cleared in the recordings), a frame with signals per chain and no combined one, and one of protocol version 3. The list, with each frame's source, is in the script:

python3 testdata/synthetic/make_reference.py
LINKSIDE_UPDATE_REFERENCE=1 swift test --filter DecodeReferenceUpdate    # in app/LinksideKit: the stored decode again
swift test --filter DecodeCheckTests

The stored decode is written again only with tshark 4.6.8, or with the tshark the check should compare with from then on, and after a change of the capture or of TsharkField: the field list's lines have its columns. tshark 4.6.8 calls frame 66 malformed (protocol version 3), on frame 28 that AKM 25 would not be an FT AKM, and on frames 64, 70 and 71, the HE A-Control of the recordings, an incorrect Control Information length. Both passes take 0.2 to 0.5 s on the developer's Mac; one that holds frames back is given 10 s (DecodeCheck.wait).

Against Wireshark's newest releases from its download page, their disk images mounted and their tshark first in PATH for linkside-cli fields (2026-11-03): 4.6.9, the newest stable release, passes the field check and the decode check. 5.0.0rc1, the release candidate, changed four things:

  • Two fields of the EML Capabilities have new names: emlsr_padding_delay and emlsr_transition_delay are emlsr_emlmr_padding_delay and emlsr_emlmr_transition_delay. The decoder reads both names, and the field check wants one of each pair (ManagementFields.renamed).
  • Wireshark's airtime estimate (wlan_radio.duration) counts the 4 bytes of the FCS that the mt7925 leaves off: at a legacy rate 4 to 8 µs more at 6 and 24 Mbit/s and 32 µs more at 1 Mbit/s, on 46 frames of the first 75; in HT 4 µs more where the FCS takes a symbol more, on 3,111 of the 65,298 HT frames of testlab-intermittent-loss, which moved its golden airtime. The engine reads no wlan_radio.duration since: a frame's airtime at a legacy rate, in HT and in VHT is its own TXTIME with the FCS (Frame.airtime), equal to 4.6.8's estimate without the FCS and to 5.0.0rc1's with it at the legacy rates and in HT without the short GI (definitions).
  • Each extension element is listed as 255 in wlan.tag.number as well, which the decoder reads alike.
  • With its input open, tshark 5.0.0rc1 holds the JSON of the management frames back and puts it out in bursts, with -l or --json-compact alike: of the ten management frames of frames.pcap fed one at a time, six came while the input stayed open, the last four only at its end; the field list came frame by frame. A live session would wait for the bodies: the decode check holds it ("holds the management frames' JSON back while its input is open"), and managementFrameWithoutTheNext in TsharkDecoderTests waits out its 300 s bound.

With those changes the golden tests, the field check and the decode comparison pass on 5.0.0rc1; the JSON held back fails the check. 1.0 holds Wireshark at 4.6 (the app); 5.0 comes with the app's own tshark (item 164).

sessions/synthetic-mlo

A synthetic session directory (capA.pcap, capB.pcap, .status, session.json, golden.json, verdict.json), not a recording. Created with:

python3 testdata/synthetic/make_session.py
Location n/a (synthetic)
Channel and width capA first a sweep (2.4 GHz, ch 100), then 5 GHz ch 36/80; capB 6 GHz ch 37/160
AP the setup of ies.pcap, plus a weak co-channel AP …:b2 (WPA2-PSK, -80 dBm)
Client …:01 (MLD …:01:00, link 1 …:11, eMLSR), SAE, AKM 25 with a 24-byte MIC
Contents 4-way complete; DL and UL traffic with Block Acks on both links in one MLD sequence space, with loss, retransmissions and two missed frames; triggers and a Multi-STA BA for AID 5; power save; an encrypted BTM (PMF); a probe storm; a reassoc to …:b2 with a 4-way that stalls after M2; deauth reason 15; 3 drops on capA
Expected outcome client side (probe storm, M2 without M3), anomaly "reassoc to a weaker BSS" (18 dB)

The frames are truncated the way linkside-cap truncates them.

sessions/synthetic-roaming

A synthetic session directory for the roam analysis, not a recording, single link. Created with:

python3 testdata/synthetic/make_roaming.py
Location n/a (synthetic)
Channel and width capA on AP1, 5 GHz ch 36 (−60 dBm); capB on AP2, 5 GHz ch 100 (−63 dBm)
AP two APs of linkside-test, WPA2-Enterprise with 802.11r offered (AKMs 1 and 3, a Mobility Domain element)
Client …:01, voice-like data each way every 20 ms, no Block Acks
Contents a full 802.1X login with AP1; at 10 s a roam to AP2 with a full login (RADIUS 200 ms per round trip) though 802.11r is offered; at 20 s back with FT; at 30 s an FT authentication with AP2 refused with status 53; at 40 s and 50 s FT roams to AP2 and back
Expected outcome AP side (the refused roam decides), with the full login instead of 802.11r and the roams back and forth found as well, both client side; delivery untested (no Block Acks)

Scorecard

One row per testlab scenario and case: the cause, the expected outcome, the app's, whether the rules of the day gave it as recorded, and what Mist alone said. A rule change made for a recording counts against "as recorded"; held-out recordings are made without one. Twenty-eight of the fifty-one recordings came out as recorded.

Recording Cause Expected App As recorded Mist
testlab-baseline none undecided, nothing abnormal as expected no: "Losses towards the client" from the first BA after the reconnect n/a (UniFi)
testlab-wrong-psk wrong PSK and SAE password client side as expected yes n/a (UniFi)
testlab-client-leaves-psk the client leaves, WPA2 client side as expected yes n/a (UniFi)
testlab-client-leaves-sae the client leaves, WPA3 undecided, reason encrypted as expected, the encrypted deauthentication a note yes n/a (UniFi)
testlab-ap-kick a kick from the controller AP side as expected no: reason 1 not taken n/a (UniFi)
testlab-ap-reboot an AP reboot AP side as expected yes n/a (UniFi)
testlab-intermittent-loss a microwave oven at the client RF environment, in the oven's window as expected, "Losses both ways" in that window: half the client's frames sent again; the client's frames sent again outside it a note no: no hint before the BAR bound, then undecided until the client's frames sent again counted n/a (UniFi)
testlab-upstream a shaper beyond the AP upstream undecided, "Nothing abnormal found", since wifi-fine was removed (2026-10-26) yes n/a (UniFi)
testlab-co-channel a second AP on the channel, with load RF environment as expected, the client's frames sent again a note no: Channel Good from the last beacon's QBSS n/a (UniFi)
testlab-sae-group an SAE group the AP refuses client side as expected no: coverage and the channel "AP not heard" n/a (UniFi)
testlab-ap-kick-eduroam a Reconnect from the Mist portal; eduroam with PMF on the AP45 AP side, reason 2 from Mist as expected no: reason 2 counted as a login CLIENT_DEAUTHENTICATION with reason 2
testlab-client-leaves-eduroam the phone with a YouTube video, dozing between its chunks, then turning its Wi-Fi off; eduroam on the Mist AP45 client side: the client left undecided, the losses after wakes a note; a known mismatch: the frames name the client (its encrypted EAPOL-Logoff before the AP's deauthentication), but it joins again after 5 s, which client-disconnect takes as the reconnect the user made no: the flush counted as loss, the losses after wakes taken as RF an auth failure for a client that left
testlab-power-save-video a video's bursts: the AP's frames at MCS 9–11 with two streams and GI 1.6 fail, the phone awake; eduroam on the AP45 undecided without a second client: losses towards the client in the bursts as expected, the losses in the bursts a note yes tx_retries 12.6 to 67.7 % of tx_pkts per minute
testlab-power-save-second-client the same, with this Mac dozing beside the phone as reference client: the phone had three times its retransmissions client side: losses towards the client that the reference client does not have client side: losses towards the client, 69.1 % of the AP's frames sent again against 11.5 % to the reference client no: BA success 98.2 %, nothing abnormal tx_retries 27.1 % of tx_pkts for the phone, 8.8 % for the Mac
ap45-phone-band-roam none: an idle phone's own roam to the AP45's 2.4 GHz radio after a scan undecided, nothing abnormal as expected, the frames sent again while the phone scanned a note, the roam 18 ms, the phone on 2.4 GHz with the AP45's 5 GHz at −50.7 dBm here a note no: 202 bytes before the scan counted as in use, a slow roam of 409 ms an 11r reassociation, then DNS and ARP
testlab-long-quiet none: the phone in normal use for 51 minutes on the AP45, cut to four stretches undecided, nothing abnormal as expected, with notes: the frames sent again and the losses after wakes, the frames sent again after the roam back, the phone on 2.4 GHz with the AP45's 5 GHz at −51.0 dBm here no: AP side from a deauthentication on a BSS the phone had left, a slow roam as it fell asleep, the frames sent again after wakes CLIENT_DEAUTHENTICATION with reason 4 for the BSS the phone had left
testlab-ip-services-dns a DNS server of its own that does not answer; eduroam on the AP45 upstream, "DNS fails" from Mist as expected yes; Mist's reason of the encrypted disassociation taken only after a fix MARVIS_DNS_FAILURE, 34 s before the reconnect
testlab-4way-ack the A9000 client does not hear the AP for some ms after its M2: M3 unacknowledged, four of nine 4-ways end in reason 15 client side client side, "The client misses M3" no: no rule for a 4-way that stops at M3 n/a (UniFi)
testlab-usb2-port capA on a USB 2 port for a focus on 5 GHz measurement limit as expected, "Radio on a USB 2 port" yes nothing: the sensor's port
u7-phone-search-again none: the phone's roam to AP B on a channel neither radio watched, found again by the search undecided, nothing abnormal as expected, the losses after wakes and the client's frames sent again notes; roaming untested, a roam the sensor did not see yes n/a (UniFi)
u7-phone-search-copies AP A's 5 GHz switched off, the phone next to the sensor: it went to AP B on a channel neither radio watched, capA decoding its frames to AP B at legacy rates AP side as expected, the frames sent again after wakes on AP B a note; found again by the search 60 s after its last frame, roaming untested yes, with "The client left" beside it from the phone's deauthentication of a join without keys until a join given up stopped counting (2026-11-05) n/a (UniFi)
ap45-radius-timeout-reauth a field case: the RADIUS server left an 802.1X reauthentication unanswered twice after the client's certificate; eduroam on the AP45 upstream, a RADIUS timeout AP side, "Disconnected by the AP" with reason 2 from Mist: a known mismatch until a rule takes a RADIUS timeout no: no rule for it an auth failure with authorization_reason_code 23 and the deauthentication with reason 2 in the same second; the portal's text names a RADIUS timeout (514), which mist.json does not keep
testlab-wifi7-one-link a Wi-Fi 7 client on one link, typed in: the A9000 client misses M3, three of four 4-ways end in reason 15 client side client side, "The client misses M3" yes n/a (UniFi)
testlab-mlo-reconnect none: an MLO client typed in by its MLD address, caught in its reconnect undecided, nothing abnormal as expected; the switch to the reconnect filter after 20 s, the link mapping from the association request yes n/a (UniFi)
testlab-mlo-not-heard an MLO client's MLD address typed in, no reconnect measurement limit as expected, "Client not heard: possibly Wi-Fi 7 multi-link" yes n/a (UniFi)
ap45-phone-reconnect none: the phone's own disconnect and connect on eduroam, the AP45 answering its logoff with reason 36 in the clear undecided, nothing abnormal as expected, the losses after wakes and the frames sent again both ways notes no: AP side, "Disconnected by the AP" with reason 36 CLIENT_DEAUTHENTICATION with reason 36
ap45-phone-reauth-asleep a field case: the phone, idle and dozing, left the AP45's 802.1X reauthentication unanswered; the AP dropped it and answered its next frame with reason 7; eduroam on the AP45 client side AP side, "Disconnected by the AP" with reason 7: a known mismatch until the rule of item 139 no: no rule reads a reauthentication the client leaves unanswered two auth failures with code 768, "STA restarts 802.11 authentication/association, before authorization complete", the second with the RADIUS timeout 514; no deauthentication
ap45-radius-unanswered-login a field case: the RADIUS server left the phone's certificate flight in a full EAP-TLS login unanswered; eduroam on the AP45 upstream as expected, "RADIUS server not answering", the AP asking for the identity again after 29.9 s yes CLIENT_AUTH_ASSOCIATION 30.3 s after the association, no failure
ap45-radius-slow-login a field case: the RADIUS server answered the phone's certificate flight in a full EAP-TLS login after 10 s; eduroam on the AP45 upstream as expected, "Slow RADIUS server", 9958 ms yes CLIENT_AUTH_ASSOCIATION 10.2 s after the association
ap45-phone-video-reconnect none: the phone with a video, dozing between its chunks, one reconnect of its own from Control Center, then carried away from the sensor; eduroam on the AP45 undecided, nothing abnormal as expected, the frames sent again after wakes a note no: "Losses both ways" from the AP45's answer to a BAR of the phone labelled TID 0 CLIENT_DEAUTHENTICATION with reason 36, then the login, DHCP, ARP and DNS
testlab-wrong-psk-psk-iphone the iPhone with a wrong WPA2 passphrase; testwlan-psk on the AP45, no PMF; held out client side as expected, "Login failed" yes, with a false "Moves to a weaker AP" from the phone's failed joins at the second AP45 until a join without keys stopped being a roam (2026-11-05) MARVIS_EVENT_CLIENT_AUTH_FAILURE with code 14 and CLIENT_DEAUTHENTICATION with reason 14 per attempt, REPEATED_AUTH_FAILURES after two
testlab-wrong-psk-psk-mac the MacBook with a wrong WPA2 passphrase; testwlan-psk, no PMF; held out client side as expected, "Login failed" yes, with a false "The client left" from its disassociation of a join the AP no longer answered until a join given up stopped counting (2026-11-05) the same, REPEATED_AUTH_FAILURES three times
testlab-wrong-psk-sae-iphone the iPhone with a wrong password, by SAE; testwlan-1 on both AP45s; held out client side as expected, "Login failed", the SAE confirm refused yes, with "The AP does not answer" from the commits the AP45s leave unanswered after a refusal until that silence went with the refusal (2026-11-05) MARVIS_EVENT_SAE_AUTH_FAILURE with status 1 per refusal, REPEATED_AUTH_FAILURES
testlab-wrong-psk-sae-mac the MacBook with a wrong password, by SAE; testwlan-1; held out client side as expected, as with the iPhone yes, with "The AP does not answer" as with the iPhone the same
testlab-client-leaves-psk-iphone the iPhone with a video, its Wi-Fi off in Settings; testwlan-psk, no PMF; held out client side as expected, "The client left (reason 8)" yes CLIENT_DEASSOCIATION with reason 8
testlab-client-leaves-psk-mac-settings the MacBook with a video, its Wi-Fi off in System Settings; testwlan-psk; held out client side as expected, "The client went silent": the Mac left without a frame, the AP's RTS and Block Ack Requests unanswered for 159 s no: no rule read a client gone silent in its traffic until client-silent (2026-11-05) nothing within the session
testlab-client-leaves-psk-mac-menu the MacBook with a video, its Wi-Fi off in the menu bar; testwlan-psk; held out client side as expected, "The client left (reason 8)" yes CLIENT_DEASSOCIATION with reason 8
testlab-client-leaves-sae-iphone the iPhone with a video, its Wi-Fi off in Settings; testwlan-1, SAE with PMF; held out client side as expected, "The client left", reason 8 from Mist yes CLIENT_DEASSOCIATION with reason 8
testlab-client-leaves-sae-mac the MacBook with a video, its Wi-Fi off in the menu bar; testwlan-1, SAE with PMF; held out client side as expected, "The client left", reason 8 from Mist yes CLIENT_DEASSOCIATION with reason 8
testlab-client-leaves-eduroam-iphone the iPhone with a video, its Wi-Fi off in Settings; eduroam on the AP45; held out client side as expected, "The client left", its logoff, its own disassociation, reason 8 from Mist yes CLIENT_DEASSOCIATION with reason 8, no auth failure
testlab-client-leaves-eduroam-mac the MacBook with a video, its Wi-Fi off in the menu bar; eduroam on the AP45; held out client side as expected, "The client logged off; the AP ended the session (reason 36)" yes CLIENT_DEAUTHENTICATION with reason 36
testlab-ap-kick-psk-iphone Reconnect from the Mist portal twice, the iPhone with a video; testwlan-psk, no PMF; held out AP side, reason 2 as expected, "Disconnected by the AP", the measured use of reason 2 on a connection without PMF no: undecided until these two recordings became that measured use (2026-11-05) CLIENT_DEAUTHENTICATION with reason 2, then CLIENT_AUTH_REASSOCIATION
testlab-ap-kick-psk-mac the same with the MacBook; held out AP side, reason 2 as expected, as with the iPhone no: the same the same
testlab-ap-kick-eduroam-mac Reconnect from the Mist portal twice, the MacBook with a video; eduroam with PMF on the AP45; held out AP side, reason 2 from Mist as expected, "Disconnected by the AP", protected while the sensor heard the link yes CLIENT_DEAUTHENTICATION with reason 2, then CLIENT_AUTH_ASSOCIATION
testlab-ap-kick-eduroam-iphone the same with the iPhone, a second sample beside testlab-ap-kick-eduroam; held out AP side, reason 2 from Mist as expected yes the same
testlab-upstream-iphone a shaper beyond the AP, the iPhone downloading in Safari; testwlan-1; held out upstream undecided, "Nothing abnormal found", no rule of the air fired no: no rule names a cause beyond a clean link no events
testlab-upstream-mac the same with the MacBook, UDP at 2 Mbit/s both ways; held out upstream undecided, as with the iPhone, the note "Sent again" just after the traffic no: the same its join before the recording, gateway and DNS OK
testlab-co-channel-mac the second AP45 on the AP45's channel, loaded by the iPhone's download; the MacBook with UDP at 2 Mbit/s both ways; testwlan-1; held out RF environment as expected, "Busy channel" over the whole load: its first 45 s with the retries, the rest on the load alone yes no events
testlab-co-channel-iphone the same, loaded by stick B; the iPhone with a download held to 2 Mbit/s; held out RF environment as expected, "Busy channel" on the load alone: QBSS 92 %, the phone's own airtime 1 to 2 %, the retry bit 3 to 5 %, BA success 99.7 % and up no: congestion asked for 10 % of retries or a low MCS besides the load until it found on the load alone (2026-11-04) its join before the recording, gateway and DNS OK
testlab-intermittent-loss-psk-iphone-moved a microwave oven 2 m from the iPhone for 33 s, a U7 on the channel beside the AP45, the phone moved by hand 2:45 after it; testwlan-psk; held out RF environment, in the oven's window as expected, "Losses both ways" in the oven's window, a jump both ways; the window of the hand on the phone a note no: frames sent again alone were a note, and a dip to 89.6 % beside the client's usual 19 % a finding nothing
testlab-intermittent-loss-psk-iphone the same, the U7's 2.4 GHz off, the phone left alone; held out RF environment, in the oven's window as expected, "Losses both ways" in the oven's window, a jump both ways with BA success at 99 % and 100 % no: "Nothing abnormal found", frames sent again alone a note nothing

Recordings

A recording stays a few MB, so the golden tests replay in seconds: the testlab scenarios that become sessions here run their traffic at 2 Mbit/s each way (testlab.md). A scenario that needs a high rate stays outside the repo, with its numbers in docs/measurements/.

Five sessions from 2026-10-13 and the testlab scenarios from 2026-10-20 on, at home, with own devices: two UniFi U7 Pro Max APs (AP A and AP B of the runbook), the Mist AP45, a phone, the MacBook and an A9000 as a client on the Fedora PC. Anonymized in one run, so the addresses agree across the sessions:

python3 tools/anonymize/anonymize.py --map <map.tsv> --keep <own.txt> \
  --ssid <home SSID>=testroam --ssid <home SSID>-iot=testroam-iot --ssid linkside=linkside \
  --ssid eduroam=testedu --ssid <OpenRoaming SSID>=testroaming \
  --realm <EAP realm>=staff.university.example.org \
  <session-dir> testdata/sessions/<name> ...

testwlan-1 and testwlan-psk, the WLANs of the site's own on the AP45s, name no place or person and stay as they are, as linkside does. The keep list holds every address of the own APs and clients; the mapping and the keep list stay outside the repo. Left out: what the context sweep heard from neighbors, and the APs' frames to other clients (mostly Block Acks to the Mac). The status files still count the frames from before that. The hidden SSID appears only as a Short SSID in the RNR; it reads as hidden-1. One neighbor report response from AP A went out in the clear with the Protected bit, right after a 4-way handshake; its BSSIDs are replaced as bytes. The realm of the EAP identity on eduroam names the institution: it becomes staff.university.example.org, of the same length; in the sessions anonymized before --realm existed it was replaced in place on 2026-10-30, the frames, their lengths and addresses otherwise unchanged.

APs AP A: testroam on 5 GHz ch 48/80 (02:00:00:00:02:c1) and 6 GHz ch 37/160, testroam-iot on 2.4 GHz ch 1, linkside on 6 GHz. AP B: testroam on 5 GHz ch 40/80 (06:00:00:00:06:c8) and 6 GHz ch 53/320, testroam-iot on 2.4 GHz ch 6. Both carry a hidden SSID on every radio. testroam is WPA2/WPA3 transition on 5 GHz and WPA3 on 6 GHz, testroam-iot WPA2, linkside WPA3. UniFi derives the BSSIDs of one radio in the first byte; the anonymized ones do the same (02:, 06:, 0a:)
AP45 testedu (eduroam, WPA3-Enterprise with FT, PMF) on 2.4 GHz (02:00:00:00:10:a1), 5 GHz ch 52/20 (02:00:00:00:10:92) and 6 GHz (02:00:00:00:10:81); its 2.4 and 6 GHz channels differ between the sessions; from 2026-11-03 also testwlan-psk (WPA2-PSK, no PMF, no 802.11r) on 5 GHz (02:00:00:00:10:95) and 2.4 GHz (02:00:00:00:10:a3), and testwlan-1 (SAE and PSK in transition mode, 802.11r, PMF capable) on 5 GHz (02:00:00:00:10:96) and 2.4 GHz (02:00:00:00:10:a4); 5 GHz ch 132/20 on 2026-11-03
Second AP45 from 2026-11-03: testedu on 5 GHz ch 108/20 (02:00:00:00:19:51), 2.4 GHz (02:00:00:00:19:61) and 6 GHz (02:00:00:00:19:41), testwlan-1 (02:00:00:00:19:53, 02:00:00:00:19:63), and testwlan-psk (02:00:00:00:19:52, 02:00:00:00:19:62) until 17:10 that day, when it went to the AP45 alone
Clients on the AP45s the iPhone (iOS 26) on testwlan-psk …:1a:70, on testwlan-1 …:1d:0d, on eduroam …:1f:9e from 2026-11-03; the MacBook (macOS 27) on testwlan-psk …:1c:16, on testwlan-1 …:1e:7f, on eduroam …:14:e4

sessions/u7-phone-same-channel

linkside-cli live --channel 5240/80 (live-1), 62 s. The phone (…:01:10, HE, connected all along) on testroam 5 GHz ch 48/80, with traffic. capA on 5240/80; capB had no lock target and stayed on its last channel, the same one: both radios hear the same frames. Expected: one link (5240), every frame counted once; BA success 99.7 % down and 100 % up; captured 93 % and 79 %; undecided, nothing abnormal, with the note "Sent again after wakes" in 21:02:31–51: 16.6 % of the AP's frames sent again, most right after the phone's wakes from power save, as with the phone in a video on the AP45 (power-save.md); downlink Good.

sessions/u7-phone-same-block

live-3, 91 s. The same phone and AP. capA on 5240/80. capB swept the context for 17 s, then locked onto AP B on 5200/80 (primary 40): the same 80 MHz block, so it decodes the phone's 80 MHz PPDUs too. Expected: one link (5240), capB's copies not counted; BA success 100 % both ways; captured 96 % and 100 %; undecided, nothing abnormal, with the note "Sent again after wakes" in 21:30:20–40: 18.4 % of the AP's frames sent again, most right after the phone's wakes; downlink Good.

sessions/eht-6g-160-low-capture

1 s from the middle of eht-6g-160-A (eht-capture.md): the A9000 client (…:07:16) on linkside, 6 GHz ch 37/160, EHT MCS 9 down and 13 up; stick A as capA on 6135/160. Only the client's frames, no beacons. Expected: undecided, no finding; BA success 99.4 % down and 96.8 % up; captured 4 % and 0 %.

0.5 s from the middle of eht-6g-160-B: the same client and channel, stick B as capB, 1 m from the client. The AP ran at EHT MCS 4. Expected: undecided, no finding; captured 91 % down and 2 % up; the uplink frames the sensor got are mostly retransmissions (retry bit 34 of 37), which the hints leave out.

sessions/u7-phone-ft-roam

Recorded by the app, 139 s. The phone starts on AP A (5 GHz ch 48, its 5 GHz power set to Low), Wi-Fi off and on at 7 s (a reconnect with PMK caching). capA on 5240/80, capB locked onto AP B on 5200/80 after 23 s. The sensor stays near AP A; the phone is carried to AP B with traffic. At 82 s the phone roams with 802.11r (FT-SAE, over the air): its first FT authentication goes unanswered, it goes back to AP A and keeps receiving data, and 341 ms later asks again and gets the answer in 4 ms. Expected: one roam, FT, 36 ms without a connection (20 ms off the air, waits 3 ms client and 13 ms AP), no slow-roam hint. The new AP is 15 dB weaker at the sensor because the sensor stayed behind: the client was away from it, so the weaker-roam rule stays untested; undecided, nothing abnormal, with the note "Sent again after wakes" in 23:06:03–23: 22.1 % of the AP's frames sent again, most right after the phone's wakes. The 17.6 % sent again in 23:05:28–48, not after wakes, as the phone walked away from AP A and the sensor (−73 dBm at the sensor), are a note too: without a reference client frames sent again have no side, and away from the sensor a loss has none either.

sessions/testlab-baseline

The testlab baseline (testlab.md), recorded by the app with capA alone, 179 s. The A9000 client (…:07:16, psk.conf: WPA2-PSK without PMF) on AP A, testroam 5 GHz ch 48/80, capA on 5240/80, 1 m from it. One minute of UDP at 2 Mbit/s both ways, Ctrl-C, a reconnect, one minute more. The data from the AP has the Fedora's Ethernet address as SA (…:0c:0e). The reconnect's first 4-way stops after M3: the client never sends M4 and the AP deauthenticates with reason 15 after 3 s; the second attempt completes 11 s after the deauthentication. The AP then starts a new BA agreement at sequence 0, and the client's first BA starts at 3842 with only 0 and 1 set. Expected: BA success 100 % both ways, from the first acknowledged bit of that BA on; captured 5 % down and 3 % up; the client's deauthentication (reason 3) is no hint, it rejoined; every question Good, coverage untested (no Mist), roaming untested (no roam); undecided, nothing abnormal found.

sessions/testlab-wrong-psk

The testlab scenario wrong-psk (testlab.md), recorded by the app with capA alone on 5240/80, the channel and BSSID typed in, 378 s. The A9000 client (…:07:16) on AP A, testroam 5 GHz ch 48, first with a wrong WPA2 passphrase (psk-wrong.conf) for two minutes, then with a wrong SAE password (sae-wrong.conf) for three; the phone (…:01:10) as reference client, Wi-Fi off and on at the start: it reconnects with SAE (H2E, status 126) and uses the network. WPA2: four attempts as wpa_supplicant backs off (10, 20, 30, 60 s); in each the AP sends M1 four times, 1 s apart, the client answers each with M2, and the AP deauthenticates with reason 15. SAE: the AP refuses the client's confirm with status 15, nine times, and about 30 s after a refused confirm sends a deauthentication with reason 2, twice; UniFi does not block the client after the failures. Expected: client side, "Login failed" at the last stalled 4-way, with M2 without M3 (PSK), the SAE confirm refused 9 times and the reference client connecting as evidence; connection Poor; both directions untested (too little traffic).

sessions/testlab-client-leaves-psk

The testlab scenario client-leaves (testlab.md) with WPA2, recorded by the app with capA alone on 5240/80, the channel and BSSID typed in, 232 s; the phone as reference client, connected all along. The A9000 client connects with psk.conf (the 4-way completes at once, then AP A's neighbor report response in the clear: no PMF), one minute of UDP at 2 Mbit/s both ways, Ctrl-C (deauthentication, reason 3) and two minutes without it. Expected: client side, "The client left" at the deauthentication; connection Poor; BA success 100 % both ways, captured 16 % down and 19 % up.

sessions/testlab-client-leaves-sae

The same with sae.conf (SAE, PMF required), 219 s. After the 4-way the AP's neighbor report response and the action frames that follow are protected and encrypted (protected-action, the Block Ack setups among them block-ack-setup encrypted); the client's deauthentication is encrypted. Expected: no finding, and since 2026-10-25 the note "The client left (PMF)" at the deauthentication (encrypted-disconnect); connection Good with "reason encrypted (PMF)" as untested; undecided, nothing abnormal found; BA success 100 % both ways, captured 26 % down and 52 % up.

sessions/testlab-client-leaves-eduroam

The client-leaves scenario (testlab.md) on eduroam (testedu, WPA3-Enterprise with FT, PMF) on the Mist AP45 in the work org at home, 5 GHz ch 52/20, beside Mist: recorded by the app with the Mist polls (mist.json), capA on the AP's channel and capB on its 2.4 GHz radio on ch 6, where the Mac's Wi-Fi was too; 280 s. The phone (…:9b) plays a YouTube video, which it fetches in chunks about every 10 s, dozing between them and waking every 1.5 to 3 s; its screen went off once. Four chunks (13:22:23.7, 13:22:33.3, 13:22:42.3 and 13:25:21.0) went with 51 to 68 % of the AP's frames retried and few Block Acks, most others with under 25 %, at −51 dBm with QBSS 1 %. Wi-Fi off at 13:22:42.39: an EAPOL-Logoff by its size and rate, 42 BARs from the AP past 171 sequence numbers it never sent, the AP's deauthentication in the clear with reason 23, an SA Query and the phone's protected disassociation; Mist logs an auth failure and deauthentications with reasons 23 and 6 (mist-events.md). Five seconds later the phone joins again with EAP-TLS. Expected: client side, the client left, a finding. The app says undecided, a known mismatch (expected.json): the frames name the client, its EAPOL-Logoff, encrypted and known by its size, 20 ms before the AP's deauthentication, but the phone joins again 5 s later, and client-disconnect takes a join within 60 s as the reconnect the user made. ap45-phone-reconnect has the same frames and is that reconnect: the frames do not tell a Wi-Fi off from a reconnect, and neither does the AP's 23 against its 36, which name the action, the switch in Settings or the disconnect with Wi-Fi left on, not whether the user left (mist-events.md). The mismatch stands until a rule tells a Wi-Fi off from a reconnect by more than the time to the join. Beside it the app gives the note "Sent again after wakes" in 13:22:23–43: the losses towards the client mostly right after its wakes from power save, its power save, the AP's rate after the pause or interference near it (the power-save scenario tells them apart); the BAR flush no loss; downlink Good; connection Good, the phone left.

sessions/testlab-ap-kick-eduroam

The ap-kick scenario (testlab.md) on the Mist AP45, beside Mist: the phone (…:9b) on eduroam (testedu, WPA3-Enterprise with FT, PMF) on 5 GHz ch 52/20 with a video playing and the screen on, recorded by the app with the Mist polls, capA on the AP's channel and capB on its 2.4 GHz radio, 131 s. At 16:50:53.789 Reconnect from the Mist portal: a protected deauthentication from the AP; Mist's CLIENT_DEAUTHENTICATION gives reason 2, in the poll 48 s later (mist-events.md). The phone probes, joins again and is back after 377 ms with a full EAP-TLS and the 4-way. The phone plays a YouTube video and dozes between its chunks with the screen on too: 92 % of the downlink comes right after a wake, and two chunks went with 54 and 74 % of the AP's frames retried. Expected: AP side, "Disconnected by the AP" with reason 2 from Mist, and the note "Sent again after wakes" in 16:50:25–45 before it, 58.2 % of the AP's frames sent again right after the phone's wakes; the other questions Good.

sessions/testlab-ap-kick

The testlab scenario ap-kick (testlab.md), the kick, recorded by the app with capA alone on 5240/80, the channel and BSSID typed in; the phone as reference client. The A9000 client connects with psk.conf (its first 4-way stops after M3, the second completes), four minutes of UDP at 2 Mbit/s both ways, and in UniFi Network Reconnect twice, two minutes apart: each a disassociation from the AP with reason 1, and the client back within a second. Cut 0.5 s before the client's own deauthentication at the end, a Ctrl-C that came before the Stop; 290 s. Expected: AP side, "Disconnected by the AP", 2 times, reason 1, at the first kick; connection Poor; BA success 100 % both ways.

sessions/testlab-ap-reboot

The reboot of the same scenario, 353 s. The client connected, five minutes of traffic, and after a minute Restart of AP A: a disassociation with reason 8 to the client and a deauthentication with reason 3 to every client, 80 s without a beacon, then a deauthentication with reason 2 to every client and the client back 4 s later. Expected: AP side; "The AP disconnected every client" decides, "Disconnected by the AP" (reason 8) and "The AP stopped beaconing" (79.8 s) found as well; connection Poor; BA success 100 % both ways.

sessions/testlab-intermittent-loss

The testlab scenario intermittent-loss (testlab.md), recorded by the app with capA alone on 2437/20, stick A on a USB 2 port, 208 s. The client is the MacBook (…:0d:78, HT, on testroam-iot) next to the sensor, both 1 m from a microwave oven, on AP A's 2.4 GHz moved to ch 6, with the other AP's 2.4 GHz off. UDP at 1 Mbit/s both ways to the Fedora; the oven on with a cup of water at about 13:34:20, for 11 s of effect. The channel has 20 to 25 % retries both ways without the oven; the Mac sends RTS before its data. During the oven the AP's data barely reaches the Mac: few BAs, and BARs from the AP that move past what it gave up, each answered with an empty BA. Expected: RF environment, a finding, in the oven's window. The app says so: "Losses both ways" with its window around the oven, BA success downlink 81.5 % there (most of it given up by BARs) and 49.9 % of the client's frames sent again while its BA success read 94.6 %; both Poor. Until the client's frames sent again counted (2026-10-29) the app said undecided, a known mismatch: "Losses towards the client", the uplink not low enough to be a loss and not clean enough to rule out the AP side. Outside the oven the Mac sent 23.5 % of its frames again in its worst window, BA success 99.9 %, a note; every one of them had been on the air within 2 s before (txop-loss.md). The session at 98.4 % down and 98.7 % up; no login captured.

sessions/testlab-upstream

The testlab scenario upstream (testlab.md), recorded by the app with capA alone on 5240/80, 285 s. The A9000 client (…:07:16) on AP A, testroam 5 GHz ch 48, with psk.conf; the Fedora's Ethernet shaped to 1 Mbit/s (tbf), UDP at 2 Mbit/s both ways for 180 s from about 15:03:53. iperf3 got 2.00 Mbit/s up without loss and 958 kbit/s down, with 52 % lost in the shaper. "Problem still there" ticked at about 15:05, recorded in session.json, an answer the app no longer asks for and ignores when read. The Mac's Wi-Fi was on the home SSID on ch 40, inside the 80 MHz of ch 48, against the runbook; QBSS 2 %, no loss. Expected: upstream, which no rule finds since wifi-fine was removed (2026-10-26): expected.json names the mismatch, and the app reads "Nothing abnormal found" with BA success 100 % both ways; no login captured. Captured 1 % down (EHT MCS 12 and 13) and 13 % up.

sessions/testlab-co-channel

The testlab scenario co-channel (testlab.md), recorded by the app with capA alone on 5240/80, the channel and BSSID typed in, 744 s. The A9000 client (…:07:16) on AP A, AP B moved to the same channel (testroam 5 GHz ch 48/80, heard at −53 dBm), UDP at 2 Mbit/s both ways for 360 s from 16:18:08. From 16:20:10 to 16:21:50 the phone (…:01:10), next to the other AP and on it, downloads an 8 GB file from the Fedora over the LAN in Safari: AP A's QBSS goes from 9 % to 93 %, the retry bit from 2.5 % to 18.9 % down and from 14.5 % to 22.1 % up, the uplink's median MCS from 12 to 5; BA success stays near 100 % and iperf3 lost nothing. Before the traffic, from 16:15:15 to 16:17:05, the same download with the phone still on AP A: QBSS 95 %, with only two null frames of the client. awdl0 was up during the recording (the Mac's change of network brought it back); the Mac's Wi-Fi was on 2.4 GHz. Expected: RF environment, "Busy channel" in the window of the download (the uplink's retries count, the downlink's do not: too little of it captured), channel Poor; no hint for the window before the traffic; no login captured.

sessions/testlab-sae-group

The testlab scenario sae-group (testlab.md), recorded by the app with capA alone on 5240/80, the channel and BSSID typed in, 140 s. The A9000 client (…:07:16) with sae-group.conf, SAE with only group 21, on AP A (testroam 5 GHz ch 48). One SAE commit with group 21 at 17:02:25, refused by the AP with status 77 and the group; after it the client sends only probe requests, wpa_supplicant disabling the network for 10, 20 and 30 s without printing the status, and 30 s later the AP deauthenticates it with reason 2. Expected: client side, "WPA3 fails for this client" at the refusal, with SAE status 77; connection Poor; coverage and the channel Good, with the AP of the client's authentication as the serving BSS; both directions untested (no traffic).

sessions/testlab-power-save-video

The testlab scenario power-save (testlab.md) with a video, on eduroam (testedu, WPA3-Enterprise with FT, PMF) on the Mist AP45, 5 GHz ch 52/20, beside Mist: recorded by the app with the Mist polls, capA on the AP's channel and capB on its 6 GHz radio, 328 s. The phone (…:9b), still and 1 m from the sensor, plays a YouTube video from 17:54 to 17:57 with the screen on; 99 of its wakes from power save bring a burst of the AP's backlog, a median of 1.3 s apart. A quarter of the AP's transmissions to the phone go again, most of them at MCS 9–11 with two streams and GI 1.6, against 5 % during a download at the same spot minutes before, which stays outside the repo for its size; Mist's tx_retries per tx_pkts reads 12.6 to 67.7 % per minute (power-save.md). Expected: undecided, nothing abnormal, with the note "Sent again after wakes" in 17:55:16–36: 63.3 % of the AP's frames sent again right after the wakes, the phone's power save, the AP's rate after the pause or interference near the phone: without a second client the session cannot tell them apart, and testlab-power-save-second-client puts most of it with the phone; downlink Good at 98.0 % over the session while 22.6 % of the AP's frames carry the retry bit; the other questions Good; no login captured, no roam.

sessions/ap45-phone-band-roam

The last minute of the download session of the power-save scenario on the Mist AP45 (eduroam, 5 GHz ch 52/20), cut 5 s after the download ended: recorded by the app with the Mist polls, capA on the AP's channel and capB on its 2.4 GHz radio on ch 1, 60 s. The phone (…:9b), idle, goes off its channel again and again, setting power save with null frames every 50 to 150 ms, exchanges a few frames with the AP, and at 17:48:25.9 roams with FT to the AP45's 2.4 GHz radio, 391 ms after its last frame on 5 GHz, with no BTM request over the air; on 2.4 GHz it goes on scanning, every 70 ms (power-save.md). Expected: undecided, nothing abnormal: the phone was idle and roamed by itself, and the roam reads 18 ms, as the phone was not in use before it (fewer than five data frames in the second before). The rules note "Sent again after wakes" in 17:48:05–25: 31.3 % of the AP's frames sent again while the phone was off the channel scanning. And "On 2.4 GHz" in 17:48:40–49:00: the AP45's 5 GHz −50.7 dBm here, a band the phone was on, while it sat idle on 2.4 GHz and lost nothing there.

sessions/testlab-power-save-second-client

The second client of the power-save scenario (testlab.md) on the Mist AP45 (eduroam, 5 GHz ch 52/20), beside Mist: recorded by the app with the Mist polls, capA on the AP's channel and capB on its 6 GHz radio, cut to the traffic from 18:37:05 to 18:40:25, 200 s. The phone (…:9b) plays a YouTube video; the MacBook (…:14:e4), on battery 1 m from the sensor on the same radio, is the reference client and fetches 1 MB every 10 s from a speed test server. Both doze between their bursts, the Mac waking every 0.6 s; the AP sends the phone 22 % of its frames again and the Mac 6.7 %, and Mist's tx_retries per tx_pkts reads 27.1 and 8.8 % (power-save.md). Expected: client side, "Losses towards the client" that the reference client does not have, in 18:37:54–38:14: 69.1 % of the AP's frames sent again against 11.5 % to the reference client on the same BSS, while BA success reads 98.4 % for the phone and 98.8 % for the Mac; no login captured, no roam.

sessions/testlab-long-quiet

The testlab scenario long-quiet (testlab.md) on the Mist AP45 (eduroam, 5 GHz ch 52/20), recorded by the app with the Mist polls, capA on ch 52 and capB on the AP45's 2.4 GHz radio (ch 1), 51 minutes of the phone in normal use: idle, a YouTube video, idle, a second video, browsing, a call of five minutes over Wi-Fi, idle. Over the whole recording the AP sent the phone 2 to 23 % of its frames again per minute in the videos, the browsing and the call. Cut to four stretches (editcap on Pi time, each starting and ending on a beacon of the serving BSS on capA, so the gaps between them read as no beacon loss; the status files left whole): 20:24:30–20:27:00, the phone falling asleep at the end of the first video, its own FT roam to the 2.4 GHz radio at 20:25:04 and back at 20:26:05 as the second video started; 20:30:40–20:31:20, the second video's bursts after wakes; 20:41:50–20:42:30, browsing; 20:56:00 to the end, the 2.4 GHz radio deauthenticating the phone at 20:56:48, 31 minutes after it left that BSS, eight times without an ACK, and Mist's CLIENT_DEAUTHENTICATION with reason 4 for that BSS (mist-events.md). Expected: no hint in any window, undecided, nothing abnormal. The deauthentications are stale, on a BSS the client had left; the roam reads 15 ms, as the phone sent no data in the 206 ms before it announced power save; what the AP sent again or lost the first time is a note, without a reference client to compare: up to 39.1 % sent again in the video's bursts after wakes (20:31:09–29, the note shown), BA success 82.0 % while browsing with all of the loss right after 33 wakes, and 16.3 % sent again right after the roam back with the phone awake. "On 2.4 GHz" is a note too, in 20:24:54–25:14 right after the roam to the 2.4 GHz radio: the AP45's 5 GHz −51.0 dBm here while the phone fell asleep on 2.4 GHz. Downlink Good at 97.7 % over the four stretches; over the whole 51 minutes uncut, outside the repo, BA success 98.4 % down and 100.0 % up (replayed at dfc1efb on 2026-11-03; 98.2 % down with the rule before).

sessions/testlab-ip-services-dns

The testlab scenario ip-services (testlab.md) with a DNS server of its own, on eduroam (testedu, WPA3-Enterprise with FT, PMF) on the Mist AP45, 5 GHz ch 52/20, beside Mist: recorded by the app with the Mist polls, capA on the AP's channel and capB on its 2.4 GHz radio, 221 s. The phone (…:9b), 1 m from the sensor, gets Configure DNS set to Manual with 192.0.2.1 alone, then its Wi-Fi off and on: an encrypted disassociation at 17:57:51 (Mist's CLIENT_DEASSOCIATION, reason 8), back after 8 s with a full EAP-TLS and the 4-way. iOS took the server at once: Mist's MARVIS_DNS_FAILURE is from 17:57:17, before the reconnect, and came 28 to 58 s late; after it DHCP took 2.0 s and the gateway's ARP 4 ms, and no second DNS failure came (mist-events.md). The other two sessions of the scenario, a gateway no host has and an address of another subnet, stay outside the repo: Mist gave no event for either. Expected: upstream, "DNS fails" from Mist, Connection Poor; the note "Sent again after wakes" in 17:57:04–24, 32.5 % of the AP's frames sent again with one wake; the other questions Good, no roam.

sessions/testlab-4way-ack

The testlab baseline again (testlab.md), on 2026-10-23 with the client's CTS and ACK to the AP in the focus filter: the A9000 client (…:07:16, psk.conf) on AP A, testroam 5 GHz ch 48/80, capA on 5240/80, recorded by the app with capA alone. Five restarts of wpa_supplicant, nine 4-ways in all, then UDP at 2 Mbit/s both ways, cut 20 s into it (editcap -B, the session's end moved along), 263 s. M1 is acknowledged every time; M3 goes out eight times in 4.5 ms, and in seven 4-ways none of them draws the client's ACK, in two the eighth. Four end without M4 in the AP's deauthentication with reason 15, 3.1 s after M3 (testlab-baseline.md). Expected: client side, "The client misses M3" (m3-missed), 4 of 9 4-ways, the last at 18:53:42; the other questions Good, no roam. Until 2026-10-31 also the note "Sent again" in 18:51:44–52:04, 19.4 % of the AP's frames sent again at 100 % BA success: the M3s of the two 4-ways in it, which count as EAPOL since, not as the link's data (definitions).

sessions/testlab-usb2-port

The USB-port check on the sensor, 2026-10-23: the phone (…:11:9b) on eduroam (testedu, WPA3-Enterprise with FT, PMF) on the Mist AP45, 5 GHz ch 52/20, beside Mist, with capA moved to a black USB 2 port of the Pi (usb=480 in every start line of capA.status) and capB on a blue USB 3 port (usb=5000) on the AP45's 2.4 GHz radio, ch 1; recorded by the app with the Mist polls, 121 s, uncut. The phone was used lightly: 330 Block Acks from it, 12,913 frames on capA, no drop counted by linkside-cap. At that load the port lost nothing that shows: the sensor decoded every downlink MPDU the phone acknowledged. The connection was there before the session, so no login and no profile. Expected: measurement limit, "Radio on a USB 2 port" (usb-port) whatever the load; the note "Sent again after wakes" in 22:47:56–48:16, the losses towards the phone right after its wakes from power save; downlink Fair at 95.9 %, the other questions Good, no roam. A busy channel on USB 2, where the port does lose Block Acks, is in sensor-link.md.

sessions/u7-phone-search-again

The re-search on a real roam (item 031), 2026-10-24, recorded by the app, 542 s, uncut. The phone (…:01:10, no 6 GHz) on testroam, starting on AP A (…:02:c1, 5 GHz ch 48/80, its 5 GHz power at Low); AP B (…:06:c8) moved to ch 100/80 for the test, −49 dBm at the sensor against AP A's −45. capA on 5240/80; capB first on AP B's 5500/80 by LockPlanner ("strongest neighbor with the same SSID"), at 09:58:12 moved by hand to 2412/20 ("chosen roam target", AP A's IoT radio), so the roam falls outside both radios. The phone, with a video, carried to the living room by AP B: its last frame on ch 48 at 09:58:44, the search from 09:59:45 (capB on the phone's last channel, capA sweeping), found at 09:59:53 on AP B, both radios locked again 2 s later (capA on 5500/80, capB back on 2412). Back at the desk the phone stays on AP B. No drop counted. Expected: one search in session.json with AP B as the BSS found, the windows across the gap judging no loss ("client not seen"); undecided, nothing abnormal, the note "Sent again after wakes" in 09:58:03–23, before the walk. Roaming untested, "not seen by the sensor", with 1 roam on its readout: the story's line at 09:59:53 reads "Client found on another AP", from AP A, and AP B serves the phone from then; the lane "AP at the sensor" draws ch 100 in the lighter step of 5 GHz's green.

sessions/u7-phone-search-copies

A client next to the sensor gone to a channel neither radio listens on, 2026-10-25 20:39, recorded by the app at cc85b5a, cut one minute after the find, 233 s. The phone (…:01:10) 1 m from the sensor with a video on testroam, on AP A (…:02:c1, 5 GHz ch 48/80 at Auto power); AP B (…:06:c8) on ch 100/80. capA on 5240/80; capB chosen on AP A's 6 GHz, 6135/160, which the phone does not use. The discovery found the phone on ch 48 and stopped there, so AP B's 5 GHz is not in the BSS table. At 20:41:21 AP A's 5 GHz transmit power went to disabled: AP A deauthenticates every client (reason 3), the phone's four authentications to AP A go unanswered and it deauthenticates (reason 3), then goes to AP B. In the minute after its last frame capA decodes 71 of the phone's frames to AP B and 46 of AP B's to it, all at legacy rates, the phone's at −83 to −94 dBm (a9000.md): copies of a BSS whose channel the table does not hold, which count nowhere. The search from 20:42:23, 60 s after the last frame (capB to 5240, capA sweeping), finds the phone on AP B at 20:42:31, AP B's beacon heard on 5500 in the sweep; capA locks on AP B (5500/80), capB goes back to 6135. No drop counted. Expected: AP side, "The AP disconnected every client"; the phone's own deauthentication ends its four unanswered authentications, a join without keys, no connection it left; the note "Sent again after wakes" on AP B in 20:42:39–59. One search in session.json with AP B as the BSS found; from the phone's last frame on AP A to the find no frame of it counts, on any link. Roaming untested, "not seen by the sensor", with 1 roam on its readout.

sessions/ap45-radius-timeout-reauth

The first field case (the case), unprovoked during a live session on 2026-10-26 at home: the phone (…:9b), idle with a video and dozing, on eduroam (testedu, WPA3-Enterprise with FT, PMF, EAP-TLS) on the Mist AP45, 5 GHz ch 128/20, beside Mist; recorded by the app at 7356529 with the Mist polls, capA on the AP's channel and capB on its 2.4 GHz radio, ch 1. Cut from 17:50 min to two minutes around the disconnect (editcap -A 1793027460.0 -B 1793027580.0 -F nsecpcap on capA and capB, Pi time; session.json's started and ended and the focus captures' ended moved to 1793027461.0 and 1793027580.5, Mac time; the status files and mist.json left whole), 120 s. At 16:11:42 the AP starts a reauthentication, encrypted: EAP on the AP's TID 5, readable by its lengths. RADIUS answers every step in 17 to 22 ms but not the phone's certificate flight; the AP starts over 29.9 s later, stops at the same step, and 17.7 s after it deauthenticates the phone (protected; Mist's CLIENT_DEAUTHENTICATION with reason 2 and MARVIS_EVENT_CLIENT_AUTH_FAILURE with authorization_reason_code 23, the portal's text naming a RADIUS timeout). The phone is back 336 ms later with a full EAP-TLS, every RADIUS round trip 17 to 26 ms. The engine sees none of the encrypted EAP. The reference client of the session (…:08:83) is left out by the keep list. Expected: upstream, a RADIUS timeout, a finding. The app says AP side, "Disconnected by the AP" with reason 2, a known mismatch (expected.json) until a rule takes a RADIUS timeout: mist.json keeps authorization_reason_code but not the 514 of the portal's text. Beside it the note "Sent again after wakes" in 16:11:10–30; the other questions Good, no roam.

Item 077's first measurement, 2026-10-27: the A9000 client (…:07:16, stick B on the Fedora PC, linkside.conf: SAE with H2E, PMF required) on AP A's linkside, WPA3 only on 6 GHz ch 37/160 with MLO off (U7 Pro Max firmware 8.7.11), typed in with its address, the channel and the BSSID; capA on 6135/160, recorded by the app with capA alone, 258 s, uncut. The session starts before the client joins: four 4-ways from 16:37:03, three ending in the AP's reason 15 after M3 went unacknowledged (testlab-baseline.md), then UDP at 2 Mbit/s both ways for two minutes; the client's address is heard, so no switch to the reconnect filter. The sensor decoded 7 % of the downlink's data and none of the uplink's at 160 MHz (eht-capture.md). Expected: client side, "The client misses M3" (m3-missed), 3 of 4 4-ways; BA success 100 % both ways, the other questions Good, no roam.

sessions/testlab-mlo-reconnect

Item 077's second measurement: the same, with MLO on (linkside on 5 GHz ch 48/80 and 6 GHz ch 37/160) and the client's MLD address (…:07:16) typed in with the 6 GHz BSSID. Recorded by the app, cut 0.5 s before the client's own deauthentication at the end (editcap -B, the session's end moved along), 239 s. The address sends nothing: after 20 s (NewSessionModel.mldAfter) the session switches to the reconnect filter, "Reconnect needed"; the client joins at 16:51:52 with an association request on 6 GHz whose Basic Multi-Link element names its link on 5 GHz, link 0 (…:17:cb to the AP's …:02:c1), beside the association link 1 (…:16:8e to …:02:c3), both accepted; the AP MLD is …:02:c0. Its per-STA profile carries no HE 6 GHz Band Capabilities and no Non-Inheritance element, so link 0's band comes from the AP's BSS, not the client's profile. The SAE commit and confirm carry the client's and the AP's Basic Multi-Link element with their MLD addresses (anonymize). Then UDP at 2 Mbit/s both ways, all of it on the 6 GHz link. Expected: undecided, nothing abnormal; BA success 100 % both ways, the other questions Good, no roam.

sessions/testlab-mlo-not-heard

Item 077's third measurement: the MLD address typed in as above, the client not connected, Stop after 83 s without a reconnect. Expected: measurement limit, "Client not heard: possibly Wi-Fi 7 multi-link" (not-heard); every question untested, "client not seen".

sessions/ap45-phone-reconnect

A disconnect and connect on the phone itself, 2026-10-27 21:06, recorded by the app at d9d4474 with the Mist polls, uncut, 142 s. The phone (…:11:9b) on eduroam (testedu, FT-802.1X, PMF) on the Mist AP45's 5 GHz ch 128/20 (…:10:92), capA on the AP's channel; capB first on the AP's 2.4 GHz radio, then on its 6 GHz ch 5/80 (…:10:81), chosen as roam target. At 21:06:47.433 an encrypted data frame from the phone with 16 bytes of payload, an EAPOL-Logoff by its size; 15 ms later the AP's deauthentication in the clear with reason 36, the phone's SA Query and its protected disassociation; Mist logs the deauthentication with reason 36 (mist-events.md). The phone joins again 3 s later with EAP-TLS and the 4-way; Mist logs ARP, DHCP and DNS. Expected: undecided, nothing abnormal, the reconnect the user made; the app gave AP side, "Disconnected by the AP" with reason 36, until client-disconnect read the AP's reason 36 as the client leaving (2026-10-27); since item 131 the logoff before the deauthentication names the client, by the frames, and Mist's use of 36 where the capture misses the logoff (reason codes). Beside it the note "Sent again after wakes"; the other questions Good, no roam.

sessions/ap45-phone-reauth-asleep

The second field case (the case), unprovoked during a live session on 2026-10-29 at home, recorded by the app at 7d84c75 with the Mist polls: the same 45 minutes as ap45-radius-slow-login, cut to 12:04 to 12:06 (editcap -A 1793271835.5 -B 1793271955.5 -F nsecpcap on capA and capB, Pi time; session.json's started and ended and the focus captures' ended moved to 1793271840.1 and 1793271960.0, Mac time; the status files and mist.json left whole), 120 s. The phone (…:18:c8) on eduroam (testedu, FT-802.1X, PMF, EAP-TLS) on the Mist AP45's 5 GHz ch 104/20 (…:10:92), idle for eleven minutes after a video, its screen black; capA on the AP's channel and capB on its 2.4 GHz radio, ch 11 (…:10:a1). At 12:04:33 the AP starts a reauthentication, encrypted on its TID 5: the phone fetches the 115-byte identity request five times in 41 s, each when the beacon's TIM flags it, acknowledges it and dozes again without an answer. 48 s after the first request the beacons flag a frame for the phone that it never fetches, and the AP drops it; at its next wake, 12:05:33.47, the AP answers its frame with a deauthentication in the clear, reason 7, and the phone's SA Query goes unanswered before it disassociates. Its first login, at 12:05:34.99, waits 6.92 s on the RADIUS server after its certificate flight and starts over; the second gets the server's answer after 9.57 s, the 4-way at 12:05:52.05. Mist logs two MARVIS_EVENT_CLIENT_AUTH_FAILURE with reason 2 and code 768, the second with 514, and no deauthentication. The engine sees none of the encrypted EAP. Expected: client side, a finding: the phone left the reauthentication unanswered. The app says AP side, "Disconnected by the AP" with reason 7, a known mismatch (expected.json) until the rule of item 139. Beside it radius-latency, upstream, "Slow RADIUS server", 9574 ms, as in ap45-radius-slow-login; the downlink untested, too little traffic; the other questions Good, no roam. Until 2026-10-30 the uplink read Fair, 93.2 % BA success on 103 MPDUs, seven of them given up by a BAR of the phone after the AP45's answer labelled TID 0 (testlab-aps.md).

sessions/ap45-radius-unanswered-login

A reconnect on the phone, 2026-10-29 11:17 at home, recorded by the app at 7d84c75 with the Mist polls, uncut, 273 s. The phone (…:18:c8, a new private address with the EAP-TLS identity of …:11:9b) on eduroam (testedu, FT-802.1X, PMF) on the Mist AP45's 5 GHz ch 104/20 (…:10:92), capA on the AP's channel and capB on its 2.4 GHz radio, ch 11 (…:10:a1). At 11:18:18.06 the phone's encrypted EAPOL-Logoff, the AP's deauthentication in the clear with reason 36 and the phone's protected disassociation, as in ap45-phone-reconnect; it joins again at 11:18:21.54 with a full EAP-TLS. RADIUS answers the TLS Start and the four fragments of the server's certificate chain in 17 to 23 ms, then not the phone's certificate flight (1251 bytes at 11:18:21.78, acknowledged by the AP): 29.88 s later the AP asks for the identity again with a new identifier (107 after 83), as in the reauthentication of ap45-radius-timeout-reauth, and the second attempt runs through in seven round trips of 16 to 26 ms, the 4-way at 11:18:51.86. Mist logs the deauthentication with reason 36 and CLIENT_AUTH_ASSOCIATION with time_since_assoc 30327 ms, then DHCP, ARP and DNS; no auth failure. Expected: upstream, a finding. The app says upstream, "RADIUS server not answering", 29.9 s without an answer before the AP started over. Beside it the note "Sent again after wakes" in 11:20:58–21:18; the other questions Good, no roam. Until 2026-10-30 also the observation "Losses towards the AP" in 11:18:33–53, the first seconds after the login: 13 MPDUs a BAR of the phone gave up after the AP45's answer labelled TID 0 (testlab-aps.md).

sessions/ap45-radius-slow-login

The same phone and AP 7 minutes later, 2026-10-29 11:24, recorded by the app at 7d84c75 with the Mist polls; 45 minutes, cut to its first four, 11:24:05 to 11:28:00 (editcap -B, the session's end moved along; mist.json left whole, with the reauthentications at 11:39 and 11:52 and the AP's disconnect at 12:05 outside the cut; session.json's outcome set to the cut's, upstream, where the app gave the whole session AP side from that disconnect's reason 7), 235 s. capA on the AP's 5 GHz ch 104/20, capB on its 2.4 GHz radio, ch 11. At 11:26:34.62 the phone's encrypted EAPOL-Logoff, the AP's deauthentication in the clear with reason 36 and the phone's protected disassociation; it joins again at 11:26:39.83 with a full EAP-TLS. RADIUS answers the identity with the TLS Start and the phone's next four messages with the four fragments of the server's certificate chain, in 17 to 20 ms; the phone's certificate flight (1250 bytes at 11:26:40.05) the AP acknowledges 44 µs later and does not ask again for, and the server's next request (identifier 182 after 181, an EAP length of 61 bytes) comes at 11:26:50.01, 9958 ms later; EAP-Success and the 4-way at 11:26:50.05. Mist logs the deauthentication with reason 36 and CLIENT_AUTH_ASSOCIATION with time_since_assoc 10231 ms, then DHCP, ARP and DNS. Expected: upstream, a finding. The app says upstream, "Slow RADIUS server", "Up to 9958 ms between the client's answer and the next question". Beside it the note "Sent again after wakes" in 11:24:55–25:15; the other questions Good, no roam.

sessions/ap45-phone-video-reconnect

The phone with a YouTube video, 2026-10-30 16:24 at home, recorded by the app at 6a06bbb-dirty with the Mist polls, uncut, 576 s. The phone (…:18:c8) on eduroam (testedu, FT-802.1X, PMF, EAP-TLS) on the Mist AP45's 5 GHz ch 112/20 (…:10:92), capA on the AP's channel and capB on its 2.4 GHz radio, ch 11 (…:10:a1). The phone dozes between the video's chunks, 818 wakes. At 16:26:53.30 its Wi-Fi off and on in Control Center: its encrypted EAPOL-Logoff, the AP's deauthentication in the clear with reason 36, its SA Query unanswered and its protected disassociation, as in ap45-phone-reconnect. It joins again at 16:27:11.33 with a full EAP-TLS, RADIUS answering in 16 to 34 ms, and sets up Block Ack agreements for TID 6, 0, 5 and 1, each with a BAR that the AP answers labelled TID 0 (testlab-aps.md). From 16:29:30 to 16:33:30 the phone was carried away from the sensor, some 25 dB weaker there, the AP unchanged at −42 dBm. Mist logs the deauthentication with reason 36, CLIENT_AUTH_ASSOCIATION, DHCP, ARP and DNS. Expected: undecided, nothing abnormal, the reconnect the user made. The app said undecided with "Losses both ways" in 16:26:58–27:18, from the 11 MPDUs the phone's BAR for TID 0 gave up, until a BA that answers a BAR went on the BAR's flow (2026-10-30). Now the note "Sent again after wakes" in 16:26:08–26:28, 57.0 % of the AP's frames sent again, all right after wakes; the other questions Good, no roam.

sessions/testlab-wrong-psk-psk-iphone

The first held-out recordings (held out), 2026-11-03 at home, each recorded by linkside-cli live at 0ad1f33 with the Mist polls, the channel and BSSID typed in, uncut; times are the Mac's, the Pi's clock 8.3 s behind. The sensor 1 m from the client, capA on the client's AP45 5 GHz ch 132/20, capB on the roam target; the Mac's Wi-Fi on the IoT SSID whenever it was not the client, awdl0 down. A wrong password was made as the runbook's setup says. Here the iPhone (…:1a:70) on testwlan-psk, then on both AP45s, 300 s, capB on Automatic: the second AP45's ch 108. Four taps on the network, at 16:57:26, 16:57:45, 16:58:04 and 16:58:28; per tap the phone tries the AP45's 5 GHz, the second AP45's 5 GHz and the AP45's 2.4 GHz, which only Mist saw. Each full attempt is the AP's M1 four times, 1 s apart, each answered with M2, then the AP's deauthentication in the clear with reason 14 (MIC failure). After two failures on a BSS Mist logs REPEATED_AUTH_FAILURES, and the AP45's 5 GHz sent no M1 to the phone's third and fourth association: the phone gave up with its deauthentication, reason 15, after 6 s. Expected: client side. The app: client side, "Login failed: the AP stopped after the client's answer in the 4-way handshake". The attempts at the second AP45 are no roams: a join after one that got no keys has no connection to move from. No reference client.

sessions/testlab-wrong-psk-psk-mac

The same with the MacBook (…:1c:16), 17:13:03 for 300 s, after testwlan-psk went to the AP45 alone; capB on Automatic took the AP45's 2.4 GHz, ch 11. Clicks on the network in the Wi-Fi menu some 25 s apart, as fast as macOS allowed, each an attempt on the AP45's 5 GHz and then its 2.4 GHz. In each band two full 4-ways with M2 without M3 and the AP's reason 14, at 17:13:17 and 17:13:26 on 5 GHz, then for about a minute associations the AP answers without an M1, Mist's penalty box, which the Mac ends after 6 s, on 5 GHz with its deauthentication (15), on 2.4 GHz with its disassociation (8); full 4-ways again from 17:14:38 and 17:16:00. Mist: REPEATED_AUTH_FAILURES three times. The poller found Mist unreachable while the Mac was off the IoT SSID, and caught up. Expected: client side. The app: client side, "Login failed", with the note "On 2.4 GHz"; the Mac's disassociation of a join that never completed is a login it gave up, no connection it left.

sessions/testlab-wrong-psk-sae-iphone

The iPhone (…:1d:0d) on testwlan-1 with a wrong password, 17:23:42 for 300 s, capB on the second AP45's ch 108. Five taps, 17:24:05 to 17:25:27. The phone takes SAE (algorithm 3). At the first tap both AP45s answer its commit and refuse its confirm with status 1, four times each; to the next three taps they leave its commits unanswered, four per tap, 1 s apart; the fifth the second AP45 answers again and refuses. Mist: MARVIS_EVENT_SAE_AUTH_FAILURE with status 1 per refusal, also on 2.4 GHz, and REPEATED_AUTH_FAILURES three times. Expected: client side. The app: client side, "Login failed: the AP did not accept the client's SAE confirm", 12 times. The commits left unanswered in between are the AP's silence after a refusal, up to 82 s after it, which goes with the refusal (join-unanswered).

sessions/testlab-wrong-psk-sae-mac

The same with the MacBook (…:1e:7f), 17:31:24 for 300 s. Both AP45s answered its commit at 17:31:37, 17:32:42, 17:33:53 and 17:34:58 and refused its confirm four times each; the commits between went unanswered, Mist's penalty box of about a minute. Mist: the SAE failure 32 times, REPEATED_AUTH_FAILURES seven times. Expected: client side. The app: client side, "Login failed", 30 confirms refused or unanswered; the commits between them, up to 63 s after a refusal, go with it, as with the iPhone.

sessions/testlab-client-leaves-psk-iphone

The iPhone (…:1a:70) on testwlan-psk with a YouTube video, 17:40:47 for 240 s, capB on the AP45's 2.4 GHz. Wi-Fi off in Settings at 17:42:06: its disassociation in the clear with reason 8, and nothing more of it. Mist: CLIENT_DEASSOCIATION with reason 8. Expected: client side. The app: as expected, "The client left (reason 8)"; downlink and uplink Good.

sessions/testlab-client-leaves-psk-mac-settings

The MacBook (…:1c:16) on testwlan-psk with a YouTube video in the browser, 17:50:47 for 240 s. Wi-Fi off in System Settings at 17:52:00 and on at 17:54:15, back on the IoT SSID. The Mac's last frames at 17:52:09 are data, Block Acks and an RTS: no deauthentication, disassociation or null frame. The AP then keeps trying for the rest of the recording, 159 s, heard on capA until the search took it away after 60 s and on capB from then on: 2,077 RTS and Block Ack Requests and 203 action frames, all unanswered, and no data; no deauthentication in the air, and Mist logged nothing of the leave within the session. Expected: client side. The app: client side, "The client went silent", with the note "Sent again" from Mist's count.

sessions/testlab-client-leaves-psk-mac-menu

The same, 17:58:50 for 240 s, Wi-Fi off in the menu bar's Wi-Fi menu at 18:00:40: the Mac's disassociation in the clear with reason 8, after its data. Mist: CLIENT_DEASSOCIATION with reason 8. Expected: client side. The app: as expected, "The client left (reason 8)", and the note "Sent again after wakes".

sessions/testlab-client-leaves-sae-iphone

The iPhone (…:1d:0d) on testwlan-1 (SAE, PMF) on the AP45's 5 GHz (…:10:96) with a YouTube video, 18:06:05 for 240 s, capB on the second AP45's ch 108. Wi-Fi off in Settings at 18:07:26: its protected disassociation, 98 bytes. Mist: CLIENT_DEASSOCIATION with reason 8. Expected: client side, the reason from Mist; on a U7 without Mist the same leave is the note "The client left (PMF)" (testlab-client-leaves-sae). The app: as expected, "The client left (reason 8)", the reason from Mist.

sessions/testlab-client-leaves-sae-mac

The MacBook (…:1e:7f) on testwlan-1 on the AP45's 5 GHz with a YouTube video, 18:13:13 for 240 s. Wi-Fi off in the menu bar at 18:14:31: its protected disassociation. Mist: CLIENT_DEASSOCIATION with reason 8. Expected: client side. The app: as expected, the reason from Mist, and the note "Sent again both ways", 24.3 % and 22.0 % with BA success 100 %.

sessions/testlab-client-leaves-eduroam-iphone

The iPhone (…:1f:9e) on eduroam (testedu, FT-802.1X, PMF) on the AP45's 5 GHz (…:10:92) with a YouTube video, 18:21:01 for 240 s. Wi-Fi off in Settings at 18:22:32: a protected data frame from the phone, its EAPOL-Logoff by its size, and 8 ms later its own protected disassociation, with no frame of the AP between, the order of 2026-11-02 (mist-events.md). Mist: CLIENT_DEASSOCIATION with reason 8, no auth failure. Expected: client side. The app: as expected, "The client left (reason 8)", the reason from Mist; client-disconnect on eduroam held out.

sessions/testlab-client-leaves-eduroam-mac

The MacBook (…:14:e4) on eduroam on the AP45's 5 GHz with a YouTube video, 18:28:13 for 240 s. Wi-Fi off in the menu bar at 18:29:44: its EAPOL-Logoff, the AP's deauthentication in the clear with reason 36 20 ms later, the Mac's SA Query and its protected disassociation, as its Disconnect of 2026-11-02; back on eduroam at 18:31:53 when Wi-Fi came on again. Mist: CLIENT_DEAUTHENTICATION with reason 36. Expected: client side. The app: as expected, "The client logged off; the AP ended the session (reason 36)", who began from its logoff 20 ms before.

sessions/testlab-ap-kick-psk-iphone

The iPhone (…:1a:70) on testwlan-psk with a YouTube video, 18:35:53 for 240 s, capB on the AP45's 2.4 GHz. Reconnect on its page in the Mist portal twice: at 18:37:06.8 and 18:38:18.7 the AP's deauthentication in the clear with reason 2, and the phone's reassociation 115 ms later, accepted. Mist: CLIENT_DEAUTHENTICATION with reason 2, then CLIENT_AUTH_REASSOCIATION. Expected: AP side. The app: AP side, "Disconnected by the AP", 2 times, reason 2. The recording and its MacBook twin are the measurement of Mist's reason 2 in the clear on a connection without PMF (reason codes).

sessions/testlab-ap-kick-psk-mac

The same with the MacBook (…:1c:16), 18:43:13 for 240 s: the AP's deauthentication with reason 2 in the clear at 18:44:31.7 and 18:45:46.8, the Mac's authentication and reassociation 120 ms later. Mist as with the iPhone. Expected: AP side. The app: AP side, as with the iPhone.

sessions/testlab-ap-kick-eduroam-mac

The MacBook (…:14:e4) on eduroam on the AP45's 5 GHz with a YouTube video, 18:50:04 for 240 s. Reconnect twice: at 18:51:15.7 and 18:52:40.7 the AP's protected deauthentication, and the Mac's authentication and association 215 ms later, a new association with a full EAP-TLS. Mist: CLIENT_DEAUTHENTICATION with reason 2, then CLIENT_AUTH_ASSOCIATION. Expected: AP side. The app: as expected, "Disconnected by the AP, 2 times, reason 2", the AP began, protected while the sensor heard the link; the note "Sent again after wakes".

sessions/testlab-ap-kick-eduroam-iphone

The iPhone (…:1f:9e) on eduroam, a second sample beside testlab-ap-kick-eduroam, 19:01:08 for 240 s. Before the recording it had joined the second AP45 and roamed by itself with 802.11r to the AP45 (Mist's MARVIS_EVENT_ROAMING_SUCCESS). Reconnect twice: the AP's protected deauthentication at 19:02:16.8 and 19:03:31.7, the phone's new association 155 ms later. Expected: AP side. The app: as expected.

sessions/testlab-upstream-iphone

The testlab scenario upstream (testlab.md), held out. The iPhone (…:1d:0d) on testwlan-1 on the AP45's 5 GHz ch 132, capB on the second AP45's ch 108, 22:25:58 for 241 s, the Fedora's Ethernet shaped to 1 Mbit/s (tbf). Safari downloads a file of 32 MB from the Fedora from about 22:26:30 to 22:29:35. Mist: no event. Expected: upstream, which no rule finds since wifi-fine was removed (2026-10-26): expected.json names the mismatch. The app: "Nothing abnormal found", BA success 100 % both ways, no rule of the air fired. Captured 98 % down and 90 % up.

sessions/testlab-upstream-mac

The same with the MacBook (…:1e:7f), 22:33:23 for 241 s, iperf3 from the Mac with UDP at 2 Mbit/s both ways (--bidir) from 22:33:55 to 22:36:56: 2.00 Mbit/s up without loss and 942 kbit/s down, with 53 % lost in the shaper. Mist: its join before the recording, with CLIENT_GW_ARP_OK and CLIENT_DNS_OK. Expected: upstream, the same known mismatch. The app: "Nothing abnormal found", BA success 99.9 % both ways, and the note "Sent again" in the window 22:37:00 to 22:37:20, just after the traffic, on 178 of the AP's data frames, 29 with the retry bit. Captured 95 % down and 61 % up.

sessions/testlab-co-channel-mac

The testlab scenario co-channel (testlab.md), held out. Both AP45s' 5 GHz radios set to ch 132 at 20 MHz in Mist. The MacBook (…:1e:7f) on testwlan-1 on the AP45, capB on the AP45's 2.4 GHz, as the roam target typed in was capA's channel, 23:10:11 for 421 s, iperf3 from the Mac with UDP at 2 Mbit/s both ways from 23:10:38 to 23:16:38: nothing lost up, 0.2 % down. The iPhone, next to the second AP45 and on it, downloads big.bin in Safari from about 23:12:10 to 23:14:05, at about 200 Mbit/s: the AP45's QBSS goes from 10 % to 91 % and back to 11 %, the retry bit of the AP's frames from 4 % to 6 to 14 %. Mist: no event. Expected: RF environment. The app: as expected, "Busy channel" from 23:12:06 to 23:14:01, the load whole: its first 45 s with the retry bit at 10 % and more both ways, the rest on the load alone, QBSS 90 to 92 % with the MacBook's own airtime 2 to 4 %, once the client's retry bit had fallen below 10 %; the worst window 23:12:06 to 23:12:26 at 91.8 %. The MacBook's RTS to the AP went unanswered 10 to 15 % under the load against 0.1 % before it. Until 2026-11-04 the finding held the first 45 s alone. BA success 99.8 % down and 99.9 % up. Captured 95 % down and 90 % up.

sessions/testlab-co-channel-iphone

The same with the iPhone (…:1d:0d) on the AP45 as the client and stick B on the second AP45 as the load, so capA alone, 23:33:32 for 451 s. Safari downloads a file of 128 MB through a shaper of 2 Mbit/s on the web server's port, from about 23:34:25 to 23:40:00; stick B takes iperf3 -R for 120 s from 23:37:10 to 23:39:05, at about 170 Mbit/s: the AP45's QBSS goes from 7 % to 92 % and back, the retry bit of the AP's frames from 2 to 3 % to 3 to 5 %. Mist: its join before the recording, with CLIENT_GW_ARP_OK and CLIENT_DNS_OK. Expected: RF environment. The app: as expected, "Busy channel" on the load alone from 23:37:06 to 23:39:06, the worst window 23:38:11 to 23:38:31 at QBSS 92.6 % with the phone's own airtime 1 to 2 %: the retry bit 3 to 7 % down and under 1 % up, the median MCS 8 to 9 as before the load, BA success 99.7 % and up, and the phone's RTS to the AP unanswered 8 to 15 % against 0 to 0.5 % before it. Until 2026-11-04 it read "Nothing abnormal found", a known mismatch, as congestion asked for 10 % of retries or a low MCS besides the load. BA success 100.0 % down and 99.9 % up; the note "Sent again after wakes" in the window 23:33:51 to 23:34:11, before the download. Captured 89 % both ways.

sessions/testlab-intermittent-loss-psk-iphone-moved

The testlab scenario intermittent-loss held out with the iPhone (…:1a:70), by linkside-cli live at 4fa1869 with changes of its own, as session.json says, with the Mist polls, capA alone on 2437/20 on a USB 2 port, 17:28:57 for 420 s, times the Mac's, the Pi's clock 10.2 s behind. testwlan-psk, on 2.4 GHz alone for the session, on the AP45 in the office, its 2.4 GHz set to ch 6 in Mist, at 17 dBm; the second AP45 on ch 11, AP B's 2.4 GHz off. The office U7 was on ch 6 as well, not ch 1 as the setup has it: testroam-iot at −62 dBm in the first second of the discovery, before the focus filter left its frames out, with Mist's util_all at 35 %. The phone and the sensor 1 m apart and 2 m from the oven, Safari downloading a file of 128 MB through a shaper of 2 Mbit/s on the web server's port. The oven with a cup of water from 17:31:00 for 33 s; in the air from about 17:31:14 to 17:31:33, the AP's frames sent again at about 46 % against 10 % before, the client's at about 30 % against 20 %. The phone's screen had to stay on, so it was held and moved now and then, around 17:34 among them. Mist: no event. Expected: RF environment, a finding, in the oven's window. The app: RF environment, "Losses both ways", in the window 17:31:12 to 17:31:32, 32.9 % of the AP's frames by Mist's count against its usual 19.4 % and 28.8 % of the client's against 19.2 %, a jump both ways. As recorded the finding stood in the window 17:34:17 to 17:34:37, with BA success down at 89.6 % and the client's frames sent again at 19.2 %, about what they are all session, and the oven's window was a note; that window, with 139 wakes against about 55, is now the note "Losses towards the client", behind the finding. The note "Sent again towards the AP" in the window 17:29:37 to 17:29:57. BA success 94.3 % down and 100.0 % up over the session. Captured 97 % down and 100 % up.

sessions/testlab-intermittent-loss-psk-iphone

The same with the office U7's 2.4 GHz off, ch 6 left to the AP45's three BSSs and a neighbor at −91 dBm, Mist's util_all at 27 %, and the phone left alone with Auto-Lock on Never, 17:47:04 for 420 s. The oven from 17:49:00 for 30 s, in the air from about 17:49:08 to 17:49:30: the AP's data frames the sensor decoded fall from about 1,500 per 10 s to about 650 as it captures less, its new MPDUs by 22 % (the oven), its frames sent again rise to about 40 % against 11 % before, the client's to about 34 % against 18 %. Mist: no event. Expected: RF environment, a finding, in the oven's window. The app: RF environment, "Losses both ways", in the window 17:49:09 to 17:49:29. BA success stayed at 99.0 % down and 100.0 % up, as the retries delivered what the oven took; 31.6 % of the AP's frames by Mist's count and 33.6 % of the client's by the retry bit are a jump both ways against a usual 18.6 % and 18.8 %. As recorded it read "Nothing abnormal found", frames sent again alone a note. Without the oven no finding in the rest of the session: the notes "Sent again towards the AP" in the window 17:47:54 to 17:48:14 and "Sent again" in the window 17:52:09 to 17:52:29. The TXOPs with RTS that failed: 55.0 % down and 38.1 % up in the oven's window, 48.7 % and 19.8 % over the session. BA success 96.1 % down and 100.0 % up over the session. Captured 97 % down and 100 % up.

mist/

Sample responses from the Mist API per endpoint (self, client search, client stats, client events, device stats), with test addresses, following the shape in the Mist OpenAPI spec. The client events follow the shape of the real responses of the AP45 (mist-events.md): a client that turned its Wi-Fi off, and its login before it. For the tests of the Mist client. const-client-events.json is Mist's own list of client event types (GET /api/v1/const/client_events, without a token, 2026-10-22), against which the types ip-services judges are checked.

Reports

reports/testlab-ap-kick-eduroam.md is the report the app writes for one recording: the AP side finding (ap-disconnect, with its clock time), a note, the story with Mist's lines, and the technical tables, under invented names (a phone of "Alex", the AP "AP47-Test-1", the site "Test site", version 0.1 (1)) that a live session would have taken from Mist. ReportTests/sampleReport makes the report from the recording again, in the time zone Europe/Amsterdam, and compares. After a deliberate change in the report or the engine:

cd app/LinksideKit && LINKSIDE_UPDATE_GOLDEN=1 swift test --filter ReportTests

and review the diff of the file. It is one report, not one per recording: a change in the wording of a hint shows in the verdict files of the golden tests, a change in the layout of the report shows here.

Golden files

Per session in sessions/ two golden files of its SessionSummary. golden.json is the engine's part: the captures, the BSS table, the AP MLDs, the clients with their events, BA success, metrics, profiles, MLO mapping and handshakes, and the anomalies. verdict.json is the verdict: the outcome of the hints with the window or the moment of each, and the answer per question with what stayed untested. A diff shows which of the two changed. The hints take the Mist polls from mist.json where a session has one. GoldenTests compares both against them. After a deliberate change:

cd app/LinksideKit && LINKSIDE_UPDATE_GOLDEN=1 swift test --filter GoldenTests

and review the diff of the golden files, golden.json and verdict.json.

No recording holds a record twice: RecordCopyTests fails on a record with the same time, length and bytes as an earlier one in the same pcap, one reception written twice, which the engine would count twice: a frame, its airtime and, of an RTS, an unanswered TXOP. Five recordings held one to five, cut out: four of them at a switch of the capture, from before Subprocess closed a stopped decode's input (2026-10-24), and u7-phone-search-again two probe requests 29 s into capB's context sweep, with no switch within a minute and no explanation. An hour live on the sensor on 2026-11-03 held none on either radio, across nine switches on capA and eight on capB, with 18 s of context sweep (decode-cost.md).

Next to it an expected.json: the right answer for the recording, written by a person from the scenario before any rule change, and rewritten by nothing. It holds what was caused (cause), the outcome (outcome), whether the app should raise a finding, a hint that is not a note (finding), for a known difference the reason (mismatch), and per rule, with its reason, a rule whose every finding in the recording is wrong beside the right outcome (falseFindings): such a finding confirms nothing and counts as no finding in the score (rules, the last column of the rule table). GoldenTests prints a score line per recording, right or wrong, and fails on a recording without an expected.json, on a wrong answer without a mismatch, and on a mismatch the app no longer has. The golden file shows that something changed, the expected outcome whether it is right.