Replace lockdown code with rppairing (#354)

This commit is contained in:
Jackson Coxson 2026-03-27 16:44:13 -06:00 • committed by GitHub
parent f8697a0b83
commit c780843105
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 1706 additions and 568 deletions

View file

@ -5,7 +5,6 @@
#include "idevice/JITEnableContext.h"
#import "Utilities/ProcessInspectorBridge.h"
#include "idevice/idevice.h"
#include "idevice/heartbeat.h"
#include "JSSupport/JSSupport.h"
#include "idevice/ideviceinfo.h"
#include "idevice/location_simulation.h"

View file

@ -117,17 +117,17 @@ class DNSChecker: ObservableObject {
// MARK: - Main App
// Global state variable for the heartbeat response.
var pubHeartBeat = false
private var heartbeatStartPending = false
private var heartbeatStartInProgress = false
private var heartbeatPendingShowUI = true
// Global state variable for the tunnel connection.
var pubTunnelConnected = false
private var tunnelStartPending = false
private var tunnelStartInProgress = false
private var tunnelPendingShowUI = true
@main
struct HeartbeatApp: App {
@StateObject private var mount = MountingProgress.shared
@Environment(\.scenePhase) private var scenePhase // Observe scene lifecycle
@State private var shouldAttemptHeartbeatRestart = false
@State private var shouldAttemptTunnelReconnect = false
init() {
registerAdvancedOptionsDefault()
@ -144,11 +144,11 @@ struct HeartbeatApp: App {
private func handleScenePhaseChange(_ newPhase: ScenePhase) {
switch newPhase {
case .background:
shouldAttemptHeartbeatRestart = true
shouldAttemptTunnelReconnect = true
case .active:
if shouldAttemptHeartbeatRestart {
shouldAttemptHeartbeatRestart = false
startHeartbeatInBackground(showErrorUI: false)
if shouldAttemptTunnelReconnect {
shouldAttemptTunnelReconnect = false
startTunnelInBackground(showErrorUI: false)
}
default:
break
@ -270,42 +270,42 @@ class MountingProgress: ObservableObject {
func isPairing() -> Bool {
let pairingpath = URL.documentsDirectory.appendingPathComponent("pairingFile.plist").path
var pairingFile: IdevicePairingFile?
let err = idevice_pairing_file_read(pairingpath, &pairingFile)
var pairingFile: RpPairingFileHandle?
let err = rp_pairing_file_read(pairingpath, &pairingFile)
if err != nil { return false }
idevice_pairing_file_free(pairingFile)
rp_pairing_file_free(pairingFile)
return true
}
func startHeartbeatInBackground(showErrorUI: Bool = true) {
assert(Thread.isMainThread, "startHeartbeatInBackground must be called on the main thread")
func startTunnelInBackground(showErrorUI: Bool = true) {
assert(Thread.isMainThread, "startTunnelInBackground must be called on the main thread")
let pairingFileURL = URL.documentsDirectory.appendingPathComponent("pairingFile.plist")
guard FileManager.default.fileExists(atPath: pairingFileURL.path) else {
heartbeatStartPending = false
heartbeatPendingShowUI = true
tunnelStartPending = false
tunnelPendingShowUI = true
return
}
guard !heartbeatStartInProgress else {
guard !tunnelStartInProgress else {
return
}
heartbeatStartPending = false
heartbeatPendingShowUI = true
heartbeatStartInProgress = true
tunnelStartPending = false
tunnelPendingShowUI = true
tunnelStartInProgress = true
DispatchQueue.global(qos: .userInteractive).async {
defer {
DispatchQueue.main.async {
heartbeatStartInProgress = false
tunnelStartInProgress = false
}
}
do {
try JITEnableContext.shared.startHeartbeat()
LogManager.shared.addInfoLog("Heartbeat started successfully")
pubHeartBeat = true
try JITEnableContext.shared.startTunnel()
LogManager.shared.addInfoLog("Tunnel connected successfully")
pubTunnelConnected = true
DispatchQueue.main.async {
let trustcachePath = URL.documentsDirectory.appendingPathComponent("DDI/Image.dmg.trustcache").path
guard FileManager.default.fileExists(atPath: trustcachePath),
@ -326,7 +326,7 @@ func startHeartbeatInBackground(showErrorUI: Bool = true) {
} catch {
LogManager.shared.addErrorLog("Failed to remove invalid pairing file: \(error.localizedDescription)")
}
showAlert(
title: "Invalid Pairing File",
message: "The pairing file is invalid or expired. Please select a new pairing file.",
@ -338,14 +338,14 @@ func startHeartbeatInBackground(showErrorUI: Bool = true) {
}
} else {
showAlert(
title: "Heartbeat Error",
title: "Connection Error",
message: "\(error.localizedDescription)\n\nMake sure Wi‑Fi and LocalDevVPN are connected and that the device is reachable.",
showOk: false,
showTryAgain: true
) { shouldTryAgain in
if shouldTryAgain {
DispatchQueue.main.async {
startHeartbeatInBackground()
startTunnelInBackground()
}
}
}

View file

@ -30,7 +30,7 @@ final class DeviceInfoManager: ObservableObject {
busy = true
Task.detached {
do {
try JITEnableContext.shared.ensureHeartbeat()
try JITEnableContext.shared.ensureTunnel()
} catch {
await MainActor.run {
self.error = ("Initialization Failed", error.localizedDescription)

View file

@ -38,7 +38,7 @@ struct InstalledAppQuery: EntityStringQuery {
}
func suggestedEntities() async throws -> [InstalledAppEntity] {
await ensureHeartbeat()
await ensureTunnel()
let allApps = (try? JITEnableContext.shared.getAppList()) ?? [:]
return allApps.map { InstalledAppEntity(id: $0.key, displayName: $0.value) }
.sorted { $0.displayName.localizedCaseInsensitiveCompare($1.displayName) == .orderedAscending }
@ -94,7 +94,7 @@ struct RunningProcessEntity: AppEntity {
struct RunningProcessQuery: EntityStringQuery {
func entities(for identifiers: [String]) async throws -> [RunningProcessEntity] {
// Always fetch fresh so PIDs are current
await ensureHeartbeat()
await ensureTunnel()
let all = try fetchProcessEntities()
let idSet = Set(identifiers)
return all.filter { idSet.contains($0.id) }
@ -112,7 +112,7 @@ struct RunningProcessQuery: EntityStringQuery {
}
func suggestedEntities() async throws -> [RunningProcessEntity] {
await ensureHeartbeat()
await ensureTunnel()
return try fetchProcessEntities()
}
@ -172,7 +172,7 @@ struct EnableJITIntent: AppIntent, ForegroundContinuableIntent {
return .result(value: "Select an app to enable JIT for.")
}
await ensureHeartbeat()
await ensureTunnel()
var scriptData: Data? = nil
var scriptName: String? = nil
@ -250,9 +250,9 @@ struct KillProcessIntent: AppIntent {
if let pid {
targetPID = pid
targetName = "PID \(pid)"
await ensureHeartbeat()
await ensureTunnel()
} else if let process {
await ensureHeartbeat()
await ensureTunnel()
// Always re-resolve to get the current PID — the stored one may be stale
guard let resolved = process.resolveCurrentPID() else {
@ -313,12 +313,12 @@ struct StikDebugShortcuts: AppShortcutsProvider {
}
}
// MARK: - Shared Heartbeat Helper
// MARK: - Shared Tunnel Helper
func ensureHeartbeat() async {
func ensureTunnel() async {
await MainActor.run {
pubHeartBeat = false
startHeartbeatInBackground(showErrorUI: false)
pubTunnelConnected = false
startTunnelInBackground(showErrorUI: false)
}
try? await Task.sleep(nanoseconds: 1_000_000_000)
}

View file

@ -7,10 +7,9 @@
import Foundation
typealias IdevicePairingFile = OpaquePointer
typealias TcpProviderHandle = OpaquePointer
typealias CoreDeviceProxyHandle = OpaquePointer
typealias RpPairingFileHandle = OpaquePointer
typealias AdapterHandle = OpaquePointer
typealias RsdHandshakeHandle = OpaquePointer
typealias ImageMounterHandle = OpaquePointer
typealias LockdowndClientHandle = OpaquePointer

View file

@ -40,7 +40,7 @@ struct HomeView: View {
startJITInBackground(bundleID: selectedBundle)
}, showDoneButton: false, onImportPairingFile: { isShowingPairingFilePicker = true })
.onAppear {
startHeartbeatInBackground()
startTunnelInBackground()
MountingProgress.shared.checkforMounted()
viewDidAppeared = true
if let config = pendingJITEnableConfiguration {
@ -94,8 +94,8 @@ struct HomeView: View {
}
case "kill-process":
if let pidStr = components?.queryItems?.first(where: { $0.name == "pid" })?.value, let pid = Int(pidStr) {
pubHeartBeat = false
startHeartbeatInBackground(showErrorUI: false)
pubTunnelConnected = false
startTunnelInBackground(showErrorUI: false)
DispatchQueue.global(qos: .userInitiated).async {
sleep(1)
do {
@ -133,10 +133,10 @@ struct HomeView: View {
try fileManager.removeItem(at: dest)
}
try fileManager.copyItem(at: url, to: dest)
pubHeartBeat = false
startHeartbeatInBackground()
pubTunnelConnected = false
startTunnelInBackground()
NotificationCenter.default.post(name: .pairingFileImported, object: nil)
// Dismiss any existing heartbeat error alert
// Dismiss any existing connection error alert
if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let root = scene.windows.first?.rootViewController {
var top = root
@ -268,8 +268,8 @@ struct HomeView: View {
BackgroundLocationManager.shared.requestStart()
if triggeredByURLScheme {
pubHeartBeat = false
startHeartbeatInBackground(showErrorUI: false)
pubTunnelConnected = false
startTunnelInBackground(showErrorUI: false)
}
DispatchQueue.global(qos: .background).async {

View file

@ -224,7 +224,7 @@ struct SettingsView: View {
RunLoop.current.add(progressTimer, forMode: .common)
DispatchQueue.main.async {
startHeartbeatInBackground()
startTunnelInBackground()
}
} catch { }

View file

@ -8,35 +8,35 @@
@import UIKit;
#include "idevice.h"
#include "jit.h"
#include "heartbeat.h"
#include "mount.h"
typedef void (^HeartbeatCompletionHandler)(int result, NSString *message);
typedef void (^LogFuncC)(const char* message, ...);
typedef void (^LogFunc)(NSString *message);
typedef void (^SyslogLineHandler)(NSString *line);
typedef void (^SyslogErrorHandler)(NSError *error);
@interface JITEnableContext : NSObject {
// tunnel
@protected AdapterHandle *adapter;
@protected RsdHandshakeHandle *handshake;
// process
@protected dispatch_queue_t processInspectorQueue;
@protected IdeviceProviderHandle* provider;
// syslog
@protected dispatch_queue_t syslogQueue;
@protected BOOL syslogStreaming;
@protected SyslogRelayClientHandle *syslogClient;
@protected SyslogLineHandler syslogLineHandler;
@protected SyslogErrorHandler syslogErrorHandler;
// ideviceInfo
@protected LockdowndClientHandle * g_client;
}
@property (class, readonly)JITEnableContext* shared;
- (IdevicePairingFile*)getPairingFileWithError:(NSError**)error;
- (IdeviceProviderHandle*)getTcpProviderHandle;
- (BOOL)ensureHeartbeatWithError:(NSError**)err;
- (BOOL)startHeartbeat:(NSError**)err;
- (RpPairingFileHandle*)getPairingFileWithError:(NSError**)error;
- (BOOL)ensureTunnelWithError:(NSError**)err;
- (BOOL)startTunnel:(NSError**)err;
@end

View file

@ -9,7 +9,6 @@
#include <signal.h>
#include <stdlib.h>
#include "heartbeat.h"
#include "jit.h"
#include "applist.h"
#include "profiles.h"
@ -21,13 +20,11 @@
static JITEnableContext* sharedJITContext = nil;
@implementation JITEnableContext {
int heartbeatToken;
NSError* lastHeartbeatError;
os_unfair_lock heartbeatLock;
BOOL heartbeatRunning;
dispatch_semaphore_t heartbeatSemaphore;
@implementation JITEnableContext {
NSError* lastTunnelError;
os_unfair_lock tunnelLock;
BOOL tunnelConnecting;
dispatch_semaphore_t tunnelSemaphore;
}
+ (instancetype)shared {
@ -48,12 +45,11 @@ static JITEnableContext* sharedJITContext = nil;
syslogClient = NULL;
dispatch_queue_attr_t qosAttr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);
processInspectorQueue = dispatch_queue_create("com.stikdebug.processInspector", qosAttr);
heartbeatToken = 0;
heartbeatLock = OS_UNFAIR_LOCK_INIT;
heartbeatRunning = NO;
heartbeatSemaphore = NULL;
lastHeartbeatError = nil;
tunnelLock = OS_UNFAIR_LOCK_INIT;
tunnelConnecting = NO;
tunnelSemaphore = NULL;
lastTunnelError = nil;
return self;
}
@ -88,7 +84,7 @@ static JITEnableContext* sharedJITContext = nil;
};
}
- (IdevicePairingFile*)getPairingFileWithError:(NSError**)error {
- (RpPairingFileHandle*)getPairingFileWithError:(NSError**)error {
NSFileManager* fm = [NSFileManager defaultManager];
NSURL* docPathUrl = [fm URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject;
NSURL* pairingFileURL = [docPathUrl URLByAppendingPathComponent:@"pairingFile.plist"];
@ -98,112 +94,108 @@ static JITEnableContext* sharedJITContext = nil;
return nil;
}
IdevicePairingFile* pairingFile = NULL;
IdeviceFfiError* err = idevice_pairing_file_read(pairingFileURL.fileSystemRepresentation, &pairingFile);
RpPairingFileHandle* pairingFile = NULL;
IdeviceFfiError* err = rp_pairing_file_read(pairingFileURL.fileSystemRepresentation, &pairingFile);
if (err) {
*error = [self errorWithStr:@"Failed to read pairing file!" code:err->code];
idevice_error_free(err);
return nil;
}
return pairingFile;
}
- (IdeviceProviderHandle*)getTcpProviderHandle {
return provider;
}
- (BOOL)startTunnel:(NSError**)err {
os_unfair_lock_lock(&tunnelLock);
// If tunnel is already being created, wait for it to complete
if (tunnelConnecting) {
dispatch_semaphore_t waitSemaphore = tunnelSemaphore;
os_unfair_lock_unlock(&tunnelLock);
// only block until first heartbeat is completed or failed.
- (BOOL)startHeartbeat:(NSError**)err {
os_unfair_lock_lock(&heartbeatLock);
// If heartbeat is already running, wait for it to complete
if (heartbeatRunning) {
dispatch_semaphore_t waitSemaphore = heartbeatSemaphore;
os_unfair_lock_unlock(&heartbeatLock);
if (waitSemaphore) {
dispatch_semaphore_wait(waitSemaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(waitSemaphore);
}
*err = lastHeartbeatError;
*err = lastTunnelError;
return *err == nil;
}
// Mark heartbeat as running
heartbeatRunning = YES;
heartbeatSemaphore = dispatch_semaphore_create(0);
dispatch_semaphore_t completionSemaphore = heartbeatSemaphore;
os_unfair_lock_unlock(&heartbeatLock);
IdevicePairingFile* pairingFile = [self getPairingFileWithError:err];
// Mark tunnel as connecting
tunnelConnecting = YES;
tunnelSemaphore = dispatch_semaphore_create(0);
dispatch_semaphore_t completionSemaphore = tunnelSemaphore;
os_unfair_lock_unlock(&tunnelLock);
RpPairingFileHandle* pairingFile = [self getPairingFileWithError:err];
if (*err) {
os_unfair_lock_lock(&heartbeatLock);
heartbeatRunning = NO;
heartbeatSemaphore = NULL;
os_unfair_lock_unlock(&heartbeatLock);
os_unfair_lock_lock(&tunnelLock);
tunnelConnecting = NO;
tunnelSemaphore = NULL;
os_unfair_lock_unlock(&tunnelLock);
dispatch_semaphore_signal(completionSemaphore);
return NO;
}
globalHeartbeatToken++;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block bool completionCalled = false;
HeartbeatCompletionHandlerC Ccompletion = ^(int result, const char *message) {
if(completionCalled) {
return;
}
if (result != 0) {
*err = [self errorWithStr:[NSString stringWithCString:message
encoding:NSASCIIStringEncoding] code:result];
self->lastHeartbeatError = *err;
} else {
self->lastHeartbeatError = nil;
}
completionCalled = true;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(49152);
dispatch_semaphore_signal(semaphore);
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
startHeartbeat(
pairingFile,
&self->provider,
globalHeartbeatToken,Ccompletion
);
});
// allow 5 seconds for heartbeat, otherwise we declare timeout
intptr_t isTimeout = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, (uint64_t)(5 * NSEC_PER_SEC)));
if(isTimeout) {
Ccompletion(-1, "Heartbeat failed to complete in reasonable time.");
NSString* deviceIP = [[NSUserDefaults standardUserDefaults] stringForKey:@"customTargetIP"];
inet_pton(AF_INET, (deviceIP && deviceIP.length > 0) ? [deviceIP UTF8String] : "10.7.0.1", &addr.sin_addr);
AdapterHandle *newAdapter = NULL;
RsdHandshakeHandle *newHandshake = NULL;
IdeviceFfiError *ffiErr = tunnel_create_rppairing(
(const idevice_sockaddr *)&addr,
sizeof(addr),
"StikDebug",
pairingFile,
NULL,
NULL,
&newAdapter,
&newHandshake
);
rp_pairing_file_free(pairingFile);
if (ffiErr) {
*err = [self errorWithStr:[NSString stringWithUTF8String:ffiErr->message ?: "Failed to create tunnel"]
code:ffiErr->code];
lastTunnelError = *err;
idevice_error_free(ffiErr);
} else {
// Clean up old tunnel if any
if (handshake) { rsd_handshake_free(handshake); }
if (adapter) { adapter_free(adapter); }
adapter = newAdapter;
handshake = newHandshake;
lastTunnelError = nil;
}
os_unfair_lock_lock(&heartbeatLock);
heartbeatRunning = NO;
heartbeatSemaphore = NULL;
os_unfair_lock_unlock(&heartbeatLock);
os_unfair_lock_lock(&tunnelLock);
tunnelConnecting = NO;
tunnelSemaphore = NULL;
os_unfair_lock_unlock(&tunnelLock);
dispatch_semaphore_signal(completionSemaphore);
return *err == nil;
}
- (BOOL)ensureHeartbeatWithError:(NSError**)err {
// if it's 15s after last heartbeat, we restart heartbeat.
if((!lastHeartbeatDate || [[NSDate now] timeIntervalSinceDate:lastHeartbeatDate] > 15)) {
return [self startHeartbeat:err];
- (BOOL)ensureTunnelWithError:(NSError**)err {
if (!adapter || !handshake) {
return [self startTunnel:err];
}
return YES;
}
- (void)dealloc {
[self stopSyslogRelay];
if (provider) {
idevice_provider_free(provider);
if (handshake) {
rsd_handshake_free(handshake);
}
if (adapter) {
adapter_free(adapter);
}
}
@end

View file

@ -10,9 +10,9 @@
@import Foundation;
@import UIKit;
NSDictionary<NSString*, NSString*>* list_installed_apps(IdeviceProviderHandle* provider, NSString** error);
NSDictionary<NSString*, NSString*>* list_all_apps(IdeviceProviderHandle* provider, NSString** error);
NSDictionary<NSString*, NSString*>* list_hidden_system_apps(IdeviceProviderHandle* provider, NSString** error);
UIImage* getAppIcon(IdeviceProviderHandle* provider, NSString* bundleID, NSString** error);
NSDictionary<NSString*, NSString*>* list_installed_apps(AdapterHandle* adapter, RsdHandshakeHandle* handshake, NSString** error);
NSDictionary<NSString*, NSString*>* list_all_apps(AdapterHandle* adapter, RsdHandshakeHandle* handshake, NSString** error);
NSDictionary<NSString*, NSString*>* list_hidden_system_apps(AdapterHandle* adapter, RsdHandshakeHandle* handshake, NSString** error);
UIImage* getAppIcon(AdapterHandle* adapter, RsdHandshakeHandle* handshake, NSString* bundleID, NSString** error);
#endif /* APPLIST_H */

View file

@ -149,10 +149,11 @@ static NSDictionary<NSString*, NSString*> *buildAppDictionary(void *apps,
return result;
}
static NSArray<NSDictionary*>* getSideloadedApps(IdeviceProviderHandle *provider,
static NSArray<NSDictionary*>* getSideloadedApps(AdapterHandle *adapter,
RsdHandshakeHandle *handshake,
NSString **error) {
InstallationProxyClientHandle *client = NULL;
IdeviceFfiError* err = installation_proxy_connect(provider, &client);
IdeviceFfiError* err = installation_proxy_connect_rsd(adapter, handshake, &client);
if (err) {
*error = [NSString stringWithFormat:@"Failed to connect to installation proxy: %s", err->message];
idevice_error_free(err);
@ -208,13 +209,14 @@ static NSArray<NSDictionary*>* getSideloadedApps(IdeviceProviderHandle *provider
return result;
}
static NSDictionary<NSString*, NSString*> *performAppQuery(IdeviceProviderHandle *provider,
static NSDictionary<NSString*, NSString*> *performAppQuery(AdapterHandle *adapter,
RsdHandshakeHandle *handshake,
BOOL requireGetTaskAllow,
NSString **error,
BOOL (^filter)(plist_t app))
{
InstallationProxyClientHandle *client = NULL;
IdeviceFfiError* err = installation_proxy_connect(provider, &client);
IdeviceFfiError* err = installation_proxy_connect_rsd(adapter, handshake, &client);
if (err) {
*error = [NSString stringWithFormat:@"Failed to connect to installation proxy: %s", err->message];
idevice_error_free(err);
@ -240,23 +242,23 @@ static NSDictionary<NSString*, NSString*> *performAppQuery(IdeviceProviderHandle
return result;
}
NSDictionary<NSString*, NSString*>* list_installed_apps(IdeviceProviderHandle* provider, NSString** error) {
return performAppQuery(provider, YES, error, nil);
NSDictionary<NSString*, NSString*>* list_installed_apps(AdapterHandle* adapter, RsdHandshakeHandle* handshake, NSString** error) {
return performAppQuery(adapter, handshake, YES, error, nil);
}
NSDictionary<NSString*, NSString*>* list_all_apps(IdeviceProviderHandle* provider, NSString** error) {
return performAppQuery(provider, NO, error, nil);
NSDictionary<NSString*, NSString*>* list_all_apps(AdapterHandle* adapter, RsdHandshakeHandle* handshake, NSString** error) {
return performAppQuery(adapter, handshake, NO, error, nil);
}
NSDictionary<NSString*, NSString*>* list_hidden_system_apps(IdeviceProviderHandle* provider, NSString** error) {
return performAppQuery(provider, NO, error, ^BOOL(plist_t app) {
NSDictionary<NSString*, NSString*>* list_hidden_system_apps(AdapterHandle* adapter, RsdHandshakeHandle* handshake, NSString** error) {
return performAppQuery(adapter, handshake, NO, error, ^BOOL(plist_t app) {
return isHiddenSystemApp(app);
});
}
UIImage* getAppIcon(IdeviceProviderHandle* provider, NSString* bundleID, NSString** error) {
UIImage* getAppIcon(AdapterHandle* adapter, RsdHandshakeHandle* handshake, NSString* bundleID, NSString** error) {
SpringBoardServicesClientHandle *client = NULL;
IdeviceFfiError *err = springboard_services_connect(provider, &client);
IdeviceFfiError *err = springboard_services_connect_rsd(adapter, handshake, &client);
if (err) {
*error = [NSString stringWithUTF8String:err->message ?: "Failed to connect to SpringBoard Services"];
idevice_error_free(err);
@ -284,13 +286,13 @@ UIImage* getAppIcon(IdeviceProviderHandle* provider, NSString* bundleID, NSStrin
@implementation JITEnableContext(App)
- (NSDictionary<NSString*, NSString*>*)getAppListWithError:(NSError**)error {
[self ensureHeartbeatWithError:error];
[self ensureTunnelWithError:error];
if(*error) {
return nil;
}
NSString* errorStr = nil;
NSDictionary<NSString*, NSString*>* apps = list_installed_apps(provider, &errorStr);
NSDictionary<NSString*, NSString*>* apps = list_installed_apps(adapter, handshake, &errorStr);
if (errorStr) {
*error = [self errorWithStr:errorStr code:-17];
return nil;
@ -299,13 +301,13 @@ UIImage* getAppIcon(IdeviceProviderHandle* provider, NSString* bundleID, NSStrin
}
- (NSDictionary<NSString*, NSString*>*)getAllAppsWithError:(NSError**)error {
[self ensureHeartbeatWithError:error];
[self ensureTunnelWithError:error];
if(*error) {
return nil;
}
NSString* errorStr = nil;
NSDictionary<NSString*, NSString*>* apps = list_all_apps(provider, &errorStr);
NSDictionary<NSString*, NSString*>* apps = list_all_apps(adapter, handshake, &errorStr);
if (errorStr) {
*error = [self errorWithStr:errorStr code:-17];
return nil;
@ -314,13 +316,13 @@ UIImage* getAppIcon(IdeviceProviderHandle* provider, NSString* bundleID, NSStrin
}
- (NSDictionary<NSString*, NSString*>*)getHiddenSystemAppsWithError:(NSError**)error {
[self ensureHeartbeatWithError:error];
[self ensureTunnelWithError:error];
if(*error) {
return nil;
}
NSString* errorStr = nil;
NSDictionary<NSString*, NSString*>* apps = list_hidden_system_apps(provider, &errorStr);
NSDictionary<NSString*, NSString*>* apps = list_hidden_system_apps(adapter, handshake, &errorStr);
if (errorStr) {
*error = [self errorWithStr:errorStr code:-17];
return nil;
@ -329,13 +331,13 @@ UIImage* getAppIcon(IdeviceProviderHandle* provider, NSString* bundleID, NSStrin
}
- (NSArray<NSDictionary*>*)getSideloadedAppsWithError:(NSError**)error {
[self ensureHeartbeatWithError:error];
[self ensureTunnelWithError:error];
if(*error) {
return nil;
}
NSString* errorStr = nil;
NSArray<NSDictionary*>* apps = getSideloadedApps(provider, &errorStr);
NSArray<NSDictionary*>* apps = getSideloadedApps(adapter, handshake, &errorStr);
if (errorStr) {
*error = [self errorWithStr:errorStr code:-17];
return nil;
@ -344,13 +346,13 @@ UIImage* getAppIcon(IdeviceProviderHandle* provider, NSString* bundleID, NSStrin
}
- (UIImage*)getAppIconWithBundleId:(NSString*)bundleId error:(NSError**)error {
[self ensureHeartbeatWithError:error];
[self ensureTunnelWithError:error];
if(*error) {
return nil;
}
NSString* errorStr = nil;
UIImage* icon = getAppIcon(provider, bundleId, &errorStr);
UIImage* icon = getAppIcon(adapter, handshake, bundleId, &errorStr);
if (errorStr) {
*error = [self errorWithStr:errorStr code:-17];
return nil;

View file

@ -1,21 +0,0 @@
//
// heartbeat.h
// StikJIT
//
// Created by Stephen on 3/27/25.
//
// heartbeat.h
#ifndef HEARTBEAT_H
#define HEARTBEAT_H
#include "idevice.h"
@import Foundation;
typedef void (^HeartbeatCompletionHandlerC)(int result, const char *message);
typedef void (^LogFuncC)(const char* message, ...);
extern int globalHeartbeatToken;
extern NSDate* lastHeartbeatDate;
void startHeartbeat(IdevicePairingFile* pairing_file, IdeviceProviderHandle** provider, int heartbeatToken, HeartbeatCompletionHandlerC completion);
#endif /* HEARTBEAT_H */

View file

@ -1,95 +0,0 @@
// Jackson Coxson
// heartbeat.c
#include "idevice.h"
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/_types/_u_int64_t.h>
#include <CoreFoundation/CoreFoundation.h>
#include <limits.h>
#include "heartbeat.h"
#include <pthread.h>
@import Foundation;
int globalHeartbeatToken = 0;
NSDate* lastHeartbeatDate = nil;
void startHeartbeat(IdevicePairingFile* pairing_file, IdeviceProviderHandle** provider, int heartbeatToken, HeartbeatCompletionHandlerC completion) {
IdeviceProviderHandle* newProvider = *provider;
IdeviceFfiError* err = nil;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(LOCKDOWN_PORT);
NSString* deviceIP = [[NSUserDefaults standardUserDefaults] stringForKey:@"customTargetIP"];
inet_pton(AF_INET, (deviceIP && deviceIP.length > 0) ? [deviceIP UTF8String] : "10.7.0.1", &addr.sin_addr);
err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file,
"ExampleProvider", &newProvider);
if (err != NULL) {
completion(err->code, err->message);
idevice_pairing_file_free(pairing_file);
idevice_error_free(err);
return;
}
HeartbeatClientHandle *client = NULL;
err = heartbeat_connect(newProvider, &client);
if (err != NULL) {
completion(err->code, err->message);
idevice_provider_free(newProvider);
idevice_error_free(err);
return;
}
*provider = newProvider;
bool completionCalled = false;
u_int64_t current_interval = 15;
while (1) {
u_int64_t new_interval = 0;
err = heartbeat_get_marco(client, current_interval, &new_interval);
if (err != NULL) {
if (!completionCalled) {
completion(err->code, err->message);
}
heartbeat_client_free(client);
idevice_error_free(err);
return;
}
// If a newer heartbeat thread has started, yield to it
if (heartbeatToken != globalHeartbeatToken) {
heartbeat_client_free(client);
return;
}
current_interval = new_interval + 5;
err = heartbeat_send_polo(client);
if (err != NULL) {
if (!completionCalled) {
completion(err->code, err->message);
}
heartbeat_client_free(client);
idevice_error_free(err);
return;
}
if (lastHeartbeatDate && [[NSDate now] timeIntervalSinceDate:lastHeartbeatDate] > current_interval) {
lastHeartbeatDate = nil;
return;
}
lastHeartbeatDate = [NSDate now];
if (!completionCalled) {
completion(0, "Heartbeat succeeded");
completionCalled = true;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -14,25 +14,16 @@
@import Foundation;
NSError* makeError(int code, NSString* msg);
LockdowndClientHandle* ideviceinfo_c_init(IdeviceProviderHandle* g_provider, IdevicePairingFile* g_sess_pf, NSError** error) {
LockdowndClientHandle* ideviceinfo_c_init(AdapterHandle* adapter, RsdHandshakeHandle* handshake, NSError** error) {
LockdowndClientHandle *g_client = NULL;
IdeviceFfiError *err = lockdownd_connect(g_provider, &g_client);
IdeviceFfiError *err = lockdownd_connect_rsd(adapter, handshake, &g_client);
if (err) {
*error = makeError(err->code, @(err->message));
idevice_pairing_file_free(g_sess_pf);
idevice_error_free(err);
return NULL;
}
err = lockdownd_start_session(g_client, g_sess_pf);
idevice_pairing_file_free(g_sess_pf);
if (err) {
*error = makeError(err->code, @(err->message));
idevice_error_free(err);
lockdownd_client_free(g_client);
return NULL;
}
// No session start needed - RSD tunnel handles authentication
return g_client;
}
@ -62,15 +53,13 @@ char *ideviceinfo_c_get_xml(LockdowndClientHandle* g_client, NSError** error) {
@implementation JITEnableContext(DeviceInfo)
- (LockdowndClientHandle*)ideviceInfoInit:(NSError**)error {
[self ensureHeartbeatWithError:error];
[self ensureTunnelWithError:error];
if (*error) { return nil; }
IdevicePairingFile *pf = [self getPairingFileWithError:error];
if (*error) { return nil; }
return ideviceinfo_c_init(provider, pf, error);
return ideviceinfo_c_init(adapter, handshake, error);
}
- (char*)ideviceInfoGetXMLWithLockdownClient:(LockdowndClientHandle*)lockdownClient error:(NSError**)error {
[self ensureHeartbeatWithError:error];
[self ensureTunnelWithError:error];
if (*error) { return NULL; }
return ideviceinfo_c_get_xml(lockdownClient, error);
}

View file

@ -9,14 +9,15 @@
#ifndef JIT_H
#define JIT_H
#include "idevice.h"
#include <dispatch/dispatch.h>
typedef void (^LogFuncC)(const char* message, ...);
typedef void (^DebugAppCallback)(int pid,
struct DebugProxyHandle* debug_proxy,
struct RemoteServerHandle* remote_server,
dispatch_semaphore_t semaphore);
int debug_app(IdeviceProviderHandle* tcp_provider, const char *bundle_id, LogFuncC logger, DebugAppCallback callback);
int debug_app_pid(IdeviceProviderHandle* tcp_provider, int pid, LogFuncC logger, DebugAppCallback callback);
int launch_app_via_proxy(IdeviceProviderHandle* tcp_provider, const char *bundle_id, LogFuncC logger);
int debug_app(AdapterHandle* adapter, RsdHandshakeHandle* handshake, const char *bundle_id, LogFuncC logger, DebugAppCallback callback);
int debug_app_pid(AdapterHandle* adapter, RsdHandshakeHandle* handshake, int pid, LogFuncC logger, DebugAppCallback callback);
int launch_app_via_proxy(AdapterHandle* adapter, RsdHandshakeHandle* handshake, const char *bundle_id, LogFuncC logger);
#endif /* JIT_H */

View file

@ -23,8 +23,6 @@
// MARK: - Shared debug session
typedef struct {
AdapterHandle *adapter;
RsdHandshakeHandle *handshake;
RemoteServerHandle *remote_server;
DebugProxyHandle *debug_proxy;
} DebugSession;
@ -32,40 +30,18 @@ typedef struct {
static void debug_session_free(DebugSession *s) {
if (s->debug_proxy) { debug_proxy_free(s->debug_proxy); s->debug_proxy = NULL; }
if (s->remote_server) { remote_server_free(s->remote_server); s->remote_server = NULL; }
if (s->handshake) { rsd_handshake_free(s->handshake); s->handshake = NULL; }
if (s->adapter) { adapter_free(s->adapter); s->adapter = NULL; }
}
// Connects to the device, performs the RSD handshake, and sets up the debug proxy.
// Connects to the device using the existing adapter+handshake and sets up the debug proxy.
// Returns 0 on success; cleans up any partial state and returns 1 on failure.
static int connect_debug_session(IdeviceProviderHandle *tcp_provider, DebugSession *out) {
static int connect_debug_session(AdapterHandle *adapter, RsdHandshakeHandle *handshake, DebugSession *out) {
memset(out, 0, sizeof(*out));
IdeviceFfiError *err = NULL;
CoreDeviceProxyHandle *core_device = NULL;
err = core_device_proxy_connect(tcp_provider, &core_device);
err = remote_server_connect_rsd(adapter, handshake, &out->remote_server);
if (err) { idevice_error_free(err); return 1; }
uint16_t rsd_port = 0;
err = core_device_proxy_get_server_rsd_port(core_device, &rsd_port);
if (err) { idevice_error_free(err); core_device_proxy_free(core_device); return 1; }
err = core_device_proxy_create_tcp_adapter(core_device, &out->adapter);
if (err) { idevice_error_free(err); core_device_proxy_free(core_device); return 1; }
core_device = NULL; // ownership transferred to adapter
AdapterStreamHandle *stream = NULL;
err = adapter_connect(out->adapter, rsd_port, (ReadWriteOpaque **)&stream);
if (err) { idevice_error_free(err); debug_session_free(out); return 1; }
err = rsd_handshake_new((ReadWriteOpaque *)stream, &out->handshake);
if (err) { idevice_error_free(err); adapter_close(stream); debug_session_free(out); return 1; }
stream = NULL; // consumed by handshake
err = remote_server_connect_rsd(out->adapter, out->handshake, &out->remote_server);
if (err) { idevice_error_free(err); debug_session_free(out); return 1; }
err = debug_proxy_connect_rsd(out->adapter, out->handshake, &out->debug_proxy);
err = debug_proxy_connect_rsd(adapter, handshake, &out->debug_proxy);
if (err) { idevice_error_free(err); debug_session_free(out); return 1; }
return 0;
@ -137,9 +113,9 @@ void runDebugServerCommand(int pid,
// MARK: - Public entry points
int debug_app(IdeviceProviderHandle* tcp_provider, const char *bundle_id, LogFuncC logger, DebugAppCallback callback) {
int debug_app(AdapterHandle* adapter, RsdHandshakeHandle* handshake, const char *bundle_id, LogFuncC logger, DebugAppCallback callback) {
DebugSession session;
if (connect_debug_session(tcp_provider, &session) != 0) return 1;
if (connect_debug_session(adapter, handshake, &session) != 0) return 1;
ProcessControlHandle *process_control = NULL;
IdeviceFfiError *err = process_control_new(session.remote_server, &process_control);
@ -166,9 +142,9 @@ int debug_app(IdeviceProviderHandle* tcp_provider, const char *bundle_id, LogFun
return 0;
}
int debug_app_pid(IdeviceProviderHandle* tcp_provider, int pid, LogFuncC logger, DebugAppCallback callback) {
int debug_app_pid(AdapterHandle* adapter, RsdHandshakeHandle* handshake, int pid, LogFuncC logger, DebugAppCallback callback) {
DebugSession session;
if (connect_debug_session(tcp_provider, &session) != 0) return 1;
if (connect_debug_session(adapter, handshake, &session) != 0) return 1;
runDebugServerCommand(pid, session.debug_proxy, session.remote_server, logger, callback);
@ -177,36 +153,14 @@ int debug_app_pid(IdeviceProviderHandle* tcp_provider, int pid, LogFuncC logger,
return 0;
}
int launch_app_via_proxy(IdeviceProviderHandle* tcp_provider, const char *bundle_id, LogFuncC logger) {
int launch_app_via_proxy(AdapterHandle* adapter, RsdHandshakeHandle* handshake, const char *bundle_id, LogFuncC logger) {
IdeviceFfiError* err = NULL;
CoreDeviceProxyHandle *core_device = NULL;
AdapterHandle *adapter = NULL;
AdapterStreamHandle *stream = NULL;
RsdHandshakeHandle *handshake = NULL;
RemoteServerHandle *remote_server = NULL;
ProcessControlHandle *process_control = NULL;
uint64_t pid = 0;
int result = 1;
err = core_device_proxy_connect(tcp_provider, &core_device);
if (err) { idevice_error_free(err); goto cleanup; }
uint16_t rsd_port = 0;
err = core_device_proxy_get_server_rsd_port(core_device, &rsd_port);
if (err) { idevice_error_free(err); goto cleanup; }
err = core_device_proxy_create_tcp_adapter(core_device, &adapter);
if (err) { idevice_error_free(err); goto cleanup; }
core_device = NULL; // ownership transferred to adapter
err = adapter_connect(adapter, rsd_port, (ReadWriteOpaque **)&stream);
if (err) { idevice_error_free(err); goto cleanup; }
err = rsd_handshake_new((ReadWriteOpaque *)stream, &handshake);
if (err) { idevice_error_free(err); goto cleanup; }
stream = NULL; // consumed by handshake/adapter stack
err = remote_server_connect_rsd(adapter, handshake, &remote_server);
if (err) { idevice_error_free(err); goto cleanup; }
@ -226,10 +180,6 @@ int launch_app_via_proxy(IdeviceProviderHandle* tcp_provider, const char *bundle
cleanup:
if (process_control) process_control_free(process_control);
if (remote_server) remote_server_free(remote_server);
if (handshake) rsd_handshake_free(handshake);
if (stream) adapter_close(stream);
if (adapter) adapter_free(adapter);
if (core_device) core_device_proxy_free(core_device);
return result;
}
@ -238,32 +188,32 @@ cleanup:
- (BOOL)debugAppWithBundleID:(NSString*)bundleID logger:(LogFunc)logger jsCallback:(DebugAppCallback)jsCallback {
NSError* err = nil;
[self ensureHeartbeatWithError:&err];
[self ensureTunnelWithError:&err];
if (err) {
logger(err.localizedDescription);
return NO;
}
return debug_app(provider, [bundleID UTF8String], [self createCLogger:logger], jsCallback) == 0;
return debug_app(adapter, handshake, [bundleID UTF8String], [self createCLogger:logger], jsCallback) == 0;
}
- (BOOL)debugAppWithPID:(int)pid logger:(LogFunc)logger jsCallback:(DebugAppCallback)jsCallback {
NSError* err = nil;
[self ensureHeartbeatWithError:&err];
[self ensureTunnelWithError:&err];
if (err) {
logger(err.localizedDescription);
return NO;
}
return debug_app_pid(provider, pid, [self createCLogger:logger], jsCallback) == 0;
return debug_app_pid(adapter, handshake, pid, [self createCLogger:logger], jsCallback) == 0;
}
- (BOOL)launchAppWithoutDebug:(NSString*)bundleID logger:(LogFunc)logger {
NSError* err = nil;
[self ensureHeartbeatWithError:&err];
[self ensureTunnelWithError:&err];
if (err) {
logger(err.localizedDescription);
return NO;
}
return launch_app_via_proxy(provider, [bundleID UTF8String], [self createCLogger:logger]) == 0;
return launch_app_via_proxy(adapter, handshake, [bundleID UTF8String], [self createCLogger:logger]) == 0;
}
@end

Binary file not shown.

View file

@ -13,9 +13,6 @@
#include <stdlib.h>
#include <stdio.h>
static IdevicePairingFile *g_pairing = NULL;
static IdeviceProviderHandle *g_provider = NULL;
static CoreDeviceProxyHandle *g_core_device = NULL;
static AdapterHandle *g_adapter = NULL;
static RsdHandshakeHandle *g_handshake = NULL;
static RemoteServerHandle *g_remote_server = NULL;
@ -26,9 +23,6 @@ static void cleanup_on_error(void) {
if (g_remote_server) { remote_server_free(g_remote_server); g_remote_server = NULL; }
if (g_handshake) { rsd_handshake_free(g_handshake); g_handshake = NULL; }
if (g_adapter) { adapter_free(g_adapter); g_adapter = NULL; }
if (g_core_device) { core_device_proxy_free(g_core_device); g_core_device = NULL; }
if (g_provider) { idevice_provider_free(g_provider); g_provider = NULL; }
if (g_pairing) { idevice_pairing_file_free(g_pairing); g_pairing = NULL; }
}
int simulate_location(const char *device_ip,
@ -37,7 +31,7 @@ int simulate_location(const char *device_ip,
const char *pairing_file)
{
IdeviceFfiError *err = NULL;
if (g_location_sim) {
if ((err = location_simulation_set(g_location_sim, latitude, longitude))) {
idevice_error_free(err);
@ -53,68 +47,28 @@ int simulate_location(const char *device_ip,
return IPA_ERR_INVALID_IP;
}
if (g_pairing) {
idevice_pairing_file_free(g_pairing);
g_pairing = NULL;
}
if ((err = idevice_pairing_file_read(pairing_file, &g_pairing))) {
RpPairingFileHandle *rp_pairing = NULL;
if ((err = rp_pairing_file_read(pairing_file, &rp_pairing))) {
idevice_error_free(err);
return IPA_ERR_PAIRING_READ;
}
if ((err = idevice_tcp_provider_new((struct sockaddr *)&addr,
g_pairing,
"LocationSimCLI",
&g_provider)))
if ((err = tunnel_create_rppairing(
(const idevice_sockaddr *)&addr,
sizeof(addr),
"StikDebugLocation",
rp_pairing,
NULL,
NULL,
&g_adapter,
&g_handshake)))
{
idevice_error_free(err);
rp_pairing_file_free(rp_pairing);
cleanup_on_error();
return IPA_ERR_PROVIDER_CREATE;
}
if ((err = core_device_proxy_connect(g_provider, &g_core_device))) {
idevice_error_free(err);
cleanup_on_error();
return IPA_ERR_CORE_DEVICE;
}
idevice_provider_free(g_provider);
g_provider = NULL;
uint16_t rsd_port;
if ((err = core_device_proxy_get_server_rsd_port(g_core_device,
&rsd_port)))
{
idevice_error_free(err);
cleanup_on_error();
return IPA_ERR_RSD_PORT;
}
if ((err = core_device_proxy_create_tcp_adapter(g_core_device,
&g_adapter)))
{
idevice_error_free(err);
cleanup_on_error();
return IPA_ERR_ADAPTER_CREATE;
}
// core_device_proxy_create_tcp_adapter takes ownership of g_core_device
// (Rust moves it into the adapter). Null the pointer so cleanup_on_error
// does not attempt a second free.
g_core_device = NULL;
AdapterStreamHandle *stream = NULL;
if ((err = adapter_connect(g_adapter, rsd_port, (ReadWriteOpaque **)&stream))) {
idevice_error_free(err);
cleanup_on_error();
return IPA_ERR_STREAM;
}
if ((err = rsd_handshake_new((ReadWriteOpaque *)stream, &g_handshake))) {
idevice_error_free(err);
adapter_stream_close(stream);
cleanup_on_error();
return IPA_ERR_HANDSHAKE;
}
rp_pairing_file_free(rp_pairing);
if ((err = remote_server_connect_rsd(g_adapter,
g_handshake,
@ -124,9 +78,6 @@ int simulate_location(const char *device_ip,
cleanup_on_error();
return IPA_ERR_REMOTE_SERVER;
}
// remote_server_connect_rsd takes ownership of g_adapter and g_handshake.
g_adapter = NULL;
g_handshake = NULL;
if ((err = location_simulation_new(g_remote_server,
&g_location_sim))) {

View file

@ -9,6 +9,6 @@
#define MOUNT_H
#include "idevice.h"
#include <Foundation/Foundation.h>
size_t getMountedDeviceCount(IdeviceProviderHandle* provider, NSError** error);
int mountPersonalDDI(IdeviceProviderHandle* provider, IdevicePairingFile* pairingFile2, NSString* imagePath, NSString* trustcachePath, NSString* manifestPath, NSError** error);
size_t getMountedDeviceCount(AdapterHandle* adapter, RsdHandshakeHandle* handshake, NSError** error);
int mountPersonalDDI(AdapterHandle* adapter, RsdHandshakeHandle* handshake, NSString* imagePath, NSString* trustcachePath, NSString* manifestPath, NSError** error);
#endif

View file

@ -11,9 +11,9 @@
NSError* makeError(int code, NSString* msg);
size_t getMountedDeviceCount(IdeviceProviderHandle* provider, NSError** error) {
size_t getMountedDeviceCount(AdapterHandle* adapter, RsdHandshakeHandle* handshake, NSError** error) {
ImageMounterHandle *client = NULL;
IdeviceFfiError *err = image_mounter_connect(provider, &client);
IdeviceFfiError *err = image_mounter_connect_rsd(adapter, handshake, &client);
if (err) {
*error = makeError(err->code, @(err->message));
idevice_error_free(err);
@ -37,34 +37,24 @@ size_t getMountedDeviceCount(IdeviceProviderHandle* provider, NSError** error) {
return deviceLength;
}
int mountPersonalDDI(IdeviceProviderHandle* provider, IdevicePairingFile* pairingFile2, NSString* imagePath, NSString* trustcachePath, NSString* manifestPath, NSError** error) {
int mountPersonalDDI(AdapterHandle* adapter, RsdHandshakeHandle* handshake, NSString* imagePath, NSString* trustcachePath, NSString* manifestPath, NSError** error) {
NSData *image = [NSData dataWithContentsOfFile:imagePath];
NSData *trustcache = [NSData dataWithContentsOfFile:trustcachePath];
NSData *buildManifest = [NSData dataWithContentsOfFile:manifestPath];
if (!image || !trustcache || !buildManifest) {
idevice_pairing_file_free(pairingFile2);
*error = makeError(1, @"Failed to read one or more files");
return 1;
}
// Get UniqueChipID via lockdownd over RSD (no session start needed - tunnel handles auth)
LockdowndClientHandle *lockdownClient = NULL;
IdeviceFfiError *err = lockdownd_connect(provider, &lockdownClient);
IdeviceFfiError *err = lockdownd_connect_rsd(adapter, handshake, &lockdownClient);
if (err) {
*error = makeError(6, @(err->message));
idevice_pairing_file_free(pairingFile2);
idevice_error_free(err);
return 6;
}
err = lockdownd_start_session(lockdownClient, pairingFile2);
idevice_pairing_file_free(pairingFile2);
if (err) {
*error = makeError(7, @(err->message));
idevice_error_free(err);
lockdownd_client_free(lockdownClient);
return 7;
}
plist_t uniqueChipIDPlist = NULL;
err = lockdownd_get_value(lockdownClient, "UniqueChipID", NULL, &uniqueChipIDPlist);
lockdownd_client_free(lockdownClient);
@ -79,50 +69,33 @@ int mountPersonalDDI(IdeviceProviderHandle* provider, IdevicePairingFile* pairin
plist_free(uniqueChipIDPlist);
ImageMounterHandle *mounterClient = NULL;
err = image_mounter_connect(provider, &mounterClient);
err = image_mounter_connect_rsd(adapter, handshake, &mounterClient);
if (err) {
*error = makeError(9, @(err->message));
idevice_error_free(err);
return 9;
}
err = image_mounter_mount_personalized(
mounterClient,
provider,
[image bytes],
[image length],
[trustcache bytes],
[trustcache length],
[buildManifest bytes],
[buildManifest length],
NULL,
uniqueChipID
);
// TODO: image_mounter_mount_personalized still requires an IdeviceProviderHandle.
// The FFI needs an RSD variant (e.g. image_mounter_mount_personalized_rsd) that
// takes an AdapterHandle instead.
*error = makeError(10, @"mount_personalized not yet available over RSD tunnels");
image_mounter_free(mounterClient);
if (err) {
*error = makeError(10, @(err->message));
idevice_error_free(err);
return 10;
}
return 0;
return 10;
}
@implementation JITEnableContext(DDI)
- (NSUInteger)getMountedDeviceCount:(NSError**)error {
[self ensureHeartbeatWithError:error];
[self ensureTunnelWithError:error];
if (*error) { return 0; }
return getMountedDeviceCount(provider, error);
return getMountedDeviceCount(adapter, handshake, error);
}
- (NSInteger)mountPersonalDDIWithImagePath:(NSString*)imagePath trustcachePath:(NSString*)trustcachePath manifestPath:(NSString*)manifestPath error:(NSError**)error {
[self ensureHeartbeatWithError:error];
[self ensureTunnelWithError:error];
if (*error) { return 0; }
IdevicePairingFile *pairing = [self getPairingFileWithError:error];
if (*error) { return 0; }
return mountPersonalDDI(provider, pairing, imagePath, trustcachePath, manifestPath, error);
return mountPersonalDDI(adapter, handshake, imagePath, trustcachePath, manifestPath, error);
}
@end

View file

@ -12,20 +12,17 @@
// MARK: - Shared AppService session
typedef struct {
AdapterHandle *adapter;
RsdHandshakeHandle *handshake;
AppServiceHandle *appService;
} AppServiceSession;
static void app_service_session_free(AppServiceSession *s) {
if (s->appService) { app_service_free(s->appService); s->appService = NULL; }
if (s->handshake) { rsd_handshake_free(s->handshake); s->handshake = NULL; }
if (s->adapter) { adapter_free(s->adapter); s->adapter = NULL; }
}
// Connects to the device via CoreDeviceProxy → Adapter → RSD → AppService.
// Connects to the device via the existing adapter+handshake → AppService.
// Returns 0 on success; cleans up any partial state and returns 1 on failure.
static int connect_app_service(IdeviceProviderHandle *provider,
static int connect_app_service(AdapterHandle *adapter,
RsdHandshakeHandle *handshake,
AppServiceSession *out,
JITEnableContext *ctx,
NSError **outError)
@ -33,62 +30,11 @@ static int connect_app_service(IdeviceProviderHandle *provider,
memset(out, 0, sizeof(*out));
IdeviceFfiError *ffiError = NULL;
CoreDeviceProxyHandle *coreProxy = NULL;
ffiError = core_device_proxy_connect(provider, &coreProxy);
if (ffiError) {
*outError = [ctx errorWithStr:[NSString stringWithUTF8String:ffiError->message ?: "Failed to connect CoreDeviceProxy"]
code:ffiError->code];
idevice_error_free(ffiError);
return 1;
}
uint16_t rsdPort = 0;
ffiError = core_device_proxy_get_server_rsd_port(coreProxy, &rsdPort);
if (ffiError) {
*outError = [ctx errorWithStr:[NSString stringWithUTF8String:ffiError->message ?: "Unable to resolve RSD port"]
code:ffiError->code];
idevice_error_free(ffiError);
core_device_proxy_free(coreProxy);
return 1;
}
ffiError = core_device_proxy_create_tcp_adapter(coreProxy, &out->adapter);
if (ffiError) {
*outError = [ctx errorWithStr:[NSString stringWithUTF8String:ffiError->message ?: "Failed to create adapter"]
code:ffiError->code];
idevice_error_free(ffiError);
core_device_proxy_free(coreProxy);
return 1;
}
coreProxy = NULL; // ownership transferred to adapter
AdapterStreamHandle *stream = NULL;
ffiError = adapter_connect(out->adapter, rsdPort, (ReadWriteOpaque **)&stream);
if (ffiError) {
*outError = [ctx errorWithStr:[NSString stringWithUTF8String:ffiError->message ?: "Adapter connect failed"]
code:ffiError->code];
idevice_error_free(ffiError);
app_service_session_free(out);
return 1;
}
ffiError = rsd_handshake_new((ReadWriteOpaque *)stream, &out->handshake);
if (ffiError) {
*outError = [ctx errorWithStr:[NSString stringWithUTF8String:ffiError->message ?: "RSD handshake failed"]
code:ffiError->code];
idevice_error_free(ffiError);
adapter_stream_close(stream);
app_service_session_free(out);
return 1;
}
stream = NULL; // consumed by handshake
ffiError = app_service_connect_rsd(out->adapter, out->handshake, &out->appService);
ffiError = app_service_connect_rsd(adapter, handshake, &out->appService);
if (ffiError) {
*outError = [ctx errorWithStr:[NSString stringWithUTF8String:ffiError->message ?: "Unable to open AppService"]
code:ffiError->code];
idevice_error_free(ffiError);
app_service_session_free(out);
return 1;
}
@ -100,11 +46,11 @@ static int connect_app_service(IdeviceProviderHandle *provider,
@implementation JITEnableContext(Process)
- (NSArray<NSDictionary*>*)fetchProcessesViaAppServiceWithError:(NSError **)error {
[self ensureHeartbeatWithError:error];
[self ensureTunnelWithError:error];
if (*error) { return nil; }
AppServiceSession session;
if (connect_app_service(provider, &session, self, error) != 0) { return nil; }
if (connect_app_service(adapter, handshake, &session, self, error) != 0) { return nil; }
ProcessTokenC *processes = NULL;
uintptr_t count = 0;
@ -138,7 +84,7 @@ static int connect_app_service(IdeviceProviderHandle *provider,
}
- (NSArray<NSDictionary*>*)_fetchProcessListLocked:(NSError**)error {
[self ensureHeartbeatWithError:error];
[self ensureTunnelWithError:error];
if (*error) { return nil; }
return [self fetchProcessesViaAppServiceWithError:error];
}
@ -156,11 +102,11 @@ static int connect_app_service(IdeviceProviderHandle *provider,
}
- (BOOL)killProcessWithPID:(int)pid error:(NSError **)error {
[self ensureHeartbeatWithError:error];
[self ensureTunnelWithError:error];
if (*error) { return NO; }
AppServiceSession session;
if (connect_app_service(provider, &session, self, error) != 0) { return NO; }
if (connect_app_service(adapter, handshake, &session, self, error) != 0) { return NO; }
SignalResponseC *signalResponse = NULL;
IdeviceFfiError *ffiError = app_service_send_signal(session.appService, (uint32_t)pid, SIGKILL, &signalResponse);

View file

@ -9,9 +9,9 @@
#define PROFILES_H
#include "idevice.h"
#include <Foundation/Foundation.h>
NSArray<NSData*>* _Nullable fetchAppProfiles(IdeviceProviderHandle* _Nonnull provider, NSError* _Nullable * _Nullable error);
bool removeProfile(IdeviceProviderHandle* _Nonnull provider, NSString* _Nonnull uuid, NSError* _Nullable * _Nullable error);
bool addProfile(IdeviceProviderHandle* _Nonnull provider, NSData* _Nonnull profile, NSError* _Nullable * _Nullable error);
NSArray<NSData*>* _Nullable fetchAppProfiles(AdapterHandle* _Nonnull adapter, RsdHandshakeHandle* _Nonnull handshake, NSError* _Nullable * _Nullable error);
bool removeProfile(AdapterHandle* _Nonnull adapter, RsdHandshakeHandle* _Nonnull handshake, NSString* _Nonnull uuid, NSError* _Nullable * _Nullable error);
bool addProfile(AdapterHandle* _Nonnull adapter, RsdHandshakeHandle* _Nonnull handshake, NSData* _Nonnull profile, NSError* _Nullable * _Nullable error);
@interface CMSDecoderHelper : NSObject
// Decode CMS/PKCS7 data and return decoded payload and any embedded certs

View file

@ -14,9 +14,9 @@ NSError* makeError(int code, NSString* msg) {
}
NSArray<NSData*>* fetchAppProfiles(IdeviceProviderHandle* provider, NSError** error) {
NSArray<NSData*>* fetchAppProfiles(AdapterHandle* adapter, RsdHandshakeHandle* handshake, NSError** error) {
MisagentClientHandle *misagentHandle = NULL;
IdeviceFfiError *err = misagent_connect(provider, &misagentHandle);
IdeviceFfiError *err = misagent_connect_rsd(adapter, handshake, &misagentHandle);
if (err) {
*error = makeError(err->code, @(err->message));
idevice_error_free(err);
@ -50,9 +50,9 @@ NSArray<NSData*>* fetchAppProfiles(IdeviceProviderHandle* provider, NSError** er
return ans;
}
bool removeProfile(IdeviceProviderHandle* provider, NSString* uuid, NSError** error) {
bool removeProfile(AdapterHandle* adapter, RsdHandshakeHandle* handshake, NSString* uuid, NSError** error) {
MisagentClientHandle *misagentHandle = NULL;
IdeviceFfiError * err = misagent_connect(provider, &misagentHandle);
IdeviceFfiError * err = misagent_connect_rsd(adapter, handshake, &misagentHandle);
if (err) {
*error = makeError(err->code, @(err->message));
idevice_error_free(err);
@ -71,9 +71,9 @@ bool removeProfile(IdeviceProviderHandle* provider, NSString* uuid, NSError** er
return true;
}
bool addProfile(IdeviceProviderHandle* provider, NSData* profile, NSError** error) {
bool addProfile(AdapterHandle* adapter, RsdHandshakeHandle* handshake, NSData* profile, NSError** error) {
MisagentClientHandle *misagentHandle = NULL;
IdeviceFfiError * err = misagent_connect(provider, &misagentHandle);
IdeviceFfiError * err = misagent_connect_rsd(adapter, handshake, &misagentHandle);
if (err) {
*error = makeError(err->code, @(err->message));
idevice_error_free(err);
@ -141,29 +141,29 @@ bool addProfile(IdeviceProviderHandle* provider, NSData* profile, NSError** erro
@implementation JITEnableContext(Profile)
- (NSArray<NSData*>*)fetchAllProfiles:(NSError **)error {
[self ensureHeartbeatWithError:error];
[self ensureTunnelWithError:error];
if(*error) {
return nil;
}
return fetchAppProfiles(provider, error);
return fetchAppProfiles(adapter, handshake, error);
}
- (BOOL)removeProfileWithUUID:(NSString*)uuid error:(NSError **)error {
[self ensureHeartbeatWithError:error];
[self ensureTunnelWithError:error];
if(*error) {
return NO;
}
return removeProfile(provider, uuid, error);
return removeProfile(adapter, handshake, uuid, error);
}
- (BOOL)addProfile:(NSData*)profile error:(NSError **)error {
[self ensureHeartbeatWithError:error];
[self ensureTunnelWithError:error];
if(*error) {
return NO;
}
return addProfile(provider, profile, error);
return addProfile(adapter, handshake, profile, error);
}

View file

@ -14,7 +14,7 @@
onError:(SyslogErrorHandler)errorHandler
{
NSError* error = nil;
[self ensureHeartbeatWithError:&error];
[self ensureTunnelWithError:&error];
if(error) {
errorHandler(error);
return;
@ -33,7 +33,7 @@
if (!strongSelf) { return; }
SyslogRelayClientHandle *client = NULL;
IdeviceFfiError *err = syslog_relay_connect_tcp(strongSelf->provider, &client);
IdeviceFfiError *err = syslog_relay_connect_rsd(strongSelf->adapter, strongSelf->handshake, &client);
if (err != NULL) {
NSString *message = err->message ? [NSString stringWithCString:err->message encoding:NSASCIIStringEncoding] : @"Failed to connect to syslog relay";
NSError *nsError = [strongSelf errorWithStr:message code:err->code];