import CryptoKit import Foundation import RastrilloApple import RastrilloNative import Security /// Thread-safe in-memory disk. Copies model a new process reading committed bytes. public final class MemoryBlobStore: BlobStore, @unchecked Sendable { private let lock = NSLock() private var files: [String: Data] public init(_ files: [String: Data] = [:]) { self.files = files } public var contents: [String: Data] { lock.withLock { files } } public func write(_ data: Data, to path: String) throws { lock.withLock { files[path] = data } } public func read(_ path: String) throws -> Data? { lock.withLock { files[path] } } public func list(_ directory: String) throws -> [String] { lock.withLock { let prefix = directory.isEmpty ? "" : directory + "/" return files.keys.filter { $0.hasPrefix(prefix) }.map { String($0.dropFirst(prefix.count)) } .filter { !$0.contains("/") }.sorted() } } public func remove(_ path: String) throws { lock.withLock { files = files.filter { !(path.isEmpty || $0.key == path || $0.key.hasPrefix(path + "/")) } } } } public struct SimulatedCrash: Error, Sendable, Equatable { public let operation: Int public init(operation: Int) { self.operation = operation } } /// Crash *before* operation N (zero based). Once crashed, the committed view /// freezes and every later operation throws, just as a dead process cannot write. public final class FaultingBlobStore: BlobStore, @unchecked Sendable { public enum Operation: Sendable, Equatable { case write(String), read(String), list(String), remove(String) } private let lock = NSLock() private let base: MemoryBlobStore private var schedule: Int? private var frozen: [String: Data]? private var history: [Operation] = [] private var locked = false public init(_ base: MemoryBlobStore = MemoryBlobStore(), crashAt: Int? = nil) { self.base = base; schedule = crashAt } public func crash(after operations: Int) { lock.withLock { schedule = history.count + operations } } public var unavailable: Bool { get { lock.withLock { locked } } set { lock.withLock { locked = newValue } } } public var hasCrashed: Bool { lock.withLock { frozen != nil } } public var operationCount: Int { lock.withLock { history.count } } public var operations: [Operation] { lock.withLock { history } } public var committed: MemoryBlobStore { lock.withLock { MemoryBlobStore(frozen ?? base.contents) } } private func perform(_ operation: Operation, _ body: () throws -> T) throws -> T { try lock.withLock { if frozen != nil || schedule == history.count { if frozen == nil { frozen = base.contents } throw SimulatedCrash(operation: history.count) } if locked { throw BlobStoreError.unavailable } let value = try body() history.append(operation) return value } } public func write(_ data: Data, to path: String) throws { try perform(.write(path)) { try base.write(data, to: path) } } public func read(_ path: String) throws -> Data? { try perform(.read(path)) { try base.read(path) } } public func list(_ directory: String) throws -> [String] { try perform(.list(directory)) { try base.list(directory) } } public func remove(_ path: String) throws { try perform(.remove(path)) { try base.remove(path) } } } public final class ScriptedKeyProvider: KeyProvider, @unchecked Sendable { private let lock = NSLock() private var key: SymmetricKey? private var state: StoreKeyState? private var copyFailure: OSStatus? private var addFailure: OSStatus? private var history: [String] = [] public init(key: SymmetricKey? = nil) { self.key = key; state = key.map { StoreKeyState(current: $0.withUnsafeBytes { Data($0) }) } } public func failCopies(with status: OSStatus?) { lock.withLock { copyFailure = status } } public func failAdds(with status: OSStatus?) { lock.withLock { addFailure = status } } public var storedKey: SymmetricKey? { lock.withLock { key } } public var log: [String] { lock.withLock { history } } public func copyKey() -> (OSStatus, SymmetricKey?) { lock.withLock { history.append("copy"); return copyFailure.map { ($0, nil) } ?? (key == nil ? errSecItemNotFound : errSecSuccess, key) } } public func addKey(_ new: SymmetricKey) -> OSStatus { lock.withLock { history.append("add") if let addFailure { return addFailure } guard key == nil else { return errSecDuplicateItem } key = new; state = StoreKeyState(current: new.withUnsafeBytes { Data($0) }); return errSecSuccess } } public func deleteKey() -> OSStatus { lock.withLock { history.append("delete"); key = nil; state = nil; return errSecSuccess } } public func sealState() -> (OSStatus, StoreKeyState?) { lock.withLock { copyFailure.map { ($0, nil) } ?? (state == nil ? errSecItemNotFound : errSecSuccess, state) } } public func saveSealState(_ value: StoreKeyState) -> OSStatus { lock.withLock { if let addFailure { return addFailure } state = value; key = SymmetricKey(data: value.current); return errSecSuccess } } }