271 lines
8.1 KiB
Nim
271 lines
8.1 KiB
Nim
## 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
|
|
|