@hla4ts/transport 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 +386 -386
- package/package.json +2 -2
- package/src/constants.ts +57 -57
- package/src/frame-decoder.ts +136 -136
- package/src/index.ts +82 -82
- package/src/message-header.ts +141 -141
- package/src/message-type.ts +62 -62
- package/src/tcp-transport.ts +154 -154
- package/src/tls-transport.ts +193 -193
- package/src/transport.ts +99 -99
package/README.md
CHANGED
|
@@ -1,386 +1,386 @@
|
|
|
1
|
-
# @hla4ts/transport
|
|
2
|
-
|
|
3
|
-
**HLA 4 Federate Protocol Transport Layer**
|
|
4
|
-
|
|
5
|
-
This package provides the transport layer for the HLA 4 Federate Protocol, handling TCP/TLS connections, message framing, and low-level communication with HLA 4 RTIs.
|
|
6
|
-
|
|
7
|
-
## Overview
|
|
8
|
-
|
|
9
|
-
The Federate Protocol uses a binary framing format over TCP or TLS connections. Each message has a 24-byte header followed by an optional payload. This package handles:
|
|
10
|
-
|
|
11
|
-
- **Connection management** - TCP and TLS socket connections
|
|
12
|
-
- **Message framing** - Encoding/decoding the 24-byte header
|
|
13
|
-
- **Stream handling** - Buffering partial messages, extracting multiple messages from a single chunk
|
|
14
|
-
|
|
15
|
-
## Installation
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
bun add @hla4ts/transport
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Quick Start
|
|
22
|
-
|
|
23
|
-
```typescript
|
|
24
|
-
import {
|
|
25
|
-
TlsTransport,
|
|
26
|
-
MessageType,
|
|
27
|
-
encodeMessage,
|
|
28
|
-
FEDERATE_PROTOCOL_VERSION,
|
|
29
|
-
} from '@hla4ts/transport';
|
|
30
|
-
|
|
31
|
-
// Create a TLS transport
|
|
32
|
-
const transport = new TlsTransport({
|
|
33
|
-
host: 'rti.example.com',
|
|
34
|
-
port: 15165,
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// Set up event handlers
|
|
38
|
-
transport.setEventHandlers({
|
|
39
|
-
onMessage: (msg) => {
|
|
40
|
-
console.log('Received:', MessageType[msg.header.messageType]);
|
|
41
|
-
console.log('Payload size:', msg.payload.length);
|
|
42
|
-
},
|
|
43
|
-
onClose: (hadError) => {
|
|
44
|
-
console.log('Connection closed', hadError ? '(with error)' : '');
|
|
45
|
-
},
|
|
46
|
-
onError: (err) => {
|
|
47
|
-
console.error('Transport error:', err);
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
// Connect
|
|
52
|
-
await transport.connect();
|
|
53
|
-
|
|
54
|
-
// Send a CTRL_NEW_SESSION message
|
|
55
|
-
const payload = new Uint8Array(4);
|
|
56
|
-
new DataView(payload.buffer).setUint32(0, FEDERATE_PROTOCOL_VERSION, false);
|
|
57
|
-
|
|
58
|
-
const message = encodeMessage(
|
|
59
|
-
1, // sequence number
|
|
60
|
-
0n, // session ID (0 for new session)
|
|
61
|
-
0, // last received sequence number
|
|
62
|
-
MessageType.CTRL_NEW_SESSION, // message type
|
|
63
|
-
payload // payload
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
await transport.send(message);
|
|
67
|
-
|
|
68
|
-
// Later: disconnect
|
|
69
|
-
await transport.disconnect();
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Sequence Diagram
|
|
73
|
-
|
|
74
|
-
```mermaid
|
|
75
|
-
sequenceDiagram
|
|
76
|
-
participant RTI
|
|
77
|
-
participant Transport as Tcp/TlsTransport
|
|
78
|
-
participant Decoder as FrameDecoder
|
|
79
|
-
participant App as Federate Code
|
|
80
|
-
RTI-->>Transport: TCP/TLS bytes
|
|
81
|
-
Transport-->>Decoder: push(chunk)
|
|
82
|
-
Decoder-->>App: onMessage(header, payload)
|
|
83
|
-
App->>Transport: send(encoded message)
|
|
84
|
-
Transport-->>RTI: TCP/TLS bytes
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
## Message Format
|
|
88
|
-
|
|
89
|
-
Every Federate Protocol message has a **24-byte header**:
|
|
90
|
-
|
|
91
|
-
```
|
|
92
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
93
|
-
│ Offset │ Size │ Field │ Description │
|
|
94
|
-
├────────┼───────┼──────────────────────────┼─────────────────────┤
|
|
95
|
-
│ 0 │ 4 │ packetSize │ Total message size │
|
|
96
|
-
│ 4 │ 4 │ sequenceNumber │ Message sequence # │
|
|
97
|
-
│ 8 │ 8 │ sessionId │ Session identifier │
|
|
98
|
-
│ 16 │ 4 │ lastReceivedSequenceNum │ ACK for flow ctrl │
|
|
99
|
-
│ 20 │ 4 │ messageType │ Type of message │
|
|
100
|
-
├────────┴───────┴──────────────────────────┴─────────────────────┤
|
|
101
|
-
│ 24 │ var │ payload │ Message payload │
|
|
102
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
All integers are **big-endian** (network byte order).
|
|
106
|
-
|
|
107
|
-
## Message Types
|
|
108
|
-
|
|
109
|
-
### Control Messages (Session Management)
|
|
110
|
-
|
|
111
|
-
| Code | Name | Direction | Description |
|
|
112
|
-
|------|------|-----------|-------------|
|
|
113
|
-
| 1 | `CTRL_NEW_SESSION` | C → S | Request new session |
|
|
114
|
-
| 2 | `CTRL_NEW_SESSION_STATUS` | S → C | Session creation result |
|
|
115
|
-
| 3 | `CTRL_HEARTBEAT` | Both | Keep-alive ping |
|
|
116
|
-
| 4 | `CTRL_HEARTBEAT_RESPONSE` | Both | Keep-alive pong |
|
|
117
|
-
| 5 | `CTRL_TERMINATE_SESSION` | C → S | Request session end |
|
|
118
|
-
| 6 | `CTRL_SESSION_TERMINATED` | S → C | Session ended |
|
|
119
|
-
| 10 | `CTRL_RESUME_REQUEST` | C → S | Resume dropped session |
|
|
120
|
-
| 11 | `CTRL_RESUME_STATUS` | S → C | Resume result |
|
|
121
|
-
|
|
122
|
-
### HLA Messages
|
|
123
|
-
|
|
124
|
-
| Code | Name | Direction | Description |
|
|
125
|
-
|------|------|-----------|-------------|
|
|
126
|
-
| 20 | `HLA_CALL_REQUEST` | C → S | RTI service call (protobuf `CallRequest`) |
|
|
127
|
-
| 21 | `HLA_CALL_RESPONSE` | S → C | RTI response (protobuf `CallResponse`) |
|
|
128
|
-
| 22 | `HLA_CALLBACK_REQUEST` | S → C | Federate callback (protobuf `CallbackRequest`) |
|
|
129
|
-
| 23 | `HLA_CALLBACK_RESPONSE` | C → S | Callback ACK (protobuf `CallbackResponse`) |
|
|
130
|
-
|
|
131
|
-
## API Reference
|
|
132
|
-
|
|
133
|
-
### Transports
|
|
134
|
-
|
|
135
|
-
#### `TlsTransport`
|
|
136
|
-
|
|
137
|
-
Secure TLS connection (recommended for production):
|
|
138
|
-
|
|
139
|
-
```typescript
|
|
140
|
-
import { TlsTransport, Ports } from '@hla4ts/transport';
|
|
141
|
-
|
|
142
|
-
const transport = new TlsTransport({
|
|
143
|
-
host: 'rti.example.com',
|
|
144
|
-
port: Ports.TLS, // 15165 (default)
|
|
145
|
-
connectionTimeout: 10000, // 10 seconds (default)
|
|
146
|
-
noDelay: true, // TCP_NODELAY (default)
|
|
147
|
-
|
|
148
|
-
// TLS options
|
|
149
|
-
rejectUnauthorized: true, // Verify server cert (default)
|
|
150
|
-
ca: '/path/to/ca.pem', // Custom CA (optional)
|
|
151
|
-
cert: '/path/to/client.pem', // Client cert for mTLS (optional)
|
|
152
|
-
key: '/path/to/client-key.pem', // Client key for mTLS (optional)
|
|
153
|
-
serverName: 'rti.example.com', // SNI hostname (optional)
|
|
154
|
-
});
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
#### `TcpTransport`
|
|
158
|
-
|
|
159
|
-
Plain TCP connection (for development/testing only):
|
|
160
|
-
|
|
161
|
-
```typescript
|
|
162
|
-
import { TcpTransport, Ports } from '@hla4ts/transport';
|
|
163
|
-
|
|
164
|
-
const transport = new TcpTransport({
|
|
165
|
-
host: 'localhost',
|
|
166
|
-
port: Ports.TCP, // 15164 (default)
|
|
167
|
-
});
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
### Transport Interface
|
|
171
|
-
|
|
172
|
-
Both transports implement the `Transport` interface:
|
|
173
|
-
|
|
174
|
-
```typescript
|
|
175
|
-
interface Transport {
|
|
176
|
-
connect(): Promise<void>;
|
|
177
|
-
disconnect(): Promise<void>;
|
|
178
|
-
isConnected(): boolean;
|
|
179
|
-
send(data: Uint8Array): Promise<void>;
|
|
180
|
-
setEventHandlers(handlers: TransportEvents): void;
|
|
181
|
-
getProtocolName(): string;
|
|
182
|
-
getRemoteAddress(): string | null;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
interface TransportEvents {
|
|
186
|
-
onMessage?: (message: ReceivedMessage) => void;
|
|
187
|
-
onClose?: (hadError: boolean) => void;
|
|
188
|
-
onError?: (error: Error) => void;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
interface ReceivedMessage {
|
|
192
|
-
header: MessageHeader;
|
|
193
|
-
payload: Uint8Array;
|
|
194
|
-
}
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Message Header Functions
|
|
198
|
-
|
|
199
|
-
```typescript
|
|
200
|
-
import {
|
|
201
|
-
encodeHeader,
|
|
202
|
-
decodeHeader,
|
|
203
|
-
encodeMessage,
|
|
204
|
-
getPayloadSize,
|
|
205
|
-
HEADER_SIZE, // 24
|
|
206
|
-
NO_SESSION_ID, // 0n
|
|
207
|
-
NO_SEQUENCE_NUMBER, // 0
|
|
208
|
-
} from '@hla4ts/transport';
|
|
209
|
-
|
|
210
|
-
// Encode just the header (24 bytes)
|
|
211
|
-
const header = encodeHeader(
|
|
212
|
-
payloadSize, // payload size in bytes
|
|
213
|
-
sequenceNumber, // sequence number
|
|
214
|
-
sessionId, // session ID (bigint)
|
|
215
|
-
lastReceivedSequenceNum, // ACK
|
|
216
|
-
messageType // MessageType enum
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
// Encode header + payload
|
|
220
|
-
const message = encodeMessage(
|
|
221
|
-
sequenceNumber,
|
|
222
|
-
sessionId,
|
|
223
|
-
lastReceivedSequenceNum,
|
|
224
|
-
messageType,
|
|
225
|
-
payload // optional Uint8Array
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
// Decode a header
|
|
229
|
-
const header = decodeHeader(buffer); // throws on invalid data
|
|
230
|
-
console.log(header.packetSize);
|
|
231
|
-
console.log(header.sequenceNumber);
|
|
232
|
-
console.log(header.sessionId);
|
|
233
|
-
console.log(header.messageType);
|
|
234
|
-
|
|
235
|
-
// Get payload size from header
|
|
236
|
-
const payloadSize = getPayloadSize(header);
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### Frame Decoder
|
|
240
|
-
|
|
241
|
-
For advanced use cases, you can use the `FrameDecoder` directly:
|
|
242
|
-
|
|
243
|
-
```typescript
|
|
244
|
-
import { FrameDecoder } from '@hla4ts/transport';
|
|
245
|
-
|
|
246
|
-
const decoder = new FrameDecoder();
|
|
247
|
-
|
|
248
|
-
decoder.onMessage = (msg) => {
|
|
249
|
-
console.log('Complete message received');
|
|
250
|
-
console.log('Type:', msg.header.messageType);
|
|
251
|
-
console.log('Payload:', msg.payload);
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
decoder.onError = (err) => {
|
|
255
|
-
console.error('Decode error:', err);
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
// Feed data as it arrives (handles fragmentation automatically)
|
|
259
|
-
socket.on('data', (chunk) => {
|
|
260
|
-
decoder.push(new Uint8Array(chunk));
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
// Check buffered data
|
|
264
|
-
console.log('Bytes waiting:', decoder.bufferedBytes);
|
|
265
|
-
|
|
266
|
-
// Reset after connection reset
|
|
267
|
-
decoder.reset();
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
### Constants
|
|
271
|
-
|
|
272
|
-
```typescript
|
|
273
|
-
import {
|
|
274
|
-
FEDERATE_PROTOCOL_VERSION, // 1
|
|
275
|
-
Ports,
|
|
276
|
-
Protocol,
|
|
277
|
-
NewSessionStatus,
|
|
278
|
-
ResumeStatus,
|
|
279
|
-
} from '@hla4ts/transport';
|
|
280
|
-
|
|
281
|
-
// Default ports
|
|
282
|
-
Ports.TCP // 15164
|
|
283
|
-
Ports.TLS // 15165
|
|
284
|
-
Ports.WS // 80
|
|
285
|
-
Ports.WSS // 443
|
|
286
|
-
|
|
287
|
-
// Protocol names
|
|
288
|
-
Protocol.TCP // "tcp"
|
|
289
|
-
Protocol.TLS // "tls"
|
|
290
|
-
Protocol.WS // "websocket"
|
|
291
|
-
Protocol.WSS // "websocketsecure"
|
|
292
|
-
|
|
293
|
-
// Session status codes
|
|
294
|
-
NewSessionStatus.SUCCESS // 0
|
|
295
|
-
NewSessionStatus.UNSUPPORTED_PROTOCOL_VERSION // 1
|
|
296
|
-
NewSessionStatus.OUT_OF_RESOURCES // 2
|
|
297
|
-
NewSessionStatus.BAD_MESSAGE // 3
|
|
298
|
-
NewSessionStatus.OTHER_ERROR // 99
|
|
299
|
-
|
|
300
|
-
// Resume status codes
|
|
301
|
-
ResumeStatus.SUCCESS // 0
|
|
302
|
-
ResumeStatus.SESSION_NOT_FOUND // 1
|
|
303
|
-
ResumeStatus.NOT_ALLOWED // 2
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
## Session Protocol Flow
|
|
307
|
-
|
|
308
|
-
```
|
|
309
|
-
┌─────────────┐ ┌─────────────┐
|
|
310
|
-
│ Federate │ │ RTI │
|
|
311
|
-
└──────┬──────┘ └──────┬──────┘
|
|
312
|
-
│ │
|
|
313
|
-
│──── TCP/TLS Connect ──────────────────────>│
|
|
314
|
-
│ │
|
|
315
|
-
│──── CTRL_NEW_SESSION (version=1) ─────────>│
|
|
316
|
-
│ │
|
|
317
|
-
│<─── CTRL_NEW_SESSION_STATUS (sessionId) ───│
|
|
318
|
-
│ │
|
|
319
|
-
│──── HLA_CALL_REQUEST (Connect) ───────────>│
|
|
320
|
-
│<─── HLA_CALL_RESPONSE ─────────────────────│
|
|
321
|
-
│ │
|
|
322
|
-
│──── HLA_CALL_REQUEST (JoinFederation) ────>│
|
|
323
|
-
│<─── HLA_CALL_RESPONSE ─────────────────────│
|
|
324
|
-
│ │
|
|
325
|
-
│ ... HLA calls and callbacks ... │
|
|
326
|
-
│ │
|
|
327
|
-
│<─── HLA_CALLBACK_REQUEST (Discover...) ────│
|
|
328
|
-
│──── HLA_CALLBACK_RESPONSE ────────────────>│
|
|
329
|
-
│ │
|
|
330
|
-
│──── CTRL_HEARTBEAT ───────────────────────>│
|
|
331
|
-
│<─── CTRL_HEARTBEAT_RESPONSE ──────────────│
|
|
332
|
-
│ │
|
|
333
|
-
│──── CTRL_TERMINATE_SESSION ───────────────>│
|
|
334
|
-
│<─── CTRL_SESSION_TERMINATED ──────────────│
|
|
335
|
-
│ │
|
|
336
|
-
│──── TCP/TLS Disconnect ───────────────────>│
|
|
337
|
-
│ │
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
## Error Handling
|
|
341
|
-
|
|
342
|
-
```typescript
|
|
343
|
-
try {
|
|
344
|
-
await transport.connect();
|
|
345
|
-
} catch (err) {
|
|
346
|
-
if (err.message.includes('Connection timeout')) {
|
|
347
|
-
console.error('RTI not reachable');
|
|
348
|
-
} else if (err.message.includes('certificate')) {
|
|
349
|
-
console.error('TLS certificate error');
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
transport.setEventHandlers({
|
|
354
|
-
onError: (err) => {
|
|
355
|
-
console.error('Transport error:', err);
|
|
356
|
-
// Attempt reconnection...
|
|
357
|
-
},
|
|
358
|
-
onClose: (hadError) => {
|
|
359
|
-
if (hadError) {
|
|
360
|
-
console.error('Connection lost unexpectedly');
|
|
361
|
-
}
|
|
362
|
-
},
|
|
363
|
-
});
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
## Testing
|
|
367
|
-
|
|
368
|
-
```bash
|
|
369
|
-
cd packages/transport
|
|
370
|
-
bun test
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
## Related Packages
|
|
374
|
-
|
|
375
|
-
- [`@hla4ts/proto`](../proto) - Protocol buffer types
|
|
376
|
-
- [`@hla4ts/session`](../session) - Session management
|
|
377
|
-
- [`@hla4ts/hla-api`](../hla-api) - High-level HLA API facade
|
|
378
|
-
|
|
379
|
-
## References
|
|
380
|
-
|
|
381
|
-
- [IEEE 1516-2025](https://standards.ieee.org/standard/1516-2025.html) - HLA 4 Standard
|
|
382
|
-
- [Pitch FedProClient](https://github.com/Pitch-Technologies/FedProClient) - Reference implementation
|
|
383
|
-
|
|
384
|
-
## License
|
|
385
|
-
|
|
386
|
-
MIT
|
|
1
|
+
# @hla4ts/transport
|
|
2
|
+
|
|
3
|
+
**HLA 4 Federate Protocol Transport Layer**
|
|
4
|
+
|
|
5
|
+
This package provides the transport layer for the HLA 4 Federate Protocol, handling TCP/TLS connections, message framing, and low-level communication with HLA 4 RTIs.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
The Federate Protocol uses a binary framing format over TCP or TLS connections. Each message has a 24-byte header followed by an optional payload. This package handles:
|
|
10
|
+
|
|
11
|
+
- **Connection management** - TCP and TLS socket connections
|
|
12
|
+
- **Message framing** - Encoding/decoding the 24-byte header
|
|
13
|
+
- **Stream handling** - Buffering partial messages, extracting multiple messages from a single chunk
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bun add @hla4ts/transport
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import {
|
|
25
|
+
TlsTransport,
|
|
26
|
+
MessageType,
|
|
27
|
+
encodeMessage,
|
|
28
|
+
FEDERATE_PROTOCOL_VERSION,
|
|
29
|
+
} from '@hla4ts/transport';
|
|
30
|
+
|
|
31
|
+
// Create a TLS transport
|
|
32
|
+
const transport = new TlsTransport({
|
|
33
|
+
host: 'rti.example.com',
|
|
34
|
+
port: 15165,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Set up event handlers
|
|
38
|
+
transport.setEventHandlers({
|
|
39
|
+
onMessage: (msg) => {
|
|
40
|
+
console.log('Received:', MessageType[msg.header.messageType]);
|
|
41
|
+
console.log('Payload size:', msg.payload.length);
|
|
42
|
+
},
|
|
43
|
+
onClose: (hadError) => {
|
|
44
|
+
console.log('Connection closed', hadError ? '(with error)' : '');
|
|
45
|
+
},
|
|
46
|
+
onError: (err) => {
|
|
47
|
+
console.error('Transport error:', err);
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Connect
|
|
52
|
+
await transport.connect();
|
|
53
|
+
|
|
54
|
+
// Send a CTRL_NEW_SESSION message
|
|
55
|
+
const payload = new Uint8Array(4);
|
|
56
|
+
new DataView(payload.buffer).setUint32(0, FEDERATE_PROTOCOL_VERSION, false);
|
|
57
|
+
|
|
58
|
+
const message = encodeMessage(
|
|
59
|
+
1, // sequence number
|
|
60
|
+
0n, // session ID (0 for new session)
|
|
61
|
+
0, // last received sequence number
|
|
62
|
+
MessageType.CTRL_NEW_SESSION, // message type
|
|
63
|
+
payload // payload
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
await transport.send(message);
|
|
67
|
+
|
|
68
|
+
// Later: disconnect
|
|
69
|
+
await transport.disconnect();
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Sequence Diagram
|
|
73
|
+
|
|
74
|
+
```mermaid
|
|
75
|
+
sequenceDiagram
|
|
76
|
+
participant RTI
|
|
77
|
+
participant Transport as Tcp/TlsTransport
|
|
78
|
+
participant Decoder as FrameDecoder
|
|
79
|
+
participant App as Federate Code
|
|
80
|
+
RTI-->>Transport: TCP/TLS bytes
|
|
81
|
+
Transport-->>Decoder: push(chunk)
|
|
82
|
+
Decoder-->>App: onMessage(header, payload)
|
|
83
|
+
App->>Transport: send(encoded message)
|
|
84
|
+
Transport-->>RTI: TCP/TLS bytes
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Message Format
|
|
88
|
+
|
|
89
|
+
Every Federate Protocol message has a **24-byte header**:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
93
|
+
│ Offset │ Size │ Field │ Description │
|
|
94
|
+
├────────┼───────┼──────────────────────────┼─────────────────────┤
|
|
95
|
+
│ 0 │ 4 │ packetSize │ Total message size │
|
|
96
|
+
│ 4 │ 4 │ sequenceNumber │ Message sequence # │
|
|
97
|
+
│ 8 │ 8 │ sessionId │ Session identifier │
|
|
98
|
+
│ 16 │ 4 │ lastReceivedSequenceNum │ ACK for flow ctrl │
|
|
99
|
+
│ 20 │ 4 │ messageType │ Type of message │
|
|
100
|
+
├────────┴───────┴──────────────────────────┴─────────────────────┤
|
|
101
|
+
│ 24 │ var │ payload │ Message payload │
|
|
102
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
All integers are **big-endian** (network byte order).
|
|
106
|
+
|
|
107
|
+
## Message Types
|
|
108
|
+
|
|
109
|
+
### Control Messages (Session Management)
|
|
110
|
+
|
|
111
|
+
| Code | Name | Direction | Description |
|
|
112
|
+
|------|------|-----------|-------------|
|
|
113
|
+
| 1 | `CTRL_NEW_SESSION` | C → S | Request new session |
|
|
114
|
+
| 2 | `CTRL_NEW_SESSION_STATUS` | S → C | Session creation result |
|
|
115
|
+
| 3 | `CTRL_HEARTBEAT` | Both | Keep-alive ping |
|
|
116
|
+
| 4 | `CTRL_HEARTBEAT_RESPONSE` | Both | Keep-alive pong |
|
|
117
|
+
| 5 | `CTRL_TERMINATE_SESSION` | C → S | Request session end |
|
|
118
|
+
| 6 | `CTRL_SESSION_TERMINATED` | S → C | Session ended |
|
|
119
|
+
| 10 | `CTRL_RESUME_REQUEST` | C → S | Resume dropped session |
|
|
120
|
+
| 11 | `CTRL_RESUME_STATUS` | S → C | Resume result |
|
|
121
|
+
|
|
122
|
+
### HLA Messages
|
|
123
|
+
|
|
124
|
+
| Code | Name | Direction | Description |
|
|
125
|
+
|------|------|-----------|-------------|
|
|
126
|
+
| 20 | `HLA_CALL_REQUEST` | C → S | RTI service call (protobuf `CallRequest`) |
|
|
127
|
+
| 21 | `HLA_CALL_RESPONSE` | S → C | RTI response (protobuf `CallResponse`) |
|
|
128
|
+
| 22 | `HLA_CALLBACK_REQUEST` | S → C | Federate callback (protobuf `CallbackRequest`) |
|
|
129
|
+
| 23 | `HLA_CALLBACK_RESPONSE` | C → S | Callback ACK (protobuf `CallbackResponse`) |
|
|
130
|
+
|
|
131
|
+
## API Reference
|
|
132
|
+
|
|
133
|
+
### Transports
|
|
134
|
+
|
|
135
|
+
#### `TlsTransport`
|
|
136
|
+
|
|
137
|
+
Secure TLS connection (recommended for production):
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import { TlsTransport, Ports } from '@hla4ts/transport';
|
|
141
|
+
|
|
142
|
+
const transport = new TlsTransport({
|
|
143
|
+
host: 'rti.example.com',
|
|
144
|
+
port: Ports.TLS, // 15165 (default)
|
|
145
|
+
connectionTimeout: 10000, // 10 seconds (default)
|
|
146
|
+
noDelay: true, // TCP_NODELAY (default)
|
|
147
|
+
|
|
148
|
+
// TLS options
|
|
149
|
+
rejectUnauthorized: true, // Verify server cert (default)
|
|
150
|
+
ca: '/path/to/ca.pem', // Custom CA (optional)
|
|
151
|
+
cert: '/path/to/client.pem', // Client cert for mTLS (optional)
|
|
152
|
+
key: '/path/to/client-key.pem', // Client key for mTLS (optional)
|
|
153
|
+
serverName: 'rti.example.com', // SNI hostname (optional)
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### `TcpTransport`
|
|
158
|
+
|
|
159
|
+
Plain TCP connection (for development/testing only):
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { TcpTransport, Ports } from '@hla4ts/transport';
|
|
163
|
+
|
|
164
|
+
const transport = new TcpTransport({
|
|
165
|
+
host: 'localhost',
|
|
166
|
+
port: Ports.TCP, // 15164 (default)
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Transport Interface
|
|
171
|
+
|
|
172
|
+
Both transports implement the `Transport` interface:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
interface Transport {
|
|
176
|
+
connect(): Promise<void>;
|
|
177
|
+
disconnect(): Promise<void>;
|
|
178
|
+
isConnected(): boolean;
|
|
179
|
+
send(data: Uint8Array): Promise<void>;
|
|
180
|
+
setEventHandlers(handlers: TransportEvents): void;
|
|
181
|
+
getProtocolName(): string;
|
|
182
|
+
getRemoteAddress(): string | null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
interface TransportEvents {
|
|
186
|
+
onMessage?: (message: ReceivedMessage) => void;
|
|
187
|
+
onClose?: (hadError: boolean) => void;
|
|
188
|
+
onError?: (error: Error) => void;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
interface ReceivedMessage {
|
|
192
|
+
header: MessageHeader;
|
|
193
|
+
payload: Uint8Array;
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Message Header Functions
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import {
|
|
201
|
+
encodeHeader,
|
|
202
|
+
decodeHeader,
|
|
203
|
+
encodeMessage,
|
|
204
|
+
getPayloadSize,
|
|
205
|
+
HEADER_SIZE, // 24
|
|
206
|
+
NO_SESSION_ID, // 0n
|
|
207
|
+
NO_SEQUENCE_NUMBER, // 0
|
|
208
|
+
} from '@hla4ts/transport';
|
|
209
|
+
|
|
210
|
+
// Encode just the header (24 bytes)
|
|
211
|
+
const header = encodeHeader(
|
|
212
|
+
payloadSize, // payload size in bytes
|
|
213
|
+
sequenceNumber, // sequence number
|
|
214
|
+
sessionId, // session ID (bigint)
|
|
215
|
+
lastReceivedSequenceNum, // ACK
|
|
216
|
+
messageType // MessageType enum
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// Encode header + payload
|
|
220
|
+
const message = encodeMessage(
|
|
221
|
+
sequenceNumber,
|
|
222
|
+
sessionId,
|
|
223
|
+
lastReceivedSequenceNum,
|
|
224
|
+
messageType,
|
|
225
|
+
payload // optional Uint8Array
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
// Decode a header
|
|
229
|
+
const header = decodeHeader(buffer); // throws on invalid data
|
|
230
|
+
console.log(header.packetSize);
|
|
231
|
+
console.log(header.sequenceNumber);
|
|
232
|
+
console.log(header.sessionId);
|
|
233
|
+
console.log(header.messageType);
|
|
234
|
+
|
|
235
|
+
// Get payload size from header
|
|
236
|
+
const payloadSize = getPayloadSize(header);
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Frame Decoder
|
|
240
|
+
|
|
241
|
+
For advanced use cases, you can use the `FrameDecoder` directly:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import { FrameDecoder } from '@hla4ts/transport';
|
|
245
|
+
|
|
246
|
+
const decoder = new FrameDecoder();
|
|
247
|
+
|
|
248
|
+
decoder.onMessage = (msg) => {
|
|
249
|
+
console.log('Complete message received');
|
|
250
|
+
console.log('Type:', msg.header.messageType);
|
|
251
|
+
console.log('Payload:', msg.payload);
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
decoder.onError = (err) => {
|
|
255
|
+
console.error('Decode error:', err);
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// Feed data as it arrives (handles fragmentation automatically)
|
|
259
|
+
socket.on('data', (chunk) => {
|
|
260
|
+
decoder.push(new Uint8Array(chunk));
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Check buffered data
|
|
264
|
+
console.log('Bytes waiting:', decoder.bufferedBytes);
|
|
265
|
+
|
|
266
|
+
// Reset after connection reset
|
|
267
|
+
decoder.reset();
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Constants
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
import {
|
|
274
|
+
FEDERATE_PROTOCOL_VERSION, // 1
|
|
275
|
+
Ports,
|
|
276
|
+
Protocol,
|
|
277
|
+
NewSessionStatus,
|
|
278
|
+
ResumeStatus,
|
|
279
|
+
} from '@hla4ts/transport';
|
|
280
|
+
|
|
281
|
+
// Default ports
|
|
282
|
+
Ports.TCP // 15164
|
|
283
|
+
Ports.TLS // 15165
|
|
284
|
+
Ports.WS // 80
|
|
285
|
+
Ports.WSS // 443
|
|
286
|
+
|
|
287
|
+
// Protocol names
|
|
288
|
+
Protocol.TCP // "tcp"
|
|
289
|
+
Protocol.TLS // "tls"
|
|
290
|
+
Protocol.WS // "websocket"
|
|
291
|
+
Protocol.WSS // "websocketsecure"
|
|
292
|
+
|
|
293
|
+
// Session status codes
|
|
294
|
+
NewSessionStatus.SUCCESS // 0
|
|
295
|
+
NewSessionStatus.UNSUPPORTED_PROTOCOL_VERSION // 1
|
|
296
|
+
NewSessionStatus.OUT_OF_RESOURCES // 2
|
|
297
|
+
NewSessionStatus.BAD_MESSAGE // 3
|
|
298
|
+
NewSessionStatus.OTHER_ERROR // 99
|
|
299
|
+
|
|
300
|
+
// Resume status codes
|
|
301
|
+
ResumeStatus.SUCCESS // 0
|
|
302
|
+
ResumeStatus.SESSION_NOT_FOUND // 1
|
|
303
|
+
ResumeStatus.NOT_ALLOWED // 2
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Session Protocol Flow
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
┌─────────────┐ ┌─────────────┐
|
|
310
|
+
│ Federate │ │ RTI │
|
|
311
|
+
└──────┬──────┘ └──────┬──────┘
|
|
312
|
+
│ │
|
|
313
|
+
│──── TCP/TLS Connect ──────────────────────>│
|
|
314
|
+
│ │
|
|
315
|
+
│──── CTRL_NEW_SESSION (version=1) ─────────>│
|
|
316
|
+
│ │
|
|
317
|
+
│<─── CTRL_NEW_SESSION_STATUS (sessionId) ───│
|
|
318
|
+
│ │
|
|
319
|
+
│──── HLA_CALL_REQUEST (Connect) ───────────>│
|
|
320
|
+
│<─── HLA_CALL_RESPONSE ─────────────────────│
|
|
321
|
+
│ │
|
|
322
|
+
│──── HLA_CALL_REQUEST (JoinFederation) ────>│
|
|
323
|
+
│<─── HLA_CALL_RESPONSE ─────────────────────│
|
|
324
|
+
│ │
|
|
325
|
+
│ ... HLA calls and callbacks ... │
|
|
326
|
+
│ │
|
|
327
|
+
│<─── HLA_CALLBACK_REQUEST (Discover...) ────│
|
|
328
|
+
│──── HLA_CALLBACK_RESPONSE ────────────────>│
|
|
329
|
+
│ │
|
|
330
|
+
│──── CTRL_HEARTBEAT ───────────────────────>│
|
|
331
|
+
│<─── CTRL_HEARTBEAT_RESPONSE ──────────────│
|
|
332
|
+
│ │
|
|
333
|
+
│──── CTRL_TERMINATE_SESSION ───────────────>│
|
|
334
|
+
│<─── CTRL_SESSION_TERMINATED ──────────────│
|
|
335
|
+
│ │
|
|
336
|
+
│──── TCP/TLS Disconnect ───────────────────>│
|
|
337
|
+
│ │
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Error Handling
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
try {
|
|
344
|
+
await transport.connect();
|
|
345
|
+
} catch (err) {
|
|
346
|
+
if (err.message.includes('Connection timeout')) {
|
|
347
|
+
console.error('RTI not reachable');
|
|
348
|
+
} else if (err.message.includes('certificate')) {
|
|
349
|
+
console.error('TLS certificate error');
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
transport.setEventHandlers({
|
|
354
|
+
onError: (err) => {
|
|
355
|
+
console.error('Transport error:', err);
|
|
356
|
+
// Attempt reconnection...
|
|
357
|
+
},
|
|
358
|
+
onClose: (hadError) => {
|
|
359
|
+
if (hadError) {
|
|
360
|
+
console.error('Connection lost unexpectedly');
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Testing
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
cd packages/transport
|
|
370
|
+
bun test
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Related Packages
|
|
374
|
+
|
|
375
|
+
- [`@hla4ts/proto`](../proto) - Protocol buffer types
|
|
376
|
+
- [`@hla4ts/session`](../session) - Session management
|
|
377
|
+
- [`@hla4ts/hla-api`](../hla-api) - High-level HLA API facade
|
|
378
|
+
|
|
379
|
+
## References
|
|
380
|
+
|
|
381
|
+
- [IEEE 1516-2025](https://standards.ieee.org/standard/1516-2025.html) - HLA 4 Standard
|
|
382
|
+
- [Pitch FedProClient](https://github.com/Pitch-Technologies/FedProClient) - Reference implementation
|
|
383
|
+
|
|
384
|
+
## License
|
|
385
|
+
|
|
386
|
+
MIT
|