Summary
lib/plugins/Label_HX.ts registers two preambles — 'RA FMT LOCATION' and 'RA FMT 43'. When MessageDecoder matches a message against those exact preambles (with nothing after) and dispatches to this plugin, the LOCATION branch calls parts[3].substring(0, 1) and parts[4].substring(0, 1) with no length check, throwing TypeError. Because MessageDecoder.decode() does not wrap plugin calls in try/catch, the exception aborts the entire decode call for that message.
Location
lib/plugins/Label_HX.ts:15-42
const parts = message.text.split(' ');
decodeResult.decoded = true;
if (parts[2] === 'LOCATION') {
let latdir = parts[3].substring(0, 1); // <-- throws when parts.length <= 3
let latdeg = Number(parts[3].substring(1, 3));
let latmin = Number(parts[3].substring(3, 7));
let londir = parts[4].substring(0, 1); // <-- throws when parts.length <= 4
let londeg = Number(parts[4].substring(1, 4));
let lonmin = Number(parts[4].substring(4, 8));
...
} else if (parts[2] === '43') {
ResultFormatter.departureAirport(decodeResult, parts[3]); // <-- passes undefined downstream
ResultFormatter.unknownArr(decodeResult, parts.slice(4), ' ');
}
Reproduction
const decoder = new MessageDecoder();
decoder.decode({ label: 'HX', text: 'RA FMT LOCATION' });
// Uncaught TypeError: Cannot read properties of undefined (reading 'substring')
// at Label_HX.decode (Label_HX.ts:25)
// at MessageDecoder.decode (MessageDecoder.ts:198)
'RA FMT LOCATION'.split(' ') produces ['RA', 'FMT', 'LOCATION'] (length 3). Since this string exactly matches the registered preamble, matchesPreambles returns true and the plugin is invoked — but parts[3] is undefined, so .substring(0, 1) throws.
The '43' branch has the same shape (parts[3] may be undefined), though it hands the undefined value to ResultFormatter.departureAirport rather than dereferencing it, so it typically silently records an undefined airport instead of throwing. Both branches should be hardened.
Expected
decoder.decode({ label: 'HX', text: 'RA FMT LOCATION' }) should return a DecodeResult (typically decoded: false) — never throw.
Actual
Uncaught TypeError propagates out of MessageDecoder.decode(). The caller receives a thrown error rather than a normal DecodeResult, and all later candidate plugins for this label are skipped for this message.
Recommendation
Guard both branches before touching parts[3]/parts[4]:
if (parts[2] === 'LOCATION') {
if (parts.length < 5) {
decodeResult.decoded = false;
return decodeResult; // (and skip the level-computation block, or fall through so it sets 'none')
}
...
} else if (parts[2] === '43') {
if (parts.length < 4) {
decodeResult.decoded = false;
return decodeResult;
}
...
}
Consider also hardening MessageDecoder.decode() to catch plugin exceptions so a single buggy plugin cannot take down the whole decoder for a message — but the primary fix belongs in this plugin.
Notes
- The existing tests only cover fully-fleshed happy-path inputs (
'RA FMT LOCATION N4009.6 W07540.8' and 'RA FMT 43 GSP B02') and the completely-unrelated fallback ('HX Bogus message'). No preamble-only truncation test exists.
Summary
lib/plugins/Label_HX.tsregisters two preambles —'RA FMT LOCATION'and'RA FMT 43'. WhenMessageDecodermatches a message against those exact preambles (with nothing after) and dispatches to this plugin, the LOCATION branch callsparts[3].substring(0, 1)andparts[4].substring(0, 1)with no length check, throwingTypeError. BecauseMessageDecoder.decode()does not wrap plugin calls intry/catch, the exception aborts the entire decode call for that message.Location
lib/plugins/Label_HX.ts:15-42Reproduction
'RA FMT LOCATION'.split(' ')produces['RA', 'FMT', 'LOCATION'](length 3). Since this string exactly matches the registered preamble,matchesPreamblesreturnstrueand the plugin is invoked — butparts[3]isundefined, so.substring(0, 1)throws.The
'43'branch has the same shape (parts[3]may beundefined), though it hands the undefined value toResultFormatter.departureAirportrather than dereferencing it, so it typically silently records an undefined airport instead of throwing. Both branches should be hardened.Expected
decoder.decode({ label: 'HX', text: 'RA FMT LOCATION' })should return aDecodeResult(typicallydecoded: false) — never throw.Actual
Uncaught
TypeErrorpropagates out ofMessageDecoder.decode(). The caller receives a thrown error rather than a normalDecodeResult, and all later candidate plugins for this label are skipped for this message.Recommendation
Guard both branches before touching
parts[3]/parts[4]:Consider also hardening
MessageDecoder.decode()to catch plugin exceptions so a single buggy plugin cannot take down the whole decoder for a message — but the primary fix belongs in this plugin.Notes
'RA FMT LOCATION N4009.6 W07540.8'and'RA FMT 43 GSP B02') and the completely-unrelated fallback ('HX Bogus message'). No preamble-only truncation test exists.