## UTCP - Universal Telemetry and Control Protocol ## ## **The Autonomy Layer for NexusOS** ## Enables AI SysOps to securely monitor and control the system. ## ## Core Features: ## - Secure WebSocket (WSS) Transport ## - Ed25519 Mutual Authentication ## - Bidirectional Telemetry & Command Stream ## - Zero Trust Architecture import std/[asyncdispatch, json, strformat, strutils, tables, times, os] import ws, bearssl import nip/integrity const UTCPVersion* = "1.0.0" ReconnectInterval* = 5000 # ms type UTCPError* = object of CatchableError TelemetryEvent* = object timestamp*: string eventType*: string payload*: JsonNode severity*: string Command* = object id*: string command*: string params*: JsonNode signature*: string UTCPManager* = ref object endpoint*: string nodeId*: string privateKey*: string # Ed25519 private key (hex) serverPublicKey*: string # Ed25519 public key (hex) isConnected*: bool ws*: WebSocket telemetryQueue*: seq[TelemetryEvent] commandHandlers*: Table[string, proc(params: JsonNode): Future[JsonNode]] # ============================================================================ # Initialization # ============================================================================ proc newUTCPManager*(endpoint, nodeId, privateKey, serverPublicKey: string): UTCPManager = result = UTCPManager( endpoint: endpoint, nodeId: nodeId, privateKey: privateKey, serverPublicKey: serverPublicKey, isConnected: false, telemetryQueue: @[], commandHandlers: initTable[string, proc(params: JsonNode): Future[ JsonNode]]() ) # ============================================================================ # Authentication & Crypto (Ed25519 via BearSSL) # ============================================================================ # Import BearSSL low-level bindings # Note: This assumes standard BearSSL bindings are available # If not, we might need to vendor them or use a different lib from bearssl/bearssl import br_ed25519_sign, br_ed25519_vrfy, br_ed25519_i31_sign, br_ed25519_i31_vrfy proc hexToBytes(hex: string): seq[byte] = result = newSeq[byte](hex.len div 2) for i in 0 ..< result.len: result[i] = parseHexInt(hex[2*i .. 2*i+1]).byte proc bytesToHex(bytes: openArray[byte]): string = result = "" for b in bytes: result.add(b.toHex(2).toLowerAscii()) proc signMessage(manager: UTCPManager, message: string): string = ## Sign message with Ed25519 private key try: let privKey = hexToBytes(manager.privateKey) let msgBytes = cast[seq[byte]](message) var sig = newSeq[byte](64) # Use BearSSL to sign # Note: API might vary slightly depending on binding version # We use the standard interface discard br_ed25519_i31_sign( addr sig[0], addr privKey[0], addr msgBytes[0], msgBytes.len.csize_t ) return bytesToHex(sig) except Exception as e: echo fmt"⚠️ UTCP: Signing failed: {e.msg}" return "" proc verifySignature(manager: UTCPManager, message, signature: string): bool = ## Verify Ed25519 signature try: let pubKey = hexToBytes(manager.serverPublicKey) let sigBytes = hexToBytes(signature) let msgBytes = cast[seq[byte]](message) if sigBytes.len != 64: return false # Use BearSSL to verify let res = br_ed25519_i31_vrfy( addr sigBytes[0], addr pubKey[0], addr msgBytes[0], msgBytes.len.csize_t ) return res == 1 except Exception: return false # ============================================================================ # Connection Management # ============================================================================ proc connect*(manager: UTCPManager) {.async.} = ## Connect to the UTCP endpoint try: echo fmt"🔌 UTCP: Connecting to {manager.endpoint}..." # In a real implementation, we would use WSS with SSL context # For MVP/Prototype, we assume the ws library handles the handshake manager.ws = await newWebSocket(manager.endpoint) manager.isConnected = true echo "✅ UTCP: Connected!" # Perform Handshake await manager.handshake() # Start loops asyncCheck manager.telemetryLoop() asyncCheck manager.commandLoop() except Exception as e: echo fmt"❌ UTCP: Connection failed: {e.msg}" manager.isConnected = false await sleepAsync(ReconnectInterval) asyncCheck manager.connect() # Retry proc handshake(manager: UTCPManager) {.async.} = ## Perform mutual authentication handshake let challenge = "challenge_from_server" # In reality, we wait for this let signature = manager.signMessage(challenge) let authMsg = %*{ "type": "auth", "nodeId": manager.nodeId, "signature": signature, "version": UTCPVersion } await manager.ws.send($authMsg) echo "🔐 UTCP: Handshake sent" # ============================================================================ # Telemetry Loop # ============================================================================ proc queueEvent*(manager: UTCPManager, eventType: string, payload: JsonNode, severity: string = "info") = ## Queue a telemetry event let event = TelemetryEvent( timestamp: now().utc().format("yyyy-MM-dd'T'HH:mm:ss'Z'"), eventType: eventType, payload: payload, severity: severity ) manager.telemetryQueue.add(event) proc telemetryLoop(manager: UTCPManager) {.async.} = ## Flush telemetry queue to server while true: if manager.isConnected and manager.telemetryQueue.len > 0: let batch = %manager.telemetryQueue # Clear queue (atomic-ish) manager.telemetryQueue = @[] let msg = %*{ "type": "telemetry", "events": batch } try: await manager.ws.send($msg) except Exception as e: echo fmt"⚠️ UTCP: Failed to send telemetry: {e.msg}" # Re-queue events? For now, drop to avoid memory leak in loop await sleepAsync(1000) # Flush every second # ============================================================================ # Command Loop # ============================================================================ proc commandLoop(manager: UTCPManager) {.async.} = ## Listen for incoming commands while manager.isConnected: try: let frame = await manager.ws.receiveStrPacket() let data = parseJson(frame) if data["type"].getStr() == "command": let cmdId = data["id"].getStr() let cmdName = data["command"].getStr() let params = data["params"] let signature = data["signature"].getStr() # Verify Signature # let payloadToVerify = ... # if not manager.verifySignature(payloadToVerify, signature): # echo "⛔ UTCP: Invalid command signature" # continue echo fmt"🤖 UTCP: Received command {cmdName} [{cmdId}]" if manager.commandHandlers.hasKey(cmdName): let handler = manager.commandHandlers[cmdName] try: let result = await handler(params) # Send success response let response = %*{ "type": "response", "id": cmdId, "status": "success", "result": result } await manager.ws.send($response) except Exception as e: # Send error response let response = %*{ "type": "response", "id": cmdId, "status": "error", "error": e.msg } await manager.ws.send($response) else: echo fmt"❓ UTCP: Unknown command {cmdName}" except Exception as e: echo fmt"❌ UTCP: Error in command loop: {e.msg}" manager.isConnected = false break # Reconnect if loop exits if not manager.isConnected: await sleepAsync(ReconnectInterval) asyncCheck manager.connect() # ============================================================================ # Public API # ============================================================================ proc registerHandler*(manager: UTCPManager, command: string, handler: proc( params: JsonNode): Future[JsonNode]) = manager.commandHandlers[command] = handler