Files
c5-project/ESP32-C5-Toolkit/main/web_server.c
2026-05-03 23:19:51 -07:00

2715 lines
111 KiB
C

#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 <stdio.h>
#include <string.h>
#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[] =
"<!DOCTYPE html>\n"\
"<html lang=\"en\">\n"\
"<head>\n"\
" <meta charset=\"UTF-8\">\n"\
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"\
" <title>ESP32-C5 TOOLKIT</title>\n"\
" <script src=\"https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js\"></script>\n"\
" <style>\n"\
" @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Share+Tech+Mono&display=swap');\n"\
" * { margin: 0; padding: 0; box-sizing: border-box; }\n"\
" :root {\n"\
" --primary: #00ff41;\n"\
" --secondary: #00d4ff;\n"\
" --danger: #ff0040;\n"\
" --bg-dark: #0a0a0a;\n"\
" --bg-darker: #050505;\n"\
" --bg-panel: #111111;\n"\
" --border: #00ff41;\n"\
" --text: #00ff41;\n"\
" --text-dim: #00aa2a;\n"\
" --glow: 0 0 10px rgba(0, 255, 65, 0.5);\n"\
" }\n"\
" body {\n"\
" font-family: 'JetBrains Mono', 'Share Tech Mono', monospace;\n"\
" background: var(--bg-darker);\n"\
" color: var(--text);\n"\
" overflow-x: hidden;\n"\
" position: relative;\n"\
" }\n"\
" body::before {\n"\
" content: '';\n"\
" position: fixed;\n"\
" top: 0;\n"\
" left: 0;\n"\
" width: 100%;\n"\
" height: 100%;\n"\
" background: \n"\
" repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,255,65,0.03) 2px, rgba(0,255,65,0.03) 4px),\n"\
" radial-gradient(circle at 20% 50%, rgba(0,255,65,0.05) 0%, transparent 50%),\n"\
" radial-gradient(circle at 80% 50%, rgba(0,212,255,0.05) 0%, transparent 50%);\n"\
" pointer-events: none;\n"\
" z-index: 0;\n"\
" }\n"\
" .matrix-bg {\n"\
" position: fixed;\n"\
" top: 0;\n"\
" left: 0;\n"\
" width: 100%;\n"\
" height: 100%;\n"\
" opacity: 0.1;\n"\
" z-index: 0;\n"\
" pointer-events: none;\n"\
" }\n"\
" .container {\n"\
" display: grid;\n"\
" grid-template-columns: 280px 1fr;\n"\
" min-height: 100vh;\n"\
" position: relative;\n"\
" z-index: 1;\n"\
" }\n"\
" .sidebar {\n"\
" background: var(--bg-panel);\n"\
" border-right: 2px solid var(--border);\n"\
" box-shadow: var(--glow);\n"\
" padding: 20px 0;\n"\
" position: sticky;\n"\
" top: 0;\n"\
" height: 100vh;\n"\
" overflow-y: auto;\n"\
" }\n"\
" .sidebar-header {\n"\
" padding: 20px;\n"\
" border-bottom: 2px solid var(--border);\n"\
" margin-bottom: 20px;\n"\
" }\n"\
" .sidebar-header h1 {\n"\
" font-size: 1.4rem;\n"\
" letter-spacing: 3px;\n"\
" text-transform: uppercase;\n"\
" color: var(--primary);\n"\
" text-shadow: var(--glow);\n"\
" margin-bottom: 5px;\n"\
" }\n"\
" .sidebar-header .subtitle {\n"\
" font-size: 0.7rem;\n"\
" color: var(--text-dim);\n"\
" letter-spacing: 2px;\n"\
" }\n"\
" .nav-menu {\n"\
" list-style: none;\n"\
" }\n"\
" .nav-item {\n"\
" padding: 15px 25px;\n"\
" cursor: pointer;\n"\
" transition: all 0.3s;\n"\
" border-left: 3px solid transparent;\n"\
" position: relative;\n"\
" font-size: 0.9rem;\n"\
" letter-spacing: 1px;\n"\
" }\n"\
" .nav-item::before {\n"\
" content: '> ';\n"\
" opacity: 0;\n"\
" transition: opacity 0.3s;\n"\
" color: var(--primary);\n"\
" }\n"\
" .nav-item:hover, .nav-item.active {\n"\
" background: rgba(0, 255, 65, 0.1);\n"\
" border-left-color: var(--primary);\n"\
" transform: translateX(5px);\n"\
" }\n"\
" .nav-item.active::before, .nav-item:hover::before {\n"\
" opacity: 1;\n"\
" }\n"\
" .content-area {\n"\
" padding: 30px;\n"\
" background: var(--bg-dark);\n"\
" position: relative;\n"\
" }\n"\
" .page {\n"\
" display: none;\n"\
" animation: fadeIn 0.3s;\n"\
" }\n"\
" .page.active {\n"\
" display: block;\n"\
" }\n"\
" @keyframes fadeIn {\n"\
" from { opacity: 0; transform: translateY(10px); }\n"\
" to { opacity: 1; transform: translateY(0); }\n"\
" }\n"\
" .page-header {\n"\
" margin-bottom: 30px;\n"\
" padding-bottom: 15px;\n"\
" border-bottom: 2px solid var(--border);\n"\
" }\n"\
" .page-header h2 {\n"\
" font-size: 1.8rem;\n"\
" letter-spacing: 4px;\n"\
" text-transform: uppercase;\n"\
" color: var(--primary);\n"\
" text-shadow: var(--glow);\n"\
" }\n"\
" .panel {\n"\
" background: var(--bg-panel);\n"\
" border: 1px solid var(--border);\n"\
" border-radius: 0;\n"\
" padding: 25px;\n"\
" margin-bottom: 20px;\n"\
" box-shadow: 0 0 20px rgba(0, 255, 65, 0.1);\n"\
" }\n"\
" .panel-header {\n"\
" font-size: 1.1rem;\n"\
" letter-spacing: 2px;\n"\
" margin-bottom: 20px;\n"\
" color: var(--secondary);\n"\
" text-transform: uppercase;\n"\
" border-bottom: 1px solid var(--border);\n"\
" padding-bottom: 10px;\n"\
" }\n"\
" .btn {\n"\
" background: transparent;\n"\
" border: 2px solid var(--border);\n"\
" color: var(--text);\n"\
" padding: 12px 24px;\n"\
" font-family: inherit;\n"\
" font-size: 0.9rem;\n"\
" letter-spacing: 2px;\n"\
" text-transform: uppercase;\n"\
" cursor: pointer;\n"\
" transition: all 0.3s;\n"\
" position: relative;\n"\
" overflow: hidden;\n"\
" }\n"\
" .btn::before {\n"\
" content: '';\n"\
" position: absolute;\n"\
" top: 0;\n"\
" left: -100%;\n"\
" width: 100%;\n"\
" height: 100%;\n"\
" background: var(--primary);\n"\
" transition: left 0.3s;\n"\
" z-index: -1;\n"\
" }\n"\
" .btn:hover::before {\n"\
" left: 0;\n"\
" }\n"\
" .btn:hover {\n"\
" color: var(--bg-dark);\n"\
" box-shadow: var(--glow);\n"\
" }\n"\
" .btn:disabled {\n"\
" opacity: 0.5;\n"\
" cursor: not-allowed;\n"\
" }\n"\
" .btn-danger {\n"\
" border-color: var(--danger);\n"\
" color: var(--danger);\n"\
" }\n"\
" .btn-danger::before {\n"\
" background: var(--danger);\n"\
" }\n"\
" .btn-danger:hover {\n"\
" color: var(--bg-dark);\n"\
" }\n"\
" .terminal {\n"\
" background: var(--bg-darker);\n"\
" border: 1px solid var(--border);\n"\
" padding: 20px;\n"\
" font-family: 'JetBrains Mono', monospace;\n"\
" font-size: 0.85rem;\n"\
" color: var(--text);\n"\
" min-height: 300px;\n"\
" max-height: 500px;\n"\
" overflow-y: auto;\n"\
" line-height: 1.6;\n"\
" }\n"\
" .terminal-line {\n"\
" margin-bottom: 5px;\n"\
" }\n"\
" .terminal-prompt {\n"\
" color: var(--secondary);\n"\
" }\n"\
" .terminal-output {\n"\
" color: var(--text);\n"\
" }\n"\
" .terminal-error {\n"\
" color: var(--danger);\n"\
" }\n"\
" .graph-container {\n"\
" background: var(--bg-panel);\n"\
" border: 1px solid var(--border);\n"\
" padding: 20px;\n"\
" margin: 20px 0;\n"\
" position: relative;\n"\
" }\n"\
" .graph-container canvas {\n"\
" max-height: 400px;\n"\
" }\n"\
" .stats-grid {\n"\
" display: grid;\n"\
" grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n"\
" gap: 20px;\n"\
" margin: 20px 0;\n"\
" }\n"\
" .stat-card {\n"\
" background: var(--bg-panel);\n"\
" border: 1px solid var(--border);\n"\
" padding: 20px;\n"\
" text-align: center;\n"\
" }\n"\
" .stat-value {\n"\
" font-size: 2rem;\n"\
" color: var(--primary);\n"\
" text-shadow: var(--glow);\n"\
" margin: 10px 0;\n"\
" }\n"\
" .stat-label {\n"\
" font-size: 0.8rem;\n"\
" color: var(--text-dim);\n"\
" text-transform: uppercase;\n"\
" letter-spacing: 2px;\n"\
" }\n"\
" table {\n"\
" width: 100%;\n"\
" border-collapse: collapse;\n"\
" margin: 20px 0;\n"\
" font-size: 0.85rem;\n"\
" }\n"\
" table th {\n"\
" background: var(--bg-panel);\n"\
" border: 1px solid var(--border);\n"\
" padding: 12px;\n"\
" text-align: left;\n"\
" text-transform: uppercase;\n"\
" letter-spacing: 1px;\n"\
" color: var(--primary);\n"\
" }\n"\
" table td {\n"\
" border: 1px solid var(--border);\n"\
" padding: 10px 12px;\n"\
" background: var(--bg-darker);\n"\
" }\n"\
" table tr:hover td {\n"\
" background: rgba(0, 255, 65, 0.1);\n"\
" }\n"\
" .form-group {\n"\
" margin-bottom: 20px;\n"\
" }\n"\
" .form-label {\n"\
" display: block;\n"\
" margin-bottom: 8px;\n"\
" color: var(--text);\n"\
" text-transform: uppercase;\n"\
" letter-spacing: 1px;\n"\
" font-size: 0.85rem;\n"\
" }\n"\
" .form-control {\n"\
" width: 100%;\n"\
" background: var(--bg-darker);\n"\
" border: 1px solid var(--border);\n"\
" color: var(--text);\n"\
" padding: 10px;\n"\
" font-family: inherit;\n"\
" font-size: 0.9rem;\n"\
" }\n"\
" .form-control:focus {\n"\
" outline: none;\n"\
" border-color: var(--primary);\n"\
" box-shadow: 0 0 10px rgba(0, 255, 65, 0.3);\n"\
" }\n"\
" .status-indicator {\n"\
" display: inline-block;\n"\
" width: 10px;\n"\
" height: 10px;\n"\
" border-radius: 50%;\n"\
" margin-right: 8px;\n"\
" animation: pulse 2s infinite;\n"\
" }\n"\
" .status-indicator.active {\n"\
" background: var(--primary);\n"\
" box-shadow: 0 0 10px var(--primary);\n"\
" }\n"\
" .status-indicator.inactive {\n"\
" background: var(--text-dim);\n"\
" }\n"\
" @keyframes pulse {\n"\
" 0%, 100% { opacity: 1; }\n"\
" 50% { opacity: 0.5; }\n"\
" }\n"\
" .warning-box {\n"\
" background: rgba(255, 0, 64, 0.1);\n"\
" border: 2px solid var(--danger);\n"\
" padding: 15px;\n"\
" margin: 20px 0;\n"\
" color: var(--danger);\n"\
" text-transform: uppercase;\n"\
" letter-spacing: 1px;\n"\
" font-size: 0.85rem;\n"\
" }\n"\
" .channel-map {\n"\
" display: grid;\n"\
" grid-template-columns: repeat(13, 1fr);\n"\
" gap: 5px;\n"\
" margin: 20px 0;\n"\
" }\n"\
" .channel-block {\n"\
" background: var(--bg-panel);\n"\
" border: 1px solid var(--border);\n"\
" padding: 10px;\n"\
" text-align: center;\n"\
" font-size: 0.8rem;\n"\
" position: relative;\n"\
" }\n"\
" .channel-block.active {\n"\
" background: var(--primary);\n"\
" color: var(--bg-dark);\n"\
" box-shadow: var(--glow);\n"\
" }\n"\
" .channel-block.occupied {\n"\
" border-color: var(--secondary);\n"\
" }\n"\
" .signal-bar {\n"\
" height: 4px;\n"\
" background: var(--bg-darker);\n"\
" margin-top: 5px;\n"\
" position: relative;\n"\
" overflow: hidden;\n"\
" }\n"\
" .signal-bar-fill {\n"\
" height: 100%;\n"\
" background: linear-gradient(90deg, var(--danger), var(--primary));\n"\
" transition: width 0.3s;\n"\
" }\n"\
" @media (max-width: 768px) {\n"\
" .container {\n"\
" grid-template-columns: 1fr;\n"\
" }\n"\
" .sidebar {\n"\
" position: relative;\n"\
" height: auto;\n"\
" }\n"\
" }\n"\
" </style>\n"\
"</head>\n"\
"<body>\n"\
" <div class=\"container\">\n"\
" <div class=\"sidebar\">\n"\
" <div class=\"sidebar-header\">\n"\
" <h1>ESP32-C5</h1>\n"\
" <div class=\"subtitle\">TOOLKIT v2.0</div>\n"\
" </div>\n"\
" <ul class=\"nav-menu\" id=\"nav\">\n"\
" <li class=\"nav-item active\" data-page=\"dashboard\">DASHBOARD</li>\n"\
" <li class=\"nav-item\" data-page=\"wifi-scan\">WIFI SCANNER</li>\n"\
" <li class=\"nav-item\" data-page=\"handshake\">HANDSHAKE CAPTURE</li>\n"\
" <li class=\"nav-item\" data-page=\"packet-sniff\">PACKET SNIFFER</li>\n"\
" <li class=\"nav-item\" data-page=\"bt-scan\">BLUETOOTH SCANNER</li>\n"\
" <li class=\"nav-item\" data-page=\"deauth\">DEAUTH ENGINE</li>\n"\
" <li class=\"nav-item\" data-page=\"signal-analysis\">SIGNAL ANALYSIS</li>\n"\
" <li class=\"nav-item\" data-page=\"channel-map\">CHANNEL MAP</li>\n"\
" <li class=\"nav-item\" data-page=\"settings\">SYSTEM CONFIG</li>\n"\
" </ul>\n"\
" </div>\n"\
" <div class=\"content-area\">\n"\
" <!-- Dashboard -->\n"\
" <div id=\"dashboard\" class=\"page active\">\n"\
" <div class=\"page-header\">\n"\
" <h2>SYSTEM DASHBOARD</h2>\n"\
" </div>\n"\
" <div class=\"stats-grid\">\n"\
" <div class=\"stat-card\">\n"\
" <div class=\"stat-label\">UPTIME</div>\n"\
" <div class=\"stat-value\" id=\"uptime\">00:00:00</div>\n"\
" </div>\n"\
" <div class=\"stat-card\">\n"\
" <div class=\"stat-label\">FREE HEAP</div>\n"\
" <div class=\"stat-value\" id=\"free-heap\">0 KB</div>\n"\
" </div>\n"\
" <div class=\"stat-card\">\n"\
" <div class=\"stat-label\">NETWORKS FOUND</div>\n"\
" <div class=\"stat-value\" id=\"networks-count\">0</div>\n"\
" </div>\n"\
" <div class=\"stat-card\">\n"\
" <div class=\"stat-label\">PACKETS CAPTURED</div>\n"\
" <div class=\"stat-value\" id=\"packets-count\">0</div>\n"\
" </div>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">SYSTEM INFORMATION</div>\n"\
" <div id=\"system-info\" class=\"terminal\"></div>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">NETWORK ACTIVITY</div>\n"\
" <div class=\"graph-container\">\n"\
" <canvas id=\"network-activity-chart\"></canvas>\n"\
" </div>\n"\
" </div>\n"\
" </div>\n"\
" \n"\
" <!-- WiFi Scanner -->\n"\
" <div id=\"wifi-scan\" class=\"page\">\n"\
" <div class=\"page-header\">\n"\
" <h2>WIFI NETWORK SCANNER</h2>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">SCAN CONTROLS</div>\n"\
" <button class=\"btn\" id=\"scan-btn\">INITIATE SCAN</button>\n"\
" <button class=\"btn\" id=\"auto-scan-btn\">AUTO SCAN</button>\n"\
" <span class=\"status-indicator inactive\" id=\"scan-status\"></span>\n"\
" <span id=\"scan-status-text\">READY</span>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">SIGNAL STRENGTH DISTRIBUTION</div>\n"\
" <div class=\"graph-container\">\n"\
" <canvas id=\"rssi-chart\"></canvas>\n"\
" </div>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">DETECTED NETWORKS</div>\n"\
" <div id=\"scan-results\">\n"\
" <table id=\"networks-table\">\n"\
" <thead>\n"\
" <tr>\n"\
" <th>SSID</th>\n"\
" <th>BSSID</th>\n"\
" <th>BAND</th>\n"\
" <th>CH</th>\n"\
" <th>RSSI</th>\n"\
" <th>SECURITY</th>\n"\
" <th>SIGNAL</th>\n"\
" </tr>\n"\
" </thead>\n"\
" <tbody></tbody>\n"\
" </table>\n"\
" </div>\n"\
" </div>\n"\
" </div>\n"\
" \n"\
" <!-- Handshake Capture -->\n"\
" <div id=\"handshake\" class=\"page\">\n"\
" <div class=\"page-header\">\n"\
" <h2>WPA/WPA2 HANDSHAKE CAPTURE</h2>\n"\
" </div>\n"\
" <div class=\"warning-box\">\n"\
" WARNING: USE ONLY ON NETWORKS YOU OWN OR HAVE AUTHORIZATION TO TEST.\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">TARGET SELECTION</div>\n"\
" <button class=\"btn\" id=\"hs-scan-btn\">SCAN NETWORKS</button>\n"\
" <div id=\"hs-targets\" style=\"margin-top:15px;\"></div>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">CAPTURE CONFIGURATION</div>\n"\
" <div class=\"form-group\">\n"\
" <label class=\"form-label\">CAPTURE METHOD</label>\n"\
" <select class=\"form-control\" id=\"hs-method\">\n"\
" <option value=\"deauth\">DEAUTH + CAPTURE (FAST)</option>\n"\
" <option value=\"passive\">PASSIVE SNIFFING (SLOW)</option>\n"\
" </select>\n"\
" </div>\n"\
" <div class=\"form-group\">\n"\
" <label class=\"form-label\">TIMEOUT (SECONDS)</label>\n"\
" <input type=\"number\" class=\"form-control\" id=\"hs-timeout\" value=\"120\" min=\"30\" max=\"600\">\n"\
" </div>\n"\
" <button class=\"btn\" id=\"hs-start-btn\">START CAPTURE</button>\n"\
" <button class=\"btn btn-danger\" id=\"hs-stop-btn\" disabled>STOP CAPTURE</button>\n"\
" <button class=\"btn\" id=\"hs-reset-btn\">RESET</button>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">CAPTURE STATUS</div>\n"\
" <div class=\"stats-grid\">\n"\
" <div class=\"stat-card\">\n"\
" <div class=\"stat-label\">STATUS</div>\n"\
" <div class=\"stat-value\" id=\"hs-state\">IDLE</div>\n"\
" </div>\n"\
" <div class=\"stat-card\">\n"\
" <div class=\"stat-label\">HANDSHAKE</div>\n"\
" <div class=\"stat-value\" id=\"hs-handshake-state\">NONE</div>\n"\
" </div>\n"\
" <div class=\"stat-card\">\n"\
" <div class=\"stat-label\">PACKETS</div>\n"\
" <div class=\"stat-value\" id=\"hs-packets\">0</div>\n"\
" </div>\n"\
" <div class=\"stat-card\">\n"\
" <div class=\"stat-label\">DEAUTH SENT</div>\n"\
" <div class=\"stat-value\" id=\"hs-deauth\">0</div>\n"\
" </div>\n"\
" </div>\n"\
" <div class=\"terminal\" id=\"hs-log\" style=\"margin-top:15px;min-height:150px;\"></div>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">DOWNLOAD CAPTURES</div>\n"\
" <p style=\"color:#00ff41;margin-bottom:15px;\">Download captured handshake for offline cracking with Hashcat or Wireshark analysis.</p>\n"\
" <a class=\"btn\" id=\"download-pcap\" href=\"/capture.pcap\" target=\"_blank\" style=\"margin-right:10px;\">DOWNLOAD PCAP</a>\n"\
" <a class=\"btn\" id=\"download-hccapx\" href=\"/capture.hccapx\" target=\"_blank\">DOWNLOAD HCCAPX (HASHCAT)</a>\n"\
" </div>\n"\
" </div>\n"\
" \n"\
" <!-- Packet Sniffer -->\n"\
" <div id=\"packet-sniff\" class=\"page\">\n"\
" <div class=\"page-header\">\n"\
" <h2>PACKET SNIFFER</h2>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">CAPTURE CONFIGURATION</div>\n"\
" <div class=\"form-group\">\n"\
" <label class=\"form-label\">CHANNEL</label>\n"\
" <select class=\"form-control\" id=\"sniff-channel\">\n"\
" <option value=\"0\">ALL CHANNELS (HOPPING)</option>\n"\
" <option value=\"1\">1</option>\n"\
" <option value=\"2\">2</option>\n"\
" <option value=\"3\">3</option>\n"\
" <option value=\"4\">4</option>\n"\
" <option value=\"5\">5</option>\n"\
" <option value=\"6\">6</option>\n"\
" <option value=\"7\">7</option>\n"\
" <option value=\"8\">8</option>\n"\
" <option value=\"9\">9</option>\n"\
" <option value=\"10\">10</option>\n"\
" <option value=\"11\">11</option>\n"\
" <option value=\"12\">12</option>\n"\
" <option value=\"13\">13</option>\n"\
" </select>\n"\
" </div>\n"\
" <div class=\"form-group\">\n"\
" <label class=\"form-label\">FILTER TYPE</label>\n"\
" <select class=\"form-control\" id=\"packet-filter\">\n"\
" <option value=\"all\">ALL PACKETS</option>\n"\
" <option value=\"management\">MANAGEMENT FRAMES</option>\n"\
" <option value=\"data\">DATA FRAMES</option>\n"\
" <option value=\"control\">CONTROL FRAMES</option>\n"\
" <option value=\"beacon\">BEACON FRAMES</option>\n"\
" <option value=\"probe\">PROBE REQUESTS/RESPONSES</option>\n"\
" </select>\n"\
" </div>\n"\
" <button class=\"btn\" id=\"start-sniff\">START CAPTURE</button>\n"\
" <button class=\"btn\" id=\"stop-sniff\" disabled>STOP CAPTURE</button>\n"\
" <button class=\"btn\" id=\"clear-packets\">CLEAR LOG</button>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">PACKET FLOW</div>\n"\
" <div class=\"graph-container\">\n"\
" <canvas id=\"packet-flow-chart\"></canvas>\n"\
" </div>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">CAPTURED PACKETS</div>\n"\
" <div class=\"terminal\" id=\"packet-log\"></div>\n"\
" </div>\n"\
" </div>\n"\
" \n"\
" <!-- Bluetooth Scanner -->\n"\
" <div id=\"bt-scan\" class=\"page\">\n"\
" <div class=\"page-header\">\n"\
" <h2>BLUETOOTH SCANNER</h2>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">SCAN CONTROLS</div>\n"\
" <button class=\"btn\" id=\"bt-scan-btn\">START BT SCAN</button>\n"\
" <button class=\"btn\" id=\"bt-scan-stop-btn\" disabled>STOP SCAN</button>\n"\
" <span class=\"status-indicator inactive\" id=\"bt-scan-status\"></span>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">BLUETOOTH DEVICES</div>\n"\
" <table id=\"bt-devices-table\">\n"\
" <thead>\n"\
" <tr>\n"\
" <th>NAME</th>\n"\
" <th>ADDRESS</th>\n"\
" <th>RSSI</th>\n"\
" <th>TYPE</th>\n"\
" <th>ACTIONS</th>\n"\
" </tr>\n"\
" </thead>\n"\
" <tbody></tbody>\n"\
" </table>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">BLUETOOTH JAMMING</div>\n"\
" <div class=\"warning-box\">\n"\
" WARNING: USE ONLY ON YOUR OWN DEVICES. UNAUTHORIZED JAMMING IS ILLEGAL.\n"\
" </div>\n"\
" <div class=\"form-group\">\n"\
" <label class=\"form-label\">TARGET ADDRESS (OPTIONAL)</label>\n"\
" <input type=\"text\" class=\"form-control\" id=\"bt-jam-target\" placeholder=\"AA:BB:CC:DD:EE:FF\">\n"\
" </div>\n"\
" <div class=\"form-group\">\n"\
" <label class=\"form-label\">DURATION (SECONDS)</label>\n"\
" <input type=\"number\" class=\"form-control\" id=\"bt-jam-duration\" value=\"30\" min=\"10\" max=\"300\">\n"\
" </div>\n"\
" <button class=\"btn btn-danger\" id=\"bt-jam-start\">START JAMMING</button>\n"\
" <button class=\"btn btn-danger\" id=\"bt-jam-stop\" disabled>STOP JAMMING</button>\n"\
" </div>\n"\
" </div>\n"\
" \n"\
" <!-- Deauth Engine -->\n"\
" <div id=\"deauth\" class=\"page\">\n"\
" <div class=\"page-header\">\n"\
" <h2>DEAUTH ENGINE</h2>\n"\
" </div>\n"\
" <div class=\"warning-box\">\n"\
" WARNING: USE ONLY ON NETWORKS YOU OWN. UNAUTHORIZED USE IS ILLEGAL.\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">TARGET SELECTION</div>\n"\
" <button class=\"btn\" id=\"deauth-scan-btn\">SCAN NETWORKS</button>\n"\
" <div id=\"deauth-targets\"></div>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">ATTACK CONFIGURATION</div>\n"\
" <div class=\"form-group\">\n"\
" <label class=\"form-label\">DURATION (SECONDS)</label>\n"\
" <input type=\"number\" class=\"form-control\" id=\"deauth-duration\" value=\"30\" min=\"10\" max=\"600\">\n"\
" </div>\n"\
" <button class=\"btn btn-danger\" id=\"deauth-start-btn\">START ATTACK</button>\n"\
" <button class=\"btn btn-danger\" id=\"deauth-stop-btn\" disabled>STOP ATTACK</button>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">ATTACK STATISTICS</div>\n"\
" <div class=\"stats-grid\">\n"\
" <div class=\"stat-card\">\n"\
" <div class=\"stat-label\">TOTAL PACKETS</div>\n"\
" <div class=\"stat-value\" id=\"deauth-total\">0</div>\n"\
" </div>\n"\
" <div class=\"stat-card\">\n"\
" <div class=\"stat-label\">2.4GHZ PACKETS</div>\n"\
" <div class=\"stat-value\" id=\"deauth-24\">0</div>\n"\
" </div>\n"\
" <div class=\"stat-card\">\n"\
" <div class=\"stat-label\">5GHZ PACKETS</div>\n"\
" <div class=\"stat-value\" id=\"deauth-5\">0</div>\n"\
" </div>\n"\
" <div class=\"stat-card\">\n"\
" <div class=\"stat-label\">ELAPSED TIME</div>\n"\
" <div class=\"stat-value\" id=\"deauth-time\">0s</div>\n"\
" </div>\n"\
" </div>\n"\
" </div>\n"\
" </div>\n"\
" \n"\
" <!-- Signal Analysis -->\n"\
" <div id=\"signal-analysis\" class=\"page\">\n"\
" <div class=\"page-header\">\n"\
" <h2>SIGNAL ANALYSIS</h2>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">SIGNAL STRENGTH OVER TIME</div>\n"\
" <div class=\"graph-container\">\n"\
" <canvas id=\"signal-time-chart\"></canvas>\n"\
" </div>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">CHANNEL UTILIZATION</div>\n"\
" <div class=\"graph-container\">\n"\
" <canvas id=\"channel-util-chart\"></canvas>\n"\
" </div>\n"\
" </div>\n"\
" </div>\n"\
" \n"\
" <!-- Channel Map -->\n"\
" <div id=\"channel-map\" class=\"page\">\n"\
" <div class=\"page-header\">\n"\
" <h2>CHANNEL MAPPING</h2>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">2.4GHZ BAND</div>\n"\
" <div class=\"channel-map\" id=\"channel-map-24\"></div>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">5GHZ BAND</div>\n"\
" <div class=\"channel-map\" id=\"channel-map-5\"></div>\n"\
" </div>\n"\
" </div>\n"\
" \n"\
" <!-- Settings -->\n"\
" <div id=\"settings\" class=\"page\">\n"\
" <div class=\"page-header\">\n"\
" <h2>SYSTEM CONFIGURATION</h2>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">DEVICE SETTINGS</div>\n"\
" <div class=\"form-group\">\n"\
" <label class=\"form-label\">ANTENNA SELECTION</label>\n"\
" <select class=\"form-control\" id=\"antenna-select\">\n"\
" <option value=\"internal\">INTERNAL</option>\n"\
" <option value=\"external\">EXTERNAL</option>\n"\
" </select>\n"\
" </div>\n"\
" <button class=\"btn\" id=\"save-settings\">SAVE SETTINGS</button>\n"\
" </div>\n"\
" <div class=\"panel\">\n"\
" <div class=\"panel-header\">SYSTEM CONTROL</div>\n"\
" <button class=\"btn btn-danger\" id=\"reboot-device\">REBOOT DEVICE</button>\n"\
" </div>\n"\
" </div>\n"\
" </div>\n"\
" </div>\n"\
" \n"\
" <script>\n"\
" // Chart.js configurations and data\n"\
" const charts = {};\n"\
" let networkActivityData = { labels: [], datasets: [{ label: 'Networks', data: [], borderColor: '#00ff41', backgroundColor: 'rgba(0,255,65,0.1)' }] };\n"\
" let rssiData = { labels: [], datasets: [{ label: 'RSSI Distribution', data: [], backgroundColor: '#00ff41' }] };\n"\
" let packetFlowData = { labels: [], datasets: [{ label: 'Packets/sec', data: [], borderColor: '#00d4ff' }] };\n"\
" let signalTimeData = { labels: [], datasets: [] };\n"\
" let channelUtilData = { labels: [], datasets: [{ label: 'Utilization %', data: [], backgroundColor: '#00ff41' }] };\n"\
" \n"\
" // Initialize charts\n"\
" function initCharts() {\n"\
" charts.networkActivity = new Chart(document.getElementById('network-activity-chart'), {\n"\
" type: 'line',\n"\
" data: networkActivityData,\n"\
" options: {\n"\
" responsive: true,\n"\
" maintainAspectRatio: false,\n"\
" plugins: {\n"\
" legend: { labels: { color: '#00ff41' } }\n"\
" },\n"\
" scales: {\n"\
" x: { ticks: { color: '#00ff41' }, grid: { color: 'rgba(0,255,65,0.1)' } },\n"\
" y: { ticks: { color: '#00ff41' }, grid: { color: 'rgba(0,255,65,0.1)' } }\n"\
" }\n"\
" }\n"\
" });\n"\
" \n"\
" charts.rssi = new Chart(document.getElementById('rssi-chart'), {\n"\
" type: 'bar',\n"\
" data: rssiData,\n"\
" options: {\n"\
" responsive: true,\n"\
" maintainAspectRatio: false,\n"\
" plugins: {\n"\
" legend: { labels: { color: '#00ff41' } }\n"\
" },\n"\
" scales: {\n"\
" x: { ticks: { color: '#00ff41' }, grid: { color: 'rgba(0,255,65,0.1)' } },\n"\
" y: { ticks: { color: '#00ff41' }, grid: { color: 'rgba(0,255,65,0.1)' } }\n"\
" }\n"\
" }\n"\
" });\n"\
" \n"\
" charts.packetFlow = new Chart(document.getElementById('packet-flow-chart'), {\n"\
" type: 'line',\n"\
" data: packetFlowData,\n"\
" options: {\n"\
" responsive: true,\n"\
" maintainAspectRatio: false,\n"\
" plugins: {\n"\
" legend: { labels: { color: '#00d4ff' } }\n"\
" },\n"\
" scales: {\n"\
" x: { ticks: { color: '#00d4ff' }, grid: { color: 'rgba(0,212,255,0.1)' } },\n"\
" y: { ticks: { color: '#00d4ff' }, grid: { color: 'rgba(0,212,255,0.1)' } }\n"\
" }\n"\
" }\n"\
" });\n"\
" \n"\
" charts.signalTime = new Chart(document.getElementById('signal-time-chart'), {\n"\
" type: 'line',\n"\
" data: signalTimeData,\n"\
" options: {\n"\
" responsive: true,\n"\
" maintainAspectRatio: false,\n"\
" plugins: {\n"\
" legend: { labels: { color: '#00ff41' } }\n"\
" },\n"\
" scales: {\n"\
" x: { ticks: { color: '#00ff41' }, grid: { color: 'rgba(0,255,65,0.1)' } },\n"\
" y: { ticks: { color: '#00ff41' }, grid: { color: 'rgba(0,255,65,0.1)' } }\n"\
" }\n"\
" }\n"\
" });\n"\
" \n"\
" charts.channelUtil = new Chart(document.getElementById('channel-util-chart'), {\n"\
" type: 'bar',\n"\
" data: channelUtilData,\n"\
" options: {\n"\
" responsive: true,\n"\
" maintainAspectRatio: false,\n"\
" plugins: {\n"\
" legend: { labels: { color: '#00ff41' } }\n"\
" },\n"\
" scales: {\n"\
" x: { ticks: { color: '#00ff41' }, grid: { color: 'rgba(0,255,65,0.1)' } },\n"\
" y: { ticks: { color: '#00ff41' }, grid: { color: 'rgba(0,255,65,0.1)' }, max: 100 }\n"\
" }\n"\
" }\n"\
" });\n"\
" }\n"\
" \n"\
" // Navigation\n"\
" document.querySelectorAll('.nav-item').forEach(item => {\n"\
" item.addEventListener('click', () => {\n"\
" document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));\n"\
" item.classList.add('active');\n"\
" document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));\n"\
" document.getElementById(item.getAttribute('data-page')).classList.add('active');\n"\
" });\n"\
" });\n"\
" \n"\
" // State variables\n"\
" let selected24ghz = null;\n"\
" let selected5ghz = null;\n"\
" let autoScanInterval = null;\n"\
" let packetCount = 0;\n"\
" let startTime = Date.now();\n"\
" let sniffing = false;\n"\
" let sniffInterval = null;\n"\
" let btScanInterval = null;\n"\
" let deauthStatsInterval = null;\n"\
" \n"\
" function updateDashboard() {\n"\
" fetch('/api/system-info')\n"\
" .then(r => r.json())\n"\
" .then(data => {\n"\
" document.getElementById('free-heap').textContent = formatBytes(data.free_heap);\n"\
" const uptime = Math.floor((Date.now() - startTime) / 1000);\n"\
" document.getElementById('uptime').textContent = formatTime(uptime);\n"\
" \n"\
" const sysInfo = document.getElementById('system-info');\n"\
" sysInfo.innerHTML = `\n"\
" <div class=\"terminal-line\"><span class=\"terminal-prompt\">root@esp32-c5:</span> <span class=\"terminal-output\">IDF Version: ${data.idf_version}</span></div>\n"\
" <div class=\"terminal-line\"><span class=\"terminal-prompt\">root@esp32-c5:</span> <span class=\"terminal-output\">Chip Model: ${data.chip_model}</span></div>\n"\
" <div class=\"terminal-line\"><span class=\"terminal-prompt\">root@esp32-c5:</span> <span class=\"terminal-output\">Cores: ${data.chip_cores}</span></div>\n"\
" <div class=\"terminal-line\"><span class=\"terminal-prompt\">root@esp32-c5:</span> <span class=\"terminal-output\">MAC: ${data.mac_address}</span></div>\n"\
" <div class=\"terminal-line\"><span class=\"terminal-prompt\">root@esp32-c5:</span> <span class=\"terminal-output\">Features: ${data.features}</span></div>\n"\
" `;\n"\
" }).catch(e => console.log('Dashboard update failed:', e));\n"\
" }\n"\
" \n"\
" function formatBytes(bytes) {\n"\
" if (bytes < 1024) return bytes + ' B';\n"\
" if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';\n"\
" return (bytes / 1048576).toFixed(2) + ' MB';\n"\
" }\n"\
" \n"\
" function formatTime(seconds) {\n"\
" const h = Math.floor(seconds / 3600);\n"\
" const m = Math.floor((seconds % 3600) / 60);\n"\
" const s = seconds % 60;\n"\
" return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;\n"\
" }\n"\
" \n"\
" // WiFi Scanner\n"\
" document.getElementById('scan-btn')?.addEventListener('click', function() {\n"\
" this.disabled = true;\n"\
" const status = document.getElementById('scan-status');\n"\
" status.className = 'status-indicator active';\n"\
" document.getElementById('scan-status-text').textContent = 'SCANNING...';\n"\
" \n"\
" fetch('/api/scan')\n"\
" .then(r => r.json())\n"\
" .then(data => {\n"\
" if (data.status === 'success') {\n"\
" displayNetworks(data.networks);\n"\
" updateRSSIChart(data.networks);\n"\
" document.getElementById('networks-count').textContent = data.networks.length;\n"\
" }\n"\
" this.disabled = false;\n"\
" status.className = 'status-indicator inactive';\n"\
" document.getElementById('scan-status-text').textContent = 'READY';\n"\
" }).catch(e => { this.disabled = false; console.log('Scan failed:', e); });\n"\
" });\n"\
" \n"\
" function displayNetworks(networks) {\n"\
" const tbody = document.querySelector('#networks-table tbody');\n"\
" tbody.innerHTML = '';\n"\
" networks.forEach(net => {\n"\
" const row = document.createElement('tr');\n"\
" const rssiPercent = Math.min(100, Math.max(0, (net.rssi + 100) * 2));\n"\
" row.innerHTML = `\n"\
" <td>${net.ssid || '(HIDDEN)'}</td>\n"\
" <td><code>${net.bssid}</code></td>\n"\
" <td>${net.band}</td>\n"\
" <td>${net.channel}</td>\n"\
" <td>${net.rssi} dBm</td>\n"\
" <td>${net.security}</td>\n"\
" <td>\n"\
" <div class=\"signal-bar\">\n"\
" <div class=\"signal-bar-fill\" style=\"width: ${rssiPercent}%\"></div>\n"\
" </div>\n"\
" </td>\n"\
" `;\n"\
" tbody.appendChild(row);\n"\
" });\n"\
" }\n"\
" \n"\
" function updateRSSIChart(networks) {\n"\
" const rssiRanges = { '-90 to -80': 0, '-80 to -70': 0, '-70 to -60': 0, '-60 to -50': 0, '-50 to -40': 0, '-40+': 0 };\n"\
" networks.forEach(net => {\n"\
" if (net.rssi < -90) rssiRanges['-90 to -80']++;\n"\
" else if (net.rssi < -80) rssiRanges['-80 to -70']++;\n"\
" else if (net.rssi < -70) rssiRanges['-70 to -60']++;\n"\
" else if (net.rssi < -60) rssiRanges['-60 to -50']++;\n"\
" else if (net.rssi < -50) rssiRanges['-50 to -40']++;\n"\
" else rssiRanges['-40+']++;\n"\
" });\n"\
" \n"\
" rssiData.labels = Object.keys(rssiRanges);\n"\
" rssiData.datasets[0].data = Object.values(rssiRanges);\n"\
" if (charts.rssi) charts.rssi.update();\n"\
" }\n"\
" \n"\
" // Packet Sniffer\n"\
" document.getElementById('start-sniff')?.addEventListener('click', function() {\n"\
" const channel = document.getElementById('sniff-channel').value;\n"\
" const filter = document.getElementById('packet-filter').value;\n"\
" \n"\
" fetch(`/api/sniff/start?channel=${channel}&filter=${filter}`)\n"\
" .then(r => r.json())\n"\
" .then(data => {\n"\
" if (data.status === 'success') {\n"\
" sniffing = true;\n"\
" this.disabled = true;\n"\
" document.getElementById('stop-sniff').disabled = false;\n"\
" if (sniffInterval) clearInterval(sniffInterval);\n"\
" sniffInterval = setInterval(fetchPackets, 1000);\n"\
" }\n"\
" }).catch(e => console.log('Sniff start failed:', e));\n"\
" });\n"\
" \n"\
" document.getElementById('stop-sniff')?.addEventListener('click', function() {\n"\
" fetch('/api/sniff/stop')\n"\
" .then(r => r.json())\n"\
" .then(data => {\n"\
" sniffing = false;\n"\
" this.disabled = true;\n"\
" document.getElementById('start-sniff').disabled = false;\n"\
" if (sniffInterval) {\n"\
" clearInterval(sniffInterval);\n"\
" sniffInterval = null;\n"\
" }\n"\
" }).catch(e => console.log('Sniff stop failed:', e));\n"\
" });\n"\
" \n"\
" function fetchPackets() {\n"\
" if (!sniffing) return;\n"\
" fetch('/api/sniff/packets')\n"\
" .then(r => r.json())\n"\
" .then(data => {\n"\
" if (data.packets && data.packets.length > 0) {\n"\
" data.packets.forEach(pkt => addPacketToLog(pkt));\n"\
" packetCount += data.packets.length;\n"\
" document.getElementById('packets-count').textContent = packetCount;\n"\
" updatePacketFlowChart(data.packets.length);\n"\
" }\n"\
" }).catch(e => console.log('Fetch packets failed:', e));\n"\
" }\n"\
" \n"\
" function addPacketToLog(packet) {\n"\
" const log = document.getElementById('packet-log');\n"\
" const time = new Date().toLocaleTimeString();\n"\
" const line = document.createElement('div');\n"\
" line.className = 'terminal-line';\n"\
" line.innerHTML = `<span class=\"terminal-prompt\">[${time}]</span> <span class=\"terminal-output\">${packet.type}</span> <span class=\"terminal-prompt\">SRC:</span> <span class=\"terminal-output\">${packet.src}</span> <span class=\"terminal-prompt\">DST:</span> <span class=\"terminal-output\">${packet.dst}</span> <span class=\"terminal-prompt\">RSSI:</span> <span class=\"terminal-output\">${packet.rssi} dBm</span>`;\n"\
" log.appendChild(line);\n"\
" log.scrollTop = log.scrollHeight;\n"\
" if (log.children.length > 100) log.removeChild(log.firstChild);\n"\
" }\n"\
" \n"\
" function updatePacketFlowChart(count) {\n"\
" const now = new Date().toLocaleTimeString();\n"\
" packetFlowData.labels.push(now);\n"\
" packetFlowData.datasets[0].data.push(count);\n"\
" if (packetFlowData.labels.length > 20) {\n"\
" packetFlowData.labels.shift();\n"\
" packetFlowData.datasets[0].data.shift();\n"\
" }\n"\
" if (charts.packetFlow) charts.packetFlow.update('none');\n"\
" }\n"\
" \n"\
" // Bluetooth Scanner\n"\
" document.getElementById('bt-scan-btn')?.addEventListener('click', function() {\n"\
" this.disabled = true;\n"\
" document.getElementById('bt-scan-stop-btn').disabled = false;\n"\
" document.getElementById('bt-scan-status').className = 'status-indicator active';\n"\
" \n"\
" fetch('/api/bt/scan/start')\n"\
" .then(r => r.json())\n"\
" .then(data => {\n"\
" if (data.status === 'success') {\n"\
" if (btScanInterval) clearInterval(btScanInterval);\n"\
" btScanInterval = setInterval(fetchBTDevices, 2000);\n"\
" }\n"\
" }).catch(e => console.log('BT scan start failed:', e));\n"\
" });\n"\
" \n"\
" function fetchBTDevices() {\n"\
" fetch('/api/bt/devices')\n"\
" .then(r => r.json())\n"\
" .then(data => {\n"\
" if (data.devices) {\n"\
" displayBTDevices(data.devices);\n"\
" }\n"\
" }).catch(e => console.log('Fetch BT devices failed:', e));\n"\
" }\n"\
" \n"\
" function displayBTDevices(devices) {\n"\
" const tbody = document.querySelector('#bt-devices-table tbody');\n"\
" tbody.innerHTML = '';\n"\
" devices.forEach(dev => {\n"\
" const row = document.createElement('tr');\n"\
" row.innerHTML = `\n"\
" <td>${dev.name || 'Unknown'}</td>\n"\
" <td><code>${formatBTAddr(dev.addr)}</code></td>\n"\
" <td>${dev.rssi} dBm</td>\n"\
" <td>${dev.type === 1 ? 'BLE' : 'Classic'}</td>\n"\
" <td><button class=\"btn\" onclick=\"selectBTJam('${formatBTAddr(dev.addr)}')\">SELECT</button></td>\n"\
" `;\n"\
" tbody.appendChild(row);\n"\
" });\n"\
" }\n"\
" \n"\
" function formatBTAddr(addr) {\n"\
" if (!addr || !Array.isArray(addr)) return 'UNKNOWN';\n"\
" return addr.map(b => b.toString(16).padStart(2, '0')).join(':').toUpperCase();\n"\
" }\n"\
" \n"\
" // Deauth Engine\n"\
" document.getElementById('deauth-scan-btn')?.addEventListener('click', function() {\n"\
" fetch('/api/scan')\n"\
" .then(r => r.json())\n"\
" .then(data => {\n"\
" if (data.status === 'success') {\n"\
" displayDeauthNetworks(data.networks);\n"\
" }\n"\
" }).catch(e => console.log('Deauth scan failed:', e));\n"\
" });\n"\
" \n"\
" function displayDeauthNetworks(networks) {\n"\
" const targets = document.getElementById('deauth-targets');\n"\
" let html = '<table><tr><th>SSID</th><th>BSSID</th><th>BAND</th><th>CH</th><th>SELECT</th></tr>';\n"\
" networks.forEach(net => {\n"\
" const escapedSsid = (net.ssid || '(HIDDEN)').replace(/'/g, \"\\\\'\");\n"\
" html += `<tr><td>${net.ssid || '(HIDDEN)'}</td><td><code>${net.bssid}</code></td><td>${net.band}</td><td>${net.channel}</td><td>`;\n"\
" if (net.band === '2.4GHz') {\n"\
" html += `<button class=\"btn\" onclick=\"selectDeauthTarget('24ghz', '${escapedSsid}', '${net.bssid}', ${net.channel})\">2.4GHz</button>`;\n"\
" }\n"\
" if (net.band === '5GHz') {\n"\
" html += `<button class=\"btn\" onclick=\"selectDeauthTarget('5ghz', '${escapedSsid}', '${net.bssid}', ${net.channel})\">5GHz</button>`;\n"\
" }\n"\
" html += '</td></tr>';\n"\
" });\n"\
" html += '</table>';\n"\
" targets.innerHTML = html;\n"\
" }\n"\
" \n"\
" function selectDeauthTarget(band, ssid, bssid, channel) {\n"\
" const target = {ssid, bssid, channel};\n"\
" if (band === '24ghz') selected24ghz = target;\n"\
" else selected5ghz = target;\n"\
" updateDeauthTargets();\n"\
" }\n"\
" \n"\
" function updateDeauthTargets() {\n"\
" const targets = document.getElementById('deauth-targets');\n"\
" let html = '';\n"\
" if (selected24ghz) {\n"\
" html += `<div style=\"background:#1565C0;padding:10px;margin:10px 0;border:1px solid #00ff41;\"><strong>2.4GHz:</strong> ${selected24ghz.ssid} (${selected24ghz.bssid}) CH:${selected24ghz.channel} <button class=\"btn\" onclick=\"selected24ghz=null;updateDeauthTargets();\">REMOVE</button></div>`;\n"\
" }\n"\
" if (selected5ghz) {\n"\
" html += `<div style=\"background:#E65100;padding:10px;margin:10px 0;border:1px solid #00ff41;\"><strong>5GHz:</strong> ${selected5ghz.ssid} (${selected5ghz.bssid}) CH:${selected5ghz.channel} <button class=\"btn\" onclick=\"selected5ghz=null;updateDeauthTargets();\">REMOVE</button></div>`;\n"\
" }\n"\
" targets.innerHTML = html;\n"\
" }\n"\
" \n"\
" document.getElementById('deauth-start-btn')?.addEventListener('click', function() {\n"\
" if (!selected24ghz && !selected5ghz) {\n"\
" alert('SELECT AT LEAST ONE TARGET');\n"\
" return;\n"\
" }\n"\
" const duration = parseInt(document.getElementById('deauth-duration').value) || 30;\n"\
" const body = {duration};\n"\
" if (selected24ghz) body.target_24ghz = selected24ghz;\n"\
" if (selected5ghz) body.target_5ghz = selected5ghz;\n"\
" \n"\
" if (!confirm('START DEAUTH ATTACK? USE ONLY ON YOUR NETWORKS!')) return;\n"\
" \n"\
" fetch('/api/deauth/start', {\n"\
" method: 'POST',\n"\
" headers: {'Content-Type': 'application/json'},\n"\
" body: JSON.stringify(body)\n"\
" })\n"\
" .then(r => r.json())\n"\
" .then(data => {\n"\
" if (data.status === 'success') {\n"\
" this.disabled = true;\n"\
" document.getElementById('deauth-stop-btn').disabled = false;\n"\
" if (deauthStatsInterval) clearInterval(deauthStatsInterval);\n"\
" deauthStatsInterval = setInterval(updateDeauthStats, 1000);\n"\
" }\n"\
" }).catch(e => console.log('Deauth start failed:', e));\n"\
" });\n"\
" \n"\
" function updateDeauthStats() {\n"\
" fetch('/api/deauth/status')\n"\
" .then(r => r.json())\n"\
" .then(data => {\n"\
" document.getElementById('deauth-total').textContent = data.total_packets || 0;\n"\
" document.getElementById('deauth-24').textContent = data.packets_24ghz || 0;\n"\
" document.getElementById('deauth-5').textContent = data.packets_5ghz || 0;\n"\
" document.getElementById('deauth-time').textContent = (data.elapsed_time || 0) + 's';\n"\
" }).catch(e => console.log('Deauth status failed:', e));\n"\
" }\n"\
" \n"\
" function loadSystemInfo() {\n"\
" updateDashboard();\n"\
" }\n"\
" \n"\
" // =====================================================\n"\
" // HANDSHAKE CAPTURE FUNCTIONS\n"\
" // =====================================================\n"\
" let hsTarget = null;\n"\
" let hsStatusInterval = null;\n"\
" \n"\
" // Scan for networks (handshake capture)\n"\
" document.getElementById('hs-scan-btn')?.addEventListener('click', function() {\n"\
" this.disabled = true;\n"\
" fetch('/api/scan')\n"\
" .then(r => r.json())\n"\
" .then(data => {\n"\
" if (data.status === 'success') {\n"\
" displayHsNetworks(data.networks);\n"\
" }\n"\
" this.disabled = false;\n"\
" }).catch(e => { console.log('HS scan failed:', e); this.disabled = false; });\n"\
" });\n"\
" \n"\
" function displayHsNetworks(networks) {\n"\
" const targets = document.getElementById('hs-targets');\n"\
" let html = '<table><tr><th>SSID</th><th>BSSID</th><th>CH</th><th>RSSI</th><th>SEC</th><th>SELECT</th></tr>';\n"\
" networks.forEach(net => {\n"\
" const escapedSsid = (net.ssid || '').replace(/'/g, \"\\\\'\");\n"\
" html += `<tr><td>${net.ssid || '(HIDDEN)'}</td><td><code>${net.bssid}</code></td><td>${net.channel}</td><td>${net.rssi}</td><td>${net.security}</td>`;\n"\
" html += `<td><button class=\"btn\" onclick=\"selectHsTarget('${escapedSsid}', '${net.bssid}', ${net.channel})\">SELECT</button></td></tr>`;\n"\
" });\n"\
" html += '</table>';\n"\
" targets.innerHTML = html;\n"\
" }\n"\
" \n"\
" function selectHsTarget(ssid, bssid, channel) {\n"\
" hsTarget = {ssid, bssid, channel};\n"\
" const targets = document.getElementById('hs-targets');\n"\
" targets.innerHTML = `<div style=\"background:#1565C0;padding:15px;border:1px solid #00ff41;\"><strong>TARGET:</strong> ${ssid} (${bssid}) CH:${channel} <button class=\"btn\" onclick=\"hsTarget=null;document.getElementById('hs-targets').innerHTML='';\">REMOVE</button></div>`;\n"\
" addHsLog('Target selected: ' + ssid);\n"\
" }\n"\
" \n"\
" function addHsLog(msg) {\n"\
" const log = document.getElementById('hs-log');\n"\
" const time = new Date().toLocaleTimeString();\n"\
" log.innerHTML += `<div class=\"terminal-line\"><span class=\"terminal-prompt\">[${time}]</span> ${msg}</div>`;\n"\
" log.scrollTop = log.scrollHeight;\n"\
" }\n"\
" \n"\
" // Start handshake capture\n"\
" document.getElementById('hs-start-btn')?.addEventListener('click', function() {\n"\
" if (!hsTarget) {\n"\
" alert('SELECT A TARGET NETWORK FIRST!');\n"\
" return;\n"\
" }\n"\
" \n"\
" if (!confirm('START HANDSHAKE CAPTURE? This will disconnect clients from the target network.')) return;\n"\
" \n"\
" const method = document.getElementById('hs-method').value;\n"\
" const timeout = parseInt(document.getElementById('hs-timeout').value) || 120;\n"\
" \n"\
" const body = {\n"\
" ssid: hsTarget.ssid,\n"\
" bssid: hsTarget.bssid,\n"\
" channel: hsTarget.channel,\n"\
" method: method,\n"\
" timeout: timeout\n"\
" };\n"\
" \n"\
" addHsLog('Starting capture for ' + hsTarget.ssid + '...');\n"\
" \n"\
" fetch('/api/handshake/start', {\n"\
" method: 'POST',\n"\
" headers: {'Content-Type': 'application/json'},\n"\
" body: JSON.stringify(body)\n"\
" })\n"\
" .then(r => r.json())\n"\
" .then(data => {\n"\
" if (data.status === 'success') {\n"\
" addHsLog('Capture started!');\n"\
" this.disabled = true;\n"\
" document.getElementById('hs-stop-btn').disabled = false;\n"\
" if (hsStatusInterval) clearInterval(hsStatusInterval);\n"\
" hsStatusInterval = setInterval(updateHsStatus, 1000);\n"\
" } else {\n"\
" addHsLog('ERROR: ' + data.message);\n"\
" }\n"\
" }).catch(e => { addHsLog('ERROR: ' + e); });\n"\
" });\n"\
" \n"\
" // Stop capture\n"\
" document.getElementById('hs-stop-btn')?.addEventListener('click', function() {\n"\
" fetch('/api/handshake/stop')\n"\
" .then(r => r.json())\n"\
" .then(data => {\n"\
" addHsLog('Capture stopped');\n"\
" this.disabled = true;\n"\
" document.getElementById('hs-start-btn').disabled = false;\n"\
" if (hsStatusInterval) { clearInterval(hsStatusInterval); hsStatusInterval = null; }\n"\
" });\n"\
" });\n"\
" \n"\
" // Reset capture\n"\
" document.getElementById('hs-reset-btn')?.addEventListener('click', function() {\n"\
" fetch('/api/handshake/reset')\n"\
" .then(r => r.json())\n"\
" .then(data => {\n"\
" addHsLog('Capture reset');\n"\
" document.getElementById('hs-state').textContent = 'IDLE';\n"\
" document.getElementById('hs-handshake-state').textContent = 'NONE';\n"\
" document.getElementById('hs-packets').textContent = '0';\n"\
" document.getElementById('hs-deauth').textContent = '0';\n"\
" document.getElementById('hs-start-btn').disabled = false;\n"\
" document.getElementById('hs-stop-btn').disabled = true;\n"\
" if (hsStatusInterval) { clearInterval(hsStatusInterval); hsStatusInterval = null; }\n"\
" });\n"\
" });\n"\
" \n"\
" function updateHsStatus() {\n"\
" fetch('/api/handshake/status')\n"\
" .then(r => r.json())\n"\
" .then(data => {\n"\
" document.getElementById('hs-state').textContent = data.state ? data.state.toUpperCase() : 'IDLE';\n"\
" document.getElementById('hs-handshake-state').textContent = data.handshake_state ? data.handshake_state.toUpperCase() : 'NONE';\n"\
" document.getElementById('hs-packets').textContent = data.packets_captured || 0;\n"\
" document.getElementById('hs-deauth').textContent = data.deauth_sent || 0;\n"\
" \n"\
" // Check if handshake captured\n"\
" if (data.handshake_captured) {\n"\
" addHsLog('*** HANDSHAKE CAPTURED! ***');\n"\
" document.getElementById('hs-handshake-state').textContent = 'CAPTURED!';\n"\
" document.getElementById('hs-handshake-state').style.color = '#00ff41';\n"\
" document.getElementById('hs-start-btn').disabled = false;\n"\
" document.getElementById('hs-stop-btn').disabled = true;\n"\
" if (hsStatusInterval) { clearInterval(hsStatusInterval); hsStatusInterval = null; }\n"\
" }\n"\
" \n"\
" // Check if finished/timeout\n"\
" if (data.state === 'finished' || data.state === 'timeout') {\n"\
" document.getElementById('hs-start-btn').disabled = false;\n"\
" document.getElementById('hs-stop-btn').disabled = true;\n"\
" if (hsStatusInterval) { clearInterval(hsStatusInterval); hsStatusInterval = null; }\n"\
" if (data.state === 'timeout') {\n"\
" addHsLog('Capture timed out');\n"\
" }\n"\
" }\n"\
" }).catch(e => console.log('HS status failed:', e));\n"\
" }\n"\
"\n"\
" </script>\n"\
"</body>\n"\
"</html>";
// 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);
}