diff --git a/libs/flatipc-derive/Cargo.toml b/libs/flatipc-derive/Cargo.toml index 1a0e962f6..e9c0f39ef 100644 --- a/libs/flatipc-derive/Cargo.toml +++ b/libs/flatipc-derive/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "flatipc-derive" -version = "0.1.0" +version = "0.1.2" authors = ["Sean Cross "] description = "Custom derive for traits from the flatipc crate" license = "BSD-2-Clause OR Apache-2.0 OR MIT" @@ -11,8 +11,8 @@ repository = "https://github.com/betrusted/xous-flatipc" proc-macro = true [dependencies] -proc-macro2 = "*" -quote = "*" +proc-macro2 = "1" +quote = "1" syn = { version = "2", features = ["parsing", "extra-traits"] } [features] diff --git a/libs/flatipc-derive/src/lib.rs b/libs/flatipc-derive/src/lib.rs index 1a8c7fb75..f10906dd3 100644 --- a/libs/flatipc-derive/src/lib.rs +++ b/libs/flatipc-derive/src/lib.rs @@ -4,12 +4,22 @@ use proc_macro::TokenStream; use quote::{format_ident, quote}; use syn::{parse_macro_input, spanned::Spanned, DeriveInput}; -fn ast_hash(ast: &syn::DeriveInput) -> u32 { +fn ast_hash(ast: &syn::DeriveInput) -> usize { use std::hash::{Hash, Hasher}; let mut hasher = std::collections::hash_map::DefaultHasher::new(); ast.hash(&mut hasher); let full_hash = hasher.finish(); - ((full_hash >> 32) as u32) ^ (full_hash as u32) + + #[cfg(target_pointer_width = "64")] + { + full_hash as usize + } + #[cfg(target_pointer_width = "32")] + { + (((full_hash >> 32) as u32) ^ (full_hash as u32)) as usize + } + #[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))] + compile_error!("Unsupported target_pointer_width"); } #[proc_macro_derive(IpcSafe)] @@ -52,11 +62,10 @@ fn derive_ipc_inner(ast: DeriveInput) -> Result generate_transmittable_checks_union(&ast, r#union)?, }; - let padded_version = generate_padded_version(&ast)?; - + let ipc_struct = generate_ipc_struct(&ast)?; Ok(quote! { #transmittable_checks - #padded_version + #ipc_struct }) } @@ -222,10 +231,10 @@ fn generate_transmittable_checks_union( }) } -fn generate_padded_version(ast: &DeriveInput) -> Result { +fn generate_ipc_struct(ast: &DeriveInput) -> Result { let visibility = ast.vis.clone(); let ident = ast.ident.clone(); - let padded_ident = format_ident!("Ipc{}", ast.ident); + let ipc_ident = format_ident!("Ipc{}", ast.ident); let ident_size = quote! { core::mem::size_of::< #ident >() }; let padded_size = quote! { (#ident_size + (4096 - 1)) & !(4096 - 1) }; let padding_size = quote! { #padded_size - #ident_size }; @@ -286,73 +295,101 @@ fn generate_padded_version(ast: &DeriveInput) -> Result(msg: &'a xous::MemoryMessage) -> Option<&'a Self> { + if msg.buf.len() < core::mem::size_of::< #ipc_ident >() { + return None; + } + let signature = msg.offset.map(|offset| offset.get()).unwrap_or_default(); + if signature != #hash { + return None; + } + unsafe { Some(&*(msg.buf.as_ptr() as *const #ipc_ident)) } + } + + fn from_memory_message_mut<'a>(msg: &'a mut xous::MemoryMessage) -> Option<&'a mut Self> { + if msg.buf.len() < core::mem::size_of::< #ipc_ident >() { + return None; + } + let signature = msg.offset.map(|offset| offset.get()).unwrap_or_default(); + if signature != #hash { + return None; + } + unsafe { Some(&mut *(msg.buf.as_mut_ptr() as *mut #ipc_ident)) } + } + } + } else { + quote! {} + }; + Ok(quote! { #[repr(C, align(4096))] - #visibility struct #padded_ident { + #visibility struct #ipc_ident { original: #ident, padding: [u8; #padding_size], } - impl core::ops::Deref for #padded_ident { + impl core::ops::Deref for #ipc_ident { type Target = #ident ; fn deref(&self) -> &Self::Target { &self.original } } - impl core::ops::DerefMut for #padded_ident { + impl core::ops::DerefMut for #ipc_ident { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.original } } impl flatipc::IntoIpc for #ident { - type IpcType = #padded_ident; + type IpcType = #ipc_ident; fn into_ipc(self) -> Self::IpcType { - #padded_ident { + #ipc_ident { original: self, padding: [0; #padding_size], } } } - impl flatipc::Ipc for #padded_ident { + unsafe impl flatipc::Ipc for #ipc_ident { type Original = #ident ; fn from_slice<'a>(data: &'a [u8], signature: usize) -> Option<&'a Self> { - if data.len() < core::mem::size_of::< #padded_ident >() { + if data.len() < core::mem::size_of::< #ipc_ident >() { return None; } - if signature as u32 != #hash { + if signature != #hash { return None; } - unsafe { Some(&*(data.as_ptr() as *const u8 as *const #padded_ident)) } + unsafe { Some(&*(data.as_ptr() as *const u8 as *const #ipc_ident)) } } unsafe fn from_buffer_unchecked<'a>(data: &'a [u8]) -> &'a Self { - &*(data.as_ptr() as *const u8 as *const #padded_ident) + &*(data.as_ptr() as *const u8 as *const #ipc_ident) } fn from_slice_mut<'a>(data: &'a mut [u8], signature: usize) -> Option<&'a mut Self> { - if data.len() < core::mem::size_of::< #padded_ident >() { + if data.len() < core::mem::size_of::< #ipc_ident >() { return None; } - if signature as u32 != #hash { + if signature != #hash { return None; } - unsafe { Some(&mut *(data.as_mut_ptr() as *mut u8 as *mut #padded_ident)) } + unsafe { Some(&mut *(data.as_mut_ptr() as *mut u8 as *mut #ipc_ident)) } } unsafe fn from_buffer_mut_unchecked<'a>(data: &'a mut [u8]) -> &'a mut Self { - unsafe { &mut *(data.as_mut_ptr() as *mut u8 as *mut #padded_ident) } + unsafe { &mut *(data.as_mut_ptr() as *mut u8 as *mut #ipc_ident) } } fn lend(&self, connection: flatipc::CID, opcode: usize) -> Result<(), flatipc::Error> { - let signature = self.signature() as usize; + let signature = self.signature(); let data = unsafe { core::slice::from_raw_parts( - self as *const #padded_ident as *const u8, - core::mem::size_of::< #padded_ident >(), + self as *const #ipc_ident as *const u8, + core::mem::size_of::< #ipc_ident >(), ) }; #lend @@ -360,11 +397,11 @@ fn generate_padded_version(ast: &DeriveInput) -> Result Result<(), flatipc::Error> { - let signature = self.signature() as usize; + let signature = self.signature(); let data = unsafe { core::slice::from_raw_parts( - self as *const #padded_ident as *const u8, - core::mem::size_of::< #padded_ident >(), + self as *const #ipc_ident as *const u8, + core::mem::size_of::< #ipc_ident >(), ) }; #try_lend @@ -372,10 +409,10 @@ fn generate_padded_version(ast: &DeriveInput) -> Result Result<(), flatipc::Error> { - let signature = self.signature() as usize; + let signature = self.signature(); let mut data = unsafe { core::slice::from_raw_parts_mut( - self as *mut #padded_ident as *mut u8, + self as *mut #ipc_ident as *mut u8, #padded_size, ) }; @@ -384,10 +421,10 @@ fn generate_padded_version(ast: &DeriveInput) -> Result Result<(), flatipc::Error> { - let signature = self.signature() as usize; + let signature = self.signature(); let mut data = unsafe { core::slice::from_raw_parts_mut( - self as *mut #padded_ident as *mut u8, + self as *mut #ipc_ident as *mut u8, #padded_size, ) }; @@ -407,9 +444,11 @@ fn generate_padded_version(ast: &DeriveInput) -> Result u32 { + fn signature(&self) -> usize { #hash } + + #memory_messages } }) } diff --git a/libs/flatipc/src/lib.rs b/libs/flatipc/src/lib.rs index f1d935189..52d3a9ff5 100644 --- a/libs/flatipc/src/lib.rs +++ b/libs/flatipc/src/lib.rs @@ -1,17 +1,129 @@ +//! Xous supports sending Messages from Clients to Servers. If a message is +//! a `MemoryMessage`, then the Server may respond by updating the buffer +//! with a response message and returning the buffer. +//! +//! A number of serialization options are available, ranging from sending +//! raw arrays of bytes all the way to sending full Protobuf messages. +//! +//! This crate takes a middle road and allows for sending rich Rust types +//! such as full enums and structs without doing extensive checks. This is +//! based on the theory that in normal operating systems, ABIs do not contain +//! any sort of verification, and it is undefined behaviour to send a malformed +//! request to a loaded module. +//! +//! An object can be made into an IPC object by implementing the `Ipc` trait. +//! The primary method of doing this is by adding `#[derive(flatipc::Ipc)]` +//! to the definition. Such an object may be included in both the Client and +//! the Server so that they share the same view of the object. +//! +//! Any object may be made into an IPC object provided it follows the following +//! requirements: +//! +//! - **The object is `#[repr(C)]`** - This is required to ensure the objecty +//! has a well-defined layout in memory. Other representations may shift +//! depending on optimizations. +//! - **The object only contains fields that are `IpcSafe`** - This trait is +//! implemented on primitive types that are able to be sent across an IPC +//! boundary. This includes all integers, floats, and booleans. It also +//! includes arrays of `IpcSafe` types, `Option` where `T` is `IpcSafe`, +//! and `Result` where `T` and `E` are `IpcSafe`. Pointers and +//! references are not `IpcSafe` and may not be used. +//! +//! When deriving `Ipc`, a new type will be created with the same name as +//! the original type prefixed with `Ipc`. For example, if you derive `Ipc` +//! on a type named `Foo`, the new type will be named `IpcFoo`. +//! +//! Objects that implement `Ipc` must be page-aligned and must be a full multiple +//! of a page size in length. This is to ensure that the object can be mapped +//! transparently between the Client and the Server without dragging any extra +//! memory along with it. +//! +//! `Ipc` objects implement `Deref` and `DerefMut` to the original object, so +//! they can be used in place of the original object in most cases by derefencing +//! the `Ipc` object. +//! +//! Because `String` and `Vec` contain pointers, they are not `IpcSafe`. As such, +//! replacements are made available in this crate that are `IpcSafe`. +//! +//!
+//! +//! # Example of a Derived Ipc Object +//! +//! ```ignore +//! #[repr(C)] +//! #[derive(flatipc::Ipc)] +//! pub struct Foo { +//! a: u32, +//! b: u64, +//! } +//! +//! // Connect to an example server +//! let conn = xous::connect(xous::SID::from_bytes(b"example---server").unwrap())?; +//! +//! // Construct our object +//! let foo = Foo { a: 1, b: 1234567890 }; +//! +//! // Create an IPC representation of the original object, consuming +//! // the original object in the process. +//! let mut foo_ipc = foo.into_ipc(); +//! +//! // Lend the object to the server with opcode 42. +//! foo_ipc.lend(conn, 42)?; +//! +//! // Note that we can still access the original object. +//! println!("a: {}", foo_ipc.a); +//! +//! // When we're done with the IPC object, we can get the original object back. +//! let foo = foo_ipc.into_original(); +//! ``` +//! +//! # Example of Server Usage +//! +//! ```ignore +//! // Ideally this comes from a common `API` file that both the +//! // client and server share. +//! #[repr(C)] +//! #[derive(flatipc::Ipc)] +//! pub struct Foo { +//! a: u32, +//! b: u64, +//! } +//! +//! let mut msg_opt = None; +//! let mut server = xous::create_server_with_sid(b"example---server").unwrap(); +//! loop { +//! let envelope = xous::reply_and_receive_next(server, &mut msg_opt).unwrap(); +//! let Some(msg) = msg_opt else { continue }; +//! +//! // Take the memory portion of the message, continuing if it's not a memory message. +//! let Some(msg_memory) = msg.memory_message() else { continue }; +//! +//! // Turn the `MemoryMessage` into an `IpcFoo` object. Note that this is the `Ipc`-prefixed +//! // version of the original object. If the object is not the correct type, `None` will be +//! // returned and the message will be returned to the sender in ghe next loop. +//! let Some(foo) = IpcFoo::from_memory_message(msg_slice, signature) else { continue }; +//! +//! // Do something with the object. +//! } +//! ``` + /// An object is Sendable if it is guaranteed to be flat and contains no pointers. /// This trait can be placed on objects that have invalid representations such as /// bools (which can only be 0 or 1) but it is up to the implementer to ensure that /// the correct object arrives on the other side. pub unsafe trait IpcSafe {} +// Enable calling this crate as `flatipc` in tests. extern crate self as flatipc; +// Allow doing `#[derive(flatipc::Ipc)]` instead of `#[derive(flatipc_derive::Ipc)]` pub use flatipc_derive::{Ipc, IpcSafe}; #[cfg(feature = "xous")] mod backend { pub use xous::Error; pub use xous::CID; } + #[cfg(not(feature = "xous"))] mod backend { pub mod mock; @@ -56,8 +168,12 @@ where { } -pub trait Ipc { - /// What this memory message is a representation of. +/// An object that can be sent across an IPC boundary, and can be reconstituted +/// on the other side without copying. An object with this trait must be page-aligned, +/// must be a multiple of the page size in length, and must not contain any pointers. +pub unsafe trait Ipc { + /// What this memory message is a representation of. This is used to turn + /// this object back into the original object. type Original; /// Create an Ipc variant from the original object. Succeeds only if @@ -87,7 +203,8 @@ pub trait Ipc { /// Consume the memory version and return the original object. fn into_original(self) -> Self::Original; - /// Lend the buffer to the specified server. + /// Lend the buffer to the specified server. The connection should already be + /// open and the server should be ready to receive the buffer. fn lend(&self, connection: CID, opcode: usize) -> Result<(), backend::Error>; /// Try to lend the buffer to the specified server, returning an error @@ -104,9 +221,23 @@ pub trait Ipc { /// Return the signature of this memory message. Useful for verifying /// that the correct message is being received. - fn signature(&self) -> u32; + fn signature(&self) -> usize; + + #[cfg(feature = "xous")] + /// Build an `Ipc` object from a `xous::MemoryMessage`. Verifies the signature and + /// returns `None` if there is no match. + fn from_memory_message<'a>(msg: &'a xous::MemoryMessage) -> Option<&'a Self>; + + #[cfg(feature = "xous")] + /// Build a mutable `Ipc` object from a mutable `xous::MemoryMessage`. Verifies the + /// signature and returns `None` if there is no match. The returned object has a + /// lifetime that's tied to the `MemoryMessage`. + fn from_memory_message_mut<'a>(msg: &'a mut xous::MemoryMessage) -> Option<&'a mut Self>; } +/// Objects that have `IntoIpc` may be turned into an object that can be passed +/// across an IPC barrier. This consumes the object and returns a new object that +/// may be dereferenced to the original object. pub trait IntoIpc { type IpcType; fn into_ipc(self) -> Self::IpcType; diff --git a/libs/flatipc/src/test/mock.rs b/libs/flatipc/src/test/mock.rs new file mode 100644 index 000000000..7cbb97da4 --- /dev/null +++ b/libs/flatipc/src/test/mock.rs @@ -0,0 +1,58 @@ +use std::sync::{LazyLock, Mutex}; + +pub struct Server { + lend: Box (usize, usize)>, + lend_mut: Box (usize, usize)>, +} + +impl Server { + pub fn new( + lend: Box (usize, usize)>, + lend_mut: Box (usize, usize)>, + ) -> Self { + Server { lend, lend_mut } + } +} + +pub struct IpcMachine { + servers: Vec, +} + +pub(crate) static IPC_MACHINE: LazyLock> = + LazyLock::new(|| Mutex::new(IpcMachine::new())); + +impl IpcMachine { + fn new() -> Self { + IpcMachine { + servers: Vec::new(), + } + } + + pub fn add_server(&mut self, server: Server) -> usize { + let server_id = self.servers.len(); + self.servers.push(server); + server_id + } + + pub fn lend( + &self, + server_id: usize, + opcode: usize, + a: usize, + b: usize, + data: &[u8], + ) -> (usize, usize) { + (self.servers[server_id].lend)(opcode, a, b, data) + } + + pub fn lend_mut( + &self, + server_id: usize, + opcode: usize, + a: usize, + b: usize, + data: &mut [u8], + ) -> (usize, usize) { + (self.servers[server_id].lend_mut)(opcode, a, b, data) + } +}