rastrillo / native Public

Clone
git clone https://amadan.net/rastrillo/native

Plain git — no account needed to clone.

Download

Download this file

1import CryptoKit
2import Foundation
3import RastrilloApple
4import RastrilloNative
5import Security
6
7/// Thread-safe in-memory disk. Copies model a new process reading committed bytes.
8public final class MemoryBlobStore: BlobStore, @unchecked Sendable {
9 private let lock = NSLock()
10 private var files: [String: Data]
11 public init(_ files: [String: Data] = [:]) { self.files = files }
12 public var contents: [String: Data] { lock.withLock { files } }
13 public func write(_ data: Data, to path: String) throws { lock.withLock { files[path] = data } }
14 public func read(_ path: String) throws -> Data? { lock.withLock { files[path] } }
15 public func list(_ directory: String) throws -> [String] {
16 lock.withLock {
17 let prefix = directory.isEmpty ? "" : directory + "/"
18 return files.keys.filter { $0.hasPrefix(prefix) }.map { String($0.dropFirst(prefix.count)) }
19 .filter { !$0.contains("/") }.sorted()
20 }
21 }
22 public func remove(_ path: String) throws {
23 lock.withLock { files = files.filter { !(path.isEmpty || $0.key == path || $0.key.hasPrefix(path + "/")) } }
24 }
25}
26public struct SimulatedCrash: Error, Sendable, Equatable {
27 public let operation: Int
28 public init(operation: Int) { self.operation = operation }
29}
30/// Crash *before* operation N (zero based). Once crashed, the committed view
31/// freezes and every later operation throws, just as a dead process cannot write.
32public final class FaultingBlobStore: BlobStore, @unchecked Sendable {
33 public enum Operation: Sendable, Equatable { case write(String), read(String), list(String), remove(String) }
34 private let lock = NSLock()
35 private let base: MemoryBlobStore
36 private var schedule: Int?
37 private var frozen: [String: Data]?
38 private var history: [Operation] = []
39 private var locked = false
40 public init(_ base: MemoryBlobStore = MemoryBlobStore(), crashAt: Int? = nil) { self.base = base; schedule = crashAt }
41 public func crash(after operations: Int) { lock.withLock { schedule = history.count + operations } }
42 public var unavailable: Bool { get { lock.withLock { locked } } set { lock.withLock { locked = newValue } } }
43 public var hasCrashed: Bool { lock.withLock { frozen != nil } }
44 public var operationCount: Int { lock.withLock { history.count } }
45 public var operations: [Operation] { lock.withLock { history } }
46 public var committed: MemoryBlobStore { lock.withLock { MemoryBlobStore(frozen ?? base.contents) } }
47 private func perform<T>(_ operation: Operation, _ body: () throws -> T) throws -> T {
48 try lock.withLock {
49 if frozen != nil || schedule == history.count {
50 if frozen == nil { frozen = base.contents }
51 throw SimulatedCrash(operation: history.count)
52 }
53 if locked { throw BlobStoreError.unavailable }
54 let value = try body()
55 history.append(operation)
56 return value
57 }
58 }
59 public func write(_ data: Data, to path: String) throws { try perform(.write(path)) { try base.write(data, to: path) } }
60 public func read(_ path: String) throws -> Data? { try perform(.read(path)) { try base.read(path) } }
61 public func list(_ directory: String) throws -> [String] { try perform(.list(directory)) { try base.list(directory) } }
62 public func remove(_ path: String) throws { try perform(.remove(path)) { try base.remove(path) } }
63}
64public final class ScriptedKeyProvider: KeyProvider, @unchecked Sendable {
65 private let lock = NSLock()
66 private var key: SymmetricKey?
67 private var state: StoreKeyState?
68 private var copyFailure: OSStatus?
69 private var addFailure: OSStatus?
70 private var history: [String] = []
71 public init(key: SymmetricKey? = nil) { self.key = key; state = key.map { StoreKeyState(current: $0.withUnsafeBytes { Data($0) }) } }
72 public func failCopies(with status: OSStatus?) { lock.withLock { copyFailure = status } }
73 public func failAdds(with status: OSStatus?) { lock.withLock { addFailure = status } }
74 public var storedKey: SymmetricKey? { lock.withLock { key } }
75 public var log: [String] { lock.withLock { history } }
76 public func copyKey() -> (OSStatus, SymmetricKey?) {
77 lock.withLock { history.append("copy"); return copyFailure.map { ($0, nil) } ?? (key == nil ? errSecItemNotFound : errSecSuccess, key) }
78 }
79 public func addKey(_ new: SymmetricKey) -> OSStatus {
80 lock.withLock {
81 history.append("add")
82 if let addFailure { return addFailure }
83 guard key == nil else { return errSecDuplicateItem }
84 key = new; state = StoreKeyState(current: new.withUnsafeBytes { Data($0) }); return errSecSuccess
85 }
86 }
87 public func deleteKey() -> OSStatus { lock.withLock { history.append("delete"); key = nil; state = nil; return errSecSuccess } }
88 public func sealState() -> (OSStatus, StoreKeyState?) {
89 lock.withLock { copyFailure.map { ($0, nil) } ?? (state == nil ? errSecItemNotFound : errSecSuccess, state) }
90 }
91 public func saveSealState(_ value: StoreKeyState) -> OSStatus {
92 lock.withLock {
93 if let addFailure { return addFailure }
94 state = value; key = SymmetricKey(data: value.current); return errSecSuccess
95 }
96 }
97
98}
99