| /*! |
| * 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; |
| } |