export const NATIVE_PROTOCOL_MAGIC = "PIV1"; export const NATIVE_HEADER_SIZE = 20; const MAX_PAYLOAD_BYTES = 8 * 1024 * 1024; export enum NativeMessageKind { Ready = 1, Frame = 2, End = 3, Error = 4, Info = 5, } export interface VideoFrame { width: number; height: number; stride: number; sequence: number; pixels: Buffer; } export type NativeMessage = | { kind: NativeMessageKind.Ready; width: number; height: number } | { kind: NativeMessageKind.Frame; frame: VideoFrame } | { kind: NativeMessageKind.End; reason: string } | { kind: NativeMessageKind.Error; message: string } | { kind: NativeMessageKind.Info; message: string }; export class NativeFrameParser { private pending = Buffer.alloc(0); push(chunk: Uint8Array): NativeMessage[] { if (chunk.byteLength === 0) return []; this.pending = this.pending.byteLength === 0 ? Buffer.from(chunk) : Buffer.concat([this.pending, chunk]); const messages: NativeMessage[] = []; while (this.pending.byteLength >= NATIVE_HEADER_SIZE) { this.validateHeader(); const payloadLength = this.pending.readUInt32LE(12); if (payloadLength > MAX_PAYLOAD_BYTES) { throw new Error(`Native message payload exceeds ${MAX_PAYLOAD_BYTES} bytes`); } const messageLength = NATIVE_HEADER_SIZE + payloadLength; if (this.pending.byteLength < messageLength) break; const message = this.decodeMessage(this.pending.subarray(0, messageLength)); messages.push(message); this.pending = this.pending.subarray(messageLength); } return messages; } private validateHeader(): void { if (this.pending.toString("ascii", 0, 4) !== NATIVE_PROTOCOL_MAGIC) { throw new Error("Invalid native protocol magic"); } } private decodeMessage(message: Buffer): NativeMessage { const kind = message.readUInt8(4) as NativeMessageKind; const width = message.readUInt16LE(6); const height = message.readUInt16LE(8); const stride = message.readUInt16LE(10); const payloadLength = message.readUInt32LE(12); const sequence = message.readUInt32LE(16); const payload = Buffer.from(message.subarray(NATIVE_HEADER_SIZE, NATIVE_HEADER_SIZE + payloadLength)); switch (kind) { case NativeMessageKind.Ready: return { kind, width, height }; case NativeMessageKind.Frame: this.validateFrame(width, height, stride, payloadLength); return { kind, frame: { width, height, stride, sequence, pixels: payload }, }; case NativeMessageKind.End: return { kind, reason: payload.toString("utf8") }; case NativeMessageKind.Error: return { kind, message: payload.toString("utf8") }; case NativeMessageKind.Info: return { kind, message: payload.toString("utf8") }; default: throw new Error(`Unknown native message kind: ${kind}`); } } private validateFrame(width: number, height: number, stride: number, payloadLength: number): void { if (width === 0 || height === 0) { throw new Error("Native frame dimensions must be positive"); } if (stride < width * 4 || stride % 4 !== 0) { throw new Error("Native frame stride is invalid for rgb0 pixels"); } if (payloadLength !== stride * height) { throw new Error("Native frame payload length does not match its geometry"); } } }