import DocsCRDT import Foundation /// Handles never leave the actor. Every untrusted decode executes in bounded WASM. actor CRDTReplica { private final class Sandbox { let host: UnsafeMutableRawPointer init() throws { guard let host = docs_host_new() else { throw Failure.unavailable }; self.host = host } deinit { docs_host_free(host) } func invoke(_ data: Data) throws -> Data { let maximum = 8 * 1024 * 1024 + 1 guard !data.isEmpty, data.count <= maximum else { throw Failure.unavailable } var output = Data(count: maximum) let count = output.withUnsafeMutableBytes { target in data.withUnsafeBytes { source in docs_host_call(host, source.bindMemory(to: UInt8.self).baseAddress!, data.count, target.bindMemory(to: UInt8.self).baseAddress!, maximum) } } guard count >= 0, count <= maximum else { throw Failure.rejected } output.count = Int(count); return output } func invoke(_ command: [String: JSONValue]) throws -> Data { try invoke(JSONEncoder().encode(command)) } func projection() throws -> CRDTProjection { try JSONDecoder().decode(CRDTProjection.self, from: invoke(["op": .string("project")])) } } private var authority: Sandbox? private var merger: Sandbox? private var opened = false private var sources: [UInt64: Sandbox] = [:] private var sourceSnapshots: [UInt64: Data] = [:] private var nextGeneration: UInt64 = 1 // IDs remain reserved for this session even when a source is retired or traps. private var reserved: Set = [] private var lastGood = Data() private var commandsApplied = 0 private var durableSequence: Int64 = 0 private let guid: String enum Failure: Error { case unavailable, rejected, invalidProjection, generationLimit } init(guid: String) { self.guid = guid } struct PersistentState: Sendable { let state: Data let sequence: Int64 let commandsApplied: Int let guid: String } func persistentState() throws -> PersistentState { _ = try currentAuthority() return PersistentState(state: lastGood, sequence: durableSequence, commandsApplied: commandsApplied, guid: guid) } func setSequence(_ sequence: Int64) { durableSequence = max(durableSequence, sequence) } func close() { opened = false; authority = nil; merger = nil; sources.removeAll(); sourceSnapshots.removeAll(); lastGood = Data() } private func fresh(update: Data) throws -> (UInt64, Sandbox) { let sandbox = try Sandbox() var client = UInt64(UInt32.random(in: 1...UInt32.max)) while reserved.contains(client) { client = UInt64(UInt32.random(in: 1...UInt32.max)) } reserved.insert(client) _ = try sandbox.invoke(["op": .string("open"), "guid": .string(guid), "client": .number(Double(client))]) if !update.isEmpty { _ = try sandbox.invoke(Data([255]) + update) } let used = try JSONDecoder().decode([UInt64].self, from: sandbox.invoke(["op": .string("clients")])) reserved.formUnion(used) // Imported state may already contain the proposed ID. Never write it. if used.contains(client) { return try fresh(update: update) } return (client, sandbox) } func open(update: Data) throws -> CRDTProjection { sources.removeAll(); sourceSnapshots.removeAll() commandsApplied = 0; durableSequence = 0 authority = try fresh(update: update).1 opened = true lastGood = try authority!.invoke(["op": .string("snapshot")]) return try projection() } private func currentAuthority() throws -> Sandbox { guard opened else { throw Failure.unavailable } if authority == nil { authority = try fresh(update: lastGood).1 } guard let authority else { throw Failure.unavailable } return authority } func projection() throws -> CRDTProjection { let authority = try currentAuthority() return try authority.projection() } /// Bound retained WASM memories. The caller defers publication until render /// acknowledgement and queued-callback drainage allow old sources to retire. func presentation(source generation: UInt64, selection: NSRange, allowReuse: Bool = false) throws -> (UInt64, CRDTProjection, NSRange)? { let authority = try currentAuthority() // Only the caller knows that this source has actually been rendered and // has no pending candidate. Byte-identical state is additionally required: // peers, undo, and divergent queued edits must still publish a fresh source. // Never reconstruct or transfer a client ID to enable this fast path. let reuse = allowReuse && sources[generation] != nil && sourceSnapshots[generation] == lastGood guard reuse || sources.count < 3 else { return nil } var resolved = selection if let displayed = sources[generation] { func endpoint(_ position: Int) throws -> Int { let encoded = try displayed.invoke(["op": .string("anchor"), "at": .number(Double(position))]) let anchor = try JSONDecoder().decode(JSONValue.self, from: encoded) return try JSONDecoder().decode(Int.self, from: authority.invoke(["op": .string("resolve"), "anchor": anchor])) } do { let start = try endpoint(selection.location), end = try endpoint(NSMaxRange(selection)) resolved = NSRange(location: min(start, end), length: abs(end - start)) } catch { if let snapshot = sourceSnapshots[generation] { sources[generation] = try? fresh(update: snapshot).1 } else { sources.removeValue(forKey: generation) } recover() throw error } } if reuse { return (generation, try authority.projection(), resolved) } let (_, source) = try fresh(update: lastGood) let generation = nextGeneration; nextGeneration += 1 sources[generation] = source; sourceSnapshots[generation] = lastGood return (generation, try authority.projection(), resolved) } func retainGenerations(_ ids: Set) { sources = sources.filter { ids.contains($0.key) } sourceSnapshots = sourceSnapshots.filter { ids.contains($0.key) } } private func recover() { // No invalid interpreter is reused. A new authority ID cannot reuse // clocks from a partially executed command. UI reports undo loss. authority = try? fresh(update: lastGood).1 } func mergeUpdates(_ updates: [Data]) throws -> Data { guard let first = updates.first else { throw Failure.rejected } if updates.count == 1 { return first } guard opened else { throw Failure.unavailable } if merger == nil { merger = try Sandbox() } var envelope = Data([253]) for update in updates { envelope.append(update) } do { return try merger!.invoke(envelope) } catch { merger = nil; throw error } } func apply(_ update: Data) throws -> CRDTProjection { let authority = try currentAuthority() do { _ = try authority.invoke(Data([255]) + update) let projected = try authority.projection() lastGood = try authority.invoke(["op": .string("snapshot")]) return projected } catch { recover(); throw error } } func command(_ command: [String: JSONValue]) throws -> (Data, CRDTProjection) { let authority = try currentAuthority() let generation: UInt64? = if case .number(let value) = command["generation"] { UInt64(exactly: value) } else { nil } let isHistory = command["op"] == .string("undo") || command["op"] == .string("redo") let writer: Sandbox if isHistory || generation == nil { writer = authority } else { let generation = generation! if sources[generation] == nil, let snapshot = sourceSnapshots[generation] { sources[generation] = try fresh(update: snapshot).1 } guard let source = sources[generation] else { throw Failure.unavailable } writer = source } let sourceBefore = writer === authority ? lastGood : (generation.flatMap { sourceSnapshots[$0] } ?? lastGood) var authorityTouched = writer === authority do { let update = try writer.invoke(command) let sourceAfter = try writer.invoke(["op": .string("snapshot")]) if writer !== authority { authorityTouched = true; _ = try authority.invoke(Data([254]) + update) } let projected = try authority.projection() lastGood = try authority.invoke(["op": .string("snapshot")]) if writer !== authority, let generation { sourceSnapshots[generation] = sourceAfter } commandsApplied += 1 return (update, projected) } catch { if writer !== authority, let generation { sources[generation] = try? fresh(update: sourceBefore).1 } if authorityTouched { recover() } throw error } } }