@aeroswarm/codec 0.1.11 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +39 -27
- package/dist/surveillance/asterix/decoder.d.ts +7 -2
- package/dist/surveillance/asterix/decoder.d.ts.map +1 -1
- package/dist/surveillance/asterix/index.d.ts +2 -1
- package/dist/surveillance/asterix/index.d.ts.map +1 -1
- package/dist/surveillance/asterix/parser.d.ts +3 -1
- package/dist/surveillance/asterix/parser.d.ts.map +1 -1
- package/dist/surveillance/asterix/repetitive.test.d.ts +2 -0
- package/dist/surveillance/asterix/repetitive.test.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -116,11 +116,15 @@ function parseBits(s) {
|
|
|
116
116
|
}
|
|
117
117
|
return out;
|
|
118
118
|
}
|
|
119
|
+
function repetitionBytes(body, fallback) {
|
|
120
|
+
if (/<BDS[\s/>]/.test(body)) return 8;
|
|
121
|
+
const m = body.match(/<Fixed\s+length="(\d+)"/);
|
|
122
|
+
return m ? parseInt(m[1]) : fallback;
|
|
123
|
+
}
|
|
119
124
|
function parseRepetitive(s) {
|
|
120
125
|
const inner = firstContent(s, "Repetitive");
|
|
121
126
|
const repAttr = inner.match(/<Repetitive\s+rep="(\d+)"/);
|
|
122
|
-
|
|
123
|
-
return { type: "repetitive", repBytes: repAttr ? parseInt(repAttr[1]) : 1, itemBytes: m ? parseInt(m[1]) : 0 };
|
|
127
|
+
return { type: "repetitive", repBytes: repAttr ? parseInt(repAttr[1]) : 1, itemBytes: repetitionBytes(inner, 0) };
|
|
124
128
|
}
|
|
125
129
|
function parseCompound(s) {
|
|
126
130
|
const inner = firstContent(s, "Compound");
|
|
@@ -139,7 +143,7 @@ function parseCompound(s) {
|
|
|
139
143
|
subs.push({ type: "variable", chunkBytes: first ? parseInt(first[1]) : 1 });
|
|
140
144
|
} else if (child.tag === "Repetitive") {
|
|
141
145
|
const repAttr = child.open.match(/rep="(\d+)"/);
|
|
142
|
-
subs.push({ type: "repetitive", repBytes: repAttr ? parseInt(repAttr[1]) : 1, itemBytes:
|
|
146
|
+
subs.push({ type: "repetitive", repBytes: repAttr ? parseInt(repAttr[1]) : 1, itemBytes: repetitionBytes(child.body, 1) });
|
|
143
147
|
} else {
|
|
144
148
|
subs.push({ type: "explicit" });
|
|
145
149
|
}
|
|
@@ -188,23 +192,23 @@ function parseFspec(buf, start = 0) {
|
|
|
188
192
|
return { bits, end: i };
|
|
189
193
|
}
|
|
190
194
|
function skipItem(buf, off, def) {
|
|
191
|
-
if (off >= buf.length) return buf.length;
|
|
195
|
+
if (off >= buf.length) return buf.length + 1;
|
|
192
196
|
switch (def.type) {
|
|
193
197
|
case "fixed":
|
|
194
|
-
return
|
|
198
|
+
return off + def.bytes;
|
|
195
199
|
case "variable": {
|
|
196
200
|
while (off + def.chunkBytes <= buf.length) {
|
|
197
201
|
const last = buf[off + def.chunkBytes - 1];
|
|
198
202
|
off += def.chunkBytes;
|
|
199
203
|
if ((last & 1) === 0) break;
|
|
200
204
|
}
|
|
201
|
-
return
|
|
205
|
+
return off;
|
|
202
206
|
}
|
|
203
207
|
case "repetitive": {
|
|
204
|
-
if (off + def.repBytes > buf.length) return buf.length;
|
|
208
|
+
if (off + def.repBytes > buf.length) return buf.length + 1;
|
|
205
209
|
let rep = 0;
|
|
206
210
|
for (let i = 0; i < def.repBytes; i++) rep = rep << 8 | buf[off + i];
|
|
207
|
-
return
|
|
211
|
+
return off + def.repBytes + rep * def.itemBytes;
|
|
208
212
|
}
|
|
209
213
|
case "compound": {
|
|
210
214
|
const presentIdxs = [];
|
|
@@ -222,13 +226,12 @@ function skipItem(buf, off, def) {
|
|
|
222
226
|
if (idx >= def.subs.length) break;
|
|
223
227
|
off = skipItem(buf, off, def.subs[idx]);
|
|
224
228
|
}
|
|
225
|
-
return
|
|
229
|
+
return off;
|
|
226
230
|
}
|
|
227
231
|
case "explicit": {
|
|
228
|
-
if (off >= buf.length) return
|
|
232
|
+
if (off >= buf.length) return buf.length + 1;
|
|
229
233
|
const len = buf[off];
|
|
230
|
-
off
|
|
231
|
-
return Math.min(off + len - 1, buf.length);
|
|
234
|
+
return off + Math.max(len, 1);
|
|
232
235
|
}
|
|
233
236
|
}
|
|
234
237
|
}
|
|
@@ -238,17 +241,18 @@ function decodeBlock(block, def, onField) {
|
|
|
238
241
|
const uapMap = new Map(def.uap.map((e) => [e.bit, e.id]));
|
|
239
242
|
for (let i = 0; i < bits.length; i++) {
|
|
240
243
|
if (!bits[i]) continue;
|
|
241
|
-
if (off >= block.length) return { end: block.length, complete: true };
|
|
244
|
+
if (off >= block.length) return { end: block.length, complete: true, overrun: true };
|
|
242
245
|
const uapPos = i + Math.floor(i / 7);
|
|
243
246
|
const id = uapMap.get(uapPos);
|
|
244
|
-
if (id === void 0) return { end: off, complete: false };
|
|
247
|
+
if (id === void 0) return { end: off, complete: false, overrun: false };
|
|
245
248
|
const itemDef = def.items.get(id);
|
|
246
|
-
if (!itemDef) return { end: off, complete: false };
|
|
249
|
+
if (!itemDef) return { end: off, complete: false, overrun: false };
|
|
247
250
|
const start = off;
|
|
248
251
|
off = skipItem(block, off, itemDef);
|
|
249
|
-
onField(id, block.slice(start, off), itemDef);
|
|
252
|
+
onField(id, block.slice(start, Math.min(off, block.length)), itemDef);
|
|
253
|
+
if (off > block.length) return { end: block.length, complete: true, overrun: true };
|
|
250
254
|
}
|
|
251
|
-
return { end: off, complete: true };
|
|
255
|
+
return { end: off, complete: true, overrun: false };
|
|
252
256
|
}
|
|
253
257
|
|
|
254
258
|
// src/surveillance/asterix/fields.ts
|
|
@@ -936,12 +940,12 @@ function parseAsterixBlocks(buf, defaultEdition) {
|
|
|
936
940
|
for (const edition of toTry) {
|
|
937
941
|
const def = loadDef(cat, edition);
|
|
938
942
|
if (!def) continue;
|
|
939
|
-
const { all, consumed } = decodeRecords(block, def, cat);
|
|
943
|
+
const { all, consumed, full } = decodeRecords(block, def, cat);
|
|
940
944
|
const first = all[0];
|
|
941
945
|
if (!first) continue;
|
|
942
946
|
const score = plausibilityScore(first);
|
|
943
|
-
if (!best || score > best.score) {
|
|
944
|
-
best = { edition, all, consumed, score };
|
|
947
|
+
if (!best || full && !best.full || full === best.full && score > best.score) {
|
|
948
|
+
best = { edition, all, consumed, score, full };
|
|
945
949
|
}
|
|
946
950
|
}
|
|
947
951
|
blocks.push({
|
|
@@ -949,15 +953,16 @@ function parseAsterixBlocks(buf, defaultEdition) {
|
|
|
949
953
|
edition: best?.edition ?? null,
|
|
950
954
|
length: block.length,
|
|
951
955
|
consumed: best?.consumed ?? 0,
|
|
956
|
+
clean: best?.full ?? false,
|
|
952
957
|
records: best?.all ?? []
|
|
953
958
|
});
|
|
954
959
|
}
|
|
955
960
|
return blocks;
|
|
956
961
|
}
|
|
957
962
|
function decodeRecords(block, def, cat) {
|
|
958
|
-
const
|
|
963
|
+
const records = [];
|
|
959
964
|
let pos = 0;
|
|
960
|
-
let
|
|
965
|
+
let clean = false;
|
|
961
966
|
while (pos < block.length) {
|
|
962
967
|
const fields = {};
|
|
963
968
|
let result;
|
|
@@ -972,13 +977,19 @@ function decodeRecords(block, def, cat) {
|
|
|
972
977
|
break;
|
|
973
978
|
}
|
|
974
979
|
if (!isPlausible(fields)) break;
|
|
975
|
-
if (
|
|
976
|
-
|
|
977
|
-
if (!result.complete || result.end <= 0) break;
|
|
980
|
+
if (result.overrun && records.length > 0) break;
|
|
981
|
+
records.push({ fields, end: pos + result.end });
|
|
982
|
+
if (!result.complete || result.end <= 0 || result.overrun) break;
|
|
978
983
|
pos += result.end;
|
|
979
|
-
|
|
984
|
+
clean = pos >= block.length;
|
|
985
|
+
}
|
|
986
|
+
if (clean) {
|
|
987
|
+
const kept = records.filter((r, i) => i === 0 || Object.keys(r.fields).length > 0).map((r) => r.fields);
|
|
988
|
+
return { all: kept, consumed: block.length, full: true };
|
|
980
989
|
}
|
|
981
|
-
|
|
990
|
+
const firstEmpty = records.findIndex((r, i) => i > 0 && Object.keys(r.fields).length === 0);
|
|
991
|
+
const usable = firstEmpty === -1 ? records : records.slice(0, firstEmpty);
|
|
992
|
+
return { all: usable.map((r) => r.fields), consumed: usable.length > 0 ? usable[usable.length - 1].end : 0, full: false };
|
|
982
993
|
}
|
|
983
994
|
function isPlausible(f) {
|
|
984
995
|
if (f.lat !== void 0 && (f.lat < -90 || f.lat > 90)) return false;
|
|
@@ -1333,6 +1344,7 @@ export {
|
|
|
1333
1344
|
decodeMlatFrame,
|
|
1334
1345
|
parseAftn,
|
|
1335
1346
|
parseAsterix,
|
|
1347
|
+
parseAsterixBlocks,
|
|
1336
1348
|
resolveAsterixAltitudeFt,
|
|
1337
1349
|
resolveAsterixPosition,
|
|
1338
1350
|
splitAftnStream
|
|
@@ -4,16 +4,21 @@ export declare function parseFspec(buf: Buffer, start?: number): {
|
|
|
4
4
|
bits: boolean[];
|
|
5
5
|
end: number;
|
|
6
6
|
};
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Advance offset past one DataItem, returning new offset. The result is NOT clamped to the buffer: an offset past
|
|
9
|
+
* `buf.length` means the item needs more octets than there are (an overrun), which `decodeBlock` reports.
|
|
10
|
+
*/
|
|
8
11
|
export declare function skipItem(buf: Buffer, off: number, def: ItemDef): number;
|
|
9
12
|
/**
|
|
10
13
|
* Decode ONE record at the start of `block` (a data block can carry many records back to back, each with its own
|
|
11
14
|
* FSPEC). Calls `onField(id, bytes, itemDef)` for each present DataItem. Returns where the record ended, so the caller
|
|
12
15
|
* can go on with the next one, and whether that position can be trusted: `complete` is false when an item the
|
|
13
|
-
* definition does not know stopped the decode (the length of what follows is then unknown)
|
|
16
|
+
* definition does not know stopped the decode (the length of what follows is then unknown); `overrun` is true when an
|
|
17
|
+
* item needed more octets than the block has (the record is cut short or an item was sized wrongly).
|
|
14
18
|
*/
|
|
15
19
|
export declare function decodeBlock(block: Buffer, def: AsterixDef, onField: (id: string, data: Buffer, itemDef: ItemDef) => void): {
|
|
16
20
|
end: number;
|
|
17
21
|
complete: boolean;
|
|
22
|
+
overrun: boolean;
|
|
18
23
|
};
|
|
19
24
|
//# sourceMappingURL=decoder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decoder.d.ts","sourceRoot":"","sources":["../../../src/surveillance/asterix/decoder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAI3D,sFAAsF;AACtF,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG;IAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAUnF;AAID
|
|
1
|
+
{"version":3,"file":"decoder.d.ts","sourceRoot":"","sources":["../../../src/surveillance/asterix/decoder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAI3D,sFAAsF;AACtF,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG;IAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAUnF;AAID;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,MAAM,CAiDvE;AAID;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,GAC5D;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CA2BtD"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { parseAsterix } from './parser.js';
|
|
1
|
+
export { parseAsterix, parseAsterixBlocks } from './parser.js';
|
|
2
|
+
export type { AsterixBlockInfo } from './parser.js';
|
|
2
3
|
export { resolveAsterixPosition, resolveAsterixAltitudeFt } from './resolvers.js';
|
|
3
4
|
export type { RadarSite } from './resolvers.js';
|
|
4
5
|
export type { AsterixRecord, AsterixFields } from './types.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/surveillance/asterix/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/surveillance/asterix/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC/D,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAClF,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -19,8 +19,10 @@ export interface AsterixBlockInfo {
|
|
|
19
19
|
edition: string | null;
|
|
20
20
|
/** Octets of the block after its 3-octet header. */
|
|
21
21
|
length: number;
|
|
22
|
-
/** Octets taken up by the decoded records.
|
|
22
|
+
/** Octets taken up by the decoded records. */
|
|
23
23
|
consumed: number;
|
|
24
|
+
/** The records tile the block exactly: no octets left over, none missing, no item that ran past the end. False points at an item sized wrongly or a tail that was not understood. */
|
|
25
|
+
clean: boolean;
|
|
24
26
|
records: AsterixFields[];
|
|
25
27
|
}
|
|
26
28
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../../src/surveillance/asterix/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAsB/D;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACtC,aAAa,EAAE,CAOjB;AAED,iJAAiJ;AACjJ,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC;IACf,
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../../src/surveillance/asterix/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAsB/D;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACtC,aAAa,EAAE,CAOjB;AAED,iJAAiJ;AACjJ,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,qLAAqL;IACrL,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACtC,gBAAgB,EAAE,CA2CpB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repetitive.test.d.ts","sourceRoot":"","sources":["../../../src/surveillance/asterix/repetitive.test.ts"],"names":[],"mappings":""}
|