Skip to main content

sim_kernel/
native_abi.rs

1//! The native ABI: byte-frame and manifest transport types for dynamic libs.
2//!
3//! The kernel defines the versioned entrypoint, header, and call/response
4//! frame contracts that native libraries are loaded across; it defines no
5//! guest semantics beyond this transport.
6
7use std::ffi::{CString, c_char, c_void};
8
9/// Exported symbol name a v1 native dynamic library must expose.
10pub const NATIVE_DYLIB_ENTRYPOINT_V1: &str = "sim_native_abi_v1";
11/// Major ABI version of the v1 native library transport.
12pub const NATIVE_LIB_ABI_V1_MAJOR: u16 = 1;
13/// Minor ABI version of the v1 native library transport.
14pub const NATIVE_LIB_ABI_V1_MINOR: u16 = 0;
15
16/// Entry that creates a fresh guest instance, returning an opaque handle.
17pub type NativeAbiInstantiate = unsafe extern "C" fn() -> *mut c_void;
18/// Entry that destroys a guest instance handle.
19pub type NativeAbiDestroyInstance = unsafe extern "C" fn(instance: *mut c_void);
20/// Entry that returns the guest's encoded manifest as a call response.
21pub type NativeAbiManifest = unsafe extern "C" fn(instance: *mut c_void) -> NativeAbiCallResponse;
22/// Entry that invokes a named guest function with borrowed argument bytes.
23pub type NativeAbiCall = unsafe extern "C" fn(
24    instance: *mut c_void,
25    function: *const c_char,
26    args: NativeAbiBorrowedBytes,
27) -> NativeAbiCallResponse;
28/// Entry that frees bytes the guest previously handed back to the host.
29pub type NativeAbiDestroyBytes = unsafe extern "C" fn(bytes: NativeAbiOwnedBytes);
30/// Entry that frees an error the guest previously handed back to the host.
31pub type NativeAbiDestroyError = unsafe extern "C" fn(error: *mut NativeAbiError);
32
33/// Version header prefix shared by every native library ABI struct.
34#[repr(C)]
35#[derive(Clone, Copy)]
36pub struct NativeLibAbiHeaderV1 {
37    /// Byte size of the full ABI struct, for forward-compatible probing.
38    pub struct_size: usize,
39    /// Major ABI version the library was built against.
40    pub abi_major: u16,
41    /// Minor ABI version the library was built against.
42    pub abi_minor: u16,
43}
44
45impl NativeLibAbiHeaderV1 {
46    /// Builds a header from an explicit size and version pair.
47    pub const fn new(struct_size: usize, abi_major: u16, abi_minor: u16) -> Self {
48        Self {
49            struct_size,
50            abi_major,
51            abi_minor,
52        }
53    }
54}
55
56/// The v1 native library vtable: header plus the entrypoint function table.
57#[repr(C)]
58#[derive(Clone, Copy)]
59pub struct NativeLibAbiV1 {
60    /// Byte size of this struct, matching [`NativeLibAbiHeaderV1::struct_size`].
61    pub struct_size: usize,
62    /// Major ABI version the library was built against.
63    pub abi_major: u16,
64    /// Minor ABI version the library was built against.
65    pub abi_minor: u16,
66    /// Instance-creation entry.
67    pub instantiate: NativeAbiInstantiate,
68    /// Instance-destruction entry.
69    pub destroy_instance: NativeAbiDestroyInstance,
70    /// Manifest-fetch entry.
71    pub manifest: NativeAbiManifest,
72    /// Function-call entry.
73    pub call: NativeAbiCall,
74    /// Byte-buffer free entry.
75    pub destroy_bytes: NativeAbiDestroyBytes,
76    /// Error free entry.
77    pub destroy_error: NativeAbiDestroyError,
78}
79
80impl NativeLibAbiV1 {
81    /// Byte size of the leading [`NativeLibAbiHeaderV1`] prefix.
82    pub const HEADER_SIZE: usize = std::mem::size_of::<NativeLibAbiHeaderV1>();
83
84    /// Builds a v1 vtable, stamping in the current size and version.
85    pub const fn new(
86        instantiate: NativeAbiInstantiate,
87        destroy_instance: NativeAbiDestroyInstance,
88        manifest: NativeAbiManifest,
89        call: NativeAbiCall,
90        destroy_bytes: NativeAbiDestroyBytes,
91        destroy_error: NativeAbiDestroyError,
92    ) -> Self {
93        Self {
94            struct_size: std::mem::size_of::<Self>(),
95            abi_major: NATIVE_LIB_ABI_V1_MAJOR,
96            abi_minor: NATIVE_LIB_ABI_V1_MINOR,
97            instantiate,
98            destroy_instance,
99            manifest,
100            call,
101            destroy_bytes,
102            destroy_error,
103        }
104    }
105
106    /// Extracts the version header prefix of this vtable.
107    pub const fn header(&self) -> NativeLibAbiHeaderV1 {
108        NativeLibAbiHeaderV1::new(self.struct_size, self.abi_major, self.abi_minor)
109    }
110}
111
112/// A borrowed byte slice passed by raw pointer across the ABI boundary.
113#[repr(C)]
114#[derive(Clone, Copy)]
115pub struct NativeAbiBorrowedBytes {
116    /// Pointer to the first byte, or null when empty.
117    pub ptr: *const u8,
118    /// Number of bytes addressed by `ptr`.
119    pub len: usize,
120}
121
122impl NativeAbiBorrowedBytes {
123    /// The empty slice (null pointer, zero length).
124    pub const fn empty() -> Self {
125        Self {
126            ptr: std::ptr::null(),
127            len: 0,
128        }
129    }
130
131    /// Borrows an existing slice for the duration of a single ABI call.
132    ///
133    /// # Examples
134    ///
135    /// ```
136    /// use sim_kernel::NativeAbiBorrowedBytes;
137    ///
138    /// let payload = [1u8, 2, 3];
139    /// let borrowed = NativeAbiBorrowedBytes::borrow(&payload);
140    /// assert_eq!(borrowed.len, 3);
141    /// assert!(!borrowed.ptr.is_null());
142    ///
143    /// let empty = NativeAbiBorrowedBytes::empty();
144    /// assert_eq!(empty.len, 0);
145    /// assert!(empty.ptr.is_null());
146    /// ```
147    pub fn borrow(bytes: &[u8]) -> Self {
148        Self {
149            ptr: bytes.as_ptr(),
150            len: bytes.len(),
151        }
152    }
153}
154
155/// An owned byte buffer transferred across the ABI, freed by its allocator.
156#[repr(C)]
157#[derive(Clone, Copy)]
158pub struct NativeAbiOwnedBytes {
159    /// Pointer to the first byte, or null when empty.
160    pub ptr: *mut u8,
161    /// Number of valid bytes addressed by `ptr`.
162    pub len: usize,
163    /// Allocated capacity, needed to reconstruct the owning `Vec`.
164    pub cap: usize,
165}
166
167impl NativeAbiOwnedBytes {
168    /// The empty buffer (null pointer, zero length and capacity).
169    pub const fn empty() -> Self {
170        Self {
171            ptr: std::ptr::null_mut(),
172            len: 0,
173            cap: 0,
174        }
175    }
176}
177
178/// Result of a manifest or call entry: either owned bytes or an error.
179#[repr(C)]
180pub struct NativeAbiCallResponse {
181    /// Success payload bytes; empty when `error` is set.
182    pub bytes: NativeAbiOwnedBytes,
183    /// Failure pointer, or null on success.
184    pub error: *mut NativeAbiError,
185}
186
187impl NativeAbiCallResponse {
188    /// Builds a successful response carrying the given bytes.
189    pub const fn success(bytes: NativeAbiOwnedBytes) -> Self {
190        Self {
191            bytes,
192            error: std::ptr::null_mut(),
193        }
194    }
195
196    /// Builds a failed response carrying the given error pointer.
197    pub const fn failure(error: *mut NativeAbiError) -> Self {
198        Self {
199            bytes: NativeAbiOwnedBytes::empty(),
200            error,
201        }
202    }
203}
204
205/// An error returned across the ABI as a heap-allocated C string holder.
206#[repr(C)]
207pub struct NativeAbiError {
208    /// NUL-terminated message owned by the allocating side.
209    pub message: *mut c_char,
210}
211
212impl NativeAbiError {
213    /// Heap-allocates an error, sanitizing interior NULs out of the message.
214    pub fn boxed(message: impl Into<String>) -> *mut Self {
215        let sanitized = message.into().replace('\0', " ");
216        let message = CString::new(sanitized)
217            .expect("sanitized native ABI error strings must not contain interior NULs");
218        Box::into_raw(Box::new(Self {
219            message: message.into_raw(),
220        }))
221    }
222}
223
224/// Transfers ownership of a `Vec<u8>` into an ABI owned-bytes descriptor.
225///
226/// # Examples
227///
228/// ```
229/// use sim_kernel::native_abi_owned_bytes;
230///
231/// let owned = native_abi_owned_bytes(vec![7u8, 8, 9]);
232/// assert_eq!(owned.len, 3);
233/// assert!(owned.cap >= owned.len);
234///
235/// // The descriptor owns the allocation; reclaim it to avoid a leak.
236/// let reclaimed = unsafe { Vec::from_raw_parts(owned.ptr, owned.len, owned.cap) };
237/// assert_eq!(reclaimed, vec![7u8, 8, 9]);
238/// ```
239pub fn native_abi_owned_bytes(mut bytes: Vec<u8>) -> NativeAbiOwnedBytes {
240    let result = NativeAbiOwnedBytes {
241        ptr: bytes.as_mut_ptr(),
242        len: bytes.len(),
243        cap: bytes.capacity(),
244    };
245    std::mem::forget(bytes);
246    result
247}