0
0
mirror of https://github.com/signalapp/libsignal.git synced 2024-09-19 19:42:19 +02:00
libsignal/rust/bridge
2024-09-06 15:19:20 -07:00
..
ffi Bump to version v0.57.0 2024-09-06 09:46:54 -04:00
jni Bump to version v0.57.0 2024-09-06 09:46:54 -04:00
node Bump to version v0.57.0 2024-09-06 09:46:54 -04:00
shared SVR - Add Kyber1024 PQ to Noise client connection. 2024-09-06 15:19:20 -07:00
README.md Add documentation for bridge crates 2024-07-08 15:51:29 -04:00

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:

  1. type conversions
  2. runtime support
  3. 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.d.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.