diff --git a/lib/MessageDecoder.ts b/lib/MessageDecoder.ts index b40f43a..4b2cea4 100644 --- a/lib/MessageDecoder.ts +++ b/lib/MessageDecoder.ts @@ -78,6 +78,7 @@ const pluginClasses = [ Plugins.Label_QQ, Plugins.Label_QR, Plugins.Label_QS, + Plugins.Label_B0_AFN, ]; export class MessageDecoder { diff --git a/lib/plugins/Label_B0_AFN.ts b/lib/plugins/Label_B0_AFN.ts new file mode 100644 index 0000000..55adcbf --- /dev/null +++ b/lib/plugins/Label_B0_AFN.ts @@ -0,0 +1,245 @@ +import { DecoderPlugin } from '../DecoderPlugin'; +import { DecodeResult, Message, Options } from '../DecoderPluginInterface'; +import { ResultFormatter } from '../utils/result_formatter'; +import { DateTimeUtils } from '../DateTimeUtils'; + +/** + * Label B0 — AFN (ATS Facilities Notification / FANS 1/A CPDLC Logon) + * + * FANS 1/A `FN_CON` / AFN-Contact message — sent by the aircraft to + * initiate a CPDLC logon with a specific ATSU (Air Traffic Services Unit). + * The B0 label carries the same AFN protocol as A0; different operators + * and/or sub-networks use one label or the other. + * + * Wire format (analyst example): + * + * /SOUCAYA.AFN/FMHUAL984,.N78004,AA92FB,080809/FCPPIKCAYA,0F897 + * | | | | | | | | | || + * | | | | | | | | | |└ CRC (last 4 hex chars) + * | | | | | | | | | └── Capability / qualifier flag (raw) + * | | | | | | | | └────────── Contact fix / position token + * | | | | | | | └────────────── Descriptor (FCPP, FCO, FPO, FAK, …) + * | | | | | | └─────────────────── UTC time HHMMSS + * | | | | | └─────────────────────────── ICAO 24-bit address (Mode-S hex) + * | | | | └───────────────────────────────── Tail / registration (leading '.') + * | | | └──────────────────────────────────────── Callsign (flight ID) + * | | └──────────────────────────────────────────── Message header descriptor (/FMH) + * | └──────────────────────────────────────────────── Application ID (.AFN) + * └───────────────────────────────────────────────────────── ATSU ground-facility address + * + * The `SOUCAYA`-style 7-character ATSU address is longer than the classic + * 4-letter FIR designator (`KZWY`, `EGGX`, …); it appears to be a + * named-waypoint-style facility address used by some FANS 1/A ground + * stations. + */ +export class Label_B0_AFN extends DecoderPlugin { + name = 'label-b0-afn'; + + qualifiers() { + return { + labels: ['B0'], + }; + } + + decode(message: Message, options: Options = {}): DecodeResult { + const decodeResult = this.initResult( + message, + 'AFN — ATS Facilities Notification (FANS 1/A CPDLC Logon)', + ); + + const text = (message.text || '').trim(); + if (!text) { + this.setDecodeLevel(decodeResult, false); + return decodeResult; + } + + // ── Envelope: /.AFN/FMH[/...] ── + const envelope = text.match( + /^\/(?[A-Z0-9]{4,9})\.AFN\/FMH(?.+)$/, + ); + if (!envelope?.groups) { + this.setDecodeLevel(decodeResult, false); + return decodeResult; + } + const { ground, rest } = envelope.groups; + decodeResult.raw.atsu_address = ground; + + // Split rest into the pre-capability header block and the zero-or-more + // slash-delimited capability blocks. + const slashIdx = rest.indexOf('/'); + const headerBlock = slashIdx >= 0 ? rest.substring(0, slashIdx) : rest; + const capBlocks = + slashIdx >= 0 + ? rest + .substring(slashIdx + 1) + .split('/') + .map((s) => s.trim()) + .filter((s) => s.length > 0) + : []; + + // ── Header: CALLSIGN,.TAIL,ICAOHEX,HHMMSS ── + // Some variants skip the ICAO hex or the time — match tolerantly. + const headerRe = + /^(?[A-Z]{2,3}\d{1,5}[A-Z]?),?\.?(?[A-Z0-9-]{3,10})?(?:,(?[A-F0-9]{6}))?(?:,(?