|
|
/*! * ws: a node.js websocket client * Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com> * MIT Licensed */
var util = require('util');
/** * State constants */
var EMPTY = 0 , BODY = 1; var BINARYLENGTH = 2 , BINARYBODY = 3;
/** * Hixie Receiver implementation */
function Receiver () { if (this instanceof Receiver === false) { throw new TypeError("Classes can't be function-called"); }
this.state = EMPTY; this.buffers = []; this.messageEnd = -1; this.spanLength = 0; this.dead = false;
this.onerror = function() {}; this.ontext = function() {}; this.onbinary = function() {}; this.onclose = function() {}; this.onping = function() {}; this.onpong = function() {}; }
module.exports = Receiver;
/** * Add new data to the parser. * * @api public */
Receiver.prototype.add = function(data) { if (this.dead) return; var self = this; function doAdd() { if (self.state === EMPTY) { if (data.length == 2 && data[0] == 0xFF && data[1] == 0x00) { self.reset(); self.onclose(); return; } if (data[0] === 0x80) { self.messageEnd = 0; self.state = BINARYLENGTH; data = data.slice(1); } else {
if (data[0] !== 0x00) { self.error('payload must start with 0x00 byte', true); return; } data = data.slice(1); self.state = BODY;
} } if (self.state === BINARYLENGTH) { var i = 0; while ((i < data.length) && (data[i] & 0x80)) { self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); ++i; } if (i < data.length) { self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); self.state = BINARYBODY; ++i; } if (i > 0) data = data.slice(i); } if (self.state === BINARYBODY) { var dataleft = self.messageEnd - self.spanLength; if (data.length >= dataleft) { // consume the whole buffer to finish the frame
self.buffers.push(data); self.spanLength += dataleft; self.messageEnd = dataleft; return self.parse(); } // frame's not done even if we consume it all
self.buffers.push(data); self.spanLength += data.length; return; } self.buffers.push(data); if ((self.messageEnd = bufferIndex(data, 0xFF)) != -1) { self.spanLength += self.messageEnd; return self.parse(); } else self.spanLength += data.length; } while(data) data = doAdd(); };
/** * Releases all resources used by the receiver. * * @api public */
Receiver.prototype.cleanup = function() { this.dead = true; this.state = EMPTY; this.buffers = []; };
/** * Process buffered data. * * @api public */
Receiver.prototype.parse = function() { var output = new Buffer(this.spanLength); var outputIndex = 0; for (var bi = 0, bl = this.buffers.length; bi < bl - 1; ++bi) { var buffer = this.buffers[bi]; buffer.copy(output, outputIndex); outputIndex += buffer.length; } var lastBuffer = this.buffers[this.buffers.length - 1]; if (this.messageEnd > 0) lastBuffer.copy(output, outputIndex, 0, this.messageEnd); if (this.state !== BODY) --this.messageEnd; var tail = null; if (this.messageEnd < lastBuffer.length - 1) { tail = lastBuffer.slice(this.messageEnd + 1); } this.reset(); this.ontext(output.toString('utf8')); return tail; };
/** * Handles an error * * @api private */
Receiver.prototype.error = function (reason, terminate) { if (this.dead) return; this.reset(); if(typeof reason == 'string'){ this.onerror(new Error(reason), terminate); } else if(reason.constructor == Error){ this.onerror(reason, terminate); } else{ this.onerror(new Error("An error occured"),terminate); } return this; };
/** * Reset parser state * * @api private */
Receiver.prototype.reset = function (reason) { if (this.dead) return; this.state = EMPTY; this.buffers = []; this.messageEnd = -1; this.spanLength = 0; };
/** * Internal api */
function bufferIndex(buffer, byte) { for (var i = 0, l = buffer.length; i < l; ++i) { if (buffer[i] === byte) return i; } return -1; }
|