From 83a0deab20647633a75cb7f0d8cb40d70912aa4e Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Wed, 25 Dec 2024 23:14:48 -0600 Subject: [PATCH] bare DDP packet decoding --- packages/ddp/src/index.ts | 116 ++++++++++++++++++++++++++++++ packages/ddp/tests/decode.test.js | 62 +++++++++++++++- 2 files changed, 175 insertions(+), 3 deletions(-) diff --git a/packages/ddp/src/index.ts b/packages/ddp/src/index.ts index e69de29..e42d759 100644 --- a/packages/ddp/src/index.ts +++ b/packages/ddp/src/index.ts @@ -0,0 +1,116 @@ +export enum DDPDataType { + UNDEFINED = 0, + RGB = 1, + HSL = 2, + RGBW = 3, + GRAYSCALE = 4, +} + +export enum DDPID { + JSON_CONTROL = 246, + JSON_CONFIG = 250, + JSON_STATUS = 251, + DMX_TRANSIT = 254, + ALL = 255, +} + +export type DDPHeader = { + flags: { + version: number; + timecode: boolean; + storage: boolean; + reply: boolean; + query: boolean; + push: boolean; + }; + sequenceNumber: number; + dataType: { + standard: boolean; + type: number; + bitsPerPixel: number; + }; + sourceOrDestinationID: number; + dataOffset: number; + dataLength: number; + timecode?: { + seconds: number; + fractionalSeconds: number; + }; +}; + +export type DDPPacket = { + header: DDPHeader; + data: Uint8Array; +}; + +export function decode(bytes: Uint8Array): DDPPacket { + if (bytes.length < 10) { + throw new Error('DDP packet must be at least 10 bytes'); + } + + const flagsByte = bytes[0]; + + const flags = { + version: (flagsByte & 0xc0) >> 6, + timecode: (flagsByte & 0x10) >> 4 === 1, + storage: (flagsByte & 0x08) >> 3 === 1, + reply: (flagsByte & 0x04) >> 2 === 1, + query: (flagsByte & 0x02) >> 1 === 1, + push: (flagsByte & 0x01) === 1, + }; + const sequenceNumber = bytes[1] & 0x0f; + + const dataTypeByte = bytes[2]; + + const dataType = { + standard: dataTypeByte >> 7 === 0, + type: (dataTypeByte & 0x38) >> 3, + bitsPerPixel: dataTypeByte & 0x07, + }; + + if (dataType.bitsPerPixel > 1) { + if (dataType.bitsPerPixel === 2) { + dataType.bitsPerPixel = 4; + } else if (dataType.bitsPerPixel === 3) { + dataType.bitsPerPixel = 8; + } else if (dataType.bitsPerPixel === 4) { + dataType.bitsPerPixel = 16; + } else if (dataType.bitsPerPixel === 5) { + dataType.bitsPerPixel = 24; + } else if (dataType.bitsPerPixel === 6) { + dataType.bitsPerPixel = 32; + } else { + throw new Error('unknown DDP size'); + } + } + + const sourceOrDestinationID = bytes[3]; + const dataOffset = (bytes[4] << 24) + (bytes[5] << 16) + (bytes[6] << 8) + bytes[7]; + const dataLength = (bytes[8] << 8) + bytes[9]; + + const header: DDPHeader = { + flags, + sequenceNumber, + dataType, + sourceOrDestinationID, + dataOffset, + dataLength, + }; + + let dataStart = 10 + if (flags.timecode) { + if (bytes.length < 14) { + throw new Error('DDP packet with timecode must be at least 14 bytes'); + } + dataStart = 14 + header.timecode = { + seconds: (bytes[10] << 8) + bytes[11], + fractionalSeconds: (bytes[12] << 8) + bytes[13], + }; + } + + return { + header, + data: bytes.subarray(dataStart) + }; +} diff --git a/packages/ddp/tests/decode.test.js b/packages/ddp/tests/decode.test.js index 7bbb439..d9c6e8f 100644 --- a/packages/ddp/tests/decode.test.js +++ b/packages/ddp/tests/decode.test.js @@ -3,16 +3,72 @@ const { describe, it } = require('node:test'); const ddp = require('../dist/cjs/index'); const goodTests = [ + { + description: 'simple DDP packet', + bytes: new Uint8Array([0x40, 0x0f, 0x92, 0x01, 0x45, 0x67, 0x89, 0x10, 0x10, 0x11]), + expected: { + header: { + flags: { + version: 1, + timecode: false, + storage: false, + reply: false, + query: false, + push: false, + }, + sequenceNumber: 15, + dataType: { + standard: false, + type: 2, + bitsPerPixel: 4, + }, + sourceOrDestinationID: 1, + dataOffset: 1164413200, + dataLength: 4113, + }, + data: new Uint8Array([]) + }, + }, + { + description: 'DDP packet with timecode', + bytes: new Uint8Array([0x50, 0x0f, 0x92, 0x01, 0x45, 0x67, 0x89, 0x10, 0x10, 0x11, 0x10, 0x11, 0x11, 0x10]), + expected: { + header: { + flags: { + version: 1, + timecode: true, + storage: false, + reply: false, + query: false, + push: false, + }, + sequenceNumber: 15, + dataType: { + standard: false, + type: 2, + bitsPerPixel: 4, + }, + sourceOrDestinationID: 1, + dataOffset: 1164413200, + dataLength: 4113, + timecode: { + seconds: 4113, + fractionalSeconds: 4368, + }, + }, + data: new Uint8Array([]) + + }, + }, ]; -const badTests = [ - -]; +const badTests = []; describe('DDP Message Decoding Pass', () => { goodTests.forEach((messageTest) => { it(messageTest.description, () => { const decoded = ddp.decode(messageTest.bytes); + console.log(decoded.header); deepEqual(decoded, messageTest.expected); }); });