diff --git a/cpp/include/idevice++/diagnostics_relay.hpp b/cpp/include/idevice++/diagnostics_relay.hpp new file mode 100644 index 0000000..77a1bda --- /dev/null +++ b/cpp/include/idevice++/diagnostics_relay.hpp @@ -0,0 +1,60 @@ +// Jackson Coxson + +#pragma once +#include +#include +#include +#include +#include +#include + +namespace IdeviceFFI { + +using DiagnosticsRelayPtr = + std::unique_ptr>; + +class DiagnosticsRelay { + 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); + + // API Methods - queries returning optional plist + Result, FfiError> ioregistry(Option current_plane, + Option entry_name, + Option entry_class) const; + + Result, FfiError> mobilegestalt(Option> keys) const; + + Result, FfiError> gasguage() const; + Result, FfiError> nand() const; + Result, FfiError> all() const; + Result, FfiError> wifi() const; + + // API Methods - actions + Result restart(); + Result shutdown(); + Result sleep(); + Result goodbye(); + + // RAII / moves + ~DiagnosticsRelay() noexcept = default; + DiagnosticsRelay(DiagnosticsRelay&&) noexcept = default; + DiagnosticsRelay& operator=(DiagnosticsRelay&&) noexcept = default; + DiagnosticsRelay(const DiagnosticsRelay&) = delete; + DiagnosticsRelay& operator=(const DiagnosticsRelay&) = delete; + + DiagnosticsRelayClientHandle* raw() const noexcept { return handle_.get(); } + static DiagnosticsRelay adopt(DiagnosticsRelayClientHandle* h) noexcept { + return DiagnosticsRelay(h); + } + + private: + explicit DiagnosticsRelay(DiagnosticsRelayClientHandle* h) noexcept : handle_(h) {} + DiagnosticsRelayPtr handle_{}; +}; + +} // namespace IdeviceFFI \ No newline at end of file diff --git a/cpp/src/diagnostics_relay.cpp b/cpp/src/diagnostics_relay.cpp new file mode 100644 index 0000000..398c923 --- /dev/null +++ b/cpp/src/diagnostics_relay.cpp @@ -0,0 +1,159 @@ +// Jackson Coxson + +#include +#include +#include +#include + +namespace IdeviceFFI { + +// -------- Factory Methods -------- + +Result DiagnosticsRelay::connect(Provider& provider) { + DiagnosticsRelayClientHandle* out = nullptr; + FfiError e(::diagnostics_relay_client_connect(provider.raw(), &out)); + if (e) { + return Err(e); + } + return Ok(DiagnosticsRelay::adopt(out)); +} + +Result DiagnosticsRelay::from_socket(Idevice&& socket) { + DiagnosticsRelayClientHandle* out = nullptr; + FfiError e(::diagnostics_relay_client_new(socket.raw(), &out)); + if (e) { + return Err(e); + } + socket.release(); + return Ok(DiagnosticsRelay::adopt(out)); +} + +// -------- API Methods -------- + +Result, FfiError> +DiagnosticsRelay::ioregistry(Option current_plane, + Option entry_name, + Option entry_class) const { + plist_t res = nullptr; + + const char* plane_ptr = current_plane.is_some() ? current_plane.unwrap().c_str() : nullptr; + const char* name_ptr = entry_name.is_some() ? entry_name.unwrap().c_str() : nullptr; + const char* class_ptr = entry_class.is_some() ? entry_class.unwrap().c_str() : nullptr; + + FfiError e( + ::diagnostics_relay_client_ioregistry(handle_.get(), plane_ptr, name_ptr, class_ptr, &res)); + if (e) { + return Err(e); + } + + if (res == nullptr) { + return Ok(Option(None)); + } + return Ok(Some(res)); +} + +Result, FfiError> +DiagnosticsRelay::mobilegestalt(Option> keys) const { + plist_t res = nullptr; + + if (!keys.is_some() || keys.unwrap().empty()) { + return Err(FfiError::InvalidArgument()); + } + + FfiError e(::diagnostics_relay_client_mobilegestalt( + handle_.get(), keys.unwrap().data(), keys.unwrap().size(), &res)); + if (e) { + return Err(e); + } + + if (res == nullptr) { + return Ok(Option(None)); + } + return Ok(Some(res)); +} + +Result, FfiError> DiagnosticsRelay::gasguage() const { + plist_t res = nullptr; + FfiError e(::diagnostics_relay_client_gasguage(handle_.get(), &res)); + if (e) { + return Err(e); + } + + if (res == nullptr) { + return Ok(Option(None)); + } + return Ok(Some(res)); +} + +Result, FfiError> DiagnosticsRelay::nand() const { + plist_t res = nullptr; + FfiError e(::diagnostics_relay_client_nand(handle_.get(), &res)); + if (e) { + return Err(e); + } + + if (res == nullptr) { + return Ok(Option(None)); + } + return Ok(Some(res)); +} + +Result, FfiError> DiagnosticsRelay::all() const { + plist_t res = nullptr; + FfiError e(::diagnostics_relay_client_all(handle_.get(), &res)); + if (e) { + return Err(e); + } + + if (res == nullptr) { + return Ok(Option(None)); + } + return Ok(Some(res)); +} + +Result, FfiError> DiagnosticsRelay::wifi() const { + plist_t res = nullptr; + FfiError e(::diagnostics_relay_client_wifi(handle_.get(), &res)); + if (e) { + return Err(e); + } + + if (res == nullptr) { + return Ok(Option(None)); + } + return Ok(Some(res)); +} + +Result DiagnosticsRelay::restart() { + FfiError e(::diagnostics_relay_client_restart(handle_.get())); + if (e) { + return Err(e); + } + return Ok(); +} + +Result DiagnosticsRelay::shutdown() { + FfiError e(::diagnostics_relay_client_shutdown(handle_.get())); + if (e) { + return Err(e); + } + return Ok(); +} + +Result DiagnosticsRelay::sleep() { + FfiError e(::diagnostics_relay_client_sleep(handle_.get())); + if (e) { + return Err(e); + } + return Ok(); +} + +Result DiagnosticsRelay::goodbye() { + FfiError e(::diagnostics_relay_client_goodbye(handle_.get())); + if (e) { + return Err(e); + } + return Ok(); +} + +} // namespace IdeviceFFI \ No newline at end of file diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 13c2b36..4e3d8f7 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -50,6 +50,7 @@ tss = ["idevice/tss"] tunneld = ["idevice/tunneld"] usbmuxd = ["idevice/usbmuxd"] xpc = ["idevice/xpc"] +screenshotr = ["idevice/screenshotr"] full = [ "afc", "amfi", @@ -75,6 +76,7 @@ full = [ "tunneld", "springboardservices", "syslog_relay", + "screenshotr", ] default = ["full", "aws-lc"] diff --git a/ffi/src/afc.rs b/ffi/src/afc.rs index 6c70436..19988da 100644 --- a/ffi/src/afc.rs +++ b/ffi/src/afc.rs @@ -620,10 +620,10 @@ pub unsafe extern "C" fn afc_file_read( let fd = unsafe { &mut *(handle as *mut FileDescriptor) }; let res: Result, IdeviceError> = run_sync({ - let mut buf = Vec::with_capacity(len); + let mut buf = vec![0u8; len]; async move { let r = fd.read(&mut buf).await?; - buf.resize(r, 0); + buf.truncate(r); Ok(buf) } }); diff --git a/ffi/src/diagnostics_relay.rs b/ffi/src/diagnostics_relay.rs index fb65a11..66e2c83 100644 --- a/ffi/src/diagnostics_relay.rs +++ b/ffi/src/diagnostics_relay.rs @@ -190,7 +190,7 @@ pub unsafe extern "C" fn diagnostics_relay_client_mobilegestalt( return ffi_err!(IdeviceError::FfiInvalidArg); } - let keys = if keys.is_null() { + let keys = if !keys.is_null() { let keys = unsafe { std::slice::from_raw_parts(keys, keys_len) }; Some( keys.iter() diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index b65c8b1..31e0147 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -35,6 +35,8 @@ mod pairing_file; pub mod provider; #[cfg(feature = "xpc")] pub mod rsd; +#[cfg(feature = "screenshotr")] +pub mod screenshotr; #[cfg(feature = "springboardservices")] pub mod springboardservices; #[cfg(feature = "syslog_relay")] diff --git a/ffi/src/lockdown.rs b/ffi/src/lockdown.rs index 046dfa2..77b7717 100644 --- a/ffi/src/lockdown.rs +++ b/ffi/src/lockdown.rs @@ -250,6 +250,60 @@ pub unsafe extern "C" fn lockdownd_enter_recovery( } } +/// Sets a value in lockdownd +/// +/// # Arguments +/// * `client` - A valid LockdowndClient handle +/// * `key` - The key to set (null-terminated string) +/// * `value` - The value to set as a plist +/// * `domain` - The domain to set in (null-terminated string, optional) +/// +/// # Returns +/// An IdeviceFfiError on error, null on success +/// +/// # Safety +/// `client` must be a valid pointer to a handle allocated by this library +/// `key` must be a valid null-terminated string +/// `value` must be a valid plist +/// `domain` must be a valid null-terminated string or NULL +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lockdownd_set_value( + client: *mut LockdowndClientHandle, + key: *const libc::c_char, + value: plist_t, + domain: *const libc::c_char, +) -> *mut IdeviceFfiError { + if client.is_null() || key.is_null() || value.is_null() { + return ffi_err!(IdeviceError::FfiInvalidArg); + } + + let key = match unsafe { std::ffi::CStr::from_ptr(key) }.to_str() { + Ok(k) => k, + Err(_) => return ffi_err!(IdeviceError::InvalidCString), + }; + + let domain = if domain.is_null() { + None + } else { + Some(match unsafe { std::ffi::CStr::from_ptr(domain) }.to_str() { + Ok(d) => d, + Err(_) => return ffi_err!(IdeviceError::InvalidCString), + }) + }; + + let value = unsafe { &mut *value }.borrow_self().clone(); + + let res: Result<(), IdeviceError> = run_sync_local(async move { + let client_ref = unsafe { &mut (*client).0 }; + client_ref.set_value(key, value, domain).await + }); + + match res { + Ok(_) => null_mut(), + Err(e) => ffi_err!(e), + } +} + /// Frees a LockdowndClient handle /// /// # Arguments diff --git a/ffi/src/screenshotr.rs b/ffi/src/screenshotr.rs new file mode 100644 index 0000000..f6b52aa --- /dev/null +++ b/ffi/src/screenshotr.rs @@ -0,0 +1,132 @@ +// Jackson Coxson + +use std::ptr::null_mut; + +use idevice::{ + IdeviceError, IdeviceService, provider::IdeviceProvider, + services::screenshotr::ScreenshotService, +}; + +use crate::{IdeviceFfiError, ffi_err, provider::IdeviceProviderHandle, run_sync_local}; + +pub struct ScreenshotrClientHandle(pub ScreenshotService); + +/// Represents a screenshot data buffer +#[repr(C)] +pub struct ScreenshotData { + pub data: *mut u8, + pub length: usize, +} + +/// Connects to screenshotr service using provider +/// +/// # Arguments +/// * [`provider`] - An IdeviceProvider +/// * [`client`] - On success, will be set to point to a newly allocated ScreenshotrClient 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 screenshotr_connect( + provider: *mut IdeviceProviderHandle, + client: *mut *mut ScreenshotrClientHandle, +) -> *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 }; + ScreenshotService::connect(provider_ref).await + }); + + match res { + Ok(r) => { + let boxed = Box::new(ScreenshotrClientHandle(r)); + unsafe { *client = Box::into_raw(boxed) }; + null_mut() + } + Err(e) => ffi_err!(e), + } +} + +/// Takes a screenshot from the device +/// +/// # Arguments +/// * `client` - A valid ScreenshotrClient handle +/// * `screenshot` - Pointer to store the screenshot data +/// +/// # Returns +/// An IdeviceFfiError on error, null on success +/// +/// # Safety +/// `client` must be a valid pointer to a handle allocated by this library +/// `screenshot` must be a valid pointer to store the screenshot data +/// The caller is responsible for freeing the screenshot data using screenshotr_screenshot_free +#[unsafe(no_mangle)] +pub unsafe extern "C" fn screenshotr_take_screenshot( + client: *mut ScreenshotrClientHandle, + screenshot: *mut ScreenshotData, +) -> *mut IdeviceFfiError { + if client.is_null() || screenshot.is_null() { + return ffi_err!(IdeviceError::FfiInvalidArg); + } + + let res: Result, IdeviceError> = run_sync_local(async move { + let client_ref = unsafe { &mut (*client).0 }; + client_ref.take_screenshot().await + }); + + match res { + Ok(data) => { + let len = data.len(); + let boxed = data.into_boxed_slice(); + let ptr = Box::into_raw(boxed) as *mut u8; + + unsafe { + (*screenshot).data = ptr; + (*screenshot).length = len; + } + null_mut() + } + Err(e) => ffi_err!(e), + } +} + +/// Frees screenshot data +/// +/// # Arguments +/// * `screenshot` - The screenshot data to free +/// +/// # Safety +/// `screenshot` must be a valid ScreenshotData that was allocated by screenshotr_take_screenshot +/// or NULL (in which case this function does nothing) +#[unsafe(no_mangle)] +pub unsafe extern "C" fn screenshotr_screenshot_free(screenshot: ScreenshotData) { + if !screenshot.data.is_null() && screenshot.length > 0 { + tracing::debug!("Freeing screenshot data"); + let _ = + unsafe { Vec::from_raw_parts(screenshot.data, screenshot.length, screenshot.length) }; + } +} + +/// Frees a ScreenshotrClient 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 screenshotr_client_free(handle: *mut ScreenshotrClientHandle) { + if !handle.is_null() { + tracing::debug!("Freeing screenshotr_client"); + let _ = unsafe { Box::from_raw(handle) }; + } +}