import Foundation import Frida public enum JSInspectValue: nonisolated Codable, nonisolated Equatable, @unchecked Sendable { case number(Double) case string(String) case object(id: Int, properties: [Property]) case array(id: Int, elements: [JSInspectValue]) case nativePointer(String) case null case boolean(Bool) case bytes(Bytes) case function(String) case error(name: String, message: String, stack: String) case undefined case bigInt(String) case symbol(String) case date(String) case regExp(pattern: String, flags: String) case map(id: Int, entries: [Property]) case set(id: Int, elements: [JSInspectValue]) case promise case weakMap case weakSet case depthLimit(container: ContainerKind) case circular(id: Int) public struct Property: nonisolated Codable, nonisolated Equatable { public let key: JSInspectValue public let value: JSInspectValue nonisolated public init(key: JSInspectValue, value: JSInspectValue) { self.key = key self.value = value } nonisolated public init(from decoder: Decoder) throws { var container = try decoder.unkeyedContainer() let key = try container.decode(JSInspectValue.self) let value = try container.decode(JSInspectValue.self) self.init(key: key, value: value) } nonisolated public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() try container.encode(key) try container.encode(value) } } public struct Bytes: nonisolated Equatable { public let data: Data public let kind: BytesKind nonisolated public init(data: Data, kind: BytesKind) { self.data = data self.kind = kind } } public enum BytesKind: String, nonisolated Codable, nonisolated Equatable { case arrayBuffer = "ArrayBuffer" case dataView = "DataView" case int8Array = "Int8Array" case uint8Array = "Uint8Array" case uint8ClampedArray = "Uint8ClampedArray" case int16Array = "Int16Array" case uint16Array = "Uint16Array" case int32Array = "Int32Array" case uint32Array = "Uint32Array" case float32Array = "Float32Array" case float64Array = "Float64Array" case bigInt64Array = "BigInt64Array" case bigUint64Array = "BigUint64Array" } public enum ContainerKind: nonisolated Equatable { case object case array case map case set } private enum Kind: Int { case number = 0 case string = 1 case object = 2 case array = 3 case nativePointer = 4 case null = 5 case boolean = 6 case bytes = 7 case function = 8 case error = 9 case undefined = 10 case bigInt = 11 case symbol = 12 case date = 13 case regExp = 14 case map = 15 case set = 16 case promise = 17 case weakMap = 18 case weakSet = 19 case depthLimit = 20 case circular = 21 } nonisolated public init(from decoder: Decoder) throws { var container = try decoder.unkeyedContainer() let rawKind = try container.decode(Int.self) guard let kind = Kind(rawValue: rawKind) else { throw Error.invalidArgument("Invalid kind") } switch kind { case .number: self = .number(try container.decode(Double.self)) case .string: self = .string(try container.decode(String.self)) case .object: if container.isAtEnd { self = .object(id: 0, properties: []) } else { let id = try container.decode(Int.self) let props = try container.decode([Property].self) self = .object(id: id, properties: props) } case .array: if container.isAtEnd { self = .array(id: 0, elements: []) } else { let id = try container.decode(Int.self) let elements = try container.decode([JSInspectValue].self) self = .array(id: id, elements: elements) } case .nativePointer: self = .nativePointer(try container.decode(String.self)) case .null: self = .null case .boolean: self = .boolean(try container.decode(Bool.self)) case .bytes: let data = try container.decode(Data.self) let kind = try container.decode(BytesKind.self) self = .bytes(Bytes(data: data, kind: kind)) case .function: self = .function(try container.decode(String.self)) case .error: let name = try container.decode(String.self) let message = try container.decode(String.self) let stack = try container.decode(String.self) self = .error(name: name, message: message, stack: stack) case .undefined: self = .undefined case .bigInt: self = .bigInt(try container.decode(String.self)) case .symbol: self = .symbol(try container.decode(String.self)) case .date: self = .date(try container.decode(String.self)) case .regExp: let pattern = try container.decode(String.self) let flags = try container.decode(String.self) self = .regExp(pattern: pattern, flags: flags) case .map: if container.isAtEnd { self = .map(id: 0, entries: []) } else { let id = try container.decode(Int.self) let entries = try container.decode([Property].self) self = .map(id: id, entries: entries) } case .set: if container.isAtEnd { self = .set(id: 0, elements: []) } else { let id = try container.decode(Int.self) let elements = try container.decode([JSInspectValue].self) self = .set(id: id, elements: elements) } case .promise: self = .promise case .weakMap: self = .weakMap case .weakSet: self = .weakSet case .depthLimit: let containerTag = try container.decode(Int.self) let containerKind: ContainerKind switch containerTag { case Kind.object.rawValue: containerKind = .object case Kind.array.rawValue: containerKind = .array case Kind.map.rawValue: containerKind = .map case Kind.set.rawValue: containerKind = .set default: containerKind = .object } self = .depthLimit(container: containerKind) case .circular: let id = try container.decode(Int.self) self = .circular(id: id) } } nonisolated public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() switch self { case .number(let n): try container.encode(Kind.number.rawValue) try container.encode(n) case .string(let s): try container.encode(Kind.string.rawValue) try container.encode(s) case .object(let id, let props): try container.encode(Kind.object.rawValue) try container.encode(id) try container.encode(props) case .array(let id, let elements): try container.encode(Kind.array.rawValue) try container.encode(id) try container.encode(elements) case .nativePointer(let s): try container.encode(Kind.nativePointer.rawValue) try container.encode(s) case .null: try container.encode(Kind.null.rawValue) case .boolean(let b): try container.encode(Kind.boolean.rawValue) try container.encode(b) case .bytes(let bytes): try container.encode(Kind.bytes.rawValue) try container.encode(bytes.data) try container.encode(bytes.kind) case .function(let t): try container.encode(Kind.function.rawValue) try container.encode(t) case .error(let name, let message, let stack): try container.encode(Kind.error.rawValue) try container.encode(name) try container.encode(message) try container.encode(stack) case .undefined: try container.encode(Kind.undefined.rawValue) case .bigInt(let s): try container.encode(Kind.bigInt.rawValue) try container.encode(s) case .symbol(let t): try container.encode(Kind.symbol.rawValue) try container.encode(t) case .date(let s): try container.encode(Kind.date.rawValue) try container.encode(s) case .regExp(let pattern, let flags): try container.encode(Kind.regExp.rawValue) try container.encode(pattern) try container.encode(flags) case .map(let id, let entries): try container.encode(Kind.map.rawValue) try container.encode(id) try container.encode(entries) case .set(let id, let elements): try container.encode(Kind.set.rawValue) try container.encode(id) try container.encode(elements) case .promise: try container.encode(Kind.promise.rawValue) case .weakMap: try container.encode(Kind.weakMap.rawValue) case .weakSet: try container.encode(Kind.weakSet.rawValue) case .depthLimit(let containerKind): try container.encode(Kind.depthLimit.rawValue) let tag: Int switch containerKind { case .object: tag = Kind.object.rawValue case .array: tag = Kind.array.rawValue case .map: tag = Kind.map.rawValue case .set: tag = Kind.set.rawValue } try container.encode(tag) case .circular(let id): try container.encode(Kind.circular.rawValue) try container.encode(id) } } public var nativePointerAddress: UInt64? { guard case .nativePointer(let s) = self else { return nil } return UInt64(s.dropFirst(2), radix: 16) } } extension JSInspectValue { public enum DecodePackedError: Swift.Error { case invalidRoot case invalidBlob case invalidNode case invalidTag(Int) case invalidBytesRange } nonisolated public static func decodePacked(from root: Any) throws -> JSInspectValue { guard let pair = root as? [Any], pair.count == 2 else { throw DecodePackedError.invalidRoot } let tree = pair[0] let blobData: Data let blobCandidate = pair[1] if let bytes = blobCandidate as? [UInt8] { blobData = Data(bytes) } else if let d = blobCandidate as? Data { blobData = d } else if blobCandidate is NSNull { blobData = Data() } else { throw DecodePackedError.invalidBlob } return try decodePacked(tree: tree, blobData: blobData) } nonisolated public static func decodePacked(tree: Any, blobBytes: [UInt8]?) throws -> JSInspectValue { return try decodePacked(tree: tree, blobData: Data(blobBytes ?? [])) } nonisolated public static func decodePacked(tree: Any, blobData: Data) throws -> JSInspectValue { func intFrom(_ any: Any) throws -> Int { if let i = any as? Int { return i } if let n = any as? NSNumber { return n.intValue } throw DecodePackedError.invalidNode } func doubleFrom(_ any: Any) throws -> Double { if let d = any as? Double { return d } if let n = any as? NSNumber { return n.doubleValue } throw DecodePackedError.invalidNode } func boolFrom(_ any: Any) throws -> Bool { if let b = any as? Bool { return b } if let n = any as? NSNumber { return n.boolValue } throw DecodePackedError.invalidNode } func stringFrom(_ any: Any) throws -> String { if let s = any as? String { return s } throw DecodePackedError.invalidNode } func decodeNode(_ node: Any) throws -> JSInspectValue { guard let arr = node as? [Any], !arr.isEmpty else { throw DecodePackedError.invalidNode } let rawTagAny = arr[0] let rawTag = try intFrom(rawTagAny) guard let kind = Kind(rawValue: rawTag) else { throw DecodePackedError.invalidTag(rawTag) } switch kind { case .number: let value = try doubleFrom(arr[1]) return .number(value) case .string: let s = try stringFrom(arr[1]) return .string(s) case .object: let id = try intFrom(arr[1]) let rawEntries = (arr.count > 2 ? arr[2] : []) as? [Any] ?? [] let properties: [Property] = try rawEntries.map { entryAny in guard let entry = entryAny as? [Any], entry.count == 2 else { throw DecodePackedError.invalidNode } let key = try decodeNode(entry[0]) let value = try decodeNode(entry[1]) return Property(key: key, value: value) } return .object(id: id, properties: properties) case .array: let id = try intFrom(arr[1]) let rawElements = (arr.count > 2 ? arr[2] : []) as? [Any] ?? [] let elements: [JSInspectValue] = try rawElements.map { try decodeNode($0) } return .array(id: id, elements: elements) case .nativePointer: let s = try stringFrom(arr[1]) return .nativePointer(s) case .null: return .null case .boolean: let b = try boolFrom(arr[1]) return .boolean(b) case .bytes: let offset = try intFrom(arr[1]) let length = try intFrom(arr[2]) let kindRaw = try stringFrom(arr[3]) let kind = BytesKind(rawValue: kindRaw) ?? .arrayBuffer let start = offset let end = offset + length guard start >= 0, length >= 0, end <= blobData.count else { throw DecodePackedError.invalidBytesRange } let slice = blobData.subdata(in: start.. 2 ? arr[2] : []) as? [Any] ?? [] let entries: [Property] = try rawEntries.map { entryAny in guard let entry = entryAny as? [Any], entry.count == 2 else { throw DecodePackedError.invalidNode } let key = try decodeNode(entry[0]) let value = try decodeNode(entry[1]) return Property(key: key, value: value) } return .map(id: id, entries: entries) case .set: let id = try intFrom(arr[1]) let rawElements = (arr.count > 2 ? arr[2] : []) as? [Any] ?? [] let elements: [JSInspectValue] = try rawElements.map { try decodeNode($0) } return .set(id: id, elements: elements) case .promise: return .promise case .weakMap: return .weakMap case .weakSet: return .weakSet case .depthLimit: let containerTag = try intFrom(arr[1]) let containerKind: ContainerKind switch containerTag { case Kind.object.rawValue: containerKind = .object case Kind.array.rawValue: containerKind = .array case Kind.map.rawValue: containerKind = .map case Kind.set.rawValue: containerKind = .set default: containerKind = .object } return .depthLimit(container: containerKind) case .circular: let id = try intFrom(arr[1]) return .circular(id: id) } } return try decodeNode(tree) } }