2013-07-10 12:02:48 +02:00
|
|
|
var EventEmitter = require('events').EventEmitter,
|
|
|
|
dns = require('dns'),
|
2014-02-01 16:45:49 +01:00
|
|
|
net = require('net'),
|
2013-07-10 12:02:48 +02:00
|
|
|
async = require('async'),
|
2014-02-03 21:00:51 +01:00
|
|
|
Class = require('../lib/Class'),
|
|
|
|
Reader = require('../lib/reader');
|
2013-07-10 12:02:48 +02:00
|
|
|
|
|
|
|
module.exports = Class.extend(EventEmitter,{
|
|
|
|
init: function() {
|
|
|
|
this._super();
|
2014-02-02 14:20:36 +01:00
|
|
|
this.options = {
|
|
|
|
tcpTimeout: 1000,
|
|
|
|
udpTimeout: 1000
|
|
|
|
};
|
2013-07-10 12:02:48 +02:00
|
|
|
this.maxAttempts = 1;
|
|
|
|
this.attempt = 1;
|
|
|
|
this.finished = false;
|
|
|
|
this.encoding = 'utf8';
|
|
|
|
this.byteorder = 'le';
|
|
|
|
this.delimiter = '\0';
|
|
|
|
|
|
|
|
var self = this;
|
|
|
|
this.globalTimeoutTimer = setTimeout(function() {
|
|
|
|
self.fatal('timeout');
|
|
|
|
},10000);
|
|
|
|
},
|
|
|
|
|
2014-02-02 12:58:36 +01:00
|
|
|
fatal: function(err,noretry) {
|
|
|
|
if(!noretry && this.attempt < this.maxAttempts) {
|
2013-07-10 12:02:48 +02:00
|
|
|
this.attempt++;
|
|
|
|
this.start();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.done({error: err.toString()});
|
|
|
|
},
|
2013-07-12 11:12:02 +02:00
|
|
|
initState: function() {
|
|
|
|
return {
|
|
|
|
name: '',
|
|
|
|
map: '',
|
|
|
|
password: false,
|
|
|
|
|
|
|
|
raw: {},
|
|
|
|
|
|
|
|
maxplayers: 0,
|
|
|
|
players: [],
|
|
|
|
bots: []
|
|
|
|
};
|
|
|
|
},
|
2013-07-12 12:36:07 +02:00
|
|
|
finalizeState: function(state) {},
|
|
|
|
|
|
|
|
finish: function(state) {
|
|
|
|
this.finalizeState(state);
|
2013-07-12 12:39:08 +02:00
|
|
|
this.done(state);
|
|
|
|
},
|
|
|
|
|
|
|
|
done: function(state) {
|
|
|
|
if(this.finished) return;
|
|
|
|
clearTimeout(this.globalTimeoutTimer);
|
2013-07-12 12:36:07 +02:00
|
|
|
|
2013-07-10 16:52:47 +02:00
|
|
|
if(this.options.notes)
|
|
|
|
state.notes = this.options.notes;
|
2013-07-12 11:12:02 +02:00
|
|
|
|
|
|
|
state.query = {};
|
|
|
|
if('host' in this.options) state.query.host = this.options.host;
|
2013-09-10 06:50:23 +02:00
|
|
|
if('address' in this.options) state.query.address = this.options.address;
|
2013-07-12 11:12:02 +02:00
|
|
|
if('port' in this.options) state.query.port = this.options.port;
|
2014-02-03 21:00:51 +01:00
|
|
|
if('port_query' in this.options) state.query.port_query = this.options.port_query;
|
2013-07-12 11:12:02 +02:00
|
|
|
state.query.type = this.type;
|
|
|
|
if('pretty' in this) state.query.pretty = this.pretty;
|
2013-07-12 12:36:07 +02:00
|
|
|
|
2013-07-10 12:02:48 +02:00
|
|
|
this.reset();
|
|
|
|
this.finished = true;
|
2013-07-12 12:39:08 +02:00
|
|
|
this.emit('finished',state);
|
|
|
|
if(this.options.callback) this.options.callback(state);
|
2013-07-10 12:02:48 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
reset: function() {
|
|
|
|
if(this.timers) {
|
|
|
|
this.timers.forEach(function(timer) {
|
|
|
|
clearTimeout(timer);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
this.timers = [];
|
2014-02-01 16:45:49 +01:00
|
|
|
|
|
|
|
if(this.tcpSocket) {
|
|
|
|
this.tcpSocket.destroy();
|
|
|
|
delete this.tcpSocket;
|
|
|
|
}
|
2013-07-10 12:02:48 +02:00
|
|
|
|
|
|
|
this.udpTimeoutTimer = false;
|
|
|
|
this.udpCallback = false;
|
|
|
|
},
|
|
|
|
start: function() {
|
|
|
|
var self = this;
|
2014-02-03 21:00:51 +01:00
|
|
|
var options = self.options;
|
2013-07-10 12:02:48 +02:00
|
|
|
this.reset();
|
|
|
|
|
|
|
|
async.series([
|
|
|
|
function(c) {
|
|
|
|
// resolve host names
|
2014-02-03 21:00:51 +01:00
|
|
|
if(!('host' in options)) return c();
|
|
|
|
if(options.host.match(/\d+\.\d+\.\d+\.\d+/)) {
|
|
|
|
options.address = options.host;
|
2013-07-10 12:02:48 +02:00
|
|
|
c();
|
|
|
|
} else {
|
2014-02-03 21:00:51 +01:00
|
|
|
self.parseDns(options.host,c);
|
2013-07-10 12:02:48 +02:00
|
|
|
}
|
2014-02-03 21:00:51 +01:00
|
|
|
},
|
|
|
|
function(c) {
|
|
|
|
// calculate query port if needed
|
|
|
|
if(!('port_query' in options) && 'port' in options) {
|
|
|
|
var offset = options.port_query_offset || 0;
|
|
|
|
options.port_query = options.port + offset;
|
|
|
|
}
|
|
|
|
c();
|
|
|
|
},
|
|
|
|
function(c) {
|
|
|
|
// run
|
2013-07-12 11:12:02 +02:00
|
|
|
self.run(self.initState());
|
2013-07-10 12:02:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
]);
|
|
|
|
},
|
2013-07-10 16:52:47 +02:00
|
|
|
parseDns: function(host,c) {
|
|
|
|
var self = this;
|
2014-02-01 16:45:49 +01:00
|
|
|
|
|
|
|
function resolveStandard(host,c) {
|
|
|
|
dns.lookup(host, function(err,address,family) {
|
2014-02-02 12:58:36 +01:00
|
|
|
if(err) return self.fatal(err);
|
2014-02-01 16:45:49 +01:00
|
|
|
self.options.address = address;
|
|
|
|
c();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
function resolveSrv(srv,host,c) {
|
|
|
|
dns.resolve(srv+'.'+host, 'SRV', function(err,addresses) {
|
|
|
|
if(err) return resolveStandard(host,c);
|
|
|
|
if(addresses.length >= 1) {
|
|
|
|
var line = addresses[0];
|
|
|
|
self.options.port = line.port;
|
|
|
|
var srvhost = line.name;
|
|
|
|
|
|
|
|
if(srvhost.match(/\d+\.\d+\.\d+\.\d+/)) {
|
|
|
|
self.options.address = srvhost;
|
|
|
|
c();
|
|
|
|
} else {
|
|
|
|
// resolve yet again
|
|
|
|
resolveStandard(srvhost,c);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return resolveStandard(host,c);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if(this.srvRecord) resolveSrv(this.srvRecord,host,c);
|
|
|
|
else resolveStandard(host,c);
|
2013-07-10 16:52:47 +02:00
|
|
|
},
|
2013-07-10 12:02:48 +02:00
|
|
|
|
2013-07-10 16:52:47 +02:00
|
|
|
// utils
|
2013-07-10 12:02:48 +02:00
|
|
|
reader: function(buffer) {
|
|
|
|
return new Reader(this,buffer);
|
|
|
|
},
|
2013-07-12 11:12:02 +02:00
|
|
|
translate: function(obj,trans) {
|
2013-07-10 16:52:47 +02:00
|
|
|
for(var from in trans) {
|
|
|
|
var to = trans[from];
|
2013-07-12 11:12:02 +02:00
|
|
|
if(from in obj) {
|
|
|
|
if(to) obj[to] = obj[from];
|
|
|
|
delete obj[from];
|
2013-07-10 16:52:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2013-07-10 12:02:48 +02:00
|
|
|
setTimeout: function(c,t) {
|
|
|
|
if(this.finished) return 0;
|
|
|
|
var id = setTimeout(c,t);
|
|
|
|
this.timers.push(id);
|
|
|
|
return id;
|
|
|
|
},
|
|
|
|
|
2014-02-02 00:46:10 +01:00
|
|
|
|
|
|
|
|
|
|
|
trueTest: function(str) {
|
|
|
|
if(typeof str == 'boolean') return str;
|
|
|
|
if(typeof str == 'number') return str != 0;
|
|
|
|
if(typeof str == 'string') {
|
|
|
|
if(str.toLowerCase() == 'true') return true;
|
|
|
|
if(str == 'yes') return true;
|
|
|
|
if(str == '1') return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
2014-02-02 03:04:41 +01:00
|
|
|
debugBuffer: function(buffer) {
|
|
|
|
var out = '';
|
|
|
|
var out2 = '';
|
|
|
|
for(var i = 0; i < buffer.length; i++) {
|
|
|
|
var sliced = buffer.slice(i,i+1);
|
|
|
|
out += sliced.toString('hex')+' ';
|
|
|
|
var chr = sliced.toString();
|
|
|
|
if(chr < ' ' || chr > '~') chr = ' ';
|
|
|
|
out2 += chr+' ';
|
|
|
|
if(out.length > 60) {
|
|
|
|
console.log(out);
|
|
|
|
console.log(out2);
|
|
|
|
out = out2 = '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
console.log(out);
|
|
|
|
console.log(out2);
|
|
|
|
},
|
2014-02-01 16:45:49 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_tcpConnect: function(c) {
|
2013-07-10 12:02:48 +02:00
|
|
|
var self = this;
|
2014-02-01 16:45:49 +01:00
|
|
|
if(this.tcpSocket) return c(this.tcpSocket);
|
2014-02-02 12:32:02 +01:00
|
|
|
|
|
|
|
var connected = false;
|
|
|
|
var received = new Buffer(0);
|
|
|
|
var address = this.options.address;
|
2014-02-03 21:00:51 +01:00
|
|
|
var port = this.options.port_query;
|
2014-02-02 12:32:02 +01:00
|
|
|
|
|
|
|
var socket = this.tcpSocket = net.connect(port,address,function() {
|
2014-02-02 13:17:05 +01:00
|
|
|
if(self.debug) console.log(address+':'+port+" TCPCONNECTED");
|
2014-02-02 14:20:36 +01:00
|
|
|
connected = true;
|
2014-02-02 12:32:02 +01:00
|
|
|
c(socket);
|
|
|
|
});
|
2014-02-01 16:45:49 +01:00
|
|
|
socket.setTimeout(10000);
|
|
|
|
socket.setNoDelay(true);
|
2014-02-02 13:17:05 +01:00
|
|
|
if(this.debug) console.log(address+':'+port+" TCPCONNECT");
|
2014-02-02 12:32:02 +01:00
|
|
|
|
|
|
|
var writeHook = socket.write;
|
|
|
|
socket.write = function(data) {
|
2014-02-02 13:17:05 +01:00
|
|
|
if(self.debug) console.log(address+':'+port+" TCP--> "+data.toString('hex'));
|
2014-02-02 12:32:02 +01:00
|
|
|
writeHook.apply(this,arguments);
|
|
|
|
}
|
2014-02-01 16:45:49 +01:00
|
|
|
|
2014-02-02 12:32:02 +01:00
|
|
|
socket.on('error', function() {});
|
|
|
|
socket.on('close', function() {
|
|
|
|
if(!self.tcpCallback) return;
|
|
|
|
if(connected) return self.fatal('Socket closed while waiting on TCP');
|
|
|
|
else return self.fatal('TCP Connection Refused');
|
|
|
|
});
|
2014-02-01 16:45:49 +01:00
|
|
|
socket.on('data', function(data) {
|
|
|
|
if(!self.tcpCallback) return;
|
2014-02-02 13:17:05 +01:00
|
|
|
if(self.debug) console.log(address+':'+port+" <--TCP "+data.toString('hex'));
|
2014-02-01 16:45:49 +01:00
|
|
|
received = Buffer.concat([received,data]);
|
|
|
|
if(self.tcpCallback(received)) {
|
2014-02-02 13:44:39 +01:00
|
|
|
clearTimeout(self.tcpTimeoutTimer);
|
2014-02-01 16:45:49 +01:00
|
|
|
self.tcpCallback = false;
|
|
|
|
received = new Buffer(0);
|
|
|
|
}
|
2013-07-10 12:02:48 +02:00
|
|
|
});
|
|
|
|
},
|
2014-02-01 16:45:49 +01:00
|
|
|
tcpSend: function(buffer,ondata) {
|
2014-02-02 00:46:10 +01:00
|
|
|
var self = this;
|
|
|
|
process.nextTick(function() {
|
|
|
|
if(self.tcpCallback) return self.fatal('Attempted to send TCP packet while still waiting on a managed response');
|
|
|
|
self._tcpConnect(function(socket) {
|
|
|
|
socket.write(buffer);
|
|
|
|
});
|
2014-02-02 12:32:02 +01:00
|
|
|
if(!ondata) return;
|
|
|
|
|
2014-02-02 12:58:36 +01:00
|
|
|
self.tcpTimeoutTimer = self.setTimeout(function() {
|
|
|
|
self.tcpCallback = false;
|
|
|
|
self.fatal('TCP Watchdog Timeout');
|
2014-02-02 14:20:36 +01:00
|
|
|
},self.options.tcpTimeout);
|
2014-02-02 12:32:02 +01:00
|
|
|
self.tcpCallback = ondata;
|
2014-02-01 16:45:49 +01:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
udpSend: function(buffer,onpacket,ontimeout) {
|
|
|
|
var self = this;
|
2014-02-02 00:46:10 +01:00
|
|
|
process.nextTick(function() {
|
|
|
|
if(self.udpCallback) return self.fatal('Attempted to send UDP packet while still waiting on a managed response');
|
|
|
|
self._udpSendNow(buffer);
|
|
|
|
if(!onpacket) return;
|
2014-02-01 16:45:49 +01:00
|
|
|
|
2014-02-02 00:46:10 +01:00
|
|
|
self.udpTimeoutTimer = self.setTimeout(function() {
|
|
|
|
self.udpCallback = false;
|
|
|
|
var timeout = false;
|
|
|
|
if(!ontimeout || ontimeout() !== true) timeout = true;
|
2014-02-02 12:58:36 +01:00
|
|
|
if(timeout) self.fatal('UDP Watchdog Timeout');
|
2014-02-02 14:20:36 +01:00
|
|
|
},self.options.udpTimeout);
|
2014-02-02 00:46:10 +01:00
|
|
|
self.udpCallback = onpacket;
|
|
|
|
});
|
2014-02-01 16:45:49 +01:00
|
|
|
},
|
2013-07-10 12:02:48 +02:00
|
|
|
_udpSendNow: function(buffer) {
|
2014-02-03 21:00:51 +01:00
|
|
|
if(!('port_query' in this.options)) return this.fatal('Attempted to send without setting a port');
|
2013-07-10 12:02:48 +02:00
|
|
|
if(!('address' in this.options)) return this.fatal('Attempted to send without setting an address');
|
|
|
|
|
|
|
|
if(typeof buffer == 'string') buffer = new Buffer(buffer,'binary');
|
2014-01-31 23:27:52 +01:00
|
|
|
|
2014-02-03 21:00:51 +01:00
|
|
|
if(this.debug) console.log(this.options.address+':'+this.options.port_query+" UDP--> "+buffer.toString('hex'));
|
|
|
|
this.udpSocket.send(buffer,0,buffer.length,this.options.port_query,this.options.address);
|
2013-07-10 12:02:48 +02:00
|
|
|
},
|
|
|
|
_udpResponse: function(buffer) {
|
|
|
|
if(this.udpCallback) {
|
|
|
|
var result = this.udpCallback(buffer);
|
|
|
|
if(result === true) {
|
|
|
|
// we're done with this udp session
|
|
|
|
clearTimeout(this.udpTimeoutTimer);
|
|
|
|
this.udpCallback = false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.udpResponse(buffer);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
udpResponse: function() {}
|
|
|
|
});
|