Documentation
¶
Overview ¶
Package ax25conn implements an AX.25 v2.0 / v2.2 LAPB data-link state machine for outbound (client) connections. One graywolf session corresponds to one (local-addr, peer-addr, channel) triple. The package owns its own goroutine per session, consumes non-UI frames from pkg/app/ingress, and emits encoded frames through pkg/txgovernor.
Outbound-only: this package responds to unsolicited inbound SABMs with DM. It is not a BBS host. See .context/2026-05-01-ax25-terminal-brainstorm.md for the design rationale.
Reference implementations: ax25-tools (axcall.c, ax25d.c) and the Linux kernel net/ax25/ stack. See CREDITS.md for the attribution policy and per-function source citations.
Index ¶
- Constants
- Variables
- func EncodeControl(c Control, mod128 bool) ([]byte, error)
- type Backoff
- type Clock
- type Condition
- type Control
- type Event
- type EventKind
- type Frame
- type FrameKind
- type LinkStats
- type Manager
- type ManagerConfig
- type OutEvent
- type OutEventKind
- type Session
- type SessionConfig
- type State
- type Stopper
Constants ¶
const ( // DefaultT1 — outstanding-I-frame ack timer. Kernel AX25_DEF_T1=10s. DefaultT1 = 10 * time.Second // DefaultT2 — response delay / piggyback timer. Kernel AX25_DEF_T2=3s. DefaultT2 = 3 * time.Second // DefaultT3 — link-inactivity probe. Kernel AX25_DEF_T3=300s (5min). DefaultT3 = 300 * time.Second // DefaultIdle — optional auto-disconnect. Kernel AX25_DEF_IDLE=0 // (disabled). Graywolf v1 inherits the disabled default; advanced // panel may surface as a future feature. DefaultIdle time.Duration = 0 // DefaultHeartbeat — housekeeping cadence (clears OWN_RX_BUSY, // emits RR(F=0,rsp) when own buffer drains). Kernel hard-codes 5s // at ax25_timer.c:50. Not user-tunable per kernel; matched here. DefaultHeartbeat = 5 * time.Second // DefaultN2 — retry count before giving up. Kernel AX25_DEF_N2=10. DefaultN2 = 10 // DefaultPaclen — max I-field byte count. Kernel AX25_DEF_PACLEN=256. DefaultPaclen = 256 // DefaultWindowMod8 — k for modulo-8. Kernel AX25_DEF_WINDOW=2. // Note: K3NA and v2.2 spec examples often quote 4; the kernel ships // 2 because it's the safest "default polite" window for shared // channels. We match the kernel. DefaultWindowMod8 = 2 // DefaultWindowMod128 — k for modulo-128. Kernel AX25_DEF_EWINDOW=32. DefaultWindowMod128 = 32 // DefaultStatsTick — cadence at which an active session emits // OutLinkStats so the UI telemetry panel can sparkline RTT and seq // numbers. Fires only while CONNECTED; never blocks. DefaultStatsTick = time.Second )
Default tuning constants. Values inherited from the Linux kernel net/ax25/ defines (AX25_DEF_T1, AX25_DEF_T2, AX25_DEF_T3, AX25_DEF_N2, AX25_DEF_PACLEN, AX25_DEF_WINDOW, AX25_DEF_EWINDOW, AX25_DEF_BACKOFF, AX25_DEF_IDLE) at include/net/ax25.h:148-160 in v6.12. Operators override per-session via the saved AX25SessionProfile or the connect-time advanced panel.
const ( RTTClampLo = 1 * time.Millisecond RTTClampHi = 30 * time.Second )
RTT clamps inherited from include/net/ax25.h:20-21:
AX25_T1CLAMPLO = 1 jiffy (treat as 1ms in Go) AX25_T1CLAMPHI = 30 * HZ (30 seconds)
const DefaultBackoff = BackoffLinear
Variables ¶
var ( // ErrManagerClosed is returned by Open after Close. ErrManagerClosed = errors.New("ax25conn: manager closed") // ErrChannelAPRSOnly is returned when Open targets a channel whose // configured Mode is APRS-only. ErrChannelAPRSOnly = errors.New("ax25conn: channel is APRS-only") // ErrMaxTotal is returned when MaxTotal sessions are already open. ErrMaxTotal = errors.New("ax25conn: max total sessions reached") // ErrMaxPerOperator is returned when an operator has hit MaxPerOperator. ErrMaxPerOperator = errors.New("ax25conn: per-operator session cap reached") // ErrSessionExists is returned when (channel, local, peer) is already // bound to a live session. ErrSessionExists = errors.New("ax25conn: session already exists for this triple") )
Sentinel errors returned by Open. Callers can errors.Is these to classify failures without substring-matching error text.
Functions ¶
Types ¶
type Backoff ¶
type Backoff uint8
Backoff selects the T1-on-retry growth strategy. Kernel default is linear backoff (AX25_DEF_BACKOFF=1, kernel constant naming differs). Value 0 is reserved for "unset" so applyDefaults can promote it to DefaultBackoff without mistakenly overwriting an explicit BackoffNone.
type Clock ¶
Clock abstracts time so tests can drive deadlines deterministically. AfterFunc schedules f to run once after d, returning a Stopper that cancels the pending callback. The fake-clock test harness iterates scheduled callbacks on advance().
type Condition ¶
type Condition uint8
Condition is a bitfield mirroring the Linux kernel's ax25->condition byte. Zeroed wholesale at link-establish per ax25_std_subr.c:37.
const ( // CondACKPending — we received an in-order I-frame without P=1 // and have not yet emitted an RR/RNR; T2 will fire if no // piggyback opportunity arrives. Kernel AX25_COND_ACK_PENDING. CondACKPending Condition = 1 << iota // CondReject — we sent REJ for an out-of-order I-frame and are // waiting for the missing N(S). Don't send another REJ until // CondReject clears. Kernel AX25_COND_REJECT. CondReject // CondPeerRxBusy — peer sent RNR; suspend our I-frame TX. // Kernel AX25_COND_PEER_RX_BUSY. CondPeerRxBusy // CondOwnRxBusy — our receive buffer is full; we're sending RNR // in lieu of RR. Cleared by the heartbeat once buffer drains. // Kernel AX25_COND_OWN_RX_BUSY. CondOwnRxBusy )
type Control ¶
type Control struct {
Kind FrameKind
NS uint8 // I-frames only
NR uint8 // I, RR, RNR, REJ, SREJ
PF bool // poll/final
}
Control is the parsed form of an AX.25 control field. NS/NR are in the range 0..7 for mod-8 and 0..127 for mod-128.
type Event ¶
type Event struct {
Kind EventKind
Frame *Frame // EventFrameRX only
Data []byte // EventDataTX only
}
Event is one input dispatched to the per-session run loop.
type EventKind ¶
type EventKind uint8
EventKind categorizes the inputs to the session goroutine.
const ( EventFrameRX EventKind = iota + 1 EventDataTX // operator typed bytes EventConnect // operator pressed Connect EventDisconnect // operator pressed Disconnect EventAbort // operator pressed Abort EventT1Expiry EventT2Expiry EventT3Expiry EventHeartbeat // 5s housekeeping tick EventStatsTick // 1Hz CONNECTED telemetry emission EventShutdown // manager tearing us down )
type Frame ¶
type Frame struct {
Source, Dest ax25.Address
Path []ax25.Address
Control Control
PID byte // I and UI only
Info []byte
IsCommand bool // direwolf-style C-bit polarity (dest C set, source C clear)
Mod128 bool
}
Frame is a connected-mode AX.25 frame. The address layout is identical to UI frames; only the control field and presence of PID/Info differ.
type FrameKind ¶
type FrameKind uint8
FrameKind enumerates the AX.25 frame types decodable from the control field. See AX.25 v2.0 §3.4 / v2.2 §4.2.
type LinkStats ¶
type LinkStats struct {
State State
VS, VR, VA uint8
RC int
PeerBusy bool
OwnBusy bool
FramesTX uint64
FramesRX uint64
BytesTX uint64
BytesRX uint64
RTT time.Duration
}
LinkStats is the snapshot the bridge can render in the telemetry side panel.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager owns the per-process LAPB session table. Caps are process-local — they limit concurrent live sessions inside one graywolf instance. After a graywolf restart, prior sessions are gone and operators may reconnect freely. The same operator across multiple browser sessions or API tokens accumulates against the cap inside this process.
func NewManager ¶
func NewManager(cfg ManagerConfig) *Manager
NewManager constructs a Manager. Caps default to 4 per operator, 64 total when zero.
func (*Manager) Close ¶
func (m *Manager) Close()
Close cancels every running session. Safe to call multiple times.
func (*Manager) Dispatch ¶
Dispatch routes a non-UI frame to the matching session. Caller is the pkg/app rxfanout; per the brainstorm §4.1, the frame is guaranteed non-UI by the caller.
func (*Manager) LastPath ¶
LastPath returns the path the most recent inbound frame for sid traversed (digipeater chain), normalized to drop the trailing not-yet-repeated entries that should not appear on inbound frames. Empty if no inbound frame has been routed to the session yet.
type ManagerConfig ¶
type ManagerConfig struct {
TxSink txgovernor.TxSink
ChannelModes configstore.ChannelModeLookup
Logger *slog.Logger
MaxPerOperator int // default 4
MaxTotal int // default 64
}
ManagerConfig is the static configuration for the per-process session manager.
type OutEvent ¶
type OutEvent struct {
Kind OutEventKind
State State // OutStateChange
Data []byte // OutDataRX
Stats LinkStats // OutLinkStats
ErrCode string // OutError
ErrMsg string // OutError
}
OutEvent is the envelope the session emits via its Observer hook.
type OutEventKind ¶
type OutEventKind uint8
OutEventKind enumerates events the session emits to its observer (the WebSocket bridge in production, a test sink in unit tests).
const ( OutStateChange OutEventKind = iota + 1 OutDataRX OutLinkStats OutError )
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
Session is the per-link LAPB state machine driver.
func NewSession ¶
func NewSession(cfg SessionConfig) (*Session, error)
NewSession constructs a Session. The returned Session is in StateDisconnected; the caller drives it via Submit + Run.
func (*Session) Run ¶
Run blocks until ctx is cancelled or EventShutdown is processed. Manager invokes Run in a goroutine. Each iteration drains pending timer bits before reading the channel so timer events never starve under RX bursts.
func (*Session) Snapshot ¶
Snapshot returns a goroutine-safe copy of the current LinkStats. Callable from any goroutine; the session goroutine mutates stats under the same mutex via mutateStats / setStateStats helpers.
type SessionConfig ¶
type SessionConfig struct {
Local, Peer ax25.Address
Path []ax25.Address
Channel uint32
Mod128 bool
T1, T2, T3 time.Duration
Heartbeat time.Duration
StatsTick time.Duration
N2 int
Paclen int
Window int
Backoff Backoff
TxSink txgovernor.TxSink
Clock Clock
Logger *slog.Logger
Observer func(OutEvent) // non-blocking; manager fans to bridge
}
SessionConfig captures the per-session knobs. Required fields: Local, Peer, Channel, TxSink. Defaults are filled in for missing timer/window/paclen.