#![allow(non_snake_case)] use std::marker::PhantomData; mod bindings; use bindings::*; #[derive(Debug, Clone)] pub enum SupportedFsvVersion { V1_40_14, // 1.40.14 V1_40_13, // 1.40.13 } impl SupportedFsvVersion { fn as_str(&self) -> &'static str { match self { Self::V1_40_14 => "1.40.14", Self::V1_40_13 => "1.40.13", } } } #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] LibLoading(#[from] libloading::Error), #[error("Symbol missing: {0}")] SymbolMissing(&'static str), } /// Macro to generate a function that implements a call to an external function in BINDINGS macro_rules! ssv_function { ($binding:ty, $func_name:ident, {$($arg_name:ident: $arg_type:ty),*}) => { /// # Safety /// This function is unsafe because it calls an external function through FFI. /// The caller must ensure that the provided arguments are valid and that the /// external function is safe to call. pub unsafe fn $func_name(&self, $($arg_name: $arg_type),*) -> Result { let symbol_name = match stringify!($binding) .split(&[' ', ':']) .last() { Some(name) => name, None => return Err(Error::SymbolMissing(stringify!($binding))), }; let func_struct: libloading::Symbol<'_, $binding> = unsafe { self.library.get(symbol_name.as_bytes())? }; let func = match *func_struct { Some(func) => func, None => return Err(Error::SymbolMissing(stringify!($binding))), }; Ok(func($($arg_name),*)) } }; } /// `sealed::Sealed` trait is used to prevent external crates from implementing the LibVersion trait. mod sealed { pub trait Sealed {}} /// Wrapper around the SESAM-VITALE library /// This struct is responsible for loading the library and providing an interface to call its functions. /// The library is loaded at creation and kept in memory until the struct is dropped. pub trait SSVLibraryCommon { fn new(path: &str) -> Result where Self: Sized; } pub trait SSVLibraryVersion: sealed::Sealed {} pub struct V1_40_13 {} impl sealed::Sealed for V1_40_13 {} impl SSVLibraryVersion for V1_40_13 {} pub struct V1_40_14 {} impl sealed::Sealed for V1_40_14 {} impl SSVLibraryVersion for V1_40_14 {} pub struct SSVLibrary { _version: PhantomData, library: libloading::Library, } impl SSVLibraryCommon for SSVLibrary { fn new(path: &str) -> Result { let library = unsafe { libloading::Library::new(path)?}; Ok(Self { _version: PhantomData, library }) } } impl SSVLibrary { pub fn library(&self) -> &libloading::Library { &self.library } ssv_function!(BINDINGS_V1_40_14::SSV_InitLIB2, ssv_init_lib2, { pcFichierSesam: *const i8 }); ssv_function!(BINDINGS_V1_40_14::SSV_LireConfig, ssv_lire_config, { pZDataOut: *mut *mut libc::c_void, psTailleDataOut: *mut usize }); ssv_function!(BINDINGS_V1_40_14::SSV_LireCartePS, ssv_lire_carte_ps, { NomRessourcePS: *const i8, NomRessourceLecteur: *const i8, CodePorteurPS: *const i8, pZDataOut: *mut *mut libc::c_void, pTailleZone: *mut usize }); } impl SSVLibrary { ssv_function!(BINDINGS_V1_40_13::SSV_InitLIB2, ssv_init_lib2, { pcFichierSesam: *const i8 }); ssv_function!(BINDINGS_V1_40_13::SSV_LireConfig, ssv_lire_config, { pZDataOut: *mut *mut libc::c_void, psTailleDataOut: *mut usize }); ssv_function!(BINDINGS_V1_40_13::SSV_LireCartePS, ssv_lire_carte_ps, { NomRessourcePS: *const i8, NomRessourceLecteur: *const i8, CodePorteurPS: *const i8, pZDataOut: *mut *mut libc::c_void, pTailleZone: *mut usize }); } pub fn get_library_path(version: &SupportedFsvVersion) -> String { let root_path = get_library_root_path(); let library_name = get_library_name(); let version = version.as_str(); format!("{root_path}/{version}/lib/{library_name}") } pub fn sesam_ini_path(version: &SupportedFsvVersion) -> String { let root_path = get_sesam_ini_root_path(); let version = version.as_str(); format!("{root_path}/{version}/conf/sesam.ini") } fn get_library_name() -> &'static str { // TODO : Use libloading::library_filename to get platform-specific filename ? "libssvlux64.so" } fn get_library_root_path() -> &'static str { "/opt/santesocial/fsv" } fn get_sesam_ini_root_path() -> &'static str { "/etc/opt/santesocial/fsv" } #[cfg(test)] mod test { use std::{ffi::CString, ptr}; use super::*; #[test] fn test_initlib2() { let lib_path = &get_library_path(&SupportedFsvVersion::V1_40_13); let ssv_library = SSVLibrary::::new(lib_path).expect("SSVLibrary::new failed"); let sesam_ini_str = CString::new(sesam_ini_path(&SupportedFsvVersion::V1_40_13)).expect("CString::new failed"); let result = unsafe { ssv_library.ssv_init_lib2(sesam_ini_str.as_ptr()) }.unwrap(); assert_eq!(result, 0); } #[test] fn test_lire_config_and_carte_ps() { let lib_path = &get_library_path(&SupportedFsvVersion::V1_40_13); let ssv_library = SSVLibrary::::new(lib_path).expect("SSVLibrary::new failed"); let sesam_ini_str = CString::new(sesam_ini_path(&SupportedFsvVersion::V1_40_13)).expect("CString::new failed"); let result = unsafe { ssv_library.ssv_init_lib2(sesam_ini_str.as_ptr()) }.unwrap(); assert_eq!(result, 0); let mut buffer_ptr: *mut libc::c_void = ptr::null_mut(); let mut size: libc::size_t = 0; let result = unsafe { ssv_library.ssv_lire_config(&mut buffer_ptr, &mut size) }.unwrap(); assert_eq!(result, 0); unsafe { libc::free(buffer_ptr) }; let nom_ressource_ps = CString::new("Gemalto PC Twin Reader (645D94C3) 00 00").expect("CString::new failed"); let nom_ressource_lecteur = CString::new("Gemalto PC Twin Reader (645D94C3) 00 00").expect("CString::new failed"); let code_porteur_ps = CString::new("1234").expect("CString::new failed"); let mut buffer_ptr: *mut libc::c_void = ptr::null_mut(); let mut size: libc::size_t = 0; let result = unsafe { ssv_library.ssv_lire_carte_ps( nom_ressource_ps.as_ptr(), nom_ressource_lecteur.as_ptr(), code_porteur_ps.as_ptr(), &mut buffer_ptr, &mut size, ) } .unwrap(); assert_eq!(result, 0); unsafe { libc::free(buffer_ptr) }; } }