uniffi_core/
lib.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5//! # Runtime support code for uniffi
6//!
7//! This crate provides the small amount of runtime code that is required by the generated uniffi
8//! component scaffolding in order to transfer data back and forth across the C-style FFI layer,
9//! as well as some utilities for testing the generated bindings.
10//!
11//! The key concept here is the [`FfiConverter`] trait, which is responsible for converting between
12//! a Rust type and a low-level C-style type that can be passed across the FFI:
13//!
14//!  * How to [represent](FfiConverter::FfiType) values of the Rust type in the low-level C-style type
15//!    system of the FFI layer.
16//!  * How to ["lower"](FfiConverter::lower) values of the Rust type into an appropriate low-level
17//!    FFI value.
18//!  * How to ["lift"](FfiConverter::try_lift) low-level FFI values back into values of the Rust
19//!    type.
20//!  * How to [write](FfiConverter::write) values of the Rust type into a buffer, for cases
21//!    where they are part of a compound data structure that is serialized for transfer.
22//!  * How to [read](FfiConverter::try_read) values of the Rust type from buffer, for cases
23//!    where they are received as part of a compound data structure that was serialized for transfer.
24//!  * How to [return](FfiConverter::lower_return) values of the Rust type from scaffolding
25//!    functions.
26//!
27//! This logic encapsulates the Rust-side handling of data transfer. Each foreign-language binding
28//! must also implement a matching set of data-handling rules for each data type.
29//!
30//! In addition to the core `FfiConverter` trait, we provide a handful of struct definitions useful
31//! for passing core rust types over the FFI, such as [`RustBuffer`].
32
33#![warn(rust_2018_idioms, unused_qualifications)]
34
35/// Print out tracing information for FFI calls if the `ffi-trace` feature is enabled
36#[cfg(feature = "ffi-trace")]
37#[macro_export]
38macro_rules! trace {
39    ($($tt:tt)*) => {
40        println!($($tt)*);
41    }
42}
43
44#[cfg(not(feature = "ffi-trace"))]
45#[macro_export]
46macro_rules! trace {
47    ($($tt:tt)*) => {};
48}
49
50use anyhow::bail;
51use bytes::buf::Buf;
52
53// Make Result<> public to support external impls of FfiConverter
54pub use anyhow::Result;
55
56pub mod ffi;
57mod ffi_converter_impls;
58mod ffi_converter_traits;
59pub mod metadata;
60mod oneshot;
61
62#[cfg(feature = "scaffolding-ffi-buffer-fns")]
63pub use ffi::ffiserialize::FfiBufferElement;
64pub use ffi::*;
65pub use ffi_converter_traits::{
66    ConvertError, FfiConverter, FfiConverterArc, HandleAlloc, Lift, LiftRef, LiftReturn, Lower,
67    LowerError, LowerReturn, TypeId,
68};
69pub use metadata::*;
70
71// Re-export the libs that we use in the generated code,
72// so the consumer doesn't have to depend on them directly.
73pub mod deps {
74    pub use crate::trace;
75    pub use anyhow;
76    #[cfg(feature = "tokio")]
77    pub use async_compat;
78    pub use bytes;
79    pub use static_assertions;
80}
81
82const PACKAGE_VERSION: &str = env!("CARGO_PKG_VERSION");
83
84// For the significance of this magic number 10 here, and the reason that
85// it can't be a named constant, see the `check_compatible_version` function.
86static_assertions::const_assert!(PACKAGE_VERSION.len() < 10);
87
88/// Check whether the uniffi runtime version is compatible a given uniffi_bindgen version.
89///
90/// The result of this check may be used to ensure that generated Rust scaffolding is
91/// using a compatible version of the uniffi runtime crate. It's a `const fn` so that it
92/// can be used to perform such a check at compile time.
93#[allow(clippy::len_zero)]
94pub const fn check_compatible_version(bindgen_version: &'static str) -> bool {
95    // While UniFFI is still under heavy development, we require that
96    // the runtime support crate be precisely the same version as the
97    // build-time bindgen crate.
98    //
99    // What we want to achieve here is checking two strings for equality.
100    // Unfortunately Rust doesn't yet support calling the `&str` equals method
101    // in a const context. We can hack around that by doing a byte-by-byte
102    // comparison of the underlying bytes.
103    let package_version = PACKAGE_VERSION.as_bytes();
104    let bindgen_version = bindgen_version.as_bytes();
105    // What we want to achieve here is a loop over the underlying bytes,
106    // something like:
107    // ```
108    //  if package_version.len() != bindgen_version.len() {
109    //      return false
110    //  }
111    //  for i in 0..package_version.len() {
112    //      if package_version[i] != bindgen_version[i] {
113    //          return false
114    //      }
115    //  }
116    //  return true
117    // ```
118    // Unfortunately stable Rust doesn't allow `if` or `for` in const contexts,
119    // so code like the above would only work in nightly. We can hack around it by
120    // statically asserting that the string is shorter than a certain length
121    // (currently 10 bytes) and then manually unrolling that many iterations of the loop.
122    //
123    // Yes, I am aware that this is horrific, but the externally-visible
124    // behaviour is quite nice for consumers!
125    package_version.len() == bindgen_version.len()
126        && (package_version.len() == 0 || package_version[0] == bindgen_version[0])
127        && (package_version.len() <= 1 || package_version[1] == bindgen_version[1])
128        && (package_version.len() <= 2 || package_version[2] == bindgen_version[2])
129        && (package_version.len() <= 3 || package_version[3] == bindgen_version[3])
130        && (package_version.len() <= 4 || package_version[4] == bindgen_version[4])
131        && (package_version.len() <= 5 || package_version[5] == bindgen_version[5])
132        && (package_version.len() <= 6 || package_version[6] == bindgen_version[6])
133        && (package_version.len() <= 7 || package_version[7] == bindgen_version[7])
134        && (package_version.len() <= 8 || package_version[8] == bindgen_version[8])
135        && (package_version.len() <= 9 || package_version[9] == bindgen_version[9])
136        && package_version.len() < 10
137}
138
139/// Assert that the uniffi runtime version matches an expected value.
140///
141/// This is a helper hook for the generated Rust scaffolding, to produce a compile-time
142/// error if the version of `uniffi_bindgen` used to generate the scaffolding was
143/// incompatible with the version of `uniffi` being used at runtime.
144#[macro_export]
145macro_rules! assert_compatible_version {
146    ($v:expr $(,)?) => {
147        uniffi::deps::static_assertions::const_assert!(uniffi::check_compatible_version($v));
148    };
149}
150
151/// Struct to use when we want to lift/lower/serialize types inside the `uniffi` crate.
152struct UniFfiTag;
153
154/// A helper function to ensure we don't read past the end of a buffer.
155///
156/// Rust won't actually let us read past the end of a buffer, but the `Buf` trait does not support
157/// returning an explicit error in this case, and will instead panic. This is a look-before-you-leap
158/// helper function to instead return an explicit error, to help with debugging.
159pub fn check_remaining(buf: &[u8], num_bytes: usize) -> Result<()> {
160    if buf.remaining() < num_bytes {
161        bail!(
162            "not enough bytes remaining in buffer ({} < {num_bytes})",
163            buf.remaining(),
164        );
165    }
166    Ok(())
167}
168
169/// Macro to implement lowering/lifting using a `RustBuffer`
170///
171/// For complex types where it's too fiddly or too unsafe to convert them into a special-purpose
172/// C-compatible value, you can use this trait to implement `lower()` in terms of `write()` and
173/// `lift` in terms of `read()`.
174///
175/// This macro implements the boilerplate needed to define `lower`, `lift` and `FFIType`.
176#[macro_export]
177macro_rules! ffi_converter_rust_buffer_lift_and_lower {
178    ($uniffi_tag:ty) => {
179        type FfiType = $crate::RustBuffer;
180
181        fn lower(v: Self) -> $crate::RustBuffer {
182            let mut buf = ::std::vec::Vec::new();
183            <Self as $crate::FfiConverter<$uniffi_tag>>::write(v, &mut buf);
184            $crate::RustBuffer::from_vec(buf)
185        }
186
187        fn try_lift(buf: $crate::RustBuffer) -> $crate::Result<Self> {
188            let vec = buf.destroy_into_vec();
189            let mut buf = vec.as_slice();
190            let value = <Self as $crate::FfiConverter<$uniffi_tag>>::try_read(&mut buf)?;
191            match $crate::deps::bytes::Buf::remaining(&buf) {
192                0 => ::std::result::Result::Ok(value),
193                n => $crate::deps::anyhow::bail!(
194                    "junk data left in buffer after lifting (count: {n})",
195                ),
196            }
197        }
198    };
199}
200
201#[cfg(test)]
202mod test {
203    use super::{FfiConverter, UniFfiTag};
204    use std::time::{Duration, SystemTime};
205
206    #[test]
207    fn timestamp_roundtrip_post_epoch() {
208        let expected = SystemTime::UNIX_EPOCH + Duration::new(100, 100);
209        let result =
210            <SystemTime as FfiConverter<UniFfiTag>>::try_lift(<SystemTime as FfiConverter<
211                UniFfiTag,
212            >>::lower(expected))
213            .expect("Failed to lift!");
214        assert_eq!(expected, result)
215    }
216
217    #[test]
218    fn timestamp_roundtrip_pre_epoch() {
219        let expected = SystemTime::UNIX_EPOCH - Duration::new(100, 100);
220        let result =
221            <SystemTime as FfiConverter<UniFfiTag>>::try_lift(<SystemTime as FfiConverter<
222                UniFfiTag,
223            >>::lower(expected))
224            .expect("Failed to lift!");
225        assert_eq!(
226            expected, result,
227            "Expected results after lowering and lifting to be equal"
228        )
229    }
230}
231
232#[cfg(test)]
233pub mod test_util {
234    use std::{error::Error, fmt};
235
236    use super::*;
237
238    #[derive(Clone, Debug, PartialEq, Eq)]
239    pub struct TestError(pub String);
240
241    // Use FfiConverter to simplify lifting TestError out of RustBuffer to check it
242    unsafe impl<UT> FfiConverter<UT> for TestError {
243        ffi_converter_rust_buffer_lift_and_lower!(UniFfiTag);
244
245        fn write(obj: TestError, buf: &mut Vec<u8>) {
246            <String as FfiConverter<UniFfiTag>>::write(obj.0, buf);
247        }
248
249        fn try_read(buf: &mut &[u8]) -> Result<TestError> {
250            <String as FfiConverter<UniFfiTag>>::try_read(buf).map(TestError)
251        }
252
253        // Use a dummy value here since we don't actually need TYPE_ID_META
254        const TYPE_ID_META: MetadataBuffer = MetadataBuffer::new();
255    }
256
257    impl fmt::Display for TestError {
258        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259            write!(f, "{}", self.0)
260        }
261    }
262
263    impl Error for TestError {}
264
265    impl<T: Into<String>> From<T> for TestError {
266        fn from(v: T) -> Self {
267            Self(v.into())
268        }
269    }
270
271    derive_ffi_traits!(blanket TestError);
272}