Rust integration
Build a Rust application that enables a feature only after the Babel native runtime authenticates and validates its license. This guide follows the Rust example source , including its Cargo build script and handle ownership.
Babel Licensing Native SDK 12.2.0 (C ABI 2) is available for macOS arm64, Linux x64 and Windows x64. The setup script downloads the pinned archive for your platform and verifies its SHA-256 before installation.
Select your platform
The SDK distributes one runtime library for each platform. Botan is embedded; users do not install or ship a Botan or SQLite library. Windows also receives a link-time import library for MSVC/Rust; that file is not another runtime DLL.
| Platform package | Runtime file | Build environment |
|---|---|---|
| macOS arm64, macOS 15+ | .sdk/lib/libBabelLicensingNative.dylib | Apple Silicon toolchain; Xcode command-line tools for compiled clients |
| Linux x64, glibc | .sdk/lib/libBabelLicensingNative.so | x86_64 compiler/toolchain; Ubuntu 22.04 is the initial build baseline |
| Windows x64 | .sdk/bin/BabelLicensingNative.dll | x64 process; MSVC import library in .sdk/lib/BabelLicensingNative.lib |
The installer selects the current OS/architecture, verifies that platform’s archive checksum and metadata, and rejects a different package. Linux checks its minimum glibc version; a musl/Alpine package is not provided. The native runtime uses the OS certificate trust store and system facilities. The release archives are available from the 12.2.0 SDK release .
Linux: install, build and run
Install Python 3.9 or later and the language toolchain. Rust needs Cargo/rustc 1.85 or newer plus a C linker; Go needs Go 1.22 or newer plus GCC/Clang for cgo; C++ needs a C++20 compiler. On Ubuntu, build-essential supplies the C/C++ compiler and linker. Python clients need no C/C++ compiler. The OS needs its normal CA certificate package for HTTPS.
git clone https://github.com/babelfornet/native-licensing-examples.git
cd native-licensing-examples
python3 scripts/setup-sdk.py
python3 scripts/run-example.py rustThe shared runner builds the selected client and runs it from the repository root. It does not build the SDK or download dependencies. A successful offline run prints License verified. Feature Pro enabled. The equivalent direct command is:
mkdir -p .build
cargo build --manifest-path rust/Cargo.toml
./rust/target/debug/babel-native-licensing-exampleThe Rust build script and Go cgo directives set the runtime path on Linux. C++ explicitly supplies -L for link-time lookup and -Wl,-rpath for runtime lookup. Python selects .so in runtime_path(). If you deploy a compiled client elsewhere, ship the single .so in your application layout and configure an appropriate runtime path, such as $ORIGIN/../lib for a sibling lib/ directory.
Windows: install, build and run
Use x64 Python 3.9 or later. For C++ or Rust/MSVC, install Visual Studio’s Desktop development with C++ workload and open an x64 Native Tools terminal. Rust uses the x86_64-pc-windows-msvc toolchain. Go/cgo additionally needs a MinGW-compatible GCC or Clang toolchain, such as MSYS2 UCRT64 or LLVM-MinGW; cl.exe is not Go’s cgo C compiler.
From PowerShell in the example checkout:
python scripts/setup-sdk.py
python scripts/run-example.py rustThe Python runner makes .sdk/bin available to the child process through its PATH. The C++ runner uses cl when available, Rust links the MSVC import .lib, and Go links the C ABI with its cgo compiler. To build and run directly:
New-Item -ItemType Directory -Force .build | Out-Null
$env:PATH = "$PWD\.sdk\bin;$env:PATH"
cargo build --manifest-path rust/Cargo.toml
& .\rust\target\debug\babel-native-licensing-example.exeFor deployment, copy BabelLicensingNative.dll next to the executable, or put its directory on the application’s controlled DLL search path. Python loads its explicit absolute DLL path through ctypes.CDLL, using the C calling convention. Do not copy a Linux .so or macOS .dylib onto Windows, and do not distribute the MSVC import .lib as a runtime dependency.
The numbered sections below show the macOS reference build and explain the shared integration code. Offline, activation and floating logic is identical on all three systems; the loader and linker settings above account for the platform differences.
1. Check the toolchain
Install a Rust toolchain with Cargo and the Xcode command-line tools. Python 3 is used by the SDK setup script, not by the compiled Rust application.
rustc --version
cargo --version
rustc -vV
xcode-select -p
python3 --version
uname -mThe Rust host should be aarch64-apple-darwin and uname -m should print arm64. The macOS SDK contains arm64 libraries. An x86_64 compiler or a shell running under Rosetta cannot link them into an Intel application.
2. Get the project and native runtime
git clone https://github.com/babelfornet/native-licensing-examples.git
cd native-licensing-examples
# Until the ABI is released, use the supplied matching archive:
python3 scripts/setup-sdk.pyAfter the coordinated release, python3 scripts/setup-sdk.py will download the package pinned by sdk/manifest.json. Both installation paths check the archive’s SHA-256. An arbitrary or older SDK archive will not pass that check.
The files used by the Rust project are:
native-licensing-examples/
├── .sdk/lib/
│ └── libBabelLicensingNative.dylib
├── demo/
│ ├── perpetual.xml
│ ├── trial.xml
│ └── publisher-key.txt
└── rust/
├── Cargo.toml
├── build.rs
└── src/main.rsThe SDK runtime is a single file: libBabelLicensingNative.dylib. The required Botan modules are linked statically into it. Users do not install or ship Botan or SQLite. You do not need .NET or a Licensing Service to verify the supplied offline license files.
3. Configure Cargo and the linker
The Cargo manifest declares an ordinary binary package with no external crate dependencies:
[package]
name = "babel-native-licensing-example"
version = "0.1.0"
edition = "2021"
license = "MIT"
build = "build.rs"
[dependencies]The C ABI does not require a C++ wrapper crate or a Botan Rust crate. The native SDK already contains its cryptographic implementation. Cargo runs this script before linking the application:
fn main() {
let root = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
let lib = std::env::var_os("BABEL_NATIVE_SDK_LIB_DIR")
.map(std::path::PathBuf::from)
.unwrap_or_else(|| root.parent().unwrap().join(".sdk/lib"));
println!("cargo:rerun-if-env-changed=BABEL_NATIVE_SDK_LIB_DIR");
println!("cargo:rustc-link-search=native={}", lib.display());
println!("cargo:rustc-link-lib=dylib=BabelLicensingNative");
if std::env::var("CARGO_CFG_TARGET_OS").unwrap() != "windows" {
println!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib.display());
}
println!("cargo:rerun-if-changed=build.rs");
}Each setting has a different purpose:
| Setting | Purpose |
|---|---|
CARGO_MANIFEST_DIR | Locates rust/ independently of the shell’s current directory. Its parent is the repository root. |
rustc-link-search=native=... | Tells the linker where to find the native library during the build. |
rustc-link-lib=dylib=BabelLicensingNative | Links libBabelLicensingNative.dylib by its library name. Do not include the lib prefix or .dylib suffix. |
-Wl,-rpath,... | Adds the runtime search directory used by the macOS loader after the binary starts. |
rerun-if-changed | Reruns the build script when its configuration changes. |
Finding a library at link time and finding it at runtime are separate requirements. The example embeds an absolute path to this checkout’s .sdk/lib; it is a development configuration. Rebuild after moving the checkout. For a distributable app, package the runtime and configure a path appropriate to the installed app layout.
4. Declare the C functions
The native boundary uses C integers, NUL-terminated UTF-8 strings and an opaque pointer. No Rust object or C++ class crosses it:
use std::ffi::{c_char, c_int, c_void, CString};
unsafe extern "C" {
fn babel_abi_version() -> c_int;
fn babel_license_load(text: *const c_char, out: *mut *mut c_void) -> c_int;
fn babel_license_verify(
lic: *const c_void,
algorithm: *const c_char,
key: *const c_char,
) -> c_int;
fn babel_license_validate(lic: *const c_void) -> c_int;
fn babel_license_feature_enabled(lic: *const c_void, feature: *const c_char) -> c_int;
fn babel_license_free(lic: *mut c_void);
}extern "C" selects the C calling convention. c_int matches the status return type. The out argument is a pointer to a handle slot: load returns its status separately from the allocated handle. The program checks babel_abi_version() == 2 before loading a license.
Compare these declarations with the public header when updating the SDK. A wrong declaration can corrupt the call even when Rust code outside the FFI compiles.
5. Own the handle and C strings
The example wraps a successfully allocated handle in a type with a destructor:
struct Handle(*mut c_void);
impl Drop for Handle {
fn drop(&mut self) {
unsafe { babel_license_free(self.0) }
}
}The wrapper does not implement Copy or Clone. It has one owner and calls babel_license_free once, including when validation returns an error through ?. It is constructed only after load succeeds.
License bytes, the trusted public key, algorithm and feature name become CString values. CString::new rejects embedded NUL bytes; its as_ptr() remains valid while the owning CString is alive. The example keeps these owners in run() for the entire sequence of native calls.
read_input limits the XML file to 1 MiB and the key to 16 KiB before making a CString. It reads at most maximum + 1 bytes to detect oversized files without loading an arbitrary file into memory.
6. Authenticate before enabling a feature
The central sequence in main.rs is:
let mut raw = std::ptr::null_mut();
success(babel_license_load(xml.as_ptr(), &mut raw), "License parsing")?;
let handle = Handle(raw);
success(
babel_license_verify(handle.0, algorithm.as_ptr(), key.as_ptr()),
"Publisher signature verification",
)?;
success(babel_license_validate(handle.0), "Runtime license validation")?;
if babel_license_feature_enabled(handle.0, feature.as_ptr()) != 1 {
return Err("Requested feature is absent or expired".into());
}These calls are inside an unsafe block in the full source. success converts a nonzero C status into a Rust Err; it does not turn parsing success into authorization.
verify requires the trusted publisher key. validate checks absolute license expiry and rejects unsupported restriction nodes. The feature query also checks feature expiry. The program prints success only after all three decisions succeed.
The native calls do not make the raw pointer safe to share arbitrarily. Keep the handle alive until a call finishes and never free it concurrently with another call.
7. Build and run the executable
Run these commands from the repository root so that the default demo/... input paths resolve:
cargo build --manifest-path rust/Cargo.toml
./rust/target/debug/babel-native-licensing-example
cargo build --release --manifest-path rust/Cargo.toml
./rust/target/release/babel-native-licensing-exampleThe expected output is:
License verified. Feature Pro enabled../run.sh rust performs the debug build and run for you. Cargo is needed for building; launching the resulting binary does not run Cargo or .NET. The single native shared library must still be available at the configured runtime path.
8. Try your own license and refusal paths
All clients accept [license.xml [publisher-key.txt [RSA|ECDSA [feature]]]]:
./rust/target/release/babel-native-licensing-example \
/absolute/path/customer.xml /absolute/path/publisher-key.txt RSA Pro
./rust/target/release/babel-native-licensing-example demo/trial.xml
./rust/target/release/babel-native-licensing-example demo/expired.xml
./rust/target/release/babel-native-licensing-example demo/no-feature.xmlThe trial is accepted while its absolute expiry covers the current time. The expired file and missing feature return a nonzero process status. demo/restricted.xml is also refused: stateful trial restrictions are not implemented in the native SDK.
To create an actual 30-day trial with your Babel Licensing .NET components, follow Issue your own files. Pass the issuer’s public key with its new files. The checked-in demo key is unrelated to your publisher key or your Babel product license.
Troubleshooting
| Error | Check |
|---|---|
library not found for -lBabelLicensingNative | .sdk/lib exists and build.rs computes the correct path. |
Library not loaded: @rpath/... | The executable’s runtime search path and the single libBabelLicensingNative.dylib file. Inspect with otool -L and otool -l. |
| Wrong architecture | rustc -vV, uname -m and file .sdk/lib/libBabelLicensingNative.dylib agree on arm64. |
| ABI version error | The application loaded ABI v2, rather than an older library found on another path. |
Status 3 | The XML is unchanged and the public key belongs to the publisher that signed it. |
Status 10 or 4 | The license expired, or contains an unsupported restriction. Do not continue to the protected feature. |
Run python3 scripts/test-examples.py from the repository root to exercise the accepted and rejected scenarios in all four languages.
Run activation and floating licensing
The online modes use the same application and the same single SDK runtime as the offline example. They need a service endpoint, a customer user key and a publisher public key pinned by your application. Do not supply the license used to evaluate or purchase Babel Licensing itself: these are licenses for your own application.
For a self-contained local protocol demonstration, start the fixture in a separate terminal from the repository root:
python3 scripts/demo-service.pyThe fixture listens only on 127.0.0.1:8765, reads signed demo payloads, and allocates one disposable floating seat. It needs Python 3, but no .NET runtime, database or signing private key. It is a protocol fixture, not a deployable Licensing Service.
In your application terminal, run:
export BABEL_SERVICE_URL=http://127.0.0.1:8765/
export BABEL_ALLOW_LOOPBACK_HTTP=1
export BABEL_MACHINE_ID=native-example-machine
export BABEL_PUBLISHER_KEY_FILE="$PWD/demo/publisher-key.txt"
export BABEL_ALGORITHM=RSA
export BABEL_CLIENT_ID=native-example-rust
export BABEL_USER_KEY=demo-activation
./run.sh rust --online activation
export BABEL_USER_KEY=demo-floating
./run.sh rust --online floatingActivation prints a verified grant, validates it again with the service, then deactivates it. Floating prints a verified grant, renews it twice, then releases its seat. Both exit with an error when acquisition, signature verification, machine validation, feature entitlement or a network operation fails.
Use your actual Licensing Service
Configure an activation or floating license for your application in Babel Licensing Service, with a supported RSA or ECDSA P-256 publisher key and a Pro feature (or adapt the feature name). The service must return a valid signed XML or compact license. Stateful trial run counts/days-from-first-use, domain, usage and custom restrictions remain unsupported; an absolute expiry is supported.
unset BABEL_ALLOW_LOOPBACK_HTTP BABEL_MACHINE_ID
export BABEL_SERVICE_URL=https://licensing.example.com/
export BABEL_PUBLISHER_KEY_FILE=/absolute/path/your-publisher-public-key.txt
export BABEL_ALGORITHM=RSA
# Supply BABEL_USER_KEY from your application's secure configuration.
./run.sh rust --online activation
# Or use a floating user key and --online floating.Replace the endpoint with your own service root. The SDK appends v1/auth/login and v1/license/*. It authenticates with the user key, then uses a bearer token for service requests. Credentials are read from the environment in these examples and are not printed or passed on the command line. Production applications should use their own secure configuration mechanism.
With BABEL_MACHINE_ID unset, the example calls babel_hardware_key. Keep the machine identity and BABEL_CLIENT_ID stable for an activation; choose distinct client identities for concurrently running floating clients. The fixed machine ID above exists only to match the signed local fixture. Pin the public key in your application or trusted configuration; never trust the key supplied inside a server response.
HTTPS always verifies certificates and hostnames; redirects are not followed. Plain HTTP requires the explicit loopback flag and cannot be used with a remote hostname. The sample timeout is 15 seconds per HTTP request; authentication and the following service request each have their own bound.
Integration code and lifecycle
// Native declarations, status checks and Drop cleanup are in rust/src/online.rs.
success(babel_online_activate(client, user_key.as_ptr()), "Activate")?;
success(babel_online_check(client), "Check before work")?;
if babel_online_feature_enabled(client, c"Pro".as_ptr()) != 1 {
return Err("Pro is not licensed".into());
}
success(babel_online_validate_activation(client), "Validate with service")?;
success(babel_online_deactivate(client), "Deactivate")?;rust/src/main.rs declares mod online; Cargo compiles online.rs automatically. The existing build.rs links the same native library and rpath for all modes. No networking or cryptography Rust crate is added. The FFI declares statuses as c_int, flags as u32, and heartbeat delays as i64. The Session owner keeps its opaque pointer alive, attempts release/deactivation from Drop on early return, and calls babel_online_free exactly once.
For floating, online.rs requests a seat, rejects a negative heartbeat delay before converting to u64, sleeps with Duration::from_millis, then calls heartbeat and validity checks. Two renewals are followed by release. The demo is synchronous: put the calls on an appropriate worker when integrating into a GUI or async application.
The session API retains the verified license and floating token internally. Use babel_online_check immediately before protected work and babel_online_feature_enabled for feature access. Do not extract or cache a Boolean from the initial acquisition and use it indefinitely.
For a running floating application, schedule heartbeats before the returned deadline throughout the protected session. The two-heartbeat loop is only the executable demonstration. Stop protected work on any heartbeat/check failure. There is no background renewal, offline grace or persistent activation cache in this version. A failed heartbeat invalidates the local grant immediately; a lease cannot be revived after its deadline and must be acquired again.
Release floating seats or deactivate activations explicitly when the application workflow requires it. The examples do this when they exit so repeated evaluations do not consume seats. A product can retain an activation between runs by keeping stable identities and activating/validating it again at startup; this SDK does not save a signed license or token to disk. Freeing a handle releases memory and performs no network request.
If release/deactivation fails, local use is still disabled. Retry cleanup with the same live handle while it exists; a floating token eventually expires at the service. Handle destruction, process termination and network failure do not prove that the server has received cleanup.
Online refusal scenarios
| Status | Meaning and application action |
|---|---|
2 / 3 | Malformed response/license or bad publisher signature: refuse protected work. |
4 | Unsupported algorithm, curve or restriction: adjust the issuing template, never bypass the check. |
10 | Signed license expired: obtain a valid application license. |
11 | Network, TLS, timeout or response-size failure: stop protected work and report the failure. |
12 / 13 / 14 | Credentials rejected, HTTP/service failure or grant refused: check service policy and seat availability. |
15 | No active grant, released/expired lease or failed heartbeat: stop use and acquire a new lease as appropriate. |
16 | Signed hardware binding does not match the configured machine: refuse activation. |
Run python3 scripts/test-online-examples.py to build/run all four languages against local activation, floating renewal/release, denied grant, hardware mismatch and unsupported-restriction cases. The native SDK separately tests malformed JSON, tampered signed payloads, oversized responses, timeout, redirects, invalid TLS and lease loss. These protocol-fixture checks do not replace acceptance tests against your deployed Licensing Service.
Online commands in Windows PowerShell
The service and user key settings have the same meaning on every OS. For the supplied loopback fixture, open one terminal for python scripts/demo-service.py, then use another:
$env:BABEL_SERVICE_URL = "http://127.0.0.1:8765/"
$env:BABEL_ALLOW_LOOPBACK_HTTP = "1"
$env:BABEL_MACHINE_ID = "native-example-machine"
$env:BABEL_PUBLISHER_KEY_FILE = "$PWD\demo\publisher-key.txt"
$env:BABEL_ALGORITHM = "RSA"
$env:BABEL_CLIENT_ID = "native-example-rust"
$env:BABEL_USER_KEY = "demo-activation"
python scripts/run-example.py rust --online activation
$env:BABEL_USER_KEY = "demo-floating"
python scripts/run-example.py rust --online floatingFor your actual HTTPS Licensing Service, remove the loopback flag and fixed demo machine identity, then supply your service root, publisher key and application user key. Run the same --online commands. Linux uses the shell export commands in the online section and can use python3 scripts/run-example.py instead of ./run.sh.
Remove-Item Env:BABEL_ALLOW_LOOPBACK_HTTP -ErrorAction SilentlyContinue
Remove-Item Env:BABEL_MACHINE_ID -ErrorAction SilentlyContinue
$env:BABEL_SERVICE_URL = "https://your-licensing-service.example/"
$env:BABEL_PUBLISHER_KEY_FILE = "C:\YourApplication\publisher-public-key.txt"
# Supply BABEL_USER_KEY through your application's secure configuration.
python scripts/run-example.py rust --online activationRun python scripts/test-examples.py and python scripts/test-online-examples.py on Windows (or python3 on Linux/macOS) to exercise the same accepted/refused paths in all four bindings.
Cargo caches and moved checkouts
The common runner reads Cargo’s JSON artifact output and executes the binary
Cargo actually produced, including when CARGO_TARGET_DIR is set. It passes
BABEL_NATIVE_SDK_LIB_DIR to the build script; Cargo watches this value so the
linker path is regenerated when the checkout moves. The runner does not execute
an old binary from rust/target/debug after building into a different directory.
CARGO_TARGET_DIR=/tmp/my-application-target python3 scripts/run-example.py rust
python3 scripts/test-rust-build.pyThe regression runs a fresh checkout with an external target directory, then
moves the checkout and reuses that cache. When building directly with Cargo,
set BABEL_NATIVE_SDK_LIB_DIR to the current SDK lib/ directory or run
cargo clean --manifest-path rust/Cargo.toml before rebuilding after relocation.
A compiled executable with an absolute rpath still needs an appropriate relative
runtime path for distribution to a different machine; the deployment notes above
show the separate runtime-layout requirement.