UNPKG

nxkit

Version:

This is a collection of tools, independent of any other libraries

363 lines (362 loc) 13.3 kB
"use strict"; /* ***** BEGIN LICENSE BLOCK ***** * Distributed under the BSD license: * * Copyright (c) 2015, xuewen.chu * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of xuewen.chu nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL xuewen.chu BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * ***** END LICENSE BLOCK ***** */ Object.defineProperty(exports, "__esModule", { value: true }); const bl_1 = require("./bl"); const events_1 = require("events"); const constants_1 = require("./constants"); class Packet { } exports.Packet = Packet; class PacketIMPL extends Packet { constructor() { super(...arguments); this.length = -1; this.qos = 0; } } class Parser extends events_1.EventEmitter { constructor() { super(...arguments); this._states = [ '_parseHeader', '_parseLength', '_parsePayload', '_newPacket' ]; this._list = new bl_1.default(); this._stateCounter = 0; this._pos = 0; this.packet = new PacketIMPL(); this.error = null; } _resetState() { this.packet = new PacketIMPL(); this.error = null; this._list = new bl_1.default(); this._stateCounter = 0; } parse(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; } _parseHeader() { // There is at least one byte in the buffer var zero = this._list.readUInt8(0); this.packet.cmd = constants_1.default.types[zero >> constants_1.default.CMD_SHIFT]; this.packet.retain = (zero & constants_1.default.RETAIN_MASK) !== 0; this.packet.qos = (zero >> constants_1.default.QOS_SHIFT) & constants_1.default.QOS_MASK; this.packet.dup = (zero & constants_1.default.DUP_MASK) !== 0; this._list.consume(1); return true; } _parseLength() { // 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_1.default.LENGTH_MASK); mul *= 0x80; if ((current & constants_1.default.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; } _parsePayload() { 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; } _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) { 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_1.default.USERNAME_MASK); flags.password = (this._list.readUInt8(this._pos) & constants_1.default.PASSWORD_MASK); // var will = flags.will = (this._list.readUInt8(this._pos) & constants_1.default.WILL_FLAG_MASK); if (flags.will) { packet.will = { topic: '', payload: '', }; packet.will.retain = (this._list.readUInt8(this._pos) & constants_1.default.WILL_RETAIN_MASK) !== 0; packet.will.qos = (this._list.readUInt8(this._pos) & constants_1.default.WILL_QOS_MASK) >> constants_1.default.WILL_QOS_SHIFT; } packet.clean = (this._list.readUInt8(this._pos) & constants_1.default.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 (packet.will) { // Parse will topic topic = this._parseString(); if (!topic) 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; } _parseConnack() { var packet = this.packet; if (this._list.length < 2) return null; packet.sessionPresent = !!(this._list.readUInt8(this._pos++) & constants_1.default.SESSIONPRESENT_MASK); packet.returnCode = this._list.readUInt8(this._pos); if (packet.returnCode === -1) return this._emitError(new Error('Cannot parse return code')); } _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; } packet.payload = this._list.slice(this._pos, packet.length); } _parseSubscribe() { 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) 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 }); } } _parseSuback() { 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++)); } } _parseUnsubscribe() { 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) return this._emitError(new Error('Cannot parse topic')); // Push topic to unsubscriptions packet.unsubscriptions.push(topic); } } _parseUnsuback() { if (!this._parseMessageId()) return this._emitError(new Error('Cannot parse messageId')); } _parseMessageId() { var packet = this.packet; packet.messageId = this._parseNum(); if (packet.messageId === null) { this._emitError(new Error('Cannot parse messageId')); return false; } return true; } _parseString( /*maybeBuffer?: Buffer*/) { var length = this._parseNum(); var result; var end = length + this._pos; if (length === -1 || end > this._list.length || end > this.packet.length) return; result = this._list.toString('utf8', this._pos, end); this._pos += length; return result; } _parseBuffer() { 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; } _parseNum() { if (this._list.length - this._pos < 2) return -1; var result = this._list.readUInt16BE(this._pos); this._pos += 2; return result; } _newPacket() { if (this.packet) { this._list.consume(this.packet.length); this.emit('packet', this.packet); } this.packet = new PacketIMPL(); return true; } _emitError(err) { this.error = err; this.emit('error', err); } } exports.default = Parser;