Initial import

This commit is contained in:
2026-03-11 19:08:39 +01:00
commit 5bf12c326a
14 changed files with 4601 additions and 0 deletions

99
test/frame.test.ts Normal file
View File

@@ -0,0 +1,99 @@
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);
});
});