StackGenVis: Alignment of Data, Algorithms, and Models for Stacking Ensemble Learning Using Performance Metrics https://doi.org/10.1109/TVCG.2020.3030352
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
StackGenVis/frontend/node_modules/fix/handlers/sessionProcessor.js

399 lines
15 KiB

4 years ago
exports.newSessionProcessor = function (isAcceptor, options) {
return new sessionProcessor(isAcceptor, options);
};
var util = require('util');
var fs = require('fs');
var fixutil = require('../fixutils.js');
var _ = require('underscore');
//TODO make sure 'ignored' messages really are not forwarded to the next handler
//TODO instead of datastore function as param, expect a data structure which:
//--ds.queue.add(sender,target,value)
//--ds.hash.put(sender,target,value)
//--ds.queue.get(sender,target,callback)
//--ds.hash.get(sender,target,callback)
//TODO outgoing message handling
//TODO Normalize input parameters
function sessionProcessor(isAcceptor, options) {
var self = this;
this.isAcceptor = isAcceptor;
this.isInitiator = !isAcceptor;
this.isDuplicateFunc = _.isUndefined(options.isDuplicateFunc)? function () {return false;} : options.isDuplicateFunc;
this.isAuthenticFunc = _.isUndefined(options.isAuthenticFunc)? function () {return true;} : options.isAuthenticFunc;
this.getSeqNums = _.isUndefined(options.getSeqNums)? function () { return {'incomingSeqNum': 1, 'outgoingSeqNum': 1 }; } : options.getSeqNums;
this.datastore = _.isUndefined(options.datastore)? function () {} : options.datastore ;
this.fixVersion = null;
this.senderCompID = null;
this.targetCompID = null;
this.defaultHeartbeatSeconds = _.isUndefined(options.defaultHeartbeatSeconds)? "10" : options.defaultHeartbeatSeconds;
this.sendHeartbeats = _.isUndefined(options.sendHeartbeats)? true : options.sendHeartbeats;
this.expectHeartbeats = _.isUndefined(options.expectHeartbeats)? true : options.expectHeartbeats ;
this.respondToLogon = _.isUndefined(options.respondToLogon)? true : options.respondToLogon;
this.isLoggedIn = false;
this.heartbeatIntervalID = "";
this.timeOfLastIncoming = new Date().getTime();
this.timeOfLastOutgoing = new Date().getTime();
this.testRequestID = 1;
this.incomingSeqNum = 1;
this.outgoingSeqNum = 1;
this.isResendRequested = false;
this.isLogoutRequested = false;
this.file = null;
//||||||||||INCOMING||||||||||INCMOING||||||||||INCOMING||||||||||INCOMING||||||||||INCOMING||||||||||INCOMING||||||||||
this.incoming = function (ctx, event) {
if (event.type !== 'data') {
ctx.sendNext(event);
return;
}
self.timeOfLastIncoming = new Date().getTime();
//==Convert to key/val map==
var raw = event.data;
var fix = fixutil.convertToMap(raw);
var msgType = fix['35'];
//==Confirm first msg is logon==
if (self.isLoggedIn === false && msgType !== 'A') {
var error = '[ERROR] First message must be logon:' + raw;
util.log(error);
ctx.stream.end();
ctx.sendNext({
data: error,
type: 'error'
});
return;
}
//==Process logon
else if (self.isLoggedIn === false && msgType === 'A') {
self.fixVersion = fix['8'];
//incoming sender and target are swapped because we want sender/comp
//from our perspective, not the counter party's
self.senderCompID = fix['56'];
self.targetCompID = fix['49'];
//==Process acceptor specific logic
if (self.isAcceptor) {
//==Check duplicate connections
if (self.isDuplicateFunc(self.senderCompID, self.targetCompID)) {
var error = '[ERROR] Session already logged in:' + raw;
util.log(error);
ctx.stream.end();
ctx.sendNext({
data: error,
type: 'error'
});
return;
}
//==Authenticate connection
if (!self.isAuthenticFunc(fix, ctx.stream.remoteAddress)) {
var error = '[ERROR] Session not authentic:' + raw;
util.log(error);
ctx.stream.end();
ctx.sendNext({
data: error,
type: 'error'
});
return;
}
//==Sync sequence numbers from data store
var seqnums = self.getSeqNums(self.senderCompID, self.targetCompID);
self.incomingSeqNum = seqnums.incomingSeqNum;
self.outgoingSeqNum = seqnums.outgoingSeqNum;
} //End Process acceptor specific logic==
var heartbeatInMilliSecondsStr = _.isUndefined(fix[108] )? self.defaultHeartbeatSeconds : fix[108];
var heartbeatInMilliSeconds = parseInt(heartbeatInMilliSecondsStr, 10) * 1000;
//console.log("heartbeatInMilliSeconds="+heartbeatInMilliSeconds);//debug
//==Set heartbeat mechanism
self.heartbeatIntervalID = setInterval(function () {
var currentTime = new Date().getTime();
//==send heartbeats
if (currentTime - self.timeOfLastOutgoing > heartbeatInMilliSeconds && self.sendHeartbeats) {
self.sendMsg(ctx.sendPrev,{
'35': '0'
}); //heartbeat
}
//==ask counter party to wake up
if (currentTime - self.timeOfLastIncoming > (heartbeatInMilliSeconds * 1.5)&& self.expectHeartbeats) {
self.sendMsg(ctx.sendPrev,{
'35': '1',
'112': self.testRequestID++
}); //test req id
}
//==counter party might be dead, kill connection
if (currentTime - self.timeOfLastIncoming > heartbeatInMilliSeconds * 2 && self.expectHeartbeats) {
var error = '[ERROR] No heartbeat from counter party in milliseconds ' + heartbeatInMilliSeconds * 1.5;
util.log(error);
ctx.stream.end();
ctx.sendNext({
data: error,
type: 'error'
});
return;
}
}, heartbeatInMilliSeconds / 2); //End Set heartbeat mechanism==
//==When session ends, stop heartbeats
ctx.stream.on('end', function () {
clearInterval(self.heartbeatIntervalID);
});
//==Logon successful
self.isLoggedIn = true;
//==Logon ack (acceptor)
if (self.isAcceptor && self.respondToLogon) {
/*var loginack = _.clone(fix);
loginack[49] = fix[56];
loginack[56] = fix[49];
self.sendMsg(ctx.sendPrev,loginack);*/
self.sendMsg(ctx.sendPrev,fix);
}
} // End Process logon==
//==Record message--TODO duplicate logic (n outgoing as well)
if (self.file === null) {
var filename = './traffic/' + self.senderCompID + '->' + self.targetCompID + '.log';
self.file = fs.createWriteStream(filename, { 'flags': 'a+' });
self.file.on('error', function(err){ console.log(err); });//todo print good log, end session
}
self.file.write(raw+'\n');
//==Process seq-reset (no gap-fill)
if (msgType === '4' && fix['123'] === undefined || fix['123'] === 'N') {
var resetseqnostr = fix['36'];
var resetseqno = parseInt(resetseqno, 10);
if (resetseqno >= self.incomingSeqNum) {
self.incomingSeqNum = resetseqno
} else {
var error = '[ERROR] Seq-reset may not decrement sequence numbers: ' + raw;
util.log(error);
ctx.stream.end();
ctx.sendNext({
data: error,
type: 'error'
});
return;
}
}
//==Check sequence numbers
var msgSeqNumStr = fix['34'];
var msgSeqNum = parseInt(msgSeqNumStr, 10);
//expected sequence number
if (msgSeqNum === self.incomingSeqNum) {
self.incomingSeqNum++;
self.isResendRequested = false;
}
//less than expected
else if (msgSeqNum < self.incomingSeqNum) {
//ignore posdup
if (fix['43'] === 'Y') {
return;
}
//if not posdup, error
else {
var error = '[ERROR] Incoming sequence number ('+msgSeqNum+') lower than expected (' + self.incomingSeqNum+ ') : ' + raw;
util.log(error);
ctx.stream.end();
ctx.sendNext({
data: error,
type: 'error'
});
return;
}
}
//greater than expected
else {
//is it resend request?
if (msgType === '2') {
//TODO remove duplication in resend processor
//get list of msgs from archive and send them out, but gap fill admin msgs
var reader = fs.createReadStream(filename, {
'flags': 'r',
'encoding': 'binary',
'mode': 0666,
'bufferSize': 4 * 1024
})
//TODO full lines may not be read
reader.addListener("data", function (chunk) {
var _fix = fixutil.converToMap(chunk);
var _msgType = _fix[35];
var _seqNo = _fix[34];
if (_.include(['A', '5', '2', '0', '1', '4'], _msgType)) {
//send seq-reset with gap-fill Y
self.sendMsg(ctx.sendPrev,{
'35': '4',
'123': 'Y',
'36': _seqNo
});
} else {
//send msg w/ posdup Y
self.sendMsg(ctx.sendPrev,_.extend(_fix, {
'43': 'Y'
}));
}
});
}
//did we already send a resend request?
if (self.isResendRequested === false) {
self.isResendRequested = true;
//send resend-request
self.sendMsg(ctx.sendPrev,{
'35': '2',
'7': self.incomingSeqNum,
'16': '0'
});
}
}
//==Process sequence-reset with gap-fill
if (msgType === '4' && fix['123'] === 'Y') {
var newSeqNoStr = fix['36'];
var newSeqNo = parseInt(newSeqNoStr, 10);
if (newSeqNo >= self.incomingSeqNum) {
self.incomingSeqNum = newSeqNo;
} else {
var error = '[ERROR] Seq-reset may not decrement sequence numbers: ' + raw;
util.log(error);
ctx.stream.end();
ctx.sendNext({
data: error,
type: 'error'
});
return;
}
}
//==Check compids and version
//TODO
//==Process test request
if (msgType === '1') {
var testReqID = fix['112'];
self.sendMsg(ctx.sendPrev,{
'35': '0',
'112': testReqID
});
}
//==Process resend-request
if (msgType === '2') {
//TODO remove duplication in resend processor
//get list of msgs from archive and send them out, but gap fill admin msgs
var reader = fs.createReadStream(filename, {
'flags': 'r',
'encoding': 'binary',
'mode': 0666,
'bufferSize': 4 * 1024
})
//TODO full lines may not be read
reader.addListener("data", function (chunk) {
var _fix = fixutil.converToMap(chunk);
var _msgType = _fix[35];
var _seqNo = _fix[34];
if (_.include(['A', '5', '2', '0', '1', '4'], _msgType)) {
//send seq-reset with gap-fill Y
self.sendMsg(ctx.sendPrev,{
'35': '4',
'123': 'Y',
'36': _seqNo
});
} else {
//send msg w/ posdup Y
self.sendMsg(ctx.sendPrev,_.extend(_fix, {
'43': 'Y'
}));
}
});
}
//==Process logout
if (msgType === '5') {
if (self.isLogoutRequested) {
ctx.stream.end();
} else {
self.sendMsg(ctx.sendPrev,fix);
}
}
ctx.sendNext({data:fix, type:'data'});
}
//||||||||||OUTGOING||||||||||OUTGOING||||||||||OUTGOING||||||||||OUTGOING||||||||||OUTGOING||||||||||OUTGOING||||||||||
this.outgoing = function (ctx, event) {
if(event.type !== 'data'){
ctx.sendNext(event);
return;
}
var fix = event.data;
var msgType = fix['35'];
if(self.isLoggedIn === false && msgType === "A"){
self.fixVersion = fix['8'];
self.senderCompID = fix['49'];
self.targetCompID = fix['56'];
//==Sync sequence numbers from data store
var seqnums = self.getSeqNums(self.senderCompID, self.targetCompID);
self.incomingSeqNum = seqnums.incomingSeqNum;
self.outgoingSeqNum = seqnums.outgoingSeqNum;
}
self.sendMsg(ctx.sendNext, fix);
}
//||||||||||UTILITY||||||||||UTILITY||||||||||UTILITY||||||||||UTILITY||||||||||UTILITY||||||||||UTIILTY||||||||||
this.sendMsg = function(senderFunc, msg){
var outmsg = fixutil.convertToFIX(msg, self.fixVersion, fixutil.getUTCTimeStamp(new Date()),
self.senderCompID, self.targetCompID, self.outgoingSeqNum);
self.outgoingSeqNum = self.outgoingSeqNum + 1;
self.timeOfLastOutgoing = new Date().getTime();
//==Record message--TODO duplicate logic (n incoming as well)
if (self.file === null) {
var filename = './traffic/' + self.senderCompID + '->' + self.targetCompID + '.log';
self.file = fs.createWriteStream(filename, { 'flags': 'a+' });
self.file.on('error', function(err){ console.log(err); });//todo print good log, end session
}
self.file.write(outmsg+'\n');
senderFunc({data:outmsg, type:'data'});
}
}