#![allow(non_camel_case_types)]
extern crate curl_sys as _;
extern crate libz_sys as _;
extern crate openssl_sys as _;
use std::os::raw::{c_char, c_int, c_void};
pub const FLAG_DEBUG: c_int = 1 << 1;
pub const FLAG_ERASE: c_int = 1 << 2;
pub const LL_ERROR: c_int = 0;
pub const LL_WARNING: c_int = 1;
pub const LL_NOTICE: c_int = 2;
pub const LL_INFO: c_int = 3;
pub const LL_VERBOSE: c_int = 4;
pub const LL_DEBUG: c_int = 5;
extern "C" {
static mut log_level: c_int;
}
pub fn set_log_level(level: c_int) {
unsafe {
log_level = level;
}
}
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
extern "C" {
fn restorekit_install_log_capture();
}
static LOG_LINES: Mutex<Vec<(c_int, String)>> = Mutex::new(Vec::new());
static LOG_ECHO: AtomicBool = AtomicBool::new(false);
const LOG_CAPACITY: usize = 512;
type LogSink = Box<dyn Fn(c_int, &str) + Send>;
static LOG_SINK: Mutex<Option<LogSink>> = Mutex::new(None);
pub fn set_log_sink(sink: Option<LogSink>) {
*LOG_SINK.lock().unwrap_or_else(|e| e.into_inner()) = sink;
}
#[no_mangle]
pub unsafe extern "C" fn restorekit_log_capture(level: c_int, msg: *const c_char) {
if msg.is_null() {
return;
}
let text = std::ffi::CStr::from_ptr(msg).to_string_lossy();
let line = text.trim_end_matches(['\n', '\r']).to_string();
if line.is_empty() {
return;
}
if LOG_ECHO.load(Ordering::Relaxed) {
eprintln!("{line}");
}
if let Ok(guard) = LOG_SINK.lock() {
if let Some(sink) = guard.as_ref() {
sink(level, &line);
}
}
if let Ok(mut buf) = LOG_LINES.lock() {
if buf.len() >= LOG_CAPACITY {
buf.remove(0);
}
buf.push((level, line));
}
}
pub fn install_log_capture(echo: bool) {
LOG_ECHO.store(echo, Ordering::Relaxed);
if let Ok(mut buf) = LOG_LINES.lock() {
buf.clear();
}
unsafe { restorekit_install_log_capture() };
}
pub fn error_tail(max_lines: usize) -> String {
let buf = match LOG_LINES.lock() {
Ok(b) => b,
Err(e) => e.into_inner(),
};
let errors: Vec<&str> = buf
.iter()
.filter(|(level, _)| *level <= LL_WARNING)
.map(|(_, line)| line.as_str())
.collect();
let start = errors.len().saturating_sub(max_lines);
errors[start..].join("\n")
}
pub const RESTORE_STEP_DETECT: c_int = 0;
pub const RESTORE_STEP_PREPARE: c_int = 1;
pub const RESTORE_STEP_UPLOAD_FS: c_int = 2;
pub const RESTORE_STEP_VERIFY_FS: c_int = 3;
pub const RESTORE_STEP_FLASH_FW: c_int = 4;
pub const RESTORE_STEP_FLASH_BB: c_int = 5;
pub const RESTORE_STEP_FUD: c_int = 6;
pub const RESTORE_STEP_UPLOAD_IMG: c_int = 7;
#[repr(C)]
pub struct idevicerestore_client_t {
_private: [u8; 0],
}
pub type idevicerestore_progress_cb_t =
Option<unsafe extern "C" fn(step: c_int, step_progress: f64, userdata: *mut c_void)>;
extern "C" {
pub fn idevicerestore_client_new() -> *mut idevicerestore_client_t;
pub fn idevicerestore_client_free(client: *mut idevicerestore_client_t);
pub fn idevicerestore_set_ecid(client: *mut idevicerestore_client_t, ecid: u64);
pub fn idevicerestore_set_udid(client: *mut idevicerestore_client_t, udid: *const c_char);
pub fn idevicerestore_set_flags(client: *mut idevicerestore_client_t, flags: c_int);
pub fn idevicerestore_set_ipsw(client: *mut idevicerestore_client_t, path: *const c_char);
pub fn idevicerestore_set_cache_path(client: *mut idevicerestore_client_t, path: *const c_char);
pub fn idevicerestore_set_progress_callback(
client: *mut idevicerestore_client_t,
cbfunc: idevicerestore_progress_cb_t,
userdata: *mut c_void,
);
pub fn idevicerestore_start(client: *mut idevicerestore_client_t) -> c_int;
pub fn register_progress(tag: u32, label: *const c_char);
fn set_update_progress_func(
func: Option<unsafe extern "C" fn(list: *mut *mut c_void, count: c_int)>,
);
}
unsafe extern "C" fn discard_progress(_list: *mut *mut c_void, _count: c_int) {}
pub fn suppress_tagged_progress() {
unsafe { set_update_progress_func(Some(discard_progress)) };
}
pub fn init_progress() {
unsafe { register_progress(0, std::ptr::null()) };
}
#[cfg(any(target_os = "linux", target_os = "windows"))]
extern "C" {
fn restorekit_usbmuxd_start(socket_path: *const c_char) -> c_int;
fn restorekit_usbmuxd_run();
fn restorekit_usbmuxd_stop();
fn restorekit_usbmuxd_cleanup();
}
#[cfg(any(target_os = "linux", target_os = "windows"))]
pub fn usbmuxd_start(path: &std::ffi::CStr) -> std::result::Result<(), c_int> {
let rc = unsafe { restorekit_usbmuxd_start(path.as_ptr()) };
if rc == 0 {
Ok(())
} else {
Err(rc)
}
}
#[cfg(any(target_os = "linux", target_os = "windows"))]
pub fn usbmuxd_run() {
unsafe { restorekit_usbmuxd_run() }
}
#[cfg(any(target_os = "linux", target_os = "windows"))]
pub fn usbmuxd_stop() {
unsafe { restorekit_usbmuxd_stop() }
}
#[cfg(any(target_os = "linux", target_os = "windows"))]
pub fn usbmuxd_cleanup() {
unsafe { restorekit_usbmuxd_cleanup() }
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CString;
#[test]
fn error_tail_filters_by_level() {
if let Ok(mut b) = LOG_LINES.lock() {
b.clear();
}
LOG_ECHO.store(false, Ordering::Relaxed);
let cap = |level, s: &str| {
let c = CString::new(s).unwrap();
unsafe { restorekit_log_capture(level, c.as_ptr()) };
};
cap(LL_INFO, "info line");
cap(LL_ERROR, "boom -3");
cap(LL_VERBOSE, "verbose noise");
cap(LL_WARNING, "careful");
let tail = error_tail(10);
assert!(tail.contains("boom -3"));
assert!(tail.contains("careful"));
assert!(!tail.contains("info line"));
assert!(!tail.contains("verbose noise"));
}
}