@blockcast/fec-worker 0.3.0-main.d9354616fdf2 → 0.3.0-main.e4bf1a9cc227
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 +10 -8
- 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 +4 -3
- package/dist/fec-worker-client.d.ts.map +1 -1
- package/dist/fec-worker-client.js +4 -3
- package/dist/fec-worker-client.js.map +1 -1
- package/dist/fec-worker-types.d.ts +45 -41
- package/dist/fec-worker-types.d.ts.map +1 -1
- package/dist/fec-worker.js +28 -120
- package/dist/fec-worker.js.map +2 -2
- package/package.json +2 -2
- package/src/block-order.ts +3 -37
- package/src/fec-worker-client.ts +4 -3
- package/src/fec-worker-types.ts +45 -41
- package/src/fec-worker.ts +53 -162
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
|
}
|
|
@@ -113,10 +99,7 @@ function clearBlockTracking() {
|
|
|
113
99
|
}
|
|
114
100
|
function isRetiredSbn(sbn) {
|
|
115
101
|
if (config === null || !liveSbnKnown) return false;
|
|
116
|
-
return
|
|
117
|
-
}
|
|
118
|
-
function isIsoProfile() {
|
|
119
|
-
return config?.blockProfile === "iso-ssbg1-v1";
|
|
102
|
+
return isoBlocksBehind(sbn, liveSbn, config.k) > config.interleaveDepth;
|
|
120
103
|
}
|
|
121
104
|
function isoKnownLength(block) {
|
|
122
105
|
return block.ssbLength ?? 1;
|
|
@@ -128,7 +111,7 @@ function findIsoBlock(ssId) {
|
|
|
128
111
|
return void 0;
|
|
129
112
|
}
|
|
130
113
|
function isRetiredBlock(key, block) {
|
|
131
|
-
return
|
|
114
|
+
return isRetiredSbn((key + isoKnownLength(block) - 1) % SS_ID_SPAN);
|
|
132
115
|
}
|
|
133
116
|
function trimBlockRetention() {
|
|
134
117
|
for (const [sbn, block] of blocks) {
|
|
@@ -139,10 +122,7 @@ function trimBlockRetention() {
|
|
|
139
122
|
}
|
|
140
123
|
function advanceMediaHorizon(sbn) {
|
|
141
124
|
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
|
-
}
|
|
125
|
+
if (liveSbnKnown && !serialPrecedes(liveSbn, sbn)) return;
|
|
146
126
|
liveSbn = sbn;
|
|
147
127
|
liveSbnKnown = true;
|
|
148
128
|
trimBlockRetention();
|
|
@@ -155,8 +135,7 @@ function markBlockTerminal(block, state) {
|
|
|
155
135
|
}
|
|
156
136
|
function synchronizeDecoderHorizon() {
|
|
157
137
|
if (!wasmDecoder || !liveSbnKnown) return;
|
|
158
|
-
const
|
|
159
|
-
const expired = iso ? wasmDecoder.cleanup_by_ss_start(liveSbn) : wasmDecoder.cleanup_by_sbn(liveSbn);
|
|
138
|
+
const expired = wasmDecoder.cleanup_by_ss_start(liveSbn);
|
|
160
139
|
for (const sbn of expired) {
|
|
161
140
|
const block = blocks.get(sbn);
|
|
162
141
|
if (!block || isTerminalBlock(block)) {
|
|
@@ -167,7 +146,7 @@ function synchronizeDecoderHorizon() {
|
|
|
167
146
|
blocksFailed++;
|
|
168
147
|
emitBlockUpdate(block);
|
|
169
148
|
}
|
|
170
|
-
|
|
149
|
+
retireUnplacedSources();
|
|
171
150
|
trimBlockRetention();
|
|
172
151
|
}
|
|
173
152
|
function retireUnplacedSources() {
|
|
@@ -192,6 +171,8 @@ function getOrCreateBlock(sbn, isoSsbLength) {
|
|
|
192
171
|
repairCount: config.repairCount,
|
|
193
172
|
firstSymbolTs: now,
|
|
194
173
|
lastSymbolTs: now,
|
|
174
|
+
// A positive interleaveMs makes this the catalog completion deadline;
|
|
175
|
+
// zero makes deliveryWindowMs a receiver-local cleanup bound.
|
|
195
176
|
deadlineTs: now + config.deliveryWindowMs,
|
|
196
177
|
altaVerified: 0,
|
|
197
178
|
relayBlockSigValid: null
|
|
@@ -217,7 +198,7 @@ function buildStatsSnapshot() {
|
|
|
217
198
|
const avgRecoveryMs = recoveryTimes.length > 0 ? recoveryTimes.reduce((a, b) => a + b, 0) / recoveryTimes.length : 0;
|
|
218
199
|
const maxRecoveryMs = recoveryTimes.length > 0 ? Math.max(...recoveryTimes) : 0;
|
|
219
200
|
const maxBlocks = Math.max(config.interleaveDepth * 3, 24);
|
|
220
|
-
const blockArray = Array.from(blocks.values()).sort((a, b) =>
|
|
201
|
+
const blockArray = Array.from(blocks.values()).sort((a, b) => serialDistance(a.sbn, liveSbn) - serialDistance(b.sbn, liveSbn)).slice(0, maxBlocks);
|
|
221
202
|
return {
|
|
222
203
|
config,
|
|
223
204
|
sourceSymbols,
|
|
@@ -248,7 +229,7 @@ function buildStatsSnapshot() {
|
|
|
248
229
|
sourceDroppedRetired,
|
|
249
230
|
sourceDroppedTerminal,
|
|
250
231
|
sourceDroppedDuplicate,
|
|
251
|
-
|
|
232
|
+
sourceUnplacedRetired,
|
|
252
233
|
decoder
|
|
253
234
|
};
|
|
254
235
|
}
|
|
@@ -280,19 +261,16 @@ function emitRecoveredSymbols(sbn, recoveredBlock, block) {
|
|
|
280
261
|
});
|
|
281
262
|
return;
|
|
282
263
|
}
|
|
283
|
-
const iso = config.blockProfile === "iso-ssbg1-v1";
|
|
284
264
|
for (let i = 0; i < ssbLength; i++) {
|
|
285
265
|
const symbol = recoveredBlock.subarray(i * symbolSize, (i + 1) * symbolSize);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
266
|
+
const invalid = invalidSsbg1Symbol(symbol, symbolSize);
|
|
267
|
+
if (invalid !== null) {
|
|
268
|
+
emit({
|
|
269
|
+
type: "error",
|
|
270
|
+
code: "recovered-ssbg1-symbol-invalid",
|
|
271
|
+
message: `[FecWorker] Dropping recovered ssbg_mode1 symbol (ssStart=${sbn}, esi=${i}, SSB_length=${ssbLength}): ${invalid}`
|
|
272
|
+
});
|
|
273
|
+
continue;
|
|
296
274
|
}
|
|
297
275
|
const recoveredAltaVerified = null;
|
|
298
276
|
if (altaEnabled) altaPending++;
|
|
@@ -510,12 +488,12 @@ async function handleConfigure(cmd) {
|
|
|
510
488
|
const blockProfile = cmd.config.blockProfile;
|
|
511
489
|
if (blockProfile === void 0) {
|
|
512
490
|
throw new Error(
|
|
513
|
-
'[FecWorker] configure: blockProfile is required: name the FEC block profile ("
|
|
491
|
+
'[FecWorker] configure: blockProfile is required: name the FEC block profile ("iso-ssbg1-v1")'
|
|
514
492
|
);
|
|
515
493
|
}
|
|
516
|
-
if (blockProfile !== "
|
|
494
|
+
if (blockProfile !== "iso-ssbg1-v1") {
|
|
517
495
|
throw new Error(
|
|
518
|
-
`[FecWorker] configure: blockProfile must be "
|
|
496
|
+
`[FecWorker] configure: blockProfile must be "iso-ssbg1-v1" (got ${JSON.stringify(blockProfile)})`
|
|
519
497
|
);
|
|
520
498
|
}
|
|
521
499
|
if (!Number.isFinite(cmd.config.interleaveDepth) || !Number.isInteger(cmd.config.interleaveDepth) || cmd.config.interleaveDepth <= 0 || cmd.config.interleaveDepth > MAX_INTERLEAVE_DEPTH) {
|
|
@@ -523,9 +501,9 @@ async function handleConfigure(cmd) {
|
|
|
523
501
|
`[FecWorker] configure: interleaveDepth must be an integer in 1..${MAX_INTERLEAVE_DEPTH} (got ${cmd.config.interleaveDepth})`
|
|
524
502
|
);
|
|
525
503
|
}
|
|
526
|
-
if (!Number.isFinite(cmd.config.interleaveMs) || cmd.config.interleaveMs
|
|
504
|
+
if (!Number.isFinite(cmd.config.interleaveMs) || !Number.isSafeInteger(cmd.config.interleaveMs) || cmd.config.interleaveMs < 0) {
|
|
527
505
|
throw new Error(
|
|
528
|
-
`[FecWorker] configure: interleaveMs must be finite
|
|
506
|
+
`[FecWorker] configure: interleaveMs must be a finite non-negative integer (got ${cmd.config.interleaveMs})`
|
|
529
507
|
);
|
|
530
508
|
}
|
|
531
509
|
if (!Number.isFinite(cmd.config.deliveryWindowMs) || cmd.config.deliveryWindowMs <= 0) {
|
|
@@ -554,7 +532,8 @@ async function handleConfigure(cmd) {
|
|
|
554
532
|
config = cmd.config;
|
|
555
533
|
altaEnabled = !!cmd.altaPublicKey;
|
|
556
534
|
pendingVerifyCap = Math.ceil(config.interleaveDepth * 1.5);
|
|
557
|
-
|
|
535
|
+
const verifyWindowMs = config.interleaveMs > 0 ? config.interleaveMs * config.k : config.deliveryWindowMs;
|
|
536
|
+
pendingVerifyTtlMs = Math.ceil(verifyWindowMs * 1.5);
|
|
558
537
|
altaVerifier = null;
|
|
559
538
|
altaInitFailed = false;
|
|
560
539
|
if (cmd.altaPublicKey && cmd.altaWasmModule) {
|
|
@@ -589,40 +568,7 @@ function handleFeedSource(cmd) {
|
|
|
589
568
|
if (!isUint32(cmd.ssId) || !(cmd.data instanceof ArrayBuffer)) {
|
|
590
569
|
throw new Error("[FecWorker] feedSource: invalid source symbol");
|
|
591
570
|
}
|
|
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);
|
|
571
|
+
feedIsoSource(cmd, new Uint8Array(cmd.data), wasmDecoder, config);
|
|
626
572
|
}
|
|
627
573
|
function handleFeedRepair(cmd) {
|
|
628
574
|
if (!wasmDecoder) {
|
|
@@ -631,8 +577,7 @@ function handleFeedRepair(cmd) {
|
|
|
631
577
|
if (!config) {
|
|
632
578
|
throw new Error("[FecWorker] feedRepair: config missing");
|
|
633
579
|
}
|
|
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)) {
|
|
580
|
+
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
581
|
throw new Error("[FecWorker] feedRepair: invalid repair symbol");
|
|
637
582
|
}
|
|
638
583
|
const view = new Uint8Array(cmd.data);
|
|
@@ -647,44 +592,7 @@ function handleFeedRepair(cmd) {
|
|
|
647
592
|
}
|
|
648
593
|
return;
|
|
649
594
|
}
|
|
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);
|
|
595
|
+
feedIsoRepair(cmd, view, wasmDecoder);
|
|
688
596
|
}
|
|
689
597
|
function handleRelayBlockSig(cmd) {
|
|
690
598
|
const block = blocks.get(cmd.sbn);
|
|
@@ -713,7 +621,7 @@ function handleCleanup(cmd) {
|
|
|
713
621
|
continue;
|
|
714
622
|
}
|
|
715
623
|
currentBlock ??= getOrCreateBlock(sbn);
|
|
716
|
-
const authoredLength =
|
|
624
|
+
const authoredLength = currentBlock.ssbLength;
|
|
717
625
|
if (authoredLength !== void 0 && currentBlock.sourceReceived >= authoredLength) {
|
|
718
626
|
markBlockTerminal(currentBlock, "complete");
|
|
719
627
|
blocksComplete++;
|