UNPKG

jspurefix

Version:
328 lines 14.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AsciiSession = void 0; const types_1 = require("../../types"); const fix_session_1 = require("../session/fix-session"); const store_1 = require("../../store"); const tcp_1 = require("../tcp"); const tick_action_1 = require("../tick-action"); const segment_type_1 = require("../../buffer/segment/segment-type"); class AsciiSession extends fix_session_1.FixSession { constructor(config) { super(config); this.config = config; this.heartbeat = true; this.store = null; this.requestLogoutType = this.respondLogoutType = types_1.MsgType.Logout; this.requestLogonType = types_1.MsgType.Logon; this.store = new store_1.FixMsgMemoryStore(this.config.description.SenderCompId, this.config); this.resender = new store_1.FixMsgAsciiStoreResend(this.store, this.config); } checkSeqNo(msgType, view) { switch (msgType) { case types_1.MsgType.SequenceReset: { return true; } default: { const state = this.sessionState; const lastSeq = state.lastPeerMsgSeqNum; const seqNo = view.getTyped(types_1.MsgTag.MsgSeqNum); let ret = false; const seqDelta = seqNo - lastSeq; if (seqDelta <= 0) { this.sessionLogger.warning(`terminate as seqDelta (${seqDelta}) < 0 lastSeq = ${lastSeq} seqNo = ${seqNo}`); this.stop(); } else if (seqDelta > 1) { if (msgType === types_1.MsgType.Logon) { this.peerLogon(view); } if (msgType === types_1.MsgType.ResendRequest) { this.onResendRequest(view); } this.sendResendRequest(lastSeq, seqNo); } else { ret = true; state.lastPeerMsgSeqNum = seqNo; } return ret; } } } checkForwardMsg(msgType, view) { const okToForward = this.validStateApplicationMsg(); if (okToForward) { this.sessionLogger.info(`ascii forwarding msgType = '${msgType}' to application`); this.setState(tcp_1.SessionState.ActiveNormalSession); this.onApplicationMsg(msgType, view); } else { this.terminate(new Error(`msgType ${msgType} received in state ${this.stateString()}`)); } } sendReject(msgType, seqNo, msg, reason) { const factory = this.config.factory; const reject = factory === null || factory === void 0 ? void 0 : factory.reject(msgType, seqNo, msg, reason); if (reject) { this.sessionLogger.warning(`rejecting with ${JSON.stringify(reject)}`); this.send(types_1.MsgType.Reject, reject); } } sendResendRequest(lastSeq, receivedSeq) { var _a; const resend = (_a = this.config.factory) === null || _a === void 0 ? void 0 : _a.resendRequest(lastSeq + 1, 0); if (resend) { this.sessionLogger.warning(`received seq ${receivedSeq}, but last known seq is ${lastSeq}. Sending resend request for all messages > ${lastSeq}`); this.send(types_1.MsgType.ResendRequest, resend); } } checkIntegrity(msgType, view) { var _a; const state = this.sessionState; const seqNum = view.getTyped(types_1.MsgTag.MsgSeqNum); const received = parseInt((_a = view.getString(types_1.MsgTag.CheckSum)) !== null && _a !== void 0 ? _a : '', 10); const computed = view.checksum(); if (received !== computed) { const msg = `msgType ${msgType} checksum failed. received = ${received} computed = ${computed}`; this.sendReject(msgType, seqNum, msg, types_1.SessionRejectReason.ValueIsIncorrect); return false; } if (view.segment.type === segment_type_1.SegmentType.Unknown) { const msg = `msgType ${msgType} unknown`; this.sendReject(msgType, seqNum, msg, types_1.SessionRejectReason.InvalidMsgType); return false; } const invalid = view.invalid(); if (invalid.length > 0) { const msg = `msgType ${msgType} invalid tag${invalid.length > 1 ? 's' : ''} ${invalid.join(', ')}`; this.sendReject(msgType, seqNum, msg, types_1.SessionRejectReason.InvalidTagNumber); return false; } const undefinedMsg = view.undefinedForMsg(); if (undefinedMsg) { const msg = `msgType ${msgType} ${undefinedMsg}`; this.sendReject(msgType, seqNum, msg, types_1.SessionRejectReason.TagNotDefinedForThisMessageType); return false; } const missingRequired = view.missing(); if (missingRequired.length > 0) { const msg = `msgType ${msgType} missing required tag${missingRequired.length > 1 ? 's' : ''} ${missingRequired.join(', ')}`; this.sendReject(msgType, seqNum, msg, types_1.SessionRejectReason.RequiredTagMissing); return false; } switch (state.state) { case tcp_1.SessionState.InitiationLogonReceived: case tcp_1.SessionState.InitiationLogonResponse: { const targetCompId = view.getString(types_1.MsgTag.TargetCompID); if (targetCompId !== state.compId) { const msg = `msgType ${msgType} unexpected TargetCompID ${targetCompId} expecting ${state.compId})`; this.sendReject(msgType, seqNum, msg, types_1.SessionRejectReason.CompIDProblem); return false; } const peerCompId = view.getString(types_1.MsgTag.SenderCompID); if (peerCompId !== state.peerCompId) { const msg = `msgType ${msgType} unexpected SenderCompID ${peerCompId} expecting ${state.compId}`; this.sendReject(msgType, seqNum, msg, types_1.SessionRejectReason.CompIDProblem); return false; } } break; default: { break; } } return true; } onResendRequest(view) { this.setState(tcp_1.SessionState.HandleResendRequest); const [beginSeqNo, requestedEndSeqNo] = view.getTypedTags([types_1.MsgTag.BeginSeqNo, types_1.MsgTag.EndSeqNo]); const endSeqNo = requestedEndSeqNo === 0 ? this.sessionState.lastSentSeqNum() : requestedEndSeqNo; this.sessionLogger.info(`onResendRequest getResendRequest beginSeqNo = ${beginSeqNo}, endSeqNo = ${endSeqNo}`); this.resender.getResendRequest(beginSeqNo, endSeqNo).then((records) => { const validRecords = records.filter(rec => rec.obj !== null); this.sessionLogger.info(`sending ${validRecords.length}`); validRecords.forEach(rec => { if (rec.obj) { this.send(rec.msgType, rec.obj); } }); this.setState(tcp_1.SessionState.ActiveNormalSession); }).catch((e) => { this.sessionLogger.error(e); }); } okForLogon() { const state = this.sessionState.state; if (this.acceptor) { return state === tcp_1.SessionState.WaitingForALogon; } return state === tcp_1.SessionState.InitiationLogonSent; } onSessionMsg(msgType, view) { const logger = this.sessionLogger; switch (msgType) { case types_1.MsgType.Logon: { if (this.okForLogon()) { this.peerLogon(view); } else { this.terminate(new Error(`state ${this.stateString()} is illegal for Logon`)); } break; } case types_1.MsgType.Logout: { this.peerLogout(view); break; } case types_1.MsgType.TestRequest: { const req = view.getString(types_1.MsgTag.TestReqID); if (req) { this.sendHeartbeat(req); } break; } case types_1.MsgType.Heartbeat: { this.sessionState.lastTestRequestAt = null; this.setState(tcp_1.SessionState.ActiveNormalSession); break; } case types_1.MsgType.ResendRequest: { logger.info(`peer sends '${msgType}' resend request.`); this.onResendRequest(view); break; } case types_1.MsgType.SequenceReset: { const newSeqNo = view.getTyped(types_1.MsgTag.NewSeqNo); logger.info(`peer sends '${msgType}' sequence reset. newSeqNo = ${newSeqNo}`); this.sessionState.lastPeerMsgSeqNum = newSeqNo - 1; break; } case types_1.MsgType.Reject: { logger.info(`peer rejects type '${msgType}' with text '${view.getTyped(types_1.MsgTag.Text)}'`); break; } } } onMsg(msgType, view) { if (!this.checkSeqNo(msgType, view)) { this.sessionLogger.info(`message '${msgType}' failed checkSeqNo.`); return; } if (this.checkMsgIntegrity && !this.checkIntegrity(msgType, view)) { this.sessionLogger.info(`message '${msgType}' failed checkIntegrity.`); switch (msgType) { case types_1.MsgType.Logon: { this.setState(tcp_1.SessionState.PeerLogonRejected); this.startTimer(); break; } } return; } switch (msgType) { case types_1.MsgType.Logon: case types_1.MsgType.Logout: case types_1.MsgType.TestRequest: case types_1.MsgType.Reject: case types_1.MsgType.SequenceReset: case types_1.MsgType.Heartbeat: case types_1.MsgType.ResendRequest: { this.onSessionMsg(msgType, view); break; } default: { this.checkForwardMsg(msgType, view); break; } } } startTimer(interval = 200) { const logger = this.sessionLogger; logger.info(`start heartbeat timer. interval = ${interval}`); this.timer = setInterval(() => { this.tick(); }, interval); } peerLogon(view) { const logger = this.sessionLogger; const [heartBtInt, peerCompId, userName, password] = view.getTypedTags([types_1.MsgTag.HeartBtInt, types_1.MsgTag.SenderCompID, types_1.MsgTag.Username, types_1.MsgTag.Password]); logger.info(`peerLogon Username = ${userName}, heartBtInt = ${heartBtInt}, peerCompId = ${peerCompId}, userName = ${userName}`); const state = this.sessionState; state.peerHeartBeatSecs = view.getTyped(types_1.MsgTag.HeartBtInt); state.peerCompId = view.getTyped(types_1.MsgTag.SenderCompID); const res = this.onLogon(view, userName, password); logger.info(`peerLogon onLogon returns ${res}`); if (this.acceptor) { this.setState(tcp_1.SessionState.InitiationLogonResponse); logger.info('acceptor responds to logon request'); this.sendLogon(); } else { logger.info('initiator receives logon response'); this.setState(tcp_1.SessionState.InitiationLogonReceived); } if (this.heartbeat) { this.startTimer(); } logger.info('system ready, inform app'); this.onReady(view); } sendTestRequest() { const factory = this.config.factory; this.setState(tcp_1.SessionState.AwaitingProcessingResponseToTestRequest); const tr = factory === null || factory === void 0 ? void 0 : factory.testRequest(); if (tr) { this.send(types_1.MsgType.TestRequest, tr); } } sendHeartbeat(testReqId) { const factory = this.config.factory; const hb = factory === null || factory === void 0 ? void 0 : factory.heartbeat(testReqId); if (hb) { this.send(types_1.MsgType.Heartbeat, hb); } } tick() { var _a; if (!this.transport) return; const sessionState = this.sessionState; const action = sessionState.calcAction(new Date()); const application = (_a = this.transport.config.description.application) !== null && _a !== void 0 ? _a : null; const logger = this.sessionLogger; switch (action) { case tick_action_1.TickAction.Nothing: { break; } case tick_action_1.TickAction.TestRequest: { logger.debug(`send test req. state = ${sessionState.toString()}`); this.sendTestRequest(); break; } case tick_action_1.TickAction.Heartbeat: { logger.debug(`send heartbeat. state = ${sessionState.toString()}`); this.sendHeartbeat(sessionState.now.toUTCString()); break; } case tick_action_1.TickAction.TerminateOnError: { logger.info(sessionState.toString()); this.terminate(new Error(`${application === null || application === void 0 ? void 0 : application.name}: peer not responding`)); break; } case tick_action_1.TickAction.Stop: { logger.info(sessionState.toString()); logger.info('stopping'); this.stop(); break; } default: throw new Error('unexpected action'); } } } exports.AsciiSession = AsciiSession; //# sourceMappingURL=ascii-session.js.map