import { describe, it, expect } from 'vitest'; import { base64ToBytes, hexToBytes, BufferReader, BufferWriter } from '../src/parser'; describe('base64ToBytes', () => { it('decodes a simple base64 string', () => { const bytes = base64ToBytes('aGVsbG8=', 5); // "hello" expect(Array.from(bytes)).toEqual([104, 101, 108, 108, 111]); }); it('handles empty string', () => { const bytes = base64ToBytes('', 0); expect(bytes).toBeInstanceOf(Uint8Array); expect(bytes.length).toBe(0); }); }); describe('BufferReader', () => { it('readByte and peekByte advance/inspect correctly', () => { const buf = new Uint8Array([1, 2, 3]); const r = new BufferReader(buf); expect(r.peekByte()).toBe(1); expect(r.readByte()).toBe(1); expect(r.peekByte()).toBe(2); }); it('readBytes with and without length', () => { const buf = new Uint8Array([10, 11, 12, 13]); const r = new BufferReader(buf); const a = r.readBytes(2); expect(Array.from(a)).toEqual([10, 11]); const b = r.readBytes(); expect(Array.from(b)).toEqual([12, 13]); }); it('hasMore and remainingBytes reflect position', () => { const buf = new Uint8Array([5, 6]); const r = new BufferReader(buf); expect(r.hasMore()).toBe(true); expect(r.remainingBytes()).toBe(2); r.readByte(); expect(r.remainingBytes()).toBe(1); r.readByte(); expect(r.hasMore()).toBe(false); }); it('reads little-endian unsigned ints', () => { const r16 = new BufferReader(new Uint8Array([0x34, 0x12])); expect(r16.readUint16LE()).toBe(0x1234); const r32 = new BufferReader(new Uint8Array([0x78, 0x56, 0x34, 0x12])); expect(r32.readUint32LE()).toBe(0x12345678); }); it('reads signed ints with two/four bytes (negative)', () => { const r16 = new BufferReader(new Uint8Array([0xff, 0xff])); expect(r16.readInt16LE()).toBe(-1); const r32 = new BufferReader(new Uint8Array([0xff, 0xff, 0xff, 0xff])); expect(r32.readInt32LE()).toBe(-1); }); it('readTimestamp returns Date with seconds->ms conversion', () => { const r = new BufferReader(new Uint8Array([0x01, 0x00, 0x00, 0x00])); const d = r.readTimestamp(); expect(d.getTime()).toBe(1000); }); }); describe('sizedStringToBytes', () => { it('decodes hex string of correct length', () => { // 4 bytes = 8 hex chars const hex = 'deadbeef'; const result = hexToBytes(hex, 4); expect(Array.from(result)).toEqual([0xde, 0xad, 0xbe, 0xef]); }); it('decodes base64 string of correct length', () => { // 4 bytes = 8 hex chars, base64 for [0xde, 0xad, 0xbe, 0xef] is '3q2+7w==' const b64 = '3q2+7w=='; const result = base64ToBytes(b64, 4); expect(Array.from(result)).toEqual([0xde, 0xad, 0xbe, 0xef]); }); it('throws on invalid string length', () => { expect(() => hexToBytes('abc', 4)).toThrow(); expect(() => hexToBytes('deadbeef00', 4)).toThrow(); }); }); describe('BufferWriter', () => { it('writeByte and toBytes', () => { const w = new BufferWriter(); w.writeByte(0x12); w.writeByte(0x34); expect(Array.from(w.toBytes())).toEqual([0x12, 0x34]); }); it('writeBytes appends bytes', () => { const w = new BufferWriter(); w.writeBytes(new Uint8Array([1, 2, 3])); expect(Array.from(w.toBytes())).toEqual([1, 2, 3]); }); it('writeUint16LE writes little-endian', () => { const w = new BufferWriter(); w.writeUint16LE(0x1234); expect(Array.from(w.toBytes())).toEqual([0x34, 0x12]); }); it('writeUint32LE writes little-endian', () => { const w = new BufferWriter(); w.writeUint32LE(0x12345678); expect(Array.from(w.toBytes())).toEqual([0x78, 0x56, 0x34, 0x12]); }); it('writeInt16LE writes signed values', () => { const w = new BufferWriter(); w.writeInt16LE(-1); expect(Array.from(w.toBytes())).toEqual([0xff, 0xff]); const w2 = new BufferWriter(); w2.writeInt16LE(0x1234); expect(Array.from(w2.toBytes())).toEqual([0x34, 0x12]); }); it('writeInt32LE writes signed values', () => { const w = new BufferWriter(); w.writeInt32LE(-1); expect(Array.from(w.toBytes())).toEqual([0xff, 0xff, 0xff, 0xff]); const w2 = new BufferWriter(); w2.writeInt32LE(0x12345678); expect(Array.from(w2.toBytes())).toEqual([0x78, 0x56, 0x34, 0x12]); }); it('writeTimestamp writes seconds as uint32le', () => { const w = new BufferWriter(); const date = new Date(1000); // 1 second w.writeTimestamp(date); expect(Array.from(w.toBytes())).toEqual([0x01, 0x00, 0x00, 0x00]); }); it('BufferWriter output can be read back by BufferReader', () => { const w = new BufferWriter(); w.writeByte(0x42); w.writeUint16LE(0x1234); w.writeInt16LE(-2); w.writeUint32LE(0xdeadbeef); w.writeInt32LE(-123456); w.writeBytes(new Uint8Array([0x01, 0x02])); const date = new Date(5000); // 5 seconds w.writeTimestamp(date); const bytes = w.toBytes(); const r = new BufferReader(bytes); expect(r.readByte()).toBe(0x42); expect(r.readUint16LE()).toBe(0x1234); expect(r.readInt16LE()).toBe(-2); expect(r.readUint32LE()).toBe(0xdeadbeef); expect(r.readInt32LE()).toBe(-123456); expect(Array.from(r.readBytes(2))).toEqual([0x01, 0x02]); const readDate = r.readTimestamp(); expect(readDate.getTime()).toBe(5000); expect(r.hasMore()).toBe(false); }); it('BufferReader throws or returns undefined if reading past end', () => { const r = new BufferReader(new Uint8Array([1, 2])); r.readByte(); r.readByte(); expect(() => r.readByte()).toThrow(); }); it('BufferWriter handles multiple writeBytes calls', () => { const w = new BufferWriter(); w.writeBytes(new Uint8Array([1, 2])); w.writeBytes(new Uint8Array([3, 4])); expect(Array.from(w.toBytes())).toEqual([1, 2, 3, 4]); }); it('encodedStringToBytes decodes raw string', () => { const str = String.fromCharCode(0xde, 0xad, 0xbe, 0xef); const bytes = new Uint8Array(4); for (let i = 0; i < 4; i++) bytes[i] = str.charCodeAt(i) & 0xff; expect(Array.from(bytes)).toEqual([0xde, 0xad, 0xbe, 0xef]); }); it('hexToBytes returns different length for wrong-size hex', () => { expect(() => hexToBytes('deadbe', 4)).toThrow(); }); it('base64ToBytes handles URL-safe base64', () => { // [0xde, 0xad, 0xbe, 0xef] in URL-safe base64: '3q2-7w==' const bytes = base64ToBytes('3q2-7w==', 4); expect(Array.from(bytes)).toEqual([0xde, 0xad, 0xbe, 0xef]); }); });