Remove cpp 17 features and implement Rust into CPP

This commit is contained in:
Jackson Coxson
2025-08-29 14:19:28 -06:00
parent 4fde7cf06b
commit 1169408da1
41 changed files with 1638 additions and 1212 deletions

View File

@@ -26,3 +26,7 @@ ReflowComments: true
PointerAlignment: Left PointerAlignment: Left
BinPackArguments: false BinPackArguments: false
BinPackParameters: false BinPackParameters: false
AllowShortBlocksOnASingleLine: Never
MacroBlockBegin: "^#define"
MacroBlockEnd: "^#undef"
InsertBraces: true

View File

@@ -3,7 +3,7 @@
cmake_minimum_required(VERSION 3.15) cmake_minimum_required(VERSION 3.15)
project(IdeviceFFI CXX) project(IdeviceFFI CXX)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

View File

@@ -9,6 +9,7 @@
#include <idevice++/app_service.hpp> #include <idevice++/app_service.hpp>
#include <idevice++/core_device_proxy.hpp> #include <idevice++/core_device_proxy.hpp>
#include <idevice++/ffi.hpp> #include <idevice++/ffi.hpp>
#include <idevice++/option.hpp>
#include <idevice++/provider.hpp> #include <idevice++/provider.hpp>
#include <idevice++/readwrite.hpp> #include <idevice++/readwrite.hpp>
#include <idevice++/rsd.hpp> #include <idevice++/rsd.hpp>
@@ -16,8 +17,9 @@
using namespace IdeviceFFI; using namespace IdeviceFFI;
[[noreturn]]
static void die(const char* msg, const FfiError& e) { static void die(const char* msg, const FfiError& e) {
std::cerr << msg << ": " << e.message << "\n"; std::cerr << msg << ": " << e.message << "(" << e.code << ")\n";
std::exit(1); std::exit(1);
} }
@@ -42,26 +44,25 @@ int main(int argc, char** argv) {
FfiError err; FfiError err;
// 1) Connect to usbmuxd and pick first device // 1) Connect to usbmuxd and pick first device
auto mux = UsbmuxdConnection::default_new(/*tag*/ 0, err); auto mux = UsbmuxdConnection::default_new(/*tag*/ 0);
if (!mux) if_let_err(mux, err, { die("failed to connect to usbmuxd", err); });
die("failed to connect to usbmuxd", err);
auto devices = mux->get_devices(err); auto devices_res = mux.unwrap().get_devices();
if (!devices) if_let_err(devices_res, err, { die("failed to list devices", err); });
die("failed to list devices", err); auto& devices = devices_res.unwrap();
if (devices->empty()) { if (devices.empty()) {
std::cerr << "no devices connected\n"; std::cerr << "no devices connected\n";
return 1; return 1;
} }
auto& dev = (*devices)[0]; auto& dev = (devices)[0];
auto udid = dev.get_udid(); auto udid = dev.get_udid();
if (!udid) { if (udid.is_none()) {
std::cerr << "device has no UDID\n"; std::cerr << "device has no UDID\n";
return 1; return 1;
} }
auto mux_id = dev.get_id(); auto mux_id = dev.get_id();
if (!mux_id) { if (mux_id.is_none()) {
std::cerr << "device has no mux id\n"; std::cerr << "device has no mux id\n";
return 1; return 1;
} }
@@ -72,53 +73,49 @@ int main(int argc, char** argv) {
const uint32_t tag = 0; const uint32_t tag = 0;
const std::string label = "app_service-jkcoxson"; const std::string label = "app_service-jkcoxson";
auto provider = Provider::usbmuxd_new(std::move(addr), tag, *udid, *mux_id, label, err); auto provider_res =
if (!provider) Provider::usbmuxd_new(std::move(addr), tag, udid.unwrap(), mux_id.unwrap(), label);
die("failed to create provider", err); if_let_err(provider_res, err, { die("failed to create provider", err); });
auto& provider = provider_res.unwrap();
// 3) CoreDeviceProxy // 3) CoreDeviceProxy
auto cdp = CoreDeviceProxy::connect(*provider, err); auto cdp = CoreDeviceProxy::connect(provider).unwrap_or_else(
if (!cdp) [](FfiError e) -> CoreDeviceProxy { die("failed to connect CoreDeviceProxy", e); });
die("failed to connect CoreDeviceProxy", err);
auto rsd_port = cdp->get_server_rsd_port(err); auto rsd_port = cdp.get_server_rsd_port().unwrap_or_else(
if (!rsd_port) [](FfiError err) -> uint16_t { die("failed to get server RSD port", err); });
die("failed to get server RSD port", err);
// 4) Create software tunnel adapter (consumes proxy) // 4) Create software tunnel adapter (consumes proxy)
auto adapter = std::move(*cdp).create_tcp_adapter(err); auto adapter = std::move(cdp).create_tcp_adapter();
if (!adapter) if_let_err(adapter, err, { die("failed to create software tunnel adapter", err); });
die("failed to create software tunnel adapter", err);
// 5) Connect adapter to RSD → ReadWrite stream // 5) Connect adapter to RSD → ReadWrite stream
auto stream = adapter->connect(*rsd_port, err); auto stream = adapter.unwrap().connect(rsd_port);
if (!stream) if_let_err(stream, err, { die("failed to connect RSD stream", err); });
die("failed to connect RSD stream", err);
// 6) RSD handshake (consumes stream) // 6) RSD handshake (consumes stream)
auto rsd = RsdHandshake::from_socket(std::move(*stream), err); auto rsd = RsdHandshake::from_socket(std::move(stream.unwrap()));
if (!rsd) if_let_err(rsd, err, { die("failed RSD handshake", err); });
die("failed RSD handshake", err);
// 7) AppService over RSD (borrows adapter + handshake) // 7) AppService over RSD (borrows adapter + handshake)
auto app = AppService::connect_rsd(*adapter, *rsd, err); auto app = AppService::connect_rsd(adapter.unwrap(), rsd.unwrap())
if (!app) .unwrap_or_else([&](FfiError e) -> AppService {
die("failed to connect AppService", err); die("failed to connect AppService", e); // never returns
});
// 8) Commands // 8) Commands
if (cmd == "list") { if (cmd == "list") {
auto apps = app->list_apps(/*app_clips*/ true, auto apps = app.list_apps(/*app_clips*/ true,
/*removable*/ true, /*removable*/ true,
/*hidden*/ true, /*hidden*/ true,
/*internal*/ true, /*internal*/ true,
/*default_apps*/ true, /*default_apps*/ true)
err); .unwrap_or_else(
if (!apps) [](FfiError e) -> std::vector<AppInfo> { die("list_apps failed", e); });
die("list_apps failed", err);
for (const auto& a : *apps) { for (const auto& a : apps) {
std::cout << "- " << a.bundle_identifier << " | name=" << a.name std::cout << "- " << a.bundle_identifier << " | name=" << a.name << " | version="
<< " | version=" << (a.version ? *a.version : std::string("<none>")) << (a.version.is_some() ? a.version.unwrap() : std::string("<none>"))
<< " | dev=" << (a.is_developer_app ? "y" : "n") << " | dev=" << (a.is_developer_app ? "y" : "n")
<< " | hidden=" << (a.is_hidden ? "y" : "n") << "\n"; << " | hidden=" << (a.is_hidden ? "y" : "n") << "\n";
} }
@@ -132,27 +129,27 @@ int main(int argc, char** argv) {
std::string bundle_id = argv[2]; std::string bundle_id = argv[2];
std::vector<std::string> args; // empty in this example std::vector<std::string> args; // empty in this example
auto resp = app->launch(bundle_id, auto resp =
app.launch(bundle_id,
args, args,
/*kill_existing*/ false, /*kill_existing*/ false,
/*start_suspended*/ false, /*start_suspended*/ false)
err); .unwrap_or_else([](FfiError e) -> LaunchResponse { die("launch failed", e); });
if (!resp)
die("launch failed", err);
std::cout << "Launched pid=" << resp->pid << " exe=" << resp->executable_url std::cout << "Launched pid=" << resp.pid << " exe=" << resp.executable_url
<< " piv=" << resp->process_identifier_version << " piv=" << resp.process_identifier_version
<< " audit_token_len=" << resp->audit_token.size() << "\n"; << " audit_token_len=" << resp.audit_token.size() << "\n";
return 0; return 0;
} else if (cmd == "processes") { } else if (cmd == "processes") {
auto procs = app->list_processes(err); auto procs = app.list_processes().unwrap_or_else(
if (!procs) [](FfiError e) -> std::vector<ProcessToken> { die("list_processes failed", e); });
die("list_processes failed", err);
for (const auto& p : *procs) { for (const auto& p : procs) {
std::cout << p.pid << " : " std::cout << p.pid << " : "
<< (p.executable_url ? *p.executable_url : std::string("<none>")) << "\n"; << (p.executable_url.is_some() ? p.executable_url.unwrap()
: std::string("<none>"))
<< "\n";
} }
return 0; return 0;
@@ -163,8 +160,7 @@ int main(int argc, char** argv) {
} }
std::string bundle_id = argv[2]; std::string bundle_id = argv[2];
if (!app->uninstall(bundle_id, err)) if_let_err(app.uninstall(bundle_id), err, { die("uninstall failed", err); });
die("uninstall failed", err);
std::cout << "Uninstalled " << bundle_id << "\n"; std::cout << "Uninstalled " << bundle_id << "\n";
return 0; return 0;
@@ -176,13 +172,14 @@ int main(int argc, char** argv) {
uint32_t pid = static_cast<uint32_t>(std::stoul(argv[2])); uint32_t pid = static_cast<uint32_t>(std::stoul(argv[2]));
uint32_t signal = static_cast<uint32_t>(std::stoul(argv[3])); uint32_t signal = static_cast<uint32_t>(std::stoul(argv[3]));
auto res = app->send_signal(pid, signal, err); auto res = app.send_signal(pid, signal).unwrap_or_else([](FfiError e) -> SignalResponse {
if (!res) die("send_signal failed", e);
die("send_signal failed", err); });
std::cout << "Signaled pid=" << res->pid << " signal=" << res->signal std::cout << "Signaled pid=" << res.pid << " signal=" << res.signal
<< " ts_ms=" << res->device_timestamp_ms << " ts_ms=" << res.device_timestamp_ms << " exe="
<< " exe=" << (res->executable_url ? *res->executable_url : std::string("<none>")) << (res.executable_url.is_some() ? res.executable_url.unwrap()
: std::string("<none>"))
<< "\n"; << "\n";
return 0; return 0;
@@ -196,22 +193,22 @@ int main(int argc, char** argv) {
float hw = (argc >= 5) ? std::stof(argv[4]) : 1.0f; float hw = (argc >= 5) ? std::stof(argv[4]) : 1.0f;
float scale = (argc >= 6) ? std::stof(argv[5]) : 1.0f; float scale = (argc >= 6) ? std::stof(argv[5]) : 1.0f;
auto icon = app->fetch_icon(bundle_id, hw, hw, scale, /*allow_placeholder*/ true, err); auto icon =
if (!icon) app.fetch_icon(bundle_id, hw, hw, scale, /*allow_placeholder*/ true)
die("fetch_app_icon failed", err); .unwrap_or_else([](FfiError e) -> IconData { die("fetch_app_icon failed", e); });
std::ofstream out(save_path, std::ios::binary); std::ofstream out(save_path, std::ios::binary);
if (!out) { if (!out) {
std::cerr << "Failed to open " << save_path << " for writing\n"; std::cerr << "Failed to open " << save_path << " for writing\n";
return 1; return 1;
} }
out.write(reinterpret_cast<const char*>(icon->data.data()), out.write(reinterpret_cast<const char*>(icon.data.data()),
static_cast<std::streamsize>(icon->data.size())); static_cast<std::streamsize>(icon.data.size()));
out.close(); out.close();
std::cout << "Saved icon to " << save_path << " (" << icon->data.size() << " bytes, " std::cout << "Saved icon to " << save_path << " (" << icon.data.size() << " bytes, "
<< icon->icon_width << "x" << icon->icon_height << ", min " << icon->minimum_width << icon.icon_width << "x" << icon.icon_height << ", min " << icon.minimum_width
<< "x" << icon->minimum_height << ")\n"; << "x" << icon.minimum_height << ")\n";
return 0; return 0;
} else { } else {

View File

@@ -8,10 +8,14 @@
#include <idevice++/core_device_proxy.hpp> #include <idevice++/core_device_proxy.hpp>
#include <idevice++/debug_proxy.hpp> #include <idevice++/debug_proxy.hpp>
#include <idevice++/ffi.hpp> #include <idevice++/ffi.hpp>
#include <idevice++/option.hpp>
#include <idevice++/provider.hpp> #include <idevice++/provider.hpp>
#include <idevice++/rsd.hpp> #include <idevice++/rsd.hpp>
#include <idevice++/usbmuxd.hpp> #include <idevice++/usbmuxd.hpp>
using namespace IdeviceFFI;
[[noreturn]]
static void die(const char* msg, const IdeviceFFI::FfiError& e) { static void die(const char* msg, const IdeviceFFI::FfiError& e) {
std::cerr << msg << ": " << e.message << "\n"; std::cerr << msg << ": " << e.message << "\n";
std::exit(1); std::exit(1);
@@ -21,8 +25,9 @@ static std::vector<std::string> split_args(const std::string& line) {
std::istringstream iss(line); std::istringstream iss(line);
std::vector<std::string> toks; std::vector<std::string> toks;
std::string tok; std::string tok;
while (iss >> tok) while (iss >> tok) {
toks.push_back(tok); toks.push_back(tok);
}
return toks; return toks;
} }
@@ -30,26 +35,24 @@ int main() {
IdeviceFFI::FfiError err; IdeviceFFI::FfiError err;
// 1) usbmuxd → pick first device // 1) usbmuxd → pick first device
auto mux = IdeviceFFI::UsbmuxdConnection::default_new(/*tag*/ 0, err); auto mux = IdeviceFFI::UsbmuxdConnection::default_new(/*tag*/ 0);
if (!mux) if_let_err(mux, err, { die("failed to connect to usbmuxd", err); });
die("failed to connect to usbmuxd", err);
auto devices = mux->get_devices(err); auto devices = mux.unwrap().get_devices();
if (!devices) if_let_err(devices, err, { die("failed to list devices", err); });
die("failed to list devices", err); if (devices.unwrap().empty()) {
if (devices->empty()) {
std::cerr << "no devices connected\n"; std::cerr << "no devices connected\n";
return 1; return 1;
} }
auto& dev = (*devices)[0]; auto& dev = (devices.unwrap())[0];
auto udid = dev.get_udid(); auto udid = dev.get_udid();
if (!udid) { if (udid.is_none()) {
std::cerr << "device has no UDID\n"; std::cerr << "device has no UDID\n";
return 1; return 1;
} }
auto mux_id = dev.get_id(); auto mux_id = dev.get_id();
if (!mux_id) { if (mux_id.is_none()) {
std::cerr << "device has no mux id\n"; std::cerr << "device has no mux id\n";
return 1; return 1;
} }
@@ -59,74 +62,72 @@ int main() {
const uint32_t tag = 0; const uint32_t tag = 0;
const std::string label = "debug-proxy-jkcoxson"; const std::string label = "debug-proxy-jkcoxson";
auto provider = auto provider = IdeviceFFI::Provider::usbmuxd_new(
IdeviceFFI::Provider::usbmuxd_new(std::move(addr), tag, *udid, *mux_id, label, err); std::move(addr), tag, udid.unwrap(), mux_id.unwrap(), label);
if (!provider) if_let_err(provider, err, { die("failed to create provider", err); });
die("failed to create provider", err);
// 3) CoreDeviceProxy // 3) CoreDeviceProxy
auto cdp = IdeviceFFI::CoreDeviceProxy::connect(*provider, err); auto cdp = CoreDeviceProxy::connect(provider.unwrap())
if (!cdp) .unwrap_or_else([](FfiError e) -> CoreDeviceProxy {
die("failed CoreDeviceProxy connect", err); die("failed to connect CoreDeviceProxy", e);
});
auto rsd_port = cdp->get_server_rsd_port(err); auto rsd_port = cdp.get_server_rsd_port().unwrap_or_else(
if (!rsd_port) [](FfiError err) -> uint16_t { die("failed to get server RSD port", err); });
die("failed to get RSD port", err);
// 4) Software tunnel → stream // 4) Create software tunnel adapter (consumes proxy)
auto adapter = std::move(*cdp).create_tcp_adapter(err); auto adapter = std::move(cdp).create_tcp_adapter();
if (!adapter) if_let_err(adapter, err, { die("failed to create software tunnel adapter", err); });
die("failed to create software tunnel adapter", err);
auto stream = adapter->connect(*rsd_port, err); // 5) Connect adapter to RSD → ReadWrite stream
if (!stream) auto stream = adapter.unwrap().connect(rsd_port);
die("failed to connect RSD stream", err); if_let_err(stream, err, { die("failed to connect RSD stream", err); });
// 5) RSD handshake // 6) RSD handshake (consumes stream)
auto rsd = IdeviceFFI::RsdHandshake::from_socket(std::move(*stream), err); auto rsd = RsdHandshake::from_socket(std::move(stream.unwrap()));
if (!rsd) if_let_err(rsd, err, { die("failed RSD handshake", err); });
die("failed RSD handshake", err);
// 6) DebugProxy over RSD // 6) DebugProxy over RSD
auto dbg = IdeviceFFI::DebugProxy::connect_rsd(*adapter, *rsd, err); auto dbg_res = IdeviceFFI::DebugProxy::connect_rsd(adapter.unwrap(), rsd.unwrap());
if (!dbg) if_let_err(dbg_res, err, { die("failed to connect DebugProxy", err); });
die("failed to connect DebugProxy", err); auto& dbg = dbg_res.unwrap();
std::cout << "Shell connected! Type 'exit' to quit.\n"; std::cout << "Shell connected! Type 'exit' to quit.\n";
for (;;) { for (;;) {
std::cout << "> " << std::flush; std::cout << "> " << std::flush;
std::string line; std::string line;
if (!std::getline(std::cin, line)) if (!std::getline(std::cin, line)) {
break; break;
}
// trim // trim
auto first = line.find_first_not_of(" \t\r\n"); auto first = line.find_first_not_of(" \t\r\n");
if (first == std::string::npos) if (first == std::string::npos) {
continue; continue;
}
auto last = line.find_last_not_of(" \t\r\n"); auto last = line.find_last_not_of(" \t\r\n");
line = line.substr(first, last - first + 1); line = line.substr(first, last - first + 1);
if (line == "exit") if (line == "exit") {
break; break;
}
// Interpret: first token = command name, rest = argv // Interpret: first token = command name, rest = argv
auto toks = split_args(line); auto toks = split_args(line);
if (toks.empty()) if (toks.empty()) {
continue; continue;
}
std::string name = toks.front(); std::string name = toks.front();
std::vector<std::string> argv(toks.begin() + 1, toks.end()); std::vector<std::string> argv(toks.begin() + 1, toks.end());
auto res = dbg->send_command(name, argv, err); auto res = dbg.send_command(name, argv);
if (!res && err) { match_result(
std::cerr << "send_command failed: " << err.message << "\n"; res,
// clear error for next loop ok_value,
err = IdeviceFFI::FfiError{}; { if_let_some(ok_value, some_value, { std::cout << some_value << "\n"; }); },
continue; err_value,
} { std::cerr << "send_command failed: " << err_value.message << "\n"; });
if (res && !res->empty()) {
std::cout << *res << "\n";
}
} }
return 0; return 0;

View File

@@ -3,7 +3,6 @@
#include <cstdint> #include <cstdint>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <optional>
#include <string> #include <string>
#include <idevice++/bindings.hpp> #include <idevice++/bindings.hpp>
@@ -16,10 +15,12 @@
using namespace IdeviceFFI; using namespace IdeviceFFI;
static void fail(const char* msg, const FfiError& e) { [[noreturn]]
static void die(const char* msg, const FfiError& e) {
std::cerr << msg; std::cerr << msg;
if (e) if (e) {
std::cerr << ": " << e.message; std::cerr << ": " << e.message;
}
std::cerr << "\n"; std::cerr << "\n";
std::exit(1); std::exit(1);
} }
@@ -28,101 +29,105 @@ int main() {
idevice_init_logger(Debug, Disabled, NULL); idevice_init_logger(Debug, Disabled, NULL);
FfiError err; FfiError err;
// 1) usbmuxd, pick first device // 1) usbmuxd pick first device
auto mux = UsbmuxdConnection::default_new(/*tag*/ 0, err); auto mux = IdeviceFFI::UsbmuxdConnection::default_new(/*tag*/ 0);
if (!mux) if_let_err(mux, err, { die("failed to connect to usbmuxd", err); });
fail("failed to connect to usbmuxd", err);
auto devices = mux->get_devices(err); auto devices = mux.unwrap().get_devices();
if (!devices) if_let_err(devices, err, { die("failed to list devices", err); });
fail("failed to list devices", err); if (devices.unwrap().empty()) {
if (devices->empty()) {
std::cerr << "no devices connected\n"; std::cerr << "no devices connected\n";
return 1; return 1;
} }
auto& dev = (*devices)[0]; auto& dev = (devices.unwrap())[0];
auto udid = dev.get_udid(); auto udid = dev.get_udid();
if (udid.is_none()) {
std::cerr << "device has no UDID\n";
return 1;
}
auto mux_id = dev.get_id(); auto mux_id = dev.get_id();
if (!udid || !mux_id) { if (mux_id.is_none()) {
std::cerr << "device missing udid or mux id\n"; std::cerr << "device has no mux id\n";
return 1; return 1;
} }
// 2) Provider via default usbmuxd addr // 2) Provider via default usbmuxd addr
auto addr = UsbmuxdAddr::default_new(); auto addr = IdeviceFFI::UsbmuxdAddr::default_new();
const uint32_t tag = 0; const uint32_t tag = 0;
const std::string label = "diagnosticsservice-jkcoxson"; const std::string label = "debug-proxy-jkcoxson";
auto provider = Provider::usbmuxd_new(std::move(addr), tag, *udid, *mux_id, label, err); auto provider = IdeviceFFI::Provider::usbmuxd_new(
if (!provider) std::move(addr), tag, udid.unwrap(), mux_id.unwrap(), label);
fail("failed to create provider", err); if_let_err(provider, err, { die("failed to create provider", err); });
// 3) CoreDeviceProxy // 3) CoreDeviceProxy
auto cdp = CoreDeviceProxy::connect(*provider, err); auto cdp = CoreDeviceProxy::connect(provider.unwrap())
if (!cdp) .unwrap_or_else([](FfiError e) -> CoreDeviceProxy {
fail("failed CoreDeviceProxy connect", err); die("failed to connect CoreDeviceProxy", e);
});
auto rsd_port = cdp->get_server_rsd_port(err); auto rsd_port = cdp.get_server_rsd_port().unwrap_or_else(
if (!rsd_port) [](FfiError err) -> uint16_t { die("failed to get server RSD port", err); });
fail("failed to get RSD port", err);
// 4) Software tunnel → connect to RSD // 4) Create software tunnel adapter (consumes proxy)
auto adapter = std::move(*cdp).create_tcp_adapter(err); auto adapter = std::move(cdp).create_tcp_adapter();
if (!adapter) if_let_err(adapter, err, { die("failed to create software tunnel adapter", err); });
fail("failed to create software tunnel adapter", err);
auto stream = adapter->connect(*rsd_port, err); // 5) Connect adapter to RSD → ReadWrite stream
if (!stream) auto stream = adapter.unwrap().connect(rsd_port);
fail("failed to connect RSD stream", err); if_let_err(stream, err, { die("failed to connect RSD stream", err); });
// 5) RSD handshake // 6) RSD handshake (consumes stream)
auto rsd = RsdHandshake::from_socket(std::move(*stream), err); auto rsd = RsdHandshake::from_socket(std::move(stream.unwrap()));
if (!rsd) if_let_err(rsd, err, { die("failed RSD handshake", err); });
fail("failed RSD handshake", err);
// 6) Diagnostics Service over RSD // 6) DebugProxy over RSD
auto diag = DiagnosticsService::connect_rsd(*adapter, *rsd, err); auto diag = DiagnosticsService::connect_rsd(adapter.unwrap(), rsd.unwrap());
if (!diag) if_let_err(diag, err, { die("failed to connect DebugProxy", err); });
fail("failed to connect DiagnosticsService", err);
std::cout << "Getting sysdiagnose, this takes a while! iOS is slow...\n"; std::cout << "Getting sysdiagnose, this takes a while! iOS is slow...\n";
auto cap = diag->capture_sysdiagnose(/*dry_run=*/false, err); auto cap = diag.unwrap().capture_sysdiagnose(/*dry_run=*/false);
if (!cap) if_let_err(cap, err, { die("capture_sysdiagnose failed", err); });
fail("capture_sysdiagnose failed", err);
std::cout << "Got sysdiagnose! Saving to file: " << cap->preferred_filename << "\n"; std::cout << "Got sysdiagnose! Saving to file: " << cap.unwrap().preferred_filename << "\n";
// 7) Stream to file with progress // 7) Stream to file with progress
std::ofstream out(cap->preferred_filename, std::ios::binary); std::ofstream out(cap.unwrap().preferred_filename, std::ios::binary);
if (!out) { if (!out) {
std::cerr << "failed to open output file\n"; std::cerr << "failed to open output file\n";
return 1; return 1;
} }
std::size_t written = 0; std::size_t written = 0;
const std::size_t total = cap->expected_length; const std::size_t total = cap.unwrap().expected_length;
for (;;) { for (;;) {
auto chunk = cap->stream.next_chunk(err); auto chunk = cap.unwrap().stream.next_chunk();
if (!chunk) { match_result(
if (err) chunk,
fail("stream error", err); // err set only on real error res,
break; // nullptr means end-of-stream {
} if_let_some(res, chunk_res, {
if (!chunk->empty()) { out.write(reinterpret_cast<const char*>(chunk_res.data()),
out.write(reinterpret_cast<const char*>(chunk->data()), static_cast<std::streamsize>(chunk_res.size()));
static_cast<std::streamsize>(chunk->size()));
if (!out) { if (!out) {
std::cerr << "write failed\n"; std::cerr << "write failed\n";
return 1; return 1;
} }
written += chunk->size(); written += chunk_res.size();
});
if (res.is_none()) {
break;
} }
},
err,
{ die("stream error", err); });
std::cout << "wrote " << written << "/" << total << " bytes\r" << std::flush; std::cout << "wrote " << written << "/" << total << " bytes\r" << std::flush;
} }
out.flush(); out.flush();
std::cout << "\nDone! Saved to " << cap->preferred_filename << "\n"; std::cout << "\nDone! Saved to " << cap.unwrap().preferred_filename << "\n";
return 0; return 0;
} }

View File

@@ -2,34 +2,31 @@
#include <idevice++/usbmuxd.hpp> #include <idevice++/usbmuxd.hpp>
#include <iostream> #include <iostream>
#include <optional>
int main() { int main() {
IdeviceFFI::FfiError e; auto u = IdeviceFFI::UsbmuxdConnection::default_new(0);
std::optional<IdeviceFFI::UsbmuxdConnection> u = if_let_err(u, e, {
IdeviceFFI::UsbmuxdConnection::default_new(0, e);
if (u == std::nullopt) {
std::cerr << "failed to connect to usbmuxd"; std::cerr << "failed to connect to usbmuxd";
std::cerr << e.message; std::cerr << e.message;
} });
auto devices = u->get_devices(e); auto devices = u.unwrap().get_devices();
if (u == std::nullopt) { if_let_err(devices, e, {
std::cerr << "failed to get devices from usbmuxd"; std::cerr << "failed to get devices from usbmuxd";
std::cerr << e.message; std::cerr << e.message;
} });
for (IdeviceFFI::UsbmuxdDevice& d : *devices) { for (IdeviceFFI::UsbmuxdDevice& d : devices.unwrap()) {
auto udid = d.get_udid(); auto udid = d.get_udid();
if (!udid) { if (udid.is_none()) {
std::cerr << "failed to get udid"; std::cerr << "failed to get udid";
continue; continue;
} }
auto connection_type = d.get_connection_type(); auto connection_type = d.get_connection_type();
if (!connection_type) { if (connection_type.is_none()) {
std::cerr << "failed to get connection type"; std::cerr << "failed to get connection type";
continue; continue;
} }
std::cout << *udid << " (" << connection_type->to_string() << ")" << "\n"; std::cout << udid.unwrap() << " (" << connection_type.unwrap().to_string() << ")" << "\n";
} }
} }

View File

@@ -4,73 +4,80 @@
#include <idevice++/provider.hpp> #include <idevice++/provider.hpp>
#include <idevice++/usbmuxd.hpp> #include <idevice++/usbmuxd.hpp>
#include <iostream> #include <iostream>
#include <optional>
#include <plist/plist++.h> #include <plist/plist++.h>
int main() { int main() {
idevice_init_logger(Debug, Disabled, NULL); idevice_init_logger(Debug, Disabled, NULL);
IdeviceFFI::FfiError e; auto u_res = IdeviceFFI::UsbmuxdConnection::default_new(0);
std::optional<IdeviceFFI::UsbmuxdConnection> u = if_let_err(u_res, e, {
IdeviceFFI::UsbmuxdConnection::default_new(0, e);
if (!u) {
std::cerr << "failed to connect to usbmuxd"; std::cerr << "failed to connect to usbmuxd";
std::cerr << e.message; std::cerr << e.message;
return 1; return 1;
} });
auto& u = u_res.unwrap();
auto devices = u->get_devices(e); auto devices_res = u.get_devices();
if (!devices) { if_let_err(devices_res, e, {
std::cerr << "failed to get devices from usbmuxd"; std::cerr << "failed to get devices from usbmuxd";
std::cerr << e.message; std::cerr << e.message;
return 1; return 1;
} });
if (devices->empty()) { auto devices = std::move(devices_res).unwrap();
if (devices.empty()) {
std::cerr << "no devices connected"; std::cerr << "no devices connected";
std::cerr << e.message;
return 1; return 1;
} }
auto& dev = (*devices)[0]; auto& dev = (devices)[0];
auto udid = dev.get_udid(); auto udid = dev.get_udid();
if (!udid) { if (udid.is_none()) {
std::cerr << "no udid\n"; std::cerr << "no udid\n";
return 1; return 1;
} }
auto id = dev.get_id(); auto id = dev.get_id();
if (!id) { if (id.is_none()) {
std::cerr << "no id\n"; std::cerr << "no id\n";
return 1; return 1;
} }
IdeviceFFI::UsbmuxdAddr addr = IdeviceFFI::UsbmuxdAddr::default_new(); IdeviceFFI::UsbmuxdAddr addr = IdeviceFFI::UsbmuxdAddr::default_new();
auto prov = auto prov_res = IdeviceFFI::Provider::usbmuxd_new(
IdeviceFFI::Provider::usbmuxd_new(std::move(addr), /*tag*/ 0, *udid, *id, "reeeeeeeee", e); std::move(addr), /*tag*/ 0, udid.unwrap(), id.unwrap(), "reeeeeeeee");
if (!prov) { if_let_err(prov_res, e, {
std::cerr << "provider failed: " << e.message << "\n"; std::cerr << "provider failed: " << e.message << "\n";
return 1; return 1;
} });
auto& prov = prov_res.unwrap();
auto client = IdeviceFFI::Lockdown::connect(*prov, e); auto client_res = IdeviceFFI::Lockdown::connect(prov);
if (!client) { if_let_err(client_res, e, {
std::cerr << "lockdown connect failed: " << e.message << "\n"; std::cerr << "lockdown connect failed: " << e.message << "\n";
return 1; return 1;
} });
auto& client = client_res.unwrap();
auto pf = prov->get_pairing_file(e); auto pf = prov.get_pairing_file();
if (!pf) { if_let_err(pf, e, {
std::cerr << "failed to get pairing file: " << e.message << "\n"; std::cerr << "failed to get pairing file: " << e.message << "\n";
return 1; return 1;
} });
client->start_session(*pf, e); client.start_session(pf.unwrap());
auto values = client->get_value(NULL, NULL, e); auto values = client.get_value(NULL, NULL);
if (!values) { match_result(
values,
ok_val,
{
PList::Dictionary res = PList::Dictionary(ok_val);
std::cout << res.ToXml();
},
e,
{
std::cerr << "get values failed: " << e.message << "\n"; std::cerr << "get values failed: " << e.message << "\n";
return 1; return 1;
} });
PList::Dictionary res = PList::Dictionary(*values);
std::cout << res.ToXml();
} }

View File

@@ -2,7 +2,6 @@
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
#include <optional>
#include <string> #include <string>
#include <thread> #include <thread>
@@ -17,6 +16,7 @@
using namespace IdeviceFFI; using namespace IdeviceFFI;
[[noreturn]]
static void die(const char* msg, const FfiError& e) { static void die(const char* msg, const FfiError& e) {
std::cerr << msg << ": " << e.message << "\n"; std::cerr << msg << ": " << e.message << "\n";
std::exit(1); std::exit(1);
@@ -27,7 +27,7 @@ int main(int argc, char** argv) {
// simulate_location clear // simulate_location clear
// simulate_location set <lat> <lon> // simulate_location set <lat> <lon>
bool do_clear = false; bool do_clear = false;
std::optional<double> lat, lon; Option<double> lat, lon;
if (argc == 2 && std::string(argv[1]) == "clear") { if (argc == 2 && std::string(argv[1]) == "clear") {
do_clear = true; do_clear = true;
@@ -41,95 +41,82 @@ int main(int argc, char** argv) {
return 2; return 2;
} }
FfiError err; // 1) usbmuxd → pick first device
auto mux = IdeviceFFI::UsbmuxdConnection::default_new(/*tag*/ 0);
if_let_err(mux, err, { die("failed to connect to usbmuxd", err); });
// 1) Connect to usbmuxd and pick first device auto devices = mux.unwrap().get_devices();
auto mux = UsbmuxdConnection::default_new(/*tag*/ 0, err); if_let_err(devices, err, { die("failed to list devices", err); });
if (!mux) if (devices.unwrap().empty()) {
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"; std::cerr << "no devices connected\n";
return 1; return 1;
} }
auto& dev = (*devices)[0];
auto udidOpt = dev.get_udid(); auto& dev = (devices.unwrap())[0];
if (!udidOpt) { auto udid = dev.get_udid();
if (udid.is_none()) {
std::cerr << "device has no UDID\n"; std::cerr << "device has no UDID\n";
return 1; return 1;
} }
auto idOpt = dev.get_id(); auto mux_id = dev.get_id();
if (!idOpt) { if (mux_id.is_none()) {
std::cerr << "device has no mux id\n"; std::cerr << "device has no mux id\n";
return 1; return 1;
} }
// 2) Make a Provider for this device via default addr // 2) Provider via default usbmuxd addr
auto addr = UsbmuxdAddr::default_new(); auto addr = IdeviceFFI::UsbmuxdAddr::default_new();
const uint32_t tag = 0; const uint32_t tag = 0;
const std::string label = "simulate_location-jkcoxson"; const std::string label = "debug-proxy-jkcoxson";
auto provider = IdeviceFFI::Provider::usbmuxd_new(
std::move(addr), tag, udid.unwrap(), mux_id.unwrap(), label);
if_let_err(provider, err, { die("failed to create provider", err); });
auto provider = Provider::usbmuxd_new(std::move(addr), tag, *udidOpt, *idOpt, label, err); // 3) CoreDeviceProxy
if (!provider) auto cdp = CoreDeviceProxy::connect(provider.unwrap())
die("failed to create provider", err); .unwrap_or_else([](FfiError e) -> CoreDeviceProxy {
die("failed to connect CoreDeviceProxy", e);
});
// 3) Connect CoreDeviceProxy (borrow provider) auto rsd_port = cdp.get_server_rsd_port().unwrap_or_else(
auto cdp = CoreDeviceProxy::connect(*provider, err); [](FfiError err) -> uint16_t { die("failed to get server RSD port", err); });
if (!cdp)
die("failed to connect CoreDeviceProxy", err);
// 4) Read handshakes server RSD port // 4) Create software tunnel adapter (consumes proxy)
auto rsd_port = cdp->get_server_rsd_port(err); auto adapter = std::move(cdp).create_tcp_adapter();
if (!rsd_port) if_let_err(adapter, err, { die("failed to create software tunnel adapter", err); });
die("failed to get server RSD port", err);
// 5) Create software tunnel adapter (consumes proxy) // 5) Connect adapter to RSD → ReadWrite stream
auto adapter = std::move(*cdp).create_tcp_adapter(err); auto stream = adapter.unwrap().connect(rsd_port);
if (!adapter) if_let_err(stream, err, { die("failed to connect RSD stream", err); });
die("failed to create software tunnel adapter", err);
// 6) Connect adapter to RSD port → ReadWrite stream // 6) RSD handshake (consumes stream)
auto stream = adapter->connect(*rsd_port, err); auto rsd = RsdHandshake::from_socket(std::move(stream.unwrap()));
if (!stream) if_let_err(rsd, err, { die("failed RSD handshake", err); });
die("failed to connect RSD stream", err);
// 7) RSD handshake (consumes stream)
auto rsd = RsdHandshake::from_socket(std::move(*stream), err);
if (!rsd)
die("failed RSD handshake", err);
// 8) RemoteServer over RSD (borrows adapter + handshake) // 8) RemoteServer over RSD (borrows adapter + handshake)
auto rs = RemoteServer::connect_rsd(*adapter, *rsd, err); auto rs = RemoteServer::connect_rsd(adapter.unwrap(), rsd.unwrap());
if (!rs) if_let_err(rs, err, { die("failed to connect RemoteServer", err); });
die("failed to connect RemoteServer", err);
// 9) LocationSimulation client (borrows RemoteServer) // 9) LocationSimulation client (borrows RemoteServer)
auto sim = LocationSimulation::create(*rs, err); auto sim_res = LocationSimulation::create(rs.unwrap());
if (!sim) if_let_err(sim_res, err, { die("failed to create LocationSimulation client", err); });
die("failed to create LocationSimulation client", err); auto& sim = sim_res.unwrap();
if (do_clear) { if (do_clear) {
if (!sim->clear(err)) if_let_err(sim.clear(), err, { die("clear failed", err); });
die("clear failed", err);
std::cout << "Location cleared!\n"; std::cout << "Location cleared!\n";
return 0; return 0;
} }
// set path // set path
if (!sim->set(*lat, *lon, err)) if_let_err(sim.set(lat.unwrap(), lon.unwrap()), err, { die("set failed", err); });
die("set failed", err); std::cout << "Location set to (" << lat.unwrap() << ", " << lon.unwrap() << ")\n";
std::cout << "Location set to (" << *lat << ", " << *lon << ")\n";
std::cout << "Press Ctrl-C to stop\n"; std::cout << "Press Ctrl-C to stop\n";
// keep process alive like the Rust example // keep process alive like the Rust example
for (;;) { for (;;) {
if (!sim->set(*lat, *lon, err)) if_let_err(sim.set(lat.unwrap(), lon.unwrap()), err, { die("set failed", err); });
die("set failed", err);
std::this_thread::sleep_for(std::chrono::seconds(3)); std::this_thread::sleep_for(std::chrono::seconds(3));
} }
} }

View File

@@ -7,6 +7,8 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
1914C7972E623CC2002EAB6E /* option.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 1914C7962E623CC2002EAB6E /* option.hpp */; };
1914C7992E623CC8002EAB6E /* result.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 1914C7982E623CC8002EAB6E /* result.hpp */; };
198077932E5CA6EF00CB501E /* adapter_stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1980776C2E5CA69800CB501E /* adapter_stream.cpp */; }; 198077932E5CA6EF00CB501E /* adapter_stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1980776C2E5CA69800CB501E /* adapter_stream.cpp */; };
198077942E5CA6EF00CB501E /* app_service.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1980776D2E5CA69800CB501E /* app_service.cpp */; }; 198077942E5CA6EF00CB501E /* app_service.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1980776D2E5CA69800CB501E /* app_service.cpp */; };
198077952E5CA6EF00CB501E /* core_device.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1980776E2E5CA69800CB501E /* core_device.cpp */; }; 198077952E5CA6EF00CB501E /* core_device.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1980776E2E5CA69800CB501E /* core_device.cpp */; };
@@ -44,6 +46,8 @@
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
1914C7962E623CC2002EAB6E /* option.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = option.hpp; sourceTree = "<group>"; };
1914C7982E623CC8002EAB6E /* result.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = result.hpp; sourceTree = "<group>"; };
1980776C2E5CA69800CB501E /* adapter_stream.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = adapter_stream.cpp; sourceTree = "<group>"; }; 1980776C2E5CA69800CB501E /* adapter_stream.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = adapter_stream.cpp; sourceTree = "<group>"; };
1980776D2E5CA69800CB501E /* app_service.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = app_service.cpp; sourceTree = "<group>"; }; 1980776D2E5CA69800CB501E /* app_service.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = app_service.cpp; sourceTree = "<group>"; };
1980776E2E5CA69800CB501E /* core_device.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = core_device.cpp; sourceTree = "<group>"; }; 1980776E2E5CA69800CB501E /* core_device.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = core_device.cpp; sourceTree = "<group>"; };
@@ -155,6 +159,8 @@
198077B12E5CA6FC00CB501E /* rsd.hpp */, 198077B12E5CA6FC00CB501E /* rsd.hpp */,
198077B22E5CA6FC00CB501E /* tcp_object_stack.hpp */, 198077B22E5CA6FC00CB501E /* tcp_object_stack.hpp */,
198077B32E5CA6FC00CB501E /* usbmuxd.hpp */, 198077B32E5CA6FC00CB501E /* usbmuxd.hpp */,
1914C7962E623CC2002EAB6E /* option.hpp */,
1914C7982E623CC8002EAB6E /* result.hpp */,
); );
path = "idevice++"; path = "idevice++";
sourceTree = "<group>"; sourceTree = "<group>";
@@ -196,11 +202,13 @@
198077C12E5CA6FC00CB501E /* readwrite.hpp in Headers */, 198077C12E5CA6FC00CB501E /* readwrite.hpp in Headers */,
198077C22E5CA6FC00CB501E /* location_simulation.hpp in Headers */, 198077C22E5CA6FC00CB501E /* location_simulation.hpp in Headers */,
198077C32E5CA6FC00CB501E /* adapter_stream.hpp in Headers */, 198077C32E5CA6FC00CB501E /* adapter_stream.hpp in Headers */,
1914C7972E623CC2002EAB6E /* option.hpp in Headers */,
198077C42E5CA6FC00CB501E /* lockdown.hpp in Headers */, 198077C42E5CA6FC00CB501E /* lockdown.hpp in Headers */,
198077C52E5CA6FC00CB501E /* usbmuxd.hpp in Headers */, 198077C52E5CA6FC00CB501E /* usbmuxd.hpp in Headers */,
198077C62E5CA6FC00CB501E /* app_service.hpp in Headers */, 198077C62E5CA6FC00CB501E /* app_service.hpp in Headers */,
198077C72E5CA6FC00CB501E /* idevice.hpp in Headers */, 198077C72E5CA6FC00CB501E /* idevice.hpp in Headers */,
198077C82E5CA6FC00CB501E /* provider.hpp in Headers */, 198077C82E5CA6FC00CB501E /* provider.hpp in Headers */,
1914C7992E623CC8002EAB6E /* result.hpp in Headers */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -433,14 +441,22 @@
198077912E5CA6C700CB501E /* Debug */ = { 198077912E5CA6C700CB501E /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 4FW3Q8784L; DEVELOPMENT_TEAM = 4FW3Q8784L;
EXECUTABLE_PREFIX = lib; EXECUTABLE_PREFIX = lib;
GCC_WARN_INHIBIT_ALL_WARNINGS = NO;
GCC_WARN_SIGN_COMPARE = YES;
GCC_WARN_UNKNOWN_PRAGMAS = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_PARAMETER = YES;
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"${TARGET_BUILD_DIR}/**", "${TARGET_BUILD_DIR}/**",
"$(PROJECT_DIR)/${DESTINATION_PATH}", "$(PROJECT_DIR)/${DESTINATION_PATH}",
); );
MACOSX_DEPLOYMENT_TARGET = 15.5; MACOSX_DEPLOYMENT_TARGET = 15.5;
OTHER_LDFLAGS = "-Wall";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx; SDKROOT = macosx;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
@@ -453,14 +469,22 @@
198077922E5CA6C700CB501E /* Release */ = { 198077922E5CA6C700CB501E /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 4FW3Q8784L; DEVELOPMENT_TEAM = 4FW3Q8784L;
EXECUTABLE_PREFIX = lib; EXECUTABLE_PREFIX = lib;
GCC_WARN_INHIBIT_ALL_WARNINGS = NO;
GCC_WARN_SIGN_COMPARE = YES;
GCC_WARN_UNKNOWN_PRAGMAS = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_PARAMETER = YES;
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"${TARGET_BUILD_DIR}/**", "${TARGET_BUILD_DIR}/**",
"$(PROJECT_DIR)/${DESTINATION_PATH}", "$(PROJECT_DIR)/${DESTINATION_PATH}",
); );
MACOSX_DEPLOYMENT_TARGET = 15.5; MACOSX_DEPLOYMENT_TARGET = 15.5;
OTHER_LDFLAGS = "-Wall";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx; SDKROOT = macosx;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;

View File

@@ -5,6 +5,8 @@
#include <cstdint> #include <cstdint>
#include <idevice++/bindings.hpp> #include <idevice++/bindings.hpp>
#include <idevice++/ffi.hpp> #include <idevice++/ffi.hpp>
#include <idevice++/option.hpp>
#include <idevice++/result.hpp>
#include <vector> #include <vector>
struct IdeviceFfiError; struct IdeviceFfiError;
@@ -33,14 +35,14 @@ class AdapterStream {
AdapterStreamHandle* raw() const noexcept { return h_; } AdapterStreamHandle* raw() const noexcept { return h_; }
bool close(FfiError& err); Result<void, FfiError> close();
bool send(const uint8_t* data, size_t len, FfiError& err); Result<void, FfiError> send(const uint8_t* data, size_t len);
bool send(const std::vector<uint8_t>& buf, FfiError& err) { Result<void, FfiError> send(const std::vector<uint8_t>& buf) {
return send(buf.data(), buf.size(), err); return send(buf.data(), buf.size());
} }
// recv into caller-provided buffer (resizes to actual length) // recv into caller-provided buffer (resizes to actual length)
bool recv(std::vector<uint8_t>& out, FfiError& err, size_t max_hint = 2048); Result<std::vector<uint8_t>, FfiError> recv(size_t max_hint = 2048);
private: private:
AdapterStreamHandle* h_{}; AdapterStreamHandle* h_{};

View File

@@ -9,7 +9,6 @@
#include <idevice++/readwrite.hpp> #include <idevice++/readwrite.hpp>
#include <idevice++/rsd.hpp> #include <idevice++/rsd.hpp>
#include <memory> #include <memory>
#include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -25,11 +24,11 @@ struct AppInfo {
std::string path; std::string path;
std::string bundle_identifier; std::string bundle_identifier;
bool is_developer_app{}; bool is_developer_app{};
std::optional<std::string> bundle_version; Option<std::string> bundle_version;
bool is_internal{}; bool is_internal{};
bool is_hidden{}; bool is_hidden{};
bool is_app_clip{}; bool is_app_clip{};
std::optional<std::string> version; Option<std::string> version;
}; };
struct LaunchResponse { struct LaunchResponse {
@@ -41,12 +40,12 @@ struct LaunchResponse {
struct ProcessToken { struct ProcessToken {
uint32_t pid{}; uint32_t pid{};
std::optional<std::string> executable_url; Option<std::string> executable_url;
}; };
struct SignalResponse { struct SignalResponse {
uint32_t pid{}; uint32_t pid{};
std::optional<std::string> executable_url; Option<std::string> executable_url;
uint64_t device_timestamp_ms{}; uint64_t device_timestamp_ms{};
uint32_t signal{}; uint32_t signal{};
}; };
@@ -62,41 +61,34 @@ struct IconData {
class AppService { class AppService {
public: public:
// Factory: connect via RSD (borrows adapter & handshake) // Factory: connect via RSD (borrows adapter & handshake)
static std::optional<AppService> static Result<AppService, FfiError> connect_rsd(Adapter& adapter, RsdHandshake& rsd);
connect_rsd(Adapter& adapter, RsdHandshake& rsd, FfiError& err);
// Factory: from socket Box<dyn ReadWrite> (consumes it). // Factory: from socket Box<dyn ReadWrite> (consumes it).
static std::optional<AppService> from_readwrite_ptr(ReadWriteOpaque* consumed, FfiError& err); static Result<AppService, FfiError> from_readwrite_ptr(ReadWriteOpaque* consumed);
// nice ergonomic overload: consume a C++ ReadWrite by releasing it // nice ergonomic overload: consume a C++ ReadWrite by releasing it
static std::optional<AppService> from_readwrite(ReadWrite&& rw, FfiError& err); static Result<AppService, FfiError> from_readwrite(ReadWrite&& rw);
// API // API
std::optional<std::vector<AppInfo>> list_apps(bool app_clips, Result<std::vector<AppInfo>, FfiError>
bool removable, list_apps(bool app_clips, bool removable, bool hidden, bool internal, bool default_apps) const;
bool hidden,
bool internal,
bool default_apps,
FfiError& err) const;
std::optional<LaunchResponse> launch(const std::string& bundle_id, Result<LaunchResponse, FfiError> launch(const std::string& bundle_id,
const std::vector<std::string>& argv, const std::vector<std::string>& argv,
bool kill_existing, bool kill_existing,
bool start_suspended, bool start_suspended);
FfiError& err);
std::optional<std::vector<ProcessToken>> list_processes(FfiError& err) const; Result<std::vector<ProcessToken>, FfiError> list_processes() const;
bool uninstall(const std::string& bundle_id, FfiError& err); Result<void, FfiError> uninstall(const std::string& bundle_id);
std::optional<SignalResponse> send_signal(uint32_t pid, uint32_t signal, FfiError& err); Result<SignalResponse, FfiError> send_signal(uint32_t pid, uint32_t signal);
std::optional<IconData> fetch_icon(const std::string& bundle_id, Result<IconData, FfiError> fetch_icon(const std::string& bundle_id,
float width, float width,
float height, float height,
float scale, float scale,
bool allow_placeholder, bool allow_placeholder);
FfiError& err);
// RAII / moves // RAII / moves
~AppService() noexcept = default; ~AppService() noexcept = default;

View File

@@ -5,8 +5,10 @@
#include <idevice++/bindings.hpp> #include <idevice++/bindings.hpp>
#include <idevice++/ffi.hpp> #include <idevice++/ffi.hpp>
#include <idevice++/option.hpp>
#include <idevice++/provider.hpp> #include <idevice++/provider.hpp>
#include <idevice++/readwrite.hpp> #include <idevice++/readwrite.hpp>
#include <idevice++/result.hpp>
namespace IdeviceFFI { namespace IdeviceFFI {
@@ -32,22 +34,23 @@ class Adapter {
AdapterHandle* raw() const noexcept { return handle_.get(); } AdapterHandle* raw() const noexcept { return handle_.get(); }
// Enable PCAP // Enable PCAP
bool pcap(const std::string& path, FfiError& err) { Result<void, FfiError> pcap(const std::string& path) {
if (IdeviceFfiError* e = ::adapter_pcap(handle_.get(), path.c_str())) { FfiError e(::adapter_pcap(handle_.get(), path.c_str()));
err = FfiError(e); if (e) {
return false; return Err(e);
} }
return true; return Ok();
} }
// Connect to a port, returns a ReadWrite stream (to be consumed by RSD/CoreDeviceProxy) // Connect to a port, returns a ReadWrite stream (to be consumed by
std::optional<ReadWrite> connect(uint16_t port, FfiError& err) { // RSD/CoreDeviceProxy)
Result<ReadWrite, FfiError> connect(uint16_t port) {
ReadWriteOpaque* s = nullptr; ReadWriteOpaque* s = nullptr;
if (IdeviceFfiError* e = ::adapter_connect(handle_.get(), port, &s)) { FfiError e(::adapter_connect(handle_.get(), port, &s));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return ReadWrite::adopt(s); return Ok(ReadWrite::adopt(s));
} }
private: private:
@@ -58,28 +61,28 @@ class Adapter {
class CoreDeviceProxy { class CoreDeviceProxy {
public: public:
// Factory: connect using a Provider (NOT consumed on success or error) // Factory: connect using a Provider (NOT consumed on success or error)
static std::optional<CoreDeviceProxy> connect(Provider& provider, FfiError& err); static Result<CoreDeviceProxy, FfiError> connect(Provider& provider);
// Factory: from a socket; Rust consumes the socket regardless of result → we release before // Factory: from a socket; Rust consumes the socket regardless of result → we
// call // release before call
static std::optional<CoreDeviceProxy> from_socket(Idevice&& socket, FfiError& err); static Result<CoreDeviceProxy, FfiError> from_socket(Idevice&& socket);
// Send/recv // Send/recv
bool send(const uint8_t* data, size_t len, FfiError& err); Result<void, FfiError> send(const uint8_t* data, size_t len);
bool send(const std::vector<uint8_t>& buf, FfiError& err) { Result<void, FfiError> send(const std::vector<uint8_t>& buf) {
return send(buf.data(), buf.size(), err); return send(buf.data(), buf.size());
} }
// recv into a pre-sized buffer; resizes to actual bytes received // recv into a pre-sized buffer; resizes to actual bytes received
bool recv(std::vector<uint8_t>& out, FfiError& err); Result<void, FfiError> recv(std::vector<uint8_t>& out);
// Handshake info // Handshake info
std::optional<CoreClientParams> get_client_parameters(FfiError& err) const; Result<CoreClientParams, FfiError> get_client_parameters() const;
std::optional<std::string> get_server_address(FfiError& err) const; Result<std::string, FfiError> get_server_address() const;
std::optional<uint16_t> get_server_rsd_port(FfiError& err) const; Result<uint16_t, FfiError> get_server_rsd_port() const;
// Consuming creation of a TCP adapter: Rust consumes the proxy handle // Consuming creation of a TCP adapter: Rust consumes the proxy handle
std::optional<Adapter> create_tcp_adapter(FfiError& err) &&; Result<Adapter, FfiError> create_tcp_adapter() &&;
// RAII / moves // RAII / moves
~CoreDeviceProxy() noexcept = default; ~CoreDeviceProxy() noexcept = default;

View File

@@ -4,13 +4,13 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
// Bring in the global C ABI (all C structs/functions are global) // Bring in the global C ABI (all C structs/functions are global)
#include <idevice++/core_device_proxy.hpp> #include <idevice++/core_device_proxy.hpp>
#include <idevice++/ffi.hpp> #include <idevice++/ffi.hpp>
#include <idevice++/result.hpp>
#include <idevice++/rsd.hpp> #include <idevice++/rsd.hpp>
namespace IdeviceFFI { namespace IdeviceFFI {
@@ -33,32 +33,33 @@ class DebugProxy {
~DebugProxy() { reset(); } ~DebugProxy() { reset(); }
// Factory: connect over RSD (borrows adapter & handshake; does not consume them) // Factory: connect over RSD (borrows adapter & handshake; does not consume
static std::optional<DebugProxy> // them)
connect_rsd(Adapter& adapter, RsdHandshake& rsd, FfiError& err); static Result<DebugProxy, FfiError> connect_rsd(Adapter& adapter, RsdHandshake& rsd);
// Factory: consume a ReadWrite stream (fat pointer) // Factory: consume a ReadWrite stream (fat pointer)
static std::optional<DebugProxy> from_readwrite_ptr(::ReadWriteOpaque* consumed, FfiError& err); static Result<DebugProxy, FfiError> from_readwrite_ptr(::ReadWriteOpaque* consumed);
// Convenience: consume a C++ ReadWrite wrapper by releasing it into the ABI // Convenience: consume a C++ ReadWrite wrapper by releasing it into the ABI
static std::optional<DebugProxy> from_readwrite(ReadWrite&& rw, FfiError& err); static Result<DebugProxy, FfiError> from_readwrite(ReadWrite&& rw);
// API // API
std::optional<std::string> Result<Option<std::string>, FfiError> send_command(const std::string& name,
send_command(const std::string& name, const std::vector<std::string>& argv, FfiError& err); const std::vector<std::string>& argv);
std::optional<std::string> read_response(FfiError& err); Result<Option<std::string>, FfiError> read_response();
bool send_raw(const std::vector<uint8_t>& data, FfiError& err); Result<void, FfiError> send_raw(const std::vector<uint8_t>& data);
// Reads up to `len` bytes; ABI returns a heap C string (we treat as bytes → string) // Reads up to `len` bytes; ABI returns a heap C string (we treat as bytes →
std::optional<std::string> read(std::size_t len, FfiError& err); // string)
Result<Option<std::string>, FfiError> read(std::size_t len);
// Sets argv, returns textual reply (OK/echo/etc) // Sets argv, returns textual reply (OK/echo/etc)
std::optional<std::string> set_argv(const std::vector<std::string>& argv, FfiError& err); Result<Option<std::string>, FfiError> set_argv(const std::vector<std::string>& argv);
bool send_ack(FfiError& err); Result<void, FfiError> send_ack();
bool send_nack(FfiError& err); Result<void, FfiError> send_nack();
// No error object in ABI; immediate effect // No error object in ABI; immediate effect
void set_ack_mode(bool enabled) { ::debug_proxy_set_ack_mode(handle_, enabled ? 1 : 0); } void set_ack_mode(bool enabled) { ::debug_proxy_set_ack_mode(handle_, enabled ? 1 : 0); }
@@ -99,8 +100,7 @@ class DebugCommand {
~DebugCommand() { reset(); } ~DebugCommand() { reset(); }
static std::optional<DebugCommand> make(const std::string& name, static Option<DebugCommand> make(const std::string& name, const std::vector<std::string>& argv);
const std::vector<std::string>& argv);
::DebugserverCommandHandle* raw() const { return handle_; } ::DebugserverCommandHandle* raw() const { return handle_; }

View File

@@ -3,7 +3,6 @@
#pragma once #pragma once
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -32,8 +31,9 @@ class SysdiagnoseStream {
~SysdiagnoseStream() { reset(); } ~SysdiagnoseStream() { reset(); }
// Pull next chunk. Returns nullopt on end-of-stream. On error, returns nullopt and sets `err`. // Pull next chunk. Returns nullopt on end-of-stream. On error, returns
std::optional<std::vector<uint8_t>> next_chunk(FfiError& err); // nullopt and sets `err`.
Result<Option<std::vector<uint8_t>>, FfiError> next_chunk();
SysdiagnoseStreamHandle* raw() const { return h_; } SysdiagnoseStreamHandle* raw() const { return h_; }
@@ -78,19 +78,18 @@ class DiagnosticsService {
~DiagnosticsService() { reset(); } ~DiagnosticsService() { reset(); }
// Connect via RSD (borrows adapter & handshake; does not consume them) // Connect via RSD (borrows adapter & handshake; does not consume them)
static std::optional<DiagnosticsService> static Result<DiagnosticsService, FfiError> connect_rsd(Adapter& adapter, RsdHandshake& rsd);
connect_rsd(Adapter& adapter, RsdHandshake& rsd, FfiError& err);
// Create from a ReadWrite stream (consumes it) // Create from a ReadWrite stream (consumes it)
static std::optional<DiagnosticsService> from_stream_ptr(::ReadWriteOpaque* consumed, static Result<DiagnosticsService, FfiError> from_stream_ptr(::ReadWriteOpaque* consumed);
FfiError& err);
static std::optional<DiagnosticsService> from_stream(ReadWrite&& rw, FfiError& err) { static Result<DiagnosticsService, FfiError> from_stream(ReadWrite&& rw) {
return from_stream_ptr(rw.release(), err); return from_stream_ptr(rw.release());
} }
// Start sysdiagnose capture; on success returns filename, length and a byte stream // Start sysdiagnose capture; on success returns filename, length and a byte
std::optional<SysdiagnoseCapture> capture_sysdiagnose(bool dry_run, FfiError& err); // stream
Result<SysdiagnoseCapture, FfiError> capture_sysdiagnose(bool dry_run);
::DiagnosticsServiceHandle* raw() const { return h_; } ::DiagnosticsServiceHandle* raw() const { return h_; }

View File

@@ -16,6 +16,9 @@ class FfiError {
FfiError(); FfiError();
explicit operator bool() const { return code != 0; } explicit operator bool() const { return code != 0; }
static FfiError NotConnected();
static FfiError InvalidArgument();
}; };
} // namespace IdeviceFFI } // namespace IdeviceFFI
#endif #endif

View File

@@ -5,7 +5,7 @@
#include <idevice++/ffi.hpp> #include <idevice++/ffi.hpp>
#include <idevice++/pairing_file.hpp> #include <idevice++/pairing_file.hpp>
#include <optional> #include <idevice++/result.hpp>
#include <string> #include <string>
#if defined(_WIN32) && !defined(__MINGW32__) #if defined(_WIN32) && !defined(__MINGW32__)
@@ -21,25 +21,25 @@ namespace IdeviceFFI {
// Generic “bind a free function” deleter // Generic “bind a free function” deleter
template <class T, void (*FreeFn)(T*)> struct FnDeleter { template <class T, void (*FreeFn)(T*)> struct FnDeleter {
void operator()(T* p) const noexcept { void operator()(T* p) const noexcept {
if (p) if (p) {
FreeFn(p); FreeFn(p);
} }
}
}; };
using IdevicePtr = std::unique_ptr<IdeviceHandle, FnDeleter<IdeviceHandle, idevice_free>>; using IdevicePtr = std::unique_ptr<IdeviceHandle, FnDeleter<IdeviceHandle, idevice_free>>;
class Idevice { class Idevice {
public: public:
static std::optional<Idevice> static Result<Idevice, FfiError> create(IdeviceSocketHandle* socket, const std::string& label);
create(IdeviceSocketHandle* socket, const std::string& label, FfiError& err);
static std::optional<Idevice> static Result<Idevice, FfiError>
create_tcp(const sockaddr* addr, socklen_t addr_len, const std::string& label, FfiError& err); create_tcp(const sockaddr* addr, socklen_t addr_len, const std::string& label);
// Methods // Methods
std::optional<std::string> get_type(FfiError& err) const; Result<std::string, FfiError> get_type() const;
bool rsd_checkin(FfiError& err); Result<void, FfiError> rsd_checkin();
bool start_session(const PairingFile& pairing_file, FfiError& err); Result<void, FfiError> start_session(const PairingFile& pairing_file);
// Ownership/RAII // Ownership/RAII
~Idevice() noexcept = default; ~Idevice() noexcept = default;

View File

@@ -3,8 +3,8 @@
#pragma once #pragma once
#include <idevice++/bindings.hpp> #include <idevice++/bindings.hpp>
#include <idevice++/remote_server.hpp> #include <idevice++/remote_server.hpp>
#include <idevice++/result.hpp>
#include <memory> #include <memory>
#include <optional>
namespace IdeviceFFI { namespace IdeviceFFI {
@@ -14,10 +14,10 @@ using LocSimPtr = std::unique_ptr<LocationSimulationHandle,
class LocationSimulation { class LocationSimulation {
public: public:
// Factory: borrows the RemoteServer; not consumed // Factory: borrows the RemoteServer; not consumed
static std::optional<LocationSimulation> create(RemoteServer& server, FfiError& err); static Result<LocationSimulation, FfiError> create(RemoteServer& server);
bool clear(FfiError& err); Result<void, FfiError> clear();
bool set(double latitude, double longitude, FfiError& err); Result<void, FfiError> set(double latitude, double longitude);
~LocationSimulation() noexcept = default; ~LocationSimulation() noexcept = default;
LocationSimulation(LocationSimulation&&) noexcept = default; LocationSimulation(LocationSimulation&&) noexcept = default;

View File

@@ -5,7 +5,6 @@
#include <idevice++/ffi.hpp> #include <idevice++/ffi.hpp>
#include <idevice++/provider.hpp> #include <idevice++/provider.hpp>
#include <memory> #include <memory>
#include <optional>
#include <string> #include <string>
namespace IdeviceFFI { namespace IdeviceFFI {
@@ -16,16 +15,15 @@ using LockdownPtr =
class Lockdown { class Lockdown {
public: public:
// Factory: connect via Provider // Factory: connect via Provider
static std::optional<Lockdown> connect(Provider& provider, FfiError& err); static Result<Lockdown, FfiError> connect(Provider& provider);
// Factory: wrap an existing Idevice socket (consumes it on success) // Factory: wrap an existing Idevice socket (consumes it on success)
static std::optional<Lockdown> from_socket(Idevice&& socket, FfiError& err); static Result<Lockdown, FfiError> from_socket(Idevice&& socket);
// Ops // Ops
bool start_session(const PairingFile& pf, FfiError& err); Result<void, FfiError> start_session(const PairingFile& pf);
std::optional<std::pair<uint16_t, bool>> start_service(const std::string& identifier, Result<std::pair<uint16_t, bool>, FfiError> start_service(const std::string& identifier);
FfiError& err); Result<plist_t, FfiError> get_value(const char* key, const char* domain);
std::optional<plist_t> get_value(const char* key, const char* domain, FfiError& err);
// RAII / moves // RAII / moves
~Lockdown() noexcept = default; ~Lockdown() noexcept = default;

View File

@@ -0,0 +1,184 @@
// So here's the thing, std::optional and friends weren't added until C++17.
// Some consumers of this codebase aren't on C++17 yet, so this won't work.
// Plus, as a professional Rust evangelist, it's my duty to place as many Rust
// idioms into other languages as possible to give everyone a taste of greatness.
// Required error handling is correct error handling. And they called me a mad man.
// Heavily influced from https://github.com/oktal/result, thank you
#pragma once
#include <cstdio>
#include <stdexcept>
#include <type_traits>
#include <utility>
namespace IdeviceFFI {
struct none_t {};
constexpr none_t None{};
template <typename T> class Option {
bool has_;
typename std::aligned_storage<sizeof(T), alignof(T)>::type storage_;
T* ptr() { return reinterpret_cast<T*>(&storage_); }
const T* ptr() const { return reinterpret_cast<const T*>(&storage_); }
public:
// None
Option() noexcept : has_(false) {}
Option(none_t) noexcept : has_(false) {}
// Some
Option(const T& v) : has_(true) { ::new (ptr()) T(v); }
Option(T&& v) : has_(true) { ::new (ptr()) T(std::move(v)); }
// Copy / move
Option(const Option& o) : has_(o.has_) {
if (has_) {
::new (ptr()) T(*o.ptr());
}
}
Option(Option&& o) noexcept(std::is_nothrow_move_constructible<T>::value) : has_(o.has_) {
if (has_) {
::new (ptr()) T(std::move(*o.ptr()));
o.reset();
}
}
Option& operator=(Option o) noexcept(std::is_nothrow_move_constructible<T>::value
&& std::is_nothrow_move_assignable<T>::value) {
swap(o);
return *this;
}
~Option() { reset(); }
void reset() noexcept {
if (has_) {
ptr()->~T();
has_ = false;
}
}
void swap(Option& other) noexcept(std::is_nothrow_move_constructible<T>::value) {
if (has_ && other.has_) {
using std::swap;
swap(*ptr(), *other.ptr());
} else if (has_ && !other.has_) {
::new (other.ptr()) T(std::move(*ptr()));
other.has_ = true;
reset();
} else if (!has_ && other.has_) {
::new (ptr()) T(std::move(*other.ptr()));
has_ = true;
other.reset();
}
}
// State
bool is_some() const noexcept { return has_; }
bool is_none() const noexcept { return !has_; }
// Unwraps (ref-qualified)
T& unwrap() & {
if (!has_) {
throw std::runtime_error("unwrap on None");
}
return *ptr();
}
const T& unwrap() const& {
if (!has_) {
throw std::runtime_error("unwrap on None");
}
return *ptr();
}
T unwrap() && {
if (!has_) {
throw std::runtime_error("unwrap on None");
}
T tmp = std::move(*ptr());
reset();
return tmp;
}
// unwrap_or / unwrap_or_else
T unwrap_or(T default_value) const& { return has_ ? *ptr() : std::move(default_value); }
T unwrap_or(T default_value) && { return has_ ? std::move(*ptr()) : std::move(default_value); }
template <typename F> T unwrap_or_else(F&& f) const& {
return has_ ? *ptr() : static_cast<T>(f());
}
template <typename F> T unwrap_or_else(F&& f) && {
return has_ ? std::move(*ptr()) : static_cast<T>(f());
}
// map
template <typename F>
auto map(F&& f) const -> Option<typename std::decay<decltype(f(*ptr()))>::type> {
using U = typename std::decay<decltype(f(*ptr()))>::type;
if (has_) {
return Option<U>(f(*ptr()));
}
return Option<U>(None);
}
};
// Helpers
template <typename T> inline Option<typename std::decay<T>::type> Some(T&& v) {
return Option<typename std::decay<T>::type>(std::forward<T>(v));
}
inline Option<void> Some() = delete; // no Option<void>
// template <typename T> inline Option<T> None() {
// return Option<T>(none);
// } // still needs T specified
// Prefer this at call sites (lets return-type drive the type):
// return none;
#define match_option(opt, SOME, NONE) \
if ((opt).is_some()) { \
auto&& SOME = (opt).unwrap();
#define or_else \
} \
else { \
NONE; \
}
// --- Option helpers: if_let_some / if_let_some_move / if_let_none ---
#define _opt_concat(a, b) a##b
#define _opt_unique(base) _opt_concat(base, __LINE__)
/* Bind a reference to the contained value if Some(...) */
#define if_let_some(expr, name, block) \
do { \
auto _opt_unique(_opt_) = (expr); \
if (_opt_unique(_opt_).is_some()) { \
auto&& name = _opt_unique(_opt_).unwrap(); \
block \
} \
} while (0)
/* Move the contained value out (consumes the Option) if Some(...) */
#define if_let_some_move(expr, name, block) \
do { \
auto _opt_unique(_opt_) = (expr); \
if (_opt_unique(_opt_).is_some()) { \
auto name = std::move(_opt_unique(_opt_)).unwrap(); \
block \
} \
} while (0)
/* Run a block if the option is None */
#define if_let_none(expr, block) \
do { \
auto _opt_unique(_opt_) = (expr); \
if (_opt_unique(_opt_).is_none()) { \
block \
} \
} while (0)
} // namespace IdeviceFFI

View File

@@ -6,10 +6,10 @@
#pragma once #pragma once
#include <idevice++/ffi.hpp> #include <idevice++/ffi.hpp>
#include <optional> #include <idevice++/result.hpp>
#include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include <memory>
namespace IdeviceFFI { namespace IdeviceFFI {
struct PairingFileDeleter { struct PairingFileDeleter {
@@ -20,8 +20,8 @@ using PairingFilePtr = std::unique_ptr<IdevicePairingFile, PairingFileDeleter>;
class PairingFile { class PairingFile {
public: public:
static std::optional<PairingFile> read(const std::string& path, FfiError& err); static Result<PairingFile, FfiError> read(const std::string& path);
static std::optional<PairingFile> from_bytes(const uint8_t* data, size_t size, FfiError& err); static Result<PairingFile, FfiError> from_bytes(const uint8_t* data, size_t size);
~PairingFile() noexcept = default; // unique_ptr handles destruction ~PairingFile() noexcept = default; // unique_ptr handles destruction
@@ -31,7 +31,7 @@ class PairingFile {
PairingFile(PairingFile&&) noexcept = default; // move is correct by default PairingFile(PairingFile&&) noexcept = default; // move is correct by default
PairingFile& operator=(PairingFile&&) noexcept = default; PairingFile& operator=(PairingFile&&) noexcept = default;
std::optional<std::vector<uint8_t>> serialize(FfiError& err) const; Result<std::vector<uint8_t>, FfiError> serialize() const;
explicit PairingFile(IdevicePairingFile* ptr) noexcept : ptr_(ptr) {} explicit PairingFile(IdevicePairingFile* ptr) noexcept : ptr_(ptr) {}
IdevicePairingFile* raw() const noexcept { return ptr_.get(); } IdevicePairingFile* raw() const noexcept { return ptr_.get(); }

View File

@@ -5,31 +5,29 @@
#include <idevice++/bindings.hpp> #include <idevice++/bindings.hpp>
#include <idevice++/ffi.hpp> #include <idevice++/ffi.hpp>
#include <idevice++/usbmuxd.hpp> #include <idevice++/usbmuxd.hpp>
#include <optional>
#include <string> #include <string>
namespace IdeviceFFI { namespace IdeviceFFI {
class FfiError; class FfiError;
class PairingFile; // has: IdevicePairingFile* raw() const; void release_on_success(); class PairingFile; // has: IdevicePairingFile* raw() const; void
class UsbmuxdAddr; // has: UsbmuxdAddrHandle* raw() const; void release_on_success(); // release_on_success();
class UsbmuxdAddr; // has: UsbmuxdAddrHandle* raw() const; void
// release_on_success();
using ProviderPtr = using ProviderPtr =
std::unique_ptr<IdeviceProviderHandle, FnDeleter<IdeviceProviderHandle, idevice_provider_free>>; std::unique_ptr<IdeviceProviderHandle, FnDeleter<IdeviceProviderHandle, idevice_provider_free>>;
class Provider { class Provider {
public: public:
static std::optional<Provider> tcp_new(const idevice_sockaddr* ip, static Result<Provider, FfiError>
PairingFile&& pairing, tcp_new(const idevice_sockaddr* ip, PairingFile&& pairing, const std::string& label);
const std::string& label,
FfiError& err);
static std::optional<Provider> usbmuxd_new(UsbmuxdAddr&& addr, static Result<Provider, FfiError> usbmuxd_new(UsbmuxdAddr&& addr,
uint32_t tag, uint32_t tag,
const std::string& udid, const std::string& udid,
uint32_t device_id, uint32_t device_id,
const std::string& label, const std::string& label);
FfiError& err);
~Provider() noexcept = default; ~Provider() noexcept = default;
Provider(Provider&&) noexcept = default; Provider(Provider&&) noexcept = default;
@@ -37,7 +35,7 @@ class Provider {
Provider(const Provider&) = delete; Provider(const Provider&) = delete;
Provider& operator=(const Provider&) = delete; Provider& operator=(const Provider&) = delete;
std::optional<PairingFile> get_pairing_file(FfiError& err); Result<PairingFile, FfiError> get_pairing_file();
IdeviceProviderHandle* raw() const noexcept { return handle_.get(); } IdeviceProviderHandle* raw() const noexcept { return handle_.get(); }
static Provider adopt(IdeviceProviderHandle* h) noexcept { return Provider(h); } static Provider adopt(IdeviceProviderHandle* h) noexcept { return Provider(h); }

View File

@@ -9,7 +9,6 @@
#include <idevice++/readwrite.hpp> #include <idevice++/readwrite.hpp>
#include <idevice++/rsd.hpp> #include <idevice++/rsd.hpp>
#include <memory> #include <memory>
#include <optional>
namespace IdeviceFFI { namespace IdeviceFFI {
@@ -19,11 +18,10 @@ using RemoteServerPtr =
class RemoteServer { class RemoteServer {
public: public:
// Factory: consumes the ReadWrite stream regardless of result // Factory: consumes the ReadWrite stream regardless of result
static std::optional<RemoteServer> from_socket(ReadWrite&& rw, FfiError& err); static Result<RemoteServer, FfiError> from_socket(ReadWrite&& rw);
// Factory: borrows adapter + handshake (neither is consumed) // Factory: borrows adapter + handshake (neither is consumed)
static std::optional<RemoteServer> static Result<RemoteServer, FfiError> connect_rsd(Adapter& adapter, RsdHandshake& rsd);
connect_rsd(Adapter& adapter, RsdHandshake& rsd, FfiError& err);
// RAII / moves // RAII / moves
~RemoteServer() noexcept = default; ~RemoteServer() noexcept = default;

View File

@@ -0,0 +1,231 @@
// Jackson Coxson
#pragma once
#include <cstdio>
#include <exception>
#include <type_traits>
#include <utility>
namespace IdeviceFFI {
namespace types {
template <typename T> struct Ok {
T val;
Ok(const T& val) : val(val) {}
Ok(T&& val) : val(std::move(val)) {}
};
template <> struct Ok<void> {};
template <typename E> struct Err {
E val;
Err(const E& val) : val(val) {}
Err(E&& val) : val(std::move(val)) {}
};
} // namespace types
template <typename T> inline types::Ok<typename std::decay<T>::type> Ok(T&& val) {
return types::Ok<typename std::decay<T>::type>(std::forward<T>(val));
}
inline types::Ok<void> Ok() {
return types::Ok<void>();
}
template <typename E> inline types::Err<typename std::decay<E>::type> Err(E&& val) {
return types::Err<typename std::decay<E>::type>(std::forward<E>(val));
}
// =======================
// Result<T, E>
// =======================
template <typename T, typename E> class Result {
bool is_ok_;
union {
T ok_value_;
E err_value_;
};
public:
Result(types::Ok<T> ok_val) : is_ok_(true), ok_value_(std::move(ok_val.val)) {}
Result(types::Err<E> err_val) : is_ok_(false), err_value_(std::move(err_val.val)) {}
Result(const Result& other) : is_ok_(other.is_ok_) {
if (is_ok_) {
new (&ok_value_) T(other.ok_value_);
} else {
new (&err_value_) E(other.err_value_);
}
}
Result(Result&& other) noexcept : is_ok_(other.is_ok_) {
if (is_ok_) {
new (&ok_value_) T(std::move(other.ok_value_));
} else {
new (&err_value_) E(std::move(other.err_value_));
}
}
~Result() {
if (is_ok_) {
ok_value_.~T();
} else {
err_value_.~E();
}
}
bool is_ok() const { return is_ok_; }
bool is_err() const { return !is_ok_; }
// lvalue (mutable)
T& unwrap() & {
if (!is_ok_) {
std::fprintf(stderr, "unwrap on Err\n");
std::terminate();
}
return ok_value_;
}
// lvalue (const)
const T& unwrap() const& {
if (!is_ok_) {
std::fprintf(stderr, "unwrap on Err\n");
std::terminate();
}
return ok_value_;
}
// rvalue (consume/move)
T unwrap() && {
if (!is_ok_) {
std::fprintf(stderr, "unwrap on Err\n");
std::terminate();
}
return std::move(ok_value_);
}
E& unwrap_err() & {
if (is_ok_) {
std::fprintf(stderr, "unwrap_err on Ok\n");
std::terminate();
}
return err_value_;
}
const E& unwrap_err() const& {
if (is_ok_) {
std::fprintf(stderr, "unwrap_err on Ok\n");
std::terminate();
}
return err_value_;
}
E unwrap_err() && {
if (is_ok_) {
std::fprintf(stderr, "unwrap_err on Ok\n");
std::terminate();
}
return std::move(err_value_);
}
T unwrap_or(T&& default_value) const { return is_ok_ ? ok_value_ : std::move(default_value); }
template <typename F> T unwrap_or_else(F&& f) & {
return is_ok_ ? ok_value_ : static_cast<T>(f(err_value_));
}
// const lvalue: returns T by copy
template <typename F> T unwrap_or_else(F&& f) const& {
return is_ok_ ? ok_value_ : static_cast<T>(f(err_value_));
}
// rvalue: moves Ok(T) out; on Err(E), allow the handler to consume/move E
template <typename F> T unwrap_or_else(F&& f) && {
if (is_ok_) {
return std::move(ok_value_);
}
return static_cast<T>(std::forward<F>(f)(std::move(err_value_)));
}
};
// Result<void, E> specialization
template <typename E> class Result<void, E> {
bool is_ok_;
union {
char dummy_;
E err_value_;
};
public:
Result(types::Ok<void>) : is_ok_(true), dummy_() {}
Result(types::Err<E> err_val) : is_ok_(false), err_value_(std::move(err_val.val)) {}
Result(const Result& other) : is_ok_(other.is_ok_) {
if (!is_ok_) {
new (&err_value_) E(other.err_value_);
}
}
Result(Result&& other) noexcept : is_ok_(other.is_ok_) {
if (!is_ok_) {
new (&err_value_) E(std::move(other.err_value_));
}
}
~Result() {
if (!is_ok_) {
err_value_.~E();
}
}
bool is_ok() const { return is_ok_; }
bool is_err() const { return !is_ok_; }
void unwrap() const {
if (!is_ok_) {
std::fprintf(stderr, "Attempted to unwrap an error Result<void, E>\n");
std::terminate();
}
}
const E& unwrap_err() const {
if (is_ok_) {
std::fprintf(stderr, "Attempted to unwrap_err on an ok Result<void, E>\n");
std::terminate();
}
return err_value_;
}
E& unwrap_err() {
if (is_ok_) {
std::fprintf(stderr, "Attempted to unwrap_err on an ok Result<void, E>\n");
std::terminate();
}
return err_value_;
}
};
#define match_result(res, ok_name, ok_block, err_name, err_block) \
if ((res).is_ok()) { \
auto&& ok_name = (res).unwrap(); \
ok_block \
} else { \
auto&& err_name = (res).unwrap_err(); \
err_block \
}
#define if_let_err(res, name, block) \
if ((res).is_err()) { \
auto&& name = (res).unwrap_err(); \
block \
}
#define if_let_ok(res, name, block) \
if ((res).is_ok()) { \
auto&& name = (res).unwrap(); \
block \
}
} // namespace IdeviceFFI

View File

@@ -25,16 +25,16 @@ using RsdPtr =
class RsdHandshake { class RsdHandshake {
public: public:
// Factory: consumes the ReadWrite socket regardless of result // Factory: consumes the ReadWrite socket regardless of result
static std::optional<RsdHandshake> from_socket(ReadWrite&& rw, FfiError& err); static Result<RsdHandshake, FfiError> from_socket(ReadWrite&& rw);
// Basic info // Basic info
std::optional<size_t> protocol_version(FfiError& err) const; Result<size_t, FfiError> protocol_version() const;
std::optional<std::string> uuid(FfiError& err) const; Result<std::string, FfiError> uuid() const;
// Services // Services
std::optional<std::vector<RsdService>> services(FfiError& err) const; Result<std::vector<RsdService>, FfiError> services() const;
std::optional<bool> service_available(const std::string& name, FfiError& err) const; Result<bool, FfiError> service_available(const std::string& name) const;
std::optional<RsdService> service_info(const std::string& name, FfiError& err) const; Result<RsdService, FfiError> service_info(const std::string& name) const;
// RAII / moves // RAII / moves
~RsdHandshake() noexcept = default; ~RsdHandshake() noexcept = default;

View File

@@ -4,7 +4,6 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <optional>
#include <string> #include <string>
#include <idevice++/core_device_proxy.hpp> #include <idevice++/core_device_proxy.hpp>
@@ -12,7 +11,8 @@
namespace IdeviceFFI { namespace IdeviceFFI {
// ---------------- OwnedBuffer: RAII for zero-copy read buffers ---------------- // ---------------- OwnedBuffer: RAII for zero-copy read buffers
// ----------------
class OwnedBuffer { class OwnedBuffer {
public: public:
OwnedBuffer() noexcept : p_(nullptr), n_(0) {} OwnedBuffer() noexcept : p_(nullptr), n_(0) {}
@@ -117,7 +117,8 @@ class TcpObjectStackEater {
~TcpObjectStackEater() { reset(); } ~TcpObjectStackEater() { reset(); }
// Blocks until a packet is available. On success, 'out' adopts the buffer // Blocks until a packet is available. On success, 'out' adopts the buffer
// and you must keep 'out' alive until done (RAII frees via idevice_data_free). // and you must keep 'out' alive until done (RAII frees via
// idevice_data_free).
bool read(OwnedBuffer& out, FfiError& err) const; bool read(OwnedBuffer& out, FfiError& err) const;
::TcpEatObject* raw() const { return h_; } ::TcpEatObject* raw() const { return h_; }
@@ -146,8 +147,8 @@ class TcpObjectStack {
TcpObjectStack& operator=(TcpObjectStack&&) noexcept = default; TcpObjectStack& operator=(TcpObjectStack&&) noexcept = default;
// Build the stack (dual-handle). Name kept to minimize churn. // Build the stack (dual-handle). Name kept to minimize churn.
static std::optional<TcpObjectStack> static Result<TcpObjectStack, FfiError> create(const std::string& our_ip,
create(const std::string& our_ip, const std::string& their_ip, FfiError& err); const std::string& their_ip);
TcpObjectStackFeeder& feeder(); TcpObjectStackFeeder& feeder();
const TcpObjectStackFeeder& feeder() const; const TcpObjectStackFeeder& feeder() const;
@@ -158,15 +159,15 @@ class TcpObjectStack {
Adapter& adapter(); Adapter& adapter();
const Adapter& adapter() const; const Adapter& adapter() const;
std::optional<TcpObjectStackFeeder> release_feeder(); // nullptr inside wrapper after call Option<TcpObjectStackFeeder> release_feeder(); // nullptr inside wrapper after call
std::optional<TcpObjectStackEater> release_eater(); // nullptr inside wrapper after call Option<TcpObjectStackEater> release_eater(); // nullptr inside wrapper after call
std::optional<Adapter> release_adapter(); Option<Adapter> release_adapter();
private: private:
struct Impl { struct Impl {
TcpObjectStackFeeder feeder; TcpObjectStackFeeder feeder;
TcpObjectStackEater eater; TcpObjectStackEater eater;
std::optional<Adapter> adapter; Option<Adapter> adapter;
}; };
// Unique ownership so theres a single point of truth to release from // Unique ownership so theres a single point of truth to release from
std::unique_ptr<Impl> impl_; std::unique_ptr<Impl> impl_;

View File

@@ -5,8 +5,8 @@
#include <cstdint> #include <cstdint>
#include <idevice++/idevice.hpp> #include <idevice++/idevice.hpp>
#include <idevice++/option.hpp>
#include <idevice++/pairing_file.hpp> #include <idevice++/pairing_file.hpp>
#include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -29,10 +29,9 @@ using ConnectionPtr =
class UsbmuxdAddr { class UsbmuxdAddr {
public: public:
static std::optional<UsbmuxdAddr> static Result<UsbmuxdAddr, FfiError> tcp_new(const sockaddr* addr, socklen_t addr_len);
tcp_new(const sockaddr* addr, socklen_t addr_len, FfiError& err);
#if defined(__unix__) || defined(__APPLE__) #if defined(__unix__) || defined(__APPLE__)
static std::optional<UsbmuxdAddr> unix_new(const std::string& path, FfiError& err); static Result<UsbmuxdAddr, FfiError> unix_new(const std::string& path);
#endif #endif
static UsbmuxdAddr default_new(); static UsbmuxdAddr default_new();
@@ -76,9 +75,9 @@ class UsbmuxdDevice {
UsbmuxdDeviceHandle* raw() const noexcept { return handle_.get(); } UsbmuxdDeviceHandle* raw() const noexcept { return handle_.get(); }
std::optional<std::string> get_udid() const; Option<std::string> get_udid() const;
std::optional<uint32_t> get_id() const; Option<uint32_t> get_id() const;
std::optional<UsbmuxdConnectionType> get_connection_type() const; Option<UsbmuxdConnectionType> get_connection_type() const;
private: private:
explicit UsbmuxdDevice(UsbmuxdDeviceHandle* h) noexcept : handle_(h) {} explicit UsbmuxdDevice(UsbmuxdDeviceHandle* h) noexcept : handle_(h) {}
@@ -91,13 +90,12 @@ class PairingFile;
class UsbmuxdConnection { class UsbmuxdConnection {
public: public:
static std::optional<UsbmuxdConnection> static Result<UsbmuxdConnection, FfiError>
tcp_new(const idevice_sockaddr* addr, idevice_socklen_t addr_len, uint32_t tag, FfiError& err); tcp_new(const idevice_sockaddr* addr, idevice_socklen_t addr_len, uint32_t tag);
#if defined(__unix__) || defined(__APPLE__) #if defined(__unix__) || defined(__APPLE__)
static std::optional<UsbmuxdConnection> static Result<UsbmuxdConnection, FfiError> unix_new(const std::string& path, uint32_t tag);
unix_new(const std::string& path, uint32_t tag, FfiError& err);
#endif #endif
static std::optional<UsbmuxdConnection> default_new(uint32_t tag, FfiError& err); static Result<UsbmuxdConnection, FfiError> default_new(uint32_t tag);
~UsbmuxdConnection() noexcept = default; ~UsbmuxdConnection() noexcept = default;
UsbmuxdConnection(UsbmuxdConnection&&) noexcept = default; UsbmuxdConnection(UsbmuxdConnection&&) noexcept = default;
@@ -105,16 +103,15 @@ class UsbmuxdConnection {
UsbmuxdConnection(const UsbmuxdConnection&) = delete; UsbmuxdConnection(const UsbmuxdConnection&) = delete;
UsbmuxdConnection& operator=(const UsbmuxdConnection&) = delete; UsbmuxdConnection& operator=(const UsbmuxdConnection&) = delete;
std::optional<std::vector<UsbmuxdDevice>> get_devices(FfiError& err) const; Result<std::vector<UsbmuxdDevice>, FfiError> get_devices() const;
std::optional<std::string> get_buid(FfiError& err) const; Result<std::string, FfiError> get_buid() const;
std::optional<PairingFile> get_pair_record(const std::string& udid, FfiError& err); Result<PairingFile, FfiError> get_pair_record(const std::string& udid);
std::optional<Idevice> Result<Idevice, FfiError>
connect_to_device(uint32_t device_id, uint16_t port, const std::string& path, FfiError& err) &&; connect_to_device(uint32_t device_id, uint16_t port, const std::string& path) &&;
std::optional<Idevice> Result<Idevice, FfiError> connect_to_device(uint32_t, uint16_t, const std::string&) & = delete;
connect_to_device(uint32_t, uint16_t, const std::string&, FfiError&) & = delete; Result<Idevice, FfiError>
std::optional<Idevice> connect_to_device(uint32_t, uint16_t, const std::string&) const& = delete;
connect_to_device(uint32_t, uint16_t, const std::string&, FfiError&) const& = delete;
UsbmuxdConnectionHandle* raw() const noexcept { return handle_.get(); } UsbmuxdConnectionHandle* raw() const noexcept { return handle_.get(); }

View File

@@ -1,43 +1,50 @@
// Jackson Coxson // Jackson Coxson
#include <idevice++/adapter_stream.hpp> #include <idevice++/adapter_stream.hpp>
#include <idevice++/option.hpp>
namespace IdeviceFFI { namespace IdeviceFFI {
bool AdapterStream::close(FfiError& err) { Result<void, FfiError> AdapterStream::close() {
if (!h_) if (!h_)
return true; return Ok();
if (IdeviceFfiError* e = ::adapter_close(h_)) {
err = FfiError(e); FfiError e(::adapter_close(h_));
return false; if (e) {
return Err(e);
} }
h_ = nullptr; h_ = nullptr;
return true; return Ok();
} }
bool AdapterStream::send(const uint8_t* data, size_t len, FfiError& err) { Result<void, FfiError> AdapterStream::send(const uint8_t *data, size_t len) {
if (!h_) if (!h_)
return false; return Err(FfiError::NotConnected());
if (IdeviceFfiError* e = ::adapter_send(h_, data, len)) { FfiError e(::adapter_send(h_, data, len));
err = FfiError(e); if (e) {
return false; return Err(e);
} }
return true; return Ok();
} }
bool AdapterStream::recv(std::vector<uint8_t>& out, FfiError& err, size_t max_hint) { Result<std::vector<uint8_t>, FfiError> AdapterStream::recv(size_t max_hint) {
if (!h_) if (!h_)
return false; return Err(FfiError::NotConnected());
if (max_hint == 0) if (max_hint == 0)
max_hint = 2048; max_hint = 2048;
out.resize(max_hint);
std::vector<uint8_t> out(max_hint);
size_t actual = 0; size_t actual = 0;
if (IdeviceFfiError* e = ::adapter_recv(h_, out.data(), &actual, out.size())) {
err = FfiError(e); FfiError e(::adapter_recv(h_, out.data(), &actual, out.size()));
return false; if (e) {
return Err(e);
} }
out.resize(actual); out.resize(actual);
return true; return Ok(std::move(out));
} }
} // namespace IdeviceFFI } // namespace IdeviceFFI

View File

@@ -5,32 +5,33 @@
namespace IdeviceFFI { namespace IdeviceFFI {
// ---- Factories ---- // ---- Factories ----
std::optional<AppService> Result<AppService, FfiError> AppService::connect_rsd(Adapter &adapter,
AppService::connect_rsd(Adapter& adapter, RsdHandshake& rsd, FfiError& err) { RsdHandshake &rsd) {
AppServiceHandle *out = nullptr; AppServiceHandle *out = nullptr;
if (IdeviceFfiError* e = ::app_service_connect_rsd(adapter.raw(), rsd.raw(), &out)) { if (IdeviceFfiError *e =
err = FfiError(e); ::app_service_connect_rsd(adapter.raw(), rsd.raw(), &out)) {
return std::nullopt; return Err(FfiError(e));
} }
return AppService::adopt(out); return Ok(AppService::adopt(out));
} }
std::optional<AppService> AppService::from_readwrite_ptr(ReadWriteOpaque* consumed, FfiError& err) { Result<AppService, FfiError>
AppService::from_readwrite_ptr(ReadWriteOpaque *consumed) {
AppServiceHandle *out = nullptr; AppServiceHandle *out = nullptr;
if (IdeviceFfiError *e = ::app_service_new(consumed, &out)) { if (IdeviceFfiError *e = ::app_service_new(consumed, &out)) {
err = FfiError(e); return Err(FfiError(e));
return std::nullopt;
} }
return AppService::adopt(out); return Ok(AppService::adopt(out));
} }
std::optional<AppService> AppService::from_readwrite(ReadWrite&& rw, FfiError& err) { Result<AppService, FfiError> AppService::from_readwrite(ReadWrite &&rw) {
// Rust consumes the stream regardless of result → release BEFORE call // Rust consumes the stream regardless of result → release BEFORE call
return from_readwrite_ptr(rw.release(), err); return from_readwrite_ptr(rw.release());
} }
// ---- Helpers to copy/free C arrays ---- // ---- Helpers to copy/free C arrays ----
static std::vector<AppInfo> copy_and_free_app_list(AppListEntryC* arr, size_t n) { static std::vector<AppInfo> copy_and_free_app_list(AppListEntryC *arr,
size_t n) {
std::vector<AppInfo> out; std::vector<AppInfo> out;
out.reserve(n); out.reserve(n);
for (size_t i = 0; i < n; ++i) { for (size_t i = 0; i < n; ++i) {
@@ -58,7 +59,8 @@ static std::vector<AppInfo> copy_and_free_app_list(AppListEntryC* arr, size_t n)
return out; return out;
} }
static std::vector<ProcessToken> copy_and_free_process_list(ProcessTokenC* arr, size_t n) { static std::vector<ProcessToken> copy_and_free_process_list(ProcessTokenC *arr,
size_t n) {
std::vector<ProcessToken> out; std::vector<ProcessToken> out;
out.reserve(n); out.reserve(n);
for (size_t i = 0; i < n; ++i) { for (size_t i = 0; i < n; ++i) {
@@ -73,49 +75,37 @@ static std::vector<ProcessToken> copy_and_free_process_list(ProcessTokenC* arr,
} }
// ---- API impls ---- // ---- API impls ----
std::optional<std::vector<AppInfo>> AppService::list_apps(bool app_clips, Result<std::vector<AppInfo>, FfiError>
bool removable, AppService::list_apps(bool app_clips, bool removable, bool hidden,
bool hidden, bool internal, bool default_apps) const {
bool internal,
bool default_apps,
FfiError& err) const {
AppListEntryC *arr = nullptr; AppListEntryC *arr = nullptr;
size_t n = 0; size_t n = 0;
if (IdeviceFfiError* e = ::app_service_list_apps(handle_.get(), if (IdeviceFfiError *e = ::app_service_list_apps(
app_clips ? 1 : 0, handle_.get(), app_clips ? 1 : 0, removable ? 1 : 0, hidden ? 1 : 0,
removable ? 1 : 0, internal ? 1 : 0, default_apps ? 1 : 0, &arr, &n)) {
hidden ? 1 : 0,
internal ? 1 : 0, return Err(FfiError(e));
default_apps ? 1 : 0,
&arr,
&n)) {
err = FfiError(e);
return std::nullopt;
} }
return copy_and_free_app_list(arr, n); return Ok(copy_and_free_app_list(arr, n));
} }
std::optional<LaunchResponse> AppService::launch(const std::string& bundle_id, Result<LaunchResponse, FfiError>
const std::vector<std::string>& argv, AppService::launch(const std::string &bundle_id,
bool kill_existing, const std::vector<std::string> &argv, bool kill_existing,
bool start_suspended, bool start_suspended) {
FfiError& err) {
std::vector<const char *> c_argv; std::vector<const char *> c_argv;
c_argv.reserve(argv.size()); c_argv.reserve(argv.size());
for (auto &s : argv) for (auto &s : argv)
c_argv.push_back(s.c_str()); c_argv.push_back(s.c_str());
LaunchResponseC *resp = nullptr; LaunchResponseC *resp = nullptr;
if (IdeviceFfiError* e = ::app_service_launch_app(handle_.get(), if (IdeviceFfiError *e = ::app_service_launch_app(
bundle_id.c_str(), handle_.get(), bundle_id.c_str(),
c_argv.empty() ? nullptr : c_argv.data(), c_argv.empty() ? nullptr : c_argv.data(), c_argv.size(),
c_argv.size(), kill_existing ? 1 : 0, start_suspended ? 1 : 0,
kill_existing ? 1 : 0,
start_suspended ? 1 : 0,
NULL, // TODO: stdio handling NULL, // TODO: stdio handling
&resp)) { &resp)) {
err = FfiError(e); return Err(FfiError(e));
return std::nullopt;
} }
LaunchResponse out; LaunchResponse out;
@@ -124,36 +114,37 @@ std::optional<LaunchResponse> AppService::launch(const std::string&
if (resp->executable_url) if (resp->executable_url)
out.executable_url = resp->executable_url; out.executable_url = resp->executable_url;
if (resp->audit_token && resp->audit_token_len > 0) { if (resp->audit_token && resp->audit_token_len > 0) {
out.audit_token.assign(resp->audit_token, resp->audit_token + resp->audit_token_len); out.audit_token.assign(resp->audit_token,
resp->audit_token + resp->audit_token_len);
} }
::app_service_free_launch_response(resp); ::app_service_free_launch_response(resp);
return out; return Ok(std::move(out));
} }
std::optional<std::vector<ProcessToken>> AppService::list_processes(FfiError& err) const { Result<std::vector<ProcessToken>, FfiError> AppService::list_processes() const {
ProcessTokenC *arr = nullptr; ProcessTokenC *arr = nullptr;
size_t n = 0; size_t n = 0;
if (IdeviceFfiError* e = ::app_service_list_processes(handle_.get(), &arr, &n)) { if (IdeviceFfiError *e =
err = FfiError(e); ::app_service_list_processes(handle_.get(), &arr, &n)) {
return std::nullopt; return Err(FfiError(e));
} }
return copy_and_free_process_list(arr, n); return Ok(copy_and_free_process_list(arr, n));
} }
bool AppService::uninstall(const std::string& bundle_id, FfiError& err) { Result<void, FfiError> AppService::uninstall(const std::string &bundle_id) {
if (IdeviceFfiError* e = ::app_service_uninstall_app(handle_.get(), bundle_id.c_str())) { if (IdeviceFfiError *e =
err = FfiError(e); ::app_service_uninstall_app(handle_.get(), bundle_id.c_str())) {
return false; return Err(FfiError(e));
} }
return true; return Ok();
} }
std::optional<SignalResponse> Result<SignalResponse, FfiError> AppService::send_signal(uint32_t pid,
AppService::send_signal(uint32_t pid, uint32_t signal, FfiError& err) { uint32_t signal) {
SignalResponseC *c = nullptr; SignalResponseC *c = nullptr;
if (IdeviceFfiError* e = ::app_service_send_signal(handle_.get(), pid, signal, &c)) { if (IdeviceFfiError *e =
err = FfiError(e); ::app_service_send_signal(handle_.get(), pid, signal, &c)) {
return std::nullopt; return Err(FfiError(e));
} }
SignalResponse out; SignalResponse out;
out.pid = c->pid; out.pid = c->pid;
@@ -162,25 +153,18 @@ AppService::send_signal(uint32_t pid, uint32_t signal, FfiError& err) {
out.device_timestamp_ms = c->device_timestamp; out.device_timestamp_ms = c->device_timestamp;
out.signal = c->signal; out.signal = c->signal;
::app_service_free_signal_response(c); ::app_service_free_signal_response(c);
return out; return Ok(std::move(out));
} }
std::optional<IconData> AppService::fetch_icon(const std::string& bundle_id, Result<IconData, FfiError> AppService::fetch_icon(const std::string &bundle_id,
float width, float width, float height,
float height,
float scale, float scale,
bool allow_placeholder, bool allow_placeholder) {
FfiError& err) {
IconDataC *c = nullptr; IconDataC *c = nullptr;
if (IdeviceFfiError* e = ::app_service_fetch_app_icon(handle_.get(), if (IdeviceFfiError *e = ::app_service_fetch_app_icon(
bundle_id.c_str(), handle_.get(), bundle_id.c_str(), width, height, scale,
width, allow_placeholder ? 1 : 0, &c)) {
height, return Err(FfiError(e));
scale,
allow_placeholder ? 1 : 0,
&c)) {
err = FfiError(e);
return std::nullopt;
} }
IconData out; IconData out;
if (c->data && c->data_len) { if (c->data && c->data_len) {
@@ -191,7 +175,7 @@ std::optional<IconData> AppService::fetch_icon(const std::string& bundle_id,
out.minimum_width = c->minimum_width; out.minimum_width = c->minimum_width;
out.minimum_height = c->minimum_height; out.minimum_height = c->minimum_height;
::app_service_free_icon_data(c); ::app_service_free_icon_data(c);
return out; return Ok(std::move(out));
} }
} // namespace IdeviceFFI } // namespace IdeviceFFI

View File

@@ -6,63 +6,64 @@ namespace IdeviceFFI {
// ---- Factories ---- // ---- Factories ----
std::optional<CoreDeviceProxy> CoreDeviceProxy::connect(Provider& provider, FfiError& err) { Result<CoreDeviceProxy, FfiError> CoreDeviceProxy::connect(Provider &provider) {
CoreDeviceProxyHandle *out = nullptr; CoreDeviceProxyHandle *out = nullptr;
if (IdeviceFfiError* e = ::core_device_proxy_connect(provider.raw(), &out)) { FfiError e(::core_device_proxy_connect(provider.raw(), &out));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return CoreDeviceProxy::adopt(out); return Ok(CoreDeviceProxy::adopt(out));
} }
std::optional<CoreDeviceProxy> CoreDeviceProxy::from_socket(Idevice&& socket, FfiError& err) { Result<CoreDeviceProxy, FfiError>
CoreDeviceProxy::from_socket(Idevice &&socket) {
CoreDeviceProxyHandle *out = nullptr; CoreDeviceProxyHandle *out = nullptr;
// Rust consumes the socket regardless of result → release BEFORE call // Rust consumes the socket regardless of result → release BEFORE call
IdeviceHandle *raw = socket.release(); IdeviceHandle *raw = socket.release();
if (IdeviceFfiError* e = ::core_device_proxy_new(raw, &out)) { FfiError e(::core_device_proxy_new(raw, &out));
// socket is already consumed on error; do NOT touch it if (e) {
err = FfiError(e); return Err(e);
return std::nullopt;
} }
return CoreDeviceProxy::adopt(out); return Ok(CoreDeviceProxy::adopt(out));
} }
// ---- IO ---- // ---- IO ----
bool CoreDeviceProxy::send(const uint8_t* data, size_t len, FfiError& err) { Result<void, FfiError> CoreDeviceProxy::send(const uint8_t *data, size_t len) {
if (IdeviceFfiError* e = ::core_device_proxy_send(handle_.get(), data, len)) { FfiError e(::core_device_proxy_send(handle_.get(), data, len));
err = FfiError(e); if (e) {
return false; return Err(e);
} }
return true; return Ok();
} }
bool CoreDeviceProxy::recv(std::vector<uint8_t>& out, FfiError& err) { Result<void, FfiError> CoreDeviceProxy::recv(std::vector<uint8_t> &out) {
if (out.empty()) if (out.empty())
out.resize(4096); // a reasonable default; caller can pre-size out.resize(4096); // a reasonable default; caller can pre-size
size_t actual = 0; size_t actual = 0;
if (IdeviceFfiError* e = FfiError e(
::core_device_proxy_recv(handle_.get(), out.data(), &actual, out.size())) { ::core_device_proxy_recv(handle_.get(), out.data(), &actual, out.size()));
err = FfiError(e); if (e) {
return false; return Err(e);
} }
out.resize(actual); out.resize(actual);
return true; return Ok();
} }
// ---- Handshake ---- // ---- Handshake ----
std::optional<CoreClientParams> CoreDeviceProxy::get_client_parameters(FfiError& err) const { Result<CoreClientParams, FfiError>
CoreDeviceProxy::get_client_parameters() const {
uint16_t mtu = 0; uint16_t mtu = 0;
char *addr_c = nullptr; char *addr_c = nullptr;
char *mask_c = nullptr; char *mask_c = nullptr;
if (IdeviceFfiError* e = FfiError e(::core_device_proxy_get_client_parameters(handle_.get(), &mtu,
::core_device_proxy_get_client_parameters(handle_.get(), &mtu, &addr_c, &mask_c)) { &addr_c, &mask_c));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
CoreClientParams params; CoreClientParams params;
@@ -75,45 +76,45 @@ std::optional<CoreClientParams> CoreDeviceProxy::get_client_parameters(FfiError&
params.netmask = mask_c; params.netmask = mask_c;
::idevice_string_free(mask_c); ::idevice_string_free(mask_c);
} }
return params; return Ok(std::move(params));
} }
std::optional<std::string> CoreDeviceProxy::get_server_address(FfiError& err) const { Result<std::string, FfiError> CoreDeviceProxy::get_server_address() const {
char *addr_c = nullptr; char *addr_c = nullptr;
if (IdeviceFfiError* e = ::core_device_proxy_get_server_address(handle_.get(), &addr_c)) { FfiError e(::core_device_proxy_get_server_address(handle_.get(), &addr_c));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
std::string s; std::string s;
if (addr_c) { if (addr_c) {
s = addr_c; s = addr_c;
::idevice_string_free(addr_c); ::idevice_string_free(addr_c);
} }
return s; return Ok(s);
} }
std::optional<uint16_t> CoreDeviceProxy::get_server_rsd_port(FfiError& err) const { Result<uint16_t, FfiError> CoreDeviceProxy::get_server_rsd_port() const {
uint16_t port = 0; uint16_t port = 0;
if (IdeviceFfiError* e = ::core_device_proxy_get_server_rsd_port(handle_.get(), &port)) { FfiError e(::core_device_proxy_get_server_rsd_port(handle_.get(), &port));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return port; return Ok(port);
} }
// ---- Adapter creation (consumes *this) ---- // ---- Adapter creation (consumes *this) ----
std::optional<Adapter> CoreDeviceProxy::create_tcp_adapter(FfiError& err) && { Result<Adapter, FfiError> CoreDeviceProxy::create_tcp_adapter() && {
AdapterHandle *out = nullptr; AdapterHandle *out = nullptr;
// Rust consumes the proxy regardless of result → release BEFORE call // Rust consumes the proxy regardless of result → release BEFORE call
CoreDeviceProxyHandle *raw = this->release(); CoreDeviceProxyHandle *raw = this->release();
if (IdeviceFfiError* e = ::core_device_proxy_create_tcp_adapter(raw, &out)) { FfiError e(::core_device_proxy_create_tcp_adapter(raw, &out));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return Adapter::adopt(out); return Ok(Adapter::adopt(out));
} }
} // namespace IdeviceFFI } // namespace IdeviceFFI

View File

@@ -6,16 +6,16 @@
namespace IdeviceFFI { namespace IdeviceFFI {
// ---- helpers ---- // ---- helpers ----
static std::optional<std::string> take_cstring(char* p) { static Option<std::string> take_cstring(char *p) {
if (!p) if (!p)
return std::nullopt; return None;
std::string s(p); std::string s(p);
::idevice_string_free(p); ::idevice_string_free(p);
return s; return Some(s);
} }
// ---- DebugCommand ---- // ---- DebugCommand ----
std::optional<DebugCommand> DebugCommand::make(const std::string& name, Option<DebugCommand> DebugCommand::make(const std::string &name,
const std::vector<std::string> &argv) { const std::vector<std::string> &argv) {
std::vector<const char *> c_argv; std::vector<const char *> c_argv;
c_argv.reserve(argv.size()); c_argv.reserve(argv.size());
@@ -27,115 +27,117 @@ std::optional<DebugCommand> DebugCommand::make(const std::string& n
c_argv.empty() ? nullptr : const_cast<const char *const *>(c_argv.data()), c_argv.empty() ? nullptr : const_cast<const char *const *>(c_argv.data()),
c_argv.size()); c_argv.size());
if (!h) if (!h)
return std::nullopt; return None;
return DebugCommand(h); return Some(DebugCommand(h));
} }
// ---- DebugProxy factories ---- // ---- DebugProxy factories ----
std::optional<DebugProxy> Result<DebugProxy, FfiError> DebugProxy::connect_rsd(Adapter &adapter,
DebugProxy::connect_rsd(Adapter& adapter, RsdHandshake& rsd, FfiError& err) { RsdHandshake &rsd) {
::DebugProxyHandle *out = nullptr; ::DebugProxyHandle *out = nullptr;
if (IdeviceFfiError* e = ::debug_proxy_connect_rsd(adapter.raw(), rsd.raw(), &out)) { FfiError e(::debug_proxy_connect_rsd(adapter.raw(), rsd.raw(), &out));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return DebugProxy(out); return Ok(DebugProxy(out));
} }
std::optional<DebugProxy> DebugProxy::from_readwrite_ptr(::ReadWriteOpaque* consumed, Result<DebugProxy, FfiError>
FfiError& err) { DebugProxy::from_readwrite_ptr(::ReadWriteOpaque *consumed) {
::DebugProxyHandle *out = nullptr; ::DebugProxyHandle *out = nullptr;
if (IdeviceFfiError* e = ::debug_proxy_new(consumed, &out)) { FfiError e(::debug_proxy_new(consumed, &out));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return DebugProxy(out); return Ok(DebugProxy(out));
} }
std::optional<DebugProxy> DebugProxy::from_readwrite(ReadWrite&& rw, FfiError& err) { Result<DebugProxy, FfiError> DebugProxy::from_readwrite(ReadWrite &&rw) {
// Rust consumes the pointer regardless of outcome; release before calling // Rust consumes the pointer regardless of outcome; release before calling
return from_readwrite_ptr(rw.release(), err); return from_readwrite_ptr(rw.release());
} }
// ---- DebugProxy API ---- // ---- DebugProxy API ----
std::optional<std::string> DebugProxy::send_command(const std::string& name, Result<Option<std::string>, FfiError>
const std::vector<std::string>& argv, DebugProxy::send_command(const std::string &name,
FfiError& err) { const std::vector<std::string> &argv) {
auto cmd = DebugCommand::make(name, argv); auto cmdRes = DebugCommand::make(name, argv);
if (!cmd) { if (cmdRes.is_none()) {
// treat as invalid arg // treat as invalid arg
FfiError err;
err.code = -1; err.code = -1;
err.message = "debugserver_command_new failed"; err.message = "debugserver_command_new failed";
return std::nullopt; return Err(err);
} }
auto cmd = std::move(cmdRes).unwrap();
char *resp_c = nullptr; char *resp_c = nullptr;
if (IdeviceFfiError* e = ::debug_proxy_send_command(handle_, cmd->raw(), &resp_c)) { FfiError e(::debug_proxy_send_command(handle_, cmd.raw(), &resp_c));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
}
return take_cstring(resp_c); // may be null → std::nullopt
} }
std::optional<std::string> DebugProxy::read_response(FfiError& err) { return Ok(take_cstring(resp_c));
}
Result<Option<std::string>, FfiError> DebugProxy::read_response() {
char *resp_c = nullptr; char *resp_c = nullptr;
if (IdeviceFfiError* e = ::debug_proxy_read_response(handle_, &resp_c)) { FfiError e(::debug_proxy_read_response(handle_, &resp_c));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return take_cstring(resp_c); return Ok(take_cstring(resp_c));
} }
bool DebugProxy::send_raw(const std::vector<uint8_t>& data, FfiError& err) { Result<void, FfiError> DebugProxy::send_raw(const std::vector<uint8_t> &data) {
if (IdeviceFfiError* e = ::debug_proxy_send_raw(handle_, data.data(), data.size())) { FfiError e(::debug_proxy_send_raw(handle_, data.data(), data.size()));
err = FfiError(e); if (e) {
return false; return Err(e);
} }
return true; return Ok();
} }
std::optional<std::string> DebugProxy::read(std::size_t len, FfiError& err) { Result<Option<std::string>, FfiError> DebugProxy::read(std::size_t len) {
char *resp_c = nullptr; char *resp_c = nullptr;
if (IdeviceFfiError* e = ::debug_proxy_read(handle_, len, &resp_c)) { FfiError e(::debug_proxy_read(handle_, len, &resp_c));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return take_cstring(resp_c); return Ok(take_cstring(resp_c));
} }
std::optional<std::string> DebugProxy::set_argv(const std::vector<std::string>& argv, Result<Option<std::string>, FfiError>
FfiError& err) { DebugProxy::set_argv(const std::vector<std::string> &argv) {
std::vector<const char *> c_argv; std::vector<const char *> c_argv;
c_argv.reserve(argv.size()); c_argv.reserve(argv.size());
for (auto &a : argv) for (auto &a : argv)
c_argv.push_back(a.c_str()); c_argv.push_back(a.c_str());
char *resp_c = nullptr; char *resp_c = nullptr;
if (IdeviceFfiError* e = ::debug_proxy_set_argv( FfiError e(::debug_proxy_set_argv(
handle_, handle_,
c_argv.empty() ? nullptr : const_cast<const char *const *>(c_argv.data()), c_argv.empty() ? nullptr : const_cast<const char *const *>(c_argv.data()),
c_argv.size(), c_argv.size(), &resp_c));
&resp_c)) { if (e) {
err = FfiError(e); return Err(e);
return std::nullopt;
} }
return take_cstring(resp_c); return Ok(take_cstring(resp_c));
} }
bool DebugProxy::send_ack(FfiError& err) { Result<void, FfiError> DebugProxy::send_ack() {
if (IdeviceFfiError* e = ::debug_proxy_send_ack(handle_)) { FfiError e(::debug_proxy_send_ack(handle_));
err = FfiError(e); if (e) {
return false; return Err(e);
} }
return true; return Ok();
} }
bool DebugProxy::send_nack(FfiError& err) { Result<void, FfiError> DebugProxy::send_nack() {
if (IdeviceFfiError* e = ::debug_proxy_send_nack(handle_)) { FfiError e(::debug_proxy_send_nack(handle_));
err = FfiError(e); if (e) {
return false; return Err(e);
} }
return true; return Ok();
} }
} // namespace IdeviceFFI } // namespace IdeviceFFI

View File

@@ -1,35 +1,39 @@
// Jackson Coxson // Jackson Coxson
#include <idevice++/diagnosticsservice.hpp>
#include <cstring> #include <cstring>
#include <idevice++/diagnosticsservice.hpp>
#include <idevice++/option.hpp>
namespace IdeviceFFI { namespace IdeviceFFI {
// Local helper: take ownership of a C string and convert to std::string // Local helper: take ownership of a C string and convert to std::string
static std::optional<std::string> take_cstring(char* p) { static Option<std::string> take_cstring(char* p) {
if (!p) if (!p) {
return std::nullopt; return None;
}
std::string s(p); std::string s(p);
::idevice_string_free(p); ::idevice_string_free(p);
return s; return Some(std::move(s));
} }
// -------- SysdiagnoseStream -------- // -------- SysdiagnoseStream --------
std::optional<std::vector<uint8_t>> SysdiagnoseStream::next_chunk(FfiError& err) { Result<Option<std::vector<uint8_t>>, FfiError> SysdiagnoseStream::next_chunk() {
if (!h_) if (!h_) {
return std::nullopt; return Err(FfiError::NotConnected());
}
uint8_t* data = nullptr; uint8_t* data = nullptr;
std::size_t len = 0; std::size_t len = 0;
if (IdeviceFfiError* e = ::sysdiagnose_stream_next(h_, &data, &len)) { FfiError e(::sysdiagnose_stream_next(h_, &data, &len));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
if (!data || len == 0) { if (!data || len == 0) {
// End of stream // End of stream
return std::nullopt; return Ok(Option<std::vector<uint8_t>>(None));
} }
// Copy into a C++ buffer // Copy into a C++ buffer
@@ -38,52 +42,52 @@ std::optional<std::vector<uint8_t>> SysdiagnoseStream::next_chunk(FfiError& err)
idevice_data_free(data, len); idevice_data_free(data, len);
return out; return Ok(Some(out));
} }
// -------- DiagnosticsService -------- // -------- DiagnosticsService --------
std::optional<DiagnosticsService> Result<DiagnosticsService, FfiError> DiagnosticsService::connect_rsd(Adapter& adapter,
DiagnosticsService::connect_rsd(Adapter& adapter, RsdHandshake& rsd, FfiError& err) { RsdHandshake& rsd) {
::DiagnosticsServiceHandle* out = nullptr; ::DiagnosticsServiceHandle* out = nullptr;
if (IdeviceFfiError* e = ::diagnostics_service_connect_rsd(adapter.raw(), rsd.raw(), &out)) { FfiError e(::diagnostics_service_connect_rsd(adapter.raw(), rsd.raw(), &out));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return DiagnosticsService(out); return Ok(DiagnosticsService(out));
} }
std::optional<DiagnosticsService> DiagnosticsService::from_stream_ptr(::ReadWriteOpaque* consumed, Result<DiagnosticsService, FfiError>
FfiError& err) { DiagnosticsService::from_stream_ptr(::ReadWriteOpaque* consumed) {
::DiagnosticsServiceHandle* out = nullptr; ::DiagnosticsServiceHandle* out = nullptr;
if (IdeviceFfiError* e = ::diagnostics_service_new(consumed, &out)) { FfiError e(::diagnostics_service_new(consumed, &out));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return DiagnosticsService(out); return Ok(DiagnosticsService(out));
} }
std::optional<SysdiagnoseCapture> DiagnosticsService::capture_sysdiagnose(bool dry_run, Result<SysdiagnoseCapture, FfiError> DiagnosticsService::capture_sysdiagnose(bool dry_run) {
FfiError& err) { if (!h_) {
if (!h_) return Err(FfiError::NotConnected());
return std::nullopt; }
char* filename_c = nullptr; char* filename_c = nullptr;
std::size_t expected_len = 0; std::size_t expected_len = 0;
::SysdiagnoseStreamHandle* stream_h = nullptr; ::SysdiagnoseStreamHandle* stream_h = nullptr;
if (IdeviceFfiError* e = ::diagnostics_service_capture_sysdiagnose( FfiError e(::diagnostics_service_capture_sysdiagnose(
h_, dry_run ? true : false, &filename_c, &expected_len, &stream_h)) { h_, dry_run ? true : false, &filename_c, &expected_len, &stream_h));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
auto fname = take_cstring(filename_c).value_or(std::string{}); auto fname = take_cstring(filename_c).unwrap_or(std::string{});
SysdiagnoseStream stream(stream_h); SysdiagnoseStream stream(stream_h);
SysdiagnoseCapture cap{/*preferred_filename*/ std::move(fname), SysdiagnoseCapture cap{/*preferred_filename*/ std::move(fname),
/*expected_length*/ expected_len, /*expected_length*/ expected_len,
/*stream*/ std::move(stream)}; /*stream*/ std::move(stream)};
return cap; return Ok(std::move(cap));
} }
} // namespace IdeviceFFI } // namespace IdeviceFFI

View File

@@ -2,6 +2,7 @@
#include <idevice++/bindings.hpp> #include <idevice++/bindings.hpp>
#include <idevice++/ffi.hpp> #include <idevice++/ffi.hpp>
#include <idevice.h>
#include <string> #include <string>
namespace IdeviceFFI { namespace IdeviceFFI {
@@ -14,4 +15,18 @@ FfiError::FfiError(const IdeviceFfiError* err)
FfiError::FfiError() : code(0), message("") { FfiError::FfiError() : code(0), message("") {
} }
FfiError FfiError::NotConnected() {
FfiError err;
err.code = -11; // from idevice/lib.rs
err.message = "No established socket connection";
return err;
}
FfiError FfiError::InvalidArgument() {
FfiError err;
err.code = -57; // from idevice/lib.rs
err.message = "No established socket connection";
return err;
}
} // namespace IdeviceFFI } // namespace IdeviceFFI

View File

@@ -4,53 +4,50 @@
namespace IdeviceFFI { namespace IdeviceFFI {
std::optional<Idevice> Result<Idevice, FfiError> Idevice::create(IdeviceSocketHandle* socket, const std::string& label) {
Idevice::create(IdeviceSocketHandle* socket, const std::string& label, FfiError& err) {
IdeviceHandle* h = nullptr; IdeviceHandle* h = nullptr;
if (IdeviceFfiError* e = idevice_new(socket, label.c_str(), &h)) { FfiError e(idevice_new(socket, label.c_str(), &h));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return Idevice(h); return Ok(Idevice(h));
} }
std::optional<Idevice> Idevice::create_tcp(const sockaddr* addr, Result<Idevice, FfiError>
socklen_t addr_len, Idevice::create_tcp(const sockaddr* addr, socklen_t addr_len, const std::string& label) {
const std::string& label,
FfiError& err) {
IdeviceHandle* h = nullptr; IdeviceHandle* h = nullptr;
if (IdeviceFfiError* e = idevice_new_tcp_socket(addr, addr_len, label.c_str(), &h)) { FfiError e(idevice_new_tcp_socket(addr, addr_len, label.c_str(), &h));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return Idevice(h); return Ok(Idevice(h));
} }
std::optional<std::string> Idevice::get_type(FfiError& err) const { Result<std::string, FfiError> Idevice::get_type() const {
char* cstr = nullptr; char* cstr = nullptr;
if (IdeviceFfiError* e = idevice_get_type(handle_.get(), &cstr)) { FfiError e(idevice_get_type(handle_.get(), &cstr));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
std::string out(cstr); std::string out(cstr);
idevice_string_free(cstr); idevice_string_free(cstr);
return out; return Ok(out);
} }
bool Idevice::rsd_checkin(FfiError& err) { Result<void, FfiError> Idevice::rsd_checkin() {
if (IdeviceFfiError* e = idevice_rsd_checkin(handle_.get())) { FfiError e(idevice_rsd_checkin(handle_.get()));
err = FfiError(e); if (e) {
return false; return Err(e);
} }
return true; return Ok();
} }
bool Idevice::start_session(const PairingFile& pairing_file, FfiError& err) { Result<void, FfiError> Idevice::start_session(const PairingFile& pairing_file) {
if (IdeviceFfiError* e = idevice_start_session(handle_.get(), pairing_file.raw())) { FfiError e(idevice_start_session(handle_.get(), pairing_file.raw()));
err = FfiError(e); if (e) {
return false; return Err(e);
} }
return true; return Ok();
} }
} // namespace IdeviceFFI } // namespace IdeviceFFI

View File

@@ -4,29 +4,29 @@
namespace IdeviceFFI { namespace IdeviceFFI {
std::optional<LocationSimulation> LocationSimulation::create(RemoteServer& server, FfiError& err) { Result<LocationSimulation, FfiError> LocationSimulation::create(RemoteServer& server) {
LocationSimulationHandle* out = nullptr; LocationSimulationHandle* out = nullptr;
if (IdeviceFfiError* e = ::location_simulation_new(server.raw(), &out)) { FfiError e(::location_simulation_new(server.raw(), &out));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return LocationSimulation::adopt(out); return Ok(LocationSimulation::adopt(out));
} }
bool LocationSimulation::clear(FfiError& err) { Result<void, FfiError> LocationSimulation::clear() {
if (IdeviceFfiError* e = ::location_simulation_clear(handle_.get())) { FfiError e(::location_simulation_clear(handle_.get()));
err = FfiError(e); if (e) {
return false; return Err(e);
} }
return true; return Ok();
} }
bool LocationSimulation::set(double latitude, double longitude, FfiError& err) { Result<void, FfiError> LocationSimulation::set(double latitude, double longitude) {
if (IdeviceFfiError* e = ::location_simulation_set(handle_.get(), latitude, longitude)) { FfiError e(::location_simulation_set(handle_.get(), latitude, longitude));
err = FfiError(e); if (e) {
return false; return Err(e);
} }
return true; return Ok();
} }
} // namespace IdeviceFFI } // namespace IdeviceFFI

View File

@@ -7,60 +7,51 @@
namespace IdeviceFFI { namespace IdeviceFFI {
std::optional<Lockdown> Lockdown::connect(Provider& provider, FfiError& err) { Result<Lockdown, FfiError> Lockdown::connect(Provider& provider) {
LockdowndClientHandle* out = nullptr; LockdowndClientHandle* out = nullptr;
FfiError e(::lockdownd_connect(provider.raw(), &out));
if (IdeviceFfiError* e = ::lockdownd_connect(provider.raw(), &out)) { if (e) {
// Rust freed the provider on error -> abandon our ownership to avoid double free.
provider.release(); provider.release();
err = FfiError(e); return Err(e);
return std::nullopt;
} }
// Success: provider is NOT consumed; keep ownership. return Ok(Lockdown::adopt(out));
return Lockdown::adopt(out);
} }
std::optional<Lockdown> Lockdown::from_socket(Idevice&& socket, FfiError& err) { Result<Lockdown, FfiError> Lockdown::from_socket(Idevice&& socket) {
LockdowndClientHandle* out = nullptr; LockdowndClientHandle* out = nullptr;
FfiError e(::lockdownd_new(socket.raw(), &out));
if (IdeviceFfiError* e = ::lockdownd_new(socket.raw(), &out)) { if (e) {
// Error: Rust did NOT consume the socket (it returns early for invalid args), return Err(e);
// so keep ownership; report error.
err = FfiError(e);
return std::nullopt;
} }
// Success: Rust consumed the socket -> abandon our ownership.
socket.release(); socket.release();
return Lockdown::adopt(out); return Ok(Lockdown::adopt(out));
} }
bool Lockdown::start_session(const PairingFile& pf, FfiError& err) { Result<void, FfiError> Lockdown::start_session(const PairingFile& pf) {
if (IdeviceFfiError* e = ::lockdownd_start_session(handle_.get(), pf.raw())) { FfiError e(::lockdownd_start_session(handle_.get(), pf.raw()));
err = FfiError(e); if (e) {
return false; return Err(e);
} }
return true; return Ok();
} }
std::optional<std::pair<uint16_t, bool>> Lockdown::start_service(const std::string& identifier, Result<std::pair<uint16_t, bool>, FfiError> Lockdown::start_service(const std::string& identifier) {
FfiError& err) {
uint16_t port = 0; uint16_t port = 0;
bool ssl = false; bool ssl = false;
if (IdeviceFfiError* e = FfiError e(::lockdownd_start_service(handle_.get(), identifier.c_str(), &port, &ssl));
::lockdownd_start_service(handle_.get(), identifier.c_str(), &port, &ssl)) { if (e) {
err = FfiError(e); return Err(e);
return std::nullopt;
} }
return std::make_pair(port, ssl); return Ok(std::make_pair(port, ssl));
} }
std::optional<plist_t> Lockdown::get_value(const char* key, const char* domain, FfiError& err) { Result<plist_t, FfiError> Lockdown::get_value(const char* key, const char* domain) {
plist_t out = nullptr; plist_t out = nullptr;
if (IdeviceFfiError* e = ::lockdownd_get_value(handle_.get(), key, domain, &out)) { FfiError e(::lockdownd_get_value(handle_.get(), key, domain, &out));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return out; // caller now owns `out` and must free with the plist API return Ok(out);
} }
} // namespace IdeviceFFI } // namespace IdeviceFFI

View File

@@ -8,46 +8,46 @@ namespace IdeviceFFI {
// Deleter definition (out-of-line) // Deleter definition (out-of-line)
void PairingFileDeleter::operator()(IdevicePairingFile* p) const noexcept { void PairingFileDeleter::operator()(IdevicePairingFile* p) const noexcept {
if (p) if (p) {
idevice_pairing_file_free(p); idevice_pairing_file_free(p);
} }
}
// Static member definitions // Static member definitions
std::optional<PairingFile> PairingFile::read(const std::string& path, FfiError& err) { Result<PairingFile, FfiError> PairingFile::read(const std::string& path) {
IdevicePairingFile* ptr = nullptr; IdevicePairingFile* ptr = nullptr;
if (IdeviceFfiError* e = idevice_pairing_file_read(path.c_str(), &ptr)) { FfiError e(idevice_pairing_file_read(path.c_str(), &ptr));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return PairingFile(ptr); return Ok(PairingFile(ptr));
} }
std::optional<PairingFile> Result<PairingFile, FfiError> PairingFile::from_bytes(const uint8_t* data, size_t size) {
PairingFile::from_bytes(const uint8_t* data, size_t size, FfiError& err) {
IdevicePairingFile* raw = nullptr; IdevicePairingFile* raw = nullptr;
if (IdeviceFfiError* e = idevice_pairing_file_from_bytes(data, size, &raw)) { FfiError e(idevice_pairing_file_from_bytes(data, size, &raw));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return PairingFile(raw); return Ok(PairingFile(raw));
} }
std::optional<std::vector<uint8_t>> PairingFile::serialize(FfiError& err) const { Result<std::vector<uint8_t>, FfiError> PairingFile::serialize() const {
if (!ptr_) { if (!ptr_) {
return std::nullopt; return Err(FfiError::InvalidArgument());
} }
uint8_t* data = nullptr; uint8_t* data = nullptr;
size_t size = 0; size_t size = 0;
if (IdeviceFfiError* e = idevice_pairing_file_serialize(ptr_.get(), &data, &size)) { FfiError e(idevice_pairing_file_serialize(ptr_.get(), &data, &size));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
std::vector<uint8_t> out(data, data + size); std::vector<uint8_t> out(data, data + size);
idevice_data_free(data, size); idevice_data_free(data, size);
return out; return Ok(out);
} }
} // namespace IdeviceFFI } // namespace IdeviceFFI

View File

@@ -6,58 +6,53 @@
namespace IdeviceFFI { namespace IdeviceFFI {
std::optional<Provider> Provider::tcp_new(const idevice_sockaddr* ip, Result<Provider, FfiError>
PairingFile&& pairing, Provider::tcp_new(const idevice_sockaddr* ip, PairingFile&& pairing, const std::string& label) {
const std::string& label,
FfiError& err) {
IdeviceProviderHandle* out = nullptr; IdeviceProviderHandle* out = nullptr;
// Call with exact types; do NOT cast to void* FfiError e(idevice_tcp_provider_new(
if (IdeviceFfiError* e = idevice_tcp_provider_new( ip, static_cast<IdevicePairingFile*>(pairing.raw()), label.c_str(), &out));
ip, static_cast<IdevicePairingFile*>(pairing.raw()), label.c_str(), &out)) { if (e) {
err = FfiError(e); return Err(e);
return std::nullopt;
} }
// Success: Rust consumed the pairing file -> abandon our ownership // Success: Rust consumed the pairing file -> abandon our ownership
pairing.release(); // implement as: ptr_.release() in PairingFile pairing.release();
return Provider::adopt(out); return Ok(Provider::adopt(out));
} }
std::optional<Provider> Provider::usbmuxd_new(UsbmuxdAddr&& addr, Result<Provider, FfiError> Provider::usbmuxd_new(UsbmuxdAddr&& addr,
uint32_t tag, uint32_t tag,
const std::string& udid, const std::string& udid,
uint32_t device_id, uint32_t device_id,
const std::string& label, const std::string& label) {
FfiError& err) {
IdeviceProviderHandle* out = nullptr; IdeviceProviderHandle* out = nullptr;
if (IdeviceFfiError* e = usbmuxd_provider_new(static_cast<UsbmuxdAddrHandle*>(addr.raw()), FfiError e(usbmuxd_provider_new(static_cast<UsbmuxdAddrHandle*>(addr.raw()),
tag, tag,
udid.c_str(), udid.c_str(),
device_id, device_id,
label.c_str(), label.c_str(),
&out)) { &out));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
// Success: Rust consumed the addr -> abandon our ownership // Success: Rust consumed the addr -> abandon our ownership
addr.release(); // implement as: ptr_.release() in UsbmuxdAddr addr.release();
return Ok(Provider::adopt(out));
return Provider::adopt(out);
} }
std::optional<PairingFile> Provider::get_pairing_file(FfiError& err) { Result<PairingFile, FfiError> Provider::get_pairing_file() {
IdevicePairingFile* out = nullptr; IdevicePairingFile* out = nullptr;
if (IdeviceFfiError* e = idevice_provider_get_pairing_file(handle_.get(), &out)) { FfiError e(idevice_provider_get_pairing_file(handle_.get(), &out));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return PairingFile(out); return Ok(PairingFile(out));
} }
} // namespace IdeviceFFI } // namespace IdeviceFFI

View File

@@ -4,27 +4,26 @@
namespace IdeviceFFI { namespace IdeviceFFI {
std::optional<RemoteServer> RemoteServer::from_socket(ReadWrite&& rw, FfiError& err) { Result<RemoteServer, FfiError> RemoteServer::from_socket(ReadWrite&& rw) {
RemoteServerHandle* out = nullptr; RemoteServerHandle* out = nullptr;
// Rust consumes the stream regardless of result, release BEFORE the call // Rust consumes the stream regardless of result, release BEFORE the call
ReadWriteOpaque* raw = rw.release(); ReadWriteOpaque* raw = rw.release();
if (IdeviceFfiError* e = ::remote_server_new(raw, &out)) { FfiError e(::remote_server_new(raw, &out));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return RemoteServer::adopt(out); return Ok(RemoteServer::adopt(out));
} }
std::optional<RemoteServer> Result<RemoteServer, FfiError> RemoteServer::connect_rsd(Adapter& adapter, RsdHandshake& rsd) {
RemoteServer::connect_rsd(Adapter& adapter, RsdHandshake& rsd, FfiError& err) {
RemoteServerHandle* out = nullptr; RemoteServerHandle* out = nullptr;
if (IdeviceFfiError* e = ::remote_server_connect_rsd(adapter.raw(), rsd.raw(), &out)) { FfiError e(::remote_server_connect_rsd(adapter.raw(), rsd.raw(), &out));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return RemoteServer::adopt(out); return Ok(RemoteServer::adopt(out));
} }
} // namespace IdeviceFFI } // namespace IdeviceFFI

View File

@@ -7,10 +7,12 @@ namespace IdeviceFFI {
// ---------- helpers to copy/free CRsdService ---------- // ---------- helpers to copy/free CRsdService ----------
static RsdService to_cpp_and_free(CRsdService* c) { static RsdService to_cpp_and_free(CRsdService* c) {
RsdService s; RsdService s;
if (c->name) if (c->name) {
s.name = c->name; s.name = c->name;
if (c->entitlement) }
if (c->entitlement) {
s.entitlement = c->entitlement; s.entitlement = c->entitlement;
}
s.port = c->port; s.port = c->port;
s.uses_remote_xpc = c->uses_remote_xpc; s.uses_remote_xpc = c->uses_remote_xpc;
s.service_version = c->service_version; s.service_version = c->service_version;
@@ -20,10 +22,11 @@ static RsdService to_cpp_and_free(CRsdService* c) {
auto** arr = c->features; auto** arr = c->features;
s.features.reserve(c->features_count); s.features.reserve(c->features_count);
for (size_t i = 0; i < c->features_count; ++i) { for (size_t i = 0; i < c->features_count; ++i) {
if (arr[i]) if (arr[i]) {
s.features.emplace_back(arr[i]); s.features.emplace_back(arr[i]);
} }
} }
}
// release the C allocation now that we've copied // release the C allocation now that we've copied
rsd_free_service(c); rsd_free_service(c);
@@ -33,8 +36,9 @@ static RsdService to_cpp_and_free(CRsdService* c) {
static std::vector<RsdService> to_cpp_and_free(CRsdServiceArray* arr) { static std::vector<RsdService> to_cpp_and_free(CRsdServiceArray* arr) {
std::vector<RsdService> out; std::vector<RsdService> out;
if (!arr || !arr->services || arr->count == 0) { if (!arr || !arr->services || arr->count == 0) {
if (arr) if (arr) {
rsd_free_services(arr); rsd_free_services(arr);
}
return out; return out;
} }
out.reserve(arr->count); out.reserve(arr->count);
@@ -51,11 +55,12 @@ static std::vector<RsdService> to_cpp_and_free(CRsdServiceArray* arr) {
auto** feats = begin[i].features; auto** feats = begin[i].features;
out.back().features.reserve(begin[i].features_count); out.back().features.reserve(begin[i].features_count);
for (size_t j = 0; j < begin[i].features_count; ++j) { for (size_t j = 0; j < begin[i].features_count; ++j) {
if (feats[j]) if (feats[j]) {
out.back().features.emplace_back(feats[j]); out.back().features.emplace_back(feats[j]);
} }
} }
} }
}
// free the array + nested C strings now that we've copied // free the array + nested C strings now that we've copied
rsd_free_services(arr); rsd_free_services(arr);
return out; return out;
@@ -80,68 +85,67 @@ RsdHandshake& RsdHandshake::operator=(const RsdHandshake& other) {
} }
// ---------- factory ---------- // ---------- factory ----------
std::optional<RsdHandshake> RsdHandshake::from_socket(ReadWrite&& rw, FfiError& err) { Result<RsdHandshake, FfiError> RsdHandshake::from_socket(ReadWrite&& rw) {
RsdHandshakeHandle* out = nullptr; RsdHandshakeHandle* out = nullptr;
// Rust consumes the socket regardless of result ⇒ release BEFORE call. // Rust consumes the socket regardless of result ⇒ release BEFORE call.
ReadWriteOpaque* raw = rw.release(); ReadWriteOpaque* raw = rw.release();
FfiError e(rsd_handshake_new(raw, &out));
if (IdeviceFfiError* e = rsd_handshake_new(raw, &out)) { if (e) {
err = FfiError(e); return Err(e);
return std::nullopt;
} }
return RsdHandshake::adopt(out); return Ok(RsdHandshake::adopt(out));
} }
// ---------- queries ---------- // ---------- queries ----------
std::optional<size_t> RsdHandshake::protocol_version(FfiError& err) const { Result<size_t, FfiError> RsdHandshake::protocol_version() const {
size_t v = 0; size_t v = 0;
if (IdeviceFfiError* e = rsd_get_protocol_version(handle_.get(), &v)) { FfiError e(rsd_get_protocol_version(handle_.get(), &v));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return v; return Ok(v);
} }
std::optional<std::string> RsdHandshake::uuid(FfiError& err) const { Result<std::string, FfiError> RsdHandshake::uuid() const {
char* c = nullptr; char* c = nullptr;
if (IdeviceFfiError* e = rsd_get_uuid(handle_.get(), &c)) { FfiError e(rsd_get_uuid(handle_.get(), &c));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
std::string out; std::string out;
if (c) { if (c) {
out = c; out = c;
rsd_free_string(c); rsd_free_string(c);
} }
return out; return Ok(out);
} }
std::optional<std::vector<RsdService>> RsdHandshake::services(FfiError& err) const { Result<std::vector<RsdService>, FfiError> RsdHandshake::services() const {
CRsdServiceArray* arr = nullptr; CRsdServiceArray* arr = nullptr;
if (IdeviceFfiError* e = rsd_get_services(handle_.get(), &arr)) { FfiError e(rsd_get_services(handle_.get(), &arr));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return to_cpp_and_free(arr); return Ok(to_cpp_and_free(arr));
} }
std::optional<bool> RsdHandshake::service_available(const std::string& name, FfiError& err) const { Result<bool, FfiError> RsdHandshake::service_available(const std::string& name) const {
bool avail = false; bool avail = false;
if (IdeviceFfiError* e = rsd_service_available(handle_.get(), name.c_str(), &avail)) { FfiError e(rsd_service_available(handle_.get(), name.c_str(), &avail));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return avail; return Ok(avail);
} }
std::optional<RsdService> RsdHandshake::service_info(const std::string& name, FfiError& err) const { Result<RsdService, FfiError> RsdHandshake::service_info(const std::string& name) const {
CRsdService* svc = nullptr; CRsdService* svc = nullptr;
if (IdeviceFfiError* e = rsd_get_service_info(handle_.get(), name.c_str(), &svc)) { FfiError e(rsd_get_service_info(handle_.get(), name.c_str(), &svc));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return to_cpp_and_free(svc); return Ok(to_cpp_and_free(svc));
} }
} // namespace IdeviceFFI } // namespace IdeviceFFI

View File

@@ -28,16 +28,16 @@ bool TcpObjectStackEater::read(OwnedBuffer& out, FfiError& err) const {
} }
// ---------- TcpStackFromCallback ---------- // ---------- TcpStackFromCallback ----------
std::optional<TcpObjectStack> Result<TcpObjectStack, FfiError> TcpObjectStack::create(const std::string& our_ip,
TcpObjectStack::create(const std::string& our_ip, const std::string& their_ip, FfiError& err) { const std::string& their_ip) {
::TcpFeedObject* feeder_h = nullptr; ::TcpFeedObject* feeder_h = nullptr;
::TcpEatObject* eater_h = nullptr; ::TcpEatObject* eater_h = nullptr;
::AdapterHandle* adapter_h = nullptr; ::AdapterHandle* adapter_h = nullptr;
if (IdeviceFfiError* e = ::idevice_tcp_stack_into_sync_objects( FfiError e(::idevice_tcp_stack_into_sync_objects(
our_ip.c_str(), their_ip.c_str(), &feeder_h, &eater_h, &adapter_h)) { our_ip.c_str(), their_ip.c_str(), &feeder_h, &eater_h, &adapter_h));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
auto impl = std::make_unique<Impl>(); auto impl = std::make_unique<Impl>();
@@ -47,7 +47,7 @@ TcpObjectStack::create(const std::string& our_ip, const std::string& their_ip, F
TcpObjectStack out; TcpObjectStack out;
out.impl_ = std::move(impl); out.impl_ = std::move(impl);
return out; return Ok(std::move(out));
} }
TcpObjectStackFeeder& TcpObjectStack::feeder() { TcpObjectStackFeeder& TcpObjectStack::feeder() {
@@ -65,49 +65,54 @@ const TcpObjectStackEater& TcpObjectStack::eater() const {
} }
Adapter& TcpObjectStack::adapter() { Adapter& TcpObjectStack::adapter() {
if (!impl_ || !impl_->adapter) { if (!impl_ || impl_->adapter.is_some()) {
static Adapter* never = nullptr; static Adapter* never = nullptr;
return *never; return *never;
} }
return *(impl_->adapter); return (impl_->adapter.unwrap());
} }
const Adapter& TcpObjectStack::adapter() const { const Adapter& TcpObjectStack::adapter() const {
if (!impl_ || !impl_->adapter) { if (!impl_ || impl_->adapter.is_none()) {
static Adapter* never = nullptr; static Adapter* never = nullptr;
return *never; return *never;
} }
return *(impl_->adapter); return (impl_->adapter.unwrap());
} }
// ---------- Release APIs ---------- // ---------- Release APIs ----------
std::optional<TcpObjectStackFeeder> TcpObjectStack::release_feeder() { Option<TcpObjectStackFeeder> TcpObjectStack::release_feeder() {
if (!impl_) if (!impl_) {
return std::nullopt; return None;
}
auto has = impl_->feeder.raw() != nullptr; auto has = impl_->feeder.raw() != nullptr;
if (!has) if (!has) {
return std::nullopt; return None;
}
TcpObjectStackFeeder out = std::move(impl_->feeder); TcpObjectStackFeeder out = std::move(impl_->feeder);
// impl_->feeder is now empty (h_ == nullptr) thanks to move // impl_->feeder is now empty (h_ == nullptr) thanks to move
return std::optional<TcpObjectStackFeeder>(std::move(out)); return Some(std::move(out));
} }
std::optional<TcpObjectStackEater> TcpObjectStack::release_eater() { Option<TcpObjectStackEater> TcpObjectStack::release_eater() {
if (!impl_) if (!impl_) {
return std::nullopt; return None;
}
auto has = impl_->eater.raw() != nullptr; auto has = impl_->eater.raw() != nullptr;
if (!has) if (!has) {
return std::nullopt; return None;
}
TcpObjectStackEater out = std::move(impl_->eater); TcpObjectStackEater out = std::move(impl_->eater);
return std::optional<TcpObjectStackEater>(std::move(out)); return Some(std::move(out));
} }
std::optional<Adapter> TcpObjectStack::release_adapter() { Option<Adapter> TcpObjectStack::release_adapter() {
if (!impl_ || !impl_->adapter) if (!impl_ || impl_->adapter.is_none()) {
return std::nullopt; return None;
}
// Move out and clear our optional // Move out and clear our optional
auto out = std::move(*(impl_->adapter)); auto out = std::move((impl_->adapter.unwrap()));
impl_->adapter.reset(); impl_->adapter.reset();
return std::optional<Adapter>(std::move(out)); return Some(std::move(out));
} }
} // namespace IdeviceFFI } // namespace IdeviceFFI

View File

@@ -6,24 +6,23 @@
namespace IdeviceFFI { namespace IdeviceFFI {
// ---------- UsbmuxdAddr ---------- // ---------- UsbmuxdAddr ----------
std::optional<UsbmuxdAddr> Result<UsbmuxdAddr, FfiError> UsbmuxdAddr::tcp_new(const sockaddr* addr, socklen_t addr_len) {
UsbmuxdAddr::tcp_new(const sockaddr* addr, socklen_t addr_len, FfiError& err) {
UsbmuxdAddrHandle* h = nullptr; UsbmuxdAddrHandle* h = nullptr;
if (IdeviceFfiError* e = idevice_usbmuxd_tcp_addr_new(addr, addr_len, &h)) { FfiError e(idevice_usbmuxd_tcp_addr_new(addr, addr_len, &h));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return UsbmuxdAddr(h); return Ok(UsbmuxdAddr(h));
} }
#if defined(__unix__) || defined(__APPLE__) #if defined(__unix__) || defined(__APPLE__)
std::optional<UsbmuxdAddr> UsbmuxdAddr::unix_new(const std::string& path, FfiError& err) { Result<UsbmuxdAddr, FfiError> UsbmuxdAddr::unix_new(const std::string& path) {
UsbmuxdAddrHandle* h = nullptr; UsbmuxdAddrHandle* h = nullptr;
if (IdeviceFfiError* e = idevice_usbmuxd_unix_addr_new(path.c_str(), &h)) { FfiError e(idevice_usbmuxd_unix_addr_new(path.c_str(), &h));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return UsbmuxdAddr(h); return Ok(UsbmuxdAddr(h));
} }
#endif #endif
@@ -48,114 +47,109 @@ std::string UsbmuxdConnectionType::to_string() const {
} }
// ---------- UsbmuxdDevice ---------- // ---------- UsbmuxdDevice ----------
std::optional<std::string> UsbmuxdDevice::get_udid() const { Option<std::string> UsbmuxdDevice::get_udid() const {
char* c = idevice_usbmuxd_device_get_udid(handle_.get()); char* c = idevice_usbmuxd_device_get_udid(handle_.get());
if (!c) if (!c) {
return std::nullopt; return None;
}
std::string out(c); std::string out(c);
idevice_string_free(c); idevice_string_free(c);
return out; return Some(out);
} }
std::optional<uint32_t> UsbmuxdDevice::get_id() const { Option<uint32_t> UsbmuxdDevice::get_id() const {
uint32_t id = idevice_usbmuxd_device_get_device_id(handle_.get()); uint32_t id = idevice_usbmuxd_device_get_device_id(handle_.get());
if (id == 0) if (id == 0) {
return std::nullopt; // adjust if 0 can be valid return None;
return id; }
return Some(id);
} }
std::optional<UsbmuxdConnectionType> UsbmuxdDevice::get_connection_type() const { Option<UsbmuxdConnectionType> UsbmuxdDevice::get_connection_type() const {
uint8_t t = idevice_usbmuxd_device_get_connection_type(handle_.get()); uint8_t t = idevice_usbmuxd_device_get_connection_type(handle_.get());
if (t == 0) if (t == 0) {
return std::nullopt; return None;
return UsbmuxdConnectionType(t); }
return Some(UsbmuxdConnectionType(t));
} }
// ---------- UsbmuxdConnection ---------- // ---------- UsbmuxdConnection ----------
std::optional<UsbmuxdConnection> UsbmuxdConnection::tcp_new(const idevice_sockaddr* addr, Result<UsbmuxdConnection, FfiError>
idevice_socklen_t addr_len, UsbmuxdConnection::tcp_new(const idevice_sockaddr* addr, idevice_socklen_t addr_len, uint32_t tag) {
uint32_t tag,
FfiError& err) {
UsbmuxdConnectionHandle* h = nullptr; UsbmuxdConnectionHandle* h = nullptr;
if (IdeviceFfiError* e = idevice_usbmuxd_new_tcp_connection(addr, addr_len, tag, &h)) { FfiError e(idevice_usbmuxd_new_tcp_connection(addr, addr_len, tag, &h));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return UsbmuxdConnection(h); return Ok(UsbmuxdConnection(h));
} }
#if defined(__unix__) || defined(__APPLE__) #if defined(__unix__) || defined(__APPLE__)
std::optional<UsbmuxdConnection> Result<UsbmuxdConnection, FfiError> UsbmuxdConnection::unix_new(const std::string& path,
UsbmuxdConnection::unix_new(const std::string& path, uint32_t tag, FfiError& err) { uint32_t tag) {
UsbmuxdConnectionHandle* h = nullptr; UsbmuxdConnectionHandle* h = nullptr;
if (IdeviceFfiError* e = idevice_usbmuxd_new_unix_socket_connection(path.c_str(), tag, &h)) { FfiError e(idevice_usbmuxd_new_unix_socket_connection(path.c_str(), tag, &h));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return UsbmuxdConnection(h); return Ok(UsbmuxdConnection(h));
} }
#endif #endif
std::optional<UsbmuxdConnection> UsbmuxdConnection::default_new(uint32_t tag, FfiError& err) { Result<UsbmuxdConnection, FfiError> UsbmuxdConnection::default_new(uint32_t tag) {
UsbmuxdConnectionHandle* h = nullptr; UsbmuxdConnectionHandle* h = nullptr;
if (IdeviceFfiError* e = idevice_usbmuxd_new_default_connection(tag, &h)) { FfiError e(idevice_usbmuxd_new_default_connection(tag, &h));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
return UsbmuxdConnection(h); return Ok(UsbmuxdConnection(h));
} }
std::optional<std::vector<UsbmuxdDevice>> UsbmuxdConnection::get_devices(FfiError& err) const { Result<std::vector<UsbmuxdDevice>, FfiError> UsbmuxdConnection::get_devices() const {
UsbmuxdDeviceHandle** list = nullptr; UsbmuxdDeviceHandle** list = nullptr;
int count = 0; int count = 0;
if (IdeviceFfiError* e = idevice_usbmuxd_get_devices(handle_.get(), &list, &count)) { FfiError e(idevice_usbmuxd_get_devices(handle_.get(), &list, &count));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
std::vector<UsbmuxdDevice> out; std::vector<UsbmuxdDevice> out;
out.reserve(count); out.reserve(count);
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
out.emplace_back(UsbmuxdDevice::adopt(list[i])); out.emplace_back(UsbmuxdDevice::adopt(list[i]));
} }
return out; return Ok(std::move(out));
} }
std::optional<std::string> UsbmuxdConnection::get_buid(FfiError& err) const { Result<std::string, FfiError> UsbmuxdConnection::get_buid() const {
char* c = nullptr; char* c = nullptr;
if (IdeviceFfiError* e = idevice_usbmuxd_get_buid(handle_.get(), &c)) { FfiError e(idevice_usbmuxd_get_buid(handle_.get(), &c));
err = FfiError(e); if (e) {
return std::nullopt; return Err(e);
} }
std::string out(c); std::string out(c);
idevice_string_free(c); idevice_string_free(c);
return out; return Ok(out);
} }
std::optional<PairingFile> UsbmuxdConnection::get_pair_record(const std::string& udid, Result<PairingFile, FfiError> UsbmuxdConnection::get_pair_record(const std::string& udid) {
FfiError& err) {
IdevicePairingFile* pf = nullptr; IdevicePairingFile* pf = nullptr;
if (IdeviceFfiError* e = idevice_usbmuxd_get_pair_record(handle_.get(), udid.c_str(), &pf)) { FfiError e(idevice_usbmuxd_get_pair_record(handle_.get(), udid.c_str(), &pf));
err = FfiError(e);
return std::nullopt;
}
return PairingFile(pf);
}
std::optional<Idevice> UsbmuxdConnection::connect_to_device(uint32_t device_id,
uint16_t port,
const std::string& path,
FfiError& err) && {
UsbmuxdConnectionHandle* raw = handle_.release();
IdeviceHandle* out = nullptr;
IdeviceFfiError* e =
idevice_usbmuxd_connect_to_device(raw, device_id, port, path.c_str(), &out);
if (e) { if (e) {
err = FfiError(e); return Err(e);
return std::nullopt;
} }
return Idevice::adopt(out); return Ok(PairingFile(pf));
}
Result<Idevice, FfiError> UsbmuxdConnection::connect_to_device(uint32_t device_id,
uint16_t port,
const std::string& path) && {
UsbmuxdConnectionHandle* raw = handle_.release();
IdeviceHandle* out = nullptr;
FfiError e(idevice_usbmuxd_connect_to_device(raw, device_id, port, path.c_str(), &out));
if (e) {
return Err(e);
}
return Ok(Idevice::adopt(out));
} }
} // namespace IdeviceFFI } // namespace IdeviceFFI