// This file is part of Bizinikiwi. // Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Provides host <-> runtime FFI marshalling strategy newtype wrappers //! for defining runtime interfaces. use crate::{ util::{pack_ptr_and_len, unpack_ptr_and_len}, RIType, }; #[cfg(not(bizinikiwi_runtime))] use crate::host::*; #[cfg(bizinikiwi_runtime)] use crate::wasm::*; #[cfg(not(bizinikiwi_runtime))] use pezsp_wasm_interface::{FunctionContext, Pointer, Result}; #[cfg(not(bizinikiwi_runtime))] use alloc::{format, string::String}; use alloc::vec::Vec; use core::{any::type_name, marker::PhantomData}; /// Pass a value into the host by a thin pointer. /// /// This casts the value into a `&[u8]` using `AsRef<[u8]>` and passes a pointer to that byte blob /// to the host. Then the host reads `N` bytes from that address into an `[u8; N]`, converts it /// into target type using `From<[u8; N]>` and passes it into the host function by a copy. /// /// Use [`PassPointerAndRead`] if you want to have the host function accept a reference type /// on the host side or if you'd like to avoid the extra copy. /// /// Raw FFI type: `u32` (a pointer) pub struct PassPointerAndReadCopy(PhantomData<(T, [u8; N])>); impl RIType for PassPointerAndReadCopy { type FFIType = u32; type Inner = T; } #[cfg(not(bizinikiwi_runtime))] impl<'a, T, const N: usize> FromFFIValue<'a> for PassPointerAndReadCopy where T: From<[u8; N]> + Copy, { type Owned = T; fn from_ffi_value( context: &mut dyn FunctionContext, arg: Self::FFIType, ) -> Result { let mut out = [0; N]; context.read_memory_into(Pointer::new(arg), &mut out)?; Ok(T::from(out)) } #[inline] fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner { *owned } } #[cfg(bizinikiwi_runtime)] impl IntoFFIValue for PassPointerAndReadCopy where T: AsRef<[u8]>, { type Destructor = (); fn into_ffi_value(value: &mut Self::Inner) -> (Self::FFIType, Self::Destructor) { // Using an 'assert' instead of a 'T: AsRef<[u8; N]>` bound since a '[u8; N]' *doesn't* // implement it. assert_eq!(value.as_ref().len(), N); (value.as_ref().as_ptr() as u32, ()) } } /// Pass a value into the host by a thin pointer. /// /// This casts the value into a `&[u8]` using `AsRef<[u8]>` and passes a pointer to that byte blob /// to the host. Then the host reads `N` bytes from that address into an `[u8; N]`, converts it /// into target type using `From<[u8; N]>` and passes it into the host function by a reference. /// /// This can only be used with reference types (e.g. `&[u8; 32]`). Use [`PassPointerAndReadCopy`] /// if you want to have the host function accept a non-reference type on the host side. /// /// Raw FFI type: `u32` (a pointer) pub struct PassPointerAndRead(PhantomData<(T, [u8; N])>); impl<'a, T, const N: usize> RIType for PassPointerAndRead<&'a T, N> { type FFIType = u32; type Inner = &'a T; } #[cfg(not(bizinikiwi_runtime))] impl<'a, T, const N: usize> FromFFIValue<'a> for PassPointerAndRead<&'a T, N> where T: From<[u8; N]>, { type Owned = T; fn from_ffi_value( context: &mut dyn FunctionContext, arg: Self::FFIType, ) -> Result { let mut out = [0; N]; context.read_memory_into(Pointer::new(arg), &mut out)?; Ok(T::from(out)) } #[inline] fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner { &*owned } } #[cfg(bizinikiwi_runtime)] impl<'a, T, const N: usize> IntoFFIValue for PassPointerAndRead<&'a T, N> where T: AsRef<[u8]>, { type Destructor = (); fn into_ffi_value(value: &mut Self::Inner) -> (Self::FFIType, Self::Destructor) { assert_eq!(value.as_ref().len(), N); (value.as_ref().as_ptr() as u32, ()) } } /// Pass a value into the host by a fat pointer. /// /// This casts the value into a `&[u8]` and passes a pointer to that byte blob and its length /// to the host. Then the host reads that blob and converts it into an owned type and passes it /// (either as an owned type or as a reference) to the host function. /// /// Raw FFI type: `u64` (a fat pointer; upper 32 bits is the size, lower 32 bits is the pointer) pub struct PassFatPointerAndRead(PhantomData); impl RIType for PassFatPointerAndRead { type FFIType = u64; type Inner = T; } #[cfg(not(bizinikiwi_runtime))] impl<'a> FromFFIValue<'a> for PassFatPointerAndRead<&'a [u8]> { type Owned = Vec; fn from_ffi_value( context: &mut dyn FunctionContext, arg: Self::FFIType, ) -> Result { let (ptr, len) = unpack_ptr_and_len(arg); context.read_memory(Pointer::new(ptr), len) } fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner { &*owned } } #[cfg(not(bizinikiwi_runtime))] impl<'a> FromFFIValue<'a> for PassFatPointerAndRead<&'a str> { type Owned = String; fn from_ffi_value( context: &mut dyn FunctionContext, arg: Self::FFIType, ) -> Result { let (ptr, len) = unpack_ptr_and_len(arg); let vec = context.read_memory(Pointer::new(ptr), len)?; String::from_utf8(vec).map_err(|_| "could not parse '&str' when marshalling hostcall's arguments through the FFI boundary: the string is not valid UTF-8".into()) } fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner { &*owned } } #[cfg(not(bizinikiwi_runtime))] impl<'a> FromFFIValue<'a> for PassFatPointerAndRead> { type Owned = Vec; fn from_ffi_value( context: &mut dyn FunctionContext, arg: Self::FFIType, ) -> Result { as FromFFIValue>::from_ffi_value(context, arg) } fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner { core::mem::take(owned) } } #[cfg(bizinikiwi_runtime)] impl IntoFFIValue for PassFatPointerAndRead where T: AsRef<[u8]>, { type Destructor = (); fn into_ffi_value(value: &mut Self::Inner) -> (Self::FFIType, Self::Destructor) { let value = value.as_ref(); (pack_ptr_and_len(value.as_ptr() as u32, value.len() as u32), ()) } } /// Pass a value into the host by a fat pointer, writing it back after the host call ends. /// /// This casts the value into a `&mut [u8]` and passes a pointer to that byte blob and its length /// to the host. Then the host reads that blob and converts it into an owned type and passes it /// as a mutable reference to the host function. After the host function finishes the byte blob /// is written back into the guest memory. /// /// Raw FFI type: `u64` (a fat pointer; upper 32 bits is the size, lower 32 bits is the pointer) pub struct PassFatPointerAndReadWrite(PhantomData); impl RIType for PassFatPointerAndReadWrite { type FFIType = u64; type Inner = T; } #[cfg(not(bizinikiwi_runtime))] impl<'a> FromFFIValue<'a> for PassFatPointerAndReadWrite<&'a mut [u8]> { type Owned = Vec; fn from_ffi_value( context: &mut dyn FunctionContext, arg: Self::FFIType, ) -> Result { let (ptr, len) = unpack_ptr_and_len(arg); context.read_memory(Pointer::new(ptr), len) } fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner { &mut *owned } fn write_back_into_runtime( value: Self::Owned, context: &mut dyn FunctionContext, arg: Self::FFIType, ) -> Result<()> { let (ptr, len) = unpack_ptr_and_len(arg); assert_eq!(len as usize, value.len()); context.write_memory(Pointer::new(ptr), &value) } } #[cfg(bizinikiwi_runtime)] impl<'a> IntoFFIValue for PassFatPointerAndReadWrite<&'a mut [u8]> { type Destructor = (); fn into_ffi_value(value: &mut Self::Inner) -> (Self::FFIType, Self::Destructor) { (pack_ptr_and_len(value.as_ptr() as u32, value.len() as u32), ()) } } /// Pass a pointer into the host and write to it after the host call ends. /// /// This casts a given type into `&mut [u8]` using `AsMut<[u8]>` and passes a pointer to /// that byte slice into the host. The host *doesn't* read from this and instead creates /// a default instance of type `T` and passes it as a `&mut T` into the host function /// implementation. After the host function finishes this value is then cast into a `&[u8]` using /// `AsRef<[u8]>` and written back into the guest memory. /// /// Raw FFI type: `u32` (a pointer) pub struct PassPointerAndWrite(PhantomData<(T, [u8; N])>); impl RIType for PassPointerAndWrite { type FFIType = u32; type Inner = T; } #[cfg(not(bizinikiwi_runtime))] impl<'a, T, const N: usize> FromFFIValue<'a> for PassPointerAndWrite<&'a mut T, N> where T: Default + AsRef<[u8]>, { type Owned = T; fn from_ffi_value( _context: &mut dyn FunctionContext, _arg: Self::FFIType, ) -> Result { Ok(T::default()) } fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner { &mut *owned } fn write_back_into_runtime( value: Self::Owned, context: &mut dyn FunctionContext, arg: Self::FFIType, ) -> Result<()> { let value = value.as_ref(); assert_eq!(value.len(), N); context.write_memory(Pointer::new(arg), value) } } #[cfg(bizinikiwi_runtime)] impl<'a, T, const N: usize> IntoFFIValue for PassPointerAndWrite<&'a mut T, N> where T: AsMut<[u8]>, { type Destructor = (); fn into_ffi_value(value: &mut Self::Inner) -> (Self::FFIType, Self::Destructor) { let value = value.as_mut(); assert_eq!(value.len(), N); (value.as_ptr() as u32, ()) } } /// Pass a `T` into the host using the SCALE codec. /// /// This encodes a `T` into a `Vec` using the SCALE codec and then /// passes a pointer to that byte blob and its length to the host, /// which then reads it and decodes back into `T`. /// /// Raw FFI type: `u64` (a fat pointer; upper 32 bits is the size, lower 32 bits is the pointer) pub struct PassFatPointerAndDecode(PhantomData); impl RIType for PassFatPointerAndDecode { type FFIType = u64; type Inner = T; } #[cfg(not(bizinikiwi_runtime))] impl<'a, T: codec::Decode> FromFFIValue<'a> for PassFatPointerAndDecode { type Owned = Option; fn from_ffi_value( context: &mut dyn FunctionContext, arg: Self::FFIType, ) -> Result { let (ptr, len) = unpack_ptr_and_len(arg); let vec = context.read_memory(Pointer::new(ptr), len)?; T::decode(&mut &vec[..]).map_err(|error| format!( "could not SCALE-decode '{}' when marshalling hostcall's arguments through the FFI boundary: {error}", type_name::()) ).map(Some) } fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner { owned.take().expect("this is called only once and is never 'None'") } } #[cfg(bizinikiwi_runtime)] impl IntoFFIValue for PassFatPointerAndDecode { type Destructor = Vec; fn into_ffi_value(value: &mut Self::Inner) -> (Self::FFIType, Self::Destructor) { let data = value.encode(); (pack_ptr_and_len(data.as_ptr() as u32, data.len() as u32), data) } } /// Pass a `&[T]` into the host using the SCALE codec. /// /// This encodes a `&[T]` into a `Vec` using the SCALE codec and then /// passes a pointer to that byte blob and its length to the host, /// which then reads it and decodes back into `Vec` and passes /// a reference to that (as `&[T]`) into the host function. /// /// Raw FFI type: `u64` (a fat pointer; upper 32 bits is the size, lower 32 bits is the pointer) pub struct PassFatPointerAndDecodeSlice(PhantomData); impl RIType for PassFatPointerAndDecodeSlice { type FFIType = u64; type Inner = T; } #[cfg(not(bizinikiwi_runtime))] impl<'a, T: codec::Decode> FromFFIValue<'a> for PassFatPointerAndDecodeSlice<&'a [T]> { type Owned = Vec; fn from_ffi_value( context: &mut dyn FunctionContext, arg: Self::FFIType, ) -> Result { let (ptr, len) = unpack_ptr_and_len(arg); let vec = context.read_memory(Pointer::new(ptr), len)?; as codec::Decode>::decode(&mut &vec[..]).map_err(|error| format!( "could not SCALE-decode '{}' when marshalling hostcall's arguments through the FFI boundary: {error}", type_name::>() )) } fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner { &*owned } } #[cfg(bizinikiwi_runtime)] impl<'a, T: codec::Encode> IntoFFIValue for PassFatPointerAndDecodeSlice<&'a [T]> { type Destructor = Vec; fn into_ffi_value(value: &mut Self::Inner) -> (Self::FFIType, Self::Destructor) { let data = codec::Encode::encode(value); (pack_ptr_and_len(data.as_ptr() as u32, data.len() as u32), data) } } /// A trait signifying a primitive type. trait Primitive: Copy {} impl Primitive for u8 {} impl Primitive for u16 {} impl Primitive for u32 {} impl Primitive for u64 {} impl Primitive for i8 {} impl Primitive for i16 {} impl Primitive for i32 {} impl Primitive for i64 {} /// Pass `T` through the FFI boundary by first converting it to `U` in the runtime, and then /// converting it back to `T` on the host's side. /// /// Raw FFI type: same as `U`'s FFI type pub struct PassAs(PhantomData<(T, U)>); impl RIType for PassAs where U: RIType, { type FFIType = ::FFIType; type Inner = T; } #[cfg(not(bizinikiwi_runtime))] impl<'a, T, U> FromFFIValue<'a> for PassAs where U: RIType + FromFFIValue<'a> + Primitive, T: TryFrom<>::Owned> + Copy, { type Owned = T; fn from_ffi_value( context: &mut dyn FunctionContext, arg: Self::FFIType, ) -> Result { ::from_ffi_value(context, arg).and_then(|value| value.try_into() .map_err(|_| format!( "failed to convert '{}' (passed as '{}') into '{}' when marshalling hostcall's arguments through the FFI boundary", type_name::(), type_name::(), type_name::() ))) } fn take_from_owned(owned: &'a mut Self::Owned) -> Self::Inner { *owned } } #[cfg(bizinikiwi_runtime)] impl IntoFFIValue for PassAs where U: RIType + IntoFFIValue + Primitive, U::Inner: From, T: Copy, { type Destructor = ::Destructor; fn into_ffi_value(value: &mut Self::Inner) -> (Self::FFIType, Self::Destructor) { let mut value = U::Inner::from(*value); ::into_ffi_value(&mut value) } } /// Return `T` through the FFI boundary by first converting it to `U` on the host's side, and then /// converting it back to `T` in the runtime. /// /// Raw FFI type: same as `U`'s FFI type pub struct ReturnAs(PhantomData<(T, U)>); impl RIType for ReturnAs where U: RIType, { type FFIType = ::FFIType; type Inner = T; } #[cfg(not(bizinikiwi_runtime))] impl IntoFFIValue for ReturnAs where U: RIType + IntoFFIValue + Primitive, ::Inner: From, { fn into_ffi_value( value: Self::Inner, context: &mut dyn FunctionContext, ) -> Result { let value: ::Inner = value.into(); ::into_ffi_value(value, context) } } #[cfg(bizinikiwi_runtime)] impl FromFFIValue for ReturnAs where U: RIType + FromFFIValue + Primitive, Self::Inner: TryFrom, { fn from_ffi_value(arg: Self::FFIType) -> Self::Inner { let value = ::from_ffi_value(arg); match Self::Inner::try_from(value) { Ok(value) => value, Err(_) => { panic!( "failed to convert '{}' (passed as '{}') into a '{}' when marshalling a hostcall's return value through the FFI boundary", type_name::(), type_name::(), type_name::() ); }, } } } /// (DEPRECATED) Return `T` as a blob of bytes into the runtime. /// /// Uses `T::AsRef<[u8]>` to cast `T` into a `&[u8]`, allocates runtime memory /// using the legacy allocator, copies the slice into the runtime memory, and /// returns a pointer to it. /// /// THIS STRATEGY IS DEPRECATED; DO NOT USE FOR NEW HOST FUNCTIONS! /// /// Ideally use a mutable slice to return data to the guest, for example using /// the [`PassPointerAndWrite`] strategy. /// /// Raw FFI type: `u32` (a pointer to the byte blob) pub struct AllocateAndReturnPointer(PhantomData<(T, [u8; N])>); impl RIType for AllocateAndReturnPointer { type FFIType = u32; type Inner = T; } #[cfg(not(bizinikiwi_runtime))] impl IntoFFIValue for AllocateAndReturnPointer where T: AsRef<[u8]>, { fn into_ffi_value( value: Self::Inner, context: &mut dyn FunctionContext, ) -> Result { let value = value.as_ref(); assert_eq!( value.len(), N, "expected the byte blob to be {N} bytes long, is {} bytes when returning '{}' from a host function", value.len(), type_name::() ); let addr = context.allocate_memory(value.len() as u32)?; context.write_memory(addr, value)?; Ok(addr.into()) } } #[cfg(bizinikiwi_runtime)] impl FromFFIValue for AllocateAndReturnPointer where T: From<[u8; N]>, { fn from_ffi_value(arg: Self::FFIType) -> Self::Inner { // SAFETY: This memory was allocated by the host allocator with the exact // capacity needed, so it's safe to make a `Vec` out of it. let value = unsafe { Vec::from_raw_parts(arg as *mut u8, N, N) }; // SAFETY: Reading a `[u8; N]` from a `&[u8]` which is at least `N` elements long is safe. let array = unsafe { *(value.as_ptr() as *const [u8; N]) }; T::from(array) } } /// (DEPRECATED) Return `T` as a blob of bytes into the runtime. /// /// Uses `T::AsRef<[u8]>` to cast `T` into a `&[u8]`, allocates runtime memory /// using the legacy allocator, copies the slice into the runtime memory, and /// returns a pointer to it. /// /// THIS STRATEGY IS DEPRECATED; DO NOT USE FOR NEW HOST FUNCTIONS! /// /// Ideally use a mutable slice to return data to the guest, for example using /// the [`PassPointerAndWrite`] strategy. /// /// Raw FFI type: `u64` (a fat pointer; upper 32 bits is the size, lower 32 bits is the pointer) pub struct AllocateAndReturnFatPointer(PhantomData); impl RIType for AllocateAndReturnFatPointer { type FFIType = u64; type Inner = T; } #[cfg(not(bizinikiwi_runtime))] impl IntoFFIValue for AllocateAndReturnFatPointer where T: AsRef<[u8]>, { fn into_ffi_value( value: Self::Inner, context: &mut dyn FunctionContext, ) -> Result { let value = value.as_ref(); let ptr = context.allocate_memory(value.len() as u32)?; context.write_memory(ptr, &value)?; Ok(pack_ptr_and_len(ptr.into(), value.len() as u32)) } } #[cfg(bizinikiwi_runtime)] impl FromFFIValue for AllocateAndReturnFatPointer where T: From>, { fn from_ffi_value(arg: Self::FFIType) -> Self::Inner { let (ptr, len) = unpack_ptr_and_len(arg); let len = len as usize; let vec = if len == 0 { Vec::new() } else { // SAFETY: This memory was allocated by the host allocator with the exact // capacity needed, so it's safe to make a `Vec` out of it. unsafe { Vec::from_raw_parts(ptr as *mut u8, len, len) } }; T::from(vec) } } /// (DEPRECATED) Return `T` into the runtime using the SCALE codec. /// /// Encodes `T` using the SCALE codec, allocates runtime memory using the legacy /// allocator, copies the encoded payload into the runtime memory, and returns /// a fat pointer to it. /// /// THIS STRATEGY IS DEPRECATED; DO NOT USE FOR NEW HOST FUNCTIONS! /// /// Ideally use a mutable slice to return data to the guest, for example using /// the [`PassPointerAndWrite`] strategy. /// /// Raw FFI type: `u64` (a fat pointer; upper 32 bits is the size, lower 32 bits is the pointer) pub struct AllocateAndReturnByCodec(PhantomData); impl RIType for AllocateAndReturnByCodec { type FFIType = u64; type Inner = T; } #[cfg(not(bizinikiwi_runtime))] impl IntoFFIValue for AllocateAndReturnByCodec { fn into_ffi_value(value: T, context: &mut dyn FunctionContext) -> Result { let vec = value.encode(); let ptr = context.allocate_memory(vec.len() as u32)?; context.write_memory(ptr, &vec)?; Ok(pack_ptr_and_len(ptr.into(), vec.len() as u32)) } } #[cfg(bizinikiwi_runtime)] impl FromFFIValue for AllocateAndReturnByCodec { fn from_ffi_value(arg: Self::FFIType) -> Self::Inner { let (ptr, len) = unpack_ptr_and_len(arg); let len = len as usize; let encoded = if len == 0 { bytes::Bytes::new() } else { // SAFETY: This memory was allocated by the host allocator with the exact // capacity needed, so it's safe to make a `Vec` out of it. bytes::Bytes::from(unsafe { Vec::from_raw_parts(ptr as *mut u8, len, len) }) }; match codec::decode_from_bytes(encoded) { Ok(value) => value, Err(error) => { panic!( "failed to decode '{}' when marshalling a hostcall's return value through the FFI boundary: {error}", type_name::(), ); }, } } }