Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/bin/acars-decoder-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function debugMessage(message: Message, decoding: DecodeResult) {
console.log("Decoded Message:");
console.log(decoding.formatted.description);
if (decoding.formatted.items && decoding.formatted.items.length > 0) {
decoding.formatted.items.forEach((item: any) => {
decoding.formatted.items.forEach((item) => {
console.log(`${item.label} - ${item.value}`);
});
}
Expand Down
2 changes: 1 addition & 1 deletion lib/bin/acars-decoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const result = decoder.decode(message, { debug: true });
console.log("Decoded Message:");
console.log(result.formatted.description);
if (result.formatted.items && result.formatted.items.length > 0) {
result.formatted.items.forEach((item: any) => {
result.formatted.items.forEach((item) => {
console.log(`${item.label} - ${item.value}`);
});
}
29 changes: 25 additions & 4 deletions lib/utils/miam.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,21 @@ test('v1, compressed, acars', () => {
const msg = 'T32!<<,W/jVEJ5u(@\\!\'s.16q/)<:-JXX|Q&a)r_CuOS?R65eRb:E8<DV/R(\'OXUd&Vsp5njIZhc3K&["hCafn>bHPILl$J-V*::RL,d]?So_4M_VS;Ln"U,;8u?`,7j=ACsP@,t.rGoaFFQm1S7*\'>kRs>,:u>X?oe2->Z-5`_Ztu,Fr.R(jR7>J^s-94:^OqFYrF][5q?tMocL[p%^T#7).P:W;.$4Ym1#&8iu%ac;%S_9i<e!Lp`bDFhu;eR!R&T>qkLZ_rC^NA6gllCR_sE-?$k^?N:\'+';
const decodeResult = MIAMCoreUtils.parse(msg);

expect(decodeResult.decoded).toBe(true);
if(!decodeResult.decoded) {
expect(decodeResult.decoded).toBe(true);
return;
}
expect(decodeResult.message.data.version).toBe(1);
expect(decodeResult.message.data.complete).toBe(true);
expect(decodeResult.message.data.crcOk).toBe(true);
expect(decodeResult.message.data.crc).toBe(0x1a764e3e);
expect(decodeResult.message.data.compression).toBe(MIAMCoreV1Compression.Deflate);
expect(decodeResult.message.data.msgNum).toBe(86);
expect(decodeResult.message.data.ackOptions).toBe(1);
if(!decodeResult.message.data.acars) {
expect(decodeResult.message.data.acars).toBeDefined();
return;
}
expect(decodeResult.message.data.acars.tail).toBe('.A7-ANS');
expect(decodeResult.message.data.acars.label).toBe('H1');
expect(decodeResult.message.data.acars.sublabel).toBe('DF');
Expand All @@ -29,14 +36,20 @@ test('v1, compressed, acars', () => {
test('v2, uncompressed, non-acars', () => {
const msg = 'T-3!YPJ?0L8c"VuQet|KJFK,KBOS,CYHZ,KPHL';
const decodeResult = MIAMCoreUtils.parse(msg);

expect(decodeResult.decoded).toBe(true);
if(!decodeResult.decoded) {
expect(decodeResult.decoded).toBe(true);
return;
}
expect(decodeResult.message.data.version).toBe(2);
expect(decodeResult.message.data.msgNum).toBe(9);
expect(decodeResult.message.data.ackOptions).toBe(0);
expect(decodeResult.message.data.compression).toBe(MIAMCoreV2Compression.None);
expect(decodeResult.message.data.crcOk).toBe(true);
expect(decodeResult.message.data.crc).toBe(0x38a8);
if(!decodeResult.message.data.non_acars) {
expect(decodeResult.message.data.non_acars).toBeDefined();
return;
}
expect(decodeResult.message.data.non_acars.appId).toBe('0AW');
expect(decodeResult.message.data.non_acars.text).toBe('KJFK,KBOS,CYHZ,KPHL');

Expand All @@ -47,14 +60,22 @@ test('v1, compressed, acars, incomplete', () => {
const msg = 'T12!<<B[67joO3AE3:!\'s.16q2Tb:9FQs|X]eG[j2M]0.rudB<$2(Xj>:Whn0\'KEp#_@kN+<G\'I.G-DIX^RW,uJ,M,&9<XO?406A(Bnse^_UPF+su$OC:KOI/*p=F8Gh5:Or%)B`e/.%tN-jYGidD%+[OhHG_Wc9K^E3Skpit$/,N);FlMj%g)Orhs^"es8)BPa6C51Im?a^0@S64/';
const decodeResult = MIAMCoreUtils.parse(msg);

expect(decodeResult.decoded).toBe(true);

if(!decodeResult.decoded) {
expect(decodeResult.decoded).toBe(true);
return;
}
expect(decodeResult.message.data.version).toBe(1);
expect(decodeResult.message.data.complete).toBe(false);
expect(decodeResult.message.data.crcOk).toBe(false);
expect(decodeResult.message.data.crc).toBe(0x7d8e4eae);
expect(decodeResult.message.data.compression).toBe(MIAMCoreV1Compression.Deflate);
expect(decodeResult.message.data.msgNum).toBe(20);
expect(decodeResult.message.data.ackOptions).toBe(1);
if(!decodeResult.message.data.acars) {
expect(decodeResult.message.data.acars).toBeDefined();
return;
}
expect(decodeResult.message.data.acars.tail).toBe('B-18910');
expect(decodeResult.message.data.acars.label).toBe('H1');
expect(decodeResult.message.data.acars.sublabel).toBe('DF');
Expand Down
140 changes: 79 additions & 61 deletions lib/utils/miam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import * as Base85 from 'base85';
import * as zlib from "minizlib";
import { Buffer } from 'node:buffer';

enum MIAMVersion {
V1 = 1,
V2 = 2,
}

enum MIAMFid {
SingleTransfer = 'T',
Expand All @@ -20,14 +24,7 @@ enum MIAMCorePdu {
AlohaReply = 3,
}

enum MIAMCoreV1App {
ACARS2Char = 0x0,
ACARS4Char = 0x1,
ACARS6Char = 0x2,
NonACARS6Char = 0x3,
}

enum MIAMCoreV2App {
enum MIAMCoreApp {
ACARS2Char = 0x0,
ACARS4Char = 0x1,
ACARS6Char = 0x2,
Expand Down Expand Up @@ -61,7 +58,7 @@ interface PduNonACARSData {
}

interface Pdu {
version: number,
version: MIAMVersion,
crc: number,
crcOk: boolean,
complete: boolean,
Expand All @@ -72,24 +69,42 @@ interface Pdu {
acars?: PduACARSData,
non_acars?: PduNonACARSData,
}
const isMIAMVersion= (x: number): x is MIAMVersion => Object.values(MIAMVersion).includes(x as MIAMVersion);
const isMIAMFid= (x: string): x is MIAMFid => Object.values(MIAMFid).includes(x as MIAMFid);
const isMIAMCoreApp= (x: number): x is MIAMCoreApp => Object.values(MIAMCoreApp).includes(x as MIAMCoreApp);
const isMIAMCorePdu= (x: number): x is MIAMCorePdu => Object.values(MIAMCorePdu).includes(x as MIAMCorePdu);

interface PduDecodingSuccess {
decoded: true;
message: {
data: Pdu;
};
}

interface PduDecodingFailure {
decoded: false;
error: string;
}

type PduDecodingResult = PduDecodingSuccess | PduDecodingFailure;

export class MIAMCoreUtils {
static AppTypeToAppIdLenTable: any = {
1: {
[MIAMCoreV1App.ACARS2Char]: 2,
[MIAMCoreV1App.ACARS4Char]: 4,
[MIAMCoreV1App.ACARS6Char]: 6,
[MIAMCoreV1App.NonACARS6Char]: 6,
static AppTypeToAppIdLenTable: Record<MIAMVersion, Record<MIAMCoreApp, number>> = {
[MIAMVersion.V1]: {
[MIAMCoreApp.ACARS2Char]: 2,
[MIAMCoreApp.ACARS4Char]: 4,
[MIAMCoreApp.ACARS6Char]: 6,
[MIAMCoreApp.NonACARS6Char]: 6,
},
2: {
[MIAMCoreV2App.ACARS2Char]: 2,
[MIAMCoreV2App.ACARS4Char]: 4,
[MIAMCoreV2App.ACARS6Char]: 6,
[MIAMCoreV2App.NonACARS6Char]: 6,
[MIAMVersion.V2]: {
[MIAMCoreApp.ACARS2Char]: 2,
[MIAMCoreApp.ACARS4Char]: 4,
[MIAMCoreApp.ACARS6Char]: 6,
[MIAMCoreApp.NonACARS6Char]: 6,
},
}

static FidHandlerTable: any = {
static FidHandlerTable: Record<MIAMFid, (txt: string) => PduDecodingResult> = {
[MIAMFid.SingleTransfer]: (txt: string) => {
if (txt.length < 3) {
return {
Expand Down Expand Up @@ -161,22 +176,22 @@ export class MIAMCoreUtils {
const version = hdr.readUInt8(0) & 0xf;
const pduType = (hdr.readUInt8(0) >> 4) & 0xf;

const versionPduHandler = this.VersionPduHandlerTable[version][pduType];
if (versionPduHandler === undefined) {
if(isMIAMVersion(version) && isMIAMCorePdu(pduType)) {
const versionPduHandler = this.VersionPduHandlerTable[version][pduType];
return versionPduHandler(hdr, body);
} else {
return {
decoded: false,
error: 'Invalid version and PDU type combination: v=' + version + ', pdu=' + pduType,
};
}

return versionPduHandler(hdr, body);
},
[MIAMFid.FileTransferRequest]: undefined,
[MIAMFid.FileTransferAccept]: undefined,
[MIAMFid.FileSegment]: undefined,
[MIAMFid.FileTransferAbort]: undefined,
[MIAMFid.XOFFIndication]: undefined,
[MIAMFid.XONIndication]: undefined,
[MIAMFid.FileTransferRequest]: () => {return {decoded: false, error: 'File Transfer Request not implemented'}},
[MIAMFid.FileTransferAccept]: () => {return {decoded: false, error: 'File Transfer Accept not implemented'}},
[MIAMFid.FileSegment]: () => {return {decoded: false, error: 'File Segment not implemented'}},
[MIAMFid.FileTransferAbort]: () => {return {decoded: false, error: 'File Transfer Abort not implemented'}},
[MIAMFid.XOFFIndication]: () => {return {decoded: false, error: 'XOFF Indication not implemented'}},
[MIAMFid.XONIndication]: () => {return {decoded: false, error: 'XON Indication not implemented'}},
}

private static arincCrc16(buf: Buffer, seed?: number) {
Expand Down Expand Up @@ -301,21 +316,22 @@ export class MIAMCoreUtils {
return crc;
}

public static parse(txt: string) {
public static parse(txt: string): PduDecodingResult {
const fidType = txt[0];

const handler = this.FidHandlerTable[fidType];
if (handler === undefined) {
if(isMIAMFid(fidType)) {
const handler = this.FidHandlerTable[fidType];
return handler(txt.substring(1));
}else {
return {
decoded: false,
error: 'Unsupported FID type: ' + fidType,
};
}

return handler(txt.substring(1));
}

private static corePduDataHandler(version: number, minHdrSize: number, crcLen: number, hdr: Buffer, body?: Buffer) {
private static corePduDataHandler(version: MIAMVersion, minHdrSize: number, crcLen: number, hdr: Buffer, body?: Buffer): PduDecodingResult {
if (hdr.length < minHdrSize) {
return {
decoded: false,
Expand All @@ -329,7 +345,7 @@ export class MIAMCoreUtils {
let pduAppType: number = 0;
let pduAppId: string = '';
let pduCrc: number = 0;
let pduData: Buffer | undefined = undefined;
let pduData: Buffer | null = null;
let pduCrcIsOk: boolean = false;
let pduIsComplete: boolean = true;

Expand All @@ -339,7 +355,7 @@ export class MIAMCoreUtils {
let msgNum: number = 0;
let ackOptions: number = 0;

if (version === 1) {
if (version === MIAMVersion.V1) {
pduSize = (hdr.readUInt8(1) << 16) | (hdr.readUInt8(2) << 8) | hdr.readUInt8(3);

const msgSize = hdr.length + (body === undefined ? 0 : body.length);
Expand All @@ -351,7 +367,7 @@ export class MIAMCoreUtils {

tail = hdr.subarray(0, 7).toString('ascii');
hdr = hdr.subarray(7);
} else if (version === 2) {
} else if (version === MIAMVersion.V2) {
hdr = hdr.subarray(1);
}

Expand All @@ -364,9 +380,11 @@ export class MIAMCoreUtils {
pduAppType = hdr.readUInt8(1) & 0xf;
hdr = hdr.subarray(2)

let appIdLen = this.AppTypeToAppIdLenTable[version][pduAppType];
if (appIdLen === undefined) {
if (version === 2 && (pduAppType & 0x8) !== 0 && pduAppType !== 0xd) {
let appIdLen;
if(isMIAMCoreApp(pduAppType)) {
appIdLen = this.AppTypeToAppIdLenTable[version][pduAppType];
} else {
if (version === MIAMVersion.V2 && (pduAppType & 0x8) !== 0 && pduAppType !== 0xd) {
appIdLen = (pduAppType & 0x7) + 1;
} else {
return {
Expand All @@ -377,8 +395,8 @@ export class MIAMCoreUtils {
}

const pduIsACARS = ([
MIAMCoreV1App.ACARS2Char, MIAMCoreV1App.ACARS4Char, MIAMCoreV1App.ACARS6Char,
MIAMCoreV2App.ACARS2Char, MIAMCoreV2App.ACARS4Char, MIAMCoreV2App.ACARS6Char].indexOf(pduAppType) >= 0);
MIAMCoreApp.ACARS2Char, MIAMCoreApp.ACARS4Char, MIAMCoreApp.ACARS6Char
].indexOf(pduAppType) >= 0);

if (hdr.length < appIdLen + crcLen) {
return {
Expand All @@ -400,7 +418,7 @@ export class MIAMCoreUtils {
if (body !== undefined && body.length > 0) {
if ([MIAMCoreV1Compression.Deflate, MIAMCoreV2Compression.Deflate].indexOf(pduCompression) >= 0) {
try {
const decompress = new zlib.InflateRaw({windowBits: 15});
const decompress = new zlib.InflateRaw({});
decompress.write(body);
decompress.flush(zlib.constants.Z_SYNC_FLUSH);
pduData = decompress.read();
Expand All @@ -413,17 +431,17 @@ export class MIAMCoreUtils {
pduErrors.push('Unsupported v' + version + ' compression type: ' + pduCompression)
}

if (pduData !== undefined) {
const crcAlgoHandlerByVersion: any = {
1: (buf: Buffer, seed?: number) => { return ~this.arinc665Crc32(buf, seed); },
2: this.arincCrc16,
if (pduData !== null) {
const crcAlgoHandlerByVersion: Record<MIAMVersion, (buf: Buffer, seed?: number) => number> = {
[MIAMVersion.V1]: (buf: Buffer, seed?: number) => { return ~this.arinc665Crc32(buf, seed); },
[MIAMVersion.V2]: this.arincCrc16,
};

const crcAlgoHandler = crcAlgoHandlerByVersion[version];
if (crcAlgoHandler === undefined) {
return {
decoded: false,
errors: 'No CRC handler for v' + version,
error: 'No CRC handler for v' + version,
};
}

Expand Down Expand Up @@ -477,18 +495,18 @@ export class MIAMCoreUtils {
};
}

static VersionPduHandlerTable: any = {
1: {
[MIAMCorePdu.Data]: (hdr: Buffer, body?: Buffer) => { return this.corePduDataHandler(1, 20, MIAMCoreV1CRCLength, hdr, body); },
[MIAMCorePdu.Ack]: undefined,
[MIAMCorePdu.Aloha]: undefined,
[MIAMCorePdu.AlohaReply]: undefined,
static VersionPduHandlerTable: Record<MIAMVersion, Record<MIAMCorePdu, ((hdr: Buffer, body?: Buffer) => PduDecodingResult)>> = {
[MIAMVersion.V1]: {
[MIAMCorePdu.Data]: (hdr: Buffer, body?: Buffer) => { return this.corePduDataHandler(MIAMVersion.V1, 20, MIAMCoreV1CRCLength, hdr, body); },
[MIAMCorePdu.Ack]: () => {return {decoded: false, error: 'v1 Ack PDU not implemented'}},
[MIAMCorePdu.Aloha]: () => { return {decoded: false, error: 'v1 Aloha PDU not implemented'}},
[MIAMCorePdu.AlohaReply]: () => {return {decoded: false, error: 'v1 AlohaReply PDU not implemented'}},
},
2: {
[MIAMCorePdu.Data]: (hdr: Buffer, body?: Buffer) => { return this.corePduDataHandler(2, 7, MIAMCoreV2CRCLength, hdr, body); },
[MIAMCorePdu.Ack]: undefined,
[MIAMCorePdu.Aloha]: undefined,
[MIAMCorePdu.AlohaReply]: undefined,
[MIAMVersion.V2]: {
[MIAMCorePdu.Data]: (hdr: Buffer, body?: Buffer) => { return this.corePduDataHandler(MIAMVersion.V2, 7, MIAMCoreV2CRCLength, hdr, body); },
[MIAMCorePdu.Ack]: () => {return {decoded: false, error: 'v2 Ack PDU not implemented'}},
[MIAMCorePdu.Aloha]: () => {return {decoded: false, error: 'v2 Aloha PDU not implemented'}},
[MIAMCorePdu.AlohaReply]: () => {return {decoded: false, error: 'v2 Aloha reply PDU not implemented'}},
}
}
}
Expand Down