0
0
mirror of https://github.com/signalapp/libsignal.git synced 2024-09-20 03:52:17 +02:00

Add documentation for bridge crates

Explain the difference between libsignal-bridge and libsignal-bridge-types, and
how the pieces fit together.
This commit is contained in:
Alex Konradi 2024-06-26 15:05:51 -04:00
parent b9efb3cc1b
commit 9355467f01

98
rust/bridge/README.md Normal file
View File

@ -0,0 +1,98 @@
# 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`](./shared/) 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`](./shared/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](#type-conversions) 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](#bridged-function-collection)) to
identify and collect the entry points and make them callable.
# Bridged types
The [`libsignal-bridge-types`](./shared/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`](./ffi/) and [`libsignal-jni`](./jni/) crates to produce a list
of function prototypes. For Java, these prototypes are munged by the
[`gen_java_decl.py`](./jni/bin/gen_java_decl.py) and interpolated into the
[`Native.java.in`](./jni/bin/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`](./node/bin/gen_ts_decl.py) and the output is interpolated into
[`Native.ts.d.in`](./node/bin/Native.d.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.
[`libsignal_bridge_types::ffi`]: ./shared/ffi/
[`libsignal_bridge_types::jni`]: ./shared/jni/
[`libsignal_bridge_types::node`]: ./shared/node/
[`libsignal-node`]: ./shared/node/
[proc-macro]: https://doc.rust-lang.org/reference/procedural-macros.html
[`linkme`]: https://crates.io/crates/linkme
[`cbindgen`]: https://crates.io/crates/cbindgen