You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
403 lines
9.3 KiB
403 lines
9.3 KiB
|
|
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
|
|
|