Skip to main content

hotpatch_rs/
abi.rs

1use core::ffi::{c_char, c_void};
2use std::ffi::CStr;
3
4use crate::{HotpatchError, Result};
5
6pub const ABI_MAGIC_V1: u32 = 0x48505431;
7pub const ABI_VERSION_V1: u16 = 1;
8pub const MODULE_EXPORT_SYMBOL_V1: &[u8] = b"HOTPATCH_MODULE_V1\0";
9
10pub type PatchFn = unsafe extern "C" fn(
11    host_state: *mut c_void,
12    input: *const c_void,
13    output: *mut c_void,
14) -> i32;
15
16pub type LifecycleFn = unsafe extern "C" fn(host_state: *mut c_void) -> i32;
17
18#[repr(C)]
19#[derive(Clone, Copy)]
20pub struct PatchEntryV1 {
21    pub name: *const c_char,
22    pub func: PatchFn,
23    pub flags: u32,
24    pub reserved: u32,
25}
26unsafe impl Sync for PatchEntryV1 {}
27
28#[repr(C)]
29#[derive(Clone, Copy)]
30pub struct PatchModuleV1 {
31    pub abi_magic: u32,
32    pub abi_version: u16,
33    pub struct_size: u16,
34    pub module_version: u64,
35    pub name: *const c_char,
36    pub entry_count: u32,
37    pub reserved: u32,
38    pub entries: *const PatchEntryV1,
39    pub on_load: Option<LifecycleFn>,
40    pub on_unload: Option<LifecycleFn>,
41}
42unsafe impl Sync for PatchModuleV1 {}
43
44impl PatchModuleV1 {
45    pub fn validate_header(&self) -> Result<()> {
46        if self.abi_magic != ABI_MAGIC_V1 {
47            return Err(HotpatchError::AbiMismatch {
48                expected_magic: ABI_MAGIC_V1,
49                got_magic: self.abi_magic,
50                expected_version: ABI_VERSION_V1,
51                got_version: self.abi_version,
52            });
53        }
54        if self.abi_version != ABI_VERSION_V1 {
55            return Err(HotpatchError::AbiMismatch {
56                expected_magic: ABI_MAGIC_V1,
57                got_magic: self.abi_magic,
58                expected_version: ABI_VERSION_V1,
59                got_version: self.abi_version,
60            });
61        }
62        if usize::from(self.struct_size) < core::mem::size_of::<PatchModuleV1>() {
63            return Err(HotpatchError::InvalidModule(
64                "module struct size is smaller than expected v1 layout".into(),
65            ));
66        }
67        if self.name.is_null() {
68            return Err(HotpatchError::InvalidModule("module name pointer is null".into()));
69        }
70        if self.entry_count > 0 && self.entries.is_null() {
71            return Err(HotpatchError::InvalidModule(
72                "module declares entries but entries pointer is null".into(),
73            ));
74        }
75        Ok(())
76    }
77
78    pub unsafe fn module_name(&self) -> Result<&str> {
79        c_ptr_to_str(self.name)
80    }
81
82    pub unsafe fn entries_slice(&self) -> &[PatchEntryV1] {
83        if self.entry_count == 0 {
84            &[]
85        } else {
86            core::slice::from_raw_parts(self.entries, self.entry_count as usize)
87        }
88    }
89}
90
91pub unsafe fn c_ptr_to_str<'a>(ptr: *const c_char) -> Result<&'a str> {
92    if ptr.is_null() {
93        return Err(HotpatchError::InvalidModule("null C string pointer".into()));
94    }
95    let value = CStr::from_ptr(ptr)
96        .to_str()
97        .map_err(|_| HotpatchError::InvalidModule("C string is not valid UTF-8".into()))?;
98    Ok(value)
99}