Skip to main content
From v0.31.0 the SDK has a typed Ruby API for scripts, tooling and servers. It is generated from the same interface definition as the Swift, Kotlin, Python and Java bindings by UniFFI’s own Ruby generator, and calls the native library directly through the ffi gem, with no native glue of its own.

Install

The binding is the offline_protocol gem on RubyGems:
Or in a Gemfile:
It needs Ruby 3.3 or later and installs its one dependency, the ffi gem (~> 1.17). The gem holds the generated binding (lib/offline_protocol.rb) and the license notices, and no native code. Download the native library for your platform from the v0.31.0 GitHub release: Take the gem and the library from the same release. The Ruby generator does not compare the library’s checksums at load, so a library from another release can load and then read the wrong layout. The loader finds the library on its search path, and reads the variable when the process starts, so set it before Ruby launches:
On macOS, the protected system Ruby (/usr/bin/ruby) drops DYLD_LIBRARY_PATH; use a Ruby you installed, or put the library in a directory the loader already searches.

Quick start

The module is OfflineProtocol, and the engine inside it is OfflineProtocol::OfflineProtocol. MeshServices and DataStore hang off the engine, and every record and enum of the definition is a class. A generated record keeps the definition’s defaults as keyword arguments, so you name only the fields that have none. An engine refusal is an exception named for the variant, such as OfflineProtocol::ProtocolError::EncryptFailed; see error codes. Keep the engine reachable for as long as the process runs. An engine that is garbage-collected while it holds a callback leaves the callback registered. The SDK’s Ruby example runs two devices in one process: they pair, form an end-to-end encrypted session, send presence, typing indicators, a read receipt, a threaded reply and a file, and create an MLS group.

Storage

Hand the engine the built-in file stores, sealed under a 32-byte store key you keep out of the source:
Or write two providers and pass them to initialize_mls. The key-material provider (MlsStorageProvider) stores and loads arrays of integers, and the protocol-state provider (ProtocolStateStorageProvider) binary strings. Prove an adapter with OfflineProtocol.run_storage_conformance.

Bytes and threads

  • Mask bytes before they go back in. A byte array the engine returns arrives signed, from -128 to 127, and is refused on the way back in. Mask it with map { |b| b & 0xFF } before passing it on, for example a key package from mls_get_or_create_key_package given to send_connection_request. Byte arrays inside event JSON are already 0 to 255.
  • Callbacks run inside the engine. A callback must be safe from any thread, must not call back into the engine, and must raise nothing but the interface’s own error. Copy the event, queue it, return. poll_event takes events without a callback.
  • Engine calls hold the VM lock. A slow call (start, initialize_mls, flush_telemetry_blocking) stops every Ruby thread until it returns. A server keeps such calls off its request threads.

What your host provides

  • Transports. The radios and sockets, through the transport callbacks and the ble*, wifi_direct*, internet*, reticulum* and nostr* methods.
  • Storage. The built-in file stores or two providers, as above.
  • Telemetry platform. If you enable telemetry, pass TelemetryConfig os and os_major as the platform the process actually runs on (TelemetryOs::LINUX, MACOS or WINDOWS), read from the host.
  • Lifecycle. start, stop and process on a timer, and one engine kept for the life of the process.

Reference

The SDK is AGPL-3.0-only with a commercial license available; see licensing.