TeraRemoteQualification.swift (7865B)
1 import Foundation 2 import RadrootsKit 3 import TeraKitBindings 4 5 enum TeraQualificationNetworkMode: Sendable, Equatable { 6 case isolatedLoopback 7 case publicEndpoint 8 9 init(profile: String?) throws { 10 switch profile { 11 case "simulator": self = .isolatedLoopback 12 case "public": self = .publicEndpoint 13 default: 14 throw TeraConfigurationError.invalid("qualification_network_profile") 15 } 16 } 17 18 var runtimeMode: String { 19 switch self { 20 case .isolatedLoopback: "simulator" 21 case .publicEndpoint: "production" 22 } 23 } 24 25 var permitsAutomatedUserPresence: Bool { 26 self == .isolatedLoopback 27 } 28 29 var permitsTestSecretPolicy: Bool { 30 self == .isolatedLoopback 31 } 32 } 33 34 struct TeraQualificationEndpoint: Sendable, Equatable { 35 enum Role: Sendable { 36 case relay 37 case blossom 38 } 39 40 let rawValue: String 41 42 init(_ raw: String, role: Role, mode: TeraQualificationNetworkMode) throws { 43 guard let value = URLComponents(string: raw), 44 let scheme = value.scheme?.lowercased(), 45 let host = value.host?.lowercased(), 46 !host.isEmpty, 47 value.user == nil, 48 value.password == nil, 49 value.query == nil, 50 value.fragment == nil 51 else { 52 throw TeraConfigurationError.invalid("qualification_endpoint") 53 } 54 let expectedScheme = switch (role, mode) { 55 case (.relay, .isolatedLoopback): "ws" 56 case (.blossom, .isolatedLoopback): "http" 57 case (.relay, .publicEndpoint): "wss" 58 case (.blossom, .publicEndpoint): "https" 59 } 60 let loopback = host == "127.0.0.1" 61 let validHost = switch mode { 62 case .isolatedLoopback: 63 loopback 64 && value.port.map { 1 ... 65535 ~= $0 } == true 65 && (value.path.isEmpty || value.path == "/") 66 case .publicEndpoint: !loopback && host != "::1" && host != "localhost" 67 } 68 guard scheme == expectedScheme, validHost else { 69 throw TeraConfigurationError.invalid("qualification_endpoint_policy") 70 } 71 rawValue = raw 72 } 73 } 74 75 struct TeraRemoteQualificationEnvironment: Sendable, Equatable { 76 static let enabledKey = "TERA_IOS_UI_TEST_REMOTE" 77 static let runIDKey = "TERA_IOS_UI_TEST_RUN_ID" 78 static let relayURLsKey = "TERA_IOS_UI_TEST_NOSTR_RELAY_URLS" 79 static let blossomOriginsKey = "TERA_IOS_UI_TEST_BLOSSOM_ORIGINS" 80 static let mediaRelativePathKey = "TERA_IOS_UI_TEST_MEDIA_RELATIVE_PATH" 81 static let networkProfileKey = "TERA_IOS_UI_TEST_NETWORK_PROFILE" 82 83 let runID: String 84 let relayURLs: [String] 85 let blossomOrigins: [String] 86 let mediaFile: RadrootsFileReference? 87 let networkMode: TeraQualificationNetworkMode 88 89 var runtimeMode: String { 90 networkMode.runtimeMode 91 } 92 93 var automatesIdentity: Bool { 94 networkMode.permitsAutomatedUserPresence && networkMode.permitsTestSecretPolicy 95 } 96 97 private static let mediaFixtureBase64 = 98 "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" 99 100 var keychainServicePrefix: String { 101 "org.radroots.ios.remote-qualification.\(runID)" 102 } 103 104 var identityMetadataKeyPrefix: String { 105 "\(keychainServicePrefix).identity" 106 } 107 108 var backgroundTransferIdentifierSuffix: String { 109 "remote-qualification.\(runID)" 110 } 111 112 func isolatedFileRoots(from base: RadrootsAppleFileRoots) throws 113 -> RadrootsAppleFileRoots 114 { 115 try RadrootsAppleFileRoots( 116 appIdentifier: base.appIdentifier, 117 dataRoot: base.dataRoot.appendingPathComponent(runID, isDirectory: true), 118 cacheRoot: base.cacheRoot.appendingPathComponent(runID, isDirectory: true), 119 temporaryRoot: base.temporaryRoot.appendingPathComponent(runID, isDirectory: true) 120 ) 121 } 122 123 static func applicationFileRoots(appIdentifier: String) throws -> RadrootsAppleFileRoots { 124 let base = try RadrootsAppleFileRoots.appContainer(appIdentifier: appIdentifier) 125 #if DEBUG 126 return try TeraDurableMediaRoots.selectingStaging(in: current()?.isolatedFileRoots(from: base) ?? base) 127 #else 128 return try TeraDurableMediaRoots.selectingStaging(in: base) 129 #endif 130 } 131 132 static func backgroundTransferIdentifier(appIdentifier: String) throws -> String { 133 #if DEBUG 134 if let qualification = try current() { 135 return "\(appIdentifier.lowercased()).\(qualification.backgroundTransferIdentifierSuffix)" 136 } 137 #endif 138 return "\(appIdentifier.lowercased()).background.transfer" 139 } 140 141 static func mediaFixtureData() throws -> Data { 142 guard let data = Data(base64Encoded: mediaFixtureBase64), !data.isEmpty else { 143 throw TeraConfigurationError.invalid("qualification_media_fixture") 144 } 145 return data 146 } 147 148 #if DEBUG 149 static func current( 150 environment: [String: String] = ProcessInfo.processInfo.environment 151 ) throws -> Self? { 152 guard environment[enabledKey] == "1" else { return nil } 153 let runID = try requiredRunID(environment[runIDKey]) 154 let relays = separatedValues(environment[relayURLsKey]) 155 let blossoms = separatedValues(environment[blossomOriginsKey]) 156 let networkMode = try TeraQualificationNetworkMode( 157 profile: environment[networkProfileKey] 158 ) 159 guard !relays.isEmpty, blossoms.count == 1 else { 160 throw TeraConfigurationError.invalid("qualification_blossom_origin") 161 } 162 let relayEndpoints = try relays.map { 163 try TeraQualificationEndpoint($0, role: .relay, mode: networkMode) 164 } 165 let blossomEndpoints = try blossoms.map { 166 try TeraQualificationEndpoint($0, role: .blossom, mode: networkMode) 167 } 168 guard Set(relayEndpoints.map(\.rawValue)).count == relayEndpoints.count else { 169 throw TeraConfigurationError.invalid("qualification_endpoint_duplicate") 170 } 171 let mediaFile = try environment[mediaRelativePathKey].map { raw in 172 guard raw == "qualification/input.png" else { 173 throw TeraConfigurationError.invalid("qualification_media_file") 174 } 175 return RadrootsFileReference(scope: .data, relativePath: raw) 176 } 177 return Self( 178 runID: runID, 179 relayURLs: relayEndpoints.map(\.rawValue), 180 blossomOrigins: blossomEndpoints.map(\.rawValue), 181 mediaFile: mediaFile, 182 networkMode: networkMode 183 ) 184 } 185 186 private static func requiredRunID(_ raw: String?) throws -> String { 187 guard let raw, 188 (8 ... 64).contains(raw.utf8.count), 189 raw == raw.lowercased(), 190 raw.unicodeScalars.allSatisfy({ 191 CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyz0123456789-") 192 .contains($0) 193 }), 194 raw.first != "-", 195 raw.last != "-" 196 else { 197 throw TeraConfigurationError.invalid("qualification_run_id") 198 } 199 return raw 200 } 201 202 private static func separatedValues(_ raw: String?) -> [String] { 203 guard let raw else { return [] } 204 return raw.components(separatedBy: CharacterSet(charactersIn: ",; \n\r\t")) 205 .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } 206 .filter { !$0.isEmpty } 207 } 208 209 #else 210 static func current(environment _: [String: String] = [:]) throws -> Self? { 211 nil 212 } 213 #endif 214 } 215 216 #if DEBUG 217 final class TeraRemoteQualificationUserPresence: RadrootsUserPresence, Sendable { 218 init(mode: TeraQualificationNetworkMode) throws { 219 guard mode.permitsAutomatedUserPresence, mode.permitsTestSecretPolicy else { 220 throw TeraConfigurationError.invalid("qualification_user_presence") 221 } 222 } 223 224 func currentStatus() async throws -> RadrootsUserPresenceStatus { 225 RadrootsUserPresenceStatus( 226 support: .deviceCredential, 227 biometryKind: .none, 228 canEvaluateDeviceCredential: true, 229 canEvaluateBiometrics: false 230 ) 231 } 232 233 func verify( 234 _ request: RadrootsUserPresenceRequest 235 ) async throws -> RadrootsUserPresenceResult { 236 RadrootsUserPresenceResult(policy: request.policy, verified: true) 237 } 238 } 239 #endif