@hla4ts/session 0.1.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/README.md ADDED
@@ -0,0 +1,377 @@
1
+ # @hla4ts/session
2
+
3
+ **HLA 4 Federate Protocol Session Layer**
4
+
5
+ This package provides session management for the HLA 4 Federate Protocol, handling connection establishment, state machine, heartbeats, request/response correlation, and session resumption.
6
+
7
+ ## Overview
8
+
9
+ The session layer sits between the transport layer and the HLA API layer, providing:
10
+
11
+ - **Session lifecycle management** - NEW → STARTING → RUNNING ⇄ DROPPED → TERMINATED
12
+ - **Request/response correlation** - Matches HLA call responses to requests
13
+ - **Heartbeat support** - Keep-alive mechanism for connection health
14
+ - **Session resumption** - Reconnect and resume after network drops
15
+ - **Timeout management** - Connection and response timeouts
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ bun add @hla4ts/session
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```typescript
26
+ import { Session, SessionState } from '@hla4ts/session';
27
+ import { TlsTransport } from '@hla4ts/transport';
28
+
29
+ // Create transport and session
30
+ const transport = new TlsTransport({
31
+ host: 'rti.example.com',
32
+ port: 15165,
33
+ });
34
+
35
+ const session = new Session(transport, {
36
+ connectionTimeout: 30000,
37
+ responseTimeout: 180000,
38
+ });
39
+
40
+ // Listen for state changes
41
+ session.addStateListener({
42
+ onStateTransition: (oldState, newState, reason) => {
43
+ console.log(`Session: ${oldState} -> ${newState} (${reason})`);
44
+
45
+ // Handle connection drops
46
+ if (newState === SessionState.DROPPED) {
47
+ session.resume().catch((err) => {
48
+ console.error('Resume failed:', err);
49
+ session.terminate();
50
+ });
51
+ }
52
+ }
53
+ });
54
+
55
+ // Start the session
56
+ await session.start({
57
+ onHlaCallbackRequest: (seqNum, callbackData) => {
58
+ // Process the callback (decode protobuf, handle it, encode response)
59
+ const response = processCallback(callbackData);
60
+ session.sendHlaCallbackResponse(seqNum, response);
61
+ }
62
+ });
63
+
64
+ // Send HLA calls
65
+ const response = await session.sendHlaCallRequest(encodedCall);
66
+
67
+ // When done
68
+ await session.terminate();
69
+ ```
70
+
71
+ ## Sequence Diagram
72
+
73
+ ```mermaid
74
+ sequenceDiagram
75
+ participant App as Federate Code
76
+ participant Session
77
+ participant Transport
78
+ participant RTI
79
+ App->>Session: start(callbackListener)
80
+ Session->>Transport: CTRL_NEW_SESSION
81
+ Transport-->>RTI: framed message
82
+ RTI-->>Transport: CTRL_NEW_SESSION_STATUS
83
+ Transport-->>Session: onMessage
84
+ App->>Session: sendHlaCallRequest(bytes)
85
+ Session->>Transport: HLA_CALL_REQUEST
86
+ RTI-->>Transport: HLA_CALL_RESPONSE
87
+ Transport-->>Session: onMessage
88
+ ```
89
+
90
+ ## Session States
91
+
92
+ The session follows a well-defined state machine:
93
+
94
+ ```
95
+ ┌───────┐
96
+ │ NEW │────────────────────┐
97
+ └───┬───┘ │
98
+ │ start() │
99
+ ▼ │
100
+ ┌──────────┐ │
101
+ │ STARTING │─────────────────┤
102
+ └────┬─────┘ │
103
+ │ success │ failure
104
+ ▼ │
105
+ ┌─────────┐ │
106
+ │ RUNNING │◄─────────────┐ │
107
+ └────┬────┘ │ │
108
+ │ │ │
109
+ │ connection lost │ resume()
110
+ ▼ │ │
111
+ ┌─────────┐ ┌────┴────┐
112
+ │ DROPPED │────────►│ RESUMING│
113
+ └────┬────┘ └────┬────┘
114
+ │ │
115
+ │ terminate() │ failure
116
+ ▼ │
117
+ ┌─────────────┐ │
118
+ │ TERMINATING │ │
119
+ └──────┬──────┘ │
120
+ │ │
121
+ ▼ ▼
122
+ ┌────────────┐◄──────────┘
123
+ │ TERMINATED │
124
+ └────────────┘
125
+ ```
126
+
127
+ ### State Descriptions
128
+
129
+ | State | Description |
130
+ |-------|-------------|
131
+ | `NEW` | Session created but not started |
132
+ | `STARTING` | Connecting to RTI, establishing session |
133
+ | `RUNNING` | Session active, can send/receive HLA messages |
134
+ | `DROPPED` | Connection lost, can attempt resume |
135
+ | `RESUMING` | Attempting to resume dropped session |
136
+ | `TERMINATING` | Graceful shutdown in progress |
137
+ | `TERMINATED` | Session ended, cannot be reused |
138
+
139
+ ## API Reference
140
+
141
+ ### Session Class
142
+
143
+ #### Constructor
144
+
145
+ ```typescript
146
+ new Session(transport: Transport, options?: SessionOptions)
147
+ ```
148
+
149
+ **Options:**
150
+
151
+ | Option | Type | Default | Description |
152
+ |--------|------|---------|-------------|
153
+ | `connectionTimeout` | `number` | `30000` | Timeout for initial connection (ms) |
154
+ | `responseTimeout` | `number` | `180000` | Timeout for server responses (ms) |
155
+ | `maxRetryAttempts` | `number` | `3` | Max connection retry attempts |
156
+ | `messageQueueSize` | `number` | `1000` | Size of outgoing message queue |
157
+ | `rateLimitEnabled` | `boolean` | `false` | Enable rate limiting |
158
+ | `heartbeatInterval` | `number` | `10000` | Heartbeat check interval (ms) |
159
+
160
+ #### Properties
161
+
162
+ ```typescript
163
+ session.id: bigint // Session ID (0 if not established)
164
+ session.state: SessionState // Current state
165
+ session.isOperational: boolean // True if can send/receive messages
166
+ ```
167
+
168
+ #### Methods
169
+
170
+ ##### `start(callbackListener)`
171
+
172
+ Start the session and connect to the RTI.
173
+
174
+ ```typescript
175
+ await session.start({
176
+ onHlaCallbackRequest: (seqNum: number, callback: Uint8Array) => {
177
+ // Handle callback
178
+ }
179
+ });
180
+ ```
181
+
182
+ ##### `resume()`
183
+
184
+ Resume a dropped session.
185
+
186
+ ```typescript
187
+ const success = await session.resume();
188
+ ```
189
+
190
+ ##### `sendHeartbeat()`
191
+
192
+ Send a heartbeat message (useful for keeping connection alive).
193
+
194
+ ```typescript
195
+ await session.sendHeartbeat();
196
+ ```
197
+
198
+ ##### `sendHlaCallRequest(encodedHlaCall)`
199
+
200
+ Send an HLA call and wait for the response.
201
+
202
+ ```typescript
203
+ const response = await session.sendHlaCallRequest(encodedCall);
204
+ ```
205
+
206
+ ##### `sendHlaCallbackResponse(responseToSequenceNumber, encodedResponse)`
207
+
208
+ Send a response to an HLA callback.
209
+
210
+ ```typescript
211
+ await session.sendHlaCallbackResponse(seqNum, encodedResponse);
212
+ ```
213
+
214
+ ##### `terminate(timeoutMs?)`
215
+
216
+ Gracefully terminate the session.
217
+
218
+ ```typescript
219
+ await session.terminate();
220
+ ```
221
+
222
+ ##### `addStateListener(listener)`
223
+
224
+ Register a state change listener.
225
+
226
+ ```typescript
227
+ session.addStateListener({
228
+ onStateTransition: (oldState, newState, reason) => {
229
+ console.log(`${oldState} -> ${newState}: ${reason}`);
230
+ }
231
+ });
232
+ ```
233
+
234
+ ##### `setMessageSentListener(listener)`
235
+
236
+ Set a listener for outgoing messages (useful for heartbeat timing).
237
+
238
+ ```typescript
239
+ session.setMessageSentListener({
240
+ onMessageSent: () => {
241
+ // Reset heartbeat timer
242
+ }
243
+ });
244
+ ```
245
+
246
+ ## Error Handling
247
+
248
+ The session layer throws specific errors:
249
+
250
+ ```typescript
251
+ import {
252
+ SessionLostError,
253
+ SessionAlreadyTerminatedError,
254
+ SessionIllegalStateError,
255
+ BadMessageError,
256
+ ConnectionTimeoutError,
257
+ ResponseTimeoutError,
258
+ } from '@hla4ts/session';
259
+
260
+ try {
261
+ await session.start(listener);
262
+ } catch (err) {
263
+ if (err instanceof ConnectionTimeoutError) {
264
+ console.error('Could not connect to RTI');
265
+ } else if (err instanceof SessionLostError) {
266
+ console.error('Session lost:', err.message);
267
+ }
268
+ }
269
+ ```
270
+
271
+ ## Sequence Numbers
272
+
273
+ The package exports utilities for working with Federate Protocol sequence numbers:
274
+
275
+ ```typescript
276
+ import {
277
+ SequenceNumber,
278
+ AtomicSequenceNumber,
279
+ isValidSequenceNumber,
280
+ nextSequenceNumber,
281
+ NO_SEQUENCE_NUMBER,
282
+ INITIAL_SEQUENCE_NUMBER,
283
+ MAX_SEQUENCE_NUMBER,
284
+ } from '@hla4ts/session';
285
+
286
+ // Mutable sequence number
287
+ const seq = new SequenceNumber(0);
288
+ seq.increment(); // Returns 1
289
+ seq.getAndIncrement(); // Returns 1, then increments to 2
290
+
291
+ // For concurrent access
292
+ const atomic = new AtomicSequenceNumber(0);
293
+ atomic.compareAndSet(0, 1); // Returns true if successful
294
+
295
+ // Utilities
296
+ isValidSequenceNumber(100); // true
297
+ isValidSequenceNumber(-1); // false
298
+ nextSequenceNumber(MAX_SEQUENCE_NUMBER); // Returns 0 (wrap-around)
299
+ ```
300
+
301
+ ## Timers
302
+
303
+ The package includes timer utilities for session management:
304
+
305
+ ```typescript
306
+ import { TimeoutTimer, OneShotTimer } from '@hla4ts/session';
307
+
308
+ // Periodic timeout timer
309
+ const timer = TimeoutTimer.createLazy(30000); // 30 second timeout
310
+ timer.start(() => {
311
+ console.log('Timeout!');
312
+ });
313
+ timer.extend(); // Reset the timeout
314
+ timer.pause(); // Stop checking
315
+ timer.resume(); // Resume checking
316
+ timer.cancel(); // Permanently cancel
317
+
318
+ // One-shot timer
319
+ const oneShot = new OneShotTimer();
320
+ oneShot.schedule(() => {
321
+ console.log('Fired!');
322
+ }, 5000);
323
+ oneShot.clear(); // Cancel before it fires
324
+ oneShot.cancel(); // Permanently cancel
325
+ ```
326
+
327
+ ## Message Encoding (Advanced)
328
+
329
+ For advanced use cases, you can encode/decode session control messages directly:
330
+
331
+ ```typescript
332
+ import {
333
+ createNewSessionMessage,
334
+ createHeartbeatMessage,
335
+ createHlaCallRequestMessage,
336
+ decodeHlaCallResponse,
337
+ decodeHlaCallbackRequest,
338
+ } from '@hla4ts/session';
339
+
340
+ // Create a new session message
341
+ const newSessionMsg = createNewSessionMessage();
342
+
343
+ // Create an HLA call request
344
+ const callMsg = createHlaCallRequestMessage(
345
+ sequenceNumber,
346
+ sessionId,
347
+ lastReceivedSequenceNumber,
348
+ protobufPayload
349
+ );
350
+
351
+ // Decode an HLA call response
352
+ const response = decodeHlaCallResponse(payload);
353
+ console.log(response.responseToSequenceNumber);
354
+ console.log(response.hlaServiceReturnValueOrException);
355
+ ```
356
+
357
+ ## Testing
358
+
359
+ ```bash
360
+ cd packages/session
361
+ bun test
362
+ ```
363
+
364
+ ## Related Packages
365
+
366
+ - [`@hla4ts/transport`](../transport) - Transport layer (TCP/TLS)
367
+ - [`@hla4ts/proto`](../proto) - Protocol buffer types
368
+ - [`@hla4ts/hla-api`](../hla-api) - High-level HLA API facade
369
+
370
+ ## References
371
+
372
+ - [IEEE 1516-2025](https://standards.ieee.org/standard/1516-2025.html) - HLA 4 Standard
373
+ - [Pitch FedProClient](https://github.com/Pitch-Technologies/FedProClient) - Reference implementation
374
+
375
+ ## License
376
+
377
+ MIT
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@hla4ts/session",
3
+ "version": "0.1.0",
4
+ "description": "HLA 4 Federate Protocol Session Layer - State machine, heartbeats, reconnection",
5
+ "type": "module",
6
+ "main": "src/index.ts",
7
+ "types": "src/index.ts",
8
+ "files": [
9
+ "README.md",
10
+ "src"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "import": "./src/index.ts",
15
+ "types": "./src/index.ts"
16
+ }
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "scripts": {
22
+ "typecheck": "tsc --noEmit",
23
+ "test": "bun test",
24
+ "clean": "rm -rf dist"
25
+ },
26
+ "dependencies": {
27
+ "@hla4ts/transport": "^0.1.0",
28
+ "@hla4ts/proto": "^0.1.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/bun": "latest",
32
+ "typescript": "^5.3.3"
33
+ },
34
+ "peerDependencies": {
35
+ "typescript": "^5.0.0"
36
+ },
37
+ "keywords": [
38
+ "hla",
39
+ "hla4",
40
+ "ieee-1516",
41
+ "federate-protocol",
42
+ "session",
43
+ "simulation"
44
+ ],
45
+ "license": "MIT"
46
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Session Exceptions
3
+ *
4
+ * Custom error types for session-related failures.
5
+ */
6
+
7
+ import { SessionState } from "./types.ts";
8
+
9
+ /**
10
+ * Base class for session-related errors
11
+ */
12
+ export class SessionError extends Error {
13
+ constructor(message: string) {
14
+ super(message);
15
+ this.name = "SessionError";
16
+ // Maintains proper stack trace for where the error was thrown
17
+ if (Error.captureStackTrace) {
18
+ Error.captureStackTrace(this, this.constructor);
19
+ }
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Thrown when the session is lost unexpectedly.
25
+ * This can happen due to:
26
+ * - Network disconnection
27
+ * - Server timeout
28
+ * - Protocol errors
29
+ */
30
+ export class SessionLostError extends SessionError {
31
+ constructor(message: string, public readonly cause?: Error) {
32
+ super(message);
33
+ this.name = "SessionLostError";
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Thrown when an operation is attempted on a session that is already terminated.
39
+ */
40
+ export class SessionAlreadyTerminatedError extends SessionError {
41
+ constructor(message: string = "Session has already terminated") {
42
+ super(message);
43
+ this.name = "SessionAlreadyTerminatedError";
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Thrown when an operation is attempted in an invalid session state.
49
+ */
50
+ export class SessionIllegalStateError extends SessionError {
51
+ constructor(
52
+ public readonly currentState: SessionState,
53
+ public readonly operation: string
54
+ ) {
55
+ super(`Cannot ${operation} in state ${currentState}`);
56
+ this.name = "SessionIllegalStateError";
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Thrown when a bad message is received from the server.
62
+ */
63
+ export class BadMessageError extends SessionError {
64
+ constructor(message: string) {
65
+ super(message);
66
+ this.name = "BadMessageError";
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Thrown when the message queue is full and cannot accept more messages.
72
+ */
73
+ export class MessageQueueFullError extends SessionError {
74
+ constructor() {
75
+ super("Message queue is full");
76
+ this.name = "MessageQueueFullError";
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Thrown when a connection attempt times out.
82
+ */
83
+ export class ConnectionTimeoutError extends SessionError {
84
+ constructor(timeoutMs: number) {
85
+ super(`Connection timed out after ${timeoutMs}ms`);
86
+ this.name = "ConnectionTimeoutError";
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Thrown when a response is not received within the expected timeout.
92
+ */
93
+ export class ResponseTimeoutError extends SessionError {
94
+ constructor(timeoutMs: number) {
95
+ super(`Response timed out after ${timeoutMs}ms`);
96
+ this.name = "ResponseTimeoutError";
97
+ }
98
+ }
package/src/index.ts ADDED
@@ -0,0 +1,147 @@
1
+ /**
2
+ * @hla4ts/session - HLA 4 Federate Protocol Session Layer
3
+ *
4
+ * This package provides session management for the HLA 4 Federate Protocol,
5
+ * including connection establishment, state machine, heartbeats, and reconnection.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { Session, SessionState } from '@hla4ts/session';
10
+ * import { TlsTransport } from '@hla4ts/transport';
11
+ *
12
+ * // Create transport and session
13
+ * const transport = new TlsTransport({
14
+ * host: 'rti.example.com',
15
+ * port: 15165,
16
+ * });
17
+ *
18
+ * const session = new Session(transport, {
19
+ * connectionTimeout: 30000,
20
+ * responseTimeout: 180000,
21
+ * });
22
+ *
23
+ * // Listen for state changes
24
+ * session.addStateListener({
25
+ * onStateTransition: (oldState, newState, reason) => {
26
+ * console.log(`Session: ${oldState} -> ${newState} (${reason})`);
27
+ * if (newState === SessionState.DROPPED) {
28
+ * // Attempt to resume
29
+ * session.resume().catch(console.error);
30
+ * }
31
+ * }
32
+ * });
33
+ *
34
+ * // Start the session
35
+ * await session.start({
36
+ * onHlaCallbackRequest: (seqNum, callbackData) => {
37
+ * // Handle RTI callback
38
+ * const response = processCallback(callbackData);
39
+ * session.sendHlaCallbackResponse(seqNum, response);
40
+ * }
41
+ * });
42
+ *
43
+ * // Send HLA calls
44
+ * const response = await session.sendHlaCallRequest(encodedCall);
45
+ *
46
+ * // Clean up
47
+ * await session.terminate();
48
+ * ```
49
+ *
50
+ * @packageDocumentation
51
+ */
52
+
53
+ // =============================================================================
54
+ // Session
55
+ // =============================================================================
56
+ export { Session } from "./session.ts";
57
+
58
+ // =============================================================================
59
+ // Types
60
+ // =============================================================================
61
+ export {
62
+ // State machine
63
+ SessionState,
64
+ VALID_TRANSITIONS,
65
+ isValidTransition,
66
+ isOperationalState,
67
+
68
+ // Options
69
+ type SessionOptions,
70
+ DEFAULT_SESSION_OPTIONS,
71
+
72
+ // Callbacks
73
+ type HlaCallbackRequestListener,
74
+ type SessionStateListener,
75
+ type MessageSentListener,
76
+
77
+ // Stats
78
+ type SessionStats,
79
+ } from "./types.ts";
80
+
81
+ // =============================================================================
82
+ // Errors
83
+ // =============================================================================
84
+ export {
85
+ SessionError,
86
+ SessionLostError,
87
+ SessionAlreadyTerminatedError,
88
+ SessionIllegalStateError,
89
+ BadMessageError,
90
+ MessageQueueFullError,
91
+ ConnectionTimeoutError,
92
+ ResponseTimeoutError,
93
+ } from "./errors.ts";
94
+
95
+ // =============================================================================
96
+ // Sequence Numbers
97
+ // =============================================================================
98
+ export {
99
+ // Constants
100
+ NO_SEQUENCE_NUMBER,
101
+ INITIAL_SEQUENCE_NUMBER,
102
+ MAX_SEQUENCE_NUMBER,
103
+
104
+ // Functions
105
+ isValidSequenceNumber,
106
+ nextSequenceNumber,
107
+ addSequenceNumbers,
108
+ isInInterval,
109
+
110
+ // Classes
111
+ SequenceNumber,
112
+ AtomicSequenceNumber,
113
+ } from "./sequence-number.ts";
114
+
115
+ // =============================================================================
116
+ // Timers
117
+ // =============================================================================
118
+ export { TimeoutTimer, OneShotTimer } from "./timeout-timer.ts";
119
+
120
+ // =============================================================================
121
+ // Messages (for advanced use)
122
+ // =============================================================================
123
+ export {
124
+ // Session control messages
125
+ createNewSessionMessage,
126
+ decodeNewSessionStatus,
127
+ createResumeRequestMessage,
128
+ decodeResumeStatus,
129
+ ResumeStatusCode,
130
+ createHeartbeatMessage,
131
+ decodeHeartbeatResponse,
132
+ createTerminateSessionMessage,
133
+
134
+ // HLA messages
135
+ createHlaCallRequestMessage,
136
+ decodeHlaCallResponse,
137
+ decodeHlaCallbackRequest,
138
+ createHlaCallbackResponseMessage,
139
+
140
+ // Types
141
+ type ResumeStatusResult,
142
+ type HlaCallResponseResult,
143
+ type HlaCallbackRequestResult,
144
+
145
+ // Utilities
146
+ describeHeader,
147
+ } from "./messages.ts";