Documentation
¶
Overview ¶
Package ax25termws bridges a per-WebSocket connection to a pkg/ax25conn session: inbound JSON envelopes from the browser drive the session's event loop; outbound observer events become envelopes pushed back to the browser.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Bridge ¶
type Bridge struct {
// contains filtered or unexported fields
}
Bridge maps inbound envelopes to ax25conn.Event submissions and outbound observer events to envelopes.
The bridge runs one internal pump goroutine that translates OutEvent -> Envelope and writes into cfg.Out. observe() is invoked directly from the session goroutine and MUST stay non-blocking; it enqueues into an internal channel that the pump drains.
The bridge owns an internal context derived from cfg.Ctx so Close() can stop the pump even when the parent ctx is still alive (e.g. a test that wants to verify Close behavior without tearing down the whole http handler).
func New ¶
func New(cfg BridgeConfig) *Bridge
New constructs a Bridge and starts its pump goroutine. The session is opened on the first KindConnect envelope. Callers MUST invoke Close exactly once when the WebSocket terminates so any active LAPB session receives a clean DISC frame on the wire.
func (*Bridge) Close ¶
func (b *Bridge) Close()
Close requests a clean LAPB DISC on any active session and waits for the pump goroutine to exit. Safe to call multiple times.
Why DISC and not Abort: when an operator closes their browser tab, the session WAS in CONNECTED -- LAPB requires a proper disconnect handshake (DISC -> UA) so the peer's state machine drops the link instead of waiting for N2 retries to time out. The session's AWAITING_RELEASE timer guarantees the goroutine still exits even if the peer never UAs.
type BridgeConfig ¶
type BridgeConfig struct {
// Manager opens and tracks ax25conn sessions. Required.
Manager *ax25conn.Manager
// Logger receives bridge-side warnings (e.g. dropped envelopes).
// Required.
Logger *slog.Logger
// Operator is the authenticated user identity, used by the
// manager for per-operator session caps.
Operator string
// Ctx scopes the bridge's lifetime. The pump goroutine that
// drains the observer inbox into Out exits when Ctx is done so
// no goroutine leaks after the WebSocket closes.
Ctx context.Context
// Out is the channel the bridge fills with outbound envelopes;
// the WebSocket handler drains it. The bridge sends from three
// goroutines: the pump (observer events), rawTailPump
// (packetlog subscriber entries), and the reader's
// emitErrorEnvelope path. Each sender selects on ctx.Done() so
// teardown drains them without closing the channel.
Out chan<- Envelope
// OnFirstConnected, if set, is invoked once per session the first
// time the link reaches CONNECTED. Wiring uses it to upsert a
// recent profile so the pre-connect form's recents list reflects
// the new connection. The callback runs on a fresh goroutine so
// it cannot stall the session loop.
OnFirstConnected func(args ConnectArgs)
// Transcripts persists per-session recordings when the operator
// toggles transcript on. Optional; nil disables transcript support
// entirely (the bridge surfaces a typed error envelope on toggle).
Transcripts TranscriptRecorder
// RawPacketLog drives the raw-tail mode (Plan §3f). Optional; nil
// disables raw-tail support so KindRawTailSubscribe surfaces a
// typed error envelope.
RawPacketLog *packetlog.Log
}
BridgeConfig configures one per-WebSocket bridge instance.
type ConnectArgs ¶
type ConnectArgs struct {
ChannelID uint32 `json:"channel_id"`
LocalCall string `json:"local_call"`
LocalSSID uint8 `json:"local_ssid"`
DestCall string `json:"dest_call"`
DestSSID uint8 `json:"dest_ssid"`
Via []string `json:"via,omitempty"`
Mod128 bool `json:"mod128,omitempty"`
Paclen int `json:"paclen,omitempty"`
// Maxframe is the LAPB window k. Defaults: 2 (mod-8), 32 (mod-128).
Maxframe int `json:"maxframe,omitempty"`
T1MS int `json:"t1_ms,omitempty"`
T2MS int `json:"t2_ms,omitempty"`
T3MS int `json:"t3_ms,omitempty"`
N2 int `json:"n2,omitempty"`
Backoff string `json:"backoff,omitempty"` // "none"|"linear"|"exponential"; default linear
}
ConnectArgs is the payload of a KindConnect envelope.
type Envelope ¶
type Envelope struct {
Kind MsgKind `json:"kind"`
Connect *ConnectArgs `json:"connect,omitempty"`
Data []byte `json:"data,omitempty"`
State *StatePayload `json:"state,omitempty"`
Stats *StatsPayload `json:"stats,omitempty"`
Error *ErrorPayload `json:"error,omitempty"`
Transcript *TranscriptSetPayload `json:"transcript,omitempty"`
RawTailSub *RawTailSubscribeArgs `json:"raw_tail_sub,omitempty"`
RawTail *RawTailEntry `json:"raw_tail,omitempty"`
}
Envelope is the on-wire JSON shape. Data is base64-encoded by encoding/json when the field type is []byte; the JS side decodes via atob.
type ErrorPayload ¶
ErrorPayload is a typed error surfaced back to the operator.
type MsgKind ¶
type MsgKind string
MsgKind enumerates wire-level message types. The wire is JSON; the kind value drives a switch in both directions.
const ( // Client -> Server KindConnect MsgKind = "connect" KindData MsgKind = "data" KindDisconnect MsgKind = "disconnect" KindAbort MsgKind = "abort" KindTranscriptSet MsgKind = "transcript_set" KindRawTailSubscribe MsgKind = "raw_tail_subscribe" KindRawTailUnsub MsgKind = "raw_tail_unsubscribe" // Server -> Client KindState MsgKind = "state" KindDataRX MsgKind = "data_rx" KindLinkStats MsgKind = "link_stats" KindError MsgKind = "error" KindRawTail MsgKind = "raw_tail" )
type RawTailEntry ¶
type RawTailEntry struct {
TS time.Time `json:"ts"`
Source string `json:"source"`
Type string `json:"type,omitempty"`
Direction string `json:"direction,omitempty"`
ChannelID uint32 `json:"channel_id,omitempty"`
From string `json:"from,omitempty"`
Raw string `json:"raw,omitempty"` // TNC2-formatted packet
}
RawTailEntry is the wire-form of a packetlog Entry, slimmed down to what RawPacketView renders. Server-only payload.
type RawTailSubscribeArgs ¶
type RawTailSubscribeArgs struct {
ChannelID uint32 `json:"channel_id"`
Source string `json:"source,omitempty"`
Type string `json:"type,omitempty"`
Direction string `json:"direction,omitempty"`
// SubstringMatch narrows by matching against the Entry's Decoded.Source
// (callsign) -- common operator-friendly filter.
SubstringMatch string `json:"substring,omitempty"`
}
RawTailSubscribeArgs scopes a raw-packet tail to one channel and optional Filter fields. Empty fields match anything.
type StatePayload ¶
type StatePayload struct {
Name string `json:"name"` // DISCONNECTED, AWAITING_CONNECTION, CONNECTED, ...
Reason string `json:"reason,omitempty"` // human-readable transition cause
}
StatePayload reports a state transition.
type StatsPayload ¶
type StatsPayload struct {
State string `json:"state"`
VS uint8 `json:"vs"`
VR uint8 `json:"vr"`
VA uint8 `json:"va"`
RC int `json:"rc"`
PeerBusy bool `json:"peer_busy"`
OwnBusy bool `json:"own_busy"`
FramesTX uint64 `json:"frames_tx"`
FramesRX uint64 `json:"frames_rx"`
BytesTX uint64 `json:"bytes_tx"`
BytesRX uint64 `json:"bytes_rx"`
RTTMS int `json:"rtt_ms"`
}
StatsPayload is the LinkStats snapshot the bridge ships to the telemetry side panel.
type TranscriptRecorder ¶
type TranscriptRecorder interface {
// Begin opens a new transcript session keyed to the connect args.
// Returns the persistent session id.
Begin(ctx context.Context, channelID uint32, peerCall string, peerSSID uint8, viaPath string) (uint32, error)
// Append persists one transcript entry.
Append(ctx context.Context, sessionID uint32, ts time.Time, direction, kind string, payload []byte) error
// End stamps the wrap-up fields on a transcript session.
End(ctx context.Context, sessionID uint32, reason string, bytes, frames uint64) error
}
TranscriptRecorder is the persistence interface the bridge calls to record one session's transcript. Implementations adapt the configstore (see pkg/webapi/ax25_terminal.go for the wiring).
type TranscriptSetPayload ¶
type TranscriptSetPayload struct {
Enabled bool `json:"enabled"`
}
TranscriptSetPayload toggles transcript recording on/off for the current session. Sent client -> server only.