var bl = require('bl') , inherits = require('inherits') , EE = require('events').EventEmitter , Packet = require('./packet') , 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 , mul = 1 , length = 0 , result = true , 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 // constants id , clientId // Client id , topic // Will topic , payload // Will payload , password // Password , username // Username , flags = {} , packet = this.packet // Parse constants id protocolId = this._parseString() if (protocolId === null) return this._emitError(new Error('cannot parse protocol id')) if (protocolId != 'MQTT' && protocolId != 'MQIsdp') { return this._emitError(new Error('invalid protocol id')) } 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 client ID 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 username')) 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 message ID 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 , topic , 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('Parse error - cannot parse topic')) if (this._pos >= packet.length) return this._emitError(new Error('Malformed Subscribe Payload')) 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 message ID 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 message id')) } Parser.prototype._parseMessageId = function() { var packet = this.packet packet.messageId = this._parseNum() if(packet.messageId === null) { this._emitError(new Error('cannot parse message id')) return false } return true } Parser.prototype._parseString = function(maybeBuffer) { var length = this._parseNum() , result , 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() , result , 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