Skip to main content

Griffon IPC Protocol (Frame + CBOR)

This document describes the IPC protocol used between Griffon components (e.g., plugin manager ↔ runner). The protocol is a binary framed transport with a fixed-size header and a variable-size payload. Payloads are encoded in CBOR using serde_cbor.

Scope / Where this protocol is used

This IPC protocol is only used for communication with the Plugin Manager.

In practice, it defines how Griffon components exchange messages such as Hello, Call, and Result over the Plugin Manager communication channel (for example through a Unix stream like a socketpair).

It is not intended as a general-purpose protocol for all Griffon modules, nor for external APIs. Any other subsystem (HTTP API, GUI, daemon internal buses, etc.) should use its own dedicated communication format.

1) High-level overview

The protocol is built on two layers:

  1. Frame layer (binary)
  • Ensures message boundaries on a raw stream (Read/Write)
  • Validates magic, version, type, payload length
  • Carries a request_id for correlating responses
  1. Message layer (typed)
  • Maps frames to typed Rust enums/structs (Message, CallPayload, etc.)
  • Serializes/deserializes payloads using CBOR (serde_cbor)

2) Constants and limits

The frame format includes these constants:

  • MAGIC = 0xBEEF
    Used to quickly detect that the stream is aligned on valid protocol data.

  • VERSION = 1
    Protocol version. Both sides must use the same version.

  • HEADER_LEN = 12 bytes
    Fixed header size.

  • MAX_PAYLOAD = 1MB
    Hard payload cap to prevent memory abuse or corrupted stream allocations.

3) Frame format (binary header)

Each message is transferred as:

  • Header (12 bytes) + Payload (N bytes)

Header layout (big-endian):

OffsetSizeFieldTypeNotes
02magicu16Must equal 0xBEEF
21versionu8Must equal 1
31typeu8One of MsgType
44request_idu32Correlation id
84payload_lenu32Payload length in bytes

Payload:

  • Raw bytes (payload_len bytes)
  • For most message types, payload is CBOR.

4) MsgType values

MsgType is a u8 enum:

  • 1Hello
  • 2HelloOk
  • 3Call
  • 4Result
  • 5Log (reserved / probably gonna be remove)
  • 6Heartbeat
  • 7Error

Unknown types are rejected as invalid data.

5) Request correlation (request_id)

The protocol supports request/response matching using request_id.

Rules:

  • Call frames carry a non-zero request_id chosen by the caller.
  • Result and Error frames reuse the same request_id as the corresponding Call.
  • Hello, HelloOk, and Heartbeat use request_id = 0 (no correlation).

This allows multiple in-flight calls over a single stream.

6) Serialization: CBOR payloads

Payloads are encoded with CBOR:

  • Encode: serde_cbor::to_vec(payload_struct)
  • Decode: serde_cbor::from_slice::<T>(&bytes)

CBOR is used for:

  • HelloOkPayload
  • CallPayload
  • ResultPayload
  • ErrorPayload

Messages without payload:

  • Hello (empty payload)
  • Heartbeat (empty payload)

7) Message types (typed layer)

The Message enum represents the protocol at a higher level:

  • Hello
  • HelloOk(HelloOkPayload)
  • Call { request_id, data: CallPayload }
  • Result { request_id, data: ResultPayload }
  • Error { request_id, data: ErrorPayload }
  • Heartbeat

8) Payload schemas

8.1) HelloOkPayload

Sent after a successful handshake to advertise capabilities.

pub struct HelloOkPayload {
pub name: String,
pub functions: Vec<String>,
}

Meaning:

  • name: component identifier (for example the runner name or the plugin name)
  • functions: list of callable function names exposed to the host

8.2) CallPayload

Represents a request to execute a function with arguments.

pub struct CallPayload {
pub fn_name: String,
pub args: Vec<String>,
}

Meaning:

  • fn_name: function identifier (for example "start", "status", "scan")
  • args: ordered string arguments (interpretation is up to the callee)

8.3) ResultPayload

Represents the result of a call.

pub struct ResultPayload {
pub ok: bool,
pub output: String,
}

Meaning:

  • ok: true if the call succeeded, false if it failed at the application level
  • output: textual output (can be a message, logs, JSON string, etc.)

8.4) ErrorPayload

Represents a protocol-level error (invalid request, decoding failure, unsupported message, etc.)

pub struct ErrorPayload {
pub code: u32,
pub message: String,
}

Meaning:

  • code: numeric error code (your convention to define)
  • message: human-readable error description

9) Encoding a message into a frame

Message::into_frame() converts a Message into a Frame:

  • Hello → type Hello, request_id = 0, empty payload
  • Heartbeat → type Heartbeat, request_id = 0, empty payload
  • HelloOk(p) → type HelloOk, request_id = 0, CBOR(payload)
  • Call{rid, data} → type Call, request_id = rid, CBOR(payload)
  • Result{rid, data} → type Result, request_id = rid, CBOR(payload)
  • Error{rid, data} → type Error, request_id = rid, CBOR(payload)

10) Sending and receiving on a stream

The transport layer is any Write/Read stream (Unix socket, socketpair, pipe, etc.).

Sending

send_message(w, msg):

  1. Convert MessageFrame
  2. Write header (12 bytes)
  3. Write payload (N bytes)

Receiving

recv_message(r):

  1. Read header (12 bytes)
  2. Validate magic, version, type
  3. Read payload (N bytes)
  4. Decode frame → typed Message

11) Validation and security checks

The frame parser enforces:

  • correct MAGIC
  • correct VERSION
  • known MsgType
  • payload length <= MAX_PAYLOAD

Invalid data results in io::ErrorKind::InvalidData.

This prevents:

  • desync due to garbage bytes
  • allocating extremely large payload buffers
  • accepting unknown frame types

12) Handshake flow

Typical flow when a connection is established between the runner and the plugin manager:

  1. Plugin Manager → Hello
  2. Runner → HelloOk { name, functions }

After this:

  • PM can send Call frames to request operations
  • Runner replies with Result or Error using the same request_id
  • Either side may send Heartbeat periodically to detect dead connections

13) Call / Result flow (example)

Example sequence:

  1. Host sends:
  • Call { request_id = 42, fn_name = "status", args = [] }
  1. Peer replies:
  • Result { request_id = 42, ok = true, output = "running=true" }

If something fails:

  • Error { request_id = 42, code = 1001, message = "unknown function" }

14) Common errors and debugging tips

  • "bad magic": stream is desynchronized or not using this protocol
  • "bad version": components compiled with different protocol versions
  • "bad type": unknown MsgType value
  • "payload too large": corrupted length field or attacker input
  • CBOR decode error: wrong payload type for the given MsgType, or schema mismatch