use super::server::ServerComponent;
use super::types::{SemanticVersion, StringView, UID};
use std::ptr::NonNull;
pub trait OmpComponentHandle: Sized + Copy {
const UID: UID;
unsafe fn from_raw(ptr: NonNull<ServerComponent>) -> Self;
fn as_raw(&self) -> NonNull<ServerComponent>;
}
const SLOT_COMPONENT_NAME: usize = 6;
const SLOT_COMPONENT_VERSION: usize = 8;
#[cfg(not(target_env = "msvc"))]
type ComponentNameFn =
unsafe extern "C" fn(*mut ServerComponent, *mut StringView) -> *mut StringView;
#[cfg(target_env = "msvc")]
type ComponentNameFn =
unsafe extern "thiscall" fn(*mut ServerComponent, *mut StringView) -> *mut StringView;
#[cfg(not(target_env = "msvc"))]
type ComponentVersionFn =
unsafe extern "C" fn(*mut ServerComponent, *mut SemanticVersion) -> *mut SemanticVersion;
#[cfg(target_env = "msvc")]
type ComponentVersionFn =
unsafe extern "thiscall" fn(*mut ServerComponent, *mut SemanticVersion) -> *mut SemanticVersion;
pub fn component_name<T: OmpComponentHandle>(c: &T) -> Option<String> {
let raw = c.as_raw().as_ptr();
let (_, slot) =
unsafe { super::vtable::secondary_call_target(raw.cast::<u8>(), 0, SLOT_COMPONENT_NAME)? };
let f: ComponentNameFn = unsafe { std::mem::transmute(slot) };
let mut sv = StringView {
data: std::ptr::null(),
len: 0,
};
unsafe { f(raw, &raw mut sv) };
if sv.data.is_null() || sv.len == 0 {
return None;
}
let bytes = unsafe { std::slice::from_raw_parts(sv.data, sv.len) };
std::str::from_utf8(bytes).ok().map(String::from)
}
pub fn component_version<T: OmpComponentHandle>(c: &T) -> Option<SemanticVersion> {
let raw = c.as_raw().as_ptr();
let (_, slot) = unsafe {
super::vtable::secondary_call_target(raw.cast::<u8>(), 0, SLOT_COMPONENT_VERSION)?
};
let f: ComponentVersionFn = unsafe { std::mem::transmute(slot) };
let mut version = SemanticVersion::new(0, 0, 0);
unsafe { f(raw, &raw mut version) };
Some(version)
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
static TEST_LOCK: Mutex<()> = Mutex::new(());
static MOCK_VTABLE: std::sync::OnceLock<[usize; 16]> = std::sync::OnceLock::new();
fn mock_vtable() -> &'static [usize; 16] {
MOCK_VTABLE.get_or_init(|| {
let mut v = [unused as *const () as usize; 16];
v[SLOT_COMPONENT_NAME] = mock_name as *const () as usize;
v[SLOT_COMPONENT_VERSION] = mock_version as *const () as usize;
v
})
}
#[cfg(not(target_env = "msvc"))]
unsafe extern "C" fn unused() {}
#[cfg(target_env = "msvc")]
unsafe extern "thiscall" fn unused() {}
static MOCK_NAME_BYTES: &[u8] = b"test-comp";
#[cfg(not(target_env = "msvc"))]
unsafe extern "C" fn mock_name(
_this: *mut ServerComponent,
out: *mut StringView,
) -> *mut StringView {
unsafe {
*out = StringView {
data: MOCK_NAME_BYTES.as_ptr(),
len: MOCK_NAME_BYTES.len(),
};
}
out
}
#[cfg(target_env = "msvc")]
unsafe extern "thiscall" fn mock_name(
_this: *mut ServerComponent,
out: *mut StringView,
) -> *mut StringView {
unsafe {
*out = StringView {
data: MOCK_NAME_BYTES.as_ptr(),
len: MOCK_NAME_BYTES.len(),
};
}
out
}
#[cfg(not(target_env = "msvc"))]
unsafe extern "C" fn mock_version(
_this: *mut ServerComponent,
out: *mut SemanticVersion,
) -> *mut SemanticVersion {
unsafe {
*out = SemanticVersion::new(2, 7, 3);
}
out
}
#[cfg(target_env = "msvc")]
unsafe extern "thiscall" fn mock_version(
_this: *mut ServerComponent,
out: *mut SemanticVersion,
) -> *mut SemanticVersion {
unsafe {
*out = SemanticVersion::new(2, 7, 3);
}
out
}
#[derive(Debug, Clone, Copy)]
struct DummyComponent {
ptr: NonNull<ServerComponent>,
}
impl OmpComponentHandle for DummyComponent {
const UID: UID = 0xDEAD_BEEF_CAFE_BABE;
unsafe fn from_raw(ptr: NonNull<ServerComponent>) -> Self {
Self { ptr }
}
fn as_raw(&self) -> NonNull<ServerComponent> {
self.ptr
}
}
fn make_mock_component() -> usize {
mock_vtable().as_ptr() as usize
}
#[test]
fn component_name_reads_slot_6_and_returns_string() {
let _g = TEST_LOCK.lock().unwrap();
let buf = make_mock_component();
let raw = (&raw const buf).cast::<ServerComponent>().cast_mut();
let nn = NonNull::new(raw).unwrap();
let comp = unsafe { DummyComponent::from_raw(nn) };
let name = component_name(&comp);
assert_eq!(name.as_deref(), Some("test-comp"));
}
#[test]
fn component_version_reads_slot_8_and_returns_semver() {
let _g = TEST_LOCK.lock().unwrap();
let buf = make_mock_component();
let raw = (&raw const buf).cast::<ServerComponent>().cast_mut();
let nn = NonNull::new(raw).unwrap();
let comp = unsafe { DummyComponent::from_raw(nn) };
let v = component_version(&comp).unwrap();
assert_eq!((v.major, v.minor, v.patch), (2, 7, 3));
}
#[test]
fn dummy_component_uid_is_consistent() {
assert_eq!(DummyComponent::UID, 0xDEAD_BEEF_CAFE_BABE);
}
}