import { describe, it, expect } from 'vitest'; import { encodeAX25, Frame, UnnumberedFrame, InformationFrame, SupervisoryFrame, buildIControl, buildSControl, buildUControl, encodeFrame, toHDLC } from '../src/frame'; import { Address } from '../src/address'; import { unescapeBuffer, computeFcs } from '../src/hdlc'; describe('Frame.buildSControl', () => { it('builds S control byte', () => { const c = buildSControl('RNR', 4, 0); // bits 0-1 == 01 and s-code 1 in bits 2-3 expect((c & 0x03)).toBe(0x01); }); }); describe('Frame.encodeFrame', () => { it('builds I control byte and roundtrips encode/parse', () => { const dst = new Address('DST', 0); const src = new Address('SRC', 1); const ns = 3; const nr = 5; const pf = 1; const control = buildIControl(ns, nr, pf); const info = new TextEncoder().encode('payload'); const f = new InformationFrame(src, dst, control, info, ns, nr, pf); const buf = encodeFrame(f); const parsed = Frame.fromBytes(buf); expect(parsed instanceof InformationFrame).toBe(true); const p = parsed as InformationFrame; expect(p.ns).toBe(ns); expect(p.nr).toBe(nr); expect(p.pf).toBe(pf); expect(new TextDecoder().decode(p.info)).toBe('payload'); }); it('encodes UI unnumbered frames including PID and info', () => { const dst = new Address('APRS', 0); const src = new Address('N0CALL', 0); const control = buildUControl('UI', 0); const info = new TextEncoder().encode('!Hello'); const f = new UnnumberedFrame(src, dst, control, 'UI', 0, info); f.pid = 0xf0; const buf = encodeFrame(f); const parsed = Frame.fromBytes(buf); expect(parsed instanceof UnnumberedFrame).toBe(true); const u = parsed as UnnumberedFrame; expect(u.pid).toBe(0xf0); expect(new TextDecoder().decode(u.info)).toBe('!Hello'); }); }); describe('Frame.fromBytes', () => { it('parses a simple UI frame', () => { const info = new TextEncoder().encode('hello'); const payload = encodeAX25('DEST-0', 'SRC-1', info, { ui: true }); const f = Frame.fromBytes(payload); expect(f.source).toBeInstanceOf(Address); expect(f.destination).toBeInstanceOf(Address); expect(f.source.toString()).toBe('SRC-1'); expect(f.destination.toString()).toBe('DEST-0'); expect(new TextDecoder().decode(f.info)).toBe('hello'); }); it('parses frames with multiple address fields', () => { const dest = new Address('DST', 0); const src = new Address('SRC', 1); const digi = new Address('DIGI', 2); const parts: number[] = []; parts.push(...dest.toBytes(false)); parts.push(...src.toBytes(false)); parts.push(...digi.toBytes(true)); parts.push(0x03); // control parts.push(0xf0); // pid parts.push(...new TextEncoder().encode('spec')); const payload = Uint8Array.from(parts); const f = Frame.fromBytes(payload); expect(f.source.toString()).toBe('SRC-1'); expect(f.destination.toString()).toBe('DST-0'); expect(new TextDecoder().decode(f.info)).toBe('spec'); }); }); describe('Frame.toHDLC', () => { it('produces HDLC frame with FCS via toHDLC', () => { const dst = new Address('APRS', 0); const src = new Address('N0CALL', 0); const control = buildUControl('UI', 0); const info = new TextEncoder().encode('!Hello'); const f = new UnnumberedFrame(src, dst, control, 'UI', 0, info); f.pid = 0xf0; const h = toHDLC(f); expect(h[0]).toBe(0x7e); expect(h[h.length - 1]).toBe(0x7e); const inner = h.slice(1, h.length - 1); const unescaped = unescapeBuffer(inner); // last two bytes of unescaped payload must equal computed FCS (LE) const ax = encodeFrame(f); const fcs = computeFcs(ax); expect(unescaped[unescaped.length - 2]).toBe(fcs & 0xff); expect(unescaped[unescaped.length - 1]).toBe((fcs >> 8) & 0xff); }); });