import { describe, it, expect } from "vitest"; import { KISS_FEND, KISS_FESC, KISS_TFEND, KISS_TFESC, encodeKISSFrame, decodeKISSFrame, kissFrameReader, kissFrameWriter, type KISSFrame, } from "../src/kiss"; const u8 = (arr: number[]) => Uint8Array.from(arr); describe("kiss.ts", () => { it("encodeKISSFrame produces proper FEND wrappers and decodes back", () => { const data = u8([1, 2, 3]); const buf = encodeKISSFrame(data); expect(buf[0]).toBe(KISS_FEND); expect(buf[buf.length - 1]).toBe(KISS_FEND); const frame = decodeKISSFrame(buf); expect(frame).not.toBeNull(); expect(frame!.port).toBe(0); expect(frame!.command).toBe(0); expect(Array.from(frame!.data)).toEqual(Array.from(data)); }); it("encodes and decodes escape sequences correctly", () => { const data = u8([0x01, KISS_FEND, 0x02, KISS_FESC, 0x03]); const encoded = encodeKISSFrame(data, 1, 2); // command byte should encode port=1, command=2 expect(encoded[1]).toBe(((1 & 0x0f) << 4) | (2 & 0x0f)); // ensure escaped bytes present const arr = Array.from(encoded); // look for FESC TFEND and FESC TFESC sequences const hasFescTfend = arr.includes(KISS_FESC) && arr.includes(KISS_TFEND); const hasFescTfesc = arr.includes(KISS_FESC) && arr.includes(KISS_TFESC); expect(hasFescTfend).toBeTruthy(); expect(hasFescTfesc).toBeTruthy(); const decoded = decodeKISSFrame(encoded)!; expect(decoded.port).toBe(1); expect(decoded.command).toBe(2); expect(Array.from(decoded.data)).toEqual(Array.from(data)); }); it("decodeKISSFrame returns null for invalid frames", () => { // missing FEND start expect(decodeKISSFrame(u8([0x00, 0x00, 0x00]))).toBeNull(); // invalid escape sequence const bad = u8([KISS_FEND, 0x00, KISS_FESC, 0x00, KISS_FEND]); expect(decodeKISSFrame(bad)).toBeNull(); }); it("parses port and command from the command byte", () => { const port = 3; const command = 4; const data = u8([9, 8]); const cmd = ((port & 0x0f) << 4) | (command & 0x0f); const buf = u8([KISS_FEND, cmd, ...Array.from(data), KISS_FEND]); const f = decodeKISSFrame(buf)!; expect(f.port).toBe(port); expect(f.command).toBe(command); expect(Array.from(f.data)).toEqual(Array.from(data)); }); it("kissFrameReader yields frames assembled across chunks and ignores incomplete frames", async () => { // create two frames and split them across chunks const a = encodeKISSFrame(u8([1, 2, 3])); const b = encodeKISSFrame(u8([4, 5])); // craft an async iterable that yields multiple chunks, splitting frames async function* source() { // first chunk: first half of a yield u8(Array.from(a.slice(0, 2))); // second chunk: rest of a plus start of b yield u8(Array.from(a.slice(2)).concat(Array.from(b.slice(0, 1)))); // third chunk: rest of b yield u8(Array.from(b.slice(1))); } const results: KISSFrame[] = []; for await (const fr of kissFrameReader(source())) { results.push(fr); } expect(results.length).toBe(2); expect(Array.from(results[0].data)).toEqual([1, 2, 3]); expect(Array.from(results[1].data)).toEqual([4, 5]); // incomplete frame (no trailing FEND) should not yield async function* sourceIncomplete() { const partial = Array.from(a.slice(0, a.length - 1)); yield u8(partial); } const got: KISSFrame[] = []; for await (const fr of kissFrameReader(sourceIncomplete())) got.push(fr); expect(got.length).toBe(0); }); it("kissFrameWriter calls the sink with the encoded buffer", async () => { const received: Uint8Array[] = []; const sink = async (data: Uint8Array) => { received.push(data); }; const frame: KISSFrame = { port: 2, command: 1, data: u8([7, 8, 9]) }; await kissFrameWriter(sink, frame); expect(received.length).toBe(1); const expected = encodeKISSFrame(frame.data, frame.port, frame.command); expect(Array.from(received[0])).toEqual(Array.from(expected)); }); });