Skip to content

Bug: Label_2P_FM4 and Label_2P_FM5 report position (0, 0) for empty lat/lon fields — same silent Gulf-of-Guinea failure as Label_2P_FM3 (#487) #495

Description

@kevinelliott

Summary

Label_2P_FM4.decode and Label_2P_FM5.decode feed Number(parts[4].replaceAll(' ', '')) / Number(parts[5].replaceAll(' ', '')) directly into ResultFormatter.position with no validation. When either coordinate field is empty (adjacent commas in the CSV, e.g. parts[4] === '' || parts[5] === ''), Number('') returns 0 (not NaN), so ResultFormatter.position's isNaN guard doesn't fire — and the plugin silently reports latitude 0, longitude 0 as if the aircraft were at 0°N 0°E off the coast of Africa. This is the exact same silent-corruption pattern as #487, in two sibling plugins that share the FM3 family's shape but weren't covered by that fix.

Affected code

lib/plugins/Label_2P_FM4.ts:48-51

ResultFormatter.position(decodeResult, {
  latitude: Number(parts[4].replaceAll(' ', '')),   // ← Number('') === 0
  longitude: Number(parts[5].replaceAll(' ', '')),  // ← Number('') === 0
});

lib/plugins/Label_2P_FM5.ts:44-47

ResultFormatter.position(decodeResult, {
  latitude: Number(parts[4].replaceAll(' ', '')),   // ← Number('') === 0
  longitude: Number(parts[5].replaceAll(' ', '')),  // ← Number('') === 0
});

Neither plugin has a hemisphere-prefix branch to guard against (unlike FM3, which has both an N…/S… branch and a numeric-only else-branch), so every invocation of these plugins with an empty coordinate CSV field silently emits (0, 0). ResultFormatter.position (lib/utils/result_formatter.ts:51-65) rejects only NaN, not exactly-0, so it lets the bogus point through.

The equator/prime-meridian display bug in #452 compounds the visual output: today the formatted string reads "0.000 N, 0.000 E"; after #452 is fixed it will read "0.000 N, 0.000 W" (depending on which sign convention wins). Either way, users see a real-looking position for a completely unpopulated field.

Reproduction

const decoder = new MessageDecoder();

// FM4: 10 comma-separated fields, parts[4] and parts[5] are empty (adjacent commas)
decoder.decode({
  label: '2P',
  text: 'XXFM4EGLL,KJFK,151400,153000,,,35000,270,x,y',
});
// decoded === true
// raw.position === { latitude: 0, longitude: 0 }
// formatted.items includes an 'aircraft_position' entry showing "0.000 N, 0.000 E"

// FM5: 12 comma-separated fields, parts[4] and parts[5] empty
decoder.decode({
  label: '2P',
  text: 'XXFM5 EGLL,KJFK,151400,153000,,,35000,x,y,z,ABC123,q',
});
// same silent (0, 0) result

The existing test files (lib/plugins/Label_2P_FM4.test.ts, Label_2P_FM5.test.ts) only exercise valid numeric coordinates and never the empty-field path.

Suggested fix

Same shape as the fix suggested in #487, applied to both files: reject the position push when either coord field is empty or non-numeric before calling ResultFormatter.position:

const latRaw = parts[4].replaceAll(' ', '');
const lonRaw = parts[5].replaceAll(' ', '');
const latNum = Number(latRaw);
const lonNum = Number(lonRaw);
if (latRaw.length > 0 && lonRaw.length > 0 &&
    Number.isFinite(latNum) && Number.isFinite(lonNum)) {
  ResultFormatter.position(decodeResult, {
    latitude: latNum,
    longitude: lonNum,
  });
}

An alternative that avoids duplicating the guard across the FM3/FM4/FM5 trio (and any future sibling) is to harden ResultFormatter.position itself to skip when both coords are exactly 0 and neither was explicitly provided — but that couples the formatter to input semantics, so the plugin-side guard (matching #487's chosen fix) is preferable.

Test coverage

Add cases to both Label_2P_FM4.test.ts and Label_2P_FM5.test.ts with empty lat/lon parts asserting that no aircraft_position entry is pushed and raw.position is left unset.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions