diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 01c3343..6017736 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ permissions: jobs: build: name: Build XCFramework - runs-on: macos-15 + runs-on: xcode-27 steps: - name: Checkout @@ -58,7 +58,7 @@ jobs: - name: Create release if: startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: files: StikJIT.xcframework.zip generate_release_notes: true diff --git a/INTEGRATION.md b/INTEGRATION.md index 31437b5..c91efc5 100644 --- a/INTEGRATION.md +++ b/INTEGRATION.md @@ -9,7 +9,7 @@ This guide separates two different jobs: - **Part 1: Add iOS 26 JIT support** changes the app's JIT memory allocator so it can cooperate with a script where TXM/SPTM is present. - **Part 2: Integrate StikDebug and StikJIT** adds the StikDebug URL scheme and optional Built-in StikJIT to an app that already works with iOS 26 JIT and manual StikDebug activation. -If the app already enables JIT successfully after the user manually selects it and its script in StikDebug, skip Part 1. Record the script filename and complete Part 2. Part 2 does not require changing a working JIT allocator. +If the app already enables JIT successfully after the user manually selects it and its script in StikDebug, skip Part 1. Record whether it uses `universal.js`, `legacy.js`, or a custom script file, then complete Part 2. Part 2 does not require changing a working JIT allocator. If the app does not already have script-based iOS 26 JIT, complete Part 1 first and then Part 2. @@ -56,11 +56,9 @@ Use this order where TXM/SPTM is present: Preallocate executable regions before detaching. A region introduced later cannot be prepared by a script that has already detached; reconnect and repeat the protocol if the engine must add one. -## Keep the legacy protocol only for backward compatibility +> **Note:** We keep legacy script support only for backward compatibility with existing applications. If you are adding JIT support to an app now, use the universal script. -The legacy script is retained only for existing engines that stop once at `brk #0x69` with the RX address in `x0` and its length in `x1`. It prepares that region, advances the program counter, and detaches. Do not build new integrations around `legacy.js`; implement the universal protocol instead. - -After Part 1 works with the matching script in StikDebug, continue with Part 2. The chosen script filename is developer-controlled and must not become a user setting. +After Part 1 works with the matching script in StikDebug, continue with Part 2. The chosen script is developer-controlled and must not become a user setting. # Part 2: Integrate StikDebug and StikJIT into an existing iOS 26 JIT app @@ -69,7 +67,9 @@ Part 2 assumes all of the following already work: - The app can wait for an external debugger and detect when JIT is ready. - The app's JIT allocator implements its iOS 26 breakpoint and executable-region protocol. - JIT works when the developer or user manually selects the app and matching script in StikDebug. -- The developer knows which script filename the app requires, normally `universal.js`. +- The developer knows whether the app requires `universal.js`, `legacy.js`, or a custom script file. + +If the app currently uses `legacy.js`, we recommend switching it to the universal method, but this is not required to complete Part 2. Part 2 preserves that existing **Wait for Debugger** path and adds automatic StikDebug URL launching and optional Built-in StikJIT. It does not change the app's JIT allocator or script protocol. If any prerequisite above is missing, complete Part 1 first. @@ -209,15 +209,19 @@ let paths = DDIPaths.default( ) ``` -Use the same `DDIPaths` for preparation, JIT enablement, and cache reset. Do not validate cached DDI versions or contents yourself; StikJIT reuses readable, nonempty files and lets the image mounter decide whether they work. +Use the same `DDIPaths` for preparation, JIT enablement, and cache reset. Before iOS 26.4, StikJIT downloads and mounts the three-file personalized DDI. On iOS 26.4 and newer, it downloads and installs the five-file cryptex DDI, which additionally includes cryptex info and a root hash. Do not validate cached DDI versions or contents yourself; StikJIT selects and replaces the cache format required by the running OS. + +A successful DDI mount persists until the device reboots, so it normally needs to be mounted only once per boot. ## Built-in StikJIT: Call the APIs at the right time StikJIT's preparation and enablement APIs are synchronous and blocking. Run them on one dedicated serial background queue in the helper. Do not split their ordered work into concurrent operations or convert it to `Task`/`async` calls. +Pass `configuration:` when the integration needs to override the tunnel endpoint or five-second connection timeout. The default endpoint is `10.7.0.1:49152`. + ### When settings opens -Use `StikJIT.isTXMPresent` to show `Present`, `Not Present`, or `Unknown`. This is informational; script selection still belongs to the developer. +Use `StikJIT.isTXMPresent` to show `Present`, `Not Present`, or `Unknown`. This is informational; the app's single script remains developer-defined. ### When the user selects Prepare JIT @@ -258,7 +262,11 @@ try StikJIT.enableJIT( ) ``` -Use `.universal` for new integrations. Use `.legacy` only when preserving an existing `legacy.js` ABI, or `.custom(URL)` for another established protocol. Keep that selection in backend code and use the corresponding filename in the StikDebug URL. Do not expose script selection to the user. The user-facing `forceScript` toggle only bypasses TXM detection and runs the developer-selected script regardless. +Configure exactly one script in backend code: `.universal` or `.legacy` for the corresponding bundled script, `.custom(URL)` for a script readable from the helper-extension process, or `.customBase64(String)` for base64-encoded UTF-8 script data held elsewhere. For StikDebug requests, send the built-in filename for universal or legacy, or send the base64-encoded contents of the custom script. Do not expose this configuration to the user. The user-facing `forceScript` toggle only bypasses TXM detection and runs the configured script regardless. + +```swift +let selectedScript: StikJIT.Script = .customBase64(storedScriptBase64) +``` ### When the user resets the cache @@ -268,7 +276,7 @@ Provide a **Reset Developer Disk Image** action that calls: try StikJIT.resetCachedDDI(at: paths) ``` -This removes only the three cached DDI files. The next preparation downloads them again if mounting is necessary. +This removes only the five possible cached DDI files. The next preparation downloads the three-file personalized DDI or five-file cryptex DDI required by the running OS if mounting is necessary. ## Configure the JIT methods @@ -282,7 +290,9 @@ Route all three choices through the same host-side coordinator. Check `get-task- For **Wait for Debugger**, keep the app's existing waiting and readiness behavior. Where TXM/SPTM is present, it must continue through the existing breakpoint protocol before reporting success; an ordinary debugger attachment without the correct script is not sufficient. -For **StikDebug**, construct the URL with `URLComponents` and include the bundle ID, current PID, and the developer-selected script filename: +For **StikDebug**, always construct the URL with the bundle ID and current PID. If TXM/SPTM is present, also send the app's JIT script. Each app must use exactly one developer-defined script; never add runtime script selection or expose it as a user setting. When TXM/SPTM is not present, omit the script parameter because debugger attachment alone enables JIT. + +This example is for an app that uses the recommended universal script: ```swift private let stikDebugScriptName = "universal.js" @@ -298,9 +308,14 @@ components.host = "enable-jit" components.queryItems = [ URLQueryItem(name: "bundle-id", value: bundleID), URLQueryItem(name: "pid", value: String(getpid())), - URLQueryItem(name: "script-name", value: stikDebugScriptName), ] +if isTXMPresent { + components.queryItems?.append( + URLQueryItem(name: "script-name", value: stikDebugScriptName) + ) +} + guard let url = components.url else { showJITError("Could not create the StikDebug request.") return @@ -309,7 +324,26 @@ guard let url = components.url else { UIApplication.shared.open(url) ``` -Use `universal.js` for new integrations. Specify `legacy.js` only for an app that already implements the legacy ABI, or another installed script for an established custom protocol. Keep this choice in developer-controlled backend code and do not expose `script-name` as a user setting. StikDebug uses the PID to target the running process and the bundle ID to identify and return to the app. +An existing legacy app can use the same code with its fixed script name set to `legacy.js`. A custom-script app must remove `stikDebugScriptName` and replace the `if isTXMPresent` block with this fixed custom-script implementation: + +```swift +if isTXMPresent { + do { + let scriptData = try Data(contentsOf: stikDebugScriptURL) + components.queryItems?.append( + URLQueryItem( + name: "script-data", + value: scriptData.base64EncodedString() + ) + ) + } catch { + showJITError("Could not read the StikDebug script: \(error.localizedDescription)") + return + } +} +``` + +`stikDebugScriptURL` must identify the app's single custom script in developer-controlled backend code. Sending it through `script-data` means users do not need to install it in StikDebug. Resolve TXM/SPTM presence before building the request rather than treating an unknown result as absent. StikDebug uses the PID to target the running process and the bundle ID to identify and return to the app. If the app checks whether StikDebug is installed with `canOpenURL`, add this to its `Info.plist`: @@ -373,19 +407,25 @@ Tell users all of the following before their first Built-in StikJIT attempt: Always surface the actual unreachable, download, mount, pairing, and script errors. Do not reduce every failure to “DDI unmounted.” +## Request endorsement + +After completing Part 1, Part 2, or both, please get in touch with us in the [idevice Discord server](https://discord.gg/qtgv6QtYbV) or leave a GitHub issue/pull request, so we can endorse your project and offer support to your users. We review app submissions on a case-by-case basis and generally decline projects without an established user base, low-quality substantially AI-generated projects, and forks of apps that already have our endorsement. + # Final checklist ## All methods -- [ ] New iOS 26 integrations implement the universal protocol; legacy is used only for backward compatibility. +- [ ] New iOS 26 integrations implement the universal protocol. - [ ] The host checks its own `get-task-allow` before starting JIT acquisition. -- [ ] The selected script matches the app's breakpoint protocol and is not user-configurable. +- [ ] The app has exactly one developer-configured script that matches its breakpoint protocol. - [ ] Where TXM/SPTM is present, executable-region preparation succeeds before the workload starts. - [ ] Settings offer one mutually exclusive JIT method selection and start only that method. ### StikDebug -- [ ] The URL includes bundle ID, current PID, and the developer-selected `script-name`. +- [ ] The URL always includes bundle ID and current PID. +- [ ] Where TXM/SPTM is present, the URL includes `script-name` for universal or legacy, or base64-encoded `script-data` for a custom script. +- [ ] Where TXM/SPTM is not present, the URL omits both script parameters. - [ ] `stikdebug` is in `LSApplicationQueriesSchemes` if the app calls `canOpenURL`. - [ ] The app does not treat successful URL opening as successful JIT acquisition. - [ ] Inside LiveContainer, the app tells users to enable **Use LiveContainer's Bundle ID**. diff --git a/README.md b/README.md index b8a7da9..5c72d1a 100644 --- a/README.md +++ b/README.md @@ -2,72 +2,11 @@ An iOS XCFramework that enables JIT for another process over the device's RSD tunnel. -JIT cannot be enabled in-process, since a process that attaches a debugger to itself deadlocks. StikJIT runs in a separate process, attaches a debugserver to the target by PID, enables JIT for it, then detaches. It is self-contained: the idevice FFI and its bundled JIT scripts (`universal.js`, `legacy.js`) are bundled inside. The target must have the `get-task-allow` entitlement. +StikJIT is self-contained and bundles the idevice FFI and its JIT scripts. -## Use +## Integration -For a complete host-app and helper-extension setup, see the [integration guide](INTEGRATION.md). - -Because it needs a separate process, you call StikJIT from a small app extension that your app launches and hands the target app's PID to (for example over XPC). StikJIT does not include that extension, its launch, or the pairing file; your app provides those. - -The device needs LocalDevVPN connected, and either Wi-Fi or Airplane Mode enabled, before JIT can be enabled or the DDI can be mounted. - -Add `StikJIT.xcframework` to the extension (Embed & Sign). StikJIT's preparation and JIT APIs are synchronous and blocking, so call them off the main thread. - -Choose the JIT script in your integration code. By default StikJIT runs it when TXM is present and uses attach-only JIT when TXM is absent. `forceScript` bypasses TXM detection and runs the selected script regardless: - -```swift -import StikJIT - -let paths = DDIPaths.default(in: documentsDirectory) - -try StikJIT.enableJIT( - targetPID: hostPID, - pairingFile: pairingFileURL, - ddiPaths: paths, - script: .universal, - forceScript: forceJITScript, - progress: { print("[StikJIT] \($0)") } -) -``` - -The developer-selected script can be `.universal`, `.legacy`, or `.custom(fileURL)`. A custom script must be readable from the extension process. Script selection should not be exposed as a user setting; a user-facing toggle maps only to `forceScript`. - -`enableJIT` prepares the device before attaching. It probes the tunnel endpoint, checks whether the DDI is mounted, downloads missing DDI files, mounts and verifies the DDI, determines whether TXM is present, and then enables JIT. These steps run serially in that order. Pass `configuration:` to override the tunnel endpoint and five-second connection timeout; the endpoint defaults to `10.7.0.1:49152`. - -### Developer Disk Image - -The device must have the personalized Developer Disk Image (DDI) mounted before JIT can be enabled. A mount persists until the device reboots, so it only needs to be redone once per boot. Apps can prepare it from settings before launching an extension: - -```swift -import StikJIT - -let paths = DDIPaths.default(in: documentsDirectory) - -let readiness = StikJIT.prepareDevice( - pairingFile: pairingFileURL, - paths: paths -) { stage in - print("[Preparation] \(stage)") -} - -switch readiness { -case .ready(let securityState): - print("Ready; TXM present: \(String(describing: securityState.isTXMPresent))") -case .unreachable(let reason): - print("Device unreachable: \(reason)") -case .preparationFailed(let reason): - print("Preparation failed: \(reason)") -} -``` - -TXM presence is also available without preparing the device through `StikJIT.isTXMPresent`. It returns `true` when TXM is present, `false` when it is absent, and `nil` when detection is unavailable. - -StikJIT treats readable, nonempty DDI files as cached and lets the device image mounter decide whether they are usable. A settings recovery action labeled **Reset Developer Disk Image** can remove the three cached files so the next preparation that needs to mount the DDI downloads them again: - -```swift -try StikJIT.resetCachedDDI(at: paths) -``` +For iOS 26 JIT support, StikDebug URL integration, Built-in StikJIT setup, API usage, requirements, and recommended app settings, see [Integrating StikJIT](INTEGRATION.md). [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/StephenDev0/StikJIT) diff --git a/Sources/BundledScript.swift b/Sources/BundledScript.swift index 3f08eeb..8aee165 100644 --- a/Sources/BundledScript.swift +++ b/Sources/BundledScript.swift @@ -3,7 +3,8 @@ import Foundation enum BundledScript { static func source(for script: StikJIT.Script) throws -> String { - if case .custom(let url) = script { + switch script { + case .custom(let url): guard url.isFileURL else { throw StikJITError.customScript("expected a file URL") } @@ -18,6 +19,19 @@ enum BundledScript { } catch { throw StikJITError.customScript("could not read \(url.path): \(error.localizedDescription)") } + case .customBase64(let encodedSource): + guard let data = Data(base64Encoded: encodedSource) else { + throw StikJITError.customScript("the provided script data is not valid base64") + } + guard let source = String(data: data, encoding: .utf8) else { + throw StikJITError.customScript("the decoded script data is not valid UTF-8") + } + guard !source.isEmpty else { + throw StikJITError.customScript("the decoded script is empty") + } + return source + case .universal, .legacy: + break } let bundle = Bundle(for: BundleToken.self) diff --git a/Sources/DDISession.swift b/Sources/DDISession.swift index f418783..8b9777c 100644 --- a/Sources/DDISession.swift +++ b/Sources/DDISession.swift @@ -36,6 +36,29 @@ final class DDISession { let tunnel = try makeTunnel() defer { tunnel.free() } + if DDIMountMethod.current == .personalized { + return try isLegacyDDIMounted(over: tunnel) + } + + var installed: UnsafeMutablePointer? + let cryptexError = cryptexd_installed_ddi(tunnel.adapter, tunnel.handshake, &installed) + defer { cryptexd_free_installed_cryptex(installed) } + if installed != nil { + if let cryptexError { idevice_error_free(cryptexError) } + return true + } + + if (try? isLegacyDDIMounted(over: tunnel)) == true { + if let cryptexError { idevice_error_free(cryptexError) } + return true + } + if let cryptexError { + try IdeviceFFI.check("failed to query installed DDI cryptex") { cryptexError } + } + return false + } + + private func isLegacyDDIMounted(over tunnel: Tunnel) throws -> Bool { var client: OpaquePointer? try IdeviceFFI.check("failed to connect to image mounter") { image_mounter_connect_rsd(tunnel.adapter, tunnel.handshake, &client) @@ -57,10 +80,39 @@ final class DDISession { return deviceCount > 0 } - func mountDDI(imagePath: String, trustcachePath: String, manifestPath: String, progress: @escaping (Double) -> Void) throws { - let imageData = try mappedFileData(atPath: imagePath, description: "developer disk image") - let trustcacheData = try mappedFileData(atPath: trustcachePath, description: "developer disk image trust cache") - let manifestData = try mappedFileData(atPath: manifestPath, description: "developer disk image manifest") + func mountDDI(paths: DDIPaths, progress: @escaping (Double) -> Void) throws { + switch DDIMountMethod.current { + case .cryptex: + try installCryptexDDI(from: URL(fileURLWithPath: paths.manifestPath).deletingLastPathComponent().path, progress: progress) + case .personalized: + try mountPersonalizedDDI(paths: paths, progress: progress) + } + } + + private func installCryptexDDI(from directoryPath: String, progress: @escaping (Double) -> Void) throws { + progress(0) + var assets: OpaquePointer? + try IdeviceFFI.check("failed to load DDI cryptex assets") { + directoryPath.withCString { cryptex1_assets_load($0, &assets) } + } + guard let assets else { + throw StikJITError.ddiFilesMissing("DDI cryptex assets could not be loaded from \(directoryPath)") + } + defer { cryptex1_assets_free(assets) } + + let tunnel = try makeTunnel() + defer { tunnel.free() } + + try IdeviceFFI.check("failed to install DDI cryptex") { + cryptexd_install_ddi(tunnel.adapter, tunnel.handshake, assets, nil) + } + progress(1) + } + + private func mountPersonalizedDDI(paths: DDIPaths, progress: @escaping (Double) -> Void) throws { + let imageData = try mappedFileData(atPath: paths.imagePath, description: "developer disk image") + let trustcacheData = try mappedFileData(atPath: paths.trustcachePath, description: "developer disk image trust cache") + let manifestData = try mappedFileData(atPath: paths.manifestPath, description: "developer disk image manifest") let tunnel = try makeTunnel() defer { tunnel.free() } @@ -78,9 +130,9 @@ final class DDISession { let context = Unmanaged.passUnretained(progressBox).toOpaque() try IdeviceFFI.check("failed to mount personalized developer disk image") { - imageData.withUnsafeBytes { imageBuffer -> UnsafeMutablePointer? in - trustcacheData.withUnsafeBytes { trustcacheBuffer -> UnsafeMutablePointer? in - manifestData.withUnsafeBytes { manifestBuffer -> UnsafeMutablePointer? in + imageData.withUnsafeBytes { imageBuffer in + trustcacheData.withUnsafeBytes { trustcacheBuffer in + manifestData.withUnsafeBytes { manifestBuffer in image_mounter_mount_personalized_with_callback_rsd( client, tunnel.adapter, @@ -131,6 +183,9 @@ final class DDISession { var value: UInt64 = 0 plist_get_uint_val(uniqueChipIDPlist, &value) + guard value != 0 else { + throw StikJITError.device(code: -1, subCode: 0, message: "Failed to decode UniqueChipID") + } return value } diff --git a/Sources/DeveloperDiskImageService.swift b/Sources/DeveloperDiskImageService.swift index 745b13f..6d97504 100644 --- a/Sources/DeveloperDiskImageService.swift +++ b/Sources/DeveloperDiskImageService.swift @@ -8,10 +8,20 @@ public struct DDIPaths: Sendable { public var manifestPath: String - public init(imagePath: String, trustcachePath: String, manifestPath: String) { + public var cryptexInfoPath: String + + public var rootHashPath: String + + public init(imagePath: String, + trustcachePath: String, + manifestPath: String, + cryptexInfoPath: String? = nil, + rootHashPath: String? = nil) { self.imagePath = imagePath self.trustcachePath = trustcachePath self.manifestPath = manifestPath + self.cryptexInfoPath = cryptexInfoPath ?? "\(imagePath).cryptex_info" + self.rootHashPath = rootHashPath ?? "\(imagePath).root_hash" } public static func `default`(in directory: URL) -> DDIPaths { @@ -23,7 +33,8 @@ public struct DDIPaths: Sendable { var allFilesUsable: Bool { let fileManager = FileManager.default - return allPaths.allSatisfy { path in + let requiredPaths = DDIMountMethod.current == .cryptex ? allPaths : personalizedPaths + let requiredFilesUsable = requiredPaths.allSatisfy { path in guard fileManager.isReadableFile(atPath: path), let attributes = try? fileManager.attributesOfItem(atPath: path), attributes[.type] as? FileAttributeType == .typeRegular, @@ -32,18 +43,49 @@ public struct DDIPaths: Sendable { } return size.int64Value > 0 } + if DDIMountMethod.current == .personalized { + return requiredFilesUsable && cryptexOnlyPaths.allSatisfy { !fileManager.fileExists(atPath: $0) } + } + return requiredFilesUsable } var allPaths: [String] { + personalizedPaths + cryptexOnlyPaths + } + + var personalizedPaths: [String] { [imagePath, trustcachePath, manifestPath] } + var cryptexOnlyPaths: [String] { + [cryptexInfoPath, rootHashPath] + } + func removeCachedFiles() throws { let fileManager = FileManager.default for path in Set(allPaths) where fileManager.fileExists(atPath: path) { try fileManager.removeItem(atPath: path) } } + + func removeCryptexOnlyFiles() throws { + let fileManager = FileManager.default + for path in Set(cryptexOnlyPaths) where fileManager.fileExists(atPath: path) { + try fileManager.removeItem(atPath: path) + } + } +} + +enum DDIMountMethod: String { + case cryptex + case personalized + + static var current: DDIMountMethod { + let version = ProcessInfo.processInfo.operatingSystemVersion + return version.majorVersion > 26 || (version.majorVersion == 26 && version.minorVersion >= 4) + ? .cryptex + : .personalized + } } struct DDIDownloadItem { @@ -53,14 +95,19 @@ struct DDIDownloadItem { } enum DDIDownloadCatalog { - private static let baseURL = URL(string: "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Personalized")! - static func items(for paths: DDIPaths) -> [DDIDownloadItem] { - [ + let directory = DDIMountMethod.current == .cryptex ? "Xcode_iOS_DDI_Cryptex" : "Xcode_iOS_DDI_Personalized" + let baseURL = URL(string: "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/\(directory)")! + var items = [ DDIDownloadItem(name: "BuildManifest.plist", destinationPath: paths.manifestPath, url: baseURL.appendingPathComponent("BuildManifest.plist")), DDIDownloadItem(name: "Image.dmg", destinationPath: paths.imagePath, url: baseURL.appendingPathComponent("Image.dmg")), DDIDownloadItem(name: "Image.dmg.trustcache", destinationPath: paths.trustcachePath, url: baseURL.appendingPathComponent("Image.dmg.trustcache")), ] + if DDIMountMethod.current == .cryptex { + items.append(DDIDownloadItem(name: "Image.dmg.cryptex_info", destinationPath: paths.cryptexInfoPath, url: baseURL.appendingPathComponent("Image.dmg.cryptex_info"))) + items.append(DDIDownloadItem(name: "Image.dmg.root_hash", destinationPath: paths.rootHashPath, url: baseURL.appendingPathComponent("Image.dmg.root_hash"))) + } + return items } } @@ -91,6 +138,9 @@ public actor DeveloperDiskImageService { try await downloadFile(from: item.url, to: URL(fileURLWithPath: item.destinationPath)) progress(Double(index + 1) / total, "\(item.name) ready") } + if DDIMountMethod.current == .personalized { + try paths.removeCryptexOnlyFiles() + } } private func downloadFile(from url: URL, to destinationURL: URL) async throws { diff --git a/Sources/StikJIT.swift b/Sources/StikJIT.swift index 6b7b02a..0e44372 100644 --- a/Sources/StikJIT.swift +++ b/Sources/StikJIT.swift @@ -10,11 +10,14 @@ public enum StikJIT { case custom(URL) + case customBase64(String) + var name: String { switch self { case .universal: return "universal" case .legacy: return "legacy" case .custom(let url): return url.lastPathComponent + case .customBase64: return "custom" } } } @@ -144,9 +147,7 @@ public enum StikJIT { progress(.downloadingDDI(fraction: fraction, status: status)) } } - try session.mountDDI(imagePath: paths.imagePath, - trustcachePath: paths.trustcachePath, - manifestPath: paths.manifestPath) { fraction in + try session.mountDDI(paths: paths) { fraction in progress(.mountingDDI(fraction: fraction)) } progress(.verifyingDDI) @@ -183,9 +184,6 @@ public enum StikJIT { paths: DDIPaths, progress: @escaping (Double) -> Void = { _ in }) throws { let session = DDISession(pairingFilePath: pairingFile.path, configuration: configuration) - try session.mountDDI(imagePath: paths.imagePath, - trustcachePath: paths.trustcachePath, - manifestPath: paths.manifestPath, - progress: progress) + try session.mountDDI(paths: paths, progress: progress) } } diff --git a/Sources/SynchronousDDIDownloader.swift b/Sources/SynchronousDDIDownloader.swift index 126d431..a69e1ef 100644 --- a/Sources/SynchronousDDIDownloader.swift +++ b/Sources/SynchronousDDIDownloader.swift @@ -16,6 +16,9 @@ enum SynchronousDDIDownloader { try download(item, session: session) progress(Double(index + 1) / total, "\(item.name) ready") } + if DDIMountMethod.current == .personalized { + try paths.removeCryptexOnlyFiles() + } } private static func download(_ item: DDIDownloadItem, session: URLSession) throws { diff --git a/idevice/idevice.h b/idevice/idevice.h index b429ce2..2aef885 100644 --- a/idevice/idevice.h +++ b/idevice/idevice.h @@ -27,6 +27,26 @@ #define LOCKDOWN_PORT 62078 +/** + * The nonce domain index cryptexes are personalized against + */ +#define IDEVICE_CRYPTEXD_NONCE_DOMAIN_CRYPTEX 2 + +/** + * The `image-type-index` a DeveloperDiskImage install uses + */ +#define IDEVICE_CRYPTEXD_DDI_IMAGE_TYPE_INDEX 10 + +/** + * The `persistence` a DeveloperDiskImage install uses + */ +#define IDEVICE_CRYPTEXD_DDI_PERSISTENCE 2 + +/** + * The `nonce-persistence` a DeveloperDiskImage install uses + */ +#define IDEVICE_CRYPTEXD_DDI_NONCE_PERSISTENCE 1 + typedef enum AfcFopenMode { AfcRdOnly = 1, AfcRw = 2, @@ -44,6 +64,46 @@ typedef enum AfcLinkType { Symbolic = 2, } AfcLinkType; +/** + * The system's light/dark appearance + */ +typedef enum IdeviceUserInterfaceStyle { + IdeviceUserInterfaceStyleLight = 0, + IdeviceUserInterfaceStyleDark = 1, +} IdeviceUserInterfaceStyle; + +/** + * Which of the device's filesystem domains a session is scoped to + */ +typedef enum IdeviceFileServiceDomain { + /** + * An app's own data container. The identifier is the bundle ID. + */ + IdeviceFileServiceDomainAppDataContainer = 1, + /** + * A shared app-group container. The identifier is the group ID. + */ + IdeviceFileServiceDomainAppGroupDataContainer = 2, + /** + * The temporary directory. + */ + IdeviceFileServiceDomainTemporary = 3, + /** + * The system crash-log store. + */ + IdeviceFileServiceDomainSystemCrashLogs = 5, +} IdeviceFileServiceDomain; + +/** + * Network event type discriminant + */ +typedef enum IdeviceNetworkEventType { + InterfaceDetection = 0, + ConnectionDetection = 1, + ConnectionUpdate = 2, + Unknown = 255, +} IdeviceNetworkEventType; + typedef enum IdeviceLoggerError { Success = 0, FileError = -1, @@ -60,6 +120,20 @@ typedef enum IdeviceLogLevel { Trace = 5, } IdeviceLogLevel; +/** + * The outcome of a `CreateStashbag` request. + */ +typedef enum IdeviceStashbagOutcome { + /** + * The device does not need a stashbag; nothing further to do. + */ + NotRequired = 0, + /** + * A stashbag was created and must be committed with the AP ticket. + */ + CommitRequired = 1, +} IdeviceStashbagOutcome; + typedef struct AdapterHandle AdapterHandle; typedef struct AdapterStreamHandle AdapterStreamHandle; @@ -78,19 +152,53 @@ typedef struct AmfiClientHandle AmfiClientHandle; */ typedef struct AppServiceHandle AppServiceHandle; +/** + * Opaque handle to an ApplicationListingClient + */ +typedef struct ApplicationListingHandle ApplicationListingHandle; + typedef struct BtPacketLoggerClientHandle BtPacketLoggerClientHandle; typedef struct CompanionProxyClientHandle CompanionProxyClientHandle; +/** + * Opaque handle to a ConditionInducerClient + */ +typedef struct ConditionInducerHandle ConditionInducerHandle; + +/** + * Opaque handle to a ConfigurationServiceClient + */ +typedef struct ConfigurationServiceHandle ConfigurationServiceHandle; + typedef struct CoreDeviceProxyHandle CoreDeviceProxyHandle; typedef struct CrashReportCopyMobileHandle CrashReportCopyMobileHandle; +/** + * Opaque handle to the payloads a Cryptex1 DeveloperDiskImage install needs + */ +typedef struct Cryptex1AssetsHandle Cryptex1AssetsHandle; + +/** + * Opaque handle to a CryptexdClient + * + * The daemon serves one routine per connection, so every call below consumes + * the handle: it is freed by the call and must not be used again, even when + * the call fails. + */ +typedef struct CryptexdHandle CryptexdHandle; + /** * Opaque handle to a DebugProxyClient */ typedef struct DebugProxyHandle DebugProxyHandle; +/** + * Opaque handle to a DeviceInfoClient + */ +typedef struct DeviceInfoHandle DeviceInfoHandle; + typedef struct DiagnosticsRelayClientHandle DiagnosticsRelayClientHandle; /** @@ -98,10 +206,24 @@ typedef struct DiagnosticsRelayClientHandle DiagnosticsRelayClientHandle; */ typedef struct DiagnosticsServiceHandle DiagnosticsServiceHandle; +typedef struct EnergyMonitorHandle EnergyMonitorHandle; + +/** + * Opaque handle to a FileServiceClient + */ +typedef struct FileServiceHandle FileServiceHandle; + +typedef struct GraphicsHandle GraphicsHandle; + typedef struct HeartbeatClientHandle HeartbeatClientHandle; typedef struct HouseArrestClientHandle HouseArrestClientHandle; +/** + * Opaque handle to an IconServiceClient + */ +typedef struct IconServiceHandle IconServiceHandle; + /** * Opaque C-compatible handle to an Idevice connection */ @@ -114,6 +236,16 @@ typedef struct IdevicePairingFile IdevicePairingFile; typedef struct IdeviceProviderHandle IdeviceProviderHandle; +/** + * An opaque, shareable cancellation flag for an in-flight restore. + * + * Create one with `idevice_restore_cancel_handle_new`, pass it to + * `idevice_restore_run`, and call `idevice_restore_cancel` from another thread to + * request a graceful cancel (the device is rebooted toward recovery). Free it with + * `idevice_restore_cancel_handle_free` once the restore has returned. + */ +typedef struct IdeviceRestoreCancelHandle IdeviceRestoreCancelHandle; + typedef struct IdeviceSocketHandle IdeviceSocketHandle; typedef struct ImageMounterHandle ImageMounterHandle; @@ -122,6 +254,11 @@ typedef struct InstallationProxyClientHandle InstallationProxyClientHandle; typedef struct InstallcoordinationProxyHandle InstallcoordinationProxyHandle; +/** + * Opaque handle to an opened IPSW archive. + */ +typedef struct IpswHandle IpswHandle; + /** * Opaque handle to a ProcessControlClient */ @@ -141,12 +278,34 @@ typedef struct MobileActivationdClientHandle MobileActivationdClientHandle; typedef struct MobileBackup2ClientHandle MobileBackup2ClientHandle; +/** + * Opaque handle to a NetworkMonitorClient + */ +typedef struct NetworkMonitorHandle NetworkMonitorHandle; + typedef struct NotificationProxyClientHandle NotificationProxyClientHandle; +typedef struct NotificationsHandle NotificationsHandle; + typedef struct OsTraceRelayClientHandle OsTraceRelayClientHandle; typedef struct OsTraceRelayReceiverHandle OsTraceRelayReceiverHandle; +/** + * Opaque cancellation token for [`pairable_host_accept`]. + * + * Create one with `pairable_host_cancel_new`, hand it to `pairable_host_accept`, + * and call `pairable_host_cancel_signal` from any other thread to abort the wait. + * Free it with `pairable_host_cancel_free` once the accept has returned. + */ +typedef struct PairableHostCancel PairableHostCancel; + +/** + * Opaque handle holding a generated host identity between + * `pairable_host_prepare` and `pairable_host_accept_fd`. + */ +typedef struct PairableHostHandle PairableHostHandle; + typedef struct PcapdClientHandle PcapdClientHandle; typedef struct PreboardServiceClientHandle PreboardServiceClientHandle; @@ -158,6 +317,21 @@ typedef struct ProcessControlHandle ProcessControlHandle; typedef struct ReadWriteOpaque ReadWriteOpaque; +/** + * Opaque handle to a device in recovery/DFU mode. + */ +typedef struct RecoveryDeviceHandle RecoveryDeviceHandle; + +/** + * Opaque handle to the RemoteXPC-native notification proxy (iOS 17+) + */ +typedef struct RemoteNotificationProxyClientHandle RemoteNotificationProxyClientHandle; + +/** + * Opaque handle to a remote pairing client speaking `RPPairing` over lockdown + */ +typedef struct RemotePairingLockdownHandle RemotePairingLockdownHandle; + /** * Opaque handle to a RemoteServerClient */ @@ -165,6 +339,11 @@ typedef struct RemoteServerHandle RemoteServerHandle; typedef struct RestoreServiceClientHandle RestoreServiceClientHandle; +/** + * Opaque handle to a restore-mode `com.apple.mobile.restored` client. + */ +typedef struct RestoredClientHandle RestoredClientHandle; + /** * Opaque handle to an RPPairing file */ @@ -191,6 +370,11 @@ typedef struct SysdiagnoseStreamHandle SysdiagnoseStreamHandle; typedef struct SyslogRelayClientHandle SyslogRelayClientHandle; +/** + * Opaque handle to a SysmontapClient + */ +typedef struct SysmontapHandle SysmontapHandle; + typedef struct TcpEatObject TcpEatObject; typedef struct TcpFeedObject TcpFeedObject; @@ -205,6 +389,20 @@ typedef struct UsbmuxdListenerHandle UsbmuxdListenerHandle; typedef struct Vec_u64 Vec_u64; +/** + * Opaque handle wrapping a [`WdaBridge`]. + */ +typedef struct WdaBridgeHandle WdaBridgeHandle; + +/** + * Opaque handle wrapping the WDA client state. + * + * The handle owns the provider so that subsequent calls can open fresh + * per-request connections without the caller juggling a separate + * `IdeviceProviderHandle`. + */ +typedef struct WdaClientHandle WdaClientHandle; + typedef struct IdeviceFfiError { int32_t code; int32_t sub_code; @@ -316,16 +514,117 @@ typedef struct SignalResponseC { } SignalResponseC; /** - * C-compatible icon data + * The accessibility color filter's state */ -typedef struct IconDataC { - uint8_t *data; - uintptr_t data_len; - double icon_width; - double icon_height; - double minimum_width; - double minimum_height; -} IconDataC; +typedef struct ColorFilterC { + int enabled; + /** + * The filter preset's name, or NULL if the device didn't report one. + * Free with `idevice_string_free`. + */ + char *filter_type; + /** + * Filter strength, 0.0 to 1.0. Only meaningful when `has_intensity` is 1. + */ + double intensity; + int has_intensity; +} ColorFilterC; + +/** + * A rendered app icon + */ +typedef struct AppIconC { + /** + * PNG-encoded image data + */ + uint8_t *png_data; + uintptr_t png_data_len; + /** + * Icon dimensions in pixels, i.e. the points multiplied by the scale + */ + double pixel_width; + double pixel_height; + /** + * Icon dimensions in points, as actually rendered. May be smaller than + * what was requested. + */ + double width; + double height; + double scale; + /** + * 1 when the device had no real icon for the app and rendered a generic + * placeholder instead + */ + int is_placeholder; +} AppIconC; + +/** + * A cryptex installed on the device + */ +typedef struct InstalledCryptexC { + /** + * Free with `idevice_string_free` + */ + char *identifier; + /** + * Free with `idevice_string_free` + */ + char *version; +} InstalledCryptexC; + +/** + * Which nonce domain a get-nonce or roll-nonce request refers to + */ +typedef struct CryptexNonceDomain { + /** + * When 1, `value` is a nonce domain handle, e.g. a build identity's + * `Cryptex1,NonceDomain`. When 0, it is a domain index, e.g. + * `IDEVICE_CRYPTEXD_NONCE_DOMAIN_CRYPTEX`. + */ + int is_handle; + uint64_t value; +} CryptexNonceDomain; + +/** + * The payloads and parameters one install needs + */ +typedef struct CryptexInstallRequestC { + /** + * The cryptex disk image, i.e. the manifest's `Cryptex1,GenericDmg` + */ + const uint8_t *image; + uintptr_t image_len; + /** + * `Cryptex1,GenericTrustCache` + */ + const uint8_t *trustcache; + uintptr_t trustcache_len; + /** + * The Cryptex1 personalization ticket + */ + const uint8_t *im4m; + uintptr_t im4m_len; + /** + * `Cryptex1,CryptexInfoPlist`, which names and versions the cryptex + */ + const uint8_t *info; + uintptr_t info_len; + /** + * `Cryptex1,GenericVolume` root hash + */ + const uint8_t *volumehash; + uintptr_t volumehash_len; + /** + * The `Cryptex1,*` parameters from the build identity, as a plist + * dictionary. Non-negative integers are sent as uint64, which the daemon + * requires. + */ + plist_t cryptex1_properties; + int64_t image_type_index; + uint64_t persistence; + uint64_t nonce_persistence; + uint64_t auth; +} CryptexInstallRequestC; /** * Represents a debugserver command @@ -336,6 +635,178 @@ typedef struct DebugserverCommandHandle { uintptr_t argv_count; } DebugserverCommandHandle; +/** + * A notification from the mobile notifications instruments channel + */ +typedef struct IdeviceNotificationInfo { + char *notification_type; + int64_t mach_absolute_time; + char *exec_name; + char *app_name; + uint32_t pid; + char *state_description; +} IdeviceNotificationInfo; + +/** + * A single condition profile + */ +typedef struct IdeviceConditionProfile { + char *identifier; + char *description; +} IdeviceConditionProfile; + +/** + * A condition inducer group containing profiles + */ +typedef struct IdeviceConditionGroup { + char *identifier; + struct IdeviceConditionProfile *profiles; + uintptr_t profiles_count; +} IdeviceConditionGroup; + +/** + * A running process on the device + */ +typedef struct IdeviceRunningProcess { + uint32_t pid; + char *name; + char *real_app_name; + bool is_application; + uint64_t start_page_count; +} IdeviceRunningProcess; + +/** + * A parsed per-PID energy sample + */ +typedef struct IdeviceEnergySample { + uint32_t pid; + int64_t timestamp; + double total_energy; + double cpu_energy; + double gpu_energy; + double networking_energy; + double display_energy; + double location_energy; + double appstate_energy; +} IdeviceEnergySample; + +/** + * A graphics sample from tddhe GPU instruments channel + */ +typedef struct IdeviceGraphicsSample { + uint64_t timestamp; + double fps; + uint64_t alloc_system_memory; + uint64_t in_use_system_memory; + uint64_t in_use_system_memory_driver; + char *gpu_bundle_name; + uint64_t recovery_count; +} IdeviceGraphicsSample; + +/** + * A socket address (IPv4 or IPv6), represented as a null-terminated string + port + */ +typedef struct IdeviceSocketAddress { + /** + * Address family (e.g. 2 = AF_INET, 30 = AF_INET6) + */ + uint8_t family; + uint16_t port; + /** + * Null-terminated address string. Must be freed with `idevice_string_free`. + */ + char *addr; +} IdeviceSocketAddress; + +/** + * A network event emitted by the device + */ +typedef struct IdeviceNetworkEvent { + enum IdeviceNetworkEventType event_type; + uint32_t interface_index; + /** + * Null-terminated interface name. Must be freed with `idevice_string_free`. + * Only valid when event_type == InterfaceDetection. + */ + char *interface_name; + struct IdeviceSocketAddress local_addr; + struct IdeviceSocketAddress remote_addr; + /** + * PID of the process owning the connection. Valid for ConnectionDetection. + */ + uint32_t pid; + uint64_t recv_buffer_size; + uint64_t recv_buffer_used; + uint64_t serial_number; + uint32_t kind; + uint64_t rx_packets; + uint64_t rx_bytes; + uint64_t tx_packets; + uint64_t tx_bytes; + uint64_t rx_dups; + uint64_t rx_ooo; + uint64_t tx_retx; + uint64_t min_rtt; + uint64_t avg_rtt; + uint64_t connection_serial; + uint64_t time; + uint64_t unknown_type; +} IdeviceNetworkEvent; + +/** + * Configuration for sysmontap sampling passed over FFI + */ +typedef struct IdeviceSysmontapConfig { + /** + * Sampling interval in milliseconds + */ + uint32_t interval_ms; + /** + * Array of process attribute name strings (null-terminated C strings) + */ + const char *const *process_attributes; + uintptr_t process_attributes_count; + /** + * Array of system attribute name strings (null-terminated C strings) + */ + const char *const *system_attributes; + uintptr_t system_attributes_count; +} IdeviceSysmontapConfig; + +/** + * Progress snapshot passed to `on_progress`. + * + * A session is split into batches of files. `batch_*` describes the batch + * currently streaming; `session_*` accumulates across the whole session. + * Fields are only ever appended to, so a callback compiled against an older + * header stays ABI-compatible. + */ +typedef struct Mobilebackup2BackupProgress { + /** + * Bytes transferred so far in the current batch. + */ + uint64_t batch_bytes_done; + /** + * Bytes the device said this batch contains, or 0 if unknown. Approximate. + */ + uint64_t batch_bytes_total; + /** + * Bytes transferred so far across every batch in this session. Monotonic. + */ + uint64_t session_bytes_done; + /** + * Estimated total bytes for the session, or 0 while not estimable. + * Derived from the device's percentage, so it drifts. Never exact. + */ + uint64_t session_bytes_total; + /** + * Overall progress percentage (0.0-100.0), or negative if not yet known. + * Interpolated within a batch and clamped to be monotonic. Not equal to + * session_bytes_done / session_bytes_total. + */ + double overall_progress; +} Mobilebackup2BackupProgress; + /** * C-compatible delegate for mobilebackup2 operations. * @@ -365,12 +836,16 @@ typedef struct Mobilebackup2BackupDelegateFFI { bool (*exists)(const char *path, void *context); bool (*is_dir)(const char *path, void *context); /** - * Optional progress callback. May be NULL. + * Optional cancellation callback. May be NULL. */ - void (*on_progress)(uint64_t bytes_done, - uint64_t bytes_total, - double overall_progress, - void *context); + bool (*is_cancelled)(void *context); + /** + * Optional progress callback. May be NULL. + * + * `progress` is owned by the caller and only valid for the duration of the + * call; copy out any fields you need to keep. + */ + void (*on_progress)(const struct Mobilebackup2BackupProgress *progress, void *context); } Mobilebackup2BackupDelegateFFI; typedef struct SyslogLabel { @@ -386,8 +861,68 @@ typedef struct OsTraceLog { const char *filename; const char *message; const struct SyslogLabel *label; + /** + * Unique process ID (the activity stream's `procid` field). Equals `pid` + * in practice on iOS. + */ + uint64_t procid; + /** + * ID of the thread that emitted the entry + */ + uint64_t thread_id; + /** + * Load address offset of the log call site within the sender image. Pair + * with `image_uuid` to symbolicate. + */ + uint32_t image_offset; + /** + * UUID of the sender image, i.e. the one named by `image_name` + */ + uint8_t image_uuid[16]; + /** + * UUID of the process' main executable, i.e. the one named by `filename` + */ + uint8_t process_image_uuid[16]; + /** + * Raw monotonic device timestamp in mach ticks + */ + uint64_t mach_timestamp; } OsTraceLog; +/** + * The peer device identity learned during a successful pair-setup. + * + * Free with `rppairing_peer_device_free`. + */ +typedef struct RpPairingPeerDeviceC { + /** + * Peer identifier, the same identifier a later `verifyManualPairing` returns. + */ + char *account_id; + /** + * The device's 16-byte `altIRK`, used to match its mDNS `authTag` records. + */ + uint8_t alt_irk[16]; + /** + * Hardware model identifier, e.g. "AppleTV14,1". + */ + char *model; + /** + * User-visible device name, e.g. "Living Room". + */ + char *name; + /** + * The device's UDID. + */ + char *udid; +} RpPairingPeerDeviceC; + +/** + * Called when the device issues a setup PIN, so the caller can surface it to the + * user. May be NULL. + */ +typedef void (*PairableHostPinCb)(const char *pin, void *context); + /** * Represents a captured device packet from pcapd */ @@ -413,6 +948,164 @@ typedef struct DevicePacketHandle { uintptr_t data_len; } DevicePacketHandle; +/** + * C delegate supplying firmware component bytes by archive path. + * + * `read_component` (required) reads a whole component into a system-allocated + * buffer (ownership transfers to the library, which frees it). The optional + * streaming trio (`open_component`/`read_chunk`/`close_component`) lets large + * source boot objects stream without buffering; when `open_component` is NULL the + * library falls back to buffering via `read_component`. + */ +typedef struct IdeviceRestoreComponentSourceFFI { + void *context; + struct IdeviceFfiError *(*read_component)(const char *path, + uint8_t **out_data, + uintptr_t *out_len, + void *context); + struct IdeviceFfiError *(*open_component)(const char *path, void **out_reader, void *context); + struct IdeviceFfiError *(*read_chunk)(void *reader, + uint8_t *buf, + uintptr_t buf_len, + uintptr_t *out_read, + void *context); + void (*close_component)(void *reader, void *context); +} IdeviceRestoreComponentSourceFFI; + +/** + * C delegate exposing a seekable, sized filesystem (DMG) image for ASR. + */ +typedef struct IdeviceRestoreFilesystemImageFFI { + void *context; + /** + * Returns the total image size in bytes. + */ + struct IdeviceFfiError *(*size)(uint64_t *out_size, void *context); + /** + * Reads up to `len` bytes at `offset` into a system-allocated buffer whose + * ownership transfers to the library. + */ + struct IdeviceFfiError *(*read_at)(uint64_t offset, + uintptr_t len, + uint8_t **out_data, + uintptr_t *out_len, + void *context); +} IdeviceRestoreFilesystemImageFFI; + +/** + * C delegate opening fresh connections to restore-mode data ports. + */ +typedef struct IdeviceRestoreDataPortConnectorFFI { + void *context; + /** + * Connects to `port`, yielding a new [`IdeviceHandle`] (ownership transfers + * to the library). + */ + struct IdeviceFfiError *(*connect)(uint16_t port, + struct IdeviceHandle **out_idevice, + void *context); +} IdeviceRestoreDataPortConnectorFFI; + +/** + * C delegate receiving restore progress callbacks. Any field may be NULL. + */ +typedef struct IdeviceRestoreProgressFFI { + void *context; + /** + * The device's operation code and completion percentage (0–100). + */ + void (*operation)(uint64_t operation, uint64_t progress, void *context); + /** + * A named host step (the `DataType` being serviced). + */ + void (*step)(const char *name, void *context); + void (*transfer)(const char *component, + uint64_t sent, + uint64_t total, + bool has_total, + void *context); +} IdeviceRestoreProgressFFI; + +/** + * C delegate implementing the raw USB surface of a recovery/DFU device. + * + * The library implements the iBoot/DFU protocol on top of these calls, so the + * caller only supplies USB I/O (via nusb, libusb, etc) against the Apple device + * already opened in a recovery/DFU mode. + */ +typedef struct IdeviceRestoreRecoveryTransportFFI { + void *context; + /** + * Host to device control transfer; writes the byte count to `out_transferred`. + */ + struct IdeviceFfiError *(*control_out)(uint8_t request_type, + uint8_t request, + uint16_t value, + uint16_t index, + const uint8_t *data, + uintptr_t data_len, + uint32_t timeout_ms, + uintptr_t *out_transferred, + void *context); + /** + * Device to host control transfer into a system-allocated buffer (ownership + * transfers to the library). + */ + struct IdeviceFfiError *(*control_in)(uint8_t request_type, + uint8_t request, + uint16_t value, + uint16_t index, + uint16_t length, + uint32_t timeout_ms, + uint8_t **out_data, + uintptr_t *out_len, + void *context); + /** + * Bulk OUT transfer; writes the byte count to `out_transferred`. + */ + struct IdeviceFfiError *(*bulk_out)(uint8_t endpoint, + const uint8_t *data, + uintptr_t data_len, + uint32_t timeout_ms, + uintptr_t *out_transferred, + void *context); + /** + * Writes the NUL-terminated USB serial-number string into `buf` + * (capacity `buf_len`). + */ + struct IdeviceFfiError *(*serial_number)(char *buf, uintptr_t buf_len, void *context); + /** + * Returns the device descriptor's `idProduct`. + */ + uint16_t (*product_id)(void *context); + /** + * Selects a configuration. + */ + struct IdeviceFfiError *(*set_configuration)(uint8_t configuration, void *context); + /** + * Claims an interface / alternate setting. + */ + struct IdeviceFfiError *(*claim_interface)(uint8_t iface, uint8_t alt_setting, void *context); + /** + * Resets the device (it re-enumerates afterwards). + */ + struct IdeviceFfiError *(*reset)(void *context); +} IdeviceRestoreRecoveryTransportFFI; + +/** + * C delegate opening FDR trust-channel connections to device ports. + */ +typedef struct IdeviceRestoreFdrConnectorFFI { + void *context; + /** + * Connects to `port`, yielding a new [`IdeviceHandle`] (ownership + * transfers to the library). + */ + struct IdeviceFfiError *(*connect_device_port)(uint16_t port, + struct IdeviceHandle **out_idevice, + void *context); +} IdeviceRestoreFdrConnectorFFI; + /** * C-compatible representation of an RSD service */ @@ -469,6 +1162,22 @@ typedef struct ScreenshotData { uintptr_t length; } ScreenshotData; +/** + * Localhost endpoints exposed by a running WDA bridge. + * + * Pointers in this struct are heap-allocated and must be released with + * `wda_bridge_endpoints_free`. + */ +typedef struct WdaBridgeEndpointsC { + char *udid; + char *wda_url; + char *mjpeg_url; + uint16_t local_http; + uint16_t local_mjpeg; + uint16_t device_http; + uint16_t device_mjpeg; +} WdaBridgeEndpointsC; + /** * Creates a new Idevice connection * @@ -570,6 +1279,14 @@ struct IdeviceFfiError *idevice_start_session(struct IdeviceHandle *idevice, const struct IdevicePairingFile *pairing_file, bool legacy); +/** + * Sets the timeout on async calls such as TCP connections + * + * # Safety + * This function is safe to call from any thread at any time + */ +void idevice_set_global_timeout(uint64_t secs); + /** * Frees an Idevice handle * @@ -1658,16 +2375,47 @@ struct IdeviceFfiError *app_service_send_signal(struct AppServiceHandle *handle, void app_service_free_signal_response(struct SignalResponseC *response); /** - * Fetches an app icon + * Creates a new ConfigurationServiceClient using RSD connection * * # Arguments - * * [`handle`] - The AppServiceClient handle - * * [`bundle_id`] - Bundle identifier of the app - * * [`width`] - Icon width - * * [`height`] - Icon height - * * [`scale`] - Icon scale - * * [`allow_placeholder`] - Whether to allow placeholder icons - * * [`icon_data`] - Pointer to store the icon data (caller must free) + * * [`provider`] - An adapter created by this library + * * [`handshake`] - An RSD handshake from the same provider + * * [`handle`] - Pointer to store the newly created handle + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * `provider` and `handshake` must be valid pointers to handles allocated by this library + * `handle` must be a valid pointer to a location where the handle will be stored + */ +struct IdeviceFfiError *configuration_service_connect_rsd(struct AdapterHandle *provider, + struct RsdHandshakeHandle *handshake, + struct ConfigurationServiceHandle **handle); + +/** + * Creates a new ConfigurationServiceClient from a socket + * + * # Arguments + * * [`socket`] - The socket to use for communication. Consumed regardless of the result. + * * [`handle`] - Pointer to store the newly created handle + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * `socket` must be a valid pointer to a handle allocated by this library + * `handle` must be a valid pointer to a location where the handle will be stored + */ +struct IdeviceFfiError *configuration_service_new(struct ReadWriteOpaque *socket, + struct ConfigurationServiceHandle **handle); + +/** + * Reads the device's light/dark appearance + * + * # Arguments + * * [`handle`] - The ConfigurationServiceClient handle + * * [`style`] - Pointer to store the appearance * * # Returns * An IdeviceFfiError on error, null on success @@ -1675,21 +2423,234 @@ void app_service_free_signal_response(struct SignalResponseC *response); * # Safety * All pointer parameters must be valid */ -struct IdeviceFfiError *app_service_fetch_app_icon(struct AppServiceHandle *handle, - const char *bundle_id, - float width, - float height, - float scale, - int allow_placeholder, - struct IconDataC **icon_data); +struct IdeviceFfiError *configuration_service_get_user_interface_style(struct ConfigurationServiceHandle *handle, + enum IdeviceUserInterfaceStyle *style); /** - * Frees an IconDataC structure + * Switches the device between light and dark appearance + * + * # Arguments + * * [`handle`] - The ConfigurationServiceClient handle + * * [`style`] - The appearance to set + * + * # Returns + * An IdeviceFfiError on error, null on success * * # Safety - * `icon_data` must be a valid pointer allocated by app_service_fetch_app_icon + * `handle` must be a valid pointer to a handle allocated by this library */ -void app_service_free_icon_data(struct IconDataC *icon_data); +struct IdeviceFfiError *configuration_service_set_user_interface_style(struct ConfigurationServiceHandle *handle, + enum IdeviceUserInterfaceStyle style); + +/** + * Sets the system liquid-glass opacity + * + * # Arguments + * * [`handle`] - The ConfigurationServiceClient handle + * * [`opacity`] - The opacity, 0.0 to 1.0 + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library + */ +struct IdeviceFfiError *configuration_service_set_liquid_glass_opacity(struct ConfigurationServiceHandle *handle, + float opacity); + +/** + * Reads the accessibility color filter's state + * + * # Arguments + * * [`handle`] - The ConfigurationServiceClient handle + * * [`filter`] - Pointer to store the state. Free its `filter_type` with + * `idevice_string_free`. + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *configuration_service_get_color_filter(struct ConfigurationServiceHandle *handle, + struct ColorFilterC *filter); + +/** + * Enables or disables the accessibility color filter + * + * # Arguments + * * [`handle`] - The ConfigurationServiceClient handle + * * [`enabled`] - Whether the filter is on + * * [`filter_type`] - The preset to use, e.g. `Protanopia`. Required when enabling, + * ignored otherwise, and may be NULL when disabling. + * * [`intensity`] - Filter strength, 0.0 to 1.0. Ignored unless `has_intensity` is set. + * * [`has_intensity`] - Whether to send `intensity` + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All non-NULL pointer parameters must be valid + */ +struct IdeviceFfiError *configuration_service_set_color_filter(struct ConfigurationServiceHandle *handle, + int enabled, + const char *filter_type, + float intensity, + int has_intensity); + +/** + * Reads the dynamic-type size's name, e.g. `medium` or `large` + * + * # Arguments + * * [`handle`] - The ConfigurationServiceClient handle + * * [`size`] - Pointer to store the name. Free with `idevice_string_free`. + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *configuration_service_get_device_text_size(struct ConfigurationServiceHandle *handle, + char **size); + +/** + * Sets the dynamic-type size by name, e.g. `medium` or `large` + * + * # Arguments + * * [`handle`] - The ConfigurationServiceClient handle + * * [`size`] - The size's name + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *configuration_service_set_device_text_size(struct ConfigurationServiceHandle *handle, + const char *size); + +/** + * Reads whether Reduce Motion is on + * + * # Arguments + * * [`handle`] - The ConfigurationServiceClient handle + * * [`enabled`] - Pointer to store the state + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *configuration_service_get_reduce_motion(struct ConfigurationServiceHandle *handle, + int *enabled); + +/** + * Toggles Reduce Motion + * + * # Arguments + * * [`handle`] - The ConfigurationServiceClient handle + * * [`enabled`] - Whether to turn it on + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library + */ +struct IdeviceFfiError *configuration_service_set_reduce_motion(struct ConfigurationServiceHandle *handle, + int enabled); + +/** + * Reads whether Reduce Transparency is on + * + * # Arguments + * * [`handle`] - The ConfigurationServiceClient handle + * * [`enabled`] - Pointer to store the state + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *configuration_service_get_reduce_transparency(struct ConfigurationServiceHandle *handle, + int *enabled); + +/** + * Toggles Reduce Transparency + * + * # Arguments + * * [`handle`] - The ConfigurationServiceClient handle + * * [`enabled`] - Whether to turn it on + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library + */ +struct IdeviceFfiError *configuration_service_set_reduce_transparency(struct ConfigurationServiceHandle *handle, + int enabled); + +/** + * Reads whether the layout-debug borders overlay is on + * + * # Arguments + * * [`handle`] - The ConfigurationServiceClient handle + * * [`enabled`] - Pointer to store the state + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *configuration_service_get_show_borders(struct ConfigurationServiceHandle *handle, + int *enabled); + +/** + * Toggles the layout-debug borders overlay + * + * # Arguments + * * [`handle`] - The ConfigurationServiceClient handle + * * [`enabled`] - Whether to turn it on + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library + */ +struct IdeviceFfiError *configuration_service_set_show_borders(struct ConfigurationServiceHandle *handle, + int enabled); + +/** + * Toggles Increase Contrast + * + * The device offers no getter for this one. + * + * # Arguments + * * [`handle`] - The ConfigurationServiceClient handle + * * [`enabled`] - Whether to turn it on + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library + */ +struct IdeviceFfiError *configuration_service_set_increase_contrast(struct ConfigurationServiceHandle *handle, + int enabled); + +/** + * Frees a ConfigurationServiceClient handle + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library or NULL + */ +void configuration_service_free(struct ConfigurationServiceHandle *handle); /** * Creates a new DiagnosticsServiceClient using RSD connection @@ -1788,6 +2749,301 @@ void diagnostics_service_free(struct DiagnosticsServiceHandle *handle); */ void sysdiagnose_stream_free(struct SysdiagnoseStreamHandle *handle); +/** + * Creates a new FileServiceClient using RSD connection + * + * This connects the service's control channel, i.e. + * `com.apple.coredevice.fileservice.control`. Downloads additionally need the + * data channel, `com.apple.coredevice.fileservice.data`. + * + * # Arguments + * * [`provider`] - An adapter created by this library + * * [`handshake`] - An RSD handshake from the same provider + * * [`handle`] - Pointer to store the newly created handle + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * `provider` and `handshake` must be valid pointers to handles allocated by this library + * `handle` must be a valid pointer to a location where the handle will be stored + */ +struct IdeviceFfiError *file_service_connect_rsd(struct AdapterHandle *provider, + struct RsdHandshakeHandle *handshake, + struct FileServiceHandle **handle); + +/** + * Creates a new FileServiceClient from a socket + * + * # Arguments + * * [`socket`] - The socket to use for communication. Consumed regardless of the result. + * * [`handle`] - Pointer to store the newly created handle + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * `socket` must be a valid pointer to a handle allocated by this library + * `handle` must be a valid pointer to a location where the handle will be stored + */ +struct IdeviceFfiError *file_service_new(struct ReadWriteOpaque *socket, + struct FileServiceHandle **handle); + +/** + * Opens a session on a domain, which every later command is scoped to + * + * # Arguments + * * [`handle`] - The FileServiceClient handle + * * [`domain`] - The domain to scope the session to + * * [`identifier`] - The container's identifier, i.e. a bundle ID or an app-group ID. + * The domains that don't take one ignore it, and it may be NULL for them. + * * [`session_id`] - Pointer to store the new session's ID, or NULL to ignore it. + * Free with `idevice_string_free`. + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All non-NULL pointer parameters must be valid + */ +struct IdeviceFfiError *file_service_create_session(struct FileServiceHandle *handle, + enum IdeviceFileServiceDomain domain, + const char *identifier, + char **session_id); + +/** + * The session ID from the last `file_service_create_session` + * + * # Arguments + * * [`handle`] - The FileServiceClient handle + * * [`session_id`] - Pointer to store the ID, set to NULL when there is no + * session. Free with `idevice_string_free`. + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *file_service_session_id(struct FileServiceHandle *handle, + char **session_id); + +/** + * Lists a directory, relative to the session's domain root + * + * # Arguments + * * [`handle`] - The FileServiceClient handle + * * [`path`] - The directory to list + * * [`entries`] - Pointer to store the entry names, freed with + * `file_service_free_directory_list` + * * [`len`] - Pointer to store the number of entries + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *file_service_retrieve_directory_list(struct FileServiceHandle *handle, + const char *path, + char ***entries, + uintptr_t *len); + +/** + * Frees the list from `file_service_retrieve_directory_list` + * + * # Safety + * `entries` must be a pointer returned by `file_service_retrieve_directory_list` + * with its reported length, or NULL + */ +void file_service_free_directory_list(char **entries, uintptr_t len); + +/** + * Downloads a file, relative to the session's domain root + * + * The transfer itself runs on the service's data channel, which the caller + * opens by connecting the adapter to the port the RSD handshake reports for + * `com.apple.coredevice.fileservice.data`. + * + * # Arguments + * * [`handle`] - The FileServiceClient handle + * * [`path`] - The file to download + * * [`adapter`] - The adapter the control channel was connected over + * * [`data_port`] - The port of `com.apple.coredevice.fileservice.data` + * * [`data`] - Pointer to store the contents, freed with `idevice_data_free` + * * [`len`] - Pointer to store the number of bytes + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *file_service_retrieve_file(struct FileServiceHandle *handle, + const char *path, + struct AdapterHandle *adapter, + uint16_t data_port, + uint8_t **data, + uintptr_t *len); + +/** + * Downloads a file over a data channel the caller already opened + * + * Like `file_service_retrieve_file`, but takes the data channel itself instead + * of opening one. Note that the device only accepts the connection once the + * control channel has announced the transfer, so a stream opened well in + * advance may have been dropped. + * + * # Arguments + * * [`handle`] - The FileServiceClient handle + * * [`path`] - The file to download + * * [`data_stream`] - The data channel. Consumed regardless of the result. + * * [`data`] - Pointer to store the contents, freed with `idevice_data_free` + * * [`len`] - Pointer to store the number of bytes + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *file_service_retrieve_file_with_stream(struct FileServiceHandle *handle, + const char *path, + struct ReadWriteOpaque *data_stream, + uint8_t **data, + uintptr_t *len); + +/** + * Creates an empty file, relative to the session's domain root + * + * # Arguments + * * [`handle`] - The FileServiceClient handle + * * [`path`] - The file to create + * * [`file_permissions`] - The file's mode, e.g. 0644 + * * [`uid`] - The owning user's ID, e.g. 501 + * * [`gid`] - The owning group's ID, e.g. 501 + * * [`creation_time`] - The creation time to set + * * [`last_modification_time`] - The modification time to set + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *file_service_propose_empty_file(struct FileServiceHandle *handle, + const char *path, + uint32_t file_permissions, + uint32_t uid, + uint32_t gid, + int64_t creation_time, + int64_t last_modification_time); + +/** + * Looks a domain up by the name the device uses, e.g. `appDataContainer` + * + * # Arguments + * * [`name`] - The domain's name + * * [`domain`] - Pointer to store the domain + * + * # Returns + * 1 when the name is known, 0 otherwise + * + * # Safety + * All pointer parameters must be valid + */ +int file_service_domain_from_name(const char *name, enum IdeviceFileServiceDomain *domain); + +/** + * Frees a FileServiceClient handle + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library or NULL + */ +void file_service_free(struct FileServiceHandle *handle); + +/** + * Creates a new IconServiceClient using RSD connection + * + * # Arguments + * * [`provider`] - An adapter created by this library + * * [`handshake`] - An RSD handshake from the same provider + * * [`handle`] - Pointer to store the newly created handle + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * `provider` and `handshake` must be valid pointers to handles allocated by this library + * `handle` must be a valid pointer to a location where the handle will be stored + */ +struct IdeviceFfiError *icon_service_connect_rsd(struct AdapterHandle *provider, + struct RsdHandshakeHandle *handshake, + struct IconServiceHandle **handle); + +/** + * Creates a new IconServiceClient from a socket + * + * # Arguments + * * [`socket`] - The socket to use for communication. Consumed regardless of the result. + * * [`handle`] - Pointer to store the newly created handle + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * `socket` must be a valid pointer to a handle allocated by this library + * `handle` must be a valid pointer to a location where the handle will be stored + */ +struct IdeviceFfiError *icon_service_new(struct ReadWriteOpaque *socket, + struct IconServiceHandle **handle); + +/** + * Fetches an app's icon, rendered as a PNG + * + * # Arguments + * * [`handle`] - The IconServiceClient handle + * * [`bundle_identifier`] - Bundle identifier of the app, or NULL to use `app_path` + * * [`app_path`] - Path of the app on the device, or NULL to use `bundle_identifier` + * * [`width`] - Requested icon width in points + * * [`height`] - Requested icon height in points + * * [`scale`] - Requested icon scale + * * [`allow_placeholder`] - Whether the device may render a generic placeholder + * * [`icon`] - Pointer to store the icon, freed with `icon_service_free_icon` + * + * Exactly one of `bundle_identifier` and `app_path` must be passed. + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All non-NULL pointer parameters must be valid + */ +struct IdeviceFfiError *icon_service_fetch_icon(struct IconServiceHandle *handle, + const char *bundle_identifier, + const char *app_path, + float width, + float height, + float scale, + int allow_placeholder, + struct AppIconC **icon); + +/** + * Frees an AppIconC + * + * # Safety + * `icon` must be a pointer returned by `icon_service_fetch_icon`, or NULL + */ +void icon_service_free_icon(struct AppIconC *icon); + +/** + * Frees an IconServiceClient handle + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library or NULL + */ +void icon_service_free(struct IconServiceHandle *handle); + /** * Automatically creates and connects to Core Device Proxy, returning a client handle * @@ -2123,6 +3379,338 @@ struct IdeviceFfiError *crash_report_flush(struct IdeviceProviderHandle *provide */ void crash_report_client_free(struct CrashReportCopyMobileHandle *handle); +/** + * Creates a new CryptexdClient using RSD connection + * + * # Arguments + * * [`provider`] - An adapter created by this library + * * [`handshake`] - An RSD handshake from the same provider + * * [`handle`] - Pointer to store the newly created handle + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * `provider` and `handshake` must be valid pointers to handles allocated by this library + * `handle` must be a valid pointer to a location where the handle will be stored + */ +struct IdeviceFfiError *cryptexd_connect_rsd(struct AdapterHandle *provider, + struct RsdHandshakeHandle *handshake, + struct CryptexdHandle **handle); + +/** + * Creates a new CryptexdClient from a socket + * + * # Arguments + * * [`socket`] - The socket to use for communication. Consumed regardless of the result. + * * [`handle`] - Pointer to store the newly created handle + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * `socket` must be a valid pointer to a handle allocated by this library + * `handle` must be a valid pointer to a location where the handle will be stored + */ +struct IdeviceFfiError *cryptexd_new(struct ReadWriteOpaque *socket, + struct CryptexdHandle **handle); + +/** + * Reads the device's AppleImage4 chip instance, which identifies it in a + * Cryptex1 personalization request + * + * The keys are the daemon's `img4_chip_*` names, e.g. `img4_chip_chip` + * (ChipID), `img4_chip_bord` (BoardID) and `img4_chip_ecid` (ECID). + * + * # Arguments + * * [`handle`] - The CryptexdClient handle. Consumed by this call. + * * [`identifiers`] - Pointer to store the identifiers + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *cryptexd_read_personalization_identifiers(struct CryptexdHandle *handle, + plist_t *identifiers); + +/** + * Lists the cryptexes installed on the device + * + * # Arguments + * * [`handle`] - The CryptexdClient handle. Consumed by this call. + * * [`cryptexes`] - Pointer to store the list, freed with `cryptexd_free_installed` + * * [`len`] - Pointer to store the number of entries + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *cryptexd_copy_installed(struct CryptexdHandle *handle, + struct InstalledCryptexC **cryptexes, + uintptr_t *len); + +/** + * Frees the list from `cryptexd_copy_installed` + * + * # Safety + * `cryptexes` must be a pointer returned by `cryptexd_copy_installed` with its + * reported length, or NULL + */ +void cryptexd_free_installed(struct InstalledCryptexC *cryptexes, uintptr_t len); + +/** + * Frees an InstalledCryptexC allocated by this library + * + * # Safety + * `cryptex` must be a pointer allocated by this library, or NULL + */ +void cryptexd_free_installed_cryptex(struct InstalledCryptexC *cryptex); + +/** + * Reads a nonce domain's nonce structure + * + * Use `cryptexd_cryptex_nonce` for the nonce a TSS request wants. + * + * # Arguments + * * [`handle`] - The CryptexdClient handle. Consumed by this call. + * * [`domain`] - The nonce domain to read + * * [`nonce`] - Pointer to store the nonce, freed with `idevice_data_free` + * * [`nonce_len`] - Pointer to store the number of bytes + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *cryptexd_get_nonce(struct CryptexdHandle *handle, + struct CryptexNonceDomain domain, + uint8_t **nonce, + uintptr_t *nonce_len); + +/** + * Reads the nonce a Cryptex1 TSS request is personalized against + * + * # Arguments + * * [`handle`] - The CryptexdClient handle. Consumed by this call. + * * [`nonce_domain_handle`] - The build identity's `Cryptex1,NonceDomain` + * * [`nonce`] - Pointer to store the nonce, freed with `idevice_data_free` + * * [`nonce_len`] - Pointer to store the number of bytes + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *cryptexd_cryptex_nonce(struct CryptexdHandle *handle, + uint64_t nonce_domain_handle, + uint8_t **nonce, + uintptr_t *nonce_len); + +/** + * Rolls (regenerates) a nonce domain's nonce, invalidating anything + * personalized against the previous one + * + * # Arguments + * * [`handle`] - The CryptexdClient handle. Consumed by this call. + * * [`domain`] - The nonce domain to roll + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library + */ +struct IdeviceFfiError *cryptexd_roll_nonce(struct CryptexdHandle *handle, + struct CryptexNonceDomain domain); + +/** + * Uninstalls a cryptex by the identifier `cryptexd_copy_installed` reports + * + * # Arguments + * * [`handle`] - The CryptexdClient handle. Consumed by this call. + * * [`identifier`] - The cryptex's identifier + * * [`version`] - The version to scope the uninstall to, or NULL for all of them + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All non-NULL pointer parameters must be valid + */ +struct IdeviceFfiError *cryptexd_uninstall(struct CryptexdHandle *handle, + const char *identifier, + const char *version); + +/** + * Installs a cryptex + * + * # Arguments + * * [`handle`] - The CryptexdClient handle. Consumed by this call. + * * [`request`] - The payloads and parameters to install + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid, and the request's buffers must be + * readable for their stated lengths + */ +struct IdeviceFfiError *cryptexd_install(struct CryptexdHandle *handle, + const struct CryptexInstallRequestC *request); + +/** + * Extracts the nonce from cryptexd's nonce structure + * + * # Arguments + * * [`blob`] - The structure `cryptexd_get_nonce` returned + * * [`blob_len`] - Its length + * * [`nonce`] - Pointer to store the nonce, freed with `idevice_data_free` + * * [`nonce_len`] - Pointer to store the number of bytes + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid, and `blob` must be readable for + * `blob_len` bytes + */ +struct IdeviceFfiError *cryptexd_unwrap_nonce(const uint8_t *blob, + uintptr_t blob_len, + uint8_t **nonce, + uintptr_t *nonce_len); + +/** + * Loads the DeveloperDiskImage payloads from an unpacked DDI `Restore` directory + * + * # Arguments + * * [`restore_dir`] - The directory to read + * * [`handle`] - Pointer to store the newly created handle + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *cryptex1_assets_load(const char *restore_dir, + struct Cryptex1AssetsHandle **handle); + +/** + * Builds the DeveloperDiskImage payloads from buffers the caller already has + * + * # Arguments + * * [`image`] / [`image_len`] - `Cryptex1,GenericDmg` + * * [`trustcache`] / [`trustcache_len`] - `Cryptex1,GenericTrustCache` + * * [`info`] / [`info_len`] - `Cryptex1,CryptexInfoPlist` + * * [`volumehash`] / [`volumehash_len`] - `Cryptex1,GenericVolume` + * * [`build_identity`] - The build identity the payloads came from + * * [`handle`] - Pointer to store the newly created handle + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid, and each buffer must be readable for + * its stated length + */ +struct IdeviceFfiError *cryptex1_assets_from_parts(const uint8_t *image, + uintptr_t image_len, + const uint8_t *trustcache, + uintptr_t trustcache_len, + const uint8_t *info, + uintptr_t info_len, + const uint8_t *volumehash, + uintptr_t volumehash_len, + plist_t build_identity, + struct Cryptex1AssetsHandle **handle); + +/** + * The handle of the nonce domain the assets are personalized against, i.e. the + * build identity's `Cryptex1,NonceDomain` + * + * # Arguments + * * [`handle`] - The assets handle + * * [`nonce_domain`] - Pointer to store the handle + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *cryptex1_assets_nonce_domain(struct Cryptex1AssetsHandle *handle, + uint64_t *nonce_domain); + +/** + * Frees a Cryptex1Assets handle + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library or NULL + */ +void cryptex1_assets_free(struct Cryptex1AssetsHandle *handle); + +/** + * Personalizes and installs the DeveloperDiskImage cryptex end to end + * + * The cryptex equivalent of the image mounter's auto-mount: reads the device's + * personalization identifiers and cryptex nonce, has Apple sign a Cryptex1 + * ticket for them, and installs the assets. Each step opens its own connection + * off the adapter, since the daemon serves one routine per connection. + * + * # Arguments + * * [`provider`] - An adapter created by this library + * * [`handshake`] - An RSD handshake from the same provider + * * [`assets`] - The payloads to install + * * [`installed`] - Pointer to store the installed cryptex, freed with + * `cryptexd_free_installed_cryptex`. May be NULL to ignore it. + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All non-NULL pointer parameters must be valid + */ +struct IdeviceFfiError *cryptexd_install_ddi(struct AdapterHandle *provider, + struct RsdHandshakeHandle *handshake, + struct Cryptex1AssetsHandle *assets, + struct InstalledCryptexC **installed); + +/** + * The installed DeveloperDiskImage cryptex, if there is one + * + * # Arguments + * * [`provider`] - An adapter created by this library + * * [`handshake`] - An RSD handshake from the same provider + * * [`installed`] - Pointer to store the cryptex, set to NULL when no DDI is + * installed. Freed with `cryptexd_free_installed_cryptex`. + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *cryptexd_installed_ddi(struct AdapterHandle *provider, + struct RsdHandshakeHandle *handshake, + struct InstalledCryptexC **installed); + +/** + * Frees a CryptexdClient handle + * + * Only needed for a handle no routine was invoked on: every routine consumes + * the handle it is passed. + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library or NULL + */ +void cryptexd_free(struct CryptexdHandle *handle); + /** * Creates a new DebugserverCommand * @@ -2616,6 +4204,64 @@ struct IdeviceFfiError *location_simulation_set(struct LocationSimulationHandle double latitude, double longitude); +/** + * Frees an IdeviceNotificationInfo and its heap-allocated string fields + * + * # Safety + * `info` must be a valid pointer allocated by this library or NULL + */ +void notifications_info_free(struct IdeviceNotificationInfo *info); + +/** + * Creates a new NotificationsClient from a RemoteServerClient + * + * # Safety + * `server` must be a valid pointer to a handle allocated by this library + * `handle` must be a valid pointer to a location where the handle will be stored + */ +struct IdeviceFfiError *notifications_new(struct RemoteServerHandle *server, + struct NotificationsHandle **handle); + +/** + * Frees a NotificationsClient handle + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library or NULL + */ +void notifications_free(struct NotificationsHandle *handle); + +/** + * Enables application state and memory notifications on the device. + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library + */ +struct IdeviceFfiError *notifications_start(struct NotificationsHandle *handle); + +/** + * Disables application state and memory notifications on the device. + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library + */ +struct IdeviceFfiError *notifications_stop(struct NotificationsHandle *handle); + +/** + * Reads the next notification pushed by the device. Blocks until a notification arrives. + * + * # Arguments + * * [`handle`] - The NotificationsClient handle + * * [`info_out`] - On success, set to a heap-allocated IdeviceNotificationInfo + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointers must be valid and non-null. Free the info with `notifications_info_free`. + */ +struct IdeviceFfiError *notifications_get_next(struct NotificationsHandle *handle, + struct IdeviceNotificationInfo **info_out); + /** * Creates a new ProcessControlClient from a RemoteServerClient * @@ -2809,6 +4455,517 @@ struct IdeviceFfiError *screenshot_client_take_screenshot(struct ScreenshotClien uint8_t **data, uintptr_t *len); +/** + * Creates a new ApplicationListingClient from a RemoteServerClient + * + * # Safety + * `server` must be a valid pointer to a handle allocated by this library + * `handle` must be a valid pointer to a location where the handle will be stored + */ +struct IdeviceFfiError *application_listing_new(struct RemoteServerHandle *server, + struct ApplicationListingHandle **handle); + +/** + * Frees an ApplicationListingClient handle + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library or NULL + */ +void application_listing_free(struct ApplicationListingHandle *handle); + +/** + * Returns the list of installed applications as an array of plist dictionaries + * + * # Arguments + * * [`handle`] - The ApplicationListingClient handle + * * [`apps_out`] - On success, set to a heap-allocated array of plist_t values (each is a dict) + * * [`count_out`] - On success, set to the number of apps returned + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointers must be valid and non-null. + * Free the returned array with `idevice_plist_array_free`. + */ +struct IdeviceFfiError *application_listing_get_apps(struct ApplicationListingHandle *handle, + plist_t **apps_out, + uintptr_t *count_out); + +/** + * Creates a new ConditionInducerClient from a RemoteServerClient + * + * # Safety + * `server` must be a valid pointer to a handle allocated by this library + * `handle` must be a valid pointer to a location where the handle will be stored + */ +struct IdeviceFfiError *condition_inducer_new(struct RemoteServerHandle *server, + struct ConditionInducerHandle **handle); + +/** + * Frees a ConditionInducerClient handle + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library or NULL + */ +void condition_inducer_free(struct ConditionInducerHandle *handle); + +/** + * Frees a single IdeviceConditionGroup and all its heap-allocated fields + * + * # Safety + * `group` must be a valid pointer allocated by this library or NULL + */ +void condition_inducer_group_free(struct IdeviceConditionGroup *group); + +/** + * Frees an array of IdeviceConditionGroup pointers + * + * # Safety + * `groups` must be a valid pointer to an array of length `count` allocated by this library, + * or NULL + */ +void condition_inducer_groups_free(struct IdeviceConditionGroup **groups, uintptr_t count); + +/** + * Returns the available condition inducer groups + * + * # Arguments + * * [`handle`] - The ConditionInducerClient handle + * * [`groups_out`] - On success, set to a heap-allocated array of group pointers + * * [`count_out`] - On success, set to the number of groups returned + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointers must be valid and non-null. Free with `condition_inducer_groups_free`. + */ +struct IdeviceFfiError *condition_inducer_available_conditions(struct ConditionInducerHandle *handle, + struct IdeviceConditionGroup ***groups_out, + uintptr_t *count_out); + +/** + * Enables a specific condition profile + * + * # Arguments + * * [`handle`] - The ConditionInducerClient handle + * * [`condition_identifier`] - The condition group identifier (null-terminated C string) + * * [`profile_identifier`] - The profile identifier within the group (null-terminated C string) + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointers must be valid and non-null + */ +struct IdeviceFfiError *condition_inducer_enable(struct ConditionInducerHandle *handle, + const char *condition_identifier, + const char *profile_identifier); + +/** + * Disables the currently active condition + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library + */ +struct IdeviceFfiError *condition_inducer_disable(struct ConditionInducerHandle *handle); + +/** + * Creates a new DeviceInfoClient from a RemoteServerClient + * + * # Safety + * `server` must be a valid pointer to a handle allocated by this library + * `handle` must be a valid pointer to a location where the handle will be stored + */ +struct IdeviceFfiError *device_info_new(struct RemoteServerHandle *server, + struct DeviceInfoHandle **handle); + +/** + * Frees a DeviceInfoClient handle + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library or NULL + */ +void device_info_free(struct DeviceInfoHandle *handle); + +/** + * Frees a single IdeviceRunningProcess struct and its heap-allocated strings + * + * # Safety + * `process` must be a valid pointer allocated by this library or NULL + */ +void device_info_running_process_free(struct IdeviceRunningProcess *process); + +/** + * Frees an array of IdeviceRunningProcess pointers + * + * # Safety + * `processes` must be a valid pointer to an array of length `count` allocated by this library, + * or NULL + */ +void device_info_running_processes_free(struct IdeviceRunningProcess **processes, uintptr_t count); + +/** + * Returns the list of running processes on the device + * + * # Arguments + * * [`handle`] - The DeviceInfoClient handle + * * [`processes`] - On success, set to a heap-allocated array of process pointers + * * [`count`] - On success, set to the number of processes returned + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointers must be valid and non-null + */ +struct IdeviceFfiError *device_info_running_processes(struct DeviceInfoHandle *handle, + struct IdeviceRunningProcess ***processes, + uintptr_t *count); + +/** + * Returns the executable name for the given PID + * + * # Safety + * All pointers must be valid and non-null. Free the returned string with `idevice_string_free`. + */ +struct IdeviceFfiError *device_info_execname_for_pid(struct DeviceInfoHandle *handle, + uint32_t pid, + char **name_out); + +/** + * Returns whether the given PID is currently running + * + * # Safety + * All pointers must be valid and non-null + */ +struct IdeviceFfiError *device_info_is_running_pid(struct DeviceInfoHandle *handle, + uint32_t pid, + bool *result); + +/** + * Returns hardware information as a plist dictionary + * + * # Safety + * All pointers must be valid and non-null. Free the returned plist with `plist_free`. + */ +struct IdeviceFfiError *device_info_hardware_information(struct DeviceInfoHandle *handle, + plist_t *plist_out); + +/** + * Returns network information as a plist dictionary + * + * # Safety + * All pointers must be valid and non-null. Free the returned plist with `plist_free`. + */ +struct IdeviceFfiError *device_info_network_information(struct DeviceInfoHandle *handle, + plist_t *plist_out); + +/** + * Returns the mach kernel name + * + * # Safety + * All pointers must be valid and non-null. Free the returned string with `idevice_string_free`. + */ +struct IdeviceFfiError *device_info_mach_kernel_name(struct DeviceInfoHandle *handle, + char **name_out); + +/** + * Frees a null-terminated string array allocated by this library + * + * # Safety + * `strings` must be a valid pointer to an array of `count` C strings allocated by this library, + * or NULL + */ +void device_info_string_array_free(char **strings, uintptr_t count); + +/** + * Returns the list of sysmon process attribute names + * + * # Safety + * All pointers must be valid and non-null. Free with `device_info_string_array_free`. + */ +struct IdeviceFfiError *device_info_sysmon_process_attributes(struct DeviceInfoHandle *handle, + char ***attrs_out, + uintptr_t *count_out); + +/** + * Returns the list of sysmon system attribute names + * + * # Safety + * All pointers must be valid and non-null. Free with `device_info_string_array_free`. + */ +struct IdeviceFfiError *device_info_sysmon_system_attributes(struct DeviceInfoHandle *handle, + char ***attrs_out, + uintptr_t *count_out); + +/** + * Returns directory listing for the given path + * + * # Safety + * All pointers must be valid and non-null. Free with `device_info_string_array_free`. + */ +struct IdeviceFfiError *device_info_directory_listing(struct DeviceInfoHandle *handle, + const char *path, + char ***entries_out, + uintptr_t *count_out); + +/** + * Creates a new EnergyMonitorClient from a RemoteServerClient + * + * # Safety + * `server` must be a valid pointer to a handle allocated by this library + * `handle` must be a valid pointer to a location where the handle will be stored + */ +struct IdeviceFfiError *energy_monitor_new(struct RemoteServerHandle *server, + struct EnergyMonitorHandle **handle); + +/** + * Frees an EnergyMonitorClient handle + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library or NULL + */ +void energy_monitor_free(struct EnergyMonitorHandle *handle); + +/** + * Starts energy sampling for the given PIDs. + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library. + * If `pids` is non-null it must point to at least `pids_count` readable `u32` values. + */ +struct IdeviceFfiError *energy_monitor_start_sampling(struct EnergyMonitorHandle *handle, + const uint32_t *pids, + uintptr_t pids_count); + +/** + * Stops energy sampling for the given PIDs. + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library. + * If `pids` is non-null it must point to at least `pids_count` readable `u32` values. + */ +struct IdeviceFfiError *energy_monitor_stop_sampling(struct EnergyMonitorHandle *handle, + const uint32_t *pids, + uintptr_t pids_count); + +/** + * Requests a one-shot energy sample and parses the response. + * + * # Arguments + * * [`handle`] - The EnergyMonitorClient handle + * * [`pids`] - Pointer to an array of u32 PIDs to sample + * * [`pids_count`] - Number of elements in `pids` + * * [`samples_out`] - On success, set to a heap-allocated array of IdeviceEnergySample + * * [`samples_count_out`] - On success, set to the number of samples + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All output pointers must be valid and non-null. Free the array with + * `energy_monitor_samples_free`. + */ +struct IdeviceFfiError *energy_monitor_sample_attributes(struct EnergyMonitorHandle *handle, + const uint32_t *pids, + uintptr_t pids_count, + struct IdeviceEnergySample **samples_out, + uintptr_t *samples_count_out); + +/** + * Frees an array of IdeviceEnergySample allocated by `energy_monitor_sample_attributes`. + * + * # Safety + * `samples` must be a pointer returned by this library with the matching `count`, or NULL + */ +void energy_monitor_samples_free(struct IdeviceEnergySample *samples, uintptr_t count); + +/** + * Frees an IdeviceGraphicsSample and its heap-allocated string field + * + * # Safety + * `sample` must be a valid pointer allocated by this library or NULL + */ +void graphics_sample_free(struct IdeviceGraphicsSample *sample); + +/** + * Creates a new GraphicsClient from a RemoteServerClient + * + * # Safety + * `server` must be a valid pointer to a handle allocated by this library + * `handle` must be a valid pointer to a location where the handle will be stored + */ +struct IdeviceFfiError *graphics_new(struct RemoteServerHandle *server, + struct GraphicsHandle **handle); + +/** + * Frees a GraphicsClient handle + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library or NULL + */ +void graphics_free(struct GraphicsHandle *handle); + +/** + * Starts graphics sampling at the given interval. Consumes the device's initial reply internally. + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library + */ +struct IdeviceFfiError *graphics_start_sampling(struct GraphicsHandle *handle, double interval); + +/** + * Stops graphics sampling. + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library + */ +struct IdeviceFfiError *graphics_stop_sampling(struct GraphicsHandle *handle); + +/** + * Reads the next graphics data frame pushed by the device. Blocks until a frame arrives. + * + * # Arguments + * * [`handle`] - The GraphicsClient handle + * * [`sample_out`] - On success, set to a heap-allocated IdeviceGraphicsSample + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointers must be valid and non-null. Free the sample with `graphics_sample_free`. + */ +struct IdeviceFfiError *graphics_next_sample(struct GraphicsHandle *handle, + struct IdeviceGraphicsSample **sample_out); + +/** + * Frees an IdeviceNetworkEvent and its heap-allocated string fields + * + * # Safety + * `event` must be a valid pointer allocated by this library or NULL + */ +void network_monitor_event_free(struct IdeviceNetworkEvent *event); + +/** + * Creates a new NetworkMonitorClient from a RemoteServerClient + * + * # Safety + * `server` must be a valid pointer to a handle allocated by this library + * `handle` must be a valid pointer to a location where the handle will be stored + */ +struct IdeviceFfiError *network_monitor_new(struct RemoteServerHandle *server, + struct NetworkMonitorHandle **handle); + +/** + * Frees a NetworkMonitorClient handle + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library or NULL + */ +void network_monitor_free(struct NetworkMonitorHandle *handle); + +/** + * Starts network monitoring. No reply is expected. + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library + */ +struct IdeviceFfiError *network_monitor_start(struct NetworkMonitorHandle *handle); + +/** + * Stops network monitoring. + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library + */ +struct IdeviceFfiError *network_monitor_stop(struct NetworkMonitorHandle *handle); + +/** + * Reads the next network event pushed by the device. Blocks until an event arrives. + * + * # Arguments + * * [`handle`] - The NetworkMonitorClient handle + * * [`event_out`] - On success, set to a heap-allocated IdeviceNetworkEvent + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointers must be valid and non-null. Free the event with `network_monitor_event_free`. + */ +struct IdeviceFfiError *network_monitor_next_event(struct NetworkMonitorHandle *handle, + struct IdeviceNetworkEvent **event_out); + +/** + * Creates a new SysmontapClient from a RemoteServerClient + * + * # Safety + * `server` must be a valid pointer to a handle allocated by this library + * `handle` must be a valid pointer to a location where the handle will be stored + */ +struct IdeviceFfiError *sysmontap_new(struct RemoteServerHandle *server, + struct SysmontapHandle **handle); + +/** + * Frees a SysmontapClient handle + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library or NULL + */ +void sysmontap_free(struct SysmontapHandle *handle); + +/** + * Sends configuration to the device + * + * # Arguments + * * [`handle`] - The SysmontapClient handle + * * [`config`] - Pointer to an IdeviceSysmontapConfig struct + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointers must be valid and non-null. String arrays must contain valid C strings. + */ +struct IdeviceFfiError *sysmontap_set_config(struct SysmontapHandle *handle, + const struct IdeviceSysmontapConfig *config); + +/** + * Starts sampling. Consumes the device's initial ack message internally. + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library + */ +struct IdeviceFfiError *sysmontap_start(struct SysmontapHandle *handle); + +/** + * Stops sampling. + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library + */ +struct IdeviceFfiError *sysmontap_stop(struct SysmontapHandle *handle); + +/** + * Reads the next sysmontap sample. Blocks until data arrives. + * + * Each output plist is a dictionary (or NULL if that field was not present in the sample): + * - `processes_out`: dict of PID → per-process attribute array + * - `system_out`: plist array of system attribute values + * - `cpu_usage_out`: dict of CPU usage keys + * + * The caller is responsible for freeing non-NULL plists with `plist_free`. + * + * # Safety + * `handle` must be valid and non-null. Output pointers may be null to ignore that field. + */ +struct IdeviceFfiError *sysmontap_next_sample(struct SysmontapHandle *handle, + plist_t *processes_out, + plist_t *system_out, + plist_t *cpu_usage_out); + /** * Frees the IdeviceFfiError * @@ -4113,6 +6270,7 @@ struct IdeviceFfiError *image_mounter_mount_personalized(struct ImageMounterHand * # Returns * An IdeviceFfiError on error, null on success * + * # Safety * All pointers must be valid (except optional ones which can be null) */ struct IdeviceFfiError *image_mounter_mount_personalized_rsd(struct ImageMounterHandle *client, @@ -4544,6 +6702,118 @@ void notification_proxy_free_string(char *s); */ void notification_proxy_client_free(struct NotificationProxyClientHandle *handle); +/** + * Connects to the remote notification proxy over RSD + * + * # Arguments + * * [`provider`] - An adapter created by this library + * * [`handshake`] - An RSD handshake from the same provider + * * [`client`] - On success, will be set to point to a newly allocated handle + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * `provider` and `handshake` must be valid pointers to handles allocated by this library + * `client` must be a valid, non-null pointer to a location where the handle will be stored + */ +struct IdeviceFfiError *remote_notification_proxy_connect_rsd(struct AdapterHandle *provider, + struct RsdHandshakeHandle *handshake, + struct RemoteNotificationProxyClientHandle **client); + +/** + * Creates a remote notification proxy client from a socket + * + * # Arguments + * * [`socket`] - The socket to use for communication. Consumed regardless of the result. + * * [`client`] - On success, will be set to point to a newly allocated handle + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * `socket` must be a valid pointer to a handle allocated by this library + * `client` must be a valid, non-null pointer to a location where the handle will be stored + */ +struct IdeviceFfiError *remote_notification_proxy_new(struct ReadWriteOpaque *socket, + struct RemoteNotificationProxyClientHandle **client); + +/** + * Posts a notification on the device + * + * # Arguments + * * [`client`] - A valid handle + * * [`name`] - The notification to post + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *remote_notification_proxy_post(struct RemoteNotificationProxyClientHandle *client, + const char *name); + +/** + * Registers interest in a notification, after which the device relays it back + * whenever it fires + * + * # Arguments + * * [`client`] - A valid handle + * * [`name`] - The notification to observe + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *remote_notification_proxy_observe(struct RemoteNotificationProxyClientHandle *client, + const char *name); + +/** + * Registers interest in several notifications at once + * + * # Arguments + * * [`client`] - A valid handle + * * [`names`] - The notifications to observe + * * [`len`] - How many notifications were passed + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid, and `names` must hold `len` strings + */ +struct IdeviceFfiError *remote_notification_proxy_observe_multiple(struct RemoteNotificationProxyClientHandle *client, + const char *const *names, + uintptr_t len); + +/** + * Waits for the next relayed notification and returns its name + * + * # Arguments + * * [`client`] - A valid handle + * * [`name_out`] - On success, set to the notification's name. Free with + * `notification_proxy_free_string`. + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *remote_notification_proxy_receive(struct RemoteNotificationProxyClientHandle *client, + char **name_out); + +/** + * Frees a remote notification proxy handle + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library, or NULL + */ +void remote_notification_proxy_free(struct RemoteNotificationProxyClientHandle *handle); + /** * Connects to the relay with the given provider * @@ -4666,6 +6936,109 @@ struct IdeviceFfiError *os_trace_relay_next(struct OsTraceRelayReceiverHandle *c */ void os_trace_relay_free_log(struct OsTraceLog *log); +/** + * Creates a cancellation token for `pairable_host_accept`. + * + * Returns NULL only if allocation fails. Free with `pairable_host_cancel_free`. + */ +struct PairableHostCancel *pairable_host_cancel_new(void); + +/** + * Signals a cancellation token, unblocking the `pairable_host_accept` it was passed + * to. That call returns the `CanceledByUser` error. + * + * Safe to call from any thread, before or during the accept, and safe to call more + * than once. Cancelling a token that was never passed to an accept, or one whose + * accept already returned, does nothing. + * + * # Safety + * `cancel` must be a pointer returned by `pairable_host_cancel_new` that has not yet + * been freed. + */ +void pairable_host_cancel_signal(const struct PairableHostCancel *cancel); + +/** + * Frees a cancellation token. + * + * The in-flight accept holds its own reference to the shared state, so freeing the + * token while an accept is still running is safe — it just means nothing can cancel + * that accept any more. + * + * # Safety + * `cancel` must be a pointer returned by `pairable_host_cancel_new` or NULL, and must + * not be used afterwards. + */ +void pairable_host_cancel_free(struct PairableHostCancel *cancel); + +/** + * Advertises this computer as a pairable host and accepts a single device-initiated + * pairing. + * + * This blocks the calling thread until a device discovers the advertised + * `_remotepairing-pairable-host._tcp` service, connects, and the pairing either + * completes or fails — or until `cancel` is signalled from another thread. While the + * pairing is in progress `pin_callback` is invoked once with the 6-digit setup code + * that the user must type into the device. + * + * On success a freshly generated [`RpPairingFileHandle`] is written to + * `out_pairing_file`; it carries this host's long-term keys plus the paired + * device's `altIRK`. Persist it (and `out_host_alt_irk`, see below) so the device + * keeps recognizing this host on future connections. + * + * # Arguments + * * `name` - human-readable name shown on the device (e.g. "Jackson's MacBook Pro"). + * * `model` - hardware model identifier shown on the device. `NULL` defaults to + * `"Mac17,7"`. iOS treats the host as a computer, so keep this a Mac identifier. + * * `port` - TCP port to listen on. `0` picks a free port. + * * `pin_callback` - invoked with the setup PIN to display. May be `NULL`. + * * `pin_context` - opaque pointer passed back to `pin_callback`. + * * `cancel` - optional cancellation token from `pairable_host_cancel_new`. Signal it + * from another thread to abort the wait (e.g. the user dismissed the pairing UI). + * `NULL` means the call can only be ended by a device connecting. Without one there + * is no way to stop advertising short of exiting the process. + * * `out_host_alt_irk` - optional. If non-NULL, must point to a 16-byte buffer that + * receives the host's generated `altIRK` (needed to re-advertise this host so an + * already-paired device recognizes it). May be `NULL`. + * * `out_peer_device` - optional. If non-NULL, receives the paired device's identity + * (name, model, UDID, `altIRK`), which the caller must free with + * `rppairing_peer_device_free`. May be `NULL`. + * * `out_pairing_file` - receives the resulting pairing file on success. + * + * # Safety + * `name` must be a valid null-terminated C string. `model` must be NULL or a valid + * null-terminated C string. `cancel` must be NULL or a live token from + * `pairable_host_cancel_new`. `out_host_alt_irk` must be NULL or point to at least 16 + * writable bytes. `out_peer_device` must be NULL or a valid writable pointer. + * `out_pairing_file` must be valid and non-null. + */ +struct IdeviceFfiError *pairable_host_accept(const char *name, + const char *model, + uint16_t port, + void (*pin_callback)(const char *pin, void *context), + void *pin_context, + const struct PairableHostCancel *cancel, + uint8_t *out_host_alt_irk, + struct RpPairingPeerDeviceC **out_peer_device, + struct RpPairingFileHandle **out_pairing_file); + +/** + * Same as `pairable_host_accept`, with explicit pairable-host policy options. + * + * # Safety + * Same pointer validity requirements as `pairable_host_accept`. + */ +struct IdeviceFfiError *pairable_host_accept_with_options(const char *name, + const char *model, + uint16_t port, + bool allows_pinless_pairing, + void (*pin_callback)(const char *pin, + void *context), + void *pin_context, + const struct PairableHostCancel *cancel, + uint8_t *out_host_alt_irk, + struct RpPairingPeerDeviceC **out_peer_device, + struct RpPairingFileHandle **out_pairing_file); + /** * Reads a pairing file from the specified path * @@ -4734,6 +7107,95 @@ struct IdeviceFfiError *idevice_pairing_file_serialize(const struct IdevicePairi */ void idevice_pairing_file_free(struct IdevicePairingFile *pairing_file); +/** + * Generates a fresh host identity and returns the data a caller needs to publish + * its own `_remotepairing-pairable-host._tcp` Bonjour service. + * + * # Arguments + * * `name` - human-readable name shown on the device. + * * `model` - hardware model shown on the device. `NULL` defaults to `"Mac17,7"`. + * * `allows_pinless_pairing` - if true, advertise pinless pairing and use the + * all-zero setup code expected by that flow; if false, generate a random PIN. + * * `out_handle` - receives the host handle; pass it to `pairable_host_accept_fd` + * and free it with `pairable_host_free`. + * * `out_service_id` - receives the Bonjour service instance name. Free with + * `idevice_string_free`. + * * `out_txt_data`/`out_txt_len` - receive an XML plist dictionary of the TXT + * records to publish. Free with `idevice_data_free`. + * * `out_host_alt_irk` - optional. If non-NULL, must point to a 16-byte buffer + * that receives the generated host `altIRK`; persist it with the pairing file. + * + * A fresh identity is generated on every call. + * + * # Safety + * `name` must be a valid null-terminated C string. `model` must be NULL or a + * valid null-terminated C string. All required out-pointers must be valid and + * non-null. `out_host_alt_irk` must be NULL or point to at least 16 writable bytes. + */ +struct IdeviceFfiError *pairable_host_prepare(const char *name, + const char *model, + bool allows_pinless_pairing, + struct PairableHostHandle **out_handle, + char **out_service_id, + uint8_t **out_txt_data, + uintptr_t *out_txt_len, + uint8_t *out_host_alt_irk); + +/** + * Backwards-compatible alias for AltStore's original function name. + * Prefer `pairable_host_prepare` for new callers. + * + * # Safety + * Same requirements as `pairable_host_prepare`, except `model` is required and + * pinless pairing is disabled. + */ +struct IdeviceFfiError *pairable_host_new(const char *name, + const char *model, + struct PairableHostHandle **out_handle, + char **out_service_id, + uint8_t **out_txt_data, + uintptr_t *out_txt_len); + +/** + * Runs pair-setup against a device that has already connected to `fd`. + * + * Blocks the calling thread until pairing succeeds or fails. The fd is duplicated + * before use, so the caller keeps ownership of the original socket. + * + * # Safety + * `handle` must be a valid handle from `pairable_host_prepare` or + * `pairable_host_new`. `fd` must be a valid connected TCP socket. + * `out_pairing_file` must be valid and non-null. `out_peer_device` must be NULL + * or a valid writable pointer. `pin_cb`/`ctx` must stay valid until this call returns. + */ +struct IdeviceFfiError *pairable_host_accept_fd(struct PairableHostHandle *handle, + int32_t fd, + PairableHostPinCb pin_cb, + void *ctx, + struct RpPairingPeerDeviceC **out_peer_device, + struct RpPairingFileHandle **out_pairing_file); + +/** + * Backwards-compatible alias for AltStore's original function name. + * Prefer `pairable_host_accept_fd` for new callers. + * + * # Safety + * Same requirements as `pairable_host_accept_fd`. + */ +struct IdeviceFfiError *pairable_host_handshake(struct PairableHostHandle *handle, + int32_t fd, + PairableHostPinCb pin_cb, + void *ctx, + struct RpPairingFileHandle **out_pairing_file); + +/** + * Frees a `PairableHostHandle`. + * + * # Safety + * `handle` must be a handle from `pairable_host_prepare`/`pairable_host_new`, or NULL. + */ +void pairable_host_free(struct PairableHostHandle *handle); + /** * Automatically creates and connects to pcapd, returning a client handle. * Note that this service only works over USB or through RSD. @@ -4886,12 +7348,14 @@ struct IdeviceFfiError *preboard_service_new(struct IdeviceHandle *socket, struct PreboardServiceClientHandle **client); /** - * Creates a stashbag on the device + * Creates a stashbag on the device from a local preboard manifest (will prompt + * for the passcode on the device), writing the outcome to `out_outcome` * * # Arguments * * `client` - A valid PreboardServiceClient handle * * `manifest` - Pointer to the manifest data * * `manifest_len` - Length of the manifest data + * * `out_outcome` - On success, set to whether a commit is required * * # Returns * An IdeviceFfiError on error, null on success @@ -4899,10 +7363,12 @@ struct IdeviceFfiError *preboard_service_new(struct IdeviceHandle *socket, * # Safety * `client` must be a valid pointer to a handle allocated by this library * `manifest` must be a valid pointer to `manifest_len` bytes of data + * `out_outcome` must be a valid, non-null pointer */ struct IdeviceFfiError *preboard_service_create_stashbag(struct PreboardServiceClientHandle *client, const uint8_t *manifest, - uintptr_t manifest_len); + uintptr_t manifest_len, + enum IdeviceStashbagOutcome *out_outcome); /** * Commits a stashbag on the device @@ -5013,6 +7479,619 @@ struct IdeviceFfiError *usbmuxd_provider_new(struct UsbmuxdAddrHandle *addr, struct IdeviceFfiError *idevice_provider_get_pairing_file(struct IdeviceProviderHandle *provider, struct IdevicePairingFile **pairing_file); +/** + * Connects to `remotepairingdeviced` over lockdown + * + * # Arguments + * * [`provider`] - An IdeviceProvider + * * [`sending_host`] - The name this computer identifies itself by, the same + * value the wireless flow uses + * * [`handle`] - Pointer to store the newly created handle + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *remote_pairing_lockdown_connect(struct IdeviceProviderHandle *provider, + const char *sending_host, + struct RemotePairingLockdownHandle **handle); + +/** + * Wraps an existing lockdown connection to `remotepairingdeviced` + * + * # Arguments + * * [`socket`] - A connection to `com.apple.dt.remotepairingdeviced.lockdown`. + * Consumed regardless of the result. + * * [`sending_host`] - The name this computer identifies itself by + * * [`handle`] - Pointer to store the newly created handle + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *remote_pairing_lockdown_new(struct IdeviceHandle *socket, + const char *sending_host, + struct RemotePairingLockdownHandle **handle); + +/** + * Runs the control channel's handshake and returns what the device reports + * about itself + * + * # Arguments + * * [`handle`] - The client handle + * * [`handshake`] - Pointer to store the device's response + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *remote_pairing_lockdown_attempt_pair_verify(struct RemotePairingLockdownHandle *handle, + plist_t *handshake); + +/** + * Checks whether the device still recognizes a pairing record + * + * The handshake must have run first, i.e. + * `remote_pairing_lockdown_attempt_pair_verify`. + * + * # Arguments + * * [`handle`] - The client handle + * * [`pairing_file`] - The RPPairing file to validate + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *remote_pairing_lockdown_validate_pairing(struct RemotePairingLockdownHandle *handle, + struct RpPairingFileHandle *pairing_file); + +/** + * Pairs with the device, saving the record into `pairing_file` + * + * # Arguments + * * [`handle`] - The client handle + * * [`pairing_file`] - The RPPairing file to pair with, e.g. a fresh one from + * `rp_pairing_file_generate`. Updated in place on success, so write it out + * afterwards to keep the pairing. + * * [`pin`] - The PIN to answer a Trust prompt with, or NULL for `000000`. + * Pairing over USB is promptless, so the device should never ask. + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All non-NULL pointer parameters must be valid + */ +struct IdeviceFfiError *remote_pairing_lockdown_pair(struct RemotePairingLockdownHandle *handle, + struct RpPairingFileHandle *pairing_file, + const char *pin); + +/** + * Pairs only if the device doesn't already recognize the pairing record + * + * Runs the handshake, validates `pairing_file`, and pairs when that fails. + * + * # Arguments + * * [`handle`] - The client handle + * * [`pairing_file`] - The RPPairing file to validate or pair with. Updated in + * place when pairing happens, so write it out afterwards. + * * [`pin`] - The PIN to answer a Trust prompt with, or NULL for `000000` + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All non-NULL pointer parameters must be valid + */ +struct IdeviceFfiError *remote_pairing_lockdown_connect_pairing(struct RemotePairingLockdownHandle *handle, + struct RpPairingFileHandle *pairing_file, + const char *pin); + +/** + * The encryption key established during pairing, used as the TLS-PSK for + * tunnel connections + * + * # Arguments + * * [`handle`] - The client handle + * * [`key`] - Pointer to store the key, freed with `idevice_data_free` + * * [`key_len`] - Pointer to store the number of bytes + * + * # Returns + * An IdeviceFfiError on error, null on success + * + * # Safety + * All pointer parameters must be valid + */ +struct IdeviceFfiError *remote_pairing_lockdown_encryption_key(struct RemotePairingLockdownHandle *handle, + uint8_t **key, + uintptr_t *key_len); + +/** + * Frees a remote pairing lockdown handle + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library or NULL + */ +void remote_pairing_lockdown_free(struct RemotePairingLockdownHandle *handle); + +/** + * Connects to `restored` over an existing [`IdeviceHandle`] (consumes it). + * + * # Safety + * `idevice` is consumed and must not be used afterwards. `out_client` must be a + * valid, non-null location for the resulting handle. + */ +struct IdeviceFfiError *idevice_restored_connect(struct IdeviceHandle *idevice, + struct RestoredClientHandle **out_client); + +/** + * Finds a restore-mode device by ECID over usbmux and connects to `restored`. + * + * After a normal to restore transition the device re-enumerates with a new usbmux + * id, so this polls the device list and matches on `HardwareInfo.UniqueChipID`, + * retrying until `timeout_ms` elapses. + * + * # Safety + * `addr` must be a valid `UsbmuxdAddrHandle` (it is borrowed, not consumed); + * `out_client` must be valid; `label` a valid C string or NULL. + */ +struct IdeviceFfiError *idevice_restored_connect_by_ecid(struct UsbmuxdAddrHandle *addr, + uint64_t ecid, + const char *label, + uint64_t timeout_ms, + struct RestoredClientHandle **out_client); + +/** + * Reads the device's ECID (from `HardwareInfo`). + * + * # Safety + * `client` and `out_ecid` must be valid, non-null pointers. + */ +struct IdeviceFfiError *idevice_restored_get_ecid(struct RestoredClientHandle *client, + uint64_t *out_ecid); + +/** + * Reads the usbmux `device_id` the client was found on. + * + * Only meaningful when the client was created with + * `idevice_restored_connect_by_ecid`; writes `true` to `out_has_device_id` in + * that case (and the id to `out_device_id`), or `false` otherwise (e.g. clients + * built from an existing `Idevice`). Pass the id to + * `idevice_restore_connect_usb_port` so data-port / FDR connections target this + * same device. + * + * # Safety + * `client`, `out_device_id`, `out_has_device_id` must be valid, non-null pointers. + */ +struct IdeviceFfiError *idevice_restored_get_device_id(struct RestoredClientHandle *client, + uint32_t *out_device_id, + bool *out_has_device_id); + +/** + * Frees a [`RestoredClientHandle`]. + * + * # Safety + * `client` must be a handle allocated by this library, or NULL. + */ +void idevice_restored_free(struct RestoredClientHandle *client); + +/** + * Connects to `port` on the usbmux device identified by `device_id`, returning a + * new [`IdeviceHandle`]. A convenience for restore data-port and FDR connectors. + * + * `device_id` must be the id `idevice_restored_get_device_id` reported for the + * restore-mode client, so the connection targets the device being restored. + * + * The entire find-device-and-connect sequence runs in one async task; splitting + * it across separate blocking calls corrupts the shared tokio I/O state, so this + * is the supported way to build those connectors from C. + * + * This is a single attempt (the restore state machine retries data-port + * connects itself); it errors rather than blocking when the device or port is + * not yet available. + * + * # Safety + * `addr` must be a valid `UsbmuxdAddrHandle` (it is borrowed, not consumed); + * `out_idevice` must be valid; `label` a valid C string or NULL. + */ +struct IdeviceFfiError *idevice_restore_connect_usb_port(struct UsbmuxdAddrHandle *addr, + uint32_t device_id, + uint16_t port, + const char *label, + struct IdeviceHandle **out_idevice); + +/** + * Allocates a cancellation handle. + * + * # Safety + * `out_handle` must be a valid, non-null location for the handle pointer. + */ +struct IdeviceFfiError *idevice_restore_cancel_handle_new(struct IdeviceRestoreCancelHandle **out_handle); + +/** + * Requests cancellation of the restore this handle was passed to. + * + * Safe to call from any thread while the restore runs; it is a no-op if `handle` + * is NULL. + * + * # Safety + * `handle` must be a valid handle from `idevice_restore_cancel_handle_new` (or NULL). + */ +void idevice_restore_cancel(struct IdeviceRestoreCancelHandle *handle); + +/** + * Frees a cancellation handle. + * + * # Safety + * `handle` must be a valid handle from `idevice_restore_cancel_handle_new` (or NULL) + * and must not be used after this call. + */ +void idevice_restore_cancel_handle_free(struct IdeviceRestoreCancelHandle *handle); + +/** + * Builds the default iOS `RestoreOptions` dictionary sent with `StartRestore`. + * + * The caller may tweak the returned plist before passing it to + * `idevice_restore_run`, and must free it with `plist_free`. + * + * # Safety + * `out_options` must be a valid, non-null location for the plist. + */ +struct IdeviceFfiError *idevice_restore_options_new(plist_t *out_options); + +/** + * Drives the restore-mode state machine to completion. + * + * Sends `StartRestore` with `options`, then services the device's data requests + * (personalizing components with `tss_ticket`, streaming the filesystem over + * ASR, proxying its key requests) until the device reports success. + * + * # Arguments + * * `client` - connected [`RestoredClientHandle`]. + * * `build_identity` - the selected build-identity dictionary (plist). + * * `board_id`, `chip_id`, `ecid` - device identifiers. + * * `tss_ticket`/`tss_ticket_len` - the `ApImg4Ticket` (IM4M) from TSS. + * * `components` - component-source delegate (required). + * * `filesystem` - filesystem-image delegate, or NULL for a restore that sends + * no filesystem. + * * `data_ports` - data-port connector delegate (required). + * * `progress` - progress delegate, or NULL. + * * `cancel` - cancellation handle from `idevice_restore_cancel_handle_new`, or + * NULL. When another thread calls `idevice_restore_cancel` on it, the restore + * stops and the device is rebooted toward recovery (returning a `Cancelled` + * error). + * * `options` - the `RestoreOptions` plist (see `idevice_restore_options_new`). + * + * # Safety + * All non-NULL pointers must be valid for the duration of the call, and each + * delegate struct must remain valid until this returns. + */ +struct IdeviceFfiError *idevice_restore_run(struct RestoredClientHandle *client, + plist_t build_identity, + uint64_t board_id, + uint64_t chip_id, + uint64_t ecid, + const uint8_t *tss_ticket, + uintptr_t tss_ticket_len, + struct IdeviceRestoreComponentSourceFFI *components, + struct IdeviceRestoreFilesystemImageFFI *filesystem, + struct IdeviceRestoreDataPortConnectorFFI *data_ports, + struct IdeviceRestoreProgressFFI *progress, + struct IdeviceRestoreCancelHandle *cancel, + plist_t options); + +/** + * Opens an IPSW archive from a filesystem path. + * + * # Safety + * `path` must be a valid C string; `out_ipsw` a valid, non-null location. + */ +struct IdeviceFfiError *idevice_ipsw_open(const char *path, struct IpswHandle **out_ipsw); + +/** + * Reads and parses the archive's `BuildManifest.plist`. + * + * # Safety + * `ipsw` must be a valid handle; `out_manifest` a valid, non-null location. The + * returned plist must be freed with `plist_free`. + */ +struct IdeviceFfiError *idevice_ipsw_build_manifest(struct IpswHandle *ipsw, plist_t *out_manifest); + +/** + * Reads a component named in `build_identity` into a caller-freed buffer. + * + * # Safety + * `ipsw`, `name`, `out_data`, `out_len` must be valid. Free the buffer with + * `idevice_data_free`. + */ +struct IdeviceFfiError *idevice_ipsw_read_component(struct IpswHandle *ipsw, + plist_t build_identity, + const char *name, + uint8_t **out_data, + uintptr_t *out_len); + +/** + * Reads an arbitrary archive entry by exact path into a caller-freed buffer. + * + * # Safety + * `ipsw`, `path`, `out_data`, `out_len` must be valid. Free with + * `idevice_data_free`. + */ +struct IdeviceFfiError *idevice_ipsw_read_file(struct IpswHandle *ipsw, + const char *path, + uint8_t **out_data, + uintptr_t *out_len); + +/** + * Extracts an archive entry to a file on disk (streamed, for large images). + * + * # Safety + * `ipsw`, `entry_path`, `dest_path` must be valid C strings. + */ +struct IdeviceFfiError *idevice_ipsw_extract_to_file(struct IpswHandle *ipsw, + const char *entry_path, + const char *dest_path); + +/** + * Frees an [`IpswHandle`]. + * + * # Safety + * `ipsw` must be a handle allocated by this library, or NULL. + */ +void idevice_ipsw_free(struct IpswHandle *ipsw); + +/** + * Selects the `BuildIdentity` matching `board_id`/`chip_id` (and, when non-NULL, + * `restore_behavior`, e.g. "Erase"/"Update") from a `BuildManifest` plist. + * + * # Safety + * `build_manifest`, `out_identity` must be valid. The result plist must be freed + * with `plist_free`. + */ +struct IdeviceFfiError *idevice_restore_select_build_identity(plist_t build_manifest, + uint64_t board_id, + uint64_t chip_id, + const char *restore_behavior, + plist_t *out_identity); + +/** + * Resolves the archive path of a component from a build identity's `Manifest`. + * + * # Safety + * `build_identity`, `name`, `out_path` must be valid. Free the string with + * `idevice_string_free`. + */ +struct IdeviceFfiError *idevice_restore_component_path(plist_t build_identity, + const char *name, + char **out_path); + +/** + * Fetches the AP `ApImg4Ticket` (IM4M) from Apple's TSS server for a build + * identity and device, returning the ticket bytes. + * + * `ap_nonce`/`sep_nonce` may be NULL (length 0); a NULL `sep_nonce` is signed + * with a zeroed nonce. + * + * # Safety + * `build_identity`, `out_ticket`, `out_ticket_len` must be valid. Free the ticket + * with `idevice_data_free`. + */ +struct IdeviceFfiError *idevice_restore_fetch_ap_ticket(plist_t build_identity, + uint64_t board_id, + uint64_t chip_id, + uint64_t ecid, + const uint8_t *ap_nonce, + uintptr_t ap_nonce_len, + const uint8_t *sep_nonce, + uintptr_t sep_nonce_len, + uint8_t **out_ticket, + uintptr_t *out_ticket_len); + +/** + * Stitches an `IM4P` component with the `ApImg4Ticket` into a personalized + * `IMG4` the device will accept. + * + * `fourcc` is either NULL (keep the payload's own type) or a pointer to exactly + * four bytes to re-tag the payload with (required for some `Restore*` components; + * see the library's `restore_fourcc_override`). + * + * # Safety + * `im4p`, `ticket`, `out_data`, `out_len` must be valid. If non-NULL, `fourcc` + * must point to 4 readable bytes. Free the buffer with `idevice_data_free`. + */ +struct IdeviceFfiError *idevice_img4_stitch_component(const uint8_t *im4p, + uintptr_t im4p_len, + const uint8_t *ticket, + uintptr_t ticket_len, + const uint8_t *fourcc, + uint8_t **out_data, + uintptr_t *out_len); + +/** + * Returns the four-character code a `Restore*` component must be re-tagged with, + * if any, writing four bytes to `out_fourcc`. + * + * Returns `true` and fills `out_fourcc` when the component needs re-tagging; + * returns `false` and leaves `out_fourcc` untouched otherwise. + * + * # Safety + * `component_name` must be a valid C string; `out_fourcc` must point to 4 + * writable bytes. + */ +bool idevice_img4_restore_fourcc_override(const char *component_name, uint8_t *out_fourcc); + +/** + * Returns the components iBoot loads during the restore boot, in manifest order, + * as a newline-separated, NUL-terminated string (empty when none). + * + * # Safety + * `build_identity`, `out_names` must be valid. Free the string with + * `idevice_string_free`. + */ +struct IdeviceFfiError *idevice_restore_boot_component_names(plist_t build_identity, + char **out_names); + +/** + * Builds the local (unsigned) `IM4M` preboard manifest for a stashbag request + * from a build identity, into a caller-freed buffer. + * + * # Safety + * `build_identity`, `out_data`, `out_len` must be valid. Free the buffer with + * `idevice_data_free`. + */ +struct IdeviceFfiError *idevice_restore_build_preboard_manifest(plist_t build_identity, + uint64_t board_id, + uint64_t chip_id, + uint8_t **out_data, + uintptr_t *out_len); + +/** + * Opens a recovery/DFU device over a caller-supplied transport delegate. + * + * The `transport` struct is copied by value; the caller may free its own + * storage after this returns (the `context` pointer must stay valid). + * + * # Safety + * `transport` and `out_device` must be valid, non-null pointers. + */ +struct IdeviceFfiError *idevice_recovery_device_new(const struct IdeviceRestoreRecoveryTransportFFI *transport, + struct RecoveryDeviceHandle **out_device); + +/** + * Sends an iBoot command (NUL-terminated), with an explicit `bRequest`. + * + * # Safety + * `device`, `command` must be valid. + */ +struct IdeviceFfiError *idevice_recovery_send_command(struct RecoveryDeviceHandle *device, + const char *command, + uint8_t b_request); + +/** + * Uploads a firmware image (bulk in recovery mode, chunked control transfers + * in DFU mode). + * + * # Safety + * `device`, `data` must be valid. + */ +struct IdeviceFfiError *idevice_recovery_send_buffer(struct RecoveryDeviceHandle *device, + const uint8_t *data, + uintptr_t len); + +/** + * Reads an environment variable via `getenv` into a caller-freed buffer. + * + * # Safety + * `device`, `name`, `out_data`, `out_len` must be valid. Free with + * `idevice_data_free`. + */ +struct IdeviceFfiError *idevice_recovery_getenv(struct RecoveryDeviceHandle *device, + const char *name, + uint8_t **out_data, + uintptr_t *out_len); + +/** + * Sets an environment variable via `setenv`. + * + * # Safety + * `device`, `name`, `value` must be valid. + */ +struct IdeviceFfiError *idevice_recovery_setenv(struct RecoveryDeviceHandle *device, + const char *name, + const char *value); + +/** + * Enables or disables auto-boot and persists it (`saveenv`). + * + * # Safety + * `device` must be valid. + */ +struct IdeviceFfiError *idevice_recovery_set_autoboot(struct RecoveryDeviceHandle *device, + bool enable); + +/** + * Issues the zero-length `finish_transfer` control request. + * + * # Safety + * `device` must be valid. + */ +struct IdeviceFfiError *idevice_recovery_finish_transfer(struct RecoveryDeviceHandle *device); + +/** + * Reboots the device. + * + * # Safety + * `device` must be valid. + */ +struct IdeviceFfiError *idevice_recovery_reboot(struct RecoveryDeviceHandle *device); + +/** + * Returns the device's USB `idProduct` (identifying its mode), and whether it + * is a recovery (iBoot) mode as opposed to DFU/WTF. + * + * # Safety + * `device`, `out_product_id`, `out_is_recovery` must be valid. + */ +struct IdeviceFfiError *idevice_recovery_get_mode(struct RecoveryDeviceHandle *device, + uint16_t *out_product_id, + bool *out_is_recovery); + +/** + * Fills device identifiers parsed from the recovery serial string. + * + * Each `has_*` output is set to whether the corresponding value was present; + * missing values leave their `out_*` untouched. + * + * # Safety + * All non-null out-pointers must be valid. + */ +struct IdeviceFfiError *idevice_recovery_get_info(struct RecoveryDeviceHandle *device, + uint64_t *out_cpid, + bool *out_has_cpid, + uint64_t *out_bdid, + bool *out_has_bdid, + uint64_t *out_ecid, + bool *out_has_ecid); + +/** + * Returns the AP nonce (`NONC`) from the recovery serial, if present, into a + * caller-freed buffer. Returns `true` when a nonce was present. + * + * # Safety + * `device`, `out_data`, `out_len` must be valid. Free with `idevice_data_free`. + */ +bool idevice_recovery_get_ap_nonce(struct RecoveryDeviceHandle *device, + uint8_t **out_data, + uintptr_t *out_len); + +/** + * Frees a [`RecoveryDeviceHandle`]. + * + * # Safety + * `device` must be a handle allocated by this library, or NULL. + */ +void idevice_recovery_device_free(struct RecoveryDeviceHandle *device); + +/** + * Starts the FDR trust channel: control handshake, then a background listener + * running for the rest of the restore. + * + * The `connector` struct is copied by value (its `context` must stay valid for + * the duration of the restore). + * + * # Safety + * `connector` must be a valid, non-null pointer. + */ +struct IdeviceFfiError *idevice_restore_fdr_start(const struct IdeviceRestoreFdrConnectorFFI *connector); + /** * Creates a new RestoreServiceClient from a ReadWrite stream * @@ -5214,6 +8293,15 @@ struct IdeviceFfiError *rp_pairing_file_write(struct RpPairingFileHandle *handle */ void rp_pairing_file_free(struct RpPairingFileHandle *handle); +/** + * Frees a peer device struct and its heap-allocated string fields. + * + * # Safety + * `peer_device` must be a pointer returned by `rppairing_pair_network` or + * `pairable_host_accept`, or NULL. + */ +void rppairing_peer_device_free(struct RpPairingPeerDeviceC *peer_device); + /** * Creates a new RSD handshake from a ReadWrite connection * @@ -5768,8 +8856,12 @@ struct IdeviceFfiError *tunnel_create_remotexpc(const idevice_sockaddr *addr, * Use this when connecting to a device discovered via `_remotepairing._tcp`. * The connection goes: direct TCP → RPPairing (JSON) → tunnel. * - * This path only supports pair-verify (existing pairing file required). - * For initial pairing, use `tunnel_pair_usb`. + * `pairing_file` is used for pair-verify. If verification fails (typically + * because the device has never been paired with this host) a full pair-setup + * runs on the same connection and `pairing_file` is updated in place, so the + * caller should persist it afterwards regardless of whether it was freshly + * generated. + * * * # Safety * All pointer arguments must be valid and non-null (except `pin_callback`/`pin_context`). @@ -5784,6 +8876,38 @@ struct IdeviceFfiError *tunnel_create_rppairing(const idevice_sockaddr *addr, struct AdapterHandle **out_adapter, struct RsdHandshakeHandle **out_handshake); +/** + * Pairs with a device over the network via raw RPPairing, without creating a tunnel. + * + * This is for tvOS. + * + * On iOS `tunnel_create_rppairing` handles both halves on its own; this function + * is only needed there if you want to pair and connect as separate steps. + * + * # Arguments + * * `addr` / `addr_len` - address of the pairing service to connect to. + * * `hostname` - name this host presents to the device. + * * `pairing_file` - borrowed, not consumed. Updated in place on success. Pass a + * freshly generated file (`rp_pairing_file_generate`) for a first-time pairing. + * * `pin_callback` / `pin_context` - invoked to obtain the PIN shown on the + * device. May be `NULL`. + * * `out_peer_device` - optional. If non-NULL, receives the paired device's + * identity, which the caller must free with `rppairing_peer_device_free`. Only + * written when a pair-setup actually ran; a successful pair-verify leaves it + * NULL. + * + * # Safety + * All pointer arguments must be valid and non-null except `pin_callback`, + * `pin_context`, and `out_peer_device`. + */ +struct IdeviceFfiError *rppairing_pair_network(const idevice_sockaddr *addr, + idevice_socklen_t addr_len, + const char *hostname, + struct RpPairingFileHandle *pairing_file, + const char *(*pin_callback)(void *context), + void *pin_context, + struct RpPairingPeerDeviceC **out_peer_device); + /** * Connects to a usbmuxd instance over TCP * @@ -6120,6 +9244,543 @@ uint32_t idevice_usbmuxd_device_get_device_id(const struct UsbmuxdDeviceHandle * */ uint8_t idevice_usbmuxd_device_get_connection_type(const struct UsbmuxdDeviceHandle *device); +/** + * Creates a new WDA client bound to the given provider. + * + * # Arguments + * * [`provider`] - An IdeviceProvider. The provider is consumed and may not + * be used again, regardless of whether this call succeeds or fails. + * * [`handle`] - On success, set to a newly allocated WdaClientHandle. + * + * # Returns + * An IdeviceFfiError on error, null on success. + * + * # Safety + * `provider` must be a valid pointer to a handle allocated by this library. + * The provider is consumed, and may not be used again. + * `handle` must be a valid, non-null pointer to a location where the handle will be stored. + */ +struct IdeviceFfiError *wda_client_new(struct IdeviceProviderHandle *provider, + struct WdaClientHandle **handle); + +/** + * Frees a WDA client handle. + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library or NULL. + */ +void wda_client_free(struct WdaClientHandle *handle); + +/** + * Sets the device-side WDA HTTP and MJPEG ports. + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library. + */ +struct IdeviceFfiError *wda_client_set_ports(struct WdaClientHandle *handle, + uint16_t http, + uint16_t mjpeg); + +/** + * Sets the per-request timeout in milliseconds. + * + * # Safety + * `handle` must be a valid pointer to a handle allocated by this library. + */ +struct IdeviceFfiError *wda_client_set_timeout_ms(struct WdaClientHandle *handle, uint64_t ms); + +/** + * Reads the configured device-side WDA ports. + * + * # Safety + * All pointers must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_get_ports(struct WdaClientHandle *handle, + uint16_t *out_http, + uint16_t *out_mjpeg); + +/** + * Returns the currently tracked session id, or NULL if none. + * + * # Arguments + * * [`handle`] - The WDA client handle. + * * [`out_str`] - On success, set to a heap-allocated UTF-8 string, or NULL + * if no session is tracked. Free with `idevice_string_free` if non-null. + * + * # Safety + * All pointers must be valid; `out_str` must be non-null. + */ +struct IdeviceFfiError *wda_client_session_id(struct WdaClientHandle *handle, char **out_str); + +/** + * Fetches `/status` from the WDA HTTP endpoint and returns the JSON response. + * + * # Arguments + * * [`handle`] - The WDA client handle. + * * [`out_json`] - On success, set to a heap-allocated JSON string. Free with + * `idevice_string_free`. + * + * # Safety + * All pointers must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_status(struct WdaClientHandle *handle, char **out_json); + +/** + * Waits until WDA begins responding on its HTTP endpoint. + * + * # Safety + * All pointers must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_wait_until_ready(struct WdaClientHandle *handle, + uint64_t timeout_ms, + char **out_json); + +/** + * Starts a WDA session and stores the resulting session id on the handle. + * + * # Arguments + * * [`handle`] - The WDA client handle. + * * [`bundle_id`] - Optional bundle identifier; pass NULL for an anonymous session. + * * [`out_session_id`] - On success, set to a heap-allocated UTF-8 string. + * Free with `idevice_string_free`. + * + * # Safety + * `handle` and `out_session_id` must be valid and non-null. `bundle_id` may be NULL. + */ +struct IdeviceFfiError *wda_client_start_session(struct WdaClientHandle *handle, + const char *bundle_id, + char **out_session_id); + +/** + * Deletes a WDA session. + * + * # Safety + * `handle` and `session_id` must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_delete_session(struct WdaClientHandle *handle, + const char *session_id); + +/** + * Finds a single element and returns its WDA element id. + * + * # Safety + * `handle`, `using`, `value`, and `out_element_id` must be valid and non-null. + * `session_id` may be NULL to use the handle's tracked session. + */ +struct IdeviceFfiError *wda_client_find_element(struct WdaClientHandle *handle, + const char *using_, + const char *value, + const char *session_id, + char **out_element_id); + +/** + * Finds multiple elements and returns their WDA element ids. + * + * # Arguments + * * [`out_array`] - On success, set to a heap-allocated array of NUL-terminated strings. + * * [`out_count`] - On success, set to the number of strings. + * + * Free the array with `wda_client_string_array_free`. + * + * # Safety + * All non-optional pointers must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_find_elements(struct WdaClientHandle *handle, + const char *using_, + const char *value, + const char *session_id, + char ***out_array, + uintptr_t *out_count); + +/** + * Frees an array of strings allocated by `wda_client_find_elements`. + * + * # Safety + * `arr` must be a pointer returned by `wda_client_find_elements` with the + * matching `count`, or NULL. + */ +void wda_client_string_array_free(char **arr, uintptr_t count); + +/** + * Returns a raw attribute value as a JSON-encoded string. + * + * # Safety + * All non-optional pointers must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_element_attribute(struct WdaClientHandle *handle, + const char *element_id, + const char *name, + const char *session_id, + char **out_json); + +/** + * Returns the element text-like value as a string. + * + * # Safety + * All non-optional pointers must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_element_text(struct WdaClientHandle *handle, + const char *element_id, + const char *session_id, + char **out_str); + +/** + * Returns the element bounds rectangle as a JSON-encoded string. + * + * # Safety + * All non-optional pointers must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_element_rect(struct WdaClientHandle *handle, + const char *element_id, + const char *session_id, + char **out_json); + +/** + * Returns whether an element is displayed. + * + * # Safety + * All non-optional pointers must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_element_displayed(struct WdaClientHandle *handle, + const char *element_id, + const char *session_id, + bool *out_bool); + +/** + * Returns whether an element is enabled. + * + * # Safety + * All non-optional pointers must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_element_enabled(struct WdaClientHandle *handle, + const char *element_id, + const char *session_id, + bool *out_bool); + +/** + * Returns whether an element is selected. + * + * # Safety + * All non-optional pointers must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_element_selected(struct WdaClientHandle *handle, + const char *element_id, + const char *session_id, + bool *out_bool); + +/** + * Clicks an element by its WDA element id. + * + * # Safety + * `handle` and `element_id` must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_click(struct WdaClientHandle *handle, + const char *element_id, + const char *session_id); + +/** + * Sends text input to the currently focused element. + * + * # Safety + * `handle` and `text` must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_send_keys(struct WdaClientHandle *handle, + const char *text, + const char *session_id); + +/** + * Presses a hardware button through WDA. + * + * # Safety + * `handle` and `name` must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_press_button(struct WdaClientHandle *handle, + const char *name, + const char *session_id); + +/** + * Unlocks the device via WDA. + * + * # Safety + * `handle` must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_unlock(struct WdaClientHandle *handle, const char *session_id); + +/** + * Swipes from one coordinate to another. + * + * # Safety + * `handle` must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_swipe(struct WdaClientHandle *handle, + int64_t start_x, + int64_t start_y, + int64_t end_x, + int64_t end_y, + double duration, + const char *session_id); + +/** + * Performs a tap gesture. + * + * `Option` arguments are encoded as `(has, value)` pairs. When `has_*` + * is false the underlying value is ignored. + * + * # Safety + * `handle` must be valid and non-null. Optional pointers may be NULL. + */ +struct IdeviceFfiError *wda_client_tap(struct WdaClientHandle *handle, + bool has_x, + double x, + bool has_y, + double y, + const char *element_id, + const char *session_id); + +/** + * Performs a double-tap gesture. + * + * # Safety + * `handle` must be valid and non-null. Optional pointers may be NULL. + */ +struct IdeviceFfiError *wda_client_double_tap(struct WdaClientHandle *handle, + bool has_x, + double x, + bool has_y, + double y, + const char *element_id, + const char *session_id); + +/** + * Performs a long-press gesture. + * + * # Safety + * `handle` must be valid and non-null. Optional pointers may be NULL. + */ +struct IdeviceFfiError *wda_client_touch_and_hold(struct WdaClientHandle *handle, + double duration, + bool has_x, + double x, + bool has_y, + double y, + const char *element_id, + const char *session_id); + +/** + * Scrolls the current view or an element using a WDA mobile command. + * + * `Option` arguments are encoded as `(has, value)`. + * + * # Safety + * `handle` must be valid and non-null. Optional string arguments may be NULL. + */ +struct IdeviceFfiError *wda_client_scroll(struct WdaClientHandle *handle, + const char *direction, + const char *name, + const char *predicate_string, + bool has_to_visible, + bool to_visible, + const char *element_id, + const char *session_id); + +/** + * Returns the current UI source tree as XML. + * + * # Safety + * `handle` and `out_str` must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_source(struct WdaClientHandle *handle, + const char *session_id, + char **out_str); + +/** + * Returns a PNG screenshot as raw bytes. + * + * # Arguments + * * [`out_bytes`] - On success, set to a heap-allocated PNG buffer. + * * [`out_len`] - On success, set to the buffer length in bytes. + * + * Free the buffer with `idevice_data_free`. + * + * # Safety + * All non-optional pointers must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_screenshot(struct WdaClientHandle *handle, + const char *session_id, + uint8_t **out_bytes, + uintptr_t *out_len); + +/** + * Returns the current window size payload from WDA. + * + * # Safety + * All non-optional pointers must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_window_size(struct WdaClientHandle *handle, + const char *session_id, + char **out_json); + +/** + * Returns the current viewport rectangle. + * + * # Safety + * All non-optional pointers must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_viewport_rect(struct WdaClientHandle *handle, + const char *session_id, + char **out_json); + +/** + * Returns the current orientation as a string. + * + * # Safety + * All non-optional pointers must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_orientation(struct WdaClientHandle *handle, + const char *session_id, + char **out_str); + +/** + * Launches or activates an application via WDA. + * + * # Arguments + * * [`bundle_id`] - The bundle identifier of the app to launch. + * * [`arguments`] - Optional array of argument strings; pass NULL for none. + * * [`arguments_count`] - Number of strings in `arguments`; ignored if NULL. + * * [`environment_json`] - Optional JSON object string of environment variables; pass NULL for none. + * + * # Safety + * `handle`, `bundle_id`, and `out_json` must be valid and non-null. Optional + * pointers may be NULL. + */ +struct IdeviceFfiError *wda_client_launch_app(struct WdaClientHandle *handle, + const char *bundle_id, + const char *const *arguments, + uintptr_t arguments_count, + const char *environment_json, + const char *session_id, + char **out_json); + +/** + * Activates an already running application. + * + * # Safety + * `handle`, `bundle_id`, and `out_json` must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_activate_app(struct WdaClientHandle *handle, + const char *bundle_id, + const char *session_id, + char **out_json); + +/** + * Terminates an application and returns whether termination succeeded. + * + * # Safety + * `handle`, `bundle_id`, and `out_bool` must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_terminate_app(struct WdaClientHandle *handle, + const char *bundle_id, + const char *session_id, + bool *out_bool); + +/** + * Queries the XCTest application state for the given bundle id. + * + * # Safety + * `handle`, `bundle_id`, and `out_state` must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_query_app_state(struct WdaClientHandle *handle, + const char *bundle_id, + const char *session_id, + int64_t *out_state); + +/** + * Backgrounds the current app for the given number of seconds. + * + * `Option` is encoded as `(has_seconds, seconds)`. + * + * # Safety + * `handle` and `out_json` must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_background_app(struct WdaClientHandle *handle, + bool has_seconds, + double seconds, + const char *session_id, + char **out_json); + +/** + * Returns whether the device is currently locked. + * + * # Safety + * `handle` and `out_bool` must be valid and non-null. + */ +struct IdeviceFfiError *wda_client_is_locked(struct WdaClientHandle *handle, + const char *session_id, + bool *out_bool); + +/** + * Starts a localhost bridge to the device's default WDA ports. + * + * # Arguments + * * [`provider`] - An IdeviceProvider. Provider ownership is transferred — + * the caller must not free or reuse the IdeviceProviderHandle on success or failure. + * * [`handle`] - On success, set to a newly allocated WdaBridgeHandle. + * + * # Returns + * An IdeviceFfiError on error, null on success. + * + * # Safety + * `provider` must be a valid pointer to a handle allocated by this library. + * The provider is consumed, and may not be used again. + * `handle` must be a valid, non-null pointer to a location where the handle will be stored. + */ +struct IdeviceFfiError *wda_bridge_start(struct IdeviceProviderHandle *provider, + struct WdaBridgeHandle **handle); + +/** + * Starts a localhost bridge to custom device-side WDA ports. + * + * # Safety + * Same requirements as [`wda_bridge_start`]. + */ +struct IdeviceFfiError *wda_bridge_start_with_ports(struct IdeviceProviderHandle *provider, + uint16_t device_http, + uint16_t device_mjpeg, + struct WdaBridgeHandle **handle); + +/** + * Reads the endpoints assigned to the running bridge. + * + * # Arguments + * * [`handle`] - The bridge handle. + * * [`out_endpoints`] - On success, set to a heap-allocated WdaBridgeEndpointsC. + * Free with `wda_bridge_endpoints_free`. + * + * # Returns + * An IdeviceFfiError on error, null on success. + * + * # Safety + * All pointers must be valid and non-null. + */ +struct IdeviceFfiError *wda_bridge_endpoints(struct WdaBridgeHandle *handle, + struct WdaBridgeEndpointsC **out_endpoints); + +/** + * Frees a WdaBridgeEndpointsC struct and its heap-allocated string fields. + * + * # Safety + * `endpoints` must be a pointer returned by `wda_bridge_endpoints` or NULL. + */ +void wda_bridge_endpoints_free(struct WdaBridgeEndpointsC *endpoints); + +/** + * Frees a WDA bridge handle. Dropping aborts the underlying forwarder tasks. + * + * # Safety + * `handle` must be a pointer returned by this library or NULL. + */ +void wda_bridge_free(struct WdaBridgeHandle *handle); + #endif /* IDEVICE_H */ diff --git a/idevice/libidevice_ffi.a b/idevice/libidevice_ffi.a index 58f0b92..3fb7e2d 100644 Binary files a/idevice/libidevice_ffi.a and b/idevice/libidevice_ffi.a differ diff --git a/project.yml b/project.yml index 5439bb2..c2c242a 100644 --- a/project.yml +++ b/project.yml @@ -8,8 +8,8 @@ options: settings: base: SWIFT_VERSION: "5.0" - MARKETING_VERSION: "1.5.0" - CURRENT_PROJECT_VERSION: "6" + MARKETING_VERSION: "1.9.0" + CURRENT_PROJECT_VERSION: "10" CLANG_CXX_LIBRARY: "libc++" targets: @@ -34,12 +34,18 @@ targets: DEFINES_MODULE: YES BUILD_LIBRARY_FOR_DISTRIBUTION: YES SKIP_INSTALL: NO - # idevice FFI: modulemap/headers to compile `import idevice`, and force_load the - # static lib so all its symbols are embedded in the framework binary (self-contained). + # Embed only the idevice code reachable from StikJIT. Keep archive symbols + # private so unused FFI/Rust exports do not become dead-strip roots. SWIFT_INCLUDE_PATHS: "$(SRCROOT)/idevice $(SRCROOT)/iokit" HEADER_SEARCH_PATHS: "$(SRCROOT)/idevice $(SRCROOT)/iokit" LIBRARY_SEARCH_PATHS: "$(SRCROOT)/idevice" - OTHER_LDFLAGS: "-force_load $(SRCROOT)/idevice/libidevice_ffi.a" + OTHER_LDFLAGS: "$(inherited) -Wl,-hidden-lidevice_ffi" + DEAD_CODE_STRIPPING: YES + configs: + Release: + # Remove private symbol names from the shipped binary; keep the dSYM. + STRIP_INSTALLED_PRODUCT: YES + STRIP_STYLE: non-global schemes: StikJIT: