usbmuxd class implementation for usbmuxd

This commit is contained in:
Jackson Coxson
2025-07-22 10:49:14 -06:00
parent f384df91d8
commit 032a6a6751
8 changed files with 689 additions and 0 deletions

29
cpp/include/ffi.hpp Normal file
View File

@@ -0,0 +1,29 @@
// Jackson Coxson
#ifndef IDEVICE_FFI
#define IDEVICE_FFI
#include "idevice.hpp"
#include <string>
namespace IdeviceFFI {
struct FfiError {
int32_t code = 0;
std::string message;
static FfiError from(const IdeviceFfiError* err) {
FfiError out;
if (err) {
out.code = err->code;
out.message = err->message ? err->message : "";
idevice_error_free(const_cast<IdeviceFfiError*>(err));
}
return out;
}
static FfiError success() { return {0, ""}; }
explicit operator bool() const { return code != 0; }
};
} // namespace IdeviceFFI
#endif

80
cpp/include/idevice++.hpp Normal file
View File

@@ -0,0 +1,80 @@
// Jackson Coxson
#ifndef IDEVICE_CPP
#define IDEVICE_CPP
#include "ffi.hpp"
#include "pairing_file.hpp"
#include <optional>
#include <string>
namespace IdeviceFFI {
class Idevice {
public:
static std::optional<Idevice>
create(IdeviceSocketHandle* socket, const std::string& label, FfiError& err) {
IdeviceHandle* handle = nullptr;
IdeviceFfiError* e = idevice_new(socket, label.c_str(), &handle);
if (e) {
err = FfiError::from(e);
return std::nullopt;
}
return Idevice(handle);
}
static std::optional<Idevice>
create_tcp(const sockaddr* addr, socklen_t addr_len, const std::string& label, FfiError& err) {
IdeviceHandle* handle = nullptr;
IdeviceFfiError* e = idevice_new_tcp_socket(addr, addr_len, label.c_str(), &handle);
if (e) {
err = FfiError::from(e);
return std::nullopt;
}
return Idevice(handle);
}
std::optional<std::string> get_type(FfiError& err) {
char* type_cstr = nullptr;
IdeviceFfiError* e = idevice_get_type(handle_, &type_cstr);
if (e) {
err = FfiError::from(e);
return std::nullopt;
}
std::string type_str(type_cstr);
idevice_string_free(type_cstr);
return type_str;
}
bool rsd_checkin(FfiError& err) {
IdeviceFfiError* e = idevice_rsd_checkin(handle_);
if (e) {
err = FfiError::from(e);
return false;
}
return true;
}
bool start_session(PairingFile& pairing_file, FfiError& err) {
IdeviceFfiError* e = idevice_start_session(handle_, pairing_file.raw());
if (e) {
err = FfiError::from(e);
return false;
}
return true;
}
~Idevice() {
if (handle_)
idevice_free(handle_);
}
explicit Idevice(IdeviceHandle* h) : handle_(h) {}
private:
IdeviceHandle* handle_;
};
} // namespace IdeviceFFI
#endif

View File

@@ -0,0 +1,82 @@
// Jackson Coxson
#ifndef IDEVICE_PAIRING_FILE
#define IDEVICE_PAIRING_FILE
#pragma once
#include "ffi.hpp"
#include <optional>
#include <string>
#include <vector>
namespace IdeviceFFI {
class PairingFile {
public:
static std::optional<PairingFile> read(const std::string& path, FfiError& err) {
IdevicePairingFile* ptr = nullptr;
IdeviceFfiError* e = idevice_pairing_file_read(path.c_str(), &ptr);
if (e) {
err = FfiError::from(e);
return std::nullopt;
}
return PairingFile(ptr);
}
static std::optional<PairingFile> from_bytes(const uint8_t* data, size_t size, FfiError& err) {
IdevicePairingFile* raw = nullptr;
IdeviceFfiError* e = idevice_pairing_file_from_bytes(data, size, &raw);
if (e) {
err = FfiError::from(e);
return std::nullopt;
}
return PairingFile(raw);
}
~PairingFile() {
if (ptr_) {
idevice_pairing_file_free(ptr_);
}
}
// Deleted copy constructor and assignment — use unique ownersship
PairingFile(const PairingFile&) = delete;
PairingFile& operator=(const PairingFile&) = delete;
// Move constructor and assignment
PairingFile(PairingFile&& other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
PairingFile& operator=(PairingFile&& other) noexcept {
if (this != &other) {
if (ptr_) {
idevice_pairing_file_free(ptr_);
}
ptr_ = other.ptr_;
other.ptr_ = nullptr;
}
return *this;
}
std::optional<std::vector<uint8_t>> serialize(FfiError& err) const {
uint8_t* data = nullptr;
size_t size = 0;
IdeviceFfiError* e = idevice_pairing_file_serialize(ptr_, &data, &size);
if (e) {
err = FfiError::from(e);
return std::nullopt;
}
std::vector<uint8_t> result(data, data + size);
delete[] data; // NOTE: adjust this if deallocation uses `free` or a custom function
return result;
}
explicit PairingFile(IdevicePairingFile* ptr) : ptr_(ptr) {}
IdevicePairingFile* raw() const { return ptr_; }
private:
IdevicePairingFile* ptr_;
};
} // namespace IdeviceFFI
#endif

352
cpp/include/usbmuxd.hpp Normal file
View File

@@ -0,0 +1,352 @@
// Jackson Coxson
#ifndef IDEVICE_USBMUXD_HPP
#define IDEVICE_USBMUXD_HPP
#include "ffi.hpp"
#include "idevice++.hpp"
#include "pairing_file.hpp"
#include <cstdint>
#include <optional>
#include <string>
#include <sys/_types/_u_int8_t.h>
#include <vector>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#endif
namespace IdeviceFFI {
/// @brief A C++ wrapper for a UsbmuxdAddrHandle.
/// @details This class manages the memory of a UsbmuxdAddrHandle pointer.
/// It is non-copyable but is movable.
class UsbmuxdAddr {
public:
/// @brief Creates a new TCP usbmuxd address.
/// @param addr The socket address to connect to.
/// @param addr_len The length of the socket address.
/// @param err An error that will be populated on failure.
/// @return A UsbmuxdAddr on success, std::nullopt on failure.
static std::optional<UsbmuxdAddr>
tcp_new(const sockaddr* addr, socklen_t addr_len, FfiError& err) {
UsbmuxdAddrHandle* handle = nullptr;
IdeviceFfiError* e = idevice_usbmuxd_tcp_addr_new(addr, addr_len, &handle);
if (e) {
err = FfiError::from(e);
return std::nullopt;
}
return UsbmuxdAddr(handle);
}
#if defined(__unix__) || defined(__APPLE__)
/// @brief Creates a new Unix socket usbmuxd address.
/// @param path The path to the unix socket.
/// @param err An error that will be populated on failure.
/// @return A UsbmuxdAddr on success, std::nullopt on failure.
static std::optional<UsbmuxdAddr> unix_new(const std::string& path, FfiError& err) {
UsbmuxdAddrHandle* handle = nullptr;
IdeviceFfiError* e = idevice_usbmuxd_unix_addr_new(path.c_str(), &handle);
if (e) {
err = FfiError::from(e);
return std::nullopt;
}
return UsbmuxdAddr(handle);
}
#endif
~UsbmuxdAddr() {
if (handle_) {
idevice_usbmuxd_addr_free(handle_);
}
}
// Delete copy constructor and assignment operator
UsbmuxdAddr(const UsbmuxdAddr&) = delete;
UsbmuxdAddr& operator=(const UsbmuxdAddr&) = delete;
// Define move constructor and assignment operator
UsbmuxdAddr(UsbmuxdAddr&& other) noexcept : handle_(other.handle_) { other.handle_ = nullptr; }
UsbmuxdAddr& operator=(UsbmuxdAddr&& other) noexcept {
if (this != &other) {
if (handle_) {
idevice_usbmuxd_addr_free(handle_);
}
handle_ = other.handle_;
other.handle_ = nullptr;
}
return *this;
}
/// @brief Gets the raw handle.
/// @return The raw UsbmuxdAddrHandle pointer.
UsbmuxdAddrHandle* raw() const { return handle_; }
private:
explicit UsbmuxdAddr(UsbmuxdAddrHandle* handle) : handle_(handle) {}
UsbmuxdAddrHandle* handle_;
};
class UsbmuxdConnectionType {
public:
enum Value : uint8_t { Usb = 1, Network = 2, Unknown = 3 };
explicit UsbmuxdConnectionType(uint8_t v) : _value(static_cast<Value>(v)) {}
std::string to_string() {
switch (_value) {
case UsbmuxdConnectionType::Usb:
return "USB";
case UsbmuxdConnectionType::Network:
return "Network";
case UsbmuxdConnectionType::Unknown:
return "Unknown";
default:
return "UnknownEnumValue";
}
}
Value value() const { return _value; }
bool operator==(Value other) const { return _value == other; }
private:
Value _value;
};
class UsbmuxdDevice {
public:
~UsbmuxdDevice() {
if (handle_) {
idevice_usbmuxd_device_free(handle_);
}
}
// Delete copy constructor and assignment operator
UsbmuxdDevice(const UsbmuxdDevice&) = delete;
UsbmuxdDevice& operator=(const UsbmuxdDevice&) = delete;
// Define move constructor and assignment operator
UsbmuxdDevice(UsbmuxdDevice&& other) noexcept : handle_(other.handle_) {
other.handle_ = nullptr;
}
UsbmuxdDevice& operator=(UsbmuxdDevice&& other) noexcept {
if (this != &other) {
if (handle_) {
idevice_usbmuxd_device_free(handle_);
}
handle_ = other.handle_;
other.handle_ = nullptr;
}
return *this;
}
/// @brief Gets the raw handle.
/// @return The raw UsbmuxdConnectionHandle pointer.
UsbmuxdDeviceHandle* raw() const { return handle_; }
std::optional<std::string> get_udid() {
char* udid = idevice_usbmuxd_device_get_udid(handle_);
if (udid) {
std::string cppUdid(udid);
idevice_string_free(udid);
return cppUdid;
} else {
return std::nullopt;
}
}
std::optional<uint32_t> get_id() {
uint32_t id = idevice_usbmuxd_device_get_device_id(handle_);
if (id == 0) {
return std::nullopt;
} else {
return id;
}
}
std::optional<UsbmuxdConnectionType> get_connection_type() {
u_int8_t t = idevice_usbmuxd_device_get_connection_type(handle_);
if (t == 0) {
return std::nullopt;
} else {
}
return static_cast<UsbmuxdConnectionType>(t);
}
explicit UsbmuxdDevice(UsbmuxdDeviceHandle* handle) : handle_(handle) {}
private:
UsbmuxdDeviceHandle* handle_;
};
/// @brief A C++ wrapper for a UsbmuxdConnectionHandle.
/// @details This class manages the memory of a UsbmuxdConnectionHandle pointer.
/// It is non-copyable but is movable.
class UsbmuxdConnection {
public:
/// @brief Creates a new TCP usbmuxd connection.
/// @param addr The socket address to connect to.
/// @param addr_len The length of the socket address.
/// @param tag A tag that will be returned by usbmuxd responses.
/// @param err An error that will be populated on failure.
/// @return A UsbmuxdConnection on success, std::nullopt on failure.
static std::optional<UsbmuxdConnection>
tcp_new(const sockaddr* addr, socklen_t addr_len, uint32_t tag, FfiError& err) {
UsbmuxdConnectionHandle* handle = nullptr;
IdeviceFfiError* e = idevice_usbmuxd_new_tcp_connection(addr, addr_len, tag, &handle);
if (e) {
err = FfiError::from(e);
return std::nullopt;
}
return UsbmuxdConnection(handle);
}
#if defined(__unix__) || defined(__APPLE__)
/// @brief Creates a new Unix socket usbmuxd connection.
/// @param path The path to the unix socket.
/// @param tag A tag that will be returned by usbmuxd responses.
/// @param err An error that will be populated on failure.
/// @return A UsbmuxdConnection on success, std::nullopt on failure.
static std::optional<UsbmuxdConnection>
unix_new(const std::string& path, uint32_t tag, FfiError& err) {
UsbmuxdConnectionHandle* handle = nullptr;
IdeviceFfiError* e = idevice_usbmuxd_new_unix_socket_connection(path.c_str(), tag, &handle);
if (e) {
err = FfiError::from(e);
return std::nullopt;
}
return UsbmuxdConnection(handle);
}
#endif
/// @brief Creates a new usbmuxd connection using the default path.
/// @param tag A tag that will be returned by usbmuxd responses.
/// @param err An error that will be populated on failure.
/// @return A UsbmuxdConnection on success, std::nullopt on failure.
static std::optional<UsbmuxdConnection> default_new(uint32_t tag, FfiError& err) {
UsbmuxdConnectionHandle* handle = nullptr;
IdeviceFfiError* e = idevice_usbmuxd_new_default_connection(tag, &handle);
if (e) {
err = FfiError::from(e);
return std::nullopt;
}
return UsbmuxdConnection(handle);
}
/// @brief Gets a list of all connected devices.
/// @param err An error that will be populated on failure.
/// @return A vector of UsbmuxdDevice objects on success, std::nullopt on failure.
std::optional<std::vector<UsbmuxdDevice>> get_devices(FfiError& err) {
UsbmuxdDeviceHandle** devices = nullptr;
int count = 0;
IdeviceFfiError* e = idevice_usbmuxd_get_devices(handle_, &devices, &count);
if (e) {
err = FfiError::from(e);
return std::nullopt;
}
std::vector<UsbmuxdDevice> result;
result.reserve(count);
for (int i = 0; i < count; ++i) {
result.emplace_back(devices[i]);
}
return result;
}
/// @brief Gets the BUID from the daemon.
/// @param err An error that will be populated on failure.
/// @return The BUID string on success, std::nullopt on failure.
std::optional<std::string> get_buid(FfiError& err) {
char* buid_c = nullptr;
IdeviceFfiError* e = idevice_usbmuxd_get_buid(handle_, &buid_c);
if (e) {
err = FfiError::from(e);
return std::nullopt;
}
std::string buid(buid_c);
idevice_string_free(buid_c);
return buid;
}
/// @brief Gets the pairing record for a device.
/// @param udid The UDID of the target device.
/// @param err An error that will be populated on failure.
/// @return A PairingFile object on success, std::nullopt on failure.
std::optional<PairingFile> get_pair_record(const std::string& udid, FfiError& err) {
IdevicePairingFile* pf_handle = nullptr;
IdeviceFfiError* e = idevice_usbmuxd_get_pair_record(handle_, udid.c_str(), &pf_handle);
if (e) {
err = FfiError::from(e);
return std::nullopt;
}
return PairingFile(pf_handle);
}
/// @brief Connects to a port on a given device.
/// @details This operation consumes the UsbmuxdConnection object. After a successful call,
/// this object will be invalid and should not be used.
/// @param device_id The ID of the target device.
/// @param port The port number to connect to.
/// @param err An error that will be populated on failure.
/// @return An Idevice connection object on success, std::nullopt on failure.
std::optional<Idevice>
connect_to_device(uint32_t device_id, uint16_t port, const std::string& path, FfiError& err) {
if (!handle_) {
// Can't connect with an invalid handle
return std::nullopt;
}
IdeviceHandle* idevice_handle = nullptr;
IdeviceFfiError* e = idevice_usbmuxd_connect_to_device(
handle_, device_id, port, path.c_str(), &idevice_handle);
// The handle is always consumed by the FFI call, so we must invalidate it.
handle_ = nullptr;
if (e) {
err = FfiError::from(e);
return std::nullopt;
}
return Idevice(idevice_handle);
}
~UsbmuxdConnection() {
if (handle_) {
idevice_usbmuxd_connection_free(handle_);
}
}
// Delete copy constructor and assignment operator
UsbmuxdConnection(const UsbmuxdConnection&) = delete;
UsbmuxdConnection& operator=(const UsbmuxdConnection&) = delete;
// Define move constructor and assignment operator
UsbmuxdConnection(UsbmuxdConnection&& other) noexcept : handle_(other.handle_) {
other.handle_ = nullptr;
}
UsbmuxdConnection& operator=(UsbmuxdConnection&& other) noexcept {
if (this != &other) {
if (handle_) {
idevice_usbmuxd_connection_free(handle_);
}
handle_ = other.handle_;
other.handle_ = nullptr;
}
return *this;
}
/// @brief Gets the raw handle.
/// @return The raw UsbmuxdConnectionHandle pointer.
UsbmuxdConnectionHandle* raw() const { return handle_; }
private:
explicit UsbmuxdConnection(UsbmuxdConnectionHandle* handle) : handle_(handle) {}
UsbmuxdConnectionHandle* handle_;
};
} // namespace IdeviceFFI
#endif