ffi gem, with no native glue of its own.
Install
The binding is theoffline_protocol gem on RubyGems:
Gemfile:
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:
/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
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: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 frommls_get_or_create_key_packagegiven tosend_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_eventtakes 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*andnostr*methods. - Storage. The built-in file stores or two providers, as above.
- Telemetry platform. If you enable telemetry, pass
TelemetryConfigosandos_majoras the platform the process actually runs on (TelemetryOs::LINUX,MACOSorWINDOWS), read from the host. - Lifecycle.
start,stopandprocesson a timer, and one engine kept for the life of the process.
Reference
- Ruby binding README: building, testing and regenerating the binding.
- Ruby binding contract: library loading, defaults, callbacks, storage, signed bytes and the gem.
offline_protocolon RubyGems.

