- 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
8.8 KiB
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 352–385
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,capturePhaseinto 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_countupdates,latest_eapol_count.
Effort: Medium | Impact: High (fewer dropped frames)
2. Early-Exit in Callback for Non-Target Frames
File: HandshakeCapture.cpp ~lines 320–385
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 that’s 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 → 50–100ms delay → 5 frames to client B → delay → etc.
- Or: 1 frame per client, round-robin, 2–3 rounds with short gaps.
Effort: Low | Impact: Medium–High (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 don’t 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 50–200ms; during that time handshakeCaptureLoop doesn’t 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: Medium–High | 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 2–5, 7–10, 12–14 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: Low–Medium (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, that’s 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. You’ve 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; it’s never called from the main sketch.
Change: Remove dead code or call it from setup() if it’s 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 |