import Foundation import Security enum KeychainStore { private static var query: [String: Any] { [kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: "MusicBridge.connection.v1", kSecAttrAccount as String: "primaryPC"] } static func load() throws -> Connection? { var q = query q[kSecReturnData as String] = true q[kSecMatchLimit as String] = kSecMatchLimitOne var item: CFTypeRef? let status = SecItemCopyMatching(q as CFDictionary, &item) if status == errSecItemNotFound { return nil } guard status == errSecSuccess, let data = item as? Data else { throw failure(status) } let saved = try JSONDecoder().decode(Connection.self, from: data) guard let token = saved.token, Connection.isFingerprint(token) else { throw BridgeError.message("Повреждена запись сопряжения.") } return try Connection(endpoint: saved.endpoint.absoluteString, fingerprint: saved.fingerprint, token: token) } static func save(_ connection: Connection) throws { let values: [String: Any] = [kSecValueData as String: try JSONEncoder().encode(connection), kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly] var status = SecItemUpdate(query as CFDictionary, values as CFDictionary) if status == errSecItemNotFound { status = SecItemAdd(query.merging(values) { _, new in new } as CFDictionary, nil) } guard status == errSecSuccess else { throw failure(status) } } static func delete() throws { let status = SecItemDelete(query as CFDictionary) guard status == errSecSuccess || status == errSecItemNotFound else { throw failure(status) } } private static func failure(_ status: OSStatus) -> BridgeError { .message("Не удалось прочитать или сохранить сопряжение в Keychain (\(status)).") } }