diff --git a/README.md b/README.md index 16d73e0..27f326a 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ To keep dependency bloat and compile time down, everything is contained in featu | `tunneld` | Interface with [pymobiledevice3](https://github.com/doronz88/pymobiledevice3)'s tunneld. | | `usbmuxd` | Connect using the usbmuxd daemon.| | `xpc` | Access protected services via XPC over RSD. | +| `notification_proxy` | Post and observe iOS notifications. | ### Planned/TODO @@ -85,7 +86,7 @@ Finish the following: Implement the following: -- notification_proxy +- file_relay As this project is done in my free time within my busy schedule, there is no ETA for any of these. Feel free to contribute or donate! diff --git a/cpp/include/idevice++/crashreportcopymobile.hpp b/cpp/include/idevice++/crashreportcopymobile.hpp new file mode 100644 index 0000000..dffb1e7 --- /dev/null +++ b/cpp/include/idevice++/crashreportcopymobile.hpp @@ -0,0 +1,50 @@ +// Jackson Coxson + +#pragma once +#include +#include +#include +#include +#include +#include + +namespace IdeviceFFI { + +using CrashReportCopyMobilePtr = + std::unique_ptr>; + +class CrashReportCopyMobile { + public: + // Factory: connect via Provider + static Result connect(Provider& provider); + + // Factory: wrap an existing Idevice socket (consumes it on success) + static Result from_socket(Idevice&& socket); + + // Static: flush crash reports from system storage + static Result flush(Provider& provider); + + // Ops + Result, FfiError> ls(const char* dir_path = nullptr); + Result, FfiError> pull(const std::string& log_name); + Result remove(const std::string& log_name); + + // RAII / moves + ~CrashReportCopyMobile() noexcept = default; + CrashReportCopyMobile(CrashReportCopyMobile&&) noexcept = default; + CrashReportCopyMobile& operator=(CrashReportCopyMobile&&) noexcept = default; + CrashReportCopyMobile(const CrashReportCopyMobile&) = delete; + CrashReportCopyMobile& operator=(const CrashReportCopyMobile&) = delete; + + CrashReportCopyMobileHandle* raw() const noexcept { return handle_.get(); } + static CrashReportCopyMobile adopt(CrashReportCopyMobileHandle* h) noexcept { + return CrashReportCopyMobile(h); + } + + private: + explicit CrashReportCopyMobile(CrashReportCopyMobileHandle* h) noexcept : handle_(h) {} + CrashReportCopyMobilePtr handle_{}; +}; + +} // namespace IdeviceFFI diff --git a/cpp/src/crashreportcopymobile.cpp b/cpp/src/crashreportcopymobile.cpp new file mode 100644 index 0000000..23013ba --- /dev/null +++ b/cpp/src/crashreportcopymobile.cpp @@ -0,0 +1,94 @@ +// Jackson Coxson + +#include +#include +#include +#include +#include + +namespace IdeviceFFI { + +// -------- Factory Methods -------- + +Result CrashReportCopyMobile::connect(Provider& provider) { + CrashReportCopyMobileHandle* out = nullptr; + FfiError e(::crash_report_client_connect(provider.raw(), &out)); + if (e) { + return Err(e); + } + return Ok(CrashReportCopyMobile::adopt(out)); +} + +Result CrashReportCopyMobile::from_socket(Idevice&& socket) { + CrashReportCopyMobileHandle* out = nullptr; + FfiError e(::crash_report_client_new(socket.raw(), &out)); + if (e) { + return Err(e); + } + socket.release(); + return Ok(CrashReportCopyMobile::adopt(out)); +} + +Result CrashReportCopyMobile::flush(Provider& provider) { + FfiError e(::crash_report_flush(provider.raw())); + if (e) { + return Err(e); + } + return Ok(); +} + +// -------- Ops -------- + +Result, FfiError> +CrashReportCopyMobile::ls(const char* dir_path) { + char** entries_raw = nullptr; + size_t count = 0; + + FfiError e(::crash_report_client_ls(handle_.get(), dir_path, &entries_raw, &count)); + if (e) { + return Err(e); + } + + std::vector result; + if (entries_raw) { + result.reserve(count); + for (size_t i = 0; i < count; ++i) { + if (entries_raw[i]) { + result.emplace_back(entries_raw[i]); + ::idevice_string_free(entries_raw[i]); + } + } + std::free(entries_raw); + } + + return Ok(std::move(result)); +} + +Result, FfiError> +CrashReportCopyMobile::pull(const std::string& log_name) { + uint8_t* data = nullptr; + size_t length = 0; + + FfiError e(::crash_report_client_pull(handle_.get(), log_name.c_str(), &data, &length)); + if (e) { + return Err(e); + } + + std::vector result; + if (data && length > 0) { + result.assign(reinterpret_cast(data), reinterpret_cast(data) + length); + ::idevice_data_free(data, length); + } + + return Ok(std::move(result)); +} + +Result CrashReportCopyMobile::remove(const std::string& log_name) { + FfiError e(::crash_report_client_remove(handle_.get(), log_name.c_str())); + if (e) { + return Err(e); + } + return Ok(); +} + +} // namespace IdeviceFFI diff --git a/ffi/src/crashreportcopymobile.rs b/ffi/src/crashreportcopymobile.rs new file mode 100644 index 0000000..95ee0da --- /dev/null +++ b/ffi/src/crashreportcopymobile.rs @@ -0,0 +1,325 @@ +// Jackson Coxson + +use std::{ + ffi::{CStr, c_char}, + ptr::null_mut, +}; + +use idevice::{ + IdeviceError, IdeviceService, + provider::IdeviceProvider, + services::crashreportcopymobile::{CrashReportCopyMobileClient, flush_reports}, +}; + +use crate::{ + IdeviceFfiError, IdeviceHandle, afc::AfcClientHandle, ffi_err, provider::IdeviceProviderHandle, + run_sync_local, +}; + +pub struct CrashReportCopyMobileHandle(pub CrashReportCopyMobileClient); + +/// Automatically creates and connects to the crash report copy mobile service, +/// returning a client handle +/// +/// # Arguments +/// * [`provider`] - An IdeviceProvider +/// * [`client`] - On success, will be set to point to a newly allocated handle +/// +/// # Returns +/// An IdeviceFfiError on error, null on success +/// +/// # Safety +/// `provider` must be a valid pointer to a handle allocated by this library +/// `client` must be a valid, non-null pointer to a location where the handle will be stored +#[unsafe(no_mangle)] +pub unsafe extern "C" fn crash_report_client_connect( + provider: *mut IdeviceProviderHandle, + client: *mut *mut CrashReportCopyMobileHandle, +) -> *mut IdeviceFfiError { + if provider.is_null() || client.is_null() { + tracing::error!("Null pointer provided"); + return ffi_err!(IdeviceError::FfiInvalidArg); + } + + let res: Result = run_sync_local(async move { + let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 }; + CrashReportCopyMobileClient::connect(provider_ref).await + }); + + match res { + Ok(r) => { + let boxed = Box::new(CrashReportCopyMobileHandle(r)); + unsafe { *client = Box::into_raw(boxed) }; + null_mut() + } + Err(e) => ffi_err!(e), + } +} + +/// Creates a new CrashReportCopyMobile client from an existing Idevice connection +/// +/// # Arguments +/// * [`socket`] - An IdeviceSocket handle +/// * [`client`] - On success, will be set to point to a newly allocated handle +/// +/// # Returns +/// An IdeviceFfiError on error, null on success +/// +/// # Safety +/// `socket` must be a valid pointer to a handle allocated by this library +/// `client` must be a valid, non-null pointer to a location where the handle will be stored +#[unsafe(no_mangle)] +pub unsafe extern "C" fn crash_report_client_new( + socket: *mut IdeviceHandle, + client: *mut *mut CrashReportCopyMobileHandle, +) -> *mut IdeviceFfiError { + if socket.is_null() || client.is_null() { + return ffi_err!(IdeviceError::FfiInvalidArg); + } + let socket = unsafe { Box::from_raw(socket) }.0; + let r = CrashReportCopyMobileClient::new(socket); + let boxed = Box::new(CrashReportCopyMobileHandle(r)); + unsafe { *client = Box::into_raw(boxed) }; + null_mut() +} + +/// Lists crash report files in the specified directory +/// +/// # Arguments +/// * [`client`] - A valid CrashReportCopyMobile handle +/// * [`dir_path`] - Optional directory path (NULL for root "/") +/// * [`entries`] - Will be set to point to an array of C strings +/// * [`count`] - Will be set to the number of entries +/// +/// # Returns +/// An IdeviceFfiError on error, null on success +/// +/// # Safety +/// All pointers must be valid and non-null +/// `dir_path` may be NULL (defaults to root) +/// Caller must free the returned array with `afc_free_directory_entries` +#[unsafe(no_mangle)] +pub unsafe extern "C" fn crash_report_client_ls( + client: *mut CrashReportCopyMobileHandle, + dir_path: *const c_char, + entries: *mut *mut *mut c_char, + count: *mut libc::size_t, +) -> *mut IdeviceFfiError { + if client.is_null() || entries.is_null() || count.is_null() { + return ffi_err!(IdeviceError::FfiInvalidArg); + } + + let path = if dir_path.is_null() { + None + } else { + match unsafe { CStr::from_ptr(dir_path) }.to_str() { + Ok(s) => Some(s), + Err(_) => return ffi_err!(IdeviceError::FfiInvalidString), + } + }; + + let res: Result, IdeviceError> = run_sync_local(async { + let client_ref = unsafe { &mut (*client).0 }; + client_ref.ls(path).await + }); + + match res { + Ok(items) => { + let c_strings = items + .into_iter() + .filter_map(|s| std::ffi::CString::new(s).ok()) + .collect::>(); + + let string_count = c_strings.len(); + + // Allocate array for char pointers (with NULL terminator) + let layout = std::alloc::Layout::array::<*mut c_char>(string_count + 1).unwrap(); + let ptr = unsafe { std::alloc::alloc(layout) as *mut *mut c_char }; + if ptr.is_null() { + return ffi_err!(IdeviceError::FfiInvalidArg); + } + + for (i, cstring) in c_strings.into_iter().enumerate() { + let string_ptr = cstring.into_raw(); + unsafe { *ptr.add(i) = string_ptr }; + } + + // NULL terminator + unsafe { *ptr.add(string_count) = std::ptr::null_mut() }; + + unsafe { + *entries = ptr; + *count = string_count; + } + + null_mut() + } + Err(e) => ffi_err!(e), + } +} + +/// Downloads a crash report file from the device +/// +/// # Arguments +/// * [`client`] - A valid CrashReportCopyMobile handle +/// * [`log_name`] - Name of the log file to download (C string) +/// * [`data`] - Will be set to point to the file contents +/// * [`length`] - Will be set to the size of the data +/// +/// # Returns +/// An IdeviceFfiError on error, null on success +/// +/// # Safety +/// All pointers must be valid and non-null +/// `log_name` must be a valid C string +/// Caller must free the returned data with `idevice_data_free` +#[unsafe(no_mangle)] +pub unsafe extern "C" fn crash_report_client_pull( + client: *mut CrashReportCopyMobileHandle, + log_name: *const c_char, + data: *mut *mut u8, + length: *mut libc::size_t, +) -> *mut IdeviceFfiError { + if client.is_null() || log_name.is_null() || data.is_null() || length.is_null() { + return ffi_err!(IdeviceError::FfiInvalidArg); + } + + let name = match unsafe { CStr::from_ptr(log_name) }.to_str() { + Ok(s) => s.to_string(), + Err(_) => return ffi_err!(IdeviceError::FfiInvalidString), + }; + + let res: Result, IdeviceError> = run_sync_local(async { + let client_ref = unsafe { &mut (*client).0 }; + client_ref.pull(name).await + }); + + match res { + Ok(file_data) => { + let len = file_data.len(); + let mut boxed = file_data.into_boxed_slice(); + unsafe { + *data = boxed.as_mut_ptr(); + *length = len; + } + std::mem::forget(boxed); + null_mut() + } + Err(e) => ffi_err!(e), + } +} + +/// Removes a crash report file from the device +/// +/// # Arguments +/// * [`client`] - A valid CrashReportCopyMobile handle +/// * [`log_name`] - Name of the log file to remove (C string) +/// +/// # Returns +/// An IdeviceFfiError on error, null on success +/// +/// # Safety +/// `client` must be a valid pointer to a handle allocated by this library +/// `log_name` must be a valid C string +#[unsafe(no_mangle)] +pub unsafe extern "C" fn crash_report_client_remove( + client: *mut CrashReportCopyMobileHandle, + log_name: *const c_char, +) -> *mut IdeviceFfiError { + if client.is_null() || log_name.is_null() { + return ffi_err!(IdeviceError::FfiInvalidArg); + } + + let name = match unsafe { CStr::from_ptr(log_name) }.to_str() { + Ok(s) => s.to_string(), + Err(_) => return ffi_err!(IdeviceError::FfiInvalidString), + }; + + let res = run_sync_local(async { + let client_ref = unsafe { &mut (*client).0 }; + client_ref.remove(name).await + }); + + match res { + Ok(_) => null_mut(), + Err(e) => ffi_err!(e), + } +} + +/// Converts this client to an AFC client for advanced file operations +/// +/// # Arguments +/// * [`client`] - A valid CrashReportCopyMobile handle (will be consumed) +/// * [`afc_client`] - On success, will be set to an AFC client handle +/// +/// # Returns +/// An IdeviceFfiError on error, null on success +/// +/// # Safety +/// `client` must be a valid pointer (will be freed after this call) +/// `afc_client` must be a valid, non-null pointer where the new AFC client will be stored +#[unsafe(no_mangle)] +pub unsafe extern "C" fn crash_report_client_to_afc( + client: *mut CrashReportCopyMobileHandle, + afc_client: *mut *mut AfcClientHandle, +) -> *mut IdeviceFfiError { + if client.is_null() || afc_client.is_null() { + return ffi_err!(IdeviceError::FfiInvalidArg); + } + + let crash_client = unsafe { Box::from_raw(client) }.0; + let afc = crash_client.to_afc_client(); + + let a = Box::into_raw(Box::new(AfcClientHandle(afc))); + unsafe { *afc_client = a }; + + null_mut() +} + +/// Triggers a flush of crash logs from system storage +/// +/// This connects to the crashreportmover service to move crash logs +/// into the AFC-accessible directory. Should be called before listing logs. +/// +/// # Arguments +/// * [`provider`] - An IdeviceProvider +/// +/// # Returns +/// An IdeviceFfiError on error, null on success +/// +/// # Safety +/// `provider` must be a valid pointer to a handle allocated by this library +#[unsafe(no_mangle)] +pub unsafe extern "C" fn crash_report_flush( + provider: *mut IdeviceProviderHandle, +) -> *mut IdeviceFfiError { + if provider.is_null() { + return ffi_err!(IdeviceError::FfiInvalidArg); + } + + let res = run_sync_local(async { + let provider_ref: &dyn IdeviceProvider = unsafe { &*(*provider).0 }; + flush_reports(provider_ref).await + }); + + match res { + Ok(_) => null_mut(), + Err(e) => ffi_err!(e), + } +} + +/// Frees a CrashReportCopyMobile client handle +/// +/// # Arguments +/// * [`handle`] - The handle to free +/// +/// # Safety +/// `handle` must be a valid pointer to the handle that was allocated by this library, +/// or NULL (in which case this function does nothing) +#[unsafe(no_mangle)] +pub unsafe extern "C" fn crash_report_client_free(handle: *mut CrashReportCopyMobileHandle) { + if !handle.is_null() { + tracing::debug!("Freeing crash_report_client"); + let _ = unsafe { Box::from_raw(handle) }; + } +} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 6b8d004..f93806a 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -10,6 +10,8 @@ pub mod amfi; pub mod core_device; #[cfg(feature = "core_device_proxy")] pub mod core_device_proxy; +#[cfg(feature = "crashreportcopymobile")] +pub mod crashreportcopymobile; #[cfg(feature = "debug_proxy")] pub mod debug_proxy; #[cfg(feature = "diagnostics_relay")]