@blockcast/fec-worker 0.3.0 → 0.4.0-main.1d3ea1cc8ff2
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 +15 -20
- package/dist/block-order.d.ts +3 -19
- package/dist/block-order.d.ts.map +1 -1
- package/dist/block-order.js +3 -33
- package/dist/block-order.js.map +1 -1
- package/dist/fec-worker-client.d.ts +6 -16
- package/dist/fec-worker-client.d.ts.map +1 -1
- package/dist/fec-worker-client.js +10 -38
- package/dist/fec-worker-client.js.map +1 -1
- package/dist/fec-worker-types.d.ts +48 -102
- package/dist/fec-worker-types.d.ts.map +1 -1
- package/dist/fec-worker-types.js +2 -2
- package/dist/fec-worker.js +39 -194
- package/dist/fec-worker.js.map +2 -2
- package/package.json +4 -7
- package/src/block-order.ts +3 -37
- package/src/fec-worker-client.ts +8 -40
- package/src/fec-worker-types.ts +48 -102
- package/src/fec-worker.ts +71 -466
package/dist/fec-worker.js
CHANGED
|
@@ -10,20 +10,6 @@ function toOwnedArrayBuffer(view) {
|
|
|
10
10
|
// src/block-order.ts
|
|
11
11
|
var SPAN = 4294967296;
|
|
12
12
|
var HALF = 2147483648;
|
|
13
|
-
function blockStart(sbn, k) {
|
|
14
|
-
return sbn * k % SPAN;
|
|
15
|
-
}
|
|
16
|
-
function precedingDistance(a, b, k) {
|
|
17
|
-
const distance = (blockStart(b, k) - blockStart(a, k) + SPAN) % SPAN;
|
|
18
|
-
return distance !== 0 && distance < HALF ? distance : null;
|
|
19
|
-
}
|
|
20
|
-
function blockPrecedes(a, b, k) {
|
|
21
|
-
return precedingDistance(a, b, k) !== null;
|
|
22
|
-
}
|
|
23
|
-
function blocksBehind(a, b, k) {
|
|
24
|
-
const distance = precedingDistance(a, b, k);
|
|
25
|
-
return distance === null ? 0 : Math.ceil(distance / k);
|
|
26
|
-
}
|
|
27
13
|
function serialDistance(from, to) {
|
|
28
14
|
return (to - from + SPAN) % SPAN;
|
|
29
15
|
}
|
|
@@ -47,8 +33,6 @@ function isoBlocksBehind(a, b, kMax) {
|
|
|
47
33
|
// src/fec-worker.ts
|
|
48
34
|
var wasmDecoder = null;
|
|
49
35
|
var config = null;
|
|
50
|
-
var altaEnabled = false;
|
|
51
|
-
var altaVerifier = null;
|
|
52
36
|
var snapshotTimer = null;
|
|
53
37
|
var blockUpdatesSubscribed = false;
|
|
54
38
|
var blocks = /* @__PURE__ */ new Map();
|
|
@@ -62,19 +46,12 @@ var SSBG1_LENGTH_FIELD_BYTES = 2;
|
|
|
62
46
|
var MAX_INTERLEAVE_DEPTH = 255;
|
|
63
47
|
var MAX_SYMBOL_SIZE = 65528;
|
|
64
48
|
var MAX_RAPTORQ_SOURCE_SYMBOLS_PER_BLOCK = 56403;
|
|
49
|
+
var MAX_REED_SOLOMON_SOURCE_SYMBOLS_PER_BLOCK = 254;
|
|
65
50
|
var sourceSymbols = 0;
|
|
66
51
|
var repairSymbols = 0;
|
|
67
52
|
var blocksComplete = 0;
|
|
68
53
|
var blocksRecovered = 0;
|
|
69
54
|
var blocksFailed = 0;
|
|
70
|
-
var altaVerified = 0;
|
|
71
|
-
var altaFailed = 0;
|
|
72
|
-
var altaFailedDetails = [];
|
|
73
|
-
var altaPending = 0;
|
|
74
|
-
var altaPendingEvictedCap = 0;
|
|
75
|
-
var altaPendingEvictedTtl = 0;
|
|
76
|
-
var altaError = 0;
|
|
77
|
-
var altaInitFailed = false;
|
|
78
55
|
var relayBlocksVerified = 0;
|
|
79
56
|
var relayBlocksFailed = 0;
|
|
80
57
|
var liveSbn = 0;
|
|
@@ -85,9 +62,6 @@ var sourceDroppedTerminal = 0;
|
|
|
85
62
|
var sourceDroppedDuplicate = 0;
|
|
86
63
|
var sourceUnplacedRetired = 0;
|
|
87
64
|
var recoveryTimes = [];
|
|
88
|
-
var pendingVerify = /* @__PURE__ */ new Map();
|
|
89
|
-
var pendingVerifyCap = 0;
|
|
90
|
-
var pendingVerifyTtlMs = 0;
|
|
91
65
|
function emit(event, transfer) {
|
|
92
66
|
self.postMessage(event, transfer ?? []);
|
|
93
67
|
}
|
|
@@ -106,17 +80,13 @@ function clearBlockTracking() {
|
|
|
106
80
|
acceptedSourceEsis.clear();
|
|
107
81
|
acceptedRepairEsis.clear();
|
|
108
82
|
unplacedSources.clear();
|
|
109
|
-
pendingVerify.clear();
|
|
110
83
|
recoveryTimes.length = 0;
|
|
111
84
|
liveSbn = 0;
|
|
112
85
|
liveSbnKnown = false;
|
|
113
86
|
}
|
|
114
87
|
function isRetiredSbn(sbn) {
|
|
115
88
|
if (config === null || !liveSbnKnown) return false;
|
|
116
|
-
return
|
|
117
|
-
}
|
|
118
|
-
function isIsoProfile() {
|
|
119
|
-
return config?.blockProfile === "iso-ssbg1-v1";
|
|
89
|
+
return isoBlocksBehind(sbn, liveSbn, config.k) > config.interleaveDepth;
|
|
120
90
|
}
|
|
121
91
|
function isoKnownLength(block) {
|
|
122
92
|
return block.ssbLength ?? 1;
|
|
@@ -128,7 +98,7 @@ function findIsoBlock(ssId) {
|
|
|
128
98
|
return void 0;
|
|
129
99
|
}
|
|
130
100
|
function isRetiredBlock(key, block) {
|
|
131
|
-
return
|
|
101
|
+
return isRetiredSbn((key + isoKnownLength(block) - 1) % SS_ID_SPAN);
|
|
132
102
|
}
|
|
133
103
|
function trimBlockRetention() {
|
|
134
104
|
for (const [sbn, block] of blocks) {
|
|
@@ -139,10 +109,7 @@ function trimBlockRetention() {
|
|
|
139
109
|
}
|
|
140
110
|
function advanceMediaHorizon(sbn) {
|
|
141
111
|
if (!config) return;
|
|
142
|
-
if (liveSbnKnown)
|
|
143
|
-
const ahead = config.blockProfile === "iso-ssbg1-v1" ? serialPrecedes(liveSbn, sbn) : blockPrecedes(liveSbn, sbn, config.k);
|
|
144
|
-
if (!ahead) return;
|
|
145
|
-
}
|
|
112
|
+
if (liveSbnKnown && !serialPrecedes(liveSbn, sbn)) return;
|
|
146
113
|
liveSbn = sbn;
|
|
147
114
|
liveSbnKnown = true;
|
|
148
115
|
trimBlockRetention();
|
|
@@ -155,8 +122,7 @@ function markBlockTerminal(block, state) {
|
|
|
155
122
|
}
|
|
156
123
|
function synchronizeDecoderHorizon() {
|
|
157
124
|
if (!wasmDecoder || !liveSbnKnown) return;
|
|
158
|
-
const
|
|
159
|
-
const expired = iso ? wasmDecoder.cleanup_by_ss_start(liveSbn) : wasmDecoder.cleanup_by_sbn(liveSbn);
|
|
125
|
+
const expired = wasmDecoder.cleanup_by_ss_start(liveSbn);
|
|
160
126
|
for (const sbn of expired) {
|
|
161
127
|
const block = blocks.get(sbn);
|
|
162
128
|
if (!block || isTerminalBlock(block)) {
|
|
@@ -167,7 +133,7 @@ function synchronizeDecoderHorizon() {
|
|
|
167
133
|
blocksFailed++;
|
|
168
134
|
emitBlockUpdate(block);
|
|
169
135
|
}
|
|
170
|
-
|
|
136
|
+
retireUnplacedSources();
|
|
171
137
|
trimBlockRetention();
|
|
172
138
|
}
|
|
173
139
|
function retireUnplacedSources() {
|
|
@@ -192,8 +158,9 @@ function getOrCreateBlock(sbn, isoSsbLength) {
|
|
|
192
158
|
repairCount: config.repairCount,
|
|
193
159
|
firstSymbolTs: now,
|
|
194
160
|
lastSymbolTs: now,
|
|
161
|
+
// A positive interleaveMs makes this the catalog completion deadline;
|
|
162
|
+
// zero makes deliveryWindowMs a receiver-local cleanup bound.
|
|
195
163
|
deadlineTs: now + config.deliveryWindowMs,
|
|
196
|
-
altaVerified: 0,
|
|
197
164
|
relayBlockSigValid: null
|
|
198
165
|
};
|
|
199
166
|
if (isoSsbLength !== void 0) block.ssbLength = isoSsbLength;
|
|
@@ -217,7 +184,7 @@ function buildStatsSnapshot() {
|
|
|
217
184
|
const avgRecoveryMs = recoveryTimes.length > 0 ? recoveryTimes.reduce((a, b) => a + b, 0) / recoveryTimes.length : 0;
|
|
218
185
|
const maxRecoveryMs = recoveryTimes.length > 0 ? Math.max(...recoveryTimes) : 0;
|
|
219
186
|
const maxBlocks = Math.max(config.interleaveDepth * 3, 24);
|
|
220
|
-
const blockArray = Array.from(blocks.values()).sort((a, b) =>
|
|
187
|
+
const blockArray = Array.from(blocks.values()).sort((a, b) => serialDistance(a.sbn, liveSbn) - serialDistance(b.sbn, liveSbn)).slice(0, maxBlocks);
|
|
221
188
|
return {
|
|
222
189
|
config,
|
|
223
190
|
sourceSymbols,
|
|
@@ -228,15 +195,6 @@ function buildStatsSnapshot() {
|
|
|
228
195
|
recoveryRate: blocksComplete + blocksRecovered > 0 ? Math.round(
|
|
229
196
|
blocksRecovered / (blocksRecovered + blocksFailed) * 100
|
|
230
197
|
) || 0 : 0,
|
|
231
|
-
altaVerified,
|
|
232
|
-
altaFailed,
|
|
233
|
-
altaPending,
|
|
234
|
-
altaPendingEvictedCap,
|
|
235
|
-
altaPendingEvictedTtl,
|
|
236
|
-
altaError,
|
|
237
|
-
altaInitFailed,
|
|
238
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
239
|
-
altaFailedDetails: altaFailedDetails.slice(),
|
|
240
198
|
relayBlocksVerified,
|
|
241
199
|
relayBlocksFailed,
|
|
242
200
|
liveSbn,
|
|
@@ -248,7 +206,7 @@ function buildStatsSnapshot() {
|
|
|
248
206
|
sourceDroppedRetired,
|
|
249
207
|
sourceDroppedTerminal,
|
|
250
208
|
sourceDroppedDuplicate,
|
|
251
|
-
|
|
209
|
+
sourceUnplacedRetired,
|
|
252
210
|
decoder
|
|
253
211
|
};
|
|
254
212
|
}
|
|
@@ -280,25 +238,19 @@ function emitRecoveredSymbols(sbn, recoveredBlock, block) {
|
|
|
280
238
|
});
|
|
281
239
|
return;
|
|
282
240
|
}
|
|
283
|
-
const iso = config.blockProfile === "iso-ssbg1-v1";
|
|
284
241
|
for (let i = 0; i < ssbLength; i++) {
|
|
285
242
|
const symbol = recoveredBlock.subarray(i * symbolSize, (i + 1) * symbolSize);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
243
|
+
const invalid = invalidSsbg1Symbol(symbol, symbolSize);
|
|
244
|
+
if (invalid !== null) {
|
|
245
|
+
emit({
|
|
246
|
+
type: "error",
|
|
247
|
+
code: "recovered-ssbg1-symbol-invalid",
|
|
248
|
+
message: `[FecWorker] Dropping recovered ssbg_mode1 symbol (ssStart=${sbn}, esi=${i}, SSB_length=${ssbLength}): ${invalid}`
|
|
249
|
+
});
|
|
250
|
+
continue;
|
|
296
251
|
}
|
|
297
|
-
const recoveredAltaVerified = null;
|
|
298
|
-
if (altaEnabled) altaPending++;
|
|
299
252
|
const buf = toOwnedArrayBuffer(symbol);
|
|
300
253
|
const meta = {
|
|
301
|
-
altaVerified: recoveredAltaVerified,
|
|
302
254
|
recovered: true,
|
|
303
255
|
sbn,
|
|
304
256
|
esi: i,
|
|
@@ -411,7 +363,6 @@ function feedIsoSource(cmd, view, decoder, cfg) {
|
|
|
411
363
|
advanceMediaHorizon(cmd.ssId);
|
|
412
364
|
unplacedSources.set(cmd.ssId, performance.now());
|
|
413
365
|
sourceSymbols++;
|
|
414
|
-
if (altaEnabled) altaPending++;
|
|
415
366
|
if (result2 && result2.byteLength > 0) {
|
|
416
367
|
emit({
|
|
417
368
|
type: "error",
|
|
@@ -442,7 +393,6 @@ function feedIsoSource(cmd, view, decoder, cfg) {
|
|
|
442
393
|
block.sourceReceived++;
|
|
443
394
|
block.lastSymbolTs = performance.now();
|
|
444
395
|
sourceSymbols++;
|
|
445
|
-
if (altaEnabled) altaPending++;
|
|
446
396
|
settleSourceArrival(key, block, result);
|
|
447
397
|
}
|
|
448
398
|
function findOverlappingIsoBlock(ssStart, ssbLength) {
|
|
@@ -510,22 +460,23 @@ async function handleConfigure(cmd) {
|
|
|
510
460
|
const blockProfile = cmd.config.blockProfile;
|
|
511
461
|
if (blockProfile === void 0) {
|
|
512
462
|
throw new Error(
|
|
513
|
-
'[FecWorker] configure: blockProfile is required: name the FEC block profile ("
|
|
463
|
+
'[FecWorker] configure: blockProfile is required: name the FEC block profile ("iso-ssbg1-v1")'
|
|
514
464
|
);
|
|
515
465
|
}
|
|
516
|
-
if (blockProfile !== "
|
|
466
|
+
if (blockProfile !== "iso-ssbg1-v1") {
|
|
517
467
|
throw new Error(
|
|
518
|
-
`[FecWorker] configure: blockProfile must be "
|
|
468
|
+
`[FecWorker] configure: blockProfile must be "iso-ssbg1-v1" (got ${JSON.stringify(blockProfile)})`
|
|
519
469
|
);
|
|
520
470
|
}
|
|
471
|
+
const algorithm = cmd.config.algorithm === "reed-solomon" ? "reed-solomon" : "raptorq";
|
|
521
472
|
if (!Number.isFinite(cmd.config.interleaveDepth) || !Number.isInteger(cmd.config.interleaveDepth) || cmd.config.interleaveDepth <= 0 || cmd.config.interleaveDepth > MAX_INTERLEAVE_DEPTH) {
|
|
522
473
|
throw new Error(
|
|
523
474
|
`[FecWorker] configure: interleaveDepth must be an integer in 1..${MAX_INTERLEAVE_DEPTH} (got ${cmd.config.interleaveDepth})`
|
|
524
475
|
);
|
|
525
476
|
}
|
|
526
|
-
if (!Number.isFinite(cmd.config.interleaveMs) || cmd.config.interleaveMs
|
|
477
|
+
if (!Number.isFinite(cmd.config.interleaveMs) || !Number.isSafeInteger(cmd.config.interleaveMs) || cmd.config.interleaveMs < 0) {
|
|
527
478
|
throw new Error(
|
|
528
|
-
`[FecWorker] configure: interleaveMs must be finite
|
|
479
|
+
`[FecWorker] configure: interleaveMs must be a finite non-negative integer (got ${cmd.config.interleaveMs})`
|
|
529
480
|
);
|
|
530
481
|
}
|
|
531
482
|
if (!Number.isFinite(cmd.config.deliveryWindowMs) || cmd.config.deliveryWindowMs <= 0) {
|
|
@@ -533,14 +484,13 @@ async function handleConfigure(cmd) {
|
|
|
533
484
|
`[FecWorker] configure: deliveryWindowMs must be > 0 (got ${cmd.config.deliveryWindowMs})`
|
|
534
485
|
);
|
|
535
486
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
);
|
|
487
|
+
const maxK = algorithm === "raptorq" ? MAX_RAPTORQ_SOURCE_SYMBOLS_PER_BLOCK : MAX_REED_SOLOMON_SOURCE_SYMBOLS_PER_BLOCK;
|
|
488
|
+
if (!Number.isInteger(cmd.config.k) || cmd.config.k <= 0 || cmd.config.k > maxK) {
|
|
489
|
+
throw new Error(`[FecWorker] configure: ${algorithm} k must be an integer in 1..${maxK} (got ${cmd.config.k})`);
|
|
540
490
|
}
|
|
541
|
-
if (!Number.isInteger(cmd.config.symbolSize) || cmd.config.symbolSize <= 0 || cmd.config.symbolSize > MAX_SYMBOL_SIZE || cmd.config.symbolSize % 8 !== 0) {
|
|
491
|
+
if (!Number.isInteger(cmd.config.symbolSize) || cmd.config.symbolSize <= 0 || cmd.config.symbolSize > MAX_SYMBOL_SIZE || algorithm === "raptorq" && cmd.config.symbolSize % 8 !== 0) {
|
|
542
492
|
throw new Error(
|
|
543
|
-
`[FecWorker] configure: symbolSize must be an 8-byte-aligned integer in 8..${MAX_SYMBOL_SIZE} (got ${cmd.config.symbolSize})`
|
|
493
|
+
`[FecWorker] configure: ${algorithm} symbolSize must be ${algorithm === "raptorq" ? "an 8-byte-aligned integer in 8" : "an integer in 1"}..${MAX_SYMBOL_SIZE} (got ${cmd.config.symbolSize})`
|
|
544
494
|
);
|
|
545
495
|
}
|
|
546
496
|
const wasm = resolveWasmBindings();
|
|
@@ -548,32 +498,15 @@ async function handleConfigure(cmd) {
|
|
|
548
498
|
wasmDecoder?.free();
|
|
549
499
|
wasmDecoder = null;
|
|
550
500
|
clearBlockTracking();
|
|
551
|
-
const decoder = new wasm.MmtFecDecoder(cmd.config.interleaveDepth, blockProfile);
|
|
501
|
+
const decoder = new wasm.MmtFecDecoder(cmd.config.interleaveDepth, blockProfile, algorithm);
|
|
552
502
|
verifyDecoderProfile(decoder, blockProfile);
|
|
503
|
+
const decoderAlgorithm = decoder.get_algorithm?.() ?? "raptorq";
|
|
504
|
+
if (decoderAlgorithm !== algorithm) {
|
|
505
|
+
decoder.free();
|
|
506
|
+
throw new Error(`[FecWorker] configure: decoder runs ${JSON.stringify(decoderAlgorithm)}, not the configured "${algorithm}"`);
|
|
507
|
+
}
|
|
553
508
|
wasmDecoder = decoder;
|
|
554
509
|
config = cmd.config;
|
|
555
|
-
altaEnabled = !!cmd.altaPublicKey;
|
|
556
|
-
pendingVerifyCap = Math.ceil(config.interleaveDepth * 1.5);
|
|
557
|
-
pendingVerifyTtlMs = Math.ceil(config.interleaveMs * config.k * 1.5);
|
|
558
|
-
altaVerifier = null;
|
|
559
|
-
altaInitFailed = false;
|
|
560
|
-
if (cmd.altaPublicKey && cmd.altaWasmModule) {
|
|
561
|
-
try {
|
|
562
|
-
const altaGlueUrl = "/wasm/alta/alta_rs.js";
|
|
563
|
-
const altaGlue = await import(
|
|
564
|
-
/* webpackIgnore: true */
|
|
565
|
-
altaGlueUrl
|
|
566
|
-
);
|
|
567
|
-
altaGlue.initSync({ module: cmd.altaWasmModule });
|
|
568
|
-
const keyBytes = new Uint8Array(cmd.altaPublicKey);
|
|
569
|
-
altaVerifier = new altaGlue.JsAltaVerifier(keyBytes);
|
|
570
|
-
} catch (e) {
|
|
571
|
-
console.warn("[FecWorker] ALTA WASM init failed, verification disabled:", e);
|
|
572
|
-
altaVerifier = null;
|
|
573
|
-
altaEnabled = false;
|
|
574
|
-
altaInitFailed = true;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
510
|
wasmDecoder.configure_params(config.k, config.symbolSize);
|
|
578
511
|
emit({ type: "capabilities", commands: ["feedSymbols", "subscribeBlockUpdates"] });
|
|
579
512
|
startSnapshotTimer();
|
|
@@ -589,40 +522,7 @@ function handleFeedSource(cmd) {
|
|
|
589
522
|
if (!isUint32(cmd.ssId) || !(cmd.data instanceof ArrayBuffer)) {
|
|
590
523
|
throw new Error("[FecWorker] feedSource: invalid source symbol");
|
|
591
524
|
}
|
|
592
|
-
|
|
593
|
-
if (config.blockProfile === "iso-ssbg1-v1") {
|
|
594
|
-
feedIsoSource(cmd, view, wasmDecoder, config);
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
const sbn = config.k > 0 ? Math.floor(cmd.ssId / config.k) : cmd.ssId;
|
|
598
|
-
const esi = cmd.ssId % config.k;
|
|
599
|
-
if (isRetiredSbn(sbn)) {
|
|
600
|
-
sourceDroppedRetired++;
|
|
601
|
-
return;
|
|
602
|
-
}
|
|
603
|
-
const existingBlock = blocks.get(sbn);
|
|
604
|
-
if (existingBlock && isTerminalBlock(existingBlock)) {
|
|
605
|
-
sourceDroppedTerminal++;
|
|
606
|
-
return;
|
|
607
|
-
}
|
|
608
|
-
if (acceptedSourceEsis.get(sbn)?.has(esi)) {
|
|
609
|
-
sourceDroppedDuplicate++;
|
|
610
|
-
return;
|
|
611
|
-
}
|
|
612
|
-
const result = wasmDecoder.add_source(cmd.ssId, view, cmd.ts);
|
|
613
|
-
advanceMediaHorizon(sbn);
|
|
614
|
-
let sourceEsis = acceptedSourceEsis.get(sbn);
|
|
615
|
-
if (!sourceEsis) {
|
|
616
|
-
sourceEsis = /* @__PURE__ */ new Set();
|
|
617
|
-
acceptedSourceEsis.set(sbn, sourceEsis);
|
|
618
|
-
}
|
|
619
|
-
sourceEsis.add(esi);
|
|
620
|
-
const block = getOrCreateBlock(sbn);
|
|
621
|
-
block.sourceReceived++;
|
|
622
|
-
block.lastSymbolTs = performance.now();
|
|
623
|
-
sourceSymbols++;
|
|
624
|
-
if (altaEnabled) altaPending++;
|
|
625
|
-
settleSourceArrival(sbn, block, result);
|
|
525
|
+
feedIsoSource(cmd, new Uint8Array(cmd.data), wasmDecoder, config);
|
|
626
526
|
}
|
|
627
527
|
function handleFeedRepair(cmd) {
|
|
628
528
|
if (!wasmDecoder) {
|
|
@@ -631,8 +531,7 @@ function handleFeedRepair(cmd) {
|
|
|
631
531
|
if (!config) {
|
|
632
532
|
throw new Error("[FecWorker] feedRepair: config missing");
|
|
633
533
|
}
|
|
634
|
-
|
|
635
|
-
if (!isUint32(cmd.ssStart) || !isUint32(cmd.ssbLength) || cmd.ssbLength <= 0 || cmd.ssbLength > config.k || !iso && cmd.ssStart % config.k !== 0 || !isUint32(cmd.rsId) || (iso ? cmd.ssbLength : config.k) + cmd.rsId > UINT24_MAX || !(cmd.data instanceof ArrayBuffer)) {
|
|
534
|
+
if (!isUint32(cmd.ssStart) || !isUint32(cmd.ssbLength) || cmd.ssbLength <= 0 || cmd.ssbLength > config.k || !isUint32(cmd.rsId) || cmd.ssbLength + cmd.rsId > UINT24_MAX || !(cmd.data instanceof ArrayBuffer)) {
|
|
636
535
|
throw new Error("[FecWorker] feedRepair: invalid repair symbol");
|
|
637
536
|
}
|
|
638
537
|
const view = new Uint8Array(cmd.data);
|
|
@@ -647,44 +546,7 @@ function handleFeedRepair(cmd) {
|
|
|
647
546
|
}
|
|
648
547
|
return;
|
|
649
548
|
}
|
|
650
|
-
|
|
651
|
-
feedIsoRepair(cmd, view, wasmDecoder);
|
|
652
|
-
return;
|
|
653
|
-
}
|
|
654
|
-
const sbn = Math.floor(cmd.ssStart / config.k);
|
|
655
|
-
const repairEsi = config.k + cmd.rsId;
|
|
656
|
-
if (isRetiredSbn(sbn)) return;
|
|
657
|
-
const existingBlock = blocks.get(sbn);
|
|
658
|
-
if (existingBlock && isTerminalBlock(existingBlock)) return;
|
|
659
|
-
if (acceptedRepairEsis.get(sbn)?.has(repairEsi)) return;
|
|
660
|
-
if (existingBlock?.ssbLength !== void 0 && existingBlock.ssbLength !== cmd.ssbLength) {
|
|
661
|
-
emit({
|
|
662
|
-
type: "error",
|
|
663
|
-
code: "repair-ssb-length-conflict",
|
|
664
|
-
message: `[FecWorker] Dropping repair symbol whose SSB_length ${cmd.ssbLength} conflicts with ${existingBlock.ssbLength} (sbn=${sbn})`
|
|
665
|
-
});
|
|
666
|
-
return;
|
|
667
|
-
}
|
|
668
|
-
const result = wasmDecoder.add_repair(
|
|
669
|
-
cmd.ssStart,
|
|
670
|
-
cmd.ssbLength,
|
|
671
|
-
cmd.rsId,
|
|
672
|
-
view,
|
|
673
|
-
cmd.ts
|
|
674
|
-
);
|
|
675
|
-
advanceMediaHorizon(sbn);
|
|
676
|
-
let repairEsis = acceptedRepairEsis.get(sbn);
|
|
677
|
-
if (!repairEsis) {
|
|
678
|
-
repairEsis = /* @__PURE__ */ new Set();
|
|
679
|
-
acceptedRepairEsis.set(sbn, repairEsis);
|
|
680
|
-
}
|
|
681
|
-
repairEsis.add(repairEsi);
|
|
682
|
-
const block = getOrCreateBlock(sbn);
|
|
683
|
-
block.ssbLength = cmd.ssbLength;
|
|
684
|
-
block.repairReceived++;
|
|
685
|
-
block.lastSymbolTs = performance.now();
|
|
686
|
-
repairSymbols++;
|
|
687
|
-
settleRepairArrival(sbn, block, cmd.ssbLength, result);
|
|
549
|
+
feedIsoRepair(cmd, view, wasmDecoder);
|
|
688
550
|
}
|
|
689
551
|
function handleRelayBlockSig(cmd) {
|
|
690
552
|
const block = blocks.get(cmd.sbn);
|
|
@@ -713,7 +575,7 @@ function handleCleanup(cmd) {
|
|
|
713
575
|
continue;
|
|
714
576
|
}
|
|
715
577
|
currentBlock ??= getOrCreateBlock(sbn);
|
|
716
|
-
const authoredLength =
|
|
578
|
+
const authoredLength = currentBlock.ssbLength;
|
|
717
579
|
if (authoredLength !== void 0 && currentBlock.sourceReceived >= authoredLength) {
|
|
718
580
|
markBlockTerminal(currentBlock, "complete");
|
|
719
581
|
blocksComplete++;
|
|
@@ -732,20 +594,10 @@ function handleDispose() {
|
|
|
732
594
|
clearInterval(snapshotTimer);
|
|
733
595
|
snapshotTimer = null;
|
|
734
596
|
}
|
|
735
|
-
if (altaVerifier) {
|
|
736
|
-
try {
|
|
737
|
-
altaVerifier.free();
|
|
738
|
-
} catch (_e) {
|
|
739
|
-
}
|
|
740
|
-
altaVerifier = null;
|
|
741
|
-
}
|
|
742
597
|
if (wasmDecoder) {
|
|
743
598
|
wasmDecoder.free();
|
|
744
599
|
wasmDecoder = null;
|
|
745
600
|
}
|
|
746
|
-
pendingVerify.clear();
|
|
747
|
-
pendingVerifyCap = 0;
|
|
748
|
-
pendingVerifyTtlMs = 0;
|
|
749
601
|
config = null;
|
|
750
602
|
clearBlockTracking();
|
|
751
603
|
}
|
|
@@ -767,13 +619,6 @@ function handleReset() {
|
|
|
767
619
|
blocksComplete = 0;
|
|
768
620
|
blocksRecovered = 0;
|
|
769
621
|
blocksFailed = 0;
|
|
770
|
-
altaVerified = 0;
|
|
771
|
-
altaFailed = 0;
|
|
772
|
-
altaFailedDetails.length = 0;
|
|
773
|
-
altaPending = 0;
|
|
774
|
-
altaPendingEvictedCap = 0;
|
|
775
|
-
altaPendingEvictedTtl = 0;
|
|
776
|
-
altaError = 0;
|
|
777
622
|
relayBlocksVerified = 0;
|
|
778
623
|
relayBlocksFailed = 0;
|
|
779
624
|
repairSymbolSizeMismatch = 0;
|