diff --git a/cpp/examples/location_simulation.cpp b/cpp/examples/location_simulation.cpp new file mode 100644 index 0000000..824eebc --- /dev/null +++ b/cpp/examples/location_simulation.cpp @@ -0,0 +1,135 @@ +// Jackson Coxson + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace IdeviceFFI; + +static void die(const char* msg, const FfiError& e) { + std::cerr << msg << ": " << e.message << "\n"; + std::exit(1); +} + +int main(int argc, char** argv) { + // Usage: + // simulate_location clear + // simulate_location set + bool do_clear = false; + std::optional lat, lon; + + if (argc == 2 && std::string(argv[1]) == "clear") { + do_clear = true; + } else if (argc == 4 && std::string(argv[1]) == "set") { + lat = std::stod(argv[2]); + lon = std::stod(argv[3]); + } else { + std::cerr << "Usage:\n" + << " " << argv[0] << " clear\n" + << " " << argv[0] << " set \n"; + return 2; + } + + FfiError err; + + // 1) Connect to usbmuxd and pick first device + auto mux = UsbmuxdConnection::default_new(/*tag*/ 0, err); + if (!mux) + die("failed to connect to usbmuxd", err); + + auto devices = mux->get_devices(err); + if (!devices) + die("failed to list devices", err); + if (devices->empty()) { + std::cerr << "no devices connected\n"; + return 1; + } + auto& dev = (*devices)[0]; + + auto udidOpt = dev.get_udid(); + if (!udidOpt) { + std::cerr << "device has no UDID\n"; + return 1; + } + auto idOpt = dev.get_id(); + if (!idOpt) { + std::cerr << "device has no mux id\n"; + return 1; + } + + // 2) Make a Provider for this device via default addr + auto addr = UsbmuxdAddr::default_new(); + + const uint32_t tag = 0; + const std::string label = "simulate_location-jkcoxson"; + + auto provider = Provider::usbmuxd_new(std::move(addr), tag, *udidOpt, *idOpt, label, err); + if (!provider) + die("failed to create provider", err); + + // 3) Connect CoreDeviceProxy (borrow provider) + auto cdp = CoreDeviceProxy::connect(*provider, err); + if (!cdp) + die("failed to connect CoreDeviceProxy", err); + + // 4) Read handshake’s server RSD port + auto rsd_port = cdp->get_server_rsd_port(err); + if (!rsd_port) + die("failed to get server RSD port", err); + + // 5) Create software tunnel adapter (consumes proxy) + auto adapter = std::move(*cdp).create_tcp_adapter(err); + if (!adapter) + die("failed to create software tunnel adapter", err); + + // 6) Connect adapter to RSD port → ReadWrite stream + auto stream = adapter->connect(*rsd_port, err); + if (!stream) + 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) + auto rs = RemoteServer::connect_rsd(*adapter, *rsd, err); + if (!rs) + die("failed to connect RemoteServer", err); + + // 9) LocationSimulation client (borrows RemoteServer) + auto sim = LocationSimulation::create(*rs, err); + if (!sim) + die("failed to create LocationSimulation client", err); + + if (do_clear) { + if (!sim->clear(err)) + die("clear failed", err); + std::cout << "Location cleared!\n"; + return 0; + } + + // set path + if (!sim->set(*lat, *lon, err)) + die("set failed", err); + std::cout << "Location set to (" << *lat << ", " << *lon << ")\n"; + std::cout << "Press Ctrl-C to stop\n"; + + // keep process alive like the Rust example + for (;;) { + if (!sim->set(*lat, *lon, err)) + die("set failed", err); + std::this_thread::sleep_for(std::chrono::seconds(3)); + } +} diff --git a/cpp/include/idevice++/adapter_stream.hpp b/cpp/include/idevice++/adapter_stream.hpp new file mode 100644 index 0000000..862e91c --- /dev/null +++ b/cpp/include/idevice++/adapter_stream.hpp @@ -0,0 +1,49 @@ +// Jackson Coxson + +#pragma once +#include +#include +#include +#include +#include + +struct IdeviceFfiError; +struct AdapterStreamHandle; + +namespace IdeviceFFI { + +// Non-owning view over a stream (must call close(); no implicit free provided) +class AdapterStream { + public: + explicit AdapterStream(AdapterStreamHandle* h) noexcept : h_(h) {} + + AdapterStream(const AdapterStream&) = delete; + AdapterStream& operator=(const AdapterStream&) = delete; + + AdapterStream(AdapterStream&& other) noexcept : h_(other.h_) { other.h_ = nullptr; } + AdapterStream& operator=(AdapterStream&& other) noexcept { + if (this != &other) { + h_ = other.h_; + other.h_ = nullptr; + } + return *this; + } + + ~AdapterStream() noexcept = default; // no auto-close; caller controls + + AdapterStreamHandle* raw() const noexcept { return h_; } + + bool close(FfiError& err); + bool send(const uint8_t* data, size_t len, FfiError& err); + bool send(const std::vector& buf, FfiError& err) { + return send(buf.data(), buf.size(), err); + } + + // recv into caller-provided buffer (resizes to actual length) + bool recv(std::vector& out, FfiError& err, size_t max_hint = 2048); + + private: + AdapterStreamHandle* h_{}; +}; + +} // namespace IdeviceFFI diff --git a/cpp/include/idevice++/core_device_proxy.hpp b/cpp/include/idevice++/core_device_proxy.hpp new file mode 100644 index 0000000..2b392d2 --- /dev/null +++ b/cpp/include/idevice++/core_device_proxy.hpp @@ -0,0 +1,101 @@ +// Jackson Coxson + +#ifndef IDEVICE_CORE_DEVICE_PROXY_H +#define IDEVICE_CORE_DEVICE_PROXY_H + +#include +#include +#include +#include + +namespace IdeviceFFI { + +using CoreProxyPtr = std::unique_ptr>; +using AdapterPtr = std::unique_ptr>; + +struct CoreClientParams { + uint16_t mtu{}; + std::string address; // freed from Rust side after copy + std::string netmask; // freed from Rust side after copy +}; + +class Adapter { + public: + ~Adapter() noexcept = default; + Adapter(Adapter&&) noexcept = default; + Adapter& operator=(Adapter&&) noexcept = default; + Adapter(const Adapter&) = delete; + Adapter& operator=(const Adapter&) = delete; + + static Adapter adopt(AdapterHandle* h) noexcept { return Adapter(h); } + AdapterHandle* raw() const noexcept { return handle_.get(); } + + // Enable PCAP + bool pcap(const std::string& path, FfiError& err) { + if (IdeviceFfiError* e = ::adapter_pcap(handle_.get(), path.c_str())) { + err = FfiError(e); + return false; + } + return true; + } + + // Connect to a port, returns a ReadWrite stream (to be consumed by RSD/CoreDeviceProxy) + std::optional connect(uint16_t port, FfiError& err) { + ReadWriteOpaque* s = nullptr; + if (IdeviceFfiError* e = ::adapter_connect(handle_.get(), port, &s)) { + err = FfiError(e); + return std::nullopt; + } + return ReadWrite::adopt(s); + } + + private: + explicit Adapter(AdapterHandle* h) noexcept : handle_(h) {} + AdapterPtr handle_{}; +}; + +class CoreDeviceProxy { + public: + // Factory: connect using a Provider (NOT consumed on success or error) + static std::optional connect(Provider& provider, FfiError& err); + + // Factory: from a socket; Rust consumes the socket regardless of result → we release before + // call + static std::optional from_socket(Idevice&& socket, FfiError& err); + + // Send/recv + bool send(const uint8_t* data, size_t len, FfiError& err); + bool send(const std::vector& buf, FfiError& err) { + return send(buf.data(), buf.size(), err); + } + + // recv into a pre-sized buffer; resizes to actual bytes received + bool recv(std::vector& out, FfiError& err); + + // Handshake info + std::optional get_client_parameters(FfiError& err) const; + std::optional get_server_address(FfiError& err) const; + std::optional get_server_rsd_port(FfiError& err) const; + + // Consuming creation of a TCP adapter: Rust consumes the proxy handle + std::optional create_tcp_adapter(FfiError& err) &&; + + // RAII / moves + ~CoreDeviceProxy() noexcept = default; + CoreDeviceProxy(CoreDeviceProxy&&) noexcept = default; + CoreDeviceProxy& operator=(CoreDeviceProxy&&) noexcept = default; + CoreDeviceProxy(const CoreDeviceProxy&) = delete; + CoreDeviceProxy& operator=(const CoreDeviceProxy&) = delete; + + CoreDeviceProxyHandle* raw() const noexcept { return handle_.get(); } + CoreDeviceProxyHandle* release() noexcept { return handle_.release(); } + static CoreDeviceProxy adopt(CoreDeviceProxyHandle* h) noexcept { return CoreDeviceProxy(h); } + + private: + explicit CoreDeviceProxy(CoreDeviceProxyHandle* h) noexcept : handle_(h) {} + CoreProxyPtr handle_{}; +}; + +} // namespace IdeviceFFI +#endif diff --git a/cpp/include/idevice++/location_simulation.hpp b/cpp/include/idevice++/location_simulation.hpp new file mode 100644 index 0000000..5324292 --- /dev/null +++ b/cpp/include/idevice++/location_simulation.hpp @@ -0,0 +1,38 @@ +// Jackson Coxson + +#pragma once +#include +#include +#include +#include + +namespace IdeviceFFI { + +using LocSimPtr = std::unique_ptr>; + +class LocationSimulation { + public: + // Factory: borrows the RemoteServer; not consumed + static std::optional create(RemoteServer& server, FfiError& err); + + bool clear(FfiError& err); + bool set(double latitude, double longitude, FfiError& err); + + ~LocationSimulation() noexcept = default; + LocationSimulation(LocationSimulation&&) noexcept = default; + LocationSimulation& operator=(LocationSimulation&&) noexcept = default; + LocationSimulation(const LocationSimulation&) = delete; + LocationSimulation& operator=(const LocationSimulation&) = delete; + + LocationSimulationHandle* raw() const noexcept { return handle_.get(); } + static LocationSimulation adopt(LocationSimulationHandle* h) noexcept { + return LocationSimulation(h); + } + + private: + explicit LocationSimulation(LocationSimulationHandle* h) noexcept : handle_(h) {} + LocSimPtr handle_{}; +}; + +} // namespace IdeviceFFI diff --git a/cpp/include/idevice++/readwrite.hpp b/cpp/include/idevice++/readwrite.hpp new file mode 100644 index 0000000..f9a8000 --- /dev/null +++ b/cpp/include/idevice++/readwrite.hpp @@ -0,0 +1,45 @@ +// Jackson Coxson + +#pragma once + +#include + +namespace IdeviceFFI { + +// A move-only holder for a fat-pointer stream. It does NOT free on destruction. +// Always pass ownership to an FFI that consumes it by calling release(). +class ReadWrite { + public: + ReadWrite() noexcept : ptr_(nullptr) {} + explicit ReadWrite(ReadWriteOpaque* p) noexcept : ptr_(p) {} + + ReadWrite(const ReadWrite&) = delete; + ReadWrite& operator=(const ReadWrite&) = delete; + + ReadWrite(ReadWrite&& other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; } + ReadWrite& operator=(ReadWrite&& other) noexcept { + if (this != &other) { + ptr_ = other.ptr_; + other.ptr_ = nullptr; + } + return *this; + } + + ~ReadWrite() noexcept = default; // no dtor – Rust consumers own free/drop + + ReadWriteOpaque* raw() const noexcept { return ptr_; } + ReadWriteOpaque* release() noexcept { + auto* p = ptr_; + ptr_ = nullptr; + return p; + } + + static ReadWrite adopt(ReadWriteOpaque* p) noexcept { return ReadWrite(p); } + + explicit operator bool() const noexcept { return ptr_ != nullptr; } + + private: + ReadWriteOpaque* ptr_; +}; + +} // namespace IdeviceFFI diff --git a/cpp/include/idevice++/remote_server.hpp b/cpp/include/idevice++/remote_server.hpp new file mode 100644 index 0000000..51f8d98 --- /dev/null +++ b/cpp/include/idevice++/remote_server.hpp @@ -0,0 +1,44 @@ +// Jackson Coxson + +#ifndef IDEVICE_REMOTE_SERVER_H +#define IDEVICE_REMOTE_SERVER_H + +#pragma once +#include +#include +#include +#include +#include +#include + +namespace IdeviceFFI { + +using RemoteServerPtr = + std::unique_ptr>; + +class RemoteServer { + public: + // Factory: consumes the ReadWrite stream regardless of result + static std::optional from_socket(ReadWrite&& rw, FfiError& err); + + // Factory: borrows adapter + handshake (neither is consumed) + static std::optional + connect_rsd(Adapter& adapter, RsdHandshake& rsd, FfiError& err); + + // RAII / moves + ~RemoteServer() noexcept = default; + RemoteServer(RemoteServer&&) noexcept = default; + RemoteServer& operator=(RemoteServer&&) noexcept = default; + RemoteServer(const RemoteServer&) = delete; + RemoteServer& operator=(const RemoteServer&) = delete; + + RemoteServerHandle* raw() const noexcept { return handle_.get(); } + static RemoteServer adopt(RemoteServerHandle* h) noexcept { return RemoteServer(h); } + + private: + explicit RemoteServer(RemoteServerHandle* h) noexcept : handle_(h) {} + RemoteServerPtr handle_{}; +}; + +} // namespace IdeviceFFI +#endif diff --git a/cpp/include/idevice++/rsd.hpp b/cpp/include/idevice++/rsd.hpp new file mode 100644 index 0000000..b3898ec --- /dev/null +++ b/cpp/include/idevice++/rsd.hpp @@ -0,0 +1,55 @@ +// Jackson Coxson + +#ifndef IDEVICE_RSD_H +#define IDEVICE_RSD_H + +#include +#include +#include +#include + +namespace IdeviceFFI { + +struct RsdService { + std::string name; + std::string entitlement; + uint16_t port{}; + bool uses_remote_xpc{}; + std::vector features; + int64_t service_version{-1}; +}; + +using RsdPtr = + std::unique_ptr>; + +class RsdHandshake { + public: + // Factory: consumes the ReadWrite socket regardless of result + static std::optional from_socket(ReadWrite&& rw, FfiError& err); + + // Basic info + std::optional protocol_version(FfiError& err) const; + std::optional uuid(FfiError& err) const; + + // Services + std::optional> services(FfiError& err) const; + std::optional service_available(const std::string& name, FfiError& err) const; + std::optional service_info(const std::string& name, FfiError& err) const; + + // RAII / moves + ~RsdHandshake() noexcept = default; + RsdHandshake(RsdHandshake&&) noexcept = default; + RsdHandshake& operator=(RsdHandshake&&) noexcept = default; + RsdHandshake(const RsdHandshake&) = delete; + RsdHandshake& operator=(const RsdHandshake&) = delete; + + RsdHandshakeHandle* raw() const noexcept { return handle_.get(); } + static RsdHandshake adopt(RsdHandshakeHandle* h) noexcept { return RsdHandshake(h); } + + private: + explicit RsdHandshake(RsdHandshakeHandle* h) noexcept : handle_(h) {} + RsdPtr handle_{}; +}; + +} // namespace IdeviceFFI +#endif diff --git a/cpp/src/adapter_stream.cpp b/cpp/src/adapter_stream.cpp new file mode 100644 index 0000000..2d782bf --- /dev/null +++ b/cpp/src/adapter_stream.cpp @@ -0,0 +1,43 @@ +// Jackson Coxson + +#include + +namespace IdeviceFFI { + +bool AdapterStream::close(FfiError& err) { + if (!h_) + return true; + if (IdeviceFfiError* e = ::adapter_close(h_)) { + err = FfiError(e); + return false; + } + h_ = nullptr; + return true; +} + +bool AdapterStream::send(const uint8_t* data, size_t len, FfiError& err) { + if (!h_) + return false; + if (IdeviceFfiError* e = ::adapter_send(h_, data, len)) { + err = FfiError(e); + return false; + } + return true; +} + +bool AdapterStream::recv(std::vector& out, FfiError& err, size_t max_hint) { + if (!h_) + return false; + if (max_hint == 0) + max_hint = 2048; + out.resize(max_hint); + size_t actual = 0; + if (IdeviceFfiError* e = ::adapter_recv(h_, out.data(), &actual, out.size())) { + err = FfiError(e); + return false; + } + out.resize(actual); + return true; +} + +} // namespace IdeviceFFI diff --git a/cpp/src/core_device.cpp b/cpp/src/core_device.cpp new file mode 100644 index 0000000..07e91e3 --- /dev/null +++ b/cpp/src/core_device.cpp @@ -0,0 +1,119 @@ +// Jackson Coxson + +#include + +namespace IdeviceFFI { + +// ---- Factories ---- + +std::optional CoreDeviceProxy::connect(Provider& provider, FfiError& err) { + CoreDeviceProxyHandle* out = nullptr; + if (IdeviceFfiError* e = ::core_device_proxy_connect(provider.raw(), &out)) { + err = FfiError(e); + return std::nullopt; + } + return CoreDeviceProxy::adopt(out); +} + +std::optional CoreDeviceProxy::from_socket(Idevice&& socket, FfiError& err) { + CoreDeviceProxyHandle* out = nullptr; + + // Rust consumes the socket regardless of result → release BEFORE call + IdeviceHandle* raw = socket.release(); + + if (IdeviceFfiError* e = ::core_device_proxy_new(raw, &out)) { + // socket is already consumed on error; do NOT touch it + err = FfiError(e); + return std::nullopt; + } + return CoreDeviceProxy::adopt(out); +} + +// ---- IO ---- + +bool CoreDeviceProxy::send(const uint8_t* data, size_t len, FfiError& err) { + if (IdeviceFfiError* e = ::core_device_proxy_send(handle_.get(), data, len)) { + err = FfiError(e); + return false; + } + return true; +} + +bool CoreDeviceProxy::recv(std::vector& out, FfiError& err) { + if (out.empty()) + out.resize(4096); // a reasonable default; caller can pre-size + size_t actual = 0; + if (IdeviceFfiError* e = + ::core_device_proxy_recv(handle_.get(), out.data(), &actual, out.size())) { + err = FfiError(e); + return false; + } + out.resize(actual); + return true; +} + +// ---- Handshake ---- + +std::optional CoreDeviceProxy::get_client_parameters(FfiError& err) const { + uint16_t mtu = 0; + char* addr_c = nullptr; + char* mask_c = nullptr; + + if (IdeviceFfiError* e = + ::core_device_proxy_get_client_parameters(handle_.get(), &mtu, &addr_c, &mask_c)) { + err = FfiError(e); + return std::nullopt; + } + + CoreClientParams params; + params.mtu = mtu; + if (addr_c) { + params.address = addr_c; + ::idevice_string_free(addr_c); + } + if (mask_c) { + params.netmask = mask_c; + ::idevice_string_free(mask_c); + } + return params; +} + +std::optional CoreDeviceProxy::get_server_address(FfiError& err) const { + char* addr_c = nullptr; + if (IdeviceFfiError* e = ::core_device_proxy_get_server_address(handle_.get(), &addr_c)) { + err = FfiError(e); + return std::nullopt; + } + std::string s; + if (addr_c) { + s = addr_c; + ::idevice_string_free(addr_c); + } + return s; +} + +std::optional CoreDeviceProxy::get_server_rsd_port(FfiError& err) const { + uint16_t port = 0; + if (IdeviceFfiError* e = ::core_device_proxy_get_server_rsd_port(handle_.get(), &port)) { + err = FfiError(e); + return std::nullopt; + } + return port; +} + +// ---- Adapter creation (consumes *this) ---- + +std::optional CoreDeviceProxy::create_tcp_adapter(FfiError& err) && { + AdapterHandle* out = nullptr; + + // Rust consumes the proxy regardless of result → release BEFORE call + CoreDeviceProxyHandle* raw = this->release(); + + if (IdeviceFfiError* e = ::core_device_proxy_create_tcp_adapter(raw, &out)) { + err = FfiError(e); + return std::nullopt; + } + return Adapter::adopt(out); +} + +} // namespace IdeviceFFI diff --git a/cpp/src/location_simulation.cpp b/cpp/src/location_simulation.cpp new file mode 100644 index 0000000..c53eef6 --- /dev/null +++ b/cpp/src/location_simulation.cpp @@ -0,0 +1,32 @@ +// Jackson Coxson + +#include + +namespace IdeviceFFI { + +std::optional LocationSimulation::create(RemoteServer& server, FfiError& err) { + LocationSimulationHandle* out = nullptr; + if (IdeviceFfiError* e = ::location_simulation_new(server.raw(), &out)) { + err = FfiError(e); + return std::nullopt; + } + return LocationSimulation::adopt(out); +} + +bool LocationSimulation::clear(FfiError& err) { + if (IdeviceFfiError* e = ::location_simulation_clear(handle_.get())) { + err = FfiError(e); + return false; + } + return true; +} + +bool LocationSimulation::set(double latitude, double longitude, FfiError& err) { + if (IdeviceFfiError* e = ::location_simulation_set(handle_.get(), latitude, longitude)) { + err = FfiError(e); + return false; + } + return true; +} + +} // namespace IdeviceFFI diff --git a/cpp/src/remote_server.cpp b/cpp/src/remote_server.cpp new file mode 100644 index 0000000..90e1b06 --- /dev/null +++ b/cpp/src/remote_server.cpp @@ -0,0 +1,30 @@ +// Jackson Coxson + +#include + +namespace IdeviceFFI { + +std::optional RemoteServer::from_socket(ReadWrite&& rw, FfiError& err) { + RemoteServerHandle* out = nullptr; + + // Rust consumes the stream regardless of result, release BEFORE the call + ReadWriteOpaque* raw = rw.release(); + + if (IdeviceFfiError* e = ::remote_server_new(raw, &out)) { + err = FfiError(e); + return std::nullopt; + } + return RemoteServer::adopt(out); +} + +std::optional +RemoteServer::connect_rsd(Adapter& adapter, RsdHandshake& rsd, FfiError& err) { + RemoteServerHandle* out = nullptr; + if (IdeviceFfiError* e = ::remote_server_connect_rsd(adapter.raw(), rsd.raw(), &out)) { + err = FfiError(e); + return std::nullopt; + } + return RemoteServer::adopt(out); +} + +} // namespace IdeviceFFI diff --git a/cpp/src/rsd.cpp b/cpp/src/rsd.cpp new file mode 100644 index 0000000..3575c6f --- /dev/null +++ b/cpp/src/rsd.cpp @@ -0,0 +1,129 @@ +// Jackson Coxson + +#include + +namespace IdeviceFFI { + +// ---------- helpers to copy/free CRsdService ---------- +static RsdService to_cpp_and_free(CRsdService* c) { + RsdService s; + if (c->name) + s.name = c->name; + if (c->entitlement) + s.entitlement = c->entitlement; + s.port = c->port; + s.uses_remote_xpc = c->uses_remote_xpc; + s.service_version = c->service_version; + + // features + if (c->features && c->features_count > 0) { + auto** arr = c->features; + s.features.reserve(c->features_count); + for (size_t i = 0; i < c->features_count; ++i) { + if (arr[i]) + s.features.emplace_back(arr[i]); + } + } + + // release the C allocation now that we've copied + rsd_free_service(c); + return s; +} + +static std::vector to_cpp_and_free(CRsdServiceArray* arr) { + std::vector out; + if (!arr || !arr->services || arr->count == 0) { + if (arr) + rsd_free_services(arr); + return out; + } + out.reserve(arr->count); + auto* begin = arr->services; + for (size_t i = 0; i < arr->count; ++i) { + out.emplace_back(RsdService{begin[i].name ? begin[i].name : "", + begin[i].entitlement ? begin[i].entitlement : "", + begin[i].port, + begin[i].uses_remote_xpc, + {}, // features, fill below + begin[i].service_version}); + // features for this service + if (begin[i].features && begin[i].features_count > 0) { + auto** feats = begin[i].features; + out.back().features.reserve(begin[i].features_count); + for (size_t j = 0; j < begin[i].features_count; ++j) { + if (feats[j]) + out.back().features.emplace_back(feats[j]); + } + } + } + // free the array + nested C strings now that we've copied + rsd_free_services(arr); + return out; +} + +// ---------- factory ---------- +std::optional RsdHandshake::from_socket(ReadWrite&& rw, FfiError& err) { + RsdHandshakeHandle* out = nullptr; + + // Rust consumes the socket regardless of result ⇒ release BEFORE call. + ReadWriteOpaque* raw = rw.release(); + + if (IdeviceFfiError* e = rsd_handshake_new(raw, &out)) { + err = FfiError(e); + return std::nullopt; + } + return RsdHandshake::adopt(out); +} + +// ---------- queries ---------- +std::optional RsdHandshake::protocol_version(FfiError& err) const { + size_t v = 0; + if (IdeviceFfiError* e = rsd_get_protocol_version(handle_.get(), &v)) { + err = FfiError(e); + return std::nullopt; + } + return v; +} + +std::optional RsdHandshake::uuid(FfiError& err) const { + char* c = nullptr; + if (IdeviceFfiError* e = rsd_get_uuid(handle_.get(), &c)) { + err = FfiError(e); + return std::nullopt; + } + std::string out; + if (c) { + out = c; + rsd_free_string(c); + } + return out; +} + +std::optional> RsdHandshake::services(FfiError& err) const { + CRsdServiceArray* arr = nullptr; + if (IdeviceFfiError* e = rsd_get_services(handle_.get(), &arr)) { + err = FfiError(e); + return std::nullopt; + } + return to_cpp_and_free(arr); +} + +std::optional RsdHandshake::service_available(const std::string& name, FfiError& err) const { + bool avail = false; + if (IdeviceFfiError* e = rsd_service_available(handle_.get(), name.c_str(), &avail)) { + err = FfiError(e); + return std::nullopt; + } + return avail; +} + +std::optional RsdHandshake::service_info(const std::string& name, FfiError& err) const { + CRsdService* svc = nullptr; + if (IdeviceFfiError* e = rsd_get_service_info(handle_.get(), name.c_str(), &svc)) { + err = FfiError(e); + return std::nullopt; + } + return to_cpp_and_free(svc); +} + +} // namespace IdeviceFFI