jspurefix 5.0.0 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BACKPORT_PLAN.md +135 -62
- package/README.md +1 -2
- package/dist/buffer/ascii/ascii-parser-state.js +0 -7
- package/dist/buffer/ascii/ascii-parser-state.js.map +1 -1
- package/dist/buffer/ascii/ascii-parser.d.ts +1 -0
- package/dist/buffer/ascii/ascii-parser.js +8 -2
- package/dist/buffer/ascii/ascii-parser.js.map +1 -1
- package/dist/buffer/fixml/fixml-view.js.map +1 -1
- package/dist/buffer/msg-encoder.js +34 -1
- package/dist/buffer/msg-encoder.js.map +1 -1
- package/dist/buffer/msg-parser.js +34 -1
- package/dist/buffer/msg-parser.js.map +1 -1
- package/dist/buffer/msg-view.js.map +1 -1
- package/dist/collections/index.js +1 -0
- package/dist/config/winston-logger.js.map +1 -1
- package/dist/dict-parser.js +34 -1
- package/dist/dict-parser.js.map +1 -1
- package/dist/dictionary/compiler/enum-compiler.js +37 -4
- package/dist/dictionary/compiler/enum-compiler.js.map +1 -1
- package/dist/dictionary/compiler/msg-compiler.js +36 -3
- package/dist/dictionary/compiler/msg-compiler.js.map +1 -1
- package/dist/dictionary/compiler/standard-snippet.js +34 -1
- package/dist/dictionary/compiler/standard-snippet.js.map +1 -1
- package/dist/dictionary/contained/contained-field-set.js +2 -0
- package/dist/dictionary/contained/contained-field-set.js.map +1 -1
- package/dist/dictionary/definition/simple-field-definition.js +34 -1
- package/dist/dictionary/definition/simple-field-definition.js.map +1 -1
- package/dist/dictionary/fix-parser.js +34 -1
- package/dist/dictionary/fix-parser.js.map +1 -1
- package/dist/dictionary/parser/fix-repository/repository-type.js +1 -0
- package/dist/dictionary/parser/fix-repository/repository-xml-parser.js +35 -2
- package/dist/dictionary/parser/fix-repository/repository-xml-parser.js.map +1 -1
- package/dist/dictionary/parser/fixml/fields-parser.js.map +1 -1
- package/dist/dictionary/parser/fixml/fix-xsd-parser.js +34 -1
- package/dist/dictionary/parser/fixml/fix-xsd-parser.js.map +1 -1
- package/dist/dictionary/parser/fixml/include-graph.js +35 -2
- package/dist/dictionary/parser/fixml/include-graph.js.map +1 -1
- package/dist/dictionary/parser/fixml/node-definitions.js +1 -0
- package/dist/dictionary/parser/fixml/xsd-parser.js +34 -1
- package/dist/dictionary/parser/fixml/xsd-parser.js.map +1 -1
- package/dist/jsfix-cmd.js +39 -3
- package/dist/jsfix-cmd.js.map +1 -1
- package/dist/runtime/session-launcher.js +34 -1
- package/dist/runtime/session-launcher.js.map +1 -1
- package/dist/sample/http/oms/app.js +34 -1
- package/dist/sample/http/oms/app.js.map +1 -1
- package/dist/sample/tcp/recovering-skeleton/app.js +34 -1
- package/dist/sample/tcp/recovering-skeleton/app.js.map +1 -1
- package/dist/store/fix-msg-ascii-store-resend.js +1 -1
- package/dist/store/fix-msg-ascii-store-resend.js.map +1 -1
- package/dist/transport/ascii/ascii-session.d.ts +6 -0
- package/dist/transport/ascii/ascii-session.js +122 -5
- package/dist/transport/ascii/ascii-session.js.map +1 -1
- package/dist/transport/duplex/http-duplex.js +4 -1
- package/dist/transport/duplex/http-duplex.js.map +1 -1
- package/dist/transport/duplex/tcp-duplex.js +34 -1
- package/dist/transport/duplex/tcp-duplex.js.map +1 -1
- package/dist/transport/fix-acceptor.js +34 -1
- package/dist/transport/fix-acceptor.js.map +1 -1
- package/dist/transport/fix-entity.js +34 -1
- package/dist/transport/fix-entity.js.map +1 -1
- package/dist/transport/fixml/fixml-msg-transmitter.js +1 -1
- package/dist/transport/fixml/fixml-msg-transmitter.js.map +1 -1
- package/dist/transport/http/http-acceptor.js +34 -1
- package/dist/transport/http/http-acceptor.js.map +1 -1
- package/dist/transport/msg-transmitter.js +34 -1
- package/dist/transport/msg-transmitter.js.map +1 -1
- package/dist/transport/session/a-session-msg-factory.d.ts +1 -1
- package/dist/transport/session/a-session-msg-factory.js.map +1 -1
- package/dist/transport/session/fix-clock.d.ts +6 -0
- package/dist/transport/session/fix-clock.js +10 -0
- package/dist/transport/session/fix-clock.js.map +1 -0
- package/dist/transport/session/fix-session.d.ts +1 -0
- package/dist/transport/session/fix-session.js +43 -1
- package/dist/transport/session/fix-session.js.map +1 -1
- package/dist/transport/session/index.d.ts +4 -0
- package/dist/transport/session/index.js +4 -0
- package/dist/transport/session/index.js.map +1 -1
- package/dist/transport/session/resend-request-manager.d.ts +69 -0
- package/dist/transport/session/resend-request-manager.js +208 -0
- package/dist/transport/session/resend-request-manager.js.map +1 -0
- package/dist/transport/session/session-msg-factory.d.ts +1 -1
- package/dist/transport/session/session-msg-factory.js.map +1 -1
- package/dist/transport/session/session-sequence-coordinator.d.ts +38 -0
- package/dist/transport/session/session-sequence-coordinator.js +180 -0
- package/dist/transport/session/session-sequence-coordinator.js.map +1 -0
- package/dist/transport/session/session-sequence-store.d.ts +14 -0
- package/dist/transport/session/session-sequence-store.js +36 -0
- package/dist/transport/session/session-sequence-store.js.map +1 -0
- package/dist/transport/tcp/tcp-acceptor.js.map +1 -1
- package/dist/transport/tcp/tcp-initiator.js +34 -1
- package/dist/transport/tcp/tcp-initiator.js.map +1 -1
- package/dist/types/FIX4.4/index.js +1 -0
- package/dist/util/buffer-helper.js +34 -1
- package/dist/util/buffer-helper.js.map +1 -1
- package/dist/util/definition-factory.js +35 -2
- package/dist/util/definition-factory.js.map +1 -1
- package/jsfix.test_client.txt +67 -66
- package/jsfix.test_server.txt +64 -63
- package/package.json +11 -10
- package/src/buffer/ascii/ascii-parser-state.ts +4 -7
- package/src/buffer/ascii/ascii-parser.ts +14 -3
- package/src/buffer/fixml/fixml-view.ts +1 -1
- package/src/buffer/msg-view.ts +1 -1
- package/src/config/winston-logger.ts +3 -3
- package/src/dictionary/contained/contained-field-set.ts +2 -1
- package/src/dictionary/parser/fixml/fields-parser.ts +2 -2
- package/src/jsfix-cmd.ts +1 -1
- package/src/store/fix-msg-ascii-store-resend.ts +1 -1
- package/src/transport/ascii/ascii-session.ts +166 -5
- package/src/transport/fixml/fixml-msg-transmitter.ts +1 -1
- package/src/transport/http/http-acceptor.ts +1 -1
- package/src/transport/session/a-session-msg-factory.ts +1 -1
- package/src/transport/session/fix-clock.ts +9 -0
- package/src/transport/session/fix-session.ts +12 -0
- package/src/transport/session/index.ts +4 -0
- package/src/transport/session/resend-request-manager.ts +268 -0
- package/src/transport/session/session-msg-factory.ts +1 -1
- package/src/transport/session/session-sequence-coordinator.ts +272 -0
- package/src/transport/session/session-sequence-store.ts +33 -0
- package/src/transport/tcp/tcp-acceptor.ts +2 -2
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ResendRequestManager = exports.PendingResendRange = exports.ResendAction = exports.ResendActionType = void 0;
|
|
4
|
+
var ResendActionType;
|
|
5
|
+
(function (ResendActionType) {
|
|
6
|
+
ResendActionType["Nothing"] = "Nothing";
|
|
7
|
+
ResendActionType["SendResendRequest"] = "SendResendRequest";
|
|
8
|
+
ResendActionType["SendGapFill"] = "SendGapFill";
|
|
9
|
+
ResendActionType["Wait"] = "Wait";
|
|
10
|
+
})(ResendActionType || (exports.ResendActionType = ResendActionType = {}));
|
|
11
|
+
class ResendAction {
|
|
12
|
+
constructor(type, begin, end, reason) {
|
|
13
|
+
this.type = type;
|
|
14
|
+
this.begin = begin;
|
|
15
|
+
this.end = end;
|
|
16
|
+
this.reason = reason;
|
|
17
|
+
}
|
|
18
|
+
static sendResendRequest(begin, end) {
|
|
19
|
+
return new ResendAction(ResendActionType.SendResendRequest, begin, end, null);
|
|
20
|
+
}
|
|
21
|
+
static gapFill(begin, end, reason) {
|
|
22
|
+
return new ResendAction(ResendActionType.SendGapFill, begin, end, reason);
|
|
23
|
+
}
|
|
24
|
+
static wait(reason) {
|
|
25
|
+
return new ResendAction(ResendActionType.Wait, null, null, reason);
|
|
26
|
+
}
|
|
27
|
+
static nothing() {
|
|
28
|
+
return new ResendAction(ResendActionType.Nothing, null, null, null);
|
|
29
|
+
}
|
|
30
|
+
toString() {
|
|
31
|
+
switch (this.type) {
|
|
32
|
+
case ResendActionType.SendResendRequest:
|
|
33
|
+
return `SendResendRequest(${this.begin}-${this.end})`;
|
|
34
|
+
case ResendActionType.SendGapFill:
|
|
35
|
+
return `GapFill(${this.begin}-${this.end}): ${this.reason}`;
|
|
36
|
+
case ResendActionType.Wait:
|
|
37
|
+
return `Wait: ${this.reason}`;
|
|
38
|
+
case ResendActionType.Nothing:
|
|
39
|
+
return 'Nothing';
|
|
40
|
+
default:
|
|
41
|
+
return `Unknown(${this.type})`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.ResendAction = ResendAction;
|
|
46
|
+
class PendingResendRange {
|
|
47
|
+
constructor(begin, end, sentAt) {
|
|
48
|
+
this.begin = begin;
|
|
49
|
+
this.end = end;
|
|
50
|
+
this.sentAt = sentAt;
|
|
51
|
+
this.receivedSeqs = new Set();
|
|
52
|
+
}
|
|
53
|
+
markReceived(seqNum) {
|
|
54
|
+
if (seqNum >= this.begin && seqNum <= this.end) {
|
|
55
|
+
this.receivedSeqs.add(seqNum);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
markRangeReceived(fromSeq, toSeq) {
|
|
59
|
+
const start = Math.max(fromSeq, this.begin);
|
|
60
|
+
const stop = Math.min(toSeq, this.end);
|
|
61
|
+
for (let seq = start; seq <= stop; seq++) {
|
|
62
|
+
this.receivedSeqs.add(seq);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
get isFullySatisfied() {
|
|
66
|
+
return this.receivedSeqs.size >= (this.end - this.begin + 1);
|
|
67
|
+
}
|
|
68
|
+
fullyCovers(begin, end) {
|
|
69
|
+
return begin >= this.begin && end <= this.end;
|
|
70
|
+
}
|
|
71
|
+
overlaps(begin, end) {
|
|
72
|
+
return begin <= this.end && end >= this.begin;
|
|
73
|
+
}
|
|
74
|
+
get pendingCount() {
|
|
75
|
+
return (this.end - this.begin + 1) - this.receivedSeqs.size;
|
|
76
|
+
}
|
|
77
|
+
toString() {
|
|
78
|
+
return `Pending(${this.begin}-${this.end}, received=${this.receivedSeqs.size}/${this.end - this.begin + 1})`;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.PendingResendRange = PendingResendRange;
|
|
82
|
+
class ResendRequestManager {
|
|
83
|
+
constructor(config) {
|
|
84
|
+
var _a, _b, _c, _d;
|
|
85
|
+
this.pendingRequests = [];
|
|
86
|
+
this.requestHistory = [];
|
|
87
|
+
this.recentRequestTimes = [];
|
|
88
|
+
this.maxPendingRequests = (_a = config === null || config === void 0 ? void 0 : config.maxPendingRequests) !== null && _a !== void 0 ? _a : 1;
|
|
89
|
+
this.maxRequestsPerWindow = (_b = config === null || config === void 0 ? void 0 : config.maxRequestsPerWindow) !== null && _b !== void 0 ? _b : 5;
|
|
90
|
+
this.rateLimitWindowMs = ((_c = config === null || config === void 0 ? void 0 : config.rateLimitWindowSeconds) !== null && _c !== void 0 ? _c : 10) * 1000;
|
|
91
|
+
this.requestTimeoutMs = ((_d = config === null || config === void 0 ? void 0 : config.requestTimeoutSeconds) !== null && _d !== void 0 ? _d : 30) * 1000;
|
|
92
|
+
}
|
|
93
|
+
computeAction(expectedSeq, receivedSeq, now) {
|
|
94
|
+
if (receivedSeq <= expectedSeq) {
|
|
95
|
+
return ResendAction.nothing();
|
|
96
|
+
}
|
|
97
|
+
const gapBegin = expectedSeq;
|
|
98
|
+
const gapEnd = receivedSeq - 1;
|
|
99
|
+
this.cleanupTimedOutRequests(now);
|
|
100
|
+
if (this.isStorming(now)) {
|
|
101
|
+
return ResendAction.gapFill(gapBegin, gapEnd, 'storm protection - too many requests');
|
|
102
|
+
}
|
|
103
|
+
for (const pending of this.pendingRequests) {
|
|
104
|
+
if (pending.fullyCovers(gapBegin, gapEnd)) {
|
|
105
|
+
return ResendAction.wait(`fully covered by pending request ${pending}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const uncovered = this.computeUncoveredRange(gapBegin, gapEnd);
|
|
109
|
+
if (!uncovered) {
|
|
110
|
+
return ResendAction.wait('gap partially covered, waiting for pending requests');
|
|
111
|
+
}
|
|
112
|
+
if (this.pendingRequests.length >= this.maxPendingRequests) {
|
|
113
|
+
return ResendAction.wait(`max pending requests (${this.maxPendingRequests}) reached`);
|
|
114
|
+
}
|
|
115
|
+
return ResendAction.sendResendRequest(uncovered.begin, uncovered.end);
|
|
116
|
+
}
|
|
117
|
+
recordRequestSent(begin, end, now, reason = null) {
|
|
118
|
+
this.pendingRequests.push(new PendingResendRange(begin, end, now));
|
|
119
|
+
this.requestHistory.push({ begin, end, sentAt: now, reason });
|
|
120
|
+
this.recentRequestTimes.push(now);
|
|
121
|
+
}
|
|
122
|
+
onMessageReceived(seqNum, possDupFlag, now) {
|
|
123
|
+
for (const pending of this.pendingRequests) {
|
|
124
|
+
pending.markReceived(seqNum);
|
|
125
|
+
}
|
|
126
|
+
this.removeFullySatisfied();
|
|
127
|
+
}
|
|
128
|
+
onGapFillReceived(gapFillSeq, newSeqNo, now) {
|
|
129
|
+
for (const pending of this.pendingRequests) {
|
|
130
|
+
pending.markRangeReceived(gapFillSeq, newSeqNo - 1);
|
|
131
|
+
}
|
|
132
|
+
this.removeFullySatisfied();
|
|
133
|
+
}
|
|
134
|
+
tick(now) {
|
|
135
|
+
this.cleanupTimedOutRequests(now);
|
|
136
|
+
this.cleanupRateLimitWindow(now);
|
|
137
|
+
}
|
|
138
|
+
reset() {
|
|
139
|
+
this.pendingRequests.length = 0;
|
|
140
|
+
this.recentRequestTimes.length = 0;
|
|
141
|
+
}
|
|
142
|
+
get pending() {
|
|
143
|
+
return this.pendingRequests.slice();
|
|
144
|
+
}
|
|
145
|
+
get history() {
|
|
146
|
+
return this.requestHistory.slice();
|
|
147
|
+
}
|
|
148
|
+
get pendingCount() {
|
|
149
|
+
return this.pendingRequests.length;
|
|
150
|
+
}
|
|
151
|
+
get hasPendingRequests() {
|
|
152
|
+
return this.pendingRequests.length > 0;
|
|
153
|
+
}
|
|
154
|
+
isStorming(now) {
|
|
155
|
+
this.cleanupRateLimitWindow(now);
|
|
156
|
+
return this.recentRequestTimes.length >= this.maxRequestsPerWindow;
|
|
157
|
+
}
|
|
158
|
+
cleanupRateLimitWindow(now) {
|
|
159
|
+
const cutoff = now.getTime() - this.rateLimitWindowMs;
|
|
160
|
+
while (this.recentRequestTimes.length > 0 && this.recentRequestTimes[0].getTime() < cutoff) {
|
|
161
|
+
this.recentRequestTimes.shift();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
cleanupTimedOutRequests(now) {
|
|
165
|
+
const cutoff = now.getTime() - this.requestTimeoutMs;
|
|
166
|
+
for (let i = this.pendingRequests.length - 1; i >= 0; i--) {
|
|
167
|
+
if (this.pendingRequests[i].sentAt.getTime() < cutoff) {
|
|
168
|
+
this.pendingRequests.splice(i, 1);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
removeFullySatisfied() {
|
|
173
|
+
for (let i = this.pendingRequests.length - 1; i >= 0; i--) {
|
|
174
|
+
if (this.pendingRequests[i].isFullySatisfied) {
|
|
175
|
+
this.pendingRequests.splice(i, 1);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
computeUncoveredRange(gapBegin, gapEnd) {
|
|
180
|
+
if (this.pendingRequests.length === 0) {
|
|
181
|
+
return { begin: gapBegin, end: gapEnd };
|
|
182
|
+
}
|
|
183
|
+
const covered = new Set();
|
|
184
|
+
for (const pending of this.pendingRequests) {
|
|
185
|
+
for (let seq = pending.begin; seq <= pending.end; seq++) {
|
|
186
|
+
covered.add(seq);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
let uncoveredBegin = null;
|
|
190
|
+
let uncoveredEnd = null;
|
|
191
|
+
for (let seq = gapBegin; seq <= gapEnd; seq++) {
|
|
192
|
+
if (!covered.has(seq)) {
|
|
193
|
+
if (uncoveredBegin === null)
|
|
194
|
+
uncoveredBegin = seq;
|
|
195
|
+
uncoveredEnd = seq;
|
|
196
|
+
}
|
|
197
|
+
else if (uncoveredBegin !== null) {
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (uncoveredBegin !== null && uncoveredEnd !== null) {
|
|
202
|
+
return { begin: uncoveredBegin, end: uncoveredEnd };
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
exports.ResendRequestManager = ResendRequestManager;
|
|
208
|
+
//# sourceMappingURL=resend-request-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resend-request-manager.js","sourceRoot":"","sources":["../../../src/transport/session/resend-request-manager.ts"],"names":[],"mappings":"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","sourcesContent":["export enum ResendActionType {\n Nothing = 'Nothing',\n SendResendRequest = 'SendResendRequest',\n SendGapFill = 'SendGapFill',\n Wait = 'Wait'\n}\n\nexport class ResendAction {\n private constructor (\n public readonly type: ResendActionType,\n public readonly begin: number | null,\n public readonly end: number | null,\n public readonly reason: string | null\n ) {}\n\n static sendResendRequest (begin: number, end: number): ResendAction {\n return new ResendAction(ResendActionType.SendResendRequest, begin, end, null)\n }\n\n static gapFill (begin: number, end: number, reason: string): ResendAction {\n return new ResendAction(ResendActionType.SendGapFill, begin, end, reason)\n }\n\n static wait (reason: string): ResendAction {\n return new ResendAction(ResendActionType.Wait, null, null, reason)\n }\n\n static nothing (): ResendAction {\n return new ResendAction(ResendActionType.Nothing, null, null, null)\n }\n\n toString (): string {\n switch (this.type) {\n case ResendActionType.SendResendRequest:\n return `SendResendRequest(${this.begin}-${this.end})`\n case ResendActionType.SendGapFill:\n return `GapFill(${this.begin}-${this.end}): ${this.reason}`\n case ResendActionType.Wait:\n return `Wait: ${this.reason}`\n case ResendActionType.Nothing:\n return 'Nothing'\n default:\n return `Unknown(${this.type})`\n }\n }\n}\n\nexport class PendingResendRange {\n private readonly receivedSeqs: Set<number> = new Set()\n\n constructor (\n public readonly begin: number,\n public readonly end: number,\n public readonly sentAt: Date\n ) {}\n\n markReceived (seqNum: number): void {\n if (seqNum >= this.begin && seqNum <= this.end) {\n this.receivedSeqs.add(seqNum)\n }\n }\n\n markRangeReceived (fromSeq: number, toSeq: number): void {\n const start = Math.max(fromSeq, this.begin)\n const stop = Math.min(toSeq, this.end)\n for (let seq = start; seq <= stop; seq++) {\n this.receivedSeqs.add(seq)\n }\n }\n\n get isFullySatisfied (): boolean {\n return this.receivedSeqs.size >= (this.end - this.begin + 1)\n }\n\n fullyCovers (begin: number, end: number): boolean {\n return begin >= this.begin && end <= this.end\n }\n\n overlaps (begin: number, end: number): boolean {\n return begin <= this.end && end >= this.begin\n }\n\n get pendingCount (): number {\n return (this.end - this.begin + 1) - this.receivedSeqs.size\n }\n\n toString (): string {\n return `Pending(${this.begin}-${this.end}, received=${this.receivedSeqs.size}/${this.end - this.begin + 1})`\n }\n}\n\nexport interface ResendRequestRecord {\n begin: number\n end: number\n sentAt: Date\n reason: string | null\n}\n\nexport interface ResendManagerConfig {\n maxPendingRequests?: number\n maxRequestsPerWindow?: number\n rateLimitWindowSeconds?: number\n requestTimeoutSeconds?: number\n}\n\n/**\n * Manages ResendRequest strategy with intelligent overlap handling and storm protection.\n *\n * Philosophy:\n * 1. Stay alive — don't let resend handling crash or overwhelm the session\n * 2. Keep receiving new messages — don't block normal message flow\n * 3. Don't make things worse — avoid storms, handle overlaps intelligently\n */\nexport class ResendRequestManager {\n private readonly pendingRequests: PendingResendRange[] = []\n private readonly requestHistory: ResendRequestRecord[] = []\n private readonly maxPendingRequests: number\n private readonly maxRequestsPerWindow: number\n private readonly rateLimitWindowMs: number\n private readonly requestTimeoutMs: number\n private readonly recentRequestTimes: Date[] = []\n\n constructor (config?: ResendManagerConfig) {\n this.maxPendingRequests = config?.maxPendingRequests ?? 1\n this.maxRequestsPerWindow = config?.maxRequestsPerWindow ?? 5\n this.rateLimitWindowMs = (config?.rateLimitWindowSeconds ?? 10) * 1000\n this.requestTimeoutMs = (config?.requestTimeoutSeconds ?? 30) * 1000\n }\n\n computeAction (expectedSeq: number, receivedSeq: number, now: Date): ResendAction {\n if (receivedSeq <= expectedSeq) {\n return ResendAction.nothing()\n }\n\n const gapBegin = expectedSeq\n const gapEnd = receivedSeq - 1\n\n this.cleanupTimedOutRequests(now)\n\n if (this.isStorming(now)) {\n return ResendAction.gapFill(gapBegin, gapEnd, 'storm protection - too many requests')\n }\n\n for (const pending of this.pendingRequests) {\n if (pending.fullyCovers(gapBegin, gapEnd)) {\n return ResendAction.wait(`fully covered by pending request ${pending}`)\n }\n }\n\n const uncovered = this.computeUncoveredRange(gapBegin, gapEnd)\n if (!uncovered) {\n return ResendAction.wait('gap partially covered, waiting for pending requests')\n }\n\n if (this.pendingRequests.length >= this.maxPendingRequests) {\n return ResendAction.wait(`max pending requests (${this.maxPendingRequests}) reached`)\n }\n\n return ResendAction.sendResendRequest(uncovered.begin, uncovered.end)\n }\n\n recordRequestSent (begin: number, end: number, now: Date, reason: string | null = null): void {\n this.pendingRequests.push(new PendingResendRange(begin, end, now))\n this.requestHistory.push({ begin, end, sentAt: now, reason })\n this.recentRequestTimes.push(now)\n }\n\n onMessageReceived (seqNum: number, possDupFlag: boolean, now: Date): void {\n for (const pending of this.pendingRequests) {\n pending.markReceived(seqNum)\n }\n this.removeFullySatisfied()\n }\n\n onGapFillReceived (gapFillSeq: number, newSeqNo: number, now: Date): void {\n for (const pending of this.pendingRequests) {\n pending.markRangeReceived(gapFillSeq, newSeqNo - 1)\n }\n this.removeFullySatisfied()\n }\n\n tick (now: Date): void {\n this.cleanupTimedOutRequests(now)\n this.cleanupRateLimitWindow(now)\n }\n\n reset (): void {\n this.pendingRequests.length = 0\n this.recentRequestTimes.length = 0\n // keep requestHistory for debugging\n }\n\n get pending (): ReadonlyArray<PendingResendRange> {\n return this.pendingRequests.slice()\n }\n\n get history (): ReadonlyArray<ResendRequestRecord> {\n return this.requestHistory.slice()\n }\n\n get pendingCount (): number {\n return this.pendingRequests.length\n }\n\n get hasPendingRequests (): boolean {\n return this.pendingRequests.length > 0\n }\n\n private isStorming (now: Date): boolean {\n this.cleanupRateLimitWindow(now)\n return this.recentRequestTimes.length >= this.maxRequestsPerWindow\n }\n\n private cleanupRateLimitWindow (now: Date): void {\n const cutoff = now.getTime() - this.rateLimitWindowMs\n while (this.recentRequestTimes.length > 0 && this.recentRequestTimes[0].getTime() < cutoff) {\n this.recentRequestTimes.shift()\n }\n }\n\n private cleanupTimedOutRequests (now: Date): void {\n const cutoff = now.getTime() - this.requestTimeoutMs\n for (let i = this.pendingRequests.length - 1; i >= 0; i--) {\n if (this.pendingRequests[i].sentAt.getTime() < cutoff) {\n this.pendingRequests.splice(i, 1)\n }\n }\n }\n\n private removeFullySatisfied (): void {\n for (let i = this.pendingRequests.length - 1; i >= 0; i--) {\n if (this.pendingRequests[i].isFullySatisfied) {\n this.pendingRequests.splice(i, 1)\n }\n }\n }\n\n private computeUncoveredRange (gapBegin: number, gapEnd: number): { begin: number, end: number } | null {\n if (this.pendingRequests.length === 0) {\n return { begin: gapBegin, end: gapEnd }\n }\n\n const covered: Set<number> = new Set()\n for (const pending of this.pendingRequests) {\n for (let seq = pending.begin; seq <= pending.end; seq++) {\n covered.add(seq)\n }\n }\n\n let uncoveredBegin: number | null = null\n let uncoveredEnd: number | null = null\n\n for (let seq = gapBegin; seq <= gapEnd; seq++) {\n if (!covered.has(seq)) {\n if (uncoveredBegin === null) uncoveredBegin = seq\n uncoveredEnd = seq\n } else if (uncoveredBegin !== null) {\n break\n }\n }\n\n if (uncoveredBegin !== null && uncoveredEnd !== null) {\n return { begin: uncoveredBegin, end: uncoveredEnd }\n }\n\n return null\n }\n}\n"]}
|
|
@@ -10,6 +10,6 @@ export interface ISessionMsgFactory {
|
|
|
10
10
|
resendRequest: (from: number, to: number) => ILooseObject;
|
|
11
11
|
sequenceReset: (newSeq: number, gapFill?: boolean) => ILooseObject;
|
|
12
12
|
heartbeat: (testReqId: string) => ILooseObject;
|
|
13
|
-
header: (msgType
|
|
13
|
+
header: (msgType: string, seqNum: number, time: Date, overrideData?: Partial<IStandardHeader>) => ILooseObject;
|
|
14
14
|
trailer: (checksum: number) => ILooseObject;
|
|
15
15
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-msg-factory.js","sourceRoot":"","sources":["../../../src/transport/session/session-msg-factory.ts"],"names":[],"mappings":"","sourcesContent":["import { ILooseObject } from '../../collections/collection'\nimport { IStandardHeader } from '../../types/FIX4.4/repo'\nimport { ISessionDescription } from './session-description'\n\nexport interface ISessionMsgFactory {\n description: ISessionDescription\n reject: (msgType: string, seqNo: number, msg: string, reason: number) => ILooseObject\n logout: (msgType: string, text: string) => ILooseObject\n logon: (userRequestId?: string, isResponse?: boolean) => ILooseObject\n testRequest: (reqId?: string) => ILooseObject\n resendRequest: (from: number, to: number) => ILooseObject\n sequenceReset: (newSeq: number, gapFill?: boolean) => ILooseObject\n heartbeat: (testReqId: string) => ILooseObject\n header: (msgType
|
|
1
|
+
{"version":3,"file":"session-msg-factory.js","sourceRoot":"","sources":["../../../src/transport/session/session-msg-factory.ts"],"names":[],"mappings":"","sourcesContent":["import { ILooseObject } from '../../collections/collection'\nimport { IStandardHeader } from '../../types/FIX4.4/repo'\nimport { ISessionDescription } from './session-description'\n\nexport interface ISessionMsgFactory {\n description: ISessionDescription\n reject: (msgType: string, seqNo: number, msg: string, reason: number) => ILooseObject\n logout: (msgType: string, text: string) => ILooseObject\n logon: (userRequestId?: string, isResponse?: boolean) => ILooseObject\n testRequest: (reqId?: string) => ILooseObject\n resendRequest: (from: number, to: number) => ILooseObject\n sequenceReset: (newSeq: number, gapFill?: boolean) => ILooseObject\n heartbeat: (testReqId: string) => ILooseObject\n header: (msgType: string, seqNum: number, time: Date, overrideData?: Partial<IStandardHeader>) => ILooseObject\n trailer: (checksum: number) => ILooseObject\n}\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ISessionSequenceStore } from './session-sequence-store';
|
|
2
|
+
import { IFixClock } from './fix-clock';
|
|
3
|
+
import { ResendAction, ResendManagerConfig, PendingResendRange } from './resend-request-manager';
|
|
4
|
+
export declare class SessionSequenceCoordinator {
|
|
5
|
+
private readonly store;
|
|
6
|
+
private readonly resendManager;
|
|
7
|
+
private readonly clock;
|
|
8
|
+
private nextSenderSeqNumValue;
|
|
9
|
+
private expectedTargetSeqNumValue;
|
|
10
|
+
private lastProcessedPeerSeqNumValue;
|
|
11
|
+
private logonRetryCountValue;
|
|
12
|
+
private timeoutRecoveryAttemptsValue;
|
|
13
|
+
onSessionReset: (() => Promise<void>) | null;
|
|
14
|
+
constructor(store: ISessionSequenceStore, clock: IFixClock, resendManagerConfig?: ResendManagerConfig);
|
|
15
|
+
initializeFromStore(): void;
|
|
16
|
+
initializeFromConfig(senderSeqNum?: number, targetSeqNum?: number): void;
|
|
17
|
+
get nextSenderSeqNum(): number;
|
|
18
|
+
get expectedTargetSeqNum(): number;
|
|
19
|
+
get lastProcessedPeerSeqNum(): number;
|
|
20
|
+
getNextSenderSeqNum(isPossDup?: boolean): number;
|
|
21
|
+
onMessageEncoded(seqNum: number, isPossDup: boolean): Promise<void>;
|
|
22
|
+
onMessageReceived(seqNum: number, possDupFlag: boolean): Promise<boolean>;
|
|
23
|
+
onGapFillReceived(gapFillSeq: number, newSeqNo: number): Promise<void>;
|
|
24
|
+
onGapDetected(expectedSeq: number, receivedSeq: number): ResendAction;
|
|
25
|
+
recordResendRequestSent(begin: number, end: number): void;
|
|
26
|
+
get pendingResendRequests(): ReadonlyArray<PendingResendRange>;
|
|
27
|
+
onLogonRejectedForSequence(maxRetries?: number): boolean;
|
|
28
|
+
resetLogonRetryCount(): void;
|
|
29
|
+
get logonRetryCount(): number;
|
|
30
|
+
incrementTimeoutRecovery(maxAttempts?: number): boolean;
|
|
31
|
+
resetTimeoutRecovery(): void;
|
|
32
|
+
get timeoutRecoveryAttempts(): number;
|
|
33
|
+
prepareForReconnect(): void;
|
|
34
|
+
resetSession(reason: string): Promise<void>;
|
|
35
|
+
handlePeerReset(peerSeqNum: number, weAlsoReset: boolean): Promise<void>;
|
|
36
|
+
resetAsAcceptor(): Promise<void>;
|
|
37
|
+
tick(): void;
|
|
38
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.SessionSequenceCoordinator = void 0;
|
|
13
|
+
const resend_request_manager_1 = require("./resend-request-manager");
|
|
14
|
+
class SessionSequenceCoordinator {
|
|
15
|
+
constructor(store, clock, resendManagerConfig) {
|
|
16
|
+
this.nextSenderSeqNumValue = 1;
|
|
17
|
+
this.expectedTargetSeqNumValue = 1;
|
|
18
|
+
this.lastProcessedPeerSeqNumValue = 0;
|
|
19
|
+
this.logonRetryCountValue = 0;
|
|
20
|
+
this.timeoutRecoveryAttemptsValue = 0;
|
|
21
|
+
this.onSessionReset = null;
|
|
22
|
+
this.store = store;
|
|
23
|
+
this.clock = clock;
|
|
24
|
+
this.resendManager = new resend_request_manager_1.ResendRequestManager(resendManagerConfig);
|
|
25
|
+
}
|
|
26
|
+
initializeFromStore() {
|
|
27
|
+
this.nextSenderSeqNumValue = this.store.senderSeqNum;
|
|
28
|
+
this.expectedTargetSeqNumValue = this.store.targetSeqNum;
|
|
29
|
+
this.lastProcessedPeerSeqNumValue = this.expectedTargetSeqNumValue - 1;
|
|
30
|
+
}
|
|
31
|
+
initializeFromConfig(senderSeqNum, targetSeqNum) {
|
|
32
|
+
if (senderSeqNum !== undefined) {
|
|
33
|
+
this.nextSenderSeqNumValue = senderSeqNum;
|
|
34
|
+
}
|
|
35
|
+
if (targetSeqNum !== undefined) {
|
|
36
|
+
this.expectedTargetSeqNumValue = targetSeqNum;
|
|
37
|
+
this.lastProcessedPeerSeqNumValue = targetSeqNum - 1;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
get nextSenderSeqNum() {
|
|
41
|
+
return this.nextSenderSeqNumValue;
|
|
42
|
+
}
|
|
43
|
+
get expectedTargetSeqNum() {
|
|
44
|
+
return this.expectedTargetSeqNumValue;
|
|
45
|
+
}
|
|
46
|
+
get lastProcessedPeerSeqNum() {
|
|
47
|
+
return this.lastProcessedPeerSeqNumValue;
|
|
48
|
+
}
|
|
49
|
+
getNextSenderSeqNum(isPossDup = false) {
|
|
50
|
+
const seq = this.nextSenderSeqNumValue;
|
|
51
|
+
if (!isPossDup) {
|
|
52
|
+
this.nextSenderSeqNumValue++;
|
|
53
|
+
}
|
|
54
|
+
return seq;
|
|
55
|
+
}
|
|
56
|
+
onMessageEncoded(seqNum, isPossDup) {
|
|
57
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
58
|
+
if (isPossDup)
|
|
59
|
+
return;
|
|
60
|
+
yield this.store.setSenderSeqNum(seqNum + 1);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
onMessageReceived(seqNum, possDupFlag) {
|
|
64
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
65
|
+
const now = this.clock.now();
|
|
66
|
+
this.resendManager.onMessageReceived(seqNum, possDupFlag, now);
|
|
67
|
+
if (possDupFlag) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
if (seqNum < this.expectedTargetSeqNumValue) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
if (seqNum === this.expectedTargetSeqNumValue) {
|
|
74
|
+
this.lastProcessedPeerSeqNumValue = seqNum;
|
|
75
|
+
this.expectedTargetSeqNumValue = seqNum + 1;
|
|
76
|
+
}
|
|
77
|
+
else if (seqNum > this.expectedTargetSeqNumValue) {
|
|
78
|
+
this.lastProcessedPeerSeqNumValue = seqNum;
|
|
79
|
+
this.expectedTargetSeqNumValue = seqNum + 1;
|
|
80
|
+
}
|
|
81
|
+
yield this.store.setTargetSeqNum(this.expectedTargetSeqNumValue);
|
|
82
|
+
return true;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
onGapFillReceived(gapFillSeq, newSeqNo) {
|
|
86
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
87
|
+
const now = this.clock.now();
|
|
88
|
+
this.resendManager.onGapFillReceived(gapFillSeq, newSeqNo, now);
|
|
89
|
+
if (newSeqNo > this.expectedTargetSeqNumValue) {
|
|
90
|
+
this.expectedTargetSeqNumValue = newSeqNo;
|
|
91
|
+
this.lastProcessedPeerSeqNumValue = Math.max(this.lastProcessedPeerSeqNumValue, newSeqNo - 1);
|
|
92
|
+
}
|
|
93
|
+
yield this.store.setTargetSeqNum(this.expectedTargetSeqNumValue);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
onGapDetected(expectedSeq, receivedSeq) {
|
|
97
|
+
const now = this.clock.now();
|
|
98
|
+
return this.resendManager.computeAction(expectedSeq, receivedSeq, now);
|
|
99
|
+
}
|
|
100
|
+
recordResendRequestSent(begin, end) {
|
|
101
|
+
const now = this.clock.now();
|
|
102
|
+
this.resendManager.recordRequestSent(begin, end, now);
|
|
103
|
+
}
|
|
104
|
+
get pendingResendRequests() {
|
|
105
|
+
return this.resendManager.pending;
|
|
106
|
+
}
|
|
107
|
+
onLogonRejectedForSequence(maxRetries = 10) {
|
|
108
|
+
this.logonRetryCountValue++;
|
|
109
|
+
if (this.logonRetryCountValue <= maxRetries) {
|
|
110
|
+
this.nextSenderSeqNumValue++;
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
resetLogonRetryCount() {
|
|
116
|
+
this.logonRetryCountValue = 0;
|
|
117
|
+
}
|
|
118
|
+
get logonRetryCount() {
|
|
119
|
+
return this.logonRetryCountValue;
|
|
120
|
+
}
|
|
121
|
+
incrementTimeoutRecovery(maxAttempts = 3) {
|
|
122
|
+
this.timeoutRecoveryAttemptsValue++;
|
|
123
|
+
return this.timeoutRecoveryAttemptsValue <= maxAttempts;
|
|
124
|
+
}
|
|
125
|
+
resetTimeoutRecovery() {
|
|
126
|
+
this.timeoutRecoveryAttemptsValue = 0;
|
|
127
|
+
}
|
|
128
|
+
get timeoutRecoveryAttempts() {
|
|
129
|
+
return this.timeoutRecoveryAttemptsValue;
|
|
130
|
+
}
|
|
131
|
+
prepareForReconnect() {
|
|
132
|
+
this.logonRetryCountValue = 0;
|
|
133
|
+
this.timeoutRecoveryAttemptsValue = 0;
|
|
134
|
+
this.resendManager.reset();
|
|
135
|
+
}
|
|
136
|
+
resetSession(reason) {
|
|
137
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
138
|
+
this.nextSenderSeqNumValue = 1;
|
|
139
|
+
this.expectedTargetSeqNumValue = 1;
|
|
140
|
+
this.lastProcessedPeerSeqNumValue = 0;
|
|
141
|
+
this.logonRetryCountValue = 0;
|
|
142
|
+
this.timeoutRecoveryAttemptsValue = 0;
|
|
143
|
+
this.resendManager.reset();
|
|
144
|
+
yield this.store.reset();
|
|
145
|
+
if (this.onSessionReset) {
|
|
146
|
+
yield this.onSessionReset();
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
handlePeerReset(peerSeqNum, weAlsoReset) {
|
|
151
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
152
|
+
const savedSenderSeq = weAlsoReset ? this.nextSenderSeqNumValue : null;
|
|
153
|
+
yield this.store.reset();
|
|
154
|
+
this.nextSenderSeqNumValue = savedSenderSeq !== null && savedSenderSeq !== void 0 ? savedSenderSeq : this.store.senderSeqNum;
|
|
155
|
+
this.expectedTargetSeqNumValue = peerSeqNum + 1;
|
|
156
|
+
this.lastProcessedPeerSeqNumValue = peerSeqNum;
|
|
157
|
+
this.resendManager.reset();
|
|
158
|
+
yield this.store.setTargetSeqNum(this.expectedTargetSeqNumValue);
|
|
159
|
+
if (this.onSessionReset) {
|
|
160
|
+
yield this.onSessionReset();
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
resetAsAcceptor() {
|
|
165
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
166
|
+
this.nextSenderSeqNumValue = 1;
|
|
167
|
+
this.expectedTargetSeqNumValue = 1;
|
|
168
|
+
this.lastProcessedPeerSeqNumValue = 0;
|
|
169
|
+
this.resendManager.reset();
|
|
170
|
+
yield this.store.reset();
|
|
171
|
+
yield this.store.setTargetSeqNum(1);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
tick() {
|
|
175
|
+
const now = this.clock.now();
|
|
176
|
+
this.resendManager.tick(now);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
exports.SessionSequenceCoordinator = SessionSequenceCoordinator;
|
|
180
|
+
//# sourceMappingURL=session-sequence-coordinator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-sequence-coordinator.js","sourceRoot":"","sources":["../../../src/transport/session/session-sequence-coordinator.ts"],"names":[],"mappings":"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","sourcesContent":["import { ISessionSequenceStore } from './session-sequence-store'\nimport { IFixClock } from './fix-clock'\nimport { ResendAction, ResendManagerConfig, ResendRequestManager, PendingResendRange } from './resend-request-manager'\n\n/**\n * Coordinates all sequence-related state for a FIX session.\n *\n * This is the SINGLE SOURCE OF TRUTH for:\n * - Outgoing sequence numbers (what we send)\n * - Expected incoming sequence numbers (what we expect from peer)\n * - Resend request tracking\n * - Reset coordination\n *\n * Philosophy:\n * 1. All sequence state lives here — no scattered state across components\n * 2. All reset operations are coordinated through here\n * 3. Components query this for sequence numbers, don't maintain their own\n * 4. Fully testable without running actual sessions\n */\nexport class SessionSequenceCoordinator {\n private readonly store: ISessionSequenceStore\n private readonly resendManager: ResendRequestManager\n private readonly clock: IFixClock\n\n // THE source of truth for sequence numbers\n private nextSenderSeqNumValue: number = 1\n private expectedTargetSeqNumValue: number = 1\n\n // Track the last peer sequence we actually processed\n private lastProcessedPeerSeqNumValue: number = 0\n\n // Transient session state (reset on reconnect)\n private logonRetryCountValue: number = 0\n private timeoutRecoveryAttemptsValue: number = 0\n\n // Event callback for full session reset\n public onSessionReset: (() => Promise<void>) | null = null\n\n constructor (\n store: ISessionSequenceStore,\n clock: IFixClock,\n resendManagerConfig?: ResendManagerConfig\n ) {\n this.store = store\n this.clock = clock\n this.resendManager = new ResendRequestManager(resendManagerConfig)\n }\n\n // ── Initialization ──\n\n initializeFromStore (): void {\n this.nextSenderSeqNumValue = this.store.senderSeqNum\n this.expectedTargetSeqNumValue = this.store.targetSeqNum\n this.lastProcessedPeerSeqNumValue = this.expectedTargetSeqNumValue - 1\n }\n\n initializeFromConfig (senderSeqNum?: number, targetSeqNum?: number): void {\n if (senderSeqNum !== undefined) {\n this.nextSenderSeqNumValue = senderSeqNum\n }\n if (targetSeqNum !== undefined) {\n this.expectedTargetSeqNumValue = targetSeqNum\n this.lastProcessedPeerSeqNumValue = targetSeqNum - 1\n }\n }\n\n // ── Sequence Access (Read) ──\n\n get nextSenderSeqNum (): number {\n return this.nextSenderSeqNumValue\n }\n\n get expectedTargetSeqNum (): number {\n return this.expectedTargetSeqNumValue\n }\n\n get lastProcessedPeerSeqNum (): number {\n return this.lastProcessedPeerSeqNumValue\n }\n\n // ── Sequence Mutations (Controlled) ──\n\n /**\n * Consumes and returns the next sender sequence number.\n * Call when encoding a message (not for PossDup resends).\n */\n getNextSenderSeqNum (isPossDup: boolean = false): number {\n const seq = this.nextSenderSeqNumValue\n if (!isPossDup) {\n this.nextSenderSeqNumValue++\n }\n return seq\n }\n\n /**\n * Records that a message was successfully encoded and will be sent.\n * Updates the store's sender sequence number.\n */\n async onMessageEncoded (seqNum: number, isPossDup: boolean): Promise<void> {\n if (isPossDup) return\n await this.store.setSenderSeqNum(seqNum + 1)\n }\n\n /**\n * Called when a message is received from the peer.\n * Updates expected sequence and resend tracking.\n * Returns true if message should be processed, false if duplicate/old.\n */\n async onMessageReceived (seqNum: number, possDupFlag: boolean): Promise<boolean> {\n const now = this.clock.now()\n\n this.resendManager.onMessageReceived(seqNum, possDupFlag, now)\n\n if (possDupFlag) {\n return true\n }\n\n if (seqNum < this.expectedTargetSeqNumValue) {\n return false\n }\n\n if (seqNum === this.expectedTargetSeqNumValue) {\n this.lastProcessedPeerSeqNumValue = seqNum\n this.expectedTargetSeqNumValue = seqNum + 1\n } else if (seqNum > this.expectedTargetSeqNumValue) {\n // Gap detected — session accepts this message and moves on\n this.lastProcessedPeerSeqNumValue = seqNum\n this.expectedTargetSeqNumValue = seqNum + 1\n }\n\n await this.store.setTargetSeqNum(this.expectedTargetSeqNumValue)\n return true\n }\n\n /**\n * Called when a SequenceReset-GapFill is received.\n */\n async onGapFillReceived (gapFillSeq: number, newSeqNo: number): Promise<void> {\n const now = this.clock.now()\n this.resendManager.onGapFillReceived(gapFillSeq, newSeqNo, now)\n\n // GapFill says \"skip from gapFillSeq to newSeqNo\" — never rewind\n if (newSeqNo > this.expectedTargetSeqNumValue) {\n this.expectedTargetSeqNumValue = newSeqNo\n this.lastProcessedPeerSeqNumValue = Math.max(this.lastProcessedPeerSeqNumValue, newSeqNo - 1)\n }\n\n await this.store.setTargetSeqNum(this.expectedTargetSeqNumValue)\n }\n\n // ── Gap Detection and Resend Requests ──\n\n onGapDetected (expectedSeq: number, receivedSeq: number): ResendAction {\n const now = this.clock.now()\n return this.resendManager.computeAction(expectedSeq, receivedSeq, now)\n }\n\n recordResendRequestSent (begin: number, end: number): void {\n const now = this.clock.now()\n this.resendManager.recordRequestSent(begin, end, now)\n }\n\n get pendingResendRequests (): ReadonlyArray<PendingResendRange> {\n return this.resendManager.pending\n }\n\n // ── Logon Retry Logic ──\n\n onLogonRejectedForSequence (maxRetries: number = 10): boolean {\n this.logonRetryCountValue++\n if (this.logonRetryCountValue <= maxRetries) {\n this.nextSenderSeqNumValue++\n return true\n }\n return false\n }\n\n resetLogonRetryCount (): void {\n this.logonRetryCountValue = 0\n }\n\n get logonRetryCount (): number {\n return this.logonRetryCountValue\n }\n\n // ── Timeout Recovery ──\n\n incrementTimeoutRecovery (maxAttempts: number = 3): boolean {\n this.timeoutRecoveryAttemptsValue++\n return this.timeoutRecoveryAttemptsValue <= maxAttempts\n }\n\n resetTimeoutRecovery (): void {\n this.timeoutRecoveryAttemptsValue = 0\n }\n\n get timeoutRecoveryAttempts (): number {\n return this.timeoutRecoveryAttemptsValue\n }\n\n // ── Reset Operations ──\n\n /**\n * Prepares for reconnection on the same session.\n * Resets transient state but preserves sequence numbers.\n */\n prepareForReconnect (): void {\n this.logonRetryCountValue = 0\n this.timeoutRecoveryAttemptsValue = 0\n this.resendManager.reset()\n }\n\n /**\n * Full session reset — clears store, resets sequences to 1.\n * Call when ResetSeqNumFlag=Y is being used.\n */\n async resetSession (reason: string): Promise<void> {\n this.nextSenderSeqNumValue = 1\n this.expectedTargetSeqNumValue = 1\n this.lastProcessedPeerSeqNumValue = 0\n this.logonRetryCountValue = 0\n this.timeoutRecoveryAttemptsValue = 0\n this.resendManager.reset()\n\n await this.store.reset()\n\n if (this.onSessionReset) {\n await this.onSessionReset()\n }\n }\n\n /**\n * Handles peer's ResetSeqNumFlag=Y in their Logon.\n */\n async handlePeerReset (peerSeqNum: number, weAlsoReset: boolean): Promise<void> {\n const savedSenderSeq = weAlsoReset ? this.nextSenderSeqNumValue : null\n\n await this.store.reset()\n\n this.nextSenderSeqNumValue = savedSenderSeq ?? this.store.senderSeqNum\n this.expectedTargetSeqNumValue = peerSeqNum + 1\n this.lastProcessedPeerSeqNumValue = peerSeqNum\n this.resendManager.reset()\n\n await this.store.setTargetSeqNum(this.expectedTargetSeqNumValue)\n\n if (this.onSessionReset) {\n await this.onSessionReset()\n }\n }\n\n /**\n * Handles acceptor responding with ResetSeqNumFlag=Y when peer didn't request it.\n */\n async resetAsAcceptor (): Promise<void> {\n this.nextSenderSeqNumValue = 1\n this.expectedTargetSeqNumValue = 1\n this.lastProcessedPeerSeqNumValue = 0\n this.resendManager.reset()\n\n await this.store.reset()\n await this.store.setTargetSeqNum(1)\n }\n\n /**\n * Periodic tick — cleans up stale resend requests.\n */\n tick (): void {\n const now = this.clock.now()\n this.resendManager.tick(now)\n }\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ISessionSequenceStore {
|
|
2
|
+
senderSeqNum: number;
|
|
3
|
+
targetSeqNum: number;
|
|
4
|
+
setSenderSeqNum(value: number): Promise<void>;
|
|
5
|
+
setTargetSeqNum(value: number): Promise<void>;
|
|
6
|
+
reset(): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export declare class MemorySequenceStore implements ISessionSequenceStore {
|
|
9
|
+
senderSeqNum: number;
|
|
10
|
+
targetSeqNum: number;
|
|
11
|
+
setSenderSeqNum(value: number): Promise<void>;
|
|
12
|
+
setTargetSeqNum(value: number): Promise<void>;
|
|
13
|
+
reset(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.MemorySequenceStore = void 0;
|
|
13
|
+
class MemorySequenceStore {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.senderSeqNum = 1;
|
|
16
|
+
this.targetSeqNum = 1;
|
|
17
|
+
}
|
|
18
|
+
setSenderSeqNum(value) {
|
|
19
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
20
|
+
this.senderSeqNum = value;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
setTargetSeqNum(value) {
|
|
24
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
25
|
+
this.targetSeqNum = value;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
reset() {
|
|
29
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
this.senderSeqNum = 1;
|
|
31
|
+
this.targetSeqNum = 1;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.MemorySequenceStore = MemorySequenceStore;
|
|
36
|
+
//# sourceMappingURL=session-sequence-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-sequence-store.js","sourceRoot":"","sources":["../../../src/transport/session/session-sequence-store.ts"],"names":[],"mappings":";;;;;;;;;;;;AAgBA,MAAa,mBAAmB;IAAhC;QACS,iBAAY,GAAW,CAAC,CAAA;QACxB,iBAAY,GAAW,CAAC,CAAA;IAcjC,CAAC;IAZO,eAAe,CAAE,KAAa;;YAClC,IAAI,CAAC,YAAY,GAAG,KAAK,CAAA;QAC3B,CAAC;KAAA;IAEK,eAAe,CAAE,KAAa;;YAClC,IAAI,CAAC,YAAY,GAAG,KAAK,CAAA;QAC3B,CAAC;KAAA;IAEK,KAAK;;YACT,IAAI,CAAC,YAAY,GAAG,CAAC,CAAA;YACrB,IAAI,CAAC,YAAY,GAAG,CAAC,CAAA;QACvB,CAAC;KAAA;CACF;AAhBD,kDAgBC","sourcesContent":["/**\n * Minimal store interface for the SessionSequenceCoordinator.\n * Both the existing FixMsgMemoryStore (adapted) and the future\n * IFixSessionStore will implement this.\n */\nexport interface ISessionSequenceStore {\n senderSeqNum: number\n targetSeqNum: number\n setSenderSeqNum (value: number): Promise<void>\n setTargetSeqNum (value: number): Promise<void>\n reset (): Promise<void>\n}\n\n/**\n * Simple in-memory implementation for testing and as default.\n */\nexport class MemorySequenceStore implements ISessionSequenceStore {\n public senderSeqNum: number = 1\n public targetSeqNum: number = 1\n\n async setSenderSeqNum (value: number): Promise<void> {\n this.senderSeqNum = value\n }\n\n async setTargetSeqNum (value: number): Promise<void> {\n this.targetSeqNum = value\n }\n\n async reset (): Promise<void> {\n this.senderSeqNum = 1\n this.targetSeqNum = 1\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tcp-acceptor.js","sourceRoot":"","sources":["../../../src/transport/tcp/tcp-acceptor.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,sCAAqC;AACrC,wCAAyC;AACzC,kDAA6C;AAE7C,6BAAqE;AACrE,6BAA4E;AAC5E,+DAAyD;AACzD,uCAA6C;AAC7C,uDAAkD;AAG3C,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,0BAAW;IAK1C,YAA4C,MAAoC;;QAC9E,KAAK,CAAC,MAAA,MAAM,CAAC,WAAW,CAAC,WAAW,mCAAI,IAAI,CAAC,CAAA;QADa,WAAM,GAAN,MAAM,CAAc;QAFxE,WAAM,GAAW,CAAC,CAAA;QAIxB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,MAAA,MAAM,CAAC,WAAW,CAAC,WAAW,0CAAE,IAAI,cAAc,CAAC,CAAA;QAC7F,MAAM,UAAU,GAAsB,IAAI,CAAC,UAAU,EAAE,CAAA;QACvD,IAAI,UAAU,EAAE,CAAC;YACf,IAAI,CAAC,SAAS,EAAE,CAAA;QAClB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,cAAc,EAAE,CAAA;QACvB,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;YACrC,MAAM,GAAG,CAAA;QACX,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,KAAK;QACH,IAAI,CAAC,MAAM,EAAE,CAAA;QACb,OAAO,IAAI,CAAC,MAAM,CAAA;IACpB,CAAC;IAED,SAAS;;QACP,IAAI,CAAC;YACH,MAAM,MAAM,GAAiB,IAAI,CAAC,MAAM,CAAA;YACxC,MAAM,GAAG,GAAG,MAAA,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,WAAW,0CAAE,GAAG,CAAA;YACpD,MAAM,UAAU,GAAsB,CAAA,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAE,GAAG,EAAC,CAAC,CAAC,uCAAiB,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;YAChG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAA;YACrC,IAAI,CAAC,UAAU;gBAAE,OAAM;YACvB,IAAI,CAAC,MAAM,GAAG,IAAA,kBAAe,EAAC,UAAU,EAAE,CAAC,SAAoB,EAAE,EAAE;;gBACjE,IAAI,MAAA,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAE,GAAG,0CAAE,WAAW,EAAE,CAAC;oBAC1B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAA;oBAC9C,SAAS,CAAC,WAAW,EAAE,CAAA;gBACzB,CAAC;gBACD,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC;oBACzB,SAAS,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;oBAC7B,MAAM,EAAE,GAAW,IAAI,CAAC,KAAK,EAAE,CAAA;oBAC/B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC,CAAA;oBACrE,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,CAAA;gBACtC,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAA;gBAC/E,CAAC;YACH,CAAC,CAAC,CAAA;QACJ,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"tcp-acceptor.js","sourceRoot":"","sources":["../../../src/transport/tcp/tcp-acceptor.ts"],"names":[],"mappings":"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","sourcesContent":["import { TcpDuplex } from '../duplex'\nimport { MsgTransport } from '../factory'\nimport { FixAcceptor } from '../fix-acceptor'\nimport { IJsFixConfig, IJsFixLogger } from '../../config'\nimport { createServer as netCreateServer, Server, Socket } from 'net'\nimport { createServer as tlsCreateServer, TlsOptions, TLSSocket } from 'tls'\nimport { TlsOptionsFactory } from './tls-options-factory'\nimport { inject, injectable } from 'tsyringe'\nimport { DITokens } from '../../runtime/di-tokens'\n\n@injectable()\nexport class TcpAcceptor extends FixAcceptor {\n private server: Server\n private readonly logger: IJsFixLogger\n private nextId: number = 0\n\n constructor (@inject(DITokens.IJsFixConfig) public readonly config: IJsFixConfig) {\n super(config.description.application ?? null)\n this.logger = config.logFactory.logger(`${config.description.application?.name}:TcpAcceptor`)\n const tlsOptions: TlsOptions | null = this.tlsOptions()\n if (tlsOptions) {\n this.tlsServer()\n } else {\n this.unsecureServer()\n }\n this.server.on('error', (err: Error) => {\n throw err\n })\n }\n\n getId (): number {\n this.nextId++\n return this.nextId\n }\n\n tlsServer (): void {\n try {\n const config: IJsFixConfig = this.config\n const tcp = this.config.description.application?.tcp\n const tlsOptions: TlsOptions | null = tcp?.tls ? TlsOptionsFactory.getTlsOptions(tcp.tls) : null\n this.logger.info('create tls server')\n if (!tlsOptions) return\n this.server = tlsCreateServer(tlsOptions, (tlsSocket: TLSSocket) => {\n if (tcp?.tls?.enableTrace) {\n this.logger.info('enabling tls session trace')\n tlsSocket.enableTrace()\n }\n if (tlsSocket.authorized) {\n tlsSocket.setEncoding('utf8')\n const id: number = this.getId()\n this.logger.info(`tls creates session ${id} ${tlsSocket.authorized}`)\n this.onSocket(id, tlsSocket, config)\n } else {\n this.logger.info('no transport created on tls with no authorized connection')\n }\n })\n } catch (e) {\n this.logger.error(e as Error)\n throw e\n }\n }\n\n unsecureServer (): void {\n try {\n const config = this.config\n this.logger.info('create unsecured server')\n this.server = netCreateServer((socket: Socket) => {\n const id: number = this.getId()\n this.logger.info(`net creates session ${id}`)\n socket.setNoDelay(true)\n this.onSocket(id, socket, config)\n })\n } catch (e) {\n this.logger.error(e as Error)\n throw e\n }\n }\n\n tlsOptions (): TlsOptions | null {\n const tcp = this.config.description.application?.tcp\n return tcp?.tls ? TlsOptionsFactory.getTlsOptions(tcp?.tls) : null\n }\n\n private onSocket (id: number, socket: Socket, config: IJsFixConfig): void {\n const transport: MsgTransport = new MsgTransport(id, config, new TcpDuplex(socket))\n this.saveTransport(id, transport)\n transport.receiver.on('end', () => {\n this.harvestTransport(id)\n })\n transport.receiver.on('error', (e: Error) => {\n this.logger.error(e)\n this.harvestTransport(id)\n })\n }\n\n public listen (): void {\n const port = this.config.description.application?.tcp?.port\n this.logger.info(`start to listen ${port}`)\n this.server.on('connection', () => {\n this.logger.info('insecure connection established')\n })\n this.server.on('secureConnection', (s) => {\n this.logger.info(`secure connection; client authorized: ${s.authorized}`)\n })\n this.server.listen(port)\n }\n\n public close (callback?: (err?: Error) => void): void {\n const port = this.config.description.application?.tcp?.port ?? -1\n this.logger.info(`close listener on port ${port}`)\n this.server.close(callback)\n }\n\n private saveTransport (tid: number, transport: MsgTransport): void {\n this.transports[tid] = transport\n const keys: string[] = Object.keys(this.transports)\n this.logger.info(`new transport id = ${tid} created total transports = ${keys.length}`)\n this.emit('transport', transport)\n }\n\n private harvestTransport (tid: number): void {\n delete this.transports[tid]\n const keys: string[] = Object.keys(this.transports)\n this.logger.info(`transport ${tid} ends total transports = ${keys.length}`)\n }\n}\n"]}
|