Skip to content

Bug: Label_2P_FM3 / FM4 / FM5 missing-header guard is dead (split.length is never 0); FM3 then throws on header[1].length, FM4/FM5 emit undefined fields #462

Description

@kevinelliott

Summary

All three Label_2P_FM* plugins try to detect a missing FM3 / FM4 / FM5 preamble with if (header.length == 0) after splitting parts[0]. String.prototype.split always returns at least one element, so the guard is never true — it's dead code. When the preamble token actually is missing, header[1] is undefined:

  • FM3 dereferences header[1].length → throws TypeError: Cannot read properties of undefined (reading 'length'). Because the dispatch in MessageDecoder.decode does not wrap individual plugins in try/catch, the exception propagates out and aborts the whole decode.
  • FM4 and FM5 silently pass undefined to ResultFormatter.departureAirport, producing a "successfully decoded" result with departureAirport: { value: undefined }.

All three plugins are reachable from MessageDecoder for label 2P (see MessageDecoder.ts:40-42), and any 2P message whose comma-count matches the plugin's expectation but whose parts[0] doesn't actually contain the FMx token hits this path.

This is not a duplicate of #459 (different file, different handlers).

Affected code

Same shape in three files. FM3:

// lib/plugins/Label_2P_FM3.ts:24-40
if (parts.length === 7) {
  const header = parts[0].split('FM3 ');
  if (header.length == 0) {                          // never true
    ResultFormatter.unknown(decodeResult, message.text);
    decodeResult.decoded = false;
    decodeResult.decoder.decodeLevel = 'none';
    return decodeResult;
  }
  if (header[0].length > 0) {
    ResultFormatter.unknown(decodeResult, header[0].substring(0, 4));
    ResultFormatter.flightNumber(decodeResult, header[0].substring(4));
  }
  if (header[1].length === 4) { ... }                // throws when header[1] is undefined

FM4 (lib/plugins/Label_2P_FM4.ts:23-37) and FM5 (lib/plugins/Label_2P_FM5.ts:23-34) have the same dead guard and then pass header[1] straight into ResultFormatter.departureAirport without checking.

Note FM3 and FM5 split on 'FM3 '/'FM5 ' (with trailing space) while FM4 splits on 'FM4' (no space). The fix is the same shape in all three.

Reproduction

const decoder = new MessageDecoder();

// FM3 throws
decoder.decode({
  label: '2P',
  text: 'noFMheader,1454,N 45.206,E 17.726,34030, 440,98',
});
// → TypeError: Cannot read properties of undefined (reading 'length')

// FM4 silently produces undefined fields (10-field text, no 'FM4' token)
decoder.decode({
  label: '2P',
  text: 'noFMheader,KORD,2114000,1518,N 45.206,E 17.726,34030,180,foo,bar',
});
// → decoded: true, but result includes departureAirport.value === undefined

// FM5 silently produces undefined fields (12-field text, no 'FM5 ' token)
decoder.decode({
  label: '2P',
  text: 'noFMhdr,KORD,142000,1518,N 45.206,E 17.726,34030,a,b,c,FLT123,d',
});
// → decoded: true, but result includes departureAirport.value === undefined

Suggested fix

Change the guard in each plugin to check for the case the comment actually describes — the FMx token wasn't present:

if (header.length < 2) {       // or:  if (parts[0].indexOf('FM3 ') === -1)
  ResultFormatter.unknown(decodeResult, message.text);
  decodeResult.decoded = false;
  decodeResult.decoder.decodeLevel = 'none';
  return decodeResult;
}

(Replace 'FM3 ' / 'FM4' / 'FM5 ' accordingly per file.)

Test coverage

The existing Label_2P_FM3 test only covers well-formed FM3 … input, so the FM3 throw is uncovered. Adding a "no preamble" case per plugin that asserts the result has decoded === false and (for FM3) that the call does not throw would lock the fix in.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    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