Summary
Label_4A_DIS.decode splits message.text on , and then unconditionally does fields[1].substring(2) — with no fields.length check and no guard on the shape of fields[1]. Because the plugin qualifies on preambles: ['DIS'], any 4A message that starts with DIS but has no comma (e.g. a truncated header, or a DIS-only marker) causes fields[1] to be undefined, so undefined.substring(2) throws TypeError: Cannot read properties of undefined (reading 'substring'). The thrown error escapes the plugin and aborts MessageDecoder.decode() for the rest of the plugin chain — same failure mode as #469, #470, #471, #474, #480, #481, #482.
Affected code
lib/plugins/Label_4A_DIS.ts:16-31
decode(message: Message, options: Options = {}): DecodeResult {
const decodeResult = this.initResult(message, 'Latest New Format');
decodeResult.decoded = true;
const fields = message.text.split(',');
ResultFormatter.timestamp(
decodeResult,
DateTimeUtils.convertHHMMSSToTod(fields[1].substring(2) + '00'), // ← throws if fields.length < 2
);
ResultFormatter.callsign(decodeResult, fields[2]); // ← also unguarded
ResultFormatter.text(decodeResult, fields.slice(3).join(''));
...
}
fields[2] is a secondary hazard: on a two-field message (DIS01,190009), fields[2] is undefined, and ResultFormatter.callsign stores it as-is and formats ${undefined} into the output — silent bad data rather than a crash, but still wrong.
The test.skip('decodes Label 4A_DIS <invalid>', …) case in lib/plugins/Label_4A_DIS.test.ts:43-52 is explicitly disabled with the comment "all messages should decode" — the plugin currently makes the opposite assumption in code and crashes instead.
Reproduction
const decoder = new MessageDecoder();
// Preamble matches, but no comma
decoder.decode({ label: '4A', text: 'DIS' });
// TypeError: Cannot read properties of undefined (reading 'substring')
// Also crashes on a truncated header like just 'DIS01'
decoder.decode({ label: '4A', text: 'DIS01' });
// TypeError: Cannot read properties of undefined (reading 'substring')
Beyond the crash itself, the failure aborts the entire MessageDecoder.decode() loop, so any plugin that would have decoded the message after Label_4A_DIS in the chain never gets a chance to try.
Suggested fix
Guard on fields.length before dereferencing, matching the pattern used in the fixes for #469/#470/#471:
const fields = message.text.split(',');
if (fields.length < 3 || fields[1].length < 2) {
return this.failUnknown(decodeResult, message.text, options);
}
ResultFormatter.timestamp(
decodeResult,
DateTimeUtils.convertHHMMSSToTod(fields[1].substring(2) + '00'),
);
ResultFormatter.callsign(decodeResult, fields[2]);
ResultFormatter.text(decodeResult, fields.slice(3).join(''));
Test coverage
Add cases to lib/plugins/Label_4A_DIS.test.ts for text: 'DIS', text: 'DIS01', and text: 'DIS01,190009' that assert the decoder returns decoded: false / decodeLevel: 'none' without throwing. The currently-skipped <invalid> case can then be re-enabled.
Summary
Label_4A_DIS.decodesplitsmessage.texton,and then unconditionally doesfields[1].substring(2)— with nofields.lengthcheck and no guard on the shape offields[1]. Because the plugin qualifies onpreambles: ['DIS'], any 4A message that starts withDISbut has no comma (e.g. a truncated header, or aDIS-only marker) causesfields[1]to beundefined, soundefined.substring(2)throwsTypeError: Cannot read properties of undefined (reading 'substring'). The thrown error escapes the plugin and abortsMessageDecoder.decode()for the rest of the plugin chain — same failure mode as #469, #470, #471, #474, #480, #481, #482.Affected code
lib/plugins/Label_4A_DIS.ts:16-31fields[2]is a secondary hazard: on a two-field message (DIS01,190009),fields[2]isundefined, andResultFormatter.callsignstores it as-is and formats${undefined}into the output — silent bad data rather than a crash, but still wrong.The
test.skip('decodes Label 4A_DIS <invalid>', …)case inlib/plugins/Label_4A_DIS.test.ts:43-52is explicitly disabled with the comment "all messages should decode" — the plugin currently makes the opposite assumption in code and crashes instead.Reproduction
Beyond the crash itself, the failure aborts the entire
MessageDecoder.decode()loop, so any plugin that would have decoded the message after Label_4A_DIS in the chain never gets a chance to try.Suggested fix
Guard on
fields.lengthbefore dereferencing, matching the pattern used in the fixes for #469/#470/#471:Test coverage
Add cases to
lib/plugins/Label_4A_DIS.test.tsfortext: 'DIS',text: 'DIS01', andtext: 'DIS01,190009'that assert the decoder returnsdecoded: false/decodeLevel: 'none'without throwing. The currently-skipped<invalid>case can then be re-enabled.