This does not yet require the same at the app layer; the parsing and enforcement has merely been pushed up to the bridge layer.
Overview
In order for libsignal to be used from Java, Swift, and TypeScript, the Rust
code must be exposed, or "bridged" to those languages with compatible entry
points. That's the purpose of the crates in this directory.
Bridged function definitions
libsignal-bridge defines functions that are callable from Java,
Swift, and TypeScript. These definitions make use of the #[bridge_fn] and
#[bridge_io] macros to provide the language-specific glue code that makes the
functions visible from other languages.
libsignal-bridge-macros is a proc-macro crate that
defines the #[bridge_fn] and #[bridge_io] macros. Attaching one of these
macros to a function definition causes the macro to emit #[cfg]-guarded entry
point for each supported language. These entry points, before calling the actual
annotated Rust function, perform functionality like language-specific parsing
and conversion of input arguments, panic trapping, and
spawning of tasks for async functions. They also handle return value
conversion and language-specific error handling.
The macros attach any needed code to the bridged functions to enable language-specific mechanisms (more below) to identify and collect the entry points and make them callable.
Bridged types
The libsignal-bridge-types crate defines traits and
impls that reduce the amount of boilerplate code required to write bridged
functions. These generally fall into three categories:
- type conversions
- runtime support
- language utilities
Type conversions
Input arguments are converted from their language-native form into Rust types
using the ArgTypeInfo traits defined for each language in the per-language
submodule (libsignal_bridge_types::ffi, libsignal_bridge_types::jni, and
libsignal_bridge_types::node). To make a type usable as an argument to a
function annotated with #[bridge_fn] or #[bridge_io], the three
ArgTypeInfo traits need to be implemented for that type, either directly or
indirectly. The type also needs to be added to the ffi_arg_type,
jni_arg_type, and node_arg_type macros.
Returned types are treated similarly, except that the language-specific traits
are named ResultTypeInfo, and the macros are ffi_result_type and similar.
Runtime support
Some Rust features aren't exposed directly to other languages, but instead
require some runtime support. As an example, when #[bridge_fn] is attached to
an async function, the exposed entry point takes as an additional argument the
async runtime to be used. libsignal_bridge_types defines traits that support
this.
Language utilities
Some languages require additional code to support bridged functions. The jni
module, for example, defines a function to be invoked when the library is loaded
that caches a class loader for use during later return type conversions. The
node module defines a function that registers a sequence of annotated
functions with a JavaScript runtime.
Bridged function collection
To make the function entry points generated by the #[bridge_fn] and
#[bridge_io] macros visible to the target languages, the functions must be
collected and exposed. The mechanism for this is language-specific, but relies
on annotations attached to the entry points.
For Swift, and Java, cbindgen is used to process the
libsignal-ffi and libsignal-jni crates to produce a list
of function prototypes. For Java, these prototypes are munged by the
gen_java_decl.py and interpolated into the
Native.java.in file to produce a self-contained
class definition. For Swift, the cbindgen output is saved directly to a
C-style .h file that the Swift toolchain can consume.
For TypeScript, the libsignal-node crate is expanded and processed by
gen_ts_decl.py and the output is interpolated into
Native.ts.in. The output, however, only
declares the function signatures; to make them accessible to the JavaScript
runtime, additional machinery is used. This takes the form of #[linkme]
annotations on to the generated entry points; the linkme crate is used to
collect all these entry points at link time for explicit registration at
runtime.