UNPKG

domotz-remote-pawn

Version:

Domotz Agent

306 lines (273 loc) 9.17 kB
"use strict"; var dns = require ("dns"); var util = require ("util"); var packets = require ("../packets"); var opcodes = require ("../opcodes"); var errors = require ("../errors"); var knownExtensions = require ("../known-extensions"); var Request = require ("../request"); //States var REQ_SENT = 0; var ACK_SENT = 1; var BLK_SENT = 2; var ClientRequest = module.exports = function (args){ Request.call (this, args.globalOptions.address, args.globalOptions.port, args.globalOptions.retries, args.globalOptions.extensions.timeout); this._isRRQ = args.reader; this._ipFamily = null; this._file = args.file; this._globalOptions = args.globalOptions; this._opOptions = args.opOptions || {}; this._prefixError = "(Server) "; this._firstPacket = true; this._oackExpected = true; this._extensionsRetransmitted = false; this._extensionsEmitted = false; this._maxDataLength = 4; this._blksize = null; var me = this; me._lookup (function (){ //Delay the opening to the next tick (the address could be an ip, hence the //callback is called immediately) process.nextTick (function (){ me._open (true); }); }); }; util.inherits (ClientRequest, Request); ClientRequest.prototype._lookup = function (cb){ var me = this; dns.lookup (this._address, function (error, address, family){ if (error) return me.onError (new Error ("Cannot resolve the domain name " + "\"" + me._address + "\"")); me._address = address; me._ipFamily = family; cb (); }); }; ClientRequest.prototype._open = function (extensions){ var me = this; this._initSocket (null, function (message, rinfo){ if (me._firstPacket){ me._firstPacket = false; //Reset the timer that was started with the request me._requestTimer.reset (); //Save the remote host with the first packet me._address = rinfo.address; me._port = rinfo.port; }else if (me._address !== rinfo.address || me._port !== rinfo.port){ //A message is received from a different remote host //This could happen when the client sends a request, the server sends a //response but the client never receives it, so it timeouts and sends the //same request again. The server responds again but the client receives //the two server responses: the first is accepted but the latter produces //this error because from the point of view of the server it receives two //different requests and sends the same file from different ports return me._send (packets.error.serialize (errors.ESOCKET)); } if (message.length < 2) return me._sendErrorAndClose (errors.EBADMSG); me._onMessage (message); }); //Create and send the RRQ/WRQ message //There are 2 possible responses from the server: //- If the server doesn't support extensions the file transfer starts //- If the server supports extensions, it sends a OACK var buffer; try{ if (this._isRRQ){ buffer = extensions ? packets.rrq.serialize (this._file, this._globalOptions, this._opOptions) : packets.rrq.serialize (this._file); }else{ buffer = extensions ? packets.wrq.serialize (this._file, this._globalOptions, this._opOptions) : packets.wrq.serialize (this._file); } }catch (error){ return this._close (new Error (error.message)); } this._sendAndRetransmit (buffer); }; ClientRequest.prototype._emitDefaultExtensions = function (){ this._extensionsEmitted = true; var stats = { blockSize: 512, windowSize: 1, size: this._opOptions.size || null, userExtensions: {} }; this._setStats (stats); this._oackExpected = false; this._onReady (stats, this._globalOptions.extensions.rollover); }; ClientRequest.prototype._setStats = function (stats){ //Save max data length this._maxDataLength += stats.blockSize; this._blksize = stats.blockSize; stats.retries = this._retries; stats.timeout = this._timeout; var address = this._socket.address (); stats.localAddress = address.address; stats.localPort = address.port; stats.remoteAddress = this._address; stats.remotePort = this._port; }; ClientRequest.prototype._onMessage = function (buffer){ var op = buffer.readUInt16BE (0); if (op === opcodes.DATA){ if (this._isRRQ){ if (!this._extensionsEmitted) this._emitDefaultExtensions (); if (buffer.length < 4 || buffer.length > this._maxDataLength){ return this._sendErrorAndClose (errors.EBADMSG); } try{ this._onData (packets.data.deserialize (buffer, this._blksize)); }catch (error){ this._sendErrorAndClose (error); } }else{ this._sendErrorAndClose (errors.EBADOP); } }else if (op === opcodes.ACK){ if (!this._isRRQ){ if (!this._extensionsEmitted){ //The server doesn't return extensions, it's probably that it doesn't //rollover automatically (old server), so we can abort the transfer //prematurely //Check the size (65535x512) if (this._opOptions.size > 33553920){ return this._sendErrorAndClose (errors.EFBIG); } this._emitDefaultExtensions (); //The first ACK with block 0 is ignored if (buffer.length !== 4 || buffer.readUInt16BE (2) !== 0){ this._sendErrorAndClose (errors.EBADMSG); } }else{ try{ this._onAck (packets.ack.deserialize (buffer)); }catch (error){ this._sendErrorAndClose (error); } } }else{ this._sendErrorAndClose (errors.EBADOP); } }else if (op === opcodes.OACK){ //OACK can be only received when RRQ/WRQ is sent if (!this._oackExpected) return this._sendErrorAndClose (errors.EBADOP); this._oackExpected = false; try{ this._onOackMessage (packets.oack.deserialize (buffer)); }catch (error){ this._sendErrorAndClose (error); } }else if (op === opcodes.ERROR){ if (buffer.length < 4) return this._closeWithError (errors.EBADMSG); try{ this._onErrorMessage (packets.error.deserialize (buffer)); }catch (error){ this._closeWithError (error); } }else{ this._sendErrorAndClose (errors.EBADOP); } }; ClientRequest.prototype._onOackMessage = function (message){ var userExtensions = {}; for (var p in message){ //Fail if the OACK message contains invalid extensions if (!knownExtensions[p]){ if (!(p in this._opOptions.userExtensions)){ return this._sendErrorAndClose (errors.EDENY); }else{ userExtensions[p] = message[p]; } } } var blockSize; var transferSize; var windowSize; var rollover; if (message.timeout){ var timeout = ~~message.timeout; if (timeout > 0 && timeout <= this._timeout){ this._timeout = timeout; }else{ return this._sendErrorAndClose (errors.EDENY); } } if (message.blksize){ blockSize = ~~message.blksize; if (blockSize < 8 || blockSize > this._globalOptions.extensions.blksize){ return this._sendErrorAndClose (errors.EDENY); } } if (message.tsize){ transferSize = ~~message.tsize; if (transferSize < 0 || (this._opOptions.size !== undefined && transferSize !== this._opOptions.size)){ return this._sendErrorAndClose (errors.EDENY); } } if (message.windowsize){ windowSize = ~~message.windowsize; if (windowSize <= 0 || windowSize > this._globalOptions.extensions.windowsize){ return this._sendErrorAndClose (errors.EDENY); } } if (message.rollover){ rollover = ~~message.rollover; if (rollover < 0 || rollover > 1){ return this._sendErrorAndClose (errors.EDENY); } } this._extensionsEmitted = true; rollover = rollover !== undefined ? rollover : this._globalOptions.extensions.rollover; var stats = { blockSize: blockSize || 512, windowSize: windowSize || 1, size: transferSize !== undefined ? transferSize : null, userExtensions: userExtensions }; this._setStats (stats); if (this._isRRQ){ //Acknowledge OACK //The ACK of the block 0 is retransmitted from the reader this._sendAck (0); this._onReady (stats, rollover, true); }else{ this._onReady (stats, rollover); } }; ClientRequest.prototype._onErrorMessage = function (message){ if (this._oackExpected && message.code === 8){ if (this._extensionsRetransmitted){ //The server has returned an ERROR with code 8 after a RRQ/WRQ without //extensions. The code 8 is only used when RRQ and WRQ messages contain //extensions return this._closeWithError (errors.EBADOP); } //If the error code is 8, the server doesn't like one or more extensions //Retransmit without extensions this._extensionsRetransmitted = true; this._port = this._globalOptions.port; this._firstPacket = this._first = true; //In order to retransmit, the socket must be closed and open a new one, that //is, cannot reuse the same socket because the server closes its socket this._socket.removeListener ("close", this._onCloseFn); var me = this; this._socket.on ("close", function (){ me._open (); }); this._socket.close (); }else{ this._closeWithError (message); } };