diff --git a/esp32_marauder/WiFiScan.cpp b/esp32_marauder/WiFiScan.cpp index 23c4ff9..8c69923 100644 --- a/esp32_marauder/WiFiScan.cpp +++ b/esp32_marauder/WiFiScan.cpp @@ -6,6 +6,9 @@ struct mac_addr* mac_history = nullptr; #endif +MacEntry WiFiScan::mac_entries[mac_history_len]; +uint8_t WiFiScan::mac_entry_state[mac_history_len]; + int num_beacon = 0; int num_deauth = 0; int num_probe = 0; @@ -1739,6 +1742,9 @@ void WiFiScan::RunSetup() { mac_history = (struct mac_addr*) ps_malloc(mac_history_len * sizeof(struct mac_addr)); #endif + for (int i = 0; i < mac_history_len; i++) + mac_entry_state[i] = 0; + #ifdef HAS_BT watch_models = new WatchModel[26] { {0x1A, "Fallback Watch"}, @@ -2611,6 +2617,16 @@ bool WiFiScan::mac_cmp(struct mac_addr addr1, struct mac_addr addr2) { return true; } +bool WiFiScan::mac_cmp(uint8_t addr1[6], uint8_t addr2[6]) { + //Return true if 2 mac_addr structs are equal. + for (int y = 0; y < 6 ; y++) { + if (addr1[y] != addr2[y]) { + return false; + } + } + return true; +} + #ifdef HAS_BT void WiFiScan::copyNimbleMac(const BLEAddress &addr, unsigned char out[6]) { #ifndef HAS_DUAL_BAND @@ -2625,7 +2641,7 @@ bool WiFiScan::mac_cmp(struct mac_addr addr1, struct mac_addr addr2) { } #endif -bool WiFiScan::seen_mac(unsigned char* mac) { +bool WiFiScan::seen_mac(unsigned char* mac, bool simple) { //Return true if this MAC address is in the recently seen array. struct mac_addr tmp; @@ -2633,6 +2649,7 @@ bool WiFiScan::seen_mac(unsigned char* mac) { tmp.bytes[x] = mac[x]; } + for (int x = 0; x < mac_history_len; x++) { if (this->mac_cmp(tmp, mac_history[x])) { return true; @@ -2641,6 +2658,150 @@ bool WiFiScan::seen_mac(unsigned char* mac) { return false; } +int16_t WiFiScan::seen_mac_int(unsigned char* mac, bool simple) { + //Return true if this MAC address is in the recently seen array. + + uint8_t tmp[6]; + for (int x = 0; x < 6 ; x++) { + tmp[x] = mac[x]; + } + + for (int x = 0; x < mac_history_len; x++) { + if (this->mac_cmp(tmp, mac_entries[x].mac)) { + return x; + } + } + return -1; +} + +inline uint32_t WiFiScan::hash_mac(const uint8_t mac[6]) { + uint32_t hash = 2166136261u; // FNV offset basis + + for (int i = 0; i < 6; i++) { + hash ^= mac[i]; + hash *= 16777619u; // FNV prime + } + + return hash; +} + +int WiFiScan::update_mac_entry(const uint8_t mac[6]) { + const uint32_t now_ms = millis(); + const uint32_t start_idx = hash_mac(mac) & (mac_history_len - 1); + + int32_t first_tombstone = -1; + + for (uint32_t probe = 0; probe < mac_history_len; probe++) { + const uint32_t idx = (start_idx + probe) & (mac_history_len - 1); + + switch (mac_entry_state[idx]) { + + case EMPTY_ENTRY: + // Insert new entry (prefer earlier tombstone if found) + if (first_tombstone >= 0) { + insert_mac_entry(first_tombstone, mac, now_ms); + } else { + insert_mac_entry(idx, mac, now_ms); + } + return EMPTY_ENTRY; + + case TOMBSTONE_ENTRY: + // Remember first tombstone for possible reuse + if (first_tombstone < 0) { + first_tombstone = idx; + } + break; + + case VALID_ENTRY: + // Check for MAC match + if (memcmp(mac_entries[idx].mac, mac, 6) == 0) { + mac_entries[idx].last_seen_ms = now_ms; + + if (mac_entries[idx].frame_count < UINT16_MAX) { + mac_entries[idx].frame_count++; + } + + return idx; + } + break; + } + } + + // Table full: evict something (simple policy: overwrite first tombstone or oldest) + evict_and_insert(mac, now_ms); + + return TOMBSTONE_ENTRY; +} + +inline void WiFiScan::insert_mac_entry(uint32_t idx, const uint8_t mac[6], uint32_t now_ms) { + memcpy(mac_entries[idx].mac, mac, 6); + mac_entries[idx].last_seen_ms = now_ms; + mac_entries[idx].frame_count = 1; + mac_entries[idx].flags = 0; // set as needed + mac_entry_state[idx] = VALID_ENTRY; +} + +void WiFiScan::evict_and_insert(const uint8_t mac[6], uint32_t now_ms) { + constexpr uint32_t EVICT_AGE_MS = 60 * 1000UL; + + // 1) Safety: if any tombstone exists, reuse it. + for (uint32_t i = 0; i < mac_history_len; i++) { + if (mac_entry_state[i] == TOMBSTONE_ENTRY) { + insert_mac_entry(i, mac, now_ms); + return; + } + } + + // 2) Find a VALID entry older than EVICT_AGE_MS (prefer stalest among those). + int32_t best_old_idx = -1; + uint32_t best_old_age = 0; + + // 3) Fallback: track absolute oldest VALID entry in case none exceed 60s. + int32_t oldest_idx = -1; + uint32_t oldest_age = 0; + + for (uint32_t i = 0; i < mac_history_len; i++) { + if (mac_entry_state[i] != VALID_ENTRY) { + continue; + } + + const uint32_t age = (uint32_t)(now_ms - mac_entries[i].last_seen_ms); + + // Track absolute oldest + if (oldest_idx < 0 || age > oldest_age) { + oldest_idx = (int32_t)i; + oldest_age = age; + } + + // Track best eviction candidate over threshold + if (age > EVICT_AGE_MS) { + if (best_old_idx < 0 || age > best_old_age) { + best_old_idx = (int32_t)i; + best_old_age = age; + } + } + } + + // Choose eviction target + int32_t victim = (best_old_idx >= 0) ? best_old_idx : oldest_idx; + + if (victim >= 0) { + // Optional: mark tombstone first (not strictly required if you're overwriting, + // but keeps state transitions explicit). + mac_entry_state[victim] = TOMBSTONE_ENTRY; + + // Overwrite with new entry + insert_mac_entry((uint32_t)victim, mac, now_ms); + Serial.println(macToString(mac_entries[victim].mac) + " expired"); + return; + } + + // If we got here, something is inconsistent (no valid entries but "full" insert path). + // As a last resort, just insert at 0. + insert_mac_entry(0, mac, now_ms); +} + + void WiFiScan::save_mac(unsigned char* mac) { //Save a MAC address into the recently seen array. if (this->mac_history_cursor >= mac_history_len) { @@ -7278,6 +7439,31 @@ void WiFiScan::beaconSnifferCallback(void* buf, wifi_promiscuous_pkt_type_t type } else if (wifi_scan_obj.currentScanMode == WIFI_SCAN_DETECT_FOLLOW) { + // Skip if is a management frame that isn't probe request + if (type == WIFI_PKT_MGMT) { + if (snifferPacket->payload[0] != 0x40) + return; + } + + char addr[] = "00:00:00:00:00:00"; + getMAC(addr, snifferPacket->payload, 10); + + int frame_check = wifi_scan_obj.update_mac_entry(src_addr); + + Serial.print(addr); + + if (frame_check == EMPTY_ENTRY) { + Serial.println(" Added to table."); + } + else if (frame_check == VALID_ENTRY) { + Serial.println(" Updated in table"); + } + else if (frame_check == TOMBSTONE_ENTRY) { + Serial.println(" Evicted"); + } + else { + Serial.println(" Frames: " + (String)mac_entries[frame_check].frame_count + " Last Seen: " + (String)(millis() - mac_entries[frame_check].last_seen_ms)); + } } } @@ -10197,6 +10383,12 @@ void WiFiScan::main(uint32_t currentTime) channelHop(); } } + else if (currentScanMode == WIFI_SCAN_DETECT_FOLLOW) { + if (currentTime - initTime >= this->channel_hop_delay * HOP_DELAY) { + initTime = millis(); + channelHop(); + } + } else if ((currentScanMode == BT_SCAN_FLOCK) || (currentScanMode == BT_SCAN_FLOCK_WARDRIVE) || (currentScanMode == BT_SCAN_WAR_DRIVE) || diff --git a/esp32_marauder/WiFiScan.h b/esp32_marauder/WiFiScan.h index 6177f93..200a6e9 100644 --- a/esp32_marauder/WiFiScan.h +++ b/esp32_marauder/WiFiScan.h @@ -220,6 +220,10 @@ esp_err_t esp_wifi_80211_tx(wifi_interface_t ifx, const void *buffer, int len, b esp_err_t esp_base_mac_addr_set(uint8_t *Mac); #endif +#define EMPTY_ENTRY 0 +#define VALID_ENTRY 1 +#define TOMBSTONE_ENTRY 2 + #pragma pack(push, 1) struct MacEntry { uint8_t mac[6]; @@ -259,9 +263,6 @@ class WiFiScan struct mac_addr mac_history[mac_history_len]; #endif - static MacEntry mac_entries[mac_history_len]; - static uint8_t mac_entry_state[mac_history_len]; - uint32_t chanActTime = 0; uint8_t ap_mac[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; @@ -556,6 +557,7 @@ class WiFiScan uint8_t getSecurityType(const uint8_t* beacon, uint16_t len); void addAnalyzerValue(int16_t value, int rssi_avg, int16_t target_array[], int array_size); bool mac_cmp(struct mac_addr addr1, struct mac_addr addr2); + bool mac_cmp(uint8_t addr1[6], uint8_t addr2[6]); void clearMacHistory(); void executeWarDrive(); void executeSourApple(); @@ -626,6 +628,9 @@ class WiFiScan //LinkedList* ssids; + static MacEntry mac_entries[mac_history_len]; + static uint8_t mac_entry_state[mac_history_len]; + // Stuff for RAW stats uint32_t mgmt_frames = 0; uint32_t data_frames = 0; @@ -746,7 +751,11 @@ class WiFiScan #ifdef HAS_SCREEN int8_t checkAnalyzerButtons(uint32_t currentTime); #endif - bool seen_mac(unsigned char* mac); + bool seen_mac(unsigned char* mac, bool simple = true); + int16_t seen_mac_int(unsigned char* mac, bool simple = true); + int update_mac_entry(const uint8_t mac[6]); + inline void insert_mac_entry(uint32_t idx, const uint8_t mac[6], uint32_t now_ms); + void evict_and_insert(const uint8_t mac[6], uint32_t now_ms); void save_mac(unsigned char* mac); #ifdef HAS_BT void copyNimbleMac(const BLEAddress &addr, unsigned char out[6]); @@ -827,5 +836,6 @@ class WiFiScan static void pineScanSnifferCallback(void* buf, wifi_promiscuous_pkt_type_t type); // Pineapple static int extractPineScanChannel(const uint8_t* payload, int len); // Pineapple static void multiSSIDSnifferCallback(void* buf, wifi_promiscuous_pkt_type_t type); // MultiSSID + static inline uint32_t hash_mac(const uint8_t mac[6]); }; #endif