Skip to content

CopperlineOS/ports

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 

ports

CopperlineOS message‑port IPC: spec + client libraries.
ports defines how CopperlineOS processes talk: a tiny, low‑latency message protocol over Unix domain sockets with optional file‑descriptor passing (SCM_RIGHTS). This repo ships the spec, a Rust crate and a small C library, plus schemas and examples. Phase‑0 uses JSON/NDJSON; later phases add a compact binary ring format.

TL;DR: predictable, scriptable IPC you can use from Rust/C/shell—great for timelines and media control.


Why it exists

We want deterministic, observable IPC without the weight of gRPC/dbus. “Ports” give every service (e.g., copperd, compositord, audiomixerd) a named, scriptable endpoint that can move small commands, events, and the occasional FD for zero‑copy buffers.

  • Small & explicit: request/response + event streams.
  • FDs for zero‑copy: DMABUFs, memfd rings, etc.
  • Scriptable: works with socat, jq, and the portctl tool.
  • Upgradable: stable JSON v0, future binary framing with zero‑copy rings.

Status

  • Protocol v0 (JSON/NDJSON): usable (subject to RFCs).
  • Libraries: Rust crate + C library scaffolding.
  • OS: Linux (Unix sockets, SCM_RIGHTS).
  • License: MIT OR Apache‑2.0.

What’s in this repo

ports/
├─ spec/                 # human-readable spec & schemas (JSON/YAML)
├─ rust/                 # Rust crate: copperline-ports
├─ c/                    # tiny C client library + headers
├─ examples/             # minimal client samples (Rust/C/shell)
└─ tests/                # protocol tests (golden cases)

Architecture (Phase‑0)

flowchart LR
  c1["Apps (Rust/C)"];
  c2["Tools/CLI (portctl)"];
  c3["Scripts (ARexx-Next)"];
  p["Service Port"];
  r["Req/Resp Handler"];
  e["Event Stream"];

  c1 --> p;
  c2 --> p;
  c3 --> p;

  p --> r;
  p --> e;

  r --> e;
Loading
Text-only fallback (if Mermaid fails)
Apps/Tools/Scripts -> Service Port -> { Req/Resp Handler, Event Stream }
Req/Resp Handler -> Event Stream
  • One socket per service (e.g., /run/copperline/copperd.sock).
  • Bidirectional: same connection carries requests, responses, and subscribed events.
  • NDJSON framing: one JSON message per line (\n). Binary payloads go via FDs.

Design goals

  • Deterministic semantics (idempotent commands where possible).
  • Backpressure‑aware (bounded queues, explicit subscription).
  • Composability (consistent envelopes across services).
  • Security‑first (capability tokens, FD quotas, message size limits).

Non‑goals: remote networking, TLS, cross‑host routing (use a gateway if needed).


Port discovery & names

  • Default path: /run/copperline/<service>.sock (override via *D_SOCKET env).
  • Optional abstract sockets (@copperline/<service>) for sandboxed cases.
  • Services announce on ping:
    {"cmd":"ping"} → {"ok":true,"name":"copperd","version":"0.1.0","protocol":0}

Message format (JSON v0, NDJSON)

Each line is a single UTF‑8 JSON object. Clients MUST NOT embed newlines.

Requests (client → server)

{
  "id": "6f8f3c2a",               // opaque by client
  "cmd": "create_layer",          // command name
  "args": { "w":128,"h":128 },    // command arguments
  "caps": "cap-xyz"               // optional capability token
}
  • id is echoed in responses; strings or integers are allowed.
  • caps carries a bearer capability (see Security).
  • FDs (e.g., DMABUFs) are passed via SCM_RIGHTS alongside the JSON. If used, include an fds array describing logical positions:
    {"id":"x","cmd":"bind_dmabuf","args":{"id":1,"fd_index":0,"w":128,"h":128,"format":"RGBA8"}}

Responses (server → client)

{ "ok": true,  "id": "6f8f3c2a", "result": { "id": 1 } }
{ "ok": false, "id": "6f8f3c2a", "error": { "code":"BAD_ARG", "message":"w must be >0" } }

Events (server → client)

Out‑of‑band notifications (after subscribe). Events MAY include a sub field linking to the subscription id.

{ "event":"vsync", "usec":12345678, "frame":4242 }
{ "event":"irq",   "tag":"tick", "time_us":123, "prog":42 }
{ "event":"error", "message":"device lost" }

Subscriptions

{ "id":"sub1", "cmd":"subscribe", "args":{"events":["vsync","pageflip"]} }

Servers may support filters, e.g., {"events":["irq"],"tag":"tick"}.


Error codes (common)

  • BAD_CMD, BAD_ARG, NOT_FOUND, DENIED, BUSY, TIMEOUT, INTERNAL.
  • Services can extend with domain‑specific codes (e.g., BAD_FORMAT).

Backpressure & timeouts

  • Servers MUST bound their per‑client queues and can drop events with:
    {"event":"dropped","reason":"backpressure","count":N}.
  • Clients SHOULD read continuously after subscribing.
  • Default request timeout is service‑specific; clients may send {"cmd":"cancel","id":"…"}' (optional v0 feature).

Security

  • Socket permissions: created with restrictive mode (e.g., 0660) and owned by service group.
  • Capabilities: bearer tokens or numeric caps allowing specific commands/regs; carried in caps.
  • Quotas: services enforce FD count, message size (e.g., 64 KiB), and rate limits.
  • Validation: every service validates IDs, dimensions, formats, and FD lifetimes.

Network IPC is out‑of‑scope for v0. Use a gateway process that proxies and authenticates if remote control is required.


Rust client (preview)

use copperline_ports::{PortClient, JsonMessage};
use serde_json::json;

fn main() -> anyhow::Result<()> {
    // Connect
    let mut cli = PortClient::connect("/run/copperline/copperd.sock")?;

    // Ping
    let resp: JsonMessage = cli.request(json!({"id":"ping1","cmd":"ping"}))?;
    println!("pong: {}", resp);

    // Load a tiny program
    let program = json!({
        "version":1,
        "program":[ {"op":"WAIT","vsync":true} ]
    });
    let load = cli.request(json!({"id":"load1","cmd":"load","program":program}))?;
    let pid = load["result"]["id"].as_i64().unwrap();

    // Start it
    cli.request(json!({"id":"start1","cmd":"start","id":pid}))?;
    Ok(())
}

Until crates are published, use a path dependency in your project’s Cargo.toml:

[dependencies]
copperline-ports = { path = "../ports/rust" }

C client (preview)

#include "cl_ports.h"
#include <stdio.h>

int main() {
  clp_client_t* c = clp_connect("/run/copperline/copperd.sock");
  clp_msg_t req = clp_msg_json("{"id":"ping1","cmd":"ping"}");
  clp_msg_t resp;
  if (clp_request(c, &req, &resp) == 0) {
    printf("resp: %.*s\n", (int)resp.len, resp.data);
  }
  clp_close(c);
  return 0;
}

Build with pkg‑config once the library is installed:

cc ping.c $(pkg-config --cflags --libs copperline-ports)

CLI: portctl

For quick testing (lives in CopperlineOS/tools):

# Send a request
portctl /run/copperline/copperd.sock '{"cmd":"ping"}'

# Subscribe to events
portctl /run/copperline/compositord.sock   '{"cmd":"subscribe","args":{"events":["vsync"]}}' --stream

Versioning

  • Protocol: protocol = 0 for JSON v0; breaking changes bump this number.
  • Libraries: semver; v0.x until protocol stabilises.
  • Services must return their protocol in ping responses.

Roadmap

  • v0.1: JSON/NDJSON, request/response, subscriptions, FD passing, Rust/C clients.
  • v0.2: Binary framing + zero‑copy rings; credit‑based backpressure.
  • v0.3: Capability negotiation, per‑message priorities, metrics stream.
  • v0.4: Formal schemas → codegen for clients/servers.

RFCs tracked in CopperlineOS/rfcs.


Contributing

  • Read spec/README.md and CONTRIBUTING.md.
  • Keep examples runnable with portctl.
  • Protocol changes require an RFC and compatibility notes.

Code of Conduct: CODE_OF_CONDUCT.md.


License

Dual‑licensed under Apache‑2.0 OR MIT.


See also

About

IPC spec + client libs

Resources

License

Unknown and 3 other licenses found

Licenses found

Unknown
LICENSE
Apache-2.0
LICENSE-APACHE
CC-BY-4.0
LICENSE-CC-BY-4.0
MIT
LICENSE-MIT

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published