import Foundation /// Events accepted from the candidate Samsung remote-control channel. /// /// This parser intentionally exposes no arbitrary method or key supplied by the TV. /// Unknown events are inert and can be counted without logging their payload. public enum SamsungRemoteEvent: Sendable, Equatable { case connected(token: String?) case unauthorized case timedOut case error case unknown } public enum SamsungEventParserError: Error, Sendable, Equatable { case empty case oversized case malformed case invalidEventName case invalidToken } /// A size-bounded, allowlist-based parser for WebSocket event messages. public struct SamsungEventParser: Sendable { public static let defaultMaximumMessageBytes = 16 * 1024 public static let maximumEventNameBytes = 128 public static let maximumTokenBytes = 4 * 1024 public let maximumMessageBytes: Int public init(maximumMessageBytes: Int = Self.defaultMaximumMessageBytes) { self.maximumMessageBytes = max(1, maximumMessageBytes) } public func parse(_ data: Data) throws -> SamsungRemoteEvent { guard !data.isEmpty else { throw SamsungEventParserError.empty } guard data.count <= maximumMessageBytes else { throw SamsungEventParserError.oversized } let message: Message do { message = try JSONDecoder().decode(Message.self, from: data) } catch { throw SamsungEventParserError.malformed } guard let eventData = message.event.data(using: .utf8), !eventData.isEmpty, eventData.count <= Self.maximumEventNameBytes else { throw SamsungEventParserError.invalidEventName } switch message.event { case "ms.channel.connect": guard let token = message.data?.token else { return .connected(token: nil) } guard let tokenData = token.data(using: .utf8), !tokenData.isEmpty, tokenData.count <= Self.maximumTokenBytes else { throw SamsungEventParserError.invalidToken } return .connected(token: token) case "ms.channel.unauthorized": return .unauthorized case "ms.channel.timeOut": return .timedOut case "ms.error": return .error default: return .unknown } } private struct Message: Decodable { let event: String let data: EventData? } private struct EventData: Decodable { let token: String? } }