jspurefix
Version:
pure node js fix engine
144 lines (130 loc) • 5.2 kB
text/typescript
import { IFixMsgStore } from './fix-msg-store'
import { FixMsgStoreRecord, IFixMsgStoreRecord } from './fix-msg-store-record'
import { IJsFixConfig } from '../config'
import { MsgType } from '../types'
import { ElasticBuffer, MsgView } from '../buffer'
import { AsciiParser } from '../buffer/ascii'
import { ISequenceReset, IStandardHeader } from '../types/FIX4.4/repo'
export class FixMsgAsciiStoreResend {
parser: AsciiParser
constructor (public readonly store: IFixMsgStore, public readonly config: IJsFixConfig) {
this.parser = new AsciiParser(this.config, null, new ElasticBuffer(160 * 1024))
}
public async getResendRequest (startSeq: number, endSeq: number): Promise<IFixMsgStoreRecord[]> {
// need to cover request from start to end where any missing numbers are
// included as gaps to allow vector of messages to be sent by the session
// on a request
return await new Promise((resolve, reject) => {
this.store.getSeqNumRange(startSeq, endSeq).then(res => {
resolve(this.inflateRange(startSeq, endSeq, res))
}).catch(e => {
reject(e)
})
})
}
private inflateRange (startSeq: number, endSeq: number, input: IFixMsgStoreRecord[]): IFixMsgStoreRecord[] {
const toResend: IFixMsgStoreRecord[] = []
// If no records for this given sequence number range, returns a single gap fill
if (input.length === 0) {
this.gap(startSeq, endSeq + 1, toResend)
return toResend
}
let expected = startSeq
for (let i = 0; i < input.length; ++i) {
const record = this.prepareRecordForRetransmission(input[i])
const seqNum = record.seqNum
const toGap = seqNum - expected
if (toGap > 0) {
this.gap(expected, seqNum, toResend)
}
expected = seqNum + 1
if (record.encoded) {
this.inflate(record)
}
toResend.push(record)
}
if (endSeq - expected > 0) {
this.gap(expected, endSeq + 1, toResend)
}
return toResend
}
private gap (beginGap: number, newSeq: number, arr: IFixMsgStoreRecord[]): void {
if (beginGap > 0) {
arr.push(this.sequenceResetGap(beginGap, newSeq))
}
}
// if records were sent as encoded text then inflate back to object
// so can be resent or examined
private inflate (record: IFixMsgStoreRecord): void {
if (record.obj) return
if (!record.encoded) return
const parser = this.parser
parser.on('error', (_: Error) => {
record.obj = null
})
parser.on('msg', (view: MsgView) => {
record.obj = view.toObject()
})
// inline parse
parser.parseText(record.encoded)
}
/**
* A continuous sequence of messages not being retransmitted should be skipped over using a
* single SequenceReset(35=4) message with GapFillFlag(123) set to “Y” and MsgSeqNum(34) set
* to the sequence number of the first skipped message and NewSeqNo(36) must always be set
* to the value of the next sequence number to be expected by the peer immediately following
* the messages being skipped.
*/
private sequenceResetGap (startGap: number, newSeq: number): IFixMsgStoreRecord {
const factory = this.config.factory
const gapFill: ISequenceReset = factory?.sequenceReset(newSeq, true) as ISequenceReset
gapFill.StandardHeader = factory?.header(MsgType.SequenceReset, startGap) as IStandardHeader
gapFill.StandardHeader.PossDupFlag = true
return new FixMsgStoreRecord(
MsgType.SequenceReset,
new Date(),
startGap,
gapFill,
null,
)
}
/**
* Prepares the FIX message as response to ResendRequest (2).
*
* The FIX session processor retransmitting a message with the PossDupFlag(43) set to "Y" must modify the following fields:
*
* SendingTime(52) set to the current sending time
* OrigSendingTime(122) set to the SendingTime(52) from the original message
* Recalculate the BodyLength(9)
* Recalculate the CheckSum(10)
*
* If the message is encrypted, SecureDataLen(90) and SecureData(91) may also require re-encryption and re-encoding
*
* @see https://www.fixtrading.org/standards/fix-session-layer-online/#message-recovery
*
* @param originalRecord the FIX message to be retransmitted as possible duplicate
* @returns the FIX message ready to be retransmitted
*/
private prepareRecordForRetransmission (originalRecord: IFixMsgStoreRecord): IFixMsgStoreRecord {
const retransmitted = originalRecord.clone() // We don't want to accidently change any fields of the original record
const factory = this.config.factory
if (!retransmitted.obj) {
retransmitted.obj = {}
}
// Rebuilds header with the updated fields
const header = factory?.header(
retransmitted.msgType,
retransmitted.seqNum,
new Date(), // SendingTime(52)
{
PossDupFlag: true,
OrigSendingTime: retransmitted.timestamp
}
)
retransmitted.obj = {
...retransmitted.obj,
StandardHeader: header
}
return retransmitted
}
}