@blockcast/fec-worker 0.4.0-main.5bfd894e7d54 → 0.4.0-main.61c9849c6b9f

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.
@@ -36,7 +36,7 @@ export interface FecTrackConfig {
36
36
  framerate?: number;
37
37
  /** Audio sample rate (omitted for video) */
38
38
  sampleRate?: number;
39
- /** FEC algorithm identifier: 'raptor', 'reed-solomon', 'none' */
39
+ /** Catalog `fec.algorithm`: `"reed-solomon"` runs the Reed-Solomon decoder; any other value (`"raptorq"`, the legacy `"raptor"`) runs RaptorQ. */
40
40
  algorithm: string;
41
41
  /** Block profile; the Worker refuses to configure without a known token. */
42
42
  blockProfile: FecBlockProfile;
@@ -1 +1 @@
1
- {"version":3,"file":"fec-worker-types.d.ts","sourceRoot":"","sources":["../src/fec-worker-types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fec-worker-types.d.ts","sourceRoot":"","sources":["../src/fec-worker-types.ts"],"names":[],"mappings":"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"}
@@ -46,6 +46,7 @@ var SSBG1_LENGTH_FIELD_BYTES = 2;
46
46
  var MAX_INTERLEAVE_DEPTH = 255;
47
47
  var MAX_SYMBOL_SIZE = 65528;
48
48
  var MAX_RAPTORQ_SOURCE_SYMBOLS_PER_BLOCK = 56403;
49
+ var MAX_REED_SOLOMON_SOURCE_SYMBOLS_PER_BLOCK = 254;
49
50
  var sourceSymbols = 0;
50
51
  var repairSymbols = 0;
51
52
  var blocksComplete = 0;
@@ -467,6 +468,7 @@ async function handleConfigure(cmd) {
467
468
  `[FecWorker] configure: blockProfile must be "iso-ssbg1-v1" (got ${JSON.stringify(blockProfile)})`
468
469
  );
469
470
  }
471
+ const algorithm = cmd.config.algorithm === "reed-solomon" ? "reed-solomon" : "raptorq";
470
472
  if (!Number.isFinite(cmd.config.interleaveDepth) || !Number.isInteger(cmd.config.interleaveDepth) || cmd.config.interleaveDepth <= 0 || cmd.config.interleaveDepth > MAX_INTERLEAVE_DEPTH) {
471
473
  throw new Error(
472
474
  `[FecWorker] configure: interleaveDepth must be an integer in 1..${MAX_INTERLEAVE_DEPTH} (got ${cmd.config.interleaveDepth})`
@@ -482,14 +484,13 @@ async function handleConfigure(cmd) {
482
484
  `[FecWorker] configure: deliveryWindowMs must be > 0 (got ${cmd.config.deliveryWindowMs})`
483
485
  );
484
486
  }
485
- if (!Number.isInteger(cmd.config.k) || cmd.config.k <= 0 || cmd.config.k > MAX_RAPTORQ_SOURCE_SYMBOLS_PER_BLOCK) {
486
- throw new Error(
487
- `[FecWorker] configure: k must be an integer in 1..${MAX_RAPTORQ_SOURCE_SYMBOLS_PER_BLOCK} (got ${cmd.config.k})`
488
- );
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})`);
489
490
  }
490
- 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) {
491
492
  throw new Error(
492
- `[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})`
493
494
  );
494
495
  }
495
496
  const wasm = resolveWasmBindings();
@@ -497,8 +498,13 @@ async function handleConfigure(cmd) {
497
498
  wasmDecoder?.free();
498
499
  wasmDecoder = null;
499
500
  clearBlockTracking();
500
- const decoder = new wasm.MmtFecDecoder(cmd.config.interleaveDepth, blockProfile);
501
+ const decoder = new wasm.MmtFecDecoder(cmd.config.interleaveDepth, blockProfile, algorithm);
501
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
+ }
502
508
  wasmDecoder = decoder;
503
509
  config = cmd.config;
504
510
  wasmDecoder.configure_params(config.k, config.symbolSize);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/transfer.ts", "../src/block-order.ts", "../src/fec-worker.ts"],
4
- "sourcesContent": ["/**\n * Return a fresh ArrayBuffer containing exactly the bytes in `view`.\n *\n * Worker outputs must not transfer buffers that might be owned by a WASM\n * binding or by a larger shared packet. A fresh JS-owned buffer gives\n * postMessage() exclusive ownership without detaching unrelated memory.\n */\nexport function toOwnedArrayBuffer(view: Uint8Array): ArrayBuffer {\n\tconst copy = new Uint8Array(view.byteLength);\n\tcopy.set(view);\n\treturn copy.buffer;\n}\n", "/**\n * Order of iso-ssbg1-v1 FEC blocks on the 32-bit SS_ID (ISO/IEC 23008-1:2023\n * C.5.2/C.5.3; RFC 1982 serial arithmetic, SERIAL_BITS = 32; BLO-35597 iso:5).\n * The K-stride (SBN * K) order of the retired k-padded-v1 profile is gone.\n */\n\nconst SPAN = 0x1_0000_0000;\nconst HALF = 0x8000_0000;\n\n// --- iso-ssbg1-v1 (ISO/IEC 23008-1 C.3.3 ssbg_mode1; C.5.2/C.5.3) ---------\n//\n// Under the ISO profile SS_ID increments by one per FEC source packet and\n// wraps at 2^32, and a block is the SSB_length consecutive SS_IDs\n// `[SS_Start, SS_Start + SSB_length)`. Blocks are keyed and ordered by their\n// SS_Start, never by an SBN derived from K: the catalog K is only the maximum\n// block length (kMax). The canonical helpers are @blockcast/mmt-base\n// `serial32Distance` / `serial32Precedes` / `isoBlockContains` /\n// `isoBlocksBehindLowerBound`; this bundle carries its own copies (it does not\n// depend on mmt-base), and block-order.test.ts pins them to the same vectors.\n\n/** Forward RFC 1982 distance from `from` to `to` on the 32-bit SS_ID, in 0..2^32-1. */\nexport function serialDistance(from: number, to: number): number {\n\treturn (to - from + SPAN) % SPAN;\n}\n\n/** Whether `a` precedes `b` under RFC 1982 serial comparison (SERIAL_BITS = 32). */\nexport function serialPrecedes(a: number, b: number): boolean {\n\tconst distance = serialDistance(a, b);\n\treturn distance !== 0 && distance < HALF;\n}\n\n/** Whether `ssId` is one of the `ssbLength` consecutive SS_IDs starting at `ssStart`, mod 2^32. */\nexport function isoBlockContains(ssStart: number, ssbLength: number, ssId: number): boolean {\n\treturn serialDistance(ssStart, ssId) < ssbLength;\n}\n\n/** Whether two SS_ID ranges `[start, start + length)` share any SS_ID, mod 2^32. */\nexport function isoBlocksOverlap(\n\taStart: number,\n\taLength: number,\n\tbStart: number,\n\tbLength: number,\n): boolean {\n\treturn isoBlockContains(aStart, aLength, bStart) || isoBlockContains(bStart, bLength, aStart);\n}\n\n/**\n * How many blocks SS_ID `a` lies behind SS_ID `b` (0 unless `a` precedes `b`):\n * a lower bound, because an iso-ssbg1-v1 block holds at most `kMax` source\n * symbols, so `n * kMax` SS_IDs always span at least `n` blocks. This is the\n * one interleave-horizon metric every ISO receiver layer shares (decoder\n * retention, worker retirement, ordered-relay settlement).\n */\nexport function isoBlocksBehind(a: number, b: number, kMax: number): number {\n\tif (!Number.isSafeInteger(kMax) || kMax <= 0) {\n\t\tthrow new Error(`iso-ssbg1-v1 block horizon needs a positive maximum K (got ${kMax})`);\n\t}\n\treturn serialPrecedes(a, b) ? Math.floor(serialDistance(a, b) / kMax) : 0;\n}\n", "/**\n * @blockcast/fec-worker \u2014 Worker Entry Point\n *\n * Runs inside DedicatedWorkerGlobalScope. Receives FecWorkerCommand messages,\n * drives the MmtFecDecoder WASM module, emits FecWorkerEvent messages back.\n *\n * WASM bindings are resolved via:\n * 1. (self as any).__MMT_WASM_BINDINGS__ \u2014 set by consumer's loader/importmap\n * 2. Falls back to error if not available (no defaults)\n *\n * This file must NOT import DOM globals incompatible with Worker scope.\n */\n\nimport type {\n\tFecWorkerCommand,\n\tFecWorkerEvent,\n\tFecTrackConfig,\n\tFecTrackStats,\n\tFecBlockSnapshot,\n\tFrameMeta,\n\tFecBlockProfile,\n} from \"./fec-worker-types.js\";\nimport { toOwnedArrayBuffer } from \"./transfer.js\";\nimport {\n\tisoBlockContains,\n\tisoBlocksBehind,\n\tisoBlocksOverlap,\n\tserialDistance,\n\tserialPrecedes,\n} from \"./block-order.js\";\n\n// --- WASM binding types (from mmt-wasm) ---\n// Signatures MUST match a freshly generated external mmt_wasm.d.ts exactly.\n// The decoder derives SBN/ESI internally \u2014 do not pre-compute.\ninterface WasmFecDecoder {\n\t/**\n\t * ISO 23008-1 \u00A7C.5.2: ss_id is the flat 32-bit Source Symbol ID; data is\n\t * the protected packet, unpadded. The decoder builds the ssbg_mode1 symbol\n\t * and holds it until a repair names the block containing it.\n\t */\n\tadd_source(\n\t\tss_id: number,\n\t\tdata: Uint8Array,\n\t\ttimestamp: number,\n\t): Uint8Array | undefined;\n\t/**\n\t * ISO 23008-1 \u00A7C.5.3: the block is `[ss_start, ss_start + ssb_length)`\n\t * with K' = ssb_length and repair ESI = ssb_length + rs_id; a recovered\n\t * block is ssb_length x T bytes of ssbg_mode1 symbols.\n\t */\n\tadd_repair(\n\t\tss_start: number,\n\t\tssb_length: number,\n\t\trs_id: number,\n\t\tdata: Uint8Array,\n\t\ttimestamp: number,\n\t): Uint8Array | undefined;\n\tconfigure_params(k: number, symbolSize: number): void;\n\t/**\n\t * Retire the named blocks (last-chance decode + stats), driven by the\n\t * manager's exact deadlines. Nothing is synthesised for a lost block:\n\t * concealment is the sink's hold-last-good-frame.\n\t */\n\tcleanup_blocks_detailed(sbns: Uint32Array): void;\n\t/**\n\t * Remove decoder blocks, and held unplaced sources, more than\n\t * interleaveDepth `isoBlocksBehind` the live SS_ID; returns the SS_Starts\n\t * of the blocks it removed. It runs the decoder's `retire_unplaced` sweep\n\t * itself, so the Worker never calls that separately: synchronizeDecoderHorizon\n\t * calls this after every source and repair it accepts, which is what keeps\n\t * the decoder's unplaced pool bounded when a block's repairs never arrive.\n\t */\n\tcleanup_by_ss_start(current: number): Uint32Array;\n\t/** The block profile the decoder was constructed with. */\n\tget_block_profile(): string;\n\tflush(): void;\n\tpending_blocks(): number;\n\treset_stats(): void;\n\tget_stats(): {\n\t\tsource_packets: bigint;\n\t\trepair_packets: bigint;\n\t\tblocks_complete: bigint;\n\t\tblocks_recovered: bigint;\n\t\tblocks_failed: bigint;\n\t\tbytes_recovered: bigint;\n\t\trecovery_rate(): number;\n\t};\n\tfree(): void;\n}\n\ninterface WasmBindings {\n\tinitSync(module: { module: WebAssembly.Module }): unknown;\n\tMmtFecDecoder: new (interleaveDepth: number, blockProfile: FecBlockProfile) => WasmFecDecoder;\n}\n\n// --- Worker state (local to this Worker instance) ---\n\nlet wasmDecoder: WasmFecDecoder | null = null;\nlet config: FecTrackConfig | null = null;\nlet snapshotTimer: ReturnType<typeof setInterval> | null = null;\n// blockUpdate events are posted only after a client subscribes (BLO-36599).\nlet blockUpdatesSubscribed = false;\n\n// Block tracking\nconst blocks = new Map<number, FecBlockSnapshot>();\nconst acceptedSourceEsis = new Map<number, Set<number>>();\nconst acceptedRepairEsis = new Map<number, Set<number>>();\n// iso-ssbg1-v1: sources fed to the decoder before any repair named a block\n// containing them, keyed by SS_ID with their arrival time. Map order is\n// arrival order, which the horizon sweep relies on (retireUnplacedSources).\nconst unplacedSources = new Map<number, number>();\nconst UINT32_MAX = 0xffff_ffff;\nconst UINT24_MAX = 0xff_ffff;\nconst SS_ID_SPAN = 0x1_0000_0000;\n// ISO/IEC 23008-1 C.3.3 ssbg_mode1: the two-octet protected-packet length field.\nconst SSBG1_LENGTH_FIELD_BYTES = 2;\n// ISO/IEC 23008-1 AL-FEC signaling carries interleave_depth in one byte.\nconst MAX_INTERLEAVE_DEPTH = 0xff;\n// Rust stores T as u16 and this decoder profile fixes RFC 6330 Al=8.\nconst MAX_SYMBOL_SIZE = 0xfff8;\n// RFC 6330 systematic table limit, enforced by the vendored raptorq decoder.\nconst MAX_RAPTORQ_SOURCE_SYMBOLS_PER_BLOCK = 56_403;\n\n// Aggregate counters\nlet sourceSymbols = 0;\nlet repairSymbols = 0;\nlet blocksComplete = 0;\nlet blocksRecovered = 0;\nlet blocksFailed = 0;\nlet relayBlocksVerified = 0;\nlet relayBlocksFailed = 0;\nlet liveSbn = 0;\n// Whether liveSbn has been set by a symbol of this decoder epoch. Block order\n// is serial on the 32-bit SS_ID (block-order.ts, BLO-35597 iso:5), where 0 is\n// a real position, so \"nothing seen yet\" needs its own state.\nlet liveSbnKnown = false;\nlet repairSymbolSizeMismatch = 0;\n// Source symbols dropped before WASM, by cause. All three sites below were\n// silent, which left the \"block settles at exactly K-1 source symbols\" shape\n// (BLO-35597 defect 2) with no evidence to attribute it to. A retired drop\n// means a late symbol past retention; a terminal drop means the block had\n// already settled \u2014 `state !== \"collecting\"`, so complete OR recovered OR\n// FAILED, and the failed arm is the one this issue is chasing (a block aged out\n// by synchronizeDecoderHorizon stays in `blocks` until its SBN retires, so a\n// late symbol for a short block lands here); a duplicate drop means a repeated\n// (SBN, ESI), which on this wire is usually a plain duplicate packet \u2014 the\n// router's ledger has deliberate gaps \u2014 and only sometimes an SBN/ESI\n// derivation collision.\n//\n// These narrow the cause; they do not uniquely attribute it. A symbol LOST IN\n// TRANSIT never reaches handleFeedSource and increments nothing, so all three\n// reading zero on a short block is NOT evidence of a publisher defect \u2014 wire\n// loss is the null hypothesis here, and it produces an identical shape. The\n// discriminator for that residual arm is `block.ssbLength` (set at the repair\n// symbol's declared SSB_length, conflict-checked on arrival):\n// ssbLength < k \u2192 genuinely short authored block\n// ssbLength === k, sourceReceived < k \u2192 loss or a still-late symbol\n// With no repair symbol yet seen, ssbLength is undefined and both stay open.\nlet sourceDroppedRetired = 0;\nlet sourceDroppedTerminal = 0;\nlet sourceDroppedDuplicate = 0;\nlet sourceUnplacedRetired = 0;\n\n// Recovery timing\nconst recoveryTimes: number[] = [];\n\n// --- Helpers ---\n\nfunction emit(event: FecWorkerEvent, transfer?: Transferable[]): void {\n\tself.postMessage(event, transfer ?? []);\n}\n\nfunction isUint32(value: number): boolean {\n\treturn Number.isInteger(value) && value >= 0 && value <= UINT32_MAX;\n}\n\nfunction isTerminalBlock(block: FecBlockSnapshot): boolean {\n\treturn block.state !== \"collecting\";\n}\n\nfunction retireAcceptedIds(sbn: number): void {\n\tacceptedSourceEsis.delete(sbn);\n\tacceptedRepairEsis.delete(sbn);\n}\n\nfunction clearBlockTracking(): void {\n\tblocks.clear();\n\tacceptedSourceEsis.clear();\n\tacceptedRepairEsis.clear();\n\tunplacedSources.clear();\n\trecoveryTimes.length = 0;\n\tliveSbn = 0;\n\tliveSbnKnown = false;\n}\n\n/**\n * Whether `sbn` has fallen behind the decoder's retained interleave window:\n * more than `interleaveDepth` blocks behind the live SBN. liveSbn only moves\n * forward within a decoder epoch and configure() resets both, so this is the\n * monotonic retirement frontier the decoder horizon implies.\n */\nfunction isRetiredSbn(sbn: number): boolean {\n\tif (config === null || !liveSbnKnown) return false;\n\t// `sbn` is an SS_ID (a block's SS_Start or a source's own SS_ID) and\n\t// liveSbn the live SS_ID; K is only the maximum block length.\n\treturn isoBlocksBehind(sbn, liveSbn, config.k) > config.interleaveDepth;\n}\n\n/**\n * iso-ssbg1-v1: the SS_IDs a known block certainly covers. Every block a\n * repair created has its SSB_length; a cleanup tombstone for a block no\n * repair declared certainly holds only its own SS_Start.\n */\nfunction isoKnownLength(block: FecBlockSnapshot): number {\n\treturn block.ssbLength ?? 1;\n}\n\n/** iso-ssbg1-v1: the known block whose SS_ID range contains `ssId` (ranges never overlap). */\nfunction findIsoBlock(ssId: number): FecBlockSnapshot | undefined {\n\tfor (const block of blocks.values()) {\n\t\tif (isoBlockContains(block.sbn, isoKnownLength(block), ssId)) return block;\n\t}\n\treturn undefined;\n}\n\n/**\n * Whether a terminal block's diagnostics may be deleted. Under iso-ssbg1-v1 a\n * source carries no block identity, so a deleted block cannot refuse a late\n * source of its own: that source would be admitted as unplaced and reopen the\n * block in the decoder. The tombstone therefore stays until its LAST SS_ID is\n * retired, after which the SS_ID retirement check refuses every member.\n */\nfunction isRetiredBlock(key: number, block: FecBlockSnapshot): boolean {\n\treturn isRetiredSbn((key + isoKnownLength(block) - 1) % SS_ID_SPAN);\n}\n\n/**\n * Retire terminal diagnostics only after the decoder's monotonic interleave\n * horizon proves that the SBN can no longer be valid out-of-order media.\n * Admission keeps the compact frontier as the durable tombstone, so deletion\n * never reopens a finalized block.\n */\nfunction trimBlockRetention(): void {\n\tfor (const [sbn, block] of blocks) {\n\t\tif (!isTerminalBlock(block) || !isRetiredBlock(sbn, block)) continue;\n\t\tblocks.delete(sbn);\n\t\tretireAcceptedIds(sbn);\n\t}\n}\n\n/** Advance the live position: an SS_ID / SS_Start, in serial order. */\nfunction advanceMediaHorizon(sbn: number): void {\n\tif (!config) return;\n\tif (liveSbnKnown && !serialPrecedes(liveSbn, sbn)) return;\n\tliveSbn = sbn;\n\tliveSbnKnown = true;\n\ttrimBlockRetention();\n}\n\nfunction markBlockTerminal(\n\tblock: FecBlockSnapshot,\n\tstate: \"complete\" | \"recovered\" | \"failed\",\n): void {\n\tblock.state = state;\n\tblock.completedTs = performance.now();\n\tretireAcceptedIds(block.sbn);\n\ttrimBlockRetention();\n}\n\nfunction synchronizeDecoderHorizon(): void {\n\tif (!wasmDecoder || !liveSbnKnown) return;\n\tconst expired = wasmDecoder.cleanup_by_ss_start(liveSbn);\n\tfor (const sbn of expired) {\n\t\tconst block = blocks.get(sbn);\n\t\tif (!block || isTerminalBlock(block)) {\n\t\t\tretireAcceptedIds(sbn);\n\t\t\tcontinue;\n\t\t}\n\t\tmarkBlockTerminal(block, \"failed\");\n\t\tblocksFailed++;\n\t\temitBlockUpdate(block);\n\t}\n\tretireUnplacedSources();\n\ttrimBlockRetention();\n}\n\n/**\n * iso-ssbg1-v1: retire unplaced sources behind the horizon, by the same\n * isoBlocksBehind metric as a block start, sweeping in arrival order and\n * stopping at the first live one (amortised O(1) per symbol). A source that\n * arrived out of order behind a newer live one is retired when it reaches the\n * front; until then nothing can place it, because any repair range holding it\n * starts at or before it and is itself retired. One the live position moved\n * at least half the SS_ID space past no longer precedes it serially; it is\n * retired too rather than pinning the sweep.\n */\nfunction retireUnplacedSources(): void {\n\tfor (const ssId of unplacedSources.keys()) {\n\t\tconst unreachable = ssId !== liveSbn && !serialPrecedes(ssId, liveSbn);\n\t\tif (!unreachable && !isRetiredSbn(ssId)) break;\n\t\tunplacedSources.delete(ssId);\n\t\tsourceUnplacedRetired++;\n\t}\n}\n\n/**\n * Look up or create a block. `isoSsbLength` creates an iso-ssbg1-v1 block,\n * whose K' is its SSB_length (C.5.3); without it the block has the catalog K.\n */\nfunction getOrCreateBlock(sbn: number, isoSsbLength?: number): FecBlockSnapshot {\n\tlet block = blocks.get(sbn);\n\tif (!block) {\n\t\tif (!config) throw new Error(\"[FecWorker] not configured\");\n\t\tconst now = performance.now();\n\t\tblock = {\n\t\t\tsbn,\n\t\t\tstate: \"collecting\",\n\t\t\tsourceReceived: 0,\n\t\t\trepairReceived: 0,\n\t\t\tk: isoSsbLength ?? config.k,\n\t\t\trepairCount: config.repairCount,\n\t\t\tfirstSymbolTs: now,\n\t\t\tlastSymbolTs: now,\n\t\t\t// A positive interleaveMs makes this the catalog completion deadline;\n\t\t\t// zero makes deliveryWindowMs a receiver-local cleanup bound.\n\t\t\tdeadlineTs: now + config.deliveryWindowMs,\n\t\t\trelayBlockSigValid: null,\n\t\t};\n\t\tif (isoSsbLength !== undefined) block.ssbLength = isoSsbLength;\n\t\tblocks.set(sbn, block);\n\t\ttrimBlockRetention();\n\t}\n\treturn block;\n}\n\nfunction buildStatsSnapshot(): FecTrackStats {\n\tif (!config) throw new Error(\"[FecWorker] not configured\");\n\tconst decoderStats = wasmDecoder?.get_stats();\n\tconst decoder = decoderStats\n\t\t? {\n\t\t\tsourcePackets: Number(decoderStats.source_packets),\n\t\t\trepairPackets: Number(decoderStats.repair_packets),\n\t\t\tblocksComplete: Number(decoderStats.blocks_complete),\n\t\t\tblocksRecovered: Number(decoderStats.blocks_recovered),\n\t\t\tblocksFailed: Number(decoderStats.blocks_failed),\n\t\t\tbytesRecovered: Number(decoderStats.bytes_recovered),\n\t\t\tpendingBlocks: wasmDecoder?.pending_blocks() ?? 0,\n\t\t}\n\t\t: undefined;\n\n\tconst avgRecoveryMs =\n\t\trecoveryTimes.length > 0\n\t\t\t? recoveryTimes.reduce((a, b) => a + b, 0) / recoveryTimes.length\n\t\t\t: 0;\n\tconst maxRecoveryMs =\n\t\trecoveryTimes.length > 0 ? Math.max(...recoveryTimes) : 0;\n\n\t// Cap block array to interleave window\n\tconst maxBlocks = Math.max(config.interleaveDepth * 3, 24);\n\t// Newest first in SS_ID serial order, so the cap keeps the newest blocks\n\t// across the 2^32 wrap too: every block start precedes the live SS_ID.\n\tconst blockArray = Array.from(blocks.values())\n\t\t.sort((a, b) => serialDistance(a.sbn, liveSbn) - serialDistance(b.sbn, liveSbn))\n\t\t.slice(0, maxBlocks);\n\n\treturn {\n\t\tconfig,\n\t\tsourceSymbols,\n\t\trepairSymbols,\n\t\tblocksComplete,\n\t\tblocksRecovered,\n\t\tblocksFailed,\n\t\trecoveryRate:\n\t\t\tblocksComplete + blocksRecovered > 0\n\t\t\t\t? Math.round(\n\t\t\t\t\t\t(blocksRecovered / (blocksRecovered + blocksFailed)) * 100,\n\t\t\t\t\t) || 0\n\t\t\t\t: 0,\n\t\trelayBlocksVerified,\n\t\trelayBlocksFailed,\n\t\tliveSbn,\n\t\tblocks: blockArray,\n\t\tinterleaveWindowBlocks: config.interleaveDepth,\n\t\tavgRecoveryMs: Math.round(avgRecoveryMs * 100) / 100,\n\t\tmaxRecoveryMs: Math.round(maxRecoveryMs * 100) / 100,\n\t\trepairSymbolSizeMismatch,\n\t\tsourceDroppedRetired,\n\t\tsourceDroppedTerminal,\n\t\tsourceDroppedDuplicate,\n\t\tsourceUnplacedRetired,\n\t\tdecoder,\n\t};\n}\n\nfunction emitSnapshot(): void {\n\tif (!config) return;\n\temit({ type: \"snapshot\", stats: buildStatsSnapshot() });\n}\n\nfunction emitBlockUpdate(block: FecBlockSnapshot): void {\n\tif (!blockUpdatesSubscribed) return;\n\temit({ type: \"blockUpdate\", block: { ...block } });\n}\n\nfunction emitRecoveredSymbols(\n\tsbn: number,\n\trecoveredBlock: Uint8Array,\n\tblock: FecBlockSnapshot,\n): void {\n\tif (!config) return;\n\tconst symbolSize = config.symbolSize;\n\tif (symbolSize <= 0) {\n\t\temit({\n\t\t\ttype: \"error\",\n\t\t\tcode: \"recovered-symbol-size-unconfigured\",\n\t\t\tmessage: \"[FecWorker] recovered block has no configured symbolSize\",\n\t\t});\n\t\treturn;\n\t}\n\t// The decoder returns only the block's authored source symbols\n\t// (SSB_length x T); the encoder's zero padding is not media.\n\tconst ssbLength = block.ssbLength ?? config.k;\n\tif (recoveredBlock.byteLength !== symbolSize * ssbLength) {\n\t\temit({\n\t\t\ttype: \"error\",\n\t\t\tcode: \"recovered-block-too-short\",\n\t\t\tmessage: `[FecWorker] recovered block has the wrong length: got ${recoveredBlock.byteLength}B, expected ${symbolSize * ssbLength}B (SSB_length=${ssbLength})`,\n\t\t});\n\t\treturn;\n\t}\n\n\tfor (let i = 0; i < ssbLength; i++) {\n\t\tconst symbol = recoveredBlock.subarray(i * symbolSize, (i + 1) * symbolSize);\n\t\t// ssbg_mode1 symbols are emitted whole: the router trims them by their\n\t\t// length field. A field past T-2 or non-zero padding means the\n\t\t// recovered symbol is not what was sent, so it is refused, not trimmed.\n\t\tconst invalid = invalidSsbg1Symbol(symbol, symbolSize);\n\t\tif (invalid !== null) {\n\t\t\temit({\n\t\t\t\ttype: \"error\",\n\t\t\t\tcode: \"recovered-ssbg1-symbol-invalid\",\n\t\t\t\tmessage: `[FecWorker] Dropping recovered ssbg_mode1 symbol (ssStart=${sbn}, esi=${i}, SSB_length=${ssbLength}): ${invalid}`,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst buf = toOwnedArrayBuffer(symbol);\n\t\tconst meta: FrameMeta = {\n\t\t\trecovered: true,\n\t\t\tsbn,\n\t\t\tesi: i,\n\t\t\tssbLength,\n\t\t};\n\t\temit({ type: \"frame\", data: buf, meta }, [buf]);\n\t}\n}\n\n/** Why a T-byte ssbg_mode1 symbol is malformed (C.3.3), or null when it is well formed. */\nfunction invalidSsbg1Symbol(symbol: Uint8Array, symbolSize: number): string | null {\n\tconst length = (symbol[0]! << 8) | symbol[1]!;\n\tconst maxLength = symbolSize - SSBG1_LENGTH_FIELD_BYTES;\n\tif (length > maxLength) {\n\t\treturn `length field ${length} exceeds T-2 = ${maxLength}`;\n\t}\n\tfor (let j = SSBG1_LENGTH_FIELD_BYTES + length; j < symbolSize; j++) {\n\t\tif (symbol[j] !== 0) return `non-zero padding at byte ${j} after a ${length}-byte packet`;\n\t}\n\treturn null;\n}\n\nfunction startSnapshotTimer(): void {\n\tif (snapshotTimer !== null) clearInterval(snapshotTimer);\n\tsnapshotTimer = setInterval(() => emitSnapshot(), 200);\n}\n\nfunction resolveWasmBindings(): WasmBindings {\n\tconst bindings = (self as unknown as Record<string, unknown>)\n\t\t.__MMT_WASM_BINDINGS__ as WasmBindings | undefined;\n\tif (!bindings) {\n\t\tthrow new Error(\n\t\t\t\"[FecWorker] WASM bindings not available. Set self.__MMT_WASM_BINDINGS__ = { initSync, MmtFecDecoder } before configure.\",\n\t\t);\n\t}\n\treturn bindings;\n}\n\n/**\n * Refuse a decoder that does not run the configured block profile: one that\n * ignored or predates the constructor argument would apply one profile's\n * block math to the other's wire.\n */\nfunction verifyDecoderProfile(decoder: WasmFecDecoder, expected: FecBlockProfile): void {\n\tlet actual: unknown;\n\ttry {\n\t\tactual = decoder.get_block_profile();\n\t} catch (err) {\n\t\tdecoder.free();\n\t\tthrow new Error(\n\t\t\t`[FecWorker] configure: decoder cannot report its block profile (${err instanceof Error ? err.message : String(err)})`,\n\t\t);\n\t}\n\tif (actual !== expected) {\n\t\tdecoder.free();\n\t\tthrow new Error(\n\t\t\t`[FecWorker] configure: decoder runs block profile ${JSON.stringify(actual)}, not the configured \"${expected}\"`,\n\t\t);\n\t}\n}\n\nfunction acceptId(ids: Map<number, Set<number>>, key: number, id: number): void {\n\tlet set = ids.get(key);\n\tif (!set) {\n\t\tset = new Set<number>();\n\t\tids.set(key, set);\n\t}\n\tset.add(id);\n}\n\n/** A block the decoder returned: record its recovery and emit its source symbols. */\nfunction recordRecovered(key: number, block: FecBlockSnapshot, result: Uint8Array): void {\n\tconst recovered = new Uint8Array(toOwnedArrayBuffer(result));\n\tconst recoveryMs = performance.now() - block.firstSymbolTs;\n\trecoveryTimes.push(recoveryMs);\n\t// Cap recovery times array at 1000 entries\n\tif (recoveryTimes.length > 1000) recoveryTimes.shift();\n\n\tif (block.state === \"collecting\") {\n\t\tmarkBlockTerminal(block, \"recovered\");\n\t\tblocksRecovered++;\n\t\temitBlockUpdate(block);\n\t}\n\temitRecoveredSymbols(key, recovered, block);\n}\n\n/** Settle a block after one of its source symbols was accepted. */\nfunction settleSourceArrival(\n\tkey: number,\n\tblock: FecBlockSnapshot,\n\tresult: Uint8Array | undefined,\n): void {\n\t// Check if block is complete (all authored source symbols received:\n\t// SSB_length once a repair symbol declared it, otherwise K)\n\tconst authoredLength = block.ssbLength ?? block.k;\n\tif (\n\t\tblock.sourceReceived >= authoredLength &&\n\t\tblock.state === \"collecting\"\n\t) {\n\t\tmarkBlockTerminal(block, \"complete\");\n\t\tblocksComplete++;\n\t\temitBlockUpdate(block);\n\t}\n\n\t// A source-triggered return can be either clean block completion or recovery\n\t// of earlier missing symbols after repair arrived. Clean completion is\n\t// already routed by the source path, so only emit when the block did not\n\t// reach K source arrivals.\n\tif (result && result.byteLength > 0 && block.sourceReceived < authoredLength) {\n\t\trecordRecovered(key, block, result);\n\t}\n\tsynchronizeDecoderHorizon();\n}\n\n/** Settle a block after one of its repair symbols was accepted. */\nfunction settleRepairArrival(\n\tkey: number,\n\tblock: FecBlockSnapshot,\n\tssbLength: number,\n\tresult: Uint8Array | undefined,\n): void {\n\t// Every authored source symbol already arrived: knowing SSB_length makes\n\t// the block clean. The decoder's return is the source already routed.\n\t// Otherwise, returned data is the recovered source block: its SSB_length\n\t// fixed-width authored symbols concatenated. Split it before emitting so\n\t// MmtpRouter gets the same one-symbol shape as a direct source packet.\n\tif (block.sourceReceived >= ssbLength && block.state === \"collecting\") {\n\t\tmarkBlockTerminal(block, \"complete\");\n\t\tblocksComplete++;\n\t\temitBlockUpdate(block);\n\t} else if (result && result.byteLength > 0) {\n\t\trecordRecovered(key, block, result);\n\t}\n\tsynchronizeDecoderHorizon();\n}\n\n/**\n * iso-ssbg1-v1 source admission. A source inside a known block is accounted\n * at ESI = SS_ID - SS_Start. One no repair has placed yet is held unplaced,\n * deduplicated by SS_ID, and still fed to the decoder, which holds it too,\n * until a repair names a range containing it.\n */\nfunction feedIsoSource(\n\tcmd: Extract<FecWorkerCommand, { type: \"feedSource\" }>,\n\tview: Uint8Array,\n\tdecoder: WasmFecDecoder,\n\tcfg: FecTrackConfig,\n): void {\n\tconst maxPacketLength = cfg.symbolSize - SSBG1_LENGTH_FIELD_BYTES;\n\tif (view.byteLength > maxPacketLength) {\n\t\tthrow new Error(\n\t\t\t`[FecWorker] feedSource: iso-ssbg1-v1 protected packet of ${view.byteLength}B exceeds T-2 = ${maxPacketLength}B`,\n\t\t);\n\t}\n\tconst block = findIsoBlock(cmd.ssId);\n\tif (!block) {\n\t\tif (isRetiredSbn(cmd.ssId)) {\n\t\t\tsourceDroppedRetired++;\n\t\t\treturn;\n\t\t}\n\t\tif (unplacedSources.has(cmd.ssId)) {\n\t\t\tsourceDroppedDuplicate++;\n\t\t\treturn;\n\t\t}\n\t\tconst result = decoder.add_source(cmd.ssId, view, cmd.ts);\n\t\tadvanceMediaHorizon(cmd.ssId);\n\t\tunplacedSources.set(cmd.ssId, performance.now());\n\t\tsourceSymbols++;\n\t\t// No repair has named this source's block, so the decoder cannot have\n\t\t// one to recover; data here is a Worker/decoder disagreement that\n\t\t// cannot be attributed to a block, and is refused rather than guessed.\n\t\tif (result && result.byteLength > 0) {\n\t\t\temit({\n\t\t\t\ttype: \"error\",\n\t\t\t\tcode: \"recovered-block-unplaced\",\n\t\t\t\tmessage: `[FecWorker] Dropping a ${result.byteLength}B recovered block returned for SS_ID ${cmd.ssId}, which no known block contains`,\n\t\t\t});\n\t\t}\n\t\tsynchronizeDecoderHorizon();\n\t\treturn;\n\t}\n\n\tconst key = block.sbn;\n\tif (isRetiredSbn(key)) {\n\t\tsourceDroppedRetired++;\n\t\treturn;\n\t}\n\tif (isTerminalBlock(block)) {\n\t\tsourceDroppedTerminal++;\n\t\treturn;\n\t}\n\tconst esi = serialDistance(key, cmd.ssId);\n\tif (acceptedSourceEsis.get(key)?.has(esi)) {\n\t\tsourceDroppedDuplicate++;\n\t\treturn;\n\t}\n\n\tconst result = decoder.add_source(cmd.ssId, view, cmd.ts);\n\tadvanceMediaHorizon(cmd.ssId);\n\tacceptId(acceptedSourceEsis, key, esi);\n\tblock.sourceReceived++;\n\tblock.lastSymbolTs = performance.now();\n\tsourceSymbols++;\n\tsettleSourceArrival(key, block, result);\n}\n\n/** iso-ssbg1-v1: a known block sharing an SS_ID with `[ssStart, ssStart + ssbLength)`. */\nfunction findOverlappingIsoBlock(\n\tssStart: number,\n\tssbLength: number,\n): FecBlockSnapshot | undefined {\n\tfor (const block of blocks.values()) {\n\t\tif (isoBlocksOverlap(block.sbn, isoKnownLength(block), ssStart, ssbLength)) return block;\n\t}\n\treturn undefined;\n}\n\n/**\n * iso-ssbg1-v1: create the block a repair first named, with K' = SSB_length,\n * and move every unplaced source in its range into its accounting.\n */\nfunction placeIsoBlock(ssStart: number, ssbLength: number): FecBlockSnapshot {\n\tconst block = getOrCreateBlock(ssStart, ssbLength);\n\tfor (let esi = 0; esi < ssbLength; esi++) {\n\t\tconst ssId = (ssStart + esi) % SS_ID_SPAN;\n\t\tconst arrivalTs = unplacedSources.get(ssId);\n\t\tif (arrivalTs === undefined) continue;\n\t\tunplacedSources.delete(ssId);\n\t\tacceptId(acceptedSourceEsis, ssStart, esi);\n\t\tblock.sourceReceived++;\n\t\tif (arrivalTs < block.firstSymbolTs) block.firstSymbolTs = arrivalTs;\n\t}\n\treturn block;\n}\n\n/**\n * iso-ssbg1-v1 repair admission. The block is `[SS_Start, SS_Start +\n * SSB_length)` keyed by SS_Start, with repair ESI = SSB_length + RS_ID. A\n * known block's SSB_length is checked before deduplication because the\n * dedup key depends on it.\n */\nfunction feedIsoRepair(\n\tcmd: Extract<FecWorkerCommand, { type: \"feedRepair\" }>,\n\tview: Uint8Array,\n\tdecoder: WasmFecDecoder,\n): void {\n\tconst key = cmd.ssStart;\n\tif (isRetiredSbn(key)) return;\n\tconst existingBlock = blocks.get(key);\n\tif (existingBlock && isTerminalBlock(existingBlock)) return;\n\tif (existingBlock) {\n\t\tif (existingBlock.ssbLength !== cmd.ssbLength) {\n\t\t\temit({\n\t\t\t\ttype: \"error\",\n\t\t\t\tcode: \"repair-ssb-length-conflict\",\n\t\t\t\tmessage: `[FecWorker] Dropping repair symbol whose SSB_length ${cmd.ssbLength} conflicts with ${existingBlock.ssbLength} (ssStart=${key})`,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t} else {\n\t\tconst overlapped = findOverlappingIsoBlock(key, cmd.ssbLength);\n\t\tif (overlapped) {\n\t\t\temit({\n\t\t\t\ttype: \"error\",\n\t\t\t\tcode: \"repair-block-overlap\",\n\t\t\t\tmessage: `[FecWorker] Dropping repair symbol whose block (ssStart=${key}, SSB_length=${cmd.ssbLength}) overlaps known block (ssStart=${overlapped.sbn}, SSB_length=${isoKnownLength(overlapped)})`,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t}\n\tconst repairEsi = cmd.ssbLength + cmd.rsId;\n\tif (acceptedRepairEsis.get(key)?.has(repairEsi)) return;\n\n\tconst result = decoder.add_repair(key, cmd.ssbLength, cmd.rsId, view, cmd.ts);\n\tadvanceMediaHorizon(key);\n\tconst block = existingBlock ?? placeIsoBlock(key, cmd.ssbLength);\n\tacceptId(acceptedRepairEsis, key, repairEsi);\n\tblock.repairReceived++;\n\tblock.lastSymbolTs = performance.now();\n\trepairSymbols++;\n\tsettleRepairArrival(key, block, cmd.ssbLength, result);\n}\n\n// --- Command handlers ---\n\nasync function handleConfigure(\n\tcmd: Extract<FecWorkerCommand, { type: \"configure\" }>,\n): Promise<void> {\n\tif (!cmd.wasmModule) {\n\t\tthrow new Error(\"[FecWorker] configure: wasmModule required\");\n\t}\n\tif (!cmd.config) {\n\t\tthrow new Error(\"[FecWorker] configure: config required\");\n\t}\n\t// The block profile is the one discriminator for two wire-identical block\n\t// maths, so it is required and never defaulted (BLO-35597).\n\tconst blockProfile: unknown = cmd.config.blockProfile;\n\tif (blockProfile === undefined) {\n\t\tthrow new Error(\n\t\t\t'[FecWorker] configure: blockProfile is required: name the FEC block profile (\"iso-ssbg1-v1\")',\n\t\t);\n\t}\n\tif (blockProfile !== \"iso-ssbg1-v1\") {\n\t\tthrow new Error(\n\t\t\t`[FecWorker] configure: blockProfile must be \"iso-ssbg1-v1\" (got ${JSON.stringify(blockProfile)})`,\n\t\t);\n\t}\n\tif (\n\t\t!Number.isFinite(cmd.config.interleaveDepth) ||\n\t\t!Number.isInteger(cmd.config.interleaveDepth) ||\n\t\tcmd.config.interleaveDepth <= 0 ||\n\t\tcmd.config.interleaveDepth > MAX_INTERLEAVE_DEPTH\n\t) {\n\t\tthrow new Error(\n\t\t\t`[FecWorker] configure: interleaveDepth must be an integer in 1..${MAX_INTERLEAVE_DEPTH} (got ${cmd.config.interleaveDepth})`,\n\t\t);\n\t}\n\tif (\n\t\t!Number.isFinite(cmd.config.interleaveMs) ||\n\t\t!Number.isSafeInteger(cmd.config.interleaveMs) ||\n\t\tcmd.config.interleaveMs < 0\n\t) {\n\t\tthrow new Error(\n\t\t\t`[FecWorker] configure: interleaveMs must be a finite non-negative integer (got ${cmd.config.interleaveMs})`,\n\t\t);\n\t}\n\tif (\n\t\t!Number.isFinite(cmd.config.deliveryWindowMs) ||\n\t\tcmd.config.deliveryWindowMs <= 0\n\t) {\n\t\tthrow new Error(\n\t\t\t`[FecWorker] configure: deliveryWindowMs must be > 0 (got ${cmd.config.deliveryWindowMs})`,\n\t\t);\n\t}\n\tif (\n\t\t!Number.isInteger(cmd.config.k) ||\n\t\tcmd.config.k <= 0 ||\n\t\tcmd.config.k > MAX_RAPTORQ_SOURCE_SYMBOLS_PER_BLOCK\n\t) {\n\t\tthrow new Error(\n\t\t\t`[FecWorker] configure: k must be an integer in 1..${MAX_RAPTORQ_SOURCE_SYMBOLS_PER_BLOCK} (got ${cmd.config.k})`,\n\t\t);\n\t}\n\tif (\n\t\t!Number.isInteger(cmd.config.symbolSize) ||\n\t\tcmd.config.symbolSize <= 0 ||\n\t\tcmd.config.symbolSize > MAX_SYMBOL_SIZE ||\n\t\tcmd.config.symbolSize % 8 !== 0\n\t) {\n\t\tthrow new Error(\n\t\t\t`[FecWorker] configure: symbolSize must be an 8-byte-aligned integer in 8..${MAX_SYMBOL_SIZE} (got ${cmd.config.symbolSize})`,\n\t\t);\n\t}\n\n\tconst wasm = resolveWasmBindings();\n\n\t// Initialize WASM from pre-compiled Module (no fetch, no re-compile)\n\twasm.initSync({ module: cmd.wasmModule });\n\n\t// A configure command starts a new decoder epoch. No terminal tombstone,\n\t// accepted-symbol ID, or active block from the previous decoder may affect it.\n\twasmDecoder?.free();\n\twasmDecoder = null;\n\tclearBlockTracking();\n\n\t// Decoder policy and geometry are explicit at the epoch boundary. The WASM\n\t// module has no guessed K/T/interleave slow path.\n\tconst decoder = new wasm.MmtFecDecoder(cmd.config.interleaveDepth, blockProfile);\n\tverifyDecoderProfile(decoder, blockProfile);\n\twasmDecoder = decoder;\n\tconfig = cmd.config;\n\twasmDecoder.configure_params(config.k, config.symbolSize);\n\n\t// Tell the client which optional commands this Worker accepts, so it can\n\t// batch symbols without risking a Worker that predates the batch command.\n\temit({ type: \"capabilities\", commands: [\"feedSymbols\", \"subscribeBlockUpdates\"] });\n\n\t// Start periodic snapshot emission (200ms floor)\n\tstartSnapshotTimer();\n\n\t// Emit initial snapshot\n\temitSnapshot();\n}\n\nfunction handleFeedSource(\n\tcmd: Extract<FecWorkerCommand, { type: \"feedSource\" }>,\n): void {\n\tif (!wasmDecoder) {\n\t\tthrow new Error(\"[FecWorker] feedSource: not configured\");\n\t}\n\tif (!config) {\n\t\tthrow new Error(\"[FecWorker] feedSource: config missing\");\n\t}\n\tif (\n\t\t!isUint32(cmd.ssId) ||\n\t\t!(cmd.data instanceof ArrayBuffer)\n\t) {\n\t\tthrow new Error(\"[FecWorker] feedSource: invalid source symbol\");\n\t}\n\n\tfeedIsoSource(cmd, new Uint8Array(cmd.data), wasmDecoder, config);\n}\n\nfunction handleFeedRepair(\n\tcmd: Extract<FecWorkerCommand, { type: \"feedRepair\" }>,\n): void {\n\tif (!wasmDecoder) {\n\t\tthrow new Error(\"[FecWorker] feedRepair: not configured\");\n\t}\n\tif (!config) {\n\t\tthrow new Error(\"[FecWorker] feedRepair: config missing\");\n\t}\n\t// iso-ssbg1-v1 blocks start at any SS_ID and repair ESI = SSB_length + RS_ID.\n\tif (\n\t\t!isUint32(cmd.ssStart) ||\n\t\t!isUint32(cmd.ssbLength) ||\n\t\tcmd.ssbLength <= 0 ||\n\t\tcmd.ssbLength > config.k ||\n\t\t!isUint32(cmd.rsId) ||\n\t\tcmd.ssbLength + cmd.rsId > UINT24_MAX ||\n\t\t!(cmd.data instanceof ArrayBuffer)\n\t) {\n\t\tthrow new Error(\"[FecWorker] feedRepair: invalid repair symbol\");\n\t}\n\n\tconst view = new Uint8Array(cmd.data);\n\tif (config.symbolSize > 0 && view.byteLength !== config.symbolSize) {\n\t\trepairSymbolSizeMismatch++;\n\t\tif (repairSymbolSizeMismatch <= 5 || repairSymbolSizeMismatch % 100 === 0) {\n\t\t\temit({\n\t\t\t\ttype: \"error\",\n\t\t\t\tcode: \"repair-symbol-size-mismatch\",\n\t\t\t\tmessage: `[FecWorker] Dropping repair symbol with invalid size: got ${view.byteLength}B, expected ${config.symbolSize}B (ssStart=${cmd.ssStart}, ssbLength=${cmd.ssbLength}, rsId=${cmd.rsId})`,\n\t\t\t});\n\t\t}\n\t\treturn;\n\t}\n\tfeedIsoRepair(cmd, view, wasmDecoder);\n}\n\nfunction handleRelayBlockSig(\n\tcmd: Extract<FecWorkerCommand, { type: \"relayBlockSig\" }>,\n): void {\n\t// No-op AUTH stub: store sig on block, mark as not-yet-verified\n\tconst block = blocks.get(cmd.sbn);\n\tif (block) {\n\t\t// Relay block signatures are not verified here; source provenance is the\n\t\t// signed per-group manifest checked at router admission.\n\t\tblock.relayBlockSigValid = null;\n\t\temitBlockUpdate(block);\n\t}\n}\n\nfunction handleCleanup(\n\tcmd: Extract<FecWorkerCommand, { type: \"cleanup\" }>,\n): void {\n\tif (\n\t\t!Array.isArray(cmd.sbns) ||\n\t\tcmd.sbns.length === 0 ||\n\t\t!cmd.sbns.every(isUint32)\n\t) {\n\t\tthrow new Error(\"[FecWorker] cleanup: invalid SBNs\");\n\t}\n\tconst sbns = [...new Set(cmd.sbns)].sort((a, b) => a - b);\n\tconst pendingSbns = sbns.filter((sbn) => !isRetiredSbn(sbn));\n\ttry {\n\t\tif (!wasmDecoder) {\n\t\t\tthrow new Error(\"[FecWorker] cleanup: not configured\");\n\t\t}\n\n\t\t// Call WASM cleanup only after all earlier symbol commands have drained,\n\t\t// and only for the exact SBNs the manager's deadlines named; command\n\t\t// coalescing must not infer ownership from the largest timed-out SBN.\n\t\tif (pendingSbns.length > 0) {\n\t\t\twasmDecoder.cleanup_blocks_detailed(Uint32Array.from(pendingSbns));\n\t\t}\n\t\t// Manager deadlines are block-scoped and may expire a newer SBN while an\n\t\t// older interleaved block is still live. Only accepted media progression\n\t\t// advances the shared JS/Rust horizon; cleanup creates exact tombstones.\n\n\t\t// Rust already finalized every SBN behind the media frontier. For each\n\t\t// remaining exact request, mirror the block-scoped WASM boundary immediately\n\t\t// so JS never retains state for a decoder block that is gone.\n\t\tfor (const sbn of pendingSbns) {\n\t\t\tlet currentBlock = blocks.get(sbn);\n\t\t\tif (currentBlock && isTerminalBlock(currentBlock)) {\n\t\t\t\tretireAcceptedIds(sbn);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tcurrentBlock ??= getOrCreateBlock(sbn);\n\t\t\t// Compare with the block's SSB_length; a key no repair declared has\n\t\t\t// none, so a block the Worker never knew can only have failed.\n\t\t\tconst authoredLength = currentBlock.ssbLength;\n\t\t\tif (authoredLength !== undefined && currentBlock.sourceReceived >= authoredLength) {\n\t\t\t\tmarkBlockTerminal(currentBlock, \"complete\");\n\t\t\t\tblocksComplete++;\n\t\t\t} else {\n\t\t\t\tmarkBlockTerminal(currentBlock, \"failed\");\n\t\t\t\tblocksFailed++;\n\t\t\t}\n\t\t\temitBlockUpdate(currentBlock);\n\t\t}\n\t} finally {\n\t\t// Message ordering makes this an acknowledgement that every source/repair\n\t\t// command posted before cleanup has completed. The main thread must not\n\t\t// commit failure accounting until it observes this event.\n\t\temit({ type: \"cleanupComplete\", sbns });\n\t}\n}\n\nfunction handleDispose(): void {\n\t// Clear snapshot timer (MUST clear to prevent leaks)\n\tif (snapshotTimer !== null) {\n\t\tclearInterval(snapshotTimer);\n\t\tsnapshotTimer = null;\n\t}\n\n\t// Free WASM decoder (MUST call to release WASM memory)\n\tif (wasmDecoder) {\n\t\twasmDecoder.free();\n\t\twasmDecoder = null;\n\t}\n\n\tconfig = null;\n\tclearBlockTracking();\n}\n\nfunction handleReset(): void {\n\ttry {\n\t\twasmDecoder?.flush();\n\t\twasmDecoder?.reset_stats();\n\t} catch (err) {\n\t\temit({\n\t\t\ttype: \"error\",\n\t\t\tcode: \"reset-failed\",\n\t\t\tmessage:\n\t\t\t\terr instanceof Error\n\t\t\t\t\t? `[FecWorker] reset failed: ${err.message}`\n\t\t\t\t\t: `[FecWorker] reset failed: ${String(err)}`,\n\t\t});\n\t\treturn;\n\t}\n\n\tclearBlockTracking();\n\tsourceSymbols = 0;\n\trepairSymbols = 0;\n\tblocksComplete = 0;\n\tblocksRecovered = 0;\n\tblocksFailed = 0;\n\trelayBlocksVerified = 0;\n\trelayBlocksFailed = 0;\n\trepairSymbolSizeMismatch = 0;\n\tsourceDroppedRetired = 0;\n\tsourceDroppedTerminal = 0;\n\tsourceDroppedDuplicate = 0;\n\tsourceUnplacedRetired = 0;\n\temit({ type: \"resetComplete\" });\n\tif (config) emitSnapshot();\n}\n\n/**\n * A batch of symbols fed in one page task (BLO-36599). Each record is\n * handled exactly as its single-symbol command would be, in order, and a\n * failing record reports the same `command-failed` error that command would\n * without stopping the records after it.\n */\nfunction handleFeedSymbols(\n\tcmd: Extract<FecWorkerCommand, { type: \"feedSymbols\" }>,\n): void {\n\tif (!Array.isArray(cmd.symbols)) {\n\t\tthrow new Error(\"[FecWorker] feedSymbols: symbols must be an array\");\n\t}\n\tfor (const symbol of cmd.symbols) {\n\t\ttry {\n\t\t\tif (symbol?.type === \"feedSource\") handleFeedSource(symbol);\n\t\t\telse if (symbol?.type === \"feedRepair\") handleFeedRepair(symbol);\n\t\t\telse {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[FecWorker] feedSymbols: unknown symbol type ${String((symbol as { type?: unknown } | null)?.type)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t} catch (err) {\n\t\t\temit({\n\t\t\t\ttype: \"error\",\n\t\t\t\tcode: \"command-failed\",\n\t\t\t\tmessage:\n\t\t\t\t\terr instanceof Error ? err.message : `[FecWorker] ${String(err)}`,\n\t\t\t});\n\t\t}\n\t}\n}\n\n// --- Main message handler ---\n\nasync function dispatchCommand(cmd: FecWorkerCommand): Promise<void> {\n\ttry {\n\t\tswitch (cmd.type) {\n\t\t\tcase \"configure\":\n\t\t\t\t// Await handleConfigure so feed/cleanup messages cannot run against a partially\n\t\t\t\t// configured decoder.\n\t\t\t\tawait handleConfigure(cmd);\n\t\t\t\tbreak;\n\t\t\tcase \"feedSource\":\n\t\t\t\thandleFeedSource(cmd);\n\t\t\t\tbreak;\n\t\t\tcase \"feedRepair\":\n\t\t\t\thandleFeedRepair(cmd);\n\t\t\t\tbreak;\n\t\t\tcase \"feedSymbols\":\n\t\t\t\thandleFeedSymbols(cmd);\n\t\t\t\tbreak;\n\t\t\tcase \"relayBlockSig\":\n\t\t\t\thandleRelayBlockSig(cmd);\n\t\t\t\tbreak;\n\t\t\tcase \"cleanup\":\n\t\t\t\thandleCleanup(cmd);\n\t\t\t\tbreak;\n\t\t\tcase \"reset\":\n\t\t\t\thandleReset();\n\t\t\t\tbreak;\n\t\t\tcase \"subscribeBlockUpdates\":\n\t\t\t\tblockUpdatesSubscribed = true;\n\t\t\t\tbreak;\n\t\t\tcase \"dispose\":\n\t\t\t\thandleDispose();\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\temit({\n\t\t\t\t\ttype: \"error\",\n\t\t\t\t\tcode: \"unknown-command\",\n\t\t\t\t\tmessage: `Unknown command type: ${(cmd as Record<string, unknown>).type}`,\n\t\t\t\t});\n\t\t}\n\t} catch (err) {\n\t\temit({\n\t\t\ttype: \"error\",\n\t\t\tcode: \"command-failed\",\n\t\t\tmessage:\n\t\t\t\terr instanceof Error ? err.message : `[FecWorker] ${String(err)}`,\n\t\t});\n\t}\n}\n\nlet commandQueue: Promise<void> = Promise.resolve();\n\nself.onmessage = (e: MessageEvent<FecWorkerCommand>) => {\n\tconst cmd = e.data;\n\tcommandQueue = commandQueue.then(() => dispatchCommand(cmd));\n};\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Return a fresh ArrayBuffer containing exactly the bytes in `view`.\n *\n * Worker outputs must not transfer buffers that might be owned by a WASM\n * binding or by a larger shared packet. A fresh JS-owned buffer gives\n * postMessage() exclusive ownership without detaching unrelated memory.\n */\nexport function toOwnedArrayBuffer(view: Uint8Array): ArrayBuffer {\n\tconst copy = new Uint8Array(view.byteLength);\n\tcopy.set(view);\n\treturn copy.buffer;\n}\n", "/**\n * Order of iso-ssbg1-v1 FEC blocks on the 32-bit SS_ID (ISO/IEC 23008-1:2023\n * C.5.2/C.5.3; RFC 1982 serial arithmetic, SERIAL_BITS = 32; BLO-35597 iso:5).\n * The K-stride (SBN * K) order of the retired k-padded-v1 profile is gone.\n */\n\nconst SPAN = 0x1_0000_0000;\nconst HALF = 0x8000_0000;\n\n// --- iso-ssbg1-v1 (ISO/IEC 23008-1 C.3.3 ssbg_mode1; C.5.2/C.5.3) ---------\n//\n// Under the ISO profile SS_ID increments by one per FEC source packet and\n// wraps at 2^32, and a block is the SSB_length consecutive SS_IDs\n// `[SS_Start, SS_Start + SSB_length)`. Blocks are keyed and ordered by their\n// SS_Start, never by an SBN derived from K: the catalog K is only the maximum\n// block length (kMax). The canonical helpers are @blockcast/mmt-base\n// `serial32Distance` / `serial32Precedes` / `isoBlockContains` /\n// `isoBlocksBehindLowerBound`; this bundle carries its own copies (it does not\n// depend on mmt-base), and block-order.test.ts pins them to the same vectors.\n\n/** Forward RFC 1982 distance from `from` to `to` on the 32-bit SS_ID, in 0..2^32-1. */\nexport function serialDistance(from: number, to: number): number {\n\treturn (to - from + SPAN) % SPAN;\n}\n\n/** Whether `a` precedes `b` under RFC 1982 serial comparison (SERIAL_BITS = 32). */\nexport function serialPrecedes(a: number, b: number): boolean {\n\tconst distance = serialDistance(a, b);\n\treturn distance !== 0 && distance < HALF;\n}\n\n/** Whether `ssId` is one of the `ssbLength` consecutive SS_IDs starting at `ssStart`, mod 2^32. */\nexport function isoBlockContains(ssStart: number, ssbLength: number, ssId: number): boolean {\n\treturn serialDistance(ssStart, ssId) < ssbLength;\n}\n\n/** Whether two SS_ID ranges `[start, start + length)` share any SS_ID, mod 2^32. */\nexport function isoBlocksOverlap(\n\taStart: number,\n\taLength: number,\n\tbStart: number,\n\tbLength: number,\n): boolean {\n\treturn isoBlockContains(aStart, aLength, bStart) || isoBlockContains(bStart, bLength, aStart);\n}\n\n/**\n * How many blocks SS_ID `a` lies behind SS_ID `b` (0 unless `a` precedes `b`):\n * a lower bound, because an iso-ssbg1-v1 block holds at most `kMax` source\n * symbols, so `n * kMax` SS_IDs always span at least `n` blocks. This is the\n * one interleave-horizon metric every ISO receiver layer shares (decoder\n * retention, worker retirement, ordered-relay settlement).\n */\nexport function isoBlocksBehind(a: number, b: number, kMax: number): number {\n\tif (!Number.isSafeInteger(kMax) || kMax <= 0) {\n\t\tthrow new Error(`iso-ssbg1-v1 block horizon needs a positive maximum K (got ${kMax})`);\n\t}\n\treturn serialPrecedes(a, b) ? Math.floor(serialDistance(a, b) / kMax) : 0;\n}\n", "/**\n * @blockcast/fec-worker \u2014 Worker Entry Point\n *\n * Runs inside DedicatedWorkerGlobalScope. Receives FecWorkerCommand messages,\n * drives the MmtFecDecoder WASM module, emits FecWorkerEvent messages back.\n *\n * WASM bindings are resolved via:\n * 1. (self as any).__MMT_WASM_BINDINGS__ \u2014 set by consumer's loader/importmap\n * 2. Falls back to error if not available (no defaults)\n *\n * This file must NOT import DOM globals incompatible with Worker scope.\n */\n\nimport type {\n\tFecWorkerCommand,\n\tFecWorkerEvent,\n\tFecTrackConfig,\n\tFecTrackStats,\n\tFecBlockSnapshot,\n\tFrameMeta,\n\tFecBlockProfile,\n} from \"./fec-worker-types.js\";\nimport { toOwnedArrayBuffer } from \"./transfer.js\";\nimport {\n\tisoBlockContains,\n\tisoBlocksBehind,\n\tisoBlocksOverlap,\n\tserialDistance,\n\tserialPrecedes,\n} from \"./block-order.js\";\n\n// --- WASM binding types (from mmt-wasm) ---\n// Signatures MUST match a freshly generated external mmt_wasm.d.ts exactly.\n// The decoder derives SBN/ESI internally \u2014 do not pre-compute.\ninterface WasmFecDecoder {\n\t/**\n\t * ISO 23008-1 \u00A7C.5.2: ss_id is the flat 32-bit Source Symbol ID; data is\n\t * the protected packet, unpadded. The decoder builds the ssbg_mode1 symbol\n\t * and holds it until a repair names the block containing it.\n\t */\n\tadd_source(\n\t\tss_id: number,\n\t\tdata: Uint8Array,\n\t\ttimestamp: number,\n\t): Uint8Array | undefined;\n\t/**\n\t * ISO 23008-1 \u00A7C.5.3: the block is `[ss_start, ss_start + ssb_length)`\n\t * with K' = ssb_length and repair ESI = ssb_length + rs_id; a recovered\n\t * block is ssb_length x T bytes of ssbg_mode1 symbols.\n\t */\n\tadd_repair(\n\t\tss_start: number,\n\t\tssb_length: number,\n\t\trs_id: number,\n\t\tdata: Uint8Array,\n\t\ttimestamp: number,\n\t): Uint8Array | undefined;\n\tconfigure_params(k: number, symbolSize: number): void;\n\t/**\n\t * Retire the named blocks (last-chance decode + stats), driven by the\n\t * manager's exact deadlines. Nothing is synthesised for a lost block:\n\t * concealment is the sink's hold-last-good-frame.\n\t */\n\tcleanup_blocks_detailed(sbns: Uint32Array): void;\n\t/**\n\t * Remove decoder blocks, and held unplaced sources, more than\n\t * interleaveDepth `isoBlocksBehind` the live SS_ID; returns the SS_Starts\n\t * of the blocks it removed. It runs the decoder's `retire_unplaced` sweep\n\t * itself, so the Worker never calls that separately: synchronizeDecoderHorizon\n\t * calls this after every source and repair it accepts, which is what keeps\n\t * the decoder's unplaced pool bounded when a block's repairs never arrive.\n\t */\n\tcleanup_by_ss_start(current: number): Uint32Array;\n\t/** The block profile the decoder was constructed with. */\n\tget_block_profile(): string;\n\t/** The `fec.algorithm` the decoder was constructed with; absent on builds that predate Reed-Solomon. */\n\tget_algorithm?(): string;\n\tflush(): void;\n\tpending_blocks(): number;\n\treset_stats(): void;\n\tget_stats(): {\n\t\tsource_packets: bigint;\n\t\trepair_packets: bigint;\n\t\tblocks_complete: bigint;\n\t\tblocks_recovered: bigint;\n\t\tblocks_failed: bigint;\n\t\tbytes_recovered: bigint;\n\t\trecovery_rate(): number;\n\t};\n\tfree(): void;\n}\n\ninterface WasmBindings {\n\tinitSync(module: { module: WebAssembly.Module }): unknown;\n\tMmtFecDecoder: new (interleaveDepth: number, blockProfile: FecBlockProfile, algorithm?: FecScheme) => WasmFecDecoder;\n}\n\n// --- Worker state (local to this Worker instance) ---\n\nlet wasmDecoder: WasmFecDecoder | null = null;\nlet config: FecTrackConfig | null = null;\nlet snapshotTimer: ReturnType<typeof setInterval> | null = null;\n// blockUpdate events are posted only after a client subscribes (BLO-36599).\nlet blockUpdatesSubscribed = false;\n\n// Block tracking\nconst blocks = new Map<number, FecBlockSnapshot>();\nconst acceptedSourceEsis = new Map<number, Set<number>>();\nconst acceptedRepairEsis = new Map<number, Set<number>>();\n// iso-ssbg1-v1: sources fed to the decoder before any repair named a block\n// containing them, keyed by SS_ID with their arrival time. Map order is\n// arrival order, which the horizon sweep relies on (retireUnplacedSources).\nconst unplacedSources = new Map<number, number>();\nconst UINT32_MAX = 0xffff_ffff;\nconst UINT24_MAX = 0xff_ffff;\nconst SS_ID_SPAN = 0x1_0000_0000;\n// ISO/IEC 23008-1 C.3.3 ssbg_mode1: the two-octet protected-packet length field.\nconst SSBG1_LENGTH_FIELD_BYTES = 2;\n// ISO/IEC 23008-1 AL-FEC signaling carries interleave_depth in one byte.\nconst MAX_INTERLEAVE_DEPTH = 0xff;\n// Rust stores T as u16 and this decoder profile fixes RFC 6330 Al=8.\nconst MAX_SYMBOL_SIZE = 0xfff8;\n// RFC 6330 systematic table limit, enforced by the vendored raptorq decoder.\nconst MAX_RAPTORQ_SOURCE_SYMBOLS_PER_BLOCK = 56_403;\n// draft-ramadan-moq-fec-01 4.5: K + P <= 255, and a block has at least one repair.\nconst MAX_REED_SOLOMON_SOURCE_SYMBOLS_PER_BLOCK = 254;\n\ntype FecScheme = \"raptorq\" | \"reed-solomon\";\n\n// Aggregate counters\nlet sourceSymbols = 0;\nlet repairSymbols = 0;\nlet blocksComplete = 0;\nlet blocksRecovered = 0;\nlet blocksFailed = 0;\nlet relayBlocksVerified = 0;\nlet relayBlocksFailed = 0;\nlet liveSbn = 0;\n// Whether liveSbn has been set by a symbol of this decoder epoch. Block order\n// is serial on the 32-bit SS_ID (block-order.ts, BLO-35597 iso:5), where 0 is\n// a real position, so \"nothing seen yet\" needs its own state.\nlet liveSbnKnown = false;\nlet repairSymbolSizeMismatch = 0;\n// Source symbols dropped before WASM, by cause. All three sites below were\n// silent, which left the \"block settles at exactly K-1 source symbols\" shape\n// (BLO-35597 defect 2) with no evidence to attribute it to. A retired drop\n// means a late symbol past retention; a terminal drop means the block had\n// already settled \u2014 `state !== \"collecting\"`, so complete OR recovered OR\n// FAILED, and the failed arm is the one this issue is chasing (a block aged out\n// by synchronizeDecoderHorizon stays in `blocks` until its SBN retires, so a\n// late symbol for a short block lands here); a duplicate drop means a repeated\n// (SBN, ESI), which on this wire is usually a plain duplicate packet \u2014 the\n// router's ledger has deliberate gaps \u2014 and only sometimes an SBN/ESI\n// derivation collision.\n//\n// These narrow the cause; they do not uniquely attribute it. A symbol LOST IN\n// TRANSIT never reaches handleFeedSource and increments nothing, so all three\n// reading zero on a short block is NOT evidence of a publisher defect \u2014 wire\n// loss is the null hypothesis here, and it produces an identical shape. The\n// discriminator for that residual arm is `block.ssbLength` (set at the repair\n// symbol's declared SSB_length, conflict-checked on arrival):\n// ssbLength < k \u2192 genuinely short authored block\n// ssbLength === k, sourceReceived < k \u2192 loss or a still-late symbol\n// With no repair symbol yet seen, ssbLength is undefined and both stay open.\nlet sourceDroppedRetired = 0;\nlet sourceDroppedTerminal = 0;\nlet sourceDroppedDuplicate = 0;\nlet sourceUnplacedRetired = 0;\n\n// Recovery timing\nconst recoveryTimes: number[] = [];\n\n// --- Helpers ---\n\nfunction emit(event: FecWorkerEvent, transfer?: Transferable[]): void {\n\tself.postMessage(event, transfer ?? []);\n}\n\nfunction isUint32(value: number): boolean {\n\treturn Number.isInteger(value) && value >= 0 && value <= UINT32_MAX;\n}\n\nfunction isTerminalBlock(block: FecBlockSnapshot): boolean {\n\treturn block.state !== \"collecting\";\n}\n\nfunction retireAcceptedIds(sbn: number): void {\n\tacceptedSourceEsis.delete(sbn);\n\tacceptedRepairEsis.delete(sbn);\n}\n\nfunction clearBlockTracking(): void {\n\tblocks.clear();\n\tacceptedSourceEsis.clear();\n\tacceptedRepairEsis.clear();\n\tunplacedSources.clear();\n\trecoveryTimes.length = 0;\n\tliveSbn = 0;\n\tliveSbnKnown = false;\n}\n\n/**\n * Whether `sbn` has fallen behind the decoder's retained interleave window:\n * more than `interleaveDepth` blocks behind the live SBN. liveSbn only moves\n * forward within a decoder epoch and configure() resets both, so this is the\n * monotonic retirement frontier the decoder horizon implies.\n */\nfunction isRetiredSbn(sbn: number): boolean {\n\tif (config === null || !liveSbnKnown) return false;\n\t// `sbn` is an SS_ID (a block's SS_Start or a source's own SS_ID) and\n\t// liveSbn the live SS_ID; K is only the maximum block length.\n\treturn isoBlocksBehind(sbn, liveSbn, config.k) > config.interleaveDepth;\n}\n\n/**\n * iso-ssbg1-v1: the SS_IDs a known block certainly covers. Every block a\n * repair created has its SSB_length; a cleanup tombstone for a block no\n * repair declared certainly holds only its own SS_Start.\n */\nfunction isoKnownLength(block: FecBlockSnapshot): number {\n\treturn block.ssbLength ?? 1;\n}\n\n/** iso-ssbg1-v1: the known block whose SS_ID range contains `ssId` (ranges never overlap). */\nfunction findIsoBlock(ssId: number): FecBlockSnapshot | undefined {\n\tfor (const block of blocks.values()) {\n\t\tif (isoBlockContains(block.sbn, isoKnownLength(block), ssId)) return block;\n\t}\n\treturn undefined;\n}\n\n/**\n * Whether a terminal block's diagnostics may be deleted. Under iso-ssbg1-v1 a\n * source carries no block identity, so a deleted block cannot refuse a late\n * source of its own: that source would be admitted as unplaced and reopen the\n * block in the decoder. The tombstone therefore stays until its LAST SS_ID is\n * retired, after which the SS_ID retirement check refuses every member.\n */\nfunction isRetiredBlock(key: number, block: FecBlockSnapshot): boolean {\n\treturn isRetiredSbn((key + isoKnownLength(block) - 1) % SS_ID_SPAN);\n}\n\n/**\n * Retire terminal diagnostics only after the decoder's monotonic interleave\n * horizon proves that the SBN can no longer be valid out-of-order media.\n * Admission keeps the compact frontier as the durable tombstone, so deletion\n * never reopens a finalized block.\n */\nfunction trimBlockRetention(): void {\n\tfor (const [sbn, block] of blocks) {\n\t\tif (!isTerminalBlock(block) || !isRetiredBlock(sbn, block)) continue;\n\t\tblocks.delete(sbn);\n\t\tretireAcceptedIds(sbn);\n\t}\n}\n\n/** Advance the live position: an SS_ID / SS_Start, in serial order. */\nfunction advanceMediaHorizon(sbn: number): void {\n\tif (!config) return;\n\tif (liveSbnKnown && !serialPrecedes(liveSbn, sbn)) return;\n\tliveSbn = sbn;\n\tliveSbnKnown = true;\n\ttrimBlockRetention();\n}\n\nfunction markBlockTerminal(\n\tblock: FecBlockSnapshot,\n\tstate: \"complete\" | \"recovered\" | \"failed\",\n): void {\n\tblock.state = state;\n\tblock.completedTs = performance.now();\n\tretireAcceptedIds(block.sbn);\n\ttrimBlockRetention();\n}\n\nfunction synchronizeDecoderHorizon(): void {\n\tif (!wasmDecoder || !liveSbnKnown) return;\n\tconst expired = wasmDecoder.cleanup_by_ss_start(liveSbn);\n\tfor (const sbn of expired) {\n\t\tconst block = blocks.get(sbn);\n\t\tif (!block || isTerminalBlock(block)) {\n\t\t\tretireAcceptedIds(sbn);\n\t\t\tcontinue;\n\t\t}\n\t\tmarkBlockTerminal(block, \"failed\");\n\t\tblocksFailed++;\n\t\temitBlockUpdate(block);\n\t}\n\tretireUnplacedSources();\n\ttrimBlockRetention();\n}\n\n/**\n * iso-ssbg1-v1: retire unplaced sources behind the horizon, by the same\n * isoBlocksBehind metric as a block start, sweeping in arrival order and\n * stopping at the first live one (amortised O(1) per symbol). A source that\n * arrived out of order behind a newer live one is retired when it reaches the\n * front; until then nothing can place it, because any repair range holding it\n * starts at or before it and is itself retired. One the live position moved\n * at least half the SS_ID space past no longer precedes it serially; it is\n * retired too rather than pinning the sweep.\n */\nfunction retireUnplacedSources(): void {\n\tfor (const ssId of unplacedSources.keys()) {\n\t\tconst unreachable = ssId !== liveSbn && !serialPrecedes(ssId, liveSbn);\n\t\tif (!unreachable && !isRetiredSbn(ssId)) break;\n\t\tunplacedSources.delete(ssId);\n\t\tsourceUnplacedRetired++;\n\t}\n}\n\n/**\n * Look up or create a block. `isoSsbLength` creates an iso-ssbg1-v1 block,\n * whose K' is its SSB_length (C.5.3); without it the block has the catalog K.\n */\nfunction getOrCreateBlock(sbn: number, isoSsbLength?: number): FecBlockSnapshot {\n\tlet block = blocks.get(sbn);\n\tif (!block) {\n\t\tif (!config) throw new Error(\"[FecWorker] not configured\");\n\t\tconst now = performance.now();\n\t\tblock = {\n\t\t\tsbn,\n\t\t\tstate: \"collecting\",\n\t\t\tsourceReceived: 0,\n\t\t\trepairReceived: 0,\n\t\t\tk: isoSsbLength ?? config.k,\n\t\t\trepairCount: config.repairCount,\n\t\t\tfirstSymbolTs: now,\n\t\t\tlastSymbolTs: now,\n\t\t\t// A positive interleaveMs makes this the catalog completion deadline;\n\t\t\t// zero makes deliveryWindowMs a receiver-local cleanup bound.\n\t\t\tdeadlineTs: now + config.deliveryWindowMs,\n\t\t\trelayBlockSigValid: null,\n\t\t};\n\t\tif (isoSsbLength !== undefined) block.ssbLength = isoSsbLength;\n\t\tblocks.set(sbn, block);\n\t\ttrimBlockRetention();\n\t}\n\treturn block;\n}\n\nfunction buildStatsSnapshot(): FecTrackStats {\n\tif (!config) throw new Error(\"[FecWorker] not configured\");\n\tconst decoderStats = wasmDecoder?.get_stats();\n\tconst decoder = decoderStats\n\t\t? {\n\t\t\tsourcePackets: Number(decoderStats.source_packets),\n\t\t\trepairPackets: Number(decoderStats.repair_packets),\n\t\t\tblocksComplete: Number(decoderStats.blocks_complete),\n\t\t\tblocksRecovered: Number(decoderStats.blocks_recovered),\n\t\t\tblocksFailed: Number(decoderStats.blocks_failed),\n\t\t\tbytesRecovered: Number(decoderStats.bytes_recovered),\n\t\t\tpendingBlocks: wasmDecoder?.pending_blocks() ?? 0,\n\t\t}\n\t\t: undefined;\n\n\tconst avgRecoveryMs =\n\t\trecoveryTimes.length > 0\n\t\t\t? recoveryTimes.reduce((a, b) => a + b, 0) / recoveryTimes.length\n\t\t\t: 0;\n\tconst maxRecoveryMs =\n\t\trecoveryTimes.length > 0 ? Math.max(...recoveryTimes) : 0;\n\n\t// Cap block array to interleave window\n\tconst maxBlocks = Math.max(config.interleaveDepth * 3, 24);\n\t// Newest first in SS_ID serial order, so the cap keeps the newest blocks\n\t// across the 2^32 wrap too: every block start precedes the live SS_ID.\n\tconst blockArray = Array.from(blocks.values())\n\t\t.sort((a, b) => serialDistance(a.sbn, liveSbn) - serialDistance(b.sbn, liveSbn))\n\t\t.slice(0, maxBlocks);\n\n\treturn {\n\t\tconfig,\n\t\tsourceSymbols,\n\t\trepairSymbols,\n\t\tblocksComplete,\n\t\tblocksRecovered,\n\t\tblocksFailed,\n\t\trecoveryRate:\n\t\t\tblocksComplete + blocksRecovered > 0\n\t\t\t\t? Math.round(\n\t\t\t\t\t\t(blocksRecovered / (blocksRecovered + blocksFailed)) * 100,\n\t\t\t\t\t) || 0\n\t\t\t\t: 0,\n\t\trelayBlocksVerified,\n\t\trelayBlocksFailed,\n\t\tliveSbn,\n\t\tblocks: blockArray,\n\t\tinterleaveWindowBlocks: config.interleaveDepth,\n\t\tavgRecoveryMs: Math.round(avgRecoveryMs * 100) / 100,\n\t\tmaxRecoveryMs: Math.round(maxRecoveryMs * 100) / 100,\n\t\trepairSymbolSizeMismatch,\n\t\tsourceDroppedRetired,\n\t\tsourceDroppedTerminal,\n\t\tsourceDroppedDuplicate,\n\t\tsourceUnplacedRetired,\n\t\tdecoder,\n\t};\n}\n\nfunction emitSnapshot(): void {\n\tif (!config) return;\n\temit({ type: \"snapshot\", stats: buildStatsSnapshot() });\n}\n\nfunction emitBlockUpdate(block: FecBlockSnapshot): void {\n\tif (!blockUpdatesSubscribed) return;\n\temit({ type: \"blockUpdate\", block: { ...block } });\n}\n\nfunction emitRecoveredSymbols(\n\tsbn: number,\n\trecoveredBlock: Uint8Array,\n\tblock: FecBlockSnapshot,\n): void {\n\tif (!config) return;\n\tconst symbolSize = config.symbolSize;\n\tif (symbolSize <= 0) {\n\t\temit({\n\t\t\ttype: \"error\",\n\t\t\tcode: \"recovered-symbol-size-unconfigured\",\n\t\t\tmessage: \"[FecWorker] recovered block has no configured symbolSize\",\n\t\t});\n\t\treturn;\n\t}\n\t// The decoder returns only the block's authored source symbols\n\t// (SSB_length x T); the encoder's zero padding is not media.\n\tconst ssbLength = block.ssbLength ?? config.k;\n\tif (recoveredBlock.byteLength !== symbolSize * ssbLength) {\n\t\temit({\n\t\t\ttype: \"error\",\n\t\t\tcode: \"recovered-block-too-short\",\n\t\t\tmessage: `[FecWorker] recovered block has the wrong length: got ${recoveredBlock.byteLength}B, expected ${symbolSize * ssbLength}B (SSB_length=${ssbLength})`,\n\t\t});\n\t\treturn;\n\t}\n\n\tfor (let i = 0; i < ssbLength; i++) {\n\t\tconst symbol = recoveredBlock.subarray(i * symbolSize, (i + 1) * symbolSize);\n\t\t// ssbg_mode1 symbols are emitted whole: the router trims them by their\n\t\t// length field. A field past T-2 or non-zero padding means the\n\t\t// recovered symbol is not what was sent, so it is refused, not trimmed.\n\t\tconst invalid = invalidSsbg1Symbol(symbol, symbolSize);\n\t\tif (invalid !== null) {\n\t\t\temit({\n\t\t\t\ttype: \"error\",\n\t\t\t\tcode: \"recovered-ssbg1-symbol-invalid\",\n\t\t\t\tmessage: `[FecWorker] Dropping recovered ssbg_mode1 symbol (ssStart=${sbn}, esi=${i}, SSB_length=${ssbLength}): ${invalid}`,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst buf = toOwnedArrayBuffer(symbol);\n\t\tconst meta: FrameMeta = {\n\t\t\trecovered: true,\n\t\t\tsbn,\n\t\t\tesi: i,\n\t\t\tssbLength,\n\t\t};\n\t\temit({ type: \"frame\", data: buf, meta }, [buf]);\n\t}\n}\n\n/** Why a T-byte ssbg_mode1 symbol is malformed (C.3.3), or null when it is well formed. */\nfunction invalidSsbg1Symbol(symbol: Uint8Array, symbolSize: number): string | null {\n\tconst length = (symbol[0]! << 8) | symbol[1]!;\n\tconst maxLength = symbolSize - SSBG1_LENGTH_FIELD_BYTES;\n\tif (length > maxLength) {\n\t\treturn `length field ${length} exceeds T-2 = ${maxLength}`;\n\t}\n\tfor (let j = SSBG1_LENGTH_FIELD_BYTES + length; j < symbolSize; j++) {\n\t\tif (symbol[j] !== 0) return `non-zero padding at byte ${j} after a ${length}-byte packet`;\n\t}\n\treturn null;\n}\n\nfunction startSnapshotTimer(): void {\n\tif (snapshotTimer !== null) clearInterval(snapshotTimer);\n\tsnapshotTimer = setInterval(() => emitSnapshot(), 200);\n}\n\nfunction resolveWasmBindings(): WasmBindings {\n\tconst bindings = (self as unknown as Record<string, unknown>)\n\t\t.__MMT_WASM_BINDINGS__ as WasmBindings | undefined;\n\tif (!bindings) {\n\t\tthrow new Error(\n\t\t\t\"[FecWorker] WASM bindings not available. Set self.__MMT_WASM_BINDINGS__ = { initSync, MmtFecDecoder } before configure.\",\n\t\t);\n\t}\n\treturn bindings;\n}\n\n/**\n * Refuse a decoder that does not run the configured block profile: one that\n * ignored or predates the constructor argument would apply one profile's\n * block math to the other's wire.\n */\nfunction verifyDecoderProfile(decoder: WasmFecDecoder, expected: FecBlockProfile): void {\n\tlet actual: unknown;\n\ttry {\n\t\tactual = decoder.get_block_profile();\n\t} catch (err) {\n\t\tdecoder.free();\n\t\tthrow new Error(\n\t\t\t`[FecWorker] configure: decoder cannot report its block profile (${err instanceof Error ? err.message : String(err)})`,\n\t\t);\n\t}\n\tif (actual !== expected) {\n\t\tdecoder.free();\n\t\tthrow new Error(\n\t\t\t`[FecWorker] configure: decoder runs block profile ${JSON.stringify(actual)}, not the configured \"${expected}\"`,\n\t\t);\n\t}\n}\n\nfunction acceptId(ids: Map<number, Set<number>>, key: number, id: number): void {\n\tlet set = ids.get(key);\n\tif (!set) {\n\t\tset = new Set<number>();\n\t\tids.set(key, set);\n\t}\n\tset.add(id);\n}\n\n/** A block the decoder returned: record its recovery and emit its source symbols. */\nfunction recordRecovered(key: number, block: FecBlockSnapshot, result: Uint8Array): void {\n\tconst recovered = new Uint8Array(toOwnedArrayBuffer(result));\n\tconst recoveryMs = performance.now() - block.firstSymbolTs;\n\trecoveryTimes.push(recoveryMs);\n\t// Cap recovery times array at 1000 entries\n\tif (recoveryTimes.length > 1000) recoveryTimes.shift();\n\n\tif (block.state === \"collecting\") {\n\t\tmarkBlockTerminal(block, \"recovered\");\n\t\tblocksRecovered++;\n\t\temitBlockUpdate(block);\n\t}\n\temitRecoveredSymbols(key, recovered, block);\n}\n\n/** Settle a block after one of its source symbols was accepted. */\nfunction settleSourceArrival(\n\tkey: number,\n\tblock: FecBlockSnapshot,\n\tresult: Uint8Array | undefined,\n): void {\n\t// Check if block is complete (all authored source symbols received:\n\t// SSB_length once a repair symbol declared it, otherwise K)\n\tconst authoredLength = block.ssbLength ?? block.k;\n\tif (\n\t\tblock.sourceReceived >= authoredLength &&\n\t\tblock.state === \"collecting\"\n\t) {\n\t\tmarkBlockTerminal(block, \"complete\");\n\t\tblocksComplete++;\n\t\temitBlockUpdate(block);\n\t}\n\n\t// A source-triggered return can be either clean block completion or recovery\n\t// of earlier missing symbols after repair arrived. Clean completion is\n\t// already routed by the source path, so only emit when the block did not\n\t// reach K source arrivals.\n\tif (result && result.byteLength > 0 && block.sourceReceived < authoredLength) {\n\t\trecordRecovered(key, block, result);\n\t}\n\tsynchronizeDecoderHorizon();\n}\n\n/** Settle a block after one of its repair symbols was accepted. */\nfunction settleRepairArrival(\n\tkey: number,\n\tblock: FecBlockSnapshot,\n\tssbLength: number,\n\tresult: Uint8Array | undefined,\n): void {\n\t// Every authored source symbol already arrived: knowing SSB_length makes\n\t// the block clean. The decoder's return is the source already routed.\n\t// Otherwise, returned data is the recovered source block: its SSB_length\n\t// fixed-width authored symbols concatenated. Split it before emitting so\n\t// MmtpRouter gets the same one-symbol shape as a direct source packet.\n\tif (block.sourceReceived >= ssbLength && block.state === \"collecting\") {\n\t\tmarkBlockTerminal(block, \"complete\");\n\t\tblocksComplete++;\n\t\temitBlockUpdate(block);\n\t} else if (result && result.byteLength > 0) {\n\t\trecordRecovered(key, block, result);\n\t}\n\tsynchronizeDecoderHorizon();\n}\n\n/**\n * iso-ssbg1-v1 source admission. A source inside a known block is accounted\n * at ESI = SS_ID - SS_Start. One no repair has placed yet is held unplaced,\n * deduplicated by SS_ID, and still fed to the decoder, which holds it too,\n * until a repair names a range containing it.\n */\nfunction feedIsoSource(\n\tcmd: Extract<FecWorkerCommand, { type: \"feedSource\" }>,\n\tview: Uint8Array,\n\tdecoder: WasmFecDecoder,\n\tcfg: FecTrackConfig,\n): void {\n\tconst maxPacketLength = cfg.symbolSize - SSBG1_LENGTH_FIELD_BYTES;\n\tif (view.byteLength > maxPacketLength) {\n\t\tthrow new Error(\n\t\t\t`[FecWorker] feedSource: iso-ssbg1-v1 protected packet of ${view.byteLength}B exceeds T-2 = ${maxPacketLength}B`,\n\t\t);\n\t}\n\tconst block = findIsoBlock(cmd.ssId);\n\tif (!block) {\n\t\tif (isRetiredSbn(cmd.ssId)) {\n\t\t\tsourceDroppedRetired++;\n\t\t\treturn;\n\t\t}\n\t\tif (unplacedSources.has(cmd.ssId)) {\n\t\t\tsourceDroppedDuplicate++;\n\t\t\treturn;\n\t\t}\n\t\tconst result = decoder.add_source(cmd.ssId, view, cmd.ts);\n\t\tadvanceMediaHorizon(cmd.ssId);\n\t\tunplacedSources.set(cmd.ssId, performance.now());\n\t\tsourceSymbols++;\n\t\t// No repair has named this source's block, so the decoder cannot have\n\t\t// one to recover; data here is a Worker/decoder disagreement that\n\t\t// cannot be attributed to a block, and is refused rather than guessed.\n\t\tif (result && result.byteLength > 0) {\n\t\t\temit({\n\t\t\t\ttype: \"error\",\n\t\t\t\tcode: \"recovered-block-unplaced\",\n\t\t\t\tmessage: `[FecWorker] Dropping a ${result.byteLength}B recovered block returned for SS_ID ${cmd.ssId}, which no known block contains`,\n\t\t\t});\n\t\t}\n\t\tsynchronizeDecoderHorizon();\n\t\treturn;\n\t}\n\n\tconst key = block.sbn;\n\tif (isRetiredSbn(key)) {\n\t\tsourceDroppedRetired++;\n\t\treturn;\n\t}\n\tif (isTerminalBlock(block)) {\n\t\tsourceDroppedTerminal++;\n\t\treturn;\n\t}\n\tconst esi = serialDistance(key, cmd.ssId);\n\tif (acceptedSourceEsis.get(key)?.has(esi)) {\n\t\tsourceDroppedDuplicate++;\n\t\treturn;\n\t}\n\n\tconst result = decoder.add_source(cmd.ssId, view, cmd.ts);\n\tadvanceMediaHorizon(cmd.ssId);\n\tacceptId(acceptedSourceEsis, key, esi);\n\tblock.sourceReceived++;\n\tblock.lastSymbolTs = performance.now();\n\tsourceSymbols++;\n\tsettleSourceArrival(key, block, result);\n}\n\n/** iso-ssbg1-v1: a known block sharing an SS_ID with `[ssStart, ssStart + ssbLength)`. */\nfunction findOverlappingIsoBlock(\n\tssStart: number,\n\tssbLength: number,\n): FecBlockSnapshot | undefined {\n\tfor (const block of blocks.values()) {\n\t\tif (isoBlocksOverlap(block.sbn, isoKnownLength(block), ssStart, ssbLength)) return block;\n\t}\n\treturn undefined;\n}\n\n/**\n * iso-ssbg1-v1: create the block a repair first named, with K' = SSB_length,\n * and move every unplaced source in its range into its accounting.\n */\nfunction placeIsoBlock(ssStart: number, ssbLength: number): FecBlockSnapshot {\n\tconst block = getOrCreateBlock(ssStart, ssbLength);\n\tfor (let esi = 0; esi < ssbLength; esi++) {\n\t\tconst ssId = (ssStart + esi) % SS_ID_SPAN;\n\t\tconst arrivalTs = unplacedSources.get(ssId);\n\t\tif (arrivalTs === undefined) continue;\n\t\tunplacedSources.delete(ssId);\n\t\tacceptId(acceptedSourceEsis, ssStart, esi);\n\t\tblock.sourceReceived++;\n\t\tif (arrivalTs < block.firstSymbolTs) block.firstSymbolTs = arrivalTs;\n\t}\n\treturn block;\n}\n\n/**\n * iso-ssbg1-v1 repair admission. The block is `[SS_Start, SS_Start +\n * SSB_length)` keyed by SS_Start, with repair ESI = SSB_length + RS_ID. A\n * known block's SSB_length is checked before deduplication because the\n * dedup key depends on it.\n */\nfunction feedIsoRepair(\n\tcmd: Extract<FecWorkerCommand, { type: \"feedRepair\" }>,\n\tview: Uint8Array,\n\tdecoder: WasmFecDecoder,\n): void {\n\tconst key = cmd.ssStart;\n\tif (isRetiredSbn(key)) return;\n\tconst existingBlock = blocks.get(key);\n\tif (existingBlock && isTerminalBlock(existingBlock)) return;\n\tif (existingBlock) {\n\t\tif (existingBlock.ssbLength !== cmd.ssbLength) {\n\t\t\temit({\n\t\t\t\ttype: \"error\",\n\t\t\t\tcode: \"repair-ssb-length-conflict\",\n\t\t\t\tmessage: `[FecWorker] Dropping repair symbol whose SSB_length ${cmd.ssbLength} conflicts with ${existingBlock.ssbLength} (ssStart=${key})`,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t} else {\n\t\tconst overlapped = findOverlappingIsoBlock(key, cmd.ssbLength);\n\t\tif (overlapped) {\n\t\t\temit({\n\t\t\t\ttype: \"error\",\n\t\t\t\tcode: \"repair-block-overlap\",\n\t\t\t\tmessage: `[FecWorker] Dropping repair symbol whose block (ssStart=${key}, SSB_length=${cmd.ssbLength}) overlaps known block (ssStart=${overlapped.sbn}, SSB_length=${isoKnownLength(overlapped)})`,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t}\n\tconst repairEsi = cmd.ssbLength + cmd.rsId;\n\tif (acceptedRepairEsis.get(key)?.has(repairEsi)) return;\n\n\tconst result = decoder.add_repair(key, cmd.ssbLength, cmd.rsId, view, cmd.ts);\n\tadvanceMediaHorizon(key);\n\tconst block = existingBlock ?? placeIsoBlock(key, cmd.ssbLength);\n\tacceptId(acceptedRepairEsis, key, repairEsi);\n\tblock.repairReceived++;\n\tblock.lastSymbolTs = performance.now();\n\trepairSymbols++;\n\tsettleRepairArrival(key, block, cmd.ssbLength, result);\n}\n\n// --- Command handlers ---\n\nasync function handleConfigure(\n\tcmd: Extract<FecWorkerCommand, { type: \"configure\" }>,\n): Promise<void> {\n\tif (!cmd.wasmModule) {\n\t\tthrow new Error(\"[FecWorker] configure: wasmModule required\");\n\t}\n\tif (!cmd.config) {\n\t\tthrow new Error(\"[FecWorker] configure: config required\");\n\t}\n\t// The block profile is the one discriminator for two wire-identical block\n\t// maths, so it is required and never defaulted (BLO-35597).\n\tconst blockProfile: unknown = cmd.config.blockProfile;\n\tif (blockProfile === undefined) {\n\t\tthrow new Error(\n\t\t\t'[FecWorker] configure: blockProfile is required: name the FEC block profile (\"iso-ssbg1-v1\")',\n\t\t);\n\t}\n\tif (blockProfile !== \"iso-ssbg1-v1\") {\n\t\tthrow new Error(\n\t\t\t`[FecWorker] configure: blockProfile must be \"iso-ssbg1-v1\" (got ${JSON.stringify(blockProfile)})`,\n\t\t);\n\t}\n\t// draft-ramadan-moq-fec-01 4: the two specified schemes. Anything but\n\t// \"reed-solomon\" (the legacy \"raptor\" included) is RaptorQ.\n\tconst algorithm: FecScheme = cmd.config.algorithm === \"reed-solomon\" ? \"reed-solomon\" : \"raptorq\";\n\tif (\n\t\t!Number.isFinite(cmd.config.interleaveDepth) ||\n\t\t!Number.isInteger(cmd.config.interleaveDepth) ||\n\t\tcmd.config.interleaveDepth <= 0 ||\n\t\tcmd.config.interleaveDepth > MAX_INTERLEAVE_DEPTH\n\t) {\n\t\tthrow new Error(\n\t\t\t`[FecWorker] configure: interleaveDepth must be an integer in 1..${MAX_INTERLEAVE_DEPTH} (got ${cmd.config.interleaveDepth})`,\n\t\t);\n\t}\n\tif (\n\t\t!Number.isFinite(cmd.config.interleaveMs) ||\n\t\t!Number.isSafeInteger(cmd.config.interleaveMs) ||\n\t\tcmd.config.interleaveMs < 0\n\t) {\n\t\tthrow new Error(\n\t\t\t`[FecWorker] configure: interleaveMs must be a finite non-negative integer (got ${cmd.config.interleaveMs})`,\n\t\t);\n\t}\n\tif (\n\t\t!Number.isFinite(cmd.config.deliveryWindowMs) ||\n\t\tcmd.config.deliveryWindowMs <= 0\n\t) {\n\t\tthrow new Error(\n\t\t\t`[FecWorker] configure: deliveryWindowMs must be > 0 (got ${cmd.config.deliveryWindowMs})`,\n\t\t);\n\t}\n\tconst maxK = algorithm === \"raptorq\" ? MAX_RAPTORQ_SOURCE_SYMBOLS_PER_BLOCK : MAX_REED_SOLOMON_SOURCE_SYMBOLS_PER_BLOCK;\n\tif (!Number.isInteger(cmd.config.k) || cmd.config.k <= 0 || cmd.config.k > maxK) {\n\t\tthrow new Error(`[FecWorker] configure: ${algorithm} k must be an integer in 1..${maxK} (got ${cmd.config.k})`);\n\t}\n\tif (\n\t\t!Number.isInteger(cmd.config.symbolSize) ||\n\t\tcmd.config.symbolSize <= 0 ||\n\t\tcmd.config.symbolSize > MAX_SYMBOL_SIZE ||\n\t\t(algorithm === \"raptorq\" && cmd.config.symbolSize % 8 !== 0)\n\t) {\n\t\tthrow new Error(\n\t\t\t`[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})`,\n\t\t);\n\t}\n\n\tconst wasm = resolveWasmBindings();\n\n\t// Initialize WASM from pre-compiled Module (no fetch, no re-compile)\n\twasm.initSync({ module: cmd.wasmModule });\n\n\t// A configure command starts a new decoder epoch. No terminal tombstone,\n\t// accepted-symbol ID, or active block from the previous decoder may affect it.\n\twasmDecoder?.free();\n\twasmDecoder = null;\n\tclearBlockTracking();\n\n\t// Decoder policy and geometry are explicit at the epoch boundary. The WASM\n\t// module has no guessed K/T/interleave slow path.\n\tconst decoder = new wasm.MmtFecDecoder(cmd.config.interleaveDepth, blockProfile, algorithm);\n\tverifyDecoderProfile(decoder, blockProfile);\n\t// A build that predates the algorithm argument ignores it and runs RaptorQ.\n\tconst decoderAlgorithm = decoder.get_algorithm?.() ?? \"raptorq\";\n\tif (decoderAlgorithm !== algorithm) {\n\t\tdecoder.free();\n\t\tthrow new Error(`[FecWorker] configure: decoder runs ${JSON.stringify(decoderAlgorithm)}, not the configured \"${algorithm}\"`);\n\t}\n\twasmDecoder = decoder;\n\tconfig = cmd.config;\n\twasmDecoder.configure_params(config.k, config.symbolSize);\n\n\t// Tell the client which optional commands this Worker accepts, so it can\n\t// batch symbols without risking a Worker that predates the batch command.\n\temit({ type: \"capabilities\", commands: [\"feedSymbols\", \"subscribeBlockUpdates\"] });\n\n\t// Start periodic snapshot emission (200ms floor)\n\tstartSnapshotTimer();\n\n\t// Emit initial snapshot\n\temitSnapshot();\n}\n\nfunction handleFeedSource(\n\tcmd: Extract<FecWorkerCommand, { type: \"feedSource\" }>,\n): void {\n\tif (!wasmDecoder) {\n\t\tthrow new Error(\"[FecWorker] feedSource: not configured\");\n\t}\n\tif (!config) {\n\t\tthrow new Error(\"[FecWorker] feedSource: config missing\");\n\t}\n\tif (\n\t\t!isUint32(cmd.ssId) ||\n\t\t!(cmd.data instanceof ArrayBuffer)\n\t) {\n\t\tthrow new Error(\"[FecWorker] feedSource: invalid source symbol\");\n\t}\n\n\tfeedIsoSource(cmd, new Uint8Array(cmd.data), wasmDecoder, config);\n}\n\nfunction handleFeedRepair(\n\tcmd: Extract<FecWorkerCommand, { type: \"feedRepair\" }>,\n): void {\n\tif (!wasmDecoder) {\n\t\tthrow new Error(\"[FecWorker] feedRepair: not configured\");\n\t}\n\tif (!config) {\n\t\tthrow new Error(\"[FecWorker] feedRepair: config missing\");\n\t}\n\t// iso-ssbg1-v1 blocks start at any SS_ID and repair ESI = SSB_length + RS_ID.\n\tif (\n\t\t!isUint32(cmd.ssStart) ||\n\t\t!isUint32(cmd.ssbLength) ||\n\t\tcmd.ssbLength <= 0 ||\n\t\tcmd.ssbLength > config.k ||\n\t\t!isUint32(cmd.rsId) ||\n\t\tcmd.ssbLength + cmd.rsId > UINT24_MAX ||\n\t\t!(cmd.data instanceof ArrayBuffer)\n\t) {\n\t\tthrow new Error(\"[FecWorker] feedRepair: invalid repair symbol\");\n\t}\n\n\tconst view = new Uint8Array(cmd.data);\n\tif (config.symbolSize > 0 && view.byteLength !== config.symbolSize) {\n\t\trepairSymbolSizeMismatch++;\n\t\tif (repairSymbolSizeMismatch <= 5 || repairSymbolSizeMismatch % 100 === 0) {\n\t\t\temit({\n\t\t\t\ttype: \"error\",\n\t\t\t\tcode: \"repair-symbol-size-mismatch\",\n\t\t\t\tmessage: `[FecWorker] Dropping repair symbol with invalid size: got ${view.byteLength}B, expected ${config.symbolSize}B (ssStart=${cmd.ssStart}, ssbLength=${cmd.ssbLength}, rsId=${cmd.rsId})`,\n\t\t\t});\n\t\t}\n\t\treturn;\n\t}\n\tfeedIsoRepair(cmd, view, wasmDecoder);\n}\n\nfunction handleRelayBlockSig(\n\tcmd: Extract<FecWorkerCommand, { type: \"relayBlockSig\" }>,\n): void {\n\t// No-op AUTH stub: store sig on block, mark as not-yet-verified\n\tconst block = blocks.get(cmd.sbn);\n\tif (block) {\n\t\t// Relay block signatures are not verified here; source provenance is the\n\t\t// signed per-group manifest checked at router admission.\n\t\tblock.relayBlockSigValid = null;\n\t\temitBlockUpdate(block);\n\t}\n}\n\nfunction handleCleanup(\n\tcmd: Extract<FecWorkerCommand, { type: \"cleanup\" }>,\n): void {\n\tif (\n\t\t!Array.isArray(cmd.sbns) ||\n\t\tcmd.sbns.length === 0 ||\n\t\t!cmd.sbns.every(isUint32)\n\t) {\n\t\tthrow new Error(\"[FecWorker] cleanup: invalid SBNs\");\n\t}\n\tconst sbns = [...new Set(cmd.sbns)].sort((a, b) => a - b);\n\tconst pendingSbns = sbns.filter((sbn) => !isRetiredSbn(sbn));\n\ttry {\n\t\tif (!wasmDecoder) {\n\t\t\tthrow new Error(\"[FecWorker] cleanup: not configured\");\n\t\t}\n\n\t\t// Call WASM cleanup only after all earlier symbol commands have drained,\n\t\t// and only for the exact SBNs the manager's deadlines named; command\n\t\t// coalescing must not infer ownership from the largest timed-out SBN.\n\t\tif (pendingSbns.length > 0) {\n\t\t\twasmDecoder.cleanup_blocks_detailed(Uint32Array.from(pendingSbns));\n\t\t}\n\t\t// Manager deadlines are block-scoped and may expire a newer SBN while an\n\t\t// older interleaved block is still live. Only accepted media progression\n\t\t// advances the shared JS/Rust horizon; cleanup creates exact tombstones.\n\n\t\t// Rust already finalized every SBN behind the media frontier. For each\n\t\t// remaining exact request, mirror the block-scoped WASM boundary immediately\n\t\t// so JS never retains state for a decoder block that is gone.\n\t\tfor (const sbn of pendingSbns) {\n\t\t\tlet currentBlock = blocks.get(sbn);\n\t\t\tif (currentBlock && isTerminalBlock(currentBlock)) {\n\t\t\t\tretireAcceptedIds(sbn);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tcurrentBlock ??= getOrCreateBlock(sbn);\n\t\t\t// Compare with the block's SSB_length; a key no repair declared has\n\t\t\t// none, so a block the Worker never knew can only have failed.\n\t\t\tconst authoredLength = currentBlock.ssbLength;\n\t\t\tif (authoredLength !== undefined && currentBlock.sourceReceived >= authoredLength) {\n\t\t\t\tmarkBlockTerminal(currentBlock, \"complete\");\n\t\t\t\tblocksComplete++;\n\t\t\t} else {\n\t\t\t\tmarkBlockTerminal(currentBlock, \"failed\");\n\t\t\t\tblocksFailed++;\n\t\t\t}\n\t\t\temitBlockUpdate(currentBlock);\n\t\t}\n\t} finally {\n\t\t// Message ordering makes this an acknowledgement that every source/repair\n\t\t// command posted before cleanup has completed. The main thread must not\n\t\t// commit failure accounting until it observes this event.\n\t\temit({ type: \"cleanupComplete\", sbns });\n\t}\n}\n\nfunction handleDispose(): void {\n\t// Clear snapshot timer (MUST clear to prevent leaks)\n\tif (snapshotTimer !== null) {\n\t\tclearInterval(snapshotTimer);\n\t\tsnapshotTimer = null;\n\t}\n\n\t// Free WASM decoder (MUST call to release WASM memory)\n\tif (wasmDecoder) {\n\t\twasmDecoder.free();\n\t\twasmDecoder = null;\n\t}\n\n\tconfig = null;\n\tclearBlockTracking();\n}\n\nfunction handleReset(): void {\n\ttry {\n\t\twasmDecoder?.flush();\n\t\twasmDecoder?.reset_stats();\n\t} catch (err) {\n\t\temit({\n\t\t\ttype: \"error\",\n\t\t\tcode: \"reset-failed\",\n\t\t\tmessage:\n\t\t\t\terr instanceof Error\n\t\t\t\t\t? `[FecWorker] reset failed: ${err.message}`\n\t\t\t\t\t: `[FecWorker] reset failed: ${String(err)}`,\n\t\t});\n\t\treturn;\n\t}\n\n\tclearBlockTracking();\n\tsourceSymbols = 0;\n\trepairSymbols = 0;\n\tblocksComplete = 0;\n\tblocksRecovered = 0;\n\tblocksFailed = 0;\n\trelayBlocksVerified = 0;\n\trelayBlocksFailed = 0;\n\trepairSymbolSizeMismatch = 0;\n\tsourceDroppedRetired = 0;\n\tsourceDroppedTerminal = 0;\n\tsourceDroppedDuplicate = 0;\n\tsourceUnplacedRetired = 0;\n\temit({ type: \"resetComplete\" });\n\tif (config) emitSnapshot();\n}\n\n/**\n * A batch of symbols fed in one page task (BLO-36599). Each record is\n * handled exactly as its single-symbol command would be, in order, and a\n * failing record reports the same `command-failed` error that command would\n * without stopping the records after it.\n */\nfunction handleFeedSymbols(\n\tcmd: Extract<FecWorkerCommand, { type: \"feedSymbols\" }>,\n): void {\n\tif (!Array.isArray(cmd.symbols)) {\n\t\tthrow new Error(\"[FecWorker] feedSymbols: symbols must be an array\");\n\t}\n\tfor (const symbol of cmd.symbols) {\n\t\ttry {\n\t\t\tif (symbol?.type === \"feedSource\") handleFeedSource(symbol);\n\t\t\telse if (symbol?.type === \"feedRepair\") handleFeedRepair(symbol);\n\t\t\telse {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`[FecWorker] feedSymbols: unknown symbol type ${String((symbol as { type?: unknown } | null)?.type)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t} catch (err) {\n\t\t\temit({\n\t\t\t\ttype: \"error\",\n\t\t\t\tcode: \"command-failed\",\n\t\t\t\tmessage:\n\t\t\t\t\terr instanceof Error ? err.message : `[FecWorker] ${String(err)}`,\n\t\t\t});\n\t\t}\n\t}\n}\n\n// --- Main message handler ---\n\nasync function dispatchCommand(cmd: FecWorkerCommand): Promise<void> {\n\ttry {\n\t\tswitch (cmd.type) {\n\t\t\tcase \"configure\":\n\t\t\t\t// Await handleConfigure so feed/cleanup messages cannot run against a partially\n\t\t\t\t// configured decoder.\n\t\t\t\tawait handleConfigure(cmd);\n\t\t\t\tbreak;\n\t\t\tcase \"feedSource\":\n\t\t\t\thandleFeedSource(cmd);\n\t\t\t\tbreak;\n\t\t\tcase \"feedRepair\":\n\t\t\t\thandleFeedRepair(cmd);\n\t\t\t\tbreak;\n\t\t\tcase \"feedSymbols\":\n\t\t\t\thandleFeedSymbols(cmd);\n\t\t\t\tbreak;\n\t\t\tcase \"relayBlockSig\":\n\t\t\t\thandleRelayBlockSig(cmd);\n\t\t\t\tbreak;\n\t\t\tcase \"cleanup\":\n\t\t\t\thandleCleanup(cmd);\n\t\t\t\tbreak;\n\t\t\tcase \"reset\":\n\t\t\t\thandleReset();\n\t\t\t\tbreak;\n\t\t\tcase \"subscribeBlockUpdates\":\n\t\t\t\tblockUpdatesSubscribed = true;\n\t\t\t\tbreak;\n\t\t\tcase \"dispose\":\n\t\t\t\thandleDispose();\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\temit({\n\t\t\t\t\ttype: \"error\",\n\t\t\t\t\tcode: \"unknown-command\",\n\t\t\t\t\tmessage: `Unknown command type: ${(cmd as Record<string, unknown>).type}`,\n\t\t\t\t});\n\t\t}\n\t} catch (err) {\n\t\temit({\n\t\t\ttype: \"error\",\n\t\t\tcode: \"command-failed\",\n\t\t\tmessage:\n\t\t\t\terr instanceof Error ? err.message : `[FecWorker] ${String(err)}`,\n\t\t});\n\t}\n}\n\nlet commandQueue: Promise<void> = Promise.resolve();\n\nself.onmessage = (e: MessageEvent<FecWorkerCommand>) => {\n\tconst cmd = e.data;\n\tcommandQueue = commandQueue.then(() => dispatchCommand(cmd));\n};\n"],
5
+ "mappings": "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",
6
6
  "names": ["result"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockcast/fec-worker",
3
- "version": "0.4.0-main.5bfd894e7d54",
3
+ "version": "0.4.0-main.61c9849c6b9f",
4
4
  "description": "Worker-per-track FEC decode with zero-copy ArrayBuffer transfer",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -38,7 +38,7 @@ export interface FecTrackConfig {
38
38
  /** Audio sample rate (omitted for video) */
39
39
  sampleRate?: number;
40
40
 
41
- /** FEC algorithm identifier: 'raptor', 'reed-solomon', 'none' */
41
+ /** Catalog `fec.algorithm`: `"reed-solomon"` runs the Reed-Solomon decoder; any other value (`"raptorq"`, the legacy `"raptor"`) runs RaptorQ. */
42
42
  algorithm: string;
43
43
  /** Block profile; the Worker refuses to configure without a known token. */
44
44
  blockProfile: FecBlockProfile;
package/src/fec-worker.ts CHANGED
@@ -73,6 +73,8 @@ interface WasmFecDecoder {
73
73
  cleanup_by_ss_start(current: number): Uint32Array;
74
74
  /** The block profile the decoder was constructed with. */
75
75
  get_block_profile(): string;
76
+ /** The `fec.algorithm` the decoder was constructed with; absent on builds that predate Reed-Solomon. */
77
+ get_algorithm?(): string;
76
78
  flush(): void;
77
79
  pending_blocks(): number;
78
80
  reset_stats(): void;
@@ -90,7 +92,7 @@ interface WasmFecDecoder {
90
92
 
91
93
  interface WasmBindings {
92
94
  initSync(module: { module: WebAssembly.Module }): unknown;
93
- MmtFecDecoder: new (interleaveDepth: number, blockProfile: FecBlockProfile) => WasmFecDecoder;
95
+ MmtFecDecoder: new (interleaveDepth: number, blockProfile: FecBlockProfile, algorithm?: FecScheme) => WasmFecDecoder;
94
96
  }
95
97
 
96
98
  // --- Worker state (local to this Worker instance) ---
@@ -120,6 +122,10 @@ const MAX_INTERLEAVE_DEPTH = 0xff;
120
122
  const MAX_SYMBOL_SIZE = 0xfff8;
121
123
  // RFC 6330 systematic table limit, enforced by the vendored raptorq decoder.
122
124
  const MAX_RAPTORQ_SOURCE_SYMBOLS_PER_BLOCK = 56_403;
125
+ // draft-ramadan-moq-fec-01 4.5: K + P <= 255, and a block has at least one repair.
126
+ const MAX_REED_SOLOMON_SOURCE_SYMBOLS_PER_BLOCK = 254;
127
+
128
+ type FecScheme = "raptorq" | "reed-solomon";
123
129
 
124
130
  // Aggregate counters
125
131
  let sourceSymbols = 0;
@@ -753,6 +759,9 @@ async function handleConfigure(
753
759
  `[FecWorker] configure: blockProfile must be "iso-ssbg1-v1" (got ${JSON.stringify(blockProfile)})`,
754
760
  );
755
761
  }
762
+ // draft-ramadan-moq-fec-01 4: the two specified schemes. Anything but
763
+ // "reed-solomon" (the legacy "raptor" included) is RaptorQ.
764
+ const algorithm: FecScheme = cmd.config.algorithm === "reed-solomon" ? "reed-solomon" : "raptorq";
756
765
  if (
757
766
  !Number.isFinite(cmd.config.interleaveDepth) ||
758
767
  !Number.isInteger(cmd.config.interleaveDepth) ||
@@ -780,23 +789,18 @@ async function handleConfigure(
780
789
  `[FecWorker] configure: deliveryWindowMs must be > 0 (got ${cmd.config.deliveryWindowMs})`,
781
790
  );
782
791
  }
783
- if (
784
- !Number.isInteger(cmd.config.k) ||
785
- cmd.config.k <= 0 ||
786
- cmd.config.k > MAX_RAPTORQ_SOURCE_SYMBOLS_PER_BLOCK
787
- ) {
788
- throw new Error(
789
- `[FecWorker] configure: k must be an integer in 1..${MAX_RAPTORQ_SOURCE_SYMBOLS_PER_BLOCK} (got ${cmd.config.k})`,
790
- );
792
+ const maxK = algorithm === "raptorq" ? MAX_RAPTORQ_SOURCE_SYMBOLS_PER_BLOCK : MAX_REED_SOLOMON_SOURCE_SYMBOLS_PER_BLOCK;
793
+ if (!Number.isInteger(cmd.config.k) || cmd.config.k <= 0 || cmd.config.k > maxK) {
794
+ throw new Error(`[FecWorker] configure: ${algorithm} k must be an integer in 1..${maxK} (got ${cmd.config.k})`);
791
795
  }
792
796
  if (
793
797
  !Number.isInteger(cmd.config.symbolSize) ||
794
798
  cmd.config.symbolSize <= 0 ||
795
799
  cmd.config.symbolSize > MAX_SYMBOL_SIZE ||
796
- cmd.config.symbolSize % 8 !== 0
800
+ (algorithm === "raptorq" && cmd.config.symbolSize % 8 !== 0)
797
801
  ) {
798
802
  throw new Error(
799
- `[FecWorker] configure: symbolSize must be an 8-byte-aligned integer in 8..${MAX_SYMBOL_SIZE} (got ${cmd.config.symbolSize})`,
803
+ `[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})`,
800
804
  );
801
805
  }
802
806
 
@@ -813,8 +817,14 @@ async function handleConfigure(
813
817
 
814
818
  // Decoder policy and geometry are explicit at the epoch boundary. The WASM
815
819
  // module has no guessed K/T/interleave slow path.
816
- const decoder = new wasm.MmtFecDecoder(cmd.config.interleaveDepth, blockProfile);
820
+ const decoder = new wasm.MmtFecDecoder(cmd.config.interleaveDepth, blockProfile, algorithm);
817
821
  verifyDecoderProfile(decoder, blockProfile);
822
+ // A build that predates the algorithm argument ignores it and runs RaptorQ.
823
+ const decoderAlgorithm = decoder.get_algorithm?.() ?? "raptorq";
824
+ if (decoderAlgorithm !== algorithm) {
825
+ decoder.free();
826
+ throw new Error(`[FecWorker] configure: decoder runs ${JSON.stringify(decoderAlgorithm)}, not the configured "${algorithm}"`);
827
+ }
818
828
  wasmDecoder = decoder;
819
829
  config = cmd.config;
820
830
  wasmDecoder.configure_params(config.k, config.symbolSize);