This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
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.
793 lines
23 KiB
793 lines
23 KiB
/*! |
|
* ws: a node.js websocket client |
|
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com> |
|
* MIT Licensed |
|
*/ |
|
|
|
var util = require('util') |
|
, isValidUTF8 = require('./Validation') |
|
, ErrorCodes = require('./ErrorCodes') |
|
, BufferPool = require('./BufferPool') |
|
, bufferUtil = require('./BufferUtil') |
|
, PerMessageDeflate = require('./PerMessageDeflate'); |
|
|
|
/** |
|
* HyBi Receiver implementation |
|
*/ |
|
|
|
function Receiver (extensions,maxPayload) { |
|
if (this instanceof Receiver === false) { |
|
throw new TypeError("Classes can't be function-called"); |
|
} |
|
if(typeof extensions==='number'){ |
|
maxPayload=extensions; |
|
extensions={}; |
|
} |
|
|
|
|
|
// memory pool for fragmented messages |
|
var fragmentedPoolPrevUsed = -1; |
|
this.fragmentedBufferPool = new BufferPool(1024, function(db, length) { |
|
return db.used + length; |
|
}, function(db) { |
|
return fragmentedPoolPrevUsed = fragmentedPoolPrevUsed >= 0 ? |
|
Math.ceil((fragmentedPoolPrevUsed + db.used) / 2) : |
|
db.used; |
|
}); |
|
|
|
// memory pool for unfragmented messages |
|
var unfragmentedPoolPrevUsed = -1; |
|
this.unfragmentedBufferPool = new BufferPool(1024, function(db, length) { |
|
return db.used + length; |
|
}, function(db) { |
|
return unfragmentedPoolPrevUsed = unfragmentedPoolPrevUsed >= 0 ? |
|
Math.ceil((unfragmentedPoolPrevUsed + db.used) / 2) : |
|
db.used; |
|
}); |
|
this.extensions = extensions || {}; |
|
this.maxPayload = maxPayload || 0; |
|
this.currentPayloadLength = 0; |
|
this.state = { |
|
activeFragmentedOperation: null, |
|
lastFragment: false, |
|
masked: false, |
|
opcode: 0, |
|
fragmentedOperation: false |
|
}; |
|
this.overflow = []; |
|
this.headerBuffer = new Buffer(10); |
|
this.expectOffset = 0; |
|
this.expectBuffer = null; |
|
this.expectHandler = null; |
|
this.currentMessage = []; |
|
this.currentMessageLength = 0; |
|
this.messageHandlers = []; |
|
this.expectHeader(2, this.processPacket); |
|
this.dead = false; |
|
this.processing = 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 dataLength = data.length; |
|
if (dataLength == 0) return; |
|
if (this.expectBuffer == null) { |
|
this.overflow.push(data); |
|
return; |
|
} |
|
var toRead = Math.min(dataLength, this.expectBuffer.length - this.expectOffset); |
|
fastCopy(toRead, data, this.expectBuffer, this.expectOffset); |
|
this.expectOffset += toRead; |
|
if (toRead < dataLength) { |
|
this.overflow.push(data.slice(toRead)); |
|
} |
|
while (this.expectBuffer && this.expectOffset == this.expectBuffer.length) { |
|
var bufferForHandler = this.expectBuffer; |
|
this.expectBuffer = null; |
|
this.expectOffset = 0; |
|
this.expectHandler.call(this, bufferForHandler); |
|
} |
|
}; |
|
|
|
/** |
|
* Releases all resources used by the receiver. |
|
* |
|
* @api public |
|
*/ |
|
|
|
Receiver.prototype.cleanup = function() { |
|
this.dead = true; |
|
this.overflow = null; |
|
this.headerBuffer = null; |
|
this.expectBuffer = null; |
|
this.expectHandler = null; |
|
this.unfragmentedBufferPool = null; |
|
this.fragmentedBufferPool = null; |
|
this.state = null; |
|
this.currentMessage = null; |
|
this.onerror = null; |
|
this.ontext = null; |
|
this.onbinary = null; |
|
this.onclose = null; |
|
this.onping = null; |
|
this.onpong = null; |
|
}; |
|
|
|
/** |
|
* Waits for a certain amount of header bytes to be available, then fires a callback. |
|
* |
|
* @api private |
|
*/ |
|
|
|
Receiver.prototype.expectHeader = function(length, handler) { |
|
if (length == 0) { |
|
handler(null); |
|
return; |
|
} |
|
this.expectBuffer = this.headerBuffer.slice(this.expectOffset, this.expectOffset + length); |
|
this.expectHandler = handler; |
|
var toRead = length; |
|
while (toRead > 0 && this.overflow.length > 0) { |
|
var fromOverflow = this.overflow.pop(); |
|
if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); |
|
var read = Math.min(fromOverflow.length, toRead); |
|
fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); |
|
this.expectOffset += read; |
|
toRead -= read; |
|
} |
|
}; |
|
|
|
/** |
|
* Waits for a certain amount of data bytes to be available, then fires a callback. |
|
* |
|
* @api private |
|
*/ |
|
|
|
Receiver.prototype.expectData = function(length, handler) { |
|
if (length == 0) { |
|
handler(null); |
|
return; |
|
} |
|
this.expectBuffer = this.allocateFromPool(length, this.state.fragmentedOperation); |
|
this.expectHandler = handler; |
|
var toRead = length; |
|
while (toRead > 0 && this.overflow.length > 0) { |
|
var fromOverflow = this.overflow.pop(); |
|
if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead)); |
|
var read = Math.min(fromOverflow.length, toRead); |
|
fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset); |
|
this.expectOffset += read; |
|
toRead -= read; |
|
} |
|
}; |
|
|
|
/** |
|
* Allocates memory from the buffer pool. |
|
* |
|
* @api private |
|
*/ |
|
|
|
Receiver.prototype.allocateFromPool = function(length, isFragmented) { |
|
return (isFragmented ? this.fragmentedBufferPool : this.unfragmentedBufferPool).get(length); |
|
}; |
|
|
|
/** |
|
* Start processing a new packet. |
|
* |
|
* @api private |
|
*/ |
|
|
|
Receiver.prototype.processPacket = function (data) { |
|
if (this.extensions[PerMessageDeflate.extensionName]) { |
|
if ((data[0] & 0x30) != 0) { |
|
this.error('reserved fields (2, 3) must be empty', 1002); |
|
return; |
|
} |
|
} else { |
|
if ((data[0] & 0x70) != 0) { |
|
this.error('reserved fields must be empty', 1002); |
|
return; |
|
} |
|
} |
|
this.state.lastFragment = (data[0] & 0x80) == 0x80; |
|
this.state.masked = (data[1] & 0x80) == 0x80; |
|
var compressed = (data[0] & 0x40) == 0x40; |
|
var opcode = data[0] & 0xf; |
|
if (opcode === 0) { |
|
if (compressed) { |
|
this.error('continuation frame cannot have the Per-message Compressed bits', 1002); |
|
return; |
|
} |
|
// continuation frame |
|
this.state.fragmentedOperation = true; |
|
this.state.opcode = this.state.activeFragmentedOperation; |
|
if (!(this.state.opcode == 1 || this.state.opcode == 2)) { |
|
this.error('continuation frame cannot follow current opcode', 1002); |
|
return; |
|
} |
|
} |
|
else { |
|
if (opcode < 3 && this.state.activeFragmentedOperation != null) { |
|
this.error('data frames after the initial data frame must have opcode 0', 1002); |
|
return; |
|
} |
|
if (opcode >= 8 && compressed) { |
|
this.error('control frames cannot have the Per-message Compressed bits', 1002); |
|
return; |
|
} |
|
this.state.compressed = compressed; |
|
this.state.opcode = opcode; |
|
if (this.state.lastFragment === false) { |
|
this.state.fragmentedOperation = true; |
|
this.state.activeFragmentedOperation = opcode; |
|
} |
|
else this.state.fragmentedOperation = false; |
|
} |
|
var handler = opcodes[this.state.opcode]; |
|
if (typeof handler == 'undefined') this.error('no handler for opcode ' + this.state.opcode, 1002); |
|
else { |
|
handler.start.call(this, data); |
|
} |
|
}; |
|
|
|
/** |
|
* Endprocessing a packet. |
|
* |
|
* @api private |
|
*/ |
|
|
|
Receiver.prototype.endPacket = function() { |
|
if (this.dead) return; |
|
if (!this.state.fragmentedOperation) this.unfragmentedBufferPool.reset(true); |
|
else if (this.state.lastFragment) this.fragmentedBufferPool.reset(true); |
|
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.currentPayloadLength = 0; |
|
this.state.lastFragment = false; |
|
this.state.opcode = this.state.activeFragmentedOperation != null ? this.state.activeFragmentedOperation : 0; |
|
this.state.masked = false; |
|
this.expectHeader(2, this.processPacket); |
|
}; |
|
|
|
/** |
|
* Reset the parser state. |
|
* |
|
* @api private |
|
*/ |
|
|
|
Receiver.prototype.reset = function() { |
|
if (this.dead) return; |
|
this.state = { |
|
activeFragmentedOperation: null, |
|
lastFragment: false, |
|
masked: false, |
|
opcode: 0, |
|
fragmentedOperation: false |
|
}; |
|
this.fragmentedBufferPool.reset(true); |
|
this.unfragmentedBufferPool.reset(true); |
|
this.expectOffset = 0; |
|
this.expectBuffer = null; |
|
this.expectHandler = null; |
|
this.overflow = []; |
|
this.currentMessage = []; |
|
this.currentMessageLength = 0; |
|
this.messageHandlers = []; |
|
this.currentPayloadLength = 0; |
|
}; |
|
|
|
/** |
|
* Unmask received data. |
|
* |
|
* @api private |
|
*/ |
|
|
|
Receiver.prototype.unmask = function (mask, buf, binary) { |
|
if (mask != null && buf != null) bufferUtil.unmask(buf, mask); |
|
if (binary) return buf; |
|
return buf != null ? buf.toString('utf8') : ''; |
|
}; |
|
|
|
/** |
|
* Handles an error |
|
* |
|
* @api private |
|
*/ |
|
|
|
Receiver.prototype.error = function (reason, protocolErrorCode) { |
|
if (this.dead) return; |
|
this.reset(); |
|
if(typeof reason == 'string'){ |
|
this.onerror(new Error(reason), protocolErrorCode); |
|
} |
|
else if(reason.constructor == Error){ |
|
this.onerror(reason, protocolErrorCode); |
|
} |
|
else{ |
|
this.onerror(new Error("An error occured"),protocolErrorCode); |
|
} |
|
return this; |
|
}; |
|
|
|
/** |
|
* Execute message handler buffers |
|
* |
|
* @api private |
|
*/ |
|
|
|
Receiver.prototype.flush = function() { |
|
if (this.processing || this.dead) return; |
|
|
|
var handler = this.messageHandlers.shift(); |
|
if (!handler) return; |
|
|
|
this.processing = true; |
|
var self = this; |
|
|
|
handler(function() { |
|
self.processing = false; |
|
self.flush(); |
|
}); |
|
}; |
|
|
|
/** |
|
* Apply extensions to message |
|
* |
|
* @api private |
|
*/ |
|
|
|
Receiver.prototype.applyExtensions = function(messageBuffer, fin, compressed, callback) { |
|
var self = this; |
|
if (compressed) { |
|
this.extensions[PerMessageDeflate.extensionName].decompress(messageBuffer, fin, function(err, buffer) { |
|
if (self.dead) return; |
|
if (err) { |
|
callback(new Error('invalid compressed data')); |
|
return; |
|
} |
|
callback(null, buffer); |
|
}); |
|
} else { |
|
callback(null, messageBuffer); |
|
} |
|
}; |
|
|
|
/** |
|
* Checks payload size, disconnects socket when it exceeds maxPayload |
|
* |
|
* @api private |
|
*/ |
|
Receiver.prototype.maxPayloadExceeded = function(length) { |
|
if (this.maxPayload=== undefined || this.maxPayload === null || this.maxPayload < 1) { |
|
return false; |
|
} |
|
var fullLength = this.currentPayloadLength + length; |
|
if (fullLength < this.maxPayload) { |
|
this.currentPayloadLength = fullLength; |
|
return false; |
|
} |
|
this.error('payload cannot exceed ' + this.maxPayload + ' bytes', 1009); |
|
this.messageBuffer=[]; |
|
this.cleanup(); |
|
|
|
return true; |
|
}; |
|
|
|
/** |
|
* Buffer utilities |
|
*/ |
|
|
|
function readUInt16BE(start) { |
|
return (this[start]<<8) + |
|
this[start+1]; |
|
} |
|
|
|
function readUInt32BE(start) { |
|
return (this[start]<<24) + |
|
(this[start+1]<<16) + |
|
(this[start+2]<<8) + |
|
this[start+3]; |
|
} |
|
|
|
function fastCopy(length, srcBuffer, dstBuffer, dstOffset) { |
|
switch (length) { |
|
default: srcBuffer.copy(dstBuffer, dstOffset, 0, length); break; |
|
case 16: dstBuffer[dstOffset+15] = srcBuffer[15]; |
|
case 15: dstBuffer[dstOffset+14] = srcBuffer[14]; |
|
case 14: dstBuffer[dstOffset+13] = srcBuffer[13]; |
|
case 13: dstBuffer[dstOffset+12] = srcBuffer[12]; |
|
case 12: dstBuffer[dstOffset+11] = srcBuffer[11]; |
|
case 11: dstBuffer[dstOffset+10] = srcBuffer[10]; |
|
case 10: dstBuffer[dstOffset+9] = srcBuffer[9]; |
|
case 9: dstBuffer[dstOffset+8] = srcBuffer[8]; |
|
case 8: dstBuffer[dstOffset+7] = srcBuffer[7]; |
|
case 7: dstBuffer[dstOffset+6] = srcBuffer[6]; |
|
case 6: dstBuffer[dstOffset+5] = srcBuffer[5]; |
|
case 5: dstBuffer[dstOffset+4] = srcBuffer[4]; |
|
case 4: dstBuffer[dstOffset+3] = srcBuffer[3]; |
|
case 3: dstBuffer[dstOffset+2] = srcBuffer[2]; |
|
case 2: dstBuffer[dstOffset+1] = srcBuffer[1]; |
|
case 1: dstBuffer[dstOffset] = srcBuffer[0]; |
|
} |
|
} |
|
|
|
function clone(obj) { |
|
var cloned = {}; |
|
for (var k in obj) { |
|
if (obj.hasOwnProperty(k)) { |
|
cloned[k] = obj[k]; |
|
} |
|
} |
|
return cloned; |
|
} |
|
|
|
/** |
|
* Opcode handlers |
|
*/ |
|
|
|
var opcodes = { |
|
// text |
|
'1': { |
|
start: function(data) { |
|
var self = this; |
|
// decode length |
|
var firstLength = data[1] & 0x7f; |
|
if (firstLength < 126) { |
|
if (self.maxPayloadExceeded(firstLength)){ |
|
self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); |
|
return; |
|
} |
|
opcodes['1'].getData.call(self, firstLength); |
|
} |
|
else if (firstLength == 126) { |
|
self.expectHeader(2, function(data) { |
|
var length = readUInt16BE.call(data, 0); |
|
if (self.maxPayloadExceeded(length)){ |
|
self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); |
|
return; |
|
} |
|
opcodes['1'].getData.call(self, length); |
|
}); |
|
} |
|
else if (firstLength == 127) { |
|
self.expectHeader(8, function(data) { |
|
if (readUInt32BE.call(data, 0) != 0) { |
|
self.error('packets with length spanning more than 32 bit is currently not supported', 1008); |
|
return; |
|
} |
|
var length = readUInt32BE.call(data, 4); |
|
if (self.maxPayloadExceeded(length)){ |
|
self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); |
|
return; |
|
} |
|
opcodes['1'].getData.call(self, readUInt32BE.call(data, 4)); |
|
}); |
|
} |
|
}, |
|
getData: function(length) { |
|
var self = this; |
|
if (self.state.masked) { |
|
self.expectHeader(4, function(data) { |
|
var mask = data; |
|
self.expectData(length, function(data) { |
|
opcodes['1'].finish.call(self, mask, data); |
|
}); |
|
}); |
|
} |
|
else { |
|
self.expectData(length, function(data) { |
|
opcodes['1'].finish.call(self, null, data); |
|
}); |
|
} |
|
}, |
|
finish: function(mask, data) { |
|
var self = this; |
|
var packet = this.unmask(mask, data, true) || new Buffer(0); |
|
var state = clone(this.state); |
|
this.messageHandlers.push(function(callback) { |
|
self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { |
|
if (err) { |
|
if(err.type===1009){ |
|
return self.error('Maximumpayload exceeded in compressed text message. Aborting...', 1009); |
|
} |
|
return self.error(err.message, 1007); |
|
} |
|
if (buffer != null) { |
|
if( self.maxPayload==0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ |
|
self.currentMessage.push(buffer); |
|
} |
|
else{ |
|
self.currentMessage=null; |
|
self.currentMessage = []; |
|
self.currentMessageLength = 0; |
|
self.error(new Error('Maximum payload exceeded. maxPayload: '+self.maxPayload), 1009); |
|
return; |
|
} |
|
self.currentMessageLength += buffer.length; |
|
} |
|
if (state.lastFragment) { |
|
var messageBuffer = Buffer.concat(self.currentMessage); |
|
self.currentMessage = []; |
|
self.currentMessageLength = 0; |
|
if (!isValidUTF8(messageBuffer)) { |
|
self.error('invalid utf8 sequence', 1007); |
|
return; |
|
} |
|
self.ontext(messageBuffer.toString('utf8'), {masked: state.masked, buffer: messageBuffer}); |
|
} |
|
callback(); |
|
}); |
|
}); |
|
this.flush(); |
|
this.endPacket(); |
|
} |
|
}, |
|
// binary |
|
'2': { |
|
start: function(data) { |
|
var self = this; |
|
// decode length |
|
var firstLength = data[1] & 0x7f; |
|
if (firstLength < 126) { |
|
if (self.maxPayloadExceeded(firstLength)){ |
|
self.error('Max payload exceeded in compressed text message. Aborting...', 1009); |
|
return; |
|
} |
|
opcodes['2'].getData.call(self, firstLength); |
|
} |
|
else if (firstLength == 126) { |
|
self.expectHeader(2, function(data) { |
|
var length = readUInt16BE.call(data, 0); |
|
if (self.maxPayloadExceeded(length)){ |
|
self.error('Max payload exceeded in compressed text message. Aborting...', 1009); |
|
return; |
|
} |
|
opcodes['2'].getData.call(self, length); |
|
}); |
|
} |
|
else if (firstLength == 127) { |
|
self.expectHeader(8, function(data) { |
|
if (readUInt32BE.call(data, 0) != 0) { |
|
self.error('packets with length spanning more than 32 bit is currently not supported', 1008); |
|
return; |
|
} |
|
var length = readUInt32BE.call(data, 4, true); |
|
if (self.maxPayloadExceeded(length)){ |
|
self.error('Max payload exceeded in compressed text message. Aborting...', 1009); |
|
return; |
|
} |
|
opcodes['2'].getData.call(self, length); |
|
}); |
|
} |
|
}, |
|
getData: function(length) { |
|
var self = this; |
|
if (self.state.masked) { |
|
self.expectHeader(4, function(data) { |
|
var mask = data; |
|
self.expectData(length, function(data) { |
|
opcodes['2'].finish.call(self, mask, data); |
|
}); |
|
}); |
|
} |
|
else { |
|
self.expectData(length, function(data) { |
|
opcodes['2'].finish.call(self, null, data); |
|
}); |
|
} |
|
}, |
|
finish: function(mask, data) { |
|
var self = this; |
|
var packet = this.unmask(mask, data, true) || new Buffer(0); |
|
var state = clone(this.state); |
|
this.messageHandlers.push(function(callback) { |
|
self.applyExtensions(packet, state.lastFragment, state.compressed, function(err, buffer) { |
|
if (err) { |
|
if(err.type===1009){ |
|
return self.error('Max payload exceeded in compressed binary message. Aborting...', 1009); |
|
} |
|
return self.error(err.message, 1007); |
|
} |
|
if (buffer != null) { |
|
if( self.maxPayload==0 || (self.maxPayload > 0 && (self.currentMessageLength + buffer.length) < self.maxPayload) ){ |
|
self.currentMessage.push(buffer); |
|
} |
|
else{ |
|
self.currentMessage=null; |
|
self.currentMessage = []; |
|
self.currentMessageLength = 0; |
|
self.error(new Error('Maximum payload exceeded'), 1009); |
|
return; |
|
} |
|
self.currentMessageLength += buffer.length; |
|
} |
|
if (state.lastFragment) { |
|
var messageBuffer = Buffer.concat(self.currentMessage); |
|
self.currentMessage = []; |
|
self.currentMessageLength = 0; |
|
self.onbinary(messageBuffer, {masked: state.masked, buffer: messageBuffer}); |
|
} |
|
callback(); |
|
}); |
|
}); |
|
this.flush(); |
|
this.endPacket(); |
|
} |
|
}, |
|
// close |
|
'8': { |
|
start: function(data) { |
|
var self = this; |
|
if (self.state.lastFragment == false) { |
|
self.error('fragmented close is not supported', 1002); |
|
return; |
|
} |
|
|
|
// decode length |
|
var firstLength = data[1] & 0x7f; |
|
if (firstLength < 126) { |
|
opcodes['8'].getData.call(self, firstLength); |
|
} |
|
else { |
|
self.error('control frames cannot have more than 125 bytes of data', 1002); |
|
} |
|
}, |
|
getData: function(length) { |
|
var self = this; |
|
if (self.state.masked) { |
|
self.expectHeader(4, function(data) { |
|
var mask = data; |
|
self.expectData(length, function(data) { |
|
opcodes['8'].finish.call(self, mask, data); |
|
}); |
|
}); |
|
} |
|
else { |
|
self.expectData(length, function(data) { |
|
opcodes['8'].finish.call(self, null, data); |
|
}); |
|
} |
|
}, |
|
finish: function(mask, data) { |
|
var self = this; |
|
data = self.unmask(mask, data, true); |
|
|
|
var state = clone(this.state); |
|
this.messageHandlers.push(function() { |
|
if (data && data.length == 1) { |
|
self.error('close packets with data must be at least two bytes long', 1002); |
|
return; |
|
} |
|
var code = data && data.length > 1 ? readUInt16BE.call(data, 0) : 1000; |
|
if (!ErrorCodes.isValidErrorCode(code)) { |
|
self.error('invalid error code', 1002); |
|
return; |
|
} |
|
var message = ''; |
|
if (data && data.length > 2) { |
|
var messageBuffer = data.slice(2); |
|
if (!isValidUTF8(messageBuffer)) { |
|
self.error('invalid utf8 sequence', 1007); |
|
return; |
|
} |
|
message = messageBuffer.toString('utf8'); |
|
} |
|
self.onclose(code, message, {masked: state.masked}); |
|
self.reset(); |
|
}); |
|
this.flush(); |
|
}, |
|
}, |
|
// ping |
|
'9': { |
|
start: function(data) { |
|
var self = this; |
|
if (self.state.lastFragment == false) { |
|
self.error('fragmented ping is not supported', 1002); |
|
return; |
|
} |
|
|
|
// decode length |
|
var firstLength = data[1] & 0x7f; |
|
if (firstLength < 126) { |
|
opcodes['9'].getData.call(self, firstLength); |
|
} |
|
else { |
|
self.error('control frames cannot have more than 125 bytes of data', 1002); |
|
} |
|
}, |
|
getData: function(length) { |
|
var self = this; |
|
if (self.state.masked) { |
|
self.expectHeader(4, function(data) { |
|
var mask = data; |
|
self.expectData(length, function(data) { |
|
opcodes['9'].finish.call(self, mask, data); |
|
}); |
|
}); |
|
} |
|
else { |
|
self.expectData(length, function(data) { |
|
opcodes['9'].finish.call(self, null, data); |
|
}); |
|
} |
|
}, |
|
finish: function(mask, data) { |
|
var self = this; |
|
data = this.unmask(mask, data, true); |
|
var state = clone(this.state); |
|
this.messageHandlers.push(function(callback) { |
|
self.onping(data, {masked: state.masked, binary: true}); |
|
callback(); |
|
}); |
|
this.flush(); |
|
this.endPacket(); |
|
} |
|
}, |
|
// pong |
|
'10': { |
|
start: function(data) { |
|
var self = this; |
|
if (self.state.lastFragment == false) { |
|
self.error('fragmented pong is not supported', 1002); |
|
return; |
|
} |
|
|
|
// decode length |
|
var firstLength = data[1] & 0x7f; |
|
if (firstLength < 126) { |
|
opcodes['10'].getData.call(self, firstLength); |
|
} |
|
else { |
|
self.error('control frames cannot have more than 125 bytes of data', 1002); |
|
} |
|
}, |
|
getData: function(length) { |
|
var self = this; |
|
if (this.state.masked) { |
|
this.expectHeader(4, function(data) { |
|
var mask = data; |
|
self.expectData(length, function(data) { |
|
opcodes['10'].finish.call(self, mask, data); |
|
}); |
|
}); |
|
} |
|
else { |
|
this.expectData(length, function(data) { |
|
opcodes['10'].finish.call(self, null, data); |
|
}); |
|
} |
|
}, |
|
finish: function(mask, data) { |
|
var self = this; |
|
data = self.unmask(mask, data, true); |
|
var state = clone(this.state); |
|
this.messageHandlers.push(function(callback) { |
|
self.onpong(data, {masked: state.masked, binary: true}); |
|
callback(); |
|
}); |
|
this.flush(); |
|
this.endPacket(); |
|
} |
|
} |
|
}
|
|
|