domotz-remote-pawn
Version:
Domotz Agent
306 lines (273 loc) • 9.17 kB
JavaScript
"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);
}
};