/**
* Implements Bandwidther class.
* @module bandwidther
*/
const crypto = require('crypto');
const NanoTimer = require('nanotimer');
const Measure = require('./measure');
const Request = require('./request');
/**
* Bandwidther class. In charge of sending pings and obtain the metrics
*/
class Bandwidther {
/**
* Constructor for the Bandwidther class. Does not validate input data
* coherence.
* @param {number} sessionId - The session id to perform the pings
* @param {ClientNetwork} network - Network object
* @param {number} requestedBw - required bandwidth is in kbits/s
* @param {number} negotiationBandwidth - Time to run the test in seconds
* @param {Boolean} proactive - Tells teh bandwidther to be proactive
*/
constructor(sessionId, network, requestedBw, negotiationBandwidth,
proactive) {
this.sessionId = sessionId;
this.network = network;
this.requestedBw = requestedBw;
this.negotiationBandwidth = negotiationBandwidth;
this.proactive = proactive;
this.recieved = [];
this.sequence = 0;
this.localMeasurements = new Measure();
this.reportedMeasurements = new Measure();
// Generate the random payload. The size is 1000 bytes.
const buffer = Buffer.alloc(1000);
crypto.randomFillSync(buffer, 1000);
this.body = buffer.toString('utf8');
// Fill the required timers for the bandwidth.
this.timers = [];
let msgPerSec = requestedBw / 8;
for (let i = 1; i <= 1000; i++) {
const intPart = Math.floor(msgPerSec * i * 0.001);
if (intPart !== 0) {
msgPerSec = msgPerSec - (intPart * 1000) / i;
this.timers.push({
ms: i,
times: intPart,
timer: new NanoTimer(),
});
if (msgPerSec === 0) {
break;
}
}
}
}
/**
* Generates a new bandwidther with Client settings.
* @param {number} sessionId - The session id to perform the pings
* @param {ClientNetwork|ServerNetwork} network - Network object
* @param {number} requestedBw - required bandwidth is in kbits/s
* @param {number} negotiationBandwidth - Time to run the test in seconds
* @return {Bandwidther}
*/
static genClient(sessionId, network, requestedBw, negotiationBandwidth) {
return new Bandwidther(sessionId, network, requestedBw,
negotiationBandwidth, true);
}
/**
* Generates a new bandwidther with Server settings.
* @param {number} sessionId - The session id to perform the pings
* @param {ClientNetwork|ServerNetwork} network - Network object
* @param {number} requestedBw - required bandwidth is in kbits/s
* @param {number} negotiationBandwidth - Time to run the test in seconds
* @return {Bandwidther}
*/
static genServer(sessionId, network, requestedBw, negotiationBandwidth) {
return new Bandwidther(sessionId, network, requestedBw,
negotiationBandwidth, false);
}
/**
* Starts the measurement stage.
* @return {Promise} On sucess the measures, on error the error
*/
measure() {
return new Promise((resolve, reject) => {
this.rejectCallback = reject;
const endFunc = () => {
this.network.removeListener('UDP-Req', this.reqFunc);
this.localMeasurements.extractBandwidth(this.recieved,
this.requestedBw);
this.recieved = [];
this.sequence = 0;
setImmediate(resolve, {
local: this.localMeasurements,
remote: this.reportedMeasurements,
});
};
const intervaFunc = (count) => {
for (let i = 0; i < count; i++) {
const headers = {
'Stage': '1',
'Session-Id': this.sessionId,
'Sequence-Number': this.sequence,
'Measurements': this.localMeasurements.toHeader(),
};
this.network.sendUDP(Request.genReq(
'BWIDTH',
'q4s://www.example.com',
headers,
undefined), this.sessionId);
this.sequence++;
}
if (Date.now() > this.endTime) {
this.timers.forEach((timer, i, arr) => {
timer.timer.clearInterval();
});
this.timers[0].timer.setTimeout(endFunc, '', '300m');
}
};
this.reqFunc = (req, date, session) => {
if (typeof session == 'undefined' || session.id === this.sessionId) {
if (req.headers['Sequence-Number']) {
this.recieved.push(parseInt(req.headers['Sequence-Number']));
}
if (req.headers.Measurements) {
this.reportedMeasurements.fromHeader(req.headers.Measurements);
}
this.localMeasurements.extractBandwidth(this.recieved,
this.requestedBw);
if (this.timers[0].timer.hasTimeout()) {
this.timers[0].timer.clearTimeout();
this.timers[0].timer.setTimeout(endFunc, '', '300m');
}
}
};
const startIterrupt = (req, time, record) => {
if (record.id === this.sessionId) {
this.timers.forEach((timer) => {
timer.timer.setInterval(intervaFunc, [timer.times], timer.ms + 'm');
});
this.endTime = Date.now() + this.negotiationBandwidth * 1000;
this.network.off('UDP-Req', startIterrupt);
}
};
this.network.on('UDP-Req', this.reqFunc);
if (this.proactive) {
this.timers.forEach((timer) => {
timer.timer.setInterval(intervaFunc, [timer.times], timer.ms + 'm');
});
this.endTime = Date.now() + this.negotiationBandwidth * 1000;
} else {
this.network.on('UDP-Req', startIterrupt);
}
});
}
/**
* Cancel communication
*/
cancel() {
if (this.timers[0].timer.hasTimeout()) {
this.timers[0].timer.clearTimeout();
}
this.timers.forEach((timer, i, arr) => {
timer.timer.clearInterval();
});
this.network.removeListener('UDPRequest', this.reqFunc);
this.recieved = [];
this.sequence = 0;
setImmediate(this.rejectCallback);
}
}
module.exports = Bandwidther;