import Foundation import Frida import GRDB public struct ProcessSession: Codable, Identifiable, Sendable, FetchableRecord, PersistableRecord { public static let databaseTableName = "process_session" public var id: UUID public var kind: Kind public var host: CollaborationSession.UserInfo? public var deviceID: String public var deviceName: String public var processName: String public var iconPNGData: Data? public var phase: Phase public var armingState: ArmingState public var lastArmPattern: String? public var detachReason: SessionDetachReason public var lastError: String? public var createdAt: Date public var lastKnownPID: UInt public var lastAttachedAt: Date? public var processInfo: ProcessInfo? public var lastKnownMainModule: ProcessModule? public var lastKnownModules: [ProcessModule]? public var lastKnownThreads: [ProcessThread]? enum CodingKeys: String, CodingKey { case id case kind case host case deviceID = "device_id" case deviceName = "device_name" case processName = "process_name" case iconPNGData = "icon_png_data" case phase case armingState = "arming_state" case lastArmPattern = "last_arm_pattern" case detachReason = "detach_reason" case lastError = "last_error" case createdAt = "created_at" case lastKnownPID = "last_known_pid" case lastAttachedAt = "last_attached_at" case processInfo = "process_info" case lastKnownMainModule = "last_known_main_module" case lastKnownModules = "last_known_modules" case lastKnownThreads = "last_known_threads" } public init( id: UUID = UUID(), kind: Kind, host: CollaborationSession.UserInfo? = nil, deviceID: String, deviceName: String, processName: String, lastKnownPID: UInt, armingState: ArmingState = .unarmed ) { self.id = id self.kind = kind self.host = host self.deviceID = deviceID self.deviceName = deviceName self.processName = processName self.phase = .idle self.armingState = armingState self.detachReason = .applicationRequested self.createdAt = Date() self.lastKnownPID = lastKnownPID } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(UUID.self, forKey: .id) kind = try container.decode(Kind.self, forKey: .kind) host = try container.decodeIfPresent(CollaborationSession.UserInfo.self, forKey: .host) deviceID = try container.decode(String.self, forKey: .deviceID) deviceName = try container.decode(String.self, forKey: .deviceName) processName = try container.decode(String.self, forKey: .processName) iconPNGData = try container.decodeIfPresent(Data.self, forKey: .iconPNGData) phase = try container.decode(Phase.self, forKey: .phase) armingState = try container.decodeIfPresent(ArmingState.self, forKey: .armingState) ?? .unarmed lastArmPattern = try container.decodeIfPresent(String.self, forKey: .lastArmPattern) detachReason = try container.decode(SessionDetachReason.self, forKey: .detachReason) lastError = try container.decodeIfPresent(String.self, forKey: .lastError) createdAt = try container.decode(Date.self, forKey: .createdAt) lastKnownPID = try container.decode(UInt.self, forKey: .lastKnownPID) lastAttachedAt = try container.decodeIfPresent(Date.self, forKey: .lastAttachedAt) processInfo = try container.decodeIfPresent(ProcessInfo.self, forKey: .processInfo) lastKnownMainModule = try container.decodeIfPresent(ProcessModule.self, forKey: .lastKnownMainModule) lastKnownModules = try container.decodeIfPresent([ProcessModule].self, forKey: .lastKnownModules) lastKnownThreads = try container.decodeIfPresent([ProcessThread].self, forKey: .lastKnownThreads) } public struct ProcessInfo: Codable, Sendable { public let platform: String public let arch: String public let pointerSize: Int public let identity: String public init(platform: String, arch: String, pointerSize: Int, identity: String) { self.platform = platform self.arch = arch self.pointerSize = pointerSize self.identity = identity } } public enum Kind: Codable, Sendable { case spawn(SpawnConfig) case attach public var verbDisplayName: String { switch self { case .spawn: return "spawn" case .attach: return "attach" } } public var reestablishLabel: String { switch self { case .spawn: return "Re-Spawn" case .attach: return "Re-Attach" } } public var inProgressLabel: String { switch self { case .spawn: return "Spawning…" case .attach: return "Attaching…" } } private enum CodingKeys: String, CodingKey { case kind case config } private enum KindTag: String, Codable { case spawn case attach } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case .spawn(let config): try container.encode(KindTag.spawn, forKey: .kind) try container.encode(config, forKey: .config) case .attach: try container.encode(KindTag.attach, forKey: .kind) } } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let tag = try container.decode(KindTag.self, forKey: .kind) switch tag { case .spawn: let config = try container.decode(SpawnConfig.self, forKey: .config) self = .spawn(config) case .attach: self = .attach } } } public var supportsArmForNextLaunch: Bool { guard case .spawn = kind else { return false } if case .armed = armingState { return true } return lastArmPattern != nil } public enum Phase: Int, Codable, Sendable { case idle case attaching case awaitingInitialResume case attached } public enum ArmingState: Codable, Sendable, Equatable { case unarmed case armed(matchPattern: String, armedAt: Date) public var armedSince: Date? { if case .armed(_, let date) = self { return date } return nil } public var matchPattern: String? { if case .armed(let pattern, _) = self { return pattern } return nil } private enum CodingKeys: String, CodingKey { case state case matchPattern = "match_pattern" case armedAt = "armed_at" } private enum StateTag: String, Codable { case unarmed case armed } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { case .unarmed: try container.encode(StateTag.unarmed, forKey: .state) case .armed(let pattern, let date): try container.encode(StateTag.armed, forKey: .state) try container.encode(pattern, forKey: .matchPattern) try container.encode(date, forKey: .armedAt) } } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) switch try container.decode(StateTag.self, forKey: .state) { case .unarmed: self = .unarmed case .armed: let pattern = try container.decode(String.self, forKey: .matchPattern) let armedAt = try container.decode(Date.self, forKey: .armedAt) self = .armed(matchPattern: pattern, armedAt: armedAt) } } } }