/**
* Contains the implementation of ServerNetwork
* @module server-network
* @license Apache-2.0
*/
const EventEmitter = require('events');
const dgram = require('dgram');
const net = require('net');
const Util = require('./util');
const ReqQ4S = require('./request');
const ResQ4S = require('./response');
/**
* Implements an abstraction layer over the network. Having access to the
* database it validates the requests and forwards the messages using
* listeners that the client, server, pingers and bandwidthers use.
* @fires ServerNetwork#UDP-Req
* @fires ServerNetwork#UDP-Res
* @fires ServerNetwork#TCP-Req
* @fires ServerNetwork#TCP-Res
* @fires ServerNetwork#handshake-Req
*/
class ServerNetwork extends EventEmitter {
/**
* Constructor
* @param {Collection} store - Session storage
*/
constructor(store) {
super();
/**
* @param {Collection} store - Session storage
*/
this.store = store;
/**
* The handshake TCP server.
* @member {net.Socket}
*/
this.handshake = new net.Server();
this.handshake.on('connection', (socket) => {
socket.on('data', (data) => {
try {
const req = ReqQ4S.fromString(data.toString('utf-8'));
// TODO -> Documentar el callback
/**
* Snowball event.
*
* @event handshake-Req
* @property {Request} req - The recieved request
*/
this.emit('handshake-Req', req, (msg) => {
socket.write(msg.toString(), 'utf8');
});
} catch (error) {
socket.write(ResQ4S.genRes(400).toString(), 'utf8');
}
});
});
/**
* The TCP Q4S socket.
* @member {net.Socket}
*/
this.TCP = new net.Server();
this.TCP.on('connection', (socket) => {
const record = this.store.findOne({
'addresses.clientAddress': socket.remoteAddress,
'addresses.q4sClientPorts.TCP': socket.remotePort,
});
const replyFunc = (reply) => {
if (reply) {
socket.write(reply.toString(), 'utf8');
}
};
if (record) {
record.TCP = socket;
socket.on('data', (data) => {
const msg = data.toString('utf-8');
if (Util.isRequest(msg)) {
try {
this.emit('TCP-Req', ReqQ4S.fromString(msg), record, replyFunc);
} catch (err) {
socket.write(ResQ4S.genRes(400).toString(), 'utf8');
}
} else {
try {
this.emit('TCP-Res', ResQ4S.fromString(msg), record, replyFunc);
} catch (e) {
this.emit('error', e);
}
}
});
this.store.update(record);
} else {
socket.write(ResQ4S.genRes(600).toString(), 'utf8');
}
});
/**
* The UDP Q4S socket.
* @member {dgram.Socket}
*/
this.UDP = dgram.createSocket('udp4');
this.UDP.on('message', (msg, rinfo) => {
const time = new Date();
const record = this.store.findOne({
'addresses.clientAddress': rinfo.address,
'addresses.q4sClientPorts.UDP': rinfo.port,
});
const msgS = msg.toString('utf-8');
const replyFunc = (reply) => {
if (reply) {
this.UDP.send(reply.toString(),
record.addresses.appClientPorts.UDP,
record.addresses.clientAddress);
}
};
if (record) {
if (Util.isRequest(msg.toString('utf-8'))) {
try {
this.emit('UDP-Req', ReqQ4S.fromString(msgS), time,
record, replyFunc);
} catch (e) {
this.UDP.send(Response.genRes(400),
record.addresses.appClientPorts.UDP,
record.addresses.clientAddress);
}
} else {
try {
this.emit('UDP-Res', ResQ4S.fromString(msgS), time,
record, replyFunc);
} catch (e) {
this.emit('error', e);
}
}
} else {
this.emit('error', new Error('Recieved UDP message without session'));
}
});
}
/**
* Async method to create the handshake socket. After completition the
* sendHandshakeTCP method can be called to send messages. After completition
* the class starts to emit events.
* @param {Number} HandshakePort - The string containing the ip to connect
* @param {Number} TCPPort - Port to connect
* @param {Number} UDPPort - Origin port for the handshake connection.
*/
listen(HandshakePort, TCPPort, UDPPort) {
const handshakeSocketOps = {
port: HandshakePort,
host: '0.0.0.0',
};
const TCPOps = {
port: TCPPort,
host: '0.0.0.0',
};
this.handshake.listen(handshakeSocketOps);
this.TCP.listen(TCPOps);
this.UDP.bind(UDPPort);
}
/**
* Close all the sockets.
*/
close() {
this.handshake.close();
this.UDP.close();
this.TCP.close();
}
/**
* Send an udp message with session Id.
* @argument {String} message
* @argument {Number} sessionId
*/
sendUDP(message, sessionId) {
const record = this.store.findOne({
'id': sessionId,
});
if (record) {
this.UDP.send(message.toString(),
record.addresses.q4sClientPorts.UDP,
record.addresses.clientAddress);
}
// else trow an exception;
}
/**
* Send an TCP message
* @argument {String} message
* @argument {Number} sessionId
*/
sendTCP(message, sessionId) {
const record = this.store.findOne({
'id': {eq: sessionId},
});
if (record) {
record.TCP.write(message.toString(), 'utf8');
}
// else trow an exception;
}
}
module.exports = ServerNetwork;