UNPKG

77.9 kBJavaScriptView Raw
1const util = require('util')
2
3const test = require('tape')
4const mqtt = require('./')
5const WS = require('readable-stream').Writable
6
7function normalExpectedObject (object) {
8 if (object.username != null) object.username = object.username.toString()
9 if (object.password != null) object.password = Buffer.from(object.password)
10 return object
11}
12
13function testParseGenerate (name, object, buffer, opts) {
14 test(`${name} parse`, t => {
15 t.plan(2)
16
17 const parser = mqtt.parser(opts)
18 const expected = object
19 const fixture = buffer
20
21 parser.on('packet', packet => {
22 if (packet.cmd !== 'publish') {
23 delete packet.topic
24 delete packet.payload
25 }
26 t.deepLooseEqual(packet, normalExpectedObject(expected), 'expected packet')
27 })
28
29 parser.on('error', err => {
30 t.fail(err)
31 })
32
33 t.equal(parser.parse(fixture), 0, 'remaining bytes')
34 })
35
36 test(`${name} generate`, t => {
37 // For really large buffers, the expanded hex string can be so long as to
38 // generate an error in nodejs 14.x, so only do the test with extra output
39 // for relatively small buffers.
40 const bigLength = 10000
41 const generatedBuffer = mqtt.generate(object, opts)
42 if (generatedBuffer.length < bigLength && buffer.length < bigLength) {
43 t.equal(generatedBuffer.toString('hex'), buffer.toString('hex'))
44 } else {
45 const bufferOkay = generatedBuffer.equals(buffer)
46 if (bufferOkay) {
47 t.pass()
48 } else {
49 // Output abbreviated representations of the buffers.
50 t.comment('Expected:\n' + util.inspect(buffer))
51 t.comment('Got:\n' + util.inspect(generatedBuffer))
52 t.fail('Large buffers not equal')
53 }
54 }
55 t.end()
56 })
57
58 test(`${name} mirror`, t => {
59 t.plan(2)
60
61 const parser = mqtt.parser(opts)
62 const expected = object
63 const fixture = mqtt.generate(object, opts)
64
65 parser.on('packet', packet => {
66 if (packet.cmd !== 'publish') {
67 delete packet.topic
68 delete packet.payload
69 }
70 t.deepLooseEqual(packet, normalExpectedObject(expected), 'expected packet')
71 })
72
73 parser.on('error', err => {
74 t.fail(err)
75 })
76
77 t.equal(parser.parse(fixture), 0, 'remaining bytes')
78 })
79
80 test(`${name} writeToStream`, t => {
81 const stream = WS()
82 stream.write = () => true
83 stream.on('error', (err) => t.fail(err))
84
85 const result = mqtt.writeToStream(object, stream, opts)
86 t.equal(result, true, 'should return true')
87 t.end()
88 })
89}
90
91// the API allows to pass strings as buffers to writeToStream and generate
92// parsing them back will result in a string so only generate and compare to buffer
93function testGenerateOnly (name, object, buffer, opts) {
94 test(name, t => {
95 t.equal(mqtt.generate(object, opts).toString('hex'), buffer.toString('hex'))
96 t.end()
97 })
98}
99
100function testParseOnly (name, object, buffer, opts) {
101 test(name, t => {
102 const parser = mqtt.parser(opts)
103 // const expected = object
104 // const fixture = buffer
105
106 t.plan(2 + Object.keys(object).length)
107
108 parser.on('packet', packet => {
109 t.equal(Object.keys(object).length, Object.keys(packet).length, 'key count')
110 Object.keys(object).forEach(key => {
111 t.deepEqual(packet[key], object[key], `expected packet property ${key}`)
112 })
113 })
114
115 t.equal(parser.parse(buffer), 0, 'remaining bytes')
116 t.end()
117 })
118}
119
120function testParseError (expected, fixture, opts) {
121 test(expected, t => {
122 t.plan(1)
123
124 const parser = mqtt.parser(opts)
125
126 parser.on('error', err => {
127 t.equal(err.message, expected, 'expected error message')
128 })
129
130 parser.on('packet', () => {
131 t.fail('parse errors should not be followed by packet events')
132 })
133
134 parser.parse(fixture)
135 t.end()
136 })
137}
138
139function testGenerateError (expected, fixture, opts, name) {
140 test(name || expected, t => {
141 t.plan(1)
142
143 try {
144 mqtt.generate(fixture, opts)
145 } catch (err) {
146 t.equal(expected, err.message)
147 }
148 t.end()
149 })
150}
151
152function testGenerateErrorMultipleCmds (cmds, expected, fixture, opts) {
153 cmds.forEach(cmd => {
154 const obj = Object.assign({}, fixture)
155 obj.cmd = cmd
156 testGenerateError(expected, obj, opts, `${expected} on ${cmd}`)
157 }
158 )
159}
160
161function testParseGenerateDefaults (name, object, buffer, generated, opts) {
162 testParseOnly(`${name} parse`, generated, buffer, opts)
163 testGenerateOnly(`${name} generate`, object, buffer, opts)
164}
165
166function testParseAndGenerate (name, object, buffer, opts) {
167 testParseOnly(`${name} parse`, object, buffer, opts)
168 testGenerateOnly(`${name} generate`, object, buffer, opts)
169}
170
171function testWriteToStreamError (expected, fixture) {
172 test(`writeToStream ${expected} error`, t => {
173 t.plan(2)
174
175 const stream = WS()
176
177 stream.write = () => t.fail('should not have called write')
178 stream.on('error', () => t.pass('error emitted'))
179
180 const result = mqtt.writeToStream(fixture, stream)
181
182 t.false(result, 'result should be false')
183 t.end()
184 })
185}
186
187test('cacheNumbers get/set/unset', t => {
188 t.true(mqtt.writeToStream.cacheNumbers, 'initial state of cacheNumbers is enabled')
189 mqtt.writeToStream.cacheNumbers = false
190 t.false(mqtt.writeToStream.cacheNumbers, 'cacheNumbers can be disabled')
191 mqtt.writeToStream.cacheNumbers = true
192 t.true(mqtt.writeToStream.cacheNumbers, 'cacheNumbers can be enabled')
193 t.end()
194})
195
196test('disabled numbers cache', t => {
197 const stream = WS()
198 const message = {
199 cmd: 'publish',
200 retain: false,
201 qos: 0,
202 dup: false,
203 length: 10,
204 topic: Buffer.from('test'),
205 payload: Buffer.from('test')
206 }
207 const expected = Buffer.from([
208 48, 10, // Header
209 0, 4, // Topic length
210 116, 101, 115, 116, // Topic (test)
211 116, 101, 115, 116 // Payload (test)
212 ])
213 let written = Buffer.alloc(0)
214
215 stream.write = (chunk) => {
216 written = Buffer.concat([written, chunk])
217 }
218 mqtt.writeToStream.cacheNumbers = false
219
220 mqtt.writeToStream(message, stream)
221
222 t.deepEqual(written, expected, 'written buffer is expected')
223
224 mqtt.writeToStream.cacheNumbers = true
225
226 stream.end()
227 t.end()
228})
229
230testGenerateError('Unknown command', {})
231
232testParseError('Not supported', Buffer.from([0, 1, 0]), {})
233
234// Length header field
235testParseError('Invalid variable byte integer', Buffer.from(
236 [16, 255, 255, 255, 255]
237), {})
238testParseError('Invalid variable byte integer', Buffer.from(
239 [16, 255, 255, 255, 128]
240), {})
241testParseError('Invalid variable byte integer', Buffer.from(
242 [16, 255, 255, 255, 255, 1]
243), {})
244testParseError('Invalid variable byte integer', Buffer.from(
245 [16, 255, 255, 255, 255, 127]
246), {})
247testParseError('Invalid variable byte integer', Buffer.from(
248 [16, 255, 255, 255, 255, 128]
249), {})
250testParseError('Invalid variable byte integer', Buffer.from(
251 [16, 255, 255, 255, 255, 255, 1]
252), {})
253
254testParseGenerate('minimal connect', {
255 cmd: 'connect',
256 retain: false,
257 qos: 0,
258 dup: false,
259 length: 18,
260 protocolId: 'MQIsdp',
261 protocolVersion: 3,
262 clean: false,
263 keepalive: 30,
264 clientId: 'test'
265}, Buffer.from([
266 16, 18, // Header
267 0, 6, // Protocol ID length
268 77, 81, 73, 115, 100, 112, // Protocol ID
269 3, // Protocol version
270 0, // Connect flags
271 0, 30, // Keepalive
272 0, 4, // Client ID length
273 116, 101, 115, 116 // Client ID
274]))
275
276testGenerateOnly('minimal connect with clientId as Buffer', {
277 cmd: 'connect',
278 retain: false,
279 qos: 0,
280 dup: false,
281 length: 18,
282 protocolId: 'MQIsdp',
283 protocolVersion: 3,
284 clean: false,
285 keepalive: 30,
286 clientId: Buffer.from('test')
287}, Buffer.from([
288 16, 18, // Header
289 0, 6, // Protocol ID length
290 77, 81, 73, 115, 100, 112, // Protocol ID
291 3, // Protocol version
292 0, // Connect flags
293 0, 30, // Keepalive
294 0, 4, // Client ID length
295 116, 101, 115, 116 // Client ID
296]))
297
298testParseGenerate('connect MQTT bridge 131', {
299 cmd: 'connect',
300 retain: false,
301 qos: 0,
302 dup: false,
303 length: 18,
304 protocolId: 'MQIsdp',
305 protocolVersion: 3,
306 bridgeMode: true,
307 clean: false,
308 keepalive: 30,
309 clientId: 'test'
310}, Buffer.from([
311 16, 18, // Header
312 0, 6, // Protocol ID length
313 77, 81, 73, 115, 100, 112, // Protocol ID
314 131, // Protocol version
315 0, // Connect flags
316 0, 30, // Keepalive
317 0, 4, // Client ID length
318 116, 101, 115, 116 // Client ID
319]))
320
321testParseGenerate('connect MQTT bridge 132', {
322 cmd: 'connect',
323 retain: false,
324 qos: 0,
325 dup: false,
326 length: 18,
327 protocolId: 'MQIsdp',
328 protocolVersion: 4,
329 bridgeMode: true,
330 clean: false,
331 keepalive: 30,
332 clientId: 'test'
333}, Buffer.from([
334 16, 18, // Header
335 0, 6, // Protocol ID length
336 77, 81, 73, 115, 100, 112, // Protocol ID
337 132, // Protocol version
338 0, // Connect flags
339 0, 30, // Keepalive
340 0, 4, // Client ID length
341 116, 101, 115, 116 // Client ID
342]))
343
344testParseGenerate('connect MQTT 5', {
345 cmd: 'connect',
346 retain: false,
347 qos: 0,
348 dup: false,
349 length: 125,
350 protocolId: 'MQTT',
351 protocolVersion: 5,
352 will: {
353 retain: true,
354 qos: 2,
355 properties: {
356 willDelayInterval: 1234,
357 payloadFormatIndicator: false,
358 messageExpiryInterval: 4321,
359 contentType: 'test',
360 responseTopic: 'topic',
361 correlationData: Buffer.from([1, 2, 3, 4]),
362 userProperties: {
363 test: 'test'
364 }
365 },
366 topic: 'topic',
367 payload: Buffer.from([4, 3, 2, 1])
368 },
369 clean: true,
370 keepalive: 30,
371 properties: {
372 sessionExpiryInterval: 1234,
373 receiveMaximum: 432,
374 maximumPacketSize: 100,
375 topicAliasMaximum: 456,
376 requestResponseInformation: true,
377 requestProblemInformation: true,
378 userProperties: {
379 test: 'test'
380 },
381 authenticationMethod: 'test',
382 authenticationData: Buffer.from([1, 2, 3, 4])
383 },
384 clientId: 'test'
385}, Buffer.from([
386 16, 125, // Header
387 0, 4, // Protocol ID length
388 77, 81, 84, 84, // Protocol ID
389 5, // Protocol version
390 54, // Connect flags
391 0, 30, // Keepalive
392 47, // properties length
393 17, 0, 0, 4, 210, // sessionExpiryInterval
394 33, 1, 176, // receiveMaximum
395 39, 0, 0, 0, 100, // maximumPacketSize
396 34, 1, 200, // topicAliasMaximum
397 25, 1, // requestResponseInformation
398 23, 1, // requestProblemInformation,
399 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties,
400 21, 0, 4, 116, 101, 115, 116, // authenticationMethod
401 22, 0, 4, 1, 2, 3, 4, // authenticationData
402 0, 4, // Client ID length
403 116, 101, 115, 116, // Client ID
404 47, // will properties
405 24, 0, 0, 4, 210, // will delay interval
406 1, 0, // payload format indicator
407 2, 0, 0, 16, 225, // message expiry interval
408 3, 0, 4, 116, 101, 115, 116, // content type
409 8, 0, 5, 116, 111, 112, 105, 99, // response topic
410 9, 0, 4, 1, 2, 3, 4, // corelation data
411 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // user properties
412 0, 5, // Will topic length
413 116, 111, 112, 105, 99, // Will topic
414 0, 4, // Will payload length
415 4, 3, 2, 1// Will payload
416]))
417
418testParseGenerate('connect MQTT 5 with will properties but with empty will payload', {
419 cmd: 'connect',
420 retain: false,
421 qos: 0,
422 dup: false,
423 length: 121,
424 protocolId: 'MQTT',
425 protocolVersion: 5,
426 will: {
427 retain: true,
428 qos: 2,
429 properties: {
430 willDelayInterval: 1234,
431 payloadFormatIndicator: false,
432 messageExpiryInterval: 4321,
433 contentType: 'test',
434 responseTopic: 'topic',
435 correlationData: Buffer.from([1, 2, 3, 4]),
436 userProperties: {
437 test: 'test'
438 }
439 },
440 topic: 'topic',
441 payload: Buffer.from([])
442 },
443 clean: true,
444 keepalive: 30,
445 properties: {
446 sessionExpiryInterval: 1234,
447 receiveMaximum: 432,
448 maximumPacketSize: 100,
449 topicAliasMaximum: 456,
450 requestResponseInformation: true,
451 requestProblemInformation: true,
452 userProperties: {
453 test: 'test'
454 },
455 authenticationMethod: 'test',
456 authenticationData: Buffer.from([1, 2, 3, 4])
457 },
458 clientId: 'test'
459}, Buffer.from([
460 16, 121, // Header
461 0, 4, // Protocol ID length
462 77, 81, 84, 84, // Protocol ID
463 5, // Protocol version
464 54, // Connect flags
465 0, 30, // Keepalive
466 47, // properties length
467 17, 0, 0, 4, 210, // sessionExpiryInterval
468 33, 1, 176, // receiveMaximum
469 39, 0, 0, 0, 100, // maximumPacketSize
470 34, 1, 200, // topicAliasMaximum
471 25, 1, // requestResponseInformation
472 23, 1, // requestProblemInformation,
473 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties,
474 21, 0, 4, 116, 101, 115, 116, // authenticationMethod
475 22, 0, 4, 1, 2, 3, 4, // authenticationData
476 0, 4, // Client ID length
477 116, 101, 115, 116, // Client ID
478 47, // will properties
479 24, 0, 0, 4, 210, // will delay interval
480 1, 0, // payload format indicator
481 2, 0, 0, 16, 225, // message expiry interval
482 3, 0, 4, 116, 101, 115, 116, // content type
483 8, 0, 5, 116, 111, 112, 105, 99, // response topic
484 9, 0, 4, 1, 2, 3, 4, // corelation data
485 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // user properties
486 0, 5, // Will topic length
487 116, 111, 112, 105, 99, // Will topic
488 0, 0 // Will payload length
489]))
490
491testParseGenerate('connect MQTT 5 w/o will properties', {
492 cmd: 'connect',
493 retain: false,
494 qos: 0,
495 dup: false,
496 length: 78,
497 protocolId: 'MQTT',
498 protocolVersion: 5,
499 will: {
500 retain: true,
501 qos: 2,
502 topic: 'topic',
503 payload: Buffer.from([4, 3, 2, 1])
504 },
505 clean: true,
506 keepalive: 30,
507 properties: {
508 sessionExpiryInterval: 1234,
509 receiveMaximum: 432,
510 maximumPacketSize: 100,
511 topicAliasMaximum: 456,
512 requestResponseInformation: true,
513 requestProblemInformation: true,
514 userProperties: {
515 test: 'test'
516 },
517 authenticationMethod: 'test',
518 authenticationData: Buffer.from([1, 2, 3, 4])
519 },
520 clientId: 'test'
521}, Buffer.from([
522 16, 78, // Header
523 0, 4, // Protocol ID length
524 77, 81, 84, 84, // Protocol ID
525 5, // Protocol version
526 54, // Connect flags
527 0, 30, // Keepalive
528 47, // properties length
529 17, 0, 0, 4, 210, // sessionExpiryInterval
530 33, 1, 176, // receiveMaximum
531 39, 0, 0, 0, 100, // maximumPacketSize
532 34, 1, 200, // topicAliasMaximum
533 25, 1, // requestResponseInformation
534 23, 1, // requestProblemInformation,
535 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties,
536 21, 0, 4, 116, 101, 115, 116, // authenticationMethod
537 22, 0, 4, 1, 2, 3, 4, // authenticationData
538 0, 4, // Client ID length
539 116, 101, 115, 116, // Client ID
540 0, // will properties
541 0, 5, // Will topic length
542 116, 111, 112, 105, 99, // Will topic
543 0, 4, // Will payload length
544 4, 3, 2, 1// Will payload
545]))
546
547testParseGenerate('no clientId with 3.1.1', {
548 cmd: 'connect',
549 retain: false,
550 qos: 0,
551 dup: false,
552 length: 12,
553 protocolId: 'MQTT',
554 protocolVersion: 4,
555 clean: true,
556 keepalive: 30,
557 clientId: ''
558}, Buffer.from([
559 16, 12, // Header
560 0, 4, // Protocol ID length
561 77, 81, 84, 84, // Protocol ID
562 4, // Protocol version
563 2, // Connect flags
564 0, 30, // Keepalive
565 0, 0 // Client ID length
566]))
567
568testParseGenerateDefaults('no clientId with 5.0', {
569 cmd: 'connect',
570 protocolId: 'MQTT',
571 protocolVersion: 5,
572 clean: true,
573 keepalive: 60,
574 properties:
575 {
576 receiveMaximum: 20
577 },
578 clientId: ''
579}, Buffer.from(
580 [16, 16, 0, 4, 77, 81, 84, 84, 5, 2, 0, 60, 3, 33, 0, 20, 0, 0]
581), {
582 cmd: 'connect',
583 retain: false,
584 qos: 0,
585 dup: false,
586 length: 16,
587 topic: null,
588 payload: null,
589 protocolId: 'MQTT',
590 protocolVersion: 5,
591 clean: true,
592 keepalive: 60,
593 properties: {
594 receiveMaximum: 20
595 },
596 clientId: ''
597}, { protocolVersion: 5 })
598
599testParseGenerateDefaults('utf-8 clientId with 5.0', {
600 cmd: 'connect',
601 retain: false,
602 qos: 0,
603 dup: false,
604 length: 23,
605 protocolId: 'MQTT',
606 protocolVersion: 4,
607 clean: true,
608 keepalive: 30,
609 clientId: 'Ŧėśt🜄'
610}, Buffer.from([
611 16, 23, // Header
612 0, 4, // Protocol ID length
613 77, 81, 84, 84, // Protocol ID
614 4, // Protocol version
615 2, // Connect flags
616 0, 30, // Keepalive
617 0, 11, // Client ID length
618 197, 166, // Ŧ (UTF-8: 0xc5a6)
619 196, 151, // ė (UTF-8: 0xc497)
620 197, 155, // ś (utf-8: 0xc59b)
621 116, // t (utf-8: 0x74)
622 240, 159, 156, 132 // 🜄 (utf-8: 0xf09f9c84)
623]), {
624 cmd: 'connect',
625 retain: false,
626 qos: 0,
627 dup: false,
628 length: 23,
629 topic: null,
630 payload: null,
631 protocolId: 'MQTT',
632 protocolVersion: 4,
633 clean: true,
634 keepalive: 30,
635 clientId: 'Ŧėśt🜄'
636}, { protocol: 5 })
637
638testParseGenerateDefaults('default connect', {
639 cmd: 'connect',
640 clientId: 'test'
641}, Buffer.from([
642 16, 16, 0, 4, 77, 81, 84,
643 84, 4, 2, 0, 0,
644 0, 4, 116, 101, 115, 116
645]), {
646 cmd: 'connect',
647 retain: false,
648 qos: 0,
649 dup: false,
650 length: 16,
651 topic: null,
652 payload: null,
653 protocolId: 'MQTT',
654 protocolVersion: 4,
655 clean: true,
656 keepalive: 0,
657 clientId: 'test'
658})
659
660testParseAndGenerate('Version 4 CONACK', {
661 cmd: 'connack',
662 retain: false,
663 qos: 0,
664 dup: false,
665 length: 2,
666 topic: null,
667 payload: null,
668 sessionPresent: false,
669 returnCode: 1
670}, Buffer.from([
671 32, 2, // Fixed Header (CONNACK, Remaining Length)
672 0, 1 // Variable Header (Session not present, Connection Refused - unacceptable protocol version)
673]), {}) // Default protocolVersion (4)
674
675testParseAndGenerate('Version 5 CONACK', {
676 cmd: 'connack',
677 retain: false,
678 qos: 0,
679 dup: false,
680 length: 3,
681 topic: null,
682 payload: null,
683 sessionPresent: false,
684 reasonCode: 140
685}, Buffer.from([
686 32, 3, // Fixed Header (CONNACK, Remaining Length)
687 0, 140, // Variable Header (Session not present, Bad authentication method)
688 0 // Property Length Zero
689]), { protocolVersion: 5 })
690
691testParseOnly('Version 4 CONACK in Version 5 mode', {
692 cmd: 'connack',
693 retain: false,
694 qos: 0,
695 dup: false,
696 length: 2,
697 topic: null,
698 payload: null,
699 sessionPresent: false,
700 reasonCode: 1 // a version 4 return code stored in the version 5 reasonCode because this client is in version 5
701}, Buffer.from([
702 32, 2, // Fixed Header (CONNACK, Remaining Length)
703 0, 1 // Variable Header (Session not present, Connection Refused - unacceptable protocol version)
704]), { protocolVersion: 5 }) // message is in version 4 format, but this client is in version 5 mode
705
706testParseOnly('Version 5 PUBACK test 1', {
707 cmd: 'puback',
708 messageId: 42,
709 retain: false,
710 qos: 0,
711 dup: false,
712 length: 2,
713 topic: null,
714 payload: null,
715 reasonCode: 0
716}, Buffer.from([
717 64, 2, // Fixed Header (PUBACK, Remaining Length)
718 0, 42 // Variable Header (2 Bytes: Packet Identifier 42, Implied Reason code: Success, Implied no properties)
719]), { protocolVersion: 5 }
720)
721
722testParseAndGenerate('Version 5 PUBACK test 2', {
723 cmd: 'puback',
724 messageId: 42,
725 retain: false,
726 qos: 0,
727 dup: false,
728 length: 3,
729 topic: null,
730 payload: null,
731 reasonCode: 0
732}, Buffer.from([
733 64, 3, // Fixed Header (PUBACK, Remaining Length)
734 0, 42, 0 // Variable Header (2 Bytes: Packet Identifier 42, Reason code: 0 Success, Implied no properties)
735]), { protocolVersion: 5 }
736)
737
738testParseOnly('Version 5 PUBACK test 3', {
739 cmd: 'puback',
740 messageId: 42,
741 retain: false,
742 qos: 0,
743 dup: false,
744 length: 4,
745 topic: null,
746 payload: null,
747 reasonCode: 0
748}, Buffer.from([
749 64, 4, // Fixed Header (PUBACK, Remaining Length)
750 0, 42, 0, // Variable Header (2 Bytes: Packet Identifier 42, Reason code: 0 Success)
751 0 // no properties
752]), { protocolVersion: 5 }
753)
754
755testParseOnly('Version 5 CONNACK test 1', {
756 cmd: 'connack',
757 retain: false,
758 qos: 0,
759 dup: false,
760 length: 1,
761 topic: null,
762 payload: null,
763 sessionPresent: true,
764 reasonCode: 0
765}, Buffer.from([
766 32, 1, // Fixed Header (CONNACK, Remaining Length)
767 1 // Variable Header (Session Present: 1 => true, Implied Reason code: Success, Implied no properties)
768]), { protocolVersion: 5 }
769)
770
771testParseOnly('Version 5 CONNACK test 2', {
772 cmd: 'connack',
773 retain: false,
774 qos: 0,
775 dup: false,
776 length: 2,
777 topic: null,
778 payload: null,
779 sessionPresent: true,
780 reasonCode: 0
781}, Buffer.from([
782 32, 2, // Fixed Header (CONNACK, Remaining Length)
783 1, 0 // Variable Header (Session Present: 1 => true, Connect Reason code: Success, Implied no properties)
784]), { protocolVersion: 5 }
785)
786
787testParseAndGenerate('Version 5 CONNACK test 3', {
788 cmd: 'connack',
789 retain: false,
790 qos: 0,
791 dup: false,
792 length: 3,
793 topic: null,
794 payload: null,
795 sessionPresent: true,
796 reasonCode: 0
797}, Buffer.from([
798 32, 3, // Fixed Header (CONNACK, Remaining Length)
799 1, 0, // Variable Header (Session Present: 1 => true, Connect Reason code: Success)
800 0 // no properties
801]), { protocolVersion: 5 }
802)
803
804testParseOnly('Version 5 DISCONNECT test 1', {
805 cmd: 'disconnect',
806 retain: false,
807 qos: 0,
808 dup: false,
809 length: 0,
810 topic: null,
811 payload: null,
812 reasonCode: 0
813}, Buffer.from([
814 224, 0 // Fixed Header (DISCONNECT, Remaining Length), Implied Reason code: Normal Disconnection
815]), { protocolVersion: 5 }
816)
817
818testParseOnly('Version 5 DISCONNECT test 2', {
819 cmd: 'disconnect',
820 retain: false,
821 qos: 0,
822 dup: false,
823 length: 1,
824 topic: null,
825 payload: null,
826 reasonCode: 0
827}, Buffer.from([
828 224, 1, // Fixed Header (DISCONNECT, Remaining Length)
829 0 // reason Code (Normal disconnection)
830]), { protocolVersion: 5 }
831)
832
833testParseAndGenerate('Version 5 DISCONNECT test 3', {
834 cmd: 'disconnect',
835 retain: false,
836 qos: 0,
837 dup: false,
838 length: 2,
839 topic: null,
840 payload: null,
841 reasonCode: 0
842}, Buffer.from([
843 224, 2, // Fixed Header (DISCONNECT, Remaining Length)
844 0, // reason Code (Normal disconnection)
845 0 // no properties
846]), { protocolVersion: 5 }
847)
848
849testParseGenerate('empty will payload', {
850 cmd: 'connect',
851 retain: false,
852 qos: 0,
853 dup: false,
854 length: 47,
855 protocolId: 'MQIsdp',
856 protocolVersion: 3,
857 will: {
858 retain: true,
859 qos: 2,
860 topic: 'topic',
861 payload: Buffer.alloc(0)
862 },
863 clean: true,
864 keepalive: 30,
865 clientId: 'test',
866 username: 'username',
867 password: Buffer.from('password')
868}, Buffer.from([
869 16, 47, // Header
870 0, 6, // Protocol ID length
871 77, 81, 73, 115, 100, 112, // Protocol ID
872 3, // Protocol version
873 246, // Connect flags
874 0, 30, // Keepalive
875 0, 4, // Client ID length
876 116, 101, 115, 116, // Client ID
877 0, 5, // Will topic length
878 116, 111, 112, 105, 99, // Will topic
879 0, 0, // Will payload length
880 // Will payload
881 0, 8, // Username length
882 117, 115, 101, 114, 110, 97, 109, 101, // Username
883 0, 8, // Password length
884 112, 97, 115, 115, 119, 111, 114, 100 // Password
885]))
886
887testParseGenerate('empty buffer username payload', {
888 cmd: 'connect',
889 retain: false,
890 qos: 0,
891 dup: false,
892 length: 20,
893 protocolId: 'MQIsdp',
894 protocolVersion: 3,
895 clean: true,
896 keepalive: 30,
897 clientId: 'test',
898 username: Buffer.from('')
899}, Buffer.from([
900 16, 20, // Header
901 0, 6, // Protocol ID length
902 77, 81, 73, 115, 100, 112, // Protocol ID
903 3, // Protocol version
904 130, // Connect flags
905 0, 30, // Keepalive
906 0, 4, // Client ID length
907 116, 101, 115, 116, // Client ID
908 0, 0 // Username length
909 // Empty Username payload
910]))
911
912testParseGenerate('empty string username payload', {
913 cmd: 'connect',
914 retain: false,
915 qos: 0,
916 dup: false,
917 length: 20,
918 protocolId: 'MQIsdp',
919 protocolVersion: 3,
920 clean: true,
921 keepalive: 30,
922 clientId: 'test',
923 username: ''
924}, Buffer.from([
925 16, 20, // Header
926 0, 6, // Protocol ID length
927 77, 81, 73, 115, 100, 112, // Protocol ID
928 3, // Protocol version
929 130, // Connect flags
930 0, 30, // Keepalive
931 0, 4, // Client ID length
932 116, 101, 115, 116, // Client ID
933 0, 0 // Username length
934 // Empty Username payload
935]))
936
937testParseGenerate('empty buffer password payload', {
938 cmd: 'connect',
939 retain: false,
940 qos: 0,
941 dup: false,
942 length: 30,
943 protocolId: 'MQIsdp',
944 protocolVersion: 3,
945 clean: true,
946 keepalive: 30,
947 clientId: 'test',
948 username: 'username',
949 password: Buffer.from('')
950}, Buffer.from([
951 16, 30, // Header
952 0, 6, // Protocol ID length
953 77, 81, 73, 115, 100, 112, // Protocol ID
954 3, // Protocol version
955 194, // Connect flags
956 0, 30, // Keepalive
957 0, 4, // Client ID length
958 116, 101, 115, 116, // Client ID
959 0, 8, // Username length
960 117, 115, 101, 114, 110, 97, 109, 101, // Username payload
961 0, 0 // Password length
962 // Empty password payload
963]))
964
965testParseGenerate('empty string password payload', {
966 cmd: 'connect',
967 retain: false,
968 qos: 0,
969 dup: false,
970 length: 30,
971 protocolId: 'MQIsdp',
972 protocolVersion: 3,
973 clean: true,
974 keepalive: 30,
975 clientId: 'test',
976 username: 'username',
977 password: ''
978}, Buffer.from([
979 16, 30, // Header
980 0, 6, // Protocol ID length
981 77, 81, 73, 115, 100, 112, // Protocol ID
982 3, // Protocol version
983 194, // Connect flags
984 0, 30, // Keepalive
985 0, 4, // Client ID length
986 116, 101, 115, 116, // Client ID
987 0, 8, // Username length
988 117, 115, 101, 114, 110, 97, 109, 101, // Username payload
989 0, 0 // Password length
990 // Empty password payload
991]))
992
993testParseGenerate('empty string username and password payload', {
994 cmd: 'connect',
995 retain: false,
996 qos: 0,
997 dup: false,
998 length: 22,
999 protocolId: 'MQIsdp',
1000 protocolVersion: 3,
1001 clean: true,
1002 keepalive: 30,
1003 clientId: 'test',
1004 username: '',
1005 password: Buffer.from('')
1006}, Buffer.from([
1007 16, 22, // Header
1008 0, 6, // Protocol ID length
1009 77, 81, 73, 115, 100, 112, // Protocol ID
1010 3, // Protocol version
1011 194, // Connect flags
1012 0, 30, // Keepalive
1013 0, 4, // Client ID length
1014 116, 101, 115, 116, // Client ID
1015 0, 0, // Username length
1016 // Empty Username payload
1017 0, 0 // Password length
1018 // Empty password payload
1019]))
1020
1021testParseGenerate('maximal connect', {
1022 cmd: 'connect',
1023 retain: false,
1024 qos: 0,
1025 dup: false,
1026 length: 54,
1027 protocolId: 'MQIsdp',
1028 protocolVersion: 3,
1029 will: {
1030 retain: true,
1031 qos: 2,
1032 topic: 'topic',
1033 payload: Buffer.from('payload')
1034 },
1035 clean: true,
1036 keepalive: 30,
1037 clientId: 'test',
1038 username: 'username',
1039 password: Buffer.from('password')
1040}, Buffer.from([
1041 16, 54, // Header
1042 0, 6, // Protocol ID length
1043 77, 81, 73, 115, 100, 112, // Protocol ID
1044 3, // Protocol version
1045 246, // Connect flags
1046 0, 30, // Keepalive
1047 0, 4, // Client ID length
1048 116, 101, 115, 116, // Client ID
1049 0, 5, // Will topic length
1050 116, 111, 112, 105, 99, // Will topic
1051 0, 7, // Will payload length
1052 112, 97, 121, 108, 111, 97, 100, // Will payload
1053 0, 8, // Username length
1054 117, 115, 101, 114, 110, 97, 109, 101, // Username
1055 0, 8, // Password length
1056 112, 97, 115, 115, 119, 111, 114, 100 // Password
1057]))
1058
1059testParseGenerate('max connect with special chars', {
1060 cmd: 'connect',
1061 retain: false,
1062 qos: 0,
1063 dup: false,
1064 length: 57,
1065 protocolId: 'MQIsdp',
1066 protocolVersion: 3,
1067 will: {
1068 retain: true,
1069 qos: 2,
1070 topic: 'tòpic',
1071 payload: Buffer.from('pay£oad')
1072 },
1073 clean: true,
1074 keepalive: 30,
1075 clientId: 'te$t',
1076 username: 'u$ern4me',
1077 password: Buffer.from('p4$$w0£d')
1078}, Buffer.from([
1079 16, 57, // Header
1080 0, 6, // Protocol ID length
1081 77, 81, 73, 115, 100, 112, // Protocol ID
1082 3, // Protocol version
1083 246, // Connect flags
1084 0, 30, // Keepalive
1085 0, 4, // Client ID length
1086 116, 101, 36, 116, // Client ID
1087 0, 6, // Will topic length
1088 116, 195, 178, 112, 105, 99, // Will topic
1089 0, 8, // Will payload length
1090 112, 97, 121, 194, 163, 111, 97, 100, // Will payload
1091 0, 8, // Username length
1092 117, 36, 101, 114, 110, 52, 109, 101, // Username
1093 0, 9, // Password length
1094 112, 52, 36, 36, 119, 48, 194, 163, 100 // Password
1095]))
1096
1097testGenerateOnly('connect all strings generate', {
1098 cmd: 'connect',
1099 retain: false,
1100 qos: 0,
1101 dup: false,
1102 length: 54,
1103 protocolId: 'MQIsdp',
1104 protocolVersion: 3,
1105 will: {
1106 retain: true,
1107 qos: 2,
1108 topic: 'topic',
1109 payload: 'payload'
1110 },
1111 clean: true,
1112 keepalive: 30,
1113 clientId: 'test',
1114 username: 'username',
1115 password: 'password'
1116}, Buffer.from([
1117 16, 54, // Header
1118 0, 6, // Protocol ID length
1119 77, 81, 73, 115, 100, 112, // Protocol ID
1120 3, // Protocol version
1121 246, // Connect flags
1122 0, 30, // Keepalive
1123 0, 4, // Client ID length
1124 116, 101, 115, 116, // Client ID
1125 0, 5, // Will topic length
1126 116, 111, 112, 105, 99, // Will topic
1127 0, 7, // Will payload length
1128 112, 97, 121, 108, 111, 97, 100, // Will payload
1129 0, 8, // Username length
1130 117, 115, 101, 114, 110, 97, 109, 101, // Username
1131 0, 8, // Password length
1132 112, 97, 115, 115, 119, 111, 114, 100 // Password
1133]))
1134
1135testParseError('Cannot parse protocolId', Buffer.from([
1136 16, 4,
1137 0, 6,
1138 77, 81
1139]))
1140
1141// missing protocol version on connect
1142testParseError('Packet too short', Buffer.from([
1143 16, 8, // Header
1144 0, 6, // Protocol ID length
1145 77, 81, 73, 115, 100, 112 // Protocol ID
1146]))
1147
1148// missing keepalive on connect
1149testParseError('Packet too short', Buffer.from([
1150 16, 10, // Header
1151 0, 6, // Protocol ID length
1152 77, 81, 73, 115, 100, 112, // Protocol ID
1153 3, // Protocol version
1154 246 // Connect flags
1155]))
1156
1157// missing clientid on connect
1158testParseError('Packet too short', Buffer.from([
1159 16, 10, // Header
1160 0, 6, // Protocol ID length
1161 77, 81, 73, 115, 100, 112, // Protocol ID
1162 3, // Protocol version
1163 246, // Connect flags
1164 0, 30 // Keepalive
1165]))
1166
1167// missing will topic on connect
1168testParseError('Cannot parse will topic', Buffer.from([
1169 16, 16, // Header
1170 0, 6, // Protocol ID length
1171 77, 81, 73, 115, 100, 112, // Protocol ID
1172 3, // Protocol version
1173 246, // Connect flags
1174 0, 30, // Keepalive
1175 0, 2, // Will topic length
1176 0, 0 // Will topic
1177]))
1178
1179// missing will payload on connect
1180testParseError('Cannot parse will payload', Buffer.from([
1181 16, 23, // Header
1182 0, 6, // Protocol ID length
1183 77, 81, 73, 115, 100, 112, // Protocol ID
1184 3, // Protocol version
1185 246, // Connect flags
1186 0, 30, // Keepalive
1187 0, 5, // Will topic length
1188 116, 111, 112, 105, 99, // Will topic
1189 0, 2, // Will payload length
1190 0, 0 // Will payload
1191]))
1192
1193// missing username on connect
1194testParseError('Cannot parse username', Buffer.from([
1195 16, 32, // Header
1196 0, 6, // Protocol ID length
1197 77, 81, 73, 115, 100, 112, // Protocol ID
1198 3, // Protocol version
1199 246, // Connect flags
1200 0, 30, // Keepalive
1201 0, 5, // Will topic length
1202 116, 111, 112, 105, 99, // Will topic
1203 0, 7, // Will payload length
1204 112, 97, 121, 108, 111, 97, 100, // Will payload
1205 0, 2, // Username length
1206 0, 0 // Username
1207]))
1208
1209// missing password on connect
1210testParseError('Cannot parse password', Buffer.from([
1211 16, 42, // Header
1212 0, 6, // Protocol ID length
1213 77, 81, 73, 115, 100, 112, // Protocol ID
1214 3, // Protocol version
1215 246, // Connect flags
1216 0, 30, // Keepalive
1217 0, 5, // Will topic length
1218 116, 111, 112, 105, 99, // Will topic
1219 0, 7, // Will payload length
1220 112, 97, 121, 108, 111, 97, 100, // Will payload
1221 0, 8, // Username length
1222 117, 115, 101, 114, 110, 97, 109, 101, // Username
1223 0, 2, // Password length
1224 0, 0 // Password
1225]))
1226
1227// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
1228testParseError('Invalid header flag bits, must be 0x0 for connect packet', Buffer.from([
1229 18, 10, // Header
1230 0, 4, // Protocol ID length
1231 0x4d, 0x51, 0x54, 0x54, // Protocol ID
1232 3, // Protocol version
1233 2, // Connect flags
1234 0, 30 // Keepalive
1235]))
1236
1237// The Server MUST validate that the reserved flag in the CONNECT Control Packet is set to zero and disconnect the Client if it is not zero [MQTT-3.1.2-3]
1238testParseError('Connect flag bit 0 must be 0, but got 1', Buffer.from([
1239 16, 10, // Header
1240 0, 4, // Protocol ID length
1241 0x4d, 0x51, 0x54, 0x54, // Protocol ID
1242 3, // Protocol version
1243 3, // Connect flags
1244 0, 30 // Keepalive
1245]))
1246
1247// If the Will Flag is set to 0 the Will QoS and Will Retain fields in the Connect Flags MUST be set to zero and the Will Topic and Will Message fields MUST NOT be present in the payload [MQTT-3.1.2-11].
1248testParseError('Will Retain Flag must be set to zero when Will Flag is set to 0', Buffer.from([
1249 16, 10, // Header
1250 0, 4, // Protocol ID length
1251 0x4d, 0x51, 0x54, 0x54, // Protocol ID
1252 3, // Protocol version
1253 0x22, // Connect flags
1254 0, 30 // Keepalive
1255]))
1256
1257// If the Will Flag is set to 0 the Will QoS and Will Retain fields in the Connect Flags MUST be set to zero and the Will Topic and Will Message fields MUST NOT be present in the payload [MQTT-3.1.2-11].
1258testParseError('Will QoS must be set to zero when Will Flag is set to 0', Buffer.from([
1259 16, 10, // Header
1260 0, 4, // Protocol ID length
1261 0x4d, 0x51, 0x54, 0x54, // Protocol ID
1262 3, // Protocol version
1263 0x12, // Connect flags
1264 0, 30 // Keepalive
1265]))
1266
1267// If the Will Flag is set to 0 the Will QoS and Will Retain fields in the Connect Flags MUST be set to zero and the Will Topic and Will Message fields MUST NOT be present in the payload [MQTT-3.1.2-11].
1268testParseError('Will QoS must be set to zero when Will Flag is set to 0', Buffer.from([
1269 16, 10, // Header
1270 0, 4, // Protocol ID length
1271 0x4d, 0x51, 0x54, 0x54, // Protocol ID
1272 3, // Protocol version
1273 0xa, // Connect flags
1274 0, 30 // Keepalive
1275]))
1276
1277testParseGenerate('connack with return code 0', {
1278 cmd: 'connack',
1279 retain: false,
1280 qos: 0,
1281 dup: false,
1282 length: 2,
1283 sessionPresent: false,
1284 returnCode: 0
1285}, Buffer.from([
1286 32, 2, 0, 0
1287]))
1288
1289testParseGenerate('connack MQTT 5 with properties', {
1290 cmd: 'connack',
1291 retain: false,
1292 qos: 0,
1293 dup: false,
1294 length: 87,
1295 sessionPresent: false,
1296 reasonCode: 0,
1297 properties: {
1298 sessionExpiryInterval: 1234,
1299 receiveMaximum: 432,
1300 maximumQoS: 2,
1301 retainAvailable: true,
1302 maximumPacketSize: 100,
1303 assignedClientIdentifier: 'test',
1304 topicAliasMaximum: 456,
1305 reasonString: 'test',
1306 userProperties: {
1307 test: 'test'
1308 },
1309 wildcardSubscriptionAvailable: true,
1310 subscriptionIdentifiersAvailable: true,
1311 sharedSubscriptionAvailable: false,
1312 serverKeepAlive: 1234,
1313 responseInformation: 'test',
1314 serverReference: 'test',
1315 authenticationMethod: 'test',
1316 authenticationData: Buffer.from([1, 2, 3, 4])
1317 }
1318}, Buffer.from([
1319 32, 87, 0, 0,
1320 84, // properties length
1321 17, 0, 0, 4, 210, // sessionExpiryInterval
1322 33, 1, 176, // receiveMaximum
1323 36, 2, // Maximum qos
1324 37, 1, // retainAvailable
1325 39, 0, 0, 0, 100, // maximumPacketSize
1326 18, 0, 4, 116, 101, 115, 116, // assignedClientIdentifier
1327 34, 1, 200, // topicAliasMaximum
1328 31, 0, 4, 116, 101, 115, 116, // reasonString
1329 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
1330 40, 1, // wildcardSubscriptionAvailable
1331 41, 1, // subscriptionIdentifiersAvailable
1332 42, 0, // sharedSubscriptionAvailable
1333 19, 4, 210, // serverKeepAlive
1334 26, 0, 4, 116, 101, 115, 116, // responseInformation
1335 28, 0, 4, 116, 101, 115, 116, // serverReference
1336 21, 0, 4, 116, 101, 115, 116, // authenticationMethod
1337 22, 0, 4, 1, 2, 3, 4 // authenticationData
1338]), { protocolVersion: 5 })
1339
1340testParseGenerate('connack MQTT 5 with properties and doubled user properties', {
1341 cmd: 'connack',
1342 retain: false,
1343 qos: 0,
1344 dup: false,
1345 length: 100,
1346 sessionPresent: false,
1347 reasonCode: 0,
1348 properties: {
1349 sessionExpiryInterval: 1234,
1350 receiveMaximum: 432,
1351 maximumQoS: 2,
1352 retainAvailable: true,
1353 maximumPacketSize: 100,
1354 assignedClientIdentifier: 'test',
1355 topicAliasMaximum: 456,
1356 reasonString: 'test',
1357 userProperties: {
1358 test: ['test', 'test']
1359 },
1360 wildcardSubscriptionAvailable: true,
1361 subscriptionIdentifiersAvailable: true,
1362 sharedSubscriptionAvailable: false,
1363 serverKeepAlive: 1234,
1364 responseInformation: 'test',
1365 serverReference: 'test',
1366 authenticationMethod: 'test',
1367 authenticationData: Buffer.from([1, 2, 3, 4])
1368 }
1369}, Buffer.from([
1370 32, 100, 0, 0,
1371 97, // properties length
1372 17, 0, 0, 4, 210, // sessionExpiryInterval
1373 33, 1, 176, // receiveMaximum
1374 36, 2, // Maximum qos
1375 37, 1, // retainAvailable
1376 39, 0, 0, 0, 100, // maximumPacketSize
1377 18, 0, 4, 116, 101, 115, 116, // assignedClientIdentifier
1378 34, 1, 200, // topicAliasMaximum
1379 31, 0, 4, 116, 101, 115, 116, // reasonString
1380 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116,
1381 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
1382 40, 1, // wildcardSubscriptionAvailable
1383 41, 1, // subscriptionIdentifiersAvailable
1384 42, 0, // sharedSubscriptionAvailable
1385 19, 4, 210, // serverKeepAlive
1386 26, 0, 4, 116, 101, 115, 116, // responseInformation
1387 28, 0, 4, 116, 101, 115, 116, // serverReference
1388 21, 0, 4, 116, 101, 115, 116, // authenticationMethod
1389 22, 0, 4, 1, 2, 3, 4 // authenticationData
1390]), { protocolVersion: 5 })
1391
1392testParseGenerate('connack with return code 0 session present bit set', {
1393 cmd: 'connack',
1394 retain: false,
1395 qos: 0,
1396 dup: false,
1397 length: 2,
1398 sessionPresent: true,
1399 returnCode: 0
1400}, Buffer.from([
1401 32, 2, 1, 0
1402]))
1403
1404testParseGenerate('connack with return code 5', {
1405 cmd: 'connack',
1406 retain: false,
1407 qos: 0,
1408 dup: false,
1409 length: 2,
1410 sessionPresent: false,
1411 returnCode: 5
1412}, Buffer.from([
1413 32, 2, 0, 5
1414]))
1415
1416// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
1417testParseError('Invalid header flag bits, must be 0x0 for connack packet', Buffer.from([
1418 33, 2, // header
1419 0, // flags
1420 5 // return code
1421]))
1422
1423// Byte 1 is the "Connect Acknowledge Flags". Bits 7-1 are reserved and MUST be set to 0 [MQTT-3.2.2-1].
1424testParseError('Invalid connack flags, bits 7-1 must be set to 0', Buffer.from([
1425 32, 2, // header
1426 2, // flags
1427 5 // return code
1428]))
1429
1430testGenerateError('Invalid return code', {
1431 cmd: 'connack',
1432 retain: false,
1433 qos: 0,
1434 dup: false,
1435 length: 2,
1436 sessionPresent: false,
1437 returnCode: '5' // returncode must be a number
1438})
1439
1440testParseGenerate('minimal publish', {
1441 cmd: 'publish',
1442 retain: false,
1443 qos: 0,
1444 dup: false,
1445 length: 10,
1446 topic: 'test',
1447 payload: Buffer.from('test')
1448}, Buffer.from([
1449 48, 10, // Header
1450 0, 4, // Topic length
1451 116, 101, 115, 116, // Topic (test)
1452 116, 101, 115, 116 // Payload (test)
1453]))
1454
1455testParseGenerate('publish MQTT 5 properties', {
1456 cmd: 'publish',
1457 retain: true,
1458 qos: 2,
1459 dup: true,
1460 length: 86,
1461 topic: 'test',
1462 payload: Buffer.from('test'),
1463 messageId: 10,
1464 properties: {
1465 payloadFormatIndicator: true,
1466 messageExpiryInterval: 4321,
1467 topicAlias: 100,
1468 responseTopic: 'topic',
1469 correlationData: Buffer.from([1, 2, 3, 4]),
1470 userProperties: {
1471 test: ['test', 'test', 'test']
1472 },
1473 subscriptionIdentifier: 120,
1474 contentType: 'test'
1475 }
1476}, Buffer.from([
1477 61, 86, // Header
1478 0, 4, // Topic length
1479 116, 101, 115, 116, // Topic (test)
1480 0, 10, // Message ID
1481 73, // properties length
1482 1, 1, // payloadFormatIndicator
1483 2, 0, 0, 16, 225, // message expiry interval
1484 35, 0, 100, // topicAlias
1485 8, 0, 5, 116, 111, 112, 105, 99, // response topic
1486 9, 0, 4, 1, 2, 3, 4, // correlationData
1487 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
1488 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
1489 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
1490 11, 120, // subscriptionIdentifier
1491 3, 0, 4, 116, 101, 115, 116, // content type
1492 116, 101, 115, 116 // Payload (test)
1493]), { protocolVersion: 5 })
1494
1495testParseGenerate('publish MQTT 5 with multiple same properties', {
1496 cmd: 'publish',
1497 retain: true,
1498 qos: 2,
1499 dup: true,
1500 length: 64,
1501 topic: 'test',
1502 payload: Buffer.from('test'),
1503 messageId: 10,
1504 properties: {
1505 payloadFormatIndicator: true,
1506 messageExpiryInterval: 4321,
1507 topicAlias: 100,
1508 responseTopic: 'topic',
1509 correlationData: Buffer.from([1, 2, 3, 4]),
1510 userProperties: {
1511 test: 'test'
1512 },
1513 subscriptionIdentifier: [120, 121, 122],
1514 contentType: 'test'
1515 }
1516}, Buffer.from([
1517 61, 64, // Header
1518 0, 4, // Topic length
1519 116, 101, 115, 116, // Topic (test)
1520 0, 10, // Message ID
1521 51, // properties length
1522 1, 1, // payloadFormatIndicator
1523 2, 0, 0, 16, 225, // message expiry interval
1524 35, 0, 100, // topicAlias
1525 8, 0, 5, 116, 111, 112, 105, 99, // response topic
1526 9, 0, 4, 1, 2, 3, 4, // correlationData
1527 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
1528 11, 120, // subscriptionIdentifier
1529 11, 121, // subscriptionIdentifier
1530 11, 122, // subscriptionIdentifier
1531 3, 0, 4, 116, 101, 115, 116, // content type
1532 116, 101, 115, 116 // Payload (test)
1533]), { protocolVersion: 5 })
1534
1535testParseGenerate('publish MQTT 5 properties with 0-4 byte varbyte', {
1536 cmd: 'publish',
1537 retain: true,
1538 qos: 2,
1539 dup: true,
1540 length: 27,
1541 topic: 'test',
1542 payload: Buffer.from('test'),
1543 messageId: 10,
1544 properties: {
1545 payloadFormatIndicator: false,
1546 subscriptionIdentifier: [128, 16384, 2097152] // this tests the varbyte handling
1547 }
1548}, Buffer.from([
1549 61, 27, // Header
1550 0, 4, // Topic length
1551 116, 101, 115, 116, // Topic (test)
1552 0, 10, // Message ID
1553 14, // properties length
1554 1, 0, // payloadFormatIndicator
1555 11, 128, 1, // subscriptionIdentifier
1556 11, 128, 128, 1, // subscriptionIdentifier
1557 11, 128, 128, 128, 1, // subscriptionIdentifier
1558 116, 101, 115, 116 // Payload (test)
1559]), { protocolVersion: 5 })
1560
1561testParseGenerate('publish MQTT 5 properties with max value varbyte', {
1562 cmd: 'publish',
1563 retain: true,
1564 qos: 2,
1565 dup: true,
1566 length: 22,
1567 topic: 'test',
1568 payload: Buffer.from('test'),
1569 messageId: 10,
1570 properties: {
1571 payloadFormatIndicator: false,
1572 subscriptionIdentifier: [1, 268435455]
1573 }
1574}, Buffer.from([
1575 61, 22, // Header
1576 0, 4, // Topic length
1577 116, 101, 115, 116, // Topic (test)
1578 0, 10, // Message ID
1579 9, // properties length
1580 1, 0, // payloadFormatIndicator
1581 11, 1, // subscriptionIdentifier
1582 11, 255, 255, 255, 127, // subscriptionIdentifier (max value)
1583 116, 101, 115, 116 // Payload (test)
1584]), { protocolVersion: 5 })
1585
1586; (() => {
1587 const buffer = Buffer.alloc(2048)
1588 testParseGenerate('2KB publish packet', {
1589 cmd: 'publish',
1590 retain: false,
1591 qos: 0,
1592 dup: false,
1593 length: 2054,
1594 topic: 'test',
1595 payload: buffer
1596 }, Buffer.concat([Buffer.from([
1597 48, 134, 16, // Header
1598 0, 4, // Topic length
1599 116, 101, 115, 116 // Topic (test)
1600 ]), buffer]))
1601})()
1602
1603; (() => {
1604 const maxLength = 268435455
1605 const buffer = Buffer.alloc(maxLength - 6)
1606 testParseGenerate('Max payload publish packet', {
1607 cmd: 'publish',
1608 retain: false,
1609 qos: 0,
1610 dup: false,
1611 length: maxLength,
1612 topic: 'test',
1613 payload: buffer
1614 }, Buffer.concat([Buffer.from([
1615 48, 255, 255, 255, 127, // Header
1616 0, 4, // Topic length
1617 116, 101, 115, 116 // Topic (test)
1618 ]), buffer]))
1619})()
1620
1621testParseGenerate('maximal publish', {
1622 cmd: 'publish',
1623 retain: true,
1624 qos: 2,
1625 length: 12,
1626 dup: true,
1627 topic: 'test',
1628 messageId: 10,
1629 payload: Buffer.from('test')
1630}, Buffer.from([
1631 61, 12, // Header
1632 0, 4, // Topic length
1633 116, 101, 115, 116, // Topic
1634 0, 10, // Message ID
1635 116, 101, 115, 116 // Payload
1636]))
1637
1638test('publish all strings generate', t => {
1639 const message = {
1640 cmd: 'publish',
1641 retain: true,
1642 qos: 2,
1643 length: 12,
1644 dup: true,
1645 topic: 'test',
1646 messageId: 10,
1647 payload: Buffer.from('test')
1648 }
1649 const expected = Buffer.from([
1650 61, 12, // Header
1651 0, 4, // Topic length
1652 116, 101, 115, 116, // Topic
1653 0, 10, // Message ID
1654 116, 101, 115, 116 // Payload
1655 ])
1656
1657 t.equal(mqtt.generate(message).toString('hex'), expected.toString('hex'))
1658 t.end()
1659})
1660
1661testParseGenerate('empty publish', {
1662 cmd: 'publish',
1663 retain: false,
1664 qos: 0,
1665 dup: false,
1666 length: 6,
1667 topic: 'test',
1668 payload: Buffer.alloc(0)
1669}, Buffer.from([
1670 48, 6, // Header
1671 0, 4, // Topic length
1672 116, 101, 115, 116 // Topic
1673 // Empty payload
1674]))
1675
1676// A PUBLISH Packet MUST NOT have both QoS bits set to 1. If a Server or Client receives a PUBLISH Packet which has both QoS bits set to 1 it MUST close the Network Connection [MQTT-3.3.1-4].
1677testParseError('Packet must not have both QoS bits set to 1', Buffer.from([
1678 0x36, 6, // Header
1679 0, 4, // Topic length
1680 116, 101, 115, 116 // Topic
1681 // Empty payload
1682]))
1683
1684test('splitted publish parse', t => {
1685 t.plan(3)
1686
1687 const parser = mqtt.parser()
1688 const expected = {
1689 cmd: 'publish',
1690 retain: false,
1691 qos: 0,
1692 dup: false,
1693 length: 10,
1694 topic: 'test',
1695 payload: Buffer.from('test')
1696 }
1697
1698 parser.on('packet', packet => {
1699 t.deepLooseEqual(packet, expected, 'expected packet')
1700 })
1701
1702 t.equal(parser.parse(Buffer.from([
1703 48, 10, // Header
1704 0, 4, // Topic length
1705 116, 101, 115, 116 // Topic (test)
1706 ])), 6, 'remaining bytes')
1707
1708 t.equal(parser.parse(Buffer.from([
1709 116, 101, 115, 116 // Payload (test)
1710 ])), 0, 'remaining bytes')
1711})
1712
1713test('split publish longer', t => {
1714 t.plan(3)
1715
1716 const length = 255
1717 const topic = 'test'
1718 // Minus two bytes for the topic length specifier
1719 const payloadLength = length - topic.length - 2
1720
1721 const parser = mqtt.parser()
1722 const expected = {
1723 cmd: 'publish',
1724 retain: false,
1725 qos: 0,
1726 dup: false,
1727 length: length,
1728 topic: topic,
1729 payload: Buffer.from('a'.repeat(payloadLength))
1730 }
1731
1732 parser.on('packet', packet => {
1733 t.deepLooseEqual(packet, expected, 'expected packet')
1734 })
1735
1736 t.equal(parser.parse(Buffer.from([
1737 48, 255, 1, // Header
1738 0, topic.length, // Topic length
1739 116, 101, 115, 116 // Topic (test)
1740 ])), 6, 'remaining bytes')
1741
1742 t.equal(parser.parse(Buffer.from(Array(payloadLength).fill(97))),
1743 0, 'remaining bytes')
1744})
1745
1746test('split length parse', t => {
1747 t.plan(4)
1748
1749 const length = 255
1750 const topic = 'test'
1751 const payloadLength = length - topic.length - 2
1752
1753 const parser = mqtt.parser()
1754 const expected = {
1755 cmd: 'publish',
1756 retain: false,
1757 qos: 0,
1758 dup: false,
1759 length: length,
1760 topic: topic,
1761 payload: Buffer.from('a'.repeat(payloadLength))
1762 }
1763
1764 parser.on('packet', packet => {
1765 t.deepLooseEqual(packet, expected, 'expected packet')
1766 })
1767
1768 t.equal(parser.parse(Buffer.from([
1769 48, 255 // Header (partial length)
1770 ])), 1, 'remaining bytes')
1771
1772 t.equal(parser.parse(Buffer.from([
1773 1, // Rest of header length
1774 0, topic.length, // Topic length
1775 116, 101, 115, 116 // Topic (test)
1776 ])), 6, 'remaining bytes')
1777
1778 t.equal(parser.parse(Buffer.from(Array(payloadLength).fill(97))),
1779 0, 'remaining bytes')
1780})
1781
1782testGenerateError('Invalid variable byte integer: 268435456', {
1783 cmd: 'publish',
1784 retain: false,
1785 qos: 0,
1786 dup: false,
1787 length: (268435455 + 1),
1788 topic: 'test',
1789 payload: Buffer.alloc(268435455 + 1 - 6)
1790}, {}, 'Length var byte integer over max allowed value throws error')
1791
1792testGenerateError('Invalid subscriptionIdentifier: 268435456', {
1793 cmd: 'publish',
1794 retain: true,
1795 qos: 2,
1796 dup: true,
1797 length: 27,
1798 topic: 'test',
1799 payload: Buffer.from('test'),
1800 messageId: 10,
1801 properties: {
1802 payloadFormatIndicator: false,
1803 subscriptionIdentifier: 268435456
1804 }
1805}, { protocolVersion: 5 }, 'MQTT 5.0 var byte integer >24 bits throws error')
1806
1807testParseGenerate('puback', {
1808 cmd: 'puback',
1809 retain: false,
1810 qos: 0,
1811 dup: false,
1812 length: 2,
1813 messageId: 2
1814}, Buffer.from([
1815 64, 2, // Header
1816 0, 2 // Message ID
1817]))
1818
1819// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
1820testParseError('Invalid header flag bits, must be 0x0 for puback packet', Buffer.from([
1821 65, 2, // Header
1822 0, 2 // Message ID
1823]))
1824
1825testParseGenerate('puback with reason and no MQTT 5 properties', {
1826 cmd: 'puback',
1827 retain: false,
1828 qos: 0,
1829 dup: false,
1830 length: 3,
1831 messageId: 2,
1832 reasonCode: 16
1833}, Buffer.from([
1834 64, 3, // Header
1835 0, 2, // Message ID
1836 16 // reason code
1837]), { protocolVersion: 5 })
1838
1839testParseGenerate('puback MQTT 5 properties', {
1840 cmd: 'puback',
1841 retain: false,
1842 qos: 0,
1843 dup: false,
1844 length: 24,
1845 messageId: 2,
1846 reasonCode: 16,
1847 properties: {
1848 reasonString: 'test',
1849 userProperties: {
1850 test: 'test'
1851 }
1852 }
1853}, Buffer.from([
1854 64, 24, // Header
1855 0, 2, // Message ID
1856 16, // reason code
1857 20, // properties length
1858 31, 0, 4, 116, 101, 115, 116, // reasonString
1859 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116 // userProperties
1860]), { protocolVersion: 5 })
1861
1862testParseError('Invalid puback reason code', Buffer.from([
1863 64, 4, // Header
1864 0, 2, // Message ID
1865 0x11, // reason code
1866 0 // properties length
1867]), { protocolVersion: 5 })
1868
1869testParseGenerate('pubrec', {
1870 cmd: 'pubrec',
1871 retain: false,
1872 qos: 0,
1873 dup: false,
1874 length: 2,
1875 messageId: 2
1876}, Buffer.from([
1877 80, 2, // Header
1878 0, 2 // Message ID
1879]))
1880
1881// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
1882testParseError('Invalid header flag bits, must be 0x0 for pubrec packet', Buffer.from([
1883 81, 2, // Header
1884 0, 2 // Message ID
1885]))
1886
1887testParseGenerate('pubrec MQTT 5 properties', {
1888 cmd: 'pubrec',
1889 retain: false,
1890 qos: 0,
1891 dup: false,
1892 length: 24,
1893 messageId: 2,
1894 reasonCode: 16,
1895 properties: {
1896 reasonString: 'test',
1897 userProperties: {
1898 test: 'test'
1899 }
1900 }
1901}, Buffer.from([
1902 80, 24, // Header
1903 0, 2, // Message ID
1904 16, // reason code
1905 20, // properties length
1906 31, 0, 4, 116, 101, 115, 116, // reasonString
1907 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116 // userProperties
1908]), { protocolVersion: 5 })
1909
1910testParseGenerate('pubrel', {
1911 cmd: 'pubrel',
1912 retain: false,
1913 qos: 1,
1914 dup: false,
1915 length: 2,
1916 messageId: 2
1917}, Buffer.from([
1918 98, 2, // Header
1919 0, 2 // Message ID
1920]))
1921
1922testParseError('Invalid pubrel reason code', Buffer.from([
1923 98, 4, // Header
1924 0, 2, // Message ID
1925 0x11, // Reason code
1926 0 // Properties length
1927]), { protocolVersion: 5 })
1928
1929// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
1930testParseError('Invalid header flag bits, must be 0x2 for pubrel packet', Buffer.from([
1931 96, 2, // Header
1932 0, 2 // Message ID
1933]))
1934
1935testParseGenerate('pubrel MQTT5 properties', {
1936 cmd: 'pubrel',
1937 retain: false,
1938 qos: 1,
1939 dup: false,
1940 length: 24,
1941 messageId: 2,
1942 reasonCode: 0x92,
1943 properties: {
1944 reasonString: 'test',
1945 userProperties: {
1946 test: 'test'
1947 }
1948 }
1949}, Buffer.from([
1950 98, 24, // Header
1951 0, 2, // Message ID
1952 0x92, // reason code
1953 20, // properties length
1954 31, 0, 4, 116, 101, 115, 116, // reasonString
1955 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116 // userProperties
1956]), { protocolVersion: 5 })
1957
1958testParseError('Invalid pubrel reason code', Buffer.from([
1959 98, 4, // Header
1960 0, 2, // Message ID
1961 16, // reason code
1962 0 // properties length
1963]), { protocolVersion: 5 })
1964
1965testParseGenerate('pubcomp', {
1966 cmd: 'pubcomp',
1967 retain: false,
1968 qos: 0,
1969 dup: false,
1970 length: 2,
1971 messageId: 2
1972}, Buffer.from([
1973 112, 2, // Header
1974 0, 2 // Message ID
1975]))
1976
1977// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
1978testParseError('Invalid header flag bits, must be 0x0 for pubcomp packet', Buffer.from([
1979 113, 2, // Header
1980 0, 2 // Message ID
1981]))
1982
1983testParseGenerate('pubcomp MQTT 5 properties', {
1984 cmd: 'pubcomp',
1985 retain: false,
1986 qos: 0,
1987 dup: false,
1988 length: 24,
1989 messageId: 2,
1990 reasonCode: 0x92,
1991 properties: {
1992 reasonString: 'test',
1993 userProperties: {
1994 test: 'test'
1995 }
1996 }
1997}, Buffer.from([
1998 112, 24, // Header
1999 0, 2, // Message ID
2000 0x92, // reason code
2001 20, // properties length
2002 31, 0, 4, 116, 101, 115, 116, // reasonString
2003 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116 // userProperties
2004]), { protocolVersion: 5 })
2005
2006testParseError('Invalid pubcomp reason code', Buffer.from([
2007 112, 4, // Header
2008 0, 2, // Message ID
2009 16, // reason code
2010 0 // properties length
2011]), { protocolVersion: 5 })
2012
2013testParseError('Invalid header flag bits, must be 0x2 for subscribe packet', Buffer.from([
2014 128, 9, // Header (subscribeqos=0length=9)
2015 0, 6, // Message ID (6)
2016 0, 4, // Topic length,
2017 116, 101, 115, 116, // Topic (test)
2018 0 // Qos (0)
2019]))
2020
2021testParseGenerate('subscribe to one topic', {
2022 cmd: 'subscribe',
2023 retain: false,
2024 qos: 1,
2025 dup: false,
2026 length: 9,
2027 subscriptions: [
2028 {
2029 topic: 'test',
2030 qos: 0
2031 }
2032 ],
2033 messageId: 6
2034}, Buffer.from([
2035 130, 9, // Header (subscribeqos=1length=9)
2036 0, 6, // Message ID (6)
2037 0, 4, // Topic length,
2038 116, 101, 115, 116, // Topic (test)
2039 0 // Qos (0)
2040]))
2041
2042testParseError('Invalid subscribe QoS, must be <= 2', Buffer.from([
2043 130, 9, // Header (subscribeqos=0length=9)
2044 0, 6, // Message ID (6)
2045 0, 4, // Topic length,
2046 116, 101, 115, 116, // Topic (test)
2047 3 // Qos
2048]))
2049
2050testParseError('Invalid subscribe topic flag bits, bits 7-6 must be 0', Buffer.from([
2051 130, 10, // Header (subscribeqos=0length=9)
2052 0, 6, // Message ID (6)
2053 0, // Property length (0)
2054 0, 4, // Topic length,
2055 116, 101, 115, 116, // Topic (test)
2056 0x80 // Flags
2057]), { protocolVersion: 5 })
2058
2059testParseError('Invalid retain handling, must be <= 2', Buffer.from([
2060 130, 10, // Header (subscribeqos=0length=9)
2061 0, 6, // Message ID (6)
2062 0, // Property length (0)
2063 0, 4, // Topic length,
2064 116, 101, 115, 116, // Topic (test)
2065 0x30 // Flags
2066]), { protocolVersion: 5 })
2067
2068testParseError('Invalid subscribe topic flag bits, bits 7-2 must be 0', Buffer.from([
2069 130, 9, // Header (subscribeqos=0length=9)
2070 0, 6, // Message ID (6)
2071 0, 4, // Topic length,
2072 116, 101, 115, 116, // Topic (test)
2073 0x08 // Flags
2074]))
2075
2076testParseGenerate('subscribe to one topic by MQTT 5', {
2077 cmd: 'subscribe',
2078 retain: false,
2079 qos: 1,
2080 dup: false,
2081 length: 26,
2082 subscriptions: [
2083 {
2084 topic: 'test',
2085 qos: 0,
2086 nl: false,
2087 rap: true,
2088 rh: 1
2089 }
2090 ],
2091 messageId: 6,
2092 properties: {
2093 subscriptionIdentifier: 145,
2094 userProperties: {
2095 test: 'test'
2096 }
2097 }
2098}, Buffer.from([
2099 130, 26, // Header (subscribeqos=1length=9)
2100 0, 6, // Message ID (6)
2101 16, // properties length
2102 11, 145, 1, // subscriptionIdentifier
2103 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
2104 0, 4, // Topic length,
2105 116, 101, 115, 116, // Topic (test)
2106 24 // settings(qos: 0, noLocal: false, Retain as Published: true, retain handling: 1)
2107]), { protocolVersion: 5 })
2108
2109testParseGenerate('subscribe to three topics', {
2110 cmd: 'subscribe',
2111 retain: false,
2112 qos: 1,
2113 dup: false,
2114 length: 23,
2115 subscriptions: [
2116 {
2117 topic: 'test',
2118 qos: 0
2119 }, {
2120 topic: 'uest',
2121 qos: 1
2122 }, {
2123 topic: 'tfst',
2124 qos: 2
2125 }
2126 ],
2127 messageId: 6
2128}, Buffer.from([
2129 130, 23, // Header (publishqos=1length=9)
2130 0, 6, // Message ID (6)
2131 0, 4, // Topic length,
2132 116, 101, 115, 116, // Topic (test)
2133 0, // Qos (0)
2134 0, 4, // Topic length
2135 117, 101, 115, 116, // Topic (uest)
2136 1, // Qos (1)
2137 0, 4, // Topic length
2138 116, 102, 115, 116, // Topic (tfst)
2139 2 // Qos (2)
2140]))
2141
2142testParseGenerate('subscribe to 3 topics by MQTT 5', {
2143 cmd: 'subscribe',
2144 retain: false,
2145 qos: 1,
2146 dup: false,
2147 length: 40,
2148 subscriptions: [
2149 {
2150 topic: 'test',
2151 qos: 0,
2152 nl: false,
2153 rap: true,
2154 rh: 1
2155 },
2156 {
2157 topic: 'uest',
2158 qos: 1,
2159 nl: false,
2160 rap: false,
2161 rh: 0
2162 }, {
2163 topic: 'tfst',
2164 qos: 2,
2165 nl: true,
2166 rap: false,
2167 rh: 0
2168 }
2169 ],
2170 messageId: 6,
2171 properties: {
2172 subscriptionIdentifier: 145,
2173 userProperties: {
2174 test: 'test'
2175 }
2176 }
2177}, Buffer.from([
2178 130, 40, // Header (subscribeqos=1length=9)
2179 0, 6, // Message ID (6)
2180 16, // properties length
2181 11, 145, 1, // subscriptionIdentifier
2182 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
2183 0, 4, // Topic length,
2184 116, 101, 115, 116, // Topic (test)
2185 24, // settings(qos: 0, noLocal: false, Retain as Published: true, retain handling: 1)
2186 0, 4, // Topic length
2187 117, 101, 115, 116, // Topic (uest)
2188 1, // Qos (1)
2189 0, 4, // Topic length
2190 116, 102, 115, 116, // Topic (tfst)
2191 6 // Qos (2), No Local: true
2192]), { protocolVersion: 5 })
2193
2194testParseGenerate('suback', {
2195 cmd: 'suback',
2196 retain: false,
2197 qos: 0,
2198 dup: false,
2199 length: 5,
2200 granted: [0, 1, 2],
2201 messageId: 6
2202}, Buffer.from([
2203 144, 5, // Header
2204 0, 6, // Message ID
2205 0, 1, 2
2206]))
2207
2208testParseGenerate('suback', {
2209 cmd: 'suback',
2210 retain: false,
2211 qos: 0,
2212 dup: false,
2213 length: 7,
2214 granted: [0, 1, 2, 128],
2215 messageId: 6
2216}, Buffer.from([
2217 144, 7, // Header
2218 0, 6, // Message ID
2219 0, // Property length
2220 0, 1, 2, 128 // Granted qos (0, 1, 2) and a rejected being 0x80
2221]), { protocolVersion: 5 })
2222
2223testParseError('Invalid suback QoS, must be <= 2', Buffer.from([
2224 144, 6, // Header
2225 0, 6, // Message ID
2226 0, 1, 2, 128 // Granted qos (0, 1, 2) and a rejected being 0x80
2227]))
2228
2229testParseError('Invalid suback code', Buffer.from([
2230 144, 6, // Header
2231 0, 6, // Message ID
2232 0, 1, 2, 0x79 // Granted qos (0, 1, 2) and an invalid code
2233]), { protocolVersion: 5 })
2234
2235testParseGenerate('suback MQTT 5', {
2236 cmd: 'suback',
2237 retain: false,
2238 qos: 0,
2239 dup: false,
2240 length: 27,
2241 granted: [0, 1, 2, 128],
2242 messageId: 6,
2243 properties: {
2244 reasonString: 'test',
2245 userProperties: {
2246 test: 'test'
2247 }
2248 }
2249}, Buffer.from([
2250 144, 27, // Header
2251 0, 6, // Message ID
2252 20, // properties length
2253 31, 0, 4, 116, 101, 115, 116, // reasonString
2254 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
2255 0, 1, 2, 128 // Granted qos (0, 1, 2) and a rejected being 0x80
2256]), { protocolVersion: 5 })
2257
2258testParseGenerate('unsubscribe', {
2259 cmd: 'unsubscribe',
2260 retain: false,
2261 qos: 1,
2262 dup: false,
2263 length: 14,
2264 unsubscriptions: [
2265 'tfst',
2266 'test'
2267 ],
2268 messageId: 7
2269}, Buffer.from([
2270 162, 14,
2271 0, 7, // Message ID (7)
2272 0, 4, // Topic length
2273 116, 102, 115, 116, // Topic (tfst)
2274 0, 4, // Topic length,
2275 116, 101, 115, 116 // Topic (test)
2276]))
2277
2278// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
2279testParseError('Invalid header flag bits, must be 0x2 for unsubscribe packet', Buffer.from([
2280 160, 14,
2281 0, 7, // Message ID (7)
2282 0, 4, // Topic length
2283 116, 102, 115, 116, // Topic (tfst)
2284 0, 4, // Topic length,
2285 116, 101, 115, 116 // Topic (test)
2286]))
2287
2288testGenerateError('Invalid unsubscriptions', {
2289 cmd: 'unsubscribe',
2290 retain: false,
2291 qos: 1,
2292 dup: true,
2293 length: 5,
2294 unsubscriptions: 5,
2295 messageId: 7
2296}, {}, 'unsubscribe with unsubscriptions not an array')
2297
2298testGenerateError('Invalid unsubscriptions', {
2299 cmd: 'unsubscribe',
2300 retain: false,
2301 qos: 1,
2302 dup: true,
2303 length: 5,
2304 unsubscriptions: [1, 2],
2305 messageId: 7
2306}, {}, 'unsubscribe with unsubscriptions as an object')
2307
2308testParseGenerate('unsubscribe MQTT 5', {
2309 cmd: 'unsubscribe',
2310 retain: false,
2311 qos: 1,
2312 dup: false,
2313 length: 28,
2314 unsubscriptions: [
2315 'tfst',
2316 'test'
2317 ],
2318 messageId: 7,
2319 properties: {
2320 userProperties: {
2321 test: 'test'
2322 }
2323 }
2324}, Buffer.from([
2325 162, 28,
2326 0, 7, // Message ID (7)
2327 13, // properties length
2328 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
2329 0, 4, // Topic length
2330 116, 102, 115, 116, // Topic (tfst)
2331 0, 4, // Topic length,
2332 116, 101, 115, 116 // Topic (test)
2333]), { protocolVersion: 5 })
2334
2335testParseGenerate('unsuback', {
2336 cmd: 'unsuback',
2337 retain: false,
2338 qos: 0,
2339 dup: false,
2340 length: 2,
2341 messageId: 8
2342}, Buffer.from([
2343 176, 2, // Header
2344 0, 8 // Message ID
2345]))
2346
2347// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
2348testParseError('Invalid header flag bits, must be 0x0 for unsuback packet', Buffer.from([
2349 177, 2, // Header
2350 0, 8 // Message ID
2351]))
2352
2353testParseGenerate('unsuback MQTT 5', {
2354 cmd: 'unsuback',
2355 retain: false,
2356 qos: 0,
2357 dup: false,
2358 length: 25,
2359 messageId: 8,
2360 properties: {
2361 reasonString: 'test',
2362 userProperties: {
2363 test: 'test'
2364 }
2365 },
2366 granted: [0, 128]
2367}, Buffer.from([
2368 176, 25, // Header
2369 0, 8, // Message ID
2370 20, // properties length
2371 31, 0, 4, 116, 101, 115, 116, // reasonString
2372 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
2373 0, 128 // success and error
2374]), { protocolVersion: 5 })
2375
2376testParseError('Invalid unsuback code', Buffer.from([
2377 176, 4, // Header
2378 0, 8, // Message ID
2379 0, // properties length
2380 0x84 // reason codes
2381]), { protocolVersion: 5 })
2382
2383testParseGenerate('pingreq', {
2384 cmd: 'pingreq',
2385 retain: false,
2386 qos: 0,
2387 dup: false,
2388 length: 0
2389}, Buffer.from([
2390 192, 0 // Header
2391]))
2392
2393// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
2394testParseError('Invalid header flag bits, must be 0x0 for pingreq packet', Buffer.from([
2395 193, 0 // Header
2396]))
2397
2398testParseGenerate('pingresp', {
2399 cmd: 'pingresp',
2400 retain: false,
2401 qos: 0,
2402 dup: false,
2403 length: 0
2404}, Buffer.from([
2405 208, 0 // Header
2406]))
2407
2408// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
2409testParseError('Invalid header flag bits, must be 0x0 for pingresp packet', Buffer.from([
2410 209, 0 // Header
2411]))
2412
2413testParseGenerate('disconnect', {
2414 cmd: 'disconnect',
2415 retain: false,
2416 qos: 0,
2417 dup: false,
2418 length: 0
2419}, Buffer.from([
2420 224, 0 // Header
2421]))
2422
2423// Where a flag bit is marked as “Reserved” in Table 2.2 - Flag Bits, it is reserved for future use and MUST be set to the value listed in that table [MQTT-2.2.2-1]. If invalid flags are received, the receiver MUST close the Network Connection [MQTT-2.2.2-2]
2424testParseError('Invalid header flag bits, must be 0x0 for disconnect packet', Buffer.from([
2425 225, 0 // Header
2426]))
2427
2428testParseGenerate('disconnect MQTT 5', {
2429 cmd: 'disconnect',
2430 retain: false,
2431 qos: 0,
2432 dup: false,
2433 length: 34,
2434 reasonCode: 0,
2435 properties: {
2436 sessionExpiryInterval: 145,
2437 reasonString: 'test',
2438 userProperties: {
2439 test: 'test'
2440 },
2441 serverReference: 'test'
2442 }
2443}, Buffer.from([
2444 224, 34, // Header
2445 0, // reason code
2446 32, // properties length
2447 17, 0, 0, 0, 145, // sessionExpiryInterval
2448 31, 0, 4, 116, 101, 115, 116, // reasonString
2449 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
2450 28, 0, 4, 116, 101, 115, 116// serverReference
2451]), { protocolVersion: 5 })
2452
2453testParseGenerate('disconnect MQTT 5 with no properties', {
2454 cmd: 'disconnect',
2455 retain: false,
2456 qos: 0,
2457 dup: false,
2458 length: 2,
2459 reasonCode: 0
2460}, Buffer.from([
2461 224, 2, // Fixed Header (DISCONNECT, Remaining Length)
2462 0, // Reason Code (Normal Disconnection)
2463 0 // Property Length (0 => No Properties)
2464]), { protocolVersion: 5 })
2465
2466testParseError('Invalid disconnect reason code', Buffer.from([
2467 224, 2, // Fixed Header (DISCONNECT, Remaining Length)
2468 0x05, // Reason Code (Normal Disconnection)
2469 0 // Property Length (0 => No Properties)
2470]), { protocolVersion: 5 })
2471
2472testParseGenerate('auth MQTT 5', {
2473 cmd: 'auth',
2474 retain: false,
2475 qos: 0,
2476 dup: false,
2477 length: 36,
2478 reasonCode: 0,
2479 properties: {
2480 authenticationMethod: 'test',
2481 authenticationData: Buffer.from([0, 1, 2, 3]),
2482 reasonString: 'test',
2483 userProperties: {
2484 test: 'test'
2485 }
2486 }
2487}, Buffer.from([
2488 240, 36, // Header
2489 0, // reason code
2490 34, // properties length
2491 21, 0, 4, 116, 101, 115, 116, // auth method
2492 22, 0, 4, 0, 1, 2, 3, // auth data
2493 31, 0, 4, 116, 101, 115, 116, // reasonString
2494 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116 // userProperties
2495]), { protocolVersion: 5 })
2496
2497testParseError('Invalid auth reason code', Buffer.from([
2498 240, 2, // Fixed Header (DISCONNECT, Remaining Length)
2499 0x17, // Reason Code
2500 0 // Property Length (0 => No Properties)
2501]), { protocolVersion: 5 })
2502
2503testGenerateError('Invalid protocolId', {
2504 cmd: 'connect',
2505 retain: false,
2506 qos: 0,
2507 dup: false,
2508 length: 54,
2509 protocolId: 42,
2510 protocolVersion: 3,
2511 will: {
2512 retain: true,
2513 qos: 2,
2514 topic: 'topic',
2515 payload: 'payload'
2516 },
2517 clean: true,
2518 keepalive: 30,
2519 clientId: 'test',
2520 username: 'username',
2521 password: 'password'
2522})
2523
2524testGenerateError('Invalid protocol version', {
2525 cmd: 'connect',
2526 retain: false,
2527 qos: 0,
2528 dup: false,
2529 length: 54,
2530 protocolId: 'MQIsdp',
2531 protocolVersion: 1,
2532 will: {
2533 retain: true,
2534 qos: 2,
2535 topic: 'topic',
2536 payload: 'payload'
2537 },
2538 clean: true,
2539 keepalive: 30,
2540 clientId: 'test',
2541 username: 'username',
2542 password: 'password'
2543})
2544
2545testGenerateError('clientId must be supplied before 3.1.1', {
2546 cmd: 'connect',
2547 retain: false,
2548 qos: 0,
2549 dup: false,
2550 length: 54,
2551 protocolId: 'MQIsdp',
2552 protocolVersion: 3,
2553 will: {
2554 retain: true,
2555 qos: 2,
2556 topic: 'topic',
2557 payload: 'payload'
2558 },
2559 clean: true,
2560 keepalive: 30,
2561 username: 'username',
2562 password: 'password'
2563})
2564
2565testGenerateError('clientId must be given if cleanSession set to 0', {
2566 cmd: 'connect',
2567 retain: false,
2568 qos: 0,
2569 dup: false,
2570 length: 54,
2571 protocolId: 'MQTT',
2572 protocolVersion: 4,
2573 will: {
2574 retain: true,
2575 qos: 2,
2576 topic: 'topic',
2577 payload: 'payload'
2578 },
2579 clean: false,
2580 keepalive: 30,
2581 username: 'username',
2582 password: 'password'
2583})
2584
2585testGenerateError('Invalid keepalive', {
2586 cmd: 'connect',
2587 retain: false,
2588 qos: 0,
2589 dup: false,
2590 length: 54,
2591 protocolId: 'MQIsdp',
2592 protocolVersion: 3,
2593 will: {
2594 retain: true,
2595 qos: 2,
2596 topic: 'topic',
2597 payload: 'payload'
2598 },
2599 clean: true,
2600 keepalive: 'hello',
2601 clientId: 'test',
2602 username: 'username',
2603 password: 'password'
2604})
2605
2606testGenerateError('Invalid keepalive', {
2607 cmd: 'connect',
2608 keepalive: 3.1416
2609})
2610
2611testGenerateError('Invalid will', {
2612 cmd: 'connect',
2613 retain: false,
2614 qos: 0,
2615 dup: false,
2616 length: 54,
2617 protocolId: 'MQIsdp',
2618 protocolVersion: 3,
2619 will: 42,
2620 clean: true,
2621 keepalive: 30,
2622 clientId: 'test',
2623 username: 'username',
2624 password: 'password'
2625})
2626
2627testGenerateError('Invalid will topic', {
2628 cmd: 'connect',
2629 retain: false,
2630 qos: 0,
2631 dup: false,
2632 length: 54,
2633 protocolId: 'MQIsdp',
2634 protocolVersion: 3,
2635 will: {
2636 retain: true,
2637 qos: 2,
2638 payload: 'payload'
2639 },
2640 clean: true,
2641 keepalive: 30,
2642 clientId: 'test',
2643 username: 'username',
2644 password: 'password'
2645})
2646
2647testGenerateError('Invalid will payload', {
2648 cmd: 'connect',
2649 retain: false,
2650 qos: 0,
2651 dup: false,
2652 length: 54,
2653 protocolId: 'MQIsdp',
2654 protocolVersion: 3,
2655 will: {
2656 retain: true,
2657 qos: 2,
2658 topic: 'topic',
2659 payload: 42
2660 },
2661 clean: true,
2662 keepalive: 30,
2663 clientId: 'test',
2664 username: 'username',
2665 password: 'password'
2666})
2667
2668testGenerateError('Invalid username', {
2669 cmd: 'connect',
2670 retain: false,
2671 qos: 0,
2672 dup: false,
2673 length: 54,
2674 protocolId: 'MQIsdp',
2675 protocolVersion: 3,
2676 will: {
2677 retain: true,
2678 qos: 2,
2679 topic: 'topic',
2680 payload: 'payload'
2681 },
2682 clean: true,
2683 keepalive: 30,
2684 clientId: 'test',
2685 username: 42,
2686 password: 'password'
2687})
2688
2689testGenerateError('Invalid password', {
2690 cmd: 'connect',
2691 retain: false,
2692 qos: 0,
2693 dup: false,
2694 length: 54,
2695 protocolId: 'MQIsdp',
2696 protocolVersion: 3,
2697 will: {
2698 retain: true,
2699 qos: 2,
2700 topic: 'topic',
2701 payload: 'payload'
2702 },
2703 clean: true,
2704 keepalive: 30,
2705 clientId: 'test',
2706 username: 'username',
2707 password: 42
2708})
2709
2710testGenerateError('Username is required to use password', {
2711 cmd: 'connect',
2712 retain: false,
2713 qos: 0,
2714 dup: false,
2715 length: 54,
2716 protocolId: 'MQIsdp',
2717 protocolVersion: 3,
2718 will: {
2719 retain: true,
2720 qos: 2,
2721 topic: 'topic',
2722 payload: 'payload'
2723 },
2724 clean: true,
2725 keepalive: 30,
2726 clientId: 'test',
2727 password: 'password'
2728})
2729
2730testGenerateError('Invalid messageExpiryInterval: -4321', {
2731 cmd: 'publish',
2732 retain: true,
2733 qos: 2,
2734 dup: true,
2735 length: 60,
2736 topic: 'test',
2737 payload: Buffer.from('test'),
2738 messageId: 10,
2739 properties: {
2740 payloadFormatIndicator: true,
2741 messageExpiryInterval: -4321,
2742 topicAlias: 100,
2743 responseTopic: 'topic',
2744 correlationData: Buffer.from([1, 2, 3, 4]),
2745 userProperties: {
2746 test: 'test'
2747 },
2748 subscriptionIdentifier: 120,
2749 contentType: 'test'
2750 }
2751}, { protocolVersion: 5 })
2752
2753testGenerateError('Invalid topicAlias: -100', {
2754 cmd: 'publish',
2755 retain: true,
2756 qos: 2,
2757 dup: true,
2758 length: 60,
2759 topic: 'test',
2760 payload: Buffer.from('test'),
2761 messageId: 10,
2762 properties: {
2763 payloadFormatIndicator: true,
2764 messageExpiryInterval: 4321,
2765 topicAlias: -100,
2766 responseTopic: 'topic',
2767 correlationData: Buffer.from([1, 2, 3, 4]),
2768 userProperties: {
2769 test: 'test'
2770 },
2771 subscriptionIdentifier: 120,
2772 contentType: 'test'
2773 }
2774}, { protocolVersion: 5 })
2775
2776testGenerateError('Invalid subscriptionIdentifier: -120', {
2777 cmd: 'publish',
2778 retain: true,
2779 qos: 2,
2780 dup: true,
2781 length: 60,
2782 topic: 'test',
2783 payload: Buffer.from('test'),
2784 messageId: 10,
2785 properties: {
2786 payloadFormatIndicator: true,
2787 messageExpiryInterval: 4321,
2788 topicAlias: 100,
2789 responseTopic: 'topic',
2790 correlationData: Buffer.from([1, 2, 3, 4]),
2791 userProperties: {
2792 test: 'test'
2793 },
2794 subscriptionIdentifier: -120,
2795 contentType: 'test'
2796 }
2797}, { protocolVersion: 5 })
2798
2799test('support cork', t => {
2800 t.plan(9)
2801
2802 const dest = WS()
2803
2804 dest._write = (chunk, enc, cb) => {
2805 t.pass('_write called')
2806 cb()
2807 }
2808
2809 mqtt.writeToStream({
2810 cmd: 'connect',
2811 retain: false,
2812 qos: 0,
2813 dup: false,
2814 length: 18,
2815 protocolId: 'MQIsdp',
2816 protocolVersion: 3,
2817 clean: false,
2818 keepalive: 30,
2819 clientId: 'test'
2820 }, dest)
2821
2822 dest.end()
2823})
2824
2825// The following test case was designed after experiencing errors
2826// when trying to connect with tls on a non tls mqtt port
2827// the specific behaviour is:
2828// - first byte suggests this is a connect message
2829// - second byte suggests message length to be smaller than buffer length
2830// thus payload processing starts
2831// - the first two bytes suggest a protocol identifier string length
2832// that leads the parser pointer close to the end of the buffer
2833// - when trying to read further connect flags the buffer produces
2834// a "out of range" Error
2835//
2836testParseError('Packet too short', Buffer.from([
2837 16, 9,
2838 0, 6,
2839 77, 81, 73, 115, 100, 112,
2840 3
2841]))
2842
2843// CONNECT Packets that show other protocol IDs than
2844// the valid values MQTT and MQIsdp should cause an error
2845// those packets are a hint that this is not a mqtt connection
2846testParseError('Invalid protocolId', Buffer.from([
2847 16, 18,
2848 0, 6,
2849 65, 65, 65, 65, 65, 65, // AAAAAA
2850 3, // Protocol version
2851 0, // Connect flags
2852 0, 10, // Keepalive
2853 0, 4, // Client ID length
2854 116, 101, 115, 116 // Client ID
2855]))
2856
2857// CONNECT Packets that contain an unsupported protocol version
2858// Flag (i.e. not `3` or `4` or '5') should cause an error
2859testParseError('Invalid protocol version', Buffer.from([
2860 16, 18,
2861 0, 6,
2862 77, 81, 73, 115, 100, 112, // Protocol ID
2863 1, // Protocol version
2864 0, // Connect flags
2865 0, 10, // Keepalive
2866 0, 4, // Client ID length
2867 116, 101, 115, 116 // Client ID
2868]))
2869
2870// When a packet contains a string in the variable header and the
2871// given string length of this exceeds the overall length of the packet that
2872// was specified in the fixed header, parsing must fail.
2873// this case simulates this behavior with the protocol ID string of the
2874// CONNECT packet. The fixed header suggests a remaining length of 8 bytes
2875// which would be exceeded by the string length of 15
2876// in this case, a protocol ID parse error is expected
2877testParseError('Cannot parse protocolId', Buffer.from([
2878 16, 8, // Fixed header
2879 0, 15, // string length 15 --> 15 > 8 --> error!
2880 77, 81, 73, 115, 100, 112,
2881 77, 81, 73, 115, 100, 112,
2882 77, 81, 73, 115, 100, 112,
2883 77, 81, 73, 115, 100, 112,
2884 77, 81, 73, 115, 100, 112,
2885 77, 81, 73, 115, 100, 112,
2886 77, 81, 73, 115, 100, 112,
2887 77, 81, 73, 115, 100, 112
2888]))
2889
2890testParseError('Unknown property', Buffer.from([
2891 61, 60, // Header
2892 0, 4, // Topic length
2893 116, 101, 115, 116, // Topic (test)
2894 0, 10, // Message ID
2895 47, // properties length
2896 126, 1, // unknown property
2897 2, 0, 0, 16, 225, // message expiry interval
2898 35, 0, 100, // topicAlias
2899 8, 0, 5, 116, 111, 112, 105, 99, // response topic
2900 9, 0, 4, 1, 2, 3, 4, // correlationData
2901 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties
2902 11, 120, // subscriptionIdentifier
2903 3, 0, 4, 116, 101, 115, 116, // content type
2904 116, 101, 115, 116 // Payload (test)
2905]), { protocolVersion: 5 })
2906
2907testParseError('Not supported auth packet for this version MQTT', Buffer.from([
2908 240, 36, // Header
2909 0, // reason code
2910 34, // properties length
2911 21, 0, 4, 116, 101, 115, 116, // auth method
2912 22, 0, 4, 0, 1, 2, 3, // auth data
2913 31, 0, 4, 116, 101, 115, 116, // reasonString
2914 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116 // userProperties
2915]))
2916
2917// When a Subscribe packet contains a topic_filter and the given
2918// length is topic_filter.length + 1 then the last byte (requested QoS) is interpreted as topic_filter
2919// reading the requested_qos at the end causes 'Index out of range' read
2920testParseError('Malformed Subscribe Payload', Buffer.from([
2921 130, 14, // subscribe header and remaining length
2922 0, 123, // packet ID
2923 0, 10, // topic filter length
2924 104, 105, 106, 107, 108, 47, 109, 110, 111, // topic filter with length of 9 bytes
2925 0 // requested QoS
2926]))
2927
2928test('Cannot parse property code type', t => {
2929 const packets = Buffer.from([
2930 16, 16, 0, 4, 77, 81, 84, 84, 5, 2, 0, 60, 3, 33, 0, 20, 0, 0, 98, 2, 211, 1, 224, 2, 0, 32
2931 ])
2932
2933 t.plan(3)
2934
2935 const parser = mqtt.parser()
2936
2937 parser.on('error', err => {
2938 t.equal(err.message, 'Cannot parse property code type', 'expected error message')
2939 t.end()
2940 })
2941
2942 parser.on('packet', (packet) => {
2943 t.pass('Packet parsed')
2944 })
2945
2946 parser.parse(packets)
2947})
2948
2949testWriteToStreamError('Invalid command', {
2950 cmd: 'invalid'
2951})
2952
2953testWriteToStreamError('Invalid protocolId', {
2954 cmd: 'connect',
2955 protocolId: {}
2956})
2957
2958test('userProperties null prototype', t => {
2959 t.plan(3)
2960
2961 const packet = mqtt.generate({
2962 cmd: 'connect',
2963 retain: false,
2964 qos: 0,
2965 dup: false,
2966 length: 125,
2967 protocolId: 'MQTT',
2968 protocolVersion: 5,
2969 will: {
2970 retain: true,
2971 qos: 2,
2972 properties: {
2973 willDelayInterval: 1234,
2974 payloadFormatIndicator: false,
2975 messageExpiryInterval: 4321,
2976 contentType: 'test',
2977 responseTopic: 'topic',
2978 correlationData: Buffer.from([1, 2, 3, 4]),
2979 userProperties: {
2980 test: 'test'
2981 }
2982 },
2983 topic: 'topic',
2984 payload: Buffer.from([4, 3, 2, 1])
2985 },
2986 clean: true,
2987 keepalive: 30,
2988 properties: {
2989 sessionExpiryInterval: 1234,
2990 receiveMaximum: 432,
2991 maximumPacketSize: 100,
2992 topicAliasMaximum: 456,
2993 requestResponseInformation: true,
2994 requestProblemInformation: true,
2995 userProperties: {
2996 test: 'test'
2997 },
2998 authenticationMethod: 'test',
2999 authenticationData: Buffer.from([1, 2, 3, 4])
3000 },
3001 clientId: 'test'
3002 })
3003
3004 const parser = mqtt.parser()
3005
3006 parser.on('packet', packet => {
3007 t.equal(packet.cmd, 'connect')
3008 t.equal(Object.getPrototypeOf(packet.properties.userProperties), null)
3009 t.equal(Object.getPrototypeOf(packet.will.properties.userProperties), null)
3010 })
3011
3012 parser.parse(packet)
3013})
3014
3015test('stops parsing after first error', t => {
3016 t.plan(4)
3017
3018 const parser = mqtt.parser()
3019
3020 let packetCount = 0
3021 let errorCount = 0
3022 let expectedPackets = 1
3023 let expectedErrors = 1
3024
3025 parser.on('packet', packet => {
3026 t.ok(++packetCount <= expectedPackets, `expected <= ${expectedPackets} packets`)
3027 })
3028
3029 parser.on('error', erroneous => {
3030 t.ok(++errorCount <= expectedErrors, `expected <= ${expectedErrors} errors`)
3031 })
3032
3033 parser.parse(Buffer.from([
3034 // First, a valid connect packet:
3035
3036 16, 12, // Header
3037 0, 4, // Protocol ID length
3038 77, 81, 84, 84, // Protocol ID
3039 4, // Protocol version
3040 2, // Connect flags
3041 0, 30, // Keepalive
3042 0, 0, // Client ID length
3043
3044 // Then an invalid subscribe packet:
3045
3046 128, 9, // Header (subscribeqos=0length=9)
3047 0, 6, // Message ID (6)
3048 0, 4, // Topic length,
3049 116, 101, 115, 116, // Topic (test)
3050 0, // Qos (0)
3051
3052 // And another invalid subscribe packet:
3053
3054 128, 9, // Header (subscribeqos=0length=9)
3055 0, 6, // Message ID (6)
3056 0, 4, // Topic length,
3057 116, 101, 115, 116, // Topic (test)
3058 0, // Qos (0)
3059
3060 // Finally, a valid disconnect packet:
3061
3062 224, 0 // Header
3063 ]))
3064
3065 // Calling parse again clears the error and continues parsing
3066 packetCount = 0
3067 errorCount = 0
3068 expectedPackets = 2
3069 expectedErrors = 0
3070
3071 parser.parse(Buffer.from([
3072 // Connect:
3073
3074 16, 12, // Header
3075 0, 4, // Protocol ID length
3076 77, 81, 84, 84, // Protocol ID
3077 4, // Protocol version
3078 2, // Connect flags
3079 0, 30, // Keepalive
3080 0, 0, // Client ID length
3081
3082 // Disconnect:
3083
3084 224, 0 // Header
3085 ]))
3086})
3087
3088testGenerateErrorMultipleCmds([
3089 'publish',
3090 'puback',
3091 'pubrec',
3092 'pubrel',
3093 'subscribe',
3094 'suback',
3095 'unsubscribe',
3096 'unsuback'
3097], 'Invalid messageId', {
3098 qos: 1, // required for publish
3099 topic: 'test', // required for publish
3100 messageId: 'a'
3101}, {})