From 46635e162a0cb10e3257b4c994c2002eec3e57f8 Mon Sep 17 00:00:00 2001 From: Jackson Coxson Date: Fri, 15 Aug 2025 16:38:56 -0600 Subject: [PATCH] Debug proxy cpp example --- cpp/examples/debug_proxy.cpp | 133 ++++++++++++++++++++++++ cpp/include/idevice++/app_service.hpp | 1 - cpp/include/idevice++/debug_proxy.hpp | 120 ++++++++++++++++++++++ cpp/src/debug_proxy.cpp | 141 ++++++++++++++++++++++++++ cpp/src/lockdown.cpp | 1 - cpp/src/usbmuxd.cpp | 2 +- ffi/src/debug_proxy.rs | 6 +- 7 files changed, 398 insertions(+), 6 deletions(-) create mode 100644 cpp/examples/debug_proxy.cpp create mode 100644 cpp/include/idevice++/debug_proxy.hpp create mode 100644 cpp/src/debug_proxy.cpp diff --git a/cpp/examples/debug_proxy.cpp b/cpp/examples/debug_proxy.cpp new file mode 100644 index 0000000..55a24c4 --- /dev/null +++ b/cpp/examples/debug_proxy.cpp @@ -0,0 +1,133 @@ +// Jackson Coxson + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static void die(const char* msg, const IdeviceFFI::FfiError& e) { + std::cerr << msg << ": " << e.message << "\n"; + std::exit(1); +} + +static std::vector split_args(const std::string& line) { + std::istringstream iss(line); + std::vector toks; + std::string tok; + while (iss >> tok) + toks.push_back(tok); + return toks; +} + +int main() { + IdeviceFFI::FfiError err; + + // 1) usbmuxd → pick first device + auto mux = IdeviceFFI::UsbmuxdConnection::default_new(/*tag*/ 0, err); + if (!mux) + die("failed to connect to usbmuxd", err); + + auto devices = mux->get_devices(err); + if (!devices) + die("failed to list devices", err); + if (devices->empty()) { + std::cerr << "no devices connected\n"; + return 1; + } + + auto& dev = (*devices)[0]; + auto udid = dev.get_udid(); + if (!udid) { + std::cerr << "device has no UDID\n"; + return 1; + } + auto mux_id = dev.get_id(); + if (!mux_id) { + std::cerr << "device has no mux id\n"; + return 1; + } + + // 2) Provider via default usbmuxd addr + auto addr = IdeviceFFI::UsbmuxdAddr::default_new(); + + const uint32_t tag = 0; + const std::string label = "debug-proxy-jkcoxson"; + auto provider = + IdeviceFFI::Provider::usbmuxd_new(std::move(addr), tag, *udid, *mux_id, label, err); + if (!provider) + die("failed to create provider", err); + + // 3) CoreDeviceProxy + auto cdp = IdeviceFFI::CoreDeviceProxy::connect(*provider, err); + if (!cdp) + die("failed CoreDeviceProxy connect", err); + + auto rsd_port = cdp->get_server_rsd_port(err); + if (!rsd_port) + die("failed to get RSD port", err); + + // 4) Software tunnel → stream + auto adapter = std::move(*cdp).create_tcp_adapter(err); + if (!adapter) + die("failed to create software tunnel adapter", err); + + auto stream = adapter->connect(*rsd_port, err); + if (!stream) + die("failed to connect RSD stream", err); + + // 5) RSD handshake + auto rsd = IdeviceFFI::RsdHandshake::from_socket(std::move(*stream), err); + if (!rsd) + die("failed RSD handshake", err); + + // 6) DebugProxy over RSD + auto dbg = IdeviceFFI::DebugProxy::connect_rsd(*adapter, *rsd, err); + if (!dbg) + die("failed to connect DebugProxy", err); + + std::cout << "Shell connected! Type 'exit' to quit.\n"; + for (;;) { + std::cout << "> " << std::flush; + + std::string line; + if (!std::getline(std::cin, line)) + break; + // trim + auto first = line.find_first_not_of(" \t\r\n"); + if (first == std::string::npos) + continue; + auto last = line.find_last_not_of(" \t\r\n"); + line = line.substr(first, last - first + 1); + + if (line == "exit") + break; + + // Interpret: first token = command name, rest = argv + auto toks = split_args(line); + if (toks.empty()) + continue; + + std::string name = toks.front(); + std::vector argv(toks.begin() + 1, toks.end()); + + auto res = dbg->send_command(name, argv, err); + if (!res && err) { + std::cerr << "send_command failed: " << err.message << "\n"; + // clear error for next loop + err = IdeviceFFI::FfiError{}; + continue; + } + if (res && !res->empty()) { + std::cout << *res << "\n"; + } + } + + return 0; +} diff --git a/cpp/include/idevice++/app_service.hpp b/cpp/include/idevice++/app_service.hpp index 12c2de9..aada1a6 100644 --- a/cpp/include/idevice++/app_service.hpp +++ b/cpp/include/idevice++/app_service.hpp @@ -66,7 +66,6 @@ class AppService { connect_rsd(Adapter& adapter, RsdHandshake& rsd, FfiError& err); // Factory: from socket Box (consumes it). - // Only use if you actually obtain such a pointer from C. static std::optional from_readwrite_ptr(ReadWriteOpaque* consumed, FfiError& err); // nice ergonomic overload: consume a C++ ReadWrite by releasing it diff --git a/cpp/include/idevice++/debug_proxy.hpp b/cpp/include/idevice++/debug_proxy.hpp new file mode 100644 index 0000000..cafd386 --- /dev/null +++ b/cpp/include/idevice++/debug_proxy.hpp @@ -0,0 +1,120 @@ +// Jackson Coxson + +#pragma once +#include +#include +#include +#include +#include +#include + +// Bring in the global C ABI (all C structs/functions are global) +#include +#include +#include + +namespace IdeviceFFI { + +class DebugProxy { + public: + DebugProxy() = default; + DebugProxy(const DebugProxy&) = delete; + DebugProxy& operator=(const DebugProxy&) = delete; + + DebugProxy(DebugProxy&& other) noexcept : handle_(other.handle_) { other.handle_ = nullptr; } + DebugProxy& operator=(DebugProxy&& other) noexcept { + if (this != &other) { + reset(); + handle_ = other.handle_; + other.handle_ = nullptr; + } + return *this; + } + + ~DebugProxy() { reset(); } + + // Factory: connect over RSD (borrows adapter & handshake; does not consume them) + static std::optional + connect_rsd(Adapter& adapter, RsdHandshake& rsd, FfiError& err); + + // Factory: consume a ReadWrite stream (fat pointer) + static std::optional from_readwrite_ptr(::ReadWriteOpaque* consumed, FfiError& err); + + // Convenience: consume a C++ ReadWrite wrapper by releasing it into the ABI + static std::optional from_readwrite(ReadWrite&& rw, FfiError& err); + + // API + std::optional + send_command(const std::string& name, const std::vector& argv, FfiError& err); + + std::optional read_response(FfiError& err); + + bool send_raw(const std::vector& data, FfiError& err); + + // Reads up to `len` bytes; ABI returns a heap C string (we treat as bytes → string) + std::optional read(std::size_t len, FfiError& err); + + // Sets argv, returns textual reply (OK/echo/etc) + std::optional set_argv(const std::vector& argv, FfiError& err); + + bool send_ack(FfiError& err); + bool send_nack(FfiError& err); + + // No error object in ABI; immediate effect + void set_ack_mode(bool enabled) { ::debug_proxy_set_ack_mode(handle_, enabled ? 1 : 0); } + + ::DebugProxyHandle* raw() const { return handle_; } + + private: + explicit DebugProxy(::DebugProxyHandle* h) : handle_(h) {} + + void reset() { + if (handle_) { + ::debug_proxy_free(handle_); + handle_ = nullptr; + } + } + + ::DebugProxyHandle* handle_ = nullptr; +}; + +// Small helper that owns a DebugserverCommandHandle +class DebugCommand { + public: + DebugCommand() = default; + DebugCommand(const DebugCommand&) = delete; + DebugCommand& operator=(const DebugCommand&) = delete; + + DebugCommand(DebugCommand&& other) noexcept : handle_(other.handle_) { + other.handle_ = nullptr; + } + DebugCommand& operator=(DebugCommand&& other) noexcept { + if (this != &other) { + reset(); + handle_ = other.handle_; + other.handle_ = nullptr; + } + return *this; + } + + ~DebugCommand() { reset(); } + + static std::optional make(const std::string& name, + const std::vector& argv); + + ::DebugserverCommandHandle* raw() const { return handle_; } + + private: + explicit DebugCommand(::DebugserverCommandHandle* h) : handle_(h) {} + + void reset() { + if (handle_) { + ::debugserver_command_free(handle_); + handle_ = nullptr; + } + } + + ::DebugserverCommandHandle* handle_ = nullptr; +}; + +} // namespace IdeviceFFI diff --git a/cpp/src/debug_proxy.cpp b/cpp/src/debug_proxy.cpp new file mode 100644 index 0000000..f3143cb --- /dev/null +++ b/cpp/src/debug_proxy.cpp @@ -0,0 +1,141 @@ +// Jackson Coxson + +#include +#include + +namespace IdeviceFFI { + +// ---- helpers ---- +static std::optional take_cstring(char* p) { + if (!p) + return std::nullopt; + std::string s(p); + ::idevice_string_free(p); + return s; +} + +// ---- DebugCommand ---- +std::optional DebugCommand::make(const std::string& name, + const std::vector& argv) { + std::vector c_argv; + c_argv.reserve(argv.size()); + for (auto& a : argv) + c_argv.push_back(a.c_str()); + + auto* h = ::debugserver_command_new( + name.c_str(), + c_argv.empty() ? nullptr : const_cast(c_argv.data()), + c_argv.size()); + if (!h) + return std::nullopt; + return DebugCommand(h); +} + +// ---- DebugProxy factories ---- +std::optional +DebugProxy::connect_rsd(Adapter& adapter, RsdHandshake& rsd, FfiError& err) { + ::DebugProxyHandle* out = nullptr; + if (IdeviceFfiError* e = ::debug_proxy_connect_rsd(adapter.raw(), rsd.raw(), &out)) { + err = FfiError(e); + return std::nullopt; + } + return DebugProxy(out); +} + +std::optional DebugProxy::from_readwrite_ptr(::ReadWriteOpaque* consumed, + FfiError& err) { + ::DebugProxyHandle* out = nullptr; + if (IdeviceFfiError* e = ::debug_proxy_new(consumed, &out)) { + err = FfiError(e); + return std::nullopt; + } + return DebugProxy(out); +} + +std::optional DebugProxy::from_readwrite(ReadWrite&& rw, FfiError& err) { + // Rust consumes the pointer regardless of outcome; release before calling + return from_readwrite_ptr(rw.release(), err); +} + +// ---- DebugProxy API ---- +std::optional DebugProxy::send_command(const std::string& name, + const std::vector& argv, + FfiError& err) { + auto cmd = DebugCommand::make(name, argv); + if (!cmd) { + // treat as invalid arg + err.code = -1; + err.message = "debugserver_command_new failed"; + return std::nullopt; + } + + char* resp_c = nullptr; + if (IdeviceFfiError* e = ::debug_proxy_send_command(handle_, cmd->raw(), &resp_c)) { + err = FfiError(e); + return std::nullopt; + } + return take_cstring(resp_c); // may be null → std::nullopt +} + +std::optional DebugProxy::read_response(FfiError& err) { + char* resp_c = nullptr; + if (IdeviceFfiError* e = ::debug_proxy_read_response(handle_, &resp_c)) { + err = FfiError(e); + return std::nullopt; + } + return take_cstring(resp_c); +} + +bool DebugProxy::send_raw(const std::vector& data, FfiError& err) { + if (IdeviceFfiError* e = ::debug_proxy_send_raw(handle_, data.data(), data.size())) { + err = FfiError(e); + return false; + } + return true; +} + +std::optional DebugProxy::read(std::size_t len, FfiError& err) { + char* resp_c = nullptr; + if (IdeviceFfiError* e = ::debug_proxy_read(handle_, len, &resp_c)) { + err = FfiError(e); + return std::nullopt; + } + return take_cstring(resp_c); +} + +std::optional DebugProxy::set_argv(const std::vector& argv, + FfiError& err) { + std::vector c_argv; + c_argv.reserve(argv.size()); + for (auto& a : argv) + c_argv.push_back(a.c_str()); + + char* resp_c = nullptr; + if (IdeviceFfiError* e = ::debug_proxy_set_argv( + handle_, + c_argv.empty() ? nullptr : const_cast(c_argv.data()), + c_argv.size(), + &resp_c)) { + err = FfiError(e); + return std::nullopt; + } + return take_cstring(resp_c); +} + +bool DebugProxy::send_ack(FfiError& err) { + if (IdeviceFfiError* e = ::debug_proxy_send_ack(handle_)) { + err = FfiError(e); + return false; + } + return true; +} + +bool DebugProxy::send_nack(FfiError& err) { + if (IdeviceFfiError* e = ::debug_proxy_send_nack(handle_)) { + err = FfiError(e); + return false; + } + return true; +} + +} // namespace IdeviceFFI diff --git a/cpp/src/lockdown.cpp b/cpp/src/lockdown.cpp index e00659a..107439e 100644 --- a/cpp/src/lockdown.cpp +++ b/cpp/src/lockdown.cpp @@ -12,7 +12,6 @@ std::optional Lockdown::connect(Provider& provider, FfiError& err) { if (IdeviceFfiError* e = ::lockdownd_connect(provider.raw(), &out)) { // Rust freed the provider on error -> abandon our ownership to avoid double free. - // Your Provider wrapper should expose release(). provider.release(); err = FfiError(e); return std::nullopt; diff --git a/cpp/src/usbmuxd.cpp b/cpp/src/usbmuxd.cpp index 45bdb08..1ae2bba 100644 --- a/cpp/src/usbmuxd.cpp +++ b/cpp/src/usbmuxd.cpp @@ -67,7 +67,7 @@ std::optional UsbmuxdDevice::get_id() const { std::optional UsbmuxdDevice::get_connection_type() const { uint8_t t = idevice_usbmuxd_device_get_connection_type(handle_.get()); if (t == 0) - return std::nullopt; // adjust to your API contract + return std::nullopt; return UsbmuxdConnectionType(t); } diff --git a/ffi/src/debug_proxy.rs b/ffi/src/debug_proxy.rs index 3bf7eef..8d7152a 100644 --- a/ffi/src/debug_proxy.rs +++ b/ffi/src/debug_proxy.rs @@ -9,7 +9,7 @@ use idevice::{IdeviceError, ReadWrite, RsdService}; use crate::core_device_proxy::AdapterHandle; use crate::rsd::RsdHandshakeHandle; -use crate::{IdeviceFfiError, RUNTIME, ffi_err}; +use crate::{IdeviceFfiError, RUNTIME, ReadWriteOpaque, ffi_err}; /// Opaque handle to a DebugProxyClient pub struct DebugProxyHandle(pub DebugProxyClient>); @@ -170,7 +170,7 @@ pub unsafe extern "C" fn debug_proxy_connect_rsd( /// `handle` must be a valid pointer to a location where the handle will be stored #[unsafe(no_mangle)] pub unsafe extern "C" fn debug_proxy_new( - socket: *mut Box, + socket: *mut ReadWriteOpaque, handle: *mut *mut DebugProxyHandle, ) -> *mut IdeviceFfiError { if socket.is_null() || handle.is_null() { @@ -178,7 +178,7 @@ pub unsafe extern "C" fn debug_proxy_new( } let socket = unsafe { Box::from_raw(socket) }; - let client = DebugProxyClient::new(*socket); + let client = DebugProxyClient::new(socket.inner.unwrap()); let new_handle = DebugProxyHandle(client); unsafe { *handle = Box::into_raw(Box::new(new_handle)) };