3.9 KiB
Communication Protocol (communication.lisp)
Communication Protocol (communication.lisp)
Architectural Intent: Secure Inter-Process Communication
The Communication Protocol is the bridge between the OpenCortex microharness and the outside world. To maintain the "Zero-Bloat" mandate, the protocol must be:
- Lightweight: Minimal overhead for low-latency terminal interaction.
- Deterministic: Strict S-expression framing to prevent injection attacks.
- Transport-Agnostic: Capable of running over TCP, Unix Sockets, or Standard I/O.
By utilizing a length-prefixed S-expression format (the "Unified Envelope"), we ensure that both human-readable text and complex Lisp data structures can be transmitted securely without the fragility of JSON or the overhead of Protobuf.
Pipeline Initialization
(in-package :opencortex)
Message Framing
Frame Serialization (frame-message)
Every message leaving the harness must be "framed." This involves two steps:
- Sanitization: Stripping raw Lisp objects (like streams or sockets) that cannot be serialized.
- Prefixed Framing: Calculating the length of the S-expression and prepending it as a 6-character hexadecimal string.
Example Frame: 00001c(:TYPE :STATUS :SCRIBE :IDLE)
(defun sanitize-protocol-message (msg)
"Recursively strips non-serializable objects (streams, sockets) from a protocol plist."
(if (and msg (listp msg))
(let ((clean nil))
(loop for (k v) on msg by #'cddr
do (unless (member k '(:reply-stream :socket :stream))
(push k clean)
(push (if (listp v) (sanitize-protocol-message v) v) clean)))
(nreverse clean))
msg))
(defun frame-message (msg)
"Serializes a message plist and prefixes it with a 6-character hex length."
(let* ((sanitized (sanitize-protocol-message msg))
(payload (let ((*print-pretty* nil) (*read-eval* nil)) (format nil "~s" sanitized)))
(len (length payload)))
(format nil "~6,'0x~a" len payload)))
Message Ingestion
Framed Message Reader (read-framed-message)
The inverse of framing. This function reads exactly the number of bytes specified by the hex-length prefix. This "byte-counted" reading is a critical security measure—it prevents buffer overflow attacks and "slowloris" type hung connections.
(defun read-framed-message (stream)
"Reads a hex-prefixed message from a stream. Returns the parsed Lisp plist or :EOF."
(handler-case
(let ((len-buf (make-string 6)))
;; 1. Read the length prefix
(let ((count (read-sequence len-buf stream)))
(if (< count 6)
:eof
(let ((len (ignore-errors (parse-integer len-buf :radix 16))))
(if (and len (> len 0))
;; 2. Read exactly 'len' bytes
(let ((payload-buf (make-string len)))
(read-sequence payload-buf stream)
(let ((*read-eval* nil))
(read-from-string payload-buf)))
:error)))))
(error (c)
(harness-log "PROTOCOL ERROR: ~a" c)
:error)))
Semantic Handshakes
Hello Message (make-hello-message)
The first message sent by the daemon upon client connection. It advertises the protocol version and the agent's current capabilities.
(defun make-hello-message (version)
"Constructs the standard HELLO handshake message."
(list :TYPE :EVENT
:PAYLOAD (list :ACTION :handshake
:VERSION version
:CAPABILITIES '(:AUTH :SWANK :ORG-AST))))