Files
Nexus-Guard-Suite/reference/esp32-agent/docs/ARCHITECTURE.md
T
datadunia 84689b208e docs: add ESP32 and iOS agent architecture reference
- reference/esp32-agent/: DESIGN, HARDWARE, API_COMPAT, ARCHITECTURE, README

- reference/ios-agent/: DESIGN, API_COMPAT, UI_DESIGN, ARCHITECTURE, README

- Both mirror Android agent architecture, UI design, and heartbeat flow

- Architecture-only (no code) — avoids submodule conflicts

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-07-06 14:20:10 +07:00

9.8 KiB

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:

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:

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:

POST /api/v1/provision
Content-Type: application/json

{
  "token": "registration-token",
  "hwid": "esp32-hardware-id"
}

HTTP Response:

{
  "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:

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:

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:

{
  "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:

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:

// 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:

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:

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:

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

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

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

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:

// 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:

// 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