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.
337 lines
9.2 KiB
337 lines
9.2 KiB
|
|
var zlib = require('zlib'); |
|
|
|
var AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15]; |
|
var DEFAULT_WINDOW_BITS = 15; |
|
var DEFAULT_MEM_LEVEL = 8; |
|
|
|
PerMessageDeflate.extensionName = 'permessage-deflate'; |
|
|
|
/** |
|
* Per-message Compression Extensions implementation |
|
*/ |
|
|
|
function PerMessageDeflate(options, isServer,maxPayload) { |
|
if (this instanceof PerMessageDeflate === false) { |
|
throw new TypeError("Classes can't be function-called"); |
|
} |
|
|
|
this._options = options || {}; |
|
this._isServer = !!isServer; |
|
this._inflate = null; |
|
this._deflate = null; |
|
this.params = null; |
|
this._maxPayload = maxPayload || 0; |
|
} |
|
|
|
/** |
|
* Create extension parameters offer |
|
* |
|
* @api public |
|
*/ |
|
|
|
PerMessageDeflate.prototype.offer = function() { |
|
var params = {}; |
|
if (this._options.serverNoContextTakeover) { |
|
params.server_no_context_takeover = true; |
|
} |
|
if (this._options.clientNoContextTakeover) { |
|
params.client_no_context_takeover = true; |
|
} |
|
if (this._options.serverMaxWindowBits) { |
|
params.server_max_window_bits = this._options.serverMaxWindowBits; |
|
} |
|
if (this._options.clientMaxWindowBits) { |
|
params.client_max_window_bits = this._options.clientMaxWindowBits; |
|
} else if (this._options.clientMaxWindowBits == null) { |
|
params.client_max_window_bits = true; |
|
} |
|
return params; |
|
}; |
|
|
|
/** |
|
* Accept extension offer |
|
* |
|
* @api public |
|
*/ |
|
|
|
PerMessageDeflate.prototype.accept = function(paramsList) { |
|
paramsList = this.normalizeParams(paramsList); |
|
|
|
var params; |
|
if (this._isServer) { |
|
params = this.acceptAsServer(paramsList); |
|
} else { |
|
params = this.acceptAsClient(paramsList); |
|
} |
|
|
|
this.params = params; |
|
return params; |
|
}; |
|
|
|
/** |
|
* Releases all resources used by the extension |
|
* |
|
* @api public |
|
*/ |
|
|
|
PerMessageDeflate.prototype.cleanup = function() { |
|
if (this._inflate) { |
|
if (this._inflate.writeInProgress) { |
|
this._inflate.pendingClose = true; |
|
} else { |
|
if (this._inflate.close) this._inflate.close(); |
|
this._inflate = null; |
|
} |
|
} |
|
if (this._deflate) { |
|
if (this._deflate.writeInProgress) { |
|
this._deflate.pendingClose = true; |
|
} else { |
|
if (this._deflate.close) this._deflate.close(); |
|
this._deflate = null; |
|
} |
|
} |
|
}; |
|
|
|
/** |
|
* Accept extension offer from client |
|
* |
|
* @api private |
|
*/ |
|
|
|
PerMessageDeflate.prototype.acceptAsServer = function(paramsList) { |
|
var accepted = {}; |
|
var result = paramsList.some(function(params) { |
|
accepted = {}; |
|
if (this._options.serverNoContextTakeover === false && params.server_no_context_takeover) { |
|
return; |
|
} |
|
if (this._options.serverMaxWindowBits === false && params.server_max_window_bits) { |
|
return; |
|
} |
|
if (typeof this._options.serverMaxWindowBits === 'number' && |
|
typeof params.server_max_window_bits === 'number' && |
|
this._options.serverMaxWindowBits > params.server_max_window_bits) { |
|
return; |
|
} |
|
if (typeof this._options.clientMaxWindowBits === 'number' && !params.client_max_window_bits) { |
|
return; |
|
} |
|
|
|
if (this._options.serverNoContextTakeover || params.server_no_context_takeover) { |
|
accepted.server_no_context_takeover = true; |
|
} |
|
if (this._options.clientNoContextTakeover) { |
|
accepted.client_no_context_takeover = true; |
|
} |
|
if (this._options.clientNoContextTakeover !== false && params.client_no_context_takeover) { |
|
accepted.client_no_context_takeover = true; |
|
} |
|
if (typeof this._options.serverMaxWindowBits === 'number') { |
|
accepted.server_max_window_bits = this._options.serverMaxWindowBits; |
|
} else if (typeof params.server_max_window_bits === 'number') { |
|
accepted.server_max_window_bits = params.server_max_window_bits; |
|
} |
|
if (typeof this._options.clientMaxWindowBits === 'number') { |
|
accepted.client_max_window_bits = this._options.clientMaxWindowBits; |
|
} else if (this._options.clientMaxWindowBits !== false && typeof params.client_max_window_bits === 'number') { |
|
accepted.client_max_window_bits = params.client_max_window_bits; |
|
} |
|
return true; |
|
}, this); |
|
|
|
if (!result) { |
|
throw new Error('Doesn\'t support the offered configuration'); |
|
} |
|
|
|
return accepted; |
|
}; |
|
|
|
/** |
|
* Accept extension response from server |
|
* |
|
* @api privaye |
|
*/ |
|
|
|
PerMessageDeflate.prototype.acceptAsClient = function(paramsList) { |
|
var params = paramsList[0]; |
|
if (this._options.clientNoContextTakeover != null) { |
|
if (this._options.clientNoContextTakeover === false && params.client_no_context_takeover) { |
|
throw new Error('Invalid value for "client_no_context_takeover"'); |
|
} |
|
} |
|
if (this._options.clientMaxWindowBits != null) { |
|
if (this._options.clientMaxWindowBits === false && params.client_max_window_bits) { |
|
throw new Error('Invalid value for "client_max_window_bits"'); |
|
} |
|
if (typeof this._options.clientMaxWindowBits === 'number' && |
|
(!params.client_max_window_bits || params.client_max_window_bits > this._options.clientMaxWindowBits)) { |
|
throw new Error('Invalid value for "client_max_window_bits"'); |
|
} |
|
} |
|
return params; |
|
}; |
|
|
|
/** |
|
* Normalize extensions parameters |
|
* |
|
* @api private |
|
*/ |
|
|
|
PerMessageDeflate.prototype.normalizeParams = function(paramsList) { |
|
return paramsList.map(function(params) { |
|
Object.keys(params).forEach(function(key) { |
|
var value = params[key]; |
|
if (value.length > 1) { |
|
throw new Error('Multiple extension parameters for ' + key); |
|
} |
|
|
|
value = value[0]; |
|
|
|
switch (key) { |
|
case 'server_no_context_takeover': |
|
case 'client_no_context_takeover': |
|
if (value !== true) { |
|
throw new Error('invalid extension parameter value for ' + key + ' (' + value + ')'); |
|
} |
|
params[key] = true; |
|
break; |
|
case 'server_max_window_bits': |
|
case 'client_max_window_bits': |
|
if (typeof value === 'string') { |
|
value = parseInt(value, 10); |
|
if (!~AVAILABLE_WINDOW_BITS.indexOf(value)) { |
|
throw new Error('invalid extension parameter value for ' + key + ' (' + value + ')'); |
|
} |
|
} |
|
if (!this._isServer && value === true) { |
|
throw new Error('Missing extension parameter value for ' + key); |
|
} |
|
params[key] = value; |
|
break; |
|
default: |
|
throw new Error('Not defined extension parameter (' + key + ')'); |
|
} |
|
}, this); |
|
return params; |
|
}, this); |
|
}; |
|
|
|
/** |
|
* Decompress message |
|
* |
|
* @api public |
|
*/ |
|
|
|
PerMessageDeflate.prototype.decompress = function (data, fin, callback) { |
|
var endpoint = this._isServer ? 'client' : 'server'; |
|
|
|
if (!this._inflate) { |
|
var maxWindowBits = this.params[endpoint + '_max_window_bits']; |
|
this._inflate = zlib.createInflateRaw({ |
|
windowBits: 'number' === typeof maxWindowBits ? maxWindowBits : DEFAULT_WINDOW_BITS |
|
}); |
|
} |
|
this._inflate.writeInProgress = true; |
|
|
|
var self = this; |
|
var buffers = []; |
|
var cumulativeBufferLength=0; |
|
|
|
this._inflate.on('error', onError).on('data', onData); |
|
this._inflate.write(data); |
|
if (fin) { |
|
this._inflate.write(new Buffer([0x00, 0x00, 0xff, 0xff])); |
|
} |
|
this._inflate.flush(function() { |
|
cleanup(); |
|
callback(null, Buffer.concat(buffers)); |
|
}); |
|
|
|
function onError(err) { |
|
cleanup(); |
|
callback(err); |
|
} |
|
|
|
function onData(data) { |
|
if(self._maxPayload!==undefined && self._maxPayload!==null && self._maxPayload>0){ |
|
cumulativeBufferLength+=data.length; |
|
if(cumulativeBufferLength>self._maxPayload){ |
|
buffers=[]; |
|
cleanup(); |
|
var err={type:1009}; |
|
callback(err); |
|
return; |
|
} |
|
} |
|
buffers.push(data); |
|
} |
|
|
|
function cleanup() { |
|
if (!self._inflate) return; |
|
self._inflate.removeListener('error', onError); |
|
self._inflate.removeListener('data', onData); |
|
self._inflate.writeInProgress = false; |
|
if ((fin && self.params[endpoint + '_no_context_takeover']) || self._inflate.pendingClose) { |
|
if (self._inflate.close) self._inflate.close(); |
|
self._inflate = null; |
|
} |
|
} |
|
}; |
|
|
|
/** |
|
* Compress message |
|
* |
|
* @api public |
|
*/ |
|
|
|
PerMessageDeflate.prototype.compress = function (data, fin, callback) { |
|
var endpoint = this._isServer ? 'server' : 'client'; |
|
|
|
if (!this._deflate) { |
|
var maxWindowBits = this.params[endpoint + '_max_window_bits']; |
|
this._deflate = zlib.createDeflateRaw({ |
|
flush: zlib.Z_SYNC_FLUSH, |
|
windowBits: 'number' === typeof maxWindowBits ? maxWindowBits : DEFAULT_WINDOW_BITS, |
|
memLevel: this._options.memLevel || DEFAULT_MEM_LEVEL |
|
}); |
|
} |
|
this._deflate.writeInProgress = true; |
|
|
|
var self = this; |
|
var buffers = []; |
|
|
|
this._deflate.on('error', onError).on('data', onData); |
|
this._deflate.write(data); |
|
this._deflate.flush(function() { |
|
cleanup(); |
|
var data = Buffer.concat(buffers); |
|
if (fin) { |
|
data = data.slice(0, data.length - 4); |
|
} |
|
callback(null, data); |
|
}); |
|
|
|
function onError(err) { |
|
cleanup(); |
|
callback(err); |
|
} |
|
|
|
function onData(data) { |
|
buffers.push(data); |
|
} |
|
|
|
function cleanup() { |
|
if (!self._deflate) return; |
|
self._deflate.removeListener('error', onError); |
|
self._deflate.removeListener('data', onData); |
|
self._deflate.writeInProgress = false; |
|
if ((fin && self.params[endpoint + '_no_context_takeover']) || self._deflate.pendingClose) { |
|
if (self._deflate.close) self._deflate.close(); |
|
self._deflate = null; |
|
} |
|
} |
|
}; |
|
|
|
module.exports = PerMessageDeflate;
|
|
|