Skip to content

Bug: Label_HX LOCATION/43 branches dereference parts[3..4] with no length guard — preamble-only text throws TypeError and aborts MessageDecoder.decode() #471

Description

@kevinelliott

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions