raptiye 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.
Files changed (41) hide show
  1. package/README.md +171 -0
  2. package/bench/bench-batch-curve.js +121 -0
  3. package/bench/bench-event-loop.js +91 -0
  4. package/bench/bench-failover.js +172 -0
  5. package/bench/bench-memory.js +107 -0
  6. package/bench/bench-partition.js +134 -0
  7. package/bench/bench-pipeline.js +94 -0
  8. package/bench/bench-recovery.js +134 -0
  9. package/bench/bench-replication.js +107 -0
  10. package/bench/bench-single-node.js +77 -0
  11. package/bench/bench-slow-follower.js +96 -0
  12. package/bench/harness.js +87 -0
  13. package/bench/run-all.js +83 -0
  14. package/bench-results.json +598 -0
  15. package/index.js +30 -0
  16. package/package.json +24 -0
  17. package/src/core/engine.js +907 -0
  18. package/src/core/invariants.js +146 -0
  19. package/src/node/raptiye.js +296 -0
  20. package/src/node/stats.js +82 -0
  21. package/src/protocol/checksum.js +66 -0
  22. package/src/protocol/wire.js +551 -0
  23. package/src/replication/pipeline.js +235 -0
  24. package/src/sim/cluster.js +316 -0
  25. package/src/sim/prng.js +52 -0
  26. package/src/sim/virtual-clock.js +82 -0
  27. package/src/storage/file-log.js +394 -0
  28. package/src/storage/interface.js +113 -0
  29. package/src/storage/memory-log.js +183 -0
  30. package/src/transport/interface.js +47 -0
  31. package/src/transport/memory-transport.js +67 -0
  32. package/src/transport/tcp-transport.js +200 -0
  33. package/src/types.js +87 -0
  34. package/test/chaos/chaos.test.js +112 -0
  35. package/test/integration/cluster.test.js +192 -0
  36. package/test/integration/node.test.js +78 -0
  37. package/test/integration/replication-pipeline.test.js +45 -0
  38. package/test/unit/core.test.js +163 -0
  39. package/test/unit/protocol.test.js +181 -0
  40. package/test/unit/storage.test.js +127 -0
  41. package/test/unit/transport.test.js +90 -0
@@ -0,0 +1,551 @@
1
+ /**
2
+ * Binary wire protocol encoder and decoder for Raptiye.
3
+ * Zero-copy scatter/gather design using Uint8Array and DataView.
4
+ */
5
+
6
+ import { MessageType, EntryType, ProtocolConstants } from '../types.js';
7
+ import { crc32, crc32Buffers } from './checksum.js';
8
+
9
+ export const HEADER_SIZE = ProtocolConstants.HEADER_SIZE; // 28 bytes
10
+ export const MAGIC = ProtocolConstants.MAGIC; // 0x5250 ('RP')
11
+ export const VERSION = ProtocolConstants.VERSION; // 1
12
+
13
+ export class WireMetrics {
14
+ constructor() {
15
+ this.wireBytes = 0;
16
+ this.payloadBytes = 0;
17
+ this.protocolOverheadBytes = 0;
18
+ this.allocatedBytes = 0;
19
+ this.copiedBytes = 0;
20
+ }
21
+
22
+ reset() {
23
+ this.wireBytes = 0;
24
+ this.payloadBytes = 0;
25
+ this.protocolOverheadBytes = 0;
26
+ this.allocatedBytes = 0;
27
+ this.copiedBytes = 0;
28
+ }
29
+ }
30
+
31
+ export const globalWireMetrics = new WireMetrics();
32
+
33
+ /**
34
+ * Write common header into a buffer.
35
+ * Header layout (28 bytes):
36
+ * 0..1: Magic (0x5250) [uint16 BE]
37
+ * 2: Version (1) [uint8]
38
+ * 3: MessageType [uint8]
39
+ * 4..5: Flags [uint16 BE]
40
+ * 6..13: Term [uint64 BigInt BE]
41
+ * 14..15: SourceNode [uint16 BE]
42
+ * 16..17: DestNode [uint16 BE]
43
+ * 18..21: PayloadLength [uint32 BE]
44
+ * 22..25: Checksum [uint32 BE]
45
+ * 26..27: Reserved [uint16 BE]
46
+ */
47
+ export function writeHeader(buf, offset, {
48
+ messageType,
49
+ flags = 0,
50
+ term = 0n,
51
+ sourceNode = 0,
52
+ destNode = 0,
53
+ payloadLength = 0,
54
+ checksum = 0
55
+ }) {
56
+ const view = new DataView(buf.buffer, buf.byteOffset + offset, HEADER_SIZE);
57
+ view.setUint16(0, MAGIC, false);
58
+ view.setUint8(2, VERSION);
59
+ view.setUint8(3, messageType);
60
+ view.setUint16(4, flags, false);
61
+ view.setBigUint64(6, BigInt(term), false);
62
+ view.setUint16(14, sourceNode, false);
63
+ view.setUint16(16, destNode, false);
64
+ view.setUint32(18, payloadLength, false);
65
+ view.setUint32(22, checksum, false);
66
+ view.setUint16(26, 0, false); // reserved
67
+ }
68
+
69
+ /**
70
+ * Read common header from a buffer.
71
+ */
72
+ export function readHeader(buf, offset = 0) {
73
+ if (buf.byteLength < offset + HEADER_SIZE) {
74
+ throw new Error(`Buffer too short for header: ${buf.byteLength} < ${offset + HEADER_SIZE}`);
75
+ }
76
+ const view = new DataView(buf.buffer, buf.byteOffset + offset, HEADER_SIZE);
77
+ const magic = view.getUint16(0, false);
78
+ if (magic !== MAGIC) {
79
+ throw new Error(`Invalid protocol magic: 0x${magic.toString(16)} (expected 0x${MAGIC.toString(16)})`);
80
+ }
81
+ const version = view.getUint8(2);
82
+ const messageType = view.getUint8(3);
83
+ const flags = view.getUint16(4, false);
84
+ const term = view.getBigUint64(6, false);
85
+ const sourceNode = view.getUint16(14, false);
86
+ const destNode = view.getUint16(16, false);
87
+ const payloadLength = view.getUint32(18, false);
88
+ const checksum = view.getUint32(22, false);
89
+
90
+ return {
91
+ magic,
92
+ version,
93
+ messageType,
94
+ flags,
95
+ term,
96
+ sourceNode,
97
+ destNode,
98
+ payloadLength,
99
+ checksum
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Encodes a message into scatter/gather chunks [header, metadata, ...payloads].
105
+ * Does NOT copy application payloads!
106
+ *
107
+ * @param {object} msg
108
+ * @param {WireMetrics} [metrics=globalWireMetrics]
109
+ * @returns {Uint8Array[]} Array of Uint8Array buffers ready for sendv
110
+ */
111
+ export function encodeMessage(msg, metrics = globalWireMetrics) {
112
+ const headerBuf = new Uint8Array(HEADER_SIZE);
113
+ let metadataBuf = null;
114
+ const payloadBuffers = [];
115
+ let payloadBytes = 0;
116
+
117
+ switch (msg.type) {
118
+ case MessageType.PRE_VOTE_REQUEST:
119
+ case MessageType.VOTE_REQUEST: {
120
+ // Body: candidateId (2), lastLogIndex (8), lastLogTerm (8) = 18 bytes
121
+ metadataBuf = new Uint8Array(18);
122
+ const view = new DataView(metadataBuf.buffer, metadataBuf.byteOffset, 18);
123
+ view.setUint16(0, msg.candidateId, false);
124
+ view.setBigUint64(2, BigInt(msg.lastLogIndex), false);
125
+ view.setBigUint64(10, BigInt(msg.lastLogTerm), false);
126
+ break;
127
+ }
128
+
129
+ case MessageType.PRE_VOTE_RESPONSE:
130
+ case MessageType.VOTE_RESPONSE: {
131
+ // Body: voteGranted (1) = 1 byte
132
+ metadataBuf = new Uint8Array(1);
133
+ metadataBuf[0] = msg.voteGranted ? 1 : 0;
134
+ break;
135
+ }
136
+
137
+ case MessageType.HEARTBEAT: {
138
+ // Body: leaderId (2), leaderCommit (8) = 10 bytes
139
+ metadataBuf = new Uint8Array(10);
140
+ const view = new DataView(metadataBuf.buffer, metadataBuf.byteOffset, 10);
141
+ view.setUint16(0, msg.leaderId, false);
142
+ view.setBigUint64(2, BigInt(msg.leaderCommit), false);
143
+ break;
144
+ }
145
+
146
+ case MessageType.APPEND_REQUEST: {
147
+ // Body: leaderId (2), prevLogIndex (8), prevLogTerm (8), leaderCommit (8), entryCount (4) = 30 bytes
148
+ // For each entry: term (8), index (8), type (1), length (4), checksum (4) = 25 bytes per entry
149
+ const entries = msg.entries || [];
150
+ const entryCount = entries.length;
151
+ const metaLen = 30 + entryCount * 25;
152
+ metadataBuf = new Uint8Array(metaLen);
153
+ const view = new DataView(metadataBuf.buffer, metadataBuf.byteOffset, metaLen);
154
+
155
+ view.setUint16(0, msg.leaderId, false);
156
+ view.setBigUint64(2, BigInt(msg.prevLogIndex), false);
157
+ view.setBigUint64(10, BigInt(msg.prevLogTerm), false);
158
+ view.setBigUint64(18, BigInt(msg.leaderCommit), false);
159
+ view.setUint32(26, entryCount, false);
160
+
161
+ let offset = 30;
162
+ for (let i = 0; i < entryCount; i++) {
163
+ const e = entries[i];
164
+ const payload = e.payload || new Uint8Array(0);
165
+ const pLen = payload.byteLength;
166
+ const pCrc = e.checksum !== undefined ? e.checksum : crc32(payload);
167
+
168
+ view.setBigUint64(offset, BigInt(e.term), false);
169
+ view.setBigUint64(offset + 8, BigInt(e.index), false);
170
+ view.setUint8(offset + 16, e.type || EntryType.NORMAL);
171
+ view.setUint32(offset + 17, pLen, false);
172
+ view.setUint32(offset + 21, pCrc, false);
173
+ offset += 25;
174
+
175
+ if (pLen > 0) {
176
+ payloadBuffers.push(payload);
177
+ payloadBytes += pLen;
178
+ }
179
+ }
180
+ break;
181
+ }
182
+
183
+ case MessageType.APPEND_RESPONSE: {
184
+ // Body: followerId (2), success (1), matchIndex (8), lastLogIndex (8) = 19 bytes
185
+ metadataBuf = new Uint8Array(19);
186
+ const view = new DataView(metadataBuf.buffer, metadataBuf.byteOffset, 19);
187
+ view.setUint16(0, msg.followerId, false);
188
+ view.setUint8(2, msg.success ? 1 : 0);
189
+ view.setBigUint64(3, BigInt(msg.matchIndex), false);
190
+ view.setBigUint64(11, BigInt(msg.lastLogIndex), false);
191
+ break;
192
+ }
193
+
194
+ case MessageType.SNAPSHOT_BEGIN: {
195
+ // Body: leaderId (2), snapshotIndex (8), snapshotTerm (8), totalBytes (8) = 26 bytes
196
+ metadataBuf = new Uint8Array(26);
197
+ const view = new DataView(metadataBuf.buffer, metadataBuf.byteOffset, 26);
198
+ view.setUint16(0, msg.leaderId, false);
199
+ view.setBigUint64(2, BigInt(msg.snapshotIndex), false);
200
+ view.setBigUint64(10, BigInt(msg.snapshotTerm), false);
201
+ view.setBigUint64(18, BigInt(msg.totalBytes), false);
202
+ break;
203
+ }
204
+
205
+ case MessageType.SNAPSHOT_CHUNK: {
206
+ // Body: snapshotIndex (8), offset (8), chunkLength (4) = 20 bytes
207
+ const chunk = msg.chunkData || new Uint8Array(0);
208
+ payloadBytes = chunk.byteLength;
209
+ metadataBuf = new Uint8Array(20);
210
+ const view = new DataView(metadataBuf.buffer, metadataBuf.byteOffset, 20);
211
+ view.setBigUint64(0, BigInt(msg.snapshotIndex), false);
212
+ view.setBigUint64(8, BigInt(msg.offset), false);
213
+ view.setUint32(16, payloadBytes, false);
214
+ if (payloadBytes > 0) {
215
+ payloadBuffers.push(chunk);
216
+ }
217
+ break;
218
+ }
219
+
220
+ case MessageType.SNAPSHOT_END: {
221
+ // Body: snapshotIndex (8), totalChecksum (4) = 12 bytes
222
+ metadataBuf = new Uint8Array(12);
223
+ const view = new DataView(metadataBuf.buffer, metadataBuf.byteOffset, 12);
224
+ view.setBigUint64(0, BigInt(msg.snapshotIndex), false);
225
+ view.setUint32(8, msg.totalChecksum >>> 0, false);
226
+ break;
227
+ }
228
+
229
+ case MessageType.SNAPSHOT_RESPONSE: {
230
+ // Body: followerId (2), success (1), lastIndex (8) = 11 bytes
231
+ metadataBuf = new Uint8Array(11);
232
+ const view = new DataView(metadataBuf.buffer, metadataBuf.byteOffset, 11);
233
+ view.setUint16(0, msg.followerId, false);
234
+ view.setUint8(2, msg.success ? 1 : 0);
235
+ view.setBigUint64(3, BigInt(msg.lastIndex), false);
236
+ break;
237
+ }
238
+
239
+
240
+ case MessageType.BATCH_DATA: {
241
+ const bufs = msg.buffers || [];
242
+ const numBuffers = bufs.length;
243
+ const metaLen = 26 + numBuffers * 4;
244
+ metadataBuf = new Uint8Array(metaLen);
245
+ const view = new DataView(metadataBuf.buffer, metadataBuf.byteOffset, metaLen);
246
+ view.setUint32(0, msg.generationId || 0, false);
247
+ view.setBigUint64(4, BigInt(msg.firstSequence || 0), false);
248
+ view.setBigUint64(12, BigInt(msg.lastSequence || 0), false);
249
+ view.setUint32(20, msg.entryCount || 0, false);
250
+ view.setUint16(24, numBuffers, false);
251
+
252
+ let off = 26;
253
+ for (let i = 0; i < numBuffers; i++) {
254
+ const b = bufs[i] || new Uint8Array(0);
255
+ view.setUint32(off, b.byteLength, false);
256
+ off += 4;
257
+ if (b.byteLength > 0) {
258
+ payloadBuffers.push(b);
259
+ payloadBytes += b.byteLength;
260
+ }
261
+ }
262
+ break;
263
+ }
264
+
265
+ case MessageType.BATCH_ACK: {
266
+ metadataBuf = new Uint8Array(21);
267
+ const view = new DataView(metadataBuf.buffer, metadataBuf.byteOffset, 21);
268
+ view.setUint32(0, msg.generationId || 0, false);
269
+ view.setBigUint64(4, BigInt(msg.sequence || 0), false);
270
+ view.setBigUint64(12, BigInt(msg.bytesReceived || 0), false);
271
+ view.setUint8(20, msg.status || 0);
272
+ break;
273
+ }
274
+
275
+ case MessageType.SESSION_HANDSHAKE:
276
+ case MessageType.SESSION_ACK: {
277
+ const payload = msg.payload || new Uint8Array(0);
278
+ payloadBytes = payload.byteLength;
279
+ metadataBuf = new Uint8Array(8);
280
+ const view = new DataView(metadataBuf.buffer, metadataBuf.byteOffset, 8);
281
+ view.setUint32(0, msg.sessionId || 0, false);
282
+ view.setUint32(4, payloadBytes, false);
283
+ if (payloadBytes > 0) {
284
+ payloadBuffers.push(payload);
285
+ }
286
+ break;
287
+ }
288
+
289
+
290
+ case MessageType.TIMEOUT_NOW: {
291
+ // Body: leaderId (2) = 2 bytes
292
+ metadataBuf = new Uint8Array(2);
293
+ const view = new DataView(metadataBuf.buffer, metadataBuf.byteOffset, 2);
294
+ view.setUint16(0, msg.leaderId, false);
295
+ break;
296
+ }
297
+
298
+ default:
299
+ metadataBuf = new Uint8Array(0);
300
+ break;
301
+ }
302
+
303
+ const metaLength = metadataBuf ? metadataBuf.byteLength : 0;
304
+ const totalPayloadLength = metaLength + payloadBytes;
305
+
306
+ // Calculate checksum of body (metadata + payloads)
307
+ const bodyChunks = [];
308
+ if (metadataBuf && metadataBuf.byteLength > 0) bodyChunks.push(metadataBuf);
309
+ for (let i = 0; i < payloadBuffers.length; i++) bodyChunks.push(payloadBuffers[i]);
310
+ const checksum = crc32Buffers(bodyChunks);
311
+
312
+ writeHeader(headerBuf, 0, {
313
+ messageType: msg.type,
314
+ flags: msg.flags || 0,
315
+ term: msg.term || 0n,
316
+ sourceNode: msg.sourceNode || 0,
317
+ destNode: msg.destNode || 0,
318
+ payloadLength: totalPayloadLength,
319
+ checksum
320
+ });
321
+
322
+ const chunks = [headerBuf];
323
+ if (metadataBuf && metadataBuf.byteLength > 0) {
324
+ chunks.push(metadataBuf);
325
+ }
326
+ for (let i = 0; i < payloadBuffers.length; i++) {
327
+ chunks.push(payloadBuffers[i]);
328
+ }
329
+
330
+ // Update wire metrics
331
+ const totalWireBytes = HEADER_SIZE + totalPayloadLength;
332
+ metrics.wireBytes += totalWireBytes;
333
+ metrics.payloadBytes += payloadBytes;
334
+ metrics.protocolOverheadBytes += (HEADER_SIZE + metaLength);
335
+ metrics.allocatedBytes += (HEADER_SIZE + metaLength); // Only headers/metadata allocated, payloads untouched!
336
+ // copiedBytes += 0 (Zero payload copies!)
337
+
338
+ return chunks;
339
+ }
340
+
341
+ /**
342
+ * Decodes a contiguous buffer or slice containing header + payload.
343
+ * Slices are returned as zero-copy views on the input buffer.
344
+ *
345
+ * @param {Uint8Array} buf
346
+ * @param {number} [offset=0]
347
+ * @param {boolean} [verifyChecksum=true]
348
+ * @returns {object} Decoded message object
349
+ */
350
+ export function decodeMessage(buf, offset = 0, verifyChecksum = true) {
351
+ const header = readHeader(buf, offset);
352
+ const bodyOffset = offset + HEADER_SIZE;
353
+ const bodyLength = header.payloadLength;
354
+
355
+ if (buf.byteLength < bodyOffset + bodyLength) {
356
+ throw new Error(`Incomplete message payload: have ${buf.byteLength - bodyOffset} bytes, needed ${bodyLength}`);
357
+ }
358
+
359
+ if (verifyChecksum && bodyLength > 0) {
360
+ const computedCrc = crc32(buf, bodyOffset, bodyLength);
361
+ if (computedCrc !== header.checksum) {
362
+ throw new Error(`Checksum mismatch: computed 0x${computedCrc.toString(16)}, header 0x${header.checksum.toString(16)}`);
363
+ }
364
+ }
365
+
366
+ const msg = {
367
+ type: header.messageType,
368
+ flags: header.flags,
369
+ term: header.term,
370
+ sourceNode: header.sourceNode,
371
+ destNode: header.destNode
372
+ };
373
+
374
+ const view = new DataView(buf.buffer, buf.byteOffset + bodyOffset, bodyLength);
375
+
376
+ switch (header.messageType) {
377
+ case MessageType.PRE_VOTE_REQUEST:
378
+ case MessageType.VOTE_REQUEST: {
379
+ msg.candidateId = view.getUint16(0, false);
380
+ msg.lastLogIndex = view.getBigUint64(2, false);
381
+ msg.lastLogTerm = view.getBigUint64(10, false);
382
+ break;
383
+ }
384
+
385
+ case MessageType.PRE_VOTE_RESPONSE:
386
+ case MessageType.VOTE_RESPONSE: {
387
+ msg.voteGranted = view.getUint8(0) === 1;
388
+ break;
389
+ }
390
+
391
+ case MessageType.HEARTBEAT: {
392
+ msg.leaderId = view.getUint16(0, false);
393
+ msg.leaderCommit = view.getBigUint64(2, false);
394
+ break;
395
+ }
396
+
397
+ case MessageType.APPEND_REQUEST: {
398
+ msg.leaderId = view.getUint16(0, false);
399
+ msg.prevLogIndex = view.getBigUint64(2, false);
400
+ msg.prevLogTerm = view.getBigUint64(10, false);
401
+ msg.leaderCommit = view.getBigUint64(18, false);
402
+ const entryCount = view.getUint32(26, false);
403
+ const entries = new Array(entryCount);
404
+
405
+ let curMetaOffset = 30;
406
+ // Calculate total metadata size for all entries
407
+ const totalMetaSize = 30 + entryCount * 25;
408
+ let curPayloadOffset = bodyOffset + totalMetaSize;
409
+
410
+ for (let i = 0; i < entryCount; i++) {
411
+ const term = view.getBigUint64(curMetaOffset, false);
412
+ const index = view.getBigUint64(curMetaOffset + 8, false);
413
+ const type = view.getUint8(curMetaOffset + 16);
414
+ const pLen = view.getUint32(curMetaOffset + 17, false);
415
+ const pCrc = view.getUint32(curMetaOffset + 21, false);
416
+ curMetaOffset += 25;
417
+
418
+ // Zero-copy view into underlying buffer
419
+ let payload = null;
420
+ if (pLen > 0) {
421
+ payload = buf.subarray(curPayloadOffset, curPayloadOffset + pLen);
422
+ curPayloadOffset += pLen;
423
+ } else {
424
+ payload = new Uint8Array(0);
425
+ }
426
+
427
+ entries[i] = {
428
+ term,
429
+ index,
430
+ type,
431
+ checksum: pCrc,
432
+ payload
433
+ };
434
+ }
435
+ msg.entries = entries;
436
+ break;
437
+ }
438
+
439
+ case MessageType.APPEND_RESPONSE: {
440
+ msg.followerId = view.getUint16(0, false);
441
+ msg.success = view.getUint8(2) === 1;
442
+ msg.matchIndex = view.getBigUint64(3, false);
443
+ msg.lastLogIndex = view.getBigUint64(11, false);
444
+ break;
445
+ }
446
+
447
+ case MessageType.SNAPSHOT_BEGIN: {
448
+ msg.leaderId = view.getUint16(0, false);
449
+ msg.snapshotIndex = view.getBigUint64(2, false);
450
+ msg.snapshotTerm = view.getBigUint64(10, false);
451
+ msg.totalBytes = view.getBigUint64(18, false);
452
+ break;
453
+ }
454
+
455
+ case MessageType.SNAPSHOT_CHUNK: {
456
+ msg.snapshotIndex = view.getBigUint64(0, false);
457
+ msg.offset = view.getBigUint64(8, false);
458
+ const chunkLen = view.getUint32(16, false);
459
+ msg.chunkData = buf.subarray(bodyOffset + 20, bodyOffset + 20 + chunkLen);
460
+ break;
461
+ }
462
+
463
+ case MessageType.SNAPSHOT_END: {
464
+ msg.snapshotIndex = view.getBigUint64(0, false);
465
+ msg.totalChecksum = view.getUint32(8, false);
466
+ break;
467
+ }
468
+
469
+ case MessageType.SNAPSHOT_RESPONSE: {
470
+ msg.followerId = view.getUint16(0, false);
471
+ msg.success = view.getUint8(2) === 1;
472
+ msg.lastIndex = view.getBigUint64(3, false);
473
+ break;
474
+ }
475
+
476
+ case MessageType.TIMEOUT_NOW: {
477
+ msg.leaderId = view.getUint16(0, false);
478
+ break;
479
+ }
480
+
481
+ case MessageType.BATCH_DATA: {
482
+ msg.generationId = view.getUint32(0, false);
483
+ msg.firstSequence = view.getBigUint64(4, false);
484
+ msg.lastSequence = view.getBigUint64(12, false);
485
+ msg.entryCount = view.getUint32(20, false);
486
+ const numBuffers = view.getUint16(24, false);
487
+
488
+ const bufferLens = new Array(numBuffers);
489
+ let off = 26;
490
+ for (let i = 0; i < numBuffers; i++) {
491
+ bufferLens[i] = view.getUint32(off, false);
492
+ off += 4;
493
+ }
494
+
495
+ const totalMetaLen = 26 + numBuffers * 4;
496
+ let payloadOff = bodyOffset + totalMetaLen;
497
+ const bufs = new Array(numBuffers);
498
+ for (let i = 0; i < numBuffers; i++) {
499
+ const len = bufferLens[i];
500
+ bufs[i] = buf.subarray(payloadOff, payloadOff + len);
501
+ payloadOff += len;
502
+ }
503
+ msg.buffers = bufs;
504
+ break;
505
+ }
506
+
507
+ case MessageType.BATCH_ACK: {
508
+ msg.generationId = view.getUint32(0, false);
509
+ msg.sequence = view.getBigUint64(4, false);
510
+ msg.bytesReceived = view.getBigUint64(12, false);
511
+ msg.status = view.getUint8(20);
512
+ break;
513
+ }
514
+
515
+ case MessageType.SESSION_HANDSHAKE:
516
+ case MessageType.SESSION_ACK: {
517
+ msg.sessionId = view.getUint32(0, false);
518
+ const dataLen = view.getUint32(4, false);
519
+ msg.payload = buf.subarray(bodyOffset + 8, bodyOffset + 8 + dataLen);
520
+ break;
521
+ }
522
+
523
+ }
524
+
525
+ return msg;
526
+ }
527
+
528
+ /**
529
+ * Flattens scatter/gather chunks into a single contiguous Uint8Array if needed
530
+ * (e.g. for streams that cannot accept scatter/gather buffers).
531
+ *
532
+ * @param {Uint8Array[]} chunks
533
+ * @param {WireMetrics} [metrics=globalWireMetrics]
534
+ * @returns {Uint8Array}
535
+ */
536
+ export function flattenChunks(chunks, metrics = globalWireMetrics) {
537
+ let totalLength = 0;
538
+ for (let i = 0; i < chunks.length; i++) {
539
+ totalLength += chunks[i].byteLength;
540
+ }
541
+ const result = new Uint8Array(totalLength);
542
+ let offset = 0;
543
+ for (let i = 0; i < chunks.length; i++) {
544
+ const chunk = chunks[i];
545
+ result.set(chunk, offset);
546
+ offset += chunk.byteLength;
547
+ metrics.copiedBytes += chunk.byteLength;
548
+ }
549
+ metrics.allocatedBytes += totalLength;
550
+ return result;
551
+ }