typeb

package
v0.1.89 Latest Latest
Warning

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

Go to latest
Published: Sep 5, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package typeb implements the IATA/ATA "Type B" teletype message envelope used by the SITA and ARINC store-and-forward networks.

A Type B message is a line-oriented, uppercase, 7-bit text frame:

ZCZC ABC1234                 <- optional network start-of-message + channel seq
QU LHRRMBA NYCRMAA           <- priority code + one or more 7-char TTY addresses
.LONXX1A 121430              <- origin line: '.' + originator address + DDHHMM
                             <- optional blank line(s)
SSR VGML BA HK1 ...          <- message text (AIRIMP, PNL/ADL, SSM, EDIFACT, ...)
NNNN                         <- optional network end-of-message

The envelope carries no indication of what the text means; classification is the job of a higher layer (see pkg/airimp and internal/gateway/classify).

Parsing is deliberately lenient. Real carrier traffic contains malformed headers, non-conforming addresses, stray control characters and unexpected line breaks; a gateway that rejects them loses messages. Parse records what it could not understand in Diagnostics and always preserves the original bytes so the message can be replayed after a parser fix. Use Strict() when you need conformance failures to be errors instead.

Index

Constants

View Source
const (
	SOH = 0x01
	STX = 0x02
	ETX = 0x03
	EOT = 0x04
	SYN = 0x16
	NUL = 0x00
)

Control characters that appear around Type B frames on real links.

View Source
const (
	StartOfMessage = "ZCZC"
	EndOfMessage   = "NNNN"
)

StartOfMessage and EndOfMessage are the classic teletype framing sequences. Many SITA feeds still wrap each message in them.

View Source
const DefaultLineLength = 63

DefaultLineLength is the maximum characters per line for Type B text.

IATA's Type B Messaging whitepaper (v2.1, June 2024) states the format limit as 60 lines of 63 characters. Configurable because individual links differ, but 63 is the number the standard gives.

View Source
const DefaultMaxBytes = 4096

DefaultMaxBytes is the maximum size of a complete Type B message, envelope included.

The same whitepaper gives the limit as 60 lines of 63 characters "whilst remaining below 4 kilo bytes of data". The line limits do not imply this one: 60 full lines is already 3780 characters, and a priority line carrying several addressees, an origin line and framing can push an otherwise conforming message past 4096 bytes. A link that polices the byte limit will truncate or reject such a message, so Encode refuses to build one.

View Source
const DefaultMaxLines = 60

DefaultMaxLines is the maximum number of text lines in a Type B message, from the same source. Exceeding it means the message must be split, which is the sender's decision and not something this package does silently.

View Source
const PDMIndicator = "PDM"

PDMIndicator marks a message as a possible duplicate.

Variables

View Source
var CharsetIA5 = newCharset("IA5", " .,-/()':=+?*#%&@<>!\"$;_")

CharsetIA5 is the wider set most modern Type B links accept.

View Source
var CharsetITA2 = newCharset("ITA2", " .,-/()':=+?")

CharsetITA2 is the conservative teletype set: uppercase letters, digits and the punctuation reliably carried by five-bit Baudot-derived links. Use it when a carrier's ICD does not say otherwise.

View Source
var ErrEmpty = errors.New("typeb: empty message")

ErrEmpty is returned when the input contains no message content.

View Source
var KnownPriorities = map[string]string{
	"QU": "urgent",
	"QK": "normal",
	"QD": "deferred",
	"QN": "no priority / bulk",
	"QX": "multiple address",
	"QS": "service",
	"QP": "priority",
	"QC": "circular",
	"QM": "multiple",
	"QF": "flight movement",
	"QY": "administrative",
}

KnownPriorities are the priority codes seen in general interline use. Others are accepted but flagged.

Functions

func MarkPossibleDuplicate

func MarkPossibleDuplicate(raw []byte) ([]byte, bool)

MarkPossibleDuplicate sets the PDM indicator on an already-encoded message.

A retransmission must carry PDM so the receiver can tell a resend from a second instruction. The edit is textual and touches only the origin line: the bytes being retransmitted are the bytes that were captured, and regenerating them from a parse would change a message the peer has already been told about.

It reports false when there is no origin line to mark or when the message already carries the indicator, in which case raw is returned unchanged.

func Normalise

func Normalise(b []byte) []byte

Normalise strips transport control characters and converts CRLF/CR line endings to LF. It is exported because raw capture layers sometimes need to normalise before hashing for deduplication.

func ParseChannel

func ParseChannel(token string) (channel string, seq int, ok bool)

ParseChannel splits a channel token such as "ABC1234" into its channel identifier and sequence number.

The token follows ZCZC and is how a store-and-forward link numbers what it sends. Reading it is what makes a missing message detectable at all: without the sequence, a message that never arrived is indistinguishable from a message that was never sent.

func PriorityOf

func PriorityOf(raw []byte) string

PriorityOf reads the priority code from encoded Type B without a full parse.

It exists so the egress side can order a queue by priority without decoding what it is about to resend: the bytes being retransmitted are the captured bytes, and nothing on that path should depend on re-reading them as structure.

func SanitiseText

func SanitiseText(s string, cs *Charset, replacement rune) (string, int)

SanitiseText uppercases s and replaces characters outside the charset with the replacement rune, returning the result and the number of substitutions. Gateways should prefer rejecting to sanitising on egress, but sanitising is the right call when relaying text that a partner already accepted.

Types

type Address

type Address struct {
	Location   string
	Department string
	Carrier    string
	// Extra holds any characters beyond the 7th. A few carriers use 8-character
	// addresses; keeping the tail avoids silently corrupting their routing.
	Extra string
}

Address is a 7-character Type B teletype address, conventionally LLLDDCC: 3-character location, 2-character department, 2-character company/airline designator (which may contain a digit, e.g. 1A = Amadeus).

func ParseAddress

func ParseAddress(s string) (Address, error)

ParseAddress parses a TTY address. It returns an error only when the input cannot plausibly be an address at all.

func (Address) Conventional

func (a Address) Conventional() bool

Conventional reports whether the address follows the LLLDDCC convention with an alphabetic location and department. Non-conventional addresses are legal on the wire but worth flagging when onboarding a new link.

func (Address) IsZero

func (a Address) IsZero() bool

IsZero reports whether the address is unset.

func (Address) String

func (a Address) String() string

String renders the address in wire form.

type Charset

type Charset struct {
	Name string
	// contains filtered or unexported fields
}

Charset describes the characters a link will accept.

func (*Charset) FirstInvalid

func (c *Charset) FirstInvalid(s string) (rune, bool)

FirstInvalid returns the first disallowed rune in s.

func (*Charset) Valid

func (c *Charset) Valid(s string) bool

Valid reports whether every rune in s is permitted.

type Diagnostic

type Diagnostic struct {
	Severity Severity
	Line     int    // 1-based line number in the normalised input, 0 if not line-specific
	Code     string // stable machine-readable code, e.g. "bad_address"
	Detail   string
}

Diagnostic is a single non-fatal observation made while parsing.

func (Diagnostic) String

func (d Diagnostic) String() string

type EncodeOptions

type EncodeOptions struct {
	// MaxLineLength wraps address lines and validates text lines. Zero uses
	// DefaultLineLength. Set to -1 to disable wrapping entirely.
	MaxLineLength int
	// MaxLines bounds the number of text lines. Zero uses DefaultMaxLines; set
	// to -1 to disable the check.
	MaxLines int
	// MaxBytes bounds the whole encoded message, envelope included. Zero uses
	// DefaultMaxBytes; set to -1 to disable the check.
	MaxBytes int
	// Frame wraps the output in ZCZC/NNNN network framing.
	Frame bool
	// Channel is emitted after ZCZC when Frame is set.
	Channel string
	// CRLF emits "\r\n" line endings, which most links expect.
	CRLF bool
	// BlankLineAfterOrigin inserts an empty line between the origin line and the
	// text. Some carriers require it; others reject it.
	BlankLineAfterOrigin bool
	// Charset validates the text. Nil skips validation.
	Charset *Charset
}

EncodeOptions controls Type B serialisation.

type Message

type Message struct {
	// Framed reports that the input was wrapped in ZCZC/NNNN.
	Framed bool
	// Channel is the optional channel/sequence token following ZCZC.
	Channel string

	Priority     string
	Destinations []Address

	Origin     Address
	OriginTime OriginTime
	// OriginExtra holds trailing tokens on the origin line beyond the time group,
	// such as relay signatures or carrier-specific sequence numbers.
	OriginExtra []string

	// PossibleDuplicate reports that the sender marked this message PDM. On a
	// store-and-forward network a sender that did not see an acknowledgement
	// retransmits, and flags the retransmission so the receiver can recognise a
	// resend rather than act on it a second time.
	//
	// IATA's Type B whitepaper describes PDM as a message header indicator
	// without stating its position. Parse therefore accepts it either as a
	// token on the origin line or as a line of its own in the header, and
	// Encode emits it on the origin line.
	PossibleDuplicate bool

	// SMI is the Standard Message Identifier line, when the message class uses
	// one. Most AIRIMP traffic does not.
	SMI string

	// Text is the message body with line endings normalised to "\n" and no
	// trailing newline.
	Text string

	// Raw is the exact input given to Parse. It is the source of truth for
	// replay and must never be regenerated from the parsed fields.
	Raw []byte

	Diagnostics []Diagnostic
}

Message is a parsed Type B envelope.

func Parse

func Parse(raw []byte) (*Message, error)

Parse decodes a Type B envelope leniently. It returns an error only when the input has no usable content; every other problem becomes a Diagnostic.

func (*Message) Encode

func (m *Message) Encode(opts EncodeOptions) ([]byte, error)

Encode renders the message in Type B wire form.

Encode is not the inverse of Parse for arbitrary input: Parse is lenient and normalises whitespace, so round-tripping a non-conforming message produces a conforming one. Never use Encode to reproduce a received message for audit or replay; use Message.Raw for that.

func (*Message) HasErrors

func (m *Message) HasErrors() bool

HasErrors reports whether any diagnostic is at Error severity.

type OriginTime

type OriginTime struct {
	Day    int
	Hour   int
	Minute int
	// Present is false when the origin line carried no time group.
	Present bool
}

OriginTime is the DDHHMM day-of-month and UTC time stamp on the origin line. It carries no month or year, which is why it must never be used as an absolute timestamp: resolve it against the receive time instead (see Resolve).

func (OriginTime) String

func (o OriginTime) String() string

type PriorityClass

type PriorityClass int

PriorityClass groups priority codes into the bands a store-and-forward network actually services differently.

Bands rather than a total order, deliberately. The published material names the codes and what they mean but does not settle a precise ranking between, say, QX and QK, and inventing one would be a guess dressed as a rule. Three bands are enough to do the thing that matters: not making an urgent message wait behind a bulk one.

const (
	// PriorityUrgent is serviced first: network service messages and traffic
	// the sender marked urgent.
	PriorityUrgent PriorityClass = iota
	// PriorityNormal is everything else, including codes this package does not
	// recognise. An unknown code is never starved.
	PriorityNormal
	// PriorityDeferred is serviced last: traffic the sender said may wait.
	PriorityDeferred
)

func ClassOf

func ClassOf(code string) PriorityClass

ClassOf returns the service band for a priority code.

The miss is handled explicitly rather than left to the zero value. Urgent sorts first, so it has to be the zero of the type for ordering to work, which would otherwise make every unrecognised code urgent -- the opposite of the documented rule and a way for one unknown code to jump every real queue.

func (PriorityClass) String

func (c PriorityClass) String() string

type SequenceGap

type SequenceGap struct {
	// Expected is the number that should have come next.
	Expected int
	// Got is the number that arrived.
	Got int
	// Missing is how many messages the jump skipped, zero when none were.
	Missing int
	// Repeat reports that the number went backwards or repeated, which means a
	// retransmission or a sender that restarted its counter.
	Repeat bool
}

SequenceGap describes what a sequence number said about the messages before it.

func CheckSequence

func CheckSequence(last, got, wrap int) (SequenceGap, bool)

CheckSequence compares an arriving sequence number against the last one seen on the same channel.

Wrap is the number the counter returns to after its highest value. Passing zero disables wrap handling, which will report a very large gap when a counter rolls over; a link whose width is known should say so.

type Severity

type Severity int

Severity classifies a parse Diagnostic.

const (
	// Info records something unusual but harmless.
	Info Severity = iota
	// Warn records a conformance deviation that was recovered from.
	Warn
	// Error records a deviation that lost information.
	Error
)

func (Severity) String

func (s Severity) String() string

Jump to

Keyboard shortcuts

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