From 289ebe16c5ee2bf8a930954063a5f53f48f08352 Mon Sep 17 00:00:00 2001 From: Mark Bumiller Date: Mon, 9 Mar 2026 08:42:04 -0400 Subject: [PATCH 1/2] Consolidated parsers - removed Label_1J_2J_FTX - removed Label_2P_POS - removed Label_4J_POS - added check in Label 80 - added labels to Label_H1 - improved Label_H1 decoding --- lib/MessageDecoder.ts | 3 -- lib/plugins/Label_1J_2J_FTX.test.ts | 6 +-- lib/plugins/Label_1J_2J_FTX.ts | 48 ----------------- lib/plugins/Label_2P_POS.test.ts | 6 +-- lib/plugins/Label_2P_POS.ts | 49 ----------------- lib/plugins/Label_4J_POS.test.ts | 16 ++---- lib/plugins/Label_4J_POS.ts | 40 -------------- lib/plugins/Label_80.test.ts | 10 ++++ lib/plugins/Label_80.ts | 3 ++ lib/plugins/Label_80_INR.test.ts | 32 +++++++++++ lib/plugins/Label_H1.test.ts | 4 +- lib/plugins/Label_H1.ts | 83 ++++++++++++++++++++++------- lib/plugins/Label_H1_PWI.test.ts | 9 ---- lib/plugins/official.ts | 3 -- 14 files changed, 119 insertions(+), 193 deletions(-) delete mode 100644 lib/plugins/Label_1J_2J_FTX.ts delete mode 100644 lib/plugins/Label_2P_POS.ts delete mode 100644 lib/plugins/Label_4J_POS.ts create mode 100644 lib/plugins/Label_80_INR.test.ts diff --git a/lib/MessageDecoder.ts b/lib/MessageDecoder.ts index 36cb6fa..3e2e0ae 100644 --- a/lib/MessageDecoder.ts +++ b/lib/MessageDecoder.ts @@ -31,7 +31,6 @@ export class MessageDecoder { this.registerPlugin(new Plugins.Label_16_N_Space(this)); this.registerPlugin(new Plugins.Label_16_POSA1(this)); this.registerPlugin(new Plugins.Label_16_TOD(this)); - this.registerPlugin(new Plugins.Label_1J_2J_FTX(this)); this.registerPlugin(new Plugins.Label_1L_3Line(this)); this.registerPlugin(new Plugins.Label_1L_070(this)); this.registerPlugin(new Plugins.Label_1L_660(this)); @@ -44,7 +43,6 @@ export class MessageDecoder { this.registerPlugin(new Plugins.Label_2P_FM3(this)); this.registerPlugin(new Plugins.Label_2P_FM4(this)); this.registerPlugin(new Plugins.Label_2P_FM5(this)); - this.registerPlugin(new Plugins.Label_2P_POS(this)); this.registerPlugin(new Plugins.Label_30_Slash_EA(this)); this.registerPlugin(new Plugins.Label_44_ETA(this)); this.registerPlugin(new Plugins.Label_44_IN(this)); @@ -57,7 +55,6 @@ export class MessageDecoder { this.registerPlugin(new Plugins.Label_4A_DIS(this)); this.registerPlugin(new Plugins.Label_4A_DOOR(this)); this.registerPlugin(new Plugins.Label_4A_Slash_01(this)); - this.registerPlugin(new Plugins.Label_4J_POS(this)); this.registerPlugin(new Plugins.Label_4N(this)); this.registerPlugin(new Plugins.Label_4T_AGFSR(this)); this.registerPlugin(new Plugins.Label_4T_ETA(this)); diff --git a/lib/plugins/Label_1J_2J_FTX.test.ts b/lib/plugins/Label_1J_2J_FTX.test.ts index 6436054..af9c4c1 100644 --- a/lib/plugins/Label_1J_2J_FTX.test.ts +++ b/lib/plugins/Label_1J_2J_FTX.test.ts @@ -1,13 +1,13 @@ import { MessageDecoder } from '../MessageDecoder'; -import { Label_1J_2J_FTX } from './Label_1J_2J_FTX'; +import { Label_H1 } from './Label_H1'; describe('Label 1J/2J FTX', () => { - let plugin: Label_1J_2J_FTX; + let plugin: Label_H1; let message = { label: '1J', text: '' }; beforeEach(() => { const decoder = new MessageDecoder(); - plugin = new Label_1J_2J_FTX(decoder); + plugin = new Label_H1(decoder); }); test('decodes Label 1J', () => { diff --git a/lib/plugins/Label_1J_2J_FTX.ts b/lib/plugins/Label_1J_2J_FTX.ts deleted file mode 100644 index 10c0fcc..0000000 --- a/lib/plugins/Label_1J_2J_FTX.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { DecoderPlugin } from '../DecoderPlugin'; -import { DecodeResult, Message, Options } from '../DecoderPluginInterface'; -import { H1Helper } from '../utils/h1_helper'; -import { ResultFormatter } from '../utils/result_formatter'; - -export class Label_1J_2J_FTX extends DecoderPlugin { - name = 'label-1j-2j-ftx'; - qualifiers() { - return { - labels: ['1J', '2J'], - }; - } - - decode(message: Message, options: Options = {}): DecodeResult { - let decodeResult = this.defaultResult(); - decodeResult.decoder.name = this.name; - decodeResult.message = message; - - const msg = message.text.replace(/\n|\r/g, ''); - const parts = msg.split('/'); - let decoded = false; - if (parts[0].length > 3) { - decoded = H1Helper.decodeH1Message( - decodeResult, - msg.slice(parts[0].length - 3), - ); - // flight number is already decoded in other fields - decodeResult.remaining.text = - parts[0].slice(0, 3) + '/' + decodeResult.remaining.text; - } else { - decoded = H1Helper.decodeH1Message(decodeResult, msg); - } - decodeResult.decoded = decoded; - - decodeResult.decoder.decodeLevel = !decodeResult.remaining.text - ? 'full' - : 'partial'; - if (decodeResult.formatted.items.length === 0) { - if (options.debug) { - console.log(`Decoder: Unknown 1J/2J message: ${message.text}`); - } - ResultFormatter.unknown(decodeResult, message.text); - decodeResult.decoded = false; - decodeResult.decoder.decodeLevel = 'none'; - } - return decodeResult; - } -} diff --git a/lib/plugins/Label_2P_POS.test.ts b/lib/plugins/Label_2P_POS.test.ts index d3e1c5d..2e81563 100644 --- a/lib/plugins/Label_2P_POS.test.ts +++ b/lib/plugins/Label_2P_POS.test.ts @@ -1,13 +1,13 @@ import { MessageDecoder } from '../MessageDecoder'; -import { Label_2P_POS } from './Label_2P_POS'; +import { Label_H1 } from './Label_H1'; describe('Label_2P Preamble POS', () => { - let plugin: Label_2P_POS; + let plugin: Label_H1; const message = { label: '2P', text: '' }; beforeEach(() => { const decoder = new MessageDecoder(); - plugin = new Label_2P_POS(decoder); + plugin = new Label_H1(decoder); }); test('variant 1', () => { diff --git a/lib/plugins/Label_2P_POS.ts b/lib/plugins/Label_2P_POS.ts deleted file mode 100644 index 535ff25..0000000 --- a/lib/plugins/Label_2P_POS.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { DecoderPlugin } from '../DecoderPlugin'; -import { DecodeResult, Message, Options } from '../DecoderPluginInterface'; -import { H1Helper } from '../utils/h1_helper'; -import { ResultFormatter } from '../utils/result_formatter'; - -export class Label_2P_POS extends DecoderPlugin { - name = 'label-2p-pos'; - - qualifiers() { - return { - labels: ['2P'], - }; - } - - decode(message: Message, options: Options = {}): DecodeResult { - let decodeResult = this.defaultResult(); - decodeResult.decoder.name = this.name; - decodeResult.message = message; - - const msg = message.text.replace(/\n|\r/g, ''); - const parts = msg.split('/'); - let decoded = false; - if (parts[0].length > 3) { - decoded = H1Helper.decodeH1Message( - decodeResult, - msg.slice(parts[0].length - 3), - ); - // flight number is already decoded in other fields - decodeResult.remaining.text = - parts[0].slice(0, 3) + '/' + decodeResult.remaining.text; - } else { - decoded = H1Helper.decodeH1Message(decodeResult, msg); - } - decodeResult.decoded = decoded; - - decodeResult.decoder.decodeLevel = !decodeResult.remaining.text - ? 'full' - : 'partial'; - if (decodeResult.formatted.items.length === 0) { - if (options.debug) { - console.log(`Decoder: Unknown H1 message: ${message.text}`); - } - ResultFormatter.unknown(decodeResult, message.text); - decodeResult.decoded = false; - decodeResult.decoder.decodeLevel = 'none'; - } - return decodeResult; - } -} diff --git a/lib/plugins/Label_4J_POS.test.ts b/lib/plugins/Label_4J_POS.test.ts index 0f29ff0..001cead 100644 --- a/lib/plugins/Label_4J_POS.test.ts +++ b/lib/plugins/Label_4J_POS.test.ts @@ -1,23 +1,13 @@ import { MessageDecoder } from '../MessageDecoder'; -import { Label_4J_POS } from './Label_4J_POS'; +import { Label_H1 } from './Label_H1'; describe('Label 4J POS', () => { - let plugin: Label_4J_POS; + let plugin: Label_H1; const message = { label: '4J', text: '' }; beforeEach(() => { const decoder = new MessageDecoder(); - plugin = new Label_4J_POS(decoder); - }); - - test('matches qualifiers', () => { - expect(plugin.decode).toBeDefined(); - expect(plugin.name).toBe('label-4j-pos'); - expect(plugin.qualifiers).toBeDefined(); - expect(plugin.qualifiers()).toEqual({ - labels: ['4J'], - preambles: ['POS/'], - }); + plugin = new Label_H1(decoder); }); test('decodes inmarsat', () => { diff --git a/lib/plugins/Label_4J_POS.ts b/lib/plugins/Label_4J_POS.ts deleted file mode 100644 index d170cec..0000000 --- a/lib/plugins/Label_4J_POS.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { DecoderPlugin } from '../DecoderPlugin'; -import { DecodeResult, Message, Options } from '../DecoderPluginInterface'; -import { H1Helper } from '../utils/h1_helper'; -import { ResultFormatter } from '../utils/result_formatter'; - -export class Label_4J_POS extends DecoderPlugin { - name = 'label-4j-pos'; - qualifiers() { - return { - labels: ['4J'], - preambles: ['POS/'], - }; - } - - // copied from Label_H1.ts since i don't really want to have to have - // something named like that decode more than 1 type - // if we figure out a good name, i'll combine them - decode(message: Message, options: Options = {}): DecodeResult { - let decodeResult = this.defaultResult(); - decodeResult.decoder.name = this.name; - decodeResult.message = message; - - const msg = message.text.replace(/\n|\r/g, ''); - const decoded = H1Helper.decodeH1Message(decodeResult, msg); - decodeResult.decoded = decoded; - - decodeResult.decoder.decodeLevel = !decodeResult.remaining.text - ? 'full' - : 'partial'; - if (decodeResult.formatted.items.length === 0) { - if (options.debug) { - console.log(`Decoder: Unknown H1 message: ${message.text}`); - } - ResultFormatter.unknown(decodeResult, message.text); - decodeResult.decoded = false; - decodeResult.decoder.decodeLevel = 'none'; - } - return decodeResult; - } -} diff --git a/lib/plugins/Label_80.test.ts b/lib/plugins/Label_80.test.ts index df34054..9a0eec4 100644 --- a/lib/plugins/Label_80.test.ts +++ b/lib/plugins/Label_80.test.ts @@ -147,6 +147,16 @@ describe('Label 80', () => { expect(decodeResult.remaining.text).toBe('3701 INRANG//ERT'); }); + test('does not decode INR variant 1', () => { + // decoded by another parser + message.text = + 'INR/ID91511S,,/DC04032026,143534/MR19,/NR,,,,,,,,950,0/ET041505/FB983/VR32BF4C'; + const decodeResult = plugin.decode(message); + + expect(decodeResult.decoded).toBe(false); + expect(decodeResult.decoder.decodeLevel).toBe('none'); + }); + test('does not decode invalid messages', () => { message.text = '3N01 POSRPT Bogus message'; const decodeResult = plugin.decode(message); diff --git a/lib/plugins/Label_80.ts b/lib/plugins/Label_80.ts index 8499fc1..7c7ade4 100644 --- a/lib/plugins/Label_80.ts +++ b/lib/plugins/Label_80.ts @@ -182,6 +182,9 @@ export class Label_80 extends DecoderPlugin { } private parseCsvFormat(text: string, results: DecodeResult) { const csvParts = text.split(','); + if(csvParts.length !== 9) { + return; + } const header = csvParts[0].trim().split(/\s+/); ResultFormatter.unknown(results, header[0], ' '); ResultFormatter.unknown(results, header[1], ' '); diff --git a/lib/plugins/Label_80_INR.test.ts b/lib/plugins/Label_80_INR.test.ts new file mode 100644 index 0000000..85b83f1 --- /dev/null +++ b/lib/plugins/Label_80_INR.test.ts @@ -0,0 +1,32 @@ +import { decode } from 'node:punycode'; +import { MessageDecoder } from '../MessageDecoder'; +import { Label_H1 } from './Label_H1'; + +describe('Label 80 Preamble INR', () => { + let plugin: Label_H1; + const message = { label: '80', text: '' }; + + beforeEach(() => { + const decoder = new MessageDecoder(); + plugin = new Label_H1(decoder); + }); + + test('decodes variant 1', () => { + message.text = + 'INR/ID91511S,,/DC04032026,143534/MR19,/NR,,,,,,,,950,0/ET041505/FB983/VR32BF4C'; + const decodeResult = plugin.decode(message); + + expect(decodeResult.decoded).toBe(true); + expect(decodeResult.decoder.decodeLevel).toBe('partial'); + expect(decodeResult.raw.tail).toBe('91511S'); + expect(decodeResult.raw.message_timestamp).toBe(1772634934); + expect(decodeResult.raw.day).toBe(4); + expect(decodeResult.raw.eta_time).toBe(54300); + expect(decodeResult.raw.fuel_on_board).toBe(983); + expect(decodeResult.raw.version).toBe(3.2); + expect(decodeResult.raw.checksum).toBe(0xbf4c); + expect(decodeResult.formatted.description).toBe('In-Range Report'); + expect(decodeResult.formatted.items.length).toBe(6); + expect(decodeResult.remaining.text).toBe('MR19,/NR,,,,,,,,950,0'); + }); +}); diff --git a/lib/plugins/Label_H1.test.ts b/lib/plugins/Label_H1.test.ts index 0526d1b..19cb488 100644 --- a/lib/plugins/Label_H1.test.ts +++ b/lib/plugins/Label_H1.test.ts @@ -1,7 +1,7 @@ import { MessageDecoder } from '../MessageDecoder'; import { Label_H1 } from './Label_H1'; -describe('Label_H1 INI', () => { +describe('Label_H1', () => { let plugin: Label_H1; beforeEach(() => { @@ -14,7 +14,7 @@ describe('Label_H1 INI', () => { expect(plugin.name).toBe('label-h1'); expect(plugin.qualifiers).toBeDefined(); expect(plugin.qualifiers()).toEqual({ - labels: ['H1'], + labels: ['1J', '2J', '2P', '4J', '80', 'H1'], }); }); }); diff --git a/lib/plugins/Label_H1.ts b/lib/plugins/Label_H1.ts index 57bb7d4..d816477 100644 --- a/lib/plugins/Label_H1.ts +++ b/lib/plugins/Label_H1.ts @@ -3,11 +3,12 @@ import { DecodeResult, Message, Options } from '../DecoderPluginInterface'; import { H1Helper } from '../utils/h1_helper'; import { ResultFormatter } from '../utils/result_formatter'; +// TODO: come up with a better name as this decodes multiple labels export class Label_H1 extends DecoderPlugin { name = 'label-h1'; qualifiers() { return { - labels: ['H1'], + labels: ['1J', '2J', '2P', '4J', '80', 'H1'], }; } @@ -17,46 +18,88 @@ export class Label_H1 extends DecoderPlugin { decodeResult.message = message; const msg = message.text.replace(/\n|\r/g, ''); - const parts = msg.split('#'); + + // try to decode the entire message let decoded = false; + decoded = H1Helper.decodeH1Message(decodeResult, msg); + if (decoded) { + return this.processDecodeResult(decodeResult, decoded, options, message); + } + + // try to handle messages like `/HDQDLUA.POSN` if (msg.startsWith('/')) { const headerData = msg.split('.'); - const decoded = H1Helper.decodeH1Message( + decoded = H1Helper.decodeH1Message( decodeResult, headerData.slice(1).join('.'), - ); // skip up to # and then a little more + ); + if (decoded) { decodeResult.remaining.text = headerData[0] + '.' + decodeResult.remaining.text; - decodeResult.decoded = decoded; - decodeResult.decoder.decodeLevel = 'partial'; + return this.processDecodeResult( + decodeResult, + decoded, + options, + message, + ); } + } - return decodeResult; - } else if (parts.length === 1) { - decoded = H1Helper.decodeH1Message(decodeResult, msg); - } else if (parts.length == 2) { + // try to handle messages like `F37AMCLL93#M1BPOS` + const hashParts = msg.split('#'); + if (hashParts.length == 2) { // need a better way to figure this out - const offset = parts[0] === '- ' || isNaN(parseInt(parts[1][1])) ? 3 : 4; + const offset = + hashParts[0] === '- ' || isNaN(parseInt(hashParts[1][1])) ? 3 : 4; decoded = H1Helper.decodeH1Message( decodeResult, - msg.slice(parts[0].length + offset), - ); // skip up to # and then a little more - if (decoded && parts[0].length > 0) { - // ResultFormatter.unknown(decodeResult, parts[0].substring(0, 4)); - ResultFormatter.flightNumber(decodeResult, parts[0].substring(4)); - //ResultFormatter.unknown(decodeResult, parts[1].length == 5 ? parts[1].substring(0, 2) : parts[1].substring(0, 3), '#'); + msg.slice(hashParts[0].length + offset), + ); + if (decoded && hashParts[0].length > 0) { + // ResultFormatter.unknown(decodeResult, hashParts[0].substring(0, 4)); + ResultFormatter.flightNumber(decodeResult, hashParts[0].substring(4)); + //ResultFormatter.unknown(decodeResult, hashParts[1].length == 5 ? hashParts[1].substring(0, 2) : hashParts[1].substring(0, 3), '#'); // hack of the highest degree as we've parsed the rest of the message already but we want the remaining text to be in order decodeResult.remaining.text = - parts[0].substring(0, 4) + + hashParts[0].substring(0, 4) + '#' + - parts[1].substring(0, offset - 1) + + hashParts[1].substring(0, offset - 1) + '/' + decodeResult.remaining.text; } + if (decoded) { + return this.processDecodeResult( + decodeResult, + decoded, + options, + message, + ); + } } - decodeResult.decoded = decoded; + // try to handle messages like `M74AMC4086FTX` + const slashParts = msg.split('/'); + if (slashParts[0].length > 3) { + decoded = H1Helper.decodeH1Message( + decodeResult, + msg.slice(slashParts[0].length - 3), + ); + // flight number is already decoded in other fields + decodeResult.remaining.text = + slashParts[0].slice(0, 3) + '/' + decodeResult.remaining.text; + } + + return this.processDecodeResult(decodeResult, decoded, options, message); + } + + private processDecodeResult( + decodeResult: DecodeResult, + decoded: boolean, + options: Options, + message: Message, + ) { + decodeResult.decoded = decoded; decodeResult.decoder.decodeLevel = !decodeResult.remaining.text ? 'full' : 'partial'; diff --git a/lib/plugins/Label_H1_PWI.test.ts b/lib/plugins/Label_H1_PWI.test.ts index 6a3ec69..824be92 100644 --- a/lib/plugins/Label_H1_PWI.test.ts +++ b/lib/plugins/Label_H1_PWI.test.ts @@ -10,15 +10,6 @@ describe('Label H1 PWI', () => { plugin = new Label_H1(decoder); }); - test('matches Label H1 Preamble PWI qualifiers', () => { - expect(plugin.decode).toBeDefined(); - expect(plugin.name).toBe('label-h1'); - expect(plugin.qualifiers).toBeDefined(); - expect(plugin.qualifiers()).toEqual({ - labels: ['H1'], - }); - }); - test('decodes Label H1 Preamble PWI valid', () => { message.text = 'PWI/WD390,COLZI,258070.AWYAT,252071.IPTAY,250065.CHOPZ,244069.MGMRY,234065.CATLN,230060/WD340,COLZI,256073,340M41.AWYAT,252070,340M41.IPTAY,244059,340M41.CHOPZ,240059,340M41.MGMRY,232056,340M41.CATLN,218053,340M40/WD300,COLZI,256065.AWYAT,254062.IPTAY,250051.CHOPZ,248050.MGMRY,232044.CATLN,222047/WD240,COLZI,260045.AWYAT,258048.IPTAY,254043.CHOPZ,256041.MGMRY,238035.CATLN,226034/DD300214059.240214040.180236024.100250018:,,,,/CB300246040.240246017.180226015.1002100080338'; diff --git a/lib/plugins/official.ts b/lib/plugins/official.ts index cea7d03..23ff69e 100644 --- a/lib/plugins/official.ts +++ b/lib/plugins/official.ts @@ -12,7 +12,6 @@ export * from './Label_16_Honeywell'; export * from './Label_16_N_Space'; export * from './Label_16_POSA1'; export * from './Label_16_TOD'; -export * from './Label_1J_2J_FTX'; export * from './Label_1M_Slash'; export * from './Label_1L_3-line'; export * from './Label_1L_070'; @@ -25,7 +24,6 @@ export * from './Label_22_POS'; export * from './Label_2P_FM3'; export * from './Label_2P_FM4'; export * from './Label_2P_FM5'; -export * from './Label_2P_POS'; export * from './Label_24_Slash'; export * from './Label_30_Slash_EA'; export * from './Label_44_ETA'; @@ -39,7 +37,6 @@ export * from './Label_4A_01'; export * from './Label_4A_DIS'; export * from './Label_4A_DOOR'; export * from './Label_4A_Slash_01'; -export * from './Label_4J_POS'; export * from './Label_4N'; export * from './Label_4T_AGFSR'; export * from './Label_58'; From 97b119ea6529dffd731d5fe6b4a5b2e8f231f385 Mon Sep 17 00:00:00 2001 From: Mark Bumiller Date: Mon, 9 Mar 2026 08:48:33 -0400 Subject: [PATCH 2/2] fix typo --- lib/plugins/Label_H1.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/Label_H1.ts b/lib/plugins/Label_H1.ts index d816477..ea37f4f 100644 --- a/lib/plugins/Label_H1.ts +++ b/lib/plugins/Label_H1.ts @@ -26,7 +26,7 @@ export class Label_H1 extends DecoderPlugin { return this.processDecodeResult(decodeResult, decoded, options, message); } - // try to handle messages like `/HDQDLUA.POSN` + // try to handle messages like `/HDQDLUA.POS` if (msg.startsWith('/')) { const headerData = msg.split('.'); decoded = H1Helper.decodeH1Message(