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}