diff --git a/components/app_console/cmd_monitor.c b/components/app_console/cmd_monitor.c index 7c9f20b..9a16888 100644 --- a/components/app_console/cmd_monitor.c +++ b/components/app_console/cmd_monitor.c @@ -54,6 +54,11 @@ static struct { struct arg_end *end; } debug_args; +static struct { + struct arg_str *mac; + struct arg_end *end; +} filter_args; + static void print_monitor_usage(void) { printf("Usage: monitor [args]\n"); printf("Subcommands:\n"); @@ -62,6 +67,7 @@ static void print_monitor_usage(void) { printf(" status Show current status\n"); printf(" channel Switch channel (while running)\n"); printf(" debug [on|off] Enable/disable debug logging (default: show status)\n"); + printf(" filter [mac] Set MAC address filter for debug (e.g., 80:84:89:93:c4:b6), or 'clear' to disable\n"); printf(" save Save current config to NVS\n"); printf(" reload Reload config from NVS\n"); printf(" clear Clear NVS config\n"); @@ -139,6 +145,73 @@ static int do_monitor_debug(int argc, char **argv) { return 0; } +static int do_monitor_filter(int argc, char **argv) { + filter_args.mac = arg_str0(NULL, NULL, "", "MAC address (XX:XX:XX:XX:XX:XX) or 'clear' to disable filter"); + filter_args.end = arg_end(1); + + int nerrors = arg_parse(argc, argv, (void **)&filter_args); + if (nerrors > 0) { + arg_print_errors(stderr, filter_args.end, argv[0]); + return 1; + } + + if (filter_args.mac->count > 0) { + const char *mac_str = filter_args.mac->sval[0]; + + if (strcmp(mac_str, "clear") == 0) { + wifi_ctl_set_monitor_debug_filter(NULL); + printf("Debug filter cleared\n"); + } else { + /* Parse MAC address string (format: XX:XX:XX:XX:XX:XX) */ + uint8_t mac[6]; + int values[6]; + int count = sscanf(mac_str, "%x:%x:%x:%x:%x:%x", + &values[0], &values[1], &values[2], + &values[3], &values[4], &values[5]); + + if (count == 6) { + /* Validate values are in range */ + bool valid = true; + for (int i = 0; i < 6; i++) { + if (values[i] < 0 || values[i] > 255) { + valid = false; + break; + } + mac[i] = (uint8_t)values[i]; + } + + if (valid) { + if (wifi_ctl_set_monitor_debug_filter(mac) == ESP_OK) { + printf("Debug filter set to: %02x:%02x:%02x:%02x:%02x:%02x\n", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + } else { + printf("Failed to set debug filter\n"); + return 1; + } + } else { + printf("Invalid MAC address: values must be 0-255\n"); + return 1; + } + } else { + printf("Invalid MAC address format. Use XX:XX:XX:XX:XX:XX or 'clear'\n"); + return 1; + } + } + } else { + /* No argument: show current filter */ + uint8_t mac[6]; + bool enabled = wifi_ctl_get_monitor_debug_filter(mac); + if (enabled) { + printf("Debug filter: %02x:%02x:%02x:%02x:%02x:%02x\n", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + } else { + printf("Debug filter: disabled\n"); + } + } + + return 0; +} + static int cmd_monitor(int argc, char **argv) { if (argc < 2) { print_monitor_usage(); @@ -154,6 +227,7 @@ static int cmd_monitor(int argc, char **argv) { if (strcmp(argv[1], "channel") == 0) return do_monitor_channel(argc - 1, &argv[1]); if (strcmp(argv[1], "debug") == 0) return do_monitor_debug(argc - 1, &argv[1]); + if (strcmp(argv[1], "filter") == 0) return do_monitor_filter(argc - 1, &argv[1]); if (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "--help") == 0) { print_monitor_usage(); @@ -175,6 +249,9 @@ void register_monitor_cmd(void) { debug_args.enable = arg_str0(NULL, NULL, "", "Enable or disable debug logging"); debug_args.end = arg_end(1); + filter_args.mac = arg_str0(NULL, NULL, "", "MAC address filter or 'clear'"); + filter_args.end = arg_end(1); + const esp_console_cmd_t cmd = { .command = "monitor", .help = "Monitor Mode: start, stop, channel, status", diff --git a/components/wifi_controller/wifi_controller.c b/components/wifi_controller/wifi_controller.c index 0b6e4b4..c07e1fe 100644 --- a/components/wifi_controller/wifi_controller.c +++ b/components/wifi_controller/wifi_controller.c @@ -324,14 +324,14 @@ static void monitor_stats_task(void *arg) { if (should_flush && current_offset > 0 && s_batch_mutex && sd_card_is_ready()) { size_t flush_size = 0; - + if (xSemaphoreTake(s_batch_mutex, portMAX_DELAY) == pdTRUE) { memcpy(s_flush_buf, s_batch_buf, current_offset); flush_size = current_offset; s_batch_offset = 0; xSemaphoreGive(s_batch_mutex); } - + if (flush_size > 0) { esp_err_t write_err = sd_card_write_file(FIWI_TELEMETRY_FILE, s_flush_buf, flush_size, true); if (write_err == ESP_OK) { @@ -627,7 +627,7 @@ void wifi_ctl_set_channel(int channel) { */ static uint32_t wifi_channel_to_frequency(uint8_t channel) { uint32_t freq = 0; - + if (channel >= 1 && channel <= 14) { /* 2.4 GHz band: 2407 + (channel * 5) MHz */ freq = 2407 + (channel * 5); @@ -644,7 +644,7 @@ static uint32_t wifi_channel_to_frequency(uint8_t channel) { /* 5 GHz UNII-4: 5000 + (channel * 5) MHz */ freq = 5000 + (channel * 5); } - + return freq; } @@ -657,11 +657,11 @@ void wifi_ctl_status(void) { if (s_current_mode == WIFI_CTL_MODE_MONITOR) { uint8_t channel = s_monitor_channel_active; uint32_t freq_mhz = wifi_channel_to_frequency(channel); - + /* Display channel info: channel N (FREQ MHz), width: 20 MHz, center1: FREQ MHz */ /* Note: Monitor mode currently uses single channel (20 MHz width) */ if (freq_mhz > 0) { - printf(" Channel: %d (%" PRIu32 " MHz), width: 20 MHz, center1: %" PRIu32 " MHz\n", + printf(" Channel: %d (%" PRIu32 " MHz), width: 20 MHz, center1: %" PRIu32 " MHz\n", channel, freq_mhz, freq_mhz); } else { printf(" Channel: %d\n", channel); @@ -673,6 +673,85 @@ void wifi_ctl_status(void) { } printf(" Frames: %lu, %.1f fps\n", (unsigned long)s_monitor_frame_count, frame_rate_fps); + /* Show frame type breakdown */ + wifi_collapse_stats_t monitor_stats; + if (wifi_monitor_get_stats(&monitor_stats) == ESP_OK) { + printf(" Frame Types: Data=%lu, Mgmt=%lu, Ctrl=%lu (RTS=%lu, CTS=%lu, ACK=%lu)\n", + (unsigned long)monitor_stats.data_frames, + (unsigned long)monitor_stats.mgmt_frames, + (unsigned long)(monitor_stats.rts_frames + monitor_stats.cts_frames + monitor_stats.ack_frames), + (unsigned long)monitor_stats.rts_frames, + (unsigned long)monitor_stats.cts_frames, + (unsigned long)monitor_stats.ack_frames); + + /* Show histograms */ + // AMPDU histogram + uint32_t total_ampdu = 0; + for (int i = 0; i < 8; i++) { + total_ampdu += monitor_stats.ampdu_hist[i]; + } + if (total_ampdu > 0) { + printf(" AMPDU Aggregation: "); + const char *ampdu_labels[] = {"1", "2-4", "5-8", "9-16", "17-32", "33-48", "49-64", "65+"}; + for (int i = 0; i < 8; i++) { + if (monitor_stats.ampdu_hist[i] > 0) { + printf("%s=%lu ", ampdu_labels[i], (unsigned long)monitor_stats.ampdu_hist[i]); + } + } + printf("\n"); + } + + // MCS histogram (show non-zero entries) + uint32_t total_mcs = 0; + for (int i = 0; i < 32; i++) { + total_mcs += monitor_stats.mcs_hist[i]; + } + if (total_mcs > 0) { + printf(" MCS Distribution: "); + int printed = 0; + for (int i = 0; i < 32; i++) { + if (monitor_stats.mcs_hist[i] > 0) { + if (printed > 0) printf(", "); + printf("MCS%d=%lu", i, (unsigned long)monitor_stats.mcs_hist[i]); + printed++; + if (printed >= 10) { // Limit output length + printf(" ..."); + break; + } + } + } + printf("\n"); + } + + // Spatial streams histogram + uint32_t total_ss = 0; + for (int i = 0; i < 8; i++) { + total_ss += monitor_stats.ss_hist[i]; + } + if (total_ss > 0) { + printf(" Spatial Streams: "); + for (int i = 0; i < 8; i++) { + if (monitor_stats.ss_hist[i] > 0) { + printf("%dSS=%lu ", i + 1, (unsigned long)monitor_stats.ss_hist[i]); + } + } + printf("\n"); + } + } + + /* Show debug and filter status */ + bool debug_enabled = wifi_ctl_get_monitor_debug(); + uint8_t filter_mac[6]; + bool filter_enabled = wifi_ctl_get_monitor_debug_filter(filter_mac); + printf(" Debug: %s\n", debug_enabled ? "enabled" : "disabled"); + if (filter_enabled) { + printf(" Filter: %02x:%02x:%02x:%02x:%02x:%02x\n", + filter_mac[0], filter_mac[1], filter_mac[2], + filter_mac[3], filter_mac[4], filter_mac[5]); + } else { + printf(" Filter: disabled (showing all frames)\n"); + } + /* Show telemetry rate statistics */ uint64_t gen_bytes = 0, write_bytes = 0; double gen_rate = 0.0, write_rate = 0.0; @@ -713,7 +792,7 @@ bool wifi_ctl_param_is_unsaved(void) { void wifi_ctl_param_save(const char *dummy) { (void)dummy; /* If monitor mode is running, save the active channel; otherwise save staging */ - uint8_t channel_to_save = (s_current_mode == WIFI_CTL_MODE_MONITOR) ? + uint8_t channel_to_save = (s_current_mode == WIFI_CTL_MODE_MONITOR) ? s_monitor_channel_active : s_monitor_channel_staging; if (wifi_cfg_set_monitor_channel(channel_to_save)) { ESP_LOGI(TAG, "Monitor channel (%d) saved to NVS", channel_to_save); @@ -824,6 +903,14 @@ bool wifi_ctl_get_monitor_debug(void) { return s_monitor_debug; } +esp_err_t wifi_ctl_set_monitor_debug_filter(const uint8_t *mac) { + return wifi_monitor_set_debug_filter(mac); +} + +bool wifi_ctl_get_monitor_debug_filter(uint8_t *mac_out) { + return wifi_monitor_get_debug_filter(mac_out); +} + // --- Deprecated --- static void auto_monitor_task_func(void *arg) { uint8_t channel = (uint8_t)(uintptr_t)arg; diff --git a/components/wifi_controller/wifi_controller.h b/components/wifi_controller/wifi_controller.h index 8a4d6b6..e42fe3b 100644 --- a/components/wifi_controller/wifi_controller.h +++ b/components/wifi_controller/wifi_controller.h @@ -82,6 +82,10 @@ esp_err_t wifi_ctl_get_sd_write_stats(uint64_t *total_bytes, double *rate_bps); void wifi_ctl_set_monitor_debug(bool enable); bool wifi_ctl_get_monitor_debug(void); +// Debug MAC filter control +esp_err_t wifi_ctl_set_monitor_debug_filter(const uint8_t *mac); +bool wifi_ctl_get_monitor_debug_filter(uint8_t *mac_out); + // Deprecated / Compatibility void wifi_ctl_auto_monitor_start(uint8_t channel); diff --git a/components/wifi_monitor/CMakeLists.txt b/components/wifi_monitor/CMakeLists.txt index 73f0473..4f838b5 100644 --- a/components/wifi_monitor/CMakeLists.txt +++ b/components/wifi_monitor/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register( SRCS "wifi_monitor.c" INCLUDE_DIRS "." - REQUIRES esp_wifi nvs_flash + REQUIRES esp_wifi nvs_flash esp_timer ) diff --git a/components/wifi_monitor/wifi_monitor.c b/components/wifi_monitor/wifi_monitor.c index 4465bf5..dfff61d 100644 --- a/components/wifi_monitor/wifi_monitor.c +++ b/components/wifi_monitor/wifi_monitor.c @@ -34,6 +34,7 @@ #include "wifi_monitor.h" #include "esp_log.h" #include "esp_wifi.h" +#include "esp_timer.h" #include "string.h" static const char *TAG = "WiFi_Monitor"; @@ -57,9 +58,12 @@ uint32_t threshold_duration_multiplier = 2; // NAV > expected * this = mism uint32_t log_every_n_mismatches = 1; // Log every Nth mismatch (1 = all, 10 = every 10th) static uint32_t s_mismatch_log_counter = 0; static bool s_monitor_debug = false; // Debug mode: enable serial logging +static uint8_t s_monitor_debug_filter_mac[6] = {0}; // MAC address filter (all zeros = no filter) +static bool s_monitor_debug_filter_enabled = false; // Whether MAC filtering is enabled // Forward declarations static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type); +static bool wifi_monitor_debug_filter_match(const uint8_t *mac); /** * @brief Parse HT/VHT/HE headers to extract PHY parameters @@ -103,10 +107,11 @@ static void wifi_parse_phy_headers(const uint8_t *payload, uint16_t len, uint16_ uint8_t ctrl_id = ht_ctrl & 0x0F; if (ctrl_id == 0) { - // HT Control (802.11n) - MCS info might be elsewhere + // HT Control (802.11n) - MCS info is typically in PLCP header (HT-SIG) + // which ESP-IDF provides in rx_ctrl->rate, not in HT Control field frame_info->sig_mode = 1; // HT - // Note: For HT, spatial streams are typically in HT Capabilities element - // or can be inferred from MCS index (MCS 0-7 = 1 SS, 8-15 = 2 SS, etc.) + // Note: MCS will be extracted from rx_ctrl->rate in the callback + // For HT, spatial streams can be inferred from MCS index (MCS 0-7 = 1 SS, 8-15 = 2 SS, etc.) } else if (ctrl_id >= 1 && ctrl_id <= 3) { // VHT Control (802.11ac) - bits layout varies by variant frame_info->sig_mode = 3; // VHT @@ -147,6 +152,101 @@ static void wifi_parse_phy_headers(const uint8_t *payload, uint16_t len, uint16_ } } +/** + * @brief Parse A-MPDU aggregation count from frame payload + * @param payload Raw frame payload + * @param len Total frame length + * @param mac_hdr_len Length of MAC header (24 or 30 bytes) + * @return Number of aggregated MPDUs (1 if not aggregated) + */ +static uint8_t wifi_parse_ampdu_count(const uint8_t *payload, uint16_t len, uint16_t mac_hdr_len) { + uint8_t count = 1; // Default: not aggregated + uint16_t offset = mac_hdr_len; + + // Check if this is a QoS data frame (required for A-MPDU) + uint8_t frame_type = (payload[0] >> 2) & 0x03; + uint8_t frame_subtype = (payload[0] >> 4) & 0x0F; + + if (frame_type != FRAME_TYPE_DATA) { + return count; // Not a data frame + } + + // Check if it's a QoS subtype + bool is_qos = (frame_subtype == DATA_QOS_DATA || + frame_subtype == DATA_QOS_DATA_CF_ACK || + frame_subtype == DATA_QOS_DATA_CF_POLL || + frame_subtype == DATA_QOS_DATA_CF_ACK_POLL || + frame_subtype == DATA_QOS_NULL || + frame_subtype == DATA_QOS_CF_POLL || + frame_subtype == DATA_QOS_CF_ACK_POLL); + + if (!is_qos || len < offset + 4) { + return count; // Not QoS or too short + } + + // Skip QoS Control field (4 bytes) + offset += 4; + + // Check for HT Control field (4 bytes) - present if Order bit set + // ORDER bit is 0x8000 (bit 15), which is in payload[1], bit 7 + bool has_ht_ctrl = (payload[1] & 0x80) != 0; + if (has_ht_ctrl && len >= offset + 4) { + offset += 4; // Skip HT Control field + } + + // A-MPDU delimiter format (IEEE 802.11-2016): + // Each delimiter is 4 bytes: [Reserved(4) | MPDU Length(12) | CRC(8) | Delimiter Signature(8)] + // Delimiter signature is 0x4E (ASCII 'N') + // We count delimiters until we find the end delimiter (length = 0) or run out of data + + uint16_t remaining = len - offset; + if (remaining < 4) { + return count; // Not enough data for even one delimiter + } + + // Count A-MPDU subframes by parsing delimiters + uint16_t pos = offset; + uint16_t delimiter_count = 0; // Use uint16_t to support up to 256 delimiters + + while (pos + 4 <= len && delimiter_count < 256) { // Support up to 256 for HE (though typically 64) + // Read delimiter (little-endian) + uint16_t delimiter = payload[pos] | (payload[pos + 1] << 8); + uint8_t sig = payload[pos + 3]; + + // Check delimiter signature (should be 0x4E) + if (sig != 0x4E) { + break; // Not a valid delimiter + } + + // Extract MPDU length (bits 4-15) + uint16_t mpdu_len = (delimiter >> 4) & 0x0FFF; + + if (mpdu_len == 0) { + break; // End delimiter + } + + delimiter_count++; + + // Move to next delimiter (skip this MPDU) + pos += 4 + mpdu_len; // Delimiter (4) + MPDU length + + // Align to 4-byte boundary + pos = (pos + 3) & ~3; + + if (pos >= len) { + break; // Reached end of frame + } + } + + // If we found multiple delimiters, this is an A-MPDU + // Cap count at 255 (max value for uint8_t return type) + if (delimiter_count > 1) { + count = (delimiter_count > 255) ? 255 : (uint8_t)delimiter_count; + } + + return count; +} + /** * @brief Parse 802.11 MAC header */ @@ -205,6 +305,9 @@ esp_err_t wifi_parse_frame(const uint8_t *payload, uint16_t len, wifi_frame_info // Parse HT/VHT/HE headers to extract PHY parameters wifi_parse_phy_headers(payload, len, mac_hdr_len, frame_info); + // Parse A-MPDU aggregation count + frame_info->ampdu_count = wifi_parse_ampdu_count(payload, len, mac_hdr_len); + frame_info->frame_len = len; return ESP_OK; @@ -236,11 +339,29 @@ static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type) frame_info.rate = rx_ctrl->rate; // This is the rate index // If MCS wasn't parsed from headers but we have HT/VHT/HE mode, try to extract from rate + // ESP-IDF encoding: rate >= 128 encodes MCS for HT/VHT/HE frames + // HT: MCS 0-31, VHT: MCS 0-9, HE: MCS 0-11 if (frame_info.sig_mode > 0 && frame_info.mcs == 0 && rx_ctrl->rate >= 128) { - // For HT/VHT/HE, rate >= 128 might encode MCS - // ESP-IDF encoding: rate 128+ might be MCS index - frame_info.mcs = rx_ctrl->rate - 128; - if (frame_info.mcs > 11) frame_info.mcs = 11; // Cap at max MCS + uint8_t extracted_mcs = rx_ctrl->rate - 128; + + // Cap MCS based on signal mode + if (frame_info.sig_mode == 1) { + // HT (802.11n): MCS 0-31 + if (extracted_mcs > 31) extracted_mcs = 31; + frame_info.mcs = extracted_mcs; + } else if (frame_info.sig_mode == 3) { + // VHT (802.11ac): MCS 0-9 + if (extracted_mcs > 9) extracted_mcs = 9; + frame_info.mcs = extracted_mcs; + } else if (frame_info.sig_mode == 4) { + // HE (802.11ax): MCS 0-11 + if (extracted_mcs > 11) extracted_mcs = 11; + frame_info.mcs = extracted_mcs; + } else { + // Unknown mode, use extracted value but cap at 31 (max HT) + if (extracted_mcs > 31) extracted_mcs = 31; + frame_info.mcs = extracted_mcs; + } } // Calculate PHY rate using parsed MCS/spatial streams if available @@ -317,20 +438,23 @@ static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type) if (frame_info.duration_id > threshold_duration_mismatch_us) { s_mismatch_log_counter++; if (s_monitor_debug && (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); + /* Check MAC filter before logging */ + if (wifi_monitor_debug_filter_match(frame_info.addr2)) { + 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]); + // 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); + 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); + } } } } @@ -341,18 +465,21 @@ static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type) if (frame_info.retry && frame_info.duration_id > threshold_high_nav_us && phy_rate_mbps < threshold_phy_rate_fallback_mbps) { if (s_monitor_debug) { - ESP_LOGW("MONITOR", "⚠⚠⚠ COLLISION DETECTED!"); + /* Check MAC filter before logging */ + if (wifi_monitor_debug_filter_match(frame_info.addr2)) { + 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]); + // 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); + 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); + } } } } @@ -375,6 +502,94 @@ static void wifi_promiscuous_rx_cb(void *buf, wifi_promiscuous_pkt_type_t type) case FRAME_TYPE_DATA: stats.data_frames++; + + /* Periodic diagnostic summary (even when debug is off) */ + static uint32_t data_frame_diag_counter = 0; + static uint64_t last_diag_log_ms = 0; + data_frame_diag_counter++; + uint64_t now_ms = esp_timer_get_time() / 1000; + if (now_ms - last_diag_log_ms > 10000) { /* Log every 10 seconds */ + ESP_LOGI("MONITOR", "Data frames: %lu total, last TA=%02x:%02x:%02x:%02x:%02x:%02x, " + "debug=%s, filter=%s", + (unsigned long)stats.data_frames, + frame_info.addr2[0], frame_info.addr2[1], frame_info.addr2[2], + frame_info.addr2[3], frame_info.addr2[4], frame_info.addr2[5], + s_monitor_debug ? "on" : "off", + s_monitor_debug_filter_enabled ? "on" : "off"); + data_frame_diag_counter = 0; + last_diag_log_ms = now_ms; + } + + /* Update histograms for data frames */ + // AMPDU histogram: [1, 2-4, 5-8, 9-16, 17-32, 33-48, 49-64, 65+] + uint8_t ampdu_count = frame_info.ampdu_count; + if (ampdu_count == 1) { + stats.ampdu_hist[0]++; + } else if (ampdu_count >= 2 && ampdu_count <= 4) { + stats.ampdu_hist[1]++; + } else if (ampdu_count >= 5 && ampdu_count <= 8) { + stats.ampdu_hist[2]++; + } else if (ampdu_count >= 9 && ampdu_count <= 16) { + stats.ampdu_hist[3]++; + } else if (ampdu_count >= 17 && ampdu_count <= 32) { + stats.ampdu_hist[4]++; + } else if (ampdu_count >= 33 && ampdu_count <= 48) { + stats.ampdu_hist[5]++; + } else if (ampdu_count >= 49 && ampdu_count <= 64) { + stats.ampdu_hist[6]++; + } else if (ampdu_count >= 65) { + stats.ampdu_hist[7]++; + } + + // MCS histogram: [0-31] + if (frame_info.mcs < 32) { + stats.mcs_hist[frame_info.mcs]++; + } + + // Spatial streams histogram: [1-8] + uint8_t ss = frame_info.spatial_streams; + if (ss >= 1 && ss <= 8) { + stats.ss_hist[ss - 1]++; // Convert to 0-indexed + } + + /* Debug logging for data frames (especially QoS data used by iperf) */ + if (s_monitor_debug) { + bool filter_match = wifi_monitor_debug_filter_match(frame_info.addr2); + uint16_t phy_rate_mbps = frame_info.phy_rate_kbps / 1000; + const char *frame_type_name = wifi_frame_type_str(frame_info.type, frame_info.subtype); + + if (filter_match) { + /* Log all data frames (QoS and non-QoS) when filter matches or filter is disabled */ + ESP_LOGI("MONITOR", "DATA: %s, TA=%02x:%02x:%02x:%02x:%02x:%02x, RA=%02x:%02x:%02x:%02x:%02x:%02x, " + "Size=%u bytes, Rate=%u Mbps, MCS=%u, SS=%u, BW=%u MHz, RSSI=%d dBm, Retry=%s", + frame_type_name, + frame_info.addr2[0], frame_info.addr2[1], frame_info.addr2[2], + frame_info.addr2[3], frame_info.addr2[4], frame_info.addr2[5], + frame_info.addr1[0], frame_info.addr1[1], frame_info.addr1[2], + frame_info.addr1[3], frame_info.addr1[4], frame_info.addr1[5], + frame_info.frame_len, phy_rate_mbps, frame_info.mcs, + frame_info.spatial_streams, + (frame_info.bandwidth == 0) ? 20 : (frame_info.bandwidth == 1) ? 40 : 80, + frame_info.rssi, frame_info.retry ? "YES" : "no"); + } else { + /* Diagnostic: log when data frames are filtered out (throttled, but more visible) */ + static uint32_t filtered_data_count = 0; + static uint64_t last_filtered_log_ms = 0; + static uint8_t last_filtered_ta[6] = {0}; + filtered_data_count++; + uint64_t now_ms = esp_timer_get_time() / 1000; + memcpy(last_filtered_ta, frame_info.addr2, 6); + if (now_ms - last_filtered_log_ms > 5000) { /* Log every 5 seconds */ + ESP_LOGW("MONITOR", "Filtered out %lu data frames (last TA=%02x:%02x:%02x:%02x:%02x:%02x, filter=%s)", + (unsigned long)filtered_data_count, + last_filtered_ta[0], last_filtered_ta[1], last_filtered_ta[2], + last_filtered_ta[3], last_filtered_ta[4], last_filtered_ta[5], + s_monitor_debug_filter_enabled ? "enabled" : "disabled"); + filtered_data_count = 0; + last_filtered_log_ms = now_ms; + } + } + } break; } @@ -608,3 +823,51 @@ void wifi_monitor_set_debug(bool enable) { bool wifi_monitor_get_debug(void) { return s_monitor_debug; } + +/** + * @brief Check if a MAC address matches the debug filter + * @param mac MAC address to check (6 bytes) + * @return true if filter is disabled or MAC matches filter, false otherwise + */ +static bool wifi_monitor_debug_filter_match(const uint8_t *mac) { + if (!s_monitor_debug_filter_enabled || mac == NULL) { + return true; /* No filter or invalid MAC - allow all */ + } + /* Compare MAC addresses byte by byte */ + for (int i = 0; i < 6; i++) { + if (mac[i] != s_monitor_debug_filter_mac[i]) { + return false; /* MAC doesn't match filter */ + } + } + return true; /* MAC matches filter */ +} + +/** + * @brief Set MAC address filter for debug logging + * @param mac MAC address to filter on (6 bytes), or NULL to disable filter + * @return ESP_OK on success, ESP_ERR_INVALID_ARG if mac is invalid + */ +esp_err_t wifi_monitor_set_debug_filter(const uint8_t *mac) { + if (mac == NULL) { + /* Disable filter */ + s_monitor_debug_filter_enabled = false; + memset(s_monitor_debug_filter_mac, 0, 6); + return ESP_OK; + } + /* Enable filter and copy MAC address */ + memcpy(s_monitor_debug_filter_mac, mac, 6); + s_monitor_debug_filter_enabled = true; + return ESP_OK; +} + +/** + * @brief Get current MAC address filter for debug logging + * @param mac_out Buffer to store MAC address (6 bytes), or NULL to just check if filter is enabled + * @return true if filter is enabled, false otherwise + */ +bool wifi_monitor_get_debug_filter(uint8_t *mac_out) { + if (mac_out != NULL && s_monitor_debug_filter_enabled) { + memcpy(mac_out, s_monitor_debug_filter_mac, 6); + } + return s_monitor_debug_filter_enabled; +} diff --git a/components/wifi_monitor/wifi_monitor.h b/components/wifi_monitor/wifi_monitor.h index d124689..5a0c19f 100644 --- a/components/wifi_monitor/wifi_monitor.h +++ b/components/wifi_monitor/wifi_monitor.h @@ -176,6 +176,9 @@ typedef struct { uint8_t bandwidth; // 0=20MHz, 1=40MHz, 2=80MHz, 3=160MHz uint32_t phy_rate_kbps; // Calculated PHY rate in Kbps (uint32_t to handle >65 Mbps) + // Aggregation info + uint8_t ampdu_count; // Number of aggregated MPDUs in A-MPDU (1 = not aggregated) + // Frame size uint16_t frame_len; } wifi_frame_info_t; @@ -205,6 +208,11 @@ typedef struct { 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 + + // Histograms + uint32_t ampdu_hist[8]; // AMPDU aggregation: [1, 2-4, 5-8, 9-16, 17-32, 33-48, 49-64, 65+] + uint32_t mcs_hist[32]; // MCS index: [0-31] + uint32_t ss_hist[8]; // Spatial streams: [1-8] } wifi_collapse_stats_t; /** @@ -300,6 +308,20 @@ void wifi_monitor_set_debug(bool enable); */ bool wifi_monitor_get_debug(void); +/** + * @brief Set MAC address filter for debug logging + * @param mac MAC address to filter on (6 bytes), or NULL to disable filter + * @return ESP_OK on success, ESP_ERR_INVALID_ARG if mac is invalid + */ +esp_err_t wifi_monitor_set_debug_filter(const uint8_t *mac); + +/** + * @brief Get current MAC address filter for debug logging + * @param mac_out Buffer to store MAC address (6 bytes), or NULL to just check if filter is enabled + * @return true if filter is enabled, false otherwise + */ +bool wifi_monitor_get_debug_filter(uint8_t *mac_out); + #ifdef __cplusplus } #endif