Prepared for release
This commit is contained in:
139
README.md
Normal file
139
README.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user