#include "web_server.h" #include "wifi_scan.h" #include "wifi_sniffer.h" #include "deauth_engine.h" // #include "bt_scanner.h" // Temporarily disabled #include "signal_analysis.h" #include "handshake_capture.h" #include "pcap_serializer.h" #include "hccapx_serializer.h" #include #include #include "esp_http_server.h" #include "esp_log.h" #include "esp_wifi.h" #include "cJSON.h" #include "esp_chip_info.h" #include "esp_system.h" #include "nvs_flash.h" // Define MIN macro if not already defined #ifndef MIN #define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif // Define struct to avoid including wifi_init.c header typedef struct { uint8_t data[1024]; uint16_t length; int8_t rssi; uint8_t channel; wifi_pkt_rx_ctrl_t rx_ctrl; } packet_info_t; static const char *TAG = "web_server"; // Web server handle static httpd_handle_t server = NULL; // Embedded web content static const char index_html[] = "\n"\ "\n"\ "\n"\ " \n"\ " \n"\ " ESP32-C5 TOOLKIT\n"\ " \n"\ " \n"\ "\n"\ "\n"\ "
\n"\ "
\n"\ "
\n"\ "

ESP32-C5

\n"\ "
TOOLKIT v2.0
\n"\ "
\n"\ "
    \n"\ "
  • DASHBOARD
  • \n"\ "
  • WIFI SCANNER
  • \n"\ "
  • HANDSHAKE CAPTURE
  • \n"\ "
  • PACKET SNIFFER
  • \n"\ "
  • BLUETOOTH SCANNER
  • \n"\ "
  • DEAUTH ENGINE
  • \n"\ "
  • SIGNAL ANALYSIS
  • \n"\ "
  • CHANNEL MAP
  • \n"\ "
  • SYSTEM CONFIG
  • \n"\ "
\n"\ "
\n"\ "
\n"\ " \n"\ "
\n"\ "
\n"\ "

SYSTEM DASHBOARD

\n"\ "
\n"\ "
\n"\ "
\n"\ "
UPTIME
\n"\ "
00:00:00
\n"\ "
\n"\ "
\n"\ "
FREE HEAP
\n"\ "
0 KB
\n"\ "
\n"\ "
\n"\ "
NETWORKS FOUND
\n"\ "
0
\n"\ "
\n"\ "
\n"\ "
PACKETS CAPTURED
\n"\ "
0
\n"\ "
\n"\ "
\n"\ "
\n"\ "
SYSTEM INFORMATION
\n"\ "
\n"\ "
\n"\ "
\n"\ "
NETWORK ACTIVITY
\n"\ "
\n"\ " \n"\ "
\n"\ "
\n"\ "
\n"\ " \n"\ " \n"\ "
\n"\ "
\n"\ "

WIFI NETWORK SCANNER

\n"\ "
\n"\ "
\n"\ "
SCAN CONTROLS
\n"\ " \n"\ " \n"\ " \n"\ " READY\n"\ "
\n"\ "
\n"\ "
SIGNAL STRENGTH DISTRIBUTION
\n"\ "
\n"\ " \n"\ "
\n"\ "
\n"\ "
\n"\ "
DETECTED NETWORKS
\n"\ "
\n"\ " \n"\ " \n"\ " \n"\ " \n"\ " \n"\ " \n"\ " \n"\ " \n"\ " \n"\ " \n"\ " \n"\ " \n"\ " \n"\ "
SSIDBSSIDBANDCHRSSISECURITYSIGNAL
\n"\ "
\n"\ "
\n"\ "
\n"\ " \n"\ " \n"\ "
\n"\ "
\n"\ "

WPA/WPA2 HANDSHAKE CAPTURE

\n"\ "
\n"\ "
\n"\ " WARNING: USE ONLY ON NETWORKS YOU OWN OR HAVE AUTHORIZATION TO TEST.\n"\ "
\n"\ "
\n"\ "
TARGET SELECTION
\n"\ " \n"\ "
\n"\ "
\n"\ "
\n"\ "
CAPTURE CONFIGURATION
\n"\ "
\n"\ " \n"\ " \n"\ "
\n"\ "
\n"\ " \n"\ " \n"\ "
\n"\ " \n"\ " \n"\ " \n"\ "
\n"\ "
\n"\ "
CAPTURE STATUS
\n"\ "
\n"\ "
\n"\ "
STATUS
\n"\ "
IDLE
\n"\ "
\n"\ "
\n"\ "
HANDSHAKE
\n"\ "
NONE
\n"\ "
\n"\ "
\n"\ "
PACKETS
\n"\ "
0
\n"\ "
\n"\ "
\n"\ "
DEAUTH SENT
\n"\ "
0
\n"\ "
\n"\ "
\n"\ "
\n"\ "
\n"\ "
\n"\ "
DOWNLOAD CAPTURES
\n"\ "

Download captured handshake for offline cracking with Hashcat or Wireshark analysis.

\n"\ " DOWNLOAD PCAP\n"\ " DOWNLOAD HCCAPX (HASHCAT)\n"\ "
\n"\ "
\n"\ " \n"\ " \n"\ "
\n"\ "
\n"\ "

PACKET SNIFFER

\n"\ "
\n"\ "
\n"\ "
CAPTURE CONFIGURATION
\n"\ "
\n"\ " \n"\ " \n"\ "
\n"\ "
\n"\ " \n"\ " \n"\ "
\n"\ " \n"\ " \n"\ " \n"\ "
\n"\ "
\n"\ "
PACKET FLOW
\n"\ "
\n"\ " \n"\ "
\n"\ "
\n"\ "
\n"\ "
CAPTURED PACKETS
\n"\ "
\n"\ "
\n"\ "
\n"\ " \n"\ " \n"\ "
\n"\ "
\n"\ "

BLUETOOTH SCANNER

\n"\ "
\n"\ "
\n"\ "
SCAN CONTROLS
\n"\ " \n"\ " \n"\ " \n"\ "
\n"\ "
\n"\ "
BLUETOOTH DEVICES
\n"\ " \n"\ " \n"\ " \n"\ " \n"\ " \n"\ " \n"\ " \n"\ " \n"\ " \n"\ " \n"\ " \n"\ "
NAMEADDRESSRSSITYPEACTIONS
\n"\ "
\n"\ "
\n"\ "
BLUETOOTH JAMMING
\n"\ "
\n"\ " WARNING: USE ONLY ON YOUR OWN DEVICES. UNAUTHORIZED JAMMING IS ILLEGAL.\n"\ "
\n"\ "
\n"\ " \n"\ " \n"\ "
\n"\ "
\n"\ " \n"\ " \n"\ "
\n"\ " \n"\ " \n"\ "
\n"\ "
\n"\ " \n"\ " \n"\ "
\n"\ "
\n"\ "

DEAUTH ENGINE

\n"\ "
\n"\ "
\n"\ " WARNING: USE ONLY ON NETWORKS YOU OWN. UNAUTHORIZED USE IS ILLEGAL.\n"\ "
\n"\ "
\n"\ "
TARGET SELECTION
\n"\ " \n"\ "
\n"\ "
\n"\ "
\n"\ "
ATTACK CONFIGURATION
\n"\ "
\n"\ " \n"\ " \n"\ "
\n"\ " \n"\ " \n"\ "
\n"\ "
\n"\ "
ATTACK STATISTICS
\n"\ "
\n"\ "
\n"\ "
TOTAL PACKETS
\n"\ "
0
\n"\ "
\n"\ "
\n"\ "
2.4GHZ PACKETS
\n"\ "
0
\n"\ "
\n"\ "
\n"\ "
5GHZ PACKETS
\n"\ "
0
\n"\ "
\n"\ "
\n"\ "
ELAPSED TIME
\n"\ "
0s
\n"\ "
\n"\ "
\n"\ "
\n"\ "
\n"\ " \n"\ " \n"\ "
\n"\ "
\n"\ "

SIGNAL ANALYSIS

\n"\ "
\n"\ "
\n"\ "
SIGNAL STRENGTH OVER TIME
\n"\ "
\n"\ " \n"\ "
\n"\ "
\n"\ "
\n"\ "
CHANNEL UTILIZATION
\n"\ "
\n"\ " \n"\ "
\n"\ "
\n"\ "
\n"\ " \n"\ " \n"\ "
\n"\ "
\n"\ "

CHANNEL MAPPING

\n"\ "
\n"\ "
\n"\ "
2.4GHZ BAND
\n"\ "
\n"\ "
\n"\ "
\n"\ "
5GHZ BAND
\n"\ "
\n"\ "
\n"\ "
\n"\ " \n"\ " \n"\ "
\n"\ "
\n"\ "

SYSTEM CONFIGURATION

\n"\ "
\n"\ "
\n"\ "
DEVICE SETTINGS
\n"\ "
\n"\ " \n"\ " \n"\ "
\n"\ " \n"\ "
\n"\ "
\n"\ "
SYSTEM CONTROL
\n"\ " \n"\ "
\n"\ "
\n"\ "
\n"\ "
\n"\ " \n"\ " \n"\ "\n"\ ""; // Function to get content type based on file extension static __attribute__((unused)) const char* get_content_type(const char *filepath) { const char *ext = strrchr(filepath, '.'); if (ext) { ext++; // Move past the dot if (strcasecmp(ext, "html") == 0) return "text/html"; if (strcasecmp(ext, "css") == 0) return "text/css"; if (strcasecmp(ext, "js") == 0) return "application/javascript"; if (strcasecmp(ext, "json") == 0) return "application/json"; if (strcasecmp(ext, "png") == 0) return "image/png"; if (strcasecmp(ext, "jpg") == 0 || strcasecmp(ext, "jpeg") == 0) return "image/jpeg"; if (strcasecmp(ext, "ico") == 0) return "image/x-icon"; } return "text/plain"; } // Handler for serving the root index page (copied from working WiFi-Scanner) static esp_err_t index_handler(httpd_req_t *req) { ESP_LOGI(TAG, "=========================================="); ESP_LOGI(TAG, "✓ ROOT HANDLER CALLED!"); ESP_LOGI(TAG, "URI: %s", req->uri); ESP_LOGI(TAG, "Method: %d", req->method); ESP_LOGI(TAG, "HTML size: %zu bytes", strlen(index_html)); ESP_LOGI(TAG, "=========================================="); // Send the index.html content directly httpd_resp_set_type(req, "text/html"); esp_err_t ret = httpd_resp_send(req, index_html, strlen(index_html)); if (ret == ESP_OK) { ESP_LOGI(TAG, "✓ HTML response sent successfully (%zu bytes)", strlen(index_html)); } else { ESP_LOGE(TAG, "✗ Failed to send HTML response: %s", esp_err_to_name(ret)); } return ret; } // API handler for scanning WiFi networks static esp_err_t api_scan_networks_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); ESP_LOGI(TAG, "Starting WiFi scan..."); // Use our unified wifi_scan module int result = wifi_scan_networks(); if (result != 0) { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Scan failed\"}"); return ESP_OK; } // Get results as JSON const char *json_results = wifi_scan_get_results_json(); if (json_results) { // Create response with status cJSON *root = cJSON_CreateObject(); cJSON_AddStringToObject(root, "status", "success"); // Parse the networks array from wifi_scan results cJSON *networks_json = cJSON_Parse(json_results); if (networks_json && cJSON_IsArray(networks_json)) { cJSON_AddItemToObject(root, "networks", networks_json); } else { cJSON *empty_array = cJSON_CreateArray(); cJSON_AddItemToObject(root, "networks", empty_array); if (networks_json) cJSON_Delete(networks_json); } char *json_response = cJSON_PrintUnformatted(root); if (json_response) { httpd_resp_sendstr(req, json_response); free(json_response); } else { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"JSON creation failed\"}"); } cJSON_Delete(root); } else { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"No scan results\"}"); } return ESP_OK; } // API handler for system information static esp_err_t api_system_info_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); // Create JSON response cJSON *root = cJSON_CreateObject(); // Add IDF version cJSON_AddStringToObject(root, "idf_version", esp_get_idf_version()); // Add heap info cJSON_AddNumberToObject(root, "free_heap", esp_get_free_heap_size()); // Add MAC address uint8_t mac[6]; esp_wifi_get_mac(WIFI_IF_STA, mac); char mac_str[18]; snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); cJSON_AddStringToObject(root, "mac_address", mac_str); // Add chip info esp_chip_info_t chip_info; esp_chip_info(&chip_info); cJSON_AddNumberToObject(root, "chip_model", chip_info.model); cJSON_AddNumberToObject(root, "chip_cores", chip_info.cores); char features[64] = {0}; if (chip_info.features & CHIP_FEATURE_WIFI_BGN) { strcat(features, "WiFi 802.11bgn"); } if (chip_info.features & CHIP_FEATURE_BLE) { if (strlen(features) > 0) strcat(features, ", "); strcat(features, "BLE"); } cJSON_AddStringToObject(root, "features", features); // Generate JSON string char *json_response = cJSON_PrintUnformatted(root); httpd_resp_sendstr(req, json_response); // Clean up free(json_response); cJSON_Delete(root); return ESP_OK; } // API handler for antenna settings static esp_err_t api_antenna_settings_handler(httpd_req_t *req) { esp_err_t ret = ESP_OK; char content[100]; httpd_resp_set_type(req, "application/json"); // Validate content length if (req->content_len == 0 || req->content_len > sizeof(content) - 1) { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Invalid request size\"}"); return ESP_FAIL; } int ret_len = httpd_req_recv(req, content, sizeof(content) - 1); if (ret_len <= 0) { httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive data"); return ESP_FAIL; } content[ret_len] = '\0'; // Parse JSON request cJSON *root = cJSON_Parse(content); if (!root) { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Invalid JSON\"}"); return ESP_FAIL; } // Get the "external_antenna" field from the JSON cJSON *ext_antenna_json = cJSON_GetObjectItem(root, "external_antenna"); if (cJSON_IsBool(ext_antenna_json)) { bool use_external = cJSON_IsTrue(ext_antenna_json); // Store the setting in NVS nvs_handle_t nvs_handle; ret = nvs_open("storage", NVS_READWRITE, &nvs_handle); if (ret == ESP_OK) { ret = nvs_set_u8(nvs_handle, "ext_antenna", use_external ? 1 : 0); if (ret == ESP_OK) { ret = nvs_commit(nvs_handle); } nvs_close(nvs_handle); } // Log the new antenna setting ESP_LOGI(TAG, "Antenna setting changed: %s", use_external ? "External" : "Internal"); // Note: For ESP32-C5, we can't directly set antenna mode through normal API // Send response if (ret == ESP_OK) { httpd_resp_sendstr(req, use_external ? "{\"status\":\"ok\",\"message\":\"External antenna selected. Please reboot for changes to take effect\",\"external_antenna\":true}" : "{\"status\":\"ok\",\"message\":\"Internal antenna selected. Please reboot for changes to take effect\",\"external_antenna\":false}" ); } else { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Failed to save antenna setting\"}"); } } else { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Missing external_antenna field\"}"); } cJSON_Delete(root); return ESP_OK; } // API handler to get current antenna settings static esp_err_t api_get_antenna_settings_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); // Get the setting from NVS nvs_handle_t nvs_handle; uint8_t use_external = 0; esp_err_t ret = nvs_open("storage", NVS_READONLY, &nvs_handle); if (ret == ESP_OK) { nvs_get_u8(nvs_handle, "ext_antenna", &use_external); nvs_close(nvs_handle); } // Create JSON response cJSON *root = cJSON_CreateObject(); cJSON_AddStringToObject(root, "status", "ok"); cJSON_AddBoolToObject(root, "external_antenna", use_external); char *json_response = cJSON_PrintUnformatted(root); if (!json_response) { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"JSON printing failed\"}"); } else { httpd_resp_sendstr(req, json_response); free(json_response); } cJSON_Delete(root); return ESP_OK; } // Translate filter type string to numeric value static uint8_t get_filter_type(const char* filter_str) { if (strcmp(filter_str, "management") == 0) return 1; if (strcmp(filter_str, "data") == 0) return 2; if (strcmp(filter_str, "control") == 0) return 3; if (strcmp(filter_str, "beacon") == 0) return 4; if (strcmp(filter_str, "probe") == 0) return 5; return 0; // default: all packets } // API handler for packet sniffing start static esp_err_t api_sniff_start_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); ESP_LOGI(TAG, "Starting packet sniffer"); // Extract query parameters char buf[100]; buf[0] = '\0'; // Get channel parameter int channel = 0; // Default: channel hopping if (httpd_req_get_url_query_str(req, buf, sizeof(buf)) == ESP_OK) { char param[32]; if (httpd_query_key_value(buf, "channel", param, sizeof(param)) == ESP_OK) { channel = atoi(param); } } // Get filter parameter char filter[32] = "all"; // Default: all packets if (buf[0] != '\0') { // If we have query parameters if (httpd_query_key_value(buf, "filter", filter, sizeof(filter)) != ESP_OK) { strcpy(filter, "all"); // Reset to default if not found } } // Convert filter string to numeric value uint8_t filter_type = get_filter_type(filter); // Start the sniffer bool success = start_wifi_sniffer(channel, filter_type); // Create response cJSON *root = cJSON_CreateObject(); if (success) { cJSON_AddStringToObject(root, "status", "success"); cJSON_AddNumberToObject(root, "channel", channel); cJSON_AddStringToObject(root, "filter", filter); cJSON_AddStringToObject(root, "message", "Packet capture started"); } else { cJSON_AddStringToObject(root, "status", "error"); cJSON_AddStringToObject(root, "message", "Failed to start packet capture"); } char *json_response = cJSON_PrintUnformatted(root); httpd_resp_sendstr(req, json_response); free(json_response); cJSON_Delete(root); return ESP_OK; } // API handler for packet sniffing stop static esp_err_t api_sniff_stop_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); ESP_LOGI(TAG, "Stopping packet sniffer"); // Stop the sniffer bool success = stop_wifi_sniffer(); // Create response cJSON *root = cJSON_CreateObject(); if (success) { cJSON_AddStringToObject(root, "status", "success"); cJSON_AddStringToObject(root, "message", "Packet capture stopped"); } else { cJSON_AddStringToObject(root, "status", "error"); cJSON_AddStringToObject(root, "message", "Sniffer was not running"); } char *json_response = cJSON_PrintUnformatted(root); httpd_resp_sendstr(req, json_response); free(json_response); cJSON_Delete(root); return ESP_OK; } // Helper function to format MAC address static void format_mac_addr(char *dest, const uint8_t *addr) { snprintf(dest, 18, "%02x:%02x:%02x:%02x:%02x:%02x", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); } // Helper function to determine packet type from frame control static const char* get_frame_type_str(uint16_t frame_ctrl) { uint8_t type = (frame_ctrl & 0x000C) >> 2; uint8_t subtype = (frame_ctrl & 0x00F0) >> 4; switch (type) { case 0: // Management switch (subtype) { case 0: return "ASSOC_REQ"; case 1: return "ASSOC_RES"; case 2: return "REASSOC_REQ"; case 3: return "REASSOC_RES"; case 4: return "PROBE_REQ"; case 5: return "PROBE_RES"; case 8: return "BEACON"; case 9: return "ATIM"; case 10: return "DISASSOC"; case 11: return "AUTH"; case 12: return "DEAUTH"; case 13: return "ACTION"; default: return "MGMT"; } case 1: // Control switch (subtype) { case 8: return "BLOCK_ACK_REQ"; case 9: return "BLOCK_ACK"; case 10: return "PS_POLL"; case 11: return "RTS"; case 12: return "CTS"; case 13: return "ACK"; case 14: return "CF_END"; default: return "CTRL"; } case 2: // Data return "DATA"; default: return "UNKNOWN"; } } // API handler for getting captured packets static esp_err_t api_sniff_packets_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); // Allocate memory for packet pointers packet_info_t *packets[10]; int num_packets = get_captured_packets((void**)packets, 10); // Create response cJSON *root = cJSON_CreateObject(); cJSON *packets_array = cJSON_AddArrayToObject(root, "packets"); for (int i = 0; i < num_packets; i++) { packet_info_t *pkt = packets[i]; // Create JSON object for this packet cJSON *packet = cJSON_CreateObject(); // Extract header info uint16_t frame_ctrl = *(uint16_t*)pkt->data; uint8_t *addr1 = pkt->data + 4; // dst uint8_t *addr2 = pkt->data + 10; // src // Format MAC addresses char src_mac[18], dst_mac[18]; format_mac_addr(dst_mac, addr1); format_mac_addr(src_mac, addr2); // Add packet information cJSON_AddStringToObject(packet, "type", get_frame_type_str(frame_ctrl)); cJSON_AddStringToObject(packet, "src", src_mac); cJSON_AddStringToObject(packet, "dst", dst_mac); cJSON_AddNumberToObject(packet, "channel", pkt->channel); cJSON_AddNumberToObject(packet, "rssi", pkt->rssi); // Format data as hex if (pkt->length > 0) { char *hex_data = malloc(pkt->length * 3 + 1); if (hex_data) { int pos = 0; for (int j = 0; j < pkt->length && j < 64; j++) { // Only show first 64 bytes pos += snprintf(hex_data + pos, 4, "%02x ", pkt->data[j]); } if (pkt->length > 64) { strcat(hex_data, "..."); } cJSON_AddStringToObject(packet, "data", hex_data); free(hex_data); } } // Add packet to array cJSON_AddItemToArray(packets_array, packet); // Free the packet free(pkt); } char *json_response = cJSON_PrintUnformatted(root); httpd_resp_sendstr(req, json_response); free(json_response); cJSON_Delete(root); return ESP_OK; } // Maximum allowed request body size #define MAX_REQUEST_BODY_SIZE 1024 // API handler for deauth attack start static esp_err_t api_deauth_start_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); // Validate content length if (req->content_len > MAX_REQUEST_BODY_SIZE) { ESP_LOGW(TAG, "Request body too large: %zu > %d", req->content_len, MAX_REQUEST_BODY_SIZE); httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Request too large\"}"); return ESP_FAIL; } char buf[MAX_REQUEST_BODY_SIZE]; int ret = httpd_req_recv(req, buf, sizeof(buf)-1); if (ret <= 0) { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"No data\"}"); return ESP_FAIL; } buf[ret] = '\0'; cJSON *root = cJSON_Parse(buf); if (!root) { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Bad JSON\"}"); return ESP_FAIL; } deauth_target_t target_24ghz = {0}; deauth_target_t target_5ghz = {0}; // Parse 2.4GHz target cJSON *t24 = cJSON_GetObjectItem(root, "target_24ghz"); if (t24) { cJSON *ssid = cJSON_GetObjectItem(t24, "ssid"); cJSON *bssid = cJSON_GetObjectItem(t24, "bssid"); cJSON *channel = cJSON_GetObjectItem(t24, "channel"); if (cJSON_IsString(ssid) && cJSON_IsString(bssid) && cJSON_IsNumber(channel)) { strncpy(target_24ghz.ssid, ssid->valuestring, sizeof(target_24ghz.ssid) - 1); if (sscanf(bssid->valuestring, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &target_24ghz.bssid[0], &target_24ghz.bssid[1], &target_24ghz.bssid[2], &target_24ghz.bssid[3], &target_24ghz.bssid[4], &target_24ghz.bssid[5]) == 6) { target_24ghz.channel = (uint8_t)channel->valueint; target_24ghz.active = true; } } } // Parse 5GHz target cJSON *t5 = cJSON_GetObjectItem(root, "target_5ghz"); if (t5) { cJSON *ssid = cJSON_GetObjectItem(t5, "ssid"); cJSON *bssid = cJSON_GetObjectItem(t5, "bssid"); cJSON *channel = cJSON_GetObjectItem(t5, "channel"); if (cJSON_IsString(ssid) && cJSON_IsString(bssid) && cJSON_IsNumber(channel)) { strncpy(target_5ghz.ssid, ssid->valuestring, sizeof(target_5ghz.ssid) - 1); if (sscanf(bssid->valuestring, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &target_5ghz.bssid[0], &target_5ghz.bssid[1], &target_5ghz.bssid[2], &target_5ghz.bssid[3], &target_5ghz.bssid[4], &target_5ghz.bssid[5]) == 6) { target_5ghz.channel = (uint8_t)channel->valueint; target_5ghz.active = true; } } } // Parse duration cJSON *duration_json = cJSON_GetObjectItem(root, "duration"); uint32_t duration = 30; // Default if (cJSON_IsNumber(duration_json)) { duration = (uint32_t)duration_json->valueint; } bool success = deauth_start_attack( target_24ghz.active ? &target_24ghz : NULL, target_5ghz.active ? &target_5ghz : NULL, duration ); cJSON_Delete(root); if (success) { httpd_resp_sendstr(req, "{\"status\":\"success\",\"message\":\"Attack started\"}"); } else { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Failed to start attack\"}"); } return ESP_OK; } // API handler for deauth attack stop static esp_err_t api_deauth_stop_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); bool success = deauth_stop_attack(); if (success) { httpd_resp_sendstr(req, "{\"status\":\"success\",\"message\":\"Attack stopped\"}"); } else { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Attack not running\"}"); } return ESP_OK; } // API handler for deauth status static esp_err_t api_deauth_status_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); cJSON *root = cJSON_CreateObject(); cJSON_AddBoolToObject(root, "running", deauth_is_running()); uint32_t total, p24, p5, elapsed; deauth_get_stats(&total, &p24, &p5, &elapsed); cJSON_AddNumberToObject(root, "total_packets", total); cJSON_AddNumberToObject(root, "packets_24ghz", p24); cJSON_AddNumberToObject(root, "packets_5ghz", p5); cJSON_AddNumberToObject(root, "elapsed_time", elapsed); char *json_response = cJSON_PrintUnformatted(root); httpd_resp_sendstr(req, json_response); free(json_response); cJSON_Delete(root); return ESP_OK; } // API endpoint for rebooting the device static esp_err_t api_reboot_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); // Send response before rebooting httpd_resp_sendstr(req, "{\"status\":\"ok\",\"message\":\"Rebooting device...\"}"); // Schedule reboot after a short delay to allow the response to be sent ESP_LOGI(TAG, "Reboot requested via web interface"); esp_restart(); return ESP_OK; } // Bluetooth API handlers (temporarily disabled) static esp_err_t api_bt_scan_start_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Bluetooth temporarily disabled\"}"); return ESP_OK; } static esp_err_t api_bt_scan_stop_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Bluetooth temporarily disabled\"}"); return ESP_OK; } static esp_err_t api_bt_devices_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); /* bt_device_t devices[50]; int count = bt_get_devices(devices, 50); */ httpd_resp_sendstr(req, "{\"devices\":[]}"); return ESP_OK; } static esp_err_t api_bt_jam_start_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Bluetooth temporarily disabled\"}"); return ESP_OK; } static esp_err_t api_bt_jam_stop_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Bluetooth temporarily disabled\"}"); return ESP_OK; } // Signal Analysis API handlers static esp_err_t api_signal_channel_util_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); channel_data_t channels[165]; int count = signal_get_channel_utilization(channels, 165); cJSON *root = cJSON_CreateObject(); cJSON *channels_array = cJSON_AddArrayToObject(root, "channels"); for (int i = 0; i < count; i++) { cJSON *ch = cJSON_CreateObject(); cJSON_AddNumberToObject(ch, "channel", channels[i].channel); cJSON_AddNumberToObject(ch, "rssi", channels[i].rssi); cJSON_AddNumberToObject(ch, "packet_count", channels[i].packet_count); cJSON_AddNumberToObject(ch, "utilization", (channels[i].packet_count * 100) / 1000); // Simplified calculation cJSON_AddItemToArray(channels_array, ch); } char *json_response = cJSON_PrintUnformatted(root); httpd_resp_sendstr(req, json_response); free(json_response); cJSON_Delete(root); return ESP_OK; } static esp_err_t api_signal_rssi_history_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); char buf[100]; if (httpd_req_get_url_query_str(req, buf, sizeof(buf)) != ESP_OK) { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Missing bssid parameter\"}"); return ESP_FAIL; } char bssid_str[20]; if (httpd_query_key_value(buf, "bssid", bssid_str, sizeof(bssid_str)) != ESP_OK) { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Missing bssid parameter\"}"); return ESP_FAIL; } uint8_t bssid[6]; if (sscanf(bssid_str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &bssid[0], &bssid[1], &bssid[2], &bssid[3], &bssid[4], &bssid[5]) != 6) { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Invalid BSSID format\"}"); return ESP_FAIL; } int8_t rssi_history[100]; uint32_t timestamps[100]; int samples = signal_get_rssi_history(bssid, rssi_history, timestamps, 100); cJSON *root = cJSON_CreateObject(); cJSON *rssi_array = cJSON_AddArrayToObject(root, "rssi"); cJSON *time_array = cJSON_AddArrayToObject(root, "timestamps"); for (int i = 0; i < samples; i++) { cJSON_AddItemToArray(rssi_array, cJSON_CreateNumber(rssi_history[i])); cJSON_AddItemToArray(time_array, cJSON_CreateNumber(timestamps[i])); } char *json_response = cJSON_PrintUnformatted(root); httpd_resp_sendstr(req, json_response); free(json_response); cJSON_Delete(root); return ESP_OK; } // Channel mapping API handler static esp_err_t api_channel_map_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); // Get scan results and create channel map int result = wifi_scan_networks(); if (result != 0) { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Scan failed\"}"); return ESP_FAIL; } const char *json_results = wifi_scan_get_results_json(); if (!json_results) { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"No scan results\"}"); return ESP_FAIL; } // Parse networks and create channel map cJSON *networks_json = cJSON_Parse(json_results); if (!networks_json || !cJSON_IsArray(networks_json)) { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Invalid scan data\"}"); return ESP_FAIL; } // Count networks per channel int channel_24[14] = {0}; // Channels 1-13 int channel_5[200] = {0}; // 5GHz channels int network_count = cJSON_GetArraySize(networks_json); for (int i = 0; i < network_count; i++) { cJSON *net = cJSON_GetArrayItem(networks_json, i); cJSON *channel_item = cJSON_GetObjectItem(net, "channel"); cJSON *band_item = cJSON_GetObjectItem(net, "band"); if (channel_item && cJSON_IsNumber(channel_item) && band_item && cJSON_IsString(band_item)) { int ch = channel_item->valueint; const char *band = band_item->valuestring; if (strstr(band, "2.4") && ch >= 1 && ch <= 13) { channel_24[ch - 1]++; } else if (strstr(band, "5") && ch >= 36 && ch <= 165) { if (ch < 200) channel_5[ch]++; } } } cJSON *root = cJSON_CreateObject(); cJSON *band_24 = cJSON_AddArrayToObject(root, "band_24ghz"); cJSON *band_5 = cJSON_AddArrayToObject(root, "band_5ghz"); for (int i = 0; i < 13; i++) { cJSON *ch_data = cJSON_CreateObject(); cJSON_AddNumberToObject(ch_data, "channel", i + 1); cJSON_AddNumberToObject(ch_data, "networks", channel_24[i]); cJSON_AddItemToArray(band_24, ch_data); } for (int i = 36; i <= 165; i++) { if (channel_5[i] > 0) { cJSON *ch_data = cJSON_CreateObject(); cJSON_AddNumberToObject(ch_data, "channel", i); cJSON_AddNumberToObject(ch_data, "networks", channel_5[i]); cJSON_AddItemToArray(band_5, ch_data); } } char *json_response = cJSON_PrintUnformatted(root); httpd_resp_sendstr(req, json_response); free(json_response); cJSON_Delete(root); cJSON_Delete(networks_json); return ESP_OK; } // ===================================================================== // HANDSHAKE CAPTURE API HANDLERS // ===================================================================== // API handler for starting handshake capture static esp_err_t api_handshake_start_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); // Get request body char buf[512]; int ret = httpd_req_recv(req, buf, sizeof(buf) - 1); if (ret <= 0) { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"No data received\"}"); return ESP_FAIL; } buf[ret] = '\0'; // Parse JSON cJSON *root = cJSON_Parse(buf); if (!root) { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Invalid JSON\"}"); return ESP_FAIL; } // Build capture config capture_config_t config = {0}; config.timeout_sec = 120; // Default 2 minutes config.method = CAPTURE_METHOD_DEAUTH_BROADCAST; config.deauth_interval_ms = 100; config.deauth_count = 5; // Get SSID cJSON *ssid_json = cJSON_GetObjectItem(root, "ssid"); if (ssid_json && cJSON_IsString(ssid_json)) { size_t len = strlen(ssid_json->valuestring); if (len > 32) len = 32; memcpy(config.ssid, ssid_json->valuestring, len); config.ssid_len = len; } // Get BSSID cJSON *bssid_json = cJSON_GetObjectItem(root, "bssid"); if (bssid_json && cJSON_IsString(bssid_json)) { sscanf(bssid_json->valuestring, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &config.bssid[0], &config.bssid[1], &config.bssid[2], &config.bssid[3], &config.bssid[4], &config.bssid[5]); } // Get channel cJSON *channel_json = cJSON_GetObjectItem(root, "channel"); if (channel_json && cJSON_IsNumber(channel_json)) { config.channel = (uint8_t)channel_json->valueint; } // Get timeout cJSON *timeout_json = cJSON_GetObjectItem(root, "timeout"); if (timeout_json && cJSON_IsNumber(timeout_json)) { config.timeout_sec = (uint32_t)timeout_json->valueint; } // Get method cJSON *method_json = cJSON_GetObjectItem(root, "method"); if (method_json && cJSON_IsString(method_json)) { if (strcmp(method_json->valuestring, "passive") == 0) { config.method = CAPTURE_METHOD_PASSIVE; } else if (strcmp(method_json->valuestring, "deauth") == 0) { config.method = CAPTURE_METHOD_DEAUTH_BROADCAST; } } cJSON_Delete(root); // Start capture bool success = handshake_capture_start(&config); if (success) { httpd_resp_sendstr(req, "{\"status\":\"success\",\"message\":\"Handshake capture started\"}"); } else { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Failed to start capture\"}"); } return ESP_OK; } // API handler for starting PMKID capture static esp_err_t api_pmkid_start_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); char buf[512]; int ret = httpd_req_recv(req, buf, sizeof(buf) - 1); if (ret <= 0) { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"No data received\"}"); return ESP_FAIL; } buf[ret] = '\0'; cJSON *root = cJSON_Parse(buf); if (!root) { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Invalid JSON\"}"); return ESP_FAIL; } capture_config_t config = {0}; config.timeout_sec = 60; cJSON *ssid_json = cJSON_GetObjectItem(root, "ssid"); if (ssid_json && cJSON_IsString(ssid_json)) { size_t len = strlen(ssid_json->valuestring); if (len > 32) len = 32; memcpy(config.ssid, ssid_json->valuestring, len); config.ssid_len = len; } cJSON *bssid_json = cJSON_GetObjectItem(root, "bssid"); if (bssid_json && cJSON_IsString(bssid_json)) { sscanf(bssid_json->valuestring, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &config.bssid[0], &config.bssid[1], &config.bssid[2], &config.bssid[3], &config.bssid[4], &config.bssid[5]); } cJSON *channel_json = cJSON_GetObjectItem(root, "channel"); if (channel_json && cJSON_IsNumber(channel_json)) { config.channel = (uint8_t)channel_json->valueint; } cJSON_Delete(root); bool success = pmkid_capture_start(&config); if (success) { httpd_resp_sendstr(req, "{\"status\":\"success\",\"message\":\"PMKID capture started\"}"); } else { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Failed to start PMKID capture\"}"); } return ESP_OK; } // API handler for stopping capture static esp_err_t api_handshake_stop_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); bool success = handshake_capture_stop(); if (success) { httpd_resp_sendstr(req, "{\"status\":\"success\",\"message\":\"Capture stopped\"}"); } else { httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"Capture was not running\"}"); } return ESP_OK; } // API handler for getting capture status static esp_err_t api_handshake_status_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); const capture_status_t *status = handshake_capture_get_status(); cJSON *root = cJSON_CreateObject(); // State const char *state_str = "idle"; switch (status->state) { case ATTACK_STATE_RUNNING: state_str = "running"; break; case ATTACK_STATE_FINISHED: state_str = "finished"; break; case ATTACK_STATE_TIMEOUT: state_str = "timeout"; break; case ATTACK_STATE_ERROR: state_str = "error"; break; default: state_str = "idle"; } cJSON_AddStringToObject(root, "state", state_str); // Handshake state const char *hs_state_str = "none"; switch (status->handshake_state) { case HANDSHAKE_STATE_M1_CAPTURED: hs_state_str = "M1"; break; case HANDSHAKE_STATE_M2_CAPTURED: hs_state_str = "M2"; break; case HANDSHAKE_STATE_M3_CAPTURED: hs_state_str = "M3"; break; case HANDSHAKE_STATE_M4_CAPTURED: hs_state_str = "M4"; break; case HANDSHAKE_STATE_COMPLETE: hs_state_str = "complete"; break; default: hs_state_str = "none"; } cJSON_AddStringToObject(root, "handshake_state", hs_state_str); cJSON_AddBoolToObject(root, "handshake_captured", status->handshake_captured); cJSON_AddBoolToObject(root, "pmkid_captured", status->pmkid_captured); cJSON_AddNumberToObject(root, "packets_captured", status->packets_captured); cJSON_AddNumberToObject(root, "eapol_packets", status->eapol_packets); cJSON_AddNumberToObject(root, "deauth_sent", status->deauth_sent); cJSON_AddNumberToObject(root, "elapsed_sec", status->elapsed_sec); cJSON_AddStringToObject(root, "target_ssid", status->target_ssid); char bssid_str[18]; snprintf(bssid_str, sizeof(bssid_str), "%02x:%02x:%02x:%02x:%02x:%02x", status->target_bssid[0], status->target_bssid[1], status->target_bssid[2], status->target_bssid[3], status->target_bssid[4], status->target_bssid[5]); cJSON_AddStringToObject(root, "target_bssid", bssid_str); char *json_response = cJSON_PrintUnformatted(root); httpd_resp_sendstr(req, json_response); free(json_response); cJSON_Delete(root); return ESP_OK; } // API handler for downloading PCAP file static esp_err_t api_capture_pcap_handler(httpd_req_t *req) { unsigned size = 0; uint8_t *buffer = handshake_capture_get_pcap(&size); if (!buffer || size == 0) { httpd_resp_set_type(req, "application/json"); httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"No PCAP data available\"}"); return ESP_FAIL; } // Set headers for file download httpd_resp_set_type(req, "application/octet-stream"); httpd_resp_set_hdr(req, "Content-Disposition", "attachment; filename=\"capture.pcap\""); // Send PCAP data return httpd_resp_send(req, (const char *)buffer, size); } // API handler for downloading HCCAPX file static esp_err_t api_capture_hccapx_handler(httpd_req_t *req) { unsigned size = 0; uint8_t *buffer = handshake_capture_get_hccapx(&size); if (!buffer || size == 0) { httpd_resp_set_type(req, "application/json"); httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"No handshake captured yet\"}"); return ESP_FAIL; } // Set headers for file download httpd_resp_set_type(req, "application/octet-stream"); httpd_resp_set_hdr(req, "Content-Disposition", "attachment; filename=\"capture.hccapx\""); // Send HCCAPX data return httpd_resp_send(req, (const char *)buffer, size); } // API handler to reset capture static esp_err_t api_handshake_reset_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); handshake_capture_reset(); httpd_resp_sendstr(req, "{\"status\":\"success\",\"message\":\"Capture reset\"}"); return ESP_OK; } // Register URI handlers esp_err_t register_uri_handlers(httpd_handle_t server) { // ==================================================================== // Register root handler for index page (SPA - Single Page Application) // Register API endpoints FIRST so they take precedence // ==================================================================== // API handler for scanning networks (registered first - takes precedence) httpd_uri_t scan_handler = { .uri = "/api/scan", .method = HTTP_GET, .handler = api_scan_networks_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &scan_handler); // API handler for system info httpd_uri_t sysinfo_handler = { .uri = "/api/system-info", .method = HTTP_GET, .handler = api_system_info_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &sysinfo_handler); // Register packet sniffing handlers httpd_uri_t sniff_start_handler = { .uri = "/api/sniff/start", .method = HTTP_GET, .handler = api_sniff_start_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &sniff_start_handler); httpd_uri_t sniff_stop_handler = { .uri = "/api/sniff/stop", .method = HTTP_GET, .handler = api_sniff_stop_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &sniff_stop_handler); httpd_uri_t sniff_packets_handler = { .uri = "/api/sniff/packets", .method = HTTP_GET, .handler = api_sniff_packets_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &sniff_packets_handler); // Register antenna settings endpoints httpd_uri_t antenna_settings_uri = { .uri = "/api/antenna", .method = HTTP_POST, .handler = api_antenna_settings_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &antenna_settings_uri); httpd_uri_t get_antenna_settings_uri = { .uri = "/api/antenna", .method = HTTP_GET, .handler = api_get_antenna_settings_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &get_antenna_settings_uri); // Register deauth endpoints httpd_uri_t deauth_start_uri = { .uri = "/api/deauth/start", .method = HTTP_POST, .handler = api_deauth_start_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &deauth_start_uri); httpd_uri_t deauth_stop_uri = { .uri = "/api/deauth/stop", .method = HTTP_GET, .handler = api_deauth_stop_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &deauth_stop_uri); httpd_uri_t deauth_status_uri = { .uri = "/api/deauth/status", .method = HTTP_GET, .handler = api_deauth_status_handler, .user_ctx = NULL }; // Register Bluetooth endpoints httpd_uri_t bt_scan_start_uri = { .uri = "/api/bt/scan/start", .method = HTTP_GET, .handler = api_bt_scan_start_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &bt_scan_start_uri); httpd_uri_t bt_scan_stop_uri = { .uri = "/api/bt/scan/stop", .method = HTTP_GET, .handler = api_bt_scan_stop_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &bt_scan_stop_uri); httpd_uri_t bt_devices_uri = { .uri = "/api/bt/devices", .method = HTTP_GET, .handler = api_bt_devices_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &bt_devices_uri); httpd_uri_t bt_jam_start_uri = { .uri = "/api/bt/jam/start", .method = HTTP_POST, .handler = api_bt_jam_start_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &bt_jam_start_uri); httpd_uri_t bt_jam_stop_uri = { .uri = "/api/bt/jam/stop", .method = HTTP_GET, .handler = api_bt_jam_stop_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &bt_jam_stop_uri); // Signal Analysis API handlers httpd_uri_t signal_channel_util_uri = { .uri = "/api/signal/channel-util", .method = HTTP_GET, .handler = api_signal_channel_util_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &signal_channel_util_uri); httpd_uri_t signal_rssi_history_uri = { .uri = "/api/signal/rssi-history", .method = HTTP_GET, .handler = api_signal_rssi_history_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &signal_rssi_history_uri); // Channel mapping API httpd_uri_t channel_map_uri = { .uri = "/api/channel-map", .method = HTTP_GET, .handler = api_channel_map_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &channel_map_uri); httpd_register_uri_handler(server, &deauth_status_uri); // Register reboot endpoint httpd_uri_t reboot_uri = { .uri = "/api/reboot", .method = HTTP_GET, .handler = api_reboot_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &reboot_uri); // ===================================================================== // HANDSHAKE CAPTURE ENDPOINTS // ===================================================================== // Start handshake capture httpd_uri_t handshake_start_uri = { .uri = "/api/handshake/start", .method = HTTP_POST, .handler = api_handshake_start_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &handshake_start_uri); // Start PMKID capture httpd_uri_t pmkid_start_uri = { .uri = "/api/pmkid/start", .method = HTTP_POST, .handler = api_pmkid_start_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &pmkid_start_uri); // Stop capture httpd_uri_t handshake_stop_uri = { .uri = "/api/handshake/stop", .method = HTTP_GET, .handler = api_handshake_stop_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &handshake_stop_uri); // Get capture status httpd_uri_t handshake_status_uri = { .uri = "/api/handshake/status", .method = HTTP_GET, .handler = api_handshake_status_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &handshake_status_uri); // Download PCAP file httpd_uri_t capture_pcap_uri = { .uri = "/capture.pcap", .method = HTTP_GET, .handler = api_capture_pcap_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &capture_pcap_uri); // Download HCCAPX file httpd_uri_t capture_hccapx_uri = { .uri = "/capture.hccapx", .method = HTTP_GET, .handler = api_capture_hccapx_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &capture_hccapx_uri); // Reset capture httpd_uri_t handshake_reset_uri = { .uri = "/api/handshake/reset", .method = HTTP_GET, .handler = api_handshake_reset_handler, .user_ctx = NULL }; httpd_register_uri_handler(server, &handshake_reset_uri); // Register root handler LAST (catch-all for SPA routing) // This will serve the index page for any non-API requests // Register for both GET and HEAD methods httpd_uri_t root_handler_get = { .uri = "/", // Root path .method = HTTP_GET, .handler = index_handler, .user_ctx = NULL }; esp_err_t root_result_get = httpd_register_uri_handler(server, &root_handler_get); httpd_uri_t root_handler_head = { .uri = "/", // Root path .method = HTTP_HEAD, .handler = index_handler, .user_ctx = NULL }; esp_err_t root_result_head = httpd_register_uri_handler(server, &root_handler_head); if (root_result_get == ESP_OK && root_result_head == ESP_OK) { ESP_LOGI(TAG, "✓ Root handler (/) registered successfully (GET & HEAD)"); } else { ESP_LOGE(TAG, "✗ Root handler registration FAILED: GET=%s, HEAD=%s", esp_err_to_name(root_result_get), esp_err_to_name(root_result_head)); } ESP_LOGI(TAG, "Total API handlers registered: ~28 (including root and handshake capture)"); // Note: We rely on the config settings for timeouts instead of trying // to set them at runtime which isn't supported in all ESP-IDF versions return ESP_OK; } // 404 Not Found handler (defined before use in start_webserver) static esp_err_t http_404_error_handler(httpd_req_t *req, httpd_err_code_t err) { ESP_LOGE(TAG, "========================================"); ESP_LOGE(TAG, "404 ERROR HANDLER CALLED"); ESP_LOGE(TAG, "URI: '%s'", req->uri); ESP_LOGE(TAG, "Method: %d", req->method); ESP_LOGE(TAG, "Error code: %d", err); ESP_LOGE(TAG, "========================================"); // For unmatched URIs (except API endpoints), serve the index page (SPA routing) if (strncmp(req->uri, "/api/", 5) != 0) { ESP_LOGI(TAG, "Serving index.html for URI: %s", req->uri); httpd_resp_set_type(req, "text/html"); esp_err_t ret = httpd_resp_send(req, index_html, strlen(index_html)); if (ret == ESP_OK) { ESP_LOGI(TAG, "✓ Successfully served index.html (%zu bytes)", strlen(index_html)); } else { ESP_LOGE(TAG, "✗ Failed to send index.html: %s", esp_err_to_name(ret)); } return ret; } // For API endpoints, return JSON error ESP_LOGE(TAG, "Returning JSON error for API endpoint: %s", req->uri); httpd_resp_set_type(req, "application/json"); httpd_resp_set_status(req, "404 Not Found"); httpd_resp_sendstr(req, "{\"status\":\"error\",\"message\":\"URI not found\"}"); return ESP_FAIL; } // Start the HTTP server (simplified based on working WiFi-Scanner) httpd_handle_t start_webserver(void) { httpd_config_t config = HTTPD_DEFAULT_CONFIG(); config.stack_size = 8192; // Larger stack for complex handlers config.max_uri_handlers = 35; // Increased for handshake capture endpoints httpd_handle_t server = NULL; ESP_LOGI(TAG, "========================================"); ESP_LOGI(TAG, "STARTING WEB SERVER"); ESP_LOGI(TAG, "Port: %d", config.server_port); ESP_LOGI(TAG, "Stack size: %d", config.stack_size); ESP_LOGI(TAG, "========================================"); if (httpd_start(&server, &config) == ESP_OK) { ESP_LOGI(TAG, "✓ HTTP server started successfully"); ESP_LOGI(TAG, "Registering URI handlers..."); esp_err_t result = register_uri_handlers(server); if (result == ESP_OK) { ESP_LOGI(TAG, "✓ All handlers registered successfully"); } else { ESP_LOGE(TAG, "✗ Handler registration failed!"); } // Register 404 error handler AFTER all URI handlers // This will catch any unmatched URIs and serve index.html for SPA routing httpd_register_err_handler(server, HTTPD_404_NOT_FOUND, http_404_error_handler); ESP_LOGI(TAG, "✓ 404 error handler registered (SPA fallback enabled)"); ESP_LOGI(TAG, "========================================"); ESP_LOGI(TAG, "WEB SERVER READY"); ESP_LOGI(TAG, "Access at: http://192.168.4.1"); ESP_LOGI(TAG, "========================================"); return server; } ESP_LOGE(TAG, "✗✗✗ FAILED TO START WEB SERVER ✗✗✗"); return NULL; } // Stop the HTTP server void stop_webserver(void) { if (server) { httpd_stop(server); server = NULL; } } // Init web server void init_web_server(void) { server = start_webserver(); if (server == NULL) { ESP_LOGE(TAG, "✗✗✗ CRITICAL: Web server failed to start! ✗✗✗"); ESP_LOGE(TAG, "The device will continue but web interface will NOT be available"); ESP_LOGE(TAG, "Possible causes:"); ESP_LOGE(TAG, " - WiFi not initialized"); ESP_LOGE(TAG, " - Insufficient memory"); ESP_LOGE(TAG, " - Port already in use"); return; } ESP_LOGI(TAG, "✓ Web server initialization complete"); ESP_LOGI(TAG, "✓ Server handle: %p", (void*)server); }