@aeroswarm/codec 0.1.11 → 0.1.13

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 CHANGED
@@ -87,7 +87,11 @@ function parseFormat(s) {
87
87
  function parseVariable(s) {
88
88
  const inner = firstContent(s, "Variable");
89
89
  const m = inner.match(/<Fixed\s+length="(\d+)"/);
90
- return { type: "variable", chunkBytes: m ? parseInt(m[1]) : 1, bits: parseBits(firstContent(inner, "Fixed")) };
90
+ const chunks = [];
91
+ const re = /<Fixed\s+length="\d+"[^>]*>([\s\S]*?)<\/Fixed>/g;
92
+ let c;
93
+ while ((c = re.exec(inner)) !== null) chunks.push(parseBits(c[1]));
94
+ return { type: "variable", chunkBytes: m ? parseInt(m[1]) : 1, bits: chunks[0] ?? parseBits(firstContent(inner, "Fixed")), chunks };
91
95
  }
92
96
  function parseBits(s) {
93
97
  const out = [];
@@ -116,11 +120,15 @@ function parseBits(s) {
116
120
  }
117
121
  return out;
118
122
  }
123
+ function repetitionBytes(body, fallback) {
124
+ if (/<BDS[\s/>]/.test(body)) return 8;
125
+ const m = body.match(/<Fixed\s+length="(\d+)"/);
126
+ return m ? parseInt(m[1]) : fallback;
127
+ }
119
128
  function parseRepetitive(s) {
120
129
  const inner = firstContent(s, "Repetitive");
121
130
  const repAttr = inner.match(/<Repetitive\s+rep="(\d+)"/);
122
- const m = inner.match(/<Fixed\s+length="(\d+)"/);
123
- return { type: "repetitive", repBytes: repAttr ? parseInt(repAttr[1]) : 1, itemBytes: m ? parseInt(m[1]) : 0 };
131
+ return { type: "repetitive", repBytes: repAttr ? parseInt(repAttr[1]) : 1, itemBytes: repetitionBytes(inner, 0) };
124
132
  }
125
133
  function parseCompound(s) {
126
134
  const inner = firstContent(s, "Compound");
@@ -139,7 +147,7 @@ function parseCompound(s) {
139
147
  subs.push({ type: "variable", chunkBytes: first ? parseInt(first[1]) : 1 });
140
148
  } else if (child.tag === "Repetitive") {
141
149
  const repAttr = child.open.match(/rep="(\d+)"/);
142
- subs.push({ type: "repetitive", repBytes: repAttr ? parseInt(repAttr[1]) : 1, itemBytes: first ? parseInt(first[1]) : 1 });
150
+ subs.push({ type: "repetitive", repBytes: repAttr ? parseInt(repAttr[1]) : 1, itemBytes: repetitionBytes(child.body, 1) });
143
151
  } else {
144
152
  subs.push({ type: "explicit" });
145
153
  }
@@ -188,23 +196,23 @@ function parseFspec(buf, start = 0) {
188
196
  return { bits, end: i };
189
197
  }
190
198
  function skipItem(buf, off, def) {
191
- if (off >= buf.length) return buf.length;
199
+ if (off >= buf.length) return buf.length + 1;
192
200
  switch (def.type) {
193
201
  case "fixed":
194
- return Math.min(off + def.bytes, buf.length);
202
+ return off + def.bytes;
195
203
  case "variable": {
196
204
  while (off + def.chunkBytes <= buf.length) {
197
205
  const last = buf[off + def.chunkBytes - 1];
198
206
  off += def.chunkBytes;
199
207
  if ((last & 1) === 0) break;
200
208
  }
201
- return Math.min(off, buf.length);
209
+ return off;
202
210
  }
203
211
  case "repetitive": {
204
- if (off + def.repBytes > buf.length) return buf.length;
212
+ if (off + def.repBytes > buf.length) return buf.length + 1;
205
213
  let rep = 0;
206
214
  for (let i = 0; i < def.repBytes; i++) rep = rep << 8 | buf[off + i];
207
- return Math.min(off + def.repBytes + rep * def.itemBytes, buf.length);
215
+ return off + def.repBytes + rep * def.itemBytes;
208
216
  }
209
217
  case "compound": {
210
218
  const presentIdxs = [];
@@ -222,13 +230,12 @@ function skipItem(buf, off, def) {
222
230
  if (idx >= def.subs.length) break;
223
231
  off = skipItem(buf, off, def.subs[idx]);
224
232
  }
225
- return Math.min(off, buf.length);
233
+ return off;
226
234
  }
227
235
  case "explicit": {
228
- if (off >= buf.length) return off;
236
+ if (off >= buf.length) return buf.length + 1;
229
237
  const len = buf[off];
230
- off++;
231
- return Math.min(off + len - 1, buf.length);
238
+ return off + Math.max(len, 1);
232
239
  }
233
240
  }
234
241
  }
@@ -238,17 +245,18 @@ function decodeBlock(block, def, onField) {
238
245
  const uapMap = new Map(def.uap.map((e) => [e.bit, e.id]));
239
246
  for (let i = 0; i < bits.length; i++) {
240
247
  if (!bits[i]) continue;
241
- if (off >= block.length) return { end: block.length, complete: true };
248
+ if (off >= block.length) return { end: block.length, complete: true, overrun: true };
242
249
  const uapPos = i + Math.floor(i / 7);
243
250
  const id = uapMap.get(uapPos);
244
- if (id === void 0) return { end: off, complete: false };
251
+ if (id === void 0) return { end: off, complete: false, overrun: false };
245
252
  const itemDef = def.items.get(id);
246
- if (!itemDef) return { end: off, complete: false };
253
+ if (!itemDef) return { end: off, complete: false, overrun: false };
247
254
  const start = off;
248
255
  off = skipItem(block, off, itemDef);
249
- onField(id, block.slice(start, off), itemDef);
256
+ onField(id, block.slice(start, Math.min(off, block.length)), itemDef);
257
+ if (off > block.length) return { end: block.length, complete: true, overrun: true };
250
258
  }
251
- return { end: off, complete: true };
259
+ return { end: off, complete: true, overrun: false };
252
260
  }
253
261
 
254
262
  // src/surveillance/asterix/fields.ts
@@ -293,6 +301,60 @@ function trdFlags(raw, out) {
293
301
  if (num(raw, "TST") === 1) out.testTarget = true;
294
302
  if (num(raw, "RAB") === 1) out.fixedTransponder = true;
295
303
  if (num(raw, "SIM") === 1) out.simulated = true;
304
+ const gbs = num(raw, "GBS");
305
+ if (gbs !== void 0) out.onGround = gbs === 1;
306
+ }
307
+ function compoundSubfields(d, sizeOf) {
308
+ const present = [];
309
+ let at = 0;
310
+ let sub = 0;
311
+ while (at < d.length) {
312
+ const b = d[at++];
313
+ for (let bit = 7; bit >= 1; bit--) {
314
+ if (b >> bit & 1) present.push(sub);
315
+ sub++;
316
+ }
317
+ if ((b & 1) === 0) break;
318
+ }
319
+ const offsets = /* @__PURE__ */ new Map();
320
+ let off = at;
321
+ for (const index of present) {
322
+ const size = sizeOf(index, d, off);
323
+ if (size === void 0) break;
324
+ offsets.set(index, off);
325
+ off += size;
326
+ }
327
+ return offsets;
328
+ }
329
+ function derivedDataSize(index, d, off) {
330
+ if (index < 7) return [3, 6, 2, 2, 2, 2, 2][index];
331
+ if (index === 7) {
332
+ let n = 1;
333
+ while (off + n - 1 < d.length && (d[off + n - 1] & 1) === 1) n++;
334
+ return n;
335
+ }
336
+ if (index === 8) return off < d.length ? 1 + 15 * d[off] : void 0;
337
+ if (index === 9 || index === 10) return 2;
338
+ return void 0;
339
+ }
340
+ function flightPlanSize(index) {
341
+ return [2, 7, 4, 1, 4, 1, 4, 4][index];
342
+ }
343
+ function asciiAt(d, off, len) {
344
+ if (off === void 0 || d.length < off + len) return void 0;
345
+ const text = d.subarray(off, off + len).toString("latin1").trim();
346
+ return text.length > 0 && /^[A-Z0-9 ]+$/.test(text) ? text : void 0;
347
+ }
348
+ function targetSize(raw, out) {
349
+ const lengthM = num(raw, "Len", "Length");
350
+ const widthM = num(raw, "WIDTH", "Width");
351
+ const orientationDeg = num(raw, "ORI", "Ori");
352
+ if (lengthM === void 0 && widthM === void 0 && orientationDeg === void 0) return;
353
+ out.targetSize = {
354
+ ...lengthM !== void 0 && { lengthM },
355
+ ...widthM !== void 0 && { widthM },
356
+ ...orientationDeg !== void 0 && { orientationDeg }
357
+ };
296
358
  }
297
359
  function decodeKnownField(cat, id, data, out, raw = {}) {
298
360
  try {
@@ -329,6 +391,21 @@ function cat021(id, d, out, raw) {
329
391
  trdFlags(raw, out);
330
392
  break;
331
393
  // I021/040 TRD
394
+ case "020": {
395
+ const ecat = num(raw, "ECAT");
396
+ if (ecat !== void 0) out.emitterCategory = ecat;
397
+ break;
398
+ }
399
+ case "155": {
400
+ const v = num(raw, "BVR");
401
+ if (v !== void 0) out.verticalRateFpm = v;
402
+ break;
403
+ }
404
+ case "157": {
405
+ const v = num(raw, "GVR");
406
+ if (v !== void 0 && out.verticalRateFpm === void 0) out.verticalRateFpm = v;
407
+ break;
408
+ }
332
409
  case "010": {
333
410
  const sac = num(raw, "SAC"), sic = num(raw, "SIC");
334
411
  if (sac !== void 0 && sic !== void 0) out.dataSource = { sac, sic };
@@ -570,6 +647,10 @@ function cat010(id, d, out, raw) {
570
647
  trdFlags(raw, out);
571
648
  break;
572
649
  // I010/020 TRD
650
+ case "270":
651
+ targetSize(raw, out);
652
+ break;
653
+ // I010/270 Target Size and Orientation
573
654
  case "010": {
574
655
  const sac = num(raw, "SAC"), sic = num(raw, "SIC");
575
656
  if (sac !== void 0 && sic !== void 0) out.dataSource = { sac, sic };
@@ -637,19 +718,6 @@ function cat010(id, d, out, raw) {
637
718
  }
638
719
  }
639
720
  var MS_TO_KT = 1 / 0.514444;
640
- function flightPlanCallsign(d) {
641
- let indicators = 0;
642
- while (indicators < d.length) {
643
- indicators++;
644
- if ((d[indicators - 1] & 1) === 0) break;
645
- }
646
- const first = d[0] ?? 0;
647
- if ((first & 64) === 0) return void 0;
648
- const at = indicators + ((first & 128) !== 0 ? 2 : 0);
649
- if (d.length < at + 7) return void 0;
650
- const text = d.subarray(at, at + 7).toString("latin1").trim();
651
- return text.length > 0 && /^[A-Z0-9 ]+$/.test(text) ? text : void 0;
652
- }
653
721
  function cat062(id, d, out, raw) {
654
722
  switch (id) {
655
723
  case "080":
@@ -695,6 +763,15 @@ function cat062(id, d, out, raw) {
695
763
  else if (d.length >= 2) out.trackNumber = d.readUIntBE(0, 2);
696
764
  break;
697
765
  }
766
+ case "220": {
767
+ const roc = num(raw, "RoC");
768
+ if (roc !== void 0) out.verticalRateFpm = roc;
769
+ break;
770
+ }
771
+ case "270":
772
+ targetSize(raw, out);
773
+ break;
774
+ // Target Size and Orientation
698
775
  case "300": {
699
776
  const vfi = num(raw, "VFI");
700
777
  out.vehicleFleet = vfi ?? (d.length >= 1 ? d[0] : void 0);
@@ -708,27 +785,30 @@ function cat062(id, d, out, raw) {
708
785
  break;
709
786
  }
710
787
  case "390": {
711
- if (out.callsign === void 0) {
712
- const csn = flightPlanCallsign(d);
713
- if (csn) out.callsign = csn;
714
- }
788
+ const at = compoundSubfields(d, flightPlanSize);
789
+ const csn = asciiAt(d, at.get(1), 7);
790
+ if (csn && out.callsign === void 0) out.callsign = csn;
791
+ const tac = asciiAt(d, at.get(4), 4);
792
+ if (tac) out.aircraftType = tac;
793
+ const wtc = asciiAt(d, at.get(5), 1);
794
+ if (wtc) out.wakeTurbulence = wtc;
795
+ const dep = asciiAt(d, at.get(6), 4);
796
+ if (dep) out.departureAirport = dep;
797
+ const dst = asciiAt(d, at.get(7), 4);
798
+ if (dst) out.destinationAirport = dst;
715
799
  break;
716
800
  }
717
801
  case "380": {
718
- let fsLen = 0;
719
- while (fsLen < d.length) {
720
- fsLen++;
721
- if ((d[fsLen - 1] & 1) === 0) break;
722
- }
723
- let at = fsLen;
724
- if (fsLen < d.length && (d[0] & 128) !== 0 && d.length >= at + 3) {
725
- out.icao24 = d.readUIntBE(at, 3).toString(16).padStart(6, "0");
726
- at += 3;
727
- }
728
- if (fsLen < d.length && (d[0] & 64) !== 0 && d.length >= at + 6 && out.callsign === void 0) {
729
- const id2 = readCallsign(d.subarray(at, at + 6));
730
- if (id2) out.callsign = id2;
802
+ const at = compoundSubfields(d, derivedDataSize);
803
+ const adr = at.get(0);
804
+ if (adr !== void 0 && d.length >= adr + 3) out.icao24 = d.readUIntBE(adr, 3).toString(16).padStart(6, "0");
805
+ const id2 = at.get(1);
806
+ if (id2 !== void 0 && d.length >= id2 + 6 && out.callsign === void 0) {
807
+ const name = readCallsign(d.subarray(id2, id2 + 6));
808
+ if (name) out.callsign = name;
731
809
  }
810
+ const sab = at.get(10);
811
+ if (sab !== void 0 && sab < d.length) out.onGround = (d[sab] >> 1 & 1) === 1;
732
812
  break;
733
813
  }
734
814
  case "070": {
@@ -936,12 +1016,12 @@ function parseAsterixBlocks(buf, defaultEdition) {
936
1016
  for (const edition of toTry) {
937
1017
  const def = loadDef(cat, edition);
938
1018
  if (!def) continue;
939
- const { all, consumed } = decodeRecords(block, def, cat);
1019
+ const { all, consumed, full } = decodeRecords(block, def, cat);
940
1020
  const first = all[0];
941
1021
  if (!first) continue;
942
1022
  const score = plausibilityScore(first);
943
- if (!best || score > best.score) {
944
- best = { edition, all, consumed, score };
1023
+ if (!best || full && !best.full || full === best.full && score > best.score) {
1024
+ best = { edition, all, consumed, score, full };
945
1025
  }
946
1026
  }
947
1027
  blocks.push({
@@ -949,36 +1029,53 @@ function parseAsterixBlocks(buf, defaultEdition) {
949
1029
  edition: best?.edition ?? null,
950
1030
  length: block.length,
951
1031
  consumed: best?.consumed ?? 0,
1032
+ clean: best?.full ?? false,
952
1033
  records: best?.all ?? []
953
1034
  });
954
1035
  }
955
1036
  return blocks;
956
1037
  }
957
1038
  function decodeRecords(block, def, cat) {
958
- const out = [];
1039
+ const records = [];
959
1040
  let pos = 0;
960
- let consumed = 0;
1041
+ let clean = false;
961
1042
  while (pos < block.length) {
962
1043
  const fields = {};
963
1044
  let result;
964
1045
  try {
965
1046
  result = decodeBlock(block.subarray(pos), def, (id, data, itemDef) => {
966
- const bits = itemDef.type === "fixed" || itemDef.type === "variable" ? itemDef.bits : void 0;
967
- const bitData = itemDef.type === "variable" ? data.subarray(0, itemDef.chunkBytes) : data;
968
- const raw = bits?.length ? extractBitFields(bitData, bits) : {};
969
- decodeKnownField(cat, id, data, fields, raw);
1047
+ decodeKnownField(cat, id, data, fields, extractItemBits(itemDef, data));
970
1048
  });
971
1049
  } catch {
972
1050
  break;
973
1051
  }
974
1052
  if (!isPlausible(fields)) break;
975
- if (out.length > 0 && Object.keys(fields).length === 0) break;
976
- out.push(fields);
977
- if (!result.complete || result.end <= 0) break;
1053
+ if (result.overrun && records.length > 0) break;
1054
+ records.push({ fields, end: pos + result.end });
1055
+ if (!result.complete || result.end <= 0 || result.overrun) break;
978
1056
  pos += result.end;
979
- consumed = pos;
1057
+ clean = pos >= block.length;
1058
+ }
1059
+ if (clean) {
1060
+ const kept = records.filter((r, i) => i === 0 || Object.keys(r.fields).length > 0).map((r) => r.fields);
1061
+ return { all: kept, consumed: block.length, full: true };
980
1062
  }
981
- return { all: out, consumed };
1063
+ const firstEmpty = records.findIndex((r, i) => i > 0 && Object.keys(r.fields).length === 0);
1064
+ const usable = firstEmpty === -1 ? records : records.slice(0, firstEmpty);
1065
+ return { all: usable.map((r) => r.fields), consumed: usable.length > 0 ? usable[usable.length - 1].end : 0, full: false };
1066
+ }
1067
+ function extractItemBits(itemDef, data) {
1068
+ if (itemDef.type === "fixed") return itemDef.bits?.length ? extractBitFields(data, itemDef.bits) : {};
1069
+ if (itemDef.type !== "variable") return {};
1070
+ const chunks = itemDef.chunks?.length ? itemDef.chunks : itemDef.bits ? [itemDef.bits] : [];
1071
+ const out = {};
1072
+ for (let i = 0; i < chunks.length; i++) {
1073
+ const start = i * itemDef.chunkBytes;
1074
+ if (start + itemDef.chunkBytes > data.length) break;
1075
+ const part = extractBitFields(data.subarray(start, start + itemDef.chunkBytes), chunks[i]);
1076
+ for (const [name, value] of Object.entries(part)) if (!(name in out)) out[name] = value;
1077
+ }
1078
+ return out;
982
1079
  }
983
1080
  function isPlausible(f) {
984
1081
  if (f.lat !== void 0 && (f.lat < -90 || f.lat > 90)) return false;
@@ -1333,6 +1430,7 @@ export {
1333
1430
  decodeMlatFrame,
1334
1431
  parseAftn,
1335
1432
  parseAsterix,
1433
+ parseAsterixBlocks,
1336
1434
  resolveAsterixAltitudeFt,
1337
1435
  resolveAsterixPosition,
1338
1436
  splitAftnStream
@@ -4,16 +4,21 @@ export declare function parseFspec(buf: Buffer, start?: number): {
4
4
  bits: boolean[];
5
5
  end: number;
6
6
  };
7
- /** Advance offset past one DataItem, returning new offset. */
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,8DAA8D;AAC9D,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,MAAM,CAiDvE;AAID;;;;;GAKG;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,CAAA;CAAE,CA0BpC"}
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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=descriptor-fields.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"descriptor-fields.test.d.ts","sourceRoot":"","sources":["../../../src/surveillance/asterix/descriptor-fields.test.ts"],"names":[],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../../../src/surveillance/asterix/fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAoE7C,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,GAAE,SAAc,GAAG,IAAI,CAWrH"}
1
+ {"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../../../src/surveillance/asterix/fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAwI7C,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,GAAE,SAAc,GAAG,IAAI,CAWrH"}
@@ -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;AAC3C,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"}
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. Less than `length` means an item was sized wrongly or the tail was not understood. */
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,iIAAiI;IACjI,QAAQ,EAAE,MAAM,CAAC;IACjB,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,CAwCpB"}
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=repetitive.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repetitive.test.d.ts","sourceRoot":"","sources":["../../../src/surveillance/asterix/repetitive.test.ts"],"names":[],"mappings":""}
@@ -41,6 +41,25 @@ export interface AsterixFields {
41
41
  vehicleFleet?: number;
42
42
  /** Sensor-assigned local track number */
43
43
  trackNumber?: number;
44
+ /** Ground bit: true = the target reports being on the ground, false = not. Undefined = not reported (CAT010/020/021 descriptor, CAT062 I062/380 SAB). */
45
+ onGround?: boolean;
46
+ /** ICAO aircraft type designator, e.g. "A320" (CAT062 I062/390 TAC) */
47
+ aircraftType?: string;
48
+ /** Wake turbulence category: L, M, H or J (CAT062 I062/390 WTC) */
49
+ wakeTurbulence?: string;
50
+ /** Departure and destination aerodrome, ICAO (CAT062 I062/390 DEP, DST) */
51
+ departureAirport?: string;
52
+ destinationAirport?: string;
53
+ /** Rate of climb (+) or descent (-), ft/min (CAT062 I062/220, CAT021 I021/155 baro, else I021/157 geometric) */
54
+ verticalRateFpm?: number;
55
+ /** Size and heading of the target on the surface (CAT062 I062/270, CAT010 I010/270) */
56
+ targetSize?: {
57
+ lengthM?: number;
58
+ widthM?: number;
59
+ orientationDeg?: number;
60
+ };
61
+ /** Emitter category of an ADS-B target (CAT021 I021/020 ECAT) */
62
+ emitterCategory?: number;
44
63
  /** TST bit — test target */
45
64
  testTarget?: boolean;
46
65
  /** RAB bit — report from a fixed field-monitor transponder (MLAT RPU etc.) */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/surveillance/asterix/types.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3D,iBAAiB,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,gBAAgB,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7D,UAAU,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAE1C,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gFAAgF;IAChF,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,OAAO,CAAA;KAAE,CAAC;IACnG,4CAA4C;IAC5C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B,0EAA0E;IAC1E,UAAU,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;IAChD,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yCAAyC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,4BAA4B;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iCAAiC;IACjC,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,aAAa,CAAC;CACvB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/surveillance/asterix/types.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3D,iBAAiB,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,gBAAgB,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7D,UAAU,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAE1C,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gFAAgF;IAChF,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,OAAO,CAAA;KAAE,CAAC;IACnG,4CAA4C;IAC5C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B,0EAA0E;IAC1E,UAAU,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;IAChD,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yCAAyC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yJAAyJ;IACzJ,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gHAAgH;IAChH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uFAAuF;IACvF,UAAU,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5E,iEAAiE;IACjE,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,4BAA4B;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iCAAiC;IACjC,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,aAAa,CAAC;CACvB"}
@@ -13,10 +13,12 @@ export type FixedDef = {
13
13
  bytes: number;
14
14
  bits?: BitFieldDef[];
15
15
  };
16
+ /** `bits` = the first octet's fields; `chunks` = the fields of every octet in order (the first extension re-numbers from bit 1). */
16
17
  export type VariableDef = {
17
18
  type: 'variable';
18
19
  chunkBytes: number;
19
20
  bits?: BitFieldDef[];
21
+ chunks?: BitFieldDef[][];
20
22
  };
21
23
  export type RepDef = {
22
24
  type: 'repetitive';
@@ -1 +1 @@
1
- {"version":3,"file":"xml-loader.d.ts","sourceRoot":"","sources":["../../../src/surveillance/asterix/xml-loader.ts"],"names":[],"mappings":"AAgBA;kEACkE;AAClE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,UAAU,GAAG,QAAQ,GAAG,WAAW,GAAG,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;IACxE,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,QAAQ,GAAQ;IAAE,IAAI,EAAE,OAAO,CAAC;IAAM,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,EAAE,CAAA;CAAE,CAAC;AACxF,MAAM,MAAM,WAAW,GAAK;IAAE,IAAI,EAAE,UAAU,CAAC;IAAG,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,EAAE,CAAA;CAAE,CAAC;AAC7F,MAAM,MAAM,MAAM,GAAU;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AACxF,MAAM,MAAM,WAAW,GAAK;IAAE,IAAI,EAAE,UAAU,CAAC;IAAG,cAAc,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,EAAE,CAAA;CAAE,CAAC;AAC/F,MAAM,MAAM,WAAW,GAAK;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AACjD,MAAM,MAAM,UAAU,GAAM,QAAQ,GAAG,WAAW,GAAG,MAAM,GAAG,WAAW,CAAC;AAC1E,MAAM,MAAM,OAAO,GAAS,QAAQ,GAAG,WAAW,GAAG,MAAM,GAAG,WAAW,GAAG,WAAW,CAAC;AAExF,MAAM,WAAW,QAAQ;IACvB,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,QAAQ,EAAE,CAAC;IAChB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7B;AAgCD,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAgBlE"}
1
+ {"version":3,"file":"xml-loader.d.ts","sourceRoot":"","sources":["../../../src/surveillance/asterix/xml-loader.ts"],"names":[],"mappings":"AAgBA;kEACkE;AAClE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,UAAU,GAAG,QAAQ,GAAG,WAAW,GAAG,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;IACxE,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,QAAQ,GAAQ;IAAE,IAAI,EAAE,OAAO,CAAC;IAAM,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,EAAE,CAAA;CAAE,CAAC;AACxF,oIAAoI;AACpI,MAAM,MAAM,WAAW,GAAK;IAAE,IAAI,EAAE,UAAU,CAAC;IAAG,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,EAAE,EAAE,CAAA;CAAE,CAAC;AACvH,MAAM,MAAM,MAAM,GAAU;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AACxF,MAAM,MAAM,WAAW,GAAK;IAAE,IAAI,EAAE,UAAU,CAAC;IAAG,cAAc,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,EAAE,CAAA;CAAE,CAAC;AAC/F,MAAM,MAAM,WAAW,GAAK;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AACjD,MAAM,MAAM,UAAU,GAAM,QAAQ,GAAG,WAAW,GAAG,MAAM,GAAG,WAAW,CAAC;AAC1E,MAAM,MAAM,OAAO,GAAS,QAAQ,GAAG,WAAW,GAAG,MAAM,GAAG,WAAW,GAAG,WAAW,CAAC;AAExF,MAAM,WAAW,QAAQ;IACvB,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,QAAQ,EAAE,CAAC;IAChB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7B;AAgCD,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAgBlE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aeroswarm/codec",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",