AX.25 is the link-layer protocol most of amateur packet radio runs on. The v2.2 specification ends with a set of SDL diagrams (figures C4.1 to C4.7) that draw the connected-mode state machine: states like Disconnected, Awaiting Connection, Connected and Timer Recovery, with every event, guard and action on the arrows.

Each AX.25 stack I know of is somebody’s reading of those figures, typed into a switch statement. The readings differ in small ways, and when two stacks disagree there is nothing executable to check against, because the reference is a PDF.

So I put the state machine into data once, generated code from it, and have the runtimes walk the generated tables instead of carrying their own copy of the logic. This post describes how that fits together.

The pieces

  • ax25spec is the specification as source. It holds AX.25 v2.2 Revision 4 as Markdown, an in-progress v2.3 draft (each iteration has a git tag), and Markdown versions of KISS, multi-drop KISS with ACKMODE, IL2P and the FBB forwarding protocol. The state-machine figures are kept there as yEd graphml files, with YAML transcriptions generated from them and checked for drift in CI.
  • ax25sdl takes those transcriptions (ax25spec is a pinned submodule in it) and generates state tables in C#, TypeScript, Go, Rust, C, Python and JSON. The C# and TypeScript ones are published as Packet.Ax25.Sdl on NuGet and ax25sdl on npm. It also has a lint that checks every state and event is handled exactly once, and a set of golden traces that replay protocol exchanges against the tables, with expected results taken from the spec’s prose.
  • packet.net is the .NET runtime. Its session machine looks up the transition for the current state and event, checks the guard and runs the action list. It supplies what each action does (start T1, send an RR and so on) and nothing about which actions come when.
  • ax25-ts is the same thing in TypeScript, published as @packet-net/ax25, and checked against the .NET side in CI.

What packet.net is now

packet.net started as a library and is now also a packet radio node. You install it on a Pi or any Debian-like machine from the apt repository, and you get ports, NET/ROM, a web control panel, radio and rig control and a platform for apps. The project page has the details.

The libraries underneath are on NuGet for building your own software: Packet.Core, Packet.Ax25, Packet.Kiss, Packet.NetRom, Packet.Agw, Packet.Rhp2, Packet.Axudp and a few more. They are listed on the NuGet page.

It is tested continuously against LinBPQ, XRouter, rax25, Dire Wolf and a pair of NinoTNCs. A simulated radio channel, net-sim, is what lets most of those tests run without radios.

Changing a figure

Say a transition in Timer Recovery is wrong, or an erratum changes one. The change goes like this:

  1. The figure is edited in ax25spec, with the prose alongside it if that changes too.
  2. The YAML transcription is regenerated from the graphml.
  3. A pin-bump pull request in ax25sdl regenerates the tables for all seven languages.
  4. New Packet.Ax25.Sdl and ax25sdl packages are published.
  5. packet.net and ax25-ts bump their dependency and release.
  6. The tools built on them pick it up by bumping theirs.

No switch statement gets edited, and no language gets its own interpretation of the change. Every transition in the YAML can carry a citation back to the spec text or to how a known-good implementation behaves.

What sits on top

  • pdn-ax25-tools: axcall, axlisten, axinetd, axsocks and axtun, a standalone reimplementation of the familiar Linux ax25 tools that doesn’t need kernel AX.25 support.
  • Packet.Term, a terminal UI for connected-mode sessions, and pdn-web, a TNC2-style terminal in a web page over Web Serial. See AX.25 apps and tools.
  • pdn-soundmodem, a software TNC that turns a sound card, a FlexRadio or a web receiver into a KISS modem. See its page.
  • pico-node, packet node firmware in Rust for the Raspberry Pi Pico W with a NinoTNC. It follows the packet.net node and uses the same SDL tables.

The same approach was used later for APRS: five libraries and a shared set of test cases, checked against each other.

What isn’t done

The generated tables come from the v2.2 figures with errata applied. Transcribing the clean published v2.2 figures is still to do, so there is no way yet to choose between the two. Tests passing doesn’t mean the tables are right, which is why the interop runs against other stacks matter. Where a figure is itself defective, I flag it rather than quietly fixing the transcription.

More on the specification and the code generator is on the ax25spec and ax25sdl page.

Credits

The AX.25 specification authors and the packethacking group for the spec rewrite, John Wiseman G8BPQ and Paula Dowie G8PZT for the stacks this has to interoperate with, Thomas Habets for rax25, and the OARC community. Transcription mistakes are mine.