From ad602f3a2f0c035e5083f28d99c933379f4cf72e Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 6 Dec 2025 14:02:17 -0800 Subject: [PATCH] add wifi monitor --- components/wifi_monitor/CMakeLists.txt | 5 + components/wifi_monitor/wifi_monitor.c | 446 +++++++++++++++++++++++++ components/wifi_monitor/wifi_monitor.h | 261 +++++++++++++++ 3 files changed, 712 insertions(+) create mode 100644 components/wifi_monitor/CMakeLists.txt create mode 100644 components/wifi_monitor/wifi_monitor.c create mode 100644 components/wifi_monitor/wifi_monitor.h diff --git a/components/wifi_monitor/CMakeLists.txt b/components/wifi_monitor/CMakeLists.txt new file mode 100644 index 0000000..73f0473 --- /dev/null +++ b/components/wifi_monitor/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "wifi_monitor.c" + INCLUDE_DIRS "." + REQUIRES esp_wifi nvs_flash +) diff --git a/components/wifi_monitor/wifi_monitor.c b/components/wifi_monitor/wifi_monitor.c new file mode 100644 index 0000000..ad26b06 --- /dev/null +++ b/components/wifi_monitor/wifi_monitor.c @@ -0,0 +1,446 @@ +#include "wifi_monitor.h" +#include "esp_log.h" +#include "esp_wifi.h" +#include "string.h" + +static const char *TAG = "WiFi_Monitor"; + +// Module state +static bool monitor_running = false; +static wifi_monitor_cb_t user_callback = NULL; +static wifi_collapse_stats_t stats = {0}; + +// Tunable thresholds (accessible via GDB for runtime adjustment) +uint32_t threshold_high_nav_us = 5000; // NAV threshold for "high" classification +uint32_t threshold_duration_mismatch_us = 10000; // Log mismatches when NAV exceeds this +uint32_t threshold_phy_rate_fallback_mbps = 100; // PHY rate below this = fallback +float threshold_retry_rate_percent = 20.0f; // Retry rate for collapse detection +uint32_t threshold_avg_nav_collapse_us = 10000; // Avg NAV threshold for collapse +float threshold_collision_percent = 10.0f; // Collision event percentage +float threshold_mismatch_percent = 5.0f; // Duration mismatch percentage +uint32_t threshold_duration_multiplier = 2; // NAV > expected * this = mismatch + +// Logging control +uint32_t log_every_n_mismatches = 1; // Log every Nth mismatch (1 = all, 10 = every 10th) +static uint32_t s_mismatch_log_counter = 0; + +// Forward declarations +static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type); + +/** + * @brief Parse 802.11 MAC header + */ +esp_err_t wifi_parse_frame(const uint8_t *payload, uint16_t len, wifi_frame_info_t *frame_info) { + if (!payload || !frame_info || len < 24) { + return ESP_ERR_INVALID_ARG; + } + + memset(frame_info, 0, sizeof(wifi_frame_info_t)); + + // Parse Frame Control (bytes 0-1) + frame_info->frame_control = (payload[1] << 8) | payload[0]; + frame_info->protocol_version = frame_info->frame_control & 0x03; + frame_info->type = (frame_info->frame_control >> 2) & 0x03; + frame_info->subtype = (frame_info->frame_control >> 4) & 0x0F; + frame_info->to_ds = (frame_info->frame_control & FRAME_CTRL_TO_DS) != 0; + frame_info->from_ds = (frame_info->frame_control & FRAME_CTRL_FROM_DS) != 0; + frame_info->more_frag = (frame_info->frame_control & FRAME_CTRL_MORE_FRAG) != 0; + frame_info->retry = (frame_info->frame_control & FRAME_CTRL_RETRY) != 0; + frame_info->pwr_mgmt = (frame_info->frame_control & FRAME_CTRL_PWR_MGMT) != 0; + frame_info->more_data = (frame_info->frame_control & FRAME_CTRL_MORE_DATA) != 0; + frame_info->protected_frame = (frame_info->frame_control & FRAME_CTRL_PROTECTED) != 0; + frame_info->order = (frame_info->frame_control & FRAME_CTRL_ORDER) != 0; + + // Parse Duration/ID (bytes 2-3) - THIS IS THE NAV FIELD! + frame_info->duration_id = (payload[3] << 8) | payload[2]; + + // Parse Address 1 (Receiver) (bytes 4-9) + memcpy(frame_info->addr1, &payload[4], 6); + + // Parse Address 2 (Transmitter) (bytes 10-15) + memcpy(frame_info->addr2, &payload[10], 6); + + // Parse Address 3 (BSSID/SA/DA) (bytes 16-21) + memcpy(frame_info->addr3, &payload[16], 6); + + // Parse Sequence Control (bytes 22-23) + frame_info->seq_ctrl = (payload[23] << 8) | payload[22]; + frame_info->fragment_num = frame_info->seq_ctrl & 0x0F; + frame_info->sequence_num = (frame_info->seq_ctrl >> 4) & 0x0FFF; + + // Check for Address 4 (only present if To DS and From DS both set) + frame_info->has_addr4 = frame_info->to_ds && frame_info->from_ds; + if (frame_info->has_addr4 && len >= 30) { + memcpy(frame_info->addr4, &payload[24], 6); + } + + frame_info->frame_len = len; + + return ESP_OK; +} + +/** + * @brief Promiscuous mode RX callback + */ +static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type) { + if (!buf) return; + + wifi_promiscuous_pkt_t *pkt = (wifi_promiscuous_pkt_t *)buf; + wifi_pkt_rx_ctrl_t *rx_ctrl = &pkt->rx_ctrl; + const uint8_t *payload = pkt->payload; + uint16_t len = rx_ctrl->sig_len; + + // Parse frame header + wifi_frame_info_t frame_info; + if (wifi_parse_frame(payload, len, &frame_info) != ESP_OK) { + return; + } + + // Add RX metadata + frame_info.rssi = rx_ctrl->rssi; + frame_info.channel = rx_ctrl->channel; + frame_info.timestamp = rx_ctrl->timestamp; + + // Extract PHY rate info from RX control + frame_info.mcs = 0; + frame_info.rate = rx_ctrl->rate; // This is the rate index + frame_info.sig_mode = 0; + frame_info.sgi = false; + frame_info.bandwidth = 0; + + // Estimate PHY rate from rate index (rough approximation) + static const uint32_t rate_table[] = { + 1000, 2000, 5500, 11000, // 1, 2, 5.5, 11 Mbps (DSSS) + 6000, 9000, 12000, 18000, 24000, 36000, 48000, 54000, // OFDM rates + 65000, 130000, 195000, 260000 // Rough HT estimates + }; + + if (rx_ctrl->rate < sizeof(rate_table) / sizeof(rate_table[0])) { + frame_info.phy_rate_kbps = rate_table[rx_ctrl->rate]; + } else { + frame_info.phy_rate_kbps = 100000; // Assume 100 Mbps default + } + + // Update statistics + stats.total_frames++; + + if (frame_info.retry) { + stats.retry_frames++; + } + + if (frame_info.duration_id > threshold_high_nav_us) { + stats.high_nav_frames++; + } + + if (frame_info.duration_id > stats.max_nav) { + stats.max_nav = frame_info.duration_id; + } + + // Track PHY rate statistics + uint16_t phy_rate_mbps = frame_info.phy_rate_kbps / 1000; + if (phy_rate_mbps > 0) { + if (stats.min_phy_rate_mbps == 0 || phy_rate_mbps < stats.min_phy_rate_mbps) { + stats.min_phy_rate_mbps = phy_rate_mbps; + } + if (phy_rate_mbps > stats.max_phy_rate_mbps) { + stats.max_phy_rate_mbps = phy_rate_mbps; + } + + stats.avg_phy_rate_mbps = (stats.avg_phy_rate_mbps * (stats.total_frames - 1) + phy_rate_mbps) / stats.total_frames; + + if (frame_info.channel >= 36 && phy_rate_mbps < threshold_phy_rate_fallback_mbps) { + stats.rate_fallback_frames++; + } + + // Calculate expected duration + uint32_t tx_time_us = (frame_info.frame_len * 8000) / frame_info.phy_rate_kbps; + uint32_t overhead_us = 44; + if (frame_info.sig_mode == 0) { + overhead_us = 24; + } else if (frame_info.bandwidth == 2) { + overhead_us = 52; + } + uint32_t expected_duration = tx_time_us + overhead_us; + + // --------------------------------------------------------- + // DURATION MISMATCH CHECK (Attacker Identification Added) + // --------------------------------------------------------- + if (frame_info.duration_id > expected_duration * threshold_duration_multiplier) { + stats.duration_mismatch_frames++; + + if (frame_info.duration_id > threshold_duration_mismatch_us) { + s_mismatch_log_counter++; + if ((s_mismatch_log_counter % log_every_n_mismatches) == 0) { + ESP_LOGW("MONITOR", "Duration mismatch: %s frame, %u bytes @ %u Mbps", + wifi_frame_type_str(frame_info.type, frame_info.subtype), + frame_info.frame_len, phy_rate_mbps); + + // NEW: Log the Source MAC (Addr2) + ESP_LOGW("MONITOR", " Source MAC: %02x:%02x:%02x:%02x:%02x:%02x", + frame_info.addr2[0], frame_info.addr2[1], frame_info.addr2[2], + frame_info.addr2[3], frame_info.addr2[4], frame_info.addr2[5]); + + ESP_LOGW("MONITOR", " Expected: %lu us, Actual NAV: %u us (+%ld us)", + expected_duration, frame_info.duration_id, + frame_info.duration_id - expected_duration); + ESP_LOGW("MONITOR", " Retry: %s, RSSI: %d dBm", + frame_info.retry ? "YES" : "no", frame_info.rssi); + } + } + } + + // --------------------------------------------------------- + // COLLISION CHECK (Attacker Identification Added) + // --------------------------------------------------------- + if (frame_info.retry && frame_info.duration_id > threshold_high_nav_us && + phy_rate_mbps < threshold_phy_rate_fallback_mbps) { + ESP_LOGW("MONITOR", "⚠⚠⚠ COLLISION DETECTED!"); + + // NEW: Log the Attacker MAC + ESP_LOGW("MONITOR", " Attacker MAC: %02x:%02x:%02x:%02x:%02x:%02x", + frame_info.addr2[0], frame_info.addr2[1], frame_info.addr2[2], + frame_info.addr2[3], frame_info.addr2[4], frame_info.addr2[5]); + + ESP_LOGW("MONITOR", " Type: %s, Size: %u bytes, Rate: %u Mbps", + wifi_frame_type_str(frame_info.type, frame_info.subtype), + frame_info.frame_len, phy_rate_mbps); + ESP_LOGW("MONITOR", " NAV: %u us (expected %lu us), Retry: YES", + frame_info.duration_id, expected_duration); + } + } + + // Count frame types + switch (frame_info.type) { + case FRAME_TYPE_MANAGEMENT: + stats.mgmt_frames++; + break; + + case FRAME_TYPE_CONTROL: + if (frame_info.subtype == CTRL_RTS) { + stats.rts_frames++; + } else if (frame_info.subtype == CTRL_CTS) { + stats.cts_frames++; + } else if (frame_info.subtype == CTRL_ACK) { + stats.ack_frames++; + } + break; + + case FRAME_TYPE_DATA: + stats.data_frames++; + break; + } + + if (frame_info.retry && frame_info.duration_id > threshold_high_nav_us) { + stats.collision_events++; + } + + if (stats.total_frames > 0) { + stats.retry_rate = (float)stats.retry_frames / stats.total_frames * 100.0f; + stats.avg_nav = (stats.avg_nav * (stats.total_frames - 1) + frame_info.duration_id) / stats.total_frames; + } + + if (user_callback) { + user_callback(&frame_info, payload, len); + } +} + +/** + * @brief Initialize WiFi monitor mode + */ +esp_err_t wifi_monitor_init(uint8_t channel, wifi_monitor_cb_t callback) { + ESP_LOGI(TAG, "Initializing WiFi monitor mode on channel %d", channel); + + user_callback = callback; + monitor_running = false; + + // Initialize WiFi in NULL mode (no STA or AP) + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + esp_err_t ret = esp_wifi_init(&cfg); + if (ret != ESP_OK && ret != ESP_ERR_WIFI_NOT_INIT) { + ESP_LOGE(TAG, "WiFi init failed: %s", esp_err_to_name(ret)); + return ret; + } + + // Set WiFi mode to NULL (required for promiscuous mode) + ret = esp_wifi_set_mode(WIFI_MODE_NULL); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Set mode failed: %s", esp_err_to_name(ret)); + return ret; + } + + // Start WiFi + ret = esp_wifi_start(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "WiFi start failed: %s", esp_err_to_name(ret)); + return ret; + } + + // Set channel + ret = wifi_monitor_set_channel(channel); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Set channel failed: %s", esp_err_to_name(ret)); + return ret; + } + + // Set promiscuous filter to capture all frame types + wifi_promiscuous_filter_t filter = { + .filter_mask = WIFI_PROMIS_FILTER_MASK_ALL + }; + ret = esp_wifi_set_promiscuous_filter(&filter); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Set filter failed: %s", esp_err_to_name(ret)); + return ret; + } + + // Register promiscuous callback + ret = esp_wifi_set_promiscuous_rx_cb(wifi_promiscuous_rx_cb); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Set callback failed: %s", esp_err_to_name(ret)); + return ret; + } + + ESP_LOGI(TAG, "WiFi monitor initialized successfully"); + return ESP_OK; +} + +/** + * @brief Start WiFi monitoring + */ +esp_err_t wifi_monitor_start(void) { + ESP_LOGI(TAG, "Starting WiFi monitor mode"); + + esp_err_t ret = esp_wifi_set_promiscuous(true); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Enable promiscuous failed: %s", esp_err_to_name(ret)); + return ret; + } + + monitor_running = true; + wifi_monitor_reset_stats(); + + ESP_LOGI(TAG, "WiFi monitor started - capturing all 802.11 frames"); + return ESP_OK; +} + +/** + * @brief Stop WiFi monitoring + */ +esp_err_t wifi_monitor_stop(void) { + ESP_LOGI(TAG, "Stopping WiFi monitor mode"); + + esp_err_t ret = esp_wifi_set_promiscuous(false); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Disable promiscuous failed: %s", esp_err_to_name(ret)); + return ret; + } + + monitor_running = false; + + ESP_LOGI(TAG, "WiFi monitor stopped"); + return ESP_OK; +} + +/** + * @brief Set WiFi channel for monitoring + */ +esp_err_t wifi_monitor_set_channel(uint8_t channel) { + ESP_LOGI(TAG, "Setting channel to %d", channel); + + esp_err_t ret = esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Set channel failed: %s", esp_err_to_name(ret)); + return ret; + } + + return ESP_OK; +} + +/** + * @brief Get WiFi collapse detection statistics + */ +esp_err_t wifi_monitor_get_stats(wifi_collapse_stats_t *out_stats) { + if (!out_stats) { + return ESP_ERR_INVALID_ARG; + } + + memcpy(out_stats, &stats, sizeof(wifi_collapse_stats_t)); + return ESP_OK; +} + +/** + * @brief Reset WiFi collapse detection statistics + */ +void wifi_monitor_reset_stats(void) { + memset(&stats, 0, sizeof(wifi_collapse_stats_t)); + ESP_LOGI(TAG, "Statistics reset"); +} + +/** + * @brief Check if current conditions indicate WiFi collapse + */ +bool wifi_monitor_is_collapsed(void) { + // Need minimum sample size + if (stats.total_frames < 100) { + return false; + } + + bool high_retry = stats.retry_rate > threshold_retry_rate_percent; + bool high_nav = stats.avg_nav > threshold_avg_nav_collapse_us; + bool high_collision = (float)stats.collision_events / stats.total_frames > (threshold_collision_percent / 100.0f); + bool duration_issues = (float)stats.duration_mismatch_frames / stats.total_frames > (threshold_mismatch_percent / 100.0f); + bool rate_fallback = stats.avg_phy_rate_mbps < threshold_phy_rate_fallback_mbps; + + // Collapse detected if 3 out of 5 indicators are true + int indicators = (high_retry ? 1 : 0) + + (high_nav ? 1 : 0) + + (high_collision ? 1 : 0) + + (duration_issues ? 1 : 0) + + (rate_fallback ? 1 : 0); + + return indicators >= 3; +} + +/** + * @brief Get string representation of frame type + */ +const char* wifi_frame_type_str(uint8_t type, uint8_t subtype) { + if (type == FRAME_TYPE_MANAGEMENT) { + switch (subtype) { + case MGMT_ASSOC_REQ: return "ASSOC_REQ"; + case MGMT_ASSOC_RESP: return "ASSOC_RESP"; + case MGMT_REASSOC_REQ: return "REASSOC_REQ"; + case MGMT_REASSOC_RESP: return "REASSOC_RESP"; + case MGMT_PROBE_REQ: return "PROBE_REQ"; + case MGMT_PROBE_RESP: return "PROBE_RESP"; + case MGMT_BEACON: return "BEACON"; + case MGMT_ATIM: return "ATIM"; + case MGMT_DISASSOC: return "DISASSOC"; + case MGMT_AUTH: return "AUTH"; + case MGMT_DEAUTH: return "DEAUTH"; + case MGMT_ACTION: return "ACTION"; + default: return "MGMT_UNKNOWN"; + } + } else if (type == FRAME_TYPE_CONTROL) { + switch (subtype) { + case CTRL_BLOCK_ACK_REQ: return "BLOCK_ACK_REQ"; + case CTRL_BLOCK_ACK: return "BLOCK_ACK"; + case CTRL_PS_POLL: return "PS_POLL"; + case CTRL_RTS: return "RTS"; + case CTRL_CTS: return "CTS"; + case CTRL_ACK: return "ACK"; + case CTRL_CF_END: return "CF_END"; + case CTRL_CF_END_ACK: return "CF_END_ACK"; + default: return "CTRL_UNKNOWN"; + } + } else if (type == FRAME_TYPE_DATA) { + switch (subtype) { + case DATA_DATA: return "DATA"; + case DATA_NULL: return "NULL"; + case DATA_QOS_DATA: return "QOS_DATA"; + case DATA_QOS_NULL: return "QOS_NULL"; + default: return "DATA_UNKNOWN"; + } + } + + return "UNKNOWN"; +} diff --git a/components/wifi_monitor/wifi_monitor.h b/components/wifi_monitor/wifi_monitor.h new file mode 100644 index 0000000..5ef1113 --- /dev/null +++ b/components/wifi_monitor/wifi_monitor.h @@ -0,0 +1,261 @@ +#ifndef WIFI_MONITOR_H +#define WIFI_MONITOR_H + +#include "esp_wifi.h" +#include "esp_wifi_types.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief 802.11 Frame Control field bits + */ +#define FRAME_CTRL_PROTOCOL_VERSION 0x0003 +#define FRAME_CTRL_TYPE 0x000C +#define FRAME_CTRL_SUBTYPE 0x00F0 +#define FRAME_CTRL_TO_DS 0x0100 +#define FRAME_CTRL_FROM_DS 0x0200 +#define FRAME_CTRL_MORE_FRAG 0x0400 +#define FRAME_CTRL_RETRY 0x0800 +#define FRAME_CTRL_PWR_MGMT 0x1000 +#define FRAME_CTRL_MORE_DATA 0x2000 +#define FRAME_CTRL_PROTECTED 0x4000 +#define FRAME_CTRL_ORDER 0x8000 + +/** + * @brief 802.11 Frame Types + */ +typedef enum { + FRAME_TYPE_MANAGEMENT = 0, + FRAME_TYPE_CONTROL = 1, + FRAME_TYPE_DATA = 2, + FRAME_TYPE_RESERVED = 3 +} wifi_frame_type_t; + +/** + * @brief 802.11 Management Frame Subtypes + */ +typedef enum { + MGMT_ASSOC_REQ = 0, + MGMT_ASSOC_RESP = 1, + MGMT_REASSOC_REQ = 2, + MGMT_REASSOC_RESP = 3, + MGMT_PROBE_REQ = 4, + MGMT_PROBE_RESP = 5, + MGMT_TIMING_ADV = 6, + MGMT_BEACON = 8, + MGMT_ATIM = 9, + MGMT_DISASSOC = 10, + MGMT_AUTH = 11, + MGMT_DEAUTH = 12, + MGMT_ACTION = 13 +} wifi_mgmt_subtype_t; + +/** + * @brief 802.11 Control Frame Subtypes + */ +typedef enum { + CTRL_TRIGGER = 2, + CTRL_BEAMFORMING_RPT = 4, + CTRL_VHT_NDP_ANNOUNCE = 5, + CTRL_CTRL_FRAME_EXT = 6, + CTRL_CTRL_WRAPPER = 7, + CTRL_BLOCK_ACK_REQ = 8, + CTRL_BLOCK_ACK = 9, + CTRL_PS_POLL = 10, + CTRL_RTS = 11, + CTRL_CTS = 12, + CTRL_ACK = 13, + CTRL_CF_END = 14, + CTRL_CF_END_ACK = 15 +} wifi_ctrl_subtype_t; + +/** + * @brief 802.11 Data Frame Subtypes + */ +typedef enum { + DATA_DATA = 0, + DATA_DATA_CF_ACK = 1, + DATA_DATA_CF_POLL = 2, + DATA_DATA_CF_ACK_POLL = 3, + DATA_NULL = 4, + DATA_CF_ACK = 5, + DATA_CF_POLL = 6, + DATA_CF_ACK_POLL = 7, + DATA_QOS_DATA = 8, + DATA_QOS_DATA_CF_ACK = 9, + DATA_QOS_DATA_CF_POLL = 10, + DATA_QOS_DATA_CF_ACK_POLL = 11, + DATA_QOS_NULL = 12, + DATA_QOS_CF_POLL = 14, + DATA_QOS_CF_ACK_POLL = 15 +} wifi_data_subtype_t; + +/** + * @brief Parsed 802.11 MAC header + */ +typedef struct { + // Frame Control + uint16_t frame_control; + uint8_t protocol_version; + uint8_t type; + uint8_t subtype; + bool to_ds; + bool from_ds; + bool more_frag; + bool retry; + bool pwr_mgmt; + bool more_data; + bool protected_frame; + bool order; + + // Duration/ID (NAV) + uint16_t duration_id; + + // MAC Addresses + uint8_t addr1[6]; // Receiver address + uint8_t addr2[6]; // Transmitter address + uint8_t addr3[6]; // Filtering address (BSSID/SA/DA) + + // Sequence Control + uint16_t seq_ctrl; + uint16_t fragment_num; + uint16_t sequence_num; + + // Optional: Address 4 (if To DS and From DS both set) + uint8_t addr4[6]; + bool has_addr4; + + // RX Info + int8_t rssi; + uint8_t channel; + uint32_t timestamp; + + // PHY rate info + uint8_t mcs; // MCS index (for HT/VHT frames) + uint8_t rate; // Legacy rate or rate index + uint8_t sig_mode; // Signal mode (0=legacy, 1=HT, 3=VHT) + bool sgi; // Short Guard Interval + uint8_t bandwidth; // 0=20MHz, 1=40MHz, 2=80MHz + uint32_t phy_rate_kbps; // Calculated PHY rate in Kbps (uint32_t to handle >65 Mbps) + + // Frame size + uint16_t frame_len; +} wifi_frame_info_t; + +/** + * @brief WiFi collapse detection statistics + */ +typedef struct { + uint32_t total_frames; + uint32_t retry_frames; + uint32_t high_nav_frames; // NAV > 5000 us + uint32_t rts_frames; + uint32_t cts_frames; + uint32_t ack_frames; + uint32_t data_frames; + uint32_t mgmt_frames; + + // Collapse indicators + float retry_rate; // Percentage of retried frames + uint16_t avg_nav; // Average NAV duration + uint16_t max_nav; // Maximum NAV seen + uint32_t collision_events; // Estimated collision count + + // Duration analysis + uint32_t duration_mismatch_frames; // NAV >> expected + uint32_t rate_fallback_frames; // PHY rate < 100 Mbps + uint16_t avg_phy_rate_mbps; // Average PHY rate + uint16_t min_phy_rate_mbps; // Minimum PHY rate seen + uint16_t max_phy_rate_mbps; // Maximum PHY rate seen +} wifi_collapse_stats_t; + +/** + * @brief Callback function type for frame capture + * + * @param frame_info Parsed frame information + * @param payload Raw frame payload (starts with MAC header) + * @param len Frame length + */ +typedef void (*wifi_monitor_cb_t)(const wifi_frame_info_t *frame_info, + const uint8_t *payload, + uint16_t len); + +/** + * @brief Initialize WiFi monitor mode + * + * @param channel WiFi channel to monitor (1-14 for 2.4GHz, 36+ for 5GHz) + * @param callback Callback function for captured frames + * @return esp_err_t ESP_OK on success + */ +esp_err_t wifi_monitor_init(uint8_t channel, wifi_monitor_cb_t callback); + +/** + * @brief Start WiFi monitoring + * + * @return esp_err_t ESP_OK on success + */ +esp_err_t wifi_monitor_start(void); + +/** + * @brief Stop WiFi monitoring + * + * @return esp_err_t ESP_OK on success + */ +esp_err_t wifi_monitor_stop(void); + +/** + * @brief Set WiFi channel for monitoring + * + * @param channel WiFi channel (1-14 for 2.4GHz, 36+ for 5GHz) + * @return esp_err_t ESP_OK on success + */ +esp_err_t wifi_monitor_set_channel(uint8_t channel); + +/** + * @brief Parse 802.11 frame header + * + * @param payload Raw frame data + * @param len Frame length + * @param frame_info Output: parsed frame information + * @return esp_err_t ESP_OK on success + */ +esp_err_t wifi_parse_frame(const uint8_t *payload, uint16_t len, wifi_frame_info_t *frame_info); + +/** + * @brief Get WiFi collapse detection statistics + * + * @param stats Output: statistics structure + * @return esp_err_t ESP_OK on success + */ +esp_err_t wifi_monitor_get_stats(wifi_collapse_stats_t *stats); + +/** + * @brief Reset WiFi collapse detection statistics + */ +void wifi_monitor_reset_stats(void); + +/** + * @brief Check if current conditions indicate WiFi collapse + * + * @return true if collapse is detected, false otherwise + */ +bool wifi_monitor_is_collapsed(void); + +/** + * @brief Get string representation of frame type + * + * @param type Frame type + * @param subtype Frame subtype + * @return const char* String description + */ +const char* wifi_frame_type_str(uint8_t type, uint8_t subtype); + +#ifdef __cplusplus +} +#endif + +#endif // WIFI_MONITOR_H