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>
This commit is contained in:
@@ -0,0 +1,425 @@
|
||||
# iOS Agent — Technical Design Document
|
||||
|
||||
**Version**: 1.0.0
|
||||
**Date**: 2026-07-06
|
||||
**Status**: Draft
|
||||
**Mirror**: Android Agent (`apps/android-agent/`)
|
||||
|
||||
## 1. Executive Summary
|
||||
|
||||
NexusGuard iOS Agent is an iOS-based WireGuard client for mobile devices. It mirrors the Android agent's architecture, UI design, and heartbeat flow, using iOS-native APIs (NetworkExtension, CryptoKit, Keychain).
|
||||
|
||||
## 2. Goals & Non-Goals
|
||||
|
||||
### Goals
|
||||
- iOS-based WireGuard tunnel (NetworkExtension)
|
||||
- Auto-provisioning via HTTP (same API as Android/ESP32)
|
||||
- Heartbeat monitoring with config sync (30s interval)
|
||||
- Keychain-based config storage (hardware-backed encryption)
|
||||
- SwiftUI UI matching Android agent design
|
||||
- Background operation via NetworkExtension
|
||||
|
||||
### Non-Goals
|
||||
- gRPC support (not needed for mobile)
|
||||
- Port forwarding (userspace agent only)
|
||||
- Jailbroken device support
|
||||
- macOS/iPadOS support (iPhone only for v1)
|
||||
|
||||
## 3. Architecture
|
||||
|
||||
### 3.1 System Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ iOS (SwiftUI) │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ App Layer │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ContentView│ │SettingsView│ │LogView │ │PortView │ │
|
||||
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ ┌────┴─────────────┴────────────┴─────────────┴────┐ │
|
||||
│ │ AgentManager │ │
|
||||
│ │ (orchestrates all components) │ │
|
||||
│ └──────────────────────────────────────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ Service Layer │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │Provisioning│ │Heartbeat│ │TunnelMgr │ │ConfigMgr│ │
|
||||
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ ┌────┴─────────────┴────────────┴─────────────┴────┐ │
|
||||
│ │ CryptoManager │ │
|
||||
│ │ AES-256-GCM (CryptoKit) │ │
|
||||
│ └──────────────────────────────────────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ NetworkExtension Layer │
|
||||
│ ┌──────────────────────────────────────────────────┐ │
|
||||
│ │ PacketTunnelProvider │ │
|
||||
│ │ (WireGuard tunnel via wireguard-apple) │ │
|
||||
│ └──────────────────────────────────────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ Storage Layer │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Keychain │ │ UserDefaults│ │ CoreData│ │ FileMgr │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.2 Task Architecture
|
||||
|
||||
```
|
||||
iOS Threads/Tasks
|
||||
├── Main Thread # UI updates
|
||||
├── AgentManager # Orchestration (async/await)
|
||||
├── HeartbeatTask # HTTP heartbeat (30s interval)
|
||||
├── TunnelManager # WireGuard tunnel management
|
||||
└── BackgroundTask # BGAppRefreshTask (background refresh)
|
||||
```
|
||||
|
||||
### 3.3 State Machine
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ BOOT │
|
||||
└──────┬──────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ APP_INIT │ ◄──────────────────────────────┐
|
||||
└──────┬──────┘ │
|
||||
│ loaded │
|
||||
▼ │
|
||||
┌─────────────┐ fail ┌─────────────┐│
|
||||
│ PROVISION │───────────────►│ APP_RETRY ││
|
||||
└──────┬──────┘ └──────┬──────┘│
|
||||
│ success │ │
|
||||
▼ └───────┘
|
||||
┌─────────────┐
|
||||
│ TUNNEL_UP │
|
||||
└──────┬──────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ HEARTBEAT │ ◄─── 30s interval
|
||||
└──────┬──────┘
|
||||
│ config_changed
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ TUNNEL_REBUILD │
|
||||
└──────┬──────┘
|
||||
│
|
||||
└──► HEARTBEAT
|
||||
```
|
||||
|
||||
## 4. Module Design
|
||||
|
||||
### 4.1 AgentManager
|
||||
|
||||
**Responsibilities**:
|
||||
- Orchestrate all agent components
|
||||
- Manage agent lifecycle (start/stop)
|
||||
- Handle state transitions
|
||||
|
||||
**Key Properties**:
|
||||
```swift
|
||||
class AgentManager: ObservableObject {
|
||||
@Published var state: AgentState = .disconnected
|
||||
@Published var tunnelInfo: TunnelInfo?
|
||||
@Published var logs: [LogEntry] = []
|
||||
|
||||
private let provisioning: ProvisioningService
|
||||
private let heartbeat: HeartbeatService
|
||||
private let tunnel: TunnelManager
|
||||
private let config: KeychainStorage
|
||||
}
|
||||
```
|
||||
|
||||
**Key Methods**:
|
||||
```swift
|
||||
func startAgent() async
|
||||
func stopAgent()
|
||||
func rebuildTunnel(with config: WireGuardConfig) async
|
||||
```
|
||||
|
||||
### 4.2 PacketTunnelProvider
|
||||
|
||||
**Responsibilities**:
|
||||
- Handle NetworkExtension tunnel lifecycle
|
||||
- Start/stop WireGuard tunnel
|
||||
- Report tunnel status
|
||||
|
||||
**Key Methods**:
|
||||
```swift
|
||||
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void)
|
||||
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void)
|
||||
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?)
|
||||
```
|
||||
|
||||
### 4.3 TunnelManager
|
||||
|
||||
**Responsibilities**:
|
||||
- Build WireGuard config from stored keys
|
||||
- Start/stop tunnel via PacketTunnelProvider
|
||||
- Monitor tunnel state
|
||||
- Track last handshake time
|
||||
|
||||
**Key Methods**:
|
||||
```swift
|
||||
func startTunnel(config: WireGuardConfig) async throws
|
||||
func stopTunnel()
|
||||
func isTunnelUp() -> Bool
|
||||
func getLastHandshake() -> Date?
|
||||
```
|
||||
|
||||
### 4.4 HeartbeatService
|
||||
|
||||
**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 Methods**:
|
||||
```swift
|
||||
func startHeartbeatLoop()
|
||||
func stopHeartbeat()
|
||||
func sendHeartbeat(config: WireGuardConfig, tunnelUp: Bool, lastHandshake: Date?) async -> WireGuardConfig?
|
||||
```
|
||||
|
||||
### 4.5 ProvisioningService
|
||||
|
||||
**Responsibilities**:
|
||||
- HTTP POST to `/api/v1/provision`
|
||||
- Parse encrypted config response
|
||||
- Decrypt config via CryptoManager
|
||||
- Store config in Keychain
|
||||
|
||||
**Key Methods**:
|
||||
```swift
|
||||
func provision(serverUrl: String, token: String, hwid: String) async -> WireGuardConfig?
|
||||
func decryptConfig(encrypted: String, hwid: String, salt: String) -> WireGuardConfig?
|
||||
```
|
||||
|
||||
### 4.6 CryptoManager
|
||||
|
||||
**Responsibilities**:
|
||||
- Key derivation: SHA-256(hwid + salt)
|
||||
- AES-256-GCM decryption
|
||||
- Secure memory handling
|
||||
|
||||
**Key Methods**:
|
||||
```swift
|
||||
func deriveKey(token: String, hwid: String) -> SymmetricKey
|
||||
func decrypt(encoded: String, key: SymmetricKey) -> Data?
|
||||
```
|
||||
|
||||
### 4.7 KeychainStorage
|
||||
|
||||
**Responsibilities**:
|
||||
- Store WireGuard keys in Keychain
|
||||
- Store agent config in UserDefaults
|
||||
- Secure key access
|
||||
|
||||
**Key Methods**:
|
||||
```swift
|
||||
func saveConfig(_ config: AgentConfig)
|
||||
func loadConfig() -> AgentConfig?
|
||||
func saveWireGuardConfig(_ config: WireGuardConfig)
|
||||
func loadWireGuardConfig() -> WireGuardConfig?
|
||||
func clearAll()
|
||||
```
|
||||
|
||||
### 4.8 LogStore
|
||||
|
||||
**Responsibilities**:
|
||||
- Buffer log entries in memory
|
||||
- Persist to CoreData (optional)
|
||||
- Provide log stream for UI
|
||||
|
||||
**Key Methods**:
|
||||
```swift
|
||||
func add(level: LogLevel, tag: String, message: String)
|
||||
func clear()
|
||||
func getLogs() -> [LogEntry]
|
||||
```
|
||||
|
||||
## 5. Data Structures
|
||||
|
||||
### 5.1 WireGuardConfig
|
||||
|
||||
```swift
|
||||
struct WireGuardConfig: Codable {
|
||||
let deviceId: String
|
||||
let privateKey: String
|
||||
let presharedKey: String
|
||||
let internalIp: String
|
||||
let serverPub: String
|
||||
let endpoint: String
|
||||
let dns: String
|
||||
let allowedIps: String
|
||||
let serverWgIp: String
|
||||
let configHash: String
|
||||
let forwardsHash: String
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 AgentConfig
|
||||
|
||||
```swift
|
||||
struct AgentConfig: Codable {
|
||||
let serverUrl: String
|
||||
let registrationToken: String
|
||||
let isProvisioned: Bool
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 AgentState
|
||||
|
||||
```swift
|
||||
enum AgentState: String {
|
||||
case disconnected = "disconnected"
|
||||
case connecting = "connecting"
|
||||
case connected = "connected"
|
||||
case failed = "failed"
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 LogEntry
|
||||
|
||||
```swift
|
||||
struct LogEntry: Identifiable {
|
||||
let id = UUID()
|
||||
let timestamp: Date
|
||||
let level: LogLevel
|
||||
let tag: String
|
||||
let message: String
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Error Handling
|
||||
|
||||
### 6.1 Error Types
|
||||
|
||||
```swift
|
||||
enum AgentError: Error {
|
||||
case wifiNotConnected
|
||||
case provisioningFailed(String)
|
||||
case heartbeatFailed(String)
|
||||
case cryptoDecryptionFailed
|
||||
case configInvalid
|
||||
case tunnelInitFailed
|
||||
case keychainError(OSStatus)
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 Recovery Strategy
|
||||
|
||||
| Error | Recovery |
|
||||
|-------|----------|
|
||||
| WiFi not connected | Retry 3x, then pause heartbeat |
|
||||
| HTTP provision fail | Retry 3x, then notify user |
|
||||
| HTTP heartbeat fail | Log only, continue |
|
||||
| Crypto decrypt fail | Clear config, re-provision |
|
||||
| Tunnel init fail | Retry 3x, then notify user |
|
||||
| Keychain error | Use UserDefaults fallback |
|
||||
|
||||
## 7. Background Tasks
|
||||
|
||||
### 7.1 BGAppRefreshTask
|
||||
|
||||
iOS doesn't have boot receiver like Android. Use Background Tasks:
|
||||
|
||||
```swift
|
||||
// Register task
|
||||
BGAppRefreshTaskRequest.register(forTaskWithIdentifier: "com.nexusguard.agent.refresh", using: nil) {
|
||||
task in
|
||||
self.handleBackgroundRefresh(task: task as! BGAppRefreshTask)
|
||||
}
|
||||
|
||||
// Schedule task
|
||||
let request = BGAppRefreshTaskRequest(identifier: "com.nexusguard.agent.refresh")
|
||||
request.earliestBeginDate = Date(timeIntervalSinceNow: 30 * 60) // 30 minutes
|
||||
try BGTaskScheduler.shared.submit(request)
|
||||
```
|
||||
|
||||
### 7.2 Background Modes
|
||||
|
||||
Add to `Info.plist`:
|
||||
```xml
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>fetch</string>
|
||||
<string>processing</string>
|
||||
<string>vpn-api</string>
|
||||
</array>
|
||||
```
|
||||
|
||||
## 8. Security Considerations
|
||||
|
||||
### 8.1 Key Storage
|
||||
|
||||
- WireGuard private keys stored in iOS Keychain (hardware-backed)
|
||||
- Non-sensitive config in UserDefaults
|
||||
- Never log keys or tokens
|
||||
|
||||
### 8.2 TLS
|
||||
|
||||
- Server communication over HTTPS (TLS 1.2+)
|
||||
- Use `URLSession` with default TLS settings
|
||||
- Certificate pinning (optional, future)
|
||||
|
||||
### 8.3 Hardware ID
|
||||
|
||||
- Use `identifierForVendor` for device identification
|
||||
- Persists across app reinstalls until device factory reset
|
||||
- Used for key derivation (HWID + salt)
|
||||
|
||||
## 9. Testing Strategy
|
||||
|
||||
### 9.1 Unit Tests
|
||||
|
||||
- Crypto: Decrypt known ciphertext with known key
|
||||
- Config: Keychain read/write cycle
|
||||
- Heartbeat: Parse server response
|
||||
|
||||
### 9.2 Integration Tests
|
||||
|
||||
- Provision → Tunnel → Heartbeat cycle
|
||||
- Config change detection
|
||||
- Tunnel rebuild on config change
|
||||
|
||||
### 9.3 UI Tests
|
||||
|
||||
- VPN toggle interaction
|
||||
- Tab switching
|
||||
- Settings save/load
|
||||
|
||||
### 9.4 Device Tests
|
||||
|
||||
- iPhone 12+ running iOS 16+
|
||||
- NetworkExtension background operation
|
||||
- Keychain access in background
|
||||
|
||||
## 10. Future Enhancements
|
||||
|
||||
### 10.1 Phase 2
|
||||
|
||||
- iPadOS support
|
||||
- Widget for quick status
|
||||
- Shortcuts integration
|
||||
- Siri commands
|
||||
|
||||
### 10.2 Phase 3
|
||||
|
||||
- macOS Catalyst support
|
||||
- Apple Watch companion
|
||||
- CarPlay integration
|
||||
|
||||
## 11. References
|
||||
|
||||
- [Apple NetworkExtension Documentation](https://developer.apple.com/documentation/networkextension)
|
||||
- [WireGuard for iOS](https://github.com/WireGuard/wireguard-apple)
|
||||
- [CryptoKit Documentation](https://developer.apple.com/cryptokit/)
|
||||
- [Background Tasks](https://developer.apple.com/documentation/backgroundtasks)
|
||||
- [NexusGuard Server API](../../server-core/docs/)
|
||||
Reference in New Issue
Block a user