Improve capture performance: shorter RX lock, PMKID-first save, channel sweep

- Reduce time spent in capture spinlock by snapshotting slot BSSIDs and
  only locking for shared-state updates
- Add PMKID KDE detection to save on EAPOL M1 when present
- Sweep channels with remaining WPA targets instead of camping one channel

Made-with: Cursor
This commit is contained in:
drjones
2026-03-17 23:19:58 -07:00
parent ec3e0449a1
commit 4bef28bd83
3 changed files with 353 additions and 33 deletions

View File

@@ -0,0 +1,209 @@
# Firmware Performance Improvements
Review of handshake-capture-c6 for ESP32-C6 1.47" LCD. Ordered by impact and effort.
---
## 1. Promiscuous Callback — Reduce Critical Section Scope
**File:** `HandshakeCapture.cpp` ~lines 352385
**Issue:** The entire slot loop runs inside `portENTER_CRITICAL` / `portEXIT_CRITICAL`. For every frame we hold the spinlock across up to 3 slot checks, each with several `memcmp` calls. This blocks all other cores/tasks and can cause missed packets at high traffic.
**Change:** Shrink the critical section to only the minimal shared writes:
- Copy `slots`, `pending_save_slots`, `is_capturing`, `capturePhase` into locals at callback entry.
- Do all reads and logic outside the lock.
- Enter critical only for: `pcapAppendSlot`, `addClientToSlot`, `pending_save_slots[i] = i`, `slot->eapol_count++`, `slot->client_count` updates, `latest_eapol_count`.
**Effort:** Medium | **Impact:** High (fewer dropped frames)
---
## 2. Early-Exit in Callback for Non-Target Frames
**File:** `HandshakeCapture.cpp` ~lines 320385
**Issue:** For every data frame we iterate all 3 slots and do BSSID comparisons even when the frame has nothing to do with our targets. On busy channels thats a lot of wasted `memcmp`.
**Change:** Build a quick-reject set: hash or simple bloom of our target BSSIDs. If neither Addr1 nor Addr2 matches any target, `continue` before the slot loop. Alternatively, compare against slot BSSIDs only when frame type is beacon or data, and bail immediately when no match.
**Effort:** Low | **Impact:** Medium (less CPU per frame)
---
## 3. Staggered Deauth Bursts
**File:** `HandshakeCapture.cpp` ~`sendDeauthBurstAll`, `sendDeauthToClient`
**Issue:** All slots are deauthed back-to-back. Radio TX blinds RX; a burst of many deauths increases the chance we miss the following EAPOL.
**Change:** Interleave deauth with short RX gaps:
- 5 frames to client A → 50100ms delay → 5 frames to client B → delay → etc.
- Or: 1 frame per client, round-robin, 23 rounds with short gaps.
**Effort:** Low | **Impact:** MediumHigh (better EAPOL capture window)
---
## 4. PMKID-First Immediate Save
**File:** `HandshakeCapture.cpp` promiscuous callback, EAPOL branch
**Issue:** We only set `pending_save_slots[i] = i` when `eapol_count >= 2`. PMKID is in EAPOL Message 1; hashcat can use it alone. If we never see M2, we discard usable data.
**Change:** On first EAPOL (M1), parse key info byte; if PMKID flag is set, set `pending_save_slots[i] = i` immediately. Keep current logic for M2 as well. Ensures we dont drop PMKID-only handshakes.
**Effort:** Low | **Impact:** High (more crackable captures)
---
## 5. Replace `String` with Fixed Buffers in Hot Paths
**File:** `HandshakeCapture.h` `WifiNetwork`, `HandshakeCapture.cpp` `handshakeCaptureProcessPending` ~line 89
**Issue:** `String` uses dynamic allocation. In `handshakeCaptureProcessPending`, `filename += s->ssid` and similar can fragment heap. `WifiNetwork::ssid` and `encryption` as `String` add overhead in scan/capture paths.
**Change:** Use `char ssid[33]` and `char encryption[16]` in `WifiNetwork`. In `handshakeCaptureProcessPending`, use `snprintf` into a stack buffer for the filename. Reduces heap use and allocation during capture.
**Effort:** Medium | **Impact:** Medium (stability, less heap pressure)
---
## 6. SD Write Off Main Loop
**File:** `HandshakeCapture.cpp` `handshakeCaptureProcessPending`
**Issue:** SD writes block the main loop. A large PCAP (e.g. 4KB) can take 50200ms; during that time `handshakeCaptureLoop` doesnt run, LVGL stutters, and we might miss phase transitions.
**Change:** Defer SD write to a lower-priority task or timer callback. `handshakeCaptureProcessPending` only marks a slot “ready to write” and copies its buffer to a secondary buffer. A separate task or `loop()`-called `processSdQueue()` does the actual `SD.open` / `write` / `close`. Alternatively, use non-blocking SPI if available.
**Effort:** MediumHigh | **Impact:** Medium (smoother UI, fewer timing glitches)
---
## 7. Channel Hop Strategy — Full Sweep
**File:** `HandshakeCapture.cpp` `getBestChannel`, `handshake-capture-c6.ino` channel logic
**Issue:** We stay on the single “best” channel (most targets) for the full timeout. APs on ch 25, 710, 1214 are never visited.
**Change:** Maintain a channel list sorted by target count. After exhausting retries on channel N, move to channel N+1 instead of rescanning. Rotate through all channels that have uncaptured WPA targets. Reduces time spent on empty channels and improves coverage.
**Effort:** Medium | **Impact:** High (more handshakes per run)
---
## 8. RSSI-Based Slot Ordering
**File:** `HandshakeCapture.cpp` `startMultiCapture`, `scanNetworksSortedByRSSI`
**Issue:** Slots are filled in scan order. Stronger-signal APs often have faster reconnects and easier captures.
**Change:** When filling slots, sort `networks` by RSSI for the current channel, then assign slots. Prioritize high-RSSI targets. Optional: during observation, weight client mapping by RSSI.
**Effort:** Low | **Impact:** LowMedium (better success on marginal APs)
---
## 9. LVGL Update Throttling
**File:** `handshake-capture-c6.ino` `updateCaptureVisuals`
**Issue:** We iterate all networks and update LVGL every 400ms. When there are many networks, this does a lot of `lv_label_set_text` and `lv_obj_set_style_text_color` per cycle.
**Change:** Track `last_updated_index` and only refresh labels that actually changed (compare new text/color to previous). Batch LVGL updates or skip when `captureState != STATE_CAPTURING`.
**Effort:** Low | **Impact:** Low (smoother UI, less CPU)
---
## 10. `wasBssidCaptured` Linear Scan
**File:** `HandshakeCapture.cpp` `wasBssidCaptured`, `scanNetworksSortedByRSSI` ~line 219
**Issue:** `wasBssidCaptured` is O(n) per network during scan. With 20 networks and 32 cached BSSIDs, thats up to 640 comparisons per scan.
**Change:** Use a small hash set or sorted array + binary search for the BSSID cache. Or cache the last few lookups. For 32 entries, linear is acceptable; optimization is only if scan becomes a bottleneck.
**Effort:** Low | **Impact:** Low
---
## 11. Progress Bar Animation
**File:** `LVGL_Driver.cpp` `Ui_SetProgress` ~line 64
**Issue:** `lv_bar_set_value(..., LV_ANIM_ON)` triggers an animation every 400ms when `updateCaptureVisuals` runs, even when the value is unchanged.
**Change:** Only call `lv_bar_set_value` when `pct` or `current`/`total` actually changed. Use `LV_ANIM_OFF` for progress bar to avoid unnecessary redraws.
**Effort:** Trivial | **Impact:** Low (less LVGL work)
---
## 12. Disable Wi-Fi AP Mode When Not Needed
**File:** `HandshakeCapture.cpp` `handshakeCaptureInit`
**Issue:** `WiFi.mode(WIFI_AP_STA)` keeps the soft AP up. Youve said no web interface; the AP may be unnecessary and draws power.
**Change:** Use `WIFI_STA` only if no AP functionality is required. Verify promiscuous mode works in STA-only. If it does, drop AP to save power.
**Effort:** Low | **Impact:** Low (power, simpler state)
---
## 13. `millis()` in Critical Section
**File:** `HandshakeCapture.cpp` `pcapAppendSlot` ~line 249
**Issue:** `millis()` is called inside the spinlock-protected `pcapAppendSlot`. `millis()` can have non-trivial cost on some platforms.
**Change:** Call `millis()` once at callback entry, store in a local, pass it into `pcapAppendSlot` as a parameter. Keeps the critical section shorter.
**Effort:** Trivial | **Impact:** Low
---
## 14. `WiFiScanner_Init` Unused
**File:** `WiFi_Scanner.cpp` defines `WiFiScanner_Init`; its never called from the main sketch.
**Change:** Remove dead code or call it from `setup()` if its meant to configure WiFi before scanning.
**Effort:** Trivial | **Impact:** None (cleanup)
---
## 15. Scan: Hidden Network Support
**File:** `HandshakeCapture.cpp` `scanNetworksSortedByRSSI` ~line 205
**Issue:** `if (ssid.isEmpty()) continue` skips hidden networks entirely.
**Change:** Use `WiFi.scanNetworks(true, true)` to include hidden, and use a placeholder like `"<hidden>"` for empty SSID. Store BSSID and channel; we can still target by BSSID. Requires handling `<hidden>` in filenames and UI.
**Effort:** Low | **Impact:** Medium (more targets)
---
## Quick Wins (minimal code, clear benefit)
| # | Change | File |
|---|--------|------|
| 3 | Stagger deauth with RX gaps | HandshakeCapture.cpp |
| 4 | PMKID-first save on M1 | HandshakeCapture.cpp |
| 8 | RSSI-sort slots in `startMultiCapture` | HandshakeCapture.cpp |
| 11 | Only update progress bar when value changes | LVGL_Driver.cpp |
| 13 | Pass `millis()` into `pcapAppendSlot` | HandshakeCapture.cpp |
---
## Higher Effort, Higher Impact
| # | Change |
|---|--------|
| 1 | Shrink callback critical section |
| 5 | Replace String with fixed buffers |
| 6 | Offload SD write from main loop |
| 7 | Full channel sweep instead of single best |