84689b208e
- 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>
388 lines
9.8 KiB
Markdown
388 lines
9.8 KiB
Markdown
# 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/)
|