use std::ffi::{CStr, CString, c_char, c_void};
use std::panic;
use std::ptr;
use std::sync::Arc;
use crate::extractor::local::{
ExtractionProgress, ExtractionStatus, ProgressCallback, extract_partition,
extract_partition_zip, list_partitions, list_partitions_zip,
};
#[cfg(feature = "remote_zip")]
use crate::extractor::remote::{
extract_partition_remote_bin, extract_partition_remote_zip, list_partitions_remote_bin,
list_partitions_remote_zip,
};
thread_local! {
static LAST_ERROR: std::cell::RefCell<Option<CString>> = std::cell::RefCell::new(None);
}
fn set_last_error(err: String) {
LAST_ERROR.with(|last| {
*last.borrow_mut() = CString::new(err).ok();
});
}
fn clear_last_error() {
LAST_ERROR.with(|last| {
*last.borrow_mut() = None;
});
}
#[unsafe(no_mangle)]
pub extern "C" fn payload_get_last_error() -> *const c_char {
LAST_ERROR.with(|last| {
last.borrow()
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(ptr::null())
})
}
#[unsafe(no_mangle)]
pub extern "C" fn payload_clear_error() {
clear_last_error();
}
#[unsafe(no_mangle)]
pub extern "C" fn payload_free_string(s: *mut c_char) {
if !s.is_null() {
unsafe {
drop(CString::from_raw(s));
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn payload_list_partitions(payload_path: *const c_char) -> *mut c_char {
clear_last_error();
let result = panic::catch_unwind(|| {
if payload_path.is_null() {
set_last_error("payload_path is NULL".to_string());
return ptr::null_mut();
}
let path_str = unsafe {
match CStr::from_ptr(payload_path).to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("Invalid UTF-8 in payload_path: {}", e));
return ptr::null_mut();
}
}
};
match list_partitions(path_str) {
Ok(json) => match CString::new(json) {
Ok(c_str) => c_str.into_raw(),
Err(e) => {
set_last_error(format!("Failed to create C string: {}", e));
ptr::null_mut()
}
},
Err(e) => {
set_last_error(format!("Failed to list partitions: {}", e));
ptr::null_mut()
}
}
});
match result {
Ok(ptr) => ptr,
Err(_) => {
set_last_error("Panic occurred in payload_list_partitions".to_string());
ptr::null_mut()
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn payload_list_partitions_zip(zip_path: *const c_char) -> *mut c_char {
clear_last_error();
let result = panic::catch_unwind(|| {
if zip_path.is_null() {
set_last_error("zip_path is NULL".to_string());
return ptr::null_mut();
}
let path_str = unsafe {
match CStr::from_ptr(zip_path).to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("Invalid UTF-8 in zip_path: {}", e));
return ptr::null_mut();
}
}
};
match list_partitions_zip(path_str) {
Ok(json) => match CString::new(json) {
Ok(c_str) => c_str.into_raw(),
Err(e) => {
set_last_error(format!("Failed to create C string: {}", e));
ptr::null_mut()
}
},
Err(e) => {
set_last_error(format!("Failed to list partitions from ZIP: {}", e));
ptr::null_mut()
}
}
});
match result {
Ok(ptr) => ptr,
Err(_) => {
set_last_error("Panic occurred in payload_list_partitions_zip".to_string());
ptr::null_mut()
}
}
}
#[cfg(feature = "remote_zip")]
#[unsafe(no_mangle)]
pub extern "C" fn payload_list_partitions_remote_zip(
url: *const c_char,
user_agent: *const c_char,
cookies: *const c_char,
out_content_length: *mut u64,
) -> *mut c_char {
clear_last_error();
let result = panic::catch_unwind(|| {
if url.is_null() {
set_last_error("url is NULL".to_string());
return ptr::null_mut();
}
let url_str = unsafe {
match CStr::from_ptr(url).to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("Invalid UTF-8 in url: {}", e));
return ptr::null_mut();
}
}
};
let user_agent_str = if user_agent.is_null() {
None
} else {
unsafe {
match CStr::from_ptr(user_agent).to_str() {
Ok(s) => Some(s),
Err(e) => {
set_last_error(format!("Invalid UTF-8 in user_agent: {}", e));
return ptr::null_mut();
}
}
}
};
let cookies_str = if cookies.is_null() {
None
} else {
unsafe {
match CStr::from_ptr(cookies).to_str() {
Ok(s) => Some(s),
Err(e) => {
set_last_error(format!("Invalid UTF-8 in cookies: {}", e));
return ptr::null_mut();
}
}
}
};
match list_partitions_remote_zip(url_str.to_string(), user_agent_str, cookies_str) {
Ok(result) => {
if !out_content_length.is_null() {
unsafe {
*out_content_length = result.content_length;
}
}
match CString::new(result.json) {
Ok(c_str) => c_str.into_raw(),
Err(e) => {
set_last_error(format!("Failed to create C string: {}", e));
ptr::null_mut()
}
}
}
Err(e) => {
set_last_error(format!("Failed to list remote partitions: {}", e));
ptr::null_mut()
}
}
});
match result {
Ok(ptr) => ptr,
Err(_) => {
set_last_error("Panic occurred in payload_list_partitions_remote_zip".to_string());
ptr::null_mut()
}
}
}
#[cfg(feature = "remote_zip")]
#[unsafe(no_mangle)]
pub extern "C" fn payload_list_partitions_remote_bin(
url: *const c_char,
user_agent: *const c_char,
cookies: *const c_char,
out_content_length: *mut u64,
) -> *mut c_char {
clear_last_error();
let result = panic::catch_unwind(|| {
if url.is_null() {
set_last_error("url is NULL".to_string());
return ptr::null_mut();
}
let url_str = unsafe {
match CStr::from_ptr(url).to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("Invalid UTF-8 in url: {}", e));
return ptr::null_mut();
}
}
};
let user_agent_str = if user_agent.is_null() {
None
} else {
unsafe {
match CStr::from_ptr(user_agent).to_str() {
Ok(s) => Some(s),
Err(e) => {
set_last_error(format!("Invalid UTF-8 in user_agent: {}", e));
return ptr::null_mut();
}
}
}
};
let cookies_str = if cookies.is_null() {
None
} else {
unsafe {
match CStr::from_ptr(cookies).to_str() {
Ok(s) => Some(s),
Err(e) => {
set_last_error(format!("Invalid UTF-8 in cookies: {}", e));
return ptr::null_mut();
}
}
}
};
match list_partitions_remote_bin(url_str.to_string(), user_agent_str, cookies_str) {
Ok(result) => {
if !out_content_length.is_null() {
unsafe {
*out_content_length = result.content_length;
}
}
match CString::new(result.json) {
Ok(c_str) => c_str.into_raw(),
Err(e) => {
set_last_error(format!("Failed to create C string: {}", e));
ptr::null_mut()
}
}
}
Err(e) => {
set_last_error(format!("Failed to list remote partitions: {}", e));
ptr::null_mut()
}
}
});
match result {
Ok(ptr) => ptr,
Err(_) => {
set_last_error("Panic occurred in payload_list_partitions_remote_bin".to_string());
ptr::null_mut()
}
}
}
pub type CProgressCallback = extern "C" fn(
user_data: *mut c_void,
partition_name: *const c_char,
current_operation: u64,
total_operations: u64,
percentage: f64,
status: i32,
warning_message: *const c_char,
) -> i32;
pub const STATUS_STARTED: i32 = 0;
pub const STATUS_IN_PROGRESS: i32 = 1;
pub const STATUS_COMPLETED: i32 = 2;
pub const STATUS_WARNING: i32 = 3;
struct CCallbackWrapper {
callback: CProgressCallback,
user_data: *mut c_void,
}
unsafe impl Send for CCallbackWrapper {}
unsafe impl Sync for CCallbackWrapper {}
impl CCallbackWrapper {
fn call(&self, progress: ExtractionProgress) -> bool {
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
let partition_name = match CString::new(progress.partition_name.clone()) {
Ok(s) => s,
Err(_) => return true, };
let warning_msg;
let (status, warning_msg_ptr) = match progress.status {
ExtractionStatus::Started => (STATUS_STARTED, ptr::null()),
ExtractionStatus::InProgress => (STATUS_IN_PROGRESS, ptr::null()),
ExtractionStatus::Completed => (STATUS_COMPLETED, ptr::null()),
ExtractionStatus::Warning { message, .. } => {
warning_msg = CString::new(message).ok();
let msg_ptr = warning_msg
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(ptr::null());
(STATUS_WARNING, msg_ptr)
}
};
let result = (self.callback)(
self.user_data,
partition_name.as_ptr(),
progress.current_operation,
progress.total_operations,
progress.percentage,
status,
warning_msg_ptr,
);
result != 0
}));
match result {
Ok(should_continue) => should_continue,
Err(_) => {
eprintln!("WARNING: Progress callback panicked - continuing extraction");
true }
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn payload_extract_partition(
payload_path: *const c_char,
partition_name: *const c_char,
output_path: *const c_char,
callback: CProgressCallback, user_data: *mut c_void,
) -> i32 {
clear_last_error();
let result = panic::catch_unwind(|| {
if payload_path.is_null() {
set_last_error("payload_path is NULL".to_string());
return -1;
}
if partition_name.is_null() {
set_last_error("partition_name is NULL".to_string());
return -1;
}
if output_path.is_null() {
set_last_error("output_path is NULL".to_string());
return -1;
}
let payload_str = unsafe {
match CStr::from_ptr(payload_path).to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("Invalid UTF-8 in payload_path: {}", e));
return -1;
}
}
};
let partition_str = unsafe {
match CStr::from_ptr(partition_name).to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("Invalid UTF-8 in partition_name: {}", e));
return -1;
}
}
};
let output_str = unsafe {
match CStr::from_ptr(output_path).to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("Invalid UTF-8 in output_path: {}", e));
return -1;
}
}
};
let progress_cb: Option<ProgressCallback> = if callback as usize == 0 {
None
} else {
let wrapper = Arc::new(CCallbackWrapper {
callback,
user_data,
});
Some(Box::new(move |progress| wrapper.call(progress)) as ProgressCallback)
};
match extract_partition(payload_str, partition_str, output_str, progress_cb) {
Ok(()) => 0,
Err(e) => {
set_last_error(format!("Extraction failed: {}", e));
-1
}
}
});
match result {
Ok(code) => code,
Err(_) => {
set_last_error("Panic occurred in payload_extract_partition".to_string());
-1
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn payload_extract_partition_zip(
zip_path: *const c_char,
partition_name: *const c_char,
output_path: *const c_char,
callback: CProgressCallback, user_data: *mut c_void,
) -> i32 {
clear_last_error();
let result = panic::catch_unwind(|| {
if zip_path.is_null() {
set_last_error("zip_path is NULL".to_string());
return -1;
}
if partition_name.is_null() {
set_last_error("partition_name is NULL".to_string());
return -1;
}
if output_path.is_null() {
set_last_error("output_path is NULL".to_string());
return -1;
}
let zip_str = unsafe {
match CStr::from_ptr(zip_path).to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("Invalid UTF-8 in zip_path: {}", e));
return -1;
}
}
};
let partition_str = unsafe {
match CStr::from_ptr(partition_name).to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("Invalid UTF-8 in partition_name: {}", e));
return -1;
}
}
};
let output_str = unsafe {
match CStr::from_ptr(output_path).to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("Invalid UTF-8 in output_path: {}", e));
return -1;
}
}
};
let progress_cb: Option<ProgressCallback> = if callback as usize == 0 {
None
} else {
let wrapper = Arc::new(CCallbackWrapper {
callback,
user_data,
});
Some(Box::new(move |progress| wrapper.call(progress)) as ProgressCallback)
};
match extract_partition_zip(zip_str, partition_str, output_str, progress_cb) {
Ok(()) => 0,
Err(e) => {
set_last_error(format!("Extraction from ZIP failed: {}", e));
-1
}
}
});
match result {
Ok(code) => code,
Err(_) => {
set_last_error("Panic occurred in payload_extract_partition_zip".to_string());
-1
}
}
}
#[cfg(feature = "remote_zip")]
#[unsafe(no_mangle)]
pub extern "C" fn payload_extract_partition_remote_zip(
url: *const c_char,
partition_name: *const c_char,
output_path: *const c_char,
user_agent: *const c_char,
cookies: *const c_char,
callback: CProgressCallback,
user_data: *mut c_void,
) -> i32 {
clear_last_error();
let result = panic::catch_unwind(|| {
if url.is_null() {
set_last_error("url is NULL".to_string());
return -1;
}
if partition_name.is_null() {
set_last_error("partition_name is NULL".to_string());
return -1;
}
if output_path.is_null() {
set_last_error("output_path is NULL".to_string());
return -1;
}
let url_str = unsafe {
match CStr::from_ptr(url).to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("Invalid UTF-8 in url: {}", e));
return -1;
}
}
};
let partition_str = unsafe {
match CStr::from_ptr(partition_name).to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("Invalid UTF-8 in partition_name: {}", e));
return -1;
}
}
};
let output_str = unsafe {
match CStr::from_ptr(output_path).to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("Invalid UTF-8 in output_path: {}", e));
return -1;
}
}
};
let user_agent_str = if user_agent.is_null() {
None
} else {
unsafe {
match CStr::from_ptr(user_agent).to_str() {
Ok(s) => Some(s),
Err(e) => {
set_last_error(format!("Invalid UTF-8 in user_agent: {}", e));
return -1;
}
}
}
};
let cookies_str = if cookies.is_null() {
None
} else {
unsafe {
match CStr::from_ptr(cookies).to_str() {
Ok(s) => Some(s),
Err(e) => {
set_last_error(format!("Invalid UTF-8 in cookies: {}", e));
return -1;
}
}
}
};
let progress_cb: Option<ProgressCallback> = if callback as usize == 0 {
None
} else {
let wrapper = Arc::new(CCallbackWrapper {
callback,
user_data,
});
Some(Box::new(move |progress| wrapper.call(progress)) as ProgressCallback)
};
match extract_partition_remote_zip(
url_str.to_string(),
partition_str,
output_str,
user_agent_str,
cookies_str,
progress_cb,
) {
Ok(()) => 0,
Err(e) => {
set_last_error(format!("Remote extraction from ZIP failed: {}", e));
-1
}
}
});
match result {
Ok(code) => code,
Err(_) => {
set_last_error("Panic occurred in payload_extract_partition_remote_zip".to_string());
-1
}
}
}
#[cfg(feature = "remote_zip")]
#[unsafe(no_mangle)]
pub extern "C" fn payload_extract_partition_remote_bin(
url: *const c_char,
partition_name: *const c_char,
output_path: *const c_char,
user_agent: *const c_char,
cookies: *const c_char,
callback: CProgressCallback,
user_data: *mut c_void,
) -> i32 {
clear_last_error();
let result = panic::catch_unwind(|| {
if url.is_null() {
set_last_error("url is NULL".to_string());
return -1;
}
if partition_name.is_null() {
set_last_error("partition_name is NULL".to_string());
return -1;
}
if output_path.is_null() {
set_last_error("output_path is NULL".to_string());
return -1;
}
let url_str = unsafe {
match CStr::from_ptr(url).to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("Invalid UTF-8 in url: {}", e));
return -1;
}
}
};
let partition_str = unsafe {
match CStr::from_ptr(partition_name).to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("Invalid UTF-8 in partition_name: {}", e));
return -1;
}
}
};
let output_str = unsafe {
match CStr::from_ptr(output_path).to_str() {
Ok(s) => s,
Err(e) => {
set_last_error(format!("Invalid UTF-8 in output_path: {}", e));
return -1;
}
}
};
let user_agent_str = if user_agent.is_null() {
None
} else {
unsafe {
match CStr::from_ptr(user_agent).to_str() {
Ok(s) => Some(s),
Err(e) => {
set_last_error(format!("Invalid UTF-8 in user_agent: {}", e));
return -1;
}
}
}
};
let cookies_str = if cookies.is_null() {
None
} else {
unsafe {
match CStr::from_ptr(cookies).to_str() {
Ok(s) => Some(s),
Err(e) => {
set_last_error(format!("Invalid UTF-8 in cookies: {}", e));
return -1;
}
}
}
};
let progress_cb: Option<ProgressCallback> = if callback as usize == 0 {
None
} else {
let wrapper = Arc::new(CCallbackWrapper {
callback,
user_data,
});
Some(Box::new(move |progress| wrapper.call(progress)) as ProgressCallback)
};
match extract_partition_remote_bin(
url_str.to_string(),
partition_str,
output_str,
user_agent_str,
cookies_str,
progress_cb,
) {
Ok(()) => 0,
Err(e) => {
set_last_error(format!("Remote extraction from .bin failed: {}", e));
-1
}
}
});
match result {
Ok(code) => code,
Err(_) => {
set_last_error("Panic occurred in payload_extract_partition_remote_bin".to_string());
-1
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn payload_get_version() -> *const c_char {
static C_VERSION: &[u8] = concat!(env!("CARGO_PKG_VERSION"), "\0").as_bytes();
C_VERSION.as_ptr() as *const c_char
}
#[unsafe(no_mangle)]
pub extern "C" fn payload_init() -> i32 {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn payload_cleanup() {
}