// A recorded playback session, replayed through the sink call by call: the playlists with their // receipts, the objects the player received, its observations, its end. The bundle the sink // signs at the end must verify as the stream's owner verifies it, and state what was recorded. import Foundation import WaiSink enum Replay { static func run(path: String) throws -> Bool { let doc = try JSONSerialization.jsonObject(with: Data(contentsOf: URL(fileURLWithPath: path))) as! [String: Any] let objects = doc["objects"] as! [String: String] func bytes(_ name: Any?) -> Data? { (name as? String).map { hex(objects[$0]!) } } let sink = try WaiSink(configJSON: doc["config"] as! [String: Any], seed: hex(doc["seed"] as! String), startUs: (doc["start_us"] as! NSNumber).uint64Value) var bundle = Data() var verified = 0 for d in doc["calls"] as! [[String: Any]] { let t = { (d["t_us"] as! NSNumber).uint64Value } switch d["call"] as! String { case "playlist": _ = try sink.playlist(url: d["url"] as! String, bytes: bytes(d["body"])!, receipt: bytes(d["receipt"])) verified += 1 case "object": _ = try sink.object(meta: d["meta"] as! [String: Any], bytes: bytes(d["body"])!) verified += 1 case "event": try sink.event(d["name"] as! String, atUs: t()) case "observe": _ = try sink.observe(d["obs"] as! [String: Any]) case "end": bundle = try sink.end(WaiSink.Ended(rawValue: d["ended"] as! String)!, atUs: t()) default: break } } let out = try WaiSink.verifyBundle(bundle, owner: hex(doc["owner"] as! String), channels: doc["channels"] as! [String]) let want = doc["outcome"] as! [String: Any] let ok = out["outcome"] as? String == "accept" && out["digest"] as? String == want["digest"] as? String print("replay: \(verified) playlists and objects through the sink; the bundle \(ok ? "verifies" : "does NOT verify") as its owner verifies it (\(out["outcome"] ?? "?"), \(out["presented_ms"] ?? "?") ms presented)") return ok } static func hex(_ s: String) -> Data { var d = Data(capacity: s.count / 2) var i = s.startIndex while i < s.endIndex { let j = s.index(i, offsetBy: 2) d.append(UInt8(s[i..