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.
 
 
 

180 lines
3.8 KiB

/*!
* 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 () {
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) {
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) {
this.reset();
this.onerror(reason, 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;
}