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
- Variables
- func MarkPossibleDuplicate(raw []byte) ([]byte, bool)
- func Normalise(b []byte) []byte
- func ParseChannel(token string) (channel string, seq int, ok bool)
- func PriorityOf(raw []byte) string
- func SanitiseText(s string, cs *Charset, replacement rune) (string, int)
- type Address
- type Charset
- type Diagnostic
- type EncodeOptions
- type Message
- type OriginTime
- type PriorityClass
- type SequenceGap
- type Severity
Constants ¶
const ( SOH = 0x01 STX = 0x02 ETX = 0x03 EOT = 0x04 SYN = 0x16 NUL = 0x00 )
Control characters that appear around Type B frames on real links.
const ( StartOfMessage = "ZCZC" EndOfMessage = "NNNN" )
StartOfMessage and EndOfMessage are the classic teletype framing sequences. Many SITA feeds still wrap each message in them.
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.
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.
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.
const PDMIndicator = "PDM"
PDMIndicator marks a message as a possible duplicate.
Variables ¶
var CharsetIA5 = newCharset("IA5", " .,-/()':=+?*#%&@<>!\"$;_")
CharsetIA5 is the wider set most modern Type B links accept.
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.
var ErrEmpty = errors.New("typeb: empty message")
ErrEmpty is returned when the input contains no message content.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
ParseAddress parses a TTY address. It returns an error only when the input cannot plausibly be an address at all.
func (Address) Conventional ¶
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.
type Charset ¶
type Charset struct {
Name string
// contains filtered or unexported fields
}
Charset describes the characters a link will accept.
func (*Charset) FirstInvalid ¶
FirstInvalid returns the first disallowed rune in s.
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 ¶
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.
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.