# ESP32 Agent — Module Architecture **Status**: ARCHITECTURE ONLY **Last Updated**: 2026-07-06 ## Overview This document defines the module architecture for the ESP32-based WireGuard agent. The agent runs on FreeRTOS + ESP-IDF and connects to NexusGuard Server Core via HTTP. ## Task Architecture ``` FreeRTOS Tasks ├── main_task # Initialization, event loop ├── wifi_task # WiFi STA management (priority: 5) ├── http_task # HTTP client (provisioning, heartbeat) (priority: 4) ├── wg_task # WireGuard tunnel management (priority: 3) └── led_task # Status LED indication (priority: 1) ``` ## State Machine ``` ┌─────────────┐ │ BOOT │ └──────┬──────┘ │ ▼ ┌─────────────┐ │ WIFI_INIT │ ◄──────────────────────────────┐ └──────┬──────┘ │ │ connected │ ▼ │ ┌─────────────┐ fail ┌─────────────┐│ │ PROVISION │───────────────►│ WIFI_RETRY ││ └──────┬──────┘ └──────┬──────┘│ │ success │ │ ▼ └───────┘ ┌─────────────┐ │ TUNNEL_UP │ └──────┬──────┘ │ ▼ ┌─────────────┐ │ HEARTBEAT │ ◄─── 30s interval └──────┬──────┘ │ config_changed ▼ ┌─────────────┐ │ TUNNEL_REBUILD │ └──────┬──────┘ │ └──► HEARTBEAT ``` ## Module Design ### 1. main.c **Responsibilities**: - Hardware initialization (NVS, WiFi, GPIO) - Task creation and event loop - Signal handling (Ctrl+C graceful shutdown) **Key Functions**: ```c void app_main(void); // Entry point void shutdown_handler(void); // Graceful shutdown ``` ### 2. provision.c **Responsibilities**: - HTTP POST to `/api/v1/provision` - Parse encrypted config response - Decrypt config via crypto.c - Store config in NVS **Key Functions**: ```c esp_err_t provision_device(const char *server_url, const char *token, const char *hwid, wg_config_t *config); esp_err_t decrypt_config(const uint8_t *encrypted, size_t len, const char *hwid, const char *salt, wg_config_t *config); ``` **HTTP Request**: ```json POST /api/v1/provision Content-Type: application/json { "token": "registration-token", "hwid": "esp32-hardware-id" } ``` **HTTP Response**: ```json { "encrypted_config": "base64-encoded-bytes" } ``` ### 3. heartbeat.c **Responsibilities**: - HTTP POST to `/api/v1/heartbeat` every 30s - Send device status + tunnel state - Receive config sync (detect changes) - Trigger tunnel rebuild if config changed **Key Functions**: ```c esp_err_t heartbeat_send(const wg_config_t *config, const char *status, bool tunnel_up, int64_t last_handshake); bool heartbeat_config_changed(const char *old_hash, const char *new_hash); ``` **HTTP Request**: ```json POST /api/v1/heartbeat Content-Type: application/json { "device_id": "uuid", "status": "connected", "tunnel_up": true, "last_handshake": "2026-06-26T09:00:00Z" } ``` **HTTP Response**: ```json { "device_id": "uuid", "status": "ok", "internal_ip": "10.172.21.2", "private_key": "hex", "preshared_key": "hex", "server_pub": "hex", "endpoint": "italy-twenty.gl.at.ply.gg:59750", "dns": "1.1.1.1", "allowed_ips": "0.0.0.0/0", "server_wg_ip": "10.172.21.1", "forwards_hash": "hash-string" } ``` ### 4. tunnel.c **Responsibilities**: - Initialize WireGuard tunnel via lwIP - Apply config (keys, endpoint, allowed IPs) - Monitor tunnel state (handshake time) - Rebuild tunnel on config change **Key Functions**: ```c esp_err_t tunnel_init(const wg_config_t *config); esp_err_t tunnel_apply_config(const wg_config_t *config); esp_err_t tunnel_rebuild(const wg_config_t *config); int64_t tunnel_get_last_handshake(void); bool tunnel_is_up(void); ``` **WireGuard Config Application**: ```c // Pseudo-code wg_device wg = { .private_key = config->private_key, .listen_port = 0, // ephemeral }; wg_peer peer = { .public_key = config->server_pub, .preshared_key = config->preshared_key, .endpoint = config->endpoint, .allowed_ips = config->allowed_ips, }; wg_set_device(&wg); wg_add_peer(&wg, &peer); wg_set_peer_allowed_ips(&wg, &peer, config->allowed_ips); ``` ### 5. crypto.c **Responsibilities**: - Key derivation: SHA256(hwid + salt) - AES-256-GCM decryption - Secure memory handling **Key Functions**: ```c esp_err_t crypto_derive_key(const char *hwid, const char *salt, uint8_t key[32]); esp_err_t crypto_decrypt(const uint8_t *ciphertext, size_t len, const uint8_t key[32], uint8_t **plaintext, size_t *plaintext_len); ``` **Algorithm**: ``` Key Derivation: key = SHA256(hwid + salt) // 32 bytes Decryption: nonce = ciphertext[0:12] // first 12 bytes encrypted = ciphertext[12:] // rest plaintext = AES-256-GCM-Decrypt(key, nonce, encrypted) ``` ### 6. config.c **Responsibilities**: - NVS read/write for config fields - Config validation - Config versioning **Key Functions**: ```c esp_err_t config_save(const wg_config_t *config); esp_err_t config_load(wg_config_t *config); esp_err_t config_clear(void); bool config_is_valid(const wg_config_t *config); ``` **NVS Keys**: | Key | Max Size | Description | |-----|----------|-------------| | `device_id` | 37 | UUID string | | `private_key` | 64 | WG private key hex | | `preshared_key` | 64 | PSK hex | | `internal_ip` | 18 | IP address | | `server_pub` | 64 | Server public key hex | | `endpoint` | 128 | Server endpoint | | `dns` | 64 | DNS server | | `allowed_ips` | 128 | Allowed IPs | | `config_hash` | 64 | Last config hash | ### 7. wifi.c **Responsibilities**: - WiFi STA initialization - Connection management - Reconnection handling **Key Functions**: ```c esp_err_t wifi_init_sta(const char *ssid, const char *password); esp_err_t wifi_connect(void); esp_err_t wifi_disconnect(void); bool wifi_is_connected(void); ``` ## Data Structures ### wg_config_t ```c typedef struct { char device_id[37]; // UUID char private_key[64]; // WG private key hex char preshared_key[64]; // PSK hex char internal_ip[18]; // e.g. "10.172.21.2" char server_pub[64]; // Server WG public key hex char endpoint[128]; // e.g. "italy-twenty.gl.at.ply.gg:59750" char dns[64]; // DNS server char allowed_ips[128]; // e.g. "0.0.0.0/0" char server_wg_ip[18]; // e.g. "10.172.21.1" char config_hash[64]; // Last config hash } wg_config_t; ``` ### device_state_t ```c typedef enum { STATE_BOOT, STATE_WIFI_INIT, STATE_WIFI_CONNECTED, STATE_PROVISIONING, STATE_PROVISIONED, STATE_TUNNEL_UP, STATE_TUNNEL_DOWN, STATE_HEARTBEAT, STATE_ERROR } device_state_t; ``` ## Error Handling ### Error Codes ```c typedef enum { ERR_OK = 0, ERR_WIFI_CONNECT = -1, ERR_HTTP_PROVISION = -2, ERR_HTTP_HEARTBEAT = -3, ERR_CRYPTO_DECRYPT = -4, ERR_CONFIG_INVALID = -5, ERR_TUNNEL_INIT = -6, ERR_TUNNEL_APPLY = -7, ERR_NVS_READ = -8, ERR_NVS_WRITE = -9, } agent_error_t; ``` ### Recovery Strategy | Error | Recovery | |-------|----------| | WiFi connect fail | Retry 3x, then reboot | | HTTP provision fail | Retry 3x, then reboot | | HTTP heartbeat fail | Log only, continue | | Crypto decrypt fail | Clear config, re-provision | | Tunnel init fail | Retry 3x, then reboot | | NVS read fail | Use defaults, re-provision | ## Implementation Status ### ✅ Implemented (Real Code) | Module | File | Status | |--------|------|--------| | Provisioning | `provision.c` | HTTP POST working | | Heartbeat | `heartbeat.c` | HTTP loop working | | Crypto | `crypto.c` | mbedtls AES-256-GCM | | Config | `config.c` | NVS read/write | | WiFi | `wifi.c` | STA connection | ### ❌ STUB (Needs Implementation) | Module | File | What's Missing | |--------|------|----------------| | Tunnel | `tunnel.c` | `wireguard-esp32` component integration | | Heartbeat | `heartbeat.c` | Config sync parsing from response | ### TODO: tunnel.c Integration When integrating `wireguard-esp32` component: ```c // 1. Parse private_key hex to binary // 2. Parse server_pub hex to binary // 3. Parse preshared_key hex to binary // 4. Create WireGuard device with private_key // 5. Add peer with server_pub, preshared_key, endpoint // 6. Set peer allowed_ips // 7. Start WireGuard tunnel ``` ### TODO: heartbeat.c Config Sync When parsing heartbeat response: ```c // 1. Parse response JSON // 2. Extract config_hash // 3. Compare with stored config_hash // 4. If different → parse new config fields // 5. Save to NVS // 6. Call tunnel_rebuild() ``` ## Future Enhancements ### Phase 2 - OTA firmware updates - Custom CA certificate support - Power optimization (deep sleep) - Multiple server support ### Phase 3 - Zephyr RTOS port - ESP32-S2 support - Ethernet (SPI) support - Bluetooth provisioning ## References - [ESP-IDF Documentation](https://docs.espressif.com/projects/esp-idf/) - [WireGuard Protocol](https://www.wireguard.com/protocol/) - [lwIP Documentation](https://www.nongnu.org/lwip/) - [mbedtls Documentation](https://mbed-tls.readthedocs.io/)