commit 3b6255b77790aabc10a25884db46997daf5029c5 Author: drjones Date: Wed May 20 10:04:07 2026 -0700 chore: import local project into Gitea diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81d430d --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# OS / tooling +.DS_Store +Thumbs.db + +# Editors +.cursor/ + +# Secrets +.env +.env.* +!.env.example +!.env.template + +# Python +__pycache__/ +*.py[cod] +.venv/ +venv/ + +# Node / frontend +node_modules/ +dist/ + +# Typical embedded / tooling noise +*.log + +# Builds (adjust per subtree if needed) +**/build/.ninja_deps +**/build/.ninja_log + diff --git a/README.md b/README.md new file mode 100644 index 0000000..3a0984c --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# iceman_webui + +Dual ESP32 firmware trees: + +| Path | Stack | +|------|-------| +| `iceman_web_ui_esp32` | Web UI bridging proxmark / iceman-style tooling | +| `proxarch_esp_idf` | ESP-IDF layout with `CMakeLists.txt` scaffolding | + +Navigate into whichever matches your chipset. For ESP-IDF children, activate the Espressif environment (`export.sh`/`export.ps1`) then: + +```bash +idf.py set-target esp32 # verify per subdirectory docs +idf.py build flash monitor +``` + +Erase flash when hopping between radically different partitions to avoid stray credentials in NVS. diff --git a/iceman_web_ui_esp32/CMakeLists.txt b/iceman_web_ui_esp32/CMakeLists.txt new file mode 100644 index 0000000..7f1f449 --- /dev/null +++ b/iceman_web_ui_esp32/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +project(proxarch_esp_idf) \ No newline at end of file diff --git a/iceman_web_ui_esp32/main/CMakeLists.txt b/iceman_web_ui_esp32/main/CMakeLists.txt new file mode 100644 index 0000000..0d44123 --- /dev/null +++ b/iceman_web_ui_esp32/main/CMakeLists.txt @@ -0,0 +1,16 @@ + +idf_component_register(SRCS "proxarch_main.c" + INCLUDE_DIRS ".") + +# Required for esp_http_server, esp_wifi, nvs_flash, esp_netif, esp_event, esp_log, freertos, lwip +set(requires esp_http_server esp_wifi nvs_flash esp_netif esp_event esp_log freertos lwip) + +# Required for TinyUSB +list(APPEND requires tinyusb) + +# If you use SPIFFS for web files later, add: +# list(APPEND requires spiffs) + +target_link_libraries(${COMPONENT_TARGET} PRIVATE + ${requires} +) \ No newline at end of file diff --git a/iceman_web_ui_esp32/main/proxarch_main.c b/iceman_web_ui_esp32/main/proxarch_main.c new file mode 100644 index 0000000..49ab995 --- /dev/null +++ b/iceman_web_ui_esp32/main/proxarch_main.c @@ -0,0 +1,708 @@ +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_event.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "esp_netif.h" +#include "esp_http_server.h" +#include "esp_vfs.h" // For esp_vfs_spiffs_register if using SPIFFS + +// For TinyUSB +#include "tinyusb.h" +#include "tusb_cdc_acm.h" +#include "sdkconfig.h" // Required for Kconfig options + +// WiFi credentials - REPLACE WITH YOURS +#define WIFI_SSID "proxarch" +#define WIFI_PASS "password" +#define WIFI_MAXIMUM_RETRY 5 + +// Proxmark3 Communication +#define PM3_CDC_ITF ITF_NUM_CDC_0 // CDC Interface number for PM3 +#define PM3_RESPONSE_BUFFER_SIZE 2048 +#define PM3_PROMPT "proxmark3>" +#define PM3_CMD_TIMEOUT_MS 10000 // 10 seconds + +static char pm3_rx_buffer[PM3_RESPONSE_BUFFER_SIZE]; +static uint32_t pm3_rx_buffer_len = 0; +static bool pm3_connected = false; +static bool pm3_command_in_progress = false; +static httpd_ws_client_t* ws_clients[CONFIG_LWIP_MAX_SOCKETS - 4]; // Max clients for WebSocket +static size_t ws_client_count = 0; + +// FreeRTOS event group to signal when we are connected +static EventGroupHandle_t s_wifi_event_group; +#define WIFI_CONNECTED_BIT BIT0 +#define WIFI_FAIL_BIT BIT1 +static int s_retry_num = 0; + +static const char *TAG = "PROXARCH"; + +// --- Forward Declarations --- +static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data); +void wifi_init_sta(void); +static esp_err_t http_get_handler(httpd_req_t *req); +static esp_err_t ws_handler(httpd_req_t *req); +static void send_pm3_command(const char* command); +static void send_ws_message_to_all(const char* message, size_t len); +static void send_ws_pm3_status_update(void); +static void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event); +static void tinyusb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event); + +// --- Embedded Web Page --- +const char index_html_start[] = R"rawliteral( + + + Proxarch ESP-IDF + + + + +

Proxarch ESP-IDF Interface

+
+
+ WiFi Status: Connecting... | Proxmark3 Status: Disconnected +
+
+ + + + + +
+
+

Proxmark3 Output:

+
Waiting for commands...
+
+ + + + +)rawliteral"; + + +// --- WiFi Initialization --- +static void wifi_event_handler(void* arg, esp_event_base_t event_base, + int32_t event_id, void* event_data) { + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { + esp_wifi_connect(); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + if (s_retry_num < WIFI_MAXIMUM_RETRY) { + esp_wifi_connect(); + s_retry_num++; + ESP_LOGI(TAG, "Retrying to connect to the AP"); + } else { + xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); + // Send WiFi status update via WebSocket if possible + char ws_msg[100]; + snprintf(ws_msg, sizeof(ws_msg), "{\"type\":\"wifi_status\", \"connected\":false, \"ip\":\"N/A\"}"); + send_ws_message_to_all(ws_msg, strlen(ws_msg)); + } + ESP_LOGI(TAG,"Connect to the AP fail"); + } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { + ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; + ESP_LOGI(TAG, "Got IP:" IPSTR, IP2STR(&event->ip_info.ip)); + s_retry_num = 0; + xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); + // Send WiFi status update via WebSocket + char ws_msg[100]; + snprintf(ws_msg, sizeof(ws_msg), "{\"type\":\"wifi_status\", \"connected\":true, \"ip\":\"" IPSTR "\"}", IP2STR(&event->ip_info.ip)); + send_ws_message_to_all(ws_msg, strlen(ws_msg)); + } +} + +void wifi_init_sta(void) { + s_wifi_event_group = xEventGroupCreate(); + + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + esp_netif_create_default_wifi_sta(); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + esp_event_handler_instance_t instance_any_id; + esp_event_handler_instance_t instance_got_ip; + ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, + ESP_EVENT_ANY_ID, + &wifi_event_handler, + NULL, + &instance_any_id)); + ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, + IP_EVENT_STA_GOT_IP, + &wifi_event_handler, + NULL, + &instance_got_ip)); + + wifi_config_t wifi_config = { + .sta = { + .ssid = WIFI_SSID, + .password = WIFI_PASS, + .threshold.authmode = WIFI_AUTH_WPA2_PSK, // Adjust if needed + }, + }; + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) ); + ESP_ERROR_CHECK(esp_wifi_start() ); + + ESP_LOGI(TAG, "wifi_init_sta finished."); + + EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, + WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, + pdFALSE, + pdFALSE, + portMAX_DELAY); + + if (bits & WIFI_CONNECTED_BIT) { + ESP_LOGI(TAG, "Connected to AP SSID:%s", WIFI_SSID); + } else if (bits & WIFI_FAIL_BIT) { + ESP_LOGI(TAG, "Failed to connect to SSID:%s", WIFI_SSID); + } else { + ESP_LOGE(TAG, "UNEXPECTED WIFI EVENT"); + } +} + + +// --- TinyUSB CDC Host Callbacks & Task --- +void tud_mount_cb(void) { + ESP_LOGI(TAG, "TinyUSB Device MOUNTED"); +} + +void tud_umount_cb(void) { + ESP_LOGI(TAG, "TinyUSB Device UNMOUNTED"); + pm3_connected = false; + pm3_command_in_progress = false; + send_ws_pm3_status_update(); +} + +// Invoked when device is suspended +void tud_suspend_cb(bool remote_wakeup_en) { + (void) remote_wakeup_en; + ESP_LOGI(TAG, "TinyUSB Device Suspended"); +} + +// Invoked when device is resumed +void tud_resume_cb(void) { + ESP_LOGI(TAG, "TinyUSB Device Resumed"); +} + +// Callback invoked when data is received from CDC host +static void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event) { + size_t rx_size = 0; + esp_err_t ret = tud_cdc_n_read(itf, pm3_rx_buffer + pm3_rx_buffer_len, PM3_RESPONSE_BUFFER_SIZE - pm3_rx_buffer_len -1); // -1 for null terminator + if (ret == ESP_OK) { + rx_size = tud_cdc_n_get_available(itf); // This might not be what we want, tud_cdc_n_read should return bytes read + // For now, let's assume ret is the size or use a fixed size from a buffer. + // A better way is to use the actual bytes read from tud_cdc_n_read if it returns that. + // For now, we'll rely on a different mechanism to know how much was read. + // This callback might be tricky with TinyUSB's API for host. + // Let's simplify: assume we read into a global buffer and process it elsewhere. + } + // This callback structure is more for CDC *Device* mode. + // For CDC *Host* mode, we typically poll tud_cdc_n_available() and call tud_cdc_n_read() in a task. + // Let's adjust the approach. The main loop will handle reads. +} + + +// Callback invoked when line state change (DTR/RTS) +static void tinyusb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event) { + ESP_LOGI(TAG, "TinyUSB CDC ITF %d Line State Changed: DTR: %d, RTS: %d", + itf, event->line_state_changed_data.dtr, event->line_state_changed_data.rts); + if (event->line_state_changed_data.dtr && event->line_state_changed_data.rts) { + // This is often a signal that the serial device is ready on the other side. + // However, for Proxmark3, simply being connected is the main indicator. + // We can set pm3_connected here if the device is enumerated. + if (tud_cdc_n_mounted(itf)) { + ESP_LOGI(TAG, "Proxmark3 (CDC ITF %d) is likely connected and ready.", itf); + pm3_connected = true; + pm3_rx_buffer_len = 0; // Clear buffer on (re)connect + memset(pm3_rx_buffer, 0, PM3_RESPONSE_BUFFER_SIZE); + send_ws_pm3_status_update(); + } + } else { + // pm3_connected = false; // Handle disconnection if DTR/RTS go low after being high + // send_ws_pm3_status_update(); + } +} + +// Invoked when a new CDC Host device is mounted +void tud_cdc_mount_cb(uint8_t itf) { + ESP_LOGI(TAG, "TinyUSB CDC Host ITF %d MOUNTED", itf); + // You might want to check VID/PID here if you have multiple CDC devices + // For now, assume any CDC device is the Proxmark3 + // The line_state_changed_callback is often a better place to confirm "readiness" + // but we can set a preliminary connected status here. + cdc_line_coding_t line_coding; + tud_cdc_n_get_line_coding(itf, &line_coding); + ESP_LOGI(TAG, "Line Coding: %d bps, %d stop, %d parity, %d data", line_coding.bit_rate, line_coding.stop_bits, line_coding.parity, line_coding.data_bits); + + // Set DTR/RTS to indicate to the device that we are ready + tud_cdc_n_set_control_line_state(itf, true, true); + ESP_LOGI(TAG, "Set DTR & RTS for ITF %d", itf); + + // At this point, the device is mounted. The line_state_changed_callback will confirm DTR/RTS. + // We can consider the PM3 connected here if we don't rely strictly on DTR/RTS from PM3. + pm3_connected = true; + pm3_rx_buffer_len = 0; + memset(pm3_rx_buffer, 0, PM3_RESPONSE_BUFFER_SIZE); + send_ws_pm3_status_update(); +} + +// Invoked when a CDC Host device is unmounted +void tud_cdc_umount_cb(uint8_t itf) { + ESP_LOGI(TAG, "TinyUSB CDC Host ITF %d UNMOUNTED", itf); + pm3_connected = false; + pm3_command_in_progress = false; + send_ws_pm3_status_update(); +} + + +// Task to handle TinyUSB host events and PM3 communication +static void tusb_host_task(void *param) { + ESP_LOGI(TAG, "TinyUSB Host task started"); + tusb_init(); // Initialize TinyUSB stack + + while (1) { + tuh_task(); // TinyUSB host task runner + + if (pm3_connected && pm3_command_in_progress) { + if (tud_cdc_n_available(PM3_CDC_ITF)) { + uint32_t count = tud_cdc_n_read(PM3_CDC_ITF, + (uint8_t*)pm3_rx_buffer + pm3_rx_buffer_len, + PM3_RESPONSE_BUFFER_SIZE - pm3_rx_buffer_len - 1); + if (count > 0) { + pm3_rx_buffer_len += count; + pm3_rx_buffer[pm3_rx_buffer_len] = '\0'; // Null-terminate + + // Send chunk to WebSocket + char chunk_json[PM3_RESPONSE_BUFFER_SIZE + 50]; // Extra space for JSON overhead + snprintf(chunk_json, sizeof(chunk_json), "{\"type\":\"pm3_response_chunk\", \"chunk\":\"%.*s\"}", (int)count, pm3_rx_buffer + (pm3_rx_buffer_len - count) ); + + // Need to escape the chunk before putting into JSON + // For simplicity, this is omitted here but is CRITICAL for robust JSON. + // The JS side has escapeHtml, but C side needs to escape for JSON string. + // Let's assume for now the data is mostly ASCII and doesn't break JSON. + // A proper implementation would use a JSON library or manual escaping. + // For now, let's send the raw new data. + char temp_chunk_buffer[count + 1]; + memcpy(temp_chunk_buffer, pm3_rx_buffer + (pm3_rx_buffer_len - count), count); + temp_chunk_buffer[count] = '\0'; + + // Proper escaping for JSON (simplified) + char escaped_chunk_buffer[count * 2 + 1]; // Worst case if all chars need escaping + int j = 0; + for(int i=0; i < count; i++) { + char ch = temp_chunk_buffer[i]; + if (ch == '"' || ch == '\\' || ch == '/') { escaped_chunk_buffer[j++] = '\\'; escaped_chunk_buffer[j++] = ch; } + else if (ch == '\b') { escaped_chunk_buffer[j++] = '\\'; escaped_chunk_buffer[j++] = 'b'; } + else if (ch == '\f') { escaped_chunk_buffer[j++] = '\\'; escaped_chunk_buffer[j++] = 'f'; } + else if (ch == '\n') { escaped_chunk_buffer[j++] = '\\'; escaped_chunk_buffer[j++] = 'n'; } + else if (ch == '\r') { escaped_chunk_buffer[j++] = '\\'; escaped_chunk_buffer[j++] = 'r'; } + else if (ch == '\t') { escaped_chunk_buffer[j++] = '\\'; escaped_chunk_buffer[j++] = 't'; } + else if (ch < 32) { /* skip control chars or use \uXXXX */ } + else { escaped_chunk_buffer[j++] = ch; } + } + escaped_chunk_buffer[j] = '\0'; + + snprintf(chunk_json, sizeof(chunk_json), "{\"type\":\"pm3_response_chunk\", \"chunk\":\"%s\"}", escaped_chunk_buffer); + send_ws_message_to_all(chunk_json, strlen(chunk_json)); + + + // Check for prompt + if (pm3_rx_buffer_len >= strlen(PM3_PROMPT)) { + if (strstr(pm3_rx_buffer, PM3_PROMPT) != NULL) { + ESP_LOGI(TAG, "PM3 Prompt detected. Command finished."); + pm3_command_in_progress = false; + pm3_rx_buffer_len = 0; // Clear buffer for next command + memset(pm3_rx_buffer, 0, PM3_RESPONSE_BUFFER_SIZE); + send_ws_message_to_all("{\"type\":\"pm3_response_end\"}", strlen("{\"type\":\"pm3_response_end\"}")); + } + } + // Buffer full without prompt? Error or very long output. + if (pm3_rx_buffer_len >= PM3_RESPONSE_BUFFER_SIZE -1) { + ESP_LOGW(TAG, "PM3 RX Buffer full without prompt!"); + // Send what we have and signal end/error + pm3_command_in_progress = false; + pm3_rx_buffer_len = 0; + memset(pm3_rx_buffer, 0, PM3_RESPONSE_BUFFER_SIZE); + send_ws_message_to_all("{\"type\":\"pm3_error\", \"message\":\"RX buffer full\"}", strlen("{\"type\":\"pm3_error\", \"message\":\"RX buffer full\"}")); + } + } + } else { + // Check for timeout if command is in progress + // This needs a timer associated with when the command was sent. + // For simplicity, this is omitted but important for robustness. + } + } + vTaskDelay(pdMS_TO_TICKS(10)); // Yield for other tasks + } +} + + +// --- HTTP Server Handlers --- +static esp_err_t http_get_handler(httpd_req_t *req) { + httpd_resp_set_type(req, "text/html"); + httpd_resp_send(req, index_html_start, HTTPD_RESP_USE_STRLEN); + return ESP_OK; +} + +static void send_ws_message_to_all(const char* message, size_t len) { + for (size_t i = 0; i < ws_client_count; ++i) { + httpd_ws_client_t *client = ws_clients[i]; + if (client) { + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + ws_pkt.payload = (uint8_t*)message; + ws_pkt.len = len; + ws_pkt.type = HTTPD_WS_TYPE_TEXT; + httpd_ws_send_frame_async(client->hd, client->fd, &ws_pkt); + } + } +} + +static void send_ws_pm3_status_update(void) { + char status_msg[100]; + snprintf(status_msg, sizeof(status_msg), "{\"type\":\"pm3_status\", \"connected\":%s}", pm3_connected ? "true" : "false"); + send_ws_message_to_all(status_msg, strlen(status_msg)); +} + + +static esp_err_t ws_handler(httpd_req_t *req) { + if (req->method == HTTP_GET) { + // Check if this client is already tracked + bool client_exists = false; + for(size_t i = 0; i < ws_client_count; ++i) { + if(ws_clients[i] && ws_clients[i]->fd == req->handle->fd) { + client_exists = true; + break; + } + } + + if (!client_exists && ws_client_count < (CONFIG_LWIP_MAX_SOCKETS - 4)) { + // Find an empty slot or add to the end + size_t new_client_idx = ws_client_count; + for(size_t i = 0; i < ws_client_count; ++i) { // Reuse slot if any client disconnected + if(ws_clients[i] == NULL) { + new_client_idx = i; + break; + } + } + + httpd_ws_client_t *client = calloc(1, sizeof(httpd_ws_client_t)); + if (!client) { + ESP_LOGE(TAG, "Failed to allocate memory for new WS client"); + return ESP_FAIL; + } + client->hd = req->handle; + client->fd = httpd_req_to_sockfd(req); // Get socket descriptor + + if (new_client_idx == ws_client_count) { // Adding new client + ws_clients[ws_client_count++] = client; + } else { // Reusing slot + ws_clients[new_client_idx] = client; + } + ESP_LOGI(TAG, "New WebSocket client connected, fd=%d, total_clients=%d", client->fd, ws_client_count); + // Send initial status + send_ws_pm3_status_update(); // Send PM3 status + // Send WiFi status (already done by wifi_event_handler, but good for new client) + char ws_msg[100]; + if ( (xEventGroupGetBits(s_wifi_event_group) & WIFI_CONNECTED_BIT) ) { + esp_netif_ip_info_t ip_info; + esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"), &ip_info); + snprintf(ws_msg, sizeof(ws_msg), "{\"type\":\"wifi_status\", \"connected\":true, \"ip\":\"" IPSTR "\"}", IP2STR(&ip_info.ip)); + } else { + snprintf(ws_msg, sizeof(ws_msg), "{\"type\":\"wifi_status\", \"connected\":false, \"ip\":\"N/A\"}"); + } + httpd_ws_frame_t pkt; + memset(&pkt, 0, sizeof(httpd_ws_frame_t)); + pkt.payload = (uint8_t*)ws_msg; + pkt.len = strlen(ws_msg); + pkt.type = HTTPD_WS_TYPE_TEXT; + httpd_ws_send_frame_async(client->hd, client->fd, &pkt); + + return ESP_OK; // Keep connection open for WebSocket + } else { + ESP_LOGW(TAG, "Max WebSocket clients reached or client already exists."); + return ESP_FAIL; // Or send 429 Too Many Requests + } + } + + // Handle WebSocket data frames + uint8_t buf[128]; + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + ws_pkt.payload = buf; + ws_pkt.type = HTTPD_WS_TYPE_TEXT; // Expecting text + esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, sizeof(buf) -1); // -1 for null terminator + + if (ret != ESP_OK) { + ESP_LOGE(TAG, "httpd_ws_recv_frame failed with %d", ret); + // Client disconnected or error + for(size_t i = 0; i < ws_client_count; ++i) { + if(ws_clients[i] && ws_clients[i]->fd == httpd_req_to_sockfd(req)) { + ESP_LOGI(TAG, "WebSocket client disconnected, fd=%d", ws_clients[i]->fd); + free(ws_clients[i]); + ws_clients[i] = NULL; + // To properly manage the array, shift elements or mark as NULL and reuse + // For simplicity, just NULLing. A robust solution would compact the array. + // If it was the last element, decrement count + if (i == ws_client_count -1) ws_client_count--; + break; + } + } + return ret; + } + + if (ws_pkt.len > 0 && ws_pkt.type == HTTPD_WS_TYPE_TEXT) { + buf[ws_pkt.len] = '\0'; // Null-terminate + ESP_LOGI(TAG, "Got WS packet with message: %s", buf); + + // Basic JSON parsing: {"type":"pm3_command", "cmd":"hw version"} + // Using simple strstr for this example. A JSON library (e.g. cJSON) is recommended for robust parsing. + char* type_str = strstr((char*)buf, "\"type\":\"pm3_command\""); + if (type_str) { + char* cmd_start_str = strstr((char*)buf, "\"cmd\":\""); + if (cmd_start_str) { + cmd_start_str += strlen("\"cmd\":\""); // Move pointer to start of command + char* cmd_end_str = strchr(cmd_start_str, '"'); + if (cmd_end_str) { + int cmd_len = cmd_end_str - cmd_start_str; + if (cmd_len > 0 && cmd_len < 100) { // Basic sanity check for command length + char command[101]; + strncpy(command, cmd_start_str, cmd_len); + command[cmd_len] = '\0'; + ESP_LOGI(TAG, "Parsed PM3 command: %s", command); + send_pm3_command(command); + } + } + } + } else if (strstr((char*)buf, "\"type\":\"client_hello\"")) { + ESP_LOGI(TAG, "Client says hello!"); + // Already sent status on connect, can send ack if needed + } + } + return ESP_OK; +} + + +static const httpd_uri_t uri_get = { + .uri = "/", + .method = HTTP_GET, + .handler = http_get_handler, + .user_ctx = NULL +}; + +static const httpd_uri_t ws = { + .uri = "/ws", + .method = HTTP_GET, + .handler = ws_handler, + .user_ctx = NULL, + .is_websocket = true +}; + +static httpd_handle_t start_webserver(void) { + httpd_handle_t server = NULL; + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.max_open_sockets = 7; // Default is 7. Need at least 1 for HTTP, 1 for WS. + config.lru_purge_enable = true; + + ESP_LOGI(TAG, "Starting httpd server on port: '%d'", config.server_port); + if (httpd_start(&server, &config) == ESP_OK) { + ESP_LOGI(TAG, "Registering URI handlers"); + httpd_register_uri_handler(server, &uri_get); + httpd_register_uri_handler(server, &ws); + return server; + } + ESP_LOGI(TAG, "Error starting server!"); + return NULL; +} + +// --- Proxmark3 Command Logic --- +void send_pm3_command(const char* command) { + if (!pm3_connected) { + ESP_LOGE(TAG, "Cannot send command: Proxmark3 not connected."); + send_ws_message_to_all("{\"type\":\"pm3_error\", \"message\":\"Proxmark3 not connected\"}", strlen("{\"type\":\"pm3_error\", \"message\":\"Proxmark3 not connected\"}")); + return; + } + if (pm3_command_in_progress) { + ESP_LOGW(TAG, "Cannot send command: Another command is already in progress."); + send_ws_message_to_all("{\"type\":\"pm3_error\", \"message\":\"Command already in progress\"}", strlen("{\"type\":\"pm3_error\", \"message\":\"Command already in progress\"}")); + return; + } + + ESP_LOGI(TAG, "Sending to PM3: %s", command); + pm3_command_in_progress = true; + pm3_rx_buffer_len = 0; // Clear RX buffer + memset(pm3_rx_buffer, 0, PM3_RESPONSE_BUFFER_SIZE); + + send_ws_message_to_all("{\"type\":\"pm3_response_start\"}", strlen("{\"type\":\"pm3_response_start\"}")); + + // Ensure command ends with newline for Proxmark3 + char full_command[128]; + snprintf(full_command, sizeof(full_command), "%s\n", command); + + if (tud_cdc_n_connected(PM3_CDC_ITF)) { + tud_cdc_n_write_str(PM3_CDC_ITF, full_command); + tud_cdc_n_write_flush(PM3_CDC_ITF); + } else { + ESP_LOGE(TAG, "PM3 CDC not connected for writing."); + pm3_command_in_progress = false; // Reset flag + send_ws_message_to_all("{\"type\":\"pm3_error\", \"message\":\"Proxmark3 CDC write error\"}", strlen("{\"type\":\"pm3_error\", \"message\":\"Proxmark3 CDC write error\"}")); + } +} + + +// --- Main Application --- +void app_main(void) { + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_LOGI(TAG, "ESP_WIFI_MODE_STA"); + wifi_init_sta(); + + // Initialize TinyUSB stack for Host mode + // This is now done in the tusb_host_task + // tusb_init(); + // ESP_LOGI(TAG, "TinyUSB tud_init() done."); + + // Create a task for TinyUSB host stack and PM3 comms + // Stack size for TinyUSB host might need adjustment. + // USB operations, especially with potential string processing, can be stack intensive. + // Let's increase it from the default. + xTaskCreate(tusb_host_task, "tusb_host_task", 4096 * 2, NULL, 5, NULL); + ESP_LOGI(TAG, "tusb_host_task created."); + + // Start Webserver + start_webserver(); + + ESP_LOGI(TAG, "Proxarch ESP-IDF Initialized."); +} \ No newline at end of file diff --git a/proxarch_esp_idf b/proxarch_esp_idf new file mode 160000 index 0000000..c22cd06 --- /dev/null +++ b/proxarch_esp_idf @@ -0,0 +1 @@ +Subproject commit c22cd0684ee2d65f27ed3b00bf90987c98d8a87a