From 2859b691df787f56c421670e72081527486e4785 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Mon, 7 Apr 2025 09:44:11 -0600 Subject: [PATCH] Finish installation proxy bindings --- ffi/src/installation_proxy.rs | 428 +++++++++++++++++++++++++++++++++- ffi/src/util.rs | 10 + 2 files changed, 432 insertions(+), 6 deletions(-) diff --git a/ffi/src/installation_proxy.rs b/ffi/src/installation_proxy.rs index bf08e07..108aa82 100644 --- a/ffi/src/installation_proxy.rs +++ b/ffi/src/installation_proxy.rs @@ -11,8 +11,6 @@ use crate::{ }; pub struct InstallationProxyClientHandle(pub InstallationProxyClient); -#[allow(non_camel_case_types)] -pub struct plist_t; /// Automatically creates and connects to Installation Proxy, returning a client handle /// @@ -143,10 +141,10 @@ pub unsafe extern "C" fn installation_proxy_new( /// Gets installed apps on the device /// /// # Arguments -/// * `client` - A valid InstallationProxyClient handle -/// * `application_type` - The application type to filter by (optional, NULL for "Any") -/// * `bundle_identifiers` - The identifiers to filter by (optional, NULL for all apps) -/// * `out_result` - On success, will be set to point to a newly allocated array of PlistRef +/// * [`client`] - A valid InstallationProxyClient handle +/// * [`application_type`] - The application type to filter by (optional, NULL for "Any") +/// * [`bundle_identifiers`] - The identifiers to filter by (optional, NULL for all apps) +/// * [`out_result`] - On success, will be set to point to a newly allocated array of PlistRef /// /// # Returns /// An error code indicating success or failure @@ -236,3 +234,421 @@ pub unsafe extern "C" fn installation_proxy_client_free( let _ = unsafe { Box::from_raw(handle) }; } } + +/// Installs an application package on the device +/// +/// # Arguments +/// * [`client`] - A valid InstallationProxyClient handle +/// * [`package_path`] - Path to the .ipa package in the AFC jail +/// * [`options`] - Optional installation options as a plist dictionary (can be NULL) +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `client` must be a valid pointer to a handle allocated by this library +/// `package_path` must be a valid C string +/// `options` must be a valid plist dictionary or NULL +#[unsafe(no_mangle)] +pub unsafe extern "C" fn installation_proxy_install( + client: *mut InstallationProxyClientHandle, + package_path: *const libc::c_char, + options: *mut c_void, +) -> IdeviceErrorCode { + if client.is_null() || package_path.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let package_path = unsafe { std::ffi::CStr::from_ptr(package_path) } + .to_string_lossy() + .into_owned(); + let options = if options.is_null() { + None + } else { + Some(util::libplist_to_plist(options)) + }; + + let res = RUNTIME.block_on(async { + unsafe { &mut *client } + .0 + .install(package_path, options) + .await + }); + + match res { + Ok(_) => IdeviceErrorCode::IdeviceSuccess, + Err(e) => e.into(), + } +} + +/// Installs an application package on the device +/// +/// # Arguments +/// * [`client`] - A valid InstallationProxyClient handle +/// * [`package_path`] - Path to the .ipa package in the AFC jail +/// * [`options`] - Optional installation options as a plist dictionary (can be NULL) +/// * [`callback`] - Progress callback function +/// * [`context`] - User context to pass to callback +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `client` must be a valid pointer to a handle allocated by this library +/// `package_path` must be a valid C string +/// `options` must be a valid plist dictionary or NULL +#[unsafe(no_mangle)] +pub unsafe extern "C" fn installation_proxy_install_with_callback( + client: *mut InstallationProxyClientHandle, + package_path: *const libc::c_char, + options: *mut c_void, + callback: extern "C" fn(progress: u64, context: *mut c_void), + context: *mut c_void, +) -> IdeviceErrorCode { + if client.is_null() || package_path.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let package_path = unsafe { std::ffi::CStr::from_ptr(package_path) } + .to_string_lossy() + .into_owned(); + let options = if options.is_null() { + None + } else { + Some(util::libplist_to_plist(options)) + }; + + let res = RUNTIME.block_on(async { + let callback_wrapper = |(progress, context)| async move { + callback(progress, context); + }; + + unsafe { &mut *client } + .0 + .install_with_callback(package_path, options, callback_wrapper, context) + .await + }); + + match res { + Ok(_) => IdeviceErrorCode::IdeviceSuccess, + Err(e) => e.into(), + } +} + +/// Upgrades an existing application on the device +/// +/// # Arguments +/// * [`client`] - A valid InstallationProxyClient handle +/// * [`package_path`] - Path to the .ipa package in the AFC jail +/// * [`options`] - Optional upgrade options as a plist dictionary (can be NULL) +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `client` must be a valid pointer to a handle allocated by this library +/// `package_path` must be a valid C string +/// `options` must be a valid plist dictionary or NULL +#[unsafe(no_mangle)] +pub unsafe extern "C" fn installation_proxy_upgrade( + client: *mut InstallationProxyClientHandle, + package_path: *const libc::c_char, + options: *mut c_void, +) -> IdeviceErrorCode { + if client.is_null() || package_path.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let package_path = unsafe { std::ffi::CStr::from_ptr(package_path) } + .to_string_lossy() + .into_owned(); + let options = if options.is_null() { + None + } else { + Some(util::libplist_to_plist(options)) + }; + + let res = RUNTIME.block_on(async { + unsafe { &mut *client } + .0 + .upgrade(package_path, options) + .await + }); + + match res { + Ok(_) => IdeviceErrorCode::IdeviceSuccess, + Err(e) => e.into(), + } +} + +/// Upgrades an existing application on the device +/// +/// # Arguments +/// * [`client`] - A valid InstallationProxyClient handle +/// * [`package_path`] - Path to the .ipa package in the AFC jail +/// * [`options`] - Optional upgrade options as a plist dictionary (can be NULL) +/// * [`callback`] - Progress callback function +/// * [`context`] - User context to pass to callback +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `client` must be a valid pointer to a handle allocated by this library +/// `package_path` must be a valid C string +/// `options` must be a valid plist dictionary or NULL +#[unsafe(no_mangle)] +pub unsafe extern "C" fn installation_proxy_upgrade_with_callback( + client: *mut InstallationProxyClientHandle, + package_path: *const libc::c_char, + options: *mut c_void, + callback: extern "C" fn(progress: u64, context: *mut c_void), + context: *mut c_void, +) -> IdeviceErrorCode { + if client.is_null() || package_path.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let package_path = unsafe { std::ffi::CStr::from_ptr(package_path) } + .to_string_lossy() + .into_owned(); + let options = if options.is_null() { + None + } else { + Some(util::libplist_to_plist(options)) + }; + + let res = RUNTIME.block_on(async { + let callback_wrapper = |(progress, context)| async move { + callback(progress, context); + }; + + unsafe { &mut *client } + .0 + .upgrade_with_callback(package_path, options, callback_wrapper, context) + .await + }); + + match res { + Ok(_) => IdeviceErrorCode::IdeviceSuccess, + Err(e) => e.into(), + } +} + +/// Uninstalls an application from the device +/// +/// # Arguments +/// * [`client`] - A valid InstallationProxyClient handle +/// * [`bundle_id`] - Bundle identifier of the application to uninstall +/// * [`options`] - Optional uninstall options as a plist dictionary (can be NULL) +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `client` must be a valid pointer to a handle allocated by this library +/// `bundle_id` must be a valid C string +/// `options` must be a valid plist dictionary or NULL +#[unsafe(no_mangle)] +pub unsafe extern "C" fn installation_proxy_uninstall( + client: *mut InstallationProxyClientHandle, + bundle_id: *const libc::c_char, + options: *mut c_void, +) -> IdeviceErrorCode { + if client.is_null() || bundle_id.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let bundle_id = unsafe { std::ffi::CStr::from_ptr(bundle_id) } + .to_string_lossy() + .into_owned(); + let options = if options.is_null() { + None + } else { + Some(util::libplist_to_plist(options)) + }; + + let res = RUNTIME.block_on(async { + unsafe { &mut *client } + .0 + .uninstall(bundle_id, options) + .await + }); + + match res { + Ok(_) => IdeviceErrorCode::IdeviceSuccess, + Err(e) => e.into(), + } +} + +/// Uninstalls an application from the device +/// +/// # Arguments +/// * [`client`] - A valid InstallationProxyClient handle +/// * [`bundle_id`] - Bundle identifier of the application to uninstall +/// * [`options`] - Optional uninstall options as a plist dictionary (can be NULL) +/// * [`callback`] - Progress callback function +/// * [`context`] - User context to pass to callback +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `client` must be a valid pointer to a handle allocated by this library +/// `bundle_id` must be a valid C string +/// `options` must be a valid plist dictionary or NULL +#[unsafe(no_mangle)] +pub unsafe extern "C" fn installation_proxy_uninstall_with_callback( + client: *mut InstallationProxyClientHandle, + bundle_id: *const libc::c_char, + options: *mut c_void, + callback: extern "C" fn(progress: u64, context: *mut c_void), + context: *mut c_void, +) -> IdeviceErrorCode { + if client.is_null() || bundle_id.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let bundle_id = unsafe { std::ffi::CStr::from_ptr(bundle_id) } + .to_string_lossy() + .into_owned(); + let options = if options.is_null() { + None + } else { + Some(util::libplist_to_plist(options)) + }; + + let res = RUNTIME.block_on(async { + let callback_wrapper = |(progress, context)| async move { + callback(progress, context); + }; + + unsafe { &mut *client } + .0 + .uninstall_with_callback(bundle_id, options, callback_wrapper, context) + .await + }); + + match res { + Ok(_) => IdeviceErrorCode::IdeviceSuccess, + Err(e) => e.into(), + } +} + +/// Checks if the device capabilities match the required capabilities +/// +/// # Arguments +/// * [`client`] - A valid InstallationProxyClient handle +/// * [`capabilities`] - Array of plist values representing required capabilities +/// * [`capabilities_len`] - Length of the capabilities array +/// * [`options`] - Optional check options as a plist dictionary (can be NULL) +/// * [`out_result`] - Will be set to true if all capabilities are supported, false otherwise +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `client` must be a valid pointer to a handle allocated by this library +/// `capabilities` must be a valid array of plist values or NULL +/// `options` must be a valid plist dictionary or NULL +/// `out_result` must be a valid pointer to a bool +#[unsafe(no_mangle)] +pub unsafe extern "C" fn installation_proxy_check_capabilities_match( + client: *mut InstallationProxyClientHandle, + capabilities: *const *mut c_void, + capabilities_len: libc::size_t, + options: *mut c_void, + out_result: *mut bool, +) -> IdeviceErrorCode { + if client.is_null() || out_result.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let capabilities = if capabilities.is_null() { + Vec::new() + } else { + unsafe { std::slice::from_raw_parts(capabilities, capabilities_len) } + .iter() + .map(|&ptr| util::libplist_to_plist(ptr)) + .collect() + }; + + let options = if options.is_null() { + None + } else { + Some(util::libplist_to_plist(options)) + }; + + let res = RUNTIME.block_on(async { + unsafe { &mut *client } + .0 + .check_capabilities_match(capabilities, options) + .await + }); + + match res { + Ok(result) => { + unsafe { *out_result = result }; + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} + +/// Browses installed applications on the device +/// +/// # Arguments +/// * [`client`] - A valid InstallationProxyClient handle +/// * [`options`] - Optional browse options as a plist dictionary (can be NULL) +/// * [`out_result`] - On success, will be set to point to a newly allocated array of PlistRef +/// * [`out_result_len`] - Will be set to the length of the result array +/// +/// # Returns +/// An error code indicating success or failure +/// +/// # Safety +/// `client` must be a valid pointer to a handle allocated by this library +/// `options` must be a valid plist dictionary or NULL +/// `out_result` must be a valid, non-null pointer to a location where the result will be stored +/// `out_result_len` must be a valid, non-null pointer to a location where the length will be stored +#[unsafe(no_mangle)] +pub unsafe extern "C" fn installation_proxy_browse( + client: *mut InstallationProxyClientHandle, + options: *mut c_void, + out_result: *mut *mut c_void, + out_result_len: *mut libc::size_t, +) -> IdeviceErrorCode { + if client.is_null() || out_result.is_null() || out_result_len.is_null() { + return IdeviceErrorCode::InvalidArg; + } + + let options = if options.is_null() { + None + } else { + Some(util::libplist_to_plist(options)) + }; + + let res: Result, IdeviceError> = RUNTIME.block_on(async { + unsafe { &mut *client }.0.browse(options).await.map(|apps| { + apps.into_iter() + .map(|v| util::plist_to_libplist(&v)) + .collect() + }) + }); + + match res { + Ok(r) => { + let len = r.len(); + let boxed_slice = r.into_boxed_slice(); + let ptr = boxed_slice.as_ptr(); + std::mem::forget(boxed_slice); + + unsafe { + *out_result = ptr as *mut c_void; + *out_result_len = len; + } + IdeviceErrorCode::IdeviceSuccess + } + Err(e) => e.into(), + } +} diff --git a/ffi/src/util.rs b/ffi/src/util.rs index 1dad008..96e0c4d 100644 --- a/ffi/src/util.rs +++ b/ffi/src/util.rs @@ -3,6 +3,7 @@ use std::{ ffi::c_int, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, + os::raw::c_void, }; use libc::{sockaddr_in, sockaddr_in6}; @@ -87,3 +88,12 @@ pub(crate) fn plist_to_libplist(v: &Value) -> *mut libc::c_void { p.false_drop(); ptr } + +pub(crate) fn libplist_to_plist(v: *mut c_void) -> Value { + let v: plist_plus::Plist = v.into(); + let v_string = v.to_string(); + v.false_drop(); + + let reader = std::io::Cursor::new(v_string.as_bytes()); + plist::from_reader(reader).unwrap() +}