[ANN] aDSA 1.3.0 — ZeroMQ-backed Annex E (DSA) runtime for GNAT — now runs on Windows and FreeBSD

aDSA is a free (GPLv3 + GCC Runtime Library Exception) Partition
Communication Subsystem for Ada’s Distributed Systems Annex (Annex E),
built on ZeroMQ — a runtime replacement for the deprecated PolyORB /
GLADE. The compiler side of Annex E is alive and well in FSF GNAT; aDSA
supplies the other half: System.RPC / System.Partition_Interface
implementations the generated stubs call into, plus pcs_gnatdist, a
gnatdist-style build tool that takes the classic .cfg configuration
language and produces the partition executables and a launcher.

It covers the full annex — RCI, RACW/RAS (including callbacks),
Shared_Passive (three backends: file, a cross-host store server, and
same-host shared memory), asynchronous calls, termination policies,
restart recovery, and the RM E.3 version check — plus opt-ins for zlib
compression, XDR wire format, and CurveZMQ encryption with per-partition
peer authentication. It is validated against the original PolyORB
examples/dsa corpus.

New in 1.3.0 — the platform release:

  • Windows 11: full bring-up with the Alire toolchain (gnat_native
    15.2.1 + gprbuild 26.0.1, Alire’s bundled MSYS2 bash). The verification
    corpus passes 6/6 on both build paths — the same scoreboard as Linux —
    and all three Shared_Passive backends run, including shared memory.
  • FreeBSD 15.1: corpus plus the full feature sweep (XDR, peer-auth,
    and a real two-machine cross-host deployment).
  • pcs_gnatdist now lints RCI specs against the RM E.2.3 legality rules
    up front, instead of letting violations surface as puzzling linker
    errors.
  • Assorted fixes — details in the changelog.

Linux remains the primary platform (Arch users get a PKGBUILD whose
pacman hook rebuilds the runtime automatically on every gcc-ada
upgrade).

Repo

Release

User’s Guide

One known FSF GNAT limitation worth mentioning: dispatching to an RACW
of a Remote_Types interface can pick the wrong slot when the concrete
type declares its own primitives before its overrides (filed as PR
ada/126014, reproducer included in the repo). The documented workaround
is to route such calls through a concrete RCI.

There is also a sibling project, ipDSA
( charlie5/ipDSA: An Ada DSA implementation using Kazakov simple component inter-process streams and sockets for comms. - Codeberg.org ), which is the same PCS with the
transport swapped for shared-memory interprocess streams — for
same-host-only deployments with no network stack involved. The two
Arch packages co-install cleanly.

Feedback, bug reports, and testing on other platforms are very welcome.

9 Likes

Hi Charlie5,

Nice work on making PolyORB ZeroMQ backed.

Some questions:

  • Is there a recommended way to detect if a partition has already started? So it is possible to gracefully shutdown a partition if it is already started.
  • I also wonder about encryption. I looked for an example where peer authentication on partitions is enabled but could not see an example in the examples/ directory. Or am I mistaken? In the documentation it says " DSA_AUTH=peer + DSA_NS_CURVE_*", how should one interpret DSA_NS_CURVE_* ? Should each partition have its own DSA_NS_CURVE_<partition_name> or how should one use the DSA_NS_CURVE_* environment variables?

Best regards,
Joakim

Hi Joakim,

Thanks — both of these were genuinely underdocumented, and you prompted me to
fix them, so this reply also comes with runnable code and clearer docs on the
main branch.

Encryption / DSA_NS_CURVE_*. You read the notation the reasonable way, and
it was ambiguous — sorry. The * is just shorthand for the two suffixes
SECRET and PUBLIC; it is not a per-partition placeholder. There is
no DSA_NS_CURVE_<partition>, and you never configure a key per partition by
hand.

There are two separate models:

  1. Shared system key (DSA_CURVE_SECRET / DSA_CURVE_PUBLIC): a single
    keypair for the whole deployment — secret on binders, public on connectors;
    in practice set both on every process. Encrypts all traffic, but any holder
    of the pair is trusted. Generate with eval "$(pcs_keygen)".

  2. Per-partition mutual authentication (DSA_AUTH=peer): here
    DSA_NS_CURVE_SECRET/DSA_NS_CURVE_PUBLIC is the infrastructure keypair —
    one pair for the entire deployment
    , generated with pcs_keygen ns.
    Distribute it like this:

    • DSA_NS_CURVE_SECRET → the name server (and pcs_dsm, if used) only;
      it becomes the trust anchor.
    • DSA_NS_CURVE_PUBLICevery process (all partitions and the name
      server), which pin the name server’s key.
    • DSA_AUTH=peerevery process as well.
    • Each partition generates its own keypair automatically at startup
      nothing to configure. It registers its public key with the name server, and
      partitions authenticate one another by asking the name server whether a
      presented key is a registered partition’s (a ZAP handler + an internal
      KEYOK check). An unregistered identity is rejected at the handshake.

    On one host you can eval "$(pcs_keygen ns)" once and launch everything from
    that shell (each process reads only what it needs). Across hosts: secret only
    on the name-server host, public everywhere.

You were also right that there was no runnable peer-auth example — only
rogue.adb. There’s now a complete one: examples/peer-auth/
(run_peer_auth.sh) — it sets up the infra key, runs a legitimate registered
client that succeeds, then fires the rogue with a fresh unregistered identity,
which is denied at the ZAP handshake. The DSA_NS_CURVE_* explanation above is
now written up in docs/users-guide.md §9 (“Encryption in practice”). Both
are on main.

Detecting an already-started partition. There’s no turnkey “singleton /
refuse-second-instance” feature today, but the pieces are there:

  • Detection: the name server is the registry — every partition registers
    its units on startup. So “is a partition hosting unit X already up?” is
    answerable by resolving X. From Ada,
    System.Partition_Interface.Get_Active_Partition_ID ("X") returns the hosting
    partition’s id if one is running, and raises Communication_Error if not —
    you can call that in your main before handing off to Run.
  • What happens if you start a second instance today: both register; the name
    server’s unit→partition mapping is latest-wins, so the new instance
    transparently takes over and callers re-resolve to it (the restart-recovery
    path). It does not error or refuse.
  • Gracefully shutting the existing one down: the mechanism exists — the name
    server sends a one-byte 'X' shutdown frame to a partition’s control endpoint
    (that’s how global termination and the liveness watchdog stop partitions) —
    but it isn’t yet exposed as a targeted “shut down partition N” command. A small
    name-server verb, or a one-line client that resolves the unit and sends 'X'
    to its control endpoint, would give exactly the “gracefully replace the
    already-started instance” behaviour you want.

This “detect and replace” pattern is really the job of a supervisor/orchestration
layer on top of the PCS, which I’ve just sketched as a design entry — see
docs/developers-guide.md §16. Happy to go deeper on any of it.

Regards.

3 Likes

Thanks charlie5 for the clarifications and detailed explanations!

Best regards,
Joakim

Is it possible to use it with a process running on a PC (Windows or Linux) on one side and a process running on a ARM device (Linux) on the other side ?

A good question and the answer should be yes.

The DSA_XDR=1 build flag should take care of any endian difference, as well as any type size differences (such as is possible with Long_Integer).

I have not tested for ARM, as yet, but will do so today and let you know the results.

I have run a test with 3 partitions across separate Windows, freeBSD and Linux VM’s successfully. The freeBSD box even used an older GCC/GNAT (v15) than on the other two (v16).

Anyways, will test ARM now.

2 Likes

Just tested on ARM with Debian 13 and GCC/GNAT v14. All tests ran successfully, except that I was mistaken about XDR handling type size differences (such as Long_Integer). These aren’t handled by XDR but a warning is now given by gnatdist when such types are used.

Which one? Debian supports both armhf (32-bit ARM) and aarch64 (64-bit ARM).

armhf

armv7l, machine virt-11.0, cortex-a15, 4 vCPUs, 3 GB RAM

The other partition was running under arch linux on an amd64 box.

Interesting. Thanks.

Maybe I’ll use it in a future project at work if Ada is used, which is not sure at all :cry: