UNPKG

domotz-remote-pawn

Version:

Domotz Agent

234 lines (204 loc) 6.55 kB
"use strict"; var opcodes = require ("./opcodes"); var Reader = module.exports = function (Super, args){ args.reader = true; Super.call (this, args); this._blockSize = null; this._windowStart = 1; this._windowEnd = null; this._windowSize = null; this._windowBlocksIndex = {}; this._windowBlocks = []; this._lastReceived = false; this._pending = null; this._oackReceived = null; this._firstWindow = true; this._mayRollover = false; this._rolloverFix = 0; this._windowStartRollovered = false; this._noMoreData = false; this._readerTimer = this._createRetransmitter (); var me = this; this._preFilterFn = function (e){ return e.block >= me._windowStart; }; this._postFilterFn = function (e){ return e.block < me._windowStart; }; this._sortFn = function (a, b){ return a.block - b.block; }; this._restransmitterStartFn = function (){ var block = me._blockToRetransmit (); if (block > 0){ //Update the window and emit back to the client the data me._windowStart = block === 65535 ? 1 : block + 1; //The last block could have been received me._lastReceived = false; me._notifyWindow (block); } me._sendAck (block); }; }; Reader.prototype._onClose = function (){ this._readerTimer.reset (); this.onClose (); }; Reader.prototype._onAbort = function (){ this._readerTimer.reset (); this.onAbort (); }; Reader.prototype._onError = function (error){ this._readerTimer.reset (); this.onError (error); }; Reader.prototype._onReady = function (stats, rollover, oack){ //The reader doesn't make use of the rollover option, it's not safe because //there isn't an specification for default values this._windowEnd = this._pending = this._windowSize = stats.windowSize; this._blockSize = stats.blockSize; //Start the timer for the first time this._readerTimer.start (this._restransmitterStartFn); this._oackReceived = oack; this.onStats (stats); }; Reader.prototype._blockToRetransmit = function (){ if (!this._windowBlocks.length){ //Resend ACK for the OACK if (this._oackReceived) return 0; //Rollover if (this._windowStart === 0 || (this._windowStart === 1 && !this._firstWindow)) return 65535; //First empty window will never happen (oack case treated before) //This is mostly executed by classic tftp server implementations return this._windowStart - 1; } //Sort the blocks and find the last well-received one this._sortWindow (); //-1 if rollovered to 0 var last = this._windowStart - 1; for (var i=0; i<this._windowBlocks.length; i++){ if (last + 1 !== this._windowBlocks[i].block){ return last === -1 || (this._mayRollover && last === 0) ? 65535 : last; } if (++last === 65535){ //Is not possible to determine the next expected block: 0 or 1, so assume //that the last valid block is 65535 even if there are more valid blocks //already received return 65535; } } //The last block of the window is missing return last; }; Reader.prototype._sortWindow = function (){ if (this._mayRollover){ //Example: [1, 65535, 65534, 0] -> [65534, 65535, 0, 1] var preRoll = this._windowBlocks.filter (this._preFilterFn); var postRoll = this._windowBlocks.filter (this._postFilterFn); preRoll.sort (this._sortFn); postRoll.sort (this._sortFn); this._windowBlocks = preRoll.concat (postRoll); }else{ this._windowBlocks.sort (this._sortFn); } }; Reader.prototype._notifyWindow = function (block){ var arr; //Emit data if (block){ //Error recovery, slow case //Two loops must be executed because a rollovered window, eg: [65535, 0, 1] var index = null; arr = []; for (var i=0; i<this._windowBlocks.length; i++){ //Search the index of the last valid block if (this._windowBlocks[i].block === block){ index = i; break; } } if (index !== null){ //Copy the valid blocks for (var i=0; i<=index; i++){ arr.push (this._windowBlocks[i]); } } }else{ //Fast case arr = this._windowBlocks; } var me = this; arr.forEach (function (message){ //Ignore DATA packets with 0 length, they are received when the file can be //split up in blocks that perfectly fits a window if (message.data.length){ me.onData (message.data); } }); if (this._lastReceived){ this._noMoreData = true; return this._close (); } this._pending = this._windowSize; this._windowBlocks = []; this._windowBlocksIndex = {}; this._oackReceived = false; this._firstWindow = false; }; Reader.prototype._onData = function (message){ //DATA packet received after sending the last packet, the socket will be //closed in the next tick if (this._noMoreData) return; if (message.block === 0 && this._rolloverFix === 0){ //The server has rollovered to 0 this._rolloverFix = 1; if (this._windowStartRollovered) this._windowStart--; this._windowEnd--; } //Check the validity of the block only when the current window doesn't //rollover and the block is outside the valid range, it could be a duplicate //(sorcerer's apprendice syndrome) if (!this._mayRollover && (message.block < this._windowStart || message.block > this._windowEnd)){ return; } //Ignore duplicates if (this._windowBlocksIndex[message.block]) return; //Insert new block this._windowBlocksIndex[message.block] = true; this._windowBlocks.push (message); //Update the pending packets if (message.data.length < this._blockSize){ //Last packet this._pending = message.block - this._windowStart + 1 - this._windowBlocks.length; this._lastReceived = true; }else{ this._pending--; } if (!this._pending){ //Cancel the current timer and set it again this._readerTimer.reset (); this._readerTimer.start (this._restransmitterStartFn); //Sort the blocks if (this._windowsSize > 1){ this._sortWindow (); } //Update the window this._windowStart += this._windowSize; if (this._windowStart > 65535){ this._windowStartRollovered = true; this._windowStart -= 65535 + this._rolloverFix; } this._windowEnd = this._windowStart + this._windowSize - 1; this._mayRollover = this._windowEnd > 65535; if (this._mayRollover){ this._windowEnd -= 65535 + this._rolloverFix; } //ACK the current window this._sendAck ( this._windowBlocks[this._windowBlocks.length - 1].block); this._notifyWindow (); } };