83 lines
2.6 KiB
Swift
83 lines
2.6 KiB
Swift
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?
|
|
}
|
|
}
|