pax_global_header00006660000000000000000000000064131224365670014523gustar00rootroot0000000000000052 comment=349e6243fa502fd0f886f63065bbb6196b539ca7 mqtt-packet-5.4.0/000077500000000000000000000000001312243656700137635ustar00rootroot00000000000000mqtt-packet-5.4.0/.gitignore000066400000000000000000000010271312243656700157530ustar00rootroot00000000000000# 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-5.4.0/.travis.yml000066400000000000000000000001241312243656700160710ustar00rootroot00000000000000language: node_js sudo: false node_js: - 8 - 7 - 6 - 4 script: npm run ci mqtt-packet-5.4.0/CONTRIBUTING.md000066400000000000000000000023251312243656700162160ustar00rootroot00000000000000# 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-5.4.0/LICENSE.md000066400000000000000000000023411312243656700153670ustar00rootroot00000000000000The 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-5.4.0/README.md000066400000000000000000000162251312243656700152500ustar00rootroot00000000000000mqtt-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 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 } 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 parser = mqtt.parser() // 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) 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) 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() 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.1 protocolVersion: 4, // Or 3 in MQTT 3.1 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 } } ``` 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 sessionPresent: false // Can also be true. } ``` The only mandatory argument is `returnCode`, as `generate` will throw if missing. ### Subscribe ```js { cmd: 'subscribe', messageId: 42, subscriptions: [{ topic: 'test', qos: 0 }] } ``` All properties are mandatory. ### Suback ```js { cmd: 'suback', messageId: 42, 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, unsubscriptions: [ 'test', 'a/topic' ] } ``` All properties are mandatory. ### Unsuback ```js { cmd: 'unsuback', messageId: 42 } ``` All properties are mandatory. ### Publish ```js { cmd: 'publish', messageId: 42, qos: 2, dup: false, topic: 'test', payload: new Buffer('test'), retain: false } ``` 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 } ``` The only mandatory property is `messageId`, as `generate` will throw if missing. ### Pubrec ```js { cmd: 'pubcomp', messageId: 42 } ``` The only mandatory property is `messageId`, as `generate` will throw if missing. ### Pubrel ```js { cmd: 'pubrel', messageId: 42 } ``` The only mandatory property is `messageId`, as `generate` will throw if missing. ### Pubcomp ```js { cmd: 'pubcomp', messageId: 42 } ``` The only mandatory property is `messageId`, as `generate` will throw if missing. ### Pingreq ```js { cmd: 'pingreq' } ``` ### Pingresp ```js { cmd: 'pingresp' } ``` ### Disconnect ```js { cmd: 'disconnect' } ``` 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
License ------- MIT mqtt-packet-5.4.0/benchmarks/000077500000000000000000000000001312243656700161005ustar00rootroot00000000000000mqtt-packet-5.4.0/benchmarks/generate.js000066400000000000000000000007131312243656700202310ustar00rootroot00000000000000'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-5.4.0/benchmarks/generateNet.js000066400000000000000000000017411312243656700207020ustar00rootroot00000000000000 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-5.4.0/benchmarks/parse.js000066400000000000000000000007431312243656700175540ustar00rootroot00000000000000 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-5.4.0/benchmarks/writeToStream.js000066400000000000000000000016411312243656700212510ustar00rootroot00000000000000 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-5.4.0/constants.js000066400000000000000000000051621312243656700163410ustar00rootroot00000000000000'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: 'reserved' } /* 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]) function genHeader (type) { return [0, 1, 2].map(function (qos) { return [0, 1].map(function (dup) { return [0, 1].map(function (retain) { var buf = new Buffer(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') /* 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]) /* 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-5.4.0/generate.js000066400000000000000000000022251312243656700161140ustar00rootroot00000000000000'use strict' var Buffer = require('safe-buffer').Buffer var writeToStream = require('./writeToStream') var EE = require('events').EventEmitter var inherits = require('inherits') function generate (packet) { var stream = new Accumulator() writeToStream(packet, stream) 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]; 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]; 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-5.4.0/mqtt.js000066400000000000000000000002171312243656700153060ustar00rootroot00000000000000'use strict' exports.parser = require('./parser') exports.generate = require('./generate') exports.writeToStream = require('./writeToStream') mqtt-packet-5.4.0/numbers.js000066400000000000000000000007161312243656700160000ustar00rootroot00000000000000'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, true) buffer.writeUInt8(i & 0x00FF, 0 + 1, true) return buffer } function generateCache () { for (var i = 0; i < max; i++) { cache[i] = generateBuffer(i) } } module.exports = { cache: cache, generateCache: generateCache, generateNumber: generateBuffer } mqtt-packet-5.4.0/package.json000066400000000000000000000023111312243656700162460ustar00rootroot00000000000000{ "name": "mqtt-packet", "version": "5.4.0", "description": "Parse and generate MQTT packets like a breeze", "main": "mqtt.js", "contributors": [ "Matteo Collina (https://github.com/mcollina)", "Adam Rudd ", "Peter Sorowka (https://github.com/psorowka)", "Wouter Klijn (https://github.com/wuhkuh)" ], "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.0", "standard": "^10.0.2", "tap-spec": "^4.1.1", "tape": "^4.6.3" }, "dependencies": { "bl": "^1.2.1", "inherits": "^2.0.3", "process-nextick-args": "^1.0.7", "safe-buffer": "^5.1.0" } } mqtt-packet-5.4.0/packet.js000066400000000000000000000002701312243656700155670ustar00rootroot00000000000000 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-5.4.0/parser.js000066400000000000000000000221241312243656700156160ustar00rootroot00000000000000'use strict' var bl = require('bl') var inherits = require('inherits') var EE = require('events').EventEmitter var Packet = require('./packet') var constants = require('./constants') function Parser () { if (!(this instanceof Parser)) return new Parser() this._states = [ '_parseHeader', '_parseLength', '_parsePayload', '_newPacket' ] this._resetState() } inherits(Parser, EE) Parser.prototype._resetState = function () { 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) while ((this.packet.length !== -1 || this._list.length > 0) && this[this._states[this._stateCounter]]() && !this.error) { this._stateCounter++ if (this._stateCounter >= this._states.length) this._stateCounter = 0 } 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 this._list.consume(1) return true } Parser.prototype._parseLength = function () { // There is at least one byte in the list var bytes = 0 var mul = 1 var length = 0 var result = true var current while (bytes < 5) { current = this._list.readUInt8(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 (result) { this.packet.length = length this._list.consume(bytes) } return result } Parser.prototype._parsePayload = function () { 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._parseMessageId() 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': case 'disconnect': // These are empty, nothing to do break default: this._emitError(new Error('Not supported')) } result = true } return result } Parser.prototype._parseConnect = function () { 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) { 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 clientId clientId = this._parseString() if (clientId === null) return this._emitError(new Error('Packet too short')) packet.clientId = clientId if (flags.will) { // Parse will topic topic = this._parseString() if (topic === null) return this._emitError(new Error('Cannot parse will topic')) packet.will.topic = topic // Parse will payload payload = this._parseBuffer() if (payload === null) return this._emitError(new Error('Cannot parse will payload')) packet.will.payload = payload } // Parse username if (flags.username) { username = this._parseString() if (username === null) return this._emitError(new Error('Cannot parse username')) packet.username = username } // Parse password if (flags.password) { password = this._parseBuffer() if (password === null) return this._emitError(new Error('Cannot parse password')) packet.password = password } return packet } Parser.prototype._parseConnack = function () { var packet = this.packet if (this._list.length < 2) return null packet.sessionPresent = !!(this._list.readUInt8(this._pos++) & constants.SESSIONPRESENT_MASK) packet.returnCode = this._list.readUInt8(this._pos) if (packet.returnCode === -1) return this._emitError(new Error('Cannot parse return code')) } Parser.prototype._parsePublish = function () { 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 } packet.payload = this._list.slice(this._pos, packet.length) } Parser.prototype._parseSubscribe = function () { var packet = this.packet var topic var qos if (packet.qos !== 1) { return this._emitError(new Error('Wrong subscribe header')) } packet.subscriptions = [] if (!this._parseMessageId()) { return } while (this._pos < packet.length) { // Parse topic topic = this._parseString() if (topic === null) return this._emitError(new Error('Cannot parse topic')) qos = this._list.readUInt8(this._pos++) // Push pair to subscriptions packet.subscriptions.push({ topic: topic, qos: qos }) } } Parser.prototype._parseSuback = function () { this.packet.granted = [] if (!this._parseMessageId()) { return } // Parse granted QoSes while (this._pos < this.packet.length) { this.packet.granted.push(this._list.readUInt8(this._pos++)) } } Parser.prototype._parseUnsubscribe = function () { var packet = this.packet packet.unsubscriptions = [] // Parse messageId if (!this._parseMessageId()) { return } 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 packet.unsubscriptions.push(topic) } } Parser.prototype._parseUnsuback = function () { if (!this._parseMessageId()) return this._emitError(new Error('Cannot parse messageId')) } 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 } 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 return result } 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 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 return result } Parser.prototype._newPacket = function () { if (this.packet) { this._list.consume(this.packet.length) this.emit('packet', this.packet) } this.packet = new Packet() return true } Parser.prototype._emitError = function (err) { this.error = err this.emit('error', err) } module.exports = Parser mqtt-packet-5.4.0/test.js000066400000000000000000000544561312243656700153160ustar00rootroot00000000000000'use strict' var test = require('tape') var mqtt = require('./') var Buffer = require('safe-buffer').Buffer var WS = require('readable-stream').Writable 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, expected, 'expected packet') }) 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() }) test(name + ' mirror', function (t) { t.plan(2) var parser = mqtt.parser(opts) var expected = object var fixture = mqtt.generate(object) parser.on('packet', function (packet) { if (packet.cmd !== 'publish') { delete packet.topic delete packet.payload } t.deepEqual(packet, expected, 'expected packet') }) t.equal(parser.parse(fixture), 0, 'remaining bytes') }) } function testParseError (expected, fixture) { test(expected, function (t) { t.plan(1) var parser = mqtt.parser() 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) { test(expected, function (t) { t.plan(1) try { mqtt.generate(fixture) } 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('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('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 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) ])) ;(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('pubrec', { cmd: 'pubrec', retain: false, qos: 0, dup: false, length: 2, messageId: 2 }, Buffer.from([ 80, 2, // Header 0, 2 // Message ID ])) testParseGenerate('pubrel', { cmd: 'pubrel', retain: false, qos: 1, dup: false, length: 2, messageId: 2 }, Buffer.from([ 98, 2, // Header 0, 2 // Message ID ])) testParseGenerate('pubcomp', { cmd: 'pubcomp', retain: false, qos: 0, dup: false, length: 2, messageId: 2 }, Buffer.from([ 112, 2, // Header 0, 2 // Message ID ])) 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 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('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('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('unsuback', { cmd: 'unsuback', retain: false, qos: 0, dup: false, length: 2, messageId: 8 }, Buffer.from([ 176, 2, // Header 0, 8 // Message ID ])) 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 ])) 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 }) 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 ])) 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-5.4.0/testRandom.js000066400000000000000000000050141312243656700164410ustar00rootroot00000000000000'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 occured 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-5.4.0/writeToStream.js000066400000000000000000000337771312243656700171530ustar00rootroot00000000000000'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') var numCache = numbers.cache var generateNumber = numbers.generateNumber var generateCache = numbers.generateCache var writeNumber = writeNumberCached var toGenerate = true function generate (packet, stream) { if (stream.cork) { stream.cork() nextTick(uncork, stream) } if (toGenerate) { toGenerate = false generateCache() } switch (packet.cmd) { case 'connect': return connect(packet, stream) case 'connack': return connack(packet, stream) case 'publish': return publish(packet, stream) case 'puback': case 'pubrec': case 'pubrel': case 'pubcomp': case 'unsuback': return confirmation(packet, stream) case 'subscribe': return subscribe(packet, stream) case 'suback': return suback(packet, stream) case 'unsubscribe': return unsubscribe(packet, stream) case 'pingreq': case 'pingresp': case 'disconnect': return emptyPacket(packet, stream) 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 (opts, stream) { var settings = opts || {} 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 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 if (protocolVersion !== 3 && protocolVersion !== 4) { 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 // 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 if (will.payload && will.payload) { if (will.payload.length >= 0) { if (typeof will.payload === 'string') { length += Buffer.byteLength(will.payload) + 2 } else { length += will.payload.length + 2 } } else { stream.emit('error', new Error('Invalid will payload')) return false } } else { length += 2 } } // Username if (username) { if (username.length) { length += Buffer.byteLength(username) + 2 } else { stream.emit('error', new Error('Invalid username')) return false } } // Password if (password) { if (password.length) { length += byteLength(password) + 2 } else { stream.emit('error', new Error('Invalid password')) return false } } // Generate header stream.write(protocol.CONNECT_HEADER) // Generate length writeLength(stream, length) // Generate protocol ID writeStringOrBuffer(stream, protocolId) stream.write( protocolVersion === 4 ? protocol.VERSION4 : protocol.VERSION3 ) // Connect flags var flags = 0 flags |= username ? protocol.USERNAME_MASK : 0 flags |= password ? 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) // Client ID writeStringOrBuffer(stream, clientId) // Will if (will) { writeString(stream, will.topic) writeStringOrBuffer(stream, will.payload) } // Username and password if (username) writeStringOrBuffer(stream, username) if (password) 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 (opts, stream) { var settings = opts || {} var rc = settings.returnCode // Check return code if (typeof rc !== 'number') { stream.emit('error', new Error('Invalid return code')) return false } stream.write(protocol.CONNACK_HEADER) writeLength(stream, 2) stream.write(opts.sessionPresent ? protocol.SESSIONPRESENT_HEADER : zeroBuf) return stream.write(Buffer.from([rc])) } function publish (opts, stream) { var settings = opts || {} 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 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 // Header stream.write(protocol.PUBLISH_HEADER[qos][opts.dup ? 1 : 0][retain ? 1 : 0]) // Remaining length writeLength(stream, length) // Topic writeNumber(stream, byteLength(topic)) stream.write(topic) // Message ID if (qos > 0) writeNumber(stream, id) // Payload return stream.write(payload) } /* Puback, pubrec, pubrel and pubcomp */ function confirmation (opts, stream) { var settings = opts || {} var type = settings.cmd || 'puback' var id = settings.messageId var dup = (settings.dup && type === 'pubrel') ? protocol.DUP_MASK : 0 var qos = 0 if (type === 'pubrel') qos = 1 // Check message ID if (typeof id !== 'number') { stream.emit('error', new Error('Invalid messageId')) return false } // Header stream.write(protocol.ACKS[type][qos][dup][0]) // Length writeLength(stream, 2) // Message ID return writeNumber(stream, id) } function subscribe (opts, stream) { var settings = opts || {} var dup = settings.dup ? protocol.DUP_MASK : 0 var id = settings.messageId var subs = settings.subscriptions var length = 0 // Check message ID if (typeof id !== 'number') { stream.emit('error', new Error('Invalid messageId')) return false } else length += 2 // 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 } length += Buffer.byteLength(itopic) + 2 + 1 } } else { stream.emit('error', new Error('Invalid subscriptions')) return false } // Generate header stream.write(protocol.SUBSCRIBE_HEADER[1][dup ? 1 : 0][0]) // Generate length writeLength(stream, length) // Generate message ID writeNumber(stream, id) 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 // Write topic string writeString(stream, jtopic) // Write qos result = stream.write(protocol.QOS[jqos]) } return result } function suback (opts, stream) { var settings = opts || {} var id = settings.messageId var granted = settings.granted 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 } // header stream.write(protocol.SUBACK_HEADER) // Length writeLength(stream, length) // Message ID writeNumber(stream, id) return stream.write(Buffer.from(granted)) } function unsubscribe (opts, stream) { var settings = opts || {} var id = settings.messageId var dup = settings.dup ? protocol.DUP_MASK : 0 var unsubs = settings.unsubscriptions 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 } // Header stream.write(protocol.UNSUBSCRIBE_HEADER[1][dup ? 1 : 0][0]) // Length writeLength(stream, length) // Message ID writeNumber(stream, id) // Unsubs var result = true for (var j = 0; j < unsubs.length; j++) { result = writeString(stream, unsubs[j]) } return result } function emptyPacket (opts, stream) { return stream.write(protocol.EMPTY[opts.cmd]) } /** * calcLengthLength - calculate the length of the remaining * length field * * @api private */ function calcLengthLength (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 genBufLength (length) { var digit = 0 var pos = 0 var buffer = Buffer.allocUnsafe(calcLengthLength(length)) do { digit = length % 128 | 0 length = length / 128 | 0 if (length > 0) digit = digit | 0x80 buffer.writeUInt8(digit, pos++, true) } while (length > 0) return buffer } /** * writeLength - write an MQTT style length field to the buffer * * @param buffer - destination * @param pos - offset * @param length - length (>0) * @returns number of bytes written * * @api private */ var lengthCache = {} function writeLength (stream, length) { var buffer = lengthCache[length] if (!buffer) { buffer = genBufLength(length) if (length < 16384) lengthCache[length] = 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) stream.write(string, 'utf8') } /** * 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) { return stream.write(numCache[number]) } function writeNumberGenerated (stream, number) { return stream.write(generateNumber(number)) } /** * 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 (toWrite && typeof toWrite === 'string') writeString(stream, toWrite) else if (toWrite) { writeNumber(stream, toWrite.length) stream.write(toWrite) } else writeNumber(stream, 0) } function byteLength (bufOrString) { if (!bufOrString) return 0 else if (Buffer.isBuffer(bufOrString)) return bufOrString.length else return Buffer.byteLength(bufOrString) } module.exports = generate