UNPKG

nxkit

Version:

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

401 lines (400 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 event_1 = require("../event"); const buffer_1 = require("../buffer"); /* * Unpacks a buffer to a number. * * @api public */ function _unpack(buffer) { var n = 0; for (var i = 0; i < buffer.length; i++) { n = i ? (n * 256) + buffer[i] : buffer[i]; } return n; } function _concat(buffers) { return buffers.length == 1 ? buffers[0] : buffer_1.default.concat(buffers); } /** * @class PacketParser */ class PacketParser { /* * WebSocket PacketParser * * @api public */ constructor() { this.state = { activeFragmentedOperation: null, lastFragment: false, masked: false, opcode: 0 }; this.overflow = null; this.expectOffset = 0; this.expectBuffer = null; this.expectHandler = null; this.currentMessage = null; this.onClose = new event_1.EventNoticer('Close', this); this.onText = new event_1.EventNoticer('Text', this); this.onData = new event_1.EventNoticer('Data', this); this.onError = new event_1.EventNoticer('Error', this); this.onPing = new event_1.EventNoticer('Ping', this); this.onPong = new event_1.EventNoticer('Pong', this); this.opcodeHandlers = { '1': (data) => { this._decode(data, (mask, data) => { if (this.currentMessage) { this.currentMessage += this.unmask(mask, data).toString('utf8'); } else { this.currentMessage = this.unmask(mask, data).toString('utf8'); } if (this.state.lastFragment) { this.onText.trigger(this.currentMessage); this.currentMessage = null; } this.endPacket(); }); }, '2': (data) => { this._decode(data, (mask, data) => { if (this.currentMessage) { this.currentMessage.push(this.unmask(mask, data)); } else { this.currentMessage = [this.unmask(mask, data)]; } if (this.state.lastFragment) { this.onData.trigger(_concat(this.currentMessage)); this.currentMessage = null; } this.endPacket(); }); }, // 0x3 - 0x7: Retain, for non-control frame '8': (data) => { this.onClose.trigger({}); this.reset(); }, '9': (data) => { if (this.state.lastFragment == false) { this.error('fragmented ping is not supported'); return; } this._decode(data, (mask, data) => { this.onPing.trigger(this.unmask(mask, data)); this.endPacket(); }); }, '10': (data) => { if (this.state.lastFragment == false) { this.error('fragmented pong is not supported'); return; } this._decode(data, (mask, data) => { this.onPong.trigger(this.unmask(mask, data)); this.endPacket(); }); }, }; this.expect('Opcode', 2, this.processPacket); } _expectData(length, finish) { var self = this; if (self.state.masked) { self.expect('Mask', 4, function (data) { var mask = data; self.expect('Data', length, function (data) { finish(mask, data); }); }); } else { self.expect('Data', length, function (data) { finish(null, data); }); } } _decode(data, finish) { var self = this; // decode length var firstLength = data[1] & 0x7f; if (firstLength < 126) { self._expectData(firstLength, finish); } else if (firstLength == 126) { self.expect('Length', 2, function (data) { self._expectData(_unpack(data), finish); }); } else if (firstLength == 127) { self.expect('Length', 8, function (data) { if (_unpack(data.slice(0, 4)) != 0) { self.error('packets with length spanning more than 32 bit is currently not supported'); return; } // var lengthBytes = data.slice(4); // note: cap to 32 bit length self._expectData(_unpack(data.slice(4, 8)), finish); }); } } /* * Add new data to the parser. * * @api public */ add(data) { if (this.expectBuffer == null) { this.addToOverflow(data); return; } var toRead = Math.min(data.length, this.expectBuffer.length - this.expectOffset); data.copy(this.expectBuffer, this.expectOffset, 0, toRead); this.expectOffset += toRead; if (toRead < data.length) { // at this point the overflow buffer shouldn't at all exist this.overflow = buffer_1.default.alloc(data.length - toRead); data.copy(this.overflow, 0, toRead, toRead + this.overflow.length); } if (this.expectOffset == this.expectBuffer.length) { var bufferForHandler = this.expectBuffer; this.expectBuffer = null; this.expectOffset = 0; this.expectHandler.call(this, bufferForHandler); } } /* * Adds a piece of data to the overflow. * * @api private */ addToOverflow(data) { if (this.overflow == null) this.overflow = data; else { var prevOverflow = this.overflow; this.overflow = buffer_1.default.alloc(this.overflow.length + data.length); prevOverflow.copy(this.overflow, 0); data.copy(this.overflow, prevOverflow.length); } } /* * Waits for a certain amount of bytes to be available, then fires a callback. * * @api private */ expect(what, length, handler) { this.expectBuffer = buffer_1.default.alloc(length); this.expectOffset = 0; this.expectHandler = handler; if (this.overflow != null) { var toOverflow = this.overflow; this.overflow = null; this.add(toOverflow); } } /* * Start processing a new packet. * * @api private */ processPacket(data) { if ((data[0] & 0x70) != 0) { this.error('reserved fields must be empty'); } this.state.lastFragment = (data[0] & 0x80) == 0x80; this.state.masked = (data[1] & 0x80) == 0x80; var opcode = data[0] & 0xf; if (opcode == 0) { // continuation frame this.state.opcode = this.state.activeFragmentedOperation; if (!(this.state.opcode == 1 || this.state.opcode == 2)) { this.error('continuation frame cannot follow current opcode'); return; } } else { this.state.opcode = opcode; if (this.state.lastFragment === false) { this.state.activeFragmentedOperation = opcode; } } var handler = this.opcodeHandlers[String(this.state.opcode)]; if (typeof handler == 'undefined') { this.error('no handler for opcode ' + this.state.opcode); } else { handler(data); } } /* * Endprocessing a packet. * * @api private */ endPacket() { this.expectOffset = 0; this.expectBuffer = null; this.expectHandler = null; if (this.state.lastFragment && this.state.opcode == this.state.activeFragmentedOperation) { // end current fragmented operation this.state.activeFragmentedOperation = null; } this.state.lastFragment = false; this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0; this.state.masked = false; this.expect('Opcode', 2, this.processPacket); } /* * Reset the parser state. * * @api private */ reset() { this.state = { activeFragmentedOperation: null, lastFragment: false, masked: false, opcode: 0 }; this.expectOffset = 0; this.expectBuffer = null; this.expectHandler = null; this.overflow = null; this.currentMessage = null; } /* * Unmask received data. * * @api private */ unmask(mask, buf) { if (mask != null) { for (var i = 0, ll = buf.length; i < ll; i++) { buf[i] ^= mask[i % 4]; } } return buf; } /** * Handles an error * * @api private */ error(reason) { this.reset(); this.onError.trigger(Error.new(reason)); return this; } } exports.PacketParser = PacketParser; /* * @func sendDataPacket() Frame server-to-client output as a text packet. * @static */ function sendDataPacket(socket, data, cb) { var opcode = 0x81; // text 0x81 | buffer 0x82 | close 0x88 | ping 0x89 if (data instanceof Uint8Array) { opcode = 0x82; // data = buffer.from(data.buffer); } else { // send json string message var s = JSON.stringify(data); data = buffer_1.default.from(s); } var dataLength = data.length; var headerLength = 2; var secondByte = dataLength; /* 0 - 125 : 2, opcode|len|data 126 - 65535 : 4, opcode|126|len|len|data 65536 - : 10, opcode|127|len|len|len|len|len|len|len|len|data */ /* opcode: 0x81: text 0x82: binary 0x88: close 0x89: ping 0x8a: pong */ if (dataLength > 65535) { headerLength = 10; secondByte = 127; } else if (dataLength > 125) { headerLength = 4; secondByte = 126; } var header = buffer_1.default.alloc(headerLength); header[0] = opcode; header[1] = secondByte; switch (secondByte) { case 126: header[2] = dataLength >> 8; header[3] = dataLength % 256; break; case 127: var l = dataLength; for (var i = 9; i > 1; i--) { header[i] = l & 0xff; l >>= 8; } } return socket.write(buffer_1.default.concat([header, data]), cb); } exports.sendDataPacket = sendDataPacket; /** * @func sendPingPacket() */ function sendPingPacket(socket, cb) { var header = buffer_1.default.alloc(3); header[0] = 0x89; header[1] = 1; // 1byte return socket.write(header, cb); } exports.sendPingPacket = sendPingPacket; /** * @func sendPongPacket() */ function sendPongPacket(socket, cb) { var header = buffer_1.default.alloc(3); header[0] = 0x8a; header[1] = 1; // 1byte return socket.write(header, cb); } exports.sendPongPacket = sendPongPacket;