From 02f818a42aeb88f3508277308ec47ae76592ac44 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Tue, 30 Sep 2025 19:52:40 -0600 Subject: [PATCH] Add installation_proxy cpp bindings --- cpp/include/idevice++/installation_proxy.hpp | 60 +++++ cpp/src/installation_proxy.cpp | 240 +++++++++++++++++++ ffi/src/installation_proxy.rs | 2 +- 3 files changed, 301 insertions(+), 1 deletion(-) create mode 100644 cpp/include/idevice++/installation_proxy.hpp create mode 100644 cpp/src/installation_proxy.cpp diff --git a/cpp/include/idevice++/installation_proxy.hpp b/cpp/include/idevice++/installation_proxy.hpp new file mode 100644 index 0000000..66165d6 --- /dev/null +++ b/cpp/include/idevice++/installation_proxy.hpp @@ -0,0 +1,60 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace IdeviceFFI { + +using InstallationProxyPtr = + std::unique_ptr>; + +class InstallationProxy { + 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); + + // Ops + Result, FfiError> + get_apps(Option application_type, + Option> bundle_identifiers); + Result install(std::string package_path, Option options); + Result install_with_callback(std::string package_path, + Option options, + std::function& lambda); + Result upgrade(std::string package_path, Option options); + Result upgrade_with_callback(std::string package_path, + Option options, + std::function& lambda); + Result uninstall(std::string package_path, Option options); + Result uninstall_with_callback(std::string package_path, + Option options, + std::function& lambda); + Result check_capabilities_match(std::vector capabilities, + Option options); + Result, FfiError> browse(Option options); + + // RAII / moves + ~InstallationProxy() noexcept = default; + InstallationProxy(InstallationProxy&&) noexcept = default; + InstallationProxy& operator=(InstallationProxy&&) noexcept = default; + InstallationProxy(const InstallationProxy&) = delete; + InstallationProxy& operator=(const InstallationProxy&) = delete; + + InstallationProxyClientHandle* raw() const noexcept { return handle_.get(); } + static InstallationProxy adopt(InstallationProxyClientHandle* h) noexcept { + return InstallationProxy(h); + } + + private: + explicit InstallationProxy(InstallationProxyClientHandle* h) noexcept : handle_(h) {} + InstallationProxyPtr handle_{}; +}; + +} // namespace IdeviceFFI diff --git a/cpp/src/installation_proxy.cpp b/cpp/src/installation_proxy.cpp new file mode 100644 index 0000000..e01918f --- /dev/null +++ b/cpp/src/installation_proxy.cpp @@ -0,0 +1,240 @@ +// Jackson Coxson + +#include +#include +#include +#include +#include + +namespace IdeviceFFI { + +// -------- Anonymous Namespace for Helpers -------- +namespace { +/** + * @brief A C-style trampoline function to call back into a C++ std::function. + * + * This function is passed to the Rust FFI layer. It receives a void* context, + * which it casts back to the original std::function object to invoke it. + */ +extern "C" void progress_trampoline(u_int64_t progress, void* context) { + if (context) { + auto& callback_fn = *static_cast*>(context); + callback_fn(progress); + } +} +} // namespace + +// -------- Factory Methods -------- + +Result InstallationProxy::connect(Provider& provider) { + InstallationProxyClientHandle* handle = nullptr; + FfiError e(::installation_proxy_connect(provider.raw(), &handle)); + if (e) { + return Err(e); + } + return Ok(InstallationProxy::adopt(handle)); +} + +Result InstallationProxy::from_socket(Idevice&& socket) { + InstallationProxyClientHandle* handle = nullptr; + // The Rust FFI function consumes the socket, so we must release it from the + // C++ RAII wrapper's control. An `Idevice::release()` method is assumed here. + FfiError e(::installation_proxy_new(socket.release(), &handle)); + if (e) { + return Err(e); + } + return Ok(InstallationProxy::adopt(handle)); +} + +// -------- Ops -------- + +Result, FfiError> +InstallationProxy::get_apps(Option application_type, + Option> bundle_identifiers) { + plist_t* apps_raw = nullptr; + size_t apps_len = 0; + + const char* application_type_ptr = NULL; + if (application_type.is_some()) { + application_type_ptr = application_type.unwrap().c_str(); + } + + std::vector c_bundle_id; + size_t bundle_identifiers_len = 0; + if (bundle_identifiers.is_some()) { + c_bundle_id.reserve(bundle_identifiers.unwrap().size()); + for (auto& a : bundle_identifiers.unwrap()) { + c_bundle_id.push_back(a.c_str()); + } + } + + FfiError e(::installation_proxy_get_apps( + this->raw(), + application_type_ptr, + c_bundle_id.empty() ? nullptr : const_cast(c_bundle_id.data()), + bundle_identifiers_len, + apps_raw, + &apps_len)); + if (e) { + return Err(e); + } + + std::vector apps; + if (apps_raw) { + apps.assign(apps_raw, apps_raw + apps_len); + } + + return Ok(std::move(apps)); +} + +Result InstallationProxy::install(std::string package_path, + Option options) { + plist_t unwrapped_options; + if (options.is_some()) { + unwrapped_options = std::move(options).unwrap(); + } else { + unwrapped_options = NULL; + } + + FfiError e(::installation_proxy_install(this->raw(), package_path.c_str(), &unwrapped_options)); + if (e) { + return Err(e); + } + return Ok(); +} + +Result InstallationProxy::install_with_callback( + std::string package_path, Option options, std::function& lambda + +) { + plist_t unwrapped_options; + if (options.is_some()) { + unwrapped_options = std::move(options).unwrap(); + } else { + unwrapped_options = NULL; + } + + FfiError e(::installation_proxy_install_with_callback( + this->raw(), package_path.c_str(), &unwrapped_options, progress_trampoline, &lambda)); + if (e) { + return Err(e); + } + return Ok(); +} + +Result InstallationProxy::upgrade(std::string package_path, + Option options) { + plist_t unwrapped_options; + if (options.is_some()) { + unwrapped_options = std::move(options).unwrap(); + } else { + unwrapped_options = NULL; + } + + FfiError e(::installation_proxy_upgrade(this->raw(), package_path.c_str(), &unwrapped_options)); + if (e) { + return Err(e); + } + return Ok(); +} + +Result InstallationProxy::upgrade_with_callback( + std::string package_path, Option options, std::function& lambda + +) { + plist_t unwrapped_options; + if (options.is_some()) { + unwrapped_options = std::move(options).unwrap(); + } else { + unwrapped_options = NULL; + } + + FfiError e(::installation_proxy_upgrade_with_callback( + this->raw(), package_path.c_str(), &unwrapped_options, progress_trampoline, &lambda)); + if (e) { + return Err(e); + } + return Ok(); +} + +Result InstallationProxy::uninstall(std::string package_path, + Option options) { + plist_t unwrapped_options; + if (options.is_some()) { + unwrapped_options = std::move(options).unwrap(); + } else { + unwrapped_options = NULL; + } + + FfiError e( + ::installation_proxy_uninstall(this->raw(), package_path.c_str(), &unwrapped_options)); + if (e) { + return Err(e); + } + return Ok(); +} + +Result InstallationProxy::uninstall_with_callback( + std::string package_path, Option options, std::function& lambda + +) { + plist_t unwrapped_options; + if (options.is_some()) { + unwrapped_options = std::move(options).unwrap(); + } else { + unwrapped_options = NULL; + } + + FfiError e(::installation_proxy_uninstall_with_callback( + this->raw(), package_path.c_str(), &unwrapped_options, progress_trampoline, &lambda)); + if (e) { + return Err(e); + } + return Ok(); +} + +Result +InstallationProxy::check_capabilities_match(std::vector capabilities, + Option options) { + plist_t unwrapped_options; + if (options.is_some()) { + unwrapped_options = std::move(options).unwrap(); + } else { + unwrapped_options = NULL; + } + + bool res = false; + FfiError e(::installation_proxy_check_capabilities_match( + this->raw(), + capabilities.empty() ? nullptr : capabilities.data(), + capabilities.size(), + unwrapped_options, + &res)); + return e ? Result(Err(e)) : Result(Ok(res)); +} + +Result, FfiError> InstallationProxy::browse(Option options) { + plist_t* apps_raw = nullptr; + size_t apps_len = 0; + + plist_t unwrapped_options; + if (options.is_some()) { + unwrapped_options = std::move(options).unwrap(); + } else { + unwrapped_options = NULL; + } + + FfiError e(::installation_proxy_browse(this->raw(), unwrapped_options, &apps_raw, &apps_len)); + if (e) { + return Err(e); + } + + std::vector apps; + if (apps_raw) { + apps.assign(apps_raw, apps_raw + apps_len); + } + + return Ok(std::move(apps)); +} + +} // namespace IdeviceFFI diff --git a/ffi/src/installation_proxy.rs b/ffi/src/installation_proxy.rs index a749ea4..e3ef63c 100644 --- a/ffi/src/installation_proxy.rs +++ b/ffi/src/installation_proxy.rs @@ -25,7 +25,7 @@ pub struct InstallationProxyClientHandle(pub InstallationProxyClient); /// `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 installation_proxy_connect_tcp( +pub unsafe extern "C" fn installation_proxy_connect( provider: *mut IdeviceProviderHandle, client: *mut *mut InstallationProxyClientHandle, ) -> *mut IdeviceFfiError {