Skip to content

Bug: Label_1M_Slash if (results) guard is dead — any 1M message with fewer than 9 fields throws TypeError and aborts MessageDecoder.decode() #469

Description

@kevinelliott

Summary

lib/plugins/Label_1M_Slash.ts splits a /-delimited 1M ETA message and unconditionally indexes into results[0..9]. The only guard — if (results) — is a no-op because String.prototype.split(...).slice(1) always returns an array (truthy, possibly empty). Any 1M message that has fewer than 9 slash-or-newline-separated fields throws TypeError: Cannot read properties of undefined (reading 'replace') at line 41. MessageDecoder.decode() does not wrap plugin calls in try/catch, so the exception aborts the entire decode call for that message — the caller receives a thrown error, not a decoded: false result.

Location

lib/plugins/Label_1M_Slash.ts:23-55

const results = message.text.split(/\n|\//).slice(1);

if (results) {                             // <-- always truthy
  ...
  decodeResult.raw.flight_number = results[0];
  ResultFormatter.departureAirport(decodeResult, results[3]);
  ResultFormatter.arrivalAirport(decodeResult, results[4]);
  ResultFormatter.alternateAirport(decodeResult, results[5]);
  ResultFormatter.arrivalRunway(
    decodeResult,
    results[8].replace(results[4], ''),    // <-- throws when results.length <= 8
  );
  ...
}

Reproduction

const decoder = new MessageDecoder();
decoder.decode({ label: '1M', text: '/BA0843' });
// Uncaught TypeError: Cannot read properties of undefined (reading 'replace')
//   at Label_1M_Slash.decode (Label_1M_Slash.ts:41)
//   at MessageDecoder.decode (MessageDecoder.ts:198)

Any 1M message shorter than the fully-fleshed variant-1 form (11 fields including the leading empty string) triggers this. Truncated messages are common on lossy ACARS links, so this is easily reachable in production data.

Root causes:

  1. split(...).slice(1) always returns an array. if (results) is unconditionally true — it isn't guarding against anything and it doesn't check .length.
  2. MessageDecoder.decode() (lib/MessageDecoder.ts:193-203) calls entry.plugin.decode(message, options) without a try/catch, so a thrown plugin error kills the whole decode call rather than falling through to the next candidate plugin.

Expected

decoder.decode({ label: '1M', text: '/BA0843' }) should return a DecodeResult (typically decoded: false) — never throw.

Actual

Uncaught TypeError propagates out of MessageDecoder.decode(). All subsequent (later-registered) plugins in the candidates list are skipped for this message.

Recommendation

Replace the dead guard with a real length check and, ideally, only mark decoded: true once you've committed to reading the required indices:

const results = message.text.split(/\n|\//).slice(1);

if (results.length < 10) {
  decodeResult.decoded = false;
  return decodeResult;
}

// … existing decode …

decodeResult.decoded = true;
decodeResult.decoder.decodeLevel = 'partial';
return decodeResult;

Also worth considering: harden MessageDecoder.decode() to catch (or at least log-and-skip) plugin exceptions so a single buggy plugin can't take down the whole decoder for a message. Filed as a separate concern; the primary fix belongs in this plugin.

Notes

  • The existing test only covers the fully-fleshed happy-path variant-1 message; no truncated / short-form test exists.
  • decodeResult.decoded = true is set unconditionally after the if (results) block on line 57, so even if the exception were caught, the current shape marks the result as successfully decoded regardless of what actually happened above.

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