@hla4ts/session 0.1.0 → 0.1.1
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/README.md +377 -377
- package/package.json +3 -3
- package/src/errors.ts +98 -98
- package/src/index.ts +147 -147
- package/src/messages.ts +329 -329
- package/src/sequence-number.ts +200 -200
- package/src/session.ts +976 -976
- package/src/timeout-timer.ts +204 -204
- package/src/types.ts +235 -235
package/src/sequence-number.ts
CHANGED
|
@@ -1,200 +1,200 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sequence Number Management
|
|
3
|
-
*
|
|
4
|
-
* Handles sequence numbers with wrap-around behavior as defined in the
|
|
5
|
-
* IEEE 1516-2025 Federate Protocol specification.
|
|
6
|
-
*
|
|
7
|
-
* Sequence numbers are 32-bit unsigned integers that wrap around from
|
|
8
|
-
* MAX_VALUE (2^31 - 1) back to 0. The value 0x80000000 is reserved as
|
|
9
|
-
* "no sequence number" and is never used as a valid sequence number.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Invalid/no sequence number marker.
|
|
14
|
-
* Binary: 10000000 00000000 00000000 00000000
|
|
15
|
-
* This is stored as a 32-bit signed integer (-2147483648 = Integer.MIN_VALUE)
|
|
16
|
-
* but transmitted as unsigned (2147483648).
|
|
17
|
-
*/
|
|
18
|
-
export const NO_SEQUENCE_NUMBER = 0x80000000 >>> 0; // Force unsigned
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Initial sequence number for new sessions
|
|
22
|
-
*/
|
|
23
|
-
export const INITIAL_SEQUENCE_NUMBER = 0;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Maximum valid sequence number (2^31 - 1)
|
|
27
|
-
*/
|
|
28
|
-
export const MAX_SEQUENCE_NUMBER = 0x7fffffff;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Check if a value is a valid sequence number
|
|
32
|
-
*/
|
|
33
|
-
export function isValidSequenceNumber(value: number): boolean {
|
|
34
|
-
// Valid sequence numbers are 0 to MAX_SEQUENCE_NUMBER (0x7FFFFFFF)
|
|
35
|
-
// Invalid is 0x80000000 and above (as unsigned)
|
|
36
|
-
return value >= 0 && value <= MAX_SEQUENCE_NUMBER;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Get the next sequence number with wrap-around
|
|
41
|
-
*/
|
|
42
|
-
export function nextSequenceNumber(current: number): number {
|
|
43
|
-
const next = current + 1;
|
|
44
|
-
// Wrap around if we exceed max or hit the invalid marker
|
|
45
|
-
return next > MAX_SEQUENCE_NUMBER ? INITIAL_SEQUENCE_NUMBER : next;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Add two sequence numbers with wrap-around
|
|
50
|
-
*/
|
|
51
|
-
export function addSequenceNumbers(a: number, b: number): number {
|
|
52
|
-
const sum = a + b;
|
|
53
|
-
// Mask to keep in valid range (clear high bit)
|
|
54
|
-
return sum & MAX_SEQUENCE_NUMBER;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Check if a candidate sequence number is within an interval [oldest, newest]
|
|
59
|
-
* Handles wrap-around correctly.
|
|
60
|
-
*/
|
|
61
|
-
export function isInInterval(
|
|
62
|
-
candidate: number,
|
|
63
|
-
oldest: number,
|
|
64
|
-
newest: number
|
|
65
|
-
): boolean {
|
|
66
|
-
if (!isValidSequenceNumber(oldest) || !isValidSequenceNumber(candidate) || !isValidSequenceNumber(newest)) {
|
|
67
|
-
throw new Error("All sequence numbers must be valid");
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (oldest <= newest) {
|
|
71
|
-
// Normal case: no wrap-around in interval
|
|
72
|
-
return oldest <= candidate && candidate <= newest;
|
|
73
|
-
} else {
|
|
74
|
-
// Wrap-around case: interval spans from oldest to MAX, then 0 to newest
|
|
75
|
-
return candidate >= oldest || candidate <= newest;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Mutable sequence number class for tracking and incrementing
|
|
81
|
-
*/
|
|
82
|
-
export class SequenceNumber {
|
|
83
|
-
private _value: number;
|
|
84
|
-
|
|
85
|
-
constructor(initialValue: number = INITIAL_SEQUENCE_NUMBER) {
|
|
86
|
-
this._value = initialValue;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Get the current value
|
|
91
|
-
*/
|
|
92
|
-
get value(): number {
|
|
93
|
-
return this._value;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Set a new value
|
|
98
|
-
*/
|
|
99
|
-
set(newValue: number): void {
|
|
100
|
-
this._value = newValue;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Increment and return the new value
|
|
105
|
-
*/
|
|
106
|
-
increment(): number {
|
|
107
|
-
this._value = nextSequenceNumber(this._value);
|
|
108
|
-
return this._value;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Get current value and then increment
|
|
113
|
-
*/
|
|
114
|
-
getAndIncrement(): number {
|
|
115
|
-
const current = this._value;
|
|
116
|
-
this._value = nextSequenceNumber(this._value);
|
|
117
|
-
return current;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Check if the current value is valid
|
|
122
|
-
*/
|
|
123
|
-
isValid(): boolean {
|
|
124
|
-
return isValidSequenceNumber(this._value);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Create a copy
|
|
129
|
-
*/
|
|
130
|
-
clone(): SequenceNumber {
|
|
131
|
-
return new SequenceNumber(this._value);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
toString(): string {
|
|
135
|
-
return String(this._value);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Thread-safe (atomic-like) sequence number for concurrent access.
|
|
141
|
-
* In JavaScript/TypeScript this provides a clean interface for
|
|
142
|
-
* atomic get/set operations.
|
|
143
|
-
*/
|
|
144
|
-
export class AtomicSequenceNumber {
|
|
145
|
-
private _value: number;
|
|
146
|
-
|
|
147
|
-
constructor(initialValue: number = NO_SEQUENCE_NUMBER) {
|
|
148
|
-
this._value = initialValue;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Get the current value
|
|
153
|
-
*/
|
|
154
|
-
get(): number {
|
|
155
|
-
return this._value;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Set a new value
|
|
160
|
-
*/
|
|
161
|
-
set(newValue: number): void {
|
|
162
|
-
this._value = newValue;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Compare and set: only set if current value equals expected
|
|
167
|
-
* Returns true if the value was set
|
|
168
|
-
*/
|
|
169
|
-
compareAndSet(expected: number, newValue: number): boolean {
|
|
170
|
-
if (this._value === expected) {
|
|
171
|
-
this._value = newValue;
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Get current value and then set new value
|
|
179
|
-
*/
|
|
180
|
-
getAndSet(newValue: number): number {
|
|
181
|
-
const current = this._value;
|
|
182
|
-
this._value = newValue;
|
|
183
|
-
return current;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Increment and return the new value
|
|
188
|
-
*/
|
|
189
|
-
incrementAndGet(): number {
|
|
190
|
-
this._value = nextSequenceNumber(this._value);
|
|
191
|
-
return this._value;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Check if the current value is valid
|
|
196
|
-
*/
|
|
197
|
-
isValid(): boolean {
|
|
198
|
-
return isValidSequenceNumber(this._value);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Sequence Number Management
|
|
3
|
+
*
|
|
4
|
+
* Handles sequence numbers with wrap-around behavior as defined in the
|
|
5
|
+
* IEEE 1516-2025 Federate Protocol specification.
|
|
6
|
+
*
|
|
7
|
+
* Sequence numbers are 32-bit unsigned integers that wrap around from
|
|
8
|
+
* MAX_VALUE (2^31 - 1) back to 0. The value 0x80000000 is reserved as
|
|
9
|
+
* "no sequence number" and is never used as a valid sequence number.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Invalid/no sequence number marker.
|
|
14
|
+
* Binary: 10000000 00000000 00000000 00000000
|
|
15
|
+
* This is stored as a 32-bit signed integer (-2147483648 = Integer.MIN_VALUE)
|
|
16
|
+
* but transmitted as unsigned (2147483648).
|
|
17
|
+
*/
|
|
18
|
+
export const NO_SEQUENCE_NUMBER = 0x80000000 >>> 0; // Force unsigned
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Initial sequence number for new sessions
|
|
22
|
+
*/
|
|
23
|
+
export const INITIAL_SEQUENCE_NUMBER = 0;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Maximum valid sequence number (2^31 - 1)
|
|
27
|
+
*/
|
|
28
|
+
export const MAX_SEQUENCE_NUMBER = 0x7fffffff;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if a value is a valid sequence number
|
|
32
|
+
*/
|
|
33
|
+
export function isValidSequenceNumber(value: number): boolean {
|
|
34
|
+
// Valid sequence numbers are 0 to MAX_SEQUENCE_NUMBER (0x7FFFFFFF)
|
|
35
|
+
// Invalid is 0x80000000 and above (as unsigned)
|
|
36
|
+
return value >= 0 && value <= MAX_SEQUENCE_NUMBER;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get the next sequence number with wrap-around
|
|
41
|
+
*/
|
|
42
|
+
export function nextSequenceNumber(current: number): number {
|
|
43
|
+
const next = current + 1;
|
|
44
|
+
// Wrap around if we exceed max or hit the invalid marker
|
|
45
|
+
return next > MAX_SEQUENCE_NUMBER ? INITIAL_SEQUENCE_NUMBER : next;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Add two sequence numbers with wrap-around
|
|
50
|
+
*/
|
|
51
|
+
export function addSequenceNumbers(a: number, b: number): number {
|
|
52
|
+
const sum = a + b;
|
|
53
|
+
// Mask to keep in valid range (clear high bit)
|
|
54
|
+
return sum & MAX_SEQUENCE_NUMBER;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Check if a candidate sequence number is within an interval [oldest, newest]
|
|
59
|
+
* Handles wrap-around correctly.
|
|
60
|
+
*/
|
|
61
|
+
export function isInInterval(
|
|
62
|
+
candidate: number,
|
|
63
|
+
oldest: number,
|
|
64
|
+
newest: number
|
|
65
|
+
): boolean {
|
|
66
|
+
if (!isValidSequenceNumber(oldest) || !isValidSequenceNumber(candidate) || !isValidSequenceNumber(newest)) {
|
|
67
|
+
throw new Error("All sequence numbers must be valid");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (oldest <= newest) {
|
|
71
|
+
// Normal case: no wrap-around in interval
|
|
72
|
+
return oldest <= candidate && candidate <= newest;
|
|
73
|
+
} else {
|
|
74
|
+
// Wrap-around case: interval spans from oldest to MAX, then 0 to newest
|
|
75
|
+
return candidate >= oldest || candidate <= newest;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Mutable sequence number class for tracking and incrementing
|
|
81
|
+
*/
|
|
82
|
+
export class SequenceNumber {
|
|
83
|
+
private _value: number;
|
|
84
|
+
|
|
85
|
+
constructor(initialValue: number = INITIAL_SEQUENCE_NUMBER) {
|
|
86
|
+
this._value = initialValue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get the current value
|
|
91
|
+
*/
|
|
92
|
+
get value(): number {
|
|
93
|
+
return this._value;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Set a new value
|
|
98
|
+
*/
|
|
99
|
+
set(newValue: number): void {
|
|
100
|
+
this._value = newValue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Increment and return the new value
|
|
105
|
+
*/
|
|
106
|
+
increment(): number {
|
|
107
|
+
this._value = nextSequenceNumber(this._value);
|
|
108
|
+
return this._value;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get current value and then increment
|
|
113
|
+
*/
|
|
114
|
+
getAndIncrement(): number {
|
|
115
|
+
const current = this._value;
|
|
116
|
+
this._value = nextSequenceNumber(this._value);
|
|
117
|
+
return current;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Check if the current value is valid
|
|
122
|
+
*/
|
|
123
|
+
isValid(): boolean {
|
|
124
|
+
return isValidSequenceNumber(this._value);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Create a copy
|
|
129
|
+
*/
|
|
130
|
+
clone(): SequenceNumber {
|
|
131
|
+
return new SequenceNumber(this._value);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
toString(): string {
|
|
135
|
+
return String(this._value);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Thread-safe (atomic-like) sequence number for concurrent access.
|
|
141
|
+
* In JavaScript/TypeScript this provides a clean interface for
|
|
142
|
+
* atomic get/set operations.
|
|
143
|
+
*/
|
|
144
|
+
export class AtomicSequenceNumber {
|
|
145
|
+
private _value: number;
|
|
146
|
+
|
|
147
|
+
constructor(initialValue: number = NO_SEQUENCE_NUMBER) {
|
|
148
|
+
this._value = initialValue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get the current value
|
|
153
|
+
*/
|
|
154
|
+
get(): number {
|
|
155
|
+
return this._value;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Set a new value
|
|
160
|
+
*/
|
|
161
|
+
set(newValue: number): void {
|
|
162
|
+
this._value = newValue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Compare and set: only set if current value equals expected
|
|
167
|
+
* Returns true if the value was set
|
|
168
|
+
*/
|
|
169
|
+
compareAndSet(expected: number, newValue: number): boolean {
|
|
170
|
+
if (this._value === expected) {
|
|
171
|
+
this._value = newValue;
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get current value and then set new value
|
|
179
|
+
*/
|
|
180
|
+
getAndSet(newValue: number): number {
|
|
181
|
+
const current = this._value;
|
|
182
|
+
this._value = newValue;
|
|
183
|
+
return current;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Increment and return the new value
|
|
188
|
+
*/
|
|
189
|
+
incrementAndGet(): number {
|
|
190
|
+
this._value = nextSequenceNumber(this._value);
|
|
191
|
+
return this._value;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Check if the current value is valid
|
|
196
|
+
*/
|
|
197
|
+
isValid(): boolean {
|
|
198
|
+
return isValidSequenceNumber(this._value);
|
|
199
|
+
}
|
|
200
|
+
}
|