Skip to content

About

Bounded local IPC for scripting hosts and native plug-ins

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

VectorIPC: Bounded local IPC for scripting hosts and native plug-ins

C11 transport core, C++17 ownership façade, and a live-certified Adobe ExtendScript ExternalObject bridge

Protocol Fuzz Illustrator Native ABI License

Part Of The Same Toolkit

Production-grade infrastructure for Adobe ExtendScript.

Runtime Primitives

ESON
Strict RFC 8259 JSON for ExtendScript.

ESB64
Base64 and UTF-8 utilities.

ESARR
ES5+ Array compatibility methods.

ESSTR
String whitespace and trim methods.

ESCHARS
Native bulk byte operations.

ESHTTP
HTTP transport for ExtendScript automation.

ESTIMER
Microsecond timing for ExtendScript automation.

ESRAND
Deterministic random streams and sampling for ExtendScript.

ESUUID
RFC 9562 UUID generation, parsing, and conversion for ExtendScript.

ESENV
Environment and capability detection for ExtendScript.

ESPATH
Deterministic Windows/POSIX path and RFC 8089 file-URI transformations.

ESFS
Synchronous ExtendScript File/Folder I/O with explicit text, BINARY, and replacement semantics.

ESHASH
CRC-32/ISO-HDLC and SHA-256 for byte strings and UTF-8 text.

ESLOG
Structured logging with bounded text and JSONL sinks.

Build & Integration Tools

ESPACK
Self-extracting ExternalObject bundles.

ESMIN
Minification for shipped JSX bundles.

ESABI
Modern ExternalObject ABI declarations for native integrations.

VectorIPC
Bounded local IPC for scripting hosts and native plug-ins.

ESTC
TypeScript-to-ExtendScript build, compatibility, and live-parse tooling.

ESDB
Native state and durable storage for Adobe tooling.

COMTool
Guarded COM, ExtendScript, plug-in, and debugger automation for Adobe desktop apps.

ESsemble
Typed framework, resolver, and composition layer for the ExtendScript toolkit.

Also from the same team: ArcFit.dev, deterministic arc warp for Illustrator.


Table of Contents


Why VectorIPC?

Adobe scripting and native plug-ins frequently need work that should not live in the host process: persistence, networking, compression, expensive parsing, long-running services, or crash-isolated native code. Reimplementing that boundary per product means reimplementing framing, deadlines, cancellation, connection lifetime, security, and host-specific adapters every time.

VectorIPC separates those concerns:

flowchart LR
    ES["ExtendScript / ExternalObject"] --> VIPC["VectorIPC"]
    Native["Native plug-in / C / C++"] --> VIPC
    VIPC --> Helper["Out-of-process helper"]
Loading

The transport owns local IPC mechanics. Applications own operation IDs, payload schemas, serializers, helper lifecycle, and business logic.

VectorIPC v0.1.3 is intentionally small: one fixed binary envelope, one public C ABI, persistent Windows named pipes, a header-only C++ ownership layer, and one ExtendScript ExternalObject adapter. The wire contains opaque bytes; JSON, ESON, CBOR, protobuf, flat structs, or no payload at all are application choices.


Features

  • Fixed 32-byte wire envelope: explicit little-endian encode/decode under VIPC/1.0; the in-memory ABI descriptor is separate from wire layout.
  • Stable C ABI v1: fixed-width public scalar types with compile-time size/offset assertions for vipc_error and vipc_message.
  • C++17 ownership façade: move-only vectoripc::Channel, Server, and non-throwing Error; no second runtime or protocol.
  • Persistent full-duplex channels: one send and one reader-side operation may run concurrently; same-direction overlap returns VIPC_ERR_BUSY.
  • Non-consuming readiness wait: vipc_channel_wait_readable() lets a long-lived server poll idle channels without starting a framed read; readiness timeout is non-destructive and leaves the channel synchronized. A definitive peer-close/I-O failure closes the channel, so vipc_channel_is_open() stays honest. Readiness is poll-based and bounded by the 15.625 ms system timer tick, not by the nominal 2 ms poll constant — see docs/BENCHMARKS.md.
  • Bounded operations: one deadline covers the whole send/receive/accept operation; once a framed receive starts, ambiguous timeout/cancellation states still poison the channel instead of risking frame desynchronization.
  • 16 MiB native wire limit: exact-boundary tests exercise payload sizes through VIPC_MAX_PAYLOAD_BYTES.
  • Local Windows security boundary: current-user DACL, PIPE_REJECT_REMOTE_CLIENTS, Windows-session-scoped endpoints, and peer PID/session metadata.
  • Recoverable listener state: transient clients that connect and disappear before accept cannot permanently strand the server listener.
  • ExternalObject adapter v3: up to 16 generation-tagged logical sessions in one loaded DLL, independent channels/staging buffers, stale-handle rejection; its host ABI is supplied by ESABI v0.3.0.
  • ExtendScript wrapper v5: dependency-free core wrapper with optional ESON, ESB64, and ESCHARS composition.
  • Release reproducibility: npm run release:manifest emits exact commit/tag metadata, per-file and aggregate SHA-256 digests, and a tagged source archive.

Which artifact should I use?

Native C Native C++ ExtendScript
Primary surface <vectoripc/vipc.h> <vectoripc/vipc.hpp> VectorIPC.jsx
Binary vectoripc.lib same vectoripc.lib VectorIPCExternalObject.dll
Runtime model direct C ABI header-only RAII over C ABI synchronous ExternalObject calls
Serialization application-owned application-owned application-owned; optional ESON helpers
Best for helpers, plug-ins, native tools C++ plug-ins/helpers Illustrator ExtendScript

Rule of thumb: share the service endpoint when components belong to the same application, but give each client its own channel.


Get the Release

VectorIPC v0.1.3 is the current public pre-release.

Release assets include:

  • vector-ipc-v0.1.3-windows-x64.zip: installed Windows x64 package: static library, C/C++ headers, CMake package files, ExternalObject DLL/wrapper, and MIT license;
  • vector-ipc-v0.1.3-source.zip: deterministic tagged source archive;
  • vector-ipc-v0.1.3.lock.json: exact commit, compatibility versions, and per-file/aggregate SHA-256 digests;
  • SHA256SUMS.txt: checksums for the published lock, source archive, and Windows package.

For source consumers, pin v0.1.3 rather than tracking the mutable main branch.


Installation

CMake dependency

include(FetchContent)

set(VIPC_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(VIPC_BUILD_BENCHMARKS OFF CACHE BOOL "" FORCE)
set(VIPC_BUILD_EXTERNALOBJECT OFF CACHE BOOL "" FORCE)

FetchContent_Declare(
    vectoripc
    GIT_REPOSITORY https://github.com/thelabcorner/vector-ipc.git
    GIT_TAG v0.1.3
)
FetchContent_MakeAvailable(vectoripc)

target_link_libraries(my_target PRIVATE VectorIPC::vectoripc)

For an installed package:

find_package(VectorIPC 0.1 CONFIG REQUIRED)
target_link_libraries(my_target PRIVATE VectorIPC::vectoripc)

ExtendScript adapter

Build with VIPC_BUILD_EXTERNALOBJECT=ON (the default on Windows). The adapter accepts installed ESABI exactly at 0.3.0; otherwise it fetches the peeled v0.3.0 commit 3e99040c43cef573b477ad372b2a3a96c4d3a7d7 as a private build dependency. Then deploy:

build/Release/VectorIPCExternalObject.dll
src/adapters/externalobject/VectorIPC.jsx

The wrapper remains dependency-free. ESON, ESB64, and ESCHARS are optional codecs used only when explicitly supplied or already loaded.


Quick Start

C++ client

#include <vectoripc/vipc.hpp>

#include <array>
#include <cstdint>

int main() {
    vectoripc::Channel channel;
    vectoripc::Error error;

    if (vectoripc::Channel::connect(
            "my-helper",
            2000,
            channel,
            &error) != VIPC_OK) {
        return 1;
    }

    const std::array<std::uint8_t, 4> request{{1, 2, 3, 4}};

    vectoripc::Message message{};
    message.kind = VIPC_KIND_REQUEST;
    message.flags = VIPC_FLAG_NONE;
    message.operation = VIPC_APP_OPERATION_MIN;
    message.payload_size = static_cast<std::uint32_t>(request.size());
    message.correlation_id = 1;

    if (channel.send(message, request.data(), 2000, &error) != VIPC_OK) {
        return 1;
    }

    return 0;
}

ExtendScript client

$.evalFile(File("/absolute/path/to/VectorIPC.jsx"));

var ipc = VectorIPC.loadPath(
    "C:/absolute/path/to/VectorIPCExternalObject.dll"
);

ipc.connect("my-helper", 2000);

var response = ipc.requestBinaryString(
    0x100,
    String.fromCharCode(0, 1, 2, 255),
    2000
);

ipc.dispose();

For structured messages, requestESON(..., ESON) composes with ESON without making ESON part of the transport.


API

Version domains

Surface v0.1.3
Product version 0.1.3
C ABI 1
Wire protocol VIPC/1.0
Wire header 32 bytes
Native max payload 16 MiB
ExternalObject adapter 3
ExtendScript wrapper 5
ExternalObject max payload 256 KiB
ExternalObject logical sessions 16

These domains are deliberately independent. A product-version bump does not imply an ABI or wire break.

C ABI

Core lifecycle and transport functions:

vipc_server_create(...)
vipc_server_accept(...)
vipc_server_destroy(...)

vipc_client_connect(...)

vipc_channel_wait_readable(...)
vipc_channel_send(...)
vipc_channel_receive(...)
vipc_channel_destroy(...)

Endpoint tokens are application identifiers, not OS paths. Valid tokens use [A-Za-z0-9._-]; Windows maps them to:

\\.\pipe\VectorIPC.<windows-session-id>.<endpoint>

Application operation IDs begin at VIPC_APP_OPERATION_MIN (0x00000100). VectorIPC reserves 0x00000000..0x000000FF for transport/runtime control.

See docs/PROTOCOL.md, docs/NATIVE.md, and docs/EXTERNALOBJECT.md.


Validation

The v0.1.3 native/package candidate was qualified on Windows x64 with MSVC 19.44.35228. The ExternalObject adapter/wrapper are byte-unchanged from v0.1.2, whose live Adobe Illustrator 30.6.0 build 109R qualification remains the current adapter evidence.

Check Command Result
ES3 wrapper + native Release suite + installed package consumers npm test pass; 12/12 CTest targets, C11 and C++17 installed consumers
Protocol fuzz included in npm test 250,000 valid round trips + 250,000 mutated 32-byte headers
Readiness primitive included in npm test repeated idle timeouts, peer close, reader-slot VIPC_ERR_BUSY, long-idle duplex survival
MSVC static analysis npm run test:analyze pass
AddressSanitizer npm run test:asan pass; 12/12 targets
Exact payload boundary npm run test:stress pass through 16 MiB
Timeout/cancellation resources npm run test:stress 2,000 timeout cycles, handles 69 → 69
Accept/write cancellation npm run test:stress 2,000 accepts + 500 blocked writes, handles 69 → 69
Readiness stress npm run test:stress 200 idle rounds, then 20 × 5,000 ms (100 s) long-idle window
Full duplex npm run test:stress 100,000 messages in each direction
Small-frame soak npm run test:stress 1,000,000 verified frames
Idle readiness cost npm run bench:idle 0.104 % of one core over 300 s; blocked control 0.000 ms; wake median 8.116 ms / p95 14.972 ms
Illustrator integration v0.1.2 live evidence (adapter/wrapper unchanged in v0.1.3) adapter v3 / wrapper v5 / raw bytes / sessions / ESON + ESB64 + ESCHARS pass

The protocol decoder is transactional: failed header validation leaves the caller's output descriptor untouched. The mutation fuzzer checks that property across the invalid-header corpus.


Performance

Native measurements below are from the documented Windows test host: AMD Ryzen 9 5900X, Windows 11 Pro build 22631, Release x64, MSVC 19.44.35228, Windows SDK 10.0.26100.0. Each case uses a separate helper process and one persistent VectorIPC channel.

Payload each way Median RTT p95 Duplex user-payload throughput
0 B 10.2 µs 12.7 µs N/A
1 KiB 20.0 µs 23.7 µs 91.6 MiB/s
16 KiB 20.8 µs 35.1 µs 1.40 GiB/s
64 KiB 22.0 µs 43.1 µs 4.69 GiB/s
256 KiB 63.8 µs 111.8 µs 6.57 GiB/s
1 MiB 432.6 µs 621.9 µs 4.24 GiB/s
4 MiB 1.480 ms 2.141 ms 4.81 GiB/s

Helper-process spawn → connected measured 27.575 ms median-of-three-run-medians. A separate repeated 1 KiB baseline measured 12.8 µs median RTT and 124.8 MiB/s median duplex payload, illustrating the scheduler sensitivity of microsecond-scale local IPC measurements.

Live Illustrator 30.6.0 measurements include the ExternalObject boundary:

Path Payload Measured result
raw adapter v3 round trip 7 B 19 µs median-of-run-medians
hardened UTF-8 requestText echo 64 KiB 1.15 ms median-of-run-medians
hardened UTF-8 requestText echo 256 KiB 4.44 ms median-of-run-medians

Representation dominates large ExtendScript payloads: the documented 64 KiB numeric-array pack measured about 4.0 s, versus about 0.11 s for the binary-string path. See docs/BENCHMARKS.md for methodology, repeated runs, and scripting-lane comparisons.


Security Model

VectorIPC's v0.1.3 Windows trust boundary is local OS identity, not application authentication:

  • named pipes use a DACL restricted to the current user;
  • PIPE_REJECT_REMOTE_CLIENTS rejects remote clients;
  • endpoint names are scoped by Windows session;
  • clients request SecurityIdentification, not impersonation;
  • peer PID and session metadata are exposed to the application;
  • malformed frames, unknown flags, invalid versions, oversized payloads, and truncated streams fail closed;
  • deadlines bound host-visible waits, and uncertain cancellation states poison the affected channel.

A different process running as the same user in the same session remains inside that OS trust boundary. Applications requiring stronger peer identity should add an application-level handshake, capability/build check, per-launch nonce, or equivalent policy above VectorIPC.

Payload bytes are untrusted application data. VectorIPC does not deserialize, evaluate, or execute them.


Compatibility

Target Status
Windows x64 native C11 supported and release-tested
Header-only C++17 façade supported and release-tested
CMake install / find_package(VectorIPC 0.1) supported; C11 + C++17 consumers tested
Adobe Illustrator 30.6.0 ExternalObject live-certified
ESON / ESB64 / ESCHARS composition live-certified in Illustrator 30.6.0
Native Adobe .aip integration same C/C++ ABI intended for direct linking; not separately host-certified in v0.1.3
macOS / POSIX explicit unsupported stub in v0.1.3; Unix-domain transport planned
32-bit Windows not a v0.1.3 release target

The generated CMake package uses SameMinorVersion: a 0.1.x package satisfies a 0.1 request, while the package smoke test proves that 0.1.3 does not satisfy a 0.2 request.


Engine quirks that shaped the design

All host-specific observations below were reproduced in Adobe Illustrator 30.6.0.

ExternalObject DLLs can remain mapped after unload(). Live verification therefore never loads the canonical build output directly. Each probe stages a uniquely named content copy under the user's temporary vector-ipc-live directory, so Illustrator module caching cannot lock or pollute the repository/build tree.

Large ES3 numeric arrays are an expensive bulk representation. At 64 KiB, numeric-array packing measured about 4.0 s on the documented host; binary-string packing measured about 0.11 s. VectorIPC keeps arbitrary binary on the wire and exposes binary-string/text paths so callers are not forced through indexed numeric arrays.

Invalid UTF-16 can be silently normalized at the host boundary. A live probe showed Illustrator 30.6.0 normalize a lone surrogate into an empty ExternalObject string. The wrapper validates Unicode scalar structure before crossing that boundary.

ExternalObject method binding is a real compatibility surface. Repository probes observed selective/reproducible binding failures per DLL build. The adapter therefore exposes one numeric vipc(command, ...) method and multiplexes commands behind that single binding.


Development

Requirements for the certified Windows path:

  • CMake 3.20+
  • Visual Studio 2022 Build Tools with the x64 C/C++ toolchain
  • Node.js for repository tooling
  • Python only for live Illustrator probe orchestration
npm test
npm run test:analyze
npm run test:asan
npm run test:stress
npm run verify

# Requires a running Illustrator instance:
npm run verify:live

# Measurements, not release thresholds:
npm run bench
npm run bench:matrix
npm run bench:live
npm run bench:live:matrix
npm run bench:live:wrapper
npm run bench:live:text -- --size 65536 --samples 5
npm run bench:live:decode

Release locks are generated only from a clean, correctly tagged tree:

npm run release:manifest

The manifest records the exact commit/tag, ABI/protocol/adapter/wrapper versions, per-file and aggregate SHA-256 digests, and a tagged source archive.


Repository layout

flowchart TD
    Repo["vector-ipc/"]

    Repo --> Include["include/vectoripc/<br/>Public C/C++ headers"]
    Repo --> Src["src/"]
    Repo --> Tests["tests/<br/>Unit, fuzz, boundary, stress, package tests"]
    Repo --> Bench["benchmarks/<br/>Native transport benchmarks"]
    Repo --> Tools["tools/<br/>Build, analysis, live probes, release tooling"]
    Repo --> Docs["docs/<br/>Protocol, architecture, adapter, benchmark docs"]

    Src --> Core["core/<br/>Protocol/status core"]
    Src --> Platform["platform/"]
    Src --> Adapters["adapters/externalobject/<br/>Native adapter + ES3 wrapper"]

    Platform --> Win["win/<br/>Windows named-pipe transport"]
    Platform --> Posix["posix/<br/>Explicit v0.1 unsupported stub"]
Loading

Durable design decisions are recorded in docs/DECISIONS.md.


Known limitations

  • v0.1.3 implements the transport only on Windows; the POSIX source is intentionally an unsupported stub.
  • vipc_channel_destroy() and vipc_server_destroy() are not cross-thread cancellation APIs. Use finite deadlines, let in-flight work return, join the owning worker, then destroy the object.
  • vipc_channel_wait_readable() is a PeekNamedPipe() poll. Sleep() rounds up to the 15.625 ms system timer tick, so readiness latency is tick-bounded (measured 8.1 ms median / 15.0 ms p95) rather than the 2 ms the poll constant names. A product needing sub-tick readiness should use a different mechanism, not a tighter constant.
  • The ExternalObject adapter is synchronous at the Illustrator call boundary even though its logical sessions own independent native channels.
  • The ExternalObject adapter caps payloads at 256 KiB even though the native wire cap is 16 MiB.
  • Large legacy ExtendScript numeric arrays remain expensive; text/binary-string representations are the preferred bulk paths.
  • Helper spawning, helper updates, authentication handshakes, application serializers, and product lifecycle policy are intentionally above the transport.

Credits

VectorIPC's Adobe-host integration builds on the public ExtendScript/ExternalObject ecosystem documented by docsforadobe and Adobe's scripting interfaces. The Windows transport is built on documented Win32 named-pipe, overlapped-I/O, ACL, and process/session APIs.

ArcFit.dev provided the public production precedent for isolating Illustrator work behind a bounded native/helper boundary; VectorIPC generalizes that pattern into a product-neutral transport rather than reusing product-specific protocol code.


License

MIT. See LICENSE.

About

Bounded local IPC for scripting hosts and native plug-ins

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages