@krokodilushka/mavlink-parser-browserify 0.1.0

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.
@@ -0,0 +1,738 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sendSigned = exports.send = exports.createMavLinkStream = exports.MavLinkPacketParser = exports.MavLinkTLogPacketSplitter = exports.MavLinkPacketSplitter = exports.MavLinkPacket = exports.MavLinkPacketSignature = exports.MavLinkProtocolV2 = exports.MavLinkProtocolV1 = exports.MavLinkProtocol = exports.MavLinkPacketHeader = void 0;
4
+ const stream_1 = require("stream");
5
+ const crypto_1 = require("crypto");
6
+ const mavlink_mappings_1 = require("mavlink-mappings");
7
+ const mavlink_mappings_2 = require("mavlink-mappings");
8
+ const utils_1 = require("./utils");
9
+ const logger_1 = require("./logger");
10
+ const serialization_1 = require("./serialization");
11
+ /**
12
+ * Header definition of the MavLink packet
13
+ */
14
+ class MavLinkPacketHeader {
15
+ timestamp = null;
16
+ magic = 0;
17
+ payloadLength = 0;
18
+ incompatibilityFlags = 0;
19
+ compatibilityFlags = 0;
20
+ seq = 0;
21
+ sysid = 0;
22
+ compid = 0;
23
+ msgid = 0;
24
+ }
25
+ exports.MavLinkPacketHeader = MavLinkPacketHeader;
26
+ /**
27
+ * Base class for protocols
28
+ *
29
+ * Implements common functionality like getting the CRC and deserializing
30
+ * data classes from the given payload buffer
31
+ */
32
+ class MavLinkProtocol {
33
+ log = logger_1.Logger.getLogger(this);
34
+ static NAME = "unknown";
35
+ static START_BYTE = 0;
36
+ static PAYLOAD_OFFSET = 0;
37
+ static CHECKSUM_LENGTH = 2;
38
+ static SYS_ID = 254;
39
+ static COMP_ID = 1;
40
+ /**
41
+ * Deserialize payload into actual data class
42
+ */
43
+ data(payload, clazz) {
44
+ this.log.trace("Deserializing", clazz.MSG_NAME, "with payload of size", payload.length);
45
+ const instance = new clazz();
46
+ for (const field of clazz.FIELDS) {
47
+ const deserialize = serialization_1.DESERIALIZERS[field.type];
48
+ if (!deserialize) {
49
+ throw new Error(`Unknown field type ${field.type}`);
50
+ }
51
+ // @ts-ignore
52
+ instance[field.name] = deserialize(payload, field.offset, field.length);
53
+ }
54
+ return instance;
55
+ }
56
+ }
57
+ exports.MavLinkProtocol = MavLinkProtocol;
58
+ /**
59
+ * MavLink Protocol V1
60
+ */
61
+ class MavLinkProtocolV1 extends MavLinkProtocol {
62
+ sysid;
63
+ compid;
64
+ static NAME = "MAV_V1";
65
+ static START_BYTE = 0xfe;
66
+ static PAYLOAD_OFFSET = 6;
67
+ constructor(sysid = MavLinkProtocol.SYS_ID, compid = MavLinkProtocol.COMP_ID) {
68
+ super();
69
+ this.sysid = sysid;
70
+ this.compid = compid;
71
+ }
72
+ serialize(message, seq) {
73
+ this.log.trace("Serializing message (seq:", seq, ")");
74
+ const definition = (message.constructor);
75
+ const buffer = Buffer.from(new Uint8Array(MavLinkProtocolV1.PAYLOAD_OFFSET +
76
+ definition.PAYLOAD_LENGTH +
77
+ MavLinkProtocol.CHECKSUM_LENGTH));
78
+ // serialize header
79
+ buffer.writeUInt8(MavLinkProtocolV1.START_BYTE, 0);
80
+ buffer.writeUInt8(definition.PAYLOAD_LENGTH, 1);
81
+ buffer.writeUInt8(seq, 2);
82
+ buffer.writeUInt8(this.sysid, 3);
83
+ buffer.writeUInt8(this.compid, 4);
84
+ buffer.writeUInt8(definition.MSG_ID, 5);
85
+ // serialize fields
86
+ definition.FIELDS.forEach((field) => {
87
+ const serialize = serialization_1.SERIALIZERS[field.type];
88
+ if (!serialize)
89
+ throw new Error(`Unknown field type ${field.type}: serializer not found`);
90
+ serialize(
91
+ // @ts-ignore
92
+ message[field.name], buffer, field.offset + MavLinkProtocolV1.PAYLOAD_OFFSET, field.length);
93
+ });
94
+ // serialize checksum
95
+ const crc = (0, mavlink_mappings_1.x25crc)(buffer, 1, 2, definition.MAGIC_NUMBER);
96
+ buffer.writeUInt16LE(crc, buffer.length - 2);
97
+ return buffer;
98
+ }
99
+ header(buffer, timestamp) {
100
+ this.log.trace("Reading header from buffer (len:", buffer.length, ")");
101
+ const startByte = buffer.readUInt8(0);
102
+ if (startByte !== MavLinkProtocolV1.START_BYTE) {
103
+ throw new Error(`Invalid start byte (expected: ${MavLinkProtocolV1.START_BYTE}, got ${startByte})`);
104
+ }
105
+ const result = new MavLinkPacketHeader();
106
+ result.timestamp = timestamp || null;
107
+ result.magic = startByte;
108
+ result.payloadLength = buffer.readUInt8(1);
109
+ result.seq = buffer.readUInt8(2);
110
+ result.sysid = buffer.readUInt8(3);
111
+ result.compid = buffer.readUInt8(4);
112
+ result.msgid = buffer.readUInt8(5);
113
+ return result;
114
+ }
115
+ /**
116
+ * Deserialize packet checksum
117
+ */
118
+ crc(buffer) {
119
+ this.log.trace("Reading crc from buffer (len:", buffer.length, ")");
120
+ const plen = buffer.readUInt8(1);
121
+ return buffer.readUInt16LE(MavLinkProtocolV1.PAYLOAD_OFFSET + plen);
122
+ }
123
+ payload(buffer) {
124
+ this.log.trace("Reading payload from buffer (len:", buffer.length, ")");
125
+ const plen = buffer.readUInt8(1);
126
+ const payload = buffer.slice(MavLinkProtocolV1.PAYLOAD_OFFSET, MavLinkProtocolV1.PAYLOAD_OFFSET + plen);
127
+ const padding = Buffer.from(new Uint8Array(255 - payload.length));
128
+ return Buffer.concat([payload, padding]);
129
+ }
130
+ }
131
+ exports.MavLinkProtocolV1 = MavLinkProtocolV1;
132
+ /**
133
+ * MavLink Protocol V2
134
+ */
135
+ class MavLinkProtocolV2 extends MavLinkProtocol {
136
+ sysid;
137
+ compid;
138
+ incompatibilityFlags;
139
+ compatibilityFlags;
140
+ static NAME = "MAV_V2";
141
+ static START_BYTE = 0xfd;
142
+ static PAYLOAD_OFFSET = 10;
143
+ static INCOMPATIBILITY_FLAGS = 0;
144
+ static COMPATIBILITY_FLAGS = 0;
145
+ static IFLAG_SIGNED = 0x01;
146
+ constructor(sysid = MavLinkProtocol.SYS_ID, compid = MavLinkProtocol.COMP_ID, incompatibilityFlags = MavLinkProtocolV2.INCOMPATIBILITY_FLAGS, compatibilityFlags = MavLinkProtocolV2.COMPATIBILITY_FLAGS) {
147
+ super();
148
+ this.sysid = sysid;
149
+ this.compid = compid;
150
+ this.incompatibilityFlags = incompatibilityFlags;
151
+ this.compatibilityFlags = compatibilityFlags;
152
+ }
153
+ serialize(message, seq) {
154
+ this.log.trace("Serializing message (seq:", seq, ")");
155
+ const definition = (message.constructor);
156
+ const buffer = Buffer.from(new Uint8Array(MavLinkProtocolV2.PAYLOAD_OFFSET +
157
+ definition.PAYLOAD_LENGTH +
158
+ MavLinkProtocol.CHECKSUM_LENGTH));
159
+ buffer.writeUInt8(MavLinkProtocolV2.START_BYTE, 0);
160
+ buffer.writeUInt8(this.incompatibilityFlags, 2);
161
+ buffer.writeUInt8(this.compatibilityFlags, 3);
162
+ buffer.writeUInt8(seq, 4);
163
+ buffer.writeUInt8(this.sysid, 5);
164
+ buffer.writeUInt8(this.compid, 6);
165
+ buffer.writeUIntLE(definition.MSG_ID, 7, 3);
166
+ definition.FIELDS.forEach((field) => {
167
+ const serialize = serialization_1.SERIALIZERS[field.type];
168
+ if (!serialize)
169
+ throw new Error(`Unknown field type ${field.type}: serializer not found`);
170
+ serialize(
171
+ // @ts-ignore
172
+ message[field.name], buffer, field.offset + MavLinkProtocolV2.PAYLOAD_OFFSET, field.length);
173
+ });
174
+ // calculate actual truncated payload length
175
+ const payloadLength = this.calculateTruncatedPayloadLength(buffer);
176
+ buffer.writeUInt8(payloadLength, 1);
177
+ // slice out the message buffer
178
+ const result = buffer.slice(0, MavLinkProtocolV2.PAYLOAD_OFFSET +
179
+ payloadLength +
180
+ MavLinkProtocol.CHECKSUM_LENGTH);
181
+ const crc = (0, mavlink_mappings_1.x25crc)(result, 1, 2, definition.MAGIC_NUMBER);
182
+ result.writeUInt16LE(crc, result.length - MavLinkProtocol.CHECKSUM_LENGTH);
183
+ return result;
184
+ }
185
+ /**
186
+ * Create a signed package buffer
187
+ *
188
+ * @param buffer buffer with the original, unsigned package
189
+ * @param linkId id of the link
190
+ * @param key key to sign the package with
191
+ * @param timestamp optional timestamp for packet signing (default: Date.now())
192
+ * @returns signed package
193
+ */
194
+ sign(buffer, linkId, key, timestamp = Date.now()) {
195
+ this.log.trace("Signing message");
196
+ const result = Buffer.concat([
197
+ buffer,
198
+ Buffer.from(new Uint8Array(MavLinkPacketSignature.SIGNATURE_LENGTH)),
199
+ ]);
200
+ const signer = new MavLinkPacketSignature(result);
201
+ signer.linkId = linkId;
202
+ signer.timestamp = timestamp;
203
+ signer.signature = signer.calculate(key);
204
+ return result;
205
+ }
206
+ calculateTruncatedPayloadLength(buffer) {
207
+ let result = buffer.length;
208
+ for (let i = buffer.length - MavLinkProtocol.CHECKSUM_LENGTH - 1; i >= MavLinkProtocolV2.PAYLOAD_OFFSET; i--) {
209
+ result = i;
210
+ if (buffer[i] !== 0) {
211
+ result++;
212
+ break;
213
+ }
214
+ }
215
+ return result - MavLinkProtocolV2.PAYLOAD_OFFSET;
216
+ }
217
+ header(buffer, timestamp) {
218
+ this.log.trace("Reading header from buffer (len:", buffer.length, ")");
219
+ const startByte = buffer.readUInt8(0);
220
+ if (startByte !== MavLinkProtocolV2.START_BYTE) {
221
+ throw new Error(`Invalid start byte (expected: ${MavLinkProtocolV2.START_BYTE}, got ${startByte})`);
222
+ }
223
+ const result = new MavLinkPacketHeader();
224
+ result.timestamp = timestamp || null;
225
+ result.magic = startByte;
226
+ result.payloadLength = buffer.readUInt8(1);
227
+ result.incompatibilityFlags = buffer.readUInt8(2);
228
+ result.compatibilityFlags = buffer.readUInt8(3);
229
+ result.seq = buffer.readUInt8(4);
230
+ result.sysid = buffer.readUInt8(5);
231
+ result.compid = buffer.readUInt8(6);
232
+ result.msgid = buffer.readUIntLE(7, 3);
233
+ return result;
234
+ }
235
+ /**
236
+ * Deserialize packet checksum
237
+ */
238
+ crc(buffer) {
239
+ this.log.trace("Reading crc from buffer (len:", buffer.length, ")");
240
+ const plen = buffer.readUInt8(1);
241
+ return buffer.readUInt16LE(MavLinkProtocolV2.PAYLOAD_OFFSET + plen);
242
+ }
243
+ payload(buffer) {
244
+ this.log.trace("Reading payload from buffer (len:", buffer.length, ")");
245
+ const plen = buffer.readUInt8(1);
246
+ const payload = buffer.slice(MavLinkProtocolV2.PAYLOAD_OFFSET, MavLinkProtocolV2.PAYLOAD_OFFSET + plen);
247
+ const padding = Buffer.from(new Uint8Array(255 - payload.length));
248
+ return Buffer.concat([payload, padding]);
249
+ }
250
+ signature(buffer, header) {
251
+ this.log.trace("Reading signature from buffer (len:", buffer.length, ")");
252
+ if (header.incompatibilityFlags & MavLinkProtocolV2.IFLAG_SIGNED) {
253
+ return new MavLinkPacketSignature(buffer);
254
+ }
255
+ else {
256
+ return null;
257
+ }
258
+ }
259
+ }
260
+ exports.MavLinkProtocolV2 = MavLinkProtocolV2;
261
+ /**
262
+ * Registry of known protocols by STX
263
+ */
264
+ const KNOWN_PROTOCOLS_BY_STX = {
265
+ [MavLinkProtocolV1.START_BYTE]: MavLinkProtocolV1,
266
+ [MavLinkProtocolV2.START_BYTE]: MavLinkProtocolV2,
267
+ };
268
+ /**
269
+ * MavLink packet signature definition
270
+ */
271
+ class MavLinkPacketSignature {
272
+ buffer;
273
+ static SIGNATURE_LENGTH = 13;
274
+ /**
275
+ * Calculate key based on secret passphrase
276
+ *
277
+ * @param passphrase secret to generate the key
278
+ * @returns key as a buffer
279
+ */
280
+ static key(passphrase) {
281
+ return (0, crypto_1.createHash)("sha256").update(passphrase).digest();
282
+ }
283
+ constructor(buffer) {
284
+ this.buffer = buffer;
285
+ }
286
+ get offset() {
287
+ return this.buffer.length - MavLinkPacketSignature.SIGNATURE_LENGTH;
288
+ }
289
+ /**
290
+ * Get the linkId from signature
291
+ */
292
+ get linkId() {
293
+ return this.buffer.readUInt8(this.offset);
294
+ }
295
+ /**
296
+ * Set the linkId in signature
297
+ */
298
+ set linkId(value) {
299
+ this.buffer.writeUInt8(this.offset);
300
+ }
301
+ /**
302
+ * Get the timestamp from signature
303
+ */
304
+ get timestamp() {
305
+ return this.buffer.readUIntLE(this.offset + 1, 6);
306
+ }
307
+ /**
308
+ * Set the linkId in signature
309
+ */
310
+ set timestamp(value) {
311
+ this.buffer.writeUIntLE(value, this.offset + 1, 6);
312
+ }
313
+ /**
314
+ * Get the signature from signature
315
+ */
316
+ get signature() {
317
+ return this.buffer
318
+ .slice(this.offset + 7, this.offset + 7 + 6)
319
+ .toString("hex");
320
+ }
321
+ /**
322
+ * Set the signature in signature
323
+ */
324
+ set signature(value) {
325
+ this.buffer.write(value, this.offset + 7, "hex");
326
+ }
327
+ /**
328
+ * Calculates signature of the packet buffer using the provided secret.
329
+ * The secret is converted to a hash using the sha256 algorithm which matches
330
+ * the way Mission Planner creates keys.
331
+ *
332
+ * @param key the secret key (Buffer)
333
+ * @returns calculated signature value
334
+ */
335
+ calculate(key) {
336
+ const hash = (0, crypto_1.createHash)("sha256")
337
+ .update(key)
338
+ .update(this.buffer.slice(0, this.buffer.length - 6))
339
+ .digest("hex")
340
+ .substr(0, 12);
341
+ return hash;
342
+ }
343
+ /**
344
+ * Checks the signature of the packet buffer against a given secret
345
+ * The secret is converted to a hash using the sha256 algorithm which matches
346
+ * the way Mission Planner creates keys.
347
+ *
348
+ * @param key key
349
+ * @returns true if the signature matches, false otherwise
350
+ */
351
+ matches(key) {
352
+ return this.calculate(key) === this.signature;
353
+ }
354
+ toString() {
355
+ return `linkid: ${this.linkId}, timestamp ${this.timestamp}, signature ${this.signature}`;
356
+ }
357
+ }
358
+ exports.MavLinkPacketSignature = MavLinkPacketSignature;
359
+ /**
360
+ * MavLink packet definition
361
+ */
362
+ class MavLinkPacket {
363
+ buffer;
364
+ header;
365
+ payload;
366
+ crc;
367
+ protocol;
368
+ signature;
369
+ constructor(buffer, header = new MavLinkPacketHeader(), payload = Buffer.from(new Uint8Array(255)), crc = 0, protocol = new MavLinkProtocolV1(), signature = null) {
370
+ this.buffer = buffer;
371
+ this.header = header;
372
+ this.payload = payload;
373
+ this.crc = crc;
374
+ this.protocol = protocol;
375
+ this.signature = signature;
376
+ }
377
+ /**
378
+ * Debug information about the packet
379
+ *
380
+ * @returns string representing debug information about a packet
381
+ */
382
+ debug() {
383
+ return ("Packet (" +
384
+ // @ts-ignore
385
+ `proto: ${this.protocol.constructor["NAME"]}, ` +
386
+ `sysid: ${this.header.sysid}, ` +
387
+ `compid: ${this.header.compid}, ` +
388
+ `msgid: ${this.header.msgid}, ` +
389
+ `seq: ${this.header.seq}, ` +
390
+ `plen: ${this.header.payloadLength}, ` +
391
+ // @ts-ignore
392
+ `magic: ${mavlink_mappings_2.MSG_ID_MAGIC_NUMBER[this.header.msgid]} (${(0, utils_1.hex)(
393
+ // @ts-ignore
394
+ mavlink_mappings_2.MSG_ID_MAGIC_NUMBER[this.header.msgid])}), ` +
395
+ `crc: ${(0, utils_1.hex)(this.crc, 4)}` +
396
+ // @ts-ignore
397
+ this.signatureToString(this.signature) +
398
+ ")");
399
+ }
400
+ signatureToString(signature) {
401
+ return signature ? `, ${signature.toString()}` : "";
402
+ }
403
+ }
404
+ exports.MavLinkPacket = MavLinkPacket;
405
+ /**
406
+ * This enum describes the different ways validation of a buffer can end
407
+ */
408
+ var PacketValidationResult;
409
+ (function (PacketValidationResult) {
410
+ PacketValidationResult[PacketValidationResult["VALID"] = 0] = "VALID";
411
+ PacketValidationResult[PacketValidationResult["INVALID"] = 1] = "INVALID";
412
+ PacketValidationResult[PacketValidationResult["UNKNOWN"] = 2] = "UNKNOWN";
413
+ })(PacketValidationResult || (PacketValidationResult = {}));
414
+ /**
415
+ * A transform stream that splits the incomming data stream into chunks containing full MavLink messages
416
+ */
417
+ class MavLinkPacketSplitter extends stream_1.Transform {
418
+ log = logger_1.Logger.getLogger(this);
419
+ buffer = Buffer.from([]);
420
+ onCrcError = null;
421
+ timestamp = null;
422
+ _validPackagesCount = 0;
423
+ _unknownPackagesCount = 0;
424
+ _invalidPackagesCount = 0;
425
+ /**
426
+ * @param opts options to pass on to the Transform constructor
427
+ * @param verbose print diagnostic information
428
+ * @param onCrcError callback executed if there is a CRC error (mostly for debugging)
429
+ */
430
+ constructor(opts = {}, onCrcError = () => { }) {
431
+ super({ ...opts, objectMode: true });
432
+ this.onCrcError = onCrcError;
433
+ }
434
+ _transform(chunk, encoding, callback) {
435
+ this.buffer = Buffer.concat([this.buffer, chunk]);
436
+ while (this.buffer.byteLength > 0) {
437
+ const offset = this.findStartOfPacket(this.buffer);
438
+ if (offset === null) {
439
+ // start of the package was not found - need more data
440
+ break;
441
+ }
442
+ // if the current offset is exactly the size of the timestamp field from tlog then read it.
443
+ if (offset >= 8) {
444
+ this.timestamp = this.buffer.readBigUInt64BE(offset - 8) / 1000n;
445
+ }
446
+ else {
447
+ this.timestamp = null;
448
+ }
449
+ // fast-forward the buffer to the first start byte
450
+ if (offset > 0) {
451
+ this.buffer = this.buffer.slice(offset);
452
+ }
453
+ this.log.debug("Found potential packet start at", offset);
454
+ // get protocol this buffer is encoded with
455
+ const Protocol = this.getPacketProtocol(this.buffer);
456
+ this.log.debug("Packet protocol is", Protocol.NAME);
457
+ // check if the buffer contains at least the minumum size of data
458
+ if (this.buffer.length <
459
+ Protocol.PAYLOAD_OFFSET + MavLinkProtocol.CHECKSUM_LENGTH) {
460
+ // current buffer shorter than the shortest message - skipping
461
+ this.log.debug("Current buffer shorter than the shortest message - skipping");
462
+ break;
463
+ }
464
+ // check if the current buffer contains the entire message
465
+ const expectedBufferLength = this.readPacketLength(this.buffer, Protocol);
466
+ this.log.debug("Expected buffer length:", expectedBufferLength, `(${(0, utils_1.hex)(expectedBufferLength)})`);
467
+ if (this.buffer.length < expectedBufferLength) {
468
+ // current buffer is not fully retrieved yet - skipping
469
+ this.log.debug("Current buffer is not fully retrieved yet - skipping");
470
+ break;
471
+ }
472
+ else {
473
+ this.log.debug("Current buffer length:", this.buffer.length, `(${(0, utils_1.hex)(this.buffer.length, 4)})`);
474
+ }
475
+ // retrieve the buffer based on payload size
476
+ const buffer = this.buffer.slice(0, expectedBufferLength);
477
+ this.log.debug("Recognized buffer length:", buffer.length, `(${(0, utils_1.hex)(buffer.length, 2)})`);
478
+ switch (this.validatePacket(buffer, Protocol)) {
479
+ case PacketValidationResult.VALID:
480
+ this.log.debug("Found a valid packet");
481
+ this._validPackagesCount++;
482
+ this.push({ buffer, timestamp: this.timestamp });
483
+ // truncate the buffer to remove the current message
484
+ this.buffer = this.buffer.slice(expectedBufferLength);
485
+ break;
486
+ case PacketValidationResult.INVALID:
487
+ this.log.debug("Found an invalid packet - skipping");
488
+ this._invalidPackagesCount++;
489
+ // truncate the buffer to remove the wrongly identified STX
490
+ this.buffer = this.buffer.slice(1);
491
+ break;
492
+ case PacketValidationResult.UNKNOWN:
493
+ this.log.debug("Found an unknown packet - skipping");
494
+ this._unknownPackagesCount++;
495
+ // truncate the buffer to remove the current message
496
+ this.buffer = this.buffer.slice(expectedBufferLength);
497
+ break;
498
+ }
499
+ }
500
+ callback(null);
501
+ }
502
+ findStartOfPacket(buffer, offset = 0) {
503
+ const stxv1 = buffer.indexOf(MavLinkProtocolV1.START_BYTE, offset);
504
+ const stxv2 = buffer.indexOf(MavLinkProtocolV2.START_BYTE, offset);
505
+ if (stxv1 >= 0 && stxv2 >= 0) {
506
+ // in the current buffer both STX v1 and v2 are found - get the first one
507
+ if (stxv1 < stxv2) {
508
+ return stxv1;
509
+ }
510
+ else {
511
+ return stxv2;
512
+ }
513
+ }
514
+ else if (stxv1 >= 0) {
515
+ // in the current buffer STX v1 is found
516
+ return stxv1;
517
+ }
518
+ else if (stxv2 >= 0) {
519
+ // in the current buffer STX v2 is found
520
+ return stxv2;
521
+ }
522
+ else {
523
+ // no STX found
524
+ return null;
525
+ }
526
+ }
527
+ getPacketProtocol(buffer) {
528
+ return KNOWN_PROTOCOLS_BY_STX[buffer.readUInt8(0)] || null;
529
+ }
530
+ readPacketLength(buffer, Protocol) {
531
+ // check if the current buffer contains the entire message
532
+ const payloadLength = buffer.readUInt8(1);
533
+ return (Protocol.PAYLOAD_OFFSET +
534
+ payloadLength +
535
+ MavLinkProtocol.CHECKSUM_LENGTH +
536
+ (this.isV2Signed(buffer) ? MavLinkPacketSignature.SIGNATURE_LENGTH : 0));
537
+ }
538
+ validatePacket(buffer, Protocol) {
539
+ const protocol = new Protocol();
540
+ const header = protocol.header(buffer);
541
+ // @ts-ignore
542
+ const magic = mavlink_mappings_2.MSG_ID_MAGIC_NUMBER[header.msgid];
543
+ if (magic) {
544
+ const crc = protocol.crc(buffer);
545
+ const trim = this.isV2Signed(buffer)
546
+ ? MavLinkPacketSignature.SIGNATURE_LENGTH +
547
+ MavLinkProtocol.CHECKSUM_LENGTH
548
+ : MavLinkProtocol.CHECKSUM_LENGTH;
549
+ const crc2 = (0, mavlink_mappings_1.x25crc)(buffer, 1, trim, magic);
550
+ if (crc === crc2) {
551
+ // this is a proper message that is known and has been validated for corrupted data
552
+ return PacketValidationResult.VALID;
553
+ }
554
+ else {
555
+ // CRC mismatch
556
+ const message = [
557
+ `CRC error; expected: ${crc2} (${(0, utils_1.hex)(crc2, 4)}), got ${crc} (${(0, utils_1.hex)(crc, 4)});`,
558
+ `msgid: ${header.msgid} (${(0, utils_1.hex)(header.msgid)}),`,
559
+ `seq: ${header.seq} (${(0, utils_1.hex)(header.seq)}),`,
560
+ `plen: ${header.payloadLength} (${(0, utils_1.hex)(header.payloadLength)}),`,
561
+ `magic: ${magic} (${(0, utils_1.hex)(magic)})`,
562
+ ];
563
+ this.log.warn(message.join(" "));
564
+ if (this.onCrcError)
565
+ this.onCrcError(buffer);
566
+ return PacketValidationResult.INVALID;
567
+ }
568
+ }
569
+ else {
570
+ // unknown message (as in not generated from the XML sources)
571
+ this.log.debug(`Unknown message with id ${header.msgid} (magic number not found) - skipping`);
572
+ return PacketValidationResult.UNKNOWN;
573
+ }
574
+ }
575
+ /**
576
+ * Checks if the buffer contains the entire message with signature
577
+ *
578
+ * @param buffer buffer with the message
579
+ */
580
+ isV2Signed(buffer) {
581
+ const protocol = buffer.readUInt8(0);
582
+ if (protocol === MavLinkProtocolV2.START_BYTE) {
583
+ const flags = buffer.readUInt8(2);
584
+ return !!(flags & MavLinkProtocolV2.IFLAG_SIGNED);
585
+ }
586
+ }
587
+ /**
588
+ * Number of invalid packages
589
+ */
590
+ get validPackages() {
591
+ return this._validPackagesCount;
592
+ }
593
+ /**
594
+ * Reset the number of valid packages
595
+ */
596
+ resetValidPackagesCount() {
597
+ this._validPackagesCount = 0;
598
+ }
599
+ /**
600
+ * Number of invalid packages
601
+ */
602
+ get invalidPackages() {
603
+ return this._invalidPackagesCount;
604
+ }
605
+ /**
606
+ * Reset the number of invalid packages
607
+ */
608
+ resetInvalidPackagesCount() {
609
+ this._invalidPackagesCount = 0;
610
+ }
611
+ /**
612
+ * Number of invalid packages
613
+ */
614
+ get unknownPackagesCount() {
615
+ return this._unknownPackagesCount;
616
+ }
617
+ /**
618
+ * Reset the number of invalid packages
619
+ */
620
+ resetUnknownPackagesCount() {
621
+ this._unknownPackagesCount = 0;
622
+ }
623
+ }
624
+ exports.MavLinkPacketSplitter = MavLinkPacketSplitter;
625
+ class MavLinkTLogPacketSplitter extends MavLinkPacketSplitter {
626
+ _transform(chunk, encoding, callback) {
627
+ return super._transform(chunk, encoding, callback);
628
+ }
629
+ findStartOfPacket(buffer, offset = 0) {
630
+ // Finding the start of packet in TLog requires locating the start byte
631
+ // at an offset greater than
632
+ let offset1 = offset;
633
+ while (true) {
634
+ const start = super.findStartOfPacket(buffer, offset1);
635
+ if (start === null)
636
+ return null;
637
+ if (start < 8)
638
+ offset1 = start + 1;
639
+ else if (offset1 >= buffer.length - 1)
640
+ return null;
641
+ else
642
+ return start;
643
+ }
644
+ }
645
+ }
646
+ exports.MavLinkTLogPacketSplitter = MavLinkTLogPacketSplitter;
647
+ /**
648
+ * A transform stream that takes a buffer with data and converts it to MavLinkPacket object
649
+ */
650
+ class MavLinkPacketParser extends stream_1.Transform {
651
+ log = logger_1.Logger.getLogger(this);
652
+ constructor(opts = {}) {
653
+ super({ ...opts, objectMode: true });
654
+ }
655
+ getProtocol(buffer) {
656
+ const startByte = buffer.readUInt8(0);
657
+ switch (startByte) {
658
+ case MavLinkProtocolV1.START_BYTE:
659
+ return new MavLinkProtocolV1();
660
+ case MavLinkProtocolV2.START_BYTE:
661
+ return new MavLinkProtocolV2();
662
+ default:
663
+ throw new Error(`Unknown protocol '${(0, utils_1.hex)(startByte)}'`);
664
+ }
665
+ }
666
+ _transform({ buffer = Buffer.from([]), timestamp = null, ...rest } = {}, encoding, callback) {
667
+ const protocol = this.getProtocol(buffer);
668
+ const header = protocol.header(buffer, timestamp || undefined);
669
+ const payload = protocol.payload(buffer);
670
+ const crc = protocol.crc(buffer);
671
+ const signature = protocol instanceof MavLinkProtocolV2
672
+ ? protocol.signature(buffer, header)
673
+ : null;
674
+ const packet = new MavLinkPacket(buffer, header, payload, crc, protocol, signature);
675
+ callback(null, packet);
676
+ }
677
+ }
678
+ exports.MavLinkPacketParser = MavLinkPacketParser;
679
+ /**
680
+ * Creates a MavLink packet stream reader that is reading packets from the given input
681
+ *
682
+ * @param input input stream to read from
683
+ */
684
+ function createMavLinkStream(input, onCrcError) {
685
+ return input
686
+ .pipe(new MavLinkPacketSplitter({}, onCrcError))
687
+ .pipe(new MavLinkPacketParser());
688
+ }
689
+ exports.createMavLinkStream = createMavLinkStream;
690
+ let seq = 0;
691
+ /**
692
+ * Send a packet to the stream
693
+ *
694
+ * @param stream Stream to send the data to
695
+ * @param msg message to serialize and send
696
+ * @param protocol protocol to use (default: MavLinkProtocolV1)
697
+ * @returns number of bytes sent
698
+ */
699
+ async function send(stream, msg, protocol = new MavLinkProtocolV1()) {
700
+ return new Promise((resolve, reject) => {
701
+ const buffer = protocol.serialize(msg, seq++);
702
+ seq &= 255;
703
+ stream.write(buffer, (err) => {
704
+ if (err)
705
+ reject(err);
706
+ else
707
+ resolve(buffer.length);
708
+ });
709
+ });
710
+ }
711
+ exports.send = send;
712
+ /**
713
+ * Send a signed packet to the stream. Signed packets are always V2 protocol
714
+ *
715
+ * @param stream Stream to send the data to
716
+ * @param msg message to serialize and send
717
+ * @param key key to sign the message with
718
+ * @param linkId link id for the signature
719
+ * @param sysid system id
720
+ * @param compid component id
721
+ * @param timestamp optional timestamp for packet signing (default: Date.now())
722
+ * @returns number of bytes sent
723
+ */
724
+ async function sendSigned(stream, msg, key, linkId = 1, sysid = MavLinkProtocol.SYS_ID, compid = MavLinkProtocol.COMP_ID, timestamp = Date.now()) {
725
+ return new Promise((resolve, reject) => {
726
+ const protocol = new MavLinkProtocolV2(sysid, compid, MavLinkProtocolV2.IFLAG_SIGNED);
727
+ const b1 = protocol.serialize(msg, seq++);
728
+ seq &= 255;
729
+ const b2 = protocol.sign(b1, linkId, key, timestamp);
730
+ stream.write(b2, (err) => {
731
+ if (err)
732
+ reject(err);
733
+ else
734
+ resolve(b2.length);
735
+ });
736
+ });
737
+ }
738
+ exports.sendSigned = sendSigned;