Skip to content
cheebinhohPublic

About

Distributed Messaging Network

Resources

Stars

1 star

Watchers

1 watching

Forks

Repository files navigation

<!-- Copyright © 2025 - 2026 Chee Bin HOH. All rights reserved. -->

# Dmn: Distributed Messaging & Synchronization

Dmn is a C++ library for distributed messaging and data synchronization. It
provides building blocks for networking, message exchange, concurrency control,
and conflict resolution, with an emphasis on delegation and integration into
existing applications.

On Linux, Dmn also provides `dmn-bluetooth-cosmic`, an optional Rust/libcosmic
desktop app for COSMIC with the same workflow (see
[COSMIC Bluetooth application](#cosmic-bluetooth-application)), and
`dmn-bluetooth-gtk`, an optional GTK 4/libadwaita
desktop app for browsing BlueZ devices and explicitly managing Bluetooth
connections and ANCS notification monitoring. It is a separate frontend from
the `dmn-bluez-cli` discovery tool and `dmn-bluetooth-cli` ANCS monitor.

## Documentation

Generated documentation: <https://cheebinhoh.github.io/Dmn/>

## Build, test, and install

The default Linux CI workflow uses the Dockerfile to build and test the
complete configuration: DMesg messaging, Kafka, all three Bluetooth
applications, and their registered tests. A plain CMake configure remains
lean: each app option defaults to `OFF`, while enabling an app automatically
enables its required ANCS, BlueZ, and D-Bus libraries. CMake fetches missing
third-party dependencies when `DMN_ALLOW_DEPENDENCY_FETCH=ON`.

### 1. Complete Linux build and test

On Ubuntu 24.04, install the native build and test prerequisites:

```bash
sudo apt-get update
sudo apt-get install build-essential cmake git pkg-config libicu-dev \
  libdbus-1-dev dbus-daemon libgtk-4-dev libadwaita-1-dev \
  desktop-file-utils appstream valgrind openjdk-17-jre-headless curl tar \
  unzip automake autoconf libtool
```

Configure and build all supported software features. This enables the two
Bluetooth CLIs, GTK desktop app, the COSMIC C library, Kafka integration tests, Valgrind test
variants, and fault-injection tests; physical-device tests remain opt-in.
The optional application flags are the only flags needed to select the apps:
their ANCS, BlueZ, and D-Bus dependencies are enabled automatically.

```bash
cmake -S . -B build-all -DCMAKE_BUILD_TYPE=Debug \
  -DDMN_BUILD_TESTS=ON \
  -DDMN_ALLOW_DEPENDENCY_FETCH=ON \
  -DBUILD_KAFKA_TEST=ON \
  -DENABLE_APP_BLUEZ_DISCOVERY_CLI=ON \
  -DENABLE_APP_BLUETOOTH_CLI=ON \
  -DENABLE_APP_BLUETOOTH_GTK=ON \
  -DENABLE_APP_BLUETOOTH_COSMIC=ON \
  -DENABLE_VALGRIND=ON \
  -DENABLE_FAULT_INJECTION=ON
cmake --build build-all --parallel 2
./build-all/scripts/run-bookstrap-dmn.sh
ctest --test-dir build-all --output-on-failure
./build-all/scripts/run-bookstrap-dmn.sh stop
```

CTest runs every test registered by this configuration, including the GTK
headless smoke/metadata tests and the COSMIC engine/C ABI test (the Rust app
itself is built by Cargo; see
[COSMIC Bluetooth application](#cosmic-bluetooth-application)). Kafka tests need Java and a first-run download;
the helper starts a local broker and creates the required topic. The GUI is
compiled and tested without opening a desktop window. To build and run the
same full Linux configuration in the project's Docker image, see
[Docker](#docker). The default Linux CI workflow builds and tests this image.

### 2. Minimal GTK app build and runtime install

For the GTK Bluetooth app without Dmn's DMesg/DMesgNet messaging stack (and
therefore without Protobuf, librdkafka, or Kafka), install only its required
Ubuntu development packages:

```bash
sudo apt-get install build-essential cmake git pkg-config libicu-dev \
  libdbus-1-dev libgtk-4-dev libadwaita-1-dev
```

Configure with just the GTK app enabled. This minimal app-only build disables
tests and dependency fetching; GTK automatically enables its ANCS, BlueZ, and
D-Bus libraries. Set the final install prefix at configure time because the
desktop launcher and D-Bus activation service contain prefix-dependent paths.

```bash
cmake -S . -B build-gtk-app \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_INSTALL_PREFIX="$HOME/.local" \
  -DENABLE_APP_BLUETOOTH_GTK=ON \
  -DENABLE_DMN_DMESGNET=OFF \
  -DDMN_BUILD_TESTS=OFF \
  -DDMN_ALLOW_DEPENDENCY_FETCH=OFF
cmake --build build-gtk-app --target dmn-bluetooth-gtk --parallel 2
cmake --install build-gtk-app --component Runtime
"$HOME/.local/bin/dmn-bluetooth-gtk"
# Hardware-free review: fake devices, no BlueZ, saved devices untouched
"$HOME/.local/bin/dmn-bluetooth-gtk" --demo
```

`Runtime` installs the executable and desktop assets. `Development` installs
the reusable headers, static libraries, and developer tools; an unqualified
`cmake --install build-gtk-app` installs both components.

### 3. Developer build and install

For the core library and regular unit tests without optional Bluetooth apps:

```bash
cmake -S . -B build-dev -DCMAKE_BUILD_TYPE=Debug \
  -DCMAKE_INSTALL_PREFIX="$HOME/.local"
cmake --build build-dev --parallel 2
ctest --test-dir build-dev -L dmn --output-on-failure
cmake --install build-dev --component Development
```

To run every test registered in this developer configuration, use
`ctest --test-dir build-dev --output-on-failure`. Optional applications and
test groups are selected at configure time; see [Build options](#build-options).

## Optional build and test configurations

The developer workflow runs the regular unit tests. To run every test
registered in that build, use:

```bash
ctest --test-dir build-dev --output-on-failure
```

The optional configurations below add features or test groups. Use a
separate build directory when trying a different set of CMake options.

### Valgrind tests

Install Valgrind (on Ubuntu, `sudo apt-get install valgrind`) and enable its
test variants at configure time:

```bash
cmake -S . -B build-valgrind -DCMAKE_BUILD_TYPE=Debug \
  -DENABLE_VALGRIND=ON
cmake --build build-valgrind --parallel 2
ctest --test-dir build-valgrind -L 'dmn|valgrind' --output-on-failure
```

This runs regular tests plus their Valgrind variants. The `valgrind` variants
are registered only when `ENABLE_VALGRIND=ON`. They cover the library tests
that do not link GUI code (including the ANCS, D-Bus and BlueZ GATT suites
when enabled); GTK/COSMIC tests and `dmn-test-bluez-device-list` (too slow
under Valgrind) are excluded.

### Kafka integration tests

Kafka tests are optional and require a broker available at
`localhost:9092`. Either start a broker and create the `timer_counter` topic
yourself, or enable `BUILD_KAFKA_TEST=ON` and use the staged project helper.
For example, with Kafka's command-line tools:

```bash
kafka-topics.sh --create --topic timer_counter --bootstrap-server localhost:9092
```

Then configure, build, and run the Kafka tests:

```bash
cmake -S . -B build-kafka -DCMAKE_BUILD_TYPE=Debug \
  -DBUILD_KAFKA_TEST=ON
cmake --build build-kafka --parallel 2
./build-kafka/scripts/run-bookstrap-dmn.sh
ctest --test-dir build-kafka -L kafka --output-on-failure
./build-kafka/scripts/run-bookstrap-dmn.sh stop
```

The helper downloads and verifies the pinned Kafka distribution when needed;
it requires Java and network access on first use. The Docker entrypoint starts
Kafka and creates this topic automatically.

### D-Bus extension

The optional D-Bus extension provides byte-oriented input/output adapters
that send and receive D-Bus signals, plus a typed generic client for method
calls, properties, and signal subscriptions. The signal adapters can carry
serialized DMesg messages through `Dmn_DMesgNet` or other application
payloads using a configured signal routing tuple. The extension is intended
for inter-process communication on one host and does not provide cross-host
transport. The core `dmn` library does not depend on D-Bus; the extension is
built separately as `dmn-dbus` and is disabled by default. `Dmn_DMesgDbus` is
the DMesg-specific composition facade for applications that do not need to
construct the underlying D-Bus endpoints directly; advanced callers can
continue to inject `Dmn_DbusInput` and `Dmn_DbusOutput` into `Dmn_DMesgNet`.
When an application links `dmn-dbus`, the target defines `DMN_ENABLE_DBUS`,
so `dmn.hpp` also includes the D-Bus configuration, endpoint, and facade
headers. Generic clients include `dmn-dbus-client.hpp` and
`dmn-dbus-value.hpp` directly; BlueZ clients include
`dmn-bluez-client.hpp`. Without the optional target and its compile
definitions, the umbrella header does not include the optional D-Bus API. The
`dmn-fault-injection.hpp` header is included by `dmn.hpp` only when
`ENABLE_FAULT_INJECTION=ON` defines `FIU_ENABLE`; its CMake target-source
registration is gated by the same option.

On Ubuntu, install the libdbus development files, pkg-config, and daemon used
by the private-bus unit test:

```bash
sudo apt-get install libdbus-1-dev pkg-config dbus-daemon
```

Enable the extension and run its test (registered under the regular `dmn`
CTest label):

```bash
cmake -S . -B build-dbus -DCMAKE_BUILD_TYPE=Debug -DENABLE_DBUS=ON
cmake --build build-dbus --parallel 2
ctest --test-dir build-dbus -R '^dmn-test-dbus-(io|facade)$' \
  --output-on-failure
```

### BlueZ discovery CLI

On Linux, the optional `dmn-bluez-cli` program prints BlueZ's known
adapter/device inventory and can explicitly start a scan on one powered
adapter. It depends on the Dmn D-Bus extension, but not on GATT or ANCS.
Install the D-Bus development package and daemon as above, then configure and
build both optional Bluetooth CLIs:

```bash
cmake -S . -B build-cli -DCMAKE_BUILD_TYPE=Debug \
  -DENABLE_APP_BLUEZ_DISCOVERY_CLI=ON -DENABLE_APP_BLUETOOTH_CLI=ON \
  -DENABLE_APP_BLUETOOTH_GTK=OFF \
  -DCMAKE_INSTALL_PREFIX="$HOME/.local"
cmake --build build-cli --target dmn-bluez-cli dmn-bluetooth-cli \
  dmn-test-bluez-device-list --parallel 2
cmake --install build-cli --component Runtime
```

This CLI-only configuration enables both command-line programs and disables
the GTK app. `Runtime` is project-wide: it installs runtime files for every
application enabled in the configured build. Add
`-DENABLE_APP_BLUETOOTH_GTK=ON` to include the GTK app too. To install Dmn's
reusable headers and libraries as well, install the `Development` component;
an unqualified install includes both components. Add the configured local
prefix to `PATH` and view the manual pages with:

```bash
export PATH="$HOME/.local/bin:$PATH"
man -M "$HOME/.local/share/man" dmn-bluez-cli
man -M "$HOME/.local/share/man" dmn-bluetooth-cli
```

Print the current known-device inventory as CSV without starting discovery:

```bash
./build-cli/src/app/dmn-bluez-cli
```

The CSV includes the adapter address and path needed to select a scan target.
Start an explicit scan with either selector; if exactly one powered adapter
exists, `--adapter` may be omitted:

```bash
./build-cli/src/app/dmn-bluez-cli --discovery \
  --adapter 'B8:F7:75:BF:F3:C3'
```

The CLI prints initial known devices, then live add/change/remove rows until
Ctrl-C or SIGTERM. It stops only the discovery session it started. It does
not pair, connect, change trust, or power an adapter. Scanning requires a
working BlueZ system service and local system-bus policy that permits the
discovery calls; the CLI does not fall back to the session bus. For argument
syntax, run `./build-cli/src/app/dmn-bluez-cli --help`. The private-bus
integration coverage can be run with:

```bash
ctest --test-dir build-cli -R '^dmn-test-bluez-device-list$' \
  --output-on-failure
```

### Shared Bluetooth engine

`dmn-bluetooth-cli`, `dmn-bluetooth-gtk` and `dmn-bluetooth-cosmic` all drive
the same GUI-independent engine (`dmn::gui::Dmn_Engine`, static library
`dmn-bluetooth-engine`, sources in `src/app/engine` and `include/app/engine`).
It has no GTK or GLib dependency and is built automatically whenever any of
`ENABLE_APP_BLUETOOTH_CLI`, `ENABLE_APP_BLUETOOTH_GTK` or
`ENABLE_APP_BLUETOOTH_COSMIC` is on, so each app can be built on its own or
together with the others. The engine has two backends: the BlueZ backend
(real hardware over BlueZ/ANCS) and a demo backend (synthetic phones, no
hardware, used by `--demo`). Only the GTK and COSMIC apps need GLib, through
`dmn-bluetooth-gui-core`; the CLI does not. See the header comment in
`include/app/engine/dmn-engine.hpp` for the architecture.

### Bluetooth ANCS monitor CLI

The optional `dmn-bluetooth-cli` command pairs/connects one selected device as
needed and monitors its ANCS notifications through the shared engine:

```bash
./build-cli/src/app/dmn-bluetooth-cli monitor "iPhone Genghis"
```

See `man dmn-bluetooth-cli` for its options, output, failure handling, and
recovery guidance. This CLI requires a working BlueZ system service and does
not provide a graphical interface.

### GTK Bluetooth application

On Linux desktops, the optional `dmn-bluetooth-gtk` app provides a graphical
interface for browsing BlueZ devices, saving a device selection, connecting
and disconnecting, and explicitly starting or stopping ANCS monitoring. The
GTK application and CLI are separate frontends to the same shared engine;
enabling the GTK build does not build or replace the CLI.
Optional desktop alerts use GNotification. All Bluetooth behavior comes from the
shared engine that the CLI and COSMIC app also use; the GTK code only renders it.
The window has a left device panel (search, **Selected only**, sorting, and the
"Previous selected devices" and "Nearby devices" groups) and a notifications
panel. Run `dmn-bluetooth-gtk --demo` to try it with fake devices and no
Bluetooth hardware; demo mode never touches saved devices.

The app never connects automatically. **Start monitoring** is an explicit
choice saved per device, so the app can restore that intent on later launches.
It starts the monitor only after the device is saved, paired, and BlueZ
confirms it is connected; if disconnected, it waits for you to connect it.
**Stop monitoring** clears the saved intent. Disconnecting or exiting stops
the live monitor but preserves the intent, so monitoring can resume after
reconnection or relaunch. The newest-first event feed contains only
notifications received while the app process is running; it is not a
persistent notification archive or a desktop notification-history reader.
Search and device, event-kind, and category filters compose; each
notification has a Details button. A sidebar toggle at the left edge of the
header bar opens and closes the device panel; its visibility and the Selected
only setting are remembered across launches. The app scans for its whole
lifetime, so there are no Scan buttons. The feed is capped at 1,000 in-memory events, with older-event eviction
reported in the interface. Hovering over notification text shows its full
value wrapped to the available feed width.
The GTK app requires a running BlueZ system service and a graphical desktop
session.

GTK 4 4.14 or newer and libadwaita 1.5 or newer are required. Follow the
[minimal GTK app build](#2-minimal-gtk-app-build-and-runtime-install) above
to build and install this app. The GTK option enables the required ANCS,
BlueZ, and D-Bus libraries but remains off in default builds.

For the implemented UI, remaining features, and current design details, see
the [GTK application specification](docs/specs/dmn-bluetooth-gtk-app-spec.md)
and its [UI storyboard](docs/specs/dmn-bluetooth-gtk-ui-storyboard.md).

### COSMIC Bluetooth application

`dmn-bluetooth-cosmic` is a native COSMIC (Rust, libcosmic) frontend with the
same workflow as the GTK app: browse BlueZ devices, save a selection, connect,
disconnect, and explicitly start or stop ANCS monitoring. It reuses Dmn's C++
BlueZ/ANCS/D-Bus code through a C library (`dmn-bluetooth-cabi`) and a
JSON-protocol engine; the UI contains no Bluetooth logic. Settings are stored
in `$XDG_CONFIG_HOME/dmn-bluetooth-cosmic/settings.json`. The device panel can
be hidden with the toggle in the header bar. The list has two groups:
"Previous selected devices" (saved selection) and "Nearby devices" (nearby or
unselected); a device changes group only when its selection is saved or
discarded. The device list and notification feed scroll independently.

The app scans for devices for as long as it runs and the list updates as
results arrive, so there are no Scan or Refresh buttons. The toolbar has a
search box (half the device panel wide, always visible), the Selected only
checkbox, and the Save and Discard buttons; the header-bar menu has the
Desktop alerts checkbox (on by default) and About; the search box filters both groups
by any part of a device (name, address, status). Notification titles and
messages can be highlighted and copied (right-click Copy / Select All), and
hovering a notification shows its full text.

Cargo is the build entry point. Its build script (`dmn-build.rs`) runs CMake
with `-DENABLE_APP_BLUETOOTH_COSMIC=ON`, so no manual CMake step is needed:

```bash
sudo apt install build-essential cmake pkg-config libdbus-1-dev \
  libfontconfig-dev libfreetype-dev libexpat1-dev libwayland-dev \
  libxkbcommon-dev
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
cd src/app/gui/cosmic
cargo build --release --locked
cargo test --workspace --locked     # no Bluetooth hardware needed
cargo fmt --all --check
./target/release/dmn-bluetooth-cosmic --demo   # fake devices, no BlueZ
```

The binary loads `libdmn-bluetooth-cabi.so` through an rpath emitted by the
build script; when copying the binary elsewhere, copy the library next to it
and set the rpath to `$ORIGIN` (for example with `patchelf`). Desktop entry,
AppStream metadata, and icons are in `src/app/gui/cosmic/data/`. Optional
environment variables: `DMN_SOURCE_DIR`, `DMN_CMAKE_GENERATOR`, and
`DMN_CMAKE_ARGS`.

Running CMake directly with `-DENABLE_APP_BLUETOOTH_COSMIC=ON` builds only the
C library and its tests (`ctest -L dmn-gui`); the option is off by default and
the default build never requires Rust. The Docker image builds and tests the
app; pass `--build-arg ENABLE_APP_BLUETOOTH_COSMIC=OFF` to skip it. See the
[COSMIC specification](docs/specs/dmn-bluetooth-cosmic-spec.md).

### Fault-injection tests

Fault-injection support is optional and disabled by default. Enabling it links
libfiu into the fault-injection build. CMake uses an installed libfiu package
found through pkg-config when available; otherwise it fetches and builds
libfiu 1.2 and its `fiu-run` launcher.

```bash
cmake -S . -B build-fi -DCMAKE_BUILD_TYPE=Debug \
  -DENABLE_FAULT_INJECTION=ON
cmake --build build-fi --target fault-injection-tests --parallel 2
ctest --test-dir build-fi -L fault-injection --output-on-failure
```

Fault-injection tests are GoogleTest executables invoked by CTest through
`fiu-run`. They use the separate `fault-injection` label and are not included
in the regular `dmn` test group. To run one test by name:

```bash
ctest --test-dir build-fi -R '^dmn-test-fi-timer-thread-start-failure$' \
  --output-on-failure
```

The recurring-reschedule failure test can be run individually with:

```bash
ctest --test-dir build-fi -R '^dmn-test-fi-timer-reschedule-failure$' \
  --output-on-failure
```

### Docker

From the repository root, build the image:

```bash
docker build -t dmn .
```

Start it interactively with its normal entrypoint:

```bash
docker run -it --rm dmn
```

The image installs GTK 4/libadwaita development packages and the Rust
toolchain, enables all three
applications plus Kafka, Valgrind, and fault-injection tests, and builds all
configured targets. On startup, the entrypoint bootstraps Kafka and runs the
regular, CLI, GTK, COSMIC (`cargo test`), Kafka, Valgrind, and
fault-injection tests. These tests do
not launch a graphical window; showing the app itself requires forwarding a
host graphical session. If tests pass, the container opens a Bash prompt; if
they fail, it exits. The run command removes the container on exit because it
uses `--rm`. The default Linux CI workflow builds and runs this Docker image.

To enter a shell immediately without starting Kafka or running tests, bypass
the entrypoint:

```bash
docker run -it --rm --entrypoint /bin/bash dmn
```

The Docker build leaves assertions enabled. To build with Docker Buildx and
load the image into the local Docker engine:

```bash
docker buildx build -t dmn:latest --load .
```

Remove the image when it is no longer needed:

```bash
docker rmi dmn
```

### Build options

| Option | Default | Purpose |
| --- | --- | --- |
| `BUILD_NDEBUG` | `OFF` | Define `NDEBUG` for the build. |
| `BUILD_KAFKA_TEST` | `OFF` | Build the additional Kafka integration tests when `ENABLE_DMN_DMESGNET` is also `ON`; otherwise they are skipped with a CMake status message. |
| `ENABLE_DMN_DMESGNET` | `ON` | Build the Protobuf-based DMesg/DMesgNet messaging stack and its Kafka transport; requires Protobuf and librdkafka. Set `OFF` for applications that do not use DMesg messaging, such as the GTK Bluetooth app. DMesg, DMesgNet, and Kafka tests are registered only when this option is `ON`; other tests remain available. |
| `ENABLE_DBUS` | `OFF` | Build the optional D-Bus signal I/O extension and generic D-Bus client. Requires libdbus development files and pkg-config; private-bus tests require `dbus-daemon`. |
| `ENABLE_BLUEZ` | `OFF` | Build the Linux BlueZ library; automatically enables its `ENABLE_DBUS` dependency. |
| `ENABLE_ANCS` | `OFF` | Build the backend-neutral ANCS protocol library. |
| `ENABLE_APP_BLUEZ_DISCOVERY_CLI` | `OFF` | Build and install `dmn-bluez-cli`; automatically enables `ENABLE_BLUEZ` and `ENABLE_DBUS`. |
| `ENABLE_APP_BLUETOOTH_CLI` | `OFF` | Build and install `dmn-bluetooth-cli`; automatically enables `ENABLE_ANCS`, `ENABLE_BLUEZ`, and `ENABLE_DBUS`. |
| `ENABLE_APP_BLUETOOTH_GTK` | `OFF` | Build and install the Linux GTK 4/libadwaita `dmn-bluetooth-gtk` desktop app; automatically enables `ENABLE_ANCS`, `ENABLE_BLUEZ`, and `ENABLE_DBUS`. Requires GTK 4 4.14+, libadwaita 1.5+, and GLib schema tools. |
| `ENABLE_APP_BLUETOOTH_COSMIC` | `OFF` | Build the `dmn-bluetooth-cabi` C library and tests used by the Rust COSMIC app; automatically enables `ENABLE_ANCS`, `ENABLE_BLUEZ`, and `ENABLE_DBUS`. Normally driven by Cargo, not set by hand. |
| `ENABLE_ANCS_HARDWARE_TESTS` | `OFF` | Register the opt-in physical-iPhone test; requires `ENABLE_APP_BLUETOOTH_CLI=ON`. |
| `ENABLE_VALGRIND` | `OFF` | Register Valgrind test variants; requires Valgrind. |
| `ENABLE_FAULT_INJECTION` | `OFF` | Build libfiu support and register tests run through `fiu-run`. |

The options are cached in the build directory. Reconfigure that directory
with the desired options before building. The `dmn` CTest label selects the
regular unit tests; `kafka`, `fault-injection`, and `valgrind` select their
respective optional test groups.

`ENABLE_DMN_DMESGNET` controls the complete DMesg messaging layer: local
`Dmn_DMesg`, `Dmn_DMesgNet`, the Kafka adapter and examples, and the
DMesgNet-over-D-Bus facade. When enabled, CMake discovers or fetches both
Protobuf and librdkafka according to `DMN_ALLOW_DEPENDENCY_FETCH`. Generic
D-Bus I/O, BlueZ, and the GTK Bluetooth app are independent of this layer.
For a GTK-only Flatpak build, explicitly pass
`-DENABLE_DMN_DMESGNET=OFF` in the module's `config-opts`: this option defaults
to `ON`, and disabling dependency fetching alone does not disable the feature
or remove its Protobuf/librdkafka requirements.
When `DMN_BUILD_TESTS=ON`, the DMesg/DMesgNet/Kafka-specific tests are added
only if `ENABLE_DMN_DMESGNET=ON`; other tests can run with the messaging stack
disabled. The minimal GTK application-only build above disables this stack,
so it does not discover or fetch Protobuf or librdkafka. For the full
Linux configuration, use the [complete build and test](#1-complete-linux-build-and-test)
instructions. BlueZ tests use a private bus and do not require a physical
adapter; physical-iPhone tests remain opt-in.

About

Distributed Messaging Network

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages