summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi/src/ffi/foreigncallbacks.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/uniffi/src/ffi/foreigncallbacks.rs')
-rw-r--r--third_party/rust/uniffi/src/ffi/foreigncallbacks.rs229
1 files changed, 229 insertions, 0 deletions
diff --git a/third_party/rust/uniffi/src/ffi/foreigncallbacks.rs b/third_party/rust/uniffi/src/ffi/foreigncallbacks.rs
new file mode 100644
index 0000000000..092b635255
--- /dev/null
+++ b/third_party/rust/uniffi/src/ffi/foreigncallbacks.rs
@@ -0,0 +1,229 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Callback interfaces are traits specified in UDL which can be implemented by foreign languages.
+//!
+//! # Using callback interfaces
+//!
+//! 1. Define a Rust trait.
+//!
+//! This toy example defines a way of Rust accessing a key-value store exposed
+//! by the host operating system (e.g. the key chain).
+//!
+//! ```
+//! trait Keychain: Send {
+//! fn get(&self, key: String) -> Option<String>;
+//! fn put(&self, key: String, value: String);
+//! }
+//! ```
+//!
+//! 2. Define a callback interface in the UDL
+//!
+//! ```idl
+//! callback interface Keychain {
+//! string? get(string key);
+//! void put(string key, string data);
+//! };
+//! ```
+//!
+//! 3. And allow it to be passed into Rust.
+//!
+//! Here, we define a constructor to pass the keychain to rust, and then another method
+//! which may use it.
+//!
+//! In UDL:
+//! ```idl
+//! object Authenticator {
+//! constructor(Keychain keychain);
+//! void login();
+//! }
+//! ```
+//!
+//! In Rust:
+//!
+//! ```
+//!# trait Keychain: Send {
+//!# fn get(&self, key: String) -> Option<String>;
+//!# fn put(&self, key: String, value: String);
+//!# }
+//! struct Authenticator {
+//! keychain: Box<dyn Keychain>,
+//! }
+//!
+//! impl Authenticator {
+//! pub fn new(keychain: Box<dyn Keychain>) -> Self {
+//! Self { keychain }
+//! }
+//! pub fn login(&self) {
+//! let username = self.keychain.get("username".into());
+//! let password = self.keychain.get("password".into());
+//! }
+//! }
+//! ```
+//! 4. Create an foreign language implementation of the callback interface.
+//!
+//! In this example, here's a Kotlin implementation.
+//!
+//! ```kotlin
+//! class AndroidKeychain: Keychain {
+//! override fun get(key: String): String? {
+//! // … elide the implementation.
+//! return value
+//! }
+//! override fun put(key: String) {
+//! // … elide the implementation.
+//! }
+//! }
+//! ```
+//! 5. Pass the implementation to Rust.
+//!
+//! Again, in Kotlin
+//!
+//! ```kotlin
+//! val authenticator = Authenticator(AndroidKeychain())
+//! authenticator.login()
+//! ```
+//!
+//! # How it works.
+//!
+//! ## High level
+//!
+//! Uniffi generates a protocol or interface in client code in the foreign language must implement.
+//!
+//! For each callback interface, a `CallbackInternals` (on the Foreign Language side) and `ForeignCallbackInternals`
+//! (on Rust side) manages the process through a `ForeignCallback`. There is one `ForeignCallback` per callback interface.
+//!
+//! Passing a callback interface implementation from foreign language (e.g. `AndroidKeychain`) into Rust causes the
+//! `KeychainCallbackInternals` to store the instance in a handlemap.
+//!
+//! The object handle is passed over to Rust, and used to instantiate a struct `KeychainProxy` which implements
+//! the trait. This proxy implementation is generate by Uniffi. The `KeychainProxy` object is then passed to
+//! client code as `Box<dyn Keychain>`.
+//!
+//! Methods on `KeychainProxy` objects (e.g. `self.keychain.get("username".into())`) encode the arguments into a `RustBuffer`.
+//! Using the `ForeignCallback`, it calls the `CallbackInternals` object on the foreign language side using the
+//! object handle, and the method selector.
+//!
+//! The `CallbackInternals` object unpacks the arguments from the passed buffer, gets the object out from the handlemap,
+//! and calls the actual implementation of the method.
+//!
+//! If there's a return value, it is packed up in to another `RustBuffer` and used as the return value for
+//! `ForeignCallback`. The caller of `ForeignCallback`, the `KeychainProxy` unpacks the returned buffer into the correct
+//! type and then returns to client code.
+//!
+
+use super::RustBuffer;
+use std::fmt;
+use std::os::raw::c_int;
+use std::sync::atomic::{AtomicUsize, Ordering};
+
+/// ForeignCallback is the Rust representation of a foreign language function.
+/// It is the basis for all callbacks interfaces. It is registered exactly once per callback interface,
+/// at library start up time.
+/// Calling this method is only done by generated objects which mirror callback interfaces objects in the foreign language.
+///
+/// * The `handle` is the key into a handle map on the other side of the FFI used to look up the foreign language object
+/// that implements the callback interface/trait.
+/// * The `method` selector specifies the method that will be called on the object, by looking it up in a list of methods from
+/// the IDL. The index is 1 indexed. Note that the list of methods is generated by at uniffi from the IDL and used in all
+/// bindings: so we can rely on the method list being stable within the same run of uniffi.
+/// * `args` is a serialized buffer of arguments to the function. UniFFI will deserialize it before
+/// passing individual arguments to the user's callback.
+/// * `buf_ptr` is a pointer to where the resulting buffer will be written. UniFFI will allocate a
+/// buffer to write the result into.
+/// * A callback returns:
+/// - `-2` An error occured that was serialized to buf_ptr
+/// - `-1` An unexpected error ocurred
+/// - `0` is a deprecated way to signal that if the call succeeded, but did not modify buf_ptr
+/// - `1` If the call succeeded. For non-void functions the return value should be serialized
+/// to buf_ptr.
+/// Note: The output buffer might still contain 0 bytes of data.
+pub type ForeignCallback = unsafe extern "C" fn(
+ handle: u64,
+ method: u32,
+ args: RustBuffer,
+ buf_ptr: *mut RustBuffer,
+) -> c_int;
+
+/// The method index used by the Drop trait to communicate to the foreign language side that Rust has finished with it,
+/// and it can be deleted from the handle map.
+pub const IDX_CALLBACK_FREE: u32 = 0;
+
+// Overly-paranoid sanity checking to ensure that these types are
+// convertible between each-other. `transmute` actually should check this for
+// us too, but this helps document the invariants we rely on in this code.
+//
+// Note that these are guaranteed by
+// https://rust-lang.github.io/unsafe-code-guidelines/layout/function-pointers.html
+// and thus this is a little paranoid.
+static_assertions::assert_eq_size!(usize, ForeignCallback);
+static_assertions::assert_eq_size!(usize, Option<ForeignCallback>);
+
+/// Struct to hold a foreign callback.
+pub struct ForeignCallbackInternals {
+ callback_ptr: AtomicUsize,
+}
+
+const EMPTY_PTR: usize = 0;
+
+impl ForeignCallbackInternals {
+ pub const fn new() -> Self {
+ ForeignCallbackInternals {
+ callback_ptr: AtomicUsize::new(EMPTY_PTR),
+ }
+ }
+
+ pub fn set_callback(&self, callback: ForeignCallback) {
+ let as_usize = callback as usize;
+ let old_ptr = self.callback_ptr.compare_exchange(
+ EMPTY_PTR,
+ as_usize,
+ Ordering::SeqCst,
+ Ordering::SeqCst,
+ );
+ match old_ptr {
+ // We get the previous value back. If this is anything except EMPTY_PTR,
+ // then this has been set before we get here.
+ Ok(EMPTY_PTR) => (),
+ _ =>
+ // This is an internal bug, the other side of the FFI should ensure
+ // it sets this only once.
+ {
+ panic!("Bug: call set_callback multiple times. This is likely a uniffi bug")
+ }
+ };
+ }
+
+ pub fn get_callback(&self) -> Option<ForeignCallback> {
+ let ptr_value = self.callback_ptr.load(Ordering::SeqCst);
+ unsafe { std::mem::transmute::<usize, Option<ForeignCallback>>(ptr_value) }
+ }
+}
+
+/// Used when internal/unexpected error happened when calling a foreign callback, for example when
+/// a unknown exception is raised
+///
+/// User callback error types must implement a From impl from this type to their own error type.
+#[derive(Debug)]
+pub struct UnexpectedUniFFICallbackError {
+ pub reason: String,
+}
+
+impl UnexpectedUniFFICallbackError {
+ pub fn from_reason(reason: String) -> Self {
+ Self { reason }
+ }
+}
+
+impl fmt::Display for UnexpectedUniFFICallbackError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "UnexpectedUniFFICallbackError(reason: {:?})",
+ self.reason
+ )
+ }
+}
+
+impl std::error::Error for UnexpectedUniFFICallbackError {}