commit eda7033bb978ad72e976860ce6f528afccc3bb37
parent 5cf21747f61a0a70d2da6ed994fb5246b59b8841
Author: triesap <tyson@radroots.org>
Date: Fri, 7 Aug 2026 10:26:46 +0000
Align mobile store sources with capsule format
- apply the established SwiftFormat indentation and import ordering
- keep the protected-data and directory contracts behaviorally unchanged
- retain strict SwiftLint compatibility for the new source and tests
- preserve green package tests and iOS Simulator compilation
Diffstat:
2 files changed, 221 insertions(+), 222 deletions(-)
diff --git a/Sources/RadrootsKit/RadrootsAppleMobileStore.swift b/Sources/RadrootsKit/RadrootsAppleMobileStore.swift
@@ -1,140 +1,140 @@
import Foundation
#if canImport(UIKit)
- import UIKit
+ import UIKit
#endif
public enum RadrootsAppleProtectedDataAvailability: Sendable, Equatable {
- case available
- case unavailable
+ case available
+ case unavailable
- @MainActor
- public static var current: Self {
- #if canImport(UIKit)
- UIApplication.shared.isProtectedDataAvailable ? .available : .unavailable
- #else
- .available
- #endif
- }
+ @MainActor
+ public static var current: Self {
+ #if canImport(UIKit)
+ UIApplication.shared.isProtectedDataAvailable ? .available : .unavailable
+ #else
+ .available
+ #endif
+ }
}
public struct RadrootsAppleMobileStoreConfiguration: Sendable, Equatable {
- public let applicationSupportDirectory: URL
- public let ownerDirectory: URL
- public let protectedDataAvailability: RadrootsAppleProtectedDataAvailability
+ public let applicationSupportDirectory: URL
+ public let ownerDirectory: URL
+ public let protectedDataAvailability: RadrootsAppleProtectedDataAvailability
}
public enum RadrootsAppleMobileStoreError: Error, Sendable, Equatable {
- case invalidPublicKey
- case protectedDataUnavailable
- case invalidDirectoryLayout
- case fileSystemFailure
+ case invalidPublicKey
+ case protectedDataUnavailable
+ case invalidDirectoryLayout
+ case fileSystemFailure
}
public enum RadrootsAppleMobileStore {
- private static let productDirectory = "radroots"
- private static let userDirectory = "users"
+ private static let productDirectory = "radroots"
+ private static let userDirectory = "users"
- /// Prepares the Apple-owned directory consumed by the Rust mobile runtime.
- ///
- /// The returned Application Support directory is passed to Rust, which
- /// independently derives and validates the same identity-scoped suffix.
- public static func prepare(
- roots: RadrootsAppleFileRoots,
- publicKeyHex: String,
- protectedDataAvailability: RadrootsAppleProtectedDataAvailability,
- fileManager: FileManager = .default
- ) throws -> RadrootsAppleMobileStoreConfiguration {
- guard protectedDataAvailability == .available else {
- throw RadrootsAppleMobileStoreError.protectedDataUnavailable
- }
- guard isCanonicalPublicKey(publicKeyHex) else {
- throw RadrootsAppleMobileStoreError.invalidPublicKey
- }
+ /// Prepares the Apple-owned directory consumed by the Rust mobile runtime.
+ ///
+ /// The returned Application Support directory is passed to Rust, which
+ /// independently derives and validates the same identity-scoped suffix.
+ public static func prepare(
+ roots: RadrootsAppleFileRoots,
+ publicKeyHex: String,
+ protectedDataAvailability: RadrootsAppleProtectedDataAvailability,
+ fileManager: FileManager = .default
+ ) throws -> RadrootsAppleMobileStoreConfiguration {
+ guard protectedDataAvailability == .available else {
+ throw RadrootsAppleMobileStoreError.protectedDataUnavailable
+ }
+ guard isCanonicalPublicKey(publicKeyHex) else {
+ throw RadrootsAppleMobileStoreError.invalidPublicKey
+ }
- let applicationSupportDirectory = roots.dataRoot.standardizedFileURL
- let productRoot =
- applicationSupportDirectory
- .appendingPathComponent(productDirectory, isDirectory: true)
- let userRoot =
- productRoot
- .appendingPathComponent(userDirectory, isDirectory: true)
- let ownerRoot =
- userRoot
- .appendingPathComponent(publicKeyHex, isDirectory: true)
-
- do {
- // swiftlint:disable trailing_comma
- for directory in [
- applicationSupportDirectory,
- productRoot,
- userRoot,
- ownerRoot,
- ] {
- try createOrValidateDirectory(directory, fileManager: fileManager)
- }
- // swiftlint:enable trailing_comma
- try excludeFromBackup(ownerRoot)
- try applyFileProtection(ownerRoot, fileManager: fileManager)
- } catch let error as RadrootsAppleMobileStoreError {
- throw error
- } catch {
- throw RadrootsAppleMobileStoreError.fileSystemFailure
- }
+ let applicationSupportDirectory = roots.dataRoot.standardizedFileURL
+ let productRoot =
+ applicationSupportDirectory
+ .appendingPathComponent(productDirectory, isDirectory: true)
+ let userRoot =
+ productRoot
+ .appendingPathComponent(userDirectory, isDirectory: true)
+ let ownerRoot =
+ userRoot
+ .appendingPathComponent(publicKeyHex, isDirectory: true)
- return RadrootsAppleMobileStoreConfiguration(
- applicationSupportDirectory: applicationSupportDirectory,
- ownerDirectory: ownerRoot,
- protectedDataAvailability: protectedDataAvailability
- )
- }
+ do {
+ // swiftlint:disable trailing_comma
+ for directory in [
+ applicationSupportDirectory,
+ productRoot,
+ userRoot,
+ ownerRoot,
+ ] {
+ try createOrValidateDirectory(directory, fileManager: fileManager)
+ }
+ // swiftlint:enable trailing_comma
+ try excludeFromBackup(ownerRoot)
+ try applyFileProtection(ownerRoot, fileManager: fileManager)
+ } catch let error as RadrootsAppleMobileStoreError {
+ throw error
+ } catch {
+ throw RadrootsAppleMobileStoreError.fileSystemFailure
+ }
- private static func isCanonicalPublicKey(_ value: String) -> Bool {
- value.utf8.count == 64
- && value.utf8.allSatisfy {
- ($0 >= 48 && $0 <= 57) || ($0 >= 97 && $0 <= 102)
- }
- }
+ return RadrootsAppleMobileStoreConfiguration(
+ applicationSupportDirectory: applicationSupportDirectory,
+ ownerDirectory: ownerRoot,
+ protectedDataAvailability: protectedDataAvailability
+ )
+ }
- private static func createOrValidateDirectory(
- _ directory: URL,
- fileManager: FileManager
- ) throws {
- var isDirectory: ObjCBool = false
- if fileManager.fileExists(atPath: directory.path, isDirectory: &isDirectory) {
- let values = try directory.resourceValues(forKeys: [.isDirectoryKey, .isSymbolicLinkKey])
- guard isDirectory.boolValue, values.isDirectory == true, values.isSymbolicLink != true else {
- throw RadrootsAppleMobileStoreError.invalidDirectoryLayout
- }
- return
+ private static func isCanonicalPublicKey(_ value: String) -> Bool {
+ value.utf8.count == 64
+ && value.utf8.allSatisfy {
+ ($0 >= 48 && $0 <= 57) || ($0 >= 97 && $0 <= 102)
+ }
}
- try fileManager.createDirectory(
- at: directory,
- withIntermediateDirectories: false,
- attributes: nil
- )
- let values = try directory.resourceValues(forKeys: [.isDirectoryKey, .isSymbolicLinkKey])
- guard values.isDirectory == true, values.isSymbolicLink != true else {
- throw RadrootsAppleMobileStoreError.invalidDirectoryLayout
+
+ private static func createOrValidateDirectory(
+ _ directory: URL,
+ fileManager: FileManager
+ ) throws {
+ var isDirectory: ObjCBool = false
+ if fileManager.fileExists(atPath: directory.path, isDirectory: &isDirectory) {
+ let values = try directory.resourceValues(forKeys: [.isDirectoryKey, .isSymbolicLinkKey])
+ guard isDirectory.boolValue, values.isDirectory == true, values.isSymbolicLink != true else {
+ throw RadrootsAppleMobileStoreError.invalidDirectoryLayout
+ }
+ return
+ }
+ try fileManager.createDirectory(
+ at: directory,
+ withIntermediateDirectories: false,
+ attributes: nil
+ )
+ let values = try directory.resourceValues(forKeys: [.isDirectoryKey, .isSymbolicLinkKey])
+ guard values.isDirectory == true, values.isSymbolicLink != true else {
+ throw RadrootsAppleMobileStoreError.invalidDirectoryLayout
+ }
}
- }
- private static func excludeFromBackup(_ directory: URL) throws {
- var directory = directory
- var values = URLResourceValues()
- values.isExcludedFromBackup = true
- try directory.setResourceValues(values)
- }
+ private static func excludeFromBackup(_ directory: URL) throws {
+ var directory = directory
+ var values = URLResourceValues()
+ values.isExcludedFromBackup = true
+ try directory.setResourceValues(values)
+ }
- private static func applyFileProtection(
- _ directory: URL,
- fileManager: FileManager
- ) throws {
- #if os(iOS)
- try fileManager.setAttributes(
- [.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication],
- ofItemAtPath: directory.path
- )
- #endif
- }
+ private static func applyFileProtection(
+ _ directory: URL,
+ fileManager: FileManager
+ ) throws {
+ #if os(iOS)
+ try fileManager.setAttributes(
+ [.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication],
+ ofItemAtPath: directory.path
+ )
+ #endif
+ }
}
diff --git a/Tests/RadrootsKitTests/RadrootsAppleMobileStoreTests.swift b/Tests/RadrootsKitTests/RadrootsAppleMobileStoreTests.swift
@@ -1,134 +1,133 @@
import Foundation
-import Testing
-
@testable import RadrootsKit
+import Testing
private let publicKey = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
@Test func mobileStorePreparesTheExactIdentityScopedDirectory() throws {
- let fixture = try MobileStoreFixture()
- defer { fixture.remove() }
-
- let configuration = try RadrootsAppleMobileStore.prepare(
- roots: fixture.roots,
- publicKeyHex: publicKey,
- protectedDataAvailability: .available
- )
-
- let expectedOwner = fixture.roots.dataRoot
- .appendingPathComponent("radroots", isDirectory: true)
- .appendingPathComponent("users", isDirectory: true)
- .appendingPathComponent(publicKey, isDirectory: true)
- .standardizedFileURL
- #expect(configuration.applicationSupportDirectory == fixture.roots.dataRoot)
- #expect(configuration.ownerDirectory == expectedOwner)
- #expect(configuration.protectedDataAvailability == .available)
-
- // swiftlint:disable trailing_comma
- let values = try expectedOwner.resourceValues(forKeys: [
- .isDirectoryKey,
- .isSymbolicLinkKey,
- .isExcludedFromBackupKey,
- ])
- // swiftlint:enable trailing_comma
- #expect(values.isDirectory == true)
- #expect(values.isSymbolicLink != true)
- #expect(values.isExcludedFromBackup == true)
+ let fixture = try MobileStoreFixture()
+ defer { fixture.remove() }
+
+ let configuration = try RadrootsAppleMobileStore.prepare(
+ roots: fixture.roots,
+ publicKeyHex: publicKey,
+ protectedDataAvailability: .available
+ )
+
+ let expectedOwner = fixture.roots.dataRoot
+ .appendingPathComponent("radroots", isDirectory: true)
+ .appendingPathComponent("users", isDirectory: true)
+ .appendingPathComponent(publicKey, isDirectory: true)
+ .standardizedFileURL
+ #expect(configuration.applicationSupportDirectory == fixture.roots.dataRoot)
+ #expect(configuration.ownerDirectory == expectedOwner)
+ #expect(configuration.protectedDataAvailability == .available)
+
+ // swiftlint:disable trailing_comma
+ let values = try expectedOwner.resourceValues(forKeys: [
+ .isDirectoryKey,
+ .isSymbolicLinkKey,
+ .isExcludedFromBackupKey,
+ ])
+ // swiftlint:enable trailing_comma
+ #expect(values.isDirectory == true)
+ #expect(values.isSymbolicLink != true)
+ #expect(values.isExcludedFromBackup == true)
}
@Test func mobileStoreDoesNotTouchDiskWhileProtectedDataIsUnavailable() throws {
- let fixture = try MobileStoreFixture()
- defer { fixture.remove() }
-
- #expect(throws: RadrootsAppleMobileStoreError.protectedDataUnavailable) {
- _ = try RadrootsAppleMobileStore.prepare(
- roots: fixture.roots,
- publicKeyHex: publicKey,
- protectedDataAvailability: .unavailable
- )
- }
- #expect(!FileManager.default.fileExists(atPath: fixture.roots.dataRoot.path))
+ let fixture = try MobileStoreFixture()
+ defer { fixture.remove() }
+
+ #expect(throws: RadrootsAppleMobileStoreError.protectedDataUnavailable) {
+ _ = try RadrootsAppleMobileStore.prepare(
+ roots: fixture.roots,
+ publicKeyHex: publicKey,
+ protectedDataAvailability: .unavailable
+ )
+ }
+ #expect(!FileManager.default.fileExists(atPath: fixture.roots.dataRoot.path))
}
@Test func mobileStoreRejectsNonCanonicalPublicKeysBeforeTouchingDisk() throws {
- let fixture = try MobileStoreFixture()
- defer { fixture.remove() }
-
- // swiftlint:disable trailing_comma
- for invalidKey in [
- "", "79BE" + String(publicKey.dropFirst(4)), String(repeating: "g", count: 64),
- ] {
- #expect(throws: RadrootsAppleMobileStoreError.invalidPublicKey) {
- _ = try RadrootsAppleMobileStore.prepare(
- roots: fixture.roots,
- publicKeyHex: invalidKey,
- protectedDataAvailability: .available
- )
+ let fixture = try MobileStoreFixture()
+ defer { fixture.remove() }
+
+ // swiftlint:disable trailing_comma
+ for invalidKey in [
+ "", "79BE" + String(publicKey.dropFirst(4)), String(repeating: "g", count: 64),
+ ] {
+ #expect(throws: RadrootsAppleMobileStoreError.invalidPublicKey) {
+ _ = try RadrootsAppleMobileStore.prepare(
+ roots: fixture.roots,
+ publicKeyHex: invalidKey,
+ protectedDataAvailability: .available
+ )
+ }
}
- }
- // swiftlint:enable trailing_comma
- #expect(!FileManager.default.fileExists(atPath: fixture.roots.dataRoot.path))
+ // swiftlint:enable trailing_comma
+ #expect(!FileManager.default.fileExists(atPath: fixture.roots.dataRoot.path))
}
@Test func mobileStoreRejectsAFileInTheDirectoryChain() throws {
- let fixture = try MobileStoreFixture()
- defer { fixture.remove() }
- try FileManager.default.createDirectory(
- at: fixture.roots.dataRoot,
- withIntermediateDirectories: true
- )
- let productPath = fixture.roots.dataRoot.appendingPathComponent("radroots")
- try Data("not a directory".utf8).write(to: productPath)
-
- #expect(throws: RadrootsAppleMobileStoreError.invalidDirectoryLayout) {
- _ = try RadrootsAppleMobileStore.prepare(
- roots: fixture.roots,
- publicKeyHex: publicKey,
- protectedDataAvailability: .available
+ let fixture = try MobileStoreFixture()
+ defer { fixture.remove() }
+ try FileManager.default.createDirectory(
+ at: fixture.roots.dataRoot,
+ withIntermediateDirectories: true
)
- }
+ let productPath = fixture.roots.dataRoot.appendingPathComponent("radroots")
+ try Data("not a directory".utf8).write(to: productPath)
+
+ #expect(throws: RadrootsAppleMobileStoreError.invalidDirectoryLayout) {
+ _ = try RadrootsAppleMobileStore.prepare(
+ roots: fixture.roots,
+ publicKeyHex: publicKey,
+ protectedDataAvailability: .available
+ )
+ }
}
@Test func mobileStoreRejectsASymlinkInTheDirectoryChain() throws {
- let fixture = try MobileStoreFixture()
- defer { fixture.remove() }
- try FileManager.default.createDirectory(
- at: fixture.roots.dataRoot,
- withIntermediateDirectories: true
- )
- let target = fixture.root.appendingPathComponent("target", isDirectory: true)
- try FileManager.default.createDirectory(at: target, withIntermediateDirectories: false)
- try FileManager.default.createSymbolicLink(
- at: fixture.roots.dataRoot.appendingPathComponent("radroots"),
- withDestinationURL: target
- )
-
- #expect(throws: RadrootsAppleMobileStoreError.invalidDirectoryLayout) {
- _ = try RadrootsAppleMobileStore.prepare(
- roots: fixture.roots,
- publicKeyHex: publicKey,
- protectedDataAvailability: .available
+ let fixture = try MobileStoreFixture()
+ defer { fixture.remove() }
+ try FileManager.default.createDirectory(
+ at: fixture.roots.dataRoot,
+ withIntermediateDirectories: true
)
- }
+ let target = fixture.root.appendingPathComponent("target", isDirectory: true)
+ try FileManager.default.createDirectory(at: target, withIntermediateDirectories: false)
+ try FileManager.default.createSymbolicLink(
+ at: fixture.roots.dataRoot.appendingPathComponent("radroots"),
+ withDestinationURL: target
+ )
+
+ #expect(throws: RadrootsAppleMobileStoreError.invalidDirectoryLayout) {
+ _ = try RadrootsAppleMobileStore.prepare(
+ roots: fixture.roots,
+ publicKeyHex: publicKey,
+ protectedDataAvailability: .available
+ )
+ }
}
private struct MobileStoreFixture {
- let root: URL
- let roots: RadrootsAppleFileRoots
-
- init() throws {
- root = FileManager.default.temporaryDirectory
- .appendingPathComponent("radroots-mobile-store-\(UUID().uuidString)", isDirectory: true)
- try FileManager.default.createDirectory(at: root, withIntermediateDirectories: false)
- roots = try RadrootsAppleFileRoots(
- appIdentifier: "org.radroots.tests",
- dataRoot: root.appendingPathComponent("data", isDirectory: true),
- cacheRoot: root.appendingPathComponent("cache", isDirectory: true),
- temporaryRoot: root.appendingPathComponent("tmp", isDirectory: true)
- )
- }
+ let root: URL
+ let roots: RadrootsAppleFileRoots
+
+ init() throws {
+ root = FileManager.default.temporaryDirectory
+ .appendingPathComponent("radroots-mobile-store-\(UUID().uuidString)", isDirectory: true)
+ try FileManager.default.createDirectory(at: root, withIntermediateDirectories: false)
+ roots = try RadrootsAppleFileRoots(
+ appIdentifier: "org.radroots.tests",
+ dataRoot: root.appendingPathComponent("data", isDirectory: true),
+ cacheRoot: root.appendingPathComponent("cache", isDirectory: true),
+ temporaryRoot: root.appendingPathComponent("tmp", isDirectory: true)
+ )
+ }
- func remove() {
- try? FileManager.default.removeItem(at: root)
- }
+ func remove() {
+ try? FileManager.default.removeItem(at: root)
+ }
}