import Foundation /// Messages exchanged with the Tangent Hub bridge. /// Wire framing: u32 little-endian length + JSON payload. /// NOTE: real Tangent Hub TIPC is a binary protocol on TCP 64246; this JSON /// framing is our internal bridge format — the macOS TIPC shim translates. /// Framing/parse logic (buffering, partial frames) is identical either way. public enum TangentMessage: Equatable, Sendable { case controlUpdate(PanelInput) case displayUpdate(line: Int, text: String) } public enum TangentWireError: Error { case badPayload } public enum TangentWire { public static func encode(_ msg: TangentMessage) -> Data { var obj: [String: Any] = [:] switch msg { case .controlUpdate(.encoder(let id, let delta)): obj = ["type": "encoder", "id": id, "delta": delta] case .controlUpdate(.button(let id, let pressed)): obj = ["type": "button", "id": id, "pressed": pressed] case .displayUpdate(let line, let text): obj = ["type": "display", "line": line, "text": text] } let payload = (try? JSONSerialization.data(withJSONObject: obj, options: [.sortedKeys])) ?? Data() var out = Data() var len = UInt32(payload.count).littleEndian withUnsafeBytes(of: &len) { out.append(contentsOf: $0) } out.append(payload) return out } /// Decode first complete frame from buffer, consuming it. nil if incomplete. public static func decodeFirst(_ buffer: inout Data) throws -> TangentMessage? { guard buffer.count >= 4 else { return nil } // Byte-wise LE assembly (Data slices may be misaligned for u32 loads). let b = [UInt8](buffer.prefix(4)) let length = UInt32(b[0]) | (UInt32(b[1]) << 8) | (UInt32(b[2]) << 16) | (UInt32(b[3]) << 24) guard buffer.count >= 4 + Int(length) else { return nil } let payload = buffer.subdata(in: buffer.startIndex + 4..