uniffi_core/metadata.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
/* 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/. */
//! Pack UniFFI interface metadata into byte arrays
//!
//! In order to generate foreign bindings, we store interface metadata inside the library file
//! using exported static byte arrays. The foreign bindings code reads that metadata from the
//! library files and generates bindings based on that.
//!
//! The metadata static variables are generated by the proc-macros, which is an issue because the
//! proc-macros don't have knowledge of the entire interface -- they can only see the item they're
//! wrapping. For example, when a proc-macro sees a type name, it doesn't know anything about the
//! actual type: it could be a Record, an Enum, or even a type alias for a `Vec<>`/`Result<>`.
//!
//! This module helps bridge the gap by providing tools that allow the proc-macros to generate code
//! to encode the interface metadata:
//! - A set of const functions to build up metadata buffers with const expressions
//! - The `export_static_metadata_var!` macro, which creates the static variable from a const metadata
//! buffer.
//! - The `FfiConverter::TYPE_ID_META` const which encodes an identifier for that type in a
//! metadata buffer.
//!
//! `uniffi_bindgen::macro_metadata` contains the code to read the metadata from a library file.
//! `fixtures/metadata` has the tests.
/// Metadata constants, make sure to keep this in sync with copy in `uniffi_meta::reader`
pub mod codes {
// Top-level metadata item codes
pub const FUNC: u8 = 0;
pub const METHOD: u8 = 1;
pub const RECORD: u8 = 2;
pub const ENUM: u8 = 3;
pub const INTERFACE: u8 = 4;
pub const ERROR: u8 = 5;
pub const NAMESPACE: u8 = 6;
pub const CONSTRUCTOR: u8 = 7;
pub const UDL_FILE: u8 = 8;
pub const CALLBACK_INTERFACE: u8 = 9;
pub const TRAIT_METHOD: u8 = 10;
pub const UNIFFI_TRAIT: u8 = 11;
pub const UNKNOWN: u8 = 255;
// Type codes
pub const TYPE_U8: u8 = 0;
pub const TYPE_U16: u8 = 1;
pub const TYPE_U32: u8 = 2;
pub const TYPE_U64: u8 = 3;
pub const TYPE_I8: u8 = 4;
pub const TYPE_I16: u8 = 5;
pub const TYPE_I32: u8 = 6;
pub const TYPE_I64: u8 = 7;
pub const TYPE_F32: u8 = 8;
pub const TYPE_F64: u8 = 9;
pub const TYPE_BOOL: u8 = 10;
pub const TYPE_STRING: u8 = 11;
pub const TYPE_OPTION: u8 = 12;
pub const TYPE_RECORD: u8 = 13;
pub const TYPE_ENUM: u8 = 14;
// 15 no longer used.
pub const TYPE_INTERFACE: u8 = 16;
pub const TYPE_VEC: u8 = 17;
pub const TYPE_HASH_MAP: u8 = 18;
pub const TYPE_SYSTEM_TIME: u8 = 19;
pub const TYPE_DURATION: u8 = 20;
pub const TYPE_CALLBACK_INTERFACE: u8 = 21;
pub const TYPE_CUSTOM: u8 = 22;
pub const TYPE_RESULT: u8 = 23;
pub const TYPE_FUTURE: u8 = 24;
pub const TYPE_FOREIGN_EXECUTOR: u8 = 25;
pub const TYPE_UNIT: u8 = 255;
// Literal codes for LiteralMetadata - note that we don't support
// all variants in the "emit/reader" context.
pub const LIT_STR: u8 = 0;
pub const LIT_INT: u8 = 1;
pub const LIT_FLOAT: u8 = 2;
pub const LIT_BOOL: u8 = 3;
pub const LIT_NULL: u8 = 4;
}
const BUF_SIZE: usize = 4096;
// This struct is a kludge around the fact that Rust const generic support doesn't quite handle our
// needs.
//
// We'd like to have code like this in `FfiConverter`:
//
// ```
// const TYPE_ID_META_SIZE: usize;
// const TYPE_ID_META: [u8, Self::TYPE_ID_META_SIZE];
// ```
//
// This would define a metadata buffer, correctly size for the data needed. However, associated
// consts as generic params aren't supported yet.
//
// To work around this, we use `const MetadataBuffer` values, which contain fixed-sized buffers
// with enough capacity to store our largest metadata arrays. Since the `MetadataBuffer` values
// are const, they're only stored at compile time and the extra bytes don't end up contributing to
// the final binary size. This was tested on Rust `1.66.0` with `--release` by increasing
// `BUF_SIZE` and checking the compiled library sizes.
#[derive(Debug)]
pub struct MetadataBuffer {
pub bytes: [u8; BUF_SIZE],
pub size: usize,
}
impl MetadataBuffer {
pub const fn new() -> Self {
Self {
bytes: [0; BUF_SIZE],
size: 0,
}
}
pub const fn from_code(value: u8) -> Self {
Self::new().concat_value(value)
}
// Concatenate another buffer to this one.
//
// This consumes self, which is convenient for the proc-macro code and also allows us to avoid
// allocated an extra buffer.
pub const fn concat(mut self, other: MetadataBuffer) -> MetadataBuffer {
assert!(self.size + other.size <= BUF_SIZE);
// It would be nice to use `copy_from_slice()`, but that's not allowed in const functions
// as of Rust 1.66.
let mut i = 0;
while i < other.size {
self.bytes[self.size] = other.bytes[i];
self.size += 1;
i += 1;
}
self
}
// Concatenate a `u8` value to this buffer
//
// This consumes self, which is convenient for the proc-macro code and also allows us to avoid
// allocated an extra buffer.
pub const fn concat_value(mut self, value: u8) -> Self {
assert!(self.size < BUF_SIZE);
self.bytes[self.size] = value;
self.size += 1;
self
}
// Concatenate a `u32` value to this buffer
//
// This consumes self, which is convenient for the proc-macro code and also allows us to avoid
// allocated an extra buffer.
pub const fn concat_u32(mut self, value: u32) -> Self {
assert!(self.size + 4 <= BUF_SIZE);
// store the value as little-endian
self.bytes[self.size] = value as u8;
self.bytes[self.size + 1] = (value >> 8) as u8;
self.bytes[self.size + 2] = (value >> 16) as u8;
self.bytes[self.size + 3] = (value >> 24) as u8;
self.size += 4;
self
}
// Concatenate a `bool` value to this buffer
//
// This consumes self, which is convenient for the proc-macro code and also allows us to avoid
// allocated an extra buffer.
pub const fn concat_bool(self, value: bool) -> Self {
self.concat_value(value as u8)
}
// Concatenate a string to this buffer.
//
// Strings are encoded as a `u8` length, followed by the utf8 data.
//
// This consumes self, which is convenient for the proc-macro code and also allows us to avoid
// allocated an extra buffer.
pub const fn concat_str(mut self, string: &str) -> Self {
assert!(string.len() < 256);
assert!(self.size + string.len() < BUF_SIZE);
self.bytes[self.size] = string.len() as u8;
self.size += 1;
let bytes = string.as_bytes();
let mut i = 0;
while i < bytes.len() {
self.bytes[self.size] = bytes[i];
self.size += 1;
i += 1;
}
self
}
// Create an array from this MetadataBuffer
//
// SIZE should always be `self.size`. This is part of the kludge to hold us over until Rust
// gets better const generic support.
pub const fn into_array<const SIZE: usize>(self) -> [u8; SIZE] {
let mut result: [u8; SIZE] = [0; SIZE];
let mut i = 0;
while i < SIZE {
result[i] = self.bytes[i];
i += 1;
}
result
}
// Create a checksum from this MetadataBuffer
//
// This is used by the bindings code to verify that the library they link to is the same one
// that the bindings were generated from.
pub const fn checksum(&self) -> u16 {
calc_checksum(&self.bytes, self.size)
}
}
impl AsRef<[u8]> for MetadataBuffer {
fn as_ref(&self) -> &[u8] {
&self.bytes[..self.size]
}
}
// Create a checksum for a MetadataBuffer
//
// This is used by the bindings code to verify that the library they link to is the same one
// that the bindings were generated from.
pub const fn checksum_metadata(buf: &[u8]) -> u16 {
calc_checksum(buf, buf.len())
}
const fn calc_checksum(bytes: &[u8], size: usize) -> u16 {
// Taken from the fnv_hash() function from the FNV crate (https://github.com/servo/rust-fnv/blob/master/lib.rs).
// fnv_hash() hasn't been released in a version yet.
const INITIAL_STATE: u64 = 0xcbf29ce484222325;
const PRIME: u64 = 0x100000001b3;
let mut hash = INITIAL_STATE;
let mut i = 0;
while i < size {
hash ^= bytes[i] as u64;
hash = hash.wrapping_mul(PRIME);
i += 1;
}
// Convert the 64-bit hash to a 16-bit hash by XORing everything together
(hash ^ (hash >> 16) ^ (hash >> 32) ^ (hash >> 48)) as u16
}