cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

fix/hero-release-note
goq / portal / frame-envelope.test.mjs
frame-envelope.test.mjs 4.3K
1import assert from 'node:assert/strict';2import test from 'node:test';34import {5 FRAME_ENVELOPE_HEADER_LENGTH,6 MAX_FRAME_PAYLOAD_LENGTH,7 parseFrameEnvelope,8} from './frame-envelope.mjs';910function frameEnvelope({11 codec = 1,12 flags = 7,13 width = 1280,14 height = 800,15 payload = Uint8Array.of(0, 0, 0, 1, 0x65),16 payloadLength = payload.length,17 sequence = 42n,18 captureTimestamp = 123456n,19 pts = 98765n,20} = {}) {21 const buffer = new ArrayBuffer(FRAME_ENVELOPE_HEADER_LENGTH + payload.length);22 const bytes = new Uint8Array(buffer);23 const view = new DataView(buffer);24 bytes.set([0x53, 0x47, 0x46, 0x52, 1, codec, flags, 0]);25 view.setUint16(8, width, false);26 view.setUint16(10, height, false);27 view.setUint32(12, payloadLength, false);28 view.setBigUint64(16, sequence, false);29 view.setBigUint64(24, captureTimestamp, false);30 view.setBigInt64(32, pts, false);31 bytes.set(payload, FRAME_ENVELOPE_HEADER_LENGTH);32 return buffer;33}3435test('parses an exact v1 frame envelope without copying its payload', () => {36 const buffer = frameEnvelope();37 const frame = parseFrameEnvelope(buffer);38 assert.deepEqual(39 {40 width: frame.width,41 height: frame.height,42 codec: frame.codec,43 keyframe: frame.keyframe,44 discontinuity: frame.discontinuity,45 codecConfig: frame.codecConfig,46 sequence: frame.sequence,47 captureTimestampMicros: frame.captureTimestampMicros,48 ptsMicros: frame.ptsMicros,49 },50 {51 width: 1280,52 height: 800,53 codec: 'h264',54 keyframe: true,55 discontinuity: true,56 codecConfig: true,57 sequence: 42,58 captureTimestampMicros: 123456,59 ptsMicros: 98765,60 },61 );62 assert.deepEqual([...frame.data], [0, 0, 0, 1, 0x65]);63 new Uint8Array(buffer)[FRAME_ENVELOPE_HEADER_LENGTH] = 9;64 assert.equal(frame.data[0], 9);65});6667test('flag bit positions match the sigil-protocol wire assignments', () => {68 const discontinuityOnly = parseFrameEnvelope(frameEnvelope({ flags: 0b100 }));69 assert.equal(discontinuityOnly.keyframe, false);70 assert.equal(discontinuityOnly.codecConfig, false);71 assert.equal(discontinuityOnly.discontinuity, true);7273 const keyframeConfig = parseFrameEnvelope(frameEnvelope({ flags: 0b011 }));74 assert.equal(keyframeConfig.keyframe, true);75 assert.equal(keyframeConfig.codecConfig, true);76 assert.equal(keyframeConfig.discontinuity, false);77});7879test('maps optional integer sentinels to null', () => {80 const frame = parseFrameEnvelope(frameEnvelope({81 sequence: 0xffffffffffffffffn,82 captureTimestamp: 0xffffffffffffffffn,83 pts: -0x8000000000000000n,84 }));85 assert.equal(frame.sequence, null);86 assert.equal(frame.captureTimestampMicros, null);87 assert.equal(frame.ptsMicros, null);88});8990test('rejects malformed identity, version, codec, flags, and reserved fields', () => {91 const mutations = [92 (bytes) => { bytes[0] = 0; },93 (bytes) => { bytes[4] = 2; },94 (bytes) => { bytes[5] = 0; },95 (bytes) => { bytes[5] = 2; },96 (bytes) => { bytes[5] = 3; },97 (bytes) => { bytes[6] = 0x80; },98 (bytes) => { bytes[6] = 0b010; },99 (bytes) => { bytes[7] = 1; },100 ];101 for (const mutate of mutations) {102 const buffer = frameEnvelope();103 mutate(new Uint8Array(buffer));104 assert.throws(() => parseFrameEnvelope(buffer));105 }106});107108test('rejects invalid dimensions and all length mismatches', () => {109 assert.throws(() => parseFrameEnvelope(frameEnvelope({ width: 0 })));110 assert.throws(() => parseFrameEnvelope(frameEnvelope({ width: 8192, height: 8192 })));111 assert.throws(() => parseFrameEnvelope(frameEnvelope({ payloadLength: 0 })));112 assert.throws(() => parseFrameEnvelope(frameEnvelope({ payloadLength: 4 })));113 assert.throws(() => parseFrameEnvelope(frameEnvelope({ payloadLength: MAX_FRAME_PAYLOAD_LENGTH + 1 })));114 assert.throws(() => parseFrameEnvelope(new ArrayBuffer(FRAME_ENVELOPE_HEADER_LENGTH - 1)));115 assert.throws(() => parseFrameEnvelope(new Uint8Array(frameEnvelope())));116});117118test('rejects timestamps that cannot be represented exactly', () => {119 assert.throws(() => parseFrameEnvelope(frameEnvelope({ sequence: 9007199254740992n })));120 assert.throws(() => parseFrameEnvelope(frameEnvelope({ captureTimestamp: 9007199254740992n })));121 assert.throws(() => parseFrameEnvelope(frameEnvelope({ pts: -1n })));122 assert.throws(() => parseFrameEnvelope(frameEnvelope({ pts: 9007199254740992n })));123});