Files
cute-handshake-capture/handshake-capture-c6/IMPROVEMENTS.md
drjones 4bef28bd83 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
2026-03-17 23:19:58 -07:00

8.8 KiB
Raw Blame History

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