node-mavlink 1.2.1 → 1.3.2

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