chore: import local project into Gitea

This commit is contained in:
2026-05-20 10:04:49 -07:00
commit d97ddea405
2228 changed files with 8277 additions and 0 deletions

40
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,40 @@
# This file was automatically generated for projects
# without default 'CMakeLists.txt' file.
FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/*.*)
idf_component_register(
SRCS
"main.cpp"
"network_manager.c"
"web_server.c"
"sd_manager.c"
"security_manager.c"
"led_manager.c"
"ota_manager.c"
"scanner_engine.c"
"config_manager.c"
"log_manager.c"
EXCLUDE_SRCS
"scanner_simple.c"
INCLUDE_DIRS
"../include"
REQUIRES
esp_http_server
esp_https_ota
esp_eth
esp_wifi
esp_netif
fatfs
sdmmc
mbedtls
app_update
nvs_flash
driver
freertos
json
esp_timer
json
PRIV_REQUIRES
esp-hosted-fg
)

447
src/config_manager.c Normal file
View File

@@ -0,0 +1,447 @@
#include "config_manager.h"
#include "sd_manager.h"
#include "security_manager.h"
#include <cJSON.h>
#include "esp_log.h"
#include "esp_err.h"
#include <string.h>
static const char *TAG = "CONFIG_MANAGER";
// Default configuration with weak passwords that must be changed
static config_t s_default_config = {
.wifi_ap = {
.ssid = "P4-Bridge-AP",
.password = "CHANGE_ME_FIRST_BOOT", // Must be changed on first boot
.channel = 1,
.max_connections = 4,
.ip = "192.168.4.1",
.gateway = "192.168.4.1",
.netmask = "255.255.255.0"
},
.ethernet = {
.dhcp_enabled = true,
.static_ip = "192.168.1.100",
.gateway = "192.168.1.1",
.netmask = "255.255.255.0",
.dns = "8.8.8.8"
},
.web_server = {
.port = 80,
.auth_enabled = true, // Enable auth by default
.username = "admin",
.password = "CHANGE_ME_FIRST_BOOT" // Must be changed on first boot
},
.scanner = {
.arp_timeout_ms = 1000,
.ping_timeout_ms = 2000,
.port_scan_timeout_ms = 3000,
.max_ports_per_scan = 100,
.scan_interval_ms = 5000
}
};
static config_t s_current_config;
static bool s_first_boot_detected = false;
// Password strength validation
static bool is_password_strong(const char* password)
{
if (password == NULL || strlen(password) < 8) {
return false;
}
bool has_upper = false, has_lower = false, has_digit = false, has_special = false;
for (int i = 0; password[i] != '\0'; i++) {
if (password[i] >= 'A' && password[i] <= 'Z') has_upper = true;
else if (password[i] >= 'a' && password[i] <= 'z') has_lower = true;
else if (password[i] >= '0' && password[i] <= '9') has_digit = true;
else has_special = true;
}
// Require at least 3 of 4 character types
int types = (has_upper ? 1 : 0) + (has_lower ? 1 : 0) + (has_digit ? 1 : 0) + (has_special ? 1 : 0);
return types >= 3;
}
esp_err_t config_manager_init(void)
{
ESP_LOGI(TAG, "Initializing configuration manager");
// Load configuration from SD card
esp_err_t ret = config_manager_load();
if (ret != ESP_OK) {
ESP_LOGW(TAG, "Failed to load config, using defaults");
memcpy(&s_current_config, &s_default_config, sizeof(config_t));
s_first_boot_detected = true;
// Try to save default config
config_manager_save();
ESP_LOGW(TAG, "FIRST BOOT DETECTED - Please change default passwords!");
ESP_LOGW(TAG, "WiFi AP Password: %s", s_current_config.wifi_ap.password);
ESP_LOGW(TAG, "Web Server Password: %s", s_current_config.web_server.password);
} else {
// Check if passwords are still default
if (strcmp(s_current_config.wifi_ap.password, "CHANGE_ME_FIRST_BOOT") == 0 ||
strcmp(s_current_config.web_server.password, "CHANGE_ME_FIRST_BOOT") == 0) {
s_first_boot_detected = true;
ESP_LOGW(TAG, "DEFAULT PASSWORDS DETECTED - Please change them!");
}
}
return ESP_OK;
}
esp_err_t config_manager_is_first_boot(bool* is_first_boot)
{
if (is_first_boot == NULL) {
return ESP_ERR_INVALID_ARG;
}
*is_first_boot = s_first_boot_detected;
return ESP_OK;
}
esp_err_t config_manager_change_password(const char* username, const char* old_password,
const char* new_password, bool* success)
{
if (username == NULL || old_password == NULL || new_password == NULL || success == NULL) {
return ESP_ERR_INVALID_ARG;
}
*success = false;
// Validate current password
if (strcmp(username, s_current_config.web_server.username) != 0 ||
!verify_password_hash(old_password, s_current_config.web_server.password)) {
return ESP_OK; // Authentication failed
}
// Validate new password strength
if (!is_password_strong(new_password)) {
ESP_LOGE(TAG, "New password does not meet strength requirements");
return ESP_OK; // Password too weak
}
// Hash the new password
char salt[33], hash[65];
esp_err_t ret = security_manager_hash_password(new_password, salt, sizeof(salt), hash, sizeof(hash));
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to hash new password");
return ret;
}
// Store salt:hash format
snprintf(s_current_config.web_server.password, sizeof(s_current_config.web_server.password),
"%s:%s", salt, hash);
// Save configuration
ret = config_manager_save();
if (ret == ESP_OK) {
*success = true;
s_first_boot_detected = false;
ESP_LOGI(TAG, "Password changed successfully");
}
return ret;
}
esp_err_t config_manager_change_wifi_password(const char* new_password, bool* success)
{
if (new_password == NULL || success == NULL) {
return ESP_ERR_INVALID_ARG;
}
*success = false;
// Validate new password strength
if (!is_password_strong(new_password)) {
ESP_LOGE(TAG, "New WiFi password does not meet strength requirements");
return ESP_OK; // Password too weak
}
// Store new password (WiFi passwords are typically not hashed)
strncpy(s_current_config.wifi_ap.password, new_password, sizeof(s_current_config.wifi_ap.password) - 1);
s_current_config.wifi_ap.password[sizeof(s_current_config.wifi_ap.password) - 1] = '\0';
// Save configuration
esp_err_t ret = config_manager_save();
if (ret == ESP_OK) {
*success = true;
ESP_LOGI(TAG, "WiFi password changed successfully");
}
return ret;
}
esp_err_t config_manager_load(void)
{
char *config_data = NULL;
size_t config_size = 0;
// Read config file from SD card
char buffer[4096];
size_t bytes_read = 0;
esp_err_t ret = sd_manager_read_file("/config/network_config.json", buffer, sizeof(buffer), &bytes_read);
if (ret == ESP_OK) {
config_data = malloc(bytes_read + 1);
if (config_data) {
memcpy(config_data, buffer, bytes_read);
config_data[bytes_read] = '\0';
config_size = bytes_read;
} else {
ret = ESP_ERR_NO_MEM;
}
}
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to read config file from SD card");
return ret;
}
if (config_data == NULL || config_size == 0) {
ESP_LOGE(TAG, "Config file is empty");
free(config_data);
return ESP_ERR_INVALID_ARG;
}
// Parse JSON
cJSON *json = cJSON_Parse(config_data);
free(config_data);
if (json == NULL) {
ESP_LOGE(TAG, "Failed to parse JSON config");
return ESP_ERR_INVALID_ARG;
}
// Parse WiFi AP config
cJSON *wifi_ap = cJSON_GetObjectItem(json, "wifi_ap");
if (wifi_ap) {
cJSON *ssid = cJSON_GetObjectItem(wifi_ap, "ssid");
cJSON *password = cJSON_GetObjectItem(wifi_ap, "password");
cJSON *channel = cJSON_GetObjectItem(wifi_ap, "channel");
cJSON *max_connections = cJSON_GetObjectItem(wifi_ap, "max_connections");
cJSON *ip = cJSON_GetObjectItem(wifi_ap, "ip");
cJSON *gateway = cJSON_GetObjectItem(wifi_ap, "gateway");
cJSON *netmask = cJSON_GetObjectItem(wifi_ap, "netmask");
if (ssid && cJSON_IsString(ssid)) {
strncpy(s_current_config.wifi_ap.ssid, ssid->valuestring, sizeof(s_current_config.wifi_ap.ssid) - 1);
}
if (password && cJSON_IsString(password)) {
strncpy(s_current_config.wifi_ap.password, password->valuestring, sizeof(s_current_config.wifi_ap.password) - 1);
}
if (channel && cJSON_IsNumber(channel)) {
s_current_config.wifi_ap.channel = (uint8_t)channel->valueint;
}
if (max_connections && cJSON_IsNumber(max_connections)) {
s_current_config.wifi_ap.max_connections = (uint8_t)max_connections->valueint;
}
if (ip && cJSON_IsString(ip)) {
strncpy(s_current_config.wifi_ap.ip, ip->valuestring, sizeof(s_current_config.wifi_ap.ip) - 1);
}
if (gateway && cJSON_IsString(gateway)) {
strncpy(s_current_config.wifi_ap.gateway, gateway->valuestring, sizeof(s_current_config.wifi_ap.gateway) - 1);
}
if (netmask && cJSON_IsString(netmask)) {
strncpy(s_current_config.wifi_ap.netmask, netmask->valuestring, sizeof(s_current_config.wifi_ap.netmask) - 1);
}
}
// Parse Ethernet config
cJSON *ethernet = cJSON_GetObjectItem(json, "ethernet");
if (ethernet) {
cJSON *dhcp_enabled = cJSON_GetObjectItem(ethernet, "dhcp_enabled");
cJSON *static_ip = cJSON_GetObjectItem(ethernet, "static_ip");
cJSON *gateway = cJSON_GetObjectItem(ethernet, "gateway");
cJSON *netmask = cJSON_GetObjectItem(ethernet, "netmask");
cJSON *dns = cJSON_GetObjectItem(ethernet, "dns");
if (dhcp_enabled && cJSON_IsBool(dhcp_enabled)) {
s_current_config.ethernet.dhcp_enabled = cJSON_IsTrue(dhcp_enabled);
}
if (static_ip && cJSON_IsString(static_ip)) {
strncpy(s_current_config.ethernet.static_ip, static_ip->valuestring, sizeof(s_current_config.ethernet.static_ip) - 1);
}
if (gateway && cJSON_IsString(gateway)) {
strncpy(s_current_config.ethernet.gateway, gateway->valuestring, sizeof(s_current_config.ethernet.gateway) - 1);
}
if (netmask && cJSON_IsString(netmask)) {
strncpy(s_current_config.ethernet.netmask, netmask->valuestring, sizeof(s_current_config.ethernet.netmask) - 1);
}
if (dns && cJSON_IsString(dns)) {
strncpy(s_current_config.ethernet.dns, dns->valuestring, sizeof(s_current_config.ethernet.dns) - 1);
}
}
// Parse Web Server config
cJSON *web_server = cJSON_GetObjectItem(json, "web_server");
if (web_server) {
cJSON *port = cJSON_GetObjectItem(web_server, "port");
cJSON *auth_enabled = cJSON_GetObjectItem(web_server, "auth_enabled");
cJSON *username = cJSON_GetObjectItem(web_server, "username");
cJSON *password = cJSON_GetObjectItem(web_server, "password");
if (port && cJSON_IsNumber(port)) {
s_current_config.web_server.port = (uint16_t)port->valueint;
}
if (auth_enabled && cJSON_IsBool(auth_enabled)) {
s_current_config.web_server.auth_enabled = cJSON_IsTrue(auth_enabled);
}
if (username && cJSON_IsString(username)) {
strncpy(s_current_config.web_server.username, username->valuestring, sizeof(s_current_config.web_server.username) - 1);
}
if (password && cJSON_IsString(password)) {
strncpy(s_current_config.web_server.password, password->valuestring, sizeof(s_current_config.web_server.password) - 1);
}
}
// Parse Scanner config
cJSON *scanner = cJSON_GetObjectItem(json, "scanner");
if (scanner) {
cJSON *arp_timeout = cJSON_GetObjectItem(scanner, "arp_timeout_ms");
cJSON *ping_timeout = cJSON_GetObjectItem(scanner, "ping_timeout_ms");
cJSON *port_scan_timeout = cJSON_GetObjectItem(scanner, "port_scan_timeout_ms");
cJSON *max_ports = cJSON_GetObjectItem(scanner, "max_ports_per_scan");
cJSON *scan_interval = cJSON_GetObjectItem(scanner, "scan_interval_ms");
if (arp_timeout && cJSON_IsNumber(arp_timeout)) {
s_current_config.scanner.arp_timeout_ms = (uint32_t)arp_timeout->valueint;
}
if (ping_timeout && cJSON_IsNumber(ping_timeout)) {
s_current_config.scanner.ping_timeout_ms = (uint32_t)ping_timeout->valueint;
}
if (port_scan_timeout && cJSON_IsNumber(port_scan_timeout)) {
s_current_config.scanner.port_scan_timeout_ms = (uint32_t)port_scan_timeout->valueint;
}
if (max_ports && cJSON_IsNumber(max_ports)) {
s_current_config.scanner.max_ports_per_scan = (uint16_t)max_ports->valueint;
}
if (scan_interval && cJSON_IsNumber(scan_interval)) {
s_current_config.scanner.scan_interval_ms = (uint32_t)scan_interval->valueint;
}
}
cJSON_Delete(json);
ESP_LOGI(TAG, "Configuration loaded successfully");
return ESP_OK;
}
esp_err_t config_manager_save(void)
{
cJSON *json = cJSON_CreateObject();
if (json == NULL) {
ESP_LOGE(TAG, "Failed to create JSON object");
return ESP_ERR_NO_MEM;
}
// WiFi AP config
cJSON *wifi_ap = cJSON_CreateObject();
cJSON_AddStringToObject(wifi_ap, "ssid", s_current_config.wifi_ap.ssid);
cJSON_AddStringToObject(wifi_ap, "password", s_current_config.wifi_ap.password);
cJSON_AddNumberToObject(wifi_ap, "channel", s_current_config.wifi_ap.channel);
cJSON_AddNumberToObject(wifi_ap, "max_connections", s_current_config.wifi_ap.max_connections);
cJSON_AddStringToObject(wifi_ap, "ip", s_current_config.wifi_ap.ip);
cJSON_AddStringToObject(wifi_ap, "gateway", s_current_config.wifi_ap.gateway);
cJSON_AddStringToObject(wifi_ap, "netmask", s_current_config.wifi_ap.netmask);
cJSON_AddItemToObject(json, "wifi_ap", wifi_ap);
// Ethernet config
cJSON *ethernet = cJSON_CreateObject();
cJSON_AddBoolToObject(ethernet, "dhcp_enabled", s_current_config.ethernet.dhcp_enabled);
cJSON_AddStringToObject(ethernet, "static_ip", s_current_config.ethernet.static_ip);
cJSON_AddStringToObject(ethernet, "gateway", s_current_config.ethernet.gateway);
cJSON_AddStringToObject(ethernet, "netmask", s_current_config.ethernet.netmask);
cJSON_AddStringToObject(ethernet, "dns", s_current_config.ethernet.dns);
cJSON_AddItemToObject(json, "ethernet", ethernet);
// Web Server config
cJSON *web_server = cJSON_CreateObject();
cJSON_AddNumberToObject(web_server, "port", s_current_config.web_server.port);
cJSON_AddBoolToObject(web_server, "auth_enabled", s_current_config.web_server.auth_enabled);
cJSON_AddStringToObject(web_server, "username", s_current_config.web_server.username);
cJSON_AddStringToObject(web_server, "password", s_current_config.web_server.password);
cJSON_AddItemToObject(json, "web_server", web_server);
// Scanner config
cJSON *scanner = cJSON_CreateObject();
cJSON_AddNumberToObject(scanner, "arp_timeout_ms", s_current_config.scanner.arp_timeout_ms);
cJSON_AddNumberToObject(scanner, "ping_timeout_ms", s_current_config.scanner.ping_timeout_ms);
cJSON_AddNumberToObject(scanner, "port_scan_timeout_ms", s_current_config.scanner.port_scan_timeout_ms);
cJSON_AddNumberToObject(scanner, "max_ports_per_scan", s_current_config.scanner.max_ports_per_scan);
cJSON_AddNumberToObject(scanner, "scan_interval_ms", s_current_config.scanner.scan_interval_ms);
cJSON_AddItemToObject(json, "scanner", scanner);
// Convert to string
char *json_string = cJSON_Print(json);
cJSON_Delete(json);
if (json_string == NULL) {
ESP_LOGE(TAG, "Failed to convert JSON to string");
return ESP_ERR_NO_MEM;
}
// Write to SD card
esp_err_t ret = sd_manager_write_file("/config/network_config.json", json_string, strlen(json_string), false);
free(json_string);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "Configuration saved successfully");
} else {
ESP_LOGE(TAG, "Failed to save configuration");
}
return ret;
}
const config_t* config_manager_get_config(void)
{
return &s_current_config;
}
esp_err_t config_manager_update_wifi_ap(const custom_wifi_ap_config_t *config)
{
if (config == NULL) {
return ESP_ERR_INVALID_ARG;
}
memcpy(&s_current_config.wifi_ap, config, sizeof(custom_wifi_ap_config_t));
return config_manager_save();
}
esp_err_t config_manager_update_ethernet(const ethernet_config_t *config)
{
if (config == NULL) {
return ESP_ERR_INVALID_ARG;
}
memcpy(&s_current_config.ethernet, config, sizeof(ethernet_config_t));
return config_manager_save();
}
esp_err_t config_manager_update_web_server(const web_server_config_t *config)
{
if (config == NULL) {
return ESP_ERR_INVALID_ARG;
}
memcpy(&s_current_config.web_server, config, sizeof(web_server_config_t));
return config_manager_save();
}
esp_err_t config_manager_update_scanner(const scanner_config_t *config)
{
if (config == NULL) {
return ESP_ERR_INVALID_ARG;
}
memcpy(&s_current_config.scanner, config, sizeof(scanner_config_t));
return config_manager_save();
}
esp_err_t config_manager_reset_to_defaults(void)
{
ESP_LOGI(TAG, "Resetting configuration to defaults");
memcpy(&s_current_config, &s_default_config, sizeof(config_t));
return config_manager_save();
}

214
src/led_manager.c Normal file
View File

@@ -0,0 +1,214 @@
#include "led_manager.h"
#include <esp_log.h>
#include <driver/gpio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/timers.h>
static const char *TAG = "LED_MANAGER";
// LED state tracking
static struct {
led_state_t status_led;
led_state_t ethernet_led;
led_state_t wifi_led;
led_state_t scan_led;
bool ethernet_active;
bool wifi_active;
bool scan_active;
uint32_t blink_counter;
} s_led_state = {0};
static TimerHandle_t s_led_timer = NULL;
// Forward declarations
static void led_timer_callback(TimerHandle_t timer);
static void update_led_pattern(int gpio_num, led_state_t state, uint32_t counter);
esp_err_t led_manager_init(void)
{
// Configure GPIO pins
gpio_config_t io_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = (1ULL << LED_STATUS_GPIO) |
(1ULL << LED_ETHERNET_GPIO) |
(1ULL << LED_WIFI_GPIO) |
(1ULL << LED_SCAN_GPIO),
.pull_down_en = 0,
.pull_up_en = 0,
};
esp_err_t ret = gpio_config(&io_conf);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure GPIO: %s", esp_err_to_name(ret));
return ret;
}
// Initialize LED states
s_led_state.status_led = LED_STATE_OFF;
s_led_state.ethernet_led = LED_STATE_OFF;
s_led_state.wifi_led = LED_STATE_OFF;
s_led_state.scan_led = LED_STATE_OFF;
s_led_state.ethernet_active = false;
s_led_state.wifi_active = false;
s_led_state.scan_active = false;
s_led_state.blink_counter = 0;
// Turn off all LEDs initially
gpio_set_level(LED_STATUS_GPIO, 0);
gpio_set_level(LED_ETHERNET_GPIO, 0);
gpio_set_level(LED_WIFI_GPIO, 0);
gpio_set_level(LED_SCAN_GPIO, 0);
// Create timer for LED updates
s_led_timer = xTimerCreate("led_timer", pdMS_TO_TICKS(100), pdTRUE, NULL, led_timer_callback);
if (s_led_timer) {
xTimerStart(s_led_timer, 0);
}
ESP_LOGI(TAG, "LED manager initialized");
return ESP_OK;
}
esp_err_t led_manager_set_state(int gpio_num, led_state_t state)
{
switch (gpio_num) {
case LED_STATUS_GPIO:
s_led_state.status_led = state;
break;
case LED_ETHERNET_GPIO:
s_led_state.ethernet_led = state;
break;
case LED_WIFI_GPIO:
s_led_state.wifi_led = state;
break;
case LED_SCAN_GPIO:
s_led_state.scan_led = state;
break;
default:
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
esp_err_t led_manager_set_status(led_status_t status)
{
switch (status) {
case LED_STATUS_SYSTEM_READY:
led_manager_set_state(LED_STATUS_GPIO, LED_STATE_BLINK_SLOW);
break;
case LED_STATUS_SYSTEM_ERROR:
led_manager_set_state(LED_STATUS_GPIO, LED_STATE_BLINK_FAST);
break;
case LED_STATUS_ETHERNET_CONNECTED:
led_manager_set_state(LED_ETHERNET_GPIO, LED_STATE_ON);
s_led_state.ethernet_active = true;
break;
case LED_STATUS_ETHERNET_DISCONNECTED:
led_manager_set_state(LED_ETHERNET_GPIO, LED_STATE_OFF);
s_led_state.ethernet_active = false;
break;
case LED_STATUS_WIFI_AP_ACTIVE:
led_manager_set_state(LED_WIFI_GPIO, LED_STATE_BLINK_SLOW);
s_led_state.wifi_active = true;
break;
case LED_STATUS_WIFI_CLIENT_CONNECTED:
led_manager_set_state(LED_WIFI_GPIO, LED_STATE_ON);
break;
case LED_STATUS_SCAN_ACTIVE:
led_manager_set_state(LED_SCAN_GPIO, LED_STATE_BLINK_FAST);
s_led_state.scan_active = true;
break;
case LED_STATUS_SCAN_COMPLETE:
led_manager_set_state(LED_SCAN_GPIO, LED_STATE_PULSE);
s_led_state.scan_active = false;
break;
case LED_STATUS_SD_CARD_ACCESS:
led_manager_set_state(LED_STATUS_GPIO, LED_STATE_PULSE);
break;
case LED_STATUS_OTA_UPDATE:
led_manager_set_state(LED_STATUS_GPIO, LED_STATE_BLINK_FAST);
break;
default:
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
void led_manager_update(void)
{
// Update LED patterns based on current state
update_led_pattern(LED_STATUS_GPIO, s_led_state.status_led, s_led_state.blink_counter);
update_led_pattern(LED_ETHERNET_GPIO, s_led_state.ethernet_led, s_led_state.blink_counter);
update_led_pattern(LED_WIFI_GPIO, s_led_state.wifi_led, s_led_state.blink_counter);
update_led_pattern(LED_SCAN_GPIO, s_led_state.scan_led, s_led_state.blink_counter);
}
void led_manager_set_ethernet_activity(bool active)
{
s_led_state.ethernet_active = active;
if (active) {
led_manager_set_state(LED_ETHERNET_GPIO, LED_STATE_ON);
} else {
led_manager_set_state(LED_ETHERNET_GPIO, LED_STATE_OFF);
}
}
void led_manager_set_wifi_activity(bool active)
{
s_led_state.wifi_active = active;
if (active) {
led_manager_set_state(LED_WIFI_GPIO, LED_STATE_BLINK_SLOW);
} else {
led_manager_set_state(LED_WIFI_GPIO, LED_STATE_OFF);
}
}
void led_manager_set_scan_activity(bool active)
{
s_led_state.scan_active = active;
if (active) {
led_manager_set_state(LED_SCAN_GPIO, LED_STATE_BLINK_FAST);
} else {
led_manager_set_state(LED_SCAN_GPIO, LED_STATE_OFF);
}
}
// Internal helper functions
static void led_timer_callback(TimerHandle_t timer)
{
s_led_state.blink_counter++;
led_manager_update();
}
static void update_led_pattern(int gpio_num, led_state_t state, uint32_t counter)
{
int level = 0;
switch (state) {
case LED_STATE_OFF:
level = 0;
break;
case LED_STATE_ON:
level = 1;
break;
case LED_STATE_BLINK_SLOW:
level = (counter % 10) < 5 ? 1 : 0; // 0.5Hz blink
break;
case LED_STATE_BLINK_FAST:
level = (counter % 4) < 2 ? 1 : 0; // 2Hz blink
break;
case LED_STATE_PULSE:
level = (counter % 20) < 2 ? 1 : 0; // Brief pulse every 2 seconds
break;
default:
level = 0;
break;
}
gpio_set_level(gpio_num, level);
}

332
src/log_manager.c Normal file
View File

@@ -0,0 +1,332 @@
#include "log_manager.h"
#include "sd_manager.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_timer.h"
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
static const char *TAG = "LOG_MANAGER";
// Log levels
static const char* LOG_LEVEL_STRINGS[] = {
"DEBUG",
"INFO",
"WARN",
"ERROR",
"CRITICAL"
};
// Log file paths
#define LOG_FILE_PATH "/logs/system.log"
#define LOG_ARCHIVE_PATH "/logs/archive/"
#define MAX_LOG_SIZE (1024 * 1024) // 1MB
#define MAX_ARCHIVE_FILES 10
static bool s_log_manager_initialized = false;
static SemaphoreHandle_t s_log_mutex = NULL;
esp_err_t log_manager_init(void)
{
ESP_LOGI(TAG, "Initializing log manager");
// Create mutex for thread safety
s_log_mutex = xSemaphoreCreateMutex();
if (s_log_mutex == NULL) {
ESP_LOGE(TAG, "Failed to create log mutex");
return ESP_ERR_NO_MEM;
}
// Create logs directory if it doesn't exist
esp_err_t ret = sd_manager_mkdir("/logs");
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
ESP_LOGW(TAG, "Failed to create logs directory: %s", esp_err_to_name(ret));
}
// Create archive directory
ret = sd_manager_mkdir("/logs/archive");
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
ESP_LOGW(TAG, "Failed to create archive directory: %s", esp_err_to_name(ret));
}
s_log_manager_initialized = true;
ESP_LOGI(TAG, "Log manager initialized successfully");
return ESP_OK;
}
esp_err_t log_manager_deinit(void)
{
if (s_log_mutex != NULL) {
vSemaphoreDelete(s_log_mutex);
s_log_mutex = NULL;
}
s_log_manager_initialized = false;
ESP_LOGI(TAG, "Log manager deinitialized");
return ESP_OK;
}
static esp_err_t log_manager_write_entry(log_level_t level, const char* tag, const char* message)
{
if (!s_log_manager_initialized || s_log_mutex == NULL) {
return ESP_ERR_INVALID_STATE;
}
if (xSemaphoreTake(s_log_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
ESP_LOGW(TAG, "Failed to acquire log mutex");
return ESP_ERR_TIMEOUT;
}
// Get current timestamp
int64_t timestamp = esp_timer_get_time() / 1000; // Convert to milliseconds
time_t now = time(NULL);
struct tm *timeinfo = localtime(&now);
// Format log entry
char log_entry[512];
int len = snprintf(log_entry, sizeof(log_entry),
"[%04d-%02d-%02d %02d:%02d:%02d.%03lld] [%s] [%s]: %s\n",
timeinfo->tm_year + 1900,
timeinfo->tm_mon + 1,
timeinfo->tm_mday,
timeinfo->tm_hour,
timeinfo->tm_min,
timeinfo->tm_sec,
timestamp % 1000,
LOG_LEVEL_STRINGS[level],
tag,
message
);
if (len >= sizeof(log_entry)) {
len = sizeof(log_entry) - 1;
log_entry[len] = '\0';
}
// Append to log file
esp_err_t ret = sd_manager_append_file(LOG_FILE_PATH, log_entry, len);
xSemaphoreGive(s_log_mutex);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "Failed to write log entry: %s", esp_err_to_name(ret));
}
return ret;
}
esp_err_t log_manager_log(log_level_t level, const char* tag, const char* format, ...)
{
if (!s_log_manager_initialized) {
return ESP_ERR_INVALID_STATE;
}
va_list args;
va_start(args, format);
char message[256];
int len = vsnprintf(message, sizeof(message), format, args);
va_end(args);
if (len >= sizeof(message)) {
len = sizeof(message) - 1;
message[len] = '\0';
}
return log_manager_write_entry(level, tag, message);
}
esp_err_t log_manager_log_debug(const char* tag, const char* format, ...)
{
va_list args;
va_start(args, format);
char message[256];
int len = vsnprintf(message, sizeof(message), format, args);
va_end(args);
if (len >= sizeof(message)) {
len = sizeof(message) - 1;
message[len] = '\0';
}
return log_manager_write_entry(LOG_LEVEL_DEBUG, tag, message);
}
esp_err_t log_manager_log_info(const char* tag, const char* format, ...)
{
va_list args;
va_start(args, format);
char message[256];
int len = vsnprintf(message, sizeof(message), format, args);
va_end(args);
if (len >= sizeof(message)) {
len = sizeof(message) - 1;
message[len] = '\0';
}
return log_manager_write_entry(LOG_LEVEL_INFO, tag, message);
}
esp_err_t log_manager_log_warn(const char* tag, const char* format, ...)
{
va_list args;
va_start(args, format);
char message[256];
int len = vsnprintf(message, sizeof(message), format, args);
va_end(args);
if (len >= sizeof(message)) {
len = sizeof(message) - 1;
message[len] = '\0';
}
return log_manager_write_entry(LOG_LEVEL_WARN, tag, message);
}
esp_err_t log_manager_log_error(const char* tag, const char* format, ...)
{
va_list args;
va_start(args, format);
char message[256];
int len = vsnprintf(message, sizeof(message), format, args);
va_end(args);
if (len >= sizeof(message)) {
len = sizeof(message) - 1;
message[len] = '\0';
}
return log_manager_write_entry(LOG_LEVEL_ERROR, tag, message);
}
esp_err_t log_manager_log_critical(const char* tag, const char* format, ...)
{
va_list args;
va_start(args, format);
char message[256];
int len = vsnprintf(message, sizeof(message), format, args);
va_end(args);
if (len >= sizeof(message)) {
len = sizeof(message) - 1;
message[len] = '\0';
}
return log_manager_write_entry(LOG_LEVEL_CRITICAL, tag, message);
}
esp_err_t log_manager_rotate_logs(void)
{
if (!s_log_manager_initialized || s_log_mutex == NULL) {
return ESP_ERR_INVALID_STATE;
}
if (xSemaphoreTake(s_log_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
// Check current log file size
size_t file_size = 0;
esp_err_t ret = sd_manager_get_file_size(LOG_FILE_PATH, &file_size);
if (ret == ESP_OK && file_size > MAX_LOG_SIZE) {
ESP_LOGI(TAG, "Log file size (%zu) exceeds limit (%d), rotating logs", file_size, MAX_LOG_SIZE);
// Generate archive filename with timestamp
time_t now = time(NULL);
struct tm *timeinfo = localtime(&now);
char archive_filename[64];
snprintf(archive_filename, sizeof(archive_filename),
"/logs/archive/system_%04d%02d%02d_%02d%02d%02d.log",
timeinfo->tm_year + 1900,
timeinfo->tm_mon + 1,
timeinfo->tm_mday,
timeinfo->tm_hour,
timeinfo->tm_min,
timeinfo->tm_sec
);
// Move current log to archive
ret = sd_manager_rename_file(LOG_FILE_PATH, archive_filename);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "Failed to archive log file: %s", esp_err_to_name(ret));
} else {
ESP_LOGI(TAG, "Log file archived to %s", archive_filename);
// Clean up old archive files
log_manager_cleanup_old_logs();
}
}
xSemaphoreGive(s_log_mutex);
return ESP_OK;
}
esp_err_t log_manager_cleanup_old_logs(void)
{
// This is a simplified implementation
// In a full implementation, you would list files in the archive directory
// and delete the oldest ones to keep only MAX_ARCHIVE_FILES
ESP_LOGI(TAG, "Log cleanup completed");
return ESP_OK;
}
esp_err_t log_manager_get_logs(char** logs, size_t* logs_size)
{
if (!s_log_manager_initialized) {
return ESP_ERR_INVALID_STATE;
}
if (logs == NULL || logs_size == NULL) {
return ESP_ERR_INVALID_ARG;
}
char buffer[8192];
size_t bytes_read = 0;
esp_err_t ret = sd_manager_read_file(LOG_FILE_PATH, buffer, sizeof(buffer), &bytes_read);
if (ret == ESP_OK) {
*logs = malloc(bytes_read + 1);
if (*logs) {
memcpy(*logs, buffer, bytes_read);
(*logs)[bytes_read] = '\0';
*logs_size = bytes_read;
} else {
ESP_LOGE(TAG, "Failed to allocate memory for logs");
ret = ESP_ERR_NO_MEM;
}
} else {
ESP_LOGW(TAG, "Failed to read log file: %s", esp_err_to_name(ret));
}
return ret;
}
esp_err_t log_manager_clear_logs(void)
{
if (!s_log_manager_initialized) {
return ESP_ERR_INVALID_STATE;
}
if (xSemaphoreTake(s_log_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
esp_err_t ret = sd_manager_delete_file(LOG_FILE_PATH);
xSemaphoreGive(s_log_mutex);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "Logs cleared successfully");
}
return ret;
}

493
src/main.cpp Normal file
View File

@@ -0,0 +1,493 @@
/**
* ESP32-P4-NANO Network Auditing Multi-Tool
* Main firmware entry point with dual-core architecture
*
* Core 0 (ESP32-P4): Ethernet network operations, scanning, monitoring
* Core 1 (ESP32-C6): Wi-Fi Access Point, web server, user interface
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/event_groups.h>
#include <freertos/semphr.h>
#include <esp_system.h>
#include <esp_log.h>
#include <esp_err.h>
#include <esp_event.h>
#include <nvs_flash.h>
#include <driver/gpio.h>
#include <esp_timer.h>
// Network interface headers
#include <esp_netif.h>
#include <esp_eth.h>
#include <esp_wifi.h>
// SD card and file system
#include <esp_vfs_fat.h>
#include <sdmmc_cmd.h>
// HTTP server
#include <esp_http_server.h>
// Custom component headers
#include "network_manager.h"
#include "web_server.h"
#include "sd_manager.h"
#include "scanner_engine.h"
#include "security_manager.h"
#include "led_manager.h"
#include "ota_manager.h"
#include "config_manager.h"
#include "log_manager.h"
static const char *TAG = "MAIN";
// System status and synchronization
static EventGroupHandle_t system_events;
// Event bits for system coordination
#define SYSTEM_INIT_BIT BIT0
#define ETHERNET_READY_BIT BIT1
#define WIFI_AP_READY_BIT BIT2
#define SD_CARD_READY_BIT BIT3
#define WEB_SERVER_READY_BIT BIT4
#define SCANNER_READY_BIT BIT5
// Status LED GPIO (customize based on your board)
#define STATUS_LED_GPIO GPIO_NUM_2
#define ERROR_LED_GPIO GPIO_NUM_4
// Debug configuration
static bool s_debug_enabled = false;
static bool s_debug_matrix_enabled = false;
/**
* Enable comprehensive debugging based on web research best practices
*/
static void enable_debugging(void) {
// Enable debug output for all components
s_debug_enabled = true;
s_debug_matrix_enabled = true;
// Configure network manager debugging
network_manager_set_debug(true);
network_manager_set_matrix_debug(true);
ESP_LOGI(TAG, "Comprehensive debugging enabled");
ESP_LOGI(TAG, "Debug features: network events, matrix scanning, error tracking");
}
/**
* Initialize system components with enhanced error handling
*/
static esp_err_t initialize_system_components(void) {
esp_err_t ret = ESP_OK;
ESP_LOGI(TAG, "Initializing system components with enhanced debugging");
// Initialize NVS
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, "NVS initialized successfully");
// Initialize event loop
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_LOGI(TAG, "Event loop created successfully");
// Initialize SD card manager
ret = sd_manager_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize SD card manager: %s", esp_err_to_name(ret));
return ret;
}
ESP_LOGI(TAG, "SD card manager initialized successfully");
xEventGroupSetBits(system_events, SD_CARD_READY_BIT);
// Initialize log manager
ret = log_manager_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize log manager: %s", esp_err_to_name(ret));
return ret;
}
ESP_LOGI(TAG, "Log manager initialized successfully");
// Initialize configuration manager
ret = config_manager_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize config manager: %s", esp_err_to_name(ret));
return ret;
}
ESP_LOGI(TAG, "Configuration manager initialized successfully");
// Initialize LED manager
ret = led_manager_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize LED manager: %s", esp_err_to_name(ret));
return ret;
}
ESP_LOGI(TAG, "LED manager initialized successfully");
// Initialize security manager
ret = security_manager_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize security manager: %s", esp_err_to_name(ret));
return ret;
}
ESP_LOGI(TAG, "Security manager initialized successfully");
// Initialize scanner engine
ret = scanner_engine_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize scanner engine: %s", esp_err_to_name(ret));
return ret;
}
ESP_LOGI(TAG, "Scanner engine initialized successfully");
// Initialize network interfaces
ret = network_manager_init_ethernet();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize Ethernet: %s", esp_err_to_name(ret));
return ret;
}
ESP_LOGI(TAG, "Ethernet interface initialized successfully");
xEventGroupSetBits(system_events, ETHERNET_READY_BIT);
ret = network_manager_init_wifi_ap();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize WiFi AP: %s", esp_err_to_name(ret));
return ret;
}
ESP_LOGI(TAG, "WiFi AP interface initialized successfully");
xEventGroupSetBits(system_events, WIFI_AP_READY_BIT);
// Initialize web server
ret = web_server_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize web server: %s", esp_err_to_name(ret));
return ret;
}
ESP_LOGI(TAG, "Web server initialized successfully");
xEventGroupSetBits(system_events, WEB_SERVER_READY_BIT);
// Initialize OTA manager
ret = ota_manager_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize OTA manager: %s", esp_err_to_name(ret));
return ret;
}
ESP_LOGI(TAG, "OTA manager initialized successfully");
ESP_LOGI(TAG, "All system components initialized successfully");
return ESP_OK;
}
/**
* System monitoring task with enhanced debugging
*/
static void system_monitor_task(void *pvParameters) {
ESP_LOGI(TAG, "System monitor task started");
while (1) {
// Update network statistics
network_manager_update_stats();
// Monitor system health
network_stats_t eth_stats, wifi_stats;
if (network_manager_get_stats(NETIF_TYPE_ETHERNET, &eth_stats) == ESP_OK) {
if (s_debug_enabled) {
ESP_LOGD(TAG, "Ethernet: link=%s, rx_pkts=%lu, tx_pkts=%lu",
eth_stats.link_up ? "UP" : "DOWN",
eth_stats.rx_packets, eth_stats.tx_packets);
}
}
if (network_manager_get_stats(NETIF_TYPE_WIFI_AP, &wifi_stats) == ESP_OK) {
if (s_debug_enabled) {
ESP_LOGD(TAG, "WiFi AP: link=%s, rx_pkts=%lu, tx_pkts=%lu",
wifi_stats.link_up ? "UP" : "DOWN",
wifi_stats.rx_packets, wifi_stats.tx_packets);
}
}
// Rotate logs periodically
log_manager_rotate_logs();
// Update LED status based on system state
led_manager_set_status(LED_STATUS_SYSTEM_ERROR);
vTaskDelay(pdMS_TO_TICKS(5000)); // 5 second interval
}
}
/**
* Debug information task
*/
static void debug_info_task(void *pvParameters) {
ESP_LOGI(TAG, "Debug info task started");
while (1) {
if (s_debug_enabled) {
// Print system information
ESP_LOGI(TAG, "=== System Debug Information ===");
ESP_LOGI(TAG, "Free heap: %lu bytes", esp_get_free_heap_size());
ESP_LOGI(TAG, "Minimum free heap: %lu bytes", esp_get_minimum_free_heap_size());
ESP_LOGI(TAG, "Uptime: %lld seconds", esp_timer_get_time() / 1000000);
// Print network status
network_stats_t eth_stats, wifi_stats;
if (network_manager_get_stats(NETIF_TYPE_ETHERNET, &eth_stats) == ESP_OK) {
ESP_LOGI(TAG, "Ethernet: link=%s, rx_pkts=%lu, tx_pkts=%lu, rx_err=%lu, tx_err=%lu",
eth_stats.link_up ? "UP" : "DOWN",
eth_stats.rx_packets, eth_stats.tx_packets,
eth_stats.rx_errors, eth_stats.tx_errors);
}
if (network_manager_get_stats(NETIF_TYPE_WIFI_AP, &wifi_stats) == ESP_OK) {
ESP_LOGI(TAG, "WiFi AP: link=%s, rx_pkts=%lu, tx_pkts=%lu, rx_err=%lu, tx_err=%lu",
wifi_stats.link_up ? "UP" : "DOWN",
wifi_stats.rx_packets, wifi_stats.tx_packets,
wifi_stats.rx_errors, wifi_stats.tx_errors);
}
ESP_LOGI(TAG, "=== End Debug Information ===");
}
vTaskDelay(pdMS_TO_TICKS(30000)); // 30 second interval
}
}
/**
* ============================================================================
* CORE 0 TASK: ETHERNET NETWORK OPERATIONS
* ============================================================================
*
* Handles all target network interactions:
* - Ethernet interface management (LAN8720 RMII PHY)
* - Network scanning (ARP, port, ICMP)
* - Passive monitoring (DNS, DHCP)
* - Packet injection and analysis
* - Real-time network reconnaissance
*
* This task runs on ESP32-P4 core and is isolated from Wi-Fi operations
* to maintain air-gapped network segmentation.
*/
void ethernet_core_task(void *pvParameters) {
ESP_LOGI(TAG, "Starting Ethernet Core Task on Core %d", xPortGetCoreID());
// Wait for system initialization
xEventGroupWaitBits(system_events, SYSTEM_INIT_BIT, false, true, portMAX_DELAY);
// Initialize Ethernet interface
esp_err_t ret = network_manager_init_ethernet();
if (ret == ESP_OK) {
xEventGroupSetBits(system_events, ETHERNET_READY_BIT);
ESP_LOGI(TAG, "Ethernet interface initialized successfully");
// Initialize network scanner engine
ret = scanner_engine_init();
if (ret == ESP_OK) {
xEventGroupSetBits(system_events, SCANNER_READY_BIT);
ESP_LOGI(TAG, "Scanner engine initialized successfully");
} else {
ESP_LOGE(TAG, "Failed to initialize scanner engine: %s", esp_err_to_name(ret));
}
} else {
ESP_LOGE(TAG, "Failed to initialize Ethernet interface: %s", esp_err_to_name(ret));
}
// Main ethernet core loop
while (1) {
// Monitor network status
network_manager_monitor_ethernet();
// Process pending scan requests
scanner_engine_process_queue();
// Update network statistics
network_manager_update_stats();
vTaskDelay(pdMS_TO_TICKS(100)); // 100ms cycle
}
}
/**
* ============================================================================
* CORE 1 TASK: WI-FI ACCESS POINT AND WEB INTERFACE
* ============================================================================
*
* Handles operator interface:
* - Wi-Fi Access Point (ESP32-C6) - isolated from Ethernet
* - HTTP web server with REST API
* - File serving from SD card (FAT32)
* - User authentication and security
* - Real-time status updates
*
* This task runs on ESP32-C6 core and provides the operator interface
* while maintaining complete network segmentation from target network.
*/
void wifi_core_task(void *pvParameters) {
ESP_LOGI(TAG, "Starting Wi-Fi Core Task on Core %d", xPortGetCoreID());
// Wait for system initialization and SD card
EventBits_t bits = xEventGroupWaitBits(system_events,
SYSTEM_INIT_BIT | SD_CARD_READY_BIT,
false, true, portMAX_DELAY);
if (bits & (SYSTEM_INIT_BIT | SD_CARD_READY_BIT)) {
// Initialize Wi-Fi Access Point
esp_err_t ret = network_manager_init_wifi_ap();
if (ret == ESP_OK) {
xEventGroupSetBits(system_events, WIFI_AP_READY_BIT);
ESP_LOGI(TAG, "Wi-Fi Access Point initialized successfully");
// Initialize web server
ret = web_server_init();
if (ret == ESP_OK) {
xEventGroupSetBits(system_events, WEB_SERVER_READY_BIT);
ESP_LOGI(TAG, "Web server initialized successfully");
} else {
ESP_LOGE(TAG, "Failed to initialize web server: %s", esp_err_to_name(ret));
}
} else {
ESP_LOGE(TAG, "Failed to initialize Wi-Fi AP: %s", esp_err_to_name(ret));
}
}
// Main wifi core loop
while (1) {
// Monitor Wi-Fi status
network_manager_monitor_wifi();
// Process web server requests (handled by HTTP server callbacks)
// Update system status
web_server_update_status();
vTaskDelay(pdMS_TO_TICKS(1000)); // 1 second cycle
}
}
/**
* ============================================================================
* STATUS LED MANAGEMENT TASK
* ============================================================================
*
* Provides visual feedback for system status:
* - Solid green: System fully operational
* - Blinking green: System initializing
* - Blinking red: Error state
*
* Uses GPIO pins for status indication on ESP32-P4-NANO board.
*/
void status_led_task(void *pvParameters) {
gpio_config_t io_conf{}; // zero-initialize
io_conf.pin_bit_mask = (1ULL << STATUS_LED_GPIO) | (1ULL << ERROR_LED_GPIO);
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.intr_type = GPIO_INTR_DISABLE;
gpio_config(&io_conf);
while (1) {
EventBits_t bits = xEventGroupGetBits(system_events);
if (bits & WEB_SERVER_READY_BIT && bits & ETHERNET_READY_BIT) {
// System fully operational - solid green
gpio_set_level(STATUS_LED_GPIO, 1);
gpio_set_level(ERROR_LED_GPIO, 0);
vTaskDelay(pdMS_TO_TICKS(1000));
} else if (bits & SYSTEM_INIT_BIT) {
// System initializing - blinking green
gpio_set_level(STATUS_LED_GPIO, 1);
vTaskDelay(pdMS_TO_TICKS(250));
gpio_set_level(STATUS_LED_GPIO, 0);
vTaskDelay(pdMS_TO_TICKS(250));
} else {
// Error state - blinking red
gpio_set_level(ERROR_LED_GPIO, 1);
vTaskDelay(pdMS_TO_TICKS(500));
gpio_set_level(ERROR_LED_GPIO, 0);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
}
/**
* ============================================================================
* MAIN APPLICATION ENTRY POINT
* ============================================================================
*
* Initializes the ESP32-P4-NANO network auditing multi-tool:
* - System initialization (NVS, network stack, event loop)
* - Component initialization (security, LED, OTA, config, log managers)
* - SD card initialization for file storage
* - Dual-core task creation with CPU affinity
* - Event group synchronization setup
*
* Creates air-gapped dual-interface architecture:
* - Core 0: Ethernet operations (target network)
* - Core 1: Wi-Fi AP operations (operator interface)
*/
extern "C" void app_main(void) {
ESP_LOGI(TAG, "ESP32-P4 Network Auditing Tool Starting...");
ESP_LOGI(TAG, "Version: 1.0.0");
ESP_LOGI(TAG, "Build Date: %s %s", __DATE__, __TIME__);
// Create system event group
system_events = xEventGroupCreate();
if (system_events == NULL) {
ESP_LOGE(TAG, "Failed to create system event group");
return;
}
// Enable comprehensive debugging
enable_debugging();
// Initialize system components
esp_err_t ret = initialize_system_components();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "System initialization failed: %s", esp_err_to_name(ret));
return;
}
// Create system monitoring task
xTaskCreate(system_monitor_task, "system_monitor", 4096, NULL, 5, NULL);
// Create debug information task
xTaskCreate(debug_info_task, "debug_info", 4096, NULL, 3, NULL);
ESP_LOGI(TAG, "ESP32-P4 Network Auditing Tool started successfully");
ESP_LOGI(TAG, "System ready for network auditing operations");
// Main application loop
while (1) {
// Check system status
EventBits_t bits = xEventGroupGetBits(system_events);
if (bits & ETHERNET_READY_BIT) {
ESP_LOGD(TAG, "Ethernet interface ready");
}
if (bits & WIFI_AP_READY_BIT) {
ESP_LOGD(TAG, "WiFi AP interface ready");
}
if (bits & SD_CARD_READY_BIT) {
ESP_LOGD(TAG, "SD card ready");
}
if (bits & WEB_SERVER_READY_BIT) {
ESP_LOGD(TAG, "Web server ready");
}
vTaskDelay(pdMS_TO_TICKS(10000)); // 10 second interval
}
}

463
src/network_manager.c Normal file
View File

@@ -0,0 +1,463 @@
/**
* Network Manager Implementation
* Handles dual interface management for ESP32-P4-NANO:
* - ESP32-P4: Ethernet interface (LAN8720 RMII PHY)
* - ESP32-C6: Wi-Fi Access Point
*
* Enhanced with comprehensive debugging and error handling
*/
#include "network_manager.h"
#include <esp_log.h>
#include <esp_netif.h>
#include <esp_eth.h>
// #include <esp_wifi.h> // ESP32-P4 doesn't have built-in WiFi
#include <esp_event.h>
#include <esp_system.h>
#include <driver/gpio.h>
#include <lwip/netdb.h>
#include <lwip/sockets.h>
#include <string.h>
#include <esp_err.h>
#include <esp_check.h>
#include "esp_hosted_fg.h"
#include <esp_mac.h>
// -----------------------------------------------------------------------------
// ESP32-P4-NANO with ESP32-C6 WiFi Configuration
// The ESP32-P4 doesn't have built-in WiFi, it uses the ESP32-C6 module
// Enhanced with comprehensive debugging and error handling
static const char *TAG = "NETWORK_MANAGER";
// Network interface handles
static esp_netif_t *ethernet_netif = NULL;
static esp_netif_t *wifi_ap_netif = NULL;
// Ethernet driver handles
static esp_eth_handle_t eth_handle = NULL;
static esp_eth_mac_t *mac = NULL;
static esp_eth_phy_t *phy = NULL;
// Network statistics
static network_stats_t ethernet_stats = {0};
static network_stats_t wifi_stats = {0};
// Event group for network status
static EventGroupHandle_t network_event_group;
static const int ETHERNET_CONNECTED_BIT = BIT0;
// static const int WIFI_AP_STARTED_BIT = BIT1; // Not used since ESP32-P4 has no WiFi
// Debug configuration
static bool s_debug_enabled = false;
static bool s_debug_matrix_enabled = false;
// Forward declarations
static void ethernet_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data);
static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data);
static void ip_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data);
/**
* Enable/disable debug output
*/
esp_err_t network_manager_set_debug(bool enable) {
s_debug_enabled = enable;
ESP_LOGI(TAG, "Network manager debug %s", enable ? "enabled" : "disabled");
return ESP_OK;
}
/**
* Enable/disable matrix debug output
*/
esp_err_t network_manager_set_matrix_debug(bool enable) {
s_debug_matrix_enabled = enable;
ESP_LOGI(TAG, "Network manager matrix debug %s", enable ? "enabled" : "disabled");
return ESP_OK;
}
/**
* Debug print function (only when debug is enabled)
*/
static void debug_print(const char* format, ...) {
if (!s_debug_enabled) {
return;
}
va_list args;
va_start(args, format);
esp_log_writev(ESP_LOG_DEBUG, TAG, format, args);
va_end(args);
}
/**
* ============================================================================
* INITIALIZE ETHERNET INTERFACE (IP101GRI RMII PHY)
* ============================================================================
*
* Sets up the ESP32-P4 Ethernet interface for target network operations:
* - Configures IP101GRI RMII PHY with GPIO pins
* - Initializes MAC and PHY drivers
* - Sets up network interface and event handlers
* - Establishes link monitoring
*
* GPIO Configuration:
* - MDC: GPIO 31, MDIO: GPIO 52
* - PHY Reset: GPIO 51, PHY Address: 0
* - RMII Interface: TX/RX data pins
*/
esp_err_t network_manager_init_ethernet(void) {
esp_err_t ret = ESP_OK;
ESP_LOGI(TAG, "Initializing Ethernet interface (IP101GRI RMII PHY)");
debug_print("Starting Ethernet initialization sequence");
// Create network event group if not exists
if (network_event_group == NULL) {
network_event_group = xEventGroupCreate();
if (network_event_group == NULL) {
ESP_LOGE(TAG, "Failed to create network event group");
return ESP_ERR_NO_MEM;
}
debug_print("Network event group created successfully");
}
// Initialize Ethernet netif
esp_netif_config_t netif_config = ESP_NETIF_DEFAULT_ETH();
ethernet_netif = esp_netif_new(&netif_config);
if (ethernet_netif == NULL) {
ESP_LOGE(TAG, "Failed to create Ethernet netif");
return ESP_FAIL;
}
debug_print("Ethernet netif created successfully");
// Initialize MAC and PHY
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
esp32_emac_config.smi_gpio.mdc_num = ETH_MDC_GPIO;
esp32_emac_config.smi_gpio.mdio_num = ETH_MDIO_GPIO;
esp32_emac_config.interface = EMAC_DATA_INTERFACE_RMII;
// The following GPIOs are not part of the esp32_emac_config for p4
// and need to be configured separately.
// esp32_emac_config.rmii_config.tx_en_gpio = ETH_TX_EN_GPIO;
// esp32_emac_config.rmii_config.tx_data_gpio[0] = ETH_TXD0_GPIO;
// esp32_emac_config.rmii_config.tx_data_gpio[1] = ETH_TXD1_GPIO;
// esp32_emac_config.rmii_config.rx_data_gpio[0] = ETH_RXD0_GPIO;
// esp32_emac_config.rmii_config.rx_data_gpio[1] = ETH_RXD1_GPIO;
// esp32_emac_config.rmii_config.crs_dv_gpio = ETH_CRS_DV_GPIO;
// esp32_emac_config.rmii_config.ref_clk_gpio = ETH_REF_CLK_GPIO;
mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config);
if (mac == NULL) {
ESP_LOGE(TAG, "Failed to create MAC instance");
return ESP_FAIL;
}
debug_print("MAC instance created successfully");
// Initialize PHY
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
phy_config.phy_addr = ETH_PHY_ADDR;
phy_config.reset_gpio_num = ETH_PHY_RST_GPIO;
// Create PHY instance for IP101
phy = esp_eth_phy_new_ip101(&phy_config);
if (phy == NULL) {
ESP_LOGE(TAG, "Failed to create PHY instance");
return ESP_FAIL;
}
debug_print("PHY instance created successfully");
// Install Ethernet driver
esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy);
ret = esp_eth_driver_install(&eth_config, &eth_handle);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to install Ethernet driver: %s", esp_err_to_name(ret));
return ret;
}
debug_print("Ethernet driver installed successfully");
// Attach Ethernet driver to netif
ret = esp_netif_attach(ethernet_netif, esp_eth_new_netif_glue(eth_handle));
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to attach Ethernet to netif: %s", esp_err_to_name(ret));
return ret;
}
debug_print("Ethernet driver attached to netif successfully");
// Register event handlers
ret = esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &ethernet_event_handler, NULL);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to register Ethernet event handler: %s", esp_err_to_name(ret));
return ret;
}
debug_print("Ethernet event handler registered successfully");
ret = esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &ip_event_handler, NULL);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to register IP event handler: %s", esp_err_to_name(ret));
return ret;
}
debug_print("IP event handler registered successfully");
// Start Ethernet driver
ret = esp_eth_start(eth_handle);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to start Ethernet: %s", esp_err_to_name(ret));
return ret;
}
debug_print("Ethernet driver started successfully");
ESP_LOGI(TAG, "Ethernet interface initialized successfully");
return ESP_OK;
}
/**
* Initialize Wi-Fi Access Point using ESP-Hosted-FG
* ESP32-P4 doesn't have built-in WiFi - requires external ESP32-C6 module
* This implementation uses ESP-Hosted-FG for proper connectivity
*/
esp_err_t network_manager_init_wifi_ap(void) {
ESP_LOGI(TAG, "ESP32-P4 doesn't have built-in WiFi - using Ethernet-only mode");
debug_print("WiFi AP initialization skipped - Ethernet only mode");
// ESP32-P4 doesn't have WiFi, so we'll just create a dummy netif
// In a real implementation, you would use ESP-Hosted-FG or external WiFi module
wifi_ap_netif = ethernet_netif; // Reuse ethernet netif for compatibility
// Initialize ESP-Hosted-FG component (mock implementation)
esp_err_t ret = esp_hosted_fg_init();
if (ret != ESP_OK) {
ESP_LOGW(TAG, "ESP-Hosted-FG init failed: %s", esp_err_to_name(ret));
// Continue anyway since this is a mock
}
// Mock WiFi AP start
ret = esp_hosted_fg_wifi_ap_start();
if (ret != ESP_OK) {
ESP_LOGW(TAG, "ESP-Hosted-FG WiFi AP start failed: %s", esp_err_to_name(ret));
// Continue anyway since this is a mock
}
wifi_stats.link_up = true;
debug_print("WiFi AP initialization completed (mock mode)");
return ESP_OK;
}
/**
* Monitor Ethernet interface status with enhanced debugging
*/
void network_manager_monitor_ethernet(void) {
if (ethernet_netif == NULL) {
debug_print("Ethernet netif is NULL, skipping monitoring");
return;
}
// Update link status
bool previous_link_state = ethernet_stats.link_up;
ethernet_stats.link_up = esp_netif_is_netif_up(ethernet_netif);
// Debug output for link state changes
if (s_debug_matrix_enabled && previous_link_state != ethernet_stats.link_up) {
debug_print("Ethernet link state changed: %s -> %s",
previous_link_state ? "UP" : "DOWN",
ethernet_stats.link_up ? "UP" : "DOWN");
}
// Get interface statistics if available
// esp_netif_get_stats() is not available on ESP32-P4 yet skip stats
if (s_debug_enabled) {
debug_print("Ethernet monitoring: link=%s", ethernet_stats.link_up ? "UP" : "DOWN");
}
}
/**
* Monitor Wi-Fi AP status with enhanced debugging
*/
void network_manager_monitor_wifi(void) {
if (wifi_ap_netif == NULL) {
debug_print("WiFi AP netif is NULL, skipping monitoring");
return;
}
// Update interface statistics if available
// Stats API not available skipped for now
// Check AP status
// Skip station list retrieval until Wi-Fi remote is integrated
if (s_debug_enabled) {
debug_print("WiFi AP monitoring: link=%s", wifi_stats.link_up ? "UP" : "DOWN");
}
}
/**
* Update network statistics with debugging
*/
void network_manager_update_stats(void) {
debug_print("Updating network statistics");
network_manager_monitor_ethernet();
network_manager_monitor_wifi();
}
/**
* Get network statistics for specified interface
*/
esp_err_t network_manager_get_stats(network_interface_type_t type, network_stats_t *stats) {
if (stats == NULL) {
ESP_LOGE(TAG, "Invalid stats pointer");
return ESP_ERR_INVALID_ARG;
}
switch (type) {
case NETIF_TYPE_ETHERNET:
memcpy(stats, &ethernet_stats, sizeof(network_stats_t));
debug_print("Retrieved Ethernet stats: link=%s", stats->link_up ? "UP" : "DOWN");
break;
case NETIF_TYPE_WIFI_AP:
memcpy(stats, &wifi_stats, sizeof(network_stats_t));
debug_print("Retrieved WiFi AP stats: link=%s", stats->link_up ? "UP" : "DOWN");
break;
default:
ESP_LOGE(TAG, "Invalid network interface type: %d", type);
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
/**
* Get Ethernet interface handle
*/
esp_netif_t* network_manager_get_ethernet_netif(void) {
return ethernet_netif;
}
/**
* Get Wi-Fi AP interface handle
*/
esp_netif_t* network_manager_get_wifi_ap_netif(void) {
return wifi_ap_netif;
}
/**
* Check if Ethernet link is up
*/
bool network_manager_ethernet_link_up(void) {
return ethernet_stats.link_up;
}
/**
* Get Ethernet MAC address
*/
esp_err_t network_manager_get_ethernet_mac(uint8_t *mac) {
if (mac == NULL || ethernet_netif == NULL) {
ESP_LOGE(TAG, "Invalid parameters for get_ethernet_mac");
return ESP_ERR_INVALID_ARG;
}
esp_err_t ret = esp_netif_get_mac(ethernet_netif, mac);
if (ret == ESP_OK) {
debug_print("Ethernet MAC: %02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
return ret;
}
/**
* Get Wi-Fi AP MAC address
*/
esp_err_t network_manager_get_wifi_mac(uint8_t *mac) {
if (mac == NULL || wifi_ap_netif == NULL) {
ESP_LOGE(TAG, "Invalid parameters for get_wifi_mac");
return ESP_ERR_INVALID_ARG;
}
esp_err_t ret = esp_netif_get_mac(wifi_ap_netif, mac);
if (ret == ESP_OK) {
debug_print("WiFi AP MAC: %02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
return ret;
}
/**
* Ethernet event handler with enhanced debugging
*/
static void ethernet_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
uint8_t mac_addr[6] = {0};
esp_eth_handle_t eth_handle = *(esp_eth_handle_t*)event_data;
debug_print("Ethernet event: %ld", event_id);
switch (event_id) {
case ETHERNET_EVENT_CONNECTED:
esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr);
ESP_LOGI(TAG, "Ethernet Link Up");
ESP_LOGI(TAG, "Ethernet HW Addr %02x:%02x:%02x:%02x:%02x:%02x",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
ethernet_stats.link_up = true;
xEventGroupSetBits(network_event_group, ETHERNET_CONNECTED_BIT);
debug_print("Ethernet link established");
break;
case ETHERNET_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "Ethernet Link Down");
ethernet_stats.link_up = false;
xEventGroupClearBits(network_event_group, ETHERNET_CONNECTED_BIT);
debug_print("Ethernet link lost");
break;
case ETHERNET_EVENT_START:
ESP_LOGI(TAG, "Ethernet Started");
debug_print("Ethernet driver started");
break;
case ETHERNET_EVENT_STOP:
ESP_LOGI(TAG, "Ethernet Stopped");
ethernet_stats.link_up = false;
xEventGroupClearBits(network_event_group, ETHERNET_CONNECTED_BIT);
debug_print("Ethernet driver stopped");
break;
default:
debug_print("Unknown Ethernet event: %ld", event_id);
break;
}
}
/**
* Wi-Fi event handler with enhanced debugging
* Note: ESP32-P4 doesn't have built-in WiFi, so this is a stub
*/
static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
(void)arg; // Unused parameter
(void)event_base; // Unused parameter
(void)event_data; // Unused parameter
debug_print("WiFi event: %ld (stub - ESP32-P4 has no WiFi)", event_id);
// ESP32-P4 doesn't have WiFi, so we just log the event
ESP_LOGI(TAG, "WiFi event received: %ld (stub implementation)", event_id);
}
/**
* IP event handler with enhanced debugging
*/
static void ip_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
debug_print("IP event: %ld", event_id);
switch (event_id) {
case IP_EVENT_ETH_GOT_IP: {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
const esp_netif_ip_info_t *ip_info = &event->ip_info;
ESP_LOGI(TAG, "Ethernet Got IP Address");
ESP_LOGI(TAG, "~~~~~~~~~~~");
ESP_LOGI(TAG, "ETHIP:" IPSTR, IP2STR(&ip_info->ip));
ESP_LOGI(TAG, "ETHMASK:" IPSTR, IP2STR(&ip_info->netmask));
ESP_LOGI(TAG, "ETHGW:" IPSTR, IP2STR(&ip_info->gw));
ESP_LOGI(TAG, "~~~~~~~~~~~");
debug_print("Ethernet IP obtained: " IPSTR, IP2STR(&ip_info->ip));
break;
}
default:
debug_print("Unknown IP event: %ld", event_id);
break;
}
}

381
src/ota_manager.c Normal file
View File

@@ -0,0 +1,381 @@
/**
* OTA Manager - Firmware Update Management
* Handles Over-The-Air firmware updates via Wi-Fi
*
* This implementation provides secure OTA updates for the ESP32-P4-NANO
* network auditing tool, supporting both HTTP and HTTPS updates.
*/
#include "ota_manager.h"
#include "log_manager.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_ota_ops.h"
#include "esp_https_ota.h"
#include "esp_http_client.h"
#include "esp_system.h"
#include "esp_app_desc.h"
#include <string.h>
#include <stdio.h>
static const char *TAG = "OTA_MANAGER";
// OTA progress structure (using types from header)
static ota_progress_t s_ota_progress = {0};
static bool s_ota_manager_initialized = false;
static SemaphoreHandle_t s_ota_mutex = NULL;
// Forward declaration
static void ota_manager_set_error(const char* error_message);
// OTA URLs (configured via web interface)
static char s_ota_server_url[256] = "http://192.168.4.1:8080/firmware/";
static char s_ota_version_url[256] = "http://192.168.4.1:8080/version.json";
esp_err_t ota_manager_init(void)
{
ESP_LOGI(TAG, "Initializing OTA manager");
// Create mutex for thread safety
s_ota_mutex = xSemaphoreCreateMutex();
if (s_ota_mutex == NULL) {
ESP_LOGE(TAG, "Failed to create OTA mutex");
return ESP_ERR_NO_MEM;
}
// Initialize progress structure
memset(&s_ota_progress, 0, sizeof(ota_progress_t));
s_ota_progress.state = OTA_STATE_IDLE;
// Get current version
const esp_app_desc_t* app_desc = esp_app_get_description();
if (app_desc) {
strncpy(s_ota_progress.current_version, app_desc->version, sizeof(s_ota_progress.current_version) - 1);
}
s_ota_manager_initialized = true;
ESP_LOGI(TAG, "OTA manager initialized successfully");
ESP_LOGI(TAG, "Current firmware version: %s", s_ota_progress.current_version);
return ESP_OK;
}
esp_err_t ota_manager_deinit(void)
{
if (s_ota_mutex != NULL) {
vSemaphoreDelete(s_ota_mutex);
s_ota_mutex = NULL;
}
s_ota_manager_initialized = false;
ESP_LOGI(TAG, "OTA manager deinitialized");
return ESP_OK;
}
esp_err_t ota_manager_set_server_url(const char* server_url)
{
if (!s_ota_manager_initialized) {
return ESP_ERR_INVALID_STATE;
}
if (server_url == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (xSemaphoreTake(s_ota_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
strncpy(s_ota_server_url, server_url, sizeof(s_ota_server_url) - 1);
s_ota_server_url[sizeof(s_ota_server_url) - 1] = '\0';
// Update version URL
snprintf(s_ota_version_url, sizeof(s_ota_version_url), "%sversion.json", server_url);
xSemaphoreGive(s_ota_mutex);
log_manager_log_info(TAG, "OTA server URL updated to: %s", server_url);
return ESP_OK;
}
esp_err_t ota_manager_check_for_updates(void)
{
if (!s_ota_manager_initialized || s_ota_mutex == NULL) {
return ESP_ERR_INVALID_STATE;
}
if (xSemaphoreTake(s_ota_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
// Update state
s_ota_progress.state = OTA_STATE_CHECKING;
s_ota_progress.progress_percent = 0;
strcpy(s_ota_progress.status_message, "Checking for updates...");
xSemaphoreGive(s_ota_mutex);
// Create HTTP client for version check
esp_http_client_config_t config = {
.url = s_ota_version_url,
.timeout_ms = 10000,
};
esp_http_client_handle_t client = esp_http_client_init(&config);
if (client == NULL) {
log_manager_log_error(TAG, "Failed to create HTTP client for version check");
ota_manager_set_error("Failed to create HTTP client");
return ESP_ERR_NO_MEM;
}
esp_err_t ret = esp_http_client_perform(client);
if (ret != ESP_OK) {
log_manager_log_error(TAG, "HTTP request failed: %s", esp_err_to_name(ret));
ota_manager_set_error("Failed to connect to update server");
esp_http_client_cleanup(client);
return ret;
}
int status_code = esp_http_client_get_status_code(client);
if (status_code != 200) {
log_manager_log_error(TAG, "HTTP request failed with status: %d", status_code);
ota_manager_set_error("Server returned error status");
esp_http_client_cleanup(client);
return ESP_ERR_INVALID_RESPONSE;
}
int content_length = esp_http_client_get_content_length(client);
if (content_length <= 0 || content_length > 1024) {
log_manager_log_error(TAG, "Invalid content length: %d", content_length);
ota_manager_set_error("Invalid server response");
esp_http_client_cleanup(client);
return ESP_ERR_INVALID_SIZE;
}
// Read version information
char* buffer = malloc(content_length + 1);
if (buffer == NULL) {
log_manager_log_error(TAG, "Failed to allocate memory for version check");
ota_manager_set_error("Memory allocation failed");
esp_http_client_cleanup(client);
return ESP_ERR_NO_MEM;
}
int bytes_read = esp_http_client_read(client, buffer, content_length);
buffer[bytes_read] = '\0';
esp_http_client_cleanup(client);
// Parse version JSON (simplified - in production, use proper JSON parsing)
char* version_start = strstr(buffer, "\"version\":");
if (version_start) {
version_start += 11; // Skip "version":
char* version_end = strchr(version_start, '"');
if (version_end) {
int version_len = version_end - version_start;
if (version_len < sizeof(s_ota_progress.new_version)) {
strncpy(s_ota_progress.new_version, version_start, version_len);
s_ota_progress.new_version[version_len] = '\0';
// Check if new version is available
if (strcmp(s_ota_progress.new_version, s_ota_progress.current_version) != 0) {
s_ota_progress.update_available = true;
log_manager_log_info(TAG, "New firmware version available: %s", s_ota_progress.new_version);
xSemaphoreTake(s_ota_mutex, pdMS_TO_TICKS(1000));
s_ota_progress.state = OTA_STATE_IDLE;
s_ota_progress.progress_percent = 100;
strcpy(s_ota_progress.status_message, "Update available");
xSemaphoreGive(s_ota_mutex);
} else {
s_ota_progress.update_available = false;
log_manager_log_info(TAG, "Firmware is up to date");
xSemaphoreTake(s_ota_mutex, pdMS_TO_TICKS(1000));
s_ota_progress.state = OTA_STATE_IDLE;
s_ota_progress.progress_percent = 100;
strcpy(s_ota_progress.status_message, "Firmware is up to date");
xSemaphoreGive(s_ota_mutex);
}
}
}
}
free(buffer);
return ESP_OK;
}
esp_err_t ota_manager_start_update(const char* url)
{
if (!s_ota_manager_initialized || s_ota_mutex == NULL) {
return ESP_ERR_INVALID_STATE;
}
if (!s_ota_progress.update_available) {
return ESP_ERR_INVALID_STATE;
}
if (xSemaphoreTake(s_ota_mutex, pdMS_TO_TICKS(5000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
// Update state
s_ota_progress.state = OTA_STATE_DOWNLOADING;
s_ota_progress.progress_percent = 0;
s_ota_progress.bytes_downloaded = 0;
s_ota_progress.total_bytes = 0;
strcpy(s_ota_progress.status_message, "Starting firmware download...");
xSemaphoreGive(s_ota_mutex);
// Use provided URL or create default URL
char ota_url[512];
if (url != NULL && strlen(url) > 0) {
strncpy(ota_url, url, sizeof(ota_url) - 1);
ota_url[sizeof(ota_url) - 1] = '\0';
} else {
snprintf(ota_url, sizeof(ota_url), "%sfirmware_%s.bin", s_ota_server_url, s_ota_progress.new_version);
}
log_manager_log_info(TAG, "Starting OTA update from: %s", ota_url);
// Configure OTA
esp_http_client_config_t config = {
.url = ota_url,
.timeout_ms = 30000,
};
esp_https_ota_config_t ota_config = {
.http_config = &config,
.http_client_init_cb = NULL,
.bulk_flash_erase = true,
.partial_http_download = true,
};
esp_err_t ret = esp_https_ota(&ota_config);
if (ret != ESP_OK) {
log_manager_log_error(TAG, "OTA HTTPS failed: %s", esp_err_to_name(ret));
ota_manager_set_error("Failed to start OTA update");
return ret;
}
// Update progress
if (xSemaphoreTake(s_ota_mutex, pdMS_TO_TICKS(1000)) == pdTRUE) {
s_ota_progress.state = OTA_STATE_READY_TO_UPDATE;
s_ota_progress.progress_percent = 100;
strcpy(s_ota_progress.status_message, "Update ready to install");
xSemaphoreGive(s_ota_mutex);
}
log_manager_log_info(TAG, "OTA update completed successfully");
return ESP_OK;
}
esp_err_t ota_manager_install_update(void)
{
if (!s_ota_manager_initialized) {
return ESP_ERR_INVALID_STATE;
}
if (xSemaphoreTake(s_ota_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
if (s_ota_progress.state != OTA_STATE_READY_TO_UPDATE) {
xSemaphoreGive(s_ota_mutex);
return ESP_ERR_INVALID_STATE;
}
s_ota_progress.state = OTA_STATE_UPDATING;
strcpy(s_ota_progress.status_message, "Installing firmware update...");
xSemaphoreGive(s_ota_mutex);
log_manager_log_info(TAG, "Installing firmware update");
// Set boot partition to OTA partition
const esp_partition_t* running = esp_ota_get_running_partition();
esp_ota_img_states_t ota_state;
if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) {
if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) {
esp_ota_mark_app_valid_cancel_rollback();
}
}
// Restart to apply update
esp_restart();
return ESP_OK;
}
esp_err_t ota_manager_get_progress(ota_progress_t* progress)
{
if (!s_ota_manager_initialized) {
return ESP_ERR_INVALID_STATE;
}
if (progress == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (xSemaphoreTake(s_ota_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
memcpy(progress, &s_ota_progress, sizeof(ota_progress_t));
xSemaphoreGive(s_ota_mutex);
return ESP_OK;
}
esp_err_t ota_manager_cancel_update(void)
{
if (!s_ota_manager_initialized) {
return ESP_ERR_INVALID_STATE;
}
if (xSemaphoreTake(s_ota_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
s_ota_progress.state = OTA_STATE_IDLE;
s_ota_progress.progress_percent = 0;
strcpy(s_ota_progress.status_message, "Update cancelled");
xSemaphoreGive(s_ota_mutex);
log_manager_log_info(TAG, "OTA update cancelled by user");
return ESP_OK;
}
esp_err_t ota_manager_get_current_version(char* version)
{
if (!s_ota_manager_initialized) {
return ESP_ERR_INVALID_STATE;
}
if (version == NULL) {
return ESP_ERR_INVALID_ARG;
}
// Get current firmware version from app description
const esp_app_desc_t* app_desc = esp_app_get_description();
if (app_desc != NULL) {
strncpy(version, app_desc->version, 31);
version[31] = '\0';
} else {
strcpy(version, "unknown");
}
return ESP_OK;
}
static void ota_manager_set_error(const char* error_message)
{
if (xSemaphoreTake(s_ota_mutex, pdMS_TO_TICKS(1000)) == pdTRUE) {
s_ota_progress.state = OTA_STATE_ERROR;
strncpy(s_ota_progress.error_message, error_message, sizeof(s_ota_progress.error_message) - 1);
s_ota_progress.error_message[sizeof(s_ota_progress.error_message) - 1] = '\0';
xSemaphoreGive(s_ota_mutex);
}
}

677
src/scanner_engine.c Normal file
View File

@@ -0,0 +1,677 @@
#include "scanner_engine.h"
#include "sd_manager.h"
#include "network_manager.h"
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/semphr.h>
#include <lwip/ip_addr.h>
#include <lwip/netdb.h>
#include <lwip/sockets.h>
#include <lwip/raw.h>
#include <lwip/ip4.h>
#include <lwip/icmp.h>
#include <lwip/etharp.h>
#include <lwip/dns.h>
#include <lwip/dhcp.h>
#include <lwip/netif.h>
#include <lwip/ethip6.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include "esp_netif.h"
static const char *TAG = "SCANNER_ENGINE";
// Scanner state
static bool s_scanner_initialized = false;
static SemaphoreHandle_t s_scanner_mutex = NULL;
static discovered_device_t s_devices[MAX_DEVICES];
static size_t s_device_count = 0;
static scanner_stats_t s_stats = {0};
// DNS monitoring state
static bool s_dns_monitoring = false;
static TaskHandle_t s_dns_task = NULL;
// DHCP monitoring state
static bool s_dhcp_monitoring = false;
static TaskHandle_t s_dhcp_task = NULL;
// Packet capture state
static bool s_capture_running = false;
static TaskHandle_t s_capture_task = NULL;
// MAC vendor database
static mac_vendor_entry_t s_mac_vendors[MAX_MAC_VENDORS];
static size_t s_mac_vendor_count = 0;
// Function prototypes
static esp_err_t perform_arp_scan(const scan_config_t *config);
static esp_err_t perform_tcp_port_scan(const scan_config_t *config);
static esp_err_t perform_icmp_ping_sweep(const scan_config_t *config);
static esp_err_t add_discovered_device(const ip4_addr_t *ip, const uint8_t *mac, const char *hostname);
static esp_err_t resolve_hostname(const ip4_addr_t *ip, char *hostname, size_t len);
// DNS monitoring task
static void dns_monitor_task(void *arg) {
ESP_LOGI(TAG, "DNS monitoring task started");
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
ESP_LOGE(TAG, "Failed to create DNS socket");
vTaskDelete(NULL);
return;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(53);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
ESP_LOGE(TAG, "Failed to bind DNS socket");
close(sock);
vTaskDelete(NULL);
return;
}
uint8_t buffer[512];
while (s_dns_monitoring) {
struct sockaddr_in from;
socklen_t fromlen = sizeof(from);
int len = recvfrom(sock, buffer, sizeof(buffer), 0,
(struct sockaddr*)&from, &fromlen);
if (len > 0) {
// Parse DNS packet and log query
ESP_LOGI(TAG, "DNS query from %s", inet_ntoa(from.sin_addr));
}
vTaskDelay(pdMS_TO_TICKS(100));
}
close(sock);
ESP_LOGI(TAG, "DNS monitoring task ended");
vTaskDelete(NULL);
}
// DHCP monitoring task
static void dhcp_monitor_task(void *arg) {
ESP_LOGI(TAG, "DHCP monitoring task started");
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
ESP_LOGE(TAG, "Failed to create DHCP socket");
vTaskDelete(NULL);
return;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(67);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
ESP_LOGE(TAG, "Failed to bind DHCP socket");
close(sock);
vTaskDelete(NULL);
return;
}
uint8_t buffer[512];
while (s_dhcp_monitoring) {
struct sockaddr_in from;
socklen_t fromlen = sizeof(from);
int len = recvfrom(sock, buffer, sizeof(buffer), 0,
(struct sockaddr*)&from, &fromlen);
if (len > 0) {
// Parse DHCP packet and log lease
ESP_LOGI(TAG, "DHCP packet from %s", inet_ntoa(from.sin_addr));
}
vTaskDelay(pdMS_TO_TICKS(100));
}
close(sock);
ESP_LOGI(TAG, "DHCP monitoring task ended");
vTaskDelete(NULL);
}
// Packet capture task (stub implementation)
static void packet_capture_task(void *arg) {
ESP_LOGI(TAG, "Packet capture not implemented for ESP-IDF");
// Packet capture not implemented - ESP-IDF compatible APIs required
// For now, this is a placeholder that simulates capture activity
while (s_capture_running) {
vTaskDelay(pdMS_TO_TICKS(100));
}
ESP_LOGI(TAG, "Packet capture task ended");
}
// Initialize scanner engine
esp_err_t scanner_engine_init(void) {
if (s_scanner_initialized) {
return ESP_OK;
}
s_scanner_mutex = xSemaphoreCreateMutex();
if (!s_scanner_mutex) {
ESP_LOGE(TAG, "Failed to create scanner mutex");
return ESP_ERR_NO_MEM;
}
// Initialize device list
memset(s_devices, 0, sizeof(s_devices));
s_device_count = 0;
// Initialize statistics
memset(&s_stats, 0, sizeof(s_stats));
// Load MAC vendor database
if (load_mac_vendor_database() != ESP_OK) {
ESP_LOGW(TAG, "Failed to load MAC vendor database");
}
s_scanner_initialized = true;
ESP_LOGI(TAG, "Scanner engine initialized");
return ESP_OK;
}
// Deinitialize scanner engine
esp_err_t scanner_engine_deinit(void) {
if (!s_scanner_initialized) {
return ESP_OK;
}
// Stop all monitoring tasks
scanner_stop_dns_monitor();
scanner_stop_dhcp_monitor();
scanner_stop_passive_sniff();
if (s_scanner_mutex) {
vSemaphoreDelete(s_scanner_mutex);
s_scanner_mutex = NULL;
}
s_scanner_initialized = false;
ESP_LOGI(TAG, "Scanner engine deinitialized");
return ESP_OK;
}
// Start ARP scan
esp_err_t scanner_arp_scan(const scan_config_t *config) {
if (!s_scanner_initialized) {
return ESP_ERR_INVALID_STATE;
}
return perform_arp_scan(config);
}
// Start TCP port scan
esp_err_t scanner_tcp_port_scan(const scan_config_t *config) {
if (!s_scanner_initialized) {
return ESP_ERR_INVALID_STATE;
}
return perform_tcp_port_scan(config);
}
// Start ICMP ping sweep
esp_err_t scanner_icmp_ping_sweep(const scan_config_t *config) {
if (!s_scanner_initialized) {
return ESP_ERR_INVALID_STATE;
}
return perform_icmp_ping_sweep(config);
}
// Start DNS monitoring
esp_err_t scanner_start_dns_monitor(uint32_t duration_seconds) {
if (s_dns_monitoring) {
return ESP_ERR_INVALID_STATE;
}
s_dns_monitoring = true;
if (xTaskCreate(dns_monitor_task, "dns_monitor", 4096, NULL, 5, &s_dns_task) != pdPASS) {
s_dns_monitoring = false;
return ESP_ERR_NO_MEM;
}
ESP_LOGI(TAG, "DNS monitoring started for %u seconds", duration_seconds);
return ESP_OK;
}
// Stop DNS monitoring
esp_err_t scanner_stop_dns_monitor(void) {
if (!s_dns_monitoring) {
return ESP_ERR_INVALID_STATE;
}
s_dns_monitoring = false;
if (s_dns_task) {
vTaskDelete(s_dns_task);
s_dns_task = NULL;
}
ESP_LOGI(TAG, "DNS monitoring stopped");
return ESP_OK;
}
// Get DNS queries (stub implementation)
esp_err_t scanner_get_dns_queries(dns_query_t *queries, size_t max_queries, size_t *query_count) {
if (!queries || !query_count) {
return ESP_ERR_INVALID_ARG;
}
*query_count = 0;
ESP_LOGI(TAG, "DNS queries retrieval not implemented");
return ESP_OK;
}
// Start DHCP monitoring
esp_err_t scanner_start_dhcp_monitor(uint32_t duration_seconds) {
if (s_dhcp_monitoring) {
return ESP_ERR_INVALID_STATE;
}
s_dhcp_monitoring = true;
if (xTaskCreate(dhcp_monitor_task, "dhcp_monitor", 4096, NULL, 5, &s_dhcp_task) != pdPASS) {
s_dhcp_monitoring = false;
return ESP_ERR_NO_MEM;
}
ESP_LOGI(TAG, "DHCP monitoring started for %u seconds", duration_seconds);
return ESP_OK;
}
// Stop DHCP monitoring
esp_err_t scanner_stop_dhcp_monitor(void) {
if (!s_dhcp_monitoring) {
return ESP_ERR_INVALID_STATE;
}
s_dhcp_monitoring = false;
if (s_dhcp_task) {
vTaskDelete(s_dhcp_task);
s_dhcp_task = NULL;
}
ESP_LOGI(TAG, "DHCP monitoring stopped");
return ESP_OK;
}
// Get DHCP leases (stub implementation)
esp_err_t scanner_get_dhcp_leases(dhcp_lease_t *leases, size_t max_leases, size_t *lease_count) {
if (!leases || !lease_count) {
return ESP_ERR_INVALID_ARG;
}
*lease_count = 0;
ESP_LOGI(TAG, "DHCP leases retrieval not implemented");
return ESP_OK;
}
// Start passive sniffing
esp_err_t scanner_start_passive_sniff(uint32_t duration_seconds, const char *pcap_file) {
if (s_capture_running) {
return ESP_ERR_INVALID_STATE;
}
s_capture_running = true;
if (xTaskCreate(packet_capture_task, "packet_capture", 4096, NULL, 5, &s_capture_task) != pdPASS) {
s_capture_running = false;
return ESP_ERR_NO_MEM;
}
ESP_LOGI(TAG, "Passive sniffing started for %u seconds", duration_seconds);
return ESP_OK;
}
// Stop passive sniffing
esp_err_t scanner_stop_passive_sniff(void) {
if (!s_capture_running) {
return ESP_ERR_INVALID_STATE;
}
s_capture_running = false;
if (s_capture_task) {
vTaskDelete(s_capture_task);
s_capture_task = NULL;
}
ESP_LOGI(TAG, "Passive sniffing stopped");
return ESP_OK;
}
// Get discovered devices
esp_err_t scanner_engine_get_devices(discovered_device_t *devices, size_t max_devices, size_t *device_count) {
if (!devices || !device_count) {
return ESP_ERR_INVALID_ARG;
}
if (xSemaphoreTake(s_scanner_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
size_t count = (s_device_count < max_devices) ? s_device_count : max_devices;
memcpy(devices, s_devices, count * sizeof(discovered_device_t));
*device_count = count;
xSemaphoreGive(s_scanner_mutex);
return ESP_OK;
}
// Clear discovered devices
esp_err_t scanner_engine_clear_devices(void) {
if (xSemaphoreTake(s_scanner_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
memset(s_devices, 0, sizeof(s_devices));
s_device_count = 0;
xSemaphoreGive(s_scanner_mutex);
ESP_LOGI(TAG, "Device list cleared");
return ESP_OK;
}
// Get scanner statistics
esp_err_t scanner_engine_get_stats(scanner_stats_t *stats) {
if (!stats) {
return ESP_ERR_INVALID_ARG;
}
if (xSemaphoreTake(s_scanner_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
memcpy(stats, &s_stats, sizeof(scanner_stats_t));
xSemaphoreGive(s_scanner_mutex);
return ESP_OK;
}
// Reset scanner statistics
esp_err_t scanner_engine_reset_stats(void) {
if (xSemaphoreTake(s_scanner_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
memset(&s_stats, 0, sizeof(s_stats));
xSemaphoreGive(s_scanner_mutex);
ESP_LOGI(TAG, "Scanner statistics reset");
return ESP_OK;
}
// Load MAC vendor database
esp_err_t load_mac_vendor_database(void) {
FILE *f = fopen("/sdcard/config/mac_vendors.csv", "r");
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open MAC vendor database file");
return ESP_FAIL;
}
char line[128];
while (fgets(line, sizeof(line), f)) {
if (s_mac_vendor_count >= MAX_MAC_VENDORS) {
ESP_LOGW(TAG, "MAC vendor database is full");
break;
}
char *prefix_str = strtok(line, ",");
char *vendor_str = strtok(NULL, "\n");
if (prefix_str && vendor_str) {
sscanf(prefix_str, "%hhx:%hhx:%hhx",
&s_mac_vendors[s_mac_vendor_count].prefix[0],
&s_mac_vendors[s_mac_vendor_count].prefix[1],
&s_mac_vendors[s_mac_vendor_count].prefix[2]);
strncpy(s_mac_vendors[s_mac_vendor_count].vendor, vendor_str,
sizeof(s_mac_vendors[s_mac_vendor_count].vendor) - 1);
s_mac_vendor_count++;
}
}
fclose(f);
ESP_LOGI(TAG, "Loaded %d MAC vendor entries", s_mac_vendor_count);
return ESP_OK;
}
// Perform ARP scan
static esp_err_t perform_arp_scan(const scan_config_t *config) {
if (!config) {
return ESP_ERR_INVALID_ARG;
}
if (xSemaphoreTake(s_scanner_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
ESP_LOGI(TAG, "Starting ARP scan from " IPSTR " to " IPSTR, IP2STR(&config->start_ip), IP2STR(&config->end_ip));
s_stats.total_scans++;
s_stats.scan_time_ms = esp_log_timestamp();
ip4_addr_t start_ip = config->start_ip;
ip4_addr_t end_ip = config->end_ip;
for (uint32_t ip = ntohl(start_ip.addr); ip <= ntohl(end_ip.addr); ip++) {
ip4_addr_t target_ip;
target_ip.addr = htonl(ip);
// Simplified ARP scan - just add the IP without MAC resolution
// In a real implementation, you would use raw sockets to capture ARP responses
add_discovered_device(&target_ip, NULL, NULL);
vTaskDelay(pdMS_TO_TICKS(config->delay_ms));
}
s_stats.scan_time_ms = esp_log_timestamp() - s_stats.scan_time_ms;
s_stats.successful_scans++;
xSemaphoreGive(s_scanner_mutex);
ESP_LOGI(TAG, "ARP scan completed in %lu ms", s_stats.scan_time_ms);
return ESP_OK;
}
// Perform TCP port scan
static esp_err_t perform_tcp_port_scan(const scan_config_t *config) {
if (!config) {
return ESP_ERR_INVALID_ARG;
}
if (xSemaphoreTake(s_scanner_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
ESP_LOGI(TAG, "Starting TCP port scan from port %d to %d",
config->port_start, config->port_end);
s_stats.total_scans++;
s_stats.scan_time_ms = esp_log_timestamp();
for (size_t i = 0; i < s_device_count; i++) {
if (!s_devices[i].is_active) {
continue;
}
for (uint16_t port = config->port_start; port <= config->port_end; port++) {
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
ESP_LOGE(TAG, "Failed to create socket");
continue;
}
struct sockaddr_in dest_addr;
dest_addr.sin_addr.s_addr = s_devices[i].ip_addr.addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(port);
struct timeval timeout;
timeout.tv_sec = config->timeout_ms / 1000;
timeout.tv_usec = (config->timeout_ms % 1000) * 1000;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
if (connect(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) == 0) {
if (s_devices[i].open_port_count < 255) {
s_devices[i].open_ports[s_devices[i].open_port_count++] = port;
ESP_LOGI(TAG, "Port %d is open on " IPSTR, port, IP2STR(&s_devices[i].ip_addr));
}
}
close(sock);
s_stats.ports_scanned++;
vTaskDelay(pdMS_TO_TICKS(config->delay_ms));
}
}
s_stats.scan_time_ms = esp_log_timestamp() - s_stats.scan_time_ms;
s_stats.successful_scans++;
xSemaphoreGive(s_scanner_mutex);
ESP_LOGI(TAG, "TCP port scan completed in %lu ms", s_stats.scan_time_ms);
return ESP_OK;
}
// Perform ICMP ping sweep
static esp_err_t perform_icmp_ping_sweep(const scan_config_t *config) {
if (!config) {
return ESP_ERR_INVALID_ARG;
}
if (xSemaphoreTake(s_scanner_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
ESP_LOGI(TAG, "Starting ICMP ping sweep from " IPSTR " to " IPSTR, IP2STR(&config->start_ip), IP2STR(&config->end_ip));
s_stats.total_scans++;
s_stats.scan_time_ms = esp_log_timestamp();
ip4_addr_t start_ip = config->start_ip;
ip4_addr_t end_ip = config->end_ip;
for (uint32_t ip = ntohl(start_ip.addr); ip <= ntohl(end_ip.addr); ip++) {
ip4_addr_t target_ip;
target_ip.addr = htonl(ip);
// This is a simplified ICMP ping. For a more robust implementation,
// you would use raw sockets and construct the ICMP packets manually.
// For now, we'll just log the attempt.
ESP_LOGI(TAG, "Pinging " IPSTR, IP2STR(&target_ip));
s_stats.packets_sent++;
vTaskDelay(pdMS_TO_TICKS(config->delay_ms));
}
s_stats.scan_time_ms = esp_log_timestamp() - s_stats.scan_time_ms;
s_stats.successful_scans++;
xSemaphoreGive(s_scanner_mutex);
ESP_LOGI(TAG, "ICMP ping sweep completed in %lu ms", s_stats.scan_time_ms);
return ESP_OK;
}
// Add discovered device
static esp_err_t add_discovered_device(const ip4_addr_t *ip, const uint8_t *mac, const char *hostname) {
if (!ip || !mac) {
return ESP_ERR_INVALID_ARG;
}
if (s_device_count >= MAX_DEVICES) {
return ESP_ERR_NO_MEM;
}
// Check if device already exists
for (size_t i = 0; i < s_device_count; i++) {
if (memcmp(s_devices[i].mac_addr, mac, 6) == 0) {
s_devices[i].last_seen = esp_log_timestamp();
return ESP_OK;
}
}
// Add new device
discovered_device_t *device = &s_devices[s_device_count];
device->ip_addr = *ip;
memcpy(device->mac_addr, mac, 6);
device->last_seen = esp_log_timestamp();
device->is_active = true;
if (hostname) {
strncpy(device->hostname, hostname, sizeof(device->hostname) - 1);
} else {
resolve_hostname(ip, device->hostname, sizeof(device->hostname));
}
// Lookup MAC vendor
for (size_t i = 0; i < s_mac_vendor_count; i++) {
if (memcmp(s_mac_vendors[i].prefix, mac, 3) == 0) {
strncpy(device->vendor, s_mac_vendors[i].vendor, sizeof(device->vendor) - 1);
break;
}
}
s_device_count++;
s_stats.devices_discovered++;
ESP_LOGI(TAG, "Discovered device: " IPSTR " [%02X:%02X:%02X:%02X:%02X:%02X]", IP2STR(ip), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return ESP_OK;
}
// Resolve hostname
static esp_err_t resolve_hostname(const ip4_addr_t *ip, char *hostname, size_t len) {
if (!ip || !hostname || len == 0) {
return ESP_ERR_INVALID_ARG;
}
// Simplified hostname resolution - just use IP address as hostname
// In a real implementation, you would use DNS reverse lookup
snprintf(hostname, len, IPSTR, IP2STR(ip));
return ESP_OK;
}
// Scanner engine queue processing (stub implementation)
void scanner_engine_process_queue(void) {
ESP_LOGI(TAG, "Scanner queue processing not implemented");
}
// Start scan (stub implementation)
esp_err_t scanner_engine_start_scan(const scan_config_t *config, uint32_t *scan_id) {
if (!config || !scan_id) {
return ESP_ERR_INVALID_ARG;
}
ESP_LOGI(TAG, "Scanner engine start scan not implemented");
*scan_id = 1; // Dummy scan ID
return ESP_OK;
}
// Stop scan (stub implementation)
esp_err_t scanner_engine_stop_scan(uint32_t scan_id) {
ESP_LOGI(TAG, "Scanner engine stop scan not implemented");
return ESP_OK;
}
// Get scan status (stub implementation)
esp_err_t scanner_engine_get_scan_status(uint32_t scan_id, scan_request_t *request) {
if (!request) {
return ESP_ERR_INVALID_ARG;
}
ESP_LOGI(TAG, "Scanner engine get scan status not implemented");
request->scan_id = scan_id;
request->status = SCAN_STATUS_COMPLETED;
return ESP_OK;
}

541
src/scanner_simple.c Normal file
View File

@@ -0,0 +1,541 @@
#include "scanner_engine.h"
#include "network_manager.h"
#include <esp_log.h>
#include <esp_timer.h>
#include <lwip/sockets.h>
#include <lwip/netdb.h>
#include <lwip/ip4_addr.h>
#include <lwip/icmp.h>
#include <lwip/inet.h>
#include <lwip/raw.h>
#include <fcntl.h>
#include <errno.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <string.h>
static const char *TAG = "SCANNER";
// Scanner state
static bool s_initialized = false;
static QueueHandle_t s_scan_queue = NULL;
static scan_request_t s_active_scans[MAX_CONCURRENT_SCANS];
static discovered_device_t s_devices[MAX_DISCOVERED_DEVICES];
static uint32_t s_device_count = 0;
static scanner_stats_t s_stats = {0};
// Task handles
static TaskHandle_t s_scanner_task = NULL;
// Helper function to check if IP is in range
static bool ip_in_range(ip4_addr_t ip, ip4_addr_t start, ip4_addr_t end) {
return (ip.addr >= start.addr && ip.addr <= end.addr);
}
// ARP scan implementation
static esp_err_t perform_arp_scan(scan_config_t *config, uint32_t scan_id) {
ESP_LOGI(TAG, "Starting ARP scan from %s to %s",
ip4addr_ntoa(&config->start_ip),
ip4addr_ntoa(&config->end_ip));
// Get Ethernet interface
esp_netif_t *eth_netif = network_manager_get_ethernet_netif();
if (!eth_netif) {
ESP_LOGE(TAG, "No Ethernet interface available");
return ESP_FAIL;
}
// Create raw socket for ARP
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
ESP_LOGE(TAG, "Failed to create socket");
return ESP_FAIL;
}
// Set socket timeout
struct timeval tv;
tv.tv_sec = config->timeout_ms / 1000;
tv.tv_usec = (config->timeout_ms % 1000) * 1000;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
uint32_t total_ips = ntohl(config->end_ip.addr) - ntohl(config->start_ip.addr) + 1;
uint32_t scanned = 0;
// Scan each IP in range
for (uint32_t ip = ntohl(config->start_ip.addr); ip <= ntohl(config->end_ip.addr); ip++) {
ip4_addr_t target;
target.addr = htonl(ip);
// Update progress
scanned++;
for (int i = 0; i < MAX_CONCURRENT_SCANS; i++) {
if (s_active_scans[i].scan_id == scan_id) {
s_active_scans[i].progress_percent = (scanned * 100) / total_ips;
break;
}
}
// Try to reach the target (this will trigger ARP)
struct sockaddr_in dest;
dest.sin_family = AF_INET;
dest.sin_port = htons(7); // Echo port
dest.sin_addr.s_addr = target.addr;
char dummy = 0;
sendto(sock, &dummy, 1, 0, (struct sockaddr*)&dest, sizeof(dest));
// Small delay between requests
vTaskDelay(pdMS_TO_TICKS(config->delay_ms));
// Check if scan was cancelled
bool cancelled = false;
for (int i = 0; i < MAX_CONCURRENT_SCANS; i++) {
if (s_active_scans[i].scan_id == scan_id &&
s_active_scans[i].status == SCAN_STATUS_CANCELLED) {
cancelled = true;
break;
}
}
if (cancelled) break;
}
close(sock);
// Give time for ARP responses
vTaskDelay(pdMS_TO_TICKS(1000));
// Read ARP table to get results
// Note: ESP-IDF doesn't provide direct ARP table access
// In a real implementation, you'd need to use netif functions
ESP_LOGI(TAG, "ARP scan completed. Scanned %lu IPs", scanned);
s_stats.total_scans++;
s_stats.successful_scans++;
return ESP_OK;
}
// ICMP ping scan
static esp_err_t perform_ping_scan(scan_config_t *config, uint32_t scan_id) {
ESP_LOGI(TAG, "Starting ICMP ping scan");
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sock < 0) {
ESP_LOGE(TAG, "Failed to create raw socket (needs root/admin)");
return ESP_FAIL;
}
// Set timeout
struct timeval tv;
tv.tv_sec = config->timeout_ms / 1000;
tv.tv_usec = (config->timeout_ms % 1000) * 1000;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
uint32_t total_ips = ntohl(config->end_ip.addr) - ntohl(config->start_ip.addr) + 1;
uint32_t scanned = 0;
// ICMP packet structure
struct {
uint8_t type;
uint8_t code;
uint16_t checksum;
uint16_t id;
uint16_t sequence;
} icmp_packet;
// Scan each IP
for (uint32_t ip = ntohl(config->start_ip.addr); ip <= ntohl(config->end_ip.addr); ip++) {
ip4_addr_t target;
target.addr = htonl(ip);
scanned++;
// Update progress
for (int i = 0; i < MAX_CONCURRENT_SCANS; i++) {
if (s_active_scans[i].scan_id == scan_id) {
s_active_scans[i].progress_percent = (scanned * 100) / total_ips;
break;
}
}
// Build ICMP echo request
icmp_packet.type = 8; // Echo request
icmp_packet.code = 0;
icmp_packet.checksum = 0;
icmp_packet.id = scan_id & 0xFFFF;
icmp_packet.sequence = scanned & 0xFFFF;
// Calculate checksum (simplified)
uint32_t sum = 0;
uint16_t *ptr = (uint16_t*)&icmp_packet;
for (int i = 0; i < sizeof(icmp_packet)/2; i++) {
sum += ptr[i];
}
icmp_packet.checksum = ~(sum + (sum >> 16));
struct sockaddr_in dest;
dest.sin_family = AF_INET;
dest.sin_addr.s_addr = target.addr;
// Send ICMP packet
int64_t send_time = esp_timer_get_time();
sendto(sock, &icmp_packet, sizeof(icmp_packet), 0,
(struct sockaddr*)&dest, sizeof(dest));
// Try to receive reply
char recv_buf[128];
struct sockaddr_in from;
socklen_t fromlen = sizeof(from);
int n = recvfrom(sock, recv_buf, sizeof(recv_buf), 0,
(struct sockaddr*)&from, &fromlen);
if (n > 0) {
int64_t recv_time = esp_timer_get_time();
uint32_t rtt_ms = (recv_time - send_time) / 1000;
// Add to discovered devices
if (s_device_count < MAX_DISCOVERED_DEVICES) {
discovered_device_t *dev = &s_devices[s_device_count++];
dev->ip_addr = target;
dev->response_time = rtt_ms;
dev->is_active = true;
dev->last_seen = xTaskGetTickCount();
ESP_LOGI(TAG, "Host %s is alive (RTT: %lu ms)",
ip4addr_ntoa(&target), rtt_ms);
s_stats.devices_discovered++;
}
}
vTaskDelay(pdMS_TO_TICKS(config->delay_ms));
// Check if cancelled
bool cancelled = false;
for (int i = 0; i < MAX_CONCURRENT_SCANS; i++) {
if (s_active_scans[i].scan_id == scan_id &&
s_active_scans[i].status == SCAN_STATUS_CANCELLED) {
cancelled = true;
break;
}
}
if (cancelled) break;
}
close(sock);
ESP_LOGI(TAG, "Ping scan completed. Found %lu active hosts", s_device_count);
return ESP_OK;
}
// TCP port scan
static esp_err_t perform_port_scan(scan_config_t *config, uint32_t scan_id) {
ESP_LOGI(TAG, "Starting TCP port scan");
uint32_t total_operations =
(ntohl(config->end_ip.addr) - ntohl(config->start_ip.addr) + 1) *
(config->port_end - config->port_start + 1);
uint32_t completed = 0;
// Scan each IP
for (uint32_t ip = ntohl(config->start_ip.addr); ip <= ntohl(config->end_ip.addr); ip++) {
ip4_addr_t target;
target.addr = htonl(ip);
// Scan each port
for (uint16_t port = config->port_start; port <= config->port_end; port++) {
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) continue;
// Set non-blocking mode
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
// Set timeout
struct timeval tv;
tv.tv_sec = config->timeout_ms / 1000;
tv.tv_usec = (config->timeout_ms % 1000) * 1000;
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
struct sockaddr_in dest;
dest.sin_family = AF_INET;
dest.sin_port = htons(port);
dest.sin_addr.s_addr = target.addr;
// Try to connect
int result = connect(sock, (struct sockaddr*)&dest, sizeof(dest));
if (result == 0 || (result < 0 && errno == EINPROGRESS)) {
// Wait for connection or timeout
fd_set write_fds;
FD_ZERO(&write_fds);
FD_SET(sock, &write_fds);
result = select(sock + 1, NULL, &write_fds, NULL, &tv);
if (result > 0 && FD_ISSET(sock, &write_fds)) {
// Check if really connected
int error = 0;
socklen_t len = sizeof(error);
getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len);
if (error == 0) {
ESP_LOGI(TAG, "Port %d open on %s", port, ip4addr_ntoa(&target));
// Find or add device
for (int i = 0; i < s_device_count; i++) {
if (s_devices[i].ip_addr.addr == target.addr) {
if (s_devices[i].open_port_count < 255) {
s_devices[i].open_ports[s_devices[i].open_port_count++] = port;
}
break;
}
}
s_stats.ports_scanned++;
}
}
}
close(sock);
completed++;
// Update progress
for (int i = 0; i < MAX_CONCURRENT_SCANS; i++) {
if (s_active_scans[i].scan_id == scan_id) {
s_active_scans[i].progress_percent = (completed * 100) / total_operations;
break;
}
}
vTaskDelay(pdMS_TO_TICKS(config->delay_ms));
// Check if cancelled
bool cancelled = false;
for (int i = 0; i < MAX_CONCURRENT_SCANS; i++) {
if (s_active_scans[i].scan_id == scan_id &&
s_active_scans[i].status == SCAN_STATUS_CANCELLED) {
cancelled = true;
break;
}
}
if (cancelled) goto scan_end;
}
}
scan_end:
ESP_LOGI(TAG, "Port scan completed");
return ESP_OK;
}
// Scanner task
static void scanner_task(void *pvParameters) {
scan_request_t request;
while (1) {
if (xQueueReceive(s_scan_queue, &request, portMAX_DELAY) == pdTRUE) {
ESP_LOGI(TAG, "Processing scan request %lu", request.scan_id);
// Mark scan as running
for (int i = 0; i < MAX_CONCURRENT_SCANS; i++) {
if (s_active_scans[i].scan_id == request.scan_id) {
s_active_scans[i].status = SCAN_STATUS_RUNNING;
s_active_scans[i].start_time = xTaskGetTickCount();
break;
}
}
// Perform scan based on type
esp_err_t result = ESP_OK;
switch (request.config.type) {
case SCAN_TYPE_ARP:
result = perform_arp_scan(&request.config, request.scan_id);
break;
case SCAN_TYPE_ICMP_PING:
result = perform_ping_scan(&request.config, request.scan_id);
break;
case SCAN_TYPE_PORT_TCP:
result = perform_port_scan(&request.config, request.scan_id);
break;
default:
ESP_LOGW(TAG, "Unsupported scan type: %d", request.config.type);
result = ESP_ERR_NOT_SUPPORTED;
}
// Mark scan as completed
for (int i = 0; i < MAX_CONCURRENT_SCANS; i++) {
if (s_active_scans[i].scan_id == request.scan_id) {
s_active_scans[i].status = (result == ESP_OK) ?
SCAN_STATUS_COMPLETED : SCAN_STATUS_ERROR;
s_active_scans[i].end_time = xTaskGetTickCount();
s_active_scans[i].devices_found = s_device_count;
break;
}
}
}
}
}
// Public functions
esp_err_t scanner_engine_init(void) {
if (s_initialized) {
return ESP_OK;
}
ESP_LOGI(TAG, "Initializing scanner engine");
// Create scan queue
s_scan_queue = xQueueCreate(10, sizeof(scan_request_t));
if (!s_scan_queue) {
ESP_LOGE(TAG, "Failed to create scan queue");
return ESP_ERR_NO_MEM;
}
// Initialize scan slots
memset(s_active_scans, 0, sizeof(s_active_scans));
memset(s_devices, 0, sizeof(s_devices));
// Create scanner task
BaseType_t ret = xTaskCreatePinnedToCore(
scanner_task,
"scanner",
8192,
NULL,
5,
&s_scanner_task,
0 // Core 0 for network operations
);
if (ret != pdPASS) {
ESP_LOGE(TAG, "Failed to create scanner task");
vQueueDelete(s_scan_queue);
return ESP_FAIL;
}
s_initialized = true;
ESP_LOGI(TAG, "Scanner engine initialized successfully");
return ESP_OK;
}
esp_err_t scanner_engine_deinit(void) {
if (!s_initialized) {
return ESP_OK;
}
// Delete task
if (s_scanner_task) {
vTaskDelete(s_scanner_task);
s_scanner_task = NULL;
}
// Delete queue
if (s_scan_queue) {
vQueueDelete(s_scan_queue);
s_scan_queue = NULL;
}
s_initialized = false;
return ESP_OK;
}
esp_err_t scanner_engine_start_scan(const scan_config_t *config, uint32_t *scan_id) {
if (!s_initialized || !config || !scan_id) {
return ESP_ERR_INVALID_ARG;
}
// Find free slot
int slot = -1;
for (int i = 0; i < MAX_CONCURRENT_SCANS; i++) {
if (s_active_scans[i].status == SCAN_STATUS_IDLE) {
slot = i;
break;
}
}
if (slot < 0) {
ESP_LOGE(TAG, "No free scan slots");
return ESP_ERR_NO_MEM;
}
// Generate scan ID
static uint32_t next_scan_id = 1;
*scan_id = next_scan_id++;
// Prepare scan request
scan_request_t *request = &s_active_scans[slot];
request->scan_id = *scan_id;
memcpy(&request->config, config, sizeof(scan_config_t));
request->status = SCAN_STATUS_IDLE;
request->progress_percent = 0;
request->devices_found = 0;
// Queue scan request
scan_request_t queue_item = *request;
if (xQueueSend(s_scan_queue, &queue_item, 0) != pdTRUE) {
ESP_LOGE(TAG, "Failed to queue scan request");
request->status = SCAN_STATUS_IDLE;
return ESP_FAIL;
}
ESP_LOGI(TAG, "Scan %lu queued successfully", *scan_id);
return ESP_OK;
}
esp_err_t scanner_engine_stop_scan(uint32_t scan_id) {
if (!s_initialized) {
return ESP_ERR_INVALID_STATE;
}
// Mark scan as cancelled
for (int i = 0; i < MAX_CONCURRENT_SCANS; i++) {
if (s_active_scans[i].scan_id == scan_id) {
s_active_scans[i].status = SCAN_STATUS_CANCELLED;
ESP_LOGI(TAG, "Scan %lu cancelled", scan_id);
return ESP_OK;
}
}
return ESP_ERR_NOT_FOUND;
}
void scanner_engine_process_queue(void) {
// Queue processing is handled by scanner task
// This function is kept for API compatibility
}
esp_err_t scanner_engine_get_results(discovered_device_t *devices, size_t *count) {
if (!devices || !count) {
return ESP_ERR_INVALID_ARG;
}
size_t copy_count = (*count < s_device_count) ? *count : s_device_count;
memcpy(devices, s_devices, copy_count * sizeof(discovered_device_t));
*count = copy_count;
return ESP_OK;
}
esp_err_t scanner_engine_get_scan_status(uint32_t scan_id, scan_request_t *status) {
if (!status) {
return ESP_ERR_INVALID_ARG;
}
for (int i = 0; i < MAX_CONCURRENT_SCANS; i++) {
if (s_active_scans[i].scan_id == scan_id) {
memcpy(status, &s_active_scans[i], sizeof(scan_request_t));
return ESP_OK;
}
}
return ESP_ERR_NOT_FOUND;
}
esp_err_t scanner_engine_get_stats(scanner_stats_t *stats) {
if (!stats) {
return ESP_ERR_INVALID_ARG;
}
memcpy(stats, &s_stats, sizeof(scanner_stats_t));
return ESP_OK;
}

303
src/sd_manager.c Normal file
View File

@@ -0,0 +1,303 @@
/**
* SD Card Manager Implementation
* Handles FAT32 SD card mounting, file operations, and directory management
* Provides interface for web server file serving and log management
*/
#include "sd_manager.h"
#include <esp_log.h>
#include <esp_vfs_fat.h>
#include <sdmmc_cmd.h>
#include <driver/sdmmc_host.h>
#include <sys/stat.h>
#include <sys/unistd.h>
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
// Note: statvfs is not available in ESP-IDF; space info currently unsupported
#include <esp_check.h>
#include <stdbool.h>
#include <esp_err.h>
static const char *TAG = "SD_MANAGER";
static bool s_mounted = false;
static sdmmc_card_t *s_card = NULL;
/**
* Internal helper to create a directory if it does not exist.
* Creates directory with 0775 permissions if it doesn't exist.
* @param path Directory path to create
* @return ESP_OK on success, ESP_FAIL on error
*/
static esp_err_t create_dir_if_needed(const char *path)
{
struct stat st;
if (stat(path, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
return ESP_OK; // already exists
}
ESP_LOGE(TAG, "%s exists but is not a directory", path);
return ESP_FAIL;
}
if (mkdir(path, 0775) != 0) {
ESP_LOGE(TAG, "mkdir failed for %s: errno %d", path, errno);
return ESP_FAIL;
}
return ESP_OK;
}
/**
* Initialize SD card and mount FAT32 file system
* Mounts SD card at /sdcard and creates required directory structure
* @return ESP_OK on success, error code otherwise
*/
esp_err_t sd_manager_init(void)
{
if (s_mounted) {
return ESP_OK; // already mounted
}
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = SD_FORMAT_IF_MOUNT_FAIL,
.max_files = SD_MAX_FILES,
.allocation_unit_size = SD_ALLOC_UNIT_SIZE
};
// Use SDMMC host by default; users can change to SDSPI if wiring requires.
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
slot_config.width = 1; // 1-bit mode for easier wiring
esp_err_t ret = esp_vfs_fat_sdmmc_mount(SD_MOUNT_POINT, &host, &slot_config, &mount_config, &s_card);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to mount SD card: %s", esp_err_to_name(ret));
return ret;
}
ESP_LOGI(TAG, "SD card mounted at %s", SD_MOUNT_POINT);
s_mounted = true;
// Ensure directory structure exists
return sd_manager_create_directories();
}
esp_err_t sd_manager_deinit(void)
{
if (!s_mounted) {
return ESP_OK;
}
esp_vfs_fat_sdcard_unmount(SD_MOUNT_POINT, s_card);
s_card = NULL;
s_mounted = false;
ESP_LOGI(TAG, "SD card unmounted");
return ESP_OK;
}
bool sd_manager_is_available(void)
{
return s_mounted;
}
/**
* Create required directory structure on SD card
* Creates /sdcard/www, /sdcard/logs, /sdcard/config, /sdcard/scripts
* @return ESP_OK on success
*/
esp_err_t sd_manager_create_directories(void)
{
ESP_RETURN_ON_FALSE(s_mounted, ESP_ERR_INVALID_STATE, TAG, "SD card not mounted");
ESP_ERROR_CHECK_WITHOUT_ABORT(create_dir_if_needed(SD_WWW_DIR));
ESP_ERROR_CHECK_WITHOUT_ABORT(create_dir_if_needed(SD_LOGS_DIR));
ESP_ERROR_CHECK_WITHOUT_ABORT(create_dir_if_needed(SD_CONFIG_DIR));
ESP_ERROR_CHECK_WITHOUT_ABORT(create_dir_if_needed(SD_SCRIPTS_DIR));
return ESP_OK;
}
/**
* Write data to file on SD card
* @param filepath Full path to file
* @param data Data to write
* @param size Size of data in bytes
* @param append Whether to append or overwrite file
* @return ESP_OK on success, ESP_FAIL on error
*/
esp_err_t sd_manager_write_file(const char *filepath, const void *data, size_t size, bool append)
{
ESP_RETURN_ON_FALSE(filepath && data && size > 0, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");
const char *mode = append ? "ab" : "wb";
FILE *f = fopen(filepath, mode);
if (!f) {
ESP_LOGE(TAG, "Failed to open %s for write: errno %d", filepath, errno);
return ESP_FAIL;
}
size_t written = fwrite(data, 1, size, f);
fclose(f);
return (written == size) ? ESP_OK : ESP_FAIL;
}
/**
* Read data from file on SD card
* @param filepath Full path to file
* @param buffer Buffer to read into
* @param size Size of buffer
* @param bytes_read Pointer to store actual bytes read
* @return ESP_OK on success, ESP_FAIL on error
*/
esp_err_t sd_manager_read_file(const char *filepath, void *buffer, size_t size, size_t *bytes_read)
{
ESP_RETURN_ON_FALSE(filepath && buffer && size > 0, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");
FILE *f = fopen(filepath, "rb");
if (!f) {
ESP_LOGE(TAG, "Failed to open %s for read: errno %d", filepath, errno);
return ESP_FAIL;
}
size_t r = fread(buffer, 1, size, f);
fclose(f);
if (bytes_read) {
*bytes_read = r;
}
return ESP_OK;
}
esp_err_t sd_manager_delete_file(const char *filepath)
{
ESP_RETURN_ON_FALSE(filepath, ESP_ERR_INVALID_ARG, TAG, "Invalid path");
if (unlink(filepath) != 0) {
ESP_LOGE(TAG, "Failed to delete %s: errno %d", filepath, errno);
return ESP_FAIL;
}
return ESP_OK;
}
bool sd_manager_file_exists(const char *filepath)
{
struct stat st;
return (stat(filepath, &st) == 0);
}
esp_err_t sd_manager_get_file_size(const char *filepath, size_t *size)
{
ESP_RETURN_ON_FALSE(filepath && size, ESP_ERR_INVALID_ARG, TAG, "Invalid args");
struct stat st;
if (stat(filepath, &st) != 0) {
return ESP_FAIL;
}
*size = st.st_size;
return ESP_OK;
}
esp_err_t sd_manager_list_files(const char *dir_path, char *file_list, size_t buffer_size)
{
ESP_RETURN_ON_FALSE(dir_path && file_list && buffer_size > 2, ESP_ERR_INVALID_ARG, TAG, "Invalid args");
DIR *dir = opendir(dir_path);
if (!dir) {
ESP_LOGE(TAG, "Failed to open dir %s: errno %d", dir_path, errno);
return ESP_FAIL;
}
// Begin JSON array
size_t used = snprintf(file_list, buffer_size, "[");
struct dirent *ent;
bool first = true;
while ((ent = readdir(dir)) != NULL) {
if (ent->d_type == DT_REG) {
if (!first) {
used += snprintf(file_list + used, buffer_size - used, ",");
}
used += snprintf(file_list + used, buffer_size - used, "\"%s\"", ent->d_name);
if (used >= buffer_size) {
break; // buffer full
}
first = false;
}
}
closedir(dir);
snprintf(file_list + used, buffer_size - used, "]");
return ESP_OK;
}
esp_err_t sd_manager_create_log_file(const char *log_type, char *filepath, size_t filepath_size)
{
ESP_RETURN_ON_FALSE(log_type && filepath && filepath_size > 0, ESP_ERR_INVALID_ARG, TAG, "Invalid args");
time_t now = time(NULL);
struct tm tm_info;
localtime_r(&now, &tm_info);
char timestamp[32];
strftime(timestamp, sizeof(timestamp), "%Y%m%d_%H%M%S", &tm_info);
snprintf(filepath, filepath_size, "%s/%s%s_%s%s", SD_LOGS_DIR, LOG_FILE_PREFIX, log_type, timestamp, LOG_FILE_EXTENSION);
// Create empty file
FILE *f = fopen(filepath, "wb");
if (!f) {
ESP_LOGE(TAG, "Failed to create log file %s: errno %d", filepath, errno);
return ESP_FAIL;
}
fclose(f);
return ESP_OK;
}
esp_err_t sd_manager_write_log(const char *filepath, const char *level, const char *message)
{
ESP_RETURN_ON_FALSE(filepath && level && message, ESP_ERR_INVALID_ARG, TAG, "Invalid args");
time_t now = time(NULL);
struct tm tm_info;
localtime_r(&now, &tm_info);
char timebuf[32];
strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", &tm_info);
char line[512];
int len = snprintf(line, sizeof(line), "[%s] %s: %s\n", timebuf, level, message);
return sd_manager_write_file(filepath, line, len, true);
}
esp_err_t sd_manager_get_card_info(sdmmc_card_t **info)
{
ESP_RETURN_ON_FALSE(info, ESP_ERR_INVALID_ARG, TAG, "Invalid arg");
*info = s_card;
return s_mounted ? ESP_OK : ESP_ERR_INVALID_STATE;
}
esp_err_t sd_manager_get_space_info(uint64_t *free_bytes, uint64_t *total_bytes)
{
ESP_RETURN_ON_FALSE(free_bytes && total_bytes, ESP_ERR_INVALID_ARG, TAG, "Invalid args");
// Note: statvfs is not available in ESP-IDF; space info currently unsupported
return ESP_ERR_NOT_SUPPORTED;
}
esp_err_t sd_manager_mkdir(const char *path)
{
ESP_RETURN_ON_FALSE(s_mounted, ESP_ERR_INVALID_STATE, TAG, "SD card not mounted");
ESP_RETURN_ON_FALSE(path, ESP_ERR_INVALID_ARG, TAG, "Invalid path");
return create_dir_if_needed(path);
}
esp_err_t sd_manager_append_file(const char *filepath, const void *data, size_t size)
{
ESP_RETURN_ON_FALSE(s_mounted, ESP_ERR_INVALID_STATE, TAG, "SD card not mounted");
ESP_RETURN_ON_FALSE(filepath && data, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");
return sd_manager_write_file(filepath, data, size, true);
}
esp_err_t sd_manager_rename_file(const char *old_path, const char *new_path)
{
ESP_RETURN_ON_FALSE(s_mounted, ESP_ERR_INVALID_STATE, TAG, "SD card not mounted");
ESP_RETURN_ON_FALSE(old_path && new_path, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");
if (rename(old_path, new_path) != 0) {
ESP_LOGE(TAG, "Failed to rename %s to %s: errno %d", old_path, new_path, errno);
return ESP_FAIL;
}
return ESP_OK;
}

466
src/security_manager.c Normal file
View File

@@ -0,0 +1,466 @@
#include "security_manager.h"
#include "config_manager.h"
#include "log_manager.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_random.h"
#include "mbedtls/md.h"
#include "mbedtls/sha256.h"
#include <string.h>
#include <time.h>
static const char *TAG = "SECURITY_MANAGER";
// Session management
#define MAX_SESSIONS 10
#define SESSION_TIMEOUT_SECONDS 3600 // 1 hour
typedef struct {
char session_id[33];
char username[32];
time_t created_time;
time_t last_activity;
bool active;
} session_t;
static session_t s_sessions[MAX_SESSIONS];
static bool s_security_manager_initialized = false;
static SemaphoreHandle_t s_security_mutex = NULL;
// Rate limiting
#define MAX_LOGIN_ATTEMPTS 5
#define LOGIN_TIMEOUT_SECONDS 300 // 5 minutes
typedef struct {
char ip_address[16];
int failed_attempts;
time_t last_attempt;
bool blocked;
} rate_limit_entry_t;
static rate_limit_entry_t s_rate_limit_entries[20];
static int s_rate_limit_count = 0;
// Password hashing constants
#define SALT_LENGTH 16
#define HASH_LENGTH 64 // SHA-256 hex string length
esp_err_t security_manager_init(void)
{
ESP_LOGI(TAG, "Initializing security manager");
// Create mutex for thread safety
s_security_mutex = xSemaphoreCreateMutex();
if (s_security_mutex == NULL) {
ESP_LOGE(TAG, "Failed to create security mutex");
return ESP_ERR_NO_MEM;
}
// Initialize sessions
memset(s_sessions, 0, sizeof(s_sessions));
// Initialize rate limiting
memset(s_rate_limit_entries, 0, sizeof(s_rate_limit_entries));
s_rate_limit_count = 0;
s_security_manager_initialized = true;
ESP_LOGI(TAG, "Security manager initialized successfully");
return ESP_OK;
}
esp_err_t security_manager_deinit(void)
{
if (s_security_mutex != NULL) {
vSemaphoreDelete(s_security_mutex);
s_security_mutex = NULL;
}
s_security_manager_initialized = false;
ESP_LOGI(TAG, "Security manager deinitialized");
return ESP_OK;
}
static void generate_session_id(char* session_id, size_t size)
{
if (session_id == NULL || size < 33) {
ESP_LOGE(TAG, "Invalid parameters for session ID generation");
return;
}
// Generate random session ID
uint8_t random_bytes[16];
esp_fill_random(random_bytes, sizeof(random_bytes));
// Convert to hex string with bounds checking
for (int i = 0; i < 16 && (i * 2 + 2) < size; i++) {
snprintf(session_id + (i * 2), size - (i * 2), "%02x", random_bytes[i]);
}
session_id[32] = '\0';
}
// Generate SHA-256 hash with salt
static esp_err_t hash_password(const char* password, const char* salt, char* hash, size_t hash_size)
{
if (password == NULL || salt == NULL || hash == NULL || hash_size < HASH_LENGTH + 1) {
return ESP_ERR_INVALID_ARG;
}
mbedtls_sha256_context ctx;
unsigned char digest[32];
char salted_password[256];
// Create salted password
snprintf(salted_password, sizeof(salted_password), "%s%s", password, salt);
// Initialize SHA-256
mbedtls_sha256_init(&ctx);
mbedtls_sha256_starts(&ctx, 0);
mbedtls_sha256_update(&ctx, (const unsigned char*)salted_password, strlen(salted_password));
mbedtls_sha256_finish(&ctx, digest);
mbedtls_sha256_free(&ctx);
// Convert to hex string
for (int i = 0; i < 32 && (i * 2 + 2) < hash_size; i++) {
snprintf(hash + (i * 2), hash_size - (i * 2), "%02x", digest[i]);
}
return ESP_OK;
}
// Generate salt and hash for new password
esp_err_t security_manager_hash_password(const char* password, char* salt, size_t salt_size, char* hash, size_t hash_size)
{
if (password == NULL || salt == NULL || hash == NULL ||
salt_size < SALT_LENGTH + 1 || hash_size < HASH_LENGTH + 1) {
return ESP_ERR_INVALID_ARG;
}
// Generate random salt
uint8_t random_bytes[SALT_LENGTH];
esp_fill_random(random_bytes, sizeof(random_bytes));
// Convert salt to hex string
for (int i = 0; i < SALT_LENGTH && (i * 2 + 2) < salt_size; i++) {
snprintf(salt + (i * 2), salt_size - (i * 2), "%02x", random_bytes[i]);
}
salt[SALT_LENGTH * 2] = '\0';
// Hash password with salt
return hash_password(password, salt, hash, hash_size);
}
bool verify_password_hash(const char* password, const char* stored_hash)
{
if (password == NULL || stored_hash == NULL) {
return false;
}
// For backward compatibility, check if stored hash is plain text
// In production, all passwords should be hashed
if (strlen(stored_hash) < HASH_LENGTH) {
// Plain text password (legacy)
return (strcmp(password, stored_hash) == 0);
}
// Extract salt from stored hash (assuming format: salt:hash)
char salt[SALT_LENGTH * 2 + 1] = {0};
char hash[HASH_LENGTH + 1] = {0};
char* colon_pos = strchr(stored_hash, ':');
if (colon_pos == NULL) {
// No salt separator, assume it's a hash without salt
strncpy(hash, stored_hash, HASH_LENGTH);
hash[HASH_LENGTH] = '\0';
} else {
// Extract salt and hash
size_t salt_len = colon_pos - stored_hash;
if (salt_len >= sizeof(salt)) {
salt_len = sizeof(salt) - 1;
}
strncpy(salt, stored_hash, salt_len);
salt[salt_len] = '\0';
strncpy(hash, colon_pos + 1, HASH_LENGTH);
hash[HASH_LENGTH] = '\0';
}
// Hash the provided password with the salt
char computed_hash[HASH_LENGTH + 1];
if (hash_password(password, salt, computed_hash, sizeof(computed_hash)) != ESP_OK) {
return false;
}
// Compare hashes
return (strcmp(computed_hash, hash) == 0);
}
static bool is_ip_rate_limited(const char* ip_address)
{
time_t now = time(NULL);
// Clean up old entries
for (int i = 0; i < s_rate_limit_count; i++) {
if (now - s_rate_limit_entries[i].last_attempt > LOGIN_TIMEOUT_SECONDS) {
// Remove expired entry
if (i < s_rate_limit_count - 1) {
memmove(&s_rate_limit_entries[i], &s_rate_limit_entries[i + 1],
(s_rate_limit_count - i - 1) * sizeof(rate_limit_entry_t));
}
s_rate_limit_count--;
i--; // Recheck this index
}
}
// Check if IP is blocked
for (int i = 0; i < s_rate_limit_count; i++) {
if (strcmp(s_rate_limit_entries[i].ip_address, ip_address) == 0) {
if (s_rate_limit_entries[i].blocked) {
return true; // IP is blocked
}
break;
}
}
return false;
}
static void record_failed_login(const char* ip_address)
{
time_t now = time(NULL);
// Find existing entry
for (int i = 0; i < s_rate_limit_count; i++) {
if (strcmp(s_rate_limit_entries[i].ip_address, ip_address) == 0) {
s_rate_limit_entries[i].failed_attempts++;
s_rate_limit_entries[i].last_attempt = now;
if (s_rate_limit_entries[i].failed_attempts >= MAX_LOGIN_ATTEMPTS) {
s_rate_limit_entries[i].blocked = true;
log_manager_log_warn(TAG, "IP %s blocked due to too many failed login attempts", ip_address);
}
return;
}
}
// Add new entry
if (s_rate_limit_count < 20) {
strncpy(s_rate_limit_entries[s_rate_limit_count].ip_address, ip_address, 15);
s_rate_limit_entries[s_rate_limit_count].failed_attempts = 1;
s_rate_limit_entries[s_rate_limit_count].last_attempt = now;
s_rate_limit_entries[s_rate_limit_count].blocked = false;
s_rate_limit_count++;
}
}
esp_err_t security_manager_authenticate(const char* username, const char* password, const char* ip_address, char* session_id, size_t session_id_size)
{
if (!s_security_manager_initialized || s_security_mutex == NULL) {
return ESP_ERR_INVALID_STATE;
}
if (username == NULL || password == NULL || ip_address == NULL || session_id == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (xSemaphoreTake(s_security_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
esp_err_t ret = ESP_ERR_INVALID_ARG;
// Check rate limiting
if (is_ip_rate_limited(ip_address)) {
log_manager_log_warn(TAG, "Login attempt from blocked IP: %s", ip_address);
xSemaphoreGive(s_security_mutex);
return ESP_ERR_INVALID_STATE;
}
// Get configuration
const config_t* config = config_manager_get_config();
if (config == NULL) {
xSemaphoreGive(s_security_mutex);
return ESP_ERR_INVALID_STATE;
}
// Verify credentials
if (strcmp(username, config->web_server.username) == 0 &&
verify_password_hash(password, config->web_server.password)) {
// Generate session ID
generate_session_id(session_id, session_id_size);
// Create session
time_t now = time(NULL);
for (int i = 0; i < MAX_SESSIONS; i++) {
if (!s_sessions[i].active) {
strncpy(s_sessions[i].session_id, session_id, 32);
strncpy(s_sessions[i].username, username, 31);
s_sessions[i].created_time = now;
s_sessions[i].last_activity = now;
s_sessions[i].active = true;
log_manager_log_info(TAG, "User %s authenticated successfully from IP %s", username, ip_address);
ret = ESP_OK;
break;
}
}
if (ret != ESP_OK) {
log_manager_log_warn(TAG, "No available session slots for user %s", username);
ret = ESP_ERR_NO_MEM;
}
} else {
// Record failed attempt
record_failed_login(ip_address);
log_manager_log_warn(TAG, "Failed login attempt for user %s from IP %s", username, ip_address);
ret = ESP_ERR_INVALID_ARG;
}
xSemaphoreGive(s_security_mutex);
return ret;
}
esp_err_t security_manager_validate_session(const char* session_id, char* username, size_t username_size)
{
if (!s_security_manager_initialized || s_security_mutex == NULL) {
return ESP_ERR_INVALID_STATE;
}
if (session_id == NULL || username == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (xSemaphoreTake(s_security_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
esp_err_t ret = ESP_ERR_INVALID_ARG;
time_t now = time(NULL);
// Find and validate session
for (int i = 0; i < MAX_SESSIONS; i++) {
if (s_sessions[i].active && strcmp(s_sessions[i].session_id, session_id) == 0) {
// Check session timeout
if (now - s_sessions[i].last_activity > SESSION_TIMEOUT_SECONDS) {
// Session expired
s_sessions[i].active = false;
log_manager_log_info(TAG, "Session expired for user %s", s_sessions[i].username);
ret = ESP_ERR_INVALID_STATE;
} else {
// Update last activity
s_sessions[i].last_activity = now;
strncpy(username, s_sessions[i].username, username_size - 1);
username[username_size - 1] = '\0';
ret = ESP_OK;
}
break;
}
}
xSemaphoreGive(s_security_mutex);
return ret;
}
esp_err_t security_manager_logout(const char* session_id)
{
if (!s_security_manager_initialized || s_security_mutex == NULL) {
return ESP_ERR_INVALID_STATE;
}
if (session_id == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (xSemaphoreTake(s_security_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
esp_err_t ret = ESP_ERR_INVALID_ARG;
// Find and invalidate session
for (int i = 0; i < MAX_SESSIONS; i++) {
if (s_sessions[i].active && strcmp(s_sessions[i].session_id, session_id) == 0) {
log_manager_log_info(TAG, "User %s logged out", s_sessions[i].username);
s_sessions[i].active = false;
ret = ESP_OK;
break;
}
}
xSemaphoreGive(s_security_mutex);
return ret;
}
esp_err_t security_manager_cleanup_expired_sessions(void)
{
if (!s_security_manager_initialized || s_security_mutex == NULL) {
return ESP_ERR_INVALID_STATE;
}
if (xSemaphoreTake(s_security_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
time_t now = time(NULL);
int expired_count = 0;
// Clean up expired sessions
for (int i = 0; i < MAX_SESSIONS; i++) {
if (s_sessions[i].active &&
(now - s_sessions[i].last_activity > SESSION_TIMEOUT_SECONDS)) {
s_sessions[i].active = false;
expired_count++;
}
}
if (expired_count > 0) {
log_manager_log_info(TAG, "Cleaned up %d expired sessions", expired_count);
}
xSemaphoreGive(s_security_mutex);
return ESP_OK;
}
esp_err_t security_manager_get_active_sessions(int* count)
{
if (!s_security_manager_initialized || s_security_mutex == NULL) {
return ESP_ERR_INVALID_STATE;
}
if (count == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (xSemaphoreTake(s_security_mutex, pdMS_TO_TICKS(1000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
*count = 0;
for (int i = 0; i < MAX_SESSIONS; i++) {
if (s_sessions[i].active) {
(*count)++;
}
}
xSemaphoreGive(s_security_mutex);
return ESP_OK;
}
esp_err_t security_manager_is_authentication_required(bool* required)
{
if (!s_security_manager_initialized) {
return ESP_ERR_INVALID_STATE;
}
if (required == NULL) {
return ESP_ERR_INVALID_ARG;
}
const config_t* config = config_manager_get_config();
if (config == NULL) {
return ESP_ERR_INVALID_STATE;
}
*required = config->web_server.auth_enabled;
return ESP_OK;
}

1504
src/web_server.c Normal file

File diff suppressed because it is too large Load Diff