pax_global_header00006660000000000000000000000064136274625660014533gustar00rootroot0000000000000052 comment=324fd31ba3d375ac8ad335cd108737c4d77f9379 mqtt-packet-6.3.2/000077500000000000000000000000001362746256600137755ustar00rootroot00000000000000mqtt-packet-6.3.2/.gitignore000066400000000000000000000010271362746256600157650ustar00rootroot00000000000000# Logs logs *.log # Runtime data pids *.pid *.seed # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release # Dependency directory # Deployed apps should consider commenting this line out: # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git node_modules mqtt-packet-6.3.2/.travis.yml000066400000000000000000000001171362746256600161050ustar00rootroot00000000000000language: node_js sudo: false node_js: - 10 - 8 - 6 script: npm run ci mqtt-packet-6.3.2/CONTRIBUTING.md000066400000000000000000000023251362746256600162300ustar00rootroot00000000000000# mqtt-packet is an OPEN Open Source Project ----------------------------------------- ## What? Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project. ## Rules There are a few basic ground-rules for contributors: 1. **No `--force` pushes** or modifying the Git history in any way. 1. **Non-master branches** ought to be used for ongoing work. 1. **External API changes and significant modifications** ought to be subject to an **internal pull-request** to solicit feedback from other contributors. 1. Internal pull-requests to solicit feedback are *encouraged* for any other non-trivial contribution but left to the discretion of the contributor. 1. Contributors should attempt to adhere to the prevailing code-style. ## Releases Declaring formal releases remains the prerogative of the project maintainer. ## Changes to this arrangement This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change. ----------------------------------------- mqtt-packet-6.3.2/LICENSE.md000066400000000000000000000023411362746256600154010ustar00rootroot00000000000000The MIT License (MIT) ===================== Copyright (c) 2014-2017 mqtt-packet contributors --------------------------------------- *mqtt-packet contributors listed at * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. mqtt-packet-6.3.2/README.md000066400000000000000000000257341362746256600152670ustar00rootroot00000000000000mqtt-packet   [![Build Status](https://travis-ci.org/mqttjs/mqtt-packet.png)](https://travis-ci.org/mqttjs/mqtt-packet) =========== Encode and Decode MQTT 3.1.1, 5.0 packets the node way. [![JavaScript Style Guide](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard) * Installation * Examples * Packets * API * Contributing * License & copyright This library is tested with node v4, v6 and v7. The last version to support older versions of node was mqtt-packet@4.1.2. Installation ------------ ```bash npm install mqtt-packet --save ``` Examples -------- ### Generating ```js var mqtt = require('mqtt-packet') var object = { cmd: 'publish', retain: false, qos: 0, dup: false, length: 10, topic: 'test', payload: 'test' // Can also be a Buffer } var opts = { protocolVersion: 4 } // default is 4. Usually, opts is a connect packet console.log(mqtt.generate(object)) // Prints: // // // // Which is the same as: // // new Buffer([ // 48, 10, // Header (publish) // 0, 4, // Topic length // 116, 101, 115, 116, // Topic (test) // 116, 101, 115, 116 // Payload (test) // ]) ``` ### Parsing ```js var mqtt = require('mqtt-packet') var opts = { protocolVersion: 4 } // default is 4. Usually, opts is a connect packet var parser = mqtt.parser(opts) // Synchronously emits all the parsed packets parser.on('packet', function(packet) { console.log(packet) // Prints: // // { // cmd: 'publish', // retain: false, // qos: 0, // dup: false, // length: 10, // topic: 'test', // payload: // } }) parser.parse(new Buffer([ 48, 10, // Header (publish) 0, 4, // Topic length 116, 101, 115, 116, // Topic (test) 116, 101, 115, 116 // Payload (test) ])) // Returns the number of bytes left in the parser ``` API --- * mqtt#generate() * mqtt#writeToStream() * mqtt#parser() ### mqtt.generate(object, [opts]) Generates a `Buffer` containing an MQTT packet. The object must be one of the ones specified by the [packets](#packets) section. Throws an `Error` if a packet cannot be generated. ### mqtt.writeToStream(object, stream, [opts]) Writes the mqtt packet defined by `object` to the given stream. The object must be one of the ones specified by the [packets](#packets) section. Emits an `Error` on the stream if a packet cannot be generated. On node >= 0.12, this function automatically calls `cork()` on your stream, and then it calls `uncork()` on the next tick. By default cache for number buffers is enabled. It creates a list of buffers for faster write. To disable cache set `mqtt.writeToStream.cacheNumbers = false`. Should be set before any `writeToStream` calls. ### mqtt.parser([opts]) Returns a new `Parser` object. `Parser` inherits from `EventEmitter` and will emit: * `packet`, when a new packet is parsed, according to [packets](#packets) * `error`, if an error happens #### Parser.parse(buffer) Parses a given `Buffer` and emits synchronously all the MQTT packets that are included. Returns the number of bytes left to parse. If an error happens, an `error` event will be emitted, but no `packet` events will be emitted after that. Calling `parse()` again clears the error and previous buffer, as if you created a new `Parser`. Packets ------- This section describes the format of all packets emitted by the `Parser` and that you can input to `generate`. ### Connect ```js { cmd: 'connect', protocolId: 'MQTT', // Or 'MQIsdp' in MQTT 3.1 and 5.0 protocolVersion: 4, // Or 3 in MQTT 3.1, or 5 in MQTT 5.0 clean: true, // Can also be false clientId: 'my-device', keepalive: 0, // Seconds which can be any positive number, with 0 as the default setting username: 'matteo', password: new Buffer('collina'), // Passwords are buffers will: { topic: 'mydevice/status', payload: new Buffer('dead'), // Payloads are buffers properties: { // MQTT 5.0 willDelayInterval: 1234, payloadFormatIndicator: false, messageExpiryInterval: 4321, contentType: 'test', responseTopic: 'topic', correlationData: Buffer.from([1, 2, 3, 4]), userProperties: { 'test': 'test' } } }, properties: { // MQTT 5.0 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]) } } ``` If `protocolVersion` is 3, `clientId` is mandatory and `generate` will throw if missing. If `password` or `will.payload` are passed as strings, they will automatically be converted into a `Buffer`. ### Connack ```js { cmd: 'connack', returnCode: 0, // Or whatever else you see fit MQTT < 5.0 sessionPresent: false, // Can also be true. reasonCode: 0, // reason code MQTT 5.0 properties: { // MQTT 5.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]) } } ``` The only mandatory argument is `returnCode`, as `generate` will throw if missing. ### Subscribe ```js { cmd: 'subscribe', messageId: 42, properties: { // MQTT 5.0 properties subscriptionIdentifier: 145, userProperties: { test: 'test' } } subscriptions: [{ topic: 'test', qos: 0, nl: false, // no Local MQTT 5.0 flag rap: true, // Retain as Published MQTT 5.0 flag rh: 1 // Retain Handling MQTT 5.0 }] } ``` All properties are mandatory. ### Suback ```js { cmd: 'suback', messageId: 42, properties: { // MQTT 5.0 properties reasonString: 'test', userProperties: { 'test': 'test' } } granted: [0, 1, 2, 128] } ``` All the granted qos __must__ be < 256, as they are encoded as UInt8. All properties are mandatory. ### Unsubscribe ```js { cmd: 'unsubscribe', messageId: 42, properties: { // MQTT 5.0 properties userProperties: { 'test': 'test' } } unsubscriptions: [ 'test', 'a/topic' ] } ``` All properties are mandatory. ### Unsuback ```js { cmd: 'unsuback', messageId: 42, properties: { // MQTT 5.0 properties reasonString: 'test', userProperties: { 'test': 'test' } } } ``` All properties are mandatory. ### Publish ```js { cmd: 'publish', messageId: 42, qos: 2, dup: false, topic: 'test', payload: new Buffer('test'), retain: false, properties: { // optional properties MQTT 5.0 payloadFormatIndicator: true, messageExpiryInterval: 4321, topicAlias: 100, responseTopic: 'topic', correlationData: Buffer.from([1, 2, 3, 4]), userProperties: { 'test': 'test' }, subscriptionIdentifier: 120, // can be an Array in message from broker, if message included in few another subscriptions contentType: 'test' } } ``` Only the `topic` property is mandatory. Both `topic` and `payload` can be `Buffer` objects instead of strings. `messageId` is mandatory for `qos > 0`. ### Puback ```js { cmd: 'puback', messageId: 42, reasonCode: 16, // only for MQTT 5.0 properties: { // MQTT 5.0 properties reasonString: 'test', userProperties: { 'test': 'test' } } } ``` The only mandatory property is `messageId`, as `generate` will throw if missing. ### Pubrec ```js { cmd: 'pubrec', messageId: 42, reasonCode: 16, // only for MQTT 5.0 properties: { // properties MQTT 5.0 reasonString: 'test', userProperties: { 'test': 'test' } } } ``` The only mandatory property is `messageId`, as `generate` will throw if missing. ### Pubrel ```js { cmd: 'pubrel', messageId: 42, reasonCode: 16, // only for MQTT 5.0 properties: { // properties MQTT 5.0 reasonString: 'test', userProperties: { 'test': 'test' } } } ``` The only mandatory property is `messageId`, as `generate` will throw if missing. ### Pubcomp ```js { cmd: 'pubcomp', messageId: 42, reasonCode: 16, // only for MQTT 5.0 properties: { // properties MQTT 5.0 reasonString: 'test', userProperties: { 'test': 'test' } } } ``` The only mandatory property is `messageId`, as `generate` will throw if missing. ### Pingreq ```js { cmd: 'pingreq' } ``` ### Pingresp ```js { cmd: 'pingresp' } ``` ### Disconnect ```js { cmd: 'disconnect', reasonCode: 0, // MQTT 5.0 code properties: { // properties MQTT 5.0 sessionExpiryInterval: 145, reasonString: 'test', userProperties: { 'test': 'test' }, serverReference: 'test' } } ``` ### Auth ```js { cmd: 'auth', reasonCode: 0, // MQTT 5.0 code properties: { // properties MQTT 5.0 authenticationMethod: 'test', authenticationData: Buffer.from([0, 1, 2, 3]), reasonString: 'test', userProperties: { 'test': 'test' } } } ``` Contributing ------------ mqtt-packet is an **OPEN Open Source Project**. This means that: > Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project. See the [CONTRIBUTING.md](https://github.com/mqttjs/mqtt-packet/blob/master/CONTRIBUTING.md) file for more details. ### Contributors mqtt-packet is only possible due to the excellent work of the following contributors:
Matteo CollinaGitHub/mcollinaTwitter/@matteocollina
Adam RuddGitHub/adamvrTwitter/@adam_vr
Peter SorowkaGitHub/psorowkaTwitter/@psorowka
Siarhei BuntsevichGitHub/scarry1992
License ------- MIT mqtt-packet-6.3.2/benchmarks/000077500000000000000000000000001362746256600161125ustar00rootroot00000000000000mqtt-packet-6.3.2/benchmarks/generate.js000066400000000000000000000007131362746256600202430ustar00rootroot00000000000000'use strict' var mqtt = require('../') var max = 100000 var i var buf = Buffer.from('test') // initialize it mqtt.generate({ cmd: 'publish', topic: 'test', payload: buf }) var start = Date.now() var time for (i = 0; i < max; i++) { mqtt.generate({ cmd: 'publish', topic: 'test', payload: buf }) } time = Date.now() - start console.log('Total time', time) console.log('Total packets', max) console.log('Packet/s', max / time * 1000) mqtt-packet-6.3.2/benchmarks/generateNet.js000066400000000000000000000017411362746256600207140ustar00rootroot00000000000000 var mqtt = require('../') var max = 1000000 var i = 0 var start = Date.now() var time var buf = Buffer.allocUnsafe(10) var net = require('net') var server = net.createServer(handle) var dest buf.fill('test') function handle (sock) { sock.resume() } server.listen(0, function () { dest = net.connect(server.address()) dest.on('connect', tickWait) dest.on('drain', tickWait) dest.on('finish', function () { time = Date.now() - start console.log('Total time', time) console.log('Total packets', max) console.log('Packet/s', max / time * 1000) server.close() }) }) function tickWait () { // console.log('tickWait', i) var res = true // var toSend = new Buffer(5 + buf.length) for (; i < max && res; i++) { res = dest.write(mqtt.generate({ cmd: 'publish', topic: 'test', payload: buf })) // buf.copy(toSend, 5) // res = dest.write(toSend, 'buffer') // console.log(res) } if (i >= max) { dest.end() } } mqtt-packet-6.3.2/benchmarks/parse.js000066400000000000000000000007431362746256600175660ustar00rootroot00000000000000 var mqtt = require('../') var parser = mqtt.parser() var max = 10000000 var i var start = Date.now() / 1000 var time for (i = 0; i < max; i++) { parser.parse(Buffer.from([ 48, 10, // Header (publish) 0, 4, // Topic length 116, 101, 115, 116, // Topic (test) 116, 101, 115, 116 // Payload (test) ])) } time = Date.now() / 1000 - start console.log('Total packets', max) console.log('Total time', Math.round(time * 100) / 100) console.log('Packet/s', max / time) mqtt-packet-6.3.2/benchmarks/writeToStream.js000066400000000000000000000016411362746256600212630ustar00rootroot00000000000000 var mqtt = require('../') var max = 1000000 var i = 0 var start = Date.now() var time var buf = Buffer.allocUnsafe(10) var net = require('net') var server = net.createServer(handle) var dest function handle (sock) { sock.resume() } buf.fill('test') server.listen(0, function () { dest = net.connect(server.address()) dest.on('connect', tickWait) dest.on('drain', tickWait) dest.on('finish', function () { time = Date.now() - start console.log('Total time', time) console.log('Total packets', max) console.log('Packet/s', max / time * 1000) server.close() }) }) function tickWait () { var res = true // var toSend = new Buffer(5) for (; i < max && res; i++) { res = mqtt.writeToStream({ cmd: 'publish', topic: 'test', payload: buf }, dest) // dest.write(toSend, 'buffer') // res = dest.write(buf, 'buffer') } if (i >= max) { dest.end() } } mqtt-packet-6.3.2/constants.js000066400000000000000000000115401362746256600163500ustar00rootroot00000000000000'use strict' var Buffer = require('safe-buffer').Buffer /* Protocol - protocol constants */ var protocol = module.exports /* Command code => mnemonic */ protocol.types = { 0: 'reserved', 1: 'connect', 2: 'connack', 3: 'publish', 4: 'puback', 5: 'pubrec', 6: 'pubrel', 7: 'pubcomp', 8: 'subscribe', 9: 'suback', 10: 'unsubscribe', 11: 'unsuback', 12: 'pingreq', 13: 'pingresp', 14: 'disconnect', 15: 'auth' } /* Mnemonic => Command code */ protocol.codes = {} for (var k in protocol.types) { var v = protocol.types[k] protocol.codes[v] = k } /* Header */ protocol.CMD_SHIFT = 4 protocol.CMD_MASK = 0xF0 protocol.DUP_MASK = 0x08 protocol.QOS_MASK = 0x03 protocol.QOS_SHIFT = 1 protocol.RETAIN_MASK = 0x01 /* Length */ protocol.LENGTH_MASK = 0x7F protocol.LENGTH_FIN_MASK = 0x80 /* Connack */ protocol.SESSIONPRESENT_MASK = 0x01 protocol.SESSIONPRESENT_HEADER = Buffer.from([protocol.SESSIONPRESENT_MASK]) protocol.CONNACK_HEADER = Buffer.from([protocol.codes['connack'] << protocol.CMD_SHIFT]) /* Connect */ protocol.USERNAME_MASK = 0x80 protocol.PASSWORD_MASK = 0x40 protocol.WILL_RETAIN_MASK = 0x20 protocol.WILL_QOS_MASK = 0x18 protocol.WILL_QOS_SHIFT = 3 protocol.WILL_FLAG_MASK = 0x04 protocol.CLEAN_SESSION_MASK = 0x02 protocol.CONNECT_HEADER = Buffer.from([protocol.codes['connect'] << protocol.CMD_SHIFT]) /* Properties */ protocol.properties = { sessionExpiryInterval: 17, willDelayInterval: 24, receiveMaximum: 33, maximumPacketSize: 39, topicAliasMaximum: 34, requestResponseInformation: 25, requestProblemInformation: 23, userProperties: 38, authenticationMethod: 21, authenticationData: 22, payloadFormatIndicator: 1, messageExpiryInterval: 2, contentType: 3, responseTopic: 8, correlationData: 9, maximumQoS: 36, retainAvailable: 37, assignedClientIdentifier: 18, reasonString: 31, wildcardSubscriptionAvailable: 40, subscriptionIdentifiersAvailable: 41, sharedSubscriptionAvailable: 42, serverKeepAlive: 19, responseInformation: 26, serverReference: 28, topicAlias: 35, subscriptionIdentifier: 11 } protocol.propertiesCodes = {} for (var prop in protocol.properties) { var id = protocol.properties[prop] protocol.propertiesCodes[id] = prop } protocol.propertiesTypes = { sessionExpiryInterval: 'int32', willDelayInterval: 'int32', receiveMaximum: 'int16', maximumPacketSize: 'int32', topicAliasMaximum: 'int16', requestResponseInformation: 'byte', requestProblemInformation: 'byte', userProperties: 'pair', authenticationMethod: 'string', authenticationData: 'binary', payloadFormatIndicator: 'byte', messageExpiryInterval: 'int32', contentType: 'string', responseTopic: 'string', correlationData: 'binary', maximumQoS: 'int8', retainAvailable: 'byte', assignedClientIdentifier: 'string', reasonString: 'string', wildcardSubscriptionAvailable: 'byte', subscriptionIdentifiersAvailable: 'byte', sharedSubscriptionAvailable: 'byte', serverKeepAlive: 'int16', responseInformation: 'string', serverReference: 'string', topicAlias: 'int16', subscriptionIdentifier: 'var' } function genHeader (type) { return [0, 1, 2].map(function (qos) { return [0, 1].map(function (dup) { return [0, 1].map(function (retain) { var buf = Buffer.alloc(1) buf.writeUInt8( protocol.codes[type] << protocol.CMD_SHIFT | (dup ? protocol.DUP_MASK : 0) | qos << protocol.QOS_SHIFT | retain, 0, true) return buf }) }) }) } /* Publish */ protocol.PUBLISH_HEADER = genHeader('publish') /* Subscribe */ protocol.SUBSCRIBE_HEADER = genHeader('subscribe') protocol.SUBSCRIBE_OPTIONS_QOS_MASK = 0x03 protocol.SUBSCRIBE_OPTIONS_NL_MASK = 0x01 protocol.SUBSCRIBE_OPTIONS_NL_SHIFT = 2 protocol.SUBSCRIBE_OPTIONS_RAP_MASK = 0x01 protocol.SUBSCRIBE_OPTIONS_RAP_SHIFT = 3 protocol.SUBSCRIBE_OPTIONS_RH_MASK = 0x03 protocol.SUBSCRIBE_OPTIONS_RH_SHIFT = 4 protocol.SUBSCRIBE_OPTIONS_RH = [0x00, 0x10, 0x20] protocol.SUBSCRIBE_OPTIONS_NL = 0x04 protocol.SUBSCRIBE_OPTIONS_RAP = 0x08 protocol.SUBSCRIBE_OPTIONS_QOS = [0x00, 0x01, 0x02] /* Unsubscribe */ protocol.UNSUBSCRIBE_HEADER = genHeader('unsubscribe') /* Confirmations */ protocol.ACKS = { unsuback: genHeader('unsuback'), puback: genHeader('puback'), pubcomp: genHeader('pubcomp'), pubrel: genHeader('pubrel'), pubrec: genHeader('pubrec') } protocol.SUBACK_HEADER = Buffer.from([protocol.codes['suback'] << protocol.CMD_SHIFT]) /* Protocol versions */ protocol.VERSION3 = Buffer.from([3]) protocol.VERSION4 = Buffer.from([4]) protocol.VERSION5 = Buffer.from([5]) /* QoS */ protocol.QOS = [0, 1, 2].map(function (qos) { return Buffer.from([qos]) }) /* Empty packets */ protocol.EMPTY = { pingreq: Buffer.from([protocol.codes['pingreq'] << 4, 0]), pingresp: Buffer.from([protocol.codes['pingresp'] << 4, 0]), disconnect: Buffer.from([protocol.codes['disconnect'] << 4, 0]) } mqtt-packet-6.3.2/generate.js000066400000000000000000000022751362746256600161330ustar00rootroot00000000000000'use strict' var Buffer = require('safe-buffer').Buffer var writeToStream = require('./writeToStream') var EE = require('events').EventEmitter var inherits = require('inherits') function generate (packet, opts) { var stream = new Accumulator() writeToStream(packet, stream, opts) return stream.concat() } function Accumulator () { this._array = new Array(20) this._i = 0 } inherits(Accumulator, EE) Accumulator.prototype.write = function (chunk) { this._array[this._i++] = chunk return true } Accumulator.prototype.concat = function () { var length = 0 var lengths = new Array(this._array.length) var list = this._array var pos = 0 var i var result for (i = 0; i < list.length && list[i] !== undefined; i++) { if (typeof list[i] !== 'string') lengths[i] = list[i].length else lengths[i] = Buffer.byteLength(list[i]) length += lengths[i] } result = Buffer.allocUnsafe(length) for (i = 0; i < list.length && list[i] !== undefined; i++) { if (typeof list[i] !== 'string') { list[i].copy(result, pos) pos += lengths[i] } else { result.write(list[i], pos) pos += lengths[i] } } return result } module.exports = generate mqtt-packet-6.3.2/mqtt.js000066400000000000000000000002171362746256600153200ustar00rootroot00000000000000'use strict' exports.parser = require('./parser') exports.generate = require('./generate') exports.writeToStream = require('./writeToStream') mqtt-packet-6.3.2/numbers.js000066400000000000000000000026161362746256600160130ustar00rootroot00000000000000'use strict' var Buffer = require('safe-buffer').Buffer var max = 65536 var cache = {} function generateBuffer (i) { var buffer = Buffer.allocUnsafe(2) buffer.writeUInt8(i >> 8, 0) buffer.writeUInt8(i & 0x00FF, 0 + 1) return buffer } function generateCache () { for (var i = 0; i < max; i++) { cache[i] = generateBuffer(i) } } /** * calcVariableByteIntLength - calculate the variable byte integer * length field * * @api private */ function calcVariableByteIntLength (length) { if (length >= 0 && length < 128) return 1 else if (length >= 128 && length < 16384) return 2 else if (length >= 16384 && length < 2097152) return 3 else if (length >= 2097152 && length < 268435456) return 4 else return 0 } function genBufVariableByteInt (num) { var digit = 0 var pos = 0 var length = calcVariableByteIntLength(num) var buffer = Buffer.allocUnsafe(length) do { digit = num % 128 | 0 num = num / 128 | 0 if (num > 0) digit = digit | 0x80 buffer.writeUInt8(digit, pos++) } while (num > 0) return { data: buffer, length: length } } function generate4ByteBuffer (num) { var buffer = Buffer.allocUnsafe(4) buffer.writeUInt32BE(num, 0) return buffer } module.exports = { cache: cache, generateCache: generateCache, generateNumber: generateBuffer, genBufVariableByteInt: genBufVariableByteInt, generate4ByteBuffer: generate4ByteBuffer } mqtt-packet-6.3.2/package.json000066400000000000000000000024721362746256600162700ustar00rootroot00000000000000{ "name": "mqtt-packet", "version": "6.3.2", "description": "Parse and generate MQTT packets like a breeze", "main": "mqtt.js", "types": "types/index.d.ts", "contributors": [ "Matteo Collina (https://github.com/mcollina)", "Adam Rudd ", "Peter Sorowka (https://github.com/psorowka)", "Wouter Klijn (https://github.com/wuhkuh)", "Siarhei Buntsevich (https://github.com/scarry1992)" ], "scripts": { "test": "tape test.js | tap-spec && standard", "ci": "tape test.js && node testRandom && standard" }, "pre-commit": "test", "repository": { "type": "git", "url": "https://github.com/mqttjs/mqtt-packet.git" }, "keywords": [ "MQTT", "packet", "parse", "publish", "subscribe", "pubsub" ], "license": "MIT", "bugs": { "url": "https://github.com/mqttjs/mqtt-packet/issues" }, "homepage": "https://github.com/mqttjs/mqtt-packet", "devDependencies": { "dev-null": "^0.1.1", "pre-commit": "^1.2.2", "readable-stream": "^2.3.6", "standard": "^10.0.2", "tap-spec": "^4.1.2", "tape": "^4.10.1" }, "dependencies": { "bl": "^1.2.2", "debug": "^4.1.1", "inherits": "^2.0.3", "process-nextick-args": "^2.0.0", "safe-buffer": "^5.1.2" } } mqtt-packet-6.3.2/packet.js000066400000000000000000000002701362746256600156010ustar00rootroot00000000000000 function Packet () { this.cmd = null this.retain = false this.qos = 0 this.dup = false this.length = -1 this.topic = null this.payload = null } module.exports = Packet mqtt-packet-6.3.2/parser.js000066400000000000000000000451321362746256600156340ustar00rootroot00000000000000'use strict' var bl = require('bl') var inherits = require('inherits') var EE = require('events').EventEmitter var Packet = require('./packet') var constants = require('./constants') var debug = require('debug')('mqtt-packet:parser') function Parser (opt) { if (!(this instanceof Parser)) return new Parser(opt) this.settings = opt || {} this._states = [ '_parseHeader', '_parseLength', '_parsePayload', '_newPacket' ] this._resetState() } inherits(Parser, EE) Parser.prototype._resetState = function () { debug('_resetState: resetting packet, error, _list, and _stateCounter') this.packet = new Packet() this.error = null this._list = bl() this._stateCounter = 0 } Parser.prototype.parse = function (buf) { if (this.error) this._resetState() this._list.append(buf) debug('parse: current state: %s', this._states[this._stateCounter]) while ((this.packet.length !== -1 || this._list.length > 0) && this[this._states[this._stateCounter]]() && !this.error) { this._stateCounter++ debug('parse: state complete. _stateCounter is now: %d', this._stateCounter) debug('parse: packet.length: %d, buffer list length: %d', this.packet.length, this._list.length) if (this._stateCounter >= this._states.length) this._stateCounter = 0 } debug('parse: exited while loop. packet: %d, buffer list length: %d', this.packet.length, this._list.length) return this._list.length } Parser.prototype._parseHeader = function () { // There is at least one byte in the buffer var zero = this._list.readUInt8(0) this.packet.cmd = constants.types[zero >> constants.CMD_SHIFT] this.packet.retain = (zero & constants.RETAIN_MASK) !== 0 this.packet.qos = (zero >> constants.QOS_SHIFT) & constants.QOS_MASK this.packet.dup = (zero & constants.DUP_MASK) !== 0 debug('_parseHeader: packet: %o', this.packet) this._list.consume(1) return true } Parser.prototype._parseLength = function () { // There is at least one byte in the list var result = this._parseVarByteNum(true) if (result) { this.packet.length = result.value this._list.consume(result.bytes) } debug('_parseLength %d', result.value) return !!result } Parser.prototype._parsePayload = function () { debug('_parsePayload: payload %O', this._list) var result = false // Do we have a payload? Do we have enough data to complete the payload? // PINGs have no payload if (this.packet.length === 0 || this._list.length >= this.packet.length) { this._pos = 0 switch (this.packet.cmd) { case 'connect': this._parseConnect() break case 'connack': this._parseConnack() break case 'publish': this._parsePublish() break case 'puback': case 'pubrec': case 'pubrel': case 'pubcomp': this._parseConfirmation() break case 'subscribe': this._parseSubscribe() break case 'suback': this._parseSuback() break case 'unsubscribe': this._parseUnsubscribe() break case 'unsuback': this._parseUnsuback() break case 'pingreq': case 'pingresp': // These are empty, nothing to do break case 'disconnect': this._parseDisconnect() break case 'auth': this._parseAuth() break default: this._emitError(new Error('Not supported')) } result = true } debug('_parsePayload complete result: %s', result) return result } Parser.prototype._parseConnect = function () { debug('_parseConnect') var protocolId // Protocol ID var clientId // Client ID var topic // Will topic var payload // Will payload var password // Password var username // Username var flags = {} var packet = this.packet // Parse protocolId protocolId = this._parseString() if (protocolId === null) return this._emitError(new Error('Cannot parse protocolId')) if (protocolId !== 'MQTT' && protocolId !== 'MQIsdp') { return this._emitError(new Error('Invalid protocolId')) } packet.protocolId = protocolId // Parse constants version number if (this._pos >= this._list.length) return this._emitError(new Error('Packet too short')) packet.protocolVersion = this._list.readUInt8(this._pos) if (packet.protocolVersion !== 3 && packet.protocolVersion !== 4 && packet.protocolVersion !== 5) { return this._emitError(new Error('Invalid protocol version')) } this._pos++ if (this._pos >= this._list.length) { return this._emitError(new Error('Packet too short')) } // Parse connect flags flags.username = (this._list.readUInt8(this._pos) & constants.USERNAME_MASK) flags.password = (this._list.readUInt8(this._pos) & constants.PASSWORD_MASK) flags.will = (this._list.readUInt8(this._pos) & constants.WILL_FLAG_MASK) if (flags.will) { packet.will = {} packet.will.retain = (this._list.readUInt8(this._pos) & constants.WILL_RETAIN_MASK) !== 0 packet.will.qos = (this._list.readUInt8(this._pos) & constants.WILL_QOS_MASK) >> constants.WILL_QOS_SHIFT } packet.clean = (this._list.readUInt8(this._pos) & constants.CLEAN_SESSION_MASK) !== 0 this._pos++ // Parse keepalive packet.keepalive = this._parseNum() if (packet.keepalive === -1) return this._emitError(new Error('Packet too short')) // parse properties if (packet.protocolVersion === 5) { var properties = this._parseProperties() if (Object.getOwnPropertyNames(properties).length) { packet.properties = properties } } // Parse clientId clientId = this._parseString() if (clientId === null) return this._emitError(new Error('Packet too short')) packet.clientId = clientId debug('_parseConnect: packet.clientId: %s', packet.clientId) if (flags.will) { if (packet.protocolVersion === 5) { var willProperties = this._parseProperties() if (Object.getOwnPropertyNames(willProperties).length) { packet.will.properties = willProperties } } // Parse will topic topic = this._parseString() if (topic === null) return this._emitError(new Error('Cannot parse will topic')) packet.will.topic = topic debug('_parseConnect: packet.will.topic: %s', packet.will.topic) // Parse will payload payload = this._parseBuffer() if (payload === null) return this._emitError(new Error('Cannot parse will payload')) packet.will.payload = payload debug('_parseConnect: packet.will.paylaod: %s', packet.will.payload) } // Parse username if (flags.username) { username = this._parseString() if (username === null) return this._emitError(new Error('Cannot parse username')) packet.username = username debug('_parseConnect: packet.username: %s', packet.username) } // Parse password if (flags.password) { password = this._parseBuffer() if (password === null) return this._emitError(new Error('Cannot parse password')) packet.password = password } // need for right parse auth packet and self set up this.settings = packet debug('_parseConnect: complete') return packet } Parser.prototype._parseConnack = function () { debug('_parseConnack') var packet = this.packet if (this._list.length < 2) return null packet.sessionPresent = !!(this._list.readUInt8(this._pos++) & constants.SESSIONPRESENT_MASK) if (this.settings.protocolVersion === 5) { packet.reasonCode = this._list.readUInt8(this._pos++) } else { packet.returnCode = this._list.readUInt8(this._pos++) } if (packet.returnCode === -1 || packet.reasonCode === -1) return this._emitError(new Error('Cannot parse return code')) // mqtt 5 properties if (this.settings.protocolVersion === 5) { var properties = this._parseProperties() if (Object.getOwnPropertyNames(properties).length) { packet.properties = properties } } debug('_parseConnack: complete') } Parser.prototype._parsePublish = function () { debug('_parsePublish') var packet = this.packet packet.topic = this._parseString() if (packet.topic === null) return this._emitError(new Error('Cannot parse topic')) // Parse messageId if (packet.qos > 0) if (!this._parseMessageId()) { return } // Properties mqtt 5 if (this.settings.protocolVersion === 5) { var properties = this._parseProperties() if (Object.getOwnPropertyNames(properties).length) { packet.properties = properties } } packet.payload = this._list.slice(this._pos, packet.length) debug('_parsePublish: payload from buffer list: %o', packet.payload) } Parser.prototype._parseSubscribe = function () { debug('_parseSubscribe') var packet = this.packet var topic var options var qos var rh var rap var nl var subscription if (packet.qos !== 1) { return this._emitError(new Error('Wrong subscribe header')) } packet.subscriptions = [] if (!this._parseMessageId()) { return } // Properties mqtt 5 if (this.settings.protocolVersion === 5) { var properties = this._parseProperties() if (Object.getOwnPropertyNames(properties).length) { packet.properties = properties } } while (this._pos < packet.length) { // Parse topic topic = this._parseString() if (topic === null) return this._emitError(new Error('Cannot parse topic')) if (this._pos >= packet.length) return this._emitError(new Error('Malformed Subscribe Payload')) options = this._parseByte() qos = options & constants.SUBSCRIBE_OPTIONS_QOS_MASK nl = ((options >> constants.SUBSCRIBE_OPTIONS_NL_SHIFT) & constants.SUBSCRIBE_OPTIONS_NL_MASK) !== 0 rap = ((options >> constants.SUBSCRIBE_OPTIONS_RAP_SHIFT) & constants.SUBSCRIBE_OPTIONS_RAP_MASK) !== 0 rh = (options >> constants.SUBSCRIBE_OPTIONS_RH_SHIFT) & constants.SUBSCRIBE_OPTIONS_RH_MASK subscription = { topic: topic, qos: qos } // mqtt 5 options if (this.settings.protocolVersion === 5) { subscription.nl = nl subscription.rap = rap subscription.rh = rh } // Push pair to subscriptions debug('_parseSubscribe: push subscription `%s` to subscription', subscription) packet.subscriptions.push(subscription) } } Parser.prototype._parseSuback = function () { debug('_parseSuback') var packet = this.packet this.packet.granted = [] if (!this._parseMessageId()) { return } // Properties mqtt 5 if (this.settings.protocolVersion === 5) { var properties = this._parseProperties() if (Object.getOwnPropertyNames(properties).length) { packet.properties = properties } } // Parse granted QoSes while (this._pos < this.packet.length) { this.packet.granted.push(this._list.readUInt8(this._pos++)) } } Parser.prototype._parseUnsubscribe = function () { debug('_parseUnsubscribe') var packet = this.packet packet.unsubscriptions = [] // Parse messageId if (!this._parseMessageId()) { return } // Properties mqtt 5 if (this.settings.protocolVersion === 5) { var properties = this._parseProperties() if (Object.getOwnPropertyNames(properties).length) { packet.properties = properties } } while (this._pos < packet.length) { var topic // Parse topic topic = this._parseString() if (topic === null) return this._emitError(new Error('Cannot parse topic')) // Push topic to unsubscriptions debug('_parseUnsubscribe: push topic `%s` to unsubscriptions', topic) packet.unsubscriptions.push(topic) } } Parser.prototype._parseUnsuback = function () { debug('_parseUnsuback') var packet = this.packet if (!this._parseMessageId()) return this._emitError(new Error('Cannot parse messageId')) // Properties mqtt 5 if (this.settings.protocolVersion === 5) { var properties = this._parseProperties() if (Object.getOwnPropertyNames(properties).length) { packet.properties = properties } // Parse granted QoSes packet.granted = [] while (this._pos < this.packet.length) { this.packet.granted.push(this._list.readUInt8(this._pos++)) } } } // parse packets like puback, pubrec, pubrel, pubcomp Parser.prototype._parseConfirmation = function () { debug('_parseConfirmation: packet.cmd: `%s`', this.packet.cmd) var packet = this.packet this._parseMessageId() if (this.settings.protocolVersion === 5) { if (packet.length > 2) { // response code packet.reasonCode = this._parseByte() debug('_parseConfirmation: packet.reasonCode `%d`', packet.reasonCode) } if (packet.length > 3) { // properies mqtt 5 var properties = this._parseProperties() if (Object.getOwnPropertyNames(properties).length) { packet.properties = properties } } } return true } // parse disconnect packet Parser.prototype._parseDisconnect = function () { var packet = this.packet debug('_parseDisconnect') if (this.settings.protocolVersion === 5) { // response code packet.reasonCode = this._parseByte() // properies mqtt 5 var properties = this._parseProperties() if (Object.getOwnPropertyNames(properties).length) { packet.properties = properties } } debug('_parseDisconnect result: true') return true } // parse auth packet Parser.prototype._parseAuth = function () { debug('_parseAuth') var packet = this.packet if (this.settings.protocolVersion !== 5) { return this._emitError(new Error('Not supported auth packet for this version MQTT')) } // response code packet.reasonCode = this._parseByte() // properies mqtt 5 var properties = this._parseProperties() if (Object.getOwnPropertyNames(properties).length) { packet.properties = properties } debug('_parseAuth: result: true') return true } Parser.prototype._parseMessageId = function () { var packet = this.packet packet.messageId = this._parseNum() if (packet.messageId === null) { this._emitError(new Error('Cannot parse messageId')) return false } debug('_parseMessageId: packet.messageId %d', packet.messageId) return true } Parser.prototype._parseString = function (maybeBuffer) { var length = this._parseNum() var result var end = length + this._pos if (length === -1 || end > this._list.length || end > this.packet.length) return null result = this._list.toString('utf8', this._pos, end) this._pos += length debug('_parseString: result: %s', result) return result } Parser.prototype._parseStringPair = function () { debug('_parseStringPair') return { name: this._parseString(), value: this._parseString() } } Parser.prototype._parseBuffer = function () { var length = this._parseNum() var result var end = length + this._pos if (length === -1 || end > this._list.length || end > this.packet.length) return null result = this._list.slice(this._pos, end) this._pos += length debug('_parseBuffer: result: %o', result) return result } Parser.prototype._parseNum = function () { if (this._list.length - this._pos < 2) return -1 var result = this._list.readUInt16BE(this._pos) this._pos += 2 debug('_parseNum: result: %s', result) return result } Parser.prototype._parse4ByteNum = function () { if (this._list.length - this._pos < 4) return -1 var result = this._list.readUInt32BE(this._pos) this._pos += 4 debug('_parse4ByteNum: result: %s', result) return result } Parser.prototype._parseVarByteNum = function (fullInfoFlag) { debug('_parseVarByteNum') var bytes = 0 var mul = 1 var length = 0 var result = true var current var padding = this._pos ? this._pos : 0 while (bytes < 5) { current = this._list.readUInt8(padding + bytes++) length += mul * (current & constants.LENGTH_MASK) mul *= 0x80 if ((current & constants.LENGTH_FIN_MASK) === 0) break if (this._list.length <= bytes) { result = false break } } if (padding) { this._pos += bytes } result = result ? fullInfoFlag ? { bytes: bytes, value: length } : length : false debug('_parseVarByteNum: result: %o', result) return result } Parser.prototype._parseByte = function () { var result = this._list.readUInt8(this._pos) this._pos++ debug('_parseByte: result: %o', result) return result } Parser.prototype._parseByType = function (type) { debug('_parseByType: type: %s', type) switch (type) { case 'byte': { return this._parseByte() !== 0 } case 'int8': { return this._parseByte() } case 'int16': { return this._parseNum() } case 'int32': { return this._parse4ByteNum() } case 'var': { return this._parseVarByteNum() } case 'string': { return this._parseString() } case 'pair': { return this._parseStringPair() } case 'binary': { return this._parseBuffer() } } } Parser.prototype._parseProperties = function () { debug('_parseProperties') var length = this._parseVarByteNum() var start = this._pos var end = start + length var result = {} while (this._pos < end) { var type = this._parseByte() var name = constants.propertiesCodes[type] if (!name) { this._emitError(new Error('Unknown property')) return false } // user properties process if (name === 'userProperties') { if (!result[name]) { result[name] = {} } var currentUserProperty = this._parseByType(constants.propertiesTypes[name]) if (result[name][currentUserProperty.name]) { if (Array.isArray(result[name][currentUserProperty.name])) { result[name][currentUserProperty.name].push(currentUserProperty.value) } else { var currentValue = result[name][currentUserProperty.name] result[name][currentUserProperty.name] = [currentValue] result[name][currentUserProperty.name].push(currentUserProperty.value) } } else { result[name][currentUserProperty.name] = currentUserProperty.value } continue } if (result[name]) { if (Array.isArray(result[name])) { result[name].push(this._parseByType(constants.propertiesTypes[name])) } else { result[name] = [result[name]] result[name].push(this._parseByType(constants.propertiesTypes[name])) } } else { result[name] = this._parseByType(constants.propertiesTypes[name]) } } return result } Parser.prototype._newPacket = function () { debug('_newPacket') if (this.packet) { this._list.consume(this.packet.length) debug('_newPacket: parser emit packet: packet.cmd: %s, packet.payload: %s, packet.length: %d', this.packet.cmd, this.packet.payload, this.packet.length) this.emit('packet', this.packet) } debug('_newPacket: new packet') this.packet = new Packet() this._pos = 0 return true } Parser.prototype._emitError = function (err) { debug('_emitError') this.error = err this.emit('error', err) } module.exports = Parser mqtt-packet-6.3.2/test.js000066400000000000000000001362011362746256600153150ustar00rootroot00000000000000'use strict' var test = require('tape') var mqtt = require('./') var Buffer = require('safe-buffer').Buffer var 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', function (t) { t.plan(2) var parser = mqtt.parser(opts) var expected = object var fixture = buffer parser.on('packet', function (packet) { if (packet.cmd !== 'publish') { delete packet.topic delete packet.payload } t.deepEqual(packet, normalExpectedObject(expected), 'expected packet') }) parser.on('error', function (err) { t.fail(err) }) t.equal(parser.parse(fixture), 0, 'remaining bytes') }) test(name + ' generate', function (t) { t.equal(mqtt.generate(object, opts).toString('hex'), buffer.toString('hex')) t.end() }) test(name + ' mirror', function (t) { t.plan(2) var parser = mqtt.parser(opts) var expected = object var fixture = mqtt.generate(object, opts) parser.on('packet', function (packet) { if (packet.cmd !== 'publish') { delete packet.topic delete packet.payload } t.deepEqual(packet, normalExpectedObject(expected), 'expected packet') }) parser.on('error', function (err) { t.fail(err) }) t.equal(parser.parse(fixture), 0, 'remaining bytes') }) test(name + ' writeToStream', function (t) { var stream = WS() stream.write = () => true stream.on('error', (err) => t.fail(err)) var result = mqtt.writeToStream(object, stream, opts) t.equal(result, true, 'should return true') t.end() }) } function testParseError (expected, fixture, opts) { test(expected, function (t) { t.plan(1) var parser = mqtt.parser(opts) parser.on('error', function (err) { t.equal(err.message, expected, 'expected error message') }) parser.on('packet', function () { t.fail('parse errors should not be followed by packet events') }) parser.parse(fixture) }) } function testGenerateError (expected, fixture, opts) { test(expected, function (t) { t.plan(1) try { mqtt.generate(fixture, opts) } catch (err) { t.equal(expected, err.message) } }) } function testParseGenerateDefaults (name, object, buffer, opts) { test(name + ' parse', function (t) { var parser = mqtt.parser(opts) var expected = object var fixture = buffer t.plan(1 + Object.keys(expected).length) parser.on('packet', function (packet) { Object.keys(expected).forEach(function (key) { t.deepEqual(packet[key], expected[key], 'expected packet property ' + key) }) }) t.equal(parser.parse(fixture), 0, 'remaining bytes') }) test(name + ' generate', function (t) { t.equal(mqtt.generate(object).toString('hex'), buffer.toString('hex')) t.end() }) } function testWriteToStreamError (expected, fixture) { test('writeToStream ' + expected + ' error', function (t) { t.plan(2) var stream = WS() stream.write = () => t.fail('should not have called write') stream.on('error', () => t.pass('error emitted')) var result = mqtt.writeToStream(fixture, stream) t.false(result, 'result should be false') }) } test('disabled numbers cache', function (t) { var stream = WS() var message = { cmd: 'publish', retain: false, qos: 0, dup: false, length: 10, topic: Buffer.from('test'), payload: Buffer.from('test') } var expected = Buffer.from([ 48, 10, // Header 0, 4, // Topic length 116, 101, 115, 116, // Topic (test) 116, 101, 115, 116 // Payload (test) ]) var 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() }) 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 ])) testParseGenerate('connect MQTT 5.0', { 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.0 with will properties but w/o 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.0 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('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 ])) 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: new Buffer(0) }, clean: true, keepalive: 30, clientId: 'test', username: 'username', password: new Buffer('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: new Buffer('') }, 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: new Buffer('') }, 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: new Buffer('') }, 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: new Buffer('payload') }, clean: true, keepalive: 30, clientId: 'test', username: 'username', password: new Buffer('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: new Buffer('pay£oad') }, clean: true, keepalive: 30, clientId: 'te$t', username: 'u$ern4me', password: new Buffer('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 ])) test('connect all strings generate', function (t) { var message = { 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' } var expected = 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 ]) t.equal(mqtt.generate(message).toString('hex'), expected.toString('hex')) t.end() }) testParseError('Cannot parse protocolId', Buffer.from([ 16, 4, 0, 6, 77, 81 ])) 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 MQTT5 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 MQTT5 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 ])) testParseGenerate('minimal publish', { cmd: 'publish', retain: false, qos: 0, dup: false, length: 10, topic: 'test', payload: new Buffer('test') }, Buffer.from([ 48, 10, // Header 0, 4, // Topic length 116, 101, 115, 116, // Topic (test) 116, 101, 115, 116 // Payload (test) ])) testParseGenerate('publish MQTT5 properties', { cmd: 'publish', retain: true, qos: 2, dup: true, length: 60, topic: 'test', payload: new Buffer('test'), messageId: 10, properties: { payloadFormatIndicator: true, messageExpiryInterval: 4321, topicAlias: 100, responseTopic: 'topic', correlationData: Buffer.from([1, 2, 3, 4]), userProperties: { 'test': 'test' }, subscriptionIdentifier: 120, contentType: 'test' } }, Buffer.from([ 61, 60, // Header 0, 4, // Topic length 116, 101, 115, 116, // Topic (test) 0, 10, // Message ID 47, // 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 3, 0, 4, 116, 101, 115, 116, // content type 116, 101, 115, 116 // Payload (test) ]), { protocolVersion: 5 }) testParseGenerate('publish MQTT5 with multiple same properties', { cmd: 'publish', retain: true, qos: 2, dup: true, length: 62, topic: 'test', payload: new Buffer('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], contentType: 'test' } }, Buffer.from([ 61, 62, // Header 0, 4, // Topic length 116, 101, 115, 116, // Topic (test) 0, 10, // Message ID 49, // 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 3, 0, 4, 116, 101, 115, 116, // content type 116, 101, 115, 116 // Payload (test) ]), { protocolVersion: 5 }) ;(function () { var buffer = new Buffer(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])) })() ;(function () { var buffer = new Buffer(2 * 1024 * 1024) testParseGenerate('2MB publish packet', { cmd: 'publish', retain: false, qos: 0, dup: false, length: 6 + 2 * 1024 * 1024, topic: 'test', payload: buffer }, Buffer.concat([Buffer.from([ 48, 134, 128, 128, 1, // 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: new Buffer('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', function (t) { var message = { cmd: 'publish', retain: true, qos: 2, length: 12, dup: true, topic: 'test', messageId: 10, payload: new Buffer('test') } var 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: new Buffer(0) }, Buffer.from([ 48, 6, // Header 0, 4, // Topic length 116, 101, 115, 116 // Topic // Empty payload ])) test('splitted publish parse', function (t) { t.plan(3) var parser = mqtt.parser() var expected = { cmd: 'publish', retain: false, qos: 0, dup: false, length: 10, topic: 'test', payload: new Buffer('test') } parser.on('packet', function (packet) { t.deepEqual(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') }) testParseGenerate('puback', { cmd: 'puback', retain: false, qos: 0, dup: false, length: 2, messageId: 2 }, Buffer.from([ 64, 2, // Header 0, 2 // Message ID ])) testParseGenerate('puback with reason and no MQTT5 properties', { cmd: 'puback', retain: false, qos: 0, dup: false, length: 3, messageId: 2, reasonCode: 16 }, Buffer.from([ 64, 3, // Header 0, 2, // Message ID 16 // reason code ]), {protocolVersion: 5}) testParseGenerate('puback MQTT5 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, 0, 4, 116, 101, 115, 116, // reasonString 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116 // userProperties ]), {protocolVersion: 5}) testParseGenerate('pubrec', { cmd: 'pubrec', retain: false, qos: 0, dup: false, length: 2, messageId: 2 }, Buffer.from([ 80, 2, // Header 0, 2 // Message ID ])) testParseGenerate('pubrec MQTT5 properties', { cmd: 'pubrec', retain: false, qos: 0, dup: false, length: 24, messageId: 2, reasonCode: 16, properties: { reasonString: 'test', userProperties: { 'test': 'test' } } }, Buffer.from([ 80, 24, // Header 0, 2, // Message ID 16, // reason code 20, // properties length 31, 0, 4, 116, 101, 115, 116, // reasonString 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116 // userProperties ]), {protocolVersion: 5}) testParseGenerate('pubrel', { cmd: 'pubrel', retain: false, qos: 1, dup: false, length: 2, messageId: 2 }, Buffer.from([ 98, 2, // Header 0, 2 // Message ID ])) testParseGenerate('pubrel MQTT5 properties', { cmd: 'pubrel', retain: false, qos: 1, dup: false, length: 24, messageId: 2, reasonCode: 16, properties: { reasonString: 'test', userProperties: { 'test': 'test' } } }, Buffer.from([ 98, 24, // Header 0, 2, // Message ID 16, // reason code 20, // properties length 31, 0, 4, 116, 101, 115, 116, // reasonString 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116 // userProperties ]), {protocolVersion: 5}) testParseGenerate('pubcomp', { cmd: 'pubcomp', retain: false, qos: 0, dup: false, length: 2, messageId: 2 }, Buffer.from([ 112, 2, // Header 0, 2 // Message ID ])) testParseGenerate('pubcomp MQTT5 properties', { cmd: 'pubcomp', retain: false, qos: 0, dup: false, length: 24, messageId: 2, reasonCode: 16, properties: { reasonString: 'test', userProperties: { 'test': 'test' } } }, Buffer.from([ 112, 24, // Header 0, 2, // Message ID 16, // reason code 20, // properties length 31, 0, 4, 116, 101, 115, 116, // reasonString 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116 // userProperties ]), {protocolVersion: 5}) testParseError('Wrong subscribe header', Buffer.from([ 128, 9, // Header (subscribeqos=0length=9) 0, 6, // Message ID (6) 0, 4, // Topic length, 116, 101, 115, 116, // Topic (test) 0 // Qos (0) ])) testParseGenerate('subscribe to one topic', { cmd: 'subscribe', retain: false, qos: 1, dup: false, length: 9, subscriptions: [ { topic: 'test', qos: 0 } ], messageId: 6 }, Buffer.from([ 130, 9, // Header (subscribeqos=1length=9) 0, 6, // Message ID (6) 0, 4, // Topic length, 116, 101, 115, 116, // Topic (test) 0 // Qos (0) ])) testParseGenerate('subscribe to one topic by MQTT 5', { cmd: 'subscribe', retain: false, qos: 1, dup: false, length: 26, subscriptions: [ { topic: 'test', qos: 0, nl: false, rap: true, rh: 1 } ], messageId: 6, properties: { subscriptionIdentifier: 145, userProperties: { test: 'test' } } }, Buffer.from([ 130, 26, // Header (subscribeqos=1length=9) 0, 6, // Message ID (6) 16, // properties length 11, 145, 1, // subscriptionIdentifier 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties 0, 4, // Topic length, 116, 101, 115, 116, // Topic (test) 24 // settings(qos: 0, noLocal: false, Retain as Published: true, retain handling: 1) ]), {protocolVersion: 5}) testParseGenerate('subscribe to three topics', { cmd: 'subscribe', retain: false, qos: 1, dup: false, length: 23, subscriptions: [ { topic: 'test', qos: 0 }, { topic: 'uest', qos: 1 }, { topic: 'tfst', qos: 2 } ], messageId: 6 }, Buffer.from([ 130, 23, // Header (publishqos=1length=9) 0, 6, // Message ID (6) 0, 4, // Topic length, 116, 101, 115, 116, // Topic (test) 0, // Qos (0) 0, 4, // Topic length 117, 101, 115, 116, // Topic (uest) 1, // Qos (1) 0, 4, // Topic length 116, 102, 115, 116, // Topic (tfst) 2 // Qos (2) ])) testParseGenerate('subscribe to 3 topics by MQTT 5', { cmd: 'subscribe', retain: false, qos: 1, dup: false, length: 40, subscriptions: [ { topic: 'test', qos: 0, nl: false, rap: true, rh: 1 }, { topic: 'uest', qos: 1, nl: false, rap: false, rh: 0 }, { topic: 'tfst', qos: 2, nl: true, rap: false, rh: 0 } ], messageId: 6, properties: { subscriptionIdentifier: 145, userProperties: { test: 'test' } } }, Buffer.from([ 130, 40, // Header (subscribeqos=1length=9) 0, 6, // Message ID (6) 16, // properties length 11, 145, 1, // subscriptionIdentifier 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties 0, 4, // Topic length, 116, 101, 115, 116, // Topic (test) 24, // settings(qos: 0, noLocal: false, Retain as Published: true, retain handling: 1) 0, 4, // Topic length 117, 101, 115, 116, // Topic (uest) 1, // Qos (1) 0, 4, // Topic length 116, 102, 115, 116, // Topic (tfst) 6 // Qos (2), No Local: true ]), {protocolVersion: 5}) testParseGenerate('suback', { cmd: 'suback', retain: false, qos: 0, dup: false, length: 6, granted: [0, 1, 2, 128], messageId: 6 }, Buffer.from([ 144, 6, // Header 0, 6, // Message ID 0, 1, 2, 128 // Granted qos (0, 1, 2) and a rejected being 0x80 ])) testParseGenerate('suback MQTT5', { cmd: 'suback', retain: false, qos: 0, dup: false, length: 27, granted: [0, 1, 2, 128], messageId: 6, properties: { reasonString: 'test', userProperties: { 'test': 'test' } } }, Buffer.from([ 144, 27, // Header 0, 6, // Message ID 20, // properties length 31, 0, 4, 116, 101, 115, 116, // reasonString 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties 0, 1, 2, 128 // Granted qos (0, 1, 2) and a rejected being 0x80 ]), {protocolVersion: 5}) testParseGenerate('unsubscribe', { cmd: 'unsubscribe', retain: false, qos: 1, dup: false, length: 14, unsubscriptions: [ 'tfst', 'test' ], messageId: 7 }, Buffer.from([ 162, 14, 0, 7, // Message ID (7) 0, 4, // Topic length 116, 102, 115, 116, // Topic (tfst) 0, 4, // Topic length, 116, 101, 115, 116 // Topic (test) ])) testParseGenerate('unsubscribe MQTT 5', { cmd: 'unsubscribe', retain: false, qos: 1, dup: false, length: 28, unsubscriptions: [ 'tfst', 'test' ], messageId: 7, properties: { userProperties: { 'test': 'test' } } }, Buffer.from([ 162, 28, 0, 7, // Message ID (7) 13, // properties length 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties 0, 4, // Topic length 116, 102, 115, 116, // Topic (tfst) 0, 4, // Topic length, 116, 101, 115, 116 // Topic (test) ]), {protocolVersion: 5}) testParseGenerate('unsuback', { cmd: 'unsuback', retain: false, qos: 0, dup: false, length: 2, messageId: 8 }, Buffer.from([ 176, 2, // Header 0, 8 // Message ID ])) testParseGenerate('unsuback MQTT 5', { cmd: 'unsuback', retain: false, qos: 0, dup: false, length: 25, messageId: 8, properties: { reasonString: 'test', userProperties: { 'test': 'test' } }, granted: [0, 128] }, Buffer.from([ 176, 25, // Header 0, 8, // Message ID 20, // properties length 31, 0, 4, 116, 101, 115, 116, // reasonString 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties 0, 128 // success and error ]), {protocolVersion: 5}) testParseGenerate('pingreq', { cmd: 'pingreq', retain: false, qos: 0, dup: false, length: 0 }, Buffer.from([ 192, 0 // Header ])) testParseGenerate('pingresp', { cmd: 'pingresp', retain: false, qos: 0, dup: false, length: 0 }, Buffer.from([ 208, 0 // Header ])) testParseGenerate('disconnect', { cmd: 'disconnect', retain: false, qos: 0, dup: false, length: 0 }, Buffer.from([ 224, 0 // Header ])) testParseGenerate('disconnect MQTT 5', { cmd: 'disconnect', retain: false, qos: 0, dup: false, length: 34, reasonCode: 0, properties: { sessionExpiryInterval: 145, reasonString: 'test', userProperties: { 'test': 'test' }, serverReference: 'test' } }, Buffer.from([ 224, 34, // Header 0, // reason code 32, // properties length 17, 0, 0, 0, 145, // sessionExpiryInterval 31, 0, 4, 116, 101, 115, 116, // reasonString 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116, // userProperties 28, 0, 4, 116, 101, 115, 116// serverReference ]), {protocolVersion: 5}) testParseGenerate('auth MQTT 5', { cmd: 'auth', retain: false, qos: 0, dup: false, length: 36, reasonCode: 0, properties: { authenticationMethod: 'test', authenticationData: Buffer.from([0, 1, 2, 3]), reasonString: 'test', userProperties: { 'test': 'test' } } }, Buffer.from([ 240, 36, // Header 0, // reason code 34, // properties length 21, 0, 4, 116, 101, 115, 116, // auth method 22, 0, 4, 0, 1, 2, 3, // auth data 31, 0, 4, 116, 101, 115, 116, // reasonString 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116 // userProperties ]), {protocolVersion: 5}) testGenerateError('Unknown command', {}) testGenerateError('Invalid protocolId', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 54, protocolId: 42, protocolVersion: 3, will: { retain: true, qos: 2, topic: 'topic', payload: 'payload' }, clean: true, keepalive: 30, clientId: 'test', username: 'username', password: 'password' }) testGenerateError('clientId must be supplied before 3.1.1', { 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, username: 'username', password: 'password' }) testGenerateError('clientId must be given if cleanSession set to 0', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 54, protocolId: 'MQTT', protocolVersion: 4, will: { retain: true, qos: 2, topic: 'topic', payload: 'payload' }, clean: false, keepalive: 30, username: 'username', password: 'password' }) testGenerateError('Invalid keepalive', { 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: 'hello', clientId: 'test', username: 'username', password: 'password' }) testGenerateError('Invalid keepalive', { cmd: 'connect', keepalive: 3.1416 }) testGenerateError('Invalid will', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 54, protocolId: 'MQIsdp', protocolVersion: 3, will: 42, clean: true, keepalive: 30, clientId: 'test', username: 'username', password: 'password' }) testGenerateError('Invalid will topic', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 54, protocolId: 'MQIsdp', protocolVersion: 3, will: { retain: true, qos: 2, payload: 'payload' }, clean: true, keepalive: 30, clientId: 'test', username: 'username', password: 'password' }) testGenerateError('Invalid will payload', { cmd: 'connect', retain: false, qos: 0, dup: false, length: 54, protocolId: 'MQIsdp', protocolVersion: 3, will: { retain: true, qos: 2, topic: 'topic', payload: 42 }, clean: true, keepalive: 30, clientId: 'test', username: 'username', password: 'password' }) testGenerateError('Invalid username', { 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: 42, password: 'password' }) testGenerateError('Invalid password', { 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: 42 }) testGenerateError('Username is required to use password', { 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', password: 'password' }) testGenerateError('Invalid messageExpiryInterval: -4321', { cmd: 'publish', retain: true, qos: 2, dup: true, length: 60, topic: 'test', payload: new Buffer('test'), messageId: 10, properties: { payloadFormatIndicator: true, messageExpiryInterval: -4321, topicAlias: 100, responseTopic: 'topic', correlationData: Buffer.from([1, 2, 3, 4]), userProperties: { 'test': 'test' }, subscriptionIdentifier: 120, contentType: 'test' } }, {protocolVersion: 5}) testGenerateError('Invalid topicAlias: -100', { cmd: 'publish', retain: true, qos: 2, dup: true, length: 60, topic: 'test', payload: new Buffer('test'), messageId: 10, properties: { payloadFormatIndicator: true, messageExpiryInterval: 4321, topicAlias: -100, responseTopic: 'topic', correlationData: Buffer.from([1, 2, 3, 4]), userProperties: { 'test': 'test' }, subscriptionIdentifier: 120, contentType: 'test' } }, {protocolVersion: 5}) testGenerateError('Invalid subscriptionIdentifier: -120', { cmd: 'publish', retain: true, qos: 2, dup: true, length: 60, topic: 'test', payload: new Buffer('test'), messageId: 10, properties: { payloadFormatIndicator: true, messageExpiryInterval: 4321, topicAlias: 100, responseTopic: 'topic', correlationData: Buffer.from([1, 2, 3, 4]), userProperties: { 'test': 'test' }, subscriptionIdentifier: -120, contentType: 'test' } }, {protocolVersion: 5}) test('support cork', function (t) { t.plan(9) var dest = WS() dest._write = function (chunk, enc, cb) { t.pass('_write called') cb() } mqtt.writeToStream({ cmd: 'connect', retain: false, qos: 0, dup: false, length: 18, protocolId: 'MQIsdp', protocolVersion: 3, clean: false, keepalive: 30, clientId: 'test' }, dest) dest.end() }) // The following test case was designed after experiencing errors // when trying to connect with tls on a non tls mqtt port // the specific behaviour is: // - first byte suggests this is a connect message // - second byte suggests message length to be smaller than buffer length // thus payload processing starts // - the first two bytes suggest a protocol identifier string length // that leads the parser pointer close to the end of the buffer // - when trying to read further connect flags the buffer produces // a "out of range" Error // testParseError('Packet too short', Buffer.from([ 16, 9, 0, 6, 77, 81, 73, 115, 100, 112, 3 ])) // CONNECT Packets that show other protocol IDs than // the valid values MQTT and MQIsdp should cause an error // those packets are a hint that this is not a mqtt connection testParseError('Invalid protocolId', Buffer.from([ 16, 18, 0, 6, 65, 65, 65, 65, 65, 65, // AAAAAA 3, // Protocol version 0, // Connect flags 0, 10, // Keepalive 0, 4, // Client ID length 116, 101, 115, 116 // Client ID ])) // CONNECT Packets that contain an unsupported protocol version // Flag (i.e. not `3` or `4`) should cause an error testParseError('Invalid protocol version', Buffer.from([ 16, 18, 0, 6, 77, 81, 73, 115, 100, 112, // Protocol ID 1, // Protocol version 0, // Connect flags 0, 10, // Keepalive 0, 4, // Client ID length 116, 101, 115, 116 // Client ID ])) // When a packet contains a string in the variable header and the // given string length of this exceeds the overall length of the packet that // was specified in the fixed header, parsing must fail. // this case simulates this behavior with the protocol ID string of the // CONNECT packet. The fixed header suggests a remaining length of 8 bytes // which would be exceeded by the string length of 15 // in this case, a protocol ID parse error is expected testParseError('Cannot parse protocolId', Buffer.from([ 16, 8, // Fixed header 0, 15, // string length 15 --> 15 > 8 --> error! 77, 81, 73, 115, 100, 112, 77, 81, 73, 115, 100, 112, 77, 81, 73, 115, 100, 112, 77, 81, 73, 115, 100, 112, 77, 81, 73, 115, 100, 112, 77, 81, 73, 115, 100, 112, 77, 81, 73, 115, 100, 112, 77, 81, 73, 115, 100, 112 ])) testParseError('Unknown property', Buffer.from([ 61, 60, // Header 0, 4, // Topic length 116, 101, 115, 116, // Topic (test) 0, 10, // Message ID 47, // properties length 126, 1, // unknown property 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 3, 0, 4, 116, 101, 115, 116, // content type 116, 101, 115, 116 // Payload (test) ]), { protocolVersion: 5 }) testParseError('Not supported auth packet for this version MQTT', Buffer.from([ 240, 36, // Header 0, // reason code 34, // properties length 21, 0, 4, 116, 101, 115, 116, // auth method 22, 0, 4, 0, 1, 2, 3, // auth data 31, 0, 4, 116, 101, 115, 116, // reasonString 38, 0, 4, 116, 101, 115, 116, 0, 4, 116, 101, 115, 116 // userProperties ])) // When a Subscribe packet contains a topic_filter and the given // length is topic_filter.length + 1 then the last byte (requested QoS) is interpreted as topic_filter // reading the requested_qos at the end causes 'Index out of range' read testParseError('Malformed Subscribe Payload', Buffer.from([ 130, 14, // subscribe header and remaining length 0, 123, // packet ID 0, 10, // topic filter length 104, 105, 106, 107, 108, 47, 109, 110, 111, // topic filter with length of 9 bytes 0 // requested QoS ])) test('stops parsing after first error', function (t) { t.plan(4) var parser = mqtt.parser() var packetCount = 0 var errorCount = 0 var expectedPackets = 1 var expectedErrors = 1 parser.on('packet', function (packet) { t.ok(++packetCount <= expectedPackets, 'expected <= ' + expectedPackets + ' packets') }) parser.on('error', function (erroneous) { t.ok(++errorCount <= expectedErrors, 'expected <= ' + expectedErrors + ' errors') }) parser.parse(Buffer.from([ // First, a valid connect packet: 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 // Then an invalid subscribe packet: 128, 9, // Header (subscribeqos=0length=9) 0, 6, // Message ID (6) 0, 4, // Topic length, 116, 101, 115, 116, // Topic (test) 0, // Qos (0) // And another invalid subscribe packet: 128, 9, // Header (subscribeqos=0length=9) 0, 6, // Message ID (6) 0, 4, // Topic length, 116, 101, 115, 116, // Topic (test) 0, // Qos (0) // Finally, a valid disconnect packet: 224, 0 // Header ])) // Calling parse again clears the error and continues parsing packetCount = 0 errorCount = 0 expectedPackets = 2 expectedErrors = 0 parser.parse(Buffer.from([ // Connect: 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 // Disconnect: 224, 0 // Header ])) }) testWriteToStreamError('Invalid protocolId', { cmd: 'connect', protocolId: {} }) testWriteToStreamError('Invalid topic', { cmd: 'publish', topic: {} }) testWriteToStreamError('Invalid messageId', { cmd: 'subscribe', mid: {} }) mqtt-packet-6.3.2/testRandom.js000066400000000000000000000050151362746256600164540ustar00rootroot00000000000000'use strict' var mqtt = require('./') var crypto = require('crypto') var max = 1E5 var i var start = Date.now() / 1000 var time var errors = 0 var packets = 0 var randomPacket var firstBytes = [ 16 * 1, // CONNECT 16 * 2, // CONNACK 16 * 3, // PUBLISH, QoS: 0, No Retain, No Dup 16 * 3 + 1, // PUBLISH, QoS: 0, Retain, No Dup 16 * 3 + 8, // PUBLISH, QoS: 0, No Retain, Dup 16 * 3 + 1 + 8, // PUBLISH, QoS: 0, Retain, Dup 16 * 3 + 2, // PUBLISH, QoS: 1, No Retain, No Dup 16 * 3 + 2 + 1, // PUBLISH, QoS: 1, Retain, No Dup 16 * 3 + 2 + 8, // PUBLISH, QoS: 1, No Retain, Dup 16 * 3 + 2 + 1 + 8, // PUBLISH, QoS: 1, Retain, Dup 16 * 3 + 4, // PUBLISH, QoS: 2, No Retain, No Dup 16 * 3 + 4 + 1, // PUBLISH, QoS: 2, Retain, No Dup 16 * 3 + 4 + 8, // PUBLISH, QoS: 2, No Retain, Dup 16 * 3 + 4 + 1 + 8, // PUBLISH, QoS: 2, Retain, Dup 16 * 4, // PUBACK 16 * 5, // PUBREC 16 * 6, // PUBREL 16 * 7, // PUBCOMP 16 * 8, // SUBSCRIBE 16 * 9, // SUBACK 16 * 10, // UNSUBSCRIBE 16 * 11, // UNSUBACK 16 * 12, // PINGREQ 16 * 13, // PINGRESP 16 * 14, // DISCONNECT 16 * 15 // RESERVED ] function doParse () { var parser = mqtt.parser() parser.on('error', onError) parser.on('packet', onPacket) randomPacket = crypto.randomBytes(Math.floor(Math.random() * 512)) // Increase probability to have a valid first byte in order to at least // enter the parser if (Math.random() > 0.2 && randomPacket.length > 0) randomPacket.writeUInt8(firstBytes[Math.floor(Math.random() * firstBytes.length)], 0) parser.parse(randomPacket) } try { console.log('Starting benchmark') for (i = 0; i < max; i++) { doParse() } } catch (e) { console.log('Exception occurred at packet') console.log(randomPacket) console.log(e.message) console.log(e.stack) } function onError () { errors++ } function onPacket () { packets++ } var delta = Math.abs(max - packets - errors) time = Date.now() / 1000 - start console.log('Benchmark complete') console.log('==========================') console.log('Sent packets:', max) console.log('Total time:', Math.round(time * 100) / 100, 'seconds', '\r\n') console.log('Valid packets:', packets) console.log('Erroneous packets:', errors) if ((max - packets - errors) < 0) console.log('Excess packets:', delta, '\r\n') else console.log('Missing packets:', delta, '\r\n') console.log('Total packets:', packets + errors) console.log('Total errors:', errors + delta) console.log('Error rate:', ((errors + delta) / max * 100).toFixed(2) + '%') console.log('==========================') mqtt-packet-6.3.2/types/000077500000000000000000000000001362746256600151415ustar00rootroot00000000000000mqtt-packet-6.3.2/types/index.d.ts000066400000000000000000000117151362746256600170470ustar00rootroot00000000000000import EventEmitter = NodeJS.EventEmitter import WritableStream = NodeJS.WritableStream export declare type QoS = 0 | 1 | 2 export declare type PacketCmd = 'connack' | 'connect' | 'disconnect' | 'pingreq' | 'pingresp' | 'puback' | 'pubcomp' | 'publish' | 'pubrel' | 'pubrec' | 'suback' | 'subscribe' | 'unsuback' | 'unsubscribe' export interface IPacket { cmd: PacketCmd messageId?: number length?: number } export interface IConnectPacket extends IPacket { cmd: 'connect' clientId: string protocolVersion?: 4 | 5 | 3 protocolId?: 'MQTT' | 'MQIsdp' clean?: boolean keepalive?: number username?: string password?: Buffer will?: { topic: string payload: Buffer qos?: QoS retain?: boolean properties?: { willDelayInterval?: number, payloadFormatIndicator?: number, messageExpiryInterval?: number, contentType?: string, responseTopic?: string, correlationData?: Buffer, userProperties?: Object } } properties?: { sessionExpiryInterval?: number, receiveMaximum?: number, maximumPacketSize?: number, topicAliasMaximum?: number, requestResponseInformation?: boolean, requestProblemInformation?: boolean, userProperties?: Object, authenticationMethod?: string, authenticationData?: Buffer } } export interface IPublishPacket extends IPacket { cmd: 'publish' qos: QoS dup: boolean retain: boolean topic: string payload: string | Buffer properties?: { payloadFormatIndicator?: boolean, messageExpiryInterval?: number, topicAlias?: number, responseTopic?: string, correlationData?: Buffer, userProperties?: Object, subscriptionIdentifier?: number, contentType?: string } } export interface IConnackPacket extends IPacket { cmd: 'connack' returnCode: number sessionPresent: boolean properties?: { sessionExpiryInterval?: number, receiveMaximum?: number, maximumQoS?: number, retainAvailable?: boolean, maximumPacketSize?: number, assignedClientIdentifier?: string, topicAliasMaximum?: number, reasonString?: string, userProperties?: Object, wildcardSubscriptionAvailable?: boolean, subscriptionIdentifiersAvailable?: boolean, sharedSubscriptionAvailable?: boolean, serverKeepAlive?: number, responseInformation?: string, serverReference?: string, authenticationMethod?: string, authenticationData?: Buffer } } export interface ISubscription { topic: string qos: QoS, nl?: boolean, rap?: boolean, rh?: number } export interface ISubscribePacket extends IPacket { cmd: 'subscribe' subscriptions: ISubscription[], properties?: { reasonString?: string, userProperties?: Object } } export interface ISubackPacket extends IPacket { cmd: 'suback', properties?: { reasonString?: string, userProperties?: Object }, granted: number[] | Object[] } export interface IUnsubscribePacket extends IPacket { cmd: 'unsubscribe', properties?: { reasonString?: string, userProperties?: Object }, unsubscriptions: string[] } export interface IUnsubackPacket extends IPacket { cmd: 'unsuback', properties?: { reasonString?: string, userProperties?: Object } } export interface IPubackPacket extends IPacket { cmd: 'puback', properties?: { reasonString?: string, userProperties?: Object } } export interface IPubcompPacket extends IPacket { cmd: 'pubcomp', properties?: { reasonString?: string, userProperties?: Object } } export interface IPubrelPacket extends IPacket { cmd: 'pubrel', properties?: { reasonString?: string, userProperties?: Object } } export interface IPubrecPacket extends IPacket { cmd: 'pubrec', properties?: { reasonString?: string, userProperties?: Object } } export interface IPingreqPacket extends IPacket { cmd: 'pingreq' } export interface IPingrespPacket extends IPacket { cmd: 'pingresp' } export interface IDisconnectPacket extends IPacket { cmd: 'disconnect', properties?: { sessionExpiryInterval?: number, reasonString?: string, userProperties?: Object, serverReference?: string } } export declare type Packet = IConnectPacket | IPublishPacket | IConnackPacket | ISubscribePacket | ISubackPacket | IUnsubscribePacket | IUnsubackPacket | IPubackPacket | IPubcompPacket | IPubrelPacket | IPingreqPacket | IPingrespPacket | IDisconnectPacket | IPubrecPacket export interface Parser extends EventEmitter { on(event: 'packet', callback: (packet: Packet) => void): this on(event: 'error', callback: (error: any) => void): this parse(buffer: Buffer, opts?: Object): number } export declare function parser(opts?: Object): Parser export declare function generate(packet: Packet, opts?: Object): Buffer export declare function writeToStream(object: Packet, stream: WritableStream, opts?: Object): void export declare namespace writeToStream { let cacheNumbers: boolean } mqtt-packet-6.3.2/writeToStream.js000066400000000000000000000733671362746256600171640ustar00rootroot00000000000000'use strict' var protocol = require('./constants') var Buffer = require('safe-buffer').Buffer var empty = Buffer.allocUnsafe(0) var zeroBuf = Buffer.from([0]) var numbers = require('./numbers') var nextTick = require('process-nextick-args').nextTick var debug = require('debug')('mqtt-packet:writeToStream') var numCache = numbers.cache var generateNumber = numbers.generateNumber var generateCache = numbers.generateCache var genBufVariableByteInt = numbers.genBufVariableByteInt var generate4ByteBuffer = numbers.generate4ByteBuffer var writeNumber = writeNumberCached var toGenerate = true function generate (packet, stream, opts) { debug('generate called') if (stream.cork) { stream.cork() nextTick(uncork, stream) } if (toGenerate) { toGenerate = false generateCache() } debug('generate: packet.cmd: %s', packet.cmd) switch (packet.cmd) { case 'connect': return connect(packet, stream, opts) case 'connack': return connack(packet, stream, opts) case 'publish': return publish(packet, stream, opts) case 'puback': case 'pubrec': case 'pubrel': case 'pubcomp': return confirmation(packet, stream, opts) case 'subscribe': return subscribe(packet, stream, opts) case 'suback': return suback(packet, stream, opts) case 'unsubscribe': return unsubscribe(packet, stream, opts) case 'unsuback': return unsuback(packet, stream, opts) case 'pingreq': case 'pingresp': return emptyPacket(packet, stream, opts) case 'disconnect': return disconnect(packet, stream, opts) case 'auth': return auth(packet, stream, opts) default: stream.emit('error', new Error('Unknown command')) return false } } /** * Controls numbers cache. * Set to "false" to allocate buffers on-the-flight instead of pre-generated cache */ Object.defineProperty(generate, 'cacheNumbers', { get: function () { return writeNumber === writeNumberCached }, set: function (value) { if (value) { if (!numCache || Object.keys(numCache).length === 0) toGenerate = true writeNumber = writeNumberCached } else { toGenerate = false writeNumber = writeNumberGenerated } } }) function uncork (stream) { stream.uncork() } function connect (packet, stream, opts) { var settings = packet || {} var protocolId = settings.protocolId || 'MQTT' var protocolVersion = settings.protocolVersion || 4 var will = settings.will var clean = settings.clean var keepalive = settings.keepalive || 0 var clientId = settings.clientId || '' var username = settings.username var password = settings.password /* mqtt5 new oprions */ var properties = settings.properties if (clean === undefined) clean = true var length = 0 // Must be a string and non-falsy if (!protocolId || (typeof protocolId !== 'string' && !Buffer.isBuffer(protocolId))) { stream.emit('error', new Error('Invalid protocolId')) return false } else length += protocolId.length + 2 // Must be 3 or 4 or 5 if (protocolVersion !== 3 && protocolVersion !== 4 && protocolVersion !== 5) { stream.emit('error', new Error('Invalid protocol version')) return false } else length += 1 // ClientId might be omitted in 3.1.1, but only if cleanSession is set to 1 if ((typeof clientId === 'string' || Buffer.isBuffer(clientId)) && (clientId || protocolVersion === 4) && (clientId || clean)) { length += clientId.length + 2 } else { if (protocolVersion < 4) { stream.emit('error', new Error('clientId must be supplied before 3.1.1')) return false } if ((clean * 1) === 0) { stream.emit('error', new Error('clientId must be given if cleanSession set to 0')) return false } } // Must be a two byte number if (typeof keepalive !== 'number' || keepalive < 0 || keepalive > 65535 || keepalive % 1 !== 0) { stream.emit('error', new Error('Invalid keepalive')) return false } else length += 2 // Connect flags length += 1 // Properties if (protocolVersion === 5) { var propertiesData = getProperties(stream, properties) if (!propertiesData) { return false } length += propertiesData.length } // If will exists... if (will) { // It must be an object if (typeof will !== 'object') { stream.emit('error', new Error('Invalid will')) return false } // It must have topic typeof string if (!will.topic || typeof will.topic !== 'string') { stream.emit('error', new Error('Invalid will topic')) return false } else { length += Buffer.byteLength(will.topic) + 2 } // Payload length += 2 // payload length if (will.payload) { if (will.payload.length >= 0) { if (typeof will.payload === 'string') { length += Buffer.byteLength(will.payload) } else { length += will.payload.length } } else { stream.emit('error', new Error('Invalid will payload')) return false } } // will properties var willProperties = {} if (protocolVersion === 5) { willProperties = getProperties(stream, will.properties) if (!willProperties) { return false } length += willProperties.length } } // Username var providedUsername = false if (username != null) { if (isStringOrBuffer(username)) { providedUsername = true length += Buffer.byteLength(username) + 2 } else { stream.emit('error', new Error('Invalid username')) return false } } // Password if (password != null) { if (!providedUsername) { stream.emit('error', new Error('Username is required to use password')) return false } if (isStringOrBuffer(password)) { length += byteLength(password) + 2 } else { stream.emit('error', new Error('Invalid password')) return false } } // Generate header stream.write(protocol.CONNECT_HEADER) // Generate length writeVarByteInt(stream, length) // Generate protocol ID writeStringOrBuffer(stream, protocolId) stream.write( protocolVersion === 4 ? protocol.VERSION4 : protocolVersion === 5 ? protocol.VERSION5 : protocol.VERSION3 ) // Connect flags var flags = 0 flags |= (username != null) ? protocol.USERNAME_MASK : 0 flags |= (password != null) ? protocol.PASSWORD_MASK : 0 flags |= (will && will.retain) ? protocol.WILL_RETAIN_MASK : 0 flags |= (will && will.qos) ? will.qos << protocol.WILL_QOS_SHIFT : 0 flags |= will ? protocol.WILL_FLAG_MASK : 0 flags |= clean ? protocol.CLEAN_SESSION_MASK : 0 stream.write(Buffer.from([flags])) // Keepalive writeNumber(stream, keepalive) // Properties if (protocolVersion === 5) { propertiesData.write() } // Client ID writeStringOrBuffer(stream, clientId) // Will if (will) { if (protocolVersion === 5) { willProperties.write() } writeString(stream, will.topic) writeStringOrBuffer(stream, will.payload) } // Username and password if (username != null) { writeStringOrBuffer(stream, username) } if (password != null) { writeStringOrBuffer(stream, password) } // This is a small packet that happens only once on a stream // We assume the stream is always free to receive more data after this return true } function connack (packet, stream, opts) { var version = opts ? opts.protocolVersion : 4 var settings = packet || {} var rc = version === 5 ? settings.reasonCode : settings.returnCode var properties = settings.properties var length = 2 // length of rc and sessionHeader // Check return code if (typeof rc !== 'number') { stream.emit('error', new Error('Invalid return code')) return false } // mqtt5 properties var propertiesData = null if (version === 5) { propertiesData = getProperties(stream, properties) if (!propertiesData) { return false } length += propertiesData.length } stream.write(protocol.CONNACK_HEADER) // length writeVarByteInt(stream, length) stream.write(settings.sessionPresent ? protocol.SESSIONPRESENT_HEADER : zeroBuf) stream.write(Buffer.from([rc])) if (propertiesData != null) { propertiesData.write() } return true } function publish (packet, stream, opts) { debug('publish: packet: %o', packet) var version = opts ? opts.protocolVersion : 4 var settings = packet || {} var qos = settings.qos || 0 var retain = settings.retain ? protocol.RETAIN_MASK : 0 var topic = settings.topic var payload = settings.payload || empty var id = settings.messageId var properties = settings.properties var length = 0 // Topic must be a non-empty string or Buffer if (typeof topic === 'string') length += Buffer.byteLength(topic) + 2 else if (Buffer.isBuffer(topic)) length += topic.length + 2 else { stream.emit('error', new Error('Invalid topic')) return false } // Get the payload length if (!Buffer.isBuffer(payload)) length += Buffer.byteLength(payload) else length += payload.length // Message ID must a number if qos > 0 if (qos && typeof id !== 'number') { stream.emit('error', new Error('Invalid messageId')) return false } else if (qos) length += 2 // mqtt5 properties var propertiesData = null if (version === 5) { propertiesData = getProperties(stream, properties) if (!propertiesData) { return false } length += propertiesData.length } // Header stream.write(protocol.PUBLISH_HEADER[qos][settings.dup ? 1 : 0][retain ? 1 : 0]) // Remaining length writeVarByteInt(stream, length) // Topic writeNumber(stream, byteLength(topic)) stream.write(topic) // Message ID if (qos > 0) writeNumber(stream, id) // Properties if (propertiesData != null) { propertiesData.write() } // Payload debug('publish: payload: %o', payload) return stream.write(payload) } /* Puback, pubrec, pubrel and pubcomp */ function confirmation (packet, stream, opts) { var version = opts ? opts.protocolVersion : 4 var settings = packet || {} var type = settings.cmd || 'puback' var id = settings.messageId var dup = (settings.dup && type === 'pubrel') ? protocol.DUP_MASK : 0 var qos = 0 var reasonCode = settings.reasonCode var properties = settings.properties var length = version === 5 ? 3 : 2 if (type === 'pubrel') qos = 1 // Check message ID if (typeof id !== 'number') { stream.emit('error', new Error('Invalid messageId')) return false } // properies mqtt 5 var propertiesData = null if (version === 5) { // Confirm should not add empty property length with no properties (rfc 3.4.2.2.1) if (typeof properties === 'object') { propertiesData = getPropertiesByMaximumPacketSize(stream, properties, opts, length) if (!propertiesData) { return false } length += propertiesData.length } } // Header stream.write(protocol.ACKS[type][qos][dup][0]) // Length writeVarByteInt(stream, length) // Message ID writeNumber(stream, id) // reason code in header if (version === 5) { stream.write(Buffer.from([reasonCode])) } // properies mqtt 5 if (propertiesData !== null) { propertiesData.write() } return true } function subscribe (packet, stream, opts) { debug('subscribe: packet: ') var version = opts ? opts.protocolVersion : 4 var settings = packet || {} var dup = settings.dup ? protocol.DUP_MASK : 0 var id = settings.messageId var subs = settings.subscriptions var properties = settings.properties var length = 0 // Check message ID if (typeof id !== 'number') { stream.emit('error', new Error('Invalid messageId')) return false } else length += 2 // properies mqtt 5 var propertiesData = null if (version === 5) { propertiesData = getProperties(stream, properties) if (!propertiesData) { return false } length += propertiesData.length } // Check subscriptions if (typeof subs === 'object' && subs.length) { for (var i = 0; i < subs.length; i += 1) { var itopic = subs[i].topic var iqos = subs[i].qos if (typeof itopic !== 'string') { stream.emit('error', new Error('Invalid subscriptions - invalid topic')) return false } if (typeof iqos !== 'number') { stream.emit('error', new Error('Invalid subscriptions - invalid qos')) return false } if (version === 5) { var nl = subs[i].nl || false if (typeof nl !== 'boolean') { stream.emit('error', new Error('Invalid subscriptions - invalid No Local')) return false } var rap = subs[i].rap || false if (typeof rap !== 'boolean') { stream.emit('error', new Error('Invalid subscriptions - invalid Retain as Published')) return false } var rh = subs[i].rh || 0 if (typeof rh !== 'number' || rh > 2) { stream.emit('error', new Error('Invalid subscriptions - invalid Retain Handling')) return false } } length += Buffer.byteLength(itopic) + 2 + 1 } } else { stream.emit('error', new Error('Invalid subscriptions')) return false } // Generate header debug('subscribe: writing to stream: %o', protocol.SUBSCRIBE_HEADER) stream.write(protocol.SUBSCRIBE_HEADER[1][dup ? 1 : 0][0]) // Generate length writeVarByteInt(stream, length) // Generate message ID writeNumber(stream, id) // properies mqtt 5 if (propertiesData !== null) { propertiesData.write() } var result = true // Generate subs for (var j = 0; j < subs.length; j++) { var sub = subs[j] var jtopic = sub.topic var jqos = sub.qos var jnl = +sub.nl var jrap = +sub.rap var jrh = sub.rh var joptions // Write topic string writeString(stream, jtopic) // options process joptions = protocol.SUBSCRIBE_OPTIONS_QOS[jqos] if (version === 5) { joptions |= jnl ? protocol.SUBSCRIBE_OPTIONS_NL : 0 joptions |= jrap ? protocol.SUBSCRIBE_OPTIONS_RAP : 0 joptions |= jrh ? protocol.SUBSCRIBE_OPTIONS_RH[jrh] : 0 } // Write options result = stream.write(Buffer.from([joptions])) } return result } function suback (packet, stream, opts) { var version = opts ? opts.protocolVersion : 4 var settings = packet || {} var id = settings.messageId var granted = settings.granted var properties = settings.properties var length = 0 // Check message ID if (typeof id !== 'number') { stream.emit('error', new Error('Invalid messageId')) return false } else length += 2 // Check granted qos vector if (typeof granted === 'object' && granted.length) { for (var i = 0; i < granted.length; i += 1) { if (typeof granted[i] !== 'number') { stream.emit('error', new Error('Invalid qos vector')) return false } length += 1 } } else { stream.emit('error', new Error('Invalid qos vector')) return false } // properies mqtt 5 var propertiesData = null if (version === 5) { propertiesData = getPropertiesByMaximumPacketSize(stream, properties, opts, length) if (!propertiesData) { return false } length += propertiesData.length } // header stream.write(protocol.SUBACK_HEADER) // Length writeVarByteInt(stream, length) // Message ID writeNumber(stream, id) // properies mqtt 5 if (propertiesData !== null) { propertiesData.write() } return stream.write(Buffer.from(granted)) } function unsubscribe (packet, stream, opts) { var version = opts ? opts.protocolVersion : 4 var settings = packet || {} var id = settings.messageId var dup = settings.dup ? protocol.DUP_MASK : 0 var unsubs = settings.unsubscriptions var properties = settings.properties var length = 0 // Check message ID if (typeof id !== 'number') { stream.emit('error', new Error('Invalid messageId')) return false } else { length += 2 } // Check unsubs if (typeof unsubs === 'object' && unsubs.length) { for (var i = 0; i < unsubs.length; i += 1) { if (typeof unsubs[i] !== 'string') { stream.emit('error', new Error('Invalid unsubscriptions')) return false } length += Buffer.byteLength(unsubs[i]) + 2 } } else { stream.emit('error', new Error('Invalid unsubscriptions')) return false } // properies mqtt 5 var propertiesData = null if (version === 5) { propertiesData = getProperties(stream, properties) if (!propertiesData) { return false } length += propertiesData.length } // Header stream.write(protocol.UNSUBSCRIBE_HEADER[1][dup ? 1 : 0][0]) // Length writeVarByteInt(stream, length) // Message ID writeNumber(stream, id) // properies mqtt 5 if (propertiesData !== null) { propertiesData.write() } // Unsubs var result = true for (var j = 0; j < unsubs.length; j++) { result = writeString(stream, unsubs[j]) } return result } function unsuback (packet, stream, opts) { var version = opts ? opts.protocolVersion : 4 var settings = packet || {} var id = settings.messageId var dup = settings.dup ? protocol.DUP_MASK : 0 var granted = settings.granted var properties = settings.properties var type = settings.cmd var qos = 0 var length = 2 // Check message ID if (typeof id !== 'number') { stream.emit('error', new Error('Invalid messageId')) return false } // Check granted if (version === 5) { if (typeof granted === 'object' && granted.length) { for (var i = 0; i < granted.length; i += 1) { if (typeof granted[i] !== 'number') { stream.emit('error', new Error('Invalid qos vector')) return false } length += 1 } } else { stream.emit('error', new Error('Invalid qos vector')) return false } } // properies mqtt 5 var propertiesData = null if (version === 5) { propertiesData = getPropertiesByMaximumPacketSize(stream, properties, opts, length) if (!propertiesData) { return false } length += propertiesData.length } // Header stream.write(protocol.ACKS[type][qos][dup][0]) // Length writeVarByteInt(stream, length) // Message ID writeNumber(stream, id) // properies mqtt 5 if (propertiesData !== null) { propertiesData.write() } // payload if (version === 5) { stream.write(Buffer.from(granted)) } return true } function emptyPacket (packet, stream, opts) { return stream.write(protocol.EMPTY[packet.cmd]) } function disconnect (packet, stream, opts) { var version = opts ? opts.protocolVersion : 4 var settings = packet || {} var reasonCode = settings.reasonCode var properties = settings.properties var length = version === 5 ? 1 : 0 // properies mqtt 5 var propertiesData = null if (version === 5) { propertiesData = getPropertiesByMaximumPacketSize(stream, properties, opts, length) if (!propertiesData) { return false } length += propertiesData.length } // Header stream.write(Buffer.from([protocol.codes['disconnect'] << 4])) // Length writeVarByteInt(stream, length) // reason code in header if (version === 5) { stream.write(Buffer.from([reasonCode])) } // properies mqtt 5 if (propertiesData !== null) { propertiesData.write() } return true } function auth (packet, stream, opts) { var version = opts ? opts.protocolVersion : 4 var settings = packet || {} var reasonCode = settings.reasonCode var properties = settings.properties var length = version === 5 ? 1 : 0 if (version !== 5) stream.emit('error', new Error('Invalid mqtt version for auth packet')) // properies mqtt 5 var propertiesData = getPropertiesByMaximumPacketSize(stream, properties, opts, length) if (!propertiesData) { return false } length += propertiesData.length // Header stream.write(Buffer.from([protocol.codes['auth'] << 4])) // Length writeVarByteInt(stream, length) // reason code in header stream.write(Buffer.from([reasonCode])) // properies mqtt 5 if (propertiesData !== null) { propertiesData.write() } return true } /** * writeVarByteInt - write an MQTT style variable byte integer to the buffer * * @param buffer - destination * @param pos - offset * @param length - length (>0) * @returns number of bytes written * * @api private */ var varByteIntCache = {} function writeVarByteInt (stream, num) { var buffer = varByteIntCache[num] if (!buffer) { buffer = genBufVariableByteInt(num).data if (num < 16384) varByteIntCache[num] = buffer } debug('writeVarByteInt: writing to stream: %o', buffer) stream.write(buffer) } /** * writeString - write a utf8 string to the buffer * * @param buffer - destination * @param pos - offset * @param string - string to write * @return number of bytes written * * @api private */ function writeString (stream, string) { var strlen = Buffer.byteLength(string) writeNumber(stream, strlen) debug('writeString: %s', string) return stream.write(string, 'utf8') } /** * writeStringPair - write a utf8 string pairs to the buffer * * @param buffer - destination * @param name - string name to write * @param value - string value to write * @return number of bytes written * * @api private */ function writeStringPair (stream, name, value) { writeString(stream, name) writeString(stream, value) } /** * writeNumber - write a two byte number to the buffer * * @param buffer - destination * @param pos - offset * @param number - number to write * @return number of bytes written * * @api private */ function writeNumberCached (stream, number) { debug('writeNumberCached: number: %d', number) debug('writeNumberCached: %o', numCache[number]) return stream.write(numCache[number]) } function writeNumberGenerated (stream, number) { var generatedNumber = generateNumber(number) debug('writeNumberGenerated: %o', generatedNumber) return stream.write(generatedNumber) } function write4ByteNumber (stream, number) { var generated4ByteBuffer = generate4ByteBuffer(number) debug('write4ByteNumber: %o', generated4ByteBuffer) return stream.write(generated4ByteBuffer) } /** * writeStringOrBuffer - write a String or Buffer with the its length prefix * * @param buffer - destination * @param pos - offset * @param toWrite - String or Buffer * @return number of bytes written */ function writeStringOrBuffer (stream, toWrite) { if (typeof toWrite === 'string') { writeString(stream, toWrite) } else if (toWrite) { writeNumber(stream, toWrite.length) stream.write(toWrite) } else writeNumber(stream, 0) } function getProperties (stream, properties) { /* connect properties */ if (typeof properties !== 'object' || properties.length != null) { return { length: 1, write: function () { writeProperties(stream, {}, 0) } } } var propertiesLength = 0 function getLengthProperty (name, value) { var type = protocol.propertiesTypes[name] var length = 0 switch (type) { case 'byte': { if (typeof value !== 'boolean') { stream.emit('error', new Error('Invalid ' + name + ': ' + value)) return false } length += 1 + 1 break } case 'int8': { if (typeof value !== 'number' || value < 0 || value > 0xff) { stream.emit('error', new Error('Invalid ' + name + ': ' + value)) return false } length += 1 + 1 break } case 'binary': { if (value && value === null) { stream.emit('error', new Error('Invalid ' + name + ': ' + value)) return false } length += 1 + Buffer.byteLength(value) + 2 break } case 'int16': { if (typeof value !== 'number' || value < 0 || value > 0xffff) { stream.emit('error', new Error('Invalid ' + name + ': ' + value)) return false } length += 1 + 2 break } case 'int32': { if (typeof value !== 'number' || value < 0 || value > 0xffffffff) { stream.emit('error', new Error('Invalid ' + name + ': ' + value)) return false } length += 1 + 4 break } case 'var': { if (typeof value !== 'number' || value < 0 || value > 0xffffffff) { stream.emit('error', new Error('Invalid ' + name + ': ' + value)) return false } length += 1 + genBufVariableByteInt(value).length break } case 'string': { if (typeof value !== 'string') { stream.emit('error', new Error('Invalid ' + name + ': ' + value)) return false } length += 1 + 2 + Buffer.byteLength(value.toString()) break } case 'pair': { if (typeof value !== 'object') { stream.emit('error', new Error('Invalid ' + name + ': ' + value)) return false } length += Object.getOwnPropertyNames(value).reduce(function (result, name) { var currentValue = value[name] if (Array.isArray(currentValue)) { result += currentValue.reduce(function (currentLength, value) { currentLength += 1 + 2 + Buffer.byteLength(name.toString()) + 2 + Buffer.byteLength(value.toString()) return currentLength }, 0) } else { result += 1 + 2 + Buffer.byteLength(name.toString()) + 2 + Buffer.byteLength(value[name].toString()) } return result }, 0) break } default: { stream.emit('error', new Error('Invalid property ' + name + ': ' + value)) return false } } return length } if (properties) { for (var propName in properties) { var propLength = 0 var propValueLength = 0 var propValue = properties[propName] if (Array.isArray(propValue)) { for (var valueIndex = 0; valueIndex < propValue.length; valueIndex++) { propValueLength = getLengthProperty(propName, propValue[valueIndex]) if (!propValueLength) { return false } propLength += propValueLength } } else { propValueLength = getLengthProperty(propName, propValue) if (!propValueLength) { return false } propLength = propValueLength } if (!propLength) return false propertiesLength += propLength } } var propertiesLengthLength = genBufVariableByteInt(propertiesLength).length return { length: propertiesLengthLength + propertiesLength, write: function () { writeProperties(stream, properties, propertiesLength) } } } function getPropertiesByMaximumPacketSize (stream, properties, opts, length) { var mayEmptyProps = ['reasonString', 'userProperties'] var maximumPacketSize = opts && opts.properties && opts.properties.maximumPacketSize ? opts.properties.maximumPacketSize : 0 var propertiesData = getProperties(stream, properties) if (maximumPacketSize) { while (length + propertiesData.length > maximumPacketSize) { var currentMayEmptyProp = mayEmptyProps.shift() if (currentMayEmptyProp && properties[currentMayEmptyProp]) { delete properties[currentMayEmptyProp] propertiesData = getProperties(stream, properties) } else { return false } } } return propertiesData } function writeProperty (stream, propName, value) { var type = protocol.propertiesTypes[propName] switch (type) { case 'byte': { stream.write(Buffer.from([protocol.properties[propName]])) stream.write(Buffer.from([+value])) break } case 'int8': { stream.write(Buffer.from([protocol.properties[propName]])) stream.write(Buffer.from([value])) break } case 'binary': { stream.write(Buffer.from([protocol.properties[propName]])) writeStringOrBuffer(stream, value) break } case 'int16': { stream.write(Buffer.from([protocol.properties[propName]])) writeNumber(stream, value) break } case 'int32': { stream.write(Buffer.from([protocol.properties[propName]])) write4ByteNumber(stream, value) break } case 'var': { stream.write(Buffer.from([protocol.properties[propName]])) writeVarByteInt(stream, value) break } case 'string': { stream.write(Buffer.from([protocol.properties[propName]])) writeString(stream, value) break } case 'pair': { Object.getOwnPropertyNames(value).forEach(function (name) { var currentValue = value[name] if (Array.isArray(currentValue)) { currentValue.forEach(function (value) { stream.write(Buffer.from([protocol.properties[propName]])) writeStringPair(stream, name.toString(), value.toString()) }) } else { stream.write(Buffer.from([protocol.properties[propName]])) writeStringPair(stream, name.toString(), currentValue.toString()) } }) break } default: { stream.emit('error', new Error('Invalid property ' + propName + ' value: ' + value)) return false } } } function writeProperties (stream, properties, propertiesLength) { /* write properties to stream */ writeVarByteInt(stream, propertiesLength) for (var propName in properties) { if (properties.hasOwnProperty(propName) && properties[propName] !== null) { var value = properties[propName] if (Array.isArray(value)) { for (var valueIndex = 0; valueIndex < value.length; valueIndex++) { writeProperty(stream, propName, value[valueIndex]) } } else { writeProperty(stream, propName, value) } } } } function byteLength (bufOrString) { if (!bufOrString) return 0 else if (bufOrString instanceof Buffer) return bufOrString.length else return Buffer.byteLength(bufOrString) } function isStringOrBuffer (field) { return typeof field === 'string' || field instanceof Buffer } module.exports = generate