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.
310 lines
7.7 KiB
310 lines
7.7 KiB
/*! |
|
* ws: a node.js websocket client |
|
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com> |
|
* MIT Licensed |
|
*/ |
|
|
|
var events = require('events') |
|
, util = require('util') |
|
, crypto = require('crypto') |
|
, EventEmitter = events.EventEmitter |
|
, ErrorCodes = require('./ErrorCodes') |
|
, bufferUtil = require('./BufferUtil') |
|
, PerMessageDeflate = require('./PerMessageDeflate'); |
|
|
|
/** |
|
* HyBi Sender implementation |
|
*/ |
|
|
|
function Sender(socket, extensions) { |
|
if (this instanceof Sender === false) { |
|
throw new TypeError("Classes can't be function-called"); |
|
} |
|
|
|
events.EventEmitter.call(this); |
|
|
|
this._socket = socket; |
|
this.extensions = extensions || {}; |
|
this.firstFragment = true; |
|
this.compress = false; |
|
this.messageHandlers = []; |
|
this.processing = false; |
|
} |
|
|
|
/** |
|
* Inherits from EventEmitter. |
|
*/ |
|
|
|
util.inherits(Sender, events.EventEmitter); |
|
|
|
/** |
|
* Sends a close instruction to the remote party. |
|
* |
|
* @api public |
|
*/ |
|
|
|
Sender.prototype.close = function(code, data, mask, cb) { |
|
if (typeof code !== 'undefined') { |
|
if (typeof code !== 'number' || |
|
!ErrorCodes.isValidErrorCode(code)) throw new Error('first argument must be a valid error code number'); |
|
} |
|
code = code || 1000; |
|
var dataBuffer = new Buffer(2 + (data ? Buffer.byteLength(data) : 0)); |
|
writeUInt16BE.call(dataBuffer, code, 0); |
|
if (dataBuffer.length > 2) dataBuffer.write(data, 2); |
|
|
|
var self = this; |
|
this.messageHandlers.push(function() { |
|
self.frameAndSend(0x8, dataBuffer, true, mask); |
|
if (typeof cb == 'function') cb(); |
|
}); |
|
this.flush(); |
|
}; |
|
|
|
/** |
|
* Sends a ping message to the remote party. |
|
* |
|
* @api public |
|
*/ |
|
|
|
Sender.prototype.ping = function(data, options) { |
|
var mask = options && options.mask; |
|
var self = this; |
|
this.messageHandlers.push(function() { |
|
self.frameAndSend(0x9, data || '', true, mask); |
|
}); |
|
this.flush(); |
|
}; |
|
|
|
/** |
|
* Sends a pong message to the remote party. |
|
* |
|
* @api public |
|
*/ |
|
|
|
Sender.prototype.pong = function(data, options) { |
|
var mask = options && options.mask; |
|
var self = this; |
|
this.messageHandlers.push(function() { |
|
self.frameAndSend(0xa, data || '', true, mask); |
|
}); |
|
this.flush(); |
|
}; |
|
|
|
/** |
|
* Sends text or binary data to the remote party. |
|
* |
|
* @api public |
|
*/ |
|
|
|
Sender.prototype.send = function(data, options, cb) { |
|
var finalFragment = options && options.fin === false ? false : true; |
|
var mask = options && options.mask; |
|
var compress = options && options.compress; |
|
var opcode = options && options.binary ? 2 : 1; |
|
if (this.firstFragment === false) { |
|
opcode = 0; |
|
compress = false; |
|
} else { |
|
this.firstFragment = false; |
|
this.compress = compress; |
|
} |
|
if (finalFragment) this.firstFragment = true |
|
|
|
var compressFragment = this.compress; |
|
|
|
var self = this; |
|
this.messageHandlers.push(function() { |
|
if (!data || !compressFragment) { |
|
self.frameAndSend(opcode, data, finalFragment, mask, compress, cb); |
|
return; |
|
} |
|
|
|
self.processing = true; |
|
self.applyExtensions(data, finalFragment, compressFragment, function(err, data) { |
|
if (err) { |
|
if (typeof cb == 'function') cb(err); |
|
else self.emit('error', err); |
|
return; |
|
} |
|
self.frameAndSend(opcode, data, finalFragment, mask, compress, cb); |
|
self.processing = false; |
|
self.flush(); |
|
}); |
|
}); |
|
this.flush(); |
|
}; |
|
|
|
/** |
|
* Frames and sends a piece of data according to the HyBi WebSocket protocol. |
|
* |
|
* @api private |
|
*/ |
|
|
|
Sender.prototype.frameAndSend = function(opcode, data, finalFragment, maskData, compressed, cb) { |
|
var canModifyData = false; |
|
|
|
if (!data) { |
|
try { |
|
this._socket.write(new Buffer([opcode | (finalFragment ? 0x80 : 0), 0 | (maskData ? 0x80 : 0)].concat(maskData ? [0, 0, 0, 0] : [])), 'binary', cb); |
|
} |
|
catch (e) { |
|
if (typeof cb == 'function') cb(e); |
|
else this.emit('error', e); |
|
} |
|
return; |
|
} |
|
|
|
if (!Buffer.isBuffer(data)) { |
|
canModifyData = true; |
|
if (data && (typeof data.byteLength !== 'undefined' || typeof data.buffer !== 'undefined')) { |
|
data = getArrayBuffer(data); |
|
} else { |
|
// |
|
// If people want to send a number, this would allocate the number in |
|
// bytes as memory size instead of storing the number as buffer value. So |
|
// we need to transform it to string in order to prevent possible |
|
// vulnerabilities / memory attacks. |
|
// |
|
if (typeof data === 'number') data = data.toString(); |
|
|
|
data = new Buffer(data); |
|
} |
|
} |
|
|
|
var dataLength = data.length |
|
, dataOffset = maskData ? 6 : 2 |
|
, secondByte = dataLength; |
|
|
|
if (dataLength >= 65536) { |
|
dataOffset += 8; |
|
secondByte = 127; |
|
} |
|
else if (dataLength > 125) { |
|
dataOffset += 2; |
|
secondByte = 126; |
|
} |
|
|
|
var mergeBuffers = dataLength < 32768 || (maskData && !canModifyData); |
|
var totalLength = mergeBuffers ? dataLength + dataOffset : dataOffset; |
|
var outputBuffer = new Buffer(totalLength); |
|
outputBuffer[0] = finalFragment ? opcode | 0x80 : opcode; |
|
if (compressed) outputBuffer[0] |= 0x40; |
|
|
|
switch (secondByte) { |
|
case 126: |
|
writeUInt16BE.call(outputBuffer, dataLength, 2); |
|
break; |
|
case 127: |
|
writeUInt32BE.call(outputBuffer, 0, 2); |
|
writeUInt32BE.call(outputBuffer, dataLength, 6); |
|
} |
|
|
|
if (maskData) { |
|
outputBuffer[1] = secondByte | 0x80; |
|
var mask = getRandomMask(); |
|
outputBuffer[dataOffset - 4] = mask[0]; |
|
outputBuffer[dataOffset - 3] = mask[1]; |
|
outputBuffer[dataOffset - 2] = mask[2]; |
|
outputBuffer[dataOffset - 1] = mask[3]; |
|
if (mergeBuffers) { |
|
bufferUtil.mask(data, mask, outputBuffer, dataOffset, dataLength); |
|
try { |
|
this._socket.write(outputBuffer, 'binary', cb); |
|
} |
|
catch (e) { |
|
if (typeof cb == 'function') cb(e); |
|
else this.emit('error', e); |
|
} |
|
} |
|
else { |
|
bufferUtil.mask(data, mask, data, 0, dataLength); |
|
try { |
|
this._socket.write(outputBuffer, 'binary'); |
|
this._socket.write(data, 'binary', cb); |
|
} |
|
catch (e) { |
|
if (typeof cb == 'function') cb(e); |
|
else this.emit('error', e); |
|
} |
|
} |
|
} |
|
else { |
|
outputBuffer[1] = secondByte; |
|
if (mergeBuffers) { |
|
data.copy(outputBuffer, dataOffset); |
|
try { |
|
this._socket.write(outputBuffer, 'binary', cb); |
|
} |
|
catch (e) { |
|
if (typeof cb == 'function') cb(e); |
|
else this.emit('error', e); |
|
} |
|
} |
|
else { |
|
try { |
|
this._socket.write(outputBuffer, 'binary'); |
|
this._socket.write(data, 'binary', cb); |
|
} |
|
catch (e) { |
|
if (typeof cb == 'function') cb(e); |
|
else this.emit('error', e); |
|
} |
|
} |
|
} |
|
}; |
|
|
|
/** |
|
* Execute message handler buffers |
|
* |
|
* @api private |
|
*/ |
|
|
|
Sender.prototype.flush = function() { |
|
while (!this.processing && this.messageHandlers.length) { |
|
this.messageHandlers.shift()(); |
|
} |
|
}; |
|
|
|
/** |
|
* Apply extensions to message |
|
* |
|
* @api private |
|
*/ |
|
|
|
Sender.prototype.applyExtensions = function(data, fin, compress, callback) { |
|
if ((data.buffer || data) instanceof ArrayBuffer) { |
|
data = getArrayBuffer(data); |
|
} |
|
this.extensions[PerMessageDeflate.extensionName].compress(data, fin, callback); |
|
}; |
|
|
|
module.exports = Sender; |
|
|
|
function writeUInt16BE(value, offset) { |
|
this[offset] = (value & 0xff00)>>8; |
|
this[offset+1] = value & 0xff; |
|
} |
|
|
|
function writeUInt32BE(value, offset) { |
|
this[offset] = (value & 0xff000000)>>24; |
|
this[offset+1] = (value & 0xff0000)>>16; |
|
this[offset+2] = (value & 0xff00)>>8; |
|
this[offset+3] = value & 0xff; |
|
} |
|
|
|
function getArrayBuffer(data) { |
|
// data is either an ArrayBuffer or ArrayBufferView. |
|
var array = new Uint8Array(data.buffer || data) |
|
, l = data.byteLength || data.length |
|
, o = data.byteOffset || 0 |
|
, buffer = new Buffer(l); |
|
for (var i = 0; i < l; ++i) { |
|
buffer[i] = array[o+i]; |
|
} |
|
return buffer; |
|
} |
|
|
|
function getRandomMask() { |
|
return crypto.randomBytes(4); |
|
}
|
|
|