From 774091b550cac6df4f25127adb4047cf4334b973 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Mon, 14 Oct 2024 21:35:00 -0500 Subject: do not use libloading --- impls/monero.rs/Cargo.lock | 4 +- impls/monero.rs/Cargo.toml | 6 +- impls/monero.rs/build.rs | 82 +++++++++++++- impls/monero.rs/src/lib.rs | 172 +++++------------------------ impls/monero.rs/tests/integration_tests.rs | 11 +- 5 files changed, 111 insertions(+), 164 deletions(-) (limited to 'impls') diff --git a/impls/monero.rs/Cargo.lock b/impls/monero.rs/Cargo.lock index 9e79d0f..9991a45 100644 --- a/impls/monero.rs/Cargo.lock +++ b/impls/monero.rs/Cargo.lock @@ -185,12 +185,10 @@ dependencies = [ ] [[package]] -name = "monero_rust" +name = "monero_c_rust" version = "0.0.1" dependencies = [ "bindgen", - "libc", - "libloading", "mockall", "tempfile", ] diff --git a/impls/monero.rs/Cargo.toml b/impls/monero.rs/Cargo.toml index fa0e6ab..f055225 100644 --- a/impls/monero.rs/Cargo.toml +++ b/impls/monero.rs/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "monero_rust" +name = "monero_c_rust" version = "0.0.1" edition = "2021" description = "monero_c Rust bindings." @@ -8,13 +8,11 @@ license = "MIT" build = "build.rs" [lib] -name = "monero_rust" +name = "monero_c_rust" path = "src/lib.rs" crate-type = ["lib", "cdylib"] [dependencies] -libc = "0.2" -libloading = "0.8.5" mockall = "0.13.0" tempfile = "3.13.0" diff --git a/impls/monero.rs/build.rs b/impls/monero.rs/build.rs index 357028b..19a4394 100644 --- a/impls/monero.rs/build.rs +++ b/impls/monero.rs/build.rs @@ -1,13 +1,86 @@ use std::env; +use std::fs::{self, OpenOptions}; +use std::io::Write; use std::path::PathBuf; use bindgen::EnumVariation; +#[cfg(unix)] +use std::os::unix::fs as unix_fs; + +#[cfg(target_os = "windows")] +use std::fs::copy; + fn main() { let header_path = "../../monero_libwallet2_api_c/src/main/cpp/wallet2_api_c.h"; + let lib_path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("../../release"); + + // Set library names based on target OS. + // + // This rigamarole is currently required because the "monero_libwallet2_api_c" library is also + // required under the names "wallet2_api_c" and "libmonero_libwallet2_api_c" by various parts of + // the stack. This is a temporary workaround until the library is refactored to use a single + // name consistently. + let original_lib = if cfg!(target_os = "windows") { + lib_path.join("monero_libwallet2_api_c.dll") + } else if cfg!(target_os = "macos") { + lib_path.join("monero_libwallet2_api_c.dylib") + } else { + lib_path.join("monero_libwallet2_api_c.so") + }; + + let symlink_1 = if cfg!(target_os = "windows") { + lib_path.join("wallet2_api_c.dll") + } else if cfg!(target_os = "macos") { + lib_path.join("libwallet2_api_c.dylib") + } else { + lib_path.join("libwallet2_api_c.so") + }; + + let symlink_2 = if cfg!(target_os = "windows") { + lib_path.join("monero_wallet2_api_c.dll") + } else if cfg!(target_os = "macos") { + lib_path.join("libmonero_libwallet2_api_c.dylib") + } else { + lib_path.join("libmonero_libwallet2_api_c.so") + }; + + // On Unix-like systems, create symlinks. + #[cfg(unix)] + { + if original_lib.exists() && !symlink_1.exists() { + unix_fs::symlink(&original_lib, &symlink_1) + .expect("Failed to create symbolic link for libwallet2_api_c.so"); + } + + if original_lib.exists() && !symlink_2.exists() { + unix_fs::symlink(&original_lib, &symlink_2) + .expect("Failed to create symbolic link for libmonero_libwallet2_api_c.so"); + } + } + + // On Windows, copy the files instead of symlinking. + #[cfg(target_os = "windows")] + { + if original_lib.exists() && !symlink_1.exists() { + copy(&original_lib, &symlink_1).expect("Failed to copy DLL file to wallet2_api_c.dll"); + } + + if original_lib.exists() && !symlink_2.exists() { + copy(&original_lib, &symlink_2) + .expect("Failed to copy DLL file to monero_wallet2_api_c.dll"); + } + } println!("cargo:rerun-if-changed={}", header_path); + println!("cargo:rerun-if-changed=build.rs"); - // Configure bindgen + println!("cargo:rustc-link-search=native={}", lib_path.display()); + println!("cargo:rustc-link-lib=dylib=monero_libwallet2_api_c"); + println!("cargo:rustc-link-lib=dylib=stdc++"); + println!("cargo:rustc-link-lib=dylib=hidapi-hidraw"); + println!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib_path.display()); + + // Generate bindings using bindgen. let bindings = bindgen::Builder::default() .header(header_path) .allowlist_function("MONERO_.*") @@ -28,18 +101,17 @@ fn main() { .blocklist_type("_.*") .blocklist_function("__.*") .layout_tests(false) - .default_enum_style(EnumVariation::Rust { - non_exhaustive: false, - }) + .default_enum_style(EnumVariation::Rust { non_exhaustive: false }) .derive_default(false) .conservative_inline_namespaces() .generate_comments(false) .generate() .expect("Unable to generate bindings"); + let out_path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) .join("src") .join("bindings.rs"); bindings - .write_to_file(out_path) + .write_to_file(out_path.clone()) .expect("Couldn't write bindings!"); } diff --git a/impls/monero.rs/src/lib.rs b/impls/monero.rs/src/lib.rs index c9b1d81..7cc334c 100644 --- a/impls/monero.rs/src/lib.rs +++ b/impls/monero.rs/src/lib.rs @@ -1,21 +1,9 @@ use std::ffi::{CStr, CString}; -use std::os::raw::{c_char, c_int, c_void}; -use std::path::PathBuf; +use std::os::raw::{c_int, c_void}; use std::ptr::NonNull; use std::sync::Arc; -use libloading::{Library, Symbol}; - -#[cfg(target_os = "android")] -const LIB_NAME: &str = "libmonero_libwallet2_api_c.so"; -#[cfg(target_os = "ios")] -const LIB_NAME: &str = "MoneroWallet.framework/MoneroWallet"; -#[cfg(target_os = "linux")] -const LIB_NAME: &str = "monero_libwallet2_api_c.so"; -#[cfg(target_os = "macos")] -const LIB_NAME: &str = "monero_libwallet2_api_c.dylib"; -#[cfg(target_os = "windows")] -const LIB_NAME: &str = "monero_libwallet2_api_c.dll"; +mod bindings; pub mod network { use std::os::raw::c_int; @@ -29,32 +17,25 @@ pub enum WalletError { NullPointer, FfiError(String), WalletErrorCode(c_int, String), - LibraryLoadError(String), } pub type WalletResult = Result; pub struct WalletManager { ptr: NonNull, - library: Library, } impl WalletManager { - /// Creates a new `WalletManager`, loading the Monero wallet library (`wallet2_api_c`). - pub fn new(lib_path: Option<&str>) -> WalletResult> { - let library = Self::load_library(lib_path)?; - + /// Creates a new `WalletManager` using the statically linked `MONERO_WalletManagerFactory_getWalletManager`. + pub fn new() -> WalletResult> { unsafe { - let func: Symbol *mut c_void> = library - .get(b"MONERO_WalletManagerFactory_getWalletManager\0") - .map_err(|e| WalletError::LibraryLoadError(e.to_string()))?; - - let ptr = NonNull::new(func()).ok_or(WalletError::NullPointer)?; - - Ok(Arc::new(WalletManager { ptr, library })) + let ptr = bindings::MONERO_WalletManagerFactory_getWalletManager(); + let ptr = NonNull::new(ptr).ok_or(WalletError::NullPointer)?; + Ok(Arc::new(WalletManager { ptr })) } } + /// Creates a new wallet. pub fn create_wallet( self: &Arc, path: &str, @@ -62,28 +43,12 @@ impl WalletManager { language: &str, network_type: c_int, ) -> WalletResult { - let c_path = CString::new(path) - .map_err(|_| WalletError::FfiError("Invalid path".to_string()))?; - let c_password = CString::new(password) - .map_err(|_| WalletError::FfiError("Invalid password".to_string()))?; - let c_language = CString::new(language) - .map_err(|_| WalletError::FfiError("Invalid language".to_string()))?; + let c_path = CString::new(path).map_err(|_| WalletError::FfiError("Invalid path".to_string()))?; + let c_password = CString::new(password).map_err(|_| WalletError::FfiError("Invalid password".to_string()))?; + let c_language = CString::new(language).map_err(|_| WalletError::FfiError("Invalid language".to_string()))?; unsafe { - let func: Symbol< - unsafe extern "C" fn( - *mut c_void, - *const c_char, - *const c_char, - *const c_char, - c_int, - ) -> *mut c_void, - > = self - .library - .get(b"MONERO_WalletManager_createWallet\0") - .map_err(|e| WalletError::LibraryLoadError(e.to_string()))?; - - let wallet_ptr = func( + let wallet_ptr = bindings::MONERO_WalletManager_createWallet( self.ptr.as_ptr(), c_path.as_ptr(), c_password.as_ptr(), @@ -92,62 +57,10 @@ impl WalletManager { ); NonNull::new(wallet_ptr) - .map(|ptr| Wallet { - ptr, - manager: Arc::clone(self), - }) + .map(|ptr| Wallet { ptr, manager: Arc::clone(self) }) .ok_or(WalletError::NullPointer) } } - - fn load_library(lib_path: Option<&str>) -> WalletResult { - if let Some(path) = lib_path { - unsafe { Library::new(path).map_err(|e| WalletError::LibraryLoadError(e.to_string())) } - } else { - let exe_path = std::env::current_exe() - .map_err(|e| WalletError::LibraryLoadError(e.to_string()))?; - let exe_dir = exe_path.parent().ok_or_else(|| { - WalletError::LibraryLoadError("Failed to get executable directory".to_string()) - })?; - - let candidates = Self::get_library_candidates(exe_dir); - - candidates - .into_iter() - .find_map(|path| unsafe { Library::new(&path).ok() }) - .ok_or_else(|| { - WalletError::LibraryLoadError(format!( - "Failed to load {} from standard paths", - LIB_NAME - )) - }) - } - } - - fn get_library_candidates(exe_dir: &std::path::Path) -> Vec { - let mut candidates = Vec::new(); - - // Candidate 1: ../../../../release/ relative to the executable. - if let Some(lib_dir) = exe_dir.ancestors().nth(4) { - candidates.push(lib_dir.join("release").join(LIB_NAME)); - } - - // Candidate 2: ../../lib/ relative to the executable. - if let Some(lib_dir) = exe_dir.ancestors().nth(2) { - candidates.push(lib_dir.join("lib").join(LIB_NAME)); - } - - // Candidate 3: Same directory as the executable. - candidates.push(exe_dir.join(LIB_NAME)); - // TODO: This should probably be the first candidate for binary - // distribution purposes; it will likely be the first place the library - // will be found in a binary distribution. - - // Candidate 4: Standard library paths. - candidates.push(PathBuf::from(LIB_NAME)); - - candidates - } } pub struct Wallet { @@ -156,19 +69,13 @@ pub struct Wallet { } impl Wallet { + /// Retrieves the wallet's seed with an optional offset. pub fn get_seed(&self, seed_offset: &str) -> WalletResult { let c_seed_offset = CString::new(seed_offset) .map_err(|_| WalletError::FfiError("Invalid seed_offset".to_string()))?; unsafe { - let func: Symbol *const c_char> = - self.manager - .library - .get(b"MONERO_Wallet_seed\0") - .map_err(|e| WalletError::LibraryLoadError(e.to_string()))?; - - let seed_ptr = func(self.ptr.as_ptr(), c_seed_offset.as_ptr()); - + let seed_ptr = bindings::MONERO_Wallet_seed(self.ptr.as_ptr(), c_seed_offset.as_ptr()); if seed_ptr.is_null() { Err(self.get_last_error()) } else { @@ -184,15 +91,10 @@ impl Wallet { } } + /// Retrieves the wallet's address for the given account and address index. pub fn get_address(&self, account_index: u64, address_index: u64) -> WalletResult { unsafe { - let func: Symbol *const c_char> = - self.manager - .library - .get(b"MONERO_Wallet_address\0") - .map_err(|e| WalletError::LibraryLoadError(e.to_string()))?; - let address_ptr = func(self.ptr.as_ptr(), account_index, address_index); - + let address_ptr = bindings::MONERO_Wallet_address(self.ptr.as_ptr(), account_index, address_index); if address_ptr.is_null() { Err(self.get_last_error()) } else { @@ -204,32 +106,19 @@ impl Wallet { } } + /// Checks if the wallet is deterministic. pub fn is_deterministic(&self) -> WalletResult { unsafe { - let func: Symbol bool> = self - .manager - .library - .get(b"MONERO_Wallet_isDeterministic\0") - .map_err(|e| WalletError::LibraryLoadError(e.to_string()))?; - Ok(func(self.ptr.as_ptr())) + let result = bindings::MONERO_Wallet_isDeterministic(self.ptr.as_ptr()); + Ok(result) } } - fn get_last_error(&self) -> WalletError { + /// Retrieves the last error from the wallet. + pub fn get_last_error(&self) -> WalletError { unsafe { - let error_func: Symbol *const c_char> = self - .manager - .library - .get(b"MONERO_Wallet_errorString\0") - .expect("Failed to load MONERO_Wallet_errorString"); - let status_func: Symbol c_int> = self - .manager - .library - .get(b"MONERO_Wallet_status\0") - .expect("Failed to load MONERO_Wallet_status"); - - let error_ptr = error_func(self.ptr.as_ptr()); - let status = status_func(self.ptr.as_ptr()); + let error_ptr = bindings::MONERO_Wallet_errorString(self.ptr.as_ptr()); + let status = bindings::MONERO_Wallet_status(self.ptr.as_ptr()); let error_msg = if error_ptr.is_null() { "Unknown error".to_string() @@ -247,14 +136,11 @@ impl Wallet { impl Drop for Wallet { fn drop(&mut self) { unsafe { - let func: Symbol< - unsafe extern "C" fn(*mut c_void, *mut c_void, bool) -> bool, - > = self - .manager - .library - .get(b"MONERO_WalletManager_closeWallet\0") - .expect("Failed to load MONERO_WalletManager_closeWallet"); - func(self.manager.ptr.as_ptr(), self.ptr.as_ptr(), false); + bindings::MONERO_WalletManager_closeWallet( + self.manager.ptr.as_ptr(), + self.ptr.as_ptr(), + false, // Don't save the wallet by default. + ); } } } diff --git a/impls/monero.rs/tests/integration_tests.rs b/impls/monero.rs/tests/integration_tests.rs index 168e882..beb28e2 100644 --- a/impls/monero.rs/tests/integration_tests.rs +++ b/impls/monero.rs/tests/integration_tests.rs @@ -1,4 +1,4 @@ -use monero_rust::{WalletManager, network, WalletError, WalletResult}; +use monero_c_rust::{WalletManager, network, WalletError, WalletResult}; use std::fs; use std::sync::Arc; use std::time::Instant; @@ -61,7 +61,7 @@ fn setup() -> WalletResult<(Arc, TempDir)> { println!("Creating WalletManager..."); let start = Instant::now(); - let manager = WalletManager::new(None)?; + let manager = WalletManager::new()?; println!("WalletManager creation took {:?}", start.elapsed()); Ok((manager, temp_dir)) @@ -255,11 +255,4 @@ fn test_wallet_error_display() { }, _ => panic!("Expected WalletErrorCode variant"), } - - // Test WalletError::LibraryLoadError variant. - let error = WalletError::LibraryLoadError("Failed to load library".to_string()); - match error { - WalletError::LibraryLoadError(msg) => assert_eq!(msg, "Failed to load library"), - _ => panic!("Expected LibraryLoadError variant"), - } } -- cgit v1.2.3