@blockcast/fec-worker 0.3.0 → 0.4.0-main.26d61a540c44

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.
@@ -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();
@@ -67,14 +51,6 @@ var repairSymbols = 0;
67
51
  var blocksComplete = 0;
68
52
  var blocksRecovered = 0;
69
53
  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
54
  var relayBlocksVerified = 0;
79
55
  var relayBlocksFailed = 0;
80
56
  var liveSbn = 0;
@@ -85,9 +61,6 @@ var sourceDroppedTerminal = 0;
85
61
  var sourceDroppedDuplicate = 0;
86
62
  var sourceUnplacedRetired = 0;
87
63
  var recoveryTimes = [];
88
- var pendingVerify = /* @__PURE__ */ new Map();
89
- var pendingVerifyCap = 0;
90
- var pendingVerifyTtlMs = 0;
91
64
  function emit(event, transfer) {
92
65
  self.postMessage(event, transfer ?? []);
93
66
  }
@@ -106,17 +79,13 @@ function clearBlockTracking() {
106
79
  acceptedSourceEsis.clear();
107
80
  acceptedRepairEsis.clear();
108
81
  unplacedSources.clear();
109
- pendingVerify.clear();
110
82
  recoveryTimes.length = 0;
111
83
  liveSbn = 0;
112
84
  liveSbnKnown = false;
113
85
  }
114
86
  function isRetiredSbn(sbn) {
115
87
  if (config === null || !liveSbnKnown) return false;
116
- return config.blockProfile === "iso-ssbg1-v1" ? isoBlocksBehind(sbn, liveSbn, config.k) > config.interleaveDepth : blocksBehind(sbn, liveSbn, config.k) > config.interleaveDepth;
117
- }
118
- function isIsoProfile() {
119
- return config?.blockProfile === "iso-ssbg1-v1";
88
+ return isoBlocksBehind(sbn, liveSbn, config.k) > config.interleaveDepth;
120
89
  }
121
90
  function isoKnownLength(block) {
122
91
  return block.ssbLength ?? 1;
@@ -128,7 +97,7 @@ function findIsoBlock(ssId) {
128
97
  return void 0;
129
98
  }
130
99
  function isRetiredBlock(key, block) {
131
- return isIsoProfile() ? isRetiredSbn((key + isoKnownLength(block) - 1) % SS_ID_SPAN) : isRetiredSbn(key);
100
+ return isRetiredSbn((key + isoKnownLength(block) - 1) % SS_ID_SPAN);
132
101
  }
133
102
  function trimBlockRetention() {
134
103
  for (const [sbn, block] of blocks) {
@@ -139,10 +108,7 @@ function trimBlockRetention() {
139
108
  }
140
109
  function advanceMediaHorizon(sbn) {
141
110
  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
- }
111
+ if (liveSbnKnown && !serialPrecedes(liveSbn, sbn)) return;
146
112
  liveSbn = sbn;
147
113
  liveSbnKnown = true;
148
114
  trimBlockRetention();
@@ -155,8 +121,7 @@ function markBlockTerminal(block, state) {
155
121
  }
156
122
  function synchronizeDecoderHorizon() {
157
123
  if (!wasmDecoder || !liveSbnKnown) return;
158
- const iso = isIsoProfile();
159
- const expired = iso ? wasmDecoder.cleanup_by_ss_start(liveSbn) : wasmDecoder.cleanup_by_sbn(liveSbn);
124
+ const expired = wasmDecoder.cleanup_by_ss_start(liveSbn);
160
125
  for (const sbn of expired) {
161
126
  const block = blocks.get(sbn);
162
127
  if (!block || isTerminalBlock(block)) {
@@ -167,7 +132,7 @@ function synchronizeDecoderHorizon() {
167
132
  blocksFailed++;
168
133
  emitBlockUpdate(block);
169
134
  }
170
- if (iso) retireUnplacedSources();
135
+ retireUnplacedSources();
171
136
  trimBlockRetention();
172
137
  }
173
138
  function retireUnplacedSources() {
@@ -192,8 +157,9 @@ function getOrCreateBlock(sbn, isoSsbLength) {
192
157
  repairCount: config.repairCount,
193
158
  firstSymbolTs: now,
194
159
  lastSymbolTs: now,
160
+ // A positive interleaveMs makes this the catalog completion deadline;
161
+ // zero makes deliveryWindowMs a receiver-local cleanup bound.
195
162
  deadlineTs: now + config.deliveryWindowMs,
196
- altaVerified: 0,
197
163
  relayBlockSigValid: null
198
164
  };
199
165
  if (isoSsbLength !== void 0) block.ssbLength = isoSsbLength;
@@ -217,7 +183,7 @@ function buildStatsSnapshot() {
217
183
  const avgRecoveryMs = recoveryTimes.length > 0 ? recoveryTimes.reduce((a, b) => a + b, 0) / recoveryTimes.length : 0;
218
184
  const maxRecoveryMs = recoveryTimes.length > 0 ? Math.max(...recoveryTimes) : 0;
219
185
  const maxBlocks = Math.max(config.interleaveDepth * 3, 24);
220
- const blockArray = Array.from(blocks.values()).sort((a, b) => b.sbn - a.sbn).slice(0, maxBlocks);
186
+ const blockArray = Array.from(blocks.values()).sort((a, b) => serialDistance(a.sbn, liveSbn) - serialDistance(b.sbn, liveSbn)).slice(0, maxBlocks);
221
187
  return {
222
188
  config,
223
189
  sourceSymbols,
@@ -228,15 +194,6 @@ function buildStatsSnapshot() {
228
194
  recoveryRate: blocksComplete + blocksRecovered > 0 ? Math.round(
229
195
  blocksRecovered / (blocksRecovered + blocksFailed) * 100
230
196
  ) || 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
197
  relayBlocksVerified,
241
198
  relayBlocksFailed,
242
199
  liveSbn,
@@ -248,7 +205,7 @@ function buildStatsSnapshot() {
248
205
  sourceDroppedRetired,
249
206
  sourceDroppedTerminal,
250
207
  sourceDroppedDuplicate,
251
- ...config.blockProfile === "iso-ssbg1-v1" ? { sourceUnplacedRetired } : {},
208
+ sourceUnplacedRetired,
252
209
  decoder
253
210
  };
254
211
  }
@@ -280,25 +237,19 @@ function emitRecoveredSymbols(sbn, recoveredBlock, block) {
280
237
  });
281
238
  return;
282
239
  }
283
- const iso = config.blockProfile === "iso-ssbg1-v1";
284
240
  for (let i = 0; i < ssbLength; i++) {
285
241
  const symbol = recoveredBlock.subarray(i * symbolSize, (i + 1) * symbolSize);
286
- if (iso) {
287
- const invalid = invalidSsbg1Symbol(symbol, symbolSize);
288
- if (invalid !== null) {
289
- emit({
290
- type: "error",
291
- code: "recovered-ssbg1-symbol-invalid",
292
- message: `[FecWorker] Dropping recovered ssbg_mode1 symbol (ssStart=${sbn}, esi=${i}, SSB_length=${ssbLength}): ${invalid}`
293
- });
294
- continue;
295
- }
242
+ const invalid = invalidSsbg1Symbol(symbol, symbolSize);
243
+ if (invalid !== null) {
244
+ emit({
245
+ type: "error",
246
+ code: "recovered-ssbg1-symbol-invalid",
247
+ message: `[FecWorker] Dropping recovered ssbg_mode1 symbol (ssStart=${sbn}, esi=${i}, SSB_length=${ssbLength}): ${invalid}`
248
+ });
249
+ continue;
296
250
  }
297
- const recoveredAltaVerified = null;
298
- if (altaEnabled) altaPending++;
299
251
  const buf = toOwnedArrayBuffer(symbol);
300
252
  const meta = {
301
- altaVerified: recoveredAltaVerified,
302
253
  recovered: true,
303
254
  sbn,
304
255
  esi: i,
@@ -411,7 +362,6 @@ function feedIsoSource(cmd, view, decoder, cfg) {
411
362
  advanceMediaHorizon(cmd.ssId);
412
363
  unplacedSources.set(cmd.ssId, performance.now());
413
364
  sourceSymbols++;
414
- if (altaEnabled) altaPending++;
415
365
  if (result2 && result2.byteLength > 0) {
416
366
  emit({
417
367
  type: "error",
@@ -442,7 +392,6 @@ function feedIsoSource(cmd, view, decoder, cfg) {
442
392
  block.sourceReceived++;
443
393
  block.lastSymbolTs = performance.now();
444
394
  sourceSymbols++;
445
- if (altaEnabled) altaPending++;
446
395
  settleSourceArrival(key, block, result);
447
396
  }
448
397
  function findOverlappingIsoBlock(ssStart, ssbLength) {
@@ -510,12 +459,12 @@ async function handleConfigure(cmd) {
510
459
  const blockProfile = cmd.config.blockProfile;
511
460
  if (blockProfile === void 0) {
512
461
  throw new Error(
513
- '[FecWorker] configure: blockProfile is required: name the FEC block profile ("k-padded-v1" or "iso-ssbg1-v1")'
462
+ '[FecWorker] configure: blockProfile is required: name the FEC block profile ("iso-ssbg1-v1")'
514
463
  );
515
464
  }
516
- if (blockProfile !== "k-padded-v1" && blockProfile !== "iso-ssbg1-v1") {
465
+ if (blockProfile !== "iso-ssbg1-v1") {
517
466
  throw new Error(
518
- `[FecWorker] configure: blockProfile must be "k-padded-v1" or "iso-ssbg1-v1" (got ${JSON.stringify(blockProfile)})`
467
+ `[FecWorker] configure: blockProfile must be "iso-ssbg1-v1" (got ${JSON.stringify(blockProfile)})`
519
468
  );
520
469
  }
521
470
  if (!Number.isFinite(cmd.config.interleaveDepth) || !Number.isInteger(cmd.config.interleaveDepth) || cmd.config.interleaveDepth <= 0 || cmd.config.interleaveDepth > MAX_INTERLEAVE_DEPTH) {
@@ -523,9 +472,9 @@ async function handleConfigure(cmd) {
523
472
  `[FecWorker] configure: interleaveDepth must be an integer in 1..${MAX_INTERLEAVE_DEPTH} (got ${cmd.config.interleaveDepth})`
524
473
  );
525
474
  }
526
- if (!Number.isFinite(cmd.config.interleaveMs) || cmd.config.interleaveMs <= 0) {
475
+ if (!Number.isFinite(cmd.config.interleaveMs) || !Number.isSafeInteger(cmd.config.interleaveMs) || cmd.config.interleaveMs < 0) {
527
476
  throw new Error(
528
- `[FecWorker] configure: interleaveMs must be finite and > 0 (got ${cmd.config.interleaveMs})`
477
+ `[FecWorker] configure: interleaveMs must be a finite non-negative integer (got ${cmd.config.interleaveMs})`
529
478
  );
530
479
  }
531
480
  if (!Number.isFinite(cmd.config.deliveryWindowMs) || cmd.config.deliveryWindowMs <= 0) {
@@ -552,28 +501,6 @@ async function handleConfigure(cmd) {
552
501
  verifyDecoderProfile(decoder, blockProfile);
553
502
  wasmDecoder = decoder;
554
503
  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
504
  wasmDecoder.configure_params(config.k, config.symbolSize);
578
505
  emit({ type: "capabilities", commands: ["feedSymbols", "subscribeBlockUpdates"] });
579
506
  startSnapshotTimer();
@@ -589,40 +516,7 @@ function handleFeedSource(cmd) {
589
516
  if (!isUint32(cmd.ssId) || !(cmd.data instanceof ArrayBuffer)) {
590
517
  throw new Error("[FecWorker] feedSource: invalid source symbol");
591
518
  }
592
- const view = new Uint8Array(cmd.data);
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);
519
+ feedIsoSource(cmd, new Uint8Array(cmd.data), wasmDecoder, config);
626
520
  }
627
521
  function handleFeedRepair(cmd) {
628
522
  if (!wasmDecoder) {
@@ -631,8 +525,7 @@ function handleFeedRepair(cmd) {
631
525
  if (!config) {
632
526
  throw new Error("[FecWorker] feedRepair: config missing");
633
527
  }
634
- const iso = config.blockProfile === "iso-ssbg1-v1";
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)) {
528
+ 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
529
  throw new Error("[FecWorker] feedRepair: invalid repair symbol");
637
530
  }
638
531
  const view = new Uint8Array(cmd.data);
@@ -647,44 +540,7 @@ function handleFeedRepair(cmd) {
647
540
  }
648
541
  return;
649
542
  }
650
- if (iso) {
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);
543
+ feedIsoRepair(cmd, view, wasmDecoder);
688
544
  }
689
545
  function handleRelayBlockSig(cmd) {
690
546
  const block = blocks.get(cmd.sbn);
@@ -713,7 +569,7 @@ function handleCleanup(cmd) {
713
569
  continue;
714
570
  }
715
571
  currentBlock ??= getOrCreateBlock(sbn);
716
- const authoredLength = isIsoProfile() ? currentBlock.ssbLength : currentBlock.k;
572
+ const authoredLength = currentBlock.ssbLength;
717
573
  if (authoredLength !== void 0 && currentBlock.sourceReceived >= authoredLength) {
718
574
  markBlockTerminal(currentBlock, "complete");
719
575
  blocksComplete++;
@@ -732,20 +588,10 @@ function handleDispose() {
732
588
  clearInterval(snapshotTimer);
733
589
  snapshotTimer = null;
734
590
  }
735
- if (altaVerifier) {
736
- try {
737
- altaVerifier.free();
738
- } catch (_e) {
739
- }
740
- altaVerifier = null;
741
- }
742
591
  if (wasmDecoder) {
743
592
  wasmDecoder.free();
744
593
  wasmDecoder = null;
745
594
  }
746
- pendingVerify.clear();
747
- pendingVerifyCap = 0;
748
- pendingVerifyTtlMs = 0;
749
595
  config = null;
750
596
  clearBlockTracking();
751
597
  }
@@ -767,13 +613,6 @@ function handleReset() {
767
613
  blocksComplete = 0;
768
614
  blocksRecovered = 0;
769
615
  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
616
  relayBlocksVerified = 0;
778
617
  relayBlocksFailed = 0;
779
618
  repairSymbolSizeMismatch = 0;