@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.
@@ -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 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";
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 isIsoProfile() ? isRetiredSbn((key + isoKnownLength(block) - 1) % SS_ID_SPAN) : isRetiredSbn(key);
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 iso = isIsoProfile();
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
- if (iso) retireUnplacedSources();
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) => b.sbn - a.sbn).slice(0, maxBlocks);
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
- ...config.blockProfile === "iso-ssbg1-v1" ? { sourceUnplacedRetired } : {},
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
- 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
- }
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 ("k-padded-v1" or "iso-ssbg1-v1")'
491
+ '[FecWorker] configure: blockProfile is required: name the FEC block profile ("iso-ssbg1-v1")'
514
492
  );
515
493
  }
516
- if (blockProfile !== "k-padded-v1" && blockProfile !== "iso-ssbg1-v1") {
494
+ if (blockProfile !== "iso-ssbg1-v1") {
517
495
  throw new Error(
518
- `[FecWorker] configure: blockProfile must be "k-padded-v1" or "iso-ssbg1-v1" (got ${JSON.stringify(blockProfile)})`
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 <= 0) {
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 and > 0 (got ${cmd.config.interleaveMs})`
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
- pendingVerifyTtlMs = Math.ceil(config.interleaveMs * config.k * 1.5);
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
- 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);
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
- 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)) {
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
- 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);
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 = isIsoProfile() ? currentBlock.ssbLength : currentBlock.k;
624
+ const authoredLength = currentBlock.ssbLength;
717
625
  if (authoredLength !== void 0 && currentBlock.sourceReceived >= authoredLength) {
718
626
  markBlockTerminal(currentBlock, "complete");
719
627
  blocksComplete++;