// KISS (Keep It Simple Stupid) frame reader/writer for AX.25 // See: http://www.ax25.net/kiss.aspx export const KISS_FEND = 0xc0; export const KISS_FESC = 0xdb; export const KISS_TFEND = 0xdc; export const KISS_TFESC = 0xdd; export type KISSFrame = { port: number; // 0-15 command: number; // 0 = data, 1 = TX delay, etc. data: Uint8Array; }; // Encode a KISS frame (data only, port 0 by default) export function encodeKISSFrame(data: Uint8Array, port = 0, command = 0): Uint8Array { const out: number[] = [KISS_FEND]; // Command byte: upper 4 bits = port, lower 4 bits = command out.push(((port & 0x0f) << 4) | (command & 0x0f)); for (const b of data) { if (b === KISS_FEND) { out.push(KISS_FESC, KISS_TFEND); } else if (b === KISS_FESC) { out.push(KISS_FESC, KISS_TFESC); } else { out.push(b); } } out.push(KISS_FEND); return Uint8Array.from(out); } // Decode a KISS frame (returns null if not a valid frame) export function decodeKISSFrame(buf: Uint8Array): KISSFrame | null { if (buf.length < 3 || buf[0] !== KISS_FEND || buf[buf.length - 1] !== KISS_FEND) return null; let i = 1; const cmd = buf[i++]; const port = (cmd >> 4) & 0x0f; const command = cmd & 0x0f; const data: number[] = []; while (i < buf.length - 1) { const b = buf[i++]; if (b === KISS_FESC) { const next = buf[i++]; if (next === KISS_TFEND) data.push(KISS_FEND); else if (next === KISS_TFESC) data.push(KISS_FESC); else return null; // Invalid escape } else { data.push(b); } } return { port, command, data: Uint8Array.from(data) }; } // Async generator to extract KISS frames from a stream of bytes export async function* kissFrameReader(source: AsyncIterable): AsyncGenerator { let buffer: number[] = []; let inFrame = false; for await (const chunk of source) { for (const b of chunk) { if (b === KISS_FEND) { if (inFrame && buffer.length > 0) { const frame = decodeKISSFrame(Uint8Array.from([KISS_FEND, ...buffer, KISS_FEND])); if (frame) yield frame; } buffer = []; inFrame = true; } else if (inFrame) { buffer.push(b); } } } } // Write a KISS frame to a sink (e.g., serial port) export async function kissFrameWriter(sink: (data: Uint8Array) => Promise, frame: KISSFrame) { const buf = encodeKISSFrame(frame.data, frame.port, frame.command); await sink(buf); }