5.5 KiB
packet.js — Packet parsing
This project exposes lightweight types (FieldType, Field, Segment, Dissected, BitField)
and a Reader/Writer API to read/write binary packet data sequentially.
Below is a quick guide showing how to use the types together with Reader to
parse a packet and store parsed fields as a Dissected structure.
Concepts
FieldType: string constants describing how bytes should be interpreted (UINT8,UINT16_LE,BYTES,UTF8_STRING,BITS, etc.).- Field: describes a single named datum in a
Segment(type, name, optional length or bit definitions). Segment: a named collection of fields; segments form theDissectedresult.Dissected: an array ofSegmentobjects that represent the parsed packet.Reader: sequential reader that advances an offset and exposes methods (uint8,uint16,uint64,bytes,utf8String,cString,words,dwords,qwords, etc.) and performs bounds checking.
Example: APRS (AX.25 UI-frame) parsing
APRS (Automatic Packet Reporting System) commonly rides on AX.25 UI-frames used by amateur radio. AX.25 address blocks are fixed-width (7 bytes per address) and the frame typically contains:
- Destination address (7 bytes)
- Source address (7 bytes)
- Optional digipeaters (n * 7 bytes)
- Control (1 byte, usually 0x03 for UI)
- PID (1 byte, usually 0xF0)
- Information field (UTF-8 text, remaining bytes) — APRS payload
Below is a minimal TypeScript example showing how to parse an AX.25 UI-frame
binary into a Dissected structure using Reader. The example assumes the
frame has a single destination, single source, no digipeaters, and a UTF-8 info
field for simplicity.
// Example segment description (informational only)
const ax25Segment = {
name: 'ax25_ui_frame',
fields: [
{ type: FieldType.BYTES, name: 'dst_addr', length: 7 },
{ type: FieldType.BYTES, name: 'src_addr', length: 7 },
{ type: FieldType.UINT8, name: 'control' },
{ type: FieldType.UINT8, name: 'pid' },
{ type: FieldType.UTF8_STRING, name: 'info', length: undefined } // rest of buffer
]
};
Parsing function (using Reader):
import { Reader } from './src/index';
import { FieldType, Dissected, Segment } from './src/types';
/**
* Parse a raw AX.25 UI-frame ArrayBuffer into a Dissected structure.
*/
function parseAX25UIFrame(buf: ArrayBuffer): Dissected {
const r = new Reader(buf); // defaults to LittleEndian where applicable
const seg: Segment = { name: 'ax25_ui_frame', fields: [] };
// destination address (7 bytes)
const dst = r.bytes(7);
seg.fields.push({ name: 'dst_addr', type: FieldType.BYTES, length: 7 });
// store raw data for convenience (optional) — store a slice of the
// underlying buffer for just this field rather than copying the entire buffer.
seg.data = dst.buffer.slice(dst.byteOffset, dst.byteOffset + dst.byteLength);
// source address (7 bytes)
const src = r.bytes(7);
seg.fields.push({ name: 'src_addr', type: FieldType.BYTES, length: 7 });
// control (1 byte)
const control = r.uint8();
seg.fields.push({ name: 'control', type: FieldType.UINT8 });
// pid (1 byte)
const pid = r.uint8();
seg.fields.push({ name: 'pid', type: FieldType.UINT8 });
// info: remaining bytes decoded as UTF-8
const info = r.utf8String(); // reads to end by default
seg.fields.push({ name: 'info', type: FieldType.UTF8_STRING });
// return a Dissected array containing the single segment
return [seg];
}
Notes on storing parsed values:
- The
SegmentandFieldtypes describe the structure. You can store actual parsed values by using theField.valueproperty or by keeping a parallelMap/Objectfor the parsed values. - In the example above,
seg.fieldsdeclare the shape. To keep parsed values, add avaluesproperty:
seg.values = {
dst_addr: new Uint8Array(dst),
src_addr: new Uint8Array(src),
control,
pid,
info
};
Handling bit fields and arrays
- Use
FieldType.BITSplusField.bitsfor bit-level definitions. Read underlying bytes withbytes()oruint8()and extract bits perBitFielddefinitions. -- For arrays (WORDS,DWORDS,QWORDS,BYTES) setField.lengthand use the correspondingReadermethod (words,dwords,qwords,bytes). Note: thewords/dwords/qwordshelpers create aligned copies and return typed arrays that are safe to use on all platforms.
Helpers and test tips:
- Use
Reader.fromBytes()/Reader.fromString()to construct readers from convenience formats. - Use
Writer.toBytes()to obtain the written bytes; tests should prefer this method instead of reaching intoWriterinternals.
Best practices for radio parsing
- Always perform bounds checks (
Readerdoes this internally viacheckBounds). - Validate control/PID bytes (AX.25 UI typically uses
0x03and0xF0). - When parsing addresses in AX.25, remember each address byte is 7-bit ASCII shifted and the last address byte has the "end" flag; real-world parsing needs address unpacking logic (not shown in the minimal example).
Summary
- The provided types describe packet structure;
Readerreads primitives and advances offset safely. - Compose
Segmentarrays (Dissected) while parsing to represent packet structure and store parsed values next to their field descriptors for debugging, display, or serialization. - The APRS/AX.25 example demonstrates how a short radio protocol payload can be
parsed into a structured
Dissectedresult suitable for display or further processing.