utils.js

/** @module utils  */

/**
 * Returns a promise that is resolved after a given amount of time.
 * @param {number} time - The time to wait in milisenconds.
 * @returns {Promise} - The promise to resolve.
 */
export function sleep(time) {
    return new Promise(resolve => {
        setTimeout(resolve, time);
    });
}


/**
 * A class that represents a timeout or interval.
 */
export class Timeout{
    
    // The different states of the timeout
    static _RUNNING = 0;
    static _PAUSED = 1;
    static _STOPPED = 2;
    static _FINISHED = 3;

    /**
     * @member {number} - The state of the timeout.
     * @private
     */
    _state;
    /**
     * @member {number} - The duration of the timeout or interval.
     * @private
     */
    _duration;
    /**
     * @member {number} - The elapsed time since the start of the timeout or a
     * cycle of the interval.
     * @private
     */
    _elapsedTime = 0;
    /**
     * @member {function} - The function to call for when the timeout or a cicle
     * of the interval finishes.
     * @private
     */
    _callback;
    /**
     * @member {boolean} - true if the instance is an interval and should
     * repeat.
     * @private
     */
    _repeat;
    /**
     * @member {number} - The ID of the current timeout or interval.
     * @private
     */
    _timeoutID;
    /**
     * @member {number} - The time in miliseconds when the execution of the
     * timeout was started or resumed.
     * @private
     */
    _resumeTime;
    
    
    /**
     * Creates and starts a new timeout.
     * @param {number} duration - The duration of the timeout in miliseconds.
     * @param {function} callback - The callback to execute when the timeout is
     * completed.
     * @param {boolean} [repeat=false] - true if the timeout should repeat like
     * an interval.
     */
    constructor(duration, callback, repeat=false){
        this._duration = duration;
        this._callback = callback;
        this._repeat = repeat;
        this._start(duration);
    }

    /**
     * Executes one cycle of the interval.
     * @private
     */
    _intervalCall(){
        this._elapsedTime = 0;
        this._resumeTime = Date.now();
        this._callback();
    }

    /**
     * Starts or resumes the timeout.
     * @param {number} duration - The duration of the timeout.
     * @private
     */
    _start(duration){
        // The first call or cycle is always a timeout, even if the instance is
        // an interval, this helps regain the execution of the timeout or
        // interval when it's paused.
        this._state = Timeout._RUNNING;
        this._timeoutID = setTimeout(() => {
            if(this._repeat){
                // Transition to use setInterval()
                this._timeoutID = setInterval(() => {
                    this._intervalCall();
                }, this._duration);
                this._intervalCall();
            }else{
                this._state = Timeout._FINISHED;
                this._callback();
            }
        }, duration);
        this._resumeTime = Date.now();
    }

    /**
     * Returns true if the timeout is finished, false otherwise.
     * @returns {boolean}
     */
    isFinished(){
        return this._state == Timeout._FINISHED;
    }

    /**
     * Returns true if the timeout is running, false otherwise.
     * @returns {boolean}
     */
    isRunning(){
        return this._state == Timeout._RUNNING;
    }
        
    /**
     * Returns true if the timeout is paused, false otherwise.
     * @returns {boolean}
     */
    isPaused(){
        return this._state == Timeout._PAUSED;
    }

    /**
     * Returns true if the timeout is stopped, false otherwise.
     * @returns {boolean}
     */
    isStopped(){
        return this._state == Timeout._STOPPED;
    }

    /**
     * Stops the timeout if it is not finished. After this, the
     * timeout cannot be resumed.
     */
    stop(){
        if(!this.isFinished()){
            clearTimeout(this._timeoutID);
            this._state = Timeout._STOPPED; 
        }
    }

    /**
     * Pauses the timeout if it is running.
     */
    pause(){
        if(this.isRunning()){
            clearTimeout(this._timeoutID);
            this._elapsedTime += Date.now() - this._resumeTime;
            this._state = Timeout._PAUSED;  
        }
    }
    
    /**
     * Resumes the timeout if it is paused.
     */
    resume(){
        if(this.isPaused()){
            const time = this._duration - this._elapsedTime;
            this._start(time < 0 ? 0 : time);
        }
    }
  
}


/**
 * A class that helps compute statistics for the packets sent between a sender
 * and a receiver.
 */
export class StatsComputer{

    // Current interval count of packets
    _pktsSent = 0;
    _pktsReceived = 0;
    _pktsReceivedOk = 0;
    _pktsReSent = 0;
    _acksSent = 0;
    _acksReceived = 0;
    _acksReceivedOk = 0;
    _pktsConfirmed = 0;
    
    // Last interval count
    _lastPktsSent = 0;
    _lastPktsReceived = 0;
    _lastPktsReceivedOk = 0;
    _lastPktsReSent = 0;
    _lastAcksSent = 0;
    _lastAcksReceived = 0;
    _lastAcksReceivedOk = 0;
    _lastPktsConfirmed = 0;

    // Cumulative count of packets
    _totalPktsSent = 0;
    _totalPktsReceived = 0;
    _totalPktsReceivedOk = 0;
    _totalPktsReSent = 0;
    _totalAcksSent = 0;
    _totalAcksReceived = 0;
    _totalAcksReceivedOk = 0;
    _totalPktsConfirmed = 0;

    _interval = null; // A Timeout object for the update interval
    _updateTime = null; // The time in miliseconds for updating the statistics.
    _elapsedTime = 0; // The total time that the computations have run

    /**
     * Creates a new StatsComputer object. The generated statistics will be
     * updated every updateTime miliseconds.
     * @param {number} updateTime - The time in miliseconds for updating the
     * statistics.
     */
    constructor(updateTime){
        this._updateTime = updateTime;
    }

    /**
     * Starts the computer.
     */
    start(){
        if(this._interval === null){
            this._interval = new Timeout(this._updateTime, () => {
                this._update();
            }, true);
        }
    }

    /**
     * Returns the rate of the last interval for count.
     * @param {number} count - The count for which to get the rate.
     * @returns {number} - The rate in the last interval.
     * @private
     */
    _intervalRate(count){
        return count / this._updateTime * 1000;
    }

    /**
     * Returns the rate since the start for count.
     * @param {number} count - The count for which to get the rate.
     * @returns {number} - The rate since the start.
     * @private
     */
    _totalRate(count){
        return count / this._elapsedTime * 1000;
    }
    
    /** 
     * @member {number} - The time in miliseconds since the start till the last
     * interval. This is a multiple of the time given to the constructor.
     * @readonly
     */
    get elapsedTime () { return this._elapsedTime; }

    /**
     * @member {number} - The total packets sent by the sender.
     * @readonly
     */
    get totalPktsSent(){ return this._totalPktsSent; }

    /**
     * @member {number} - The total packets received by the receiver.
     * @readonly
     */
    get totalPktsReceived(){ return this._totalPktsReceived; }

    /** 
     * @member {number} - The total number of correct packets received by the
     * receiver. The definition of a correcly received packet depends on each
     * protocol.
     * @readonly
     */
    get totalPktsReceivedOk(){ return this._totalPktsReceivedOk; }

    /**
     * @member {number} - The total number of packets re-sent by the sender.
     * @readonly
     */
    get totalPktsReSent(){ return this._totalPktsReSent; }

    /**
     * @member {number} - The total number of acknowledgments sent by the
     * receiver.
     * @readonly
     */
    get totalAcksSent(){ return this._totalAcksSent; }

    /**
     * @member {number} - The total number of acknowledgments received by the
     * sender.
     * @readonly
     */
    get totalAcksReceived(){ return this._totalAcksReceived; }

    /**
     * @member {number} - The total number of acknowledgments correctly received
     * by the sender. The definition of a correcly received packet depends on
     * each protocol.
     * @readonly
     */
    get totalAcksReceivedOk(){ return this._totalAcksReceivedOk; }

    /**
     * @member {number} - The total number of packets confirmed to the sender.
     * @readonly
     */
    get totalPktsConfirmed(){ return this._totalPktsConfirmed; }

    /** 
     * @member {number} - The packets per second sent by the sender in the last
     * interval.
     * @readonly
     */
    get pktsSentRate() { return this._intervalRate(this._lastPktsSent); }

    /** 
     * @member {number} - The packets per second received by the receiver in the
     * last interval.
     * @readonly
     */
    get pktsReceivedRate(){ return this._intervalRate(this._lastPktsReceived); }

    /** 
     * @member {number} - The packets per second correctly received by the
     * receiver in the last interval.
     * @readonly
     */
    get pktsReceivedOkRate(){ return this._intervalRate(this._lastPktsReceivedOk); }

    /** 
     * @member {number} - The packets per second re-sent by the sender in the
     * last interval.
     * @readonly
     */
    get pktsReSentRate(){ return this._intervalRate(this._lastPktsReSent); }

    /** 
     * @member {number} - The acks per second sent by the receiver in the last
     * interval.
     * @readonly
     */
    get acksSentRate(){ return this._intervalRate(this._lastAcksSent); }
    /** 
     * @member {number} - The acks per second received by the sender in the last
     * interval.
     * @readonly
     */
    get acksReceivedRate(){ return this._intervalRate(this._lastAcksReceived); }

    /** 
     * @member {number} - The acks per second correctly received by the sender
     * in the last interval.
     * @readonly
     */
    get acksReceivedOkRate(){ return this._intervalRate(this._lastAcksReceivedOk); }

    /**
     * @member {number} - The packets per second confirmed to the sender in the
     * last interval.
     * @readonly
     */
    get pktsConfirmedRate(){ return this._intervalRate(this._lastPktsConfirmed); }

    /**
     * @member {number} - The packets per second sent by the sender since the
     * start.
     * @readonly
     */
    get totalPktsSentRate(){ return this._totalRate(this._totalPktsSent); }

    /**
     * @member {number} - The packets per second received by the receiver since
     * the start.
     * @readonly
     */
    get totalPktsReceivedRate(){ return this._totalRate(this._totalPktsReceived); }

    /**
     * @member {number} - The packets per second correctly received by the
     * receiver since the start.
     * @readonly
     */
    get totalPktsReceivedOkRate(){ return this._totalRate(this._totalPktsReceivedOk); }

    /**
     * @member {number} - The packets per second re-sent by the sender since the
     * start.
     * @readonly
     */
    get totalPktsReSentRate(){ return this._totalRate(this._totalPktsReSent); }

    /**
     * @member {number} - The acks per second sent by the receiver since the
     * start.
     * @readonly
     */
    get totalAcksSentRate(){ return this._totalRate(this._totalAcksSent); }

    /**
     * @member {number} - The acks per second received by the sender since the
     * start.
     * @readonly
     */
    get totalAcksReceivedRate(){ return this._totalRate(this._totalAcksReceived); }

    /**
     * @member {number} - The acks per second correctly received by the sender
     * since the start.
     * @readonly
     */
    get totalAcksReceivedOkRate(){ return this._totalRate(this._totalAcksReceivedOk); }

    /**
     * @member {number} - The packets per second confirmed to the sender since
     * the start.
     * @readonly
     */
    get totalPktsConfirmedRate(){ return this._totalRate(this._totalPktsConfirmed); }

    /**
     * Updates the counting variables and calls onUpdate().
     * @private
     */
    _update(){
        this._elapsedTime += this._updateTime;
        // Update cumulative variables
        this._totalPktsSent += this._pktsSent;
        this._totalPktsReceived += this._pktsReceived;
        this._totalPktsReceivedOk += this._pktsReceivedOk;
        this._totalPktsReSent += this._pktsReSent;
        this._totalAcksSent += this._acksSent;
        this._totalAcksReceived += this._acksReceived;
        this._totalAcksReceivedOk += this._acksReceivedOk;
        this._totalPktsConfirmed += this._pktsConfirmed;
        // Update last interval variables
        this._lastPktsSent = this._pktsSent;
        this._lastPktsReceived = this._pktsReceived;
        this._lastPktsReceivedOk = this._pktsReceivedOk;
        this._lastPktsReSent = this._pktsReSent;
        this._lastAcksSent = this._acksSent;
        this._lastAcksReceived = this._acksReceived;
        this._lastAcksReceivedOk = this._acksReceivedOk;
        this._lastPktsConfirmed = this._pktsConfirmed
        // Reset the current count of packets
        this._pktsSent = 0;
        this._pktsReceived = 0;
        this._pktsReceivedOk = 0;
        this._pktsReSent = 0;
        this._acksSent = 0;
        this._acksReceived = 0;
        this._acksReceivedOk = 0;
        this._pktsConfirmed = 0;
        this.onUpdate();
    }

    /**
     * A callback that is called every time an update of the statistics occurs.
     */
    onUpdate(){
        return;
    }

    /**
     * Counts and clasifies a packet sent by a sender or a receiver.
     * @param {Packet} packet - The packet to add to the statistics.
     */
    pktSent(packet){
        if(packet.isAck){
            this._acksSent++;
        }else{
            this._pktsSent++;
            if(packet.wasReSent)
                this._pktsReSent++;
        }
    }

    /**
     * Counts and clasifies a packet received by a sender or a receiver.
     * @param {Packet} packet - The packet to add to the statistics.
     * @param {boolean} isOK - true if the packet was correctly received by the
     * node, false otherwise.
     */
    pktReceived(packet, isOK){
        if(packet.isAck){
            this._acksReceived++;
            if(isOK)
                this._acksReceivedOk++;
        }else{
            this._pktsReceived++;
            if(isOK)
                this._pktsReceivedOk++;
        }
    }

    /**
     * Increases the count of confirmed packets.
     */
    pktConfirmed(){
        this._pktsConfirmed++;
    }

    /**
     * Pauses the computer.
     */
    pause(){
        this._interval.pause();
    }

    /**
     * Resumes the computer.
     */
    resume(){
        this._interval.resume();
    }

    /**
     * Stops the computer.
     */
    stop(){
        this._interval.stop();
    }
}