# 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 the `Dissected` result. - `Dissected`: an array of `Segment` objects 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. ```ts // 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`): ```ts 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 `Segment` and `Field` types describe the structure. You can store actual parsed values by using the `Field.value` property or by keeping a parallel `Map`/`Object` for the parsed values. - In the example above, `seg.fields` declare the shape. To keep parsed values, add a `values` property: ```ts seg.values = { dst_addr: new Uint8Array(dst), src_addr: new Uint8Array(src), control, pid, info }; ``` ## Handling bit fields and arrays - Use `FieldType.BITS` plus `Field.bits` for bit-level definitions. Read underlying bytes with `bytes()` or `uint8()` and extract bits per `BitField` definitions. -- For arrays (`WORDS`, `DWORDS`, `QWORDS`, `BYTES`) set `Field.length` and use the corresponding `Reader` method (`words`, `dwords`, `qwords`, `bytes`). Note: the `words`/`dwords`/`qwords` helpers 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 into `Writer` internals. ## Best practices for radio parsing - Always perform bounds checks (`Reader` does this internally via `checkBounds`). - Validate control/PID bytes (AX.25 UI typically uses `0x03` and `0xF0`). - 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; `Reader` reads primitives and advances offset safely. - Compose `Segment` arrays (`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 `Dissected` result suitable for display or further processing.