txgovernor

package
v0.13.13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 30, 2026 License: GPL-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package txgovernor is graywolf's centralized transmit governor. All transmit sources — KISS, AGW, beacons, digipeater, iGate IS→RF — funnel through a single Governor before frames reach the Rust modem. It enforces:

  • Per-channel rate limits (packets/min and packets/5min, sliding window)
  • Deduplication keyed on (dest + source + info) within a configurable window (default 30s) across all input sources
  • Priority queue (beacons < digipeated < KISS/AGW < iGate message) so higher-priority traffic preempts lower-priority traffic
  • DCD-aware timing: before sending, wait until the radio channel is clear (DCD low) and run a p-persistence / slot-time CSMA decision

Index

Constants

View Source
const (
	PriorityBeacon     = ax25.PriorityBeacon
	PriorityDigipeated = ax25.PriorityDigipeated
	PriorityClient     = ax25.PriorityClient // KISS/AGW client-originated
	PriorityAX25Conn   = ax25.PriorityAX25Conn
	PriorityIGateMsg   = ax25.PriorityIGateMsg
)

Priority levels. Higher value = sent sooner. These re-export the canonical constants defined in pkg/ax25 so existing call sites keep working; the ax25 package owns the truth so pkg/kiss, pkg/agw, and pkg/aprs can reference the same values without importing txgovernor.

Variables

View Source
var (
	// ErrQueueFull is returned when Submit cannot enqueue because the
	// governor's pending queue has reached QueueCapacity. Back-pressure
	// — retry later with a fresher context.
	ErrQueueFull = errors.New("txgovernor: queue full")
	// ErrStopped is returned when Submit is called after the governor's
	// Run loop has exited (context cancelled). Terminal — do not retry.
	ErrStopped = errors.New("txgovernor: closed")
	// ErrNilFrame is returned when Submit is called with a nil *Frame.
	// Caller bug; should never happen at runtime.
	ErrNilFrame = errors.New("txgovernor: nil frame")
)

Sentinel errors returned by Submit. Callers can errors.Is these to classify drops without substring-matching error text. Keep this list closed: new error kinds get their own exported sentinel so callers can always distinguish "queue full" (back-pressure, retry) from "stopped" (terminal, abandon) from "nil frame" (caller bug).

Functions

This section is empty.

Types

type ChannelTiming

type ChannelTiming struct {
	SlotTime time.Duration // defaults to 100 ms
	Persist  uint8         // 0..255, default 63
	FullDup  bool          // skip CSMA entirely
}

ChannelTiming holds the CSMA parameters for one radio channel. Values mirror the tx_timing SQLite row (ms units). TX delay and tail live in ConfigurePtt (hot-reloaded by the bridge) and are not duplicated here.

type Config

type Config struct {
	// Sender is the downstream TransmitFrame consumer. Required.
	Sender Sender
	// DcdEvents is an optional channel of per-channel DCD state changes
	// from modembridge. If nil, CSMA falls back to "always clear".
	DcdEvents <-chan *pb.DcdChange
	// Rate1MinLimit and Rate5MinLimit cap the number of frames transmitted
	// per channel in the last 1 and 5 minutes. Zero = unlimited.
	Rate1MinLimit int
	Rate5MinLimit int
	// DedupWindow is the suppression window for identical frames. Default
	// 30s if zero.
	DedupWindow time.Duration
	// QueueCapacity caps the total pending queue. Default 256.
	QueueCapacity int
	// Channels maps channel number to timing parameters. Missing channels
	// use defaults.
	Channels map[uint32]ChannelTiming
	// Logger is optional.
	Logger *slog.Logger
	// RandSource allows tests to inject a deterministic random source for
	// p-persist decisions. Defaults to time-seeded rand.
	RandSource *rand.Rand
}

Config is the Governor's static configuration.

type Governor

type Governor struct {
	// contains filtered or unexported fields
}

Governor is the centralized TX scheduler.

func New

func New(cfg Config) *Governor

New builds a Governor. Call Run to start its background loop.

func (*Governor) AddTxHook

func (g *Governor) AddTxHook(h TxHook) (id uint64, unregister func())

AddTxHook registers h to be invoked after every successful frame submission. It returns the assigned id (for diagnostics) and an unregister closure that removes h when called. unregister is idempotent: calling it twice is a no-op.

Hooks run inline in the governor's worker goroutine, in registration order. See TxHook for the non-blocking contract — any slow work (disk I/O, network, blocking channel sends) must be pushed to a separate goroutine by the hook itself.

AddTxHook is safe to call at any time, including while the worker loop is invoking hooks. A hook registered concurrently with a send may or may not observe that send; subsequent sends will see it.

func (*Governor) QueueLen

func (g *Governor) QueueLen() int

QueueLen returns the current number of pending frames.

func (*Governor) Run

func (g *Governor) Run(ctx context.Context) error

Run executes the worker loop until ctx is cancelled. It consumes DCD events from cfg.DcdEvents, drains the queue, and calls Sender. Blocks.

func (*Governor) SetChannelTiming

func (g *Governor) SetChannelTiming(channel uint32, t ChannelTiming)

SetChannelTiming installs or replaces the timing parameters for one channel under the governor's lock. Safe to call from startup and live-reconfig paths.

func (*Governor) SetSkipCSMA

func (g *Governor) SetSkipCSMA(fn func(channel uint32) bool)

SetSkipCSMA installs a predicate consulted per frame to decide whether the p-persistence / slot-time / DCD wait should be bypassed for a channel. Wired by the txbackend dispatcher so KISS-only channels short-circuit CSMA (no carrier on TCP). Safe to call from startup; not expected to change after wiring.

Passing nil restores the default "never skip" behaviour.

func (*Governor) Stats

func (g *Governor) Stats() Stats

Stats returns a snapshot of the counters.

func (*Governor) Submit

func (g *Governor) Submit(ctx context.Context, channel uint32, frame *ax25.Frame, src SubmitSource) error

Submit enqueues a frame. Deduplicates, rate-checks are deferred to the worker loop so Submit never blocks on the channel. Returns an error if the queue is full, the governor is closed, or the caller's context has already been cancelled. Submit honors ctx so that a caller-imposed deadline (e.g. the iGate's IS->RF timeout) propagates into the governor's accept path and so that a cancelled session cannot be charged a newly enqueued frame.

type Sender

type Sender func(*pb.TransmitFrame) error

Sender transmits one frame to the Rust modem. In production this is modembridge.Bridge.SendTransmitFrame; in tests, a fake.

type Stats

type Stats struct {
	Enqueued     uint64
	Sent         uint64
	Deduped      uint64
	RateLimited  uint64
	QueueDropped uint64
}

Stats exposes counters for metrics.

type SubmitSource

type SubmitSource struct {
	Kind      string // "kiss" | "agw" | "beacon" | "digipeater" | "igate"
	Detail    string
	Priority  int
	SkipDedup bool // bypass dedup window (e.g. operator-triggered send-now)
}

SubmitSource describes the origin of a TX request for logging, dedup scoping, and metrics.

type TxHook

type TxHook func(channel uint32, frame *ax25.Frame, source SubmitSource)

TxHook is invoked after a frame has been successfully submitted to the downstream Sender. It runs inline in the governor's worker goroutine, so implementations MUST be fast and non-blocking (packetlog record, counter bumps, channel send with default, etc). A blocking hook stalls the governor's send loop for every registered consumer.

Multiple hooks may be registered concurrently via AddTxHook; they are invoked in registration order on each successful send.

type TxHookRegistry

type TxHookRegistry interface {
	AddTxHook(h TxHook) (id uint64, unregister func())
}

TxHookRegistry is the narrow hook-registration interface. Callers that only need to register/unregister a TxHook should depend on this interface rather than the concrete *Governor so tests can inject a fake. The returned unregister closure is idempotent.

type TxSink

type TxSink interface {
	Submit(ctx context.Context, channel uint32, frame *ax25.Frame, src SubmitSource) error
}

TxSink is implemented by anything that accepts AX.25 frames for transmit scheduling. The canonical implementation is *Governor; callers that need only to submit frames should depend on this interface rather than the concrete type so tests can inject a fake without reaching for a package-local interface copy.

Every production call site funnels through txgovernor.Governor. Previously each caller (kiss, agw, beacon, igate) declared its own TxSink + SubmitSource types duplicating this signature, which meant a change to the governor's Submit contract could silently drift away from the duplicates until a compile error finally surfaced in the adapter layer. Depending on this type eliminates that risk.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL