node-gamedig/protocols/core.js

313 lines
7.6 KiB
JavaScript

var EventEmitter = require('events').EventEmitter,
dns = require('dns'),
net = require('net'),
async = require('async'),
Class = require('../lib/Class'),
Reader = require('../lib/reader');
module.exports = Class.extend(EventEmitter,{
init: function() {
this._super();
this.options = {
tcpTimeout: 1000,
udpTimeout: 1000
};
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);
},
fatal: function(err,noretry) {
if(!noretry && this.attempt < this.maxAttempts) {
this.attempt++;
this.start();
return;
}
this.done({error: err.toString()});
},
initState: function() {
return {
name: '',
map: '',
password: false,
raw: {},
maxplayers: 0,
players: [],
bots: []
};
},
finalizeState: function(state) {},
finish: function(state) {
this.finalizeState(state);
this.done(state);
},
done: function(state) {
if(this.finished) return;
clearTimeout(this.globalTimeoutTimer);
if(this.options.notes)
state.notes = this.options.notes;
state.query = {};
if('host' in this.options) state.query.host = this.options.host;
if('address' in this.options) state.query.address = this.options.address;
if('port' in this.options) state.query.port = this.options.port;
if('port_query' in this.options) state.query.port_query = this.options.port_query;
state.query.type = this.type;
if('pretty' in this) state.query.pretty = this.pretty;
this.reset();
this.finished = true;
this.emit('finished',state);
if(this.options.callback) this.options.callback(state);
},
reset: function() {
if(this.timers) {
this.timers.forEach(function(timer) {
clearTimeout(timer);
});
}
this.timers = [];
if(this.tcpSocket) {
this.tcpSocket.destroy();
delete this.tcpSocket;
}
this.udpTimeoutTimer = false;
this.udpCallback = false;
},
start: function() {
var self = this;
var options = self.options;
this.reset();
async.series([
function(c) {
// resolve host names
if(!('host' in options)) return c();
if(options.host.match(/\d+\.\d+\.\d+\.\d+/)) {
options.address = options.host;
c();
} else {
self.parseDns(options.host,c);
}
},
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
self.run(self.initState());
}
]);
},
parseDns: function(host,c) {
var self = this;
function resolveStandard(host,c) {
dns.lookup(host, function(err,address,family) {
if(err) return self.fatal(err);
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);
},
// utils
reader: function(buffer) {
return new Reader(this,buffer);
},
translate: function(obj,trans) {
for(var from in trans) {
var to = trans[from];
if(from in obj) {
if(to) obj[to] = obj[from];
delete obj[from];
}
}
},
setTimeout: function(c,t) {
if(this.finished) return 0;
var id = setTimeout(c,t);
this.timers.push(id);
return id;
},
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;
},
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);
},
_tcpConnect: function(c) {
var self = this;
if(this.tcpSocket) return c(this.tcpSocket);
var connected = false;
var received = new Buffer(0);
var address = this.options.address;
var port = this.options.port_query;
var socket = this.tcpSocket = net.connect(port,address,function() {
if(self.debug) console.log(address+':'+port+" TCPCONNECTED");
connected = true;
c(socket);
});
socket.setTimeout(10000);
socket.setNoDelay(true);
if(this.debug) console.log(address+':'+port+" TCPCONNECT");
var writeHook = socket.write;
socket.write = function(data) {
if(self.debug) console.log(address+':'+port+" TCP--> "+data.toString('hex'));
writeHook.apply(this,arguments);
}
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');
});
socket.on('data', function(data) {
if(!self.tcpCallback) return;
if(self.debug) console.log(address+':'+port+" <--TCP "+data.toString('hex'));
received = Buffer.concat([received,data]);
if(self.tcpCallback(received)) {
clearTimeout(self.tcpTimeoutTimer);
self.tcpCallback = false;
received = new Buffer(0);
}
});
},
tcpSend: function(buffer,ondata) {
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);
});
if(!ondata) return;
self.tcpTimeoutTimer = self.setTimeout(function() {
self.tcpCallback = false;
self.fatal('TCP Watchdog Timeout');
},self.options.tcpTimeout);
self.tcpCallback = ondata;
});
},
udpSend: function(buffer,onpacket,ontimeout) {
var self = this;
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;
self.udpTimeoutTimer = self.setTimeout(function() {
self.udpCallback = false;
var timeout = false;
if(!ontimeout || ontimeout() !== true) timeout = true;
if(timeout) self.fatal('UDP Watchdog Timeout');
},self.options.udpTimeout);
self.udpCallback = onpacket;
});
},
_udpSendNow: function(buffer) {
if(!('port_query' in this.options)) return this.fatal('Attempted to send without setting a port');
if(!('address' in this.options)) return this.fatal('Attempted to send without setting an address');
if(typeof buffer == 'string') buffer = new Buffer(buffer,'binary');
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);
},
_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() {}
});