@eventmsg/core
Version:
EventMsgV3 TypeScript library - Core protocol implementation with transport abstraction
1 lines • 6.27 kB
Source Map (JSON)
{"version":3,"file":"byte-stuffing.cjs","names":["result: number[]"],"sources":["../../src/internal/byte-stuffing.ts"],"sourcesContent":["/**\n * EventMsgV3 Byte Stuffing Implementation\n *\n * Implements the exact byte stuffing algorithm from the C implementation\n * to ensure protocol compatibility.\n */\n\n/**\n * Control characters used in EventMsgV3 protocol\n */\nexport const CONTROL_CHARS = {\n SOH: 0x01, // Start of Header\n STX: 0x02, // Start of Text\n EOT: 0x04, // End of Transmission\n US: 0x1f, // Unit Separator\n ESC: 0x1b, // Escape Character\n} as const;\n\n/**\n * XOR mask for byte stuffing\n */\nconst STUFF_MASK = 0x20;\n\n/**\n * Check if a byte is a control character that needs stuffing\n */\nfunction isControlChar(byte: number): boolean {\n return (\n byte === CONTROL_CHARS.SOH ||\n byte === CONTROL_CHARS.STX ||\n byte === CONTROL_CHARS.EOT ||\n byte === CONTROL_CHARS.US ||\n byte === CONTROL_CHARS.ESC\n );\n}\n\n/**\n * Stuff (encode) bytes by escaping control characters\n *\n * Algorithm:\n * - If byte is a control character: Insert ESC, then XOR byte with 0x20\n * - Otherwise: Insert byte as-is\n *\n * @param data Input data to stuff\n * @returns Stuffed data\n */\nexport function stuff(data: Uint8Array): Uint8Array {\n const result: number[] = [];\n\n for (let i = 0; i < data.length; i++) {\n const byte = data[i];\n if (byte === undefined) {\n continue;\n }\n\n if (isControlChar(byte)) {\n // Escape the control character\n result.push(CONTROL_CHARS.ESC);\n result.push(byte ^ STUFF_MASK);\n } else {\n // Normal byte, no stuffing needed\n result.push(byte);\n }\n }\n\n return new Uint8Array(result);\n}\n\n/**\n * Unstuff (decode) bytes by handling escaped characters\n *\n * Algorithm:\n * - If byte is ESC: Mark next byte as escaped, continue\n * - If previous byte was ESC: XOR current byte with 0x20, add to output\n * - Otherwise: Add byte as-is to output\n *\n * @param data Stuffed data to unstuff\n * @returns Unstuffed data\n * @throws {Error} If data contains invalid escape sequences\n */\nexport function unstuff(data: Uint8Array): Uint8Array {\n const result: number[] = [];\n let escaped = false;\n\n for (let i = 0; i < data.length; i++) {\n const byte = data[i];\n if (byte === undefined) {\n continue;\n }\n if (escaped) {\n // Previous byte was ESC, so this byte is escaped\n result.push(byte ^ STUFF_MASK);\n escaped = false;\n } else if (byte === CONTROL_CHARS.ESC) {\n // This is an escape character, next byte will be escaped\n escaped = true;\n } else {\n // Normal byte\n result.push(byte);\n }\n }\n\n // Check for incomplete escape sequence\n if (escaped) {\n throw new Error(\n \"Invalid byte stuffing: data ends with incomplete escape sequence\"\n );\n }\n\n return new Uint8Array(result);\n}\n\n/**\n * Calculate the maximum possible size after stuffing\n * In worst case, every byte could be a control character, doubling the size\n *\n * @param originalSize Original data size\n * @returns Maximum size after stuffing\n */\nexport function getMaxStuffedSize(originalSize: number): number {\n return originalSize * 2; // Worst case: every byte is stuffed\n}\n\n/**\n * Calculate the minimum possible size after unstuffing\n * In best case, no bytes are stuffed\n *\n * @param stuffedSize Stuffed data size\n * @returns Minimum size after unstuffing\n */\nexport function getMinUnstuffedSize(stuffedSize: number): number {\n return Math.floor(stuffedSize / 2); // Worst case: every pair is stuffed\n}\n\n/**\n * Test if data contains any control characters that would need stuffing\n *\n * @param data Data to test\n * @returns True if data contains control characters\n */\nexport function needsStuffing(data: Uint8Array): boolean {\n for (let i = 0; i < data.length; i++) {\n const byte = data[i];\n\n if (byte !== undefined && isControlChar(byte)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Validate that stuffed data has proper escape sequences\n *\n * @param data Stuffed data to validate\n * @returns True if data has valid stuffing\n */\nexport function isValidStuffing(data: Uint8Array): boolean {\n let escaped = false;\n\n for (let i = 0; i < data.length; i++) {\n const byte = data[i];\n if (byte === undefined) {\n continue;\n }\n\n if (escaped) {\n // Previous byte was ESC, this byte should be valid escaped value\n const unescaped = byte ^ STUFF_MASK;\n if (!isControlChar(unescaped)) {\n return false; // Invalid escape sequence\n }\n escaped = false;\n } else if (byte === CONTROL_CHARS.ESC) {\n escaped = true;\n } else if (isControlChar(byte)) {\n return false; // Unescaped control character\n }\n }\n\n return !escaped; // Should not end with incomplete escape\n}\n"],"mappings":";;;;;;;;;;;AAUA,MAAa,gBAAgB;CAC3B,KAAK;CACL,KAAK;CACL,KAAK;CACL,IAAI;CACJ,KAAK;CACN;;;;AAKD,MAAM,aAAa;;;;AAKnB,SAAS,cAAc,MAAuB;AAC5C,QACE,SAAS,cAAc,OACvB,SAAS,cAAc,OACvB,SAAS,cAAc,OACvB,SAAS,cAAc,MACvB,SAAS,cAAc;;;;;;;;;;;;AAc3B,SAAgB,MAAM,MAA8B;CAClD,MAAMA,SAAmB,EAAE;AAE3B,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,OAAO,KAAK;AAClB,MAAI,SAAS,OACX;AAGF,MAAI,cAAc,KAAK,EAAE;AAEvB,UAAO,KAAK,cAAc,IAAI;AAC9B,UAAO,KAAK,OAAO,WAAW;QAG9B,QAAO,KAAK,KAAK;;AAIrB,QAAO,IAAI,WAAW,OAAO;;;;;;;;;;;;;;AAe/B,SAAgB,QAAQ,MAA8B;CACpD,MAAMA,SAAmB,EAAE;CAC3B,IAAI,UAAU;AAEd,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,OAAO,KAAK;AAClB,MAAI,SAAS,OACX;AAEF,MAAI,SAAS;AAEX,UAAO,KAAK,OAAO,WAAW;AAC9B,aAAU;aACD,SAAS,cAAc,IAEhC,WAAU;MAGV,QAAO,KAAK,KAAK;;AAKrB,KAAI,QACF,OAAM,IAAI,MACR,mEACD;AAGH,QAAO,IAAI,WAAW,OAAO;;;;;;;;;AAU/B,SAAgB,kBAAkB,cAA8B;AAC9D,QAAO,eAAe;;;;;;;;;AAUxB,SAAgB,oBAAoB,aAA6B;AAC/D,QAAO,KAAK,MAAM,cAAc,EAAE;;;;;;;;AASpC,SAAgB,cAAc,MAA2B;AACvD,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,OAAO,KAAK;AAElB,MAAI,SAAS,UAAa,cAAc,KAAK,CAC3C,QAAO;;AAGX,QAAO;;;;;;;;AAST,SAAgB,gBAAgB,MAA2B;CACzD,IAAI,UAAU;AAEd,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,OAAO,KAAK;AAClB,MAAI,SAAS,OACX;AAGF,MAAI,SAAS;AAGX,OAAI,CAAC,cADa,OAAO,WACI,CAC3B,QAAO;AAET,aAAU;aACD,SAAS,cAAc,IAChC,WAAU;WACD,cAAc,KAAK,CAC5B,QAAO;;AAIX,QAAO,CAAC"}