From 9355467f01aea7e922b4bc60fc6f3c9fa6805f64 Mon Sep 17 00:00:00 2001 From: Alex Konradi Date: Wed, 26 Jun 2024 15:05:51 -0400 Subject: [PATCH] Add documentation for bridge crates Explain the difference between libsignal-bridge and libsignal-bridge-types, and how the pieces fit together. --- rust/bridge/README.md | 98 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 rust/bridge/README.md diff --git a/rust/bridge/README.md b/rust/bridge/README.md new file mode 100644 index 00000000..f15fa403 --- /dev/null +++ b/rust/bridge/README.md @@ -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