Initial commit
This commit is contained in:
72
src/transport/tcp/lengthPrefixMessageFramer.js
Normal file
72
src/transport/tcp/lengthPrefixMessageFramer.js
Normal 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;
|
151
src/transport/tcp/tcpConnection.js
Normal file
151
src/transport/tcp/tcpConnection.js
Normal 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;
|
149
src/transport/tcp/tcpPackageConnection.js
Normal file
149
src/transport/tcp/tcpPackageConnection.js
Normal 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;
|
Reference in New Issue
Block a user