ipcez 0.1.0

Rust library for ipcez.
Documentation

ipcez

Rust library for inter-process communication (IPC) with deployment and platform flexibility: it detects whether the process you talk to is local or remote and picks the right transport (WebSocket, Unix domain socket, or Windows named pipe) based on the operating system and backend deployment.

Recommendation: Use socket_init(endpoint) (see Socket API): pass one endpoint string and set target / os in the environment for local connections, or use a ws:// / wss:// URL for remote. You do not need to know which transport is used.

Requirements

  • Rust 1.93+ (edition 2024)
  • Tokio runtime when using the async socket API (ipcez depends on tokio)

Installation

Add to your Cargo.toml:

[dependencies]
ipcez = "0.1"

If you use socket_init, ensure your binary has a Tokio runtime (e.g. #[tokio::main] or tokio::runtime::Runtime).


Environment variables

The library uses two optional environment variables to choose transport. Set them in your deployment or config so the same binary can run locally or remotely.

Variable Allowed values Meaning
os linux, windows Operating system (case-insensitive).
target local, remote Whether the other process is on this machine or a remote server.

For local endpoints, socket_init(endpoint) reads them automatically and returns a clear error (SocketError::Detection) if os or target is missing or invalid (the error message states which variable is the problem and, for invalid values, what is expected). When detection fails, the library also prints guidance to the console (stderr) explaining the missing or invalid variable and what to do to fix it.


Socket API

Socket API

You connect with one function and one endpoint string. Set the target and os environment variables for local connections; use a WebSocket URL for remote. No need to know which transport (WebSocket, Unix socket, or named pipe) is used.

Endpoint rule:

  • URL (ws:// or wss://) → connects as remote (WebSocket).
  • Otherwise → treated as local: the library reads target and os from the environment. On Windows (local), if the string does not start with \\.\pipe\, that prefix is added automatically.

Rust: socket_init(endpoint). Node/TS: connect(addr). Python: ipcez.connect(addr). C#: Ipcez.Connect(addr).

Rust example:

use ipcez::{socket_init, Socket};
use tokio::sync::mpsc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Set env: target=local, os=windows (or linux). For remote, use a ws:// or wss:// URL.
    let socket = socket_init("ipcez").await?;  // or "ws://localhost:8080/" for remote
    let (tx, mut rx) = mpsc::unbounded_channel();
    socket.message_handler(move |result| {
        let _ = tx.send(result);
        async {}
    });
    socket.send_message(b"hello").await?;
    if let Some(Ok(msg)) = rx.recv().await {
        println!("received {} bytes", msg.len());
    }
    Ok(())
}

Python example:

import os
os.environ["target"] = "local"
os.environ["os"] = "windows"
import ipcez
with ipcez.connect("ipcez") as sock:
    sock.write(b"hello")
    data = sock.read(256)

Transport selection

socket_init(endpoint) resolves transport from the endpoint and (for local) from the target and os environment variables:

Target OS Transport Endpoint format
Remote any WebSocket ws://host:port/path or wss://...
Local Linux Unix domain socket Socket path, e.g. /tmp/ipcez.sock
Local Windows Named pipe \\.\pipe\pipename or just pipename

Unsupported combinations (e.g. local + Linux on a Windows build) return SocketError::UnsupportedCombination(target, os).

Address format by transport

  • WebSocket (remote): Full URL. Use ws:// for plain, wss:// for TLS.
  • Unix (local + Linux): Absolute or relative path to the socket file (e.g. /tmp/ipcez.sock).
  • Named pipe (local + Windows): Either the full path \\.\pipe\pipename or only pipename; the library prefixes \\.\pipe\ when needed. After each send_message(), the library signals the named event Global\pipename.data_ready so a receiver can wait on it before reading, then waits up to 5 seconds for the receiver to signal Global\pipename.data_acked to confirm receipt. External recipients must signal the same-named "data acked" event after each successful read.

Using the socket

Socket exposes a message API:

  • send_message(&self, msg: &[u8]) — sends one message (async). Message length is limited to 4 MiB. For local transports, the call waits up to 5 seconds for the recipient to signal "data acked" (named event on Windows, named semaphore on Linux); if no ack is received, returns SocketError::RecipientAckTimeout. The recipient (e.g. message_handler or an external process) must signal the "data acked" event/semaphore after each successful read.
  • message_handler(&self, callback) — invokes an async callback for each incoming message. The callback receives Result<Vec<u8>, SocketError>: Ok(msg) for each message, and Err(e) once when the read loop fails (e.g. connection lost), then the handler task stops.
  • disconnect() — closes the connection (async). After calling it, send_message() and the message_handler callback will see a connection-lost error; the handler task then stops.

Example: send a message and handle incoming messages with a channel:

use ipcez::{socket_init, Socket};
use tokio::sync::mpsc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let socket = socket_init("ipcez").await?;
    let (tx, mut rx) = mpsc::unbounded_channel();
    socket.message_handler(move |result| {
        let _ = tx.send(result);
        async {}
    });
    socket.send_message(b"hello").await?;
    if let Some(Ok(msg)) = rx.recv().await {
        println!("received {} bytes", msg.len());
    }
    Ok(())
}

Connection loss (all transports)

For local connections (Unix domain socket and Windows named pipe), the library runs a liveness check (default 10 ms) on the read path. When the peer disconnects, message_handler receives one Err(SocketError::Io(...)) (e.g. ConnectionReset, message "connection lost") and then the handler task stops, so you get the same kind of notification as when a WebSocket connection is lost. You can also close the connection yourself by calling disconnect(); the same error behavior applies (e.g. one Err(...) to the callback, then the handler exits). send_message can also return an I/O error if the connection is already down. No configuration or code change is needed; this is under the hood.

Local wire format (message framing)

For local connections, the wire format is length-prefixed frames so that one flush sends one message and read returns one message at a time (same semantics as WebSocket). Each frame is: 4-byte big-endian unsigned length (u32), then exactly that many bytes of payload. Messages larger than 4 MiB are rejected with an I/O error. Custom peers (e.g. a server that accepts the raw stream) must use the same frame format to interoperate.

Socket errors

socket_init returns Result<Socket, SocketError>:

  • SocketError::Io(e) — I/O failure (e.g. connection refused, pipe not found).
  • SocketError::Ws(e) — WebSocket handshake or protocol error.
  • SocketError::UnsupportedCombination(target, os) — That (target, os) pair is not supported on this platform (e.g. local + Linux when building on Windows).
  • SocketError::Detection(e) — Environment variable detection failed (os or target unset or invalid); only when using socket_init(endpoint) with a non-URL endpoint.
  • SocketError::RecipientAckTimeout — Local transport: the recipient did not signal "data acked" within 5 seconds after the sender signaled "data ready".

Once connected, send_message can return an I/O error when the connection is lost, or RecipientAckTimeout if the recipient never acknowledges; message_handler receives one Err(...) before the handler task exits, for any transport.


Minimal end-to-end example

use ipcez::{socket_init, Socket};
use tokio::sync::mpsc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Set env: target=local, os=windows (or linux). Or use a ws:// URL for remote.
    let socket = socket_init("ipcez").await?;  // or "ws://localhost:8080/" for remote
    let (tx, mut rx) = mpsc::unbounded_channel();
    socket.message_handler(move |result| {
        let _ = tx.send(result);
        async {}
    });
    socket.send_message(b"hello").await?;
    if let Some(Ok(msg)) = rx.recv().await {
        println!("received {} bytes", msg.len());
    }
    Ok(())
}

Language bindings

Binding Location Requirements
Node/TS wrappers/ts Node 18+, Rust (build addon)
Python wrappers/python Python 3.9+, build ipcez-ffi cdylib
C# wrappers/csharp .NET 8, build ipcez-ffi cdylib

Build the C ABI library for Python and C# from the repo root: cargo build -p ipcez-ffi [--release]. Build the Node addon for TS: cargo build -p ipcez-node [--release]. See each wrapper’s README for install and usage.


Build and test

cargo build
cargo test -- --test-threads=1

Use --test-threads=1 so env-dependent tests do not race.

On Windows you can build and test everything (Rust, C#, Node addon, optional Python smoke) with:

build-and-test.bat

API documentation

Generate and open the crate docs locally:

cargo doc --open

License

MIT OR Apache-2.0