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.
- package/README.md +171 -0
- package/bench/bench-batch-curve.js +121 -0
- package/bench/bench-event-loop.js +91 -0
- package/bench/bench-failover.js +172 -0
- package/bench/bench-memory.js +107 -0
- package/bench/bench-partition.js +134 -0
- package/bench/bench-pipeline.js +94 -0
- package/bench/bench-recovery.js +134 -0
- package/bench/bench-replication.js +107 -0
- package/bench/bench-single-node.js +77 -0
- package/bench/bench-slow-follower.js +96 -0
- package/bench/harness.js +87 -0
- package/bench/run-all.js +83 -0
- package/bench-results.json +598 -0
- package/index.js +30 -0
- package/package.json +24 -0
- package/src/core/engine.js +907 -0
- package/src/core/invariants.js +146 -0
- package/src/node/raptiye.js +296 -0
- package/src/node/stats.js +82 -0
- package/src/protocol/checksum.js +66 -0
- package/src/protocol/wire.js +551 -0
- package/src/replication/pipeline.js +235 -0
- package/src/sim/cluster.js +316 -0
- package/src/sim/prng.js +52 -0
- package/src/sim/virtual-clock.js +82 -0
- package/src/storage/file-log.js +394 -0
- package/src/storage/interface.js +113 -0
- package/src/storage/memory-log.js +183 -0
- package/src/transport/interface.js +47 -0
- package/src/transport/memory-transport.js +67 -0
- package/src/transport/tcp-transport.js +200 -0
- package/src/types.js +87 -0
- package/test/chaos/chaos.test.js +112 -0
- package/test/integration/cluster.test.js +192 -0
- package/test/integration/node.test.js +78 -0
- package/test/integration/replication-pipeline.test.js +45 -0
- package/test/unit/core.test.js +163 -0
- package/test/unit/protocol.test.js +181 -0
- package/test/unit/storage.test.js +127 -0
- 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
|
+
}
|