npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hamradio/packet

v1.1.1

Published

Low level packet parsing library (for radio protocols)

Readme

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.

// 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 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:
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.