Sign in

    dcipc

    Local daemon control channel for MoonBit: length-prefixed frames, token auth over loopback TCP, contact files in the state dir

    ipc
    daemon
    frame
    token
    tcp
    Download zip
    Author
    Version
    0.3.0
    License
    MIT
    Last updated
    17 days ago
    Downloads
    30

    #chensuiyi/dcipc

    Control channel for local daemons: length-prefixed frames, shared-token authentication over loopback TCP, and contact files (port / token) in the state dir. Asynchronous (async runtime).

    #Install

    moon add chensuiyi/dcipc

    #Features

    • Auth first: the server acks the token frame with one byte; rejection closes the connection, so a wrong token surfaces as EOF rather than a protocol error
    • Constant-time token comparison — timing does not reveal a matching prefix
    • 4 MB frame ceiling; 5 s timeouts on the handshake and silent clients
    • Protocol is transport-agnostic: Unix sockets and other transports are future extension points
    • Depends on moonbitlang/async + chensuiyi/fsx

    #Scenarios

    • CLI ↔ daemon control channels: start / stop / status commands with JSON payloads
    • Any local tool that needs to talk to a background daemon it spawned

    #API

    FunctionDescription
    generate_token()256 bits from the kernel RNG
    dial(state_dir)Connect via the port file
    accept_authed(server, token)Accept + authenticate
    round_trip_bytes(conn, token, payload, timeout_ms)Authenticated round trip
    read_frame_timeout(conn, timeout_ms)Read one frame, silence-tolerant
    read_token / read_daemon_portContact files

    // Daemon side
    let token = @dcipc.generate_token()
    @fsx.write_atomic(state_dir + "/daemon.token", @utf8.encode(token))
    let conn = @dcipc.accept_authed(server, token)
    @dcipc.write_frame(conn, response_payload)

    // Client side
    let conn = @dcipc.dial(state_dir)
    let resp = @dcipc.round_trip_bytes(conn, token, request_payload, 5000)

    #Author

    #License

    MIT

    #chensuiyi/dcipc

    MoonBit 本地守护进程控制通道:长度前缀帧协议、共享令牌认证(loopback TCP)、状态目录接触文件(port / token)。异步(async 运行时)。

    #安装

    moon add chensuiyi/dcipc

    #功能

    • 认证先行:token 帧后服务端回单字节确认,拒绝 = 直接断连——错误 token 呈现为 EOF 而非协议错误
    • 常量时间 token 比较,时序不泄露前缀
    • 帧上限 4MB;握手与静默客户端均有 5s 超时

    #场景

    • CLI ↔ 守护进程控制通道(start/stop/list/status)
    • 协议与传输解耦:Unix socket 等传输可作为后续扩展点

    #API

    函数说明
    generate_token()内核 RNG 256-bit
    dial(state_dir)按 port 文件连接
    accept_authed(server, token)接受并认证
    round_trip_bytes(conn, token, payload, timeout_ms)认证往返
    read_frame_timeout(conn, timeout_ms)读一帧(静默容错)
    read_token / read_daemon_port接触文件

    #用法示例

    // 守护进程侧
    let token = @dcipc.generate_token()
    @fsx.write_atomic(state_dir + "/daemon.token", @utf8.encode(token))
    let conn = @dcipc.accept_authed(server, token)
    @dcipc.write_frame(conn, response_payload)

    // 客户端侧
    let conn = @dcipc.dial(state_dir)
    let resp = @dcipc.round_trip_bytes(conn, token, request_payload, 5000)

    #作者

    陈随易 (@chenbimo)

    #协议

    MIT

    IpcError

    pub suberror IpcError {
    Timeout
    AuthFailed
    Closed(String)
    TooLarge(size~ : Int, limit~ : Int)
    } derive(
    Debug
    )

    Control-channel failures. AuthFailed closes the connection without a response, so a wrong token looks to its presenter like a closed port.

    IpcError::to_repr

    AUTH_TIMEOUT_MS

    let AUTH_TIMEOUT_MS : Int

    How long either side waits for the auth handshake (and the daemon for a silent client) before dropping the connection.

    MAX_MSG_BYTES

    let MAX_MSG_BYTES : Int

    Hard ceiling for one control message. The receiver allocates exactly the announced length, so this is not a limit on how many instances may be listed — it only stops a hostile peer from forcing an absurd allocation.

    PORT_FILE

    let PORT_FILE : String

    Files under the state dir that publish the daemon's contact point.

    TOKEN_FILE

    let TOKEN_FILE : String

    accept_authed

    async fn accept_authed(server :
    TcpServer
    , token : String) ->
    Tcp
    ?

    Accept one connection and authenticate it. Returns the open connection when the peer presented the right token (the ack byte is already sent), None for wrong token / silence / garbage — those connections are closed here without ever reaching the dispatcher. Cancellation propagates so a shutting-down daemon stops accepting.

    dial

    async fn dial(state_dir : String) ->
    Tcp

    Dial the daemon over loopback TCP using the recorded port. Raises Closed when there is no readable port file (no daemon, or a stale one).

    generate_token

    fn generate_token() -> String raise

    256 bits from the kernel RNG, hex-encoded: 64 characters, no ambiguity on the wire or in a file.

    hex_encode

    fn hex_encode(bytes : Bytes) -> String

    read_daemon_port

    fn read_daemon_port(state_dir : String) -> Int?

    Read the port file the daemon wrote after binding. None when there is nothing readable (no daemon, or a stale file from a dead one).

    read_frame_timeout

    async fn read_frame_timeout(conn :
    Tcp
    , timeout_ms : Int) -> Bytes?

    Read one framed message from an authenticated connection, tolerating a client that connects and goes silent: None means the connection yielded nothing usable and has been closed.

    read_token

    fn read_token(state_dir : String) -> String?

    Read the shared token. None when missing/unreadable — callers report "no daemon" rather than leaking the reason.

    round_trip_bytes

    async fn round_trip_bytes(conn :
    Tcp
    , token : String, payload : Bytes, timeout_ms : Int) -> Bytes

    One authenticated round trip on a fresh connection, over raw bytes: the token frame goes first, the server answers with a single ack byte before the payload frame is accepted (a rejected token therefore surfaces as EOF — AuthFailed — rather than a protocol error), then the payload frame is written and the response frame read. The whole exchange is bounded by timeout_ms; silence raises Timeout.

    write_frame

    async fn write_frame(conn :
    Tcp
    , payload : Bytes) -> Unit

    4-byte big-endian length header + payload, over the connection.

    Source Files