mirror of
https://github.com/signalapp/libsignal.git
synced 2024-09-19 19:42:19 +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:
parent
b9efb3cc1b
commit
9355467f01
98
rust/bridge/README.md
Normal file
98
rust/bridge/README.md
Normal 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
|
Loading…
Reference in New Issue
Block a user