Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gap fill with wrong seq num & not inflating correctly if end seq is 0 #83

Merged
merged 1 commit into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions src/store/fix-msg-ascii-store-resend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ export class FixMsgAsciiStoreResend {

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])
Expand All @@ -48,16 +54,16 @@ export class FixMsgAsciiStoreResend {
return toResend
}

public gap (beginGap: number, seqNum: number, arr: IFixMsgStoreRecord[]): void {
private gap (beginGap: number, newSeq: number, arr: IFixMsgStoreRecord[]): void {
if (beginGap > 0) {
arr.push(this.sequenceResetGap(beginGap, seqNum))
arr.push(this.sequenceResetGap(beginGap, newSeq))
}
}

// if records were sent as encoded text then inflate back to object
// so can be resent or examined

public inflate (record: IFixMsgStoreRecord): void {
private inflate (record: IFixMsgStoreRecord): void {
if (record.obj) return
if (!record.encoded) return
const parser = this.parser
Expand All @@ -71,18 +77,26 @@ export class FixMsgAsciiStoreResend {
parser.parseText(record.encoded)
}

public sequenceResetGap (startGap: number, newSeq: number): IFixMsgStoreRecord {
/**
* 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
gapFill.NewSeqNo = newSeq

return new FixMsgStoreRecord(
MsgType.SequenceReset,
new Date(),
newSeq,
startGap,
gapFill,
null)
null,
)
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/test/ascii/ascii-store-replay.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ function checkSeqReset (rec: IFixMsgStoreRecord, from: number, to: number): void
const reset: ISequenceReset = rec.obj as ISequenceReset
expect(rec.msgType).toEqual(MsgType.SequenceReset)
expect(rec.obj).toBeTruthy()
expect(rec.seqNum).toEqual(to)
expect(rec.seqNum).toEqual(from)
expect(reset.NewSeqNo).toEqual(to)
expect(reset.GapFillFlag).toBeTruthy()
expect(reset.StandardHeader.MsgType).toEqual(MsgType.SequenceReset)
expect(reset.StandardHeader.PossDupFlag).toBeTruthy()
Expand Down
93 changes: 79 additions & 14 deletions src/test/ascii/session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,23 +72,88 @@ test('end to end logon', async () => {
test('session send resendRequest when logged on', async () => {
const runner: SkeletonRunner = new SkeletonRunner(experiment, 2)
const factory = experiment.client.config.factory
const resend = factory?.resendRequest(1, 2)
const resend = factory?.resendRequest(1, 1)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this scenario, we've only sent the login message so far, so there's no reason for it to have end seq num = 2 (unless it is 2 on purpose, for some reason)...

expect(resend).toBeTruthy()
if (!resend) return
runner.sendMsg(MsgType.ResendRequest, resend)
try {
const cViews = experiment.client.views
const sViews = experiment.server.views
await runner.wait()
const last = experiment.client.views[experiment.client.views.length - 1]
expect(last).toBeTruthy()
const clientResets = countOfType('SequenceReset', cViews)
const serverResets = countOfType('SequenceReset', sViews)
expect(clientResets).toEqual(1)
expect(serverResets).toEqual(0)
} catch (e) {
expect(true).toEqual(false)
}

const cViews = experiment.client.views
const sViews = experiment.server.views
await runner.wait()

expect(cViews).toHaveLength(3)
expect(sViews).toHaveLength(3)

const resendRequestView = sViews[1]
expect(resendRequestView.segment.name).toBe('ResendRequest')
expect(resendRequestView.getTyped('MsgSeqNum')).toBe(2)
expect(resendRequestView.getTyped('BeginSeqNo')).toBe(1)
expect(resendRequestView.getTyped('EndSeqNo')).toBe(1)

const seqResetView = cViews[1]
expect(seqResetView.segment.name).toBe('SequenceReset')
expect(seqResetView.getTyped('MsgSeqNum')).toBe(1)
expect(seqResetView.getTyped('NewSeqNo')).toBe(2)
expect(seqResetView.getTyped('GapFillFlag')).toBe(true)
expect(seqResetView.getTyped('PossDupFlag')).toBe(true)

const logoutSView = sViews[2]
expect(logoutSView.segment.name).toBe('Logout')
expect(logoutSView.getTyped('MsgSeqNum')).toBe(3)

const logoutCView = cViews[2]
expect(logoutCView.segment.name).toBe('Logout')
expect(logoutCView.getTyped('MsgSeqNum')).toBe(2)

const clientResets = countOfType('SequenceReset', cViews)
const serverResets = countOfType('SequenceReset', sViews)
console.log('SERVER VIEWS', sViews.map((a => a.toJson())));
console.log('CLIENT VIEWS', cViews.map((a => a.toJson())));
expect(clientResets).toEqual(1)
expect(serverResets).toEqual(0)
})

test('session send resendRequest with endSeqNo = 0 when logged on', async () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test must have the same behavior as above, as end seq num = 0 should be resolved to 1 (which is the last seq num sent).

const runner: SkeletonRunner = new SkeletonRunner(experiment, 2)
const factory = experiment.client.config.factory

const resend = factory?.resendRequest(1, 0)
expect(resend).toBeTruthy()
if (!resend) return
runner.sendMsg(MsgType.ResendRequest, resend)

const cViews = experiment.client.views
const sViews = experiment.server.views
await runner.wait()

expect(cViews).toHaveLength(3)
expect(sViews).toHaveLength(3)

const resendRequestView = sViews[1]
expect(resendRequestView.segment.name).toBe('ResendRequest')
expect(resendRequestView.getTyped('MsgSeqNum')).toBe(2)
expect(resendRequestView.getTyped('BeginSeqNo')).toBe(1)
expect(resendRequestView.getTyped('EndSeqNo')).toBe(0)

const seqResetView = cViews[1]
expect(seqResetView.segment.name).toBe('SequenceReset')
expect(seqResetView.getTyped('MsgSeqNum')).toBe(1)
expect(seqResetView.getTyped('NewSeqNo')).toBe(2)
expect(seqResetView.getTyped('GapFillFlag')).toBe(true)
expect(seqResetView.getTyped('PossDupFlag')).toBe(true)

const logoutSView = sViews[2]
expect(logoutSView.segment.name).toBe('Logout')
expect(logoutSView.getTyped('MsgSeqNum')).toBe(3)

const logoutCView = cViews[2]
expect(logoutCView.segment.name).toBe('Logout')
expect(logoutCView.getTyped('MsgSeqNum')).toBe(2)

const clientResets = countOfType('SequenceReset', cViews)
const serverResets = countOfType('SequenceReset', sViews)
expect(clientResets).toEqual(1)
expect(serverResets).toEqual(0)
})

test('session send logon when logged on', async () => {
Expand Down
6 changes: 5 additions & 1 deletion src/transport/ascii/ascii-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,11 @@ export abstract class AsciiSession extends FixSession {
protected onResendRequest (view: MsgView): void {
// if no records are in store then send a gap fill for entire sequence
this.setState(SessionState.HandleResendRequest)
const [beginSeqNo, endSeqNo] = view.getTypedTags([MsgTag.BeginSeqNo, MsgTag.EndSeqNo])
const [beginSeqNo, requestedEndSeqNo] = view.getTypedTags([MsgTag.BeginSeqNo, MsgTag.EndSeqNo])
const endSeqNo = requestedEndSeqNo === 0
? this.sessionState.lastSentSeqNum()
: requestedEndSeqNo

this.sessionLogger.info(`onResendRequest getResendRequest beginSeqNo = ${beginSeqNo}, endSeqNo = ${endSeqNo}`)
this.resender.getResendRequest(beginSeqNo as number, endSeqNo as number).then((records: IFixMsgStoreRecord[]) => {
const validRecords = records.filter(rec => rec.obj !== null)
Expand Down