Initial commit

This commit is contained in:
Nicolas Dextraze
2016-03-09 12:46:15 -08:00
parent 3a5a4111c1
commit 9be67bf7c7
54 changed files with 5172 additions and 1 deletions

View File

@ -0,0 +1,72 @@
var createBufferSegment = require('../../common/bufferSegment');
const HeaderLength = 4;
function LengthPrefixMessageFramer(maxPackageSize) {
this._maxPackageSize = maxPackageSize || 64*1024*1024;
this._receivedHandler = null;
this.reset();
}
LengthPrefixMessageFramer.prototype.reset = function() {
this._messageBuffer = null;
this._headerBytes = 0;
this._packageLength = 0;
this._bufferIndex = 0;
};
LengthPrefixMessageFramer.prototype.unframeData = function(bufferSegments) {
for(var i = 0; i < bufferSegments.length; i++) {
this._parse(bufferSegments[i]);
}
};
LengthPrefixMessageFramer.prototype._parse = function(bytes) {
var buffer = bytes.buffer;
for (var i = bytes.offset; i < bytes.offset + bytes.count; i++)
{
if (this._headerBytes < HeaderLength)
{
this._packageLength |= (buffer[i] << (this._headerBytes * 8)); // little-endian order
++this._headerBytes;
if (this._headerBytes == HeaderLength)
{
if (this._packageLength <= 0 || this._packageLength > this._maxPackageSize)
throw new Error(["Package size is out of bounds: ", this._packageLength, "(max: ", this._maxPackageSize, "."].join(''));
this._messageBuffer = new Buffer(this._packageLength);
}
}
else
{
var copyCnt = Math.min(bytes.count + bytes.offset - i, this._packageLength - this._bufferIndex);
bytes.buffer.copy(this._messageBuffer, this._bufferIndex, i, i + copyCnt);
this._bufferIndex += copyCnt;
i += copyCnt - 1;
if (this._bufferIndex == this._packageLength)
{
if (this._receivedHandler != null)
this._receivedHandler(createBufferSegment(this._messageBuffer, 0, this._bufferIndex));
this.reset();
}
}
}
};
LengthPrefixMessageFramer.prototype.frameData = function(data) {
var length = data.count;
var lengthBuffer = new Buffer(HeaderLength);
lengthBuffer.writeInt32LE(length, 0);
return [
createBufferSegment(lengthBuffer, 0, HeaderLength),
data
];
};
LengthPrefixMessageFramer.prototype.registerMessageArrivedCallback = function(handler) {
this._receivedHandler = handler;
};
module.exports = LengthPrefixMessageFramer;

View File

@ -0,0 +1,151 @@
var net = require('net');
var createBufferSegment = require('../../common/bufferSegment');
const MaxSendPacketSize = 64 * 1000;
function TcpConnection(log, connectionId, remoteEndPoint, onConnectionClosed) {
this._socket = null;
this._log = log;
this._connectionId = connectionId;
this._remoteEndPoint = remoteEndPoint;
this._localEndPoint = null;
this._onConnectionClosed = onConnectionClosed;
this._receiveCallback = null;
this._closed = false;
this._sendQueue = [];
this._receiveQueue = [];
Object.defineProperty(this, 'remoteEndPoint', {
get: function() {
return this._remoteEndPoint;
}
});
Object.defineProperty(this, 'localEndPoint', {
get: function() {
return this._localEndPoint;
}
});
}
TcpConnection.prototype._initSocket = function(socket) {
this._socket = socket;
this._localEndPoint = {host: socket.localAddress, port: socket.localPort};
this._socket.on('error', this._processError.bind(this));
this._socket.on('data', this._processReceive.bind(this));
};
TcpConnection.prototype.enqueueSend = function(bufSegmentArray) {
//console.log(bufSegmentArray);
for(var i = 0; i < bufSegmentArray.length; i++) {
var bufSegment = bufSegmentArray[i];
this._sendQueue.push(bufSegment.toBuffer());
}
this._trySend();
};
TcpConnection.prototype._trySend = function() {
if (this._sendQueue.length === 0 || this._socket === null) return;
var buffers = [];
var bytes = 0;
var sendPiece = this._sendQueue.shift();
while(sendPiece) {
if (bytes + sendPiece.length > MaxSendPacketSize)
break;
buffers.push(sendPiece);
bytes += sendPiece.length;
sendPiece = this._sendQueue.shift();
}
var joinedBuffers = Buffer.concat(buffers, bytes);
this._socket.write(joinedBuffers);
};
TcpConnection.prototype._processError = function(err) {
this._closeInternal(err, "Socket error");
};
TcpConnection.prototype._processReceive = function(buf) {
if (buf.length === 0) {
//NotifyReceiveCompleted(0);
this._closeInternal(null, "Socket closed");
return;
}
//NotifyReceiveCompleted(buf.length)
this._receiveQueue.push(buf);
this._tryDequeueReceivedData();
};
TcpConnection.prototype.receive = function(cb) {
this._receiveCallback = cb;
this._tryDequeueReceivedData();
};
TcpConnection.prototype._tryDequeueReceivedData = function() {
if (this._receiveCallback === null || this._receiveQueue.length === 0)
return;
var res = [];
while(this._receiveQueue.length > 0) {
var buf = this._receiveQueue.shift();
var bufferSegment = createBufferSegment(buf);
res.push(bufferSegment);
}
var callback = this._receiveCallback;
this._receiveCallback = null;
callback(this, res);
var bytes = 0;
for(var i=0;i<res.length;i++)
bytes += res[i].count;
//this._pendingReceivedBytes -= bytes;
};
TcpConnection.prototype.close = function(reason) {
this._closeInternal(null, reason || "Normal socket close.");
};
TcpConnection.prototype._closeInternal = function(err, reason) {
if (this._closed) return;
this._closed = true;
if (this._socket != null) {
this._socket.end();
this._socket.unref();
this._socket = null;
}
if (this._onConnectionClosed != null)
this._onConnectionClosed(this, err);
};
TcpConnection.createConnectingConnection = function(
log, connectionId, remoteEndPoint, connectionTimeout,
onConnectionEstablished, onConnectionFailed, onConnectionClosed
) {
var connection = new TcpConnection(log, connectionId, remoteEndPoint, onConnectionClosed);
var socket = net.connect(remoteEndPoint);
function onError(err) {
if (onConnectionFailed)
onConnectionFailed(connection, err);
}
socket.once('error', onError);
socket.on('connect', function() {
socket.removeListener('error', onError);
connection._initSocket(socket);
if (onConnectionEstablished)
onConnectionEstablished(connection);
});
return connection;
};
module.exports = TcpConnection;

View File

@ -0,0 +1,149 @@
var util = require('util');
var uuid = require('uuid');
var LengthPrefixMessageFramer = require('./lengthPrefixMessageFramer');
var TcpConnection = require('./tcpConnection');
var TcpPackage = require('../../systemData/tcpPackage');
var TcpCommand = require('../../systemData/tcpCommand');
/**
* @param log
* @param remoteEndPoint
* @param connectionId
* @param ssl
* @param targetHost
* @param validateServer
* @param timeout
* @param handlePackage
* @param onError
* @param connectionEstablished
* @param connectionClosed
* @constructor
* @property {string} connectionId
* @property {boolean} isClosed
* @property {object} remoteEndPoint
* @property {object} localEndPoint
*/
function TcpPackageConnection(
log, remoteEndPoint, connectionId, ssl, targetHost, validateServer, timeout,
handlePackage, onError, connectionEstablished, connectionClosed)
{
this._connectionId = connectionId;
this._log = log;
this._handlePackage = handlePackage;
this._onError = onError;
//Setup callback for incoming messages
this._framer = new LengthPrefixMessageFramer();
this._framer.registerMessageArrivedCallback(this._incomingMessageArrived.bind(this));
//TODO ssl
var self = this;
this._connection = TcpConnection.createConnectingConnection(
log,
connectionId,
remoteEndPoint,
//ssl,
//targetHost,
//validateServer,
timeout,
function(tcpConnection) {
log.debug("TcpPackageConnection: connected to [%j, L%j, %s].", tcpConnection.remoteEndPoint, tcpConnection.localEndPoint, connectionId);
connectionEstablished(self);
},
function(conn, error) {
log.debug("TcpPackageConnection: connection to [%j, L%j, %s] failed. Error: %s.", conn.remoteEndPoint, conn.localEndPoint, connectionId, error);
connectionClosed(self, error);
},
function (conn, had_error) {
var error;
if (had_error)
error = new Error('transmission error.');
log.debug("TcpPackageConnection: connection [%j, L%j, %s] was closed %s", conn.remoteEndPoint, conn.localEndPoint,
connectionId, had_error ? "with error: " + error + "." : "cleanly.");
connectionClosed(self, error);
});
}
Object.defineProperty(TcpPackageConnection.prototype, 'connectionId', {
get: function() {
return this._connectionId;
}
});
Object.defineProperty(TcpPackageConnection.prototype, 'isClosed', {
get: function() {
return this._connection.isClosed;
}
});
Object.defineProperty(TcpPackageConnection.prototype, 'remoteEndPoint', {
get: function() {
return this._connection.remoteEndPoint;
}
});
Object.defineProperty(TcpPackageConnection.prototype, 'localEndPoint', {
get: function() {
return this._connection.localEndPoint;
}
});
TcpPackageConnection.prototype._onRawDataReceived = function(connection, data) {
try {
this._framer.unframeData(data);
} catch(e) {
this._log.error(e, "TcpPackageConnection: [%j, L%j, %s]. Invalid TCP frame received.", this.remoteEndPoint, this.localEndPoint, this._connectionId);
this.close("Invalid TCP frame received");
return;
}
connection.receive(this._onRawDataReceived.bind(this));
};
TcpPackageConnection.prototype._incomingMessageArrived = function(data) {
var valid = false;
var pkg;
try
{
pkg = TcpPackage.fromBufferSegment(data);
valid = true;
this._handlePackage(this, pkg);
}
catch (e)
{
this._connection.close(util.format("Error when processing TcpPackage %s: %s",
valid ? TcpCommand.getName(pkg.command) : "<invalid package>", e.message));
var message = util.format("TcpPackageConnection: [%j, L%j, %s] ERROR for %s. Connection will be closed.",
this.remoteEndPoint, this.localEndPoint, this._connectionId,
valid ? TcpCommand.getName(pkg.command) : "<invalid package>");
if (this._onError != null)
this._onError(this, e);
this._log.debug(e, message);
}
};
TcpPackageConnection.prototype.startReceiving = function() {
if (this._connection == null)
throw new Error("Failed connection.");
this._connection.receive(this._onRawDataReceived.bind(this));
};
TcpPackageConnection.prototype.enqueueSend = function(pkg) {
if (this._connection == null)
throw new Error("Failed connection.");
this._connection.enqueueSend(this._framer.frameData(pkg.asBufferSegment()));
};
TcpPackageConnection.prototype.close = function(reason) {
if (this._connection == null)
throw new Error("Failed connection.");
this._connection.close(reason);
};
TcpPackageConnection.prototype.equals = function(other) {
if (other === null) return false;
return this._connectionId === other._connectionId;
};
module.exports = TcpPackageConnection;