UNPKG

mqtt-packet

Version:

Parse and generate MQTT packets like a breeze

1,934 lines (1,797 loc) 81.2 kB
const util = require('util') const test = require('tape') const mqtt = require('./') const WS = require('readable-stream').Writable function normalExpectedObject (object) { if (object.username != null) object.username = object.username.toString() if (object.password != null) object.password = Buffer.from(object.password) return object } function testParseGenerate (name, object, buffer, opts) { test(`${name} parse`, t => { t.plan(2) const parser = mqtt.parser(opts) const expected = object const fixture = buffer parser.on('packet', packet => { if (packet.cmd !== 'publish') { delete packet.topic delete packet.payload } t.deepLooseEqual(packet, normalExpectedObject(expected), 'expected packet') }) parser.on('error', err => { t.fail(err) }) t.equal(parser.parse(fixture), 0, 'remaining bytes') }) test(`${name} generate`, t => { // For really large buffers, the expanded hex string can be so long as to // generate an error in nodejs 14.x, so only do the test with extra output // for relatively small buffers. const bigLength = 10000 const generatedBuffer = mqtt.generate(object, opts) if (generatedBuffer.length < bigLength && buffer.length < bigLength) { t.equal(generatedBuffer.toString('hex'), buffer.toString('hex')) } else { const bufferOkay = generatedBuffer.equals(buffer) if (bufferOkay) { t.pass() } else { // Output abbreviated representations of the buffers. t.comment('Expected:\n' + util.inspect(buffer)) t.comment('Got:\n' + util.inspect(generatedBuffer)) t.fail('Large buffers not equal') } } t.end() }) test(`${name} mirror`, t => { t.plan(2) const parser = mqtt.parser(opts) const expected = object const fixture = mqtt.generate(object, opts) parser.on('packet', packet => { if (packet.cmd !== 'publish') { delete packet.topic delete packet.payload } t.deepLooseEqual(packet, normalExpectedObject(expected), 'expected packet') }) parser.on('error', err => { t.fail(err) }) t.equal(parser.parse(fixture), 0, 'remaining bytes') }) test(`${name} writeToStream`, t => { const stream = WS() stream.write = () => true stream.on('error', (err) => t.fail(err)) const result = mqtt.writeToStream(object, stream, opts) t.equal(result, true, 'should return true') t.end() }) } // the API allows to pass strings as buffers to writeToStream and generate // parsing them back will result in a string so only generate and compare to buffer function testGenerateOnly (name, object, buffer, opts) { test(name, t => { t.equal(mqtt.generate(object, opts).toString('hex'), buffer.toString('hex')) t.end() }) } function testParseOnly (name, object, buffer, opts) { test(name, t => { const parser = mqtt.parser(opts) // const expected = object // const fixture = buffer t.plan(2 + Object.keys(object).length) parser.on('packet', packet => { t.equal(Object.keys(object).length, Object.keys(packet).length, 'key count') Object.keys(object).forEach(key => { t.deepEqual(packet[key], object[key], `expected packet property ${key}`) }) }) t.equal(parser.parse(buffer), 0, 'remaining bytes') t.end() }) } function testParseError (expected, fixture, opts) { test(expected, t => { t.plan(1) const parser = mqtt.parser(opts) parser.on('error', err => { t.equal(err.message, expected, 'expected error message') }) parser.on('packet', () => { t.fail('parse errors should not be followed by packet events') }) parser.parse(fixture) t.end() }) } function testGenerateError (expected, fixture, opts, name) { test(name || expected, t => { t.plan(1) try { mqtt.generate(fixture, opts) } catch (err) { t.equal(expected, err.message) } t.end() }) } function testGenerateErrorMultipleCmds (cmds, expected, fixture, opts) { cmds.forEach(cmd => { const obj = Object.assign({}, fixture) obj.cmd = cmd testGenerateError(expected, obj, opts, `${expected} on ${cmd}`) } ) } function testParseGenerateDefaults (name, object, buffer, generated, opts) { testParseOnly(`${name} parse`, generated, buffer, opts) testGenerateOnly(`${name} generate`, object, buffer, opts) } function testParseAndGenerate (name, object, buffer, opts) { testParseOnly(`${name} parse`, object, buffer, opts) testGenerateOnly(`${name} generate`, object, buffer, opts) } function testWriteToStreamError (expected, fixture) { test(`writeToStream ${expected} error`, t => { t.plan(2) const stream = WS() stream.write = () => t.fail('should not have called write') stream.on('error', () => t.pass('error emitted')) const result = mqtt.writeToStream(fixture, stream) t.false(result, 'result should be false') }) } test('cacheNumbers get/set/unset', t => { t.true(mqtt.writeToStream.cacheNumbers, 'initial state of cacheNumbers is enabled') mqtt.writeToStream.cacheNumbers = false t.false(mqtt.writeToStream.cacheNumbers, 'cacheNumbers can be disabled') mqtt.writeToStream.cacheNumbers = true t.true(mqtt.writeToStream.cacheNumbers, 'cacheNumbers can be enabled') t.end() }) test('disabled numbers cache', t => { const stream = WS() const message = { cmd: 'publish', retain: false, qos: 0, dup: false, length: 10, topic: Buffer.from('test'), payload: Buffer.from('test') } const expected = Buffer.from([ 48, 10, // Header 0, 4, // Topic length 116, 101, 115, 116, // Topic (test) 116, 101, 115, 116 // Payload (test) ]) let written = Buffer.alloc(0) stream.write = (chunk) => { written = Buffer.concat([written, chunk]) } mqtt.writeToStream.cacheNumbers = false mqtt.writeToStream(message, stream) t.deepEqual(written, expected, 'written buffer is expected') mqtt.writeToStream.cacheNumbers = true stream.end() t.end() }) testGenerateError('Unknown command', {}) testParseError('Not supported', Buffer.from([0, 1, 0]), {}) // Length header field testParseError('Invalid variable byte integer', Buffer.from( [16, 255, 255, 255, 255] ), {}) testParseError('Invalid variable byte integer', Buffer.from( [16, 255, 255, 255, 128] ), {}) testParseError('Invalid variable byte integer', Buffer.from( [16, 255, 255, 255, 255, 1] ), {}) testParseError('Invalid variable byte integer', Buffer.from( [16, 255, 255, 255, 255, 127] ), {}) testParseError('Invalid variable byte integer', Buffer.from( [16, 255, 255, 255, 255, 128] ), {}) testParseError('Invalid variable byte integer', Buffer.from( [16, 255, 255, 255, 255, 255, 1] ), {}) testParseGenerate('minimal connect', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 18, protocolId: 'MQIsdp', protocolVersion: 3, clean: false, keepalive: 30, clientId: 'test' }, Buffer.from([ 16, 18, // Header 0, 6, // Protocol ID length 77, 81, 73, 115, 100, 112, // Protocol ID 3, // Protocol version 0, // Connect flags 0, 30, // Keepalive 0, 4, // Client ID length 116, 101, 115, 116 // Client ID ])) testGenerateOnly('minimal connect with clientId as Buffer', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 18, protocolId: 'MQIsdp', protocolVersion: 3, clean: false, keepalive: 30, clientId: Buffer.from('test') }, Buffer.from([ 16, 18, // Header 0, 6, // Protocol ID length 77, 81, 73, 115, 100, 112, // Protocol ID 3, // Protocol version 0, // Connect flags 0, 30, // Keepalive 0, 4, // Client ID length 116, 101, 115, 116 // Client ID ])) testParseGenerate('connect MQTT bridge 131', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 18, protocolId: 'MQIsdp', protocolVersion: 3, bridgeMode: true, clean: false, keepalive: 30, clientId: 'test' }, Buffer.from([ 16, 18, // Header 0, 6, // Protocol ID length 77, 81, 73, 115, 100, 112, // Protocol ID 131, // Protocol version 0, // Connect flags 0, 30, // Keepalive 0, 4, // Client ID length 116, 101, 115, 116 // Client ID ])) testParseGenerate('connect MQTT bridge 132', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 18, protocolId: 'MQIsdp', protocolVersion: 4, bridgeMode: true, clean: false, keepalive: 30, clientId: 'test' }, Buffer.from([ 16, 18, // Header 0, 6, // Protocol ID length 77, 81, 73, 115, 100, 112, // Protocol ID 132, // Protocol version 0, // Connect flags 0, 30, // Keepalive 0, 4, // Client ID length 116, 101, 115, 116 // Client ID ])) testParseGenerate('connect MQTT 5', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 125, protocolId: 'MQTT', protocolVersion: 5, will: { retain: true, qos: 2, properties: { willDelayInterval: 1234, payloadFormatIndicator: false, messageExpiryInterval: 4321, contentType: 'test', responseTopic: 'topic', correlationData: Buffer.from([1, 2, 3, 4]), userProperties: { test: 'test' } }, topic: 'topic', payload: Buffer.from([4, 3, 2, 1]) }, clean: true, keepalive: 30, properties: { sessionExpiryInterval: 1234, receiveMaximum: 432, maximumPacketSize: 100, topicAliasMaximum: 456, requestResponseInformation: true, requestProblemInformation: true, userProperties: { test: 'test' }, authenticationMethod: 'test', authenticationData: Buffer.from([1, 2, 3, 4]) }, clientId: 'test' }, Buffer.from([ 16, 125, // Header 0, 4, // Protocol ID length 77, 81, 84, 84, // Protocol ID 5, // Protocol version 54, // Connect flags 0, 30, // Keepalive 47, // properties length 17, 0, 0, 4, 210, // sessionExpiryInterval 33, 1, 176, // receiveMaximum 39, 0, 0, 0, 100, // maximumPacketSize 34, 1, 200, // topicAliasMaximum 25, 1, // requestResponseInformation 23, 1, // requestProblemInformation, 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties, 21, 0, 4, 116, 101, 115, 116, // authenticationMethod 22, 0, 4, 1, 2, 3, 4, // authenticationData 0, 4, // Client ID length 116, 101, 115, 116, // Client ID 47, // will properties 24, 0, 0, 4, 210, // will delay interval 1, 0, // payload format indicator 2, 0, 0, 16, 225, // message expiry interval 3, 0, 4, 116, 101, 115, 116, // content type 8, 0, 5, 116, 111, 112, 105, 99, // response topic 9, 0, 4, 1, 2, 3, 4, // corelation data 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // user properties 0, 5, // Will topic length 116, 111, 112, 105, 99, // Will topic 0, 4, // Will payload length 4, 3, 2, 1// Will payload ])) testParseGenerate('connect MQTT 5 with will properties but with empty will payload', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 121, protocolId: 'MQTT', protocolVersion: 5, will: { retain: true, qos: 2, properties: { willDelayInterval: 1234, payloadFormatIndicator: false, messageExpiryInterval: 4321, contentType: 'test', responseTopic: 'topic', correlationData: Buffer.from([1, 2, 3, 4]), userProperties: { test: 'test' } }, topic: 'topic', payload: Buffer.from([]) }, clean: true, keepalive: 30, properties: { sessionExpiryInterval: 1234, receiveMaximum: 432, maximumPacketSize: 100, topicAliasMaximum: 456, requestResponseInformation: true, requestProblemInformation: true, userProperties: { test: 'test' }, authenticationMethod: 'test', authenticationData: Buffer.from([1, 2, 3, 4]) }, clientId: 'test' }, Buffer.from([ 16, 121, // Header 0, 4, // Protocol ID length 77, 81, 84, 84, // Protocol ID 5, // Protocol version 54, // Connect flags 0, 30, // Keepalive 47, // properties length 17, 0, 0, 4, 210, // sessionExpiryInterval 33, 1, 176, // receiveMaximum 39, 0, 0, 0, 100, // maximumPacketSize 34, 1, 200, // topicAliasMaximum 25, 1, // requestResponseInformation 23, 1, // requestProblemInformation, 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties, 21, 0, 4, 116, 101, 115, 116, // authenticationMethod 22, 0, 4, 1, 2, 3, 4, // authenticationData 0, 4, // Client ID length 116, 101, 115, 116, // Client ID 47, // will properties 24, 0, 0, 4, 210, // will delay interval 1, 0, // payload format indicator 2, 0, 0, 16, 225, // message expiry interval 3, 0, 4, 116, 101, 115, 116, // content type 8, 0, 5, 116, 111, 112, 105, 99, // response topic 9, 0, 4, 1, 2, 3, 4, // corelation data 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // user properties 0, 5, // Will topic length 116, 111, 112, 105, 99, // Will topic 0, 0 // Will payload length ])) testParseGenerate('connect MQTT 5 w/o will properties', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 78, protocolId: 'MQTT', protocolVersion: 5, will: { retain: true, qos: 2, topic: 'topic', payload: Buffer.from([4, 3, 2, 1]) }, clean: true, keepalive: 30, properties: { sessionExpiryInterval: 1234, receiveMaximum: 432, maximumPacketSize: 100, topicAliasMaximum: 456, requestResponseInformation: true, requestProblemInformation: true, userProperties: { test: 'test' }, authenticationMethod: 'test', authenticationData: Buffer.from([1, 2, 3, 4]) }, clientId: 'test' }, Buffer.from([ 16, 78, // Header 0, 4, // Protocol ID length 77, 81, 84, 84, // Protocol ID 5, // Protocol version 54, // Connect flags 0, 30, // Keepalive 47, // properties length 17, 0, 0, 4, 210, // sessionExpiryInterval 33, 1, 176, // receiveMaximum 39, 0, 0, 0, 100, // maximumPacketSize 34, 1, 200, // topicAliasMaximum 25, 1, // requestResponseInformation 23, 1, // requestProblemInformation, 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties, 21, 0, 4, 116, 101, 115, 116, // authenticationMethod 22, 0, 4, 1, 2, 3, 4, // authenticationData 0, 4, // Client ID length 116, 101, 115, 116, // Client ID 0, // will properties 0, 5, // Will topic length 116, 111, 112, 105, 99, // Will topic 0, 4, // Will payload length 4, 3, 2, 1// Will payload ])) testParseGenerate('no clientId with 3.1.1', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 12, protocolId: 'MQTT', protocolVersion: 4, clean: true, keepalive: 30, clientId: '' }, Buffer.from([ 16, 12, // Header 0, 4, // Protocol ID length 77, 81, 84, 84, // Protocol ID 4, // Protocol version 2, // Connect flags 0, 30, // Keepalive 0, 0 // Client ID length ])) testParseGenerateDefaults('no clientId with 5.0', { cmd: 'connect', protocolId: 'MQTT', protocolVersion: 5, clean: true, keepalive: 60, properties: { receiveMaximum: 20 }, clientId: '' }, Buffer.from( [16, 16, 0, 4, 77, 81, 84, 84, 5, 2, 0, 60, 3, 33, 0, 20, 0, 0] ), { cmd: 'connect', retain: false, qos: 0, dup: false, length: 16, topic: null, payload: null, protocolId: 'MQTT', protocolVersion: 5, clean: true, keepalive: 60, properties: { receiveMaximum: 20 }, clientId: '' }, { protocolVersion: 5 }) testParseGenerateDefaults('utf-8 clientId with 5.0', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 23, protocolId: 'MQTT', protocolVersion: 4, clean: true, keepalive: 30, clientId: 'Ŧėśt🜄' }, Buffer.from([ 16, 23, // Header 0, 4, // Protocol ID length 77, 81, 84, 84, // Protocol ID 4, // Protocol version 2, // Connect flags 0, 30, // Keepalive 0, 11, // Client ID length 197, 166, // Ŧ (UTF-8: 0xc5a6) 196, 151, // ė (UTF-8: 0xc497) 197, 155, // ś (utf-8: 0xc59b) 116, // t (utf-8: 0x74) 240, 159, 156, 132 // 🜄 (utf-8: 0xf09f9c84) ]), { cmd: 'connect', retain: false, qos: 0, dup: false, length: 23, topic: null, payload: null, protocolId: 'MQTT', protocolVersion: 4, clean: true, keepalive: 30, clientId: 'Ŧėśt🜄' }, { protocol: 5 }) testParseGenerateDefaults('default connect', { cmd: 'connect', clientId: 'test' }, Buffer.from([ 16, 16, 0, 4, 77, 81, 84, 84, 4, 2, 0, 0, 0, 4, 116, 101, 115, 116 ]), { cmd: 'connect', retain: false, qos: 0, dup: false, length: 16, topic: null, payload: null, protocolId: 'MQTT', protocolVersion: 4, clean: true, keepalive: 0, clientId: 'test' }) testParseAndGenerate('Version 4 CONACK', { cmd: 'connack', retain: false, qos: 0, dup: false, length: 2, topic: null, payload: null, sessionPresent: false, returnCode: 1 }, Buffer.from([ 32, 2, // Fixed Header (CONNACK, Remaining Length) 0, 1 // Variable Header (Session not present, Connection Refused - unacceptable protocol version) ]), {}) // Default protocolVersion (4) testParseAndGenerate('Version 5 CONACK', { cmd: 'connack', retain: false, qos: 0, dup: false, length: 3, topic: null, payload: null, sessionPresent: false, reasonCode: 140 }, Buffer.from([ 32, 3, // Fixed Header (CONNACK, Remaining Length) 0, 140, // Variable Header (Session not present, Bad authentication method) 0 // Property Length Zero ]), { protocolVersion: 5 }) testParseOnly('Version 4 CONACK in Version 5 mode', { cmd: 'connack', retain: false, qos: 0, dup: false, length: 2, topic: null, payload: null, sessionPresent: false, reasonCode: 1 // a version 4 return code stored in the version 5 reasonCode because this client is in version 5 }, Buffer.from([ 32, 2, // Fixed Header (CONNACK, Remaining Length) 0, 1 // Variable Header (Session not present, Connection Refused - unacceptable protocol version) ]), { protocolVersion: 5 }) // message is in version 4 format, but this client is in version 5 mode testParseOnly('Version 5 PUBACK test 1', { cmd: 'puback', messageId: 42, retain: false, qos: 0, dup: false, length: 2, topic: null, payload: null, reasonCode: 0 }, Buffer.from([ 64, 2, // Fixed Header (PUBACK, Remaining Length) 0, 42 // Variable Header (2 Bytes: Packet Identifier 42, Implied Reason code: Success, Implied no properties) ]), { protocolVersion: 5 } ) testParseAndGenerate('Version 5 PUBACK test 2', { cmd: 'puback', messageId: 42, retain: false, qos: 0, dup: false, length: 2, topic: null, payload: null, reasonCode: 0 }, Buffer.from([ 64, 2, // Fixed Header (PUBACK, Remaining Length) 0, 42 // Variable Header (2 Bytes: Packet Identifier 42, Implied reason code: 0 Success, Implied no properties) ]), { protocolVersion: 5 } ) testParseOnly('Version 5 PUBACK test 2.1', { cmd: 'puback', messageId: 42, retain: false, qos: 0, dup: false, length: 3, topic: null, payload: null, reasonCode: 0 }, Buffer.from([ 64, 3, // Fixed Header (PUBACK, Remaining Length) 0, 42, 0 // Variable Header (2 Bytes: Packet Identifier 42, Reason code: 0 Success, Implied no properties) ]), { protocolVersion: 5 } ) testParseOnly('Version 5 PUBACK test 3', { cmd: 'puback', messageId: 42, retain: false, qos: 0, dup: false, length: 4, topic: null, payload: null, reasonCode: 0 }, Buffer.from([ 64, 4, // Fixed Header (PUBACK, Remaining Length) 0, 42, 0, // Variable Header (2 Bytes: Packet Identifier 42, Reason code: 0 Success) 0 // no properties ]), { protocolVersion: 5 } ) testParseOnly('Version 5 CONNACK test 1', { cmd: 'connack', retain: false, qos: 0, dup: false, length: 1, topic: null, payload: null, sessionPresent: true, reasonCode: 0 }, Buffer.from([ 32, 1, // Fixed Header (CONNACK, Remaining Length) 1 // Variable Header (Session Present: 1 => true, Implied Reason code: Success, Implied no properties) ]), { protocolVersion: 5 } ) testParseOnly('Version 5 CONNACK test 2', { cmd: 'connack', retain: false, qos: 0, dup: false, length: 2, topic: null, payload: null, sessionPresent: true, reasonCode: 0 }, Buffer.from([ 32, 2, // Fixed Header (CONNACK, Remaining Length) 1, 0 // Variable Header (Session Present: 1 => true, Connect Reason code: Success, Implied no properties) ]), { protocolVersion: 5 } ) testParseAndGenerate('Version 5 CONNACK test 3', { cmd: 'connack', retain: false, qos: 0, dup: false, length: 3, topic: null, payload: null, sessionPresent: true, reasonCode: 0 }, Buffer.from([ 32, 3, // Fixed Header (CONNACK, Remaining Length) 1, 0, // Variable Header (Session Present: 1 => true, Connect Reason code: Success) 0 // no properties ]), { protocolVersion: 5 } ) testParseOnly('Version 5 DISCONNECT test 1', { cmd: 'disconnect', retain: false, qos: 0, dup: false, length: 0, topic: null, payload: null, reasonCode: 0 }, Buffer.from([ 224, 0 // Fixed Header (DISCONNECT, Remaining Length), Implied Reason code: Normal Disconnection ]), { protocolVersion: 5 } ) testParseOnly('Version 5 DISCONNECT test 2', { cmd: 'disconnect', retain: false, qos: 0, dup: false, length: 1, topic: null, payload: null, reasonCode: 0 }, Buffer.from([ 224, 1, // Fixed Header (DISCONNECT, Remaining Length) 0 // reason Code (Normal disconnection) ]), { protocolVersion: 5 } ) testParseAndGenerate('Version 5 DISCONNECT test 3', { cmd: 'disconnect', retain: false, qos: 0, dup: false, length: 2, topic: null, payload: null, reasonCode: 0 }, Buffer.from([ 224, 2, // Fixed Header (DISCONNECT, Remaining Length) 0, // reason Code (Normal disconnection) 0 // no properties ]), { protocolVersion: 5 } ) testParseGenerate('empty will payload', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 47, protocolId: 'MQIsdp', protocolVersion: 3, will: { retain: true, qos: 2, topic: 'topic', payload: Buffer.alloc(0) }, clean: true, keepalive: 30, clientId: 'test', username: 'username', password: Buffer.from('password') }, Buffer.from([ 16, 47, // Header 0, 6, // Protocol ID length 77, 81, 73, 115, 100, 112, // Protocol ID 3, // Protocol version 246, // Connect flags 0, 30, // Keepalive 0, 4, // Client ID length 116, 101, 115, 116, // Client ID 0, 5, // Will topic length 116, 111, 112, 105, 99, // Will topic 0, 0, // Will payload length // Will payload 0, 8, // Username length 117, 115, 101, 114, 110, 97, 109, 101, // Username 0, 8, // Password length 112, 97, 115, 115, 119, 111, 114, 100 // Password ])) testParseGenerate('empty buffer username payload', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 20, protocolId: 'MQIsdp', protocolVersion: 3, clean: true, keepalive: 30, clientId: 'test', username: Buffer.from('') }, Buffer.from([ 16, 20, // Header 0, 6, // Protocol ID length 77, 81, 73, 115, 100, 112, // Protocol ID 3, // Protocol version 130, // Connect flags 0, 30, // Keepalive 0, 4, // Client ID length 116, 101, 115, 116, // Client ID 0, 0 // Username length // Empty Username payload ])) testParseGenerate('empty string username payload', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 20, protocolId: 'MQIsdp', protocolVersion: 3, clean: true, keepalive: 30, clientId: 'test', username: '' }, Buffer.from([ 16, 20, // Header 0, 6, // Protocol ID length 77, 81, 73, 115, 100, 112, // Protocol ID 3, // Protocol version 130, // Connect flags 0, 30, // Keepalive 0, 4, // Client ID length 116, 101, 115, 116, // Client ID 0, 0 // Username length // Empty Username payload ])) testParseGenerate('empty buffer password payload', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 30, protocolId: 'MQIsdp', protocolVersion: 3, clean: true, keepalive: 30, clientId: 'test', username: 'username', password: Buffer.from('') }, Buffer.from([ 16, 30, // Header 0, 6, // Protocol ID length 77, 81, 73, 115, 100, 112, // Protocol ID 3, // Protocol version 194, // Connect flags 0, 30, // Keepalive 0, 4, // Client ID length 116, 101, 115, 116, // Client ID 0, 8, // Username length 117, 115, 101, 114, 110, 97, 109, 101, // Username payload 0, 0 // Password length // Empty password payload ])) testParseGenerate('empty string password payload', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 30, protocolId: 'MQIsdp', protocolVersion: 3, clean: true, keepalive: 30, clientId: 'test', username: 'username', password: '' }, Buffer.from([ 16, 30, // Header 0, 6, // Protocol ID length 77, 81, 73, 115, 100, 112, // Protocol ID 3, // Protocol version 194, // Connect flags 0, 30, // Keepalive 0, 4, // Client ID length 116, 101, 115, 116, // Client ID 0, 8, // Username length 117, 115, 101, 114, 110, 97, 109, 101, // Username payload 0, 0 // Password length // Empty password payload ])) testParseGenerate('empty string username and password payload', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 22, protocolId: 'MQIsdp', protocolVersion: 3, clean: true, keepalive: 30, clientId: 'test', username: '', password: Buffer.from('') }, Buffer.from([ 16, 22, // Header 0, 6, // Protocol ID length 77, 81, 73, 115, 100, 112, // Protocol ID 3, // Protocol version 194, // Connect flags 0, 30, // Keepalive 0, 4, // Client ID length 116, 101, 115, 116, // Client ID 0, 0, // Username length // Empty Username payload 0, 0 // Password length // Empty password payload ])) testParseGenerate('maximal connect', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 54, protocolId: 'MQIsdp', protocolVersion: 3, will: { retain: true, qos: 2, topic: 'topic', payload: Buffer.from('payload') }, clean: true, keepalive: 30, clientId: 'test', username: 'username', password: Buffer.from('password') }, Buffer.from([ 16, 54, // Header 0, 6, // Protocol ID length 77, 81, 73, 115, 100, 112, // Protocol ID 3, // Protocol version 246, // Connect flags 0, 30, // Keepalive 0, 4, // Client ID length 116, 101, 115, 116, // Client ID 0, 5, // Will topic length 116, 111, 112, 105, 99, // Will topic 0, 7, // Will payload length 112, 97, 121, 108, 111, 97, 100, // Will payload 0, 8, // Username length 117, 115, 101, 114, 110, 97, 109, 101, // Username 0, 8, // Password length 112, 97, 115, 115, 119, 111, 114, 100 // Password ])) testParseGenerate('max connect with special chars', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 57, protocolId: 'MQIsdp', protocolVersion: 3, will: { retain: true, qos: 2, topic: 'tòpic', payload: Buffer.from('pay£oad') }, clean: true, keepalive: 30, clientId: 'te$t', username: 'u$ern4me', password: Buffer.from('p4$$w0£d') }, Buffer.from([ 16, 57, // Header 0, 6, // Protocol ID length 77, 81, 73, 115, 100, 112, // Protocol ID 3, // Protocol version 246, // Connect flags 0, 30, // Keepalive 0, 4, // Client ID length 116, 101, 36, 116, // Client ID 0, 6, // Will topic length 116, 195, 178, 112, 105, 99, // Will topic 0, 8, // Will payload length 112, 97, 121, 194, 163, 111, 97, 100, // Will payload 0, 8, // Username length 117, 36, 101, 114, 110, 52, 109, 101, // Username 0, 9, // Password length 112, 52, 36, 36, 119, 48, 194, 163, 100 // Password ])) testGenerateOnly('connect all strings generate', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 54, protocolId: 'MQIsdp', protocolVersion: 3, will: { retain: true, qos: 2, topic: 'topic', payload: 'payload' }, clean: true, keepalive: 30, clientId: 'test', username: 'username', password: 'password' }, Buffer.from([ 16, 54, // Header 0, 6, // Protocol ID length 77, 81, 73, 115, 100, 112, // Protocol ID 3, // Protocol version 246, // Connect flags 0, 30, // Keepalive 0, 4, // Client ID length 116, 101, 115, 116, // Client ID 0, 5, // Will topic length 116, 111, 112, 105, 99, // Will topic 0, 7, // Will payload length 112, 97, 121, 108, 111, 97, 100, // Will payload 0, 8, // Username length 117, 115, 101, 114, 110, 97, 109, 101, // Username 0, 8, // Password length 112, 97, 115, 115, 119, 111, 114, 100 // Password ])) testParseError('Cannot parse protocolId', Buffer.from([ 16, 4, 0, 6, 77, 81 ])) // missing protocol version on connect testParseError('Packet too short', Buffer.from([ 16, 8, // Header 0, 6, // Protocol ID length 77, 81, 73, 115, 100, 112 // Protocol ID ])) // missing keepalive on connect testParseError('Packet too short', Buffer.from([ 16, 10, // Header 0, 6, // Protocol ID length 77, 81, 73, 115, 100, 112, // Protocol ID 3, // Protocol version 246 // Connect flags ])) // missing clientid on connect testParseError('Packet too short', Buffer.from([ 16, 10, // Header 0, 6, // Protocol ID length 77, 81, 73, 115, 100, 112, // Protocol ID 3, // Protocol version 246, // Connect flags 0, 30 // Keepalive ])) // missing will topic on connect testParseError('Cannot parse will topic', Buffer.from([ 16, 16, // Header 0, 6, // Protocol ID length 77, 81, 73, 115, 100, 112, // Protocol ID 3, // Protocol version 246, // Connect flags 0, 30, // Keepalive 0, 2, // Will topic length 0, 0 // Will topic ])) // missing will payload on connect testParseError('Cannot parse will payload', Buffer.from([ 16, 23, // Header 0, 6, // Protocol ID length 77, 81, 73, 115, 100, 112, // Protocol ID 3, // Protocol version 246, // Connect flags 0, 30, // Keepalive 0, 5, // Will topic length 116, 111, 112, 105, 99, // Will topic 0, 2, // Will payload length 0, 0 // Will payload ])) // missing username on connect testParseError('Cannot parse username', Buffer.from([ 16, 32, // Header 0, 6, // Protocol ID length 77, 81, 73, 115, 100, 112, // Protocol ID 3, // Protocol version 246, // Connect flags 0, 30, // Keepalive 0, 5, // Will topic length 116, 111, 112, 105, 99, // Will topic 0, 7, // Will payload length 112, 97, 121, 108, 111, 97, 100, // Will payload 0, 2, // Username length 0, 0 // Username ])) // missing password on connect testParseError('Cannot parse password', Buffer.from([ 16, 42, // Header 0, 6, // Protocol ID length 77, 81, 73, 115, 100, 112, // Protocol ID 3, // Protocol version 246, // Connect flags 0, 30, // Keepalive 0, 5, // Will topic length 116, 111, 112, 105, 99, // Will topic 0, 7, // Will payload length 112, 97, 121, 108, 111, 97, 100, // Will payload 0, 8, // Username length 117, 115, 101, 114, 110, 97, 109, 101, // Username 0, 2, // Password length 0, 0 // Password ])) // 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] testParseError('Invalid header flag bits, must be 0x0 for connect packet', Buffer.from([ 18, 10, // Header 0, 4, // Protocol ID length 0x4d, 0x51, 0x54, 0x54, // Protocol ID 3, // Protocol version 2, // Connect flags 0, 30 // Keepalive ])) // 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] testParseError('Connect flag bit 0 must be 0, but got 1', Buffer.from([ 16, 10, // Header 0, 4, // Protocol ID length 0x4d, 0x51, 0x54, 0x54, // Protocol ID 3, // Protocol version 3, // Connect flags 0, 30 // Keepalive ])) // 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]. testParseError('Will Retain Flag must be set to zero when Will Flag is set to 0', Buffer.from([ 16, 10, // Header 0, 4, // Protocol ID length 0x4d, 0x51, 0x54, 0x54, // Protocol ID 3, // Protocol version 0x22, // Connect flags 0, 30 // Keepalive ])) // 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]. testParseError('Will QoS must be set to zero when Will Flag is set to 0', Buffer.from([ 16, 10, // Header 0, 4, // Protocol ID length 0x4d, 0x51, 0x54, 0x54, // Protocol ID 3, // Protocol version 0x12, // Connect flags 0, 30 // Keepalive ])) // 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]. testParseError('Will QoS must be set to zero when Will Flag is set to 0', Buffer.from([ 16, 10, // Header 0, 4, // Protocol ID length 0x4d, 0x51, 0x54, 0x54, // Protocol ID 3, // Protocol version 0xa, // Connect flags 0, 30 // Keepalive ])) // CONNECT, SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK (v.5) packets must have payload // CONNECT testParseError('Packet too short', Buffer.from([ 16, // Header 8, // Packet length 0, 4, // Protocol ID length 77, 81, 84, 84, // MQTT 5, // Version 2, // Clean Start enabled 0, 0, // Keep-Alive 0, // Property Length 0, 0 // Properties // No payload ]), { protocolVersion: 5 }) // SUBSCRIBE testParseError('Malformed subscribe, no payload specified', Buffer.from([ 130, // Header 0 // Packet length ]), { protocolVersion: 5 }) // SUBACK testParseError('Malformed suback, no payload specified', Buffer.from([ 144, // Header 0 // Packet length ]), { protocolVersion: 5 }) // UNSUBSCRIBE testParseError('Malformed unsubscribe, no payload specified', Buffer.from([ 162, // Header 0 // Packet length ]), { protocolVersion: 5 }) // UNSUBACK (v.5) testParseError('Malformed unsuback, no payload specified', Buffer.from([ 176, // Header 0 // Packet length ]), { protocolVersion: 5 }) // UNSUBACK (v.4) testParseError('Malformed unsuback, payload length must be 2', Buffer.from([ 176, // Header 1, // Packet length 1 ]), { protocolVersion: 4 }) // UNSUBACK (v.3) testParseError('Malformed unsuback, payload length must be 2', Buffer.from([ 176, // Header 1, // Packet length 1 ]), { protocolVersion: 3 }) testParseGenerate('connack with return code 0', { cmd: 'connack', retain: false, qos: 0, dup: false, length: 2, sessionPresent: false, returnCode: 0 }, Buffer.from([ 32, 2, 0, 0 ])) testParseGenerate('connack MQTT 5 with properties', { cmd: 'connack', retain: false, qos: 0, dup: false, length: 87, sessionPresent: false, reasonCode: 0, properties: { sessionExpiryInterval: 1234, receiveMaximum: 432, maximumQoS: 2, retainAvailable: true, maximumPacketSize: 100, assignedClientIdentifier: 'test', topicAliasMaximum: 456, reasonString: 'test', userProperties: { test: 'test' }, wildcardSubscriptionAvailable: true, subscriptionIdentifiersAvailable: true, sharedSubscriptionAvailable: false, serverKeepAlive: 1234, responseInformation: 'test', serverReference: 'test', authenticationMethod: 'test', authenticationData: Buffer.from([1, 2, 3, 4]) } }, Buffer.from([ 32, 87, 0, 0, 84, // properties length 17, 0, 0, 4, 210, // sessionExpiryInterval 33, 1, 176, // receiveMaximum 36, 2, // Maximum qos 37, 1, // retainAvailable 39, 0, 0, 0, 100, // maximumPacketSize 18, 0, 4, 116, 101, 115, 116, // assignedClientIdentifier 34, 1, 200, // topicAliasMaximum 31, 0, 4, 116, 101, 115, 116, // reasonString 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties 40, 1, // wildcardSubscriptionAvailable 41, 1, // subscriptionIdentifiersAvailable 42, 0, // sharedSubscriptionAvailable 19, 4, 210, // serverKeepAlive 26, 0, 4, 116, 101, 115, 116, // responseInformation 28, 0, 4, 116, 101, 115, 116, // serverReference 21, 0, 4, 116, 101, 115, 116, // authenticationMethod 22, 0, 4, 1, 2, 3, 4 // authenticationData ]), { protocolVersion: 5 }) testParseGenerate('connack MQTT 5 with properties and doubled user properties', { cmd: 'connack', retain: false, qos: 0, dup: false, length: 100, sessionPresent: false, reasonCode: 0, properties: { sessionExpiryInterval: 1234, receiveMaximum: 432, maximumQoS: 2, retainAvailable: true, maximumPacketSize: 100, assignedClientIdentifier: 'test', topicAliasMaximum: 456, reasonString: 'test', userProperties: { test: ['test', 'test'] }, wildcardSubscriptionAvailable: true, subscriptionIdentifiersAvailable: true, sharedSubscriptionAvailable: false, serverKeepAlive: 1234, responseInformation: 'test', serverReference: 'test', authenticationMethod: 'test', authenticationData: Buffer.from([1, 2, 3, 4]) } }, Buffer.from([ 32, 100, 0, 0, 97, // properties length 17, 0, 0, 4, 210, // sessionExpiryInterval 33, 1, 176, // receiveMaximum 36, 2, // Maximum qos 37, 1, // retainAvailable 39, 0, 0, 0, 100, // maximumPacketSize 18, 0, 4, 116, 101, 115, 116, // assignedClientIdentifier 34, 1, 200, // topicAliasMaximum 31, 0, 4, 116, 101, 115, 116, // reasonString 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties 40, 1, // wildcardSubscriptionAvailable 41, 1, // subscriptionIdentifiersAvailable 42, 0, // sharedSubscriptionAvailable 19, 4, 210, // serverKeepAlive 26, 0, 4, 116, 101, 115, 116, // responseInformation 28, 0, 4, 116, 101, 115, 116, // serverReference 21, 0, 4, 116, 101, 115, 116, // authenticationMethod 22, 0, 4, 1, 2, 3, 4 // authenticationData ]), { protocolVersion: 5 }) testParseGenerate('connack with return code 0 session present bit set', { cmd: 'connack', retain: false, qos: 0, dup: false, length: 2, sessionPresent: true, returnCode: 0 }, Buffer.from([ 32, 2, 1, 0 ])) testParseGenerate('connack with return code 5', { cmd: 'connack', retain: false, qos: 0, dup: false, length: 2, sessionPresent: false, returnCode: 5 }, Buffer.from([ 32, 2, 0, 5 ])) // 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] testParseError('Invalid header flag bits, must be 0x0 for connack packet', Buffer.from([ 33, 2, // header 0, // flags 5 // return code ])) // Byte 1 is the "Connect Acknowledge Flags". Bits 7-1 are reserved and MUST be set to 0 [MQTT-3.2.2-1]. testParseError('Invalid connack flags, bits 7-1 must be set to 0', Buffer.from([ 32, 2, // header 2, // flags 5 // return code ])) testGenerateError('Invalid return code', { cmd: 'connack', retain: false, qos: 0, dup: false, length: 2, sessionPresent: false, returnCode: '5' // returncode must be a number }) testParseGenerate('minimal publish', { cmd: 'publish', retain: false, qos: 0, dup: false, length: 10, topic: 'test', payload: Buffer.from('test') }, Buffer.from([ 48, 10, // Header 0, 4, // Topic length 116, 101, 115, 116, // Topic (test) 116, 101, 115, 116 // Payload (test) ])) testParseGenerate('publish MQTT 5 properties', { cmd: 'publish', retain: true, qos: 2, dup: true, length: 86, topic: 'test', payload: Buffer.from('test'), messageId: 10, properties: { payloadFormatIndicator: true, messageExpiryInterval: 4321, topicAlias: 100, responseTopic: 'topic', correlationData: Buffer.from([1, 2, 3, 4]), userProperties: { test: ['test', 'test', 'test'] }, subscriptionIdentifier: 120, contentType: 'test' } }, Buffer.from([ 61, 86, // Header 0, 4, // Topic length 116, 101, 115, 116, // Topic (test) 0, 10, // Message ID 73, // properties length 1, 1, // payloadFormatIndicator 2, 0, 0, 16, 225, // message expiry interval 35, 0, 100, // topicAlias 8, 0, 5, 116, 111, 112, 105, 99, // response topic 9, 0, 4, 1, 2, 3, 4, // correlationData 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties 11, 120, // subscriptionIdentifier 3, 0, 4, 116, 101, 115, 116, // content type 116, 101, 115, 116 // Payload (test) ]), { protocolVersion: 5 }) testParseGenerate('publish MQTT 5 with multiple same properties', { cmd: 'publish', retain: true, qos: 2, dup: true, length: 64, topic: 'test', payload: Buffer.from('test'), messageId: 10, properties: { payloadFormatIndicator: true, messageExpiryInterval: 4321, topicAlias: 100, responseTopic: 'topic', correlationData: Buffer.from([1, 2, 3, 4]), userProperties: { test: 'test' }, subscriptionIdentifier: [120, 121, 122], contentType: 'test' } }, Buffer.from([ 61, 64, // Header 0, 4, // Topic length 116, 101, 115, 116, // Topic (test) 0, 10, // Message ID 51, // properties length 1, 1, // payloadFormatIndicator 2, 0, 0, 16, 225, // message expiry interval 35, 0, 100, // topicAlias 8, 0, 5, 116, 111, 112, 105, 99, // response topic 9, 0, 4, 1, 2, 3, 4, // correlationData 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties 11, 120, // subscriptionIdentifier 11, 121, // subscriptionIdentifier 11, 122, // subscriptionIdentifier 3, 0, 4, 116, 101, 115, 116, // content type 116, 101, 115, 116 // Payload (test) ]), { protocolVersion: 5 }) testParseGenerate('publish MQTT 5 properties with 0-4 byte varbyte', { cmd: 'publish', retain: true, qos: 2, dup: true, length: 27, topic: 'test', payload: Buffer.from('test'), messageId: 10, properties: { payloadFormatIndicator: false, subscriptionIdentifier: [128, 16384, 2097152] // this tests the varbyte handling } }, Buffer.from([ 61, 27, // Header 0, 4, // Topic length 116, 101, 115, 116, // Topic (test) 0, 10, // Message ID 14, // properties length 1, 0, // payloadFormatIndicator 11, 128, 1, // subscriptionIdentifier 11, 128, 128, 1, // subscriptionIdentifier 11, 128, 128, 128, 1, // subscriptionIdentifier 116, 101, 115, 116 // Payload (test) ]), { protocolVersion: 5 }) testParseGenerate('publish MQTT 5 properties with max value varbyte', { cmd: 'publish', retain: true, qos: 2, dup: true, length: 22, topic: 'test', payload: Buffer.from('test'), messageId: 10, properties: { payloadFormatIndicator: false, subscriptionIdentifier: [1, 268435455] } }, Buffer.from([ 61, 22, // Header 0, 4, // Topic length 116, 101, 115, 116, // Topic (test) 0, 10, // Message ID 9, // properties length 1, 0, // payloadFormatIndicator 11, 1, // subscriptionIdentifier 11, 255, 255, 255, 127, // subscriptionIdentifier (max value) 116, 101, 115, 116 // Payload (test) ]), { protocolVersion: 5 }) ; (() => { const buffer = Buffer.alloc(2048) testParseGenerate('2KB publish packet', { cmd: 'publish', retain: false, qos: 0, dup: false, length: 2054, topic: 'test', payload: buffer }, Buffer.concat([Buffer.from([ 48, 134, 16, // Header 0, 4, // Topic length 116, 101, 115, 116 // Topic (test) ]), buffer])) })() ; (() => { const maxLength = 268435455 const buffer = Buffer.alloc(maxLength - 6) testParseGenerate('Max payload publish packet', { cmd: 'publish', retain: false, qos: 0, dup: false, length: maxLength, topic: 'test', payload: buffer }, Buffer.concat([Buffer.from([ 48, 255, 255, 255, 127, // Header 0, 4, // Topic length 116, 101, 115, 116 // Topic (test) ]), buffer])) })() testParseGenerate('maximal publish', { cmd: 'publish', retain: true, qos: 2, length: 12, dup: true, topic: 'test', messageId: 10, payload: Buffer.from('test') }, Buffer.from([ 61, 12, // Header 0, 4, // Topic length 116, 101, 115, 116, // Topic 0, 10, // Message ID 116, 101, 115, 116 // Payload ])) test('publish all strings generate', t => { const message = { cmd: 'publish', retain: true, qos: 2, length: 12, dup: true, topic: 'test', messageId: 10, payload: Buffer.from('test') } const expected = Buffer.from([ 61, 12, // Header 0, 4, // Topic length 116, 101, 115, 116, // Topic 0, 10, // Message ID 116, 101, 115, 116 // Payload ]) t.equal(mqtt.generate(message).toString('hex'), expected.toString('hex')) t.end() }) testParseGenerate('empty publish', { cmd: 'publish', retain: false, qos: 0, dup: false, length: 6, topic: 'test', payload: Buffer.alloc(0) }, Buffer.from([ 48, 6, // Header 0, 4, // Topic length 116, 101, 115, 116 // Topic // Empty payload ])) // 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]. testParseError('Packet must not have both QoS bits set to 1', Buffer.from([ 0x36, 6, // Header 0, 4, // Topic length 116, 101, 115, 116 // Topic // Empty payload ])) test('splitted publish parse', t => { t.plan(3) const parser = mqtt.parser() const expected = { cmd: 'publish', retain: false, qos: 0, dup: false, length: 10, topic: 'test', payload: Buffer.from('test') } parser.on('packet', packet => { t.deepLooseEqual(packet, expected, 'expected packet') }) t.equal(parser.parse(Buffer.from([ 48, 10, // Header 0, 4, // Topic length 116, 101, 115, 116 // Topic (test) ])), 6, 'remaining bytes') t.equal(parser.parse(Buffer.from([ 116, 101, 115, 116 // Payload (test) ])), 0, 'remaining bytes') }) test('split publish longer', t => { t.plan(3) const length = 255 const topic = 'test' // Minus two bytes for the topic length specifier const payloadLength = length - topic.length - 2 const parser = mqtt.parser() const expected = { cmd: 'publish', retain: false, qos: 0, dup: false, length, topic, payload: Buffer.from('a'.repeat(payloadLength)) } parser.on('packet', packet => { t.deepLooseEqual(packet, expected, 'expected packet') }) t.equal(parser.parse(Buffer.from([ 48, 255, 1, // Header 0, topic.length, // Topic length 116, 101, 115, 116 // Topic (test) ])), 6, 'remaining bytes') t.equal(parser.parse(Buffer.from(Array(payloadLength).fill(97))), 0, 'remaining bytes') }) test('split length parse', t => { t.plan(4) const length = 255 const topic = 'test' const payloadLength = length - topic.length - 2 const parser = mqtt.parser() const expected = { cmd: 'publish', retain: false, qos: 0, dup: false, length, topic, payload: Buffer.from('a'.repeat(payloadLength)) } parser.on('packet', packet => { t.deepLooseEqual(packet, expected, 'expected packet') }) t.equal(parser.parse(Buffer.from([ 48, 255 // Header (partial length) ])), 1, 'remaining bytes') t.equal(parser.parse(Buffer.from([ 1, // Rest of header length 0, topic.length, // Topic length 116, 101, 115, 116 // Topic (test) ])), 6, 'remaining bytes') t.equal(parser.parse(Buffer.from(Array(payloadLength).fill(97))), 0, 'remaining bytes') }) testGenerateError('Invalid variable byte integer: 268435456', { cmd: 'publish', retain: false, qos: 0, dup: false, length: (268435455 + 1), topic: 'test', payload: Buffer.alloc(268435455 + 1 - 6) }, {}, 'Length var byte integer over max allowed value throws error') testGenerateError('Invalid subscriptionIdentifier: 268435456', { cmd: 'publish', retain: true, qos: 2, dup: true, length: 27, topic: 'test', payload: Buffer.from('test'), messageId: 10, properties: { payloadFormatIndicator: false, subscriptionIdentifier: 268435456 } }, { protocolVersion: 5 }, 'MQTT 5.0 var byte integer >24 bits throws error') testParseGenerate('puback', { cmd: 'puback', retain: false, qos: 0, dup: false, length: 2, messageId: 2 }, Buffer.from([ 64, 2, // Header 0, 2 // Message ID ])) // 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] testParseError('Invalid header flag bits, must be 0x0 for puback packet', Buffer.from([ 65, 2, // Header 0, 2 // Message ID ])) testParseGenerate('puback without reason and no MQTT 5 properties', { cmd: 'puback', retain: false, qos: 0, dup: false, length: 2, messageId: 2, reasonCode: 0 }, Buffer.from([ 64, 2, // Header 0, 2 // Message ID ]), { protocolVersion: 5 }) testParseGenerate('puback with reason and no MQTT 5 properties', { cmd: 'puback', retain: false, qos: 0, dup: false, length: 4, messageId: 2, reasonCode: 16 }, Buffer.from([ 64, 4, // Header 0, 2, // Message ID 16, // reason code 0 // no user properties ]), { protocolVersion: 5 }) testParseGenerate('puback MQTT 5 properties', { cmd: 'puback', retain: false, qos: 0, dup: false, length: 24, messageId: 2, reasonCode: 16, properties: { reasonString: 'test', userProperties: { test: 'test' } } }, Buffer.from([ 64, 24, // Header 0, 2, // Message ID 16, // reason code 20, // properties length 31