diff --git a/score/mw/com/impl/rust/com-api/com-api-concept/error.rs b/score/mw/com/impl/rust/com-api/com-api-concept/error.rs index ef7c2fd93..3ec970d5a 100644 --- a/score/mw/com/impl/rust/com-api/com-api-concept/error.rs +++ b/score/mw/com/impl/rust/com-api/com-api-concept/error.rs @@ -86,6 +86,8 @@ pub enum EventFailedReason { SendingDataFailed, #[error("Event not available for subscription, possibly due to missing event type or incompatible service")] EventNotAvailable, + #[error("Sample count out of bounds, expected at most {max}, but got {requested}")] + MaxSampleOutOfBounds { max: usize, requested: usize }, } /// Error enumeration for different failure cases in the Consumer/Producer/Runtime APIs. diff --git a/score/mw/com/impl/rust/com-api/com-api-ffi-lola/BUILD b/score/mw/com/impl/rust/com-api/com-api-ffi-lola/BUILD index 92a7ad922..1b376b759 100644 --- a/score/mw/com/impl/rust/com-api/com-api-ffi-lola/BUILD +++ b/score/mw/com/impl/rust/com-api/com-api-ffi-lola/BUILD @@ -44,3 +44,26 @@ rust_library( "//score/mw/com/impl/rust:mw_com", ], ) + +rust_library( + name = "bridge_ffi_mock", + srcs = ["bridge_ffi_mock.rs"], + visibility = [ + "//score/mw/com:__subpackages__", + ], + deps = [ + ":bridge_ffi_rs", + "//score/mw/com/impl/plumbing/rust:sample_allocatee_ptr_rs", + ], +) + +rust_library( + name = "bridge_ffi_lola", + srcs = ["bridge_ffi_lola.rs"], + visibility = [ + "//score/mw/com:__subpackages__", + ], + deps = [ + ":bridge_ffi_rs", + ], +) diff --git a/score/mw/com/impl/rust/com-api/com-api-ffi-lola/bridge_ffi.rs b/score/mw/com/impl/rust/com-api/com-api-ffi-lola/bridge_ffi.rs index 243ba487f..6d14cb541 100644 --- a/score/mw/com/impl/rust/com-api/com-api-ffi-lola/bridge_ffi.rs +++ b/score/mw/com/impl/rust/com-api/com-api-ffi-lola/bridge_ffi.rs @@ -67,6 +67,7 @@ // - Enables efficient zero-copy string passing for interface_id, event_id, and type_name parameters use core::fmt::{Debug, Formatter}; +use core::marker::Unpin; use std::ffi::c_char; /// Opaque C++ void* pointer wrapper @@ -82,6 +83,184 @@ pub use mw_com::proxy::ProxyEventBase; pub use mw_com::proxy::ProxyWrapperClass; pub use mw_com::InstanceSpecifier; +/// FFIBridge trait defines the interface for FFI interactions between Rust COM-API and C++ Lola runtime. +/// This trait abstracts the FFI calls and allows for different implementations +/// e.g., Lola bridge, mock bridge for testing. +/// All this trait bound required because runtime types which use this bridge has these bounds, +/// an because of that this bridge also need to be bound by these traits to be used in runtime implementation. +pub trait FFIBridge: Send + Sync + Clone + Debug + 'static + Unpin { + /// # Safety + /// `event_ptr` must be a valid, non-null pointer to a `SkeletonEventBase` obtained from + /// `get_event_from_skeleton`. `allocatee_ptr` must point to valid memory large enough to + /// hold the allocatee instance for `event_type`. + unsafe fn get_allocatee_ptr( + event_ptr: *mut SkeletonEventBase, + allocatee_ptr: *mut std::ffi::c_void, + event_type: &str, + ) -> bool; + + /// # Safety + /// `allocatee_ptr` must be a valid pointer previously returned by `get_allocatee_ptr` + /// with the same `type_name`, and must not be used after this call. + unsafe fn delete_allocatee_ptr(allocatee_ptr: *mut std::ffi::c_void, type_name: &str); + + /// # Safety + /// `allocatee_ptr` must be a valid pointer to a `SampleAllocateePtr` of the specified + /// `type_name`, previously obtained from `get_allocatee_ptr`. + unsafe fn get_allocatee_data_ptr( + allocatee_ptr: *const std::ffi::c_void, + type_name: &str, + ) -> *mut std::ffi::c_void; + + /// # Safety + /// `event_ptr` must be a valid pointer to a `SkeletonEventBase` obtained from + /// `get_event_from_skeleton`. `allocatee_ptr` must be a valid `SampleAllocateePtr` + /// whose type matches `event_type`. + unsafe fn skeleton_event_send_sample_allocatee( + event_ptr: *mut SkeletonEventBase, + event_type: &str, + allocatee_ptr: *const std::ffi::c_void, + ) -> bool; + + /// # Safety + /// `sample_ptr` must be a valid pointer to a `SamplePtr` of the specified `type_name`. + unsafe fn sample_ptr_get( + sample_ptr: *const std::ffi::c_void, + type_name: &str, + ) -> *const std::ffi::c_void; + + /// # Safety + /// `sample_ptr` must be a valid pointer to a `SamplePtr` of the specified `type_name`, + /// and must not be used after this call. + unsafe fn sample_ptr_delete(sample_ptr: *mut std::ffi::c_void, type_name: &str); + + /// # Safety + /// `skeleton_ptr` must be a valid, non-null pointer to a `SkeletonBase` previously created + /// with `create_skeleton` and not yet destroyed. + unsafe fn skeleton_offer_service(skeleton_ptr: *mut SkeletonBase) -> bool; + + /// # Safety + /// `skeleton_ptr` must be a valid, non-null pointer to a `SkeletonBase` previously created + /// with `create_skeleton` and not yet destroyed. + unsafe fn skeleton_stop_offer_service(skeleton_ptr: *mut SkeletonBase); + + /// # Safety + /// `handle_ptr` must be a valid reference to a `HandleType`. The returned pointer must + /// eventually be destroyed via `destroy_proxy`. + unsafe fn create_proxy(interface_id: &str, handle_ptr: &HandleType) -> *mut ProxyBase; + + /// # Safety + /// `instance_spec` must be a valid pointer to a `NativeInstanceSpecifier`. The returned + /// pointer must eventually be destroyed via `destroy_skeleton`. + unsafe fn create_skeleton( + interface_id: &str, + instance_spec: *const NativeInstanceSpecifier, + ) -> *mut SkeletonBase; + + /// # Safety + /// `proxy_ptr` must be a valid pointer returned from `create_proxy` that has not been + /// destroyed yet. No other references to this proxy may be used after this call. + unsafe fn destroy_proxy(proxy_ptr: *mut ProxyBase); + + /// # Safety + /// `skeleton_ptr` must be a valid pointer returned from `create_skeleton` that has not + /// been destroyed yet. No other references to this skeleton may be used after this call. + unsafe fn destroy_skeleton(skeleton_ptr: *mut SkeletonBase); + + /// # Safety + /// `proxy_ptr` must be a valid pointer to a `ProxyBase` previously created with + /// `create_proxy`. The returned pointer remains valid only as long as the proxy is alive. + unsafe fn get_event_from_proxy( + proxy_ptr: *mut ProxyBase, + interface_id: &str, + event_id: &str, + ) -> *mut ProxyEventBase; + + /// # Safety + /// `skeleton_ptr` must be a valid pointer to a `SkeletonBase` previously created with + /// `create_skeleton`. The returned pointer remains valid only as long as the skeleton is alive. + unsafe fn get_event_from_skeleton( + skeleton_ptr: *mut SkeletonBase, + interface_id: &str, + event_id: &str, + ) -> *mut SkeletonEventBase; + + /// # Safety + /// `event_ptr` must be a valid pointer to a `ProxyEventBase` obtained from + /// `get_event_from_proxy`, and the event must have been subscribed via `subscribe_to_event`. + /// `callback` must be a valid `FatPtr` referencing a callable compatible with `event_type`. + unsafe fn get_samples_from_event( + event_ptr: *mut ProxyEventBase, + event_type: &str, + callback: &FatPtr, + max_samples: u32, + ) -> u32; + + /// # Safety + /// `event_ptr` must be a valid pointer to a `SkeletonEventBase` obtained from + /// `get_event_from_skeleton`. `data_ptr` must point to valid data whose type matches + /// `event_type` and must remain valid for the duration of this call. + unsafe fn skeleton_send_event( + event_ptr: *mut SkeletonEventBase, + event_type: &str, + data_ptr: *const std::ffi::c_void, + ) -> bool; + + /// # Safety + /// `event_ptr` must be a valid pointer to a `ProxyEventBase` obtained from + /// `get_event_from_proxy`. Must be called before `get_samples_from_event`. + unsafe fn subscribe_to_event(event_ptr: *mut ProxyEventBase, max_sample_count: u32) -> bool; + + /// # Safety + /// `event_ptr` must be a valid pointer to a `ProxyEventBase` obtained from + /// `get_event_from_proxy`. Must be called only when no `SamplePtr` for this event is + /// still held by the caller. + unsafe fn unsubscribe_to_event(event_ptr: *mut ProxyEventBase); + + /// # Safety + /// `proxy_event_ptr` must be a valid pointer to a `ProxyEventBase` obtained from + /// `get_event_from_proxy`. `handler` must be a valid `FatPtr` referencing a callable + /// compatible with the receive-handler signature expected by the implementation. + unsafe fn set_event_receive_handler( + proxy_event_ptr: *mut ProxyEventBase, + handler: &FatPtr, + event_type: &str, + ) -> bool; + + /// # Safety + /// `proxy_event_ptr` must be a valid pointer to a `ProxyEventBase` obtained from + /// `get_event_from_proxy`. `event_type` must match the type used when the handler was set. + unsafe fn clear_event_receive_handler(proxy_event_ptr: *mut ProxyEventBase, event_type: &str); + + /// # Safety + /// `callback` must be a valid `FatPtr` referencing a callable compatible with the + /// find-service callback signature. `instance_spec` must be a valid `InstanceSpecifier`. + /// The returned handle must eventually be passed to `stop_find_service`. + unsafe fn start_find_service( + callback: &FatPtr, + instance_spec: InstanceSpecifier, + ) -> *mut FindServiceHandle; + + /// # Safety + /// `handle` must be a valid pointer returned from `start_find_service` that has not + /// been stopped yet. The handle must not be used after this call. + unsafe fn stop_find_service(handle: *mut FindServiceHandle); + + /// # Safety + /// `container` must be a valid reference to a `HandleContainer` that was produced by the + /// bridge (e.g. from `start_find_service` callback). Calling this on a sentinel mock pointer + /// is only safe when the mock implementation ignores the inner pointer. + unsafe fn handle_container_size(container: &HandleContainer) -> usize; + + /// # Safety + /// Same as `handle_container_size`. `index` need not be in-bounds; the method must return + /// `None` when `index >= handle_container_size(container)`. + unsafe fn handle_container_get_at( + container: &HandleContainer, + index: usize, + ) -> Option<&HandleType>; +} + /// Opaque proxy base struct #[repr(C)] pub struct ProxyBase { @@ -164,755 +343,3 @@ impl From<&'_ str> for StringView { } } } - -/// Rust closure invocation for C++ callbacks -/// -/// This function is called by C++ to invoke a Rust closure with a sample pointer. -/// The closure is reconstructed from the FatPtr trait object. -/// -/// # Safety -/// - `ptr` must point to a valid FatPtr -/// - `sample_ptr` must point to valid placement-new storage containing SamplePtr -#[no_mangle] -pub unsafe extern "C" fn mw_com_impl_call_dyn_ref_fnmut_sample( - ptr: *const FatPtr, - sample_ptr: *mut std::ffi::c_void, -) { - if ptr.is_null() || sample_ptr.is_null() { - return; - } - - // Reconstruct the closure from FatPtr - // SAFETY: ptr is guaranteed to point to a valid FatPtr that was created by the C++ side - // and represents a valid dynamic trait object (dyn FnMut). The transmute is safe because - // FatPtr is a binary representation of a trait object's fat pointer (data + vtable). - // The caller must ensure the lifetime of the closure outlives this call. - let callable: &mut dyn FnMut(*mut std::ffi::c_void) = std::mem::transmute(*ptr); - - // Invoke the closure with the void* sample pointer - callable(sample_ptr); -} - -/// Rust closure invocation for C++ callbacks for FindService -/// This function is called by C++ to invoke a Rust closure for finding services, -/// passing a vector of service handles. -/// This function invokes a Rust closure that takes a HandleContainer and a FindServiceHandle. -/// # Safety -/// - `ptr` must point to a valid FatPtr representing a closure of type -/// FnMut(HandleContainer, NativeFindServiceHandle). -/// - `service_handles` must point to valid NativeHandleContainer data that can be safely wrapped -/// in a HandleContainer. -/// - `find_service_handle` must point to valid FindServiceHandle data that can be safely wrapped -/// in a NativeFindServiceHandle. -#[no_mangle] -pub unsafe extern "C" fn mw_com_impl_call_dyn_ref_fnmut_find_service( - ptr: *const FatPtr, - service_handles: *mut NativeHandleContainer, - find_service_handle: *mut FindServiceHandle, -) { - if ptr.is_null() || service_handles.is_null() { - return; - } - - // Reconstruct the closure from FatPtr - let callable: &mut dyn FnMut(HandleContainer, NativeFindServiceHandle) = - std::mem::transmute(*ptr); - - // Invoke with correct types - callable( - HandleContainer::new(service_handles), - NativeFindServiceHandle { - handle: find_service_handle, - }, - ); -} - -// FFI declarations for C++ functions implemented in registry_bridge_macro.cpp -extern "C" { - - /// Subscribe to event to start receiving samples - /// - /// # Arguments - /// * `event_ptr` - Opaque event pointer - /// * `max_sample_count` - Maximum number of samples to buffer - /// - /// # Returns - /// true if subscription successful, false otherwise - fn mw_com_proxy_event_subscribe(event_ptr: *mut ProxyEventBase, max_sample_count: u32) -> bool; - - /// Get event pointer from proxy by event name - /// - /// # Arguments - /// * `proxy_ptr` - Opaque proxy pointer - /// * `interface_id` - UTF-8 C string of interface UID - /// * `event_id` - UTF-8 C string of event name - /// - /// # Returns - /// Opaque event pointer, or nullptr if event not found - fn mw_com_get_event_from_proxy( - proxy_ptr: *mut ProxyBase, - interface_id: StringView, - event_id: StringView, - ) -> *mut ProxyEventBase; - - /// Get event pointer from skeleton by event name - /// - /// # Arguments - /// * `skeleton_ptr` - Opaque skeleton pointer - /// * `interface_id` - UTF-8 C string of interface UID - /// * `event_id` - UTF-8 C string of event name - /// - /// # Returns - /// Opaque event pointer, or nullptr if event not found - fn mw_com_get_event_from_skeleton( - skeleton_ptr: *mut SkeletonBase, - interface_id: StringView, - event_id: StringView, - ) -> *mut SkeletonEventBase; - - /// Get new samples from an event - /// - /// # Arguments - /// * `event_ptr` - Opaque event pointer - /// * `event_type` - Event type name string - /// * `callback` - FatPtr to callback function - /// * `max_samples` - Maximum number of samples to retrieve - /// - /// # Returns - /// Number of samples retrieved - fn mw_com_type_registry_get_samples_from_event( - event_ptr: *mut ProxyEventBase, - event_type: StringView, - callback: *const FatPtr, - max_samples: u32, - ) -> u32; - - /// Send data via skeleton event - /// - /// # Arguments - /// * `event_ptr` - Opaque skeleton event pointer - /// * `event_type` - Event type name string - /// * `data_ptr` - Pointer to event data - /// - /// # Returns - /// None (void function) - fn mw_com_skeleton_send_event( - event_ptr: *mut SkeletonEventBase, - event_type: StringView, - data_ptr: CVoidPtr, - ) -> bool; - - /// Create proxy by UID and handle - /// - /// # Arguments - /// * `interface_id` - UTF-8 C string of interface UID - /// * `handle_ptr` - Opaque handle pointer - /// - /// # Returns - /// Opaque proxy pointer, or nullptr if creation failed - fn mw_com_create_proxy(interface_id: StringView, handle_ptr: &HandleType) -> *mut ProxyBase; - - /// Create skeleton by UID and instance specifier - /// - /// # Arguments - /// * `interface_id` - UTF-8 C string of interface UID - /// * `instance_spec` - Opaque instance specifier pointer - /// - /// # Returns - /// Opaque skeleton pointer, or nullptr if creation failed - fn mw_com_create_skeleton( - interface_id: StringView, - instance_spec: *const NativeInstanceSpecifier, - ) -> *mut SkeletonBase; - - /// Destroy proxy - /// - /// # Arguments - /// * `proxy_ptr` - Opaque proxy pointer - /// - /// # Returns - /// None (void function) - fn mw_com_destroy_proxy(proxy_ptr: *mut ProxyBase); - - /// Destroy skeleton - /// - /// # Arguments - /// * `skeleton_ptr` - Opaque skeleton pointer - /// - /// # Returns - /// None (void function) - fn mw_com_destroy_skeleton(skeleton_ptr: *mut SkeletonBase); - - /// Offer service via skeleton - /// - /// # Arguments - /// * `skeleton_ptr` - Opaque skeleton pointer - /// - /// # Returns - /// true if offer successful, false otherwise - fn mw_com_skeleton_offer_service(skeleton_ptr: *mut SkeletonBase) -> bool; - - /// Stop offering service via skeleton - /// - /// # Arguments - /// * `skeleton_ptr` - Opaque skeleton pointer - /// - /// # Returns - /// None (void function) - fn mw_com_skeleton_stop_offer_service(skeleton_ptr: *mut SkeletonBase); - - /// Get sample data pointer from SamplePtr - /// - /// # Arguments - /// * `sample_ptr` - Opaque sample pointer - /// * `type_name` - Type name string - /// - /// # Returns - /// Pointer to sample data, or nullptr if type mismatch - fn mw_com_get_sample_ptr( - sample_ptr: *const std::ffi::c_void, - type_name: StringView, - ) -> *const std::ffi::c_void; - - /// Delete sample pointer of SamplePtr' - /// - /// # Arguments - /// * `sample_ptr` - Opaque sample pointer - /// * `type_name` - Type name string - fn mw_com_delete_sample_ptr(sample_ptr: *mut std::ffi::c_void, type_name: StringView); - - /// Get allocatee pointer from skeleton event of specific type - /// - /// # Arguments - /// * `event_ptr` - Opaque skeleton event pointer - /// * `allocatee_ptr` - Pointer to pre-allocated memory for allocatee - /// * `event_type` - Type name string - /// - /// # Returns - /// True if allocatee pointer was retrieved successfully, false otherwise - fn mw_com_get_allocatee_ptr( - event_ptr: *mut SkeletonEventBase, - allocatee_ptr: *mut std::ffi::c_void, - event_type: StringView, - ) -> bool; - - /// Delete allocatee pointer of SampleAllocateePtr - /// - /// # Arguments - /// * `allocatee_ptr` - Pointer to SampleAllocateePtr - /// * `type_name` - Type name string - fn mw_com_delete_allocatee_ptr(allocatee_ptr: *mut std::ffi::c_void, type_name: StringView); - - /// Get allocatee data pointer from allocatee of specific type - /// - /// # Arguments - /// * `allocatee_ptr` - Pointer to SampleAllocateePtr - /// * `type_name` - Type name string - /// - /// # Returns - /// Pointer to allocatee data, or nullptr if type mismatch - fn mw_com_get_allocatee_data_ptr( - allocatee_ptr: *const std::ffi::c_void, - type_name: StringView, - ) -> *mut std::ffi::c_void; - - /// Send event via skeleton using allocatee pointer of specific type - /// - /// # Arguments - /// * `event_ptr` - Opaque skeleton event pointer - /// * `event_type` - Type name string - /// * `allocatee_ptr` - Pointer to SampleAllocateePtr - /// - /// # Returns - /// True if event was sent successfully, false otherwise - fn mw_com_skeleton_send_event_allocatee( - event_ptr: *mut SkeletonEventBase, - event_type: StringView, - allocatee_ptr: *const std::ffi::c_void, - ) -> bool; - - /// Set event receive handler for proxy event - /// - /// # Arguments - /// * `proxy_event_ptr` - Opaque proxy event pointer - /// * `handler` - FatPtr to event receive handler function - /// - /// # Returns - /// True if handler was set successfully, false otherwise - fn mw_com_proxy_set_event_receive_handler( - proxy_event_ptr: *mut ProxyEventBase, - handler: *const FatPtr, - event_type: StringView, - ) -> bool; - - /// Clear event receive handler for proxy event - /// - /// # Arguments - /// * `proxy_event_ptr` - Opaque proxy event pointer - fn mw_com_proxy_clear_event_receive_handler( - proxy_event_ptr: *mut ProxyEventBase, - event_type: StringView, - ); - - /// Start finding services with callback for results - /// - /// # Arguments - /// * `callback` - FatPtr to callback function that will be called with discovery results - /// * `instance_spec` - Pointer to instance specifier for the service to find - /// - /// # Returns - /// Opaque pointer to FindServiceHandle which can be used to stop the find operation - fn mw_com_start_find_service( - callback: *const FatPtr, - instance_spec: *const NativeInstanceSpecifier, - ) -> *mut FindServiceHandle; - - /// Stop finding services using the provided FindServiceHandle - /// - /// # Arguments - /// * `handle` - Opaque pointer to FindServiceHandle returned by mw_com_start_find_service - fn mw_com_stop_find_service(handle: *mut FindServiceHandle); - - /// Unsubscribe from event to stop receiving samples - /// - /// # Arguments - /// * `event_ptr` - Opaque event pointer - fn mw_com_proxy_event_unsubscribe(event_ptr: *mut ProxyEventBase); -} - -/// Get allocatee pointer from skeleton event of specific type -/// -/// # Arguments -/// * `event_ptr` - Opaque skeleton event pointer -/// * `allocatee_ptr` - Pointer to pre-allocated memory for allocatee -/// * `event_type` - Type name string -/// -/// # Returns -/// True if allocatee pointer was retrieved successfully, false otherwise -/// -/// # Safety -/// event_ptr must point to a valid SkeletonEventBase, -/// allocatee_ptr must point to valid memory for the allocatee, -/// and event_type must be a valid UTF-8 string representing the event type. -pub unsafe fn get_allocatee_ptr( - event_ptr: *mut SkeletonEventBase, - allocatee_ptr: *mut std::ffi::c_void, - event_type: &str, -) -> bool { - // SAFETY: event_ptr is valid which is created using create_skeleton() - // and allocatee_ptr has enough memory allocated for the construction of allocatee instance. - let event_type = StringView::from(event_type); - mw_com_get_allocatee_ptr(event_ptr, allocatee_ptr, event_type) -} - -/// Delete allocatee pointer of SampleAllocateePtr -/// -/// # Arguments -/// * `allocatee_ptr` - Pointer to SampleAllocateePtr -/// * `type_name` - Type name string -/// -/// # Safety -/// allocatee_ptr must point to a valid SampleAllocateePtr of the specified type. -pub unsafe fn delete_allocatee_ptr(allocatee_ptr: *mut std::ffi::c_void, type_name: &str) { - // SAFETY: allocatee_ptr is valid which is created using get_allocatee_ptr() and - // type_name is constructed using static str. - let type_name = StringView::from(type_name); - mw_com_delete_allocatee_ptr(allocatee_ptr, type_name); -} - -/// Get allocatee data pointer from allocatee of specific type -/// -/// # Arguments -/// * `allocatee_ptr` - Pointer to SampleAllocateePtr -/// * `type_name` - Type name string -/// -/// # Returns -/// Pointer to allocatee data, or nullptr if type mismatch -/// -/// # Safety -/// allocatee_ptr must point to a valid SampleAllocateePtr of the specified type -pub unsafe fn get_allocatee_data_ptr( - allocatee_ptr: *const std::ffi::c_void, - type_name: &str, -) -> *mut std::ffi::c_void { - // SAFETY: allocatee_ptr is valid which is created using get_allocatee_ptr() and - // type_name is constructed using static str. - let type_name = StringView::from(type_name); - mw_com_get_allocatee_data_ptr(allocatee_ptr, type_name) -} - -/// Send event via skeleton using allocatee pointer of specific type -/// -/// # Arguments -/// * `event_ptr` - Opaque skeleton event pointer -/// * `event_type` - Type name string -/// * `allocatee_ptr` - Pointer to SampleAllocateePtr -/// -/// # Returns -/// True if event was sent successfully, false otherwise -/// -/// # Safety -/// event_ptr must point to a valid SkeletonEventBase, -/// allocatee_ptr must point to a valid SampleAllocateePtr, -/// and event_type must be a valid UTF-8 string representing the event type. -pub unsafe fn skeleton_event_send_sample_allocatee( - event_ptr: *mut SkeletonEventBase, - event_type: &str, - allocatee_ptr: *const std::ffi::c_void, -) -> bool { - // SAFETY: event_ptr is valid which is created using create_skeleton() and allocatee_ptr is - // valid which is created using get_allocatee_ptr(). - let event_type = StringView::from(event_type); - mw_com_skeleton_send_event_allocatee(event_ptr, event_type, allocatee_ptr) -} - -/// Get SamplePtr data pointer -/// -/// # Arguments -/// * `sample_ptr` - Opaque sample pointer -/// * `type_name` - Type name string -/// -/// # Returns -/// Pointer to sample data, or nullptr if type mismatch -/// # Safety -/// sample_ptr must point to a valid SamplePtr of the specified type. -pub unsafe fn sample_ptr_get( - sample_ptr: *const std::ffi::c_void, - type_name: &str, -) -> *const std::ffi::c_void { - // SAFETY: sample_ptr is guaranteed to be valid per the caller's contract. - // The C++ implementation handles type checking and data retrieval safely. - let type_name = StringView::from(type_name); - mw_com_get_sample_ptr(sample_ptr, type_name) -} - -/// Delete SamplePtr -/// -/// # Arguments -/// * `sample_ptr` - Opaque sample pointer -/// * `type_name` - Type name string -/// # Safety -/// sample_ptr must point to a valid SamplePtr of the specified type. -pub unsafe fn sample_ptr_delete(sample_ptr: *mut std::ffi::c_void, type_name: &str) { - // SAFETY: sample_ptr is guaranteed to be valid per the caller's contract. - // The C++ implementation handles type checking and deletion safely. - let type_name = StringView::from(type_name); - mw_com_delete_sample_ptr(sample_ptr, type_name); -} - -/// Unsafe wrapper around mw_com_skeleton_offer_service -/// -/// # Arguments -/// * `skeleton_ptr` - Opaque skeleton pointer -/// -/// # Returns -/// true if offer was successful, false otherwise -/// -/// # Safety -/// skeleton_ptr must be a valid pointer to a SkeletonBase previously created -/// with create_skeleton(). -/// The pointer must remain valid for the duration of this call. -pub unsafe fn skeleton_offer_service(skeleton_ptr: *mut SkeletonBase) -> bool { - // SAFETY: skeleton_ptr is guaranteed to be valid per the caller's contract. - // The C++ implementation handles service offering safely. - mw_com_skeleton_offer_service(skeleton_ptr) -} - -/// Unsafe wrapper around mw_com_skeleton_stop_offer_service -/// -/// # Arguments -/// * `skeleton_ptr` - Opaque skeleton pointer -/// -/// # Safety -/// skeleton_ptr must be a valid pointer to a SkeletonBase previously created -/// with create_skeleton(). -/// The pointer must remain valid for the duration of this call. -pub unsafe fn skeleton_stop_offer_service(skeleton_ptr: *mut SkeletonBase) { - // SAFETY: skeleton_ptr is guaranteed to be valid per the caller's contract. - // The C++ implementation handles stopping the service safely. - mw_com_skeleton_stop_offer_service(skeleton_ptr); -} - -/// Unsafe wrapper around mw_com_create_proxy -/// -/// # Arguments -/// * `interface_id` - Interface UID string -/// * `handle_ptr` - Opaque handle pointer -/// -/// # Returns -/// Opaque proxy pointer, or nullptr if creation failed -/// -/// # Safety -/// handle_ptr must be a valid reference to a HandleType. -/// The returned pointer must eventually be destroyed via destroy_proxy(). -pub unsafe fn create_proxy(interface_id: &str, handle_ptr: &HandleType) -> *mut ProxyBase { - // SAFETY: interface_id is a valid string reference and handle_ptr is guaranteed to be valid - // per the caller's contract. - // The C++ implementation creates and returns a valid proxy pointer or nullptr on failure. - let c_uid = StringView::from(interface_id); - mw_com_create_proxy(c_uid, handle_ptr) -} - -/// Unsafe wrapper around mw_com_create_skeleton -/// -/// # Arguments -/// * `interface_id` - Interface UID string -/// * `instance_spec` - Opaque instance specifier pointer -/// -/// # Returns -/// Opaque skeleton pointer, or nullptr if creation failed -/// -/// # Safety -/// instance_spec must be a valid NativeInstanceSpecifier. -/// The returned pointer must eventually be destroyed via destroy_skeleton(). -pub unsafe fn create_skeleton( - interface_id: &str, - instance_spec: *const NativeInstanceSpecifier, -) -> *mut SkeletonBase { - // SAFETY: interface_id is a valid string reference and instance_spec is guaranteed to be - // valid per the caller's contract. - // The C++ implementation creates and returns a valid skeleton pointer or nullptr on failure. - let c_uid = StringView::from(interface_id); - mw_com_create_skeleton(c_uid, instance_spec) -} - -/// Unsafe wrapper around mw_com_destroy_proxy -/// -/// # Arguments -/// * `proxy_ptr` - Opaque proxy pointer to destroy -/// -/// # Safety -/// proxy_ptr must be a valid pointer returned from create_proxy() that has not been destroyed yet. -/// The caller must ensure no other references to this proxy exist after calling this function. -pub unsafe fn destroy_proxy(proxy_ptr: *mut ProxyBase) { - // SAFETY: proxy_ptr is guaranteed to be valid per the caller's contract. - // The C++ implementation safely deallocates the proxy. - mw_com_destroy_proxy(proxy_ptr); -} - -/// Unsafe wrapper around mw_com_destroy_skeleton -/// -/// # Arguments -/// * `skeleton_ptr` - Opaque skeleton pointer to destroy -/// -/// # Safety -/// skeleton_ptr must be a valid pointer returned from create_skeleton()- -/// that has not been destroyed yet. -/// The caller must ensure no other references to this skeleton exist after calling this function. -pub unsafe fn destroy_skeleton(skeleton_ptr: *mut SkeletonBase) { - // SAFETY: skeleton_ptr is guaranteed to be valid per the caller's contract. - // The C++ implementation safely deallocates the skeleton. - mw_com_destroy_skeleton(skeleton_ptr); -} - -/// Unsafe wrapper around mw_com_get_event_from_proxy -/// -/// # Arguments -/// * `proxy_ptr` - Opaque proxy pointer -/// * `interface_id` - Interface UID string -/// * `event_id` - Event name string -/// -/// # Returns -/// Opaque event pointer, or nullptr if not found -/// -/// # Safety -/// proxy_ptr must be a valid pointer to a ProxyBase previously created with create_proxy(). -/// The returned pointer remains valid only as long as the proxy remains alive. -pub unsafe fn get_event_from_proxy( - proxy_ptr: *mut ProxyBase, - interface_id: &str, - event_id: &str, -) -> *mut ProxyEventBase { - // SAFETY: proxy_ptr is guaranteed to be valid per the caller's contract. - // The C++ implementation returns a valid pointer or nullptr if the event is not found. - let c_id = StringView::from(interface_id); - let c_name = StringView::from(event_id); - mw_com_get_event_from_proxy(proxy_ptr, c_id, c_name) -} - -/// Unsafe wrapper around mw_com_get_event_from_skeleton -/// -/// # Arguments -/// * `skeleton_ptr` - Opaque skeleton pointer -/// * `interface_id` - Interface UID string -/// * `event_id` - Event name string -/// -/// # Returns -/// Opaque event pointer, or nullptr if not found -/// -/// # Safety -/// skeleton_ptr must be a valid pointer to a SkeletonBase previously created -/// with create_skeleton(). -/// The returned pointer remains valid only as long as the skeleton remains alive. -pub unsafe fn get_event_from_skeleton( - skeleton_ptr: *mut SkeletonBase, - interface_id: &str, - event_id: &str, -) -> *mut SkeletonEventBase { - // SAFETY: skeleton_ptr is guaranteed to be valid per the caller's contract. - // The C++ implementation returns a valid pointer or nullptr if the event is not found. - let c_id = StringView::from(interface_id); - let c_name = StringView::from(event_id); - mw_com_get_event_from_skeleton(skeleton_ptr, c_id, c_name) -} - -/// Unsafe wrapper around mw_com_get_samples_from_event -/// -/// # Arguments -/// * `event_ptr` - Opaque event pointer -/// * `event_type` - Event type name string -/// * `callback` - FatPtr to callback function -/// * `max_samples` - Maximum number of samples to retrieve -/// -/// # Returns -/// Number of samples retrieved, or u32::MAX on error -/// -/// # Safety -/// event_ptr must be a valid pointer to a ProxyEventBase previously obtained -/// from get_event_from_proxy(). -/// callback must be a valid FatPtr referencing a callable compatible with the event type. -/// The event must have been subscribed to via subscribe_to_event() before calling this function. -pub unsafe fn get_samples_from_event( - event_ptr: *mut ProxyEventBase, - event_type: &str, - callback: &FatPtr, - max_samples: u32, -) -> u32 { - // SAFETY: event_ptr, callback, and event_type are guaranteed - // to be valid per the caller's contract. - // The C++ implementation handles sample retrieval and callback invocation safely. - let c_name = StringView::from(event_type); - mw_com_type_registry_get_samples_from_event(event_ptr, c_name, callback, max_samples) -} - -/// Unsafe wrapper around mw_com_skeleton_send_event -/// -/// # Arguments -/// * `event_ptr` - Opaque skeleton event pointer -/// * `event_type` - Event type name string -/// * `data_ptr` - Pointer to event data of the matching type -/// -/// # Safety -/// event_ptr must be a valid pointer to a SkeletonEventBase previously obtained -/// from get_event_from_skeleton(). -/// data_ptr must point to valid data whose type matches the event_type. -/// The lifetime of the data must extend through this function call. -pub unsafe fn skeleton_send_event( - event_ptr: *mut SkeletonEventBase, - event_type: &str, - data_ptr: *const std::ffi::c_void, -) -> bool { - // SAFETY: event_ptr and data_ptr are guaranteed to be valid per the caller's contract. - // The C++ implementation handles type matching and data sending safely. - let c_name = StringView::from(event_type); - mw_com_skeleton_send_event(event_ptr, c_name, data_ptr) -} - -/// Unsafe wrapper around mw_com_proxy_event_subscribe -/// -/// # Arguments -/// * `event_ptr` - Opaque event pointer -/// * `max_sample_count` - Maximum number of samples to buffer concurrently -/// -/// # Returns -/// true if subscription was successful, false otherwise -/// -/// # Safety -/// event_ptr must be a valid pointer to a ProxyEventBase previously obtained -/// from get_event_from_proxy(). -/// This function must be called before attempting to retrieve samples via get_samples_from_event(). -pub unsafe fn subscribe_to_event(event_ptr: *mut ProxyEventBase, max_sample_count: u32) -> bool { - // SAFETY: event_ptr is guaranteed to be valid per the caller's contract. - // The C++ implementation handles subscription and buffer allocation safely. - mw_com_proxy_event_subscribe(event_ptr, max_sample_count) -} - -/// Unsafe wrapper around mw_com_proxy_event_unsubscribe -/// -/// # Arguments -/// * `event_ptr` - Opaque event pointer -/// -/// # Safety -/// event_ptr must be a valid pointer to a ProxyEventBase previously obtained from get_event_from_proxy(). -/// This function should be called only when no `SamplePtr` is held by the user for this event. -pub unsafe fn unsubscribe_to_event(event_ptr: *mut ProxyEventBase) { - // SAFETY: event_ptr is guaranteed to be valid per the caller's contract. - // The C++ implementation handles unsubscription and buffer cleanup safely. - mw_com_proxy_event_unsubscribe(event_ptr); -} - -/// Unsafe wrapper around mw_com_proxy_set_event_receive_handler -/// -/// # Arguments -/// * `proxy_event_ptr` - Opaque proxy event pointer -/// * `handler` - FatPtr to event receive handler function -/// -/// # Returns -/// true if handler was set successfully, false otherwise -/// -/// # Safety -/// proxy_event_ptr must be a valid pointer to a ProxyEventBase previously obtained -/// from get_event_from_proxy(). -/// handler must be a valid FatPtr which is used for notifying the proxy of incoming events. -pub unsafe fn set_event_receive_handler( - proxy_event_ptr: *mut ProxyEventBase, - handler: &FatPtr, - event_type: &str, -) -> bool { - // SAFETY: proxy_event_ptr must be valid per the caller's contract, - // and handler must be a valid FatPtr referencing a callable compatible with the callback - // signature expected by the C++ implementation. - let c_name = StringView::from(event_type); - mw_com_proxy_set_event_receive_handler(proxy_event_ptr, handler, c_name) -} - -/// Unsafe wrapper around mw_com_proxy_clear_event_receive_handler -/// -/// # Arguments -/// * `proxy_event_ptr` - Opaque proxy event pointer -/// * `event_type` - Event type name string -/// -/// # Safety -/// proxy_event_ptr must be a valid pointer to a ProxyEventBase previously obtained -/// from get_event_from_proxy(). -/// event_type must be a valid string corresponding to the event type. -pub unsafe fn clear_event_receive_handler(proxy_event_ptr: *mut ProxyEventBase, event_type: &str) { - // SAFETY: proxy_event_ptr must be valid per the caller's contract. - let c_name = StringView::from(event_type); - mw_com_proxy_clear_event_receive_handler(proxy_event_ptr, c_name); -} - -/// Unsafe wrapper around mw_com_start_find_service -/// -/// # Arguments -/// * `callback` - FatPtr to callback function to be invoked when services are found -/// * `instance_spec` - InstanceSpecifier identifying the service instance to find -/// -/// # Returns -/// Opaque handle pointer for the find service operation, or nullptr on failure -/// -/// # Safety -/// callback must be a valid FatPtr referencing a callable compatible with the find service results. -/// instance_spec must be a valid InstanceSpecifier for the service to find. -pub unsafe fn start_find_service( - callback: &FatPtr, - instance_spec: InstanceSpecifier, -) -> *mut FindServiceHandle { - // SAFETY: callback and instance_spec are guaranteed to be valid per the caller's contract. - // The C++ implementation handles the find service operation and callback invocation safely. - mw_com_start_find_service(callback, instance_spec.as_native()) -} - -/// Unsafe wrapper around mw_com_stop_find_service -/// -/// # Arguments -/// * `handle` - Opaque handle pointer returned from start_find_service() -/// -/// # Safety -/// handle must be a valid pointer returned from start_find_service() that has not been stopped yet, -/// and the caller must ensure no further use of this handle after calling this function. -pub unsafe fn stop_find_service(handle: *mut FindServiceHandle) { - // SAFETY: handle is valid per the caller's contract and has not been stopped yet. - // The C++ implementation handles stopping the find service safely. - mw_com_stop_find_service(handle); -} diff --git a/score/mw/com/impl/rust/com-api/com-api-ffi-lola/bridge_ffi_lola.rs b/score/mw/com/impl/rust/com-api/com-api-ffi-lola/bridge_ffi_lola.rs new file mode 100644 index 000000000..4aa9e4d38 --- /dev/null +++ b/score/mw/com/impl/rust/com-api/com-api-ffi-lola/bridge_ffi_lola.rs @@ -0,0 +1,801 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +use bridge_ffi_rs::*; + +#[derive(Debug, Clone)] +/// unit struct representing the FFI bridge for Lola runtime +pub struct LolaFFIBridge; + +/// Rust closure invocation for C++ callbacks +/// +/// This function is called by C++ to invoke a Rust closure with a sample pointer. +/// The closure is reconstructed from the FatPtr trait object. +/// +/// # Safety +/// - `ptr` must point to a valid FatPtr +/// - `sample_ptr` must point to valid placement-new storage containing SamplePtr +#[no_mangle] +unsafe extern "C" fn mw_com_impl_call_dyn_ref_fnmut_sample( + ptr: *const FatPtr, + sample_ptr: *mut std::ffi::c_void, +) { + if ptr.is_null() || sample_ptr.is_null() { + return; + } + + // Reconstruct the closure from FatPtr + // SAFETY: ptr is guaranteed to point to a valid FatPtr that was created by the C++ side + // and represents a valid dynamic trait object (dyn FnMut). The transmute is safe because + // FatPtr is a binary representation of a trait object's fat pointer (data + vtable). + // The caller must ensure the lifetime of the closure outlives this call. + let callable: &mut dyn FnMut(*mut std::ffi::c_void) = std::mem::transmute(*ptr); + + // Invoke the closure with the void* sample pointer + callable(sample_ptr); +} + +/// Rust closure invocation for C++ callbacks for FindService +/// This function is called by C++ to invoke a Rust closure for finding services, +/// passing a vector of service handles. +/// This function invokes a Rust closure that takes a HandleContainer and a FindServiceHandle. +/// # Safety +/// - `ptr` must point to a valid FatPtr representing a closure of type +/// FnMut(HandleContainer, NativeFindServiceHandle). +/// - `service_handles` must point to valid NativeHandleContainer data that can be safely wrapped +/// in a HandleContainer. +/// - `find_service_handle` must point to valid FindServiceHandle data that can be safely wrapped +/// in a NativeFindServiceHandle. +#[no_mangle] +unsafe extern "C" fn mw_com_impl_call_dyn_ref_fnmut_find_service( + ptr: *const FatPtr, + service_handles: *mut NativeHandleContainer, + find_service_handle: *mut FindServiceHandle, +) { + if ptr.is_null() || service_handles.is_null() { + return; + } + + // Reconstruct the closure from FatPtr + let callable: &mut dyn FnMut(HandleContainer, NativeFindServiceHandle) = + std::mem::transmute(*ptr); + + // Invoke with correct types + callable( + HandleContainer::new(service_handles), + NativeFindServiceHandle::new(find_service_handle), + ); +} + +// FFI declarations for C++ functions implemented in registry_bridge_macro.cpp +extern "C" { + + /// Subscribe to event to start receiving samples + /// + /// # Arguments + /// * `event_ptr` - Opaque event pointer + /// * `max_sample_count` - Maximum number of samples to buffer + /// + /// # Returns + /// true if subscription successful, false otherwise + fn mw_com_proxy_event_subscribe(event_ptr: *mut ProxyEventBase, max_sample_count: u32) -> bool; + + /// Get event pointer from proxy by event name + /// + /// # Arguments + /// * `proxy_ptr` - Opaque proxy pointer + /// * `interface_id` - UTF-8 C string of interface UID + /// * `event_id` - UTF-8 C string of event name + /// + /// # Returns + /// Opaque event pointer, or nullptr if event not found + fn mw_com_get_event_from_proxy( + proxy_ptr: *mut ProxyBase, + interface_id: StringView, + event_id: StringView, + ) -> *mut ProxyEventBase; + + /// Get event pointer from skeleton by event name + /// + /// # Arguments + /// * `skeleton_ptr` - Opaque skeleton pointer + /// * `interface_id` - UTF-8 C string of interface UID + /// * `event_id` - UTF-8 C string of event name + /// + /// # Returns + /// Opaque event pointer, or nullptr if event not found + fn mw_com_get_event_from_skeleton( + skeleton_ptr: *mut SkeletonBase, + interface_id: StringView, + event_id: StringView, + ) -> *mut SkeletonEventBase; + + /// Get new samples from an event + /// + /// # Arguments + /// * `event_ptr` - Opaque event pointer + /// * `event_type` - Event type name string + /// * `callback` - FatPtr to callback function + /// * `max_samples` - Maximum number of samples to retrieve + /// + /// # Returns + /// Number of samples retrieved + fn mw_com_type_registry_get_samples_from_event( + event_ptr: *mut ProxyEventBase, + event_type: StringView, + callback: *const FatPtr, + max_samples: u32, + ) -> u32; + + /// Send data via skeleton event + /// + /// # Arguments + /// * `event_ptr` - Opaque skeleton event pointer + /// * `event_type` - Event type name string + /// * `data_ptr` - Pointer to event data + /// + /// # Returns + /// None (void function) + fn mw_com_skeleton_send_event( + event_ptr: *mut SkeletonEventBase, + event_type: StringView, + data_ptr: CVoidPtr, + ) -> bool; + + /// Create proxy by UID and handle + /// + /// # Arguments + /// * `interface_id` - UTF-8 C string of interface UID + /// * `handle_ptr` - Opaque handle pointer + /// + /// # Returns + /// Opaque proxy pointer, or nullptr if creation failed + fn mw_com_create_proxy(interface_id: StringView, handle_ptr: &HandleType) -> *mut ProxyBase; + + /// Create skeleton by UID and instance specifier + /// + /// # Arguments + /// * `interface_id` - UTF-8 C string of interface UID + /// * `instance_spec` - Opaque instance specifier pointer + /// + /// # Returns + /// Opaque skeleton pointer, or nullptr if creation failed + fn mw_com_create_skeleton( + interface_id: StringView, + instance_spec: *const NativeInstanceSpecifier, + ) -> *mut SkeletonBase; + + /// Destroy proxy + /// + /// # Arguments + /// * `proxy_ptr` - Opaque proxy pointer + /// + /// # Returns + /// None (void function) + fn mw_com_destroy_proxy(proxy_ptr: *mut ProxyBase); + + /// Destroy skeleton + /// + /// # Arguments + /// * `skeleton_ptr` - Opaque skeleton pointer + /// + /// # Returns + /// None (void function) + fn mw_com_destroy_skeleton(skeleton_ptr: *mut SkeletonBase); + + /// Offer service via skeleton + /// + /// # Arguments + /// * `skeleton_ptr` - Opaque skeleton pointer + /// + /// # Returns + /// true if offer successful, false otherwise + fn mw_com_skeleton_offer_service(skeleton_ptr: *mut SkeletonBase) -> bool; + + /// Stop offering service via skeleton + /// + /// # Arguments + /// * `skeleton_ptr` - Opaque skeleton pointer + /// + /// # Returns + /// None (void function) + fn mw_com_skeleton_stop_offer_service(skeleton_ptr: *mut SkeletonBase); + + /// Get sample data pointer from SamplePtr + /// + /// # Arguments + /// * `sample_ptr` - Opaque sample pointer + /// * `type_name` - Type name string + /// + /// # Returns + /// Pointer to sample data, or nullptr if type mismatch + fn mw_com_get_sample_ptr( + sample_ptr: *const std::ffi::c_void, + type_name: StringView, + ) -> *const std::ffi::c_void; + + /// Delete sample pointer of SamplePtr' + /// + /// # Arguments + /// * `sample_ptr` - Opaque sample pointer + /// * `type_name` - Type name string + fn mw_com_delete_sample_ptr(sample_ptr: *mut std::ffi::c_void, type_name: StringView); + + /// Get allocatee pointer from skeleton event of specific type + /// + /// # Arguments + /// * `event_ptr` - Opaque skeleton event pointer + /// * `allocatee_ptr` - Pointer to pre-allocated memory for allocatee + /// * `event_type` - Type name string + /// + /// # Returns + /// True if allocatee pointer was retrieved successfully, false otherwise + fn mw_com_get_allocatee_ptr( + event_ptr: *mut SkeletonEventBase, + allocatee_ptr: *mut std::ffi::c_void, + event_type: StringView, + ) -> bool; + + /// Delete allocatee pointer of SampleAllocateePtr + /// + /// # Arguments + /// * `allocatee_ptr` - Pointer to SampleAllocateePtr + /// * `type_name` - Type name string + fn mw_com_delete_allocatee_ptr(allocatee_ptr: *mut std::ffi::c_void, type_name: StringView); + + /// Get allocatee data pointer from allocatee of specific type + /// + /// # Arguments + /// * `allocatee_ptr` - Pointer to SampleAllocateePtr + /// * `type_name` - Type name string + /// + /// # Returns + /// Pointer to allocatee data, or nullptr if type mismatch + fn mw_com_get_allocatee_data_ptr( + allocatee_ptr: *const std::ffi::c_void, + type_name: StringView, + ) -> *mut std::ffi::c_void; + + /// Send event via skeleton using allocatee pointer of specific type + /// + /// # Arguments + /// * `event_ptr` - Opaque skeleton event pointer + /// * `event_type` - Type name string + /// * `allocatee_ptr` - Pointer to SampleAllocateePtr + /// + /// # Returns + /// True if event was sent successfully, false otherwise + fn mw_com_skeleton_send_event_allocatee( + event_ptr: *mut SkeletonEventBase, + event_type: StringView, + allocatee_ptr: *const std::ffi::c_void, + ) -> bool; + + /// Set event receive handler for proxy event + /// + /// # Arguments + /// * `proxy_event_ptr` - Opaque proxy event pointer + /// * `handler` - FatPtr to event receive handler function + /// + /// # Returns + /// True if handler was set successfully, false otherwise + fn mw_com_proxy_set_event_receive_handler( + proxy_event_ptr: *mut ProxyEventBase, + handler: *const FatPtr, + event_type: StringView, + ) -> bool; + + /// Clear event receive handler for proxy event + /// + /// # Arguments + /// * `proxy_event_ptr` - Opaque proxy event pointer + fn mw_com_proxy_clear_event_receive_handler( + proxy_event_ptr: *mut ProxyEventBase, + event_type: StringView, + ); + + /// Start finding services with callback for results + /// + /// # Arguments + /// * `callback` - FatPtr to callback function that will be called with discovery results + /// * `instance_spec` - Pointer to instance specifier for the service to find + /// + /// # Returns + /// Opaque pointer to FindServiceHandle which can be used to stop the find operation + fn mw_com_start_find_service( + callback: *const FatPtr, + instance_spec: *const NativeInstanceSpecifier, + ) -> *mut FindServiceHandle; + + /// Stop finding services using the provided FindServiceHandle + /// + /// # Arguments + /// * `handle` - Opaque pointer to FindServiceHandle returned by mw_com_start_find_service + fn mw_com_stop_find_service(handle: *mut FindServiceHandle); + + /// Unsubscribe from event to stop receiving samples + /// + /// # Arguments + /// * `event_ptr` - Opaque event pointer + fn mw_com_proxy_event_unsubscribe(event_ptr: *mut ProxyEventBase); +} + +impl FFIBridge for LolaFFIBridge { + /// Get allocatee pointer from skeleton event of specific type + /// + /// # Arguments + /// * `event_ptr` - Opaque skeleton event pointer + /// * `allocatee_ptr` - Pointer to pre-allocated memory for allocatee + /// * `event_type` - Type name string + /// + /// # Returns + /// True if allocatee pointer was retrieved successfully, false otherwise + /// + /// # Safety + /// event_ptr must point to a valid SkeletonEventBase, + /// allocatee_ptr must point to valid memory for the allocatee, + /// and event_type must be a valid UTF-8 string representing the event type. + unsafe fn get_allocatee_ptr( + event_ptr: *mut SkeletonEventBase, + allocatee_ptr: *mut std::ffi::c_void, + event_type: &str, + ) -> bool { + // SAFETY: event_ptr is valid which is created using create_skeleton() + // and allocatee_ptr has enough memory allocated for the construction of allocatee instance. + let event_type = StringView::from(event_type); + mw_com_get_allocatee_ptr(event_ptr, allocatee_ptr, event_type) + } + + /// Delete allocatee pointer of SampleAllocateePtr + /// + /// # Arguments + /// * `allocatee_ptr` - Pointer to SampleAllocateePtr + /// * `type_name` - Type name string + /// + /// # Safety + /// allocatee_ptr must point to a valid SampleAllocateePtr of the specified type. + unsafe fn delete_allocatee_ptr(allocatee_ptr: *mut std::ffi::c_void, type_name: &str) { + // SAFETY: allocatee_ptr is valid which is created using get_allocatee_ptr() and + // type_name is constructed using static str. + let type_name = StringView::from(type_name); + mw_com_delete_allocatee_ptr(allocatee_ptr, type_name); + } + + /// Get allocatee data pointer from allocatee of specific type + /// + /// # Arguments + /// * `allocatee_ptr` - Pointer to SampleAllocateePtr + /// * `type_name` - Type name string + /// + /// # Returns + /// Pointer to allocatee data, or nullptr if type mismatch + /// + /// # Safety + /// allocatee_ptr must point to a valid SampleAllocateePtr of the specified type + unsafe fn get_allocatee_data_ptr( + allocatee_ptr: *const std::ffi::c_void, + type_name: &str, + ) -> *mut std::ffi::c_void { + // SAFETY: allocatee_ptr is valid which is created using get_allocatee_ptr() and + // type_name is constructed using static str. + let type_name = StringView::from(type_name); + mw_com_get_allocatee_data_ptr(allocatee_ptr, type_name) + } + + /// Send event via skeleton using allocatee pointer of specific type + /// + /// # Arguments + /// * `event_ptr` - Opaque skeleton event pointer + /// * `event_type` - Type name string + /// * `allocatee_ptr` - Pointer to SampleAllocateePtr + /// + /// # Returns + /// True if event was sent successfully, false otherwise + /// + /// # Safety + /// event_ptr must point to a valid SkeletonEventBase, + /// allocatee_ptr must point to a valid SampleAllocateePtr, + /// and event_type must be a valid UTF-8 string representing the event type. + unsafe fn skeleton_event_send_sample_allocatee( + event_ptr: *mut SkeletonEventBase, + event_type: &str, + allocatee_ptr: *const std::ffi::c_void, + ) -> bool { + // SAFETY: event_ptr is valid which is created using create_skeleton() and allocatee_ptr is + // valid which is created using get_allocatee_ptr(). + let event_type = StringView::from(event_type); + mw_com_skeleton_send_event_allocatee(event_ptr, event_type, allocatee_ptr) + } + + /// Get SamplePtr data pointer + /// + /// # Arguments + /// * `sample_ptr` - Opaque sample pointer + /// * `type_name` - Type name string + /// + /// # Returns + /// Pointer to sample data, or nullptr if type mismatch + /// # Safety + /// sample_ptr must point to a valid SamplePtr of the specified type. + unsafe fn sample_ptr_get( + sample_ptr: *const std::ffi::c_void, + type_name: &str, + ) -> *const std::ffi::c_void { + // SAFETY: sample_ptr is guaranteed to be valid per the caller's contract. + // The C++ implementation handles type checking and data retrieval safely. + let type_name = StringView::from(type_name); + mw_com_get_sample_ptr(sample_ptr, type_name) + } + + /// Delete SamplePtr + /// + /// # Arguments + /// * `sample_ptr` - Opaque sample pointer + /// * `type_name` - Type name string + /// # Safety + /// sample_ptr must point to a valid SamplePtr of the specified type. + unsafe fn sample_ptr_delete(sample_ptr: *mut std::ffi::c_void, type_name: &str) { + // SAFETY: sample_ptr is guaranteed to be valid per the caller's contract. + // The C++ implementation handles type checking and deletion safely. + let type_name = StringView::from(type_name); + mw_com_delete_sample_ptr(sample_ptr, type_name); + } + + /// Unsafe wrapper around mw_com_skeleton_offer_service + /// + /// # Arguments + /// * `skeleton_ptr` - Opaque skeleton pointer + /// + /// # Returns + /// true if offer was successful, false otherwise + /// + /// # Safety + /// skeleton_ptr must be a valid pointer to a SkeletonBase previously created + /// with create_skeleton(). + /// The pointer must remain valid for the duration of this call. + unsafe fn skeleton_offer_service(skeleton_ptr: *mut SkeletonBase) -> bool { + // SAFETY: skeleton_ptr is guaranteed to be valid per the caller's contract. + // The C++ implementation handles service offering safely. + mw_com_skeleton_offer_service(skeleton_ptr) + } + + /// Unsafe wrapper around mw_com_skeleton_stop_offer_service + /// + /// # Arguments + /// * `skeleton_ptr` - Opaque skeleton pointer + /// + /// # Safety + /// skeleton_ptr must be a valid pointer to a SkeletonBase previously created + /// with create_skeleton(). + /// The pointer must remain valid for the duration of this call. + unsafe fn skeleton_stop_offer_service(skeleton_ptr: *mut SkeletonBase) { + // SAFETY: skeleton_ptr is guaranteed to be valid per the caller's contract. + // The C++ implementation handles stopping the service safely. + mw_com_skeleton_stop_offer_service(skeleton_ptr); + } + + /// Unsafe wrapper around mw_com_create_proxy + /// + /// # Arguments + /// * `interface_id` - Interface UID string + /// * `handle_ptr` - Opaque handle pointer + /// + /// # Returns + /// Opaque proxy pointer, or nullptr if creation failed + /// + /// # Safety + /// handle_ptr must be a valid reference to a HandleType. + /// The returned pointer must eventually be destroyed via destroy_proxy(). + unsafe fn create_proxy(interface_id: &str, handle_ptr: &HandleType) -> *mut ProxyBase { + // SAFETY: interface_id is a valid string reference and handle_ptr is guaranteed to be valid + // per the caller's contract. + // The C++ implementation creates and returns a valid proxy pointer or nullptr on failure. + let c_uid = StringView::from(interface_id); + mw_com_create_proxy(c_uid, handle_ptr) + } + + /// Unsafe wrapper around mw_com_create_skeleton + /// + /// # Arguments + /// * `interface_id` - Interface UID string + /// * `instance_spec` - Opaque instance specifier pointer + /// + /// # Returns + /// Opaque skeleton pointer, or nullptr if creation failed + /// + /// # Safety + /// instance_spec must be a valid NativeInstanceSpecifier. + /// The returned pointer must eventually be destroyed via destroy_skeleton(). + unsafe fn create_skeleton( + interface_id: &str, + instance_spec: *const NativeInstanceSpecifier, + ) -> *mut SkeletonBase { + // SAFETY: interface_id is a valid string reference and instance_spec is guaranteed to be + // valid per the caller's contract. + // The C++ implementation creates and returns a valid skeleton pointer or nullptr on failure. + let c_uid = StringView::from(interface_id); + mw_com_create_skeleton(c_uid, instance_spec) + } + + /// Unsafe wrapper around mw_com_destroy_proxy + /// + /// # Arguments + /// * `proxy_ptr` - Opaque proxy pointer to destroy + /// + /// # Safety + /// proxy_ptr must be a valid pointer returned from create_proxy() that has not been destroyed yet. + /// The caller must ensure no other references to this proxy exist after calling this function. + unsafe fn destroy_proxy(proxy_ptr: *mut ProxyBase) { + // SAFETY: proxy_ptr is guaranteed to be valid per the caller's contract. + // The C++ implementation safely deallocates the proxy. + mw_com_destroy_proxy(proxy_ptr); + } + + /// Unsafe wrapper around mw_com_destroy_skeleton + /// + /// # Arguments + /// * `skeleton_ptr` - Opaque skeleton pointer to destroy + /// + /// # Safety + /// skeleton_ptr must be a valid pointer returned from create_skeleton()- + /// that has not been destroyed yet. + /// The caller must ensure no other references to this skeleton exist after calling this function. + unsafe fn destroy_skeleton(skeleton_ptr: *mut SkeletonBase) { + // SAFETY: skeleton_ptr is guaranteed to be valid per the caller's contract. + // The C++ implementation safely deallocates the skeleton. + mw_com_destroy_skeleton(skeleton_ptr); + } + + /// Unsafe wrapper around mw_com_get_event_from_proxy + /// + /// # Arguments + /// * `proxy_ptr` - Opaque proxy pointer + /// * `interface_id` - Interface UID string + /// * `event_id` - Event name string + /// + /// # Returns + /// Opaque event pointer, or nullptr if not found + /// + /// # Safety + /// proxy_ptr must be a valid pointer to a ProxyBase previously created with create_proxy(). + /// The returned pointer remains valid only as long as the proxy remains alive. + unsafe fn get_event_from_proxy( + proxy_ptr: *mut ProxyBase, + interface_id: &str, + event_id: &str, + ) -> *mut ProxyEventBase { + // SAFETY: proxy_ptr is guaranteed to be valid per the caller's contract. + // The C++ implementation returns a valid pointer or nullptr if the event is not found. + let c_id = StringView::from(interface_id); + let c_name = StringView::from(event_id); + mw_com_get_event_from_proxy(proxy_ptr, c_id, c_name) + } + + /// Unsafe wrapper around mw_com_get_event_from_skeleton + /// + /// # Arguments + /// * `skeleton_ptr` - Opaque skeleton pointer + /// * `interface_id` - Interface UID string + /// * `event_id` - Event name string + /// + /// # Returns + /// Opaque event pointer, or nullptr if not found + /// + /// # Safety + /// skeleton_ptr must be a valid pointer to a SkeletonBase previously created + /// with create_skeleton(). + /// The returned pointer remains valid only as long as the skeleton remains alive. + unsafe fn get_event_from_skeleton( + skeleton_ptr: *mut SkeletonBase, + interface_id: &str, + event_id: &str, + ) -> *mut SkeletonEventBase { + // SAFETY: skeleton_ptr is guaranteed to be valid per the caller's contract. + // The C++ implementation returns a valid pointer or nullptr if the event is not found. + let c_id = StringView::from(interface_id); + let c_name = StringView::from(event_id); + mw_com_get_event_from_skeleton(skeleton_ptr, c_id, c_name) + } + + /// Unsafe wrapper around mw_com_get_samples_from_event + /// + /// # Arguments + /// * `event_ptr` - Opaque event pointer + /// * `event_type` - Event type name string + /// * `callback` - FatPtr to callback function + /// * `max_samples` - Maximum number of samples to retrieve + /// + /// # Returns + /// Number of samples retrieved, or u32::MAX on error + /// + /// # Safety + /// event_ptr must be a valid pointer to a ProxyEventBase previously obtained + /// from get_event_from_proxy(). + /// callback must be a valid FatPtr referencing a callable compatible with the event type. + /// The event must have been subscribed to via subscribe_to_event() before calling this function. + unsafe fn get_samples_from_event( + event_ptr: *mut ProxyEventBase, + event_type: &str, + callback: &FatPtr, + max_samples: u32, + ) -> u32 { + // SAFETY: event_ptr, callback, and event_type are guaranteed + // to be valid per the caller's contract. + // The C++ implementation handles sample retrieval and callback invocation safely. + let c_name = StringView::from(event_type); + mw_com_type_registry_get_samples_from_event(event_ptr, c_name, callback, max_samples) + } + + /// Unsafe wrapper around mw_com_skeleton_send_event + /// + /// # Arguments + /// * `event_ptr` - Opaque skeleton event pointer + /// * `event_type` - Event type name string + /// * `data_ptr` - Pointer to event data of the matching type + /// + /// # Safety + /// event_ptr must be a valid pointer to a SkeletonEventBase previously obtained + /// from get_event_from_skeleton(). + /// data_ptr must point to valid data whose type matches the event_type. + /// The lifetime of the data must extend through this function call. + unsafe fn skeleton_send_event( + event_ptr: *mut SkeletonEventBase, + event_type: &str, + data_ptr: *const std::ffi::c_void, + ) -> bool { + // SAFETY: event_ptr and data_ptr are guaranteed to be valid per the caller's contract. + // The C++ implementation handles type matching and data sending safely. + let c_name = StringView::from(event_type); + mw_com_skeleton_send_event(event_ptr, c_name, data_ptr) + } + + /// Unsafe wrapper around mw_com_proxy_event_subscribe + /// + /// # Arguments + /// * `event_ptr` - Opaque event pointer + /// * `max_sample_count` - Maximum number of samples to buffer concurrently + /// + /// # Returns + /// true if subscription was successful, false otherwise + /// + /// # Safety + /// event_ptr must be a valid pointer to a ProxyEventBase previously obtained + /// from get_event_from_proxy(). + /// This function must be called before attempting to retrieve samples via get_samples_from_event(). + unsafe fn subscribe_to_event(event_ptr: *mut ProxyEventBase, max_sample_count: u32) -> bool { + // SAFETY: event_ptr is guaranteed to be valid per the caller's contract. + // The C++ implementation handles subscription and buffer allocation safely. + mw_com_proxy_event_subscribe(event_ptr, max_sample_count) + } + + /// Unsafe wrapper around mw_com_proxy_event_unsubscribe + /// + /// # Arguments + /// * `event_ptr` - Opaque event pointer + /// + /// # Safety + /// event_ptr must be a valid pointer to a ProxyEventBase previously obtained from get_event_from_proxy(). + /// This function should be called only when no `SamplePtr` is held by the user for this event. + unsafe fn unsubscribe_to_event(event_ptr: *mut ProxyEventBase) { + // SAFETY: event_ptr is guaranteed to be valid per the caller's contract. + // The C++ implementation handles unsubscription and buffer cleanup safely. + mw_com_proxy_event_unsubscribe(event_ptr); + } + + /// Unsafe wrapper around mw_com_proxy_set_event_receive_handler + /// + /// # Arguments + /// * `proxy_event_ptr` - Opaque proxy event pointer + /// * `handler` - FatPtr to event receive handler function + /// + /// # Returns + /// true if handler was set successfully, false otherwise + /// + /// # Safety + /// proxy_event_ptr must be a valid pointer to a ProxyEventBase previously obtained + /// from get_event_from_proxy(). + /// handler must be a valid FatPtr which is used for notifying the proxy of incoming events. + unsafe fn set_event_receive_handler( + proxy_event_ptr: *mut ProxyEventBase, + handler: &FatPtr, + event_type: &str, + ) -> bool { + // SAFETY: proxy_event_ptr must be valid per the caller's contract, + // and handler must be a valid FatPtr referencing a callable compatible with the callback + // signature expected by the C++ implementation. + let c_name = StringView::from(event_type); + mw_com_proxy_set_event_receive_handler(proxy_event_ptr, handler, c_name) + } + + /// Unsafe wrapper around mw_com_proxy_clear_event_receive_handler + /// + /// # Arguments + /// * `proxy_event_ptr` - Opaque proxy event pointer + /// * `event_type` - Event type name string + /// + /// # Safety + /// proxy_event_ptr must be a valid pointer to a ProxyEventBase previously obtained + /// from get_event_from_proxy(). + /// event_type must be a valid string corresponding to the event type. + unsafe fn clear_event_receive_handler(proxy_event_ptr: *mut ProxyEventBase, event_type: &str) { + // SAFETY: proxy_event_ptr must be valid per the caller's contract. + let c_name = StringView::from(event_type); + mw_com_proxy_clear_event_receive_handler(proxy_event_ptr, c_name); + } + + /// Unsafe wrapper around mw_com_start_find_service + /// + /// # Arguments + /// * `callback` - FatPtr to callback function to be invoked when services are found + /// * `instance_spec` - InstanceSpecifier identifying the service instance to find + /// + /// # Returns + /// Opaque handle pointer for the find service operation, or nullptr on failure + /// + /// # Safety + /// callback must be a valid FatPtr referencing a callable compatible with the find service results. + /// instance_spec must be a valid InstanceSpecifier for the service to find. + unsafe fn start_find_service( + callback: &FatPtr, + instance_spec: InstanceSpecifier, + ) -> *mut FindServiceHandle { + // SAFETY: callback and instance_spec are guaranteed to be valid per the caller's contract. + // The C++ implementation handles the find service operation and callback invocation safely. + mw_com_start_find_service(callback, instance_spec.as_native()) + } + + /// Unsafe wrapper around mw_com_stop_find_service + /// + /// # Arguments + /// * `handle` - Opaque handle pointer returned from start_find_service() + /// + /// # Safety + /// handle must be a valid pointer returned from start_find_service() that has not been stopped yet, + /// and the caller must ensure no further use of this handle after calling this function. + unsafe fn stop_find_service(handle: *mut FindServiceHandle) { + // SAFETY: handle is valid per the caller's contract and has not been stopped yet. + // The C++ implementation handles stopping the find service safely. + mw_com_stop_find_service(handle); + } + + /// Get the number of service handles in the container + /// + /// # Arguments + /// * `container` - HandleContainer wrapping the native service handle container + /// # Returns + /// The number of service handles in the container + /// + /// # Safety + /// `container` must wrap a valid C++ `ServiceHandleContainer` pointer. + unsafe fn handle_container_size(container: &HandleContainer) -> usize { + container.len() + } + /// Get a reference to the service handle at the specified index in the container + /// + /// # Arguments + /// * `container` - HandleContainer wrapping the native service handle container + /// * `index` - Index of the service handle to retrieve + /// + /// # Returns + /// Some reference to the service handle at the specified index, + /// or None if the index is out of bounds + /// + /// # Safety + /// `container` must wrap a valid C++ `ServiceHandleContainer` pointer. + unsafe fn handle_container_get_at( + container: &HandleContainer, + index: usize, + ) -> Option<&HandleType> { + container.get(index) + } +} diff --git a/score/mw/com/impl/rust/com-api/com-api-ffi-lola/bridge_ffi_mock.rs b/score/mw/com/impl/rust/com-api/com-api-ffi-lola/bridge_ffi_mock.rs new file mode 100644 index 000000000..2af988a3e --- /dev/null +++ b/score/mw/com/impl/rust/com-api/com-api-ffi-lola/bridge_ffi_mock.rs @@ -0,0 +1,334 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +// This file provides a mock implementation of the FFIBridge trait for testing purposes. +// It is kind of stub code which we will change according to the needs of our tests. +// As of now, it just provide basic mock implementation and returning values based on +// input parameters. In future, we will enhance this mock implementation to verify +// all the test cases and scenarios. +#![doc(hidden)] +#![allow(clippy::missing_safety_doc)] + +use bridge_ffi_rs::{ + FatPtr, FindServiceHandle, HandleContainer, HandleType, InstanceSpecifier, + NativeHandleContainer, NativeInstanceSpecifier, ProxyBase, ProxyEventBase, SkeletonBase, + SkeletonEventBase, +}; +use std::cell::RefCell; + +// Type alias for the heap-pointer + drop-glue pair stored in backing thread-locals. +type BackingEntry = (*mut std::ffi::c_void, fn(*mut std::ffi::c_void)); + +// The thread-local storage is used to hold the backing data for the mock FFI bridge. +thread_local! { + //DATA_BACKING holds a pointer to the heap-allocated backing data and a drop function to free it. + //We are using this when user calls get_allocatee_data_ptr to return a pointer to this backing data. + static DATA_BACKING: RefCell> = RefCell::new(None); + //ALLOC_SIZE holds the size of the allocatee type for the next get_allocatee_ptr call, allowing + //the mock to zero-fill the caller's slot correctly. + static ALLOC_SIZE: RefCell = const { RefCell::new(0) }; + //SAMPLE_BACKING holds a pointer to the heap-allocated sample data and a drop function to free it. + static SAMPLE_BACKING: RefCell> = RefCell::new(None); +} + +#[derive(Debug, Clone)] +pub struct MockFFIBridge; + +impl bridge_ffi_rs::FFIBridge for MockFFIBridge { + unsafe fn get_allocatee_ptr( + event_ptr: *mut SkeletonEventBase, + allocatee_ptr: *mut std::ffi::c_void, + _event_type: &str, + ) -> bool { + if event_ptr.is_null() || allocatee_ptr.is_null() { + return false; + } + let size = ALLOC_SIZE.with(|s| *s.borrow()); + if size == 0 { + return false; + } + std::ptr::write_bytes(allocatee_ptr as *mut u8, 0, size); + true + } + + unsafe fn delete_allocatee_ptr(_allocatee_ptr: *mut std::ffi::c_void, _type_name: &str) { + DATA_BACKING.with(|d| { + if let Some((ptr, drop_fn)) = d.borrow_mut().take() { + drop_fn(ptr); + } + }); + ALLOC_SIZE.with(|s| *s.borrow_mut() = 0); + } + + unsafe fn get_allocatee_data_ptr( + _allocatee_ptr: *const std::ffi::c_void, + _type_name: &str, + ) -> *mut std::ffi::c_void { + DATA_BACKING.with(|d| { + d.borrow() + .as_ref() + .map(|(ptr, _)| *ptr) + .unwrap_or(std::ptr::null_mut()) + }) + } + + unsafe fn skeleton_event_send_sample_allocatee( + event_ptr: *mut SkeletonEventBase, + _event_type: &str, + _allocatee_ptr: *const std::ffi::c_void, + ) -> bool { + !event_ptr.is_null() + } + + unsafe fn sample_ptr_get( + _sample_ptr: *const std::ffi::c_void, + _type_name: &str, + ) -> *const std::ffi::c_void { + SAMPLE_BACKING.with(|s| { + s.borrow() + .as_ref() + .map(|(ptr, _)| *ptr as *const std::ffi::c_void) + .unwrap_or(std::ptr::null()) + }) + } + + unsafe fn sample_ptr_delete(_sample_ptr: *mut std::ffi::c_void, _type_name: &str) { + SAMPLE_BACKING.with(|s| { + if let Some((ptr, drop_fn)) = s.borrow_mut().take() { + drop_fn(ptr); + } + }); + } + + unsafe fn skeleton_offer_service(skeleton_ptr: *mut SkeletonBase) -> bool { + !skeleton_ptr.is_null() + } + + unsafe fn skeleton_stop_offer_service(_skeleton_ptr: *mut SkeletonBase) {} + + unsafe fn create_proxy(_interface_id: &str, _handle_ptr: &HandleType) -> *mut ProxyBase { + Box::into_raw(Box::new(std::mem::zeroed::())) + } + + unsafe fn create_skeleton( + _interface_id: &str, + _instance_spec: *const NativeInstanceSpecifier, + ) -> *mut SkeletonBase { + Box::into_raw(Box::new(std::mem::zeroed::())) + } + + unsafe fn destroy_proxy(proxy_ptr: *mut ProxyBase) { + if !proxy_ptr.is_null() { + // SAFETY: proxy_ptr was allocated by create_proxy via Box::into_raw + drop(Box::from_raw(proxy_ptr)); + } + } + + unsafe fn destroy_skeleton(skeleton_ptr: *mut SkeletonBase) { + if !skeleton_ptr.is_null() { + // SAFETY: skeleton_ptr was allocated by create_skeleton via Box::into_raw + drop(Box::from_raw(skeleton_ptr)); + } + } + + unsafe fn get_event_from_proxy( + proxy_ptr: *mut ProxyBase, + _interface_id: &str, + _event_id: &str, + ) -> *mut ProxyEventBase { + if proxy_ptr.is_null() { + return std::ptr::null_mut(); + } + // ProxyEventBase is a ZST opaque type; a non-null sentinel is sufficient. + // LIMITATION: The returned pointer is intentionally dangling and must NOT be + // dereferenced. It is only valid as a non-null sentinel for null-checks in the + // mock. Any test that dereferences this pointer will trigger undefined behavior. + std::ptr::NonNull::::dangling().as_ptr() + } + + unsafe fn get_event_from_skeleton( + _skeleton_ptr: *mut SkeletonBase, + _interface_id: &str, + _event_id: &str, + ) -> *mut SkeletonEventBase { + // SkeletonEventBase is a ZST opaque type; a non-null sentinel is sufficient. + // LIMITATION: The returned pointer is intentionally dangling and must NOT be + // dereferenced. It is only valid as a non-null sentinel for null-checks in the + // mock. Any test that dereferences this pointer will trigger undefined behavior. + std::ptr::NonNull::::dangling().as_ptr() + } + + unsafe fn get_samples_from_event( + event_ptr: *mut ProxyEventBase, + _event_type: &str, + _callback: &FatPtr, + _max_samples: u32, + ) -> u32 { + if event_ptr.is_null() { + return u32::MAX; + } + 0 + } + + unsafe fn skeleton_send_event( + event_ptr: *mut SkeletonEventBase, + _event_type: &str, + _data_ptr: *const std::ffi::c_void, + ) -> bool { + !event_ptr.is_null() + } + + unsafe fn subscribe_to_event(event_ptr: *mut ProxyEventBase, _max_sample_count: u32) -> bool { + !event_ptr.is_null() + } + + unsafe fn unsubscribe_to_event(_event_ptr: *mut ProxyEventBase) {} + + unsafe fn set_event_receive_handler( + proxy_event_ptr: *mut ProxyEventBase, + _handler: &FatPtr, + _event_type: &str, + ) -> bool { + !proxy_event_ptr.is_null() + } + + unsafe fn clear_event_receive_handler( + _proxy_event_ptr: *mut ProxyEventBase, + _event_type: &str, + ) { + } + + unsafe fn start_find_service( + _callback: &FatPtr, + _instance_spec: InstanceSpecifier, + ) -> *mut FindServiceHandle { + Box::into_raw(Box::new(std::mem::zeroed::())) + } + + unsafe fn stop_find_service(handle: *mut FindServiceHandle) { + if !handle.is_null() { + // SAFETY: handle was allocated by start_find_service via Box::into_raw + drop(Box::from_raw(handle)); + } + } + + unsafe fn handle_container_size(_container: &HandleContainer) -> usize { + 0 + } + + unsafe fn handle_container_get_at( + _container: &HandleContainer, + _index: usize, + ) -> Option<&HandleType> { + None + } +} + +impl MockFFIBridge { + // The handle container is backed by a heap-allocated zeroed stub so the pointer + // is stable and non-dangling. An extra `Arc` clone is intentionally forgotten + // inside this function so the strong count is permanently pinned above zero — + // meaning `HandleContainer::drop` (which calls real C++) is never invoked + // regardless of how many clones the caller makes or drops. + pub fn make_handle_container() -> std::sync::Arc { + // A dangling non-null sentinel: the inner pointer is never passed to C++ because + // MockFFIBridge::handle_container_size / handle_container_get_at always return 0 / None. + // LIMITATION: `ptr` is intentionally dangling and must NOT be dereferenced. + // It is safe only because the mock's handle_container_size / handle_container_get_at + // implementations never expose or forward this pointer to any code that would read it. + // Tests must not call any real C++ handle-container operations on the returned Arc. + let ptr = std::ptr::NonNull::::dangling().as_ptr(); + let hc = std::sync::Arc::new(HandleContainer::new(ptr)); + // Prevent HandleContainer::drop (which would call real C++ delete) by pinning the refcount. + std::mem::forget(std::sync::Arc::clone(&hc)); + hc + } + + // `ProxyEventBase` is a ZST opaque type, a dangling non-null pointer is the + // canonical representation for "valid but empty" in the mock. + // + // LIMITATION: The returned pointer is intentionally dangling and must NOT be + // dereferenced. Tests may only use it as a non-null sentinel (e.g., to satisfy + // null-checks in other mock methods). Dereferencing it is undefined behavior. + pub fn make_proxy_event_ptr() -> *mut ProxyEventBase { + std::ptr::NonNull::::dangling().as_ptr() + } + + // Registers type `T` as the backing type for the next `get_allocatee_ptr` / + // `get_allocatee_data_ptr` cycle. + // Heap-allocates a `MaybeUninit` buffer whose address is returned by + // `get_allocatee_data_ptr`, and records `size_of::>()` so that + // `get_allocatee_ptr` can zero-fill the caller's slot (making `assume_init()` safe). + pub fn set_alloc_backing() { + use sample_allocatee_ptr_rs::SampleAllocateePtr; + let data_ptr = + Box::into_raw(Box::new(std::mem::MaybeUninit::::uninit())) as *mut std::ffi::c_void; + // Drop glue: frees the MaybeUninit box without running T's destructor. + let drop_fn: fn(*mut std::ffi::c_void) = |p| { + // SAFETY: p was allocated as Box> by set_alloc_backing::. + drop(unsafe { Box::from_raw(p as *mut std::mem::MaybeUninit) }); + }; + // Replace any previous backing (prevents leaks on repeated calls). + DATA_BACKING.with(|d| { + if let Some((old_ptr, old_drop)) = d.borrow_mut().replace((data_ptr, drop_fn)) { + old_drop(old_ptr); + } + }); + ALLOC_SIZE.with(|s| *s.borrow_mut() = std::mem::size_of::>()); + } + + // Registers `data` as the backing value for the next `sample_ptr_get` / + // `sample_ptr_delete` cycle. + // + // Heap-allocates `data` and stores a typed drop-glue so `sample_ptr_delete` + // can free it without knowing `T`. `sample_ptr_get` returns the pointer to + // the heap `T`, making `get_data()` return a valid `&T`. + pub fn set_sample_backing(data: T) { + let ptr = Box::into_raw(Box::new(data)) as *mut std::ffi::c_void; + let drop_fn: fn(*mut std::ffi::c_void) = |p| { + // SAFETY: p was allocated as Box by set_sample_backing::. + drop(unsafe { Box::from_raw(p as *mut T) }); + }; + // Replace any previous backing (prevents leaks on repeated calls). + SAMPLE_BACKING.with(|s| { + if let Some((old_ptr, old_drop)) = s.borrow_mut().replace((ptr, drop_fn)) { + old_drop(old_ptr); + } + }); + } + + // Drops all heap-allocated backing data stored in thread-locals and resets ALLOC_SIZE + // to zero. + fn reset() { + DATA_BACKING.with(|d| { + if let Some((ptr, drop_fn)) = d.borrow_mut().take() { + drop_fn(ptr); + } + }); + ALLOC_SIZE.with(|s| *s.borrow_mut() = 0); + SAMPLE_BACKING.with(|s| { + if let Some((ptr, drop_fn)) = s.borrow_mut().take() { + drop_fn(ptr); + } + }); + } +} + +// RAII guard that resets all `MockFFIBridge` thread-local state when it goes out of scope. +pub struct MockFFIBridgeGuard; + +impl Drop for MockFFIBridgeGuard { + fn drop(&mut self) { + MockFFIBridge::reset(); + } +} diff --git a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/BUILD b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/BUILD index c35184833..8c806976b 100644 --- a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/BUILD +++ b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/BUILD @@ -29,6 +29,7 @@ rust_library( "//score/mw/com/impl/plumbing/rust:sample_ptr_rs", "//score/mw/com/impl/rust:mw_com", "//score/mw/com/impl/rust/com-api/com-api-concept", + "//score/mw/com/impl/rust/com-api/com-api-ffi-lola:bridge_ffi_lola", "//score/mw/com/impl/rust/com-api/com-api-ffi-lola:bridge_ffi_rs", "@score_communication_crate_index//:futures", ], @@ -37,8 +38,12 @@ rust_library( rust_test( name = "com-api-runtime-lola-tests", crate = ":com-api-runtime-lola", + #tag will be removed wwhen sanitizer issue resolved for this tags = ["manual"], - deps = [":com-api-runtime-lola"], + deps = [ + ":com-api-runtime-lola", + "//score/mw/com/impl/rust/com-api/com-api-ffi-lola:bridge_ffi_mock", + ], ) rust_doc_test( diff --git a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs index 73c8f90f5..9b6269b84 100644 --- a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs +++ b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/consumer.rs @@ -53,16 +53,19 @@ use bridge_ffi_rs::*; use crate::LolaRuntimeImpl; #[derive(Clone, Debug)] -pub struct LolaConsumerInfo { +pub struct LolaConsumerInfo { handle_container: Arc, handle_index: usize, interface_id: &'static str, + _marker: PhantomData, } -impl LolaConsumerInfo { +impl LolaConsumerInfo { /// Get a reference to the handle, guaranteed valid as long as this struct exists pub fn get_handle(&self) -> Option<&HandleType> { - self.handle_container.get(self.handle_index) + // SAFETY: handle_container was produced by the bridge or a trusted mock; + // both implementations uphold the contract of handle_container_get_at. + unsafe { B::handle_container_get_at(&self.handle_container, self.handle_index) } } } @@ -70,14 +73,15 @@ impl LolaConsumerInfo { //And sample_ptr_rs::SamplePtr FFI function should be move in plumbing folder //sample_ptr_rs module #[derive(Debug)] -pub struct LolaBinding +pub struct LolaBinding where T: CommData + Debug, { data: ManuallyDrop>, + _bridge: PhantomData, } -impl Drop for LolaBinding +impl Drop for LolaBinding where T: CommData + Debug, { @@ -86,7 +90,7 @@ where //SamplePtr created by FFI unsafe { let mut sample_ptr = ManuallyDrop::take(&mut self.data); - bridge_ffi_rs::sample_ptr_delete( + B::sample_ptr_delete( std::ptr::from_mut(&mut sample_ptr) as *mut std::ffi::c_void, T::ID, ); @@ -95,19 +99,19 @@ where } #[derive(Debug)] -pub struct Sample +pub struct Sample where T: CommData + Debug, { //we need unique id for each sample to implement Ord and Eq traits for sorting in // SampleContainer id: usize, - inner: LolaBinding, + inner: LolaBinding, } pub static ID_COUNTER: AtomicUsize = AtomicUsize::new(0); -impl Sample +impl Sample where T: CommData + Debug, { @@ -115,7 +119,7 @@ where //SAFETY: It is safe to get the data pointer because SamplePtr is valid //and data is valid as long as SamplePtr is valid unsafe { - let data_ptr = bridge_ffi_rs::sample_ptr_get( + let data_ptr = B::sample_ptr_get( std::ptr::from_ref(&(*self.inner.data)) as *const std::ffi::c_void, T::ID, ); @@ -126,7 +130,7 @@ where } } -impl Deref for Sample +impl Deref for Sample where T: CommData + Debug, { @@ -137,10 +141,10 @@ where } } -impl com_api_concept::Sample for Sample where T: CommData + Debug {} +impl com_api_concept::Sample for Sample where T: CommData + Debug {} // Ordering traits for Sample are using id field to provide total ordering -impl PartialEq for Sample +impl PartialEq for Sample where T: CommData + Debug, { @@ -149,9 +153,9 @@ where } } -impl Eq for Sample where T: CommData + Debug {} +impl Eq for Sample where T: CommData + Debug {} -impl PartialOrd for Sample +impl PartialOrd for Sample where T: CommData + Debug, { @@ -160,7 +164,7 @@ where } } -impl Ord for Sample +impl Ord for Sample where T: CommData + Debug, { @@ -172,15 +176,15 @@ where /// Manages the lifetime of the native proxy instance, /// user should clone this to share between threads /// Always use this struct to manage the proxy instance pointer -pub struct ProxyInstanceManager(Arc); +pub struct ProxyInstanceManager(Arc>); -impl Clone for ProxyInstanceManager { +impl Clone for ProxyInstanceManager { fn clone(&self) -> Self { Self(Arc::clone(&self.0)) } } -impl std::fmt::Debug for ProxyInstanceManager { +impl std::fmt::Debug for ProxyInstanceManager { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("ProxyInstanceManager").finish() } @@ -197,8 +201,9 @@ impl std::fmt::Debug for ProxyInstanceManager { /// As it has Send and Sync unsafe impls, /// it must not expose any mutable access to the proxy instance /// Or must not provide any method to access the proxy instance directly -pub struct NativeProxyBase { +pub struct NativeProxyBase { proxy: NonNull, // Stores the proxy instance + _marker: PhantomData, } //SAFETY: NativeProxyBase is safe to share between threads because: @@ -206,34 +211,37 @@ pub struct NativeProxyBase { // Access is controlled through Arc which provides atomic reference counting // The proxy lifetime is managed safely through Drop // and it does not provide any mutable access to the underlying proxy instance -unsafe impl Send for NativeProxyBase {} -unsafe impl Sync for NativeProxyBase {} +unsafe impl Send for NativeProxyBase {} +unsafe impl Sync for NativeProxyBase {} -impl Drop for NativeProxyBase { +impl Drop for NativeProxyBase { fn drop(&mut self) { //SAFETY: It is safe to destroy the proxy because it was created by FFI // and proxy pointer received at the time of create_proxy called unsafe { - bridge_ffi_rs::destroy_proxy(self.proxy.as_ptr()); + B::destroy_proxy(self.proxy.as_ptr()); } } } -impl std::fmt::Debug for NativeProxyBase { +impl std::fmt::Debug for NativeProxyBase { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("NativeProxyBase").finish() } } -impl NativeProxyBase { +impl NativeProxyBase { pub fn new(interface_id: &str, handle: &HandleType) -> Result { //SAFETY: It is safe to create the proxy because interface_id and handle are valid //Handle received at the time of get_avaible_instances called with correct interface_id - let raw_proxy_ptr = unsafe { bridge_ffi_rs::create_proxy(interface_id, handle) }; + let raw_proxy_ptr = unsafe { B::create_proxy(interface_id, handle) }; let proxy = std::ptr::NonNull::new(raw_proxy_ptr).ok_or(Error::ConsumerError( ConsumerFailedReason::ProxyCreationFailed, ))?; - Ok(Self { proxy }) + Ok(Self { + proxy, + _marker: PhantomData, + }) } } @@ -255,11 +263,15 @@ pub struct NativeProxyEventBase { unsafe impl Send for NativeProxyEventBase {} impl NativeProxyEventBase { - pub fn new(proxy: &NonNull, interface_id: &str, identifier: &str) -> Result { + pub fn new( + proxy: &NonNull, + interface_id: &str, + identifier: &str, + ) -> Result { //SAFETY: It is safe as we are passing valid proxy pointer and interface id to get event // proxy pointer is created during consumer creation let raw_event_ptr = - unsafe { bridge_ffi_rs::get_event_from_proxy(proxy.as_ptr(), interface_id, identifier) }; + unsafe { B::get_event_from_proxy(proxy.as_ptr(), interface_id, identifier) }; let proxy_event_ptr = std::ptr::NonNull::new(raw_event_ptr) .ok_or(Error::EventError(EventFailedReason::EventCreationFailed))?; Ok(Self { proxy_event_ptr }) @@ -280,16 +292,18 @@ impl std::fmt::Debug for NativeProxyEventBase { } #[derive(Debug)] -pub struct SubscribableImpl { +pub struct SubscribableImpl { identifier: &'static str, - instance_info: LolaConsumerInfo, - proxy_instance: ProxyInstanceManager, + instance_info: LolaConsumerInfo, + proxy_instance: ProxyInstanceManager, data: PhantomData, } -impl Subscriber for SubscribableImpl { - type Subscription = SubscriberImpl; - fn new(identifier: &'static str, instance_info: LolaConsumerInfo) -> Result { +impl Subscriber> + for SubscribableImpl +{ + type Subscription = SubscriberImpl; + fn new(identifier: &'static str, instance_info: LolaConsumerInfo) -> Result { let handle = instance_info.get_handle().ok_or(Error::ConsumerError( ConsumerFailedReason::ServiceHandleNotFound, ))?; @@ -304,17 +318,23 @@ impl Subscriber for SubscribableImpl } fn subscribe(self, max_num_samples: usize) -> Result { let instance_info = self.instance_info.clone(); - let event_instance = NativeProxyEventBase::new( + let event_instance = NativeProxyEventBase::new::( &self.proxy_instance.0.proxy, self.instance_info.interface_id, self.identifier, )?; + let max_num_samples_u32 = u32::try_from(max_num_samples).map_err(|_| { + Error::EventError(EventFailedReason::MaxSampleOutOfBounds { + max: u32::MAX as usize, + requested: max_num_samples, + }) + })?; //SAFETY: It is safe to subscribe to event because event_instance is valid // which was obtained from valid proxy instance let status = unsafe { - bridge_ffi_rs::subscribe_to_event( + B::subscribe_to_event( std::ptr::from_ref(event_instance.get_proxy_event_base()) as *mut ProxyEventBase, - max_num_samples.try_into().unwrap(), + max_num_samples_u32, ) }; if !status { @@ -329,7 +349,7 @@ impl Subscriber for SubscribableImpl max_num_samples, instance_info, waker_storage: Arc::default(), - async_init_status: std::sync::Once::new(), + async_init_status: std::sync::OnceLock::new(), _proxy: self.proxy_instance.clone(), _phantom: PhantomData, }) @@ -426,21 +446,21 @@ impl<'a> DerefMut for ProxyEventManagerGuard<'a> { /// It also manages the asynchronous initialization of the receive callback /// and the waker storage for async notifications when new samples arrive. #[derive(Debug)] -pub struct SubscriberImpl +pub struct SubscriberImpl where T: CommData + Debug, { event: ProxyEventManager, event_id: &'static str, max_num_samples: usize, - instance_info: LolaConsumerInfo, + instance_info: LolaConsumerInfo, waker_storage: Arc, - async_init_status: std::sync::Once, - _proxy: ProxyInstanceManager, + async_init_status: std::sync::OnceLock<()>, + _proxy: ProxyInstanceManager, _phantom: PhantomData, } -impl Drop for SubscriberImpl { +impl Drop for SubscriberImpl { fn drop(&mut self) { // SAFETY: It is safe to unsubscribe from the event because the event pointer is valid // and was created during subscription. @@ -449,17 +469,17 @@ impl Drop for SubscriberImpl { // and then unsubscribe from the event to clean up resources on the C++ side. let mut guard = self.event.get_proxy_event(); unsafe { - if self.async_init_status.is_completed() + if self.async_init_status.get().is_some() // Check if the async receive callback was initialized { - bridge_ffi_rs::clear_event_receive_handler(guard.deref_mut(), T::ID); + B::clear_event_receive_handler(guard.deref_mut(), T::ID); } - bridge_ffi_rs::unsubscribe_to_event(guard.deref_mut()); + B::unsubscribe_to_event(guard.deref_mut()); } } } -impl SubscriberImpl { +impl SubscriberImpl { fn init_async_receive(&self, event_guard: &mut ProxyEventManagerGuard) -> Result<()> { let callback_waker = Arc::clone(&self.waker_storage); let waker_callback = move || { @@ -474,19 +494,26 @@ impl SubscriberImpl { // The callback is a simple waker that wakes the future when new samples arrive, // and the lifetime of the callback is managed by Rust, it will not outlive the scope of // this function call. - unsafe { - bridge_ffi_rs::set_event_receive_handler(event_guard.deref_mut(), &fat_ptr, T::ID); + let status = unsafe { + B::set_event_receive_handler(event_guard.deref_mut(), &fat_ptr, T::ID) + }; + if !status { + // SAFETY: ptr was allocated as Box via Box::into_raw + // above. The handler was not registered so this side retains ownership and must + // free it to prevent a leak. + drop(unsafe { Box::from_raw(ptr) }); + return Err(Error::ReceiveError(ReceiveFailedReason::ReceiveError)); } Ok(()) } } -impl Subscription for SubscriberImpl +impl Subscription> for SubscriberImpl where T: CommData + Debug, { - type Subscriber = SubscribableImpl; - type Sample<'a> = Sample; + type Subscriber = SubscribableImpl; + type Sample<'a> = Sample; /// The unsubscribe method consumes the subscription and returns the subscribable instance. /// Calling `unsubscribe` while a `SampleContainer` holding samples whose lifetime @@ -527,7 +554,7 @@ where scratch: &'_ mut SampleContainer>, max_samples: usize, ) -> Result { - try_receive_samples::( + try_receive_samples::( self.event.get_proxy_event().deref_mut(), scratch, self.max_num_samples, @@ -556,12 +583,16 @@ where // Get the event guard to ensure no concurrent receive calls // on the same subscriber instance. let mut event_guard = self.event.get_proxy_event(); - // Initialize the async receive callback only once when the first receive call is made - // We are using std::sync::Once to ensure that the callback is set only once. - self.async_init_status.call_once(|| { - self.init_async_receive(&mut event_guard) - .expect("Failed to initialize async receive callback"); - }); + // Initialize the async receive callback only once when the first receive call is made. + // ProxyEventManager already prevents concurrent receive calls, so the check-then-set + // sequence is race-free. Using get/set instead of get_or_try_init avoids the + // unstable `once_cell_try` feature while still propagating errors via `?`. + if self.async_init_status.get().is_none() { + self.init_async_receive(&mut event_guard)?; + // Ignore Err: the only way set() fails is if another thread raced us, + // which cannot happen because ProxyEventManager panics on concurrent access. + let _ = self.async_init_status.set(()); + } ReceiveFuture { event_guard: Some(event_guard), waker_storage: Arc::clone(&self.waker_storage), @@ -581,18 +612,18 @@ where // a waker storage for async notifications, and parameters for managing the receive operation. // The Future implementation for ReceiveFuture defines the polling logic, // which attempts to receive samples and manages the state of the receive operation. -struct ReceiveFuture<'a, T: CommData + Debug> { +struct ReceiveFuture<'a, T: CommData + Debug, B: FFIBridge> { event_guard: Option>, waker_storage: Arc, max_num_samples: usize, - scratch: Option>>, + scratch: Option>>, new_samples: usize, max_samples: usize, total_received: usize, } -impl<'a, T: CommData + Debug> Future for ReceiveFuture<'a, T> { - type Output = Result>>; +impl<'a, T: CommData + Debug, B: FFIBridge> Future for ReceiveFuture<'a, T, B> { + type Output = Result>>; fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { // Extract all immutable values upfront to avoid borrow conflicts with self in the callback @@ -608,7 +639,7 @@ impl<'a, T: CommData + Debug> Future for ReceiveFuture<'a, T> { // Temporarily take ownership of scratch to avoid borrow issues if let Some(mut scratch) = self.scratch.take() { if let Some(event_guard) = self.event_guard.as_mut() { - let result = try_receive_samples::( + let result = try_receive_samples::( event_guard.deref_mut(), &mut scratch, max_num_samples, @@ -673,26 +704,29 @@ impl std::fmt::Debug for DiscoveryStateData { } } -pub struct LolaConsumerDiscovery { +pub struct LolaConsumerDiscovery { pub instance_specifier: InstanceSpecifier, pub _interface: PhantomData, + pub _bridge: PhantomData, } -impl LolaConsumerDiscovery { - pub fn new(_runtime: &LolaRuntimeImpl, instance_specifier: InstanceSpecifier) -> Self { +impl LolaConsumerDiscovery { + pub fn new(_runtime: &LolaRuntimeImpl, instance_specifier: InstanceSpecifier) -> Self { Self { instance_specifier, _interface: PhantomData, + _bridge: PhantomData, } } } -impl ServiceDiscovery for LolaConsumerDiscovery +impl ServiceDiscovery> + for LolaConsumerDiscovery where - LolaConsumerBuilder: ConsumerBuilder, + LolaConsumerBuilder: ConsumerBuilder>, { - type ConsumerBuilder = LolaConsumerBuilder; - type ServiceEnumerator = Vec>; + type ConsumerBuilder = LolaConsumerBuilder; + type ServiceEnumerator = Vec>; fn get_available_instances(&self) -> Result { //If ANY Support is added in Lola, then we need to return all available instances @@ -711,6 +745,7 @@ where handle_container: Arc::clone(&service_handle_arc), handle_index, interface_id: I::INTERFACE_ID, + _marker: PhantomData, }; LolaConsumerBuilder { instance_info, @@ -778,7 +813,7 @@ where // instance specifier is valid. The returned handle is stored // synchronously — before any async polling — guaranteeing // stop_find_service is always called in Drop. - let raw_handle = unsafe { bridge_ffi_rs::start_find_service(&fat_ptr, spec) }; + let raw_handle = unsafe { B::start_find_service(&fat_ptr, spec) }; if raw_handle.is_null() { Err(Error::ServiceError( ServiceFailedReason::FailedToStartDiscovery, @@ -799,6 +834,7 @@ where discovery_state, waker_storage, _interface: PhantomData, + _bridge: PhantomData, } .await } @@ -812,29 +848,28 @@ where /// resources. /// Stop find service in Drop implementation to ensure that we clean up the find service if the /// future is dropped before completion -struct ServiceDiscoveryFuture { +struct ServiceDiscoveryFuture { find_handle: bridge_ffi_rs::NativeFindServiceHandle, discovery_state: Arc>, waker_storage: Arc, _interface: PhantomData, + _bridge: PhantomData, } -impl Drop for ServiceDiscoveryFuture { +impl Drop for ServiceDiscoveryFuture { fn drop(&mut self) { // SAFETY: find_handle is always valid here — it was stored synchronously // from start_find_service return value before any async polling began. // This unconditional call ensures the C++ discovery operation is always // cleaned up, even when the future is dropped before the callback fires. unsafe { - bridge_ffi_rs::stop_find_service( - self.find_handle.as_mut() as *mut bridge_ffi_rs::FindServiceHandle - ); + B::stop_find_service(self.find_handle.as_mut() as *mut bridge_ffi_rs::FindServiceHandle); } } } -impl Future for ServiceDiscoveryFuture { - type Output = Result>>; +impl Future for ServiceDiscoveryFuture { + type Output = Result>>; fn poll( self: std::pin::Pin<&mut Self>, @@ -866,6 +901,7 @@ impl Future for ServiceDiscoveryFuture { handle_container: Arc::clone(&service_handle_arc), handle_index, interface_id: I::INTERFACE_ID, + _marker: PhantomData, }; LolaConsumerBuilder { instance_info, @@ -881,20 +917,27 @@ impl Future for ServiceDiscoveryFuture { } } -impl ConsumerBuilder for LolaConsumerBuilder {} +impl ConsumerBuilder> + for LolaConsumerBuilder +{ +} -impl Builder> for LolaConsumerBuilder { - fn build(self) -> Result> { +impl Builder>> + for LolaConsumerBuilder +{ + fn build(self) -> Result>> { Ok(Consumer::new(self.instance_info)) } } -pub struct LolaConsumerBuilder { - pub instance_info: LolaConsumerInfo, +pub struct LolaConsumerBuilder { + pub instance_info: LolaConsumerInfo, pub _interface: PhantomData, } -impl ConsumerDescriptor for LolaConsumerBuilder { +impl ConsumerDescriptor> + for LolaConsumerBuilder +{ fn get_instance_identifier(&self) -> &InstanceSpecifier { //if InstanceSpecifier::ANY support enable by lola //then this API should get InstanceSpecifier from FFI Call @@ -915,9 +958,9 @@ impl ConsumerDescriptor for LolaConsumerBuilder( +fn try_receive_samples( event: &mut ProxyEventBase, - scratch: &mut SampleContainer>, + scratch: &mut SampleContainer>, max_num_samples: usize, max_samples: usize, ) -> Result { @@ -930,7 +973,7 @@ fn try_receive_samples( )); } // Create a callback that will be called by the C++ side for each new sample arrival - let mut callback = create_sample_callback(scratch, max_samples); + let mut callback = create_sample_callback::(scratch, max_samples); // Convert closure to FatPtr for C++ callback let dyn_callback: &mut dyn FnMut(*mut sample_ptr_rs::SamplePtr) = &mut callback; // SAFETY: it is safe to transmute the closure reference to a FatPtr because @@ -939,7 +982,7 @@ fn try_receive_samples( // SAFETY: event is a valid ProxyEventBase pointer obtained during subscription. // The lifetime of the callback is managed by Rust and will not outlive this function call. let count = unsafe { - bridge_ffi_rs::get_samples_from_event( + B::get_samples_from_event( event as *mut ProxyEventBase, T::ID, &fat_ptr, @@ -970,8 +1013,8 @@ fn try_receive_samples( /// # Parameters /// * `scratch` - Mutable reference to the sample container /// * `max_samples` - Maximum number of samples to maintain in the container -pub fn create_sample_callback<'a, T: CommData + Debug>( - scratch: &'a mut SampleContainer>, +pub fn create_sample_callback<'a, T: CommData + Debug, B: FFIBridge>( + scratch: &'a mut SampleContainer>, max_samples: usize, ) -> impl FnMut(*mut sample_ptr_rs::SamplePtr) + 'a { move |raw_sample: *mut sample_ptr_rs::SamplePtr| { @@ -986,6 +1029,7 @@ pub fn create_sample_callback<'a, T: CommData + Debug>( id: ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed), inner: LolaBinding { data: ManuallyDrop::new(sample_ptr), + _bridge: PhantomData, }, }; while scratch.sample_count() >= max_samples { @@ -1003,120 +1047,106 @@ pub fn create_sample_callback<'a, T: CommData + Debug>( #[cfg(test)] mod test { - use com_api_concept::{ - CommData, Consumer, Interface, OfferedProducer, Producer, Result, Runtime, ServiceDiscovery, - }; - #[derive(Debug, Clone, Copy)] + use super::*; + use bridge_ffi_mock::{MockFFIBridge, MockFFIBridgeGuard}; + + #[derive(Debug)] #[repr(C)] - struct TestData(u32); + struct TestData { + value: i32, + } unsafe impl com_api_concept::Reloc for TestData {} - impl CommData for TestData { + impl com_api_concept::CommData for TestData { const ID: &'static str = "TestData"; } - // Test Consumer implementation - #[derive(Debug)] - struct TestConsumer; - - impl Consumer for TestConsumer { - fn new(_: R::ConsumerInfo) -> Self { - TestConsumer - } - } - - // Test Producer implementation - #[derive(Debug)] - struct TestProducer; - - impl Producer for TestProducer { - type Interface = TestVehicleInterface; - type OfferedProducer = TestOfferedProducer; - - fn offer(self) -> Result { - Ok(TestOfferedProducer) - } - - fn new(_: R::ProviderInfo) -> Result - where - Self: Sized, - { - Ok(TestProducer) - } + // Builds a ProxyInstanceManager + fn make_proxy_instance(interface_id: &'static str) -> ProxyInstanceManager { + // SAFETY: HandleType is a ZST; zeroed() produces a valid value. + let handle: HandleType = unsafe { core::mem::zeroed() }; + let native_proxy = NativeProxyBase::::new(interface_id, &handle) + .expect("MockFFIBridge::create_proxy should not fail"); + ProxyInstanceManager(Arc::new(native_proxy)) } - // Test OfferedProducer implementation - struct TestOfferedProducer; - - impl OfferedProducer for TestOfferedProducer { - type Interface = TestVehicleInterface; - type Producer = TestProducer; - - fn unoffer(self) -> Result { - Ok(TestProducer) + // Builds a `LolaConsumerInfo` backed by a mock handle container. + fn make_instance_info() -> LolaConsumerInfo { + LolaConsumerInfo:: { + handle_container: MockFFIBridge::make_handle_container(), + handle_index: 0, + interface_id: "TestInterface", + _marker: PhantomData, } } - // Test Interface implementation - struct TestVehicleInterface; - - impl Interface for TestVehicleInterface { - const INTERFACE_ID: &'static str = "TestVehicleInterface"; - type Consumer = TestConsumer; - type Producer = TestProducer; - } - #[test] - fn test_comm_data_trait() { - // Verify TestData implements CommData - assert_eq!(TestData::ID, "TestData"); - let _data = TestData(42); + fn test_lola_consumer_info() { + let instance_info = make_instance_info(); + assert!(instance_info.get_handle().is_none()); } - #[test] - fn test_discovery_state_data_creation() { - // Test that DiscoveryStateData can be created with None values - let state = super::DiscoveryStateData { handles: None }; - assert!(state.handles.is_none()); - } - - #[test] - fn test_discovery_state_data_debug() { - // Test that DiscoveryStateData debug formatting works - let state = super::DiscoveryStateData { handles: None }; - let debug_string = format!("{:?}", state); - assert!(debug_string.contains("DiscoveryStateData")); - assert!(debug_string.contains("handles")); + fn test_sample() { + //this is for cleanup of thread local state. + let _guard = MockFFIBridgeGuard; + MockFFIBridge::set_sample_backing::(TestData { value: 42 }); + let sample = Sample:: { + id: 1, + inner: LolaBinding:: { + data: ManuallyDrop::new(unsafe { + core::mem::zeroed::>() + }), + _bridge: PhantomData, + }, + }; + assert_eq!(sample.id, 1); + assert_eq!(sample.get_data().value, 42); } #[test] - fn test_sample_consumer_discovery_creation() { - // Test that LolaConsumerDiscovery can be created with valid interface - let instance_specifier = com_api_concept::InstanceSpecifier::new("/test/vehicle") - .expect("Failed to create instance specifier"); - - let _discovery = super::LolaConsumerDiscovery::::new( - &super::super::LolaRuntimeImpl {}, - instance_specifier, + fn test_subscribable_impl() { + let subscribable = SubscribableImpl:: { + identifier: "TestEvent", + instance_info: make_instance_info(), + proxy_instance: make_proxy_instance("TestInterface"), + data: PhantomData, + }; + assert!( + subscribable.subscribe(3).is_ok(), + "subscribe should succeed when the mock returns a non-null proxy event sentinel" ); - // Discovery created successfully } #[test] - #[ignore] // Requires LoLa runtime initialization and configuration - fn test_get_available_instances_method_exists() { - // Test that get_available_instances() can be called on ServiceDiscovery trait - let instance_specifier = com_api_concept::InstanceSpecifier::new("/test/vehicle") - .expect("Failed to create instance specifier"); - - let discovery = super::LolaConsumerDiscovery::::new( - &super::super::LolaRuntimeImpl {}, - instance_specifier, + fn test_subscriber_impl() { + let subscriber = SubscriberImpl:: { + event: ProxyEventManager { + event: MockFFIBridge::make_proxy_event_ptr(), + in_progress: AtomicBool::new(false), + }, + event_id: "TestEvent", + max_num_samples: 10, + instance_info: make_instance_info(), + waker_storage: Arc::new(AtomicWaker::new()), + async_init_status: std::sync::OnceLock::new(), + _proxy: make_proxy_instance("TestInterface"), + _phantom: PhantomData, + }; + let mut sample_container = SampleContainer::new(5); + let count = subscriber + .try_receive(&mut sample_container, 5) + .expect("try_receive should succeed with a valid mock event"); + assert_eq!( + count, 0, + "no samples should be returned by try_receive when the mock event is empty" ); - - // Call get_available_instances - verifies the method exists and is callable - let _result = discovery.get_available_instances(); + let mut event_guard = subscriber.event.get_proxy_event(); + subscriber + .init_async_receive(&mut event_guard) + .expect("init_async_receive should succeed"); + drop(event_guard); + drop(subscriber.receive(sample_container, 5, 5)); } } diff --git a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs index 5d25bb8cd..721c81111 100644 --- a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs +++ b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/producer.rs @@ -47,19 +47,17 @@ use bridge_ffi_rs::*; use crate::LolaRuntimeImpl; #[derive(Clone, Debug)] -pub struct LolaProviderInfo { +pub struct LolaProviderInfo { pub instance_specifier: InstanceSpecifier, pub interface_id: &'static str, - pub skeleton_handle: SkeletonInstanceManager, + pub skeleton_handle: SkeletonInstanceManager, } -impl ProviderInfo for LolaProviderInfo { +impl ProviderInfo for LolaProviderInfo { fn offer_service(&self) -> Result<()> { //SAFETY: it is safe as we are passing valid skeleton handle to offer service // the skeleton handle is created during building the provider info instance - let status = unsafe { - bridge_ffi_rs::skeleton_offer_service(self.skeleton_handle.0.handle.as_ptr()) - }; + let status = unsafe { B::skeleton_offer_service(self.skeleton_handle.0.handle.as_ptr()) }; if !status { return Err(Error::ServiceError(ServiceFailedReason::OfferServiceFailed)); } @@ -69,9 +67,7 @@ impl ProviderInfo for LolaProviderInfo { fn stop_offer_service(&self) -> Result<()> { //SAFETY: it is safe as we are passing valid skeleton handle to stop offer service // the skeleton handle is created during building the provider info instance - unsafe { - bridge_ffi_rs::skeleton_stop_offer_service(self.skeleton_handle.0.handle.as_ptr()) - }; + unsafe { B::skeleton_stop_offer_service(self.skeleton_handle.0.handle.as_ptr()) }; Ok(()) } } @@ -81,14 +77,15 @@ impl ProviderInfo for LolaProviderInfo { /// Safe to move between SampleMaybeUninit and SampleMut because /// the Drop impl guarantees cleanup exactly once. #[derive(Debug)] -pub struct AllocateePtrWrapper +pub struct AllocateePtrWrapper where T: CommData + Debug, { pub inner: ManuallyDrop>, + pub _bridge: PhantomData, } -impl Drop for AllocateePtrWrapper +impl Drop for AllocateePtrWrapper where T: CommData + Debug, { @@ -97,7 +94,7 @@ where //SampleAllocateePtr created by FFI unsafe { let mut allocatee_ptr = ManuallyDrop::take(&mut self.inner); - bridge_ffi_rs::delete_allocatee_ptr( + B::delete_allocatee_ptr( std::ptr::from_mut(&mut allocatee_ptr) as *mut std::ffi::c_void, T::ID, ); @@ -105,7 +102,8 @@ where } } -impl AsRef> for AllocateePtrWrapper +impl AsRef> + for AllocateePtrWrapper where T: CommData + Debug, { @@ -115,16 +113,16 @@ where } #[derive(Debug)] -pub struct SampleMut<'a, T> +pub struct SampleMut<'a, T, B: FFIBridge> where T: CommData + Debug, { skeleton_event: NativeSkeletonEventBase, - allocatee_ptr: AllocateePtrWrapper, + allocatee_ptr: AllocateePtrWrapper, lifetime: PhantomData<&'a T>, } -impl<'a, T> SampleMut<'a, T> +impl<'a, T, B: FFIBridge> SampleMut<'a, T, B> where T: CommData + Debug, { @@ -132,7 +130,7 @@ where //SAFETY: allocatee_ptr is valid which is created using get_allocatee_ptr() and // it will be again type casted to T type pointer in cpp side so valid to send as void pointer unsafe { - let data_ptr = bridge_ffi_rs::get_allocatee_data_ptr( + let data_ptr = B::get_allocatee_data_ptr( std::ptr::from_ref(&(*self.allocatee_ptr.inner)) as *const std::ffi::c_void, T::ID, ); @@ -144,7 +142,7 @@ where //SAFETY: allocatee_ptr is valid which is created using get_allocatee_ptr() and // it will be again type casted to T type pointer in cpp side so valid to send as void pointer unsafe { - let data_ptr = bridge_ffi_rs::get_allocatee_data_ptr( + let data_ptr = B::get_allocatee_data_ptr( std::ptr::from_mut(&mut (*self.allocatee_ptr.inner)) as *mut std::ffi::c_void, T::ID, ); @@ -153,7 +151,7 @@ where } } -impl<'a, T> Deref for SampleMut<'a, T> +impl<'a, T, B: FFIBridge> Deref for SampleMut<'a, T, B> where T: CommData + Debug, { @@ -165,7 +163,7 @@ where } } -impl<'a, T> DerefMut for SampleMut<'a, T> +impl<'a, T, B: FFIBridge> DerefMut for SampleMut<'a, T, B> where T: CommData + Debug, { @@ -175,7 +173,7 @@ where } } -impl<'a, T> com_api_concept::SampleMut for SampleMut<'a, T> +impl<'a, T, B: FFIBridge> com_api_concept::SampleMut for SampleMut<'a, T, B> where T: CommData + Debug, { @@ -185,7 +183,7 @@ where // We've taken ownership via self (consumed, not borrowed), and // FFI call will complete before drop run on AllocateePtrWrapper and NativeSkeletonEventBase let status = unsafe { - bridge_ffi_rs::skeleton_event_send_sample_allocatee( + B::skeleton_event_send_sample_allocatee( self.skeleton_event.skeleton_event_ptr.as_ptr(), T::ID, std::ptr::from_ref(self.allocatee_ptr.as_ref()) as *const std::ffi::c_void, @@ -199,16 +197,16 @@ where } #[derive(Debug)] -pub struct SampleMaybeUninit<'a, T> +pub struct SampleMaybeUninit<'a, T, B: FFIBridge> where T: CommData + Debug, { skeleton_event: NativeSkeletonEventBase, - allocatee_ptr: AllocateePtrWrapper, + allocatee_ptr: AllocateePtrWrapper, lifetime: PhantomData<&'a T>, } -impl<'a, T> SampleMaybeUninit<'a, T> +impl<'a, T, B: FFIBridge> SampleMaybeUninit<'a, T, B> where T: CommData + Debug, { @@ -216,7 +214,7 @@ where //SAFETY: allocatee_ptr is valid which is created using get_allocatee_ptr() and // it will be again type casted to T type pointer in cpp side so valid to send as void pointer let data_ptr = unsafe { - bridge_ffi_rs::get_allocatee_data_ptr( + B::get_allocatee_data_ptr( std::ptr::from_ref(self.allocatee_ptr.as_ref()) as *const std::ffi::c_void, T::ID, ) as *mut core::mem::MaybeUninit @@ -225,13 +223,13 @@ where } } -impl<'a, T> com_api_concept::SampleMaybeUninit for SampleMaybeUninit<'a, T> +impl<'a, T, B: FFIBridge> com_api_concept::SampleMaybeUninit for SampleMaybeUninit<'a, T, B> where T: CommData + Debug, { - type SampleMut = SampleMut<'a, T>; + type SampleMut = SampleMut<'a, T, B>; - fn write(mut self, val: T) -> SampleMut<'a, T> { + fn write(mut self, val: T) -> SampleMut<'a, T, B> { let data_ptr = self .get_allocatee_data_ptr() .expect("Allocatee data pointer is null"); @@ -247,7 +245,7 @@ where } } - unsafe fn assume_init(self) -> SampleMut<'a, T> { + unsafe fn assume_init(self) -> SampleMut<'a, T, B> { SampleMut { skeleton_event: self.skeleton_event, allocatee_ptr: self.allocatee_ptr, @@ -256,7 +254,7 @@ where } } -impl<'a, T> AsMut> for SampleMaybeUninit<'a, T> +impl<'a, T, B: FFIBridge> AsMut> for SampleMaybeUninit<'a, T, B> where T: CommData + Debug, { @@ -268,15 +266,15 @@ where /// Manages the lifetime of the native skeleton instance, user should clone this to share between threads /// Always use this struct to manage the skeleton instance pointer -pub struct SkeletonInstanceManager(pub Arc); +pub struct SkeletonInstanceManager(pub Arc>); -impl Clone for SkeletonInstanceManager { +impl Clone for SkeletonInstanceManager { fn clone(&self) -> Self { Self(Arc::clone(&self.0)) } } -impl std::fmt::Debug for SkeletonInstanceManager { +impl std::fmt::Debug for SkeletonInstanceManager { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("SkeletonInstanceManager").finish() } @@ -290,34 +288,39 @@ impl std::fmt::Debug for SkeletonInstanceManager { /// If any additional method is required to be added, ensure that the safety of the skeleton handle is maintained /// And the lifetime is managed correctly /// As it has Send and Sync unsafe impls, it must not expose any mutable access to the skeleton handle -pub struct NativeSkeletonHandle { +pub struct NativeSkeletonHandle { handle: NonNull, + _marker: PhantomData, } //SAFETY: NativeSkeletonHandle is safe to share between threads because: // It is created by FFI call and no mutable access is provided to the underlying skeleton handle // Access is controlled through Arc which provides atomic reference counting // The skeleton lifetime is managed safely through new and Drop -unsafe impl Sync for NativeSkeletonHandle {} -unsafe impl Send for NativeSkeletonHandle {} +unsafe impl Sync for NativeSkeletonHandle {} +unsafe impl Send for NativeSkeletonHandle {} -impl NativeSkeletonHandle { +impl NativeSkeletonHandle { pub fn new(interface_id: &str, instance_specifier: &mw_com::InstanceSpecifier) -> Result { //SAFETY: It is safe as we are passing valid type id and instance specifier to create skeleton let raw_handle = - unsafe { bridge_ffi_rs::create_skeleton(interface_id, instance_specifier.as_native()) }; - let handle = std::ptr::NonNull::new(raw_handle) - .ok_or(Error::ProducerError(ProducerFailedReason::SkeletonCreationFailed))?; - Ok(Self { handle }) + unsafe { B::create_skeleton(interface_id, instance_specifier.as_native()) }; + let handle = std::ptr::NonNull::new(raw_handle).ok_or(Error::ProducerError( + ProducerFailedReason::SkeletonCreationFailed, + ))?; + Ok(Self { + handle, + _marker: PhantomData, + }) } } -impl Drop for NativeSkeletonHandle { +impl Drop for NativeSkeletonHandle { fn drop(&mut self) { //SAFETY: It is safe as we are passing valid skeleton handle to destroy skeleton // the handle was created using create_skeleton unsafe { - bridge_ffi_rs::destroy_skeleton(self.handle.as_ptr()); + B::destroy_skeleton(self.handle.as_ptr()); } } } @@ -336,11 +339,14 @@ pub struct NativeSkeletonEventBase { unsafe impl Send for NativeSkeletonEventBase {} impl NativeSkeletonEventBase { - pub fn new(instance_info: &LolaProviderInfo, identifier: &str) -> Result { + pub fn new( + instance_info: &LolaProviderInfo, + identifier: &str, + ) -> Result { //SAFETY: It is safe as we are passing valid skeleton handle and interface id to get event // skeleton handle is created during producer offer call let raw_event_ptr = unsafe { - bridge_ffi_rs::get_event_from_skeleton( + B::get_event_from_skeleton( instance_info.skeleton_handle.0.handle.as_ptr(), instance_info.interface_id, identifier, @@ -368,18 +374,18 @@ impl std::fmt::Debug for NativeSkeletonEventBase { } #[derive(Debug)] -pub struct Publisher { +pub struct Publisher { skeleton_event: NativeSkeletonEventBase, _data: PhantomData, - _skeleton_instance: SkeletonInstanceManager, + _skeleton_instance: SkeletonInstanceManager, } -impl com_api_concept::Publisher for Publisher +impl com_api_concept::Publisher> for Publisher where T: CommData + Debug, { type SampleMaybeUninit<'a> - = SampleMaybeUninit<'a, T> + = SampleMaybeUninit<'a, T, B> where Self: 'a; @@ -392,7 +398,7 @@ where let allocatee_ptr = unsafe { let mut sample = core::mem::MaybeUninit::>::uninit(); - let status = bridge_ffi_rs::get_allocatee_ptr( + let status = B::get_allocatee_ptr( self.skeleton_event.skeleton_event_ptr.as_ptr(), sample.as_mut_ptr() as *mut std::ffi::c_void, T::ID, @@ -409,13 +415,14 @@ where skeleton_event: self.skeleton_event.clone(), allocatee_ptr: AllocateePtrWrapper { inner: ManuallyDrop::new(allocatee_ptr), + _bridge: PhantomData, }, lifetime: PhantomData, }) } - fn new(identifier: &str, instance_info: LolaProviderInfo) -> Result { - let skeleton_event = NativeSkeletonEventBase::new(&instance_info, identifier)?; + fn new(identifier: &str, instance_info: LolaProviderInfo) -> Result { + let skeleton_event = NativeSkeletonEventBase::new::(&instance_info, identifier)?; Ok(Self { skeleton_event, _data: PhantomData, @@ -424,24 +431,31 @@ where } } -pub struct LolaProducerBuilder { +pub struct LolaProducerBuilder { pub instance_specifier: InstanceSpecifier, pub _interface: PhantomData, + pub _bridge: PhantomData, } -impl LolaProducerBuilder { - pub fn new(_runtime: &LolaRuntimeImpl, instance_specifier: InstanceSpecifier) -> Self { +impl LolaProducerBuilder { + pub fn new(_runtime: &LolaRuntimeImpl, instance_specifier: InstanceSpecifier) -> Self { Self { instance_specifier, _interface: PhantomData, + _bridge: PhantomData, } } } -impl ProducerBuilder for LolaProducerBuilder {} +impl ProducerBuilder> + for LolaProducerBuilder +{ +} -impl Builder> for LolaProducerBuilder { - fn build(self) -> Result> { +impl Builder>> + for LolaProducerBuilder +{ + fn build(self) -> Result>> { //Once FFI layer error handling is in place (SWP-253124), we should convert this error to a proper FFI error instead of using map_err here let instance_specifier_runtime = mw_com::InstanceSpecifier::try_from( self.instance_specifier.as_ref(), @@ -449,11 +463,11 @@ impl Builder> for LolaProducerBuilder .map_err(|_| Error::ProducerError(ProducerFailedReason::InstanceSpecifierInvalid))?; let skeleton_handle = - NativeSkeletonHandle::new(I::INTERFACE_ID, &instance_specifier_runtime)?; + NativeSkeletonHandle::::new(I::INTERFACE_ID, &instance_specifier_runtime)?; let instance_info = LolaProviderInfo { instance_specifier: self.instance_specifier, interface_id: I::INTERFACE_ID, - skeleton_handle: SkeletonInstanceManager(Arc::new(skeleton_handle)), + skeleton_handle: SkeletonInstanceManager::(Arc::new(skeleton_handle)), }; I::Producer::new(instance_info) @@ -462,22 +476,73 @@ impl Builder> for LolaProducerBuilder #[cfg(test)] mod test { - use com_api_concept::CommData; - use std::fmt::Debug; - - #[derive(Debug, Clone, Copy)] + use super::*; + use bridge_ffi_mock::{MockFFIBridge, MockFFIBridgeGuard}; + use com_api_concept::InstanceSpecifier; + // Bring trait methods into scope without shadowing local struct names. + use com_api_concept::Publisher as _; + use com_api_concept::SampleMaybeUninit as _; + use com_api_concept::SampleMut as _; + + #[derive(Debug)] #[repr(C)] - struct TestData(u32); + struct TestData { + value: i32, + } unsafe impl com_api_concept::Reloc for TestData {} - impl CommData for TestData { + impl com_api_concept::CommData for TestData { const ID: &'static str = "TestData"; } + // Creates a `NativeSkeletonHandle` . + fn make_skeleton_handle() -> NativeSkeletonHandle { + let spec = mw_com::InstanceSpecifier::try_from("/test_instance") + .expect("valid instance specifier"); + NativeSkeletonHandle::::new("", &spec) + .expect("MockFFIBridge::create_skeleton should not fail") + } + + // Creates a `LolaProviderInfo` with a valid heap-backed skeleton handle. + fn make_provider_info(interface_id: &'static str) -> LolaProviderInfo { + LolaProviderInfo { + instance_specifier: InstanceSpecifier::new("/test_instance") + .expect("valid instance specifier"), + interface_id, + skeleton_handle: SkeletonInstanceManager(Arc::new(make_skeleton_handle())), + } + } + + #[test] + fn test_provider_info_offer_service() { + let provider_info = make_provider_info("TestInterface"); + assert!( + provider_info.offer_service().is_ok(), + "offer_service should succeed when the mock returns a non-null skeleton sentinel" + ); + assert!( + provider_info.stop_offer_service().is_ok(), + "stop_offer_service should always succeed in the mock" + ); + } + #[test] - #[ignore] // Test will be update with Ticket-242140 - fn send_stuff() { - let _test_data = TestData(42); + fn test_publisher_allocate_and_send() { + //this is for cleanup of thread local state. + let _guard = MockFFIBridgeGuard; + MockFFIBridge::set_alloc_backing::(); + let instance_info = make_provider_info("TestData"); + let publisher: Publisher = + Publisher::::new("TestEvent", instance_info) + .expect("Failed to create publisher"); + let sample = publisher.allocate().expect("Failed to allocate sample"); + let test_data = TestData { value: 42 }; + let sample_mut = sample.write(test_data); + assert_eq!( + sample_mut.value, 42, + "written value should be readable back through SampleMut before sending" + ); + sample_mut.send().expect("Failed to send sample"); } } diff --git a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/runtime.rs b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/runtime.rs index 14b7220af..0eea1d0a7 100644 --- a/score/mw/com/impl/rust/com-api/com-api-runtime-lola/runtime.rs +++ b/score/mw/com/impl/rust/com-api/com-api-runtime-lola/runtime.rs @@ -23,15 +23,20 @@ use com_api_concept::{ Builder, CommData, FindServiceSpecifier, InstanceSpecifier, Interface, Result, Runtime, }; -pub struct LolaRuntimeImpl {} +use bridge_ffi_rs::FFIBridge; +use bridge_ffi_lola::LolaFFIBridge; -impl Runtime for LolaRuntimeImpl { - type ServiceDiscovery = LolaConsumerDiscovery; - type Subscriber = SubscribableImpl; - type ProducerBuilder = LolaProducerBuilder; - type Publisher = Publisher; - type ProviderInfo = LolaProviderInfo; - type ConsumerInfo = LolaConsumerInfo; +pub struct LolaRuntimeImpl { + _marker: PhantomData, +} + +impl Runtime for LolaRuntimeImpl { + type ServiceDiscovery = LolaConsumerDiscovery; + type Subscriber = SubscribableImpl; + type ProducerBuilder = LolaProducerBuilder; + type Publisher = Publisher; + type ProviderInfo = LolaProviderInfo; + type ConsumerInfo = LolaConsumerInfo; fn find_service( &self, @@ -46,6 +51,7 @@ impl Runtime for LolaRuntimeImpl { FindServiceSpecifier::Specific(spec) => spec, }, _interface: PhantomData, + _bridge: PhantomData, } } @@ -57,33 +63,39 @@ impl Runtime for LolaRuntimeImpl { } } -pub struct RuntimeBuilderImpl { +pub struct RuntimeBuilderImpl { config_path: Option, + _marker: PhantomData, } -impl Builder for RuntimeBuilderImpl { - fn build(self) -> Result { +impl Builder> for RuntimeBuilderImpl { + fn build(self) -> Result> { mw_com::initialize(self.config_path.as_deref()); - Ok(LolaRuntimeImpl {}) + Ok(LolaRuntimeImpl { + _marker: PhantomData, + }) } } -impl com_api_concept::RuntimeBuilder for RuntimeBuilderImpl { +impl com_api_concept::RuntimeBuilder> for RuntimeBuilderImpl { fn load_config(&mut self, config: &Path) -> &mut Self { self.config_path = Some(config.to_path_buf()); self } } -impl Default for RuntimeBuilderImpl { +impl Default for RuntimeBuilderImpl { fn default() -> Self { Self::new() } } -impl RuntimeBuilderImpl { +impl RuntimeBuilderImpl { /// Creates a new instance of the default implementation of the com layer pub fn new() -> Self { - Self { config_path: None } + Self { + config_path: None, + _marker: PhantomData, + } } } diff --git a/score/mw/com/test/basic_rust_api/consumer_async_apis/consumer_app.rs b/score/mw/com/test/basic_rust_api/consumer_async_apis/consumer_app.rs index 309e926ca..c3a0459ed 100644 --- a/score/mw/com/test/basic_rust_api/consumer_async_apis/consumer_app.rs +++ b/score/mw/com/test/basic_rust_api/consumer_async_apis/consumer_app.rs @@ -55,7 +55,7 @@ async fn async_main() { ); // Initialise the Lola runtime. - let mut runtime_builder = LolaRuntimeBuilderImpl::new(); + let mut runtime_builder: LolaRuntimeBuilderImpl = LolaRuntimeBuilderImpl::new(); runtime_builder.load_config(Path::new(CONFIG_PATH)); let runtime = runtime_builder .build() diff --git a/score/mw/com/test/basic_rust_api/consumer_sync_apis/consumer_app.rs b/score/mw/com/test/basic_rust_api/consumer_sync_apis/consumer_app.rs index 0e93fe9c5..acf046c28 100644 --- a/score/mw/com/test/basic_rust_api/consumer_sync_apis/consumer_app.rs +++ b/score/mw/com/test/basic_rust_api/consumer_sync_apis/consumer_app.rs @@ -317,7 +317,7 @@ fn main() { let num_cycles = args.num_cycles; // Initialise the Lola runtime. - let mut runtime_builder = LolaRuntimeBuilderImpl::new(); + let mut runtime_builder: LolaRuntimeBuilderImpl = LolaRuntimeBuilderImpl::new(); runtime_builder.load_config(Path::new(CONFIG_PATH)); let runtime = runtime_builder .build() diff --git a/score/mw/com/test/basic_rust_api/producer_app/producer_app.rs b/score/mw/com/test/basic_rust_api/producer_app/producer_app.rs index f9c1eee60..bf86ac299 100644 --- a/score/mw/com/test/basic_rust_api/producer_app/producer_app.rs +++ b/score/mw/com/test/basic_rust_api/producer_app/producer_app.rs @@ -199,7 +199,7 @@ fn main() { let num_cycles = args.num_cycles; // Initialise the Lola runtime. - let mut runtime_builder = LolaRuntimeBuilderImpl::new(); + let mut runtime_builder: LolaRuntimeBuilderImpl = LolaRuntimeBuilderImpl::new(); runtime_builder.load_config(Path::new(CONFIG_PATH)); let runtime = runtime_builder .build()