@blockcast/mmt-container 0.1.0-main.0399efef1f07
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +432 -0
- package/dist/.build-stamp +0 -0
- package/dist/__tests__/test-utils.d.ts +71 -0
- package/dist/__tests__/test-utils.d.ts.map +1 -0
- package/dist/align.d.ts +152 -0
- package/dist/align.d.ts.map +1 -0
- package/dist/align.js +88 -0
- package/dist/align.js.map +7 -0
- package/dist/audio-init-parser.d.ts +66 -0
- package/dist/audio-init-parser.d.ts.map +1 -0
- package/dist/audio-init-parser.js +118 -0
- package/dist/audio-init-parser.js.map +7 -0
- package/dist/audio-ring-buffer.d.ts +50 -0
- package/dist/audio-ring-buffer.d.ts.map +1 -0
- package/dist/audio-ring-buffer.js +93 -0
- package/dist/audio-ring-buffer.js.map +7 -0
- package/dist/authored-timing.d.ts +32 -0
- package/dist/authored-timing.d.ts.map +1 -0
- package/dist/authored-timing.js +32 -0
- package/dist/authored-timing.js.map +7 -0
- package/dist/browser-runtime.js +20087 -0
- package/dist/browser-runtime.js.map +7 -0
- package/dist/carp-catalog.d.ts +318 -0
- package/dist/carp-catalog.d.ts.map +1 -0
- package/dist/carp-catalog.js +873 -0
- package/dist/carp-catalog.js.map +7 -0
- package/dist/catalog-fec-params.d.ts +202 -0
- package/dist/catalog-fec-params.d.ts.map +1 -0
- package/dist/catalog-fec-params.js +568 -0
- package/dist/catalog-fec-params.js.map +7 -0
- package/dist/catalog-network-source.d.ts +19 -0
- package/dist/catalog-network-source.d.ts.map +1 -0
- package/dist/catalog-network-source.js +22 -0
- package/dist/catalog-network-source.js.map +7 -0
- package/dist/catalog-validator.d.ts +18 -0
- package/dist/catalog-validator.d.ts.map +1 -0
- package/dist/catalog-validator.js +7747 -0
- package/dist/catalog-validator.js.map +7 -0
- package/dist/cmaf-block-assembler.d.ts +217 -0
- package/dist/cmaf-block-assembler.d.ts.map +1 -0
- package/dist/cmaf-block-assembler.js +1769 -0
- package/dist/cmaf-block-assembler.js.map +7 -0
- package/dist/cmaf-group-batcher.d.ts +38 -0
- package/dist/cmaf-group-batcher.d.ts.map +1 -0
- package/dist/cmaf-group-batcher.js +195 -0
- package/dist/cmaf-group-batcher.js.map +7 -0
- package/dist/cmcd-producer.d.ts +104 -0
- package/dist/cmcd-producer.d.ts.map +1 -0
- package/dist/cmcd-producer.js +207 -0
- package/dist/cmcd-producer.js.map +7 -0
- package/dist/codec-utils.d.ts +248 -0
- package/dist/codec-utils.d.ts.map +1 -0
- package/dist/codec-utils.js +1268 -0
- package/dist/codec-utils.js.map +7 -0
- package/dist/fec-codec.d.ts +32 -0
- package/dist/fec-codec.d.ts.map +1 -0
- package/dist/fec-codec.js +47 -0
- package/dist/fec-codec.js.map +7 -0
- package/dist/fec-manager.d.ts +307 -0
- package/dist/fec-manager.d.ts.map +1 -0
- package/dist/fec-manager.js +1530 -0
- package/dist/fec-manager.js.map +7 -0
- package/dist/fec-starvation-detector.d.ts +59 -0
- package/dist/fec-starvation-detector.d.ts.map +1 -0
- package/dist/fec-starvation-detector.js +75 -0
- package/dist/fec-starvation-detector.js.map +7 -0
- package/dist/fec-worker-setup.d.ts +88 -0
- package/dist/fec-worker-setup.d.ts.map +1 -0
- package/dist/fec-worker-setup.js +614 -0
- package/dist/fec-worker-setup.js.map +7 -0
- package/dist/fmp4-completion.d.ts +30 -0
- package/dist/fmp4-completion.d.ts.map +1 -0
- package/dist/fmp4-completion.js +21 -0
- package/dist/fmp4-completion.js.map +7 -0
- package/dist/fmp4-reader.d.ts +37 -0
- package/dist/fmp4-reader.d.ts.map +1 -0
- package/dist/fmp4-reader.js +100 -0
- package/dist/fmp4-reader.js.map +7 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +380 -0
- package/dist/index.js.map +7 -0
- package/dist/iso-bmff-timing.d.ts +78 -0
- package/dist/iso-bmff-timing.d.ts.map +1 -0
- package/dist/iso-bmff-timing.js +618 -0
- package/dist/iso-bmff-timing.js.map +7 -0
- package/dist/loc.d.ts +68 -0
- package/dist/loc.d.ts.map +1 -0
- package/dist/loc.js +143 -0
- package/dist/loc.js.map +7 -0
- package/dist/manifest-auth-plan.d.ts +30 -0
- package/dist/manifest-auth-plan.d.ts.map +1 -0
- package/dist/manifest-auth-plan.js +1285 -0
- package/dist/manifest-auth-plan.js.map +7 -0
- package/dist/media-router.d.ts +251 -0
- package/dist/media-router.d.ts.map +1 -0
- package/dist/media-router.js +624 -0
- package/dist/media-router.js.map +7 -0
- package/dist/mfu-reassembler.d.ts +396 -0
- package/dist/mfu-reassembler.d.ts.map +1 -0
- package/dist/mfu-reassembler.js +3085 -0
- package/dist/mfu-reassembler.js.map +7 -0
- package/dist/mfu-reassembly-policy.d.ts +24 -0
- package/dist/mfu-reassembly-policy.d.ts.map +1 -0
- package/dist/mfu-reassembly-policy.js +99 -0
- package/dist/mfu-reassembly-policy.js.map +7 -0
- package/dist/mmtp-container-parser.d.ts +434 -0
- package/dist/mmtp-container-parser.d.ts.map +1 -0
- package/dist/mmtp-container-parser.js +6164 -0
- package/dist/mmtp-container-parser.js.map +7 -0
- package/dist/mmtp-container-parser.no-modules.js +8999 -0
- package/dist/mmtp-container-parser.no-modules.js.map +7 -0
- package/dist/mmtp-decode-pipeline.d.ts +132 -0
- package/dist/mmtp-decode-pipeline.d.ts.map +1 -0
- package/dist/mmtp-decode-pipeline.js +6413 -0
- package/dist/mmtp-decode-pipeline.js.map +7 -0
- package/dist/mmtp-router.d.ts +390 -0
- package/dist/mmtp-router.d.ts.map +1 -0
- package/dist/mmtp-router.js +9242 -0
- package/dist/mmtp-router.js.map +7 -0
- package/dist/mmtp-to-cmaf.d.ts +181 -0
- package/dist/mmtp-to-cmaf.d.ts.map +1 -0
- package/dist/mmtp-to-cmaf.js +802 -0
- package/dist/mmtp-to-cmaf.js.map +7 -0
- package/dist/multicast-auth-catalog.d.ts +115 -0
- package/dist/multicast-auth-catalog.d.ts.map +1 -0
- package/dist/multicast-auth-catalog.js +202 -0
- package/dist/multicast-auth-catalog.js.map +7 -0
- package/dist/multicast-input.d.ts +107 -0
- package/dist/multicast-input.d.ts.map +1 -0
- package/dist/multicast-input.js +256 -0
- package/dist/multicast-input.js.map +7 -0
- package/dist/multicast-manager.d.ts +369 -0
- package/dist/multicast-manager.d.ts.map +1 -0
- package/dist/multicast-manager.js +13400 -0
- package/dist/multicast-manager.js.map +7 -0
- package/dist/multicast-transport.d.ts +61 -0
- package/dist/multicast-transport.d.ts.map +1 -0
- package/dist/multicast-transport.js +1665 -0
- package/dist/multicast-transport.js.map +7 -0
- package/dist/multicast-unwrap.d.ts +57 -0
- package/dist/multicast-unwrap.d.ts.map +1 -0
- package/dist/multicast-unwrap.js +384 -0
- package/dist/multicast-unwrap.js.map +7 -0
- package/dist/no-modules.d.ts +9 -0
- package/dist/no-modules.d.ts.map +1 -0
- package/dist/no-modules.js +8965 -0
- package/dist/no-modules.js.map +7 -0
- package/dist/phase14-catalog-override.d.ts +126 -0
- package/dist/phase14-catalog-override.d.ts.map +1 -0
- package/dist/phase14-catalog-override.js +155 -0
- package/dist/phase14-catalog-override.js.map +7 -0
- package/dist/repair-fetch.d.ts +58 -0
- package/dist/repair-fetch.d.ts.map +1 -0
- package/dist/repair-fetch.js +127 -0
- package/dist/repair-fetch.js.map +7 -0
- package/dist/repair-layers.d.ts +141 -0
- package/dist/repair-layers.d.ts.map +1 -0
- package/dist/repair-layers.js +303 -0
- package/dist/repair-layers.js.map +7 -0
- package/dist/sample-combiner.d.ts +212 -0
- package/dist/sample-combiner.d.ts.map +1 -0
- package/dist/sample-combiner.js +616 -0
- package/dist/sample-combiner.js.map +7 -0
- package/dist/signaling.d.ts +29 -0
- package/dist/signaling.d.ts.map +1 -0
- package/dist/signaling.js +232 -0
- package/dist/signaling.js.map +7 -0
- package/dist/source-auth.d.ts +2 -0
- package/dist/source-auth.d.ts.map +1 -0
- package/dist/source-auth.js +1238 -0
- package/dist/source-auth.js.map +7 -0
- package/dist/switching-set-alignment.d.ts +61 -0
- package/dist/switching-set-alignment.d.ts.map +1 -0
- package/dist/switching-set-alignment.js +85 -0
- package/dist/switching-set-alignment.js.map +7 -0
- package/dist/timestamp-codec.d.ts +34 -0
- package/dist/timestamp-codec.d.ts.map +1 -0
- package/dist/timestamp-codec.js +456 -0
- package/dist/timestamp-codec.js.map +7 -0
- package/dist/track-pipeline-setup.d.ts +72 -0
- package/dist/track-pipeline-setup.d.ts.map +1 -0
- package/dist/track-pipeline-setup.js +306 -0
- package/dist/track-pipeline-setup.js.map +7 -0
- package/dist/track-pipeline.d.ts +121 -0
- package/dist/track-pipeline.d.ts.map +1 -0
- package/dist/track-pipeline.js +229 -0
- package/dist/track-pipeline.js.map +7 -0
- package/dist/track-session.d.ts +398 -0
- package/dist/track-session.d.ts.map +1 -0
- package/dist/track-session.js +767 -0
- package/dist/track-session.js.map +7 -0
- package/dist/transport-pool.d.ts +69 -0
- package/dist/transport-pool.d.ts.map +1 -0
- package/dist/transport-pool.js +88 -0
- package/dist/transport-pool.js.map +7 -0
- package/dist/wasm-mfu-reassembler.d.ts +92 -0
- package/dist/wasm-mfu-reassembler.d.ts.map +1 -0
- package/dist/wasm-mfu-reassembler.js +2139 -0
- package/dist/wasm-mfu-reassembler.js.map +7 -0
- package/dist/wasm-mmtp-container-parser.d.ts +10 -0
- package/dist/wasm-mmtp-container-parser.d.ts.map +1 -0
- package/dist/wasm-mmtp-container-parser.js +6418 -0
- package/dist/wasm-mmtp-container-parser.js.map +7 -0
- package/node_modules/@blockcast/fec-worker/README.md +213 -0
- package/node_modules/@blockcast/fec-worker/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts +87 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js +279 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts +252 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js +11 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts +14 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker.js +565 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker.js.map +7 -0
- package/node_modules/@blockcast/fec-worker/dist/index.d.ts +10 -0
- package/node_modules/@blockcast/fec-worker/dist/index.d.ts.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/index.js +8 -0
- package/node_modules/@blockcast/fec-worker/dist/index.js.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts +20 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js +349 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js.map +7 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts +104 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js +181 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts +179 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js +47 -0
- package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts +9 -0
- package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts.map +1 -0
- package/node_modules/@blockcast/fec-worker/dist/transfer.js +13 -0
- package/node_modules/@blockcast/fec-worker/dist/transfer.js.map +1 -0
- package/node_modules/@blockcast/fec-worker/package.json +59 -0
- package/node_modules/@blockcast/fec-worker/src/fec-worker-client.ts +319 -0
- package/node_modules/@blockcast/fec-worker/src/fec-worker-types.ts +268 -0
- package/node_modules/@blockcast/fec-worker/src/fec-worker.ts +1048 -0
- package/node_modules/@blockcast/fec-worker/src/index.ts +32 -0
- package/node_modules/@blockcast/fec-worker/src/shred-fec-worker.ts +558 -0
- package/node_modules/@blockcast/fec-worker/src/shred-worker-client.ts +222 -0
- package/node_modules/@blockcast/fec-worker/src/shred-worker-types.ts +194 -0
- package/node_modules/@blockcast/fec-worker/src/transfer.ts +12 -0
- package/node_modules/@blockcast/mmt-alta-parse/README.md +181 -0
- package/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +57 -0
- package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-alta-parse/dist/index.js +115 -0
- package/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +1 -0
- package/node_modules/@blockcast/mmt-alta-parse/package.json +50 -0
- package/node_modules/@blockcast/mmt-alta-parse/src/index.ts +136 -0
- package/node_modules/@blockcast/mmt-base/README.md +120 -0
- package/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/mmt-base/dist/index.d.ts +2 -0
- package/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/index.js +2 -0
- package/node_modules/@blockcast/mmt-base/dist/index.js.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +276 -0
- package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-base/dist/mmtp.js +645 -0
- package/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +1 -0
- package/node_modules/@blockcast/mmt-base/package.json +49 -0
- package/node_modules/@blockcast/mmt-base/src/index.ts +1 -0
- package/node_modules/@blockcast/mmt-base/src/mmtp.ts +899 -0
- package/node_modules/@blockcast/mmt-fec/README.md +270 -0
- package/node_modules/@blockcast/mmt-fec/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts +82 -0
- package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/block-manager.js +296 -0
- package/node_modules/@blockcast/mmt-fec/dist/block-manager.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts +72 -0
- package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/block-processor.js +192 -0
- package/node_modules/@blockcast/mmt-fec/dist/block-processor.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts +46 -0
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js +96 -0
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts +33 -0
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js +57 -0
- package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts +185 -0
- package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js +273 -0
- package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/index.d.ts +22 -0
- package/node_modules/@blockcast/mmt-fec/dist/index.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/index.js +22 -0
- package/node_modules/@blockcast/mmt-fec/dist/index.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts +20 -0
- package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js +28 -0
- package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts +172 -0
- package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/oti.js +254 -0
- package/node_modules/@blockcast/mmt-fec/dist/oti.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts +41 -0
- package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js +81 -0
- package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/README.md +181 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +57 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js +115 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/package.json +50 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/src/index.ts +136 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/README.md +120 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts +2 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js +2 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +276 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js +645 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/package.json +49 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/index.ts +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/mmtp.ts +899 -0
- package/node_modules/@blockcast/mmt-fec/package.json +79 -0
- package/node_modules/@blockcast/mmt-fec/src/block-manager.ts +371 -0
- package/node_modules/@blockcast/mmt-fec/src/block-processor.ts +277 -0
- package/node_modules/@blockcast/mmt-fec/src/cmaf-recovery.ts +127 -0
- package/node_modules/@blockcast/mmt-fec/src/cmaf-symbols.ts +68 -0
- package/node_modules/@blockcast/mmt-fec/src/fec-block-mapper.ts +332 -0
- package/node_modules/@blockcast/mmt-fec/src/index.ts +77 -0
- package/node_modules/@blockcast/mmt-fec/src/loc-symbols.ts +34 -0
- package/node_modules/@blockcast/mmt-fec/src/oti.ts +317 -0
- package/node_modules/@blockcast/mmt-fec/src/reorder-buffer.ts +101 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/.build-stamp +0 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts +28 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js +128 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js.map +1 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts +4 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js +4 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js.map +1 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts +37 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js +179 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js.map +1 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts +99 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts.map +1 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js +492 -0
- package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js.map +1 -0
- package/node_modules/@blockcast/mmt-manifest-verify/package.json +45 -0
- package/node_modules/@blockcast/mmt-manifest-verify/src/admission.ts +171 -0
- package/node_modules/@blockcast/mmt-manifest-verify/src/index.ts +3 -0
- package/node_modules/@blockcast/mmt-manifest-verify/src/source-auth.ts +230 -0
- package/node_modules/@blockcast/mmt-manifest-verify/src/verifier.ts +669 -0
- package/node_modules/@noble/hashes/LICENSE +21 -0
- package/node_modules/@noble/hashes/README.md +521 -0
- package/node_modules/@noble/hashes/_assert.d.ts +17 -0
- package/node_modules/@noble/hashes/_assert.d.ts.map +1 -0
- package/node_modules/@noble/hashes/_assert.js +18 -0
- package/node_modules/@noble/hashes/_assert.js.map +1 -0
- package/node_modules/@noble/hashes/_blake.d.ts +14 -0
- package/node_modules/@noble/hashes/_blake.d.ts.map +1 -0
- package/node_modules/@noble/hashes/_blake.js +50 -0
- package/node_modules/@noble/hashes/_blake.js.map +1 -0
- package/node_modules/@noble/hashes/_md.d.ts +51 -0
- package/node_modules/@noble/hashes/_md.d.ts.map +1 -0
- package/node_modules/@noble/hashes/_md.js +162 -0
- package/node_modules/@noble/hashes/_md.js.map +1 -0
- package/node_modules/@noble/hashes/_u64.d.ts +55 -0
- package/node_modules/@noble/hashes/_u64.d.ts.map +1 -0
- package/node_modules/@noble/hashes/_u64.js +90 -0
- package/node_modules/@noble/hashes/_u64.js.map +1 -0
- package/node_modules/@noble/hashes/argon2.d.ts +32 -0
- package/node_modules/@noble/hashes/argon2.d.ts.map +1 -0
- package/node_modules/@noble/hashes/argon2.js +401 -0
- package/node_modules/@noble/hashes/argon2.js.map +1 -0
- package/node_modules/@noble/hashes/blake1.d.ts +106 -0
- package/node_modules/@noble/hashes/blake1.d.ts.map +1 -0
- package/node_modules/@noble/hashes/blake1.js +459 -0
- package/node_modules/@noble/hashes/blake1.js.map +1 -0
- package/node_modules/@noble/hashes/blake2.d.ts +116 -0
- package/node_modules/@noble/hashes/blake2.d.ts.map +1 -0
- package/node_modules/@noble/hashes/blake2.js +420 -0
- package/node_modules/@noble/hashes/blake2.js.map +1 -0
- package/node_modules/@noble/hashes/blake2b.d.ts +11 -0
- package/node_modules/@noble/hashes/blake2b.d.ts.map +1 -0
- package/node_modules/@noble/hashes/blake2b.js +14 -0
- package/node_modules/@noble/hashes/blake2b.js.map +1 -0
- package/node_modules/@noble/hashes/blake2s.d.ts +20 -0
- package/node_modules/@noble/hashes/blake2s.d.ts.map +1 -0
- package/node_modules/@noble/hashes/blake2s.js +24 -0
- package/node_modules/@noble/hashes/blake2s.js.map +1 -0
- package/node_modules/@noble/hashes/blake3.d.ts +54 -0
- package/node_modules/@noble/hashes/blake3.d.ts.map +1 -0
- package/node_modules/@noble/hashes/blake3.js +255 -0
- package/node_modules/@noble/hashes/blake3.js.map +1 -0
- package/node_modules/@noble/hashes/crypto.d.ts +2 -0
- package/node_modules/@noble/hashes/crypto.d.ts.map +1 -0
- package/node_modules/@noble/hashes/crypto.js +5 -0
- package/node_modules/@noble/hashes/crypto.js.map +1 -0
- package/node_modules/@noble/hashes/cryptoNode.d.ts +2 -0
- package/node_modules/@noble/hashes/cryptoNode.d.ts.map +1 -0
- package/node_modules/@noble/hashes/cryptoNode.js +18 -0
- package/node_modules/@noble/hashes/cryptoNode.js.map +1 -0
- package/node_modules/@noble/hashes/eskdf.d.ts +47 -0
- package/node_modules/@noble/hashes/eskdf.d.ts.map +1 -0
- package/node_modules/@noble/hashes/eskdf.js +166 -0
- package/node_modules/@noble/hashes/eskdf.js.map +1 -0
- package/node_modules/@noble/hashes/esm/_assert.d.ts +17 -0
- package/node_modules/@noble/hashes/esm/_assert.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/_assert.js +15 -0
- package/node_modules/@noble/hashes/esm/_assert.js.map +1 -0
- package/node_modules/@noble/hashes/esm/_blake.d.ts +14 -0
- package/node_modules/@noble/hashes/esm/_blake.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/_blake.js +45 -0
- package/node_modules/@noble/hashes/esm/_blake.js.map +1 -0
- package/node_modules/@noble/hashes/esm/_md.d.ts +51 -0
- package/node_modules/@noble/hashes/esm/_md.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/_md.js +155 -0
- package/node_modules/@noble/hashes/esm/_md.js.map +1 -0
- package/node_modules/@noble/hashes/esm/_u64.d.ts +55 -0
- package/node_modules/@noble/hashes/esm/_u64.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/_u64.js +67 -0
- package/node_modules/@noble/hashes/esm/_u64.js.map +1 -0
- package/node_modules/@noble/hashes/esm/argon2.d.ts +32 -0
- package/node_modules/@noble/hashes/esm/argon2.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/argon2.js +392 -0
- package/node_modules/@noble/hashes/esm/argon2.js.map +1 -0
- package/node_modules/@noble/hashes/esm/blake1.d.ts +106 -0
- package/node_modules/@noble/hashes/esm/blake1.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/blake1.js +452 -0
- package/node_modules/@noble/hashes/esm/blake1.js.map +1 -0
- package/node_modules/@noble/hashes/esm/blake2.d.ts +116 -0
- package/node_modules/@noble/hashes/esm/blake2.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/blake2.js +413 -0
- package/node_modules/@noble/hashes/esm/blake2.js.map +1 -0
- package/node_modules/@noble/hashes/esm/blake2b.d.ts +11 -0
- package/node_modules/@noble/hashes/esm/blake2b.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/blake2b.js +11 -0
- package/node_modules/@noble/hashes/esm/blake2b.js.map +1 -0
- package/node_modules/@noble/hashes/esm/blake2s.d.ts +20 -0
- package/node_modules/@noble/hashes/esm/blake2s.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/blake2s.js +21 -0
- package/node_modules/@noble/hashes/esm/blake2s.js.map +1 -0
- package/node_modules/@noble/hashes/esm/blake3.d.ts +54 -0
- package/node_modules/@noble/hashes/esm/blake3.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/blake3.js +251 -0
- package/node_modules/@noble/hashes/esm/blake3.js.map +1 -0
- package/node_modules/@noble/hashes/esm/crypto.d.ts +2 -0
- package/node_modules/@noble/hashes/esm/crypto.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/crypto.js +2 -0
- package/node_modules/@noble/hashes/esm/crypto.js.map +1 -0
- package/node_modules/@noble/hashes/esm/cryptoNode.d.ts +2 -0
- package/node_modules/@noble/hashes/esm/cryptoNode.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/cryptoNode.js +15 -0
- package/node_modules/@noble/hashes/esm/cryptoNode.js.map +1 -0
- package/node_modules/@noble/hashes/esm/eskdf.d.ts +47 -0
- package/node_modules/@noble/hashes/esm/eskdf.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/eskdf.js +160 -0
- package/node_modules/@noble/hashes/esm/eskdf.js.map +1 -0
- package/node_modules/@noble/hashes/esm/hkdf.d.ts +36 -0
- package/node_modules/@noble/hashes/esm/hkdf.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/hkdf.js +82 -0
- package/node_modules/@noble/hashes/esm/hkdf.js.map +1 -0
- package/node_modules/@noble/hashes/esm/hmac.d.ts +35 -0
- package/node_modules/@noble/hashes/esm/hmac.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/hmac.js +86 -0
- package/node_modules/@noble/hashes/esm/hmac.js.map +1 -0
- package/node_modules/@noble/hashes/esm/index.d.ts +2 -0
- package/node_modules/@noble/hashes/esm/index.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/index.js +33 -0
- package/node_modules/@noble/hashes/esm/index.js.map +1 -0
- package/node_modules/@noble/hashes/esm/legacy.d.ts +71 -0
- package/node_modules/@noble/hashes/esm/legacy.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/legacy.js +281 -0
- package/node_modules/@noble/hashes/esm/legacy.js.map +1 -0
- package/node_modules/@noble/hashes/esm/package.json +10 -0
- package/node_modules/@noble/hashes/esm/pbkdf2.d.ts +23 -0
- package/node_modules/@noble/hashes/esm/pbkdf2.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/pbkdf2.js +97 -0
- package/node_modules/@noble/hashes/esm/pbkdf2.js.map +1 -0
- package/node_modules/@noble/hashes/esm/ripemd160.d.ts +13 -0
- package/node_modules/@noble/hashes/esm/ripemd160.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/ripemd160.js +13 -0
- package/node_modules/@noble/hashes/esm/ripemd160.js.map +1 -0
- package/node_modules/@noble/hashes/esm/scrypt.d.ts +34 -0
- package/node_modules/@noble/hashes/esm/scrypt.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/scrypt.js +228 -0
- package/node_modules/@noble/hashes/esm/scrypt.js.map +1 -0
- package/node_modules/@noble/hashes/esm/sha1.d.ts +11 -0
- package/node_modules/@noble/hashes/esm/sha1.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/sha1.js +11 -0
- package/node_modules/@noble/hashes/esm/sha1.js.map +1 -0
- package/node_modules/@noble/hashes/esm/sha2.d.ts +159 -0
- package/node_modules/@noble/hashes/esm/sha2.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/sha2.js +375 -0
- package/node_modules/@noble/hashes/esm/sha2.js.map +1 -0
- package/node_modules/@noble/hashes/esm/sha256.d.ts +20 -0
- package/node_modules/@noble/hashes/esm/sha256.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/sha256.js +20 -0
- package/node_modules/@noble/hashes/esm/sha256.js.map +1 -0
- package/node_modules/@noble/hashes/esm/sha3-addons.d.ts +142 -0
- package/node_modules/@noble/hashes/esm/sha3-addons.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/sha3-addons.js +393 -0
- package/node_modules/@noble/hashes/esm/sha3-addons.js.map +1 -0
- package/node_modules/@noble/hashes/esm/sha3.d.ts +53 -0
- package/node_modules/@noble/hashes/esm/sha3.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/sha3.js +234 -0
- package/node_modules/@noble/hashes/esm/sha3.js.map +1 -0
- package/node_modules/@noble/hashes/esm/sha512.d.ts +26 -0
- package/node_modules/@noble/hashes/esm/sha512.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/sha512.js +26 -0
- package/node_modules/@noble/hashes/esm/sha512.js.map +1 -0
- package/node_modules/@noble/hashes/esm/utils.d.ts +161 -0
- package/node_modules/@noble/hashes/esm/utils.d.ts.map +1 -0
- package/node_modules/@noble/hashes/esm/utils.js +281 -0
- package/node_modules/@noble/hashes/esm/utils.js.map +1 -0
- package/node_modules/@noble/hashes/hkdf.d.ts +36 -0
- package/node_modules/@noble/hashes/hkdf.d.ts.map +1 -0
- package/node_modules/@noble/hashes/hkdf.js +88 -0
- package/node_modules/@noble/hashes/hkdf.js.map +1 -0
- package/node_modules/@noble/hashes/hmac.d.ts +35 -0
- package/node_modules/@noble/hashes/hmac.d.ts.map +1 -0
- package/node_modules/@noble/hashes/hmac.js +91 -0
- package/node_modules/@noble/hashes/hmac.js.map +1 -0
- package/node_modules/@noble/hashes/index.d.ts +1 -0
- package/node_modules/@noble/hashes/index.d.ts.map +1 -0
- package/node_modules/@noble/hashes/index.js +33 -0
- package/node_modules/@noble/hashes/index.js.map +1 -0
- package/node_modules/@noble/hashes/legacy.d.ts +71 -0
- package/node_modules/@noble/hashes/legacy.d.ts.map +1 -0
- package/node_modules/@noble/hashes/legacy.js +287 -0
- package/node_modules/@noble/hashes/legacy.js.map +1 -0
- package/node_modules/@noble/hashes/package.json +266 -0
- package/node_modules/@noble/hashes/pbkdf2.d.ts +23 -0
- package/node_modules/@noble/hashes/pbkdf2.d.ts.map +1 -0
- package/node_modules/@noble/hashes/pbkdf2.js +101 -0
- package/node_modules/@noble/hashes/pbkdf2.js.map +1 -0
- package/node_modules/@noble/hashes/ripemd160.d.ts +13 -0
- package/node_modules/@noble/hashes/ripemd160.d.ts.map +1 -0
- package/node_modules/@noble/hashes/ripemd160.js +16 -0
- package/node_modules/@noble/hashes/ripemd160.js.map +1 -0
- package/node_modules/@noble/hashes/scrypt.d.ts +34 -0
- package/node_modules/@noble/hashes/scrypt.d.ts.map +1 -0
- package/node_modules/@noble/hashes/scrypt.js +232 -0
- package/node_modules/@noble/hashes/scrypt.js.map +1 -0
- package/node_modules/@noble/hashes/sha1.d.ts +11 -0
- package/node_modules/@noble/hashes/sha1.d.ts.map +1 -0
- package/node_modules/@noble/hashes/sha1.js +14 -0
- package/node_modules/@noble/hashes/sha1.js.map +1 -0
- package/node_modules/@noble/hashes/sha2.d.ts +159 -0
- package/node_modules/@noble/hashes/sha2.d.ts.map +1 -0
- package/node_modules/@noble/hashes/sha2.js +384 -0
- package/node_modules/@noble/hashes/sha2.js.map +1 -0
- package/node_modules/@noble/hashes/sha256.d.ts +20 -0
- package/node_modules/@noble/hashes/sha256.d.ts.map +1 -0
- package/node_modules/@noble/hashes/sha256.js +23 -0
- package/node_modules/@noble/hashes/sha256.js.map +1 -0
- package/node_modules/@noble/hashes/sha3-addons.d.ts +142 -0
- package/node_modules/@noble/hashes/sha3-addons.d.ts.map +1 -0
- package/node_modules/@noble/hashes/sha3-addons.js +402 -0
- package/node_modules/@noble/hashes/sha3-addons.js.map +1 -0
- package/node_modules/@noble/hashes/sha3.d.ts +53 -0
- package/node_modules/@noble/hashes/sha3.d.ts.map +1 -0
- package/node_modules/@noble/hashes/sha3.js +239 -0
- package/node_modules/@noble/hashes/sha3.js.map +1 -0
- package/node_modules/@noble/hashes/sha512.d.ts +26 -0
- package/node_modules/@noble/hashes/sha512.d.ts.map +1 -0
- package/node_modules/@noble/hashes/sha512.js +29 -0
- package/node_modules/@noble/hashes/sha512.js.map +1 -0
- package/node_modules/@noble/hashes/src/_assert.ts +22 -0
- package/node_modules/@noble/hashes/src/_blake.ts +50 -0
- package/node_modules/@noble/hashes/src/_md.ts +176 -0
- package/node_modules/@noble/hashes/src/_u64.ts +91 -0
- package/node_modules/@noble/hashes/src/argon2.ts +497 -0
- package/node_modules/@noble/hashes/src/blake1.ts +534 -0
- package/node_modules/@noble/hashes/src/blake2.ts +486 -0
- package/node_modules/@noble/hashes/src/blake2b.ts +10 -0
- package/node_modules/@noble/hashes/src/blake2s.ts +20 -0
- package/node_modules/@noble/hashes/src/blake3.ts +272 -0
- package/node_modules/@noble/hashes/src/crypto.ts +9 -0
- package/node_modules/@noble/hashes/src/cryptoNode.ts +15 -0
- package/node_modules/@noble/hashes/src/eskdf.ts +187 -0
- package/node_modules/@noble/hashes/src/hkdf.ts +88 -0
- package/node_modules/@noble/hashes/src/hmac.ts +94 -0
- package/node_modules/@noble/hashes/src/index.ts +31 -0
- package/node_modules/@noble/hashes/src/legacy.ts +293 -0
- package/node_modules/@noble/hashes/src/pbkdf2.ts +122 -0
- package/node_modules/@noble/hashes/src/ripemd160.ts +12 -0
- package/node_modules/@noble/hashes/src/scrypt.ts +257 -0
- package/node_modules/@noble/hashes/src/sha1.ts +10 -0
- package/node_modules/@noble/hashes/src/sha2.ts +402 -0
- package/node_modules/@noble/hashes/src/sha256.ts +24 -0
- package/node_modules/@noble/hashes/src/sha3-addons.ts +499 -0
- package/node_modules/@noble/hashes/src/sha3.ts +258 -0
- package/node_modules/@noble/hashes/src/sha512.ts +34 -0
- package/node_modules/@noble/hashes/src/utils.ts +395 -0
- package/node_modules/@noble/hashes/utils.d.ts +161 -0
- package/node_modules/@noble/hashes/utils.d.ts.map +1 -0
- package/node_modules/@noble/hashes/utils.js +313 -0
- package/node_modules/@noble/hashes/utils.js.map +1 -0
- package/package.json +203 -0
- package/schemas/catalog.schema.json +360 -0
- package/src/align.ts +239 -0
- package/src/audio-init-parser.ts +189 -0
- package/src/audio-ring-buffer.ts +120 -0
- package/src/authored-timing.ts +63 -0
- package/src/carp-catalog.ts +845 -0
- package/src/catalog-fec-params.ts +535 -0
- package/src/catalog-network-source.ts +49 -0
- package/src/catalog-validator.ts +34 -0
- package/src/cmaf-block-assembler.ts +1167 -0
- package/src/cmaf-group-batcher.ts +104 -0
- package/src/cmcd-producer.ts +334 -0
- package/src/codec-utils.ts +1611 -0
- package/src/fec-codec.ts +61 -0
- package/src/fec-manager.ts +2081 -0
- package/src/fec-starvation-detector.ts +128 -0
- package/src/fec-worker-setup.ts +213 -0
- package/src/fmp4-completion.ts +47 -0
- package/src/fmp4-reader.ts +112 -0
- package/src/index.ts +486 -0
- package/src/iso-bmff-timing.ts +813 -0
- package/src/loc.ts +181 -0
- package/src/manifest-auth-plan.ts +107 -0
- package/src/media-router.ts +795 -0
- package/src/mfu-reassembler.ts +3741 -0
- package/src/mfu-reassembly-policy.ts +134 -0
- package/src/mmtp-container-parser.ts +1836 -0
- package/src/mmtp-decode-pipeline.ts +389 -0
- package/src/mmtp-router.ts +3136 -0
- package/src/mmtp-to-cmaf.ts +1024 -0
- package/src/multicast-auth-catalog.ts +340 -0
- package/src/multicast-input.ts +439 -0
- package/src/multicast-manager.ts +1720 -0
- package/src/multicast-transport.ts +321 -0
- package/src/multicast-unwrap.ts +132 -0
- package/src/no-modules.ts +20 -0
- package/src/phase14-catalog-override.ts +336 -0
- package/src/repair-fetch.ts +238 -0
- package/src/repair-layers.ts +490 -0
- package/src/sample-combiner.ts +938 -0
- package/src/signaling.ts +78 -0
- package/src/source-auth.ts +1 -0
- package/src/switching-set-alignment.ts +109 -0
- package/src/timestamp-codec.ts +265 -0
- package/src/track-pipeline-setup.ts +189 -0
- package/src/track-pipeline.ts +332 -0
- package/src/track-session.ts +994 -0
- package/src/transport-pool.ts +128 -0
- package/src/wasm-mfu-reassembler.ts +389 -0
- package/src/wasm-mmtp-container-parser.ts +23 -0
- package/test-vectors/align.json +199 -0
- package/test-vectors/catalog/README.md +40 -0
- package/test-vectors/catalog/negative/fec-enhancement-repair-removed.json +27 -0
- package/test-vectors/catalog/negative/fec-repair-layer-geometry.json +32 -0
- package/test-vectors/catalog/negative/fec-repair-layer-missing-symbols.json +30 -0
- package/test-vectors/catalog/negative/fec-repair-multiple-depends.json +31 -0
- package/test-vectors/catalog/negative/fec-repair-overlay-with-layer.json +33 -0
- package/test-vectors/catalog/negative/fec-repair-priority-out-of-band.json +23 -0
- package/test-vectors/catalog/negative/fec-repair-source-symbols-without-scope.json +32 -0
- package/test-vectors/catalog/negative/legacy-selection-params.json +15 -0
- package/test-vectors/catalog/negative/media-track-repair-layer.json +16 -0
- package/test-vectors/catalog/negative/missing-mmtp-fields.json +12 -0
- package/test-vectors/catalog/negative/multicast-auth-padded-key.json +37 -0
- package/test-vectors/catalog/negative/multicast-auth-reused-track-format.json +53 -0
- package/test-vectors/catalog/negative/multicast-auth-trailing-pad-bits.json +37 -0
- package/test-vectors/catalog/negative/multicast-endpoint-missing-protocol-and-source.json +24 -0
- package/test-vectors/catalog/negative/network-source-object.json +25 -0
- package/test-vectors/catalog/negative/raptorq-source-symbols-over-max.json +23 -0
- package/test-vectors/catalog/negative/raptorq-unaligned-symbol.json +26 -0
- package/test-vectors/catalog/negative/repair-track-legacy-shape.json +19 -0
- package/test-vectors/catalog/negative/track-role-empty.json +16 -0
- package/test-vectors/catalog/negative/track-role-non-string.json +16 -0
- package/test-vectors/catalog/positive/fec-layered-repair.json +104 -0
- package/test-vectors/catalog/positive/fec-multicast.json +51 -0
- package/test-vectors/catalog/positive/flat-av.json +30 -0
- package/test-vectors/catalog/positive/multicast-auth-rotation.json +55 -0
- package/test-vectors/catalog/positive/ticks-and-mixed-timescales.json +39 -0
- package/test-vectors/catalog/positive/track-role.json +49 -0
- package/test-vectors/catalog/releases/moqcast-native-v16-v1/catalog.json +65 -0
- package/test-vectors/catalog/releases/moqcast-native-v16-v1.archive.sha256 +1 -0
- package/test-vectors/catalog-multicast-auth-v1.json +26 -0
- package/test-vectors/catalog-validation.json +148 -0
- package/test-vectors/catalog-validator-public-api.mjs +25 -0
- package/test-vectors/generate-source-auth-fixture.mjs +114 -0
- package/test-vectors/source-auth-v1.json +61 -0
- package/test-vectors/source-contiguity-v1.json +131 -0
- package/test-vectors/subsample-placement-v1.json +230 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../mmt-alta-parse/src/index.ts", "../../mmt-base/src/mmtp.ts", "../src/mfu-reassembler.ts", "../src/wasm-mfu-reassembler.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Zero-dependency helper for extracting the detached ALTA authenticator trailer\n * from an MMTP source packet.\n *\n * Lives in its own leaf package so both `@blockcast/mmt-container` (wire-path\n * parsing) and `@blockcast/fec-worker` (FEC-recovered-source parsing) can share\n * one implementation without creating a package cycle between them.\n *\n * Spec: ISO/IEC 23008-1:2023 + draft-ramadan-moq-mmt \u00A712 (detached trailer, Path 2).\n */\n\n// --- Private MMTP/MPU/MFU constants (ISO/IEC 23008-1 \u00A79.2.2\u20139.2.3) ---\n// Duplicated here as immutable spec values so this package stays a leaf with\n// zero `@blockcast/*` dependencies. Re-deriving from `mmt-container` would\n// reintroduce the package cycle this split exists to avoid.\n\nconst MMTP_HEADER_SIZE = 12\nconst MPU_HEADER_SIZE = 8\nconst MPU_LENGTH_FIELD_SIZE = 2\nconst MPU_HEADER_REMAINDER_SIZE = MPU_HEADER_SIZE - MPU_LENGTH_FIELD_SIZE\nconst TIMED_MFU_DU_HEADER_SIZE = 14\nconst NONTIMED_MFU_DU_HEADER_SIZE = 4\nconst PACKET_TYPE_MPU = 0x00\n\n// --- Public ALTA trailer constants ---\n\n/**\n * MMTP header-extension ext_type assigned to the detached ALTA authenticator\n * trailer (Path 2 / 08-07). Carried in an `[ext_type][ext_length][auth]`\n * block appended after the MPU's declared payload bytes, before the 4-byte\n * Source FEC Payload ID. MMTP X-bit stays 0 \u2014 non-ALTA MMTP receivers treat\n * the trailer as bytes past `payload_length` and skip it (per ISO 23008-1\n * \u00A79.2.2 \"may be discarded without impacting correct processing\").\n */\nexport const MMTP_EXT_TYPE_ALTA_AUTH = 0xf0a1\n\n/** Fixed size of the trailer's `[ext_type][ext_length]` prefix (2 + 2 BE). */\nexport const MMTP_EXT_TRAILER_HEADER_SIZE = 4\n\n/** Result of extracting the detached ALTA authenticator from an MMTP packet. */\nexport interface AltaTrailer {\n /** Authenticator bytes (no ext_type/ext_length prefix, no trailing padding). */\n auth: Uint8Array\n /**\n * Wire-packet byte offset where the signed payload ends, i.e. where the\n * trailer begins. Caller uses this to produce\n * `payload = wirePacket.subarray(0, signedEnd)` for verify_detached.\n */\n signedEnd: number\n}\n\n/**\n * Extract the detached ALTA authenticator trailer from an MMTP source packet.\n *\n * Wire layout:\n * `[MMTP hdr][length(2)][remaining MPU hdr(6)][DU?][media][TRAILER][SS_ID?]`\n * `payload_length = 6 + DU bytes + media bytes`\n *\n * Returns `null` when:\n * - the packet is too small / malformed,\n * - `payload_length` cannot contain the remaining six MPU-header bytes,\n * - the declared payload overruns the packet,\n * - no bytes remain between `payload_length` and the SS_ID,\n * - the trailer's `ext_type` isn't {@link MMTP_EXT_TYPE_ALTA_AUTH},\n * - the declared `ext_length` would extend past the SS_ID boundary.\n *\n * Non-MPU packets (repair, signaling) always return `null`.\n *\n * @param options.skipFecId When true, do NOT reserve 4 trailing bytes for the\n * Source FEC Payload ID. Use on FEC-recovered symbols where the PID has\n * already been stripped upstream.\n */\nexport function extractAltaTrailer(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n): AltaTrailer | null {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) return null\n // ISO/IEC 23008-1:2023 Figure 8 places V=0 packet_type in the low six bits.\n const packetType = mmtpPacket[1]! & 0x3f\n if (packetType !== PACKET_TYPE_MPU) return null\n\n const fecType = (mmtpPacket[0]! >> 3) & 0x03\n let headerLength = MMTP_HEADER_SIZE\n if (fecType < 2 && (mmtpPacket[0]! & 0x20) !== 0) headerLength += 4\n if (fecType < 2 && (mmtpPacket[0]! & 0x02) !== 0) {\n if (headerLength + 4 > mmtpPacket.length) return null\n const extensionLength = new DataView(\n mmtpPacket.buffer,\n mmtpPacket.byteOffset + headerLength + 2,\n 2,\n ).getUint16(0, false)\n headerLength += 4 + extensionLength\n }\n if (headerLength + MPU_HEADER_SIZE > mmtpPacket.length) return null\n\n const payload = mmtpPacket.subarray(headerLength)\n\n // Inline a minimal MPU-header read \u2014 only the fields extractAltaTrailer needs.\n // payloadLength: 16 BE at bytes 0-1.\n // Byte 2 packs FT(4) | T(1) | FI(2) | A(1).\n const payloadLength = (payload[0]! << 8) | payload[1]!\n const flagsByte = payload[2]!\n const fragmentType = (flagsByte >> 4) & 0x0f\n const timed = ((flagsByte >> 3) & 0x01) === 1\n const fragmentationIndicator = (flagsByte >> 1) & 0x03\n\n // Only MFU packets (FT=2) carry the detached trailer.\n if (fragmentType !== 2) return null\n\n // FI=0/1 structurally carries a DU header; FI=2/3 does not. Require at\n // least one media byte after that deterministic layout so the bytes at the\n // declared boundary cannot impersonate ALTA for a header-only fragment.\n const dataUnitHeaderSize = fragmentationIndicator <= 1\n ? (timed ? TIMED_MFU_DU_HEADER_SIZE : NONTIMED_MFU_DU_HEADER_SIZE)\n : 0\n if (payloadLength <= MPU_HEADER_REMAINDER_SIZE + dataUnitHeaderSize) return null\n\n if (fecType > 1) return null\n const hasFecId = !options?.skipFecId && fecType === 1\n const payloadEnd = hasFecId ? payload.length - 4 : payload.length\n const declaredEnd = MPU_LENGTH_FIELD_SIZE + payloadLength\n if (declaredEnd > payloadEnd) return null\n\n if (declaredEnd + MMTP_EXT_TRAILER_HEADER_SIZE > payloadEnd) return null\n const extType = (payload[declaredEnd]! << 8) | payload[declaredEnd + 1]!\n if (extType !== MMTP_EXT_TYPE_ALTA_AUTH) return null\n const extLen = (payload[declaredEnd + 2]! << 8) | payload[declaredEnd + 3]!\n\n const authStart = declaredEnd + MMTP_EXT_TRAILER_HEADER_SIZE\n if (authStart + extLen !== payloadEnd) return null\n\n return {\n auth: payload.subarray(authStart, payloadEnd),\n signedEnd: headerLength + declaredEnd,\n }\n}\n", "import {\n MMTP_EXT_TYPE_ALTA_AUTH as _LEAF_MMTP_EXT_TYPE_ALTA_AUTH,\n MMTP_EXT_TRAILER_HEADER_SIZE as _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE,\n extractAltaTrailer as _leafExtractAltaTrailer,\n type AltaTrailer as _LeafAltaTrailer,\n} from '@blockcast/mmt-alta-parse'\n\n/**\n * @mmt/container - MMTP Parser\n *\n * MMTP (MMT Protocol) packet parser based on ISO/IEC 23008-1:2023 Section 9.2\n *\n * MMTP header format (12 bytes):\n * - Byte 0: V(2) | C(1) | FEC_type(2) | r(1) | X(1) | R(1)\n * - Byte 1: reserved(2) | packet_type(6)\n * - Bytes 2-3: packet_id (big-endian)\n * - Bytes 4-7: timestamp (big-endian, 32-bit)\n * - Bytes 8-11: sequence_number (big-endian)\n *\n * FEC_type values:\n * - 0: No FEC / source packet\n * - 1: FEC mode 0 (source with FEC payload ID)\n * - 2: FEC repair packet mode 0\n * - 3: FEC repair packet mode 1\n *\n * @packageDocumentation\n */\n\nexport interface MMTPHeader {\n version: number\n /** Packet type: 0x00=MPU, 0x02=Signaling, 0x03=Repair */\n packetType: number\n packetId: number\n timestamp: bigint\n sequenceNumber: number\n payloadLength: number\n fecType: number\n /** C flag: header carries a 32-bit packet_counter. */\n packetCounterFlag: boolean\n packetCounter?: number\n /** X flag: header carries an extension header and value. */\n extensionFlag: boolean\n extension?: { type: number; data: Uint8Array }\n /** Byte offset at which the MMTP payload begins. */\n headerLength: number\n /** Random Access Point flag - indicates keyframe (ISO 23008-1, byte 0 bit 0) */\n rapFlag: boolean\n}\n\nexport interface MPUHeader {\n mpuSequenceNumber: number\n fragmentationIndicator: number // FI: 0=complete, 1=first, 2=middle, 3=last\n /** 8-bit MPU fragment_counter from byte 3. */\n fragmentCounter: number\n /** T flag: 1=timed media (14-byte MFU DU header), 0=non-timed (4-byte header) */\n timedFlag: number\n /** A flag: aggregated MPU payload. This strict profile requires A=0. */\n aggregationFlag: boolean\n /** MPU payload_length (bytes 0-1): bytes after the length field, including\n * the remaining six MPU-header bytes and all data-unit bytes. */\n payloadLength: number\n}\n\nexport interface MFUDuHeader {\n movieFragmentSequenceNumber: number\n sampleNumber: number\n offset: number\n priority: number\n dependencyCounter: number\n}\n\nexport interface MMTPPacket {\n header: MMTPHeader\n payload: Uint8Array\n}\n\nexport const MMTP_HEADER_SIZE = 12\nexport const MPU_HEADER_SIZE = 8\nexport const MFU_DU_HEADER_SIZE_TIMED = 14 // T=1: timed media (video/audio samples)\nexport const MFU_DU_HEADER_SIZE_NONTIMED = 4 // T=0: non-timed media (item_id only)\n\nconst MPU_LENGTH_FIELD_SIZE = 2\nconst MPU_HEADER_REMAINDER_SIZE = MPU_HEADER_SIZE - MPU_LENGTH_FIELD_SIZE\nconst SOURCE_FEC_PAYLOAD_ID_SIZE = 4\n\n// MMTP Packet Types (ISO/IEC 23008-1:2023 Section 9.2.2)\nexport const PACKET_TYPE_MPU = 0x00\nexport const PACKET_TYPE_SIGNALING = 0x02\nexport const PACKET_TYPE_REPAIR = 0x03\n\n// AL-FEC Signaling Message ID (ISO/IEC 23008-1:2023 Amendment 1:2025)\nexport const ALFEC_MESSAGE_ID = 0x0203\n\n// FEC Code IDs\nexport const FEC_CODE_XOR = 1\nexport const FEC_CODE_RAPTORQ = 2\n\n/**\n * Parse MMTP header from buffer\n */\nexport function parseMMTPHeader(buffer: Uint8Array): MMTPHeader {\n if (buffer.length < MMTP_HEADER_SIZE) {\n throw new Error(`MMTP packet too small: ${buffer.length} bytes`)\n }\n\n const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength)\n\n const version = (buffer[0]! >> 6) & 0x03\n if (version !== 0) {\n throw new Error(`Unsupported MMTP version: ${version}`)\n }\n\n // Extract FEC type from byte 0, bits 3-4\n const fecType = (buffer[0]! >> 3) & 0x03\n const packetCounterFlag = (buffer[0]! & 0x20) !== 0\n const extensionFlag = (buffer[0]! & 0x02) !== 0\n\n // Extract RAP flag from byte 0, bit 0 (R bit - Random Access Point)\n const rapFlag = (buffer[0]! & 0x01) === 1\n\n // ISO/IEC 23008-1:2023 Figure 8 (V=0) places reserved(2) before\n // packet_type(6), so packet_type occupies the low six bits of byte 1.\n const packetType = buffer[1]! & 0x3F\n\n let offset = 2\n\n // Packet ID (2 bytes, big-endian)\n const packetId = view.getUint16(offset, false)\n offset += 2\n\n // Timestamp (4 bytes, big-endian)\n const timestamp = BigInt(view.getUint32(offset, false))\n offset += 4\n\n // Sequence number (4 bytes, big-endian)\n const sequenceNumber = view.getUint32(offset, false)\n offset += 4\n\n let packetCounter: number | undefined\n if (packetCounterFlag) {\n if (offset + 4 > buffer.length) throw new Error('MMTP packet truncated in packet_counter')\n packetCounter = view.getUint32(offset, false)\n offset += 4\n }\n\n let extension: { type: number; data: Uint8Array } | undefined\n if (extensionFlag) {\n if (offset + 4 > buffer.length) throw new Error('MMTP packet truncated in header extension')\n const type = view.getUint16(offset, false)\n const length = view.getUint16(offset + 2, false)\n offset += 4\n if (offset + length > buffer.length) throw new Error('MMTP packet truncated in header extension value')\n extension = { type, data: buffer.subarray(offset, offset + length) }\n offset += length\n }\n\n return {\n version,\n packetType,\n packetId,\n timestamp,\n sequenceNumber,\n payloadLength: buffer.length - offset,\n fecType,\n packetCounterFlag,\n packetCounter,\n extensionFlag,\n extension,\n headerLength: offset,\n rapFlag,\n }\n}\n\n/**\n * Unwrap MMTP packet to extract payload\n */\nexport function unwrapMMTP(buffer: Uint8Array): MMTPPacket {\n const header = parseMMTPHeader(buffer)\n const payload = buffer.subarray(header.headerLength)\n\n return { header, payload }\n}\n\n/**\n * Parse MPU header from payload\n * ISO/IEC 23008-1:2023 Section 9.2.3.3\n *\n * MPU Header format (8 bytes) as written by FFmpeg/libmmt:\n * - Bytes 0-1: payload_length (16 bits, big-endian)\n * - Byte 2: FT(4) | T(1) | FI(2) | A(1)\n * - Byte 3: fragment_counter (8 bits)\n * - Bytes 4-7: MPU_sequence_number (32 bits, big-endian)\n */\nexport function parseMPUHeader(payload: Uint8Array): MPUHeader {\n if (payload.length < MPU_HEADER_SIZE) {\n throw new Error(`Payload too small for MPU header: ${payload.length} bytes`)\n }\n\n const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength)\n\n // Bytes 0-1: payload_length (16 bits, big-endian)\n const payloadLength = view.getUint16(0, false)\n\n // Byte 2: FT(4) | T(1) | FI(2) | A(1)\n const flagsByte = payload[2]!\n const timedFlag = (flagsByte >> 3) & 0x01\n const fragmentationIndicator = (flagsByte >> 1) & 0x03\n const aggregationFlag = (flagsByte & 0x01) !== 0\n\n // Byte 3: fragment_counter (8 bits). The high bit is part of the counter in\n // the current libmmt/FFmpeg writer ABI.\n const fragmentCounter = payload[3]!\n\n // Bytes 4-7: MPU_sequence_number (32 bits, big-endian)\n const mpuSequenceNumber = view.getUint32(4, false)\n\n return {\n mpuSequenceNumber,\n fragmentationIndicator,\n fragmentCounter,\n timedFlag,\n aggregationFlag,\n payloadLength,\n }\n}\n\nexport function parseTimedMfuDuHeader(payload: Uint8Array, offset: number): MFUDuHeader | null {\n if (offset < 0 || offset + MFU_DU_HEADER_SIZE_TIMED > payload.length) return null\n const view = new DataView(payload.buffer, payload.byteOffset + offset, MFU_DU_HEADER_SIZE_TIMED)\n return {\n movieFragmentSequenceNumber: view.getUint32(0, false),\n sampleNumber: view.getUint32(4, false),\n offset: view.getUint32(8, false),\n priority: payload[offset + 12]!,\n dependencyCounter: payload[offset + 13]!,\n }\n}\n\n/**\n * Why `extractMFUPayload` refused a packet.\n *\n * These identifiers are part of the public contract: callers surface them in\n * diagnostics, so renaming one is a breaking change.\n */\nexport type MFURejectionGate =\n | 'packet-shorter-than-mmtp-plus-mpu-header'\n | 'mmtp-header-unparseable'\n | 'not-an-mpu-packet'\n | 'payload-shorter-than-mpu-header'\n | 'aggregation-flag-set'\n | 'mpu-payload-length-below-header-remainder'\n | 'mpu-declared-end-exceeds-payload'\n | 'mfu-offset-at-or-past-declared-end'\n | 'source-fec-payload-id-width-mismatch'\n | 'declared-trailers-unresolved'\n\nexport interface MFURejection {\n gate: MFURejectionGate\n /** Human-readable detail, including the byte arithmetic that decided it. */\n detail: string\n}\n\ninterface MfuPayloadLayout {\n hasMfuDuHeader: boolean\n mfuOffset: number\n dataEnd: number\n}\n\nfunction resolveMfuPayloadLayout(\n mpu: MPUHeader,\n fragmentType: number,\n payload: Uint8Array,\n): { layout: MfuPayloadLayout } | { rejection: MFURejection } {\n if (mpu.payloadLength < MPU_HEADER_REMAINDER_SIZE) {\n return {\n rejection: {\n gate: 'mpu-payload-length-below-header-remainder',\n detail: `payload_length=${mpu.payloadLength} < ${MPU_HEADER_REMAINDER_SIZE}`,\n },\n }\n }\n\n // ISO/IEC 23008-1 defines payload_length from immediately after its own\n // two-byte field. It therefore includes the remaining six MPU-header bytes.\n const declaredEnd = MPU_LENGTH_FIELD_SIZE + mpu.payloadLength\n if (declaredEnd > payload.length) {\n return {\n rejection: {\n gate: 'mpu-declared-end-exceeds-payload',\n detail:\n `payload_length=${mpu.payloadLength} implies end=${declaredEnd}, ` +\n `payload is ${payload.length} bytes`,\n },\n }\n }\n\n const hasHeader = fragmentType === 2 && mpu.fragmentationIndicator <= 1\n const mfuHeaderSize = mpu.timedFlag ? MFU_DU_HEADER_SIZE_TIMED : MFU_DU_HEADER_SIZE_NONTIMED\n const mfuOffset = MPU_HEADER_SIZE + (hasHeader ? mfuHeaderSize : 0)\n if (mfuOffset >= declaredEnd) {\n return {\n rejection: {\n gate: 'mfu-offset-at-or-past-declared-end',\n detail: `mfuOffset=${mfuOffset} >= declaredEnd=${declaredEnd}`,\n },\n }\n }\n\n return {\n layout: {\n hasMfuDuHeader: hasHeader,\n mfuOffset,\n dataEnd: declaredEnd,\n },\n }\n}\n\nfunction isEmptyOrExactAltaTrailer(payload: Uint8Array, start: number, end: number): boolean {\n if (start === end) return true\n if (start < 0 || end > payload.length || start + _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE > end) {\n return false\n }\n\n const view = new DataView(\n payload.buffer,\n payload.byteOffset + start,\n end - start,\n )\n if (view.getUint16(0, false) !== _LEAF_MMTP_EXT_TYPE_ALTA_AUTH) return false\n\n const authLength = view.getUint16(2, false)\n return start + _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE + authLength === end\n}\n\ninterface DeclaredTrailerLayout {\n hasFecId: boolean\n}\n\nfunction resolveDeclaredTrailers(\n payload: Uint8Array,\n declaredEnd: number,\n fecType: number,\n options?: { skipFecId?: boolean },\n): DeclaredTrailerLayout | null {\n if (declaredEnd > payload.length) return null\n\n if (options?.skipFecId) {\n return isEmptyOrExactAltaTrailer(payload, declaredEnd, payload.length)\n ? { hasFecId: false }\n : null\n }\n\n if (fecType === 1) {\n const fecStart = payload.length - SOURCE_FEC_PAYLOAD_ID_SIZE\n if (fecStart < declaredEnd || !isEmptyOrExactAltaTrailer(payload, declaredEnd, fecStart)) {\n return null\n }\n return { hasFecId: true }\n }\n if (fecType !== 0) return null\n\n if (isEmptyOrExactAltaTrailer(payload, declaredEnd, payload.length)) {\n return { hasFecId: false }\n }\n\n return null\n}\n\n/**\n * Extract NAL unit payload from MMTP packet\n */\nexport function extractNALUnit(mmtpPacket: Uint8Array): Uint8Array {\n const extraction = extractMFUPayload(mmtpPacket)\n if (!extraction) throw new Error('Malformed MMTP MPU payload')\n return extraction.data\n}\n\n/** Result of extracting MFU payload from an MMTP packet */\nexport interface MFUExtraction {\n /** MMTP header fields */\n header: MMTPHeader\n /** MPU header fields */\n mpu: MPUHeader\n /** Fragment type (FT field, 4 bits): 0=metadata, 2=MFU data */\n fragmentType: number\n /** MFU payload data (video/audio bytes, headers stripped, FEC ID excluded) */\n data: Uint8Array\n /** Parsed timed MFU DU header when present, otherwise null */\n mfuDuHeader: MFUDuHeader | null\n /** Non-timed MFU Item_ID when the 4-byte DU header is present. */\n itemId: number | null\n /** Source FEC Payload ID if present (`FEC_type == 1`), or null */\n fecPayloadId: { ssId: number } | null\n}\n\n/**\n * Extract MFU payload from a raw MMTP packet.\n *\n * Handles all the byte arithmetic for:\n * - MMTP header (12 bytes)\n * - MPU sub-header (8 bytes, payloadLength field)\n * - MFU DU header (14 bytes timed / 4 bytes non-timed, only for FI \u2264 1)\n * - Source FEC Payload ID (4 bytes at end, only when `FEC_type == 1`)\n *\n * `payloadLength` is the ISO length-after-field value: six remaining MPU-header\n * bytes, then the MFU DU header when present, then media. ALTA and Source FEC\n * trailers are outside that declared span and must consume the exact suffix.\n *\n * @param mmtpPacket Raw MMTP packet (from multicast or MoQ)\n * @param options.skipFecId When true, do NOT subtract the 4-byte Source FEC Payload ID\n * from the payload boundary, even if fecType >= 1 in the MMTP header. Use this for\n * FEC-recovered symbols where the FEC PID was already stripped before WASM decoding\n * but the fecType bits in byte 0 still reflect the original packet (FEC Type=1 per\n * draft-ramadan-moq-mmt-00 \u00A73.1).\n * @returns Extracted MFU data + metadata, or null if packet is too small\n */\ninterface MfuPlan {\n header: MMTPHeader\n payload: Uint8Array\n mpu: MPUHeader\n fragmentType: number\n layout: MfuPayloadLayout\n trailerLayout: DeclaredTrailerLayout\n}\n\n/**\n * Single source of truth for whether a packet yields an MFU payload.\n *\n * `extractMFUPayload` and `explainMFURejection` both delegate here, so the\n * reason reported to operators can never drift from the decision the parser\n * actually made.\n */\nfunction planMfuExtraction(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n): { plan: MfuPlan } | { rejection: MFURejection } {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) {\n return {\n rejection: {\n gate: 'packet-shorter-than-mmtp-plus-mpu-header',\n detail: `${mmtpPacket.length} bytes < ${MMTP_HEADER_SIZE + MPU_HEADER_SIZE}`,\n },\n }\n }\n\n let header: MMTPHeader\n try {\n header = parseMMTPHeader(mmtpPacket)\n } catch (error) {\n return {\n rejection: {\n gate: 'mmtp-header-unparseable',\n detail: error instanceof Error ? error.message : String(error),\n },\n }\n }\n if (header.packetType !== PACKET_TYPE_MPU) {\n return {\n rejection: {\n gate: 'not-an-mpu-packet',\n detail: `packetType=0x${header.packetType.toString(16).padStart(2, '0')}`,\n },\n }\n }\n\n const payload = mmtpPacket.subarray(header.headerLength)\n if (payload.length < MPU_HEADER_SIZE) {\n return {\n rejection: {\n gate: 'payload-shorter-than-mpu-header',\n detail: `${payload.length} bytes < ${MPU_HEADER_SIZE}`,\n },\n }\n }\n\n const mpu = parseMPUHeader(payload)\n if (mpu.aggregationFlag) {\n return {\n rejection: { gate: 'aggregation-flag-set', detail: 'strict profile requires A=0' },\n }\n }\n\n // Fragment type from byte 2 upper nibble (not returned by parseMPUHeader)\n const fragmentType = (payload[2]! >> 4) & 0x0f\n\n const layoutResult = resolveMfuPayloadLayout(mpu, fragmentType, payload)\n if ('rejection' in layoutResult) return layoutResult\n const { layout } = layoutResult\n\n const trailerLayout = resolveDeclaredTrailers(\n payload,\n layout.dataEnd,\n header.fecType,\n options,\n )\n if (!trailerLayout) {\n // The most common cause is a producer whose Source FEC Payload ID width\n // disagrees with ISO/IEC 23008-1 \u00A7C.5.2. Name that explicitly rather than\n // leaving operators to infer it from a generic trailer error.\n if (!options?.skipFecId && header.fecType === 1) {\n const observed = payload.length - layout.dataEnd\n if (observed !== SOURCE_FEC_PAYLOAD_ID_SIZE) {\n return {\n rejection: {\n gate: 'source-fec-payload-id-width-mismatch',\n detail:\n `producer wrote a ${observed}-byte Source FEC Payload ID, ` +\n `ISO/IEC 23008-1 \u00A7C.5.2 requires ${SOURCE_FEC_PAYLOAD_ID_SIZE} ` +\n `(payload=${payload.length}, declaredEnd=${layout.dataEnd})`,\n },\n }\n }\n }\n return {\n rejection: {\n gate: 'declared-trailers-unresolved',\n detail:\n `trailer bytes [${layout.dataEnd}, ${payload.length}) are neither empty ` +\n `nor an exact ALTA trailer (fecType=${header.fecType})`,\n },\n }\n }\n\n return { plan: { header, payload, mpu, fragmentType, layout, trailerLayout } }\n}\n\n/**\n * Report why `extractMFUPayload` would refuse this packet, or null if it would\n * accept it.\n *\n * `extractMFUPayload` returns bare `null` on ten distinct conditions, which\n * makes a producer/consumer framing disagreement indistinguishable from a\n * publisher that never started \u2014 both render as silence. Use this wherever\n * that distinction matters: diagnostics, contract tests, and operator-facing\n * logs.\n */\nexport function explainMFURejection(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n): MFURejection | null {\n const result = planMfuExtraction(mmtpPacket, options)\n return 'rejection' in result ? result.rejection : null\n}\n\nexport function extractMFUPayload(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n): MFUExtraction | null {\n const result = planMfuExtraction(mmtpPacket, options)\n if ('rejection' in result) return null\n const { header, payload, mpu, fragmentType, layout, trailerLayout } = result.plan\n\n const mfuDuHeaderPresent = layout.hasMfuDuHeader\n const mfuOffset = layout.mfuOffset\n const dataEnd = layout.dataEnd\n\n const mfuDuHeader =\n mfuDuHeaderPresent && mpu.timedFlag === 1\n ? parseTimedMfuDuHeader(payload, MPU_HEADER_SIZE)\n : null\n const itemId =\n mfuDuHeaderPresent && mpu.timedFlag === 0\n ? new DataView(payload.buffer, payload.byteOffset + MPU_HEADER_SIZE, 4).getUint32(0, false)\n : null\n\n const data = payload.subarray(mfuOffset, dataEnd)\n\n const fecPayloadId = trailerLayout.hasFecId\n ? {\n ssId: new DataView(\n mmtpPacket.buffer,\n mmtpPacket.byteOffset + mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE,\n SOURCE_FEC_PAYLOAD_ID_SIZE,\n ).getUint32(0, false),\n }\n : null\n\n return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId }\n}\n\n/**\n * Extract timestamp from MMTP packet\n */\nexport function extractTimestamp(mmtpPacket: Uint8Array): bigint {\n const { header } = unwrapMMTP(mmtpPacket)\n return header.timestamp\n}\n\n/**\n * Check if MMTP packet is an FEC repair packet\n * FEC_type 2 and 3 are repair packets\n */\nexport function isFecRepairPacket(mmtpPacket: Uint8Array): boolean {\n if (mmtpPacket.length < 1) return false\n const fecType = (mmtpPacket[0]! >> 3) & 0x03\n return fecType >= 2\n}\n\n/**\n * Extract Source FEC Payload ID (SS_ID) from the last 4 bytes of an MMTP\n * source packet per ISO/IEC 23008-1:2023 Section C.5.2 Figure C.10.\n *\n * SS_ID is a flat 32-bit monotonic counter (incremented by 1 per packet\n * for ssbg_mode0 and ssbg_mode1). Block membership and ESI use the\n * configured K; repair SS_Start identifies the first SS_ID of the block and\n * SSB_length records the authored source-symbol count:\n * SBN = floor(SS_ID / K), ESI = SS_ID % K\n * SS_Start = SBN * K (first SS_ID of the block)\n *\n * This matches FFmpeg moqenc_mmt.c, the drafts (draft-ramadan-moq-fec \u00A76.2),\n * and ATSC A/331 \u00A78.1.2.5 which defers to ISO 23008-1 Annex C.\n */\nexport function extractFecPayloadId(mmtpPacket: Uint8Array): { ssId: number } | null {\n if (!hasSourceFecPayloadId(mmtpPacket)) return null\n // Flat 4-byte SS_ID: encoder writes block_id * K + symbol_id\n const off = mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE\n const view = new DataView(\n mmtpPacket.buffer,\n mmtpPacket.byteOffset + off,\n SOURCE_FEC_PAYLOAD_ID_SIZE,\n )\n return { ssId: view.getUint32(0, false) }\n}\n\n/**\n * Return true when FEC_type=1 and the exact suffix after the ISO-declared MPU\n * payload contains a four-byte Source FEC Payload ID.\n */\nexport function hasSourceFecPayloadId(mmtpPacket: Uint8Array): boolean {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) return false\n\n let header: MMTPHeader\n try {\n header = parseMMTPHeader(mmtpPacket)\n } catch {\n return false\n }\n if (header.packetType !== PACKET_TYPE_MPU) return false\n\n const payload = mmtpPacket.subarray(header.headerLength)\n if (payload.length < MPU_HEADER_SIZE) return false\n const mpu = parseMPUHeader(payload)\n if (mpu.payloadLength < MPU_HEADER_REMAINDER_SIZE) return false\n const declaredEnd = MPU_LENGTH_FIELD_SIZE + mpu.payloadLength\n return resolveDeclaredTrailers(payload, declaredEnd, header.fecType)?.hasFecId ?? false\n}\n\n/**\n * MMTP header-extension ext_type assigned to the detached ALTA authenticator\n * trailer (Path 2 / 08-07). Carried in an `[ext_type][ext_length][auth]`\n * block appended after the MPU's declared payload bytes, before the 4-byte\n * Source FEC Payload ID. MMTP X-bit stays 0 \u2014 non-ALTA MMTP receivers treat\n * the trailer as bytes past `payload_length` and skip it (per ISO 23008-1\n * \u00A79.2.2 \"may be discarded without impacting correct processing\").\n */\n// Re-export the ALTA trailer helper from the zero-dep leaf package. The leaf\n// owns the implementation so container and FEC consumers share one parser\n// without creating a package cycle.\nexport const MMTP_EXT_TYPE_ALTA_AUTH = _LEAF_MMTP_EXT_TYPE_ALTA_AUTH\nexport const MMTP_EXT_TRAILER_HEADER_SIZE = _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE\nexport type AltaTrailer = _LeafAltaTrailer\nexport const extractAltaTrailer = _leafExtractAltaTrailer\n\n/** Size of Repair FEC Payload ID per ISO/IEC 23008-1 Section C.5.3 */\nexport const REPAIR_FEC_PID_SIZE = 13\n\n/** Parse the 13-byte Repair FEC Payload ID after the complete dynamic MMTP header. */\nfunction readRepairFecPayloadIdFields(mmtpPacket: Uint8Array): {\n packetId: number; ssStart: number; rsbLength: number; rsId: number; ssbLength: number; dataOffset: number\n} | null {\n let header: MMTPHeader\n try {\n header = parseMMTPHeader(mmtpPacket)\n } catch {\n return null\n }\n if (header.packetType !== PACKET_TYPE_REPAIR || header.fecType !== 2) return null\n\n const off = header.headerLength\n if (mmtpPacket.length < off + REPAIR_FEC_PID_SIZE) return null\n const view = new DataView(mmtpPacket.buffer, mmtpPacket.byteOffset + off, REPAIR_FEC_PID_SIZE)\n const ssStart = view.getUint32(0, false) // 32 bits\n const rsbLength = (mmtpPacket[off + 4]! << 16) | (mmtpPacket[off + 5]! << 8) | mmtpPacket[off + 6]! // 24 bits\n const rsId = (mmtpPacket[off + 7]! << 16) | (mmtpPacket[off + 8]! << 8) | mmtpPacket[off + 9]! // 24 bits\n const ssbLength = (mmtpPacket[off + 10]! << 16) | (mmtpPacket[off + 11]! << 8) | mmtpPacket[off + 12]! // 24 bits\n return {\n packetId: header.packetId,\n ssStart,\n rsbLength,\n rsId,\n ssbLength,\n dataOffset: off + REPAIR_FEC_PID_SIZE,\n }\n}\n\n/**\n * Extract Repair FEC Payload ID from a raw-multicast MMTP repair packet.\n * Per ISO/IEC 23008-1:2023 Section C.5.3 (ssbg_mode0, one-stage, no FFSRP_TS):\n * [MMTP header][SS_Start:4][RSB_length:3][RS_ID:3][SSB_length:3][repair data]\n * OTI is delivered via AL-FEC signaling message (Table C.3), not per-packet.\n *\n * Byte 1 is reserved(2)|packet_type(6) for V=0, so repair type 0x03 is 0x03.\n * Pre-migration high-bits bytes (0x0c) are rejected \u2014 no compatibility decode.\n */\nexport function extractRepairFecPayloadId(mmtpPacket: Uint8Array): {\n ssStart: number; rsbLength: number; rsId: number; ssbLength: number\n} | null {\n const fields = readRepairFecPayloadIdFields(mmtpPacket)\n if (!fields) return null\n return {\n ssStart: fields.ssStart,\n rsbLength: fields.rsbLength,\n rsId: fields.rsId,\n ssbLength: fields.ssbLength,\n }\n}\n\n/** Parsed MoQ repair frame per ISO/IEC 23008-1 Section C.4.3 + C.5.3 */\nexport interface MoqRepairFrame {\n /** MMTP packetId (1=video, 2=audio) \u2014 for routing to correct FEC decoder */\n packetId: number\n /** SS_Start (32 bits): SS_ID of first source symbol in block */\n ssStart: number\n /** RSB_length (24 bits): number of repair symbols (P) */\n rsbLength: number\n /** RS_ID (24 bits): repair symbol index (0-based) */\n rsId: number\n /** SSB_length (24 bits): authored source-symbol count (1..=configured K) */\n ssbLength: number\n /** Repair symbol data after the dynamic MMTP header and 13-byte FEC payload ID. */\n symbolData: Uint8Array\n}\n\n/**\n * Parse a MoQ repair frame (ISO/IEC 23008-1 Annex C format).\n *\n * Wire format per ISO 23008-1 \u00A7C.4.3 + \u00A7C.5.3:\n * MMTP Header: fixed 12 bytes plus C/X optional fields\n * then SS_Start (4 bytes) \u2192 block identifier\n * then RSB_length (3 bytes) \u2192 P\n * then RS_ID (3 bytes) \u2192 ESI = K + RS_ID\n * then SSB_length (3 bytes) \u2192 authored source-symbol count (1..=K)\n * then Repair Symbol Data (T bytes)\n *\n * @param frame - Raw MoQ repair frame bytes\n * @returns Parsed frame or null if invalid\n */\nexport function parseMoqRepairFrame(frame: Uint8Array): MoqRepairFrame | null {\n const repairId = readRepairFecPayloadIdFields(frame)\n if (!repairId) return null\n\n const symbolData = frame.subarray(repairId.dataOffset)\n if (\n repairId.rsbLength === 0 ||\n repairId.ssbLength === 0 ||\n repairId.rsId >= repairId.rsbLength ||\n symbolData.length === 0\n ) return null\n\n return {\n packetId: repairId.packetId,\n ssStart: repairId.ssStart,\n rsbLength: repairId.rsbLength,\n rsId: repairId.rsId,\n ssbLength: repairId.ssbLength,\n symbolData,\n }\n}\n\n/**\n * AL-FEC signaling configuration parsed from ISO/IEC 23008-1:2023 Table C.3 message.\n */\nexport interface AlFecConfig {\n fecCodeId: number\n fecPayloadIdMode: 0\n repairSymbolSize: number\n maximumK: number\n maximumP: number\n interleaveDepth: number\n oti: Uint8Array\n}\n\nfunction canonicalRaptorQOtiK(oti: Uint8Array, repairSymbolSize: number): number | null {\n if (oti.length !== 12 || oti[5] !== 0) return null\n const transferLength = (oti[0]! * 0x1_0000_0000) +\n (((oti[1]! << 24) | (oti[2]! << 16) | (oti[3]! << 8) | oti[4]!) >>> 0)\n const symbolSize = (oti[6]! << 8) | oti[7]!\n const sourceBlocks = oti[8]!\n const subBlocks = (oti[9]! << 8) | oti[10]!\n const alignment = oti[11]!\n if (!Number.isSafeInteger(transferLength) || transferLength <= 0 ||\n symbolSize !== repairSymbolSize || symbolSize === 0 || symbolSize % 8 !== 0 ||\n sourceBlocks !== 1 || subBlocks !== 1 || alignment !== 8 ||\n transferLength % symbolSize !== 0) {\n return null\n }\n const k = transferLength / symbolSize\n return Number.isInteger(k) && k >= 1 && k <= 56_403 ? k : null\n}\n\n/**\n * Parse AL-FEC signaling message from a full MMTP signaling packet.\n *\n * Wire format (ISO/IEC 23008-1:2023 Table C.3, Amendment 1:2025):\n * [12-byte MMTP header (packet_type=0x02)]\n * message_id(2) = 0x0203\n * version(1)\n * message_length(2) = length of remaining payload\n * flags(1) = fec_flag(1) | private_fec_flag(1) | reserved(6)\n * fec_flow_descriptor:\n * length(2)\n * num_flows(1)\n * per flow:\n * fec_flow_id(1), source_flow_id(1), num_assets(1), packet_ids(2*N),\n * coding_params(1), repair_symbol_size(2),\n * [one-stage]: repair_flow_id(2), fec_code_id(1),\n * [private_fec_flag]: private_header(1){flag(1)|len(7)},\n * current RaptorQ profile OTI(12) + interleave_depth(1)\n * max_k(3), max_p(3), protection_window_time(4), protection_window_size(4)\n *\n * @param mmtpPacket Full MMTP packet including 12-byte header\n * @returns Parsed config or null if not an AL-FEC signaling message\n */\nexport function parseAlFecMessage(mmtpPacket: Uint8Array): AlFecConfig | null {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + 9) return null\n let header: MMTPHeader\n try {\n header = parseMMTPHeader(mmtpPacket)\n } catch {\n return null\n }\n if (header.packetType !== PACKET_TYPE_SIGNALING || header.fecType !== 0) return null\n const payload = mmtpPacket.subarray(header.headerLength)\n if (payload.length < 9) return null\n const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength)\n let off = 0\n\n const messageId = view.getUint16(off, false); off += 2\n if (messageId !== ALFEC_MESSAGE_ID) return null\n const version = payload[off]!; off += 1\n const messageLength = view.getUint16(off, false); off += 2\n if (version !== 1 || messageLength === 0 || messageLength !== payload.length - off) return null\n\n const flags = payload[off]!; off += 1\n const fecFlag = (flags >> 7) & 1\n const privateFecFlag = (flags >> 6) & 1\n if (fecFlag !== 1 || privateFecFlag !== 1 || (flags & 0x3f) !== 0x3f) return null\n\n if (off + 2 > payload.length) return null\n const descriptorLength = view.getUint16(off, false); off += 2\n const descriptorEnd = off + descriptorLength\n if (descriptorLength === 0 || descriptorEnd !== payload.length) return null\n\n const numFlows = payload[off]!; off += 1\n if (numFlows !== 1 || off + 3 > descriptorEnd) return null\n\n off += 2 // fec_flow_id, source_flow_id\n const numAssets = payload[off]!; off += 1\n if (numAssets === 0 || off + (numAssets * 2) > descriptorEnd) return null\n off += numAssets * 2\n\n if (off + 3 > descriptorEnd) return null\n const codingParams = payload[off]!; off += 1\n const fecCodingStructure = (codingParams >> 4) & 0x0f\n const ssbgMode = (codingParams >> 2) & 0x03\n const ffsrptsFlag = (codingParams >> 1) & 0x01\n const fecPayloadIdMode = codingParams & 0x01\n if (fecCodingStructure !== 1 || ssbgMode !== 0 || ffsrptsFlag !== 0 || fecPayloadIdMode !== 0) {\n return null\n }\n const repairSymbolSize = view.getUint16(off, false); off += 2\n if (repairSymbolSize === 0 || repairSymbolSize % 8 !== 0) return null\n\n if (off + 4 > descriptorEnd) return null\n off += 2 // Amendment 1:2025 replaces Table C.3 with a 16-bit repair_flow_id\n const fecCodeId = payload[off]!; off += 1\n if (fecCodeId !== FEC_CODE_RAPTORQ) return null\n const privateHeader = payload[off]!; off += 1\n const privateFlag = privateHeader >> 7\n const privateFieldLength = privateHeader & 0x7f\n if (privateFlag !== 1 || privateFieldLength !== 13 || off + 13 > descriptorEnd) return null\n const oti = payload.slice(off, off + 12); off += 12\n const interleaveDepth = payload[off]!; off += 1\n const otiK = canonicalRaptorQOtiK(oti, repairSymbolSize)\n if (otiK === null || interleaveDepth === 0) return null\n\n if (off + 14 !== descriptorEnd) return null\n const maximumK = (payload[off]! << 16) | (payload[off + 1]! << 8) | payload[off + 2]!\n off += 3\n const maximumP = (payload[off]! << 16) | (payload[off + 1]! << 8) | payload[off + 2]!\n off += 3\n if (maximumK !== otiK || maximumP === 0 || maximumK + maximumP - 1 > 0x00ff_ffff) {\n return null\n }\n off += 8 // protection_window_time, protection_window_size\n if (off !== descriptorEnd) return null\n\n return { fecCodeId, fecPayloadIdMode, repairSymbolSize, maximumK, maximumP, interleaveDepth, oti }\n}\n", "/**\n * @mmt/container - MFU Fragment Reassembler\n *\n * Handles authored fragmented MFUs (Media Fragment Units) according to\n * ISO/IEC 23008-1:2023 Section 9.2.3.3. Fragmented runs are admitted in the\n * exact FI/fragment_counter countdown carried on the wire. FI=1 supplies the\n * intrinsic data-unit key. Every fragmented fragment is addressed by\n * (stream scope, MPU, packetSequenceNumber + fragmentCounter mod 2^32); a\n * continuation joins, parks under, or is stale against the one run holding\n * its address, and other runs are never consulted.\n *\n * Fragmentation Indicator (FI) values:\n * - 0: Complete MFU (single packet, no fragmentation)\n * - 1: First fragment\n * - 2: Middle fragment\n * - 3: Last fragment\n *\n * @packageDocumentation\n */\n\nimport { extractMFUPayload, isFecRepairPacket } from '@blockcast/mmt-base'\n\n/** Source transport admitted by the MMTP router. */\nexport type MmtpSourcePath = 'multicast' | 'moq'\n\n/**\n * Strict whole-frame delivery provenance, folded over EVERY contributing\n * fragment rather than the head fragment alone.\n *\n * This is deliberately NOT `source`. `source` answers a routing question \u2014\n * \"is this the reliable-MoQ copy that must not confirm multicast takeover?\" \u2014\n * and head-fragment-wins is the documented, deterministic answer to it.\n * Provenance answers an evidence question: \"did every byte of this frame\n * physically arrive over multicast?\" A frame with a multicast head and a\n * QUIC-delivered tail is a legitimate routing win for `source: 'multicast'`\n * and is emphatically NOT multicast-exclusive delivery.\n *\n * - 'multicast' \u2014 every contributing fragment was explicitly multicast.\n * The ONLY value that may be counted as physical-delivery evidence.\n * - 'mixed' \u2014 at least one contributing fragment arrived over MoQ.\n * - 'unknown' \u2014 no fragment arrived over MoQ, but at least one carried no\n * attribution at all. Absence is preserved as its own state rather than\n * defaulted, so a consumer cannot mistake \"never recorded\" for \"proven\".\n */\nexport type MmtpDeliveryProvenance = 'multicast' | 'mixed' | 'unknown'\n\n/**\n * Fold one fragment's `source` into a running provenance verdict.\n *\n * Fail-closed, order-independent and associative: 'mixed' absorbs everything,\n * a missing attribution degrades 'multicast' to 'unknown', and nothing ever\n * upgrades a verdict. Fold over an empty set is 'unknown' \u2014 no fragment ever\n * proved anything.\n */\nexport function foldDeliveryProvenance(\n verdict: MmtpDeliveryProvenance,\n source: MmtpSourcePath | undefined\n): MmtpDeliveryProvenance {\n if (verdict === 'mixed') return 'mixed'\n if (source === 'moq') return 'mixed'\n if (source === undefined) return 'unknown'\n return verdict\n}\n\n/**\n * One fragment's own provenance.\n *\n * An explicit `deliveryProvenance` wins: FEC-recovered fragments carry no\n * `source` (they were never delivered as themselves) but their block's symbol\n * provenance IS known, and the router supplies it. Falling back to `source`\n * keeps directly-delivered fragments working unchanged.\n */\nfunction fragmentProvenance(fragment: {\n source?: MmtpSourcePath\n deliveryProvenance?: MmtpDeliveryProvenance\n}): MmtpDeliveryProvenance {\n if (fragment.deliveryProvenance !== undefined) return fragment.deliveryProvenance\n if (fragment.source === 'moq') return 'mixed'\n if (fragment.source === 'multicast') return 'multicast'\n return 'unknown'\n}\n\n/** Strict provenance for a complete fragment set. */\nexport function deliveryProvenanceOf(\n fragments: readonly {\n source?: MmtpSourcePath\n deliveryProvenance?: MmtpDeliveryProvenance\n }[]\n): MmtpDeliveryProvenance {\n if (fragments.length === 0) return 'unknown'\n let verdict: MmtpDeliveryProvenance = 'multicast'\n for (const fragment of fragments) {\n const own = fragmentProvenance(fragment)\n verdict =\n verdict === 'mixed' || own === 'mixed'\n ? 'mixed'\n : verdict === 'unknown' || own === 'unknown'\n ? 'unknown'\n : 'multicast'\n }\n return verdict\n}\n\n/**\n * MFU Fragment for reassembly\n */\nexport interface MFUFragment {\n fragmentationIndicator: number\n mpuSequenceNumber: number\n fragmentCounter: number\n data: Uint8Array\n timestamp: bigint\n /** Random Access Point flag from MMTP header - indicates keyframe */\n rapFlag: boolean\n /** MFU DU priority. FFmpeg marks random-access samples with high priority. */\n priority?: number\n sampleNumber?: number\n movieFragmentSequenceNumber?: number\n sampleOffset?: number\n /** Non-timed MFU Item_ID from the 4-byte DU header. */\n itemId?: number\n /** Authored u32 MMTP packet_sequence_number, including on recovered packets. */\n packetSequenceNumber?: number\n /** True when this accepted source packet was reconstructed by FEC. */\n recovered?: boolean\n /** Transport that supplied this accepted fragment. Absent for recovered data. */\n source?: MmtpSourcePath\n /**\n * Explicit provenance for this fragment, overriding what `source` implies.\n * Set by the router for FEC-recovered fragments, which have no `source` of\n * their own but whose FEC block's symbol provenance is known.\n */\n deliveryProvenance?: MmtpDeliveryProvenance\n /** Immutable catalog identity supplied by the owning ingress subscription. */\n catalogTrackName?: string\n /** Physical MMTP stream identity when no catalog identity is available. */\n packetId?: number\n}\n\n/**\n * Reassembled MFU result\n */\nexport interface ReassembledMFU {\n mpuSequenceNumber: number\n /** MMTP packet_sequence_number of the first fragment in delivery order. */\n packetSequenceNumber?: number\n /** Every authoritative MMTP source-packet sequence that formed this MFU. */\n packetSequenceNumbers?: number[]\n /** Subset of packetSequenceNumbers reconstructed by FEC. */\n repairedPacketSequenceNumbers?: number[]\n data: Uint8Array\n timestamp: bigint\n fragmentCount: number\n /** Random Access Point flag - indicates keyframe (from first fragment) */\n rapFlag: boolean\n /** MFU DU priority from the first fragment that carried it. */\n priority?: number\n sampleNumber?: number\n movieFragmentSequenceNumber?: number\n /**\n * This DU's authored base byte offset within its sample (moqcast #37). 0\n * for a whole-sample DU; nonzero identifies one subsample DU among\n * siblings partitioning an oversized sample. Constant across the DU's own\n * fragments \u2014 feed into a sample combiner to reassemble the full sample.\n */\n sampleOffset?: number\n /**\n * Transport that supplied the accepted FI=1 head fragment (or the complete\n * FI=0 frame). This is the deterministic winner for mixed-path assembly.\n *\n * ROUTING ONLY. It describes one fragment, so it is not evidence about the\n * whole frame \u2014 read {@link ReassembledMFU.deliveryProvenance} for that.\n */\n source?: MmtpSourcePath\n /**\n * Strict provenance folded over every contributing fragment. Only\n * 'multicast' proves multicast-exclusive delivery of this frame.\n */\n deliveryProvenance: MmtpDeliveryProvenance\n /** Catalog identity from the accepted FI=1 head fragment, or FI=0 frame. */\n catalogTrackName?: string\n}\n\n/**\n * Reassembler statistics\n */\nexport interface ReassemblerStats {\n totalFragments: number\n reassembledMFUs: number\n /** Fragments rejected by strict authored DU-state validation. */\n rejectedFragments: number\n /** Incomplete MFUs discarded after their configured deadline. */\n timedOutMFUs: number\n /** Incomplete MFUs discarded to enforce the configured memory bound. */\n evictedMemoryLimit: number\n outOfOrderFragments: number\n}\n\nexport const MFU_REASSEMBLY_ERROR_CODES = [\n 'invalid-fragment-state',\n 'missing-data-unit-identity',\n 'missing-packet-sequence',\n 'conflicting-data-unit-identity',\n 'overlapping-data-unit',\n 'fragment-counter-mismatch',\n 'data-unit-identity-mismatch',\n 'offset-overflow',\n 'active-data-unit-limit',\n 'incomplete-terminal-fragment',\n 'ambiguous-continuation',\n] as const\n\nexport type MfuReassemblyErrorCode = (typeof MFU_REASSEMBLY_ERROR_CODES)[number]\n\nconst mfuReassemblyErrorCodes = new Set<string>(MFU_REASSEMBLY_ERROR_CODES)\n\nexport function isMfuReassemblyErrorCode(value: unknown): value is MfuReassemblyErrorCode {\n return typeof value === 'string' && mfuReassemblyErrorCodes.has(value)\n}\n\nexport class MfuReassemblyError extends Error {\n readonly name = 'MfuReassemblyError'\n\n constructor(\n readonly code: MfuReassemblyErrorCode,\n message: string\n ) {\n super(message)\n }\n}\n\n/**\n * Identity-free test for a reassembly rejection.\n *\n * Consumers that inline `@blockcast/mmt-container` more than once (a component\n * bundle whose `@blockcast/*` dependencies each carry their own copy) end up\n * with several distinct `MfuReassemblyError` classes. An `instanceof` check\n * against one copy then misses rejections raised by another, and a per-fragment\n * rejection escapes as an uncaught throw that aborts the rest of the packet's\n * processing. The wire contract of a rejection is its name plus one of the\n * closed error codes, so that is what this guard checks.\n */\nexport function isMfuReassemblyError(error: unknown): error is MfuReassemblyError {\n if (error instanceof MfuReassemblyError) return true\n if (typeof error !== 'object' || error === null) return false\n return (\n Reflect.get(error, 'name') === 'MfuReassemblyError' &&\n isMfuReassemblyErrorCode(Reflect.get(error, 'code'))\n )\n}\n\nfunction assembledPacketSequences(fragments: readonly MFUFragment[]): {\n packetSequenceNumbers?: number[]\n repairedPacketSequenceNumbers?: number[]\n} {\n const packetSequenceNumbers: number[] = []\n const repairedPacketSequenceNumbers: number[] = []\n const seen = new Set<number>()\n const repaired = new Set<number>()\n for (const fragment of fragments) {\n const sequence = fragment.packetSequenceNumber\n if (typeof sequence !== 'number' || !Number.isSafeInteger(sequence) || sequence < 0 || sequence > 0xffffffff) {\n return {}\n }\n if (!seen.has(sequence)) {\n seen.add(sequence)\n packetSequenceNumbers.push(sequence)\n }\n if (fragment.recovered === true && !repaired.has(sequence)) {\n repaired.add(sequence)\n repairedPacketSequenceNumbers.push(sequence)\n }\n }\n return { packetSequenceNumbers, repairedPacketSequenceNumbers }\n}\n\nexport interface PendingMfuDebug {\n key: string\n fragments: number\n fi0: number\n fi1: number\n fi2: number\n fi3: number\n firstFi: number\n lastFi: number\n firstOrder: number\n lastOrder: number\n minCounter: number\n maxCounter: number\n minOffset?: number\n maxOffset?: number\n movieFragmentSequenceNumber?: number\n sampleNumber?: number\n mpuSequenceNumber?: number\n sortedHead: Array<{\n fi: number\n counter: number\n order: number\n seq?: number\n offset?: number\n }>\n sortedTail: Array<{\n fi: number\n counter: number\n order: number\n seq?: number\n offset?: number\n }>\n}\n\nexport interface MFUReassemblerOptions {\n /** Timeout (ms) for incomplete MFUs. Derive from frame duration or FEC interleaveDepthMs.\n * Pass null for live clock-free operation; memory remains bounded by maxBuffered. */\n timeoutMs: number | null\n /** Max concurrent incomplete MFUs, including continuations awaiting FI=1. */\n maxBuffered?: number\n}\n\n/** Reassembly surface consumed by MmtpContainerParser and MmtpRouter.\n * Implemented by both the TypeScript engine and the shared\n * Rust/WASM IFC adapter. */\nexport interface MfuReassemblerBackend {\n timeoutMs: number | null\n readonly stats: ReassemblerStats\n onDeferredAssembly?: (result: ReassembledMFU) => void\n /** Returns null only for a valid incomplete DU; strict rejection throws\n * `MfuReassemblyError`. */\n addFragment(fragment: MFUFragment): ReassembledMFU | null\n flush(): void\n resetStream(catalogTrackName: string): void\n reset(): void\n readonly pendingCount: number\n getStatus(): {\n bufferedMFUs: number\n pendingFragments: number\n pendingSamples: PendingMfuDebug[]\n stats: ReassemblerStats\n }\n}\n\nfunction streamScope(fragment: MFUFragment): readonly [kind: string, identity: string | number | null] {\n if (fragment.catalogTrackName !== undefined) return ['catalog', fragment.catalogTrackName]\n if (fragment.packetId !== undefined) return ['packet', fragment.packetId]\n return ['unspecified', null]\n}\n\nfunction isU32(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value) && value >= 0 && value <= 0xffffffff\n}\n\n/**\n * Wire address of one fragmented data unit: the physical stream scope, the\n * MPU, and `packetSequenceNumber + fragmentCounter` mod 2^32. Under the\n * contiguous-source producer profile every fragment of one data unit occupies\n * consecutive packet sequence numbers on its packet ID while\n * `fragmentCounter` counts the payloads that follow, so the sum is the\n * sequence number of the data unit's last fragment: equal for every fragment\n * of one data unit and distinct between data units of one MPU. Transport\n * association only; objectKey remains the intrinsic DU identity.\n */\nfunction dataUnitAddress(fragment: MFUFragment, packetSequenceNumber: number): string {\n return JSON.stringify([\n ...streamScope(fragment),\n fragment.mpuSequenceNumber,\n (packetSequenceNumber + fragment.fragmentCounter) >>> 0,\n ])\n}\n\nfunction hasAnySampleIdentityField(fragment: MFUFragment): boolean {\n return (\n fragment.movieFragmentSequenceNumber !== undefined ||\n fragment.sampleNumber !== undefined ||\n fragment.sampleOffset !== undefined\n )\n}\n\ntype IdentifiedSampleFragment = MFUFragment &\n Required<Pick<MFUFragment, 'movieFragmentSequenceNumber' | 'sampleNumber' | 'sampleOffset'>>\n\nfunction hasSampleIdentity(fragment: MFUFragment): fragment is IdentifiedSampleFragment {\n return (\n isU32(fragment.movieFragmentSequenceNumber) &&\n fragment.movieFragmentSequenceNumber > 0 &&\n isU32(fragment.sampleNumber) &&\n fragment.sampleNumber > 0 &&\n isU32(fragment.sampleOffset)\n )\n}\n\nfunction hasValidFragmentHeader(fragment: MFUFragment): boolean {\n const { fragmentationIndicator: fi, fragmentCounter: counter } = fragment\n if (!Number.isInteger(fi) || fi < 0 || fi > 3) return false\n if (!Number.isInteger(counter) || counter < 0 || counter > 0xff) return false\n return fi === 0 || fi === 3 ? counter === 0 : counter > 0\n}\n\nfunction fragmentCounterOrder(fragment: MFUFragment): number {\n return 0xff - fragment.fragmentCounter\n}\n\nfunction objectKey(fragment: MFUFragment): string {\n const scope = streamScope(fragment)\n if (fragment.itemId !== undefined) {\n return JSON.stringify([...scope, fragment.mpuSequenceNumber, 'item', fragment.itemId])\n }\n if (hasSampleIdentity(fragment)) {\n return JSON.stringify([\n ...scope,\n fragment.mpuSequenceNumber,\n 'sample',\n fragment.movieFragmentSequenceNumber,\n fragment.sampleNumber,\n // The DU's authored base offset (moqcast #37): sibling subsample DUs\n // of one sample get distinct keys instead of colliding. Only ever\n // called with the unmutated FI=1 (or FI=0) fragment, whose\n // sampleOffset is that authored base -- never the accumulating\n // per-continuation value written by #associateDataUnit.\n fragment.sampleOffset,\n ])\n }\n return JSON.stringify([...scope, fragment.mpuSequenceNumber, 'unidentified'])\n}\n\nfunction ordersByOffset(fragments: MFUFragment[]): boolean {\n return fragments.every(fragment => hasSampleIdentity(fragment))\n}\n\ntype ActiveDataUnit = {\n /** Wire address every continuation of this data unit carries. */\n address: string\n bufferKey: string\n nextFragmentCounter: number\n /** Monotonic admission ordinal of this data unit's slot. Never a clock. */\n admissionSeq: number\n catalogTrackName?: string\n} & (\n | {\n kind: 'sample'\n movieFragmentSequenceNumber: number\n sampleNumber: number\n nextOffset: number\n }\n | {\n kind: 'item'\n itemId: number\n }\n)\n\ninterface DataUnitAssociation {\n bufferKey: string\n first: boolean\n terminal: boolean\n}\n\ninterface PendingContinuationRun {\n fragments: Map<number, MFUFragment>\n startedAt: number\n /** Monotonic admission ordinal of this data unit's slot. Never a clock. */\n admissionSeq: number\n timeoutId?: ReturnType<typeof setTimeout>\n}\n\ninterface BufferTimeoutState {\n startedAt: number\n timeoutId?: ReturnType<typeof setTimeout>\n}\n\nfunction checkedSampleEnd(offset: number, byteLength: number): number | null {\n const end = offset + byteLength\n return Number.isSafeInteger(end) && end <= 0xffffffff ? end : null\n}\n\n/**\n * MFU Reassembler with bounded reordered-fragment association\n * Based on ISO/IEC 23008-1:2023 Section 9.2.3.3\n */\n\n/**\n * Completed-address window, as a multiple of maxBuffered. A late continuation\n * is refused as stale while its data unit's address is within this window and\n * parks as a headless data unit once the address has been forgotten. The\n * value is a cross-engine invariant shared with the Rust reassembler\n * (mmt-core COMPLETED_ADDRESS_WINDOW_MULTIPLE); change both together.\n */\nexport const COMPLETED_ADDRESS_WINDOW_MULTIPLE = 2\n\n/**\n * Cross-engine anchors for the clock-free eviction guard. The bound itself is\n * `maxBuffered`, a host parameter \u2014 these fix the one scenario both regression\n * corpora drive so the Rust and TypeScript guards assert the same boundary\n * rather than each deriving its own. Shared with the Rust reassembler\n * (`EVICTION_GUARD_MAX_BUFFERED` / `EVICTION_GUARD_DATA_UNITS` in\n * `mmt-core/src/reassembler/policy/mmt.rs`); change both together.\n *\n * `EVICTION_GUARD_DATA_UNITS` headless data units admitted against a bound of\n * `EVICTION_GUARD_MAX_BUFFERED` must evict exactly the difference.\n */\nexport const EVICTION_GUARD_MAX_BUFFERED = 4\nexport const EVICTION_GUARD_DATA_UNITS = 12\n\n/**\n * Does a parked continuation queue hold a `maxBuffered` slot of its own?\n *\n * One rule, four call sites. Two read the bound: the queue that is *counted*\n * toward it (`#unopenedParkedRunCount`) is exactly the queue that is *offered*\n * to eviction (`#evictLeastRecentlyAdmitted`). Two gate admission against it:\n * `#parkContinuation` and the FI=1 arm of `#associateDataUnit` each ask whether\n * this address is already charged before moving the bound. Stating the rule once\n * is what keeps those four answers from drifting apart \u2014 a queue that charges a\n * slot no eviction can reclaim wedges the receiver, because the bound then\n * refuses every new data unit forever with `active-data-unit-limit`.\n *\n * Two exclusions, for two different reasons:\n *\n * - **An open run owns the address.** The data unit is already charged through\n * that run, so its queue costs nothing further.\n * - **The queue is empty.** It holds nothing, so it charges nothing and offers\n * nothing.\n *\n * The emptiness half is belt-and-braces, not load-bearing. Both engines discard\n * an emptied queue eagerly \u2014 `#deletePendingRun` here on every drain exit,\n * `discard_empty_parked_run` in Rust \u2014 and `#parkContinuation` always inserts\n * into a queue it just created, so an empty queue is not observable today.\n * Correctness must not depend on that invariant holding in all three places per\n * engine: routing every site that asks the question through this one predicate\n * is what keeps the engines agreeing if a future edit ever misses one of them.\n *\n * Scope: this rule decides *slot accounting* \u2014 counted, offered, admitted. A\n * queue carries two further per-slot facts, its admission ordinal and its\n * retention anchor (`startedAt` plus the timer armed from it), and both must\n * move when an empty queue is charged again. `#parkContinuation` resets them\n * explicitly; Rust gets the same contract structurally, by deriving each from\n * the queue's live entries. Same contract, two mechanisms.\n *\n * The *reap schedule* asks the same question, and every site that arms a\n * parked queue reaches one function, `#armHeadlessPendingTimeout`, which takes\n * the test through this rule: `set timeoutMs` calls it for every queue when\n * the policy changes, `#parkContinuation` calls it after a created or\n * re-anchored queue holds its first fragment, and a run closing without\n * completing calls it for the queue it leaves behind. It never arms a timer\n * for an empty queue, because a timeout reports a data unit that failed to\n * complete in time and an empty queue holds no such data unit. Charging one\n * would record an MFU that never existed. No site installs a parked-queue\n * timer of its own, so neither the slot rule nor the `startedAt` anchoring can\n * drift one site at a time.\n *\n * That is the answer Rust reaches structurally: `next_timeout_at` derives\n * candidate deadlines from the queue's live entries, so an empty queue yields\n * none and no sweep is ever scheduled on its account, and `cleanup_timeouts`\n * retains by `parked_run_holds_a_slot`, so a sweep arriving for any *other*\n * queue leaves an empty one in place rather than charging\n * `record_policy_timeout` for it. Both engines therefore agree on when the\n * reap is scheduled, and the recorded statistic is unchanged for every queue\n * that really did hold a data unit: `#onPendingTimeout` charges\n * `stats.timedOutMFUs`, the shape Rust's `record_policy_timeout` produces, and\n * takes the same slot test before charging, so the statistic cannot be\n * recorded for an empty queue whatever order the arming sites run in.\n *\n * The trade this makes, in both engines: a queue that is emptied without being\n * discarded is invisible to the reap schedule, to the slot count, and to\n * eviction, so nothing reclaims it until `reset()` clears the policy state.\n * Disposal of an emptied queue therefore rests entirely on the eager discard\n * sites (`#deletePendingRun` here, `discard_empty_parked_run` in Rust), where\n * it is housekeeping rather than an expiry; a drain exit that skips that call\n * leaks the queue for the life of the reassembler rather than charging a\n * phantom timeout for it. Converged under BLO-32420. The arming half of this\n * rule is pinned in `mfu-reassembler-eviction.test.ts`: a headless queue that\n * holds fragments is armed by `set timeoutMs` and by its run closing, and\n * disarmed by `timeoutMs = null`. The emptiness half is Rust-only, because\n * `#pendingContinuations` is unreachable from this package's tests.\n *\n * The public counters are a separate matter and *are* governed: `pendingCount`\n * \u2014 and through it `getStatus().bufferedMFUs` \u2014 delegates to\n * `#unopenedParkedRunCount`, exactly as Rust's `buffered_count` is built on\n * `unopened_parked_run_count`. Filtering either on the open run alone makes the\n * engines report different numbers for the same state out of a host-observable\n * API.\n *\n * Twin of `parked_run_holds_a_slot` in\n * `mmt-core/src/reassembler/policy/mmt.rs`; change both together.\n */\nexport function parkedRunHoldsASlot(hasOpenRun: boolean, fragmentCount: number): boolean {\n return !hasOpenRun && fragmentCount > 0\n}\n\nexport class MFUReassembler {\n // Fragment buffers and active runs use the authored MFU DU identity.\n // Continuations may wait in one counter-indexed queue per data-unit address\n // until FI=1 supplies that identity. The address is transport association\n // only; the object key stays the intrinsic DU identity.\n #buffers: Map<string, MFUFragment[]> = new Map()\n #timeouts: Map<string, BufferTimeoutState> = new Map()\n #activeDataUnits: Map<string, ActiveDataUnit> = new Map()\n #pendingContinuations: Map<string, PendingContinuationRun> = new Map()\n #emitted: Set<string> = new Set() // same sample keys, so FEC-recovered duplicate heads are dropped\n #emittedOrder: string[] = []\n // Addresses of emitted data units. A continuation addressed to one of them\n // is a late copy of a fragment the run already consumed (FEC recovery\n // supplied it first), so it is rejected as stale rather than parked as a\n // headless data unit that would hold a slot until reset. Bounded at\n // COMPLETED_ADDRESS_WINDOW_MULTIPLE x maxBuffered, the same window the Rust\n // engine keeps, so both engines forget an address after the same number of\n // completions. (#emitted is a separate, per-engine window: this engine keeps\n // maxBuffered sample keys where Rust keeps 2x; the completed set does not\n // derive from it.)\n #completed: Set<string> = new Set()\n #completedOrder: string[] = []\n #timeoutMs: number | null\n #maxBuffered: number\n /**\n * Monotonic admission ordinal. Counts slot admissions, never reads a clock,\n * so eviction order survives on hosts that run `timeoutMs: null`.\n */\n #nextAdmissionSeq = 0\n\n /** Statistics for debugging */\n readonly stats: ReassemblerStats = {\n totalFragments: 0,\n reassembledMFUs: 0,\n rejectedFragments: 0,\n timedOutMFUs: 0,\n evictedMemoryLimit: 0,\n outOfOrderFragments: 0,\n }\n\n constructor(options: MFUReassemblerOptions) {\n if (options.timeoutMs !== null && (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0)) {\n throw new Error('[MFUReassembler] timeoutMs must be > 0 (derive from frame duration or FEC config)')\n }\n const maxBuffered = options.maxBuffered ?? 100\n if (!Number.isInteger(maxBuffered) || maxBuffered <= 0) {\n throw new Error('[MFUReassembler] maxBuffered must be a positive integer')\n }\n this.#timeoutMs = options.timeoutMs\n this.#maxBuffered = maxBuffered\n }\n\n /** Update the expiry policy for all retained and future incomplete MFUs. */\n set timeoutMs(ms: number | null) {\n if (ms !== null && (!Number.isFinite(ms) || ms <= 0)) {\n throw new Error('[MFUReassembler] timeoutMs must be > 0 (derive from frame duration or FEC config)')\n }\n this.#timeoutMs = ms\n const now = Date.now()\n for (const [bufferKey, state] of this.#timeouts) {\n if (state.timeoutId !== undefined) clearTimeout(state.timeoutId)\n delete state.timeoutId\n if (ms !== null) {\n state.timeoutId = setTimeout(() => this.#onTimeout(bufferKey), Math.max(0, state.startedAt + ms - now))\n }\n }\n // Parked queues re-arm through the one arming function, which reads the\n // new policy from `#timeoutMs` (assigned above) and keeps each queue on its\n // own `startedAt` anchor. Clearing first releases any timer armed under the\n // old policy so the helper's already-armed guard cannot skip the re-arm.\n for (const address of this.#pendingContinuations.keys()) {\n this.#clearPendingTimeout(address)\n this.#armHeadlessPendingTimeout(address)\n }\n }\n get timeoutMs(): number | null {\n return this.#timeoutMs\n }\n\n /**\n * Add a fragment and return reassembled MFU if complete\n */\n addFragment(fragment: MFUFragment): ReassembledMFU | null {\n this.stats.totalFragments++\n\n if (!hasValidFragmentHeader(fragment)) {\n this.#reject('invalid-fragment-state', 'invalid MFU fragmentation indicator or counter')\n }\n if (fragment.itemId !== undefined && hasAnySampleIdentityField(fragment)) {\n this.#reject('conflicting-data-unit-identity', 'MFU carries timed and non-timed DU identities')\n }\n\n const { mpuSequenceNumber, fragmentationIndicator } = fragment\n\n // A complete FT=2 data unit still requires its intrinsic DU identity.\n // The transport parser strips the deterministic timed/non-timed DU header\n // before calling this layer; direct callers must provide the same identity.\n // Don't check or update #emitted: in MMTP over MoQ, multiple complete MFUs\n // share the same mpuSequenceNumber (all frames in a GOP have the same mpuSeq).\n // Blocking FI=0 would prevent keyframe fragment assembly at the same mpuSeq.\n if (fragmentationIndicator === 0) {\n const sampleIdentity = hasSampleIdentity(fragment)\n const hasSampleFields = hasAnySampleIdentityField(fragment)\n const hasItemIdentity = fragment.itemId !== undefined\n if (!sampleIdentity && !hasItemIdentity) {\n this.#reject('missing-data-unit-identity', 'complete MFU has no intrinsic DU identity')\n }\n if (hasSampleFields && !sampleIdentity) {\n this.#reject('missing-data-unit-identity', 'complete MFU has an incomplete sample identity')\n }\n if (hasItemIdentity && !isU32(fragment.itemId)) {\n this.#reject('missing-data-unit-identity', 'complete MFU has an invalid Item_ID')\n }\n this.stats.reassembledMFUs++\n return {\n mpuSequenceNumber,\n packetSequenceNumber: fragment.packetSequenceNumber,\n ...assembledPacketSequences([fragment]),\n data: fragment.data,\n timestamp: fragment.timestamp,\n fragmentCount: 1,\n rapFlag: fragment.rapFlag,\n priority: fragment.priority,\n sampleNumber: fragment.sampleNumber,\n movieFragmentSequenceNumber: fragment.movieFragmentSequenceNumber,\n sampleOffset: fragment.sampleOffset,\n source: fragment.source,\n deliveryProvenance: deliveryProvenanceOf([fragment]),\n catalogTrackName: fragment.catalogTrackName,\n }\n }\n\n const sequence = fragment.packetSequenceNumber\n if (!isU32(sequence)) {\n this.#reject('missing-packet-sequence', 'fragmented MFU has no MMTP packet sequence number')\n }\n const address = dataUnitAddress(fragment, sequence)\n\n if (fragmentationIndicator !== 1) {\n // Attribution is an exact per-data-unit lookup. The continuation joins\n // the one open run holding its address when its counter is that run's\n // next. It is stale when the run has already counted past the counter,\n // or when the data unit was already emitted: the position was accepted\n // once, so no run can ever take it again. Otherwise (no open run at this\n // address, or the run has not reached the counter yet) it parks under\n // its own address. Other runs' counters are never consulted.\n const open = this.#openRunAt(address)\n if (open === undefined) {\n if (this.#completed.has(address)) {\n this.#reject('fragment-counter-mismatch', 'MFU continuation names a data unit that already completed')\n }\n this.#parkContinuation(address, fragment)\n return null\n }\n const [runKey, active] = open\n if (active.nextFragmentCounter > fragment.fragmentCounter) {\n this.#parkContinuation(address, fragment)\n return null\n }\n if (active.nextFragmentCounter < fragment.fragmentCounter) {\n this.#reject('fragment-counter-mismatch', 'MFU continuation is behind the authored fragment-counter countdown')\n }\n const result = this.#addAssociatedFragment(fragment, runKey, address)\n if (result !== null) return result\n return this.#drainPendingContinuations(address, runKey)\n }\n\n const runKey = objectKey(fragment)\n if ((hasSampleIdentity(fragment) || isU32(fragment.itemId)) && this.#emitted.has(runKey)) {\n // The identity already emitted, so this first fragment is a duplicate\n // and is dropped. Queues under other addresses belong to other data\n // units and are untouched.\n return null\n }\n if (this.#activeDataUnits.has(runKey)) {\n this.#reject('overlapping-data-unit', 'fragmented MFU overlaps an active data unit')\n }\n // Two heads with one address are one data unit delivered twice or an\n // identity clash on the wire. The established run keeps the address.\n if (this.#openRunAt(address) !== undefined) {\n this.#reject('overlapping-data-unit', 'fragmented MFU opens on an address held by an open data unit')\n }\n const pending = this.#pendingContinuations.get(address)\n // A parked continuation of this data unit cannot carry a counter at or\n // above its own head's countdown.\n if (pending !== undefined && [...pending.fragments.keys()].some(counter => counter >= fragment.fragmentCounter)) {\n this.#reject(\n 'fragment-counter-mismatch',\n 'queued continuation counter is not below the authored first-fragment counter'\n )\n }\n\n // The run's deadline starts at the earliest arrival of its own fragments.\n const result = this.#addAssociatedFragment(fragment, runKey, address, pending?.startedAt)\n this.#clearPendingTimeout(address)\n if (result !== null) return result\n return this.#drainPendingContinuations(address, runKey)\n }\n\n #addAssociatedFragment(\n fragment: MFUFragment,\n runKey: string,\n address: string,\n startedAt?: number\n ): ReassembledMFU | null {\n const association = this.#associateDataUnit(fragment, runKey, address)\n const { bufferKey, first, terminal } = association\n\n let fragments = this.#buffers.get(bufferKey)\n if (first) {\n if (fragments !== undefined) {\n this.#reject('overlapping-data-unit', 'intrinsic data-unit buffer already exists')\n }\n fragments = []\n this.#buffers.set(bufferKey, fragments)\n } else if (fragments === undefined) {\n throw new Error('[MFUReassembler] active data unit has no fragment buffer')\n }\n\n // A lossy-path deadline is measured from the first received fragment,\n // including a continuation that arrived before FI=1. Reliable paths are\n // clock-free and never install this timer.\n if (first) this.#setTimeout(bufferKey, startedAt)\n fragments.push(fragment)\n\n if (!terminal) return null\n const result = this.#tryReassemble(bufferKey)\n this.#activeDataUnits.delete(runKey)\n if (result === null) {\n this.#clearTimeout(bufferKey)\n this.#buffers.delete(bufferKey)\n this.#reject('incomplete-terminal-fragment', 'validated terminal fragment did not complete its data unit')\n }\n this.#markCompleted(address)\n return result\n }\n\n #parkContinuation(address: string, fragment: MFUFragment): void {\n let pending = this.#pendingContinuations.get(address)\n const open = this.#openRunAt(address)\n // A data unit is charged one slot: through its open run, or through its\n // queue while no run is open. Parking under an address that is already\n // charged adds nothing to the bound, so admission is judged by what this\n // fragment would add, not by the total alone. The queue half routes through\n // `parkedRunHoldsASlot` rather than restating it, so this gate cannot drift\n // from the sites that count and evict by it: an empty queue charges nothing\n // here for the same reason it is offered nothing there. Gating on\n // `pending === undefined` instead would skip the bound entirely for a queue\n // that exists but holds nothing, overshooting `maxBuffered` by one while\n // Rust evicts \u2014 the cross-engine split BLO-31813 exists to close.\n const alreadyCharged =\n open !== undefined || parkedRunHoldsASlot(open !== undefined, pending?.fragments.size ?? 0)\n if (!alreadyCharged && this.#incompleteRunCount() >= this.#maxBuffered) {\n // `open` and `pending` are captured above, and eviction can delete from\n // `#pendingContinuations` \u2014 so both are read across a mutation. Neither\n // goes stale, and the predicate is what makes that true: eviction only\n // runs when `open === undefined`, and an empty queue is not a candidate,\n // so the record the `else if` below writes to cannot be the one released\n // here. Re-read both if that rule is ever loosened.\n if (!this.#evictLeastRecentlyAdmitted()) {\n this.#reject('active-data-unit-limit', 'active MFU data-unit limit reached')\n }\n }\n // Both branches below leave the timer to `#armHeadlessPendingTimeout`,\n // called once the fragment is in, so the queue is armed only while it\n // holds a data unit and through the same slot rule as every other site.\n let armAfterInsert = false\n if (pending === undefined) {\n // A queue opened under an already-charged address inherits that slot's\n // ordinal, so a run does not renew its admission by parking a fragment.\n // The arm below is a no-op under an open run: the run's own deadline\n // covers the data unit, and `#armHeadlessPendingTimeout` tests for it.\n pending = {\n fragments: new Map(),\n startedAt: Date.now(),\n admissionSeq: open !== undefined ? open[1].admissionSeq : this.#nextAdmissionSeq++,\n }\n this.#pendingContinuations.set(address, pending)\n armAfterInsert = true\n } else if (!alreadyCharged) {\n // A queue that exists but holds nothing charged no slot, so this fragment\n // is a fresh admission \u2014 and every per-slot fact the record still carries\n // describes an occupancy that ended when its last entry left. Rust derives\n // all of them from the queue's *live* entries, so re-parking moves them\n // together; TypeScript caches them as fields written once at creation, so\n // they have to be reset together here or the queue goes transparent to the\n // bound while staying stale on its deadline:\n //\n // - Ordinal. `slot_admission_seq` mins over the entries and an empty queue\n // has none, so Rust mints a fresh one. Decides victim ordering.\n // - Retention anchor. `cleanup_timeouts` retains on `min(received_at)`,\n // which a re-parked entry moves forward. Left stale, `#onPendingTimeout`\n // can discard the queue on the emptied queue's deadline while it holds a\n // fragment that was just charged a slot \u2014 and that may have evicted\n // another data unit to get it.\n // - Armed timer. `#armHeadlessPendingTimeout` anchors on `startedAt` and\n // arms only a queue that holds a fragment, so the re-arm runs once this\n // fragment is in (below), where the creation branch arms too, rather\n // than here, where the queue is still empty and the call would arm\n // nothing. Any timer left on the record is cleared first so the queue\n // cannot keep running on the old anchor.\n //\n // `Date.now()` here is the same retention anchor the queue-creation branch\n // above already reads, not a clock in the admission order: the ordinal\n // stays a monotonic sequence, so eviction remains clock-free per PR #93.\n //\n // Unreachable while every drain exit discards an emptied queue; pinned so\n // the engines cannot pick different victims \u2014 or different expiries \u2014 if\n // one ever misses a discard site.\n pending.admissionSeq = this.#nextAdmissionSeq++\n pending.startedAt = Date.now()\n this.#clearPendingTimeout(address)\n armAfterInsert = true\n }\n // The same data unit naming the same countdown position twice: the first\n // parked copy is kept.\n if (pending.fragments.has(fragment.fragmentCounter)) {\n this.#reject('fragment-counter-mismatch', 'duplicate queued continuation counter would replace retained media')\n }\n pending.fragments.set(fragment.fragmentCounter, fragment)\n // A created or re-anchored queue holds its first fragment only now, so this\n // is the first point at which `#armHeadlessPendingTimeout` arms it. With\n // `startedAt` set one tick earlier the deadline it computes is the full\n // `timeoutMs`, the same schedule a raw timer armed at creation would have\n // kept. The duplicate check above cannot reject on this path: both branches\n // that set the flag run exactly when the queue is empty.\n if (armAfterInsert) this.#armHeadlessPendingTimeout(address)\n }\n\n #drainPendingContinuations(address: string, runKey: string): ReassembledMFU | null {\n while (true) {\n const active = this.#activeDataUnits.get(runKey)\n const pending = this.#pendingContinuations.get(address)\n if (active === undefined) {\n // The run closed. Its queue holds only this data unit's fragments;\n // drop it once empty so it is not counted as a headless data unit.\n if (pending?.fragments.size === 0) this.#deletePendingRun(address)\n return null\n }\n const fragment = pending?.fragments.get(active.nextFragmentCounter)\n if (fragment === undefined) {\n if (pending?.fragments.size === 0) this.#deletePendingRun(address)\n return null\n }\n pending!.fragments.delete(active.nextFragmentCounter)\n if (pending!.fragments.size === 0) this.#deletePendingRun(address)\n const result = this.#addAssociatedFragment(fragment, runKey, address)\n if (result !== null) {\n return result\n }\n }\n }\n\n /** Parked data units that hold a `maxBuffered` slot of their own. Membership\n * is `parkedRunHoldsASlot`, the same rule #evictLeastRecentlyAdmitted offers\n * as candidates, so nothing can be counted here that eviction cannot reclaim.\n *\n * One site, two callers \u2014 `#incompleteRunCount` and `pendingCount` \u2014 mirroring\n * Rust's `unopened_parked_run_count`, which `incomplete_run_count` and\n * `buffered_count` both delegate to. Both callers used to inline this loop\n * verbatim, so converging the *rule* moved one copy and left the other as the\n * divergence (Ally's review of #400). The rule can no longer drift now that\n * every site routes through `parkedRunHoldsASlot`; keeping the loop itself at\n * one site is what stops the scaffolding drifting the next time this question\n * grows an argument. */\n #unopenedParkedRunCount(): number {\n let count = 0\n for (const [address, pending] of this.#pendingContinuations) {\n if (parkedRunHoldsASlot(this.#openRunAt(address) !== undefined, pending.fragments.size)) {\n count++\n }\n }\n return count\n }\n\n /** Every open run plus every headless parked data unit that holds a slot,\n * one slot each. This is the quantity maxBuffered bounds. The parked term is\n * `#unopenedParkedRunCount`, whose membership is `parkedRunHoldsASlot` \u2014 the\n * same rule #evictLeastRecentlyAdmitted offers as candidates, so nothing can\n * be counted here that eviction cannot reclaim. */\n #incompleteRunCount(): number {\n return this.#activeDataUnits.size + this.#unopenedParkedRunCount()\n }\n\n /**\n * Drop the incomplete data unit admitted longest ago so a newly arriving one\n * can take its slot. Selection is by monotonic admission ordinal, never by\n * wall clock, so this is a bounded-capacity replacement policy and not a\n * timeout: it behaves identically on hosts that never drive `cleanup_timeouts`\n * (`timeoutMs: null`). Only incomplete data units are candidates \u2014 a data\n * unit that completed has already left both maps.\n *\n * Returns false only if no slot is charged, which cannot happen at the bound\n * (`maxBuffered` is a positive integer); admission then falls back to the\n * fail-closed `active-data-unit-limit` reject, which stays a stable host code.\n * That holds because the candidate set here is built from the same\n * `parkedRunHoldsASlot` rule `#incompleteRunCount` counts by, so reaching the\n * bound implies at least one candidate. Filtering one and not the other would\n * make the claim false: a queue counted but not offered charges a slot no\n * eviction can reclaim, and the receiver refuses every later data unit\n * permanently.\n */\n #evictLeastRecentlyAdmitted(): boolean {\n let victimAddress: string | undefined\n let victimSeq = Number.POSITIVE_INFINITY\n for (const [, active] of this.#activeDataUnits) {\n if (active.admissionSeq < victimSeq) {\n victimSeq = active.admissionSeq\n victimAddress = active.address\n }\n }\n for (const [address, pending] of this.#pendingContinuations) {\n // Exactly the queues #incompleteRunCount charges a slot to: a queue under\n // an open run is that run's slot, already considered above, and an empty\n // queue holds nothing to evict.\n if (!parkedRunHoldsASlot(this.#openRunAt(address) !== undefined, pending.fragments.size)) {\n continue\n }\n if (pending.admissionSeq < victimSeq) {\n victimSeq = pending.admissionSeq\n victimAddress = address\n }\n }\n if (victimAddress === undefined) return false\n this.#releaseSlot(victimAddress)\n this.stats.evictedMemoryLimit++\n return true\n }\n\n /**\n * Release every form of state one address holds: its open run, that run's\n * buffered fragments and deadline, and its continuation queue. The address is\n * deliberately not marked completed \u2014 the data unit never emitted, so a later\n * fragment of it may open a fresh slot rather than being rejected as stale.\n */\n #releaseSlot(address: string): void {\n const open = this.#openRunAt(address)\n if (open !== undefined) {\n const [runKey, active] = open\n this.#activeDataUnits.delete(runKey)\n this.#clearTimeout(active.bufferKey)\n this.#buffers.delete(active.bufferKey)\n }\n this.#deletePendingRun(address)\n }\n\n #reject(code: MfuReassemblyErrorCode, message: string): never {\n this.stats.rejectedFragments++\n throw new MfuReassemblyError(code, message)\n }\n\n #associateDataUnit(fragment: MFUFragment, runKey: string, address: string): DataUnitAssociation {\n const fi = fragment.fragmentationIndicator\n if (fi === 1) {\n // Opening a run is free when this data unit already holds a parked\n // slot: the run inherits it, so the bound does not move.\n //\n // A second head on an open address is refused upstream, so no run is open\n // here. Read that live rather than assume it, and route the answer through\n // `parkedRunHoldsASlot`, so this gate is the fourth site of one rule\n // instead of a fourth restatement of it. `parked` is non-null wherever\n // this is true, because the rule requires a non-empty queue.\n const parked = this.#pendingContinuations.get(address)\n const inheritsParkedSlot = parkedRunHoldsASlot(\n this.#openRunAt(address) !== undefined,\n parked?.fragments.size ?? 0,\n )\n\n // Resolve identity and byte extent BEFORE taking a slot. Eviction\n // replaces a healthy data unit, so it must only ever run for a fragment\n // that is going to be admitted -- a reject reached after eviction would\n // destroy valid state and break the contract these rejects exist to\n // uphold (\"without advancing or replacing valid state\"). addFragment\n // pre-validates only the FI/counter pair and the itemId/sample-field\n // conflict, so every reject below is reachable at the bound.\n const sampleIdentity = hasSampleIdentity(fragment)\n const hasSampleFields = hasAnySampleIdentityField(fragment)\n const hasItemIdentity = fragment.itemId !== undefined\n if (sampleIdentity && hasItemIdentity) {\n this.#reject('conflicting-data-unit-identity', 'fragmented MFU carries timed and non-timed DU identities')\n }\n if (!sampleIdentity && !hasItemIdentity) {\n this.#reject('missing-data-unit-identity', 'first fragment has no intrinsic DU identity')\n }\n if (hasSampleFields && !sampleIdentity) {\n this.#reject('missing-data-unit-identity', 'first fragment has an incomplete sample identity')\n }\n if (hasItemIdentity && !isU32(fragment.itemId)) {\n this.#reject('missing-data-unit-identity', 'first fragment has an invalid Item_ID')\n }\n\n const bufferKey = objectKey(fragment)\n if (this.#buffers.has(bufferKey)) {\n this.#reject('overlapping-data-unit', 'intrinsic data-unit buffer already exists')\n }\n\n let sampleEnd: number | null = null\n if (sampleIdentity) {\n sampleEnd = checkedSampleEnd(fragment.sampleOffset, fragment.data.byteLength)\n if (sampleEnd === null) this.#reject('offset-overflow', 'sample byte offset overflow')\n }\n\n // The fragment is admissible from here on, so charging it a slot cannot\n // be undone by a later reject. A second head on an open address was\n // already refused upstream, so this address holds no run and can never\n // be its own eviction victim.\n if (!inheritsParkedSlot && this.#incompleteRunCount() >= this.#maxBuffered) {\n if (!this.#evictLeastRecentlyAdmitted()) {\n this.#reject('active-data-unit-limit', 'active MFU data-unit limit reached')\n }\n }\n // An inherited slot keeps the ordinal it was admitted with: the head\n // arriving late does not make the data unit younger than one parked\n // after it.\n const admissionSeq = inheritsParkedSlot ? parked!.admissionSeq : this.#nextAdmissionSeq++\n\n let active: ActiveDataUnit\n if (sampleIdentity) {\n active = {\n address,\n kind: 'sample',\n bufferKey,\n nextFragmentCounter: fragment.fragmentCounter - 1,\n admissionSeq,\n movieFragmentSequenceNumber: fragment.movieFragmentSequenceNumber,\n sampleNumber: fragment.sampleNumber,\n nextOffset: sampleEnd!,\n ...(fragment.catalogTrackName === undefined ? {} : { catalogTrackName: fragment.catalogTrackName }),\n }\n } else {\n active = {\n address,\n kind: 'item',\n bufferKey,\n nextFragmentCounter: fragment.fragmentCounter - 1,\n admissionSeq,\n itemId: fragment.itemId!,\n ...(fragment.catalogTrackName === undefined ? {} : { catalogTrackName: fragment.catalogTrackName }),\n }\n }\n this.#activeDataUnits.set(objectKey(fragment), active)\n return { bufferKey, first: true, terminal: false }\n }\n\n const active = this.#activeDataUnits.get(runKey)\n if (active === undefined) {\n throw new Error('[MFUReassembler] continuation was not parked before association')\n }\n if (fragment.fragmentCounter !== active.nextFragmentCounter) {\n this.#reject(\n 'fragment-counter-mismatch',\n 'MFU continuation does not follow the authored fragment-counter countdown'\n )\n }\n\n if (active.kind === 'sample') {\n if (fragment.itemId !== undefined) {\n this.#reject('data-unit-identity-mismatch', 'sample continuation carries an Item_ID')\n }\n if (\n hasAnySampleIdentityField(fragment) &&\n (!hasSampleIdentity(fragment) ||\n fragment.movieFragmentSequenceNumber !== active.movieFragmentSequenceNumber ||\n fragment.sampleNumber !== active.sampleNumber ||\n fragment.sampleOffset !== active.nextOffset)\n ) {\n this.#reject('data-unit-identity-mismatch', 'sample continuation identity does not match')\n }\n const nextOffset = checkedSampleEnd(active.nextOffset, fragment.data.byteLength)\n if (nextOffset === null) this.#reject('offset-overflow', 'sample byte offset overflow')\n fragment.movieFragmentSequenceNumber = active.movieFragmentSequenceNumber\n fragment.sampleNumber = active.sampleNumber\n fragment.sampleOffset = active.nextOffset\n if (fi === 2) {\n active.nextOffset = nextOffset\n active.nextFragmentCounter = fragment.fragmentCounter - 1\n }\n } else {\n if (hasAnySampleIdentityField(fragment) || (fragment.itemId !== undefined && fragment.itemId !== active.itemId)) {\n this.#reject('data-unit-identity-mismatch', 'Item_ID continuation identity does not match')\n }\n fragment.itemId = active.itemId\n if (fi === 2) active.nextFragmentCounter = fragment.fragmentCounter - 1\n }\n\n return {\n bufferKey: active.bufferKey,\n first: false,\n terminal: fi === 3,\n }\n }\n\n #tryReassemble(bufferKey: string): ReassembledMFU | null {\n const fragments = this.#buffers.get(bufferKey)\n if (!fragments || fragments.length === 0) {\n return null\n }\n\n const sorted = [...fragments].sort((a, b) => this.#orderValue(a) - this.#orderValue(b))\n\n if (sorted[0]!.fragmentationIndicator !== 1 || sorted[sorted.length - 1]!.fragmentationIndicator !== 3) {\n return null\n }\n if (sorted.slice(1, -1).some(fragment => fragment.fragmentationIndicator !== 2)) {\n return null\n }\n\n // ISO fragment_counter is the number of succeeding payloads. After\n // normalization (`255 - counter`), a complete non-wrapping chain is strictly\n // consecutive. This also caps a data unit at 256 payloads.\n for (let i = 1; i < sorted.length; i++) {\n if (this.#orderValue(sorted[i]!) !== this.#orderValue(sorted[i - 1]!) + 1) {\n return null\n }\n }\n\n // When every fragment carries a timed-MFU byte offset, independently prove\n // byte adjacency within this data unit. Counter order remains authoritative;\n // conflicting offsets fail closed instead of changing the fragment order.\n // sorted[0]'s offset is this DU's authored base (moqcast #37: zero for a\n // whole-sample DU, nonzero for one subsample DU of a partitioned sample) --\n // never asserted against a literal zero.\n if (ordersByOffset(sorted)) {\n for (let i = 1; i < sorted.length; i++) {\n const previous = sorted[i - 1]!\n if (sorted[i]!.sampleOffset !== previous.sampleOffset! + previous.data.byteLength) {\n return null\n }\n }\n }\n\n fragments.splice(0, fragments.length, ...sorted)\n return this.#assemble(bufferKey, fragments)\n }\n\n #assemble(bufferKey: string, fragments: MFUFragment[]): ReassembledMFU {\n // Reassemble: concatenate all fragment data\n const totalSize = fragments.reduce((sum, f) => sum + f.data.length, 0)\n const data = new Uint8Array(totalSize)\n\n let offset = 0\n for (const fragment of fragments) {\n data.set(fragment.data, offset)\n offset += fragment.data.length\n }\n\n // Cleanup + mark the intrinsic identity as emitted to prevent a recovered\n // duplicate from starting another fragmented run.\n this.#clearTimeout(bufferKey)\n this.#buffers.delete(bufferKey)\n this.#markEmitted(bufferKey)\n this.stats.reassembledMFUs++\n\n return {\n mpuSequenceNumber: fragments[0]!.mpuSequenceNumber,\n packetSequenceNumber: fragments[0]!.packetSequenceNumber,\n ...assembledPacketSequences(fragments),\n data,\n timestamp: fragments[0]!.timestamp,\n fragmentCount: fragments.length,\n rapFlag: fragments[0]!.rapFlag,\n priority: fragments.find(f => f.priority !== undefined)?.priority,\n sampleNumber: fragments[0]!.sampleNumber,\n movieFragmentSequenceNumber: fragments[0]!.movieFragmentSequenceNumber,\n sampleOffset: fragments[0]!.sampleOffset,\n source: fragments[0]!.source,\n deliveryProvenance: deliveryProvenanceOf(fragments),\n catalogTrackName: fragments[0]!.catalogTrackName,\n }\n }\n\n #setTimeout(bufferKey: string, startedAt = Date.now()): void {\n const state: BufferTimeoutState = { startedAt }\n if (this.#timeoutMs !== null) {\n state.timeoutId = setTimeout(\n () => this.#onTimeout(bufferKey),\n Math.max(0, startedAt + this.#timeoutMs - Date.now())\n )\n }\n this.#timeouts.set(bufferKey, state)\n }\n\n #clearPendingTimeout(address: string): void {\n const pending = this.#pendingContinuations.get(address)\n if (pending?.timeoutId !== undefined) clearTimeout(pending.timeoutId)\n if (pending !== undefined) {\n delete pending.timeoutId\n }\n }\n\n /**\n * A queue whose run closed without completing is headless again and expires\n * from its own first arrival, exactly as it would had the head never come.\n *\n * An empty queue is not armed: it holds no data unit that could fail to\n * complete, so there is nothing for a deadline to expire. Every arming site\n * reaches this function, which takes that test through `parkedRunHoldsASlot`\n * rather than filtering on the open run alone, which is how this engine\n * reaches Rust's answer: its\n * `next_timeout_at` derives candidate deadlines from the queue's live\n * entries, so an empty queue yields none (BLO-32420).\n */\n #armHeadlessPendingTimeout(address: string): void {\n const pending = this.#pendingContinuations.get(address)\n if (pending === undefined || pending.timeoutId !== undefined || this.#timeoutMs === null) return\n if (!parkedRunHoldsASlot(this.#openRunAt(address) !== undefined, pending.fragments.size)) return\n pending.timeoutId = setTimeout(\n () => this.#onPendingTimeout(address),\n Math.max(0, pending.startedAt + this.#timeoutMs - Date.now())\n )\n }\n\n #deletePendingRun(address: string): void {\n this.#clearPendingTimeout(address)\n this.#pendingContinuations.delete(address)\n }\n\n #onPendingTimeout(address: string): void {\n const pending = this.#pendingContinuations.get(address)\n if (pending === undefined) return\n // The charging site takes the same slot rule as the arming sites, evaluated\n // live at expiry the way Rust's `cleanup_timeouts` evaluates it at sweep\n // time: a queue that holds no slot is retained rather than charged\n // `record_policy_timeout`. That makes \"an empty queue is never charged a\n // timeout\" a property of this method, not of arming order. The fired timer\n // is released so a later re-park can arm the queue again.\n if (!parkedRunHoldsASlot(this.#openRunAt(address) !== undefined, pending.fragments.size)) {\n this.#clearPendingTimeout(address)\n return\n }\n this.stats.timedOutMFUs++\n console.warn(\n `[MfuReassembler] timeout: run=${address} fragments=${pending.fragments.size} ` +\n '(continuations awaiting first fragment, discarded)'\n )\n this.#pendingContinuations.delete(address)\n }\n\n #clearTimeout(bufferKey: string): void {\n const state = this.#timeouts.get(bufferKey)\n if (state !== undefined) {\n if (state.timeoutId !== undefined) clearTimeout(state.timeoutId)\n this.#timeouts.delete(bufferKey)\n }\n }\n\n /** Callback for deferred assembly results (set by caller for async frame delivery) */\n onDeferredAssembly?: (result: ReassembledMFU) => void\n\n #markEmitted(bufferKey: string): void {\n if (this.#emitted.has(bufferKey)) return\n this.#emitted.add(bufferKey)\n this.#emittedOrder.push(bufferKey)\n while (this.#emittedOrder.length > this.#maxBuffered) {\n const oldest = this.#emittedOrder.shift()\n if (oldest !== undefined) this.#emitted.delete(oldest)\n }\n }\n\n #markCompleted(address: string): void {\n if (this.#completed.has(address)) return\n this.#completed.add(address)\n this.#completedOrder.push(address)\n const cap = this.#maxBuffered * COMPLETED_ADDRESS_WINDOW_MULTIPLE\n while (this.#completedOrder.length > cap) {\n const oldest = this.#completedOrder.shift()\n if (oldest !== undefined) this.#completed.delete(oldest)\n }\n }\n\n #orderValue(fragment: MFUFragment): number {\n return fragmentCounterOrder(fragment)\n }\n\n /** Close the run behind an expired buffer. Early arrivals parked under its\n * address stay, now headless, and expire from their own arrival. */\n #removeActiveDataUnitForBuffer(bufferKey: string): void {\n for (const [objectKey, active] of this.#activeDataUnits) {\n if (active.bufferKey === bufferKey) {\n this.#activeDataUnits.delete(objectKey)\n this.#armHeadlessPendingTimeout(active.address)\n }\n }\n }\n\n /** The one open run holding this address, with its object key. Admission\n * rejects a second head on an open address, so at most one run can match. */\n #openRunAt(address: string): readonly [string, ActiveDataUnit] | undefined {\n for (const entry of this.#activeDataUnits) {\n if (entry[1].address === address) return entry\n }\n return undefined\n }\n\n #onTimeout(bufferKey: string): void {\n const fragments = this.#buffers.get(bufferKey)\n if (!fragments || fragments.length === 0) {\n this.#buffers.delete(bufferKey)\n this.#timeouts.delete(bufferKey)\n this.#removeActiveDataUnitForBuffer(bufferKey)\n this.stats.timedOutMFUs++\n console.warn(`[MfuReassembler] timeout: key=${bufferKey} fragments=0 (no data buffered)`)\n return\n }\n\n // Discard partial MFUs \u2014 incomplete AVCC data crashes WebCodecs with\n // \"Unable to determine size of bitstream buffer\". FEC recovery handles\n // loss; partial assembly only produces garbage frames.\n this.stats.timedOutMFUs++\n console.warn(`[MfuReassembler] timeout: key=${bufferKey} fragments=${fragments.length} (partial, discarded)`)\n\n this.#buffers.delete(bufferKey)\n this.#timeouts.delete(bufferKey)\n this.#removeActiveDataUnitForBuffer(bufferKey)\n }\n\n /**\n * Flush all buffered fragments\n */\n flush(): void {\n for (const key of this.#buffers.keys()) {\n this.#clearTimeout(key)\n }\n this.#buffers.clear()\n this.#timeouts.clear()\n this.#activeDataUnits.clear()\n for (const address of this.#pendingContinuations.keys()) {\n this.#clearPendingTimeout(address)\n }\n this.#pendingContinuations.clear()\n this.#emitted.clear()\n this.#emittedOrder.length = 0\n this.#completed.clear()\n this.#completedOrder.length = 0\n }\n\n /** Drop one catalog stream's incomplete and dedup state. */\n resetStream(catalogTrackName: string): void {\n const belongsToStream = (key: string): boolean => {\n try {\n const decoded = JSON.parse(key)\n return Array.isArray(decoded) && decoded[0] === 'catalog' && decoded[1] === catalogTrackName\n } catch {\n return false\n }\n }\n const discarded = new Set<string>()\n for (const [key, fragments] of this.#buffers) {\n if (fragments.some(fragment => fragment.catalogTrackName === catalogTrackName)) {\n discarded.add(key)\n }\n }\n for (const [objectKey, active] of this.#activeDataUnits) {\n if (active.catalogTrackName === catalogTrackName) {\n discarded.add(active.bufferKey)\n this.#activeDataUnits.delete(objectKey)\n }\n }\n for (const [address, pending] of this.#pendingContinuations) {\n if (\n [...pending.fragments.values()].some(fragment => fragment.catalogTrackName === catalogTrackName) ||\n belongsToStream(address)\n )\n this.#deletePendingRun(address)\n }\n for (const key of this.#emitted) {\n if (belongsToStream(key)) discarded.add(key)\n }\n for (const key of discarded) {\n this.#clearTimeout(key)\n this.#buffers.delete(key)\n this.#emitted.delete(key)\n }\n this.#emittedOrder = this.#emittedOrder.filter(key => !discarded.has(key))\n for (const address of this.#completed) {\n if (belongsToStream(address)) this.#completed.delete(address)\n }\n this.#completedOrder = this.#completedOrder.filter(address => this.#completed.has(address))\n }\n\n /** Reset all state \u2014 flush buffers and zero stats */\n reset(): void {\n this.flush()\n this.stats.totalFragments = 0\n this.stats.reassembledMFUs = 0\n this.stats.rejectedFragments = 0\n this.stats.timedOutMFUs = 0\n this.stats.evictedMemoryLimit = 0\n this.stats.outOfOrderFragments = 0\n }\n\n /**\n * Get number of pending MFUs: engine buffers plus every headless parked data\n * unit that holds a slot.\n *\n * This is not one of the four charging sites, but it shares their second term,\n * so it takes the same rule \u2014 by delegating to `#unopenedParkedRunCount`, the\n * one site that applies it. Rust's public `buffered_count()` is\n * `inner.buffered_count() + unopened_parked_run_count()`, and that count is\n * `parked_run_holds_a_slot` \u2014 so filtering here on the open run alone would\n * report a phantom slot for an empty published queue in TypeScript and not in\n * Rust, out of a *host-observable* counter (`getStatus().bufferedMFUs`). Same\n * state, two numbers, decided by which engine the host loaded: the divergence\n * class this file exists to close, so the public counters have to agree too.\n *\n * Distinct from `#incompleteRunCount`, which sums `#activeDataUnits` rather\n * than `#buffers`. Those are different quantities by design and are not being\n * merged; only the parked term they share is required to agree, which is what\n * routing both through one method makes structural rather than conventional.\n */\n get pendingCount(): number {\n return this.#buffers.size + this.#unopenedParkedRunCount()\n }\n\n /**\n * Get current buffer status\n */\n getStatus(): {\n bufferedMFUs: number\n pendingFragments: number\n pendingSamples: PendingMfuDebug[]\n stats: ReassemblerStats\n } {\n let pendingFragments = 0\n for (const fragments of this.#buffers.values()) {\n pendingFragments += fragments.length\n }\n for (const pending of this.#pendingContinuations.values()) {\n pendingFragments += pending.fragments.size\n }\n\n return {\n bufferedMFUs: this.pendingCount,\n pendingFragments,\n pendingSamples: this.#pendingDebugSamples(),\n stats: { ...this.stats },\n }\n }\n\n #pendingDebugSamples(): PendingMfuDebug[] {\n const out: PendingMfuDebug[] = []\n for (const [key, fragments] of this.#buffers) {\n if (fragments.length === 0) continue\n const sorted = [...fragments].sort((a, b) => this.#orderValue(a) - this.#orderValue(b))\n const fiCounts = [0, 0, 0, 0]\n let minCounter = Infinity\n let maxCounter = -Infinity\n let minOffset = Infinity\n let maxOffset = -Infinity\n for (const fragment of sorted) {\n fiCounts[fragment.fragmentationIndicator] = (fiCounts[fragment.fragmentationIndicator] ?? 0) + 1\n minCounter = Math.min(minCounter, fragment.fragmentCounter)\n maxCounter = Math.max(maxCounter, fragment.fragmentCounter)\n if (typeof fragment.sampleOffset === 'number') {\n minOffset = Math.min(minOffset, fragment.sampleOffset)\n maxOffset = Math.max(maxOffset, fragment.sampleOffset)\n }\n }\n const point = (fragment: MFUFragment) => ({\n fi: fragment.fragmentationIndicator,\n counter: fragment.fragmentCounter,\n order: this.#orderValue(fragment),\n ...(typeof fragment.packetSequenceNumber === 'number' ? { seq: fragment.packetSequenceNumber } : {}),\n ...(typeof fragment.sampleOffset === 'number' ? { offset: fragment.sampleOffset } : {}),\n })\n const summary: PendingMfuDebug = {\n key,\n fragments: sorted.length,\n fi0: fiCounts[0] ?? 0,\n fi1: fiCounts[1] ?? 0,\n fi2: fiCounts[2] ?? 0,\n fi3: fiCounts[3] ?? 0,\n firstFi: sorted[0]!.fragmentationIndicator,\n lastFi: sorted[sorted.length - 1]!.fragmentationIndicator,\n firstOrder: this.#orderValue(sorted[0]!),\n lastOrder: this.#orderValue(sorted[sorted.length - 1]!),\n minCounter,\n maxCounter,\n movieFragmentSequenceNumber: sorted.find(f => f.movieFragmentSequenceNumber !== undefined)\n ?.movieFragmentSequenceNumber,\n sampleNumber: sorted.find(f => f.sampleNumber !== undefined)?.sampleNumber,\n mpuSequenceNumber: sorted[0]!.mpuSequenceNumber,\n sortedHead: sorted.slice(0, 6).map(point),\n sortedTail: sorted.slice(-6).map(point),\n }\n if (minOffset !== Infinity) summary.minOffset = minOffset\n if (maxOffset !== -Infinity) summary.maxOffset = maxOffset\n out.push(summary)\n if (out.length >= 12) break\n }\n return out\n }\n}\n\n/**\n * Create MFU fragment from MMTP packet for reassembly\n */\nexport function createFragment(mmtpPacket: Uint8Array): MFUFragment {\n const extraction = extractMFUPayload(mmtpPacket)\n if (!extraction) throw new Error('Unable to extract MFU payload from MMTP packet')\n const { header, mpu, data, mfuDuHeader, itemId } = extraction\n\n return {\n fragmentationIndicator: mpu.fragmentationIndicator,\n mpuSequenceNumber: mpu.mpuSequenceNumber,\n fragmentCounter: mpu.fragmentCounter,\n data,\n timestamp: header.timestamp,\n rapFlag: header.rapFlag,\n sampleNumber: mfuDuHeader?.sampleNumber,\n movieFragmentSequenceNumber: mfuDuHeader?.movieFragmentSequenceNumber,\n sampleOffset: mfuDuHeader?.offset,\n priority: mfuDuHeader?.priority,\n itemId: itemId ?? undefined,\n packetSequenceNumber: header.sequenceNumber,\n packetId: header.packetId,\n }\n}\n\n/**\n * Process MMTP packet with fragment reassembly\n */\nexport function processMMTPWithReassembly(mmtpPacket: Uint8Array, reassembler: MFUReassembler): ReassembledMFU | null {\n // Skip FEC repair packets\n if (isFecRepairPacket(mmtpPacket)) {\n return null\n }\n\n const fragment = createFragment(mmtpPacket)\n return reassembler.addFragment(fragment)\n}\n\n// =============================================================================\n// Inline Tests (vitest)\n// =============================================================================\n\nif (import.meta.vitest) {\n const { describe, test, expect, vi } = import.meta.vitest\n\n const expectRejection = (call: () => unknown, code: MfuReassemblyErrorCode): void => {\n let rejection: unknown\n try {\n call()\n } catch (error) {\n rejection = error\n }\n expect(rejection).toBeInstanceOf(MfuReassemblyError)\n expect((rejection as MfuReassemblyError).code).toBe(code)\n }\n\n const testPacketSequence = (mpu: number, counter: number, run = 0): number =>\n (mpu * 1000 + run - counter + 0x1_0000_0000) % 0x1_0000_0000\n\n describe('MFUReassembler', () => {\n test('should return complete MFU for FI=0', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const result = reassembler.addFragment({\n fragmentationIndicator: 0,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n itemId: 1,\n data: new Uint8Array([1, 2, 3, 4]),\n timestamp: 1000n,\n\n rapFlag: true,\n })\n expect(result).not.toBeNull()\n expect(result?.data).toEqual(new Uint8Array([1, 2, 3, 4]))\n expect(result?.fragmentCount).toBe(1)\n expect(result?.rapFlag).toBe(true)\n })\n\n test('rejects a complete MFU without a valid sample identity', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const complete = (identity: Partial<MFUFragment>): MFUFragment => ({\n fragmentationIndicator: 0,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([1]),\n timestamp: 1000n,\n rapFlag: true,\n ...identity,\n })\n expectRejection(() => reassembler.addFragment(complete({})), 'missing-data-unit-identity')\n expectRejection(\n () =>\n reassembler.addFragment(\n complete({\n movieFragmentSequenceNumber: 2,\n sampleNumber: 1,\n })\n ),\n 'missing-data-unit-identity'\n )\n })\n\n test('accepts a complete subsample MFU with a nonzero authored offset', () => {\n // moqcast #37: a subsample DU's offset is its authored base byte\n // position within the sample -- nonzero identifies one subsample DU\n // among siblings partitioning an oversized sample.\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const result = reassembler.addFragment({\n fragmentationIndicator: 0,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n movieFragmentSequenceNumber: 2,\n sampleNumber: 1,\n sampleOffset: 4,\n data: new Uint8Array([1]),\n timestamp: 1000n,\n rapFlag: true,\n })\n expect(result).not.toBeNull()\n expect(result?.sampleOffset).toBe(4)\n })\n\n test('accepts a fragmented subsample DU whose first fragment has a nonzero authored offset', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 2,\n fragmentCounter: 1,\n movieFragmentSequenceNumber: 2,\n sampleNumber: 1,\n sampleOffset: 4,\n packetSequenceNumber: testPacketSequence(2, 1),\n data: new Uint8Array([0xa1]),\n timestamp: 1000n,\n rapFlag: true,\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(1)\n expect(reassembler.getStatus().pendingFragments).toBe(1)\n\n const out = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 2,\n fragmentCounter: 0,\n data: new Uint8Array([0xa2]),\n timestamp: 1001n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(2, 0),\n })\n expect(out?.data).toEqual(new Uint8Array([0xa1, 0xa2]))\n expect(out?.sampleOffset).toBe(4)\n })\n\n // Rust twin: `a_mid_data_unit_sequence_gap_strands_the_data_unit` in\n // `mmt-core/src/reassembler/policy/mmt.rs`.\n //\n // One data unit's fragments occupy sequences 100, 102, 103 with an\n // unrelated complete unit at 101 -- an interleave *inside* the fragmented\n // unit rather than after it. ISO/IEC 23008-1 permits that, so this is the\n // boundary of the contiguous-source producer profile, not a bug within it.\n //\n // This test exists because no fixture in either corpus could previously\n // express a mid-data-unit sequence gap at all: the helpers derive\n // continuation sequences arithmetically from the head, so every fixture\n // was contiguous by construction and the profile's edge was untestable\n // (BLO-33550). It pins current behaviour rather than the guarantee we\n // would prefer -- change the association path and update it deliberately.\n test('a mid-data-unit packet sequence gap strands the data unit', () => {\n const reassembler = new MFUReassembler({ timeoutMs: null, maxBuffered: 10 })\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 7,\n fragmentCounter: 2,\n movieFragmentSequenceNumber: 9,\n sampleNumber: 17,\n sampleOffset: 0,\n packetSequenceNumber: 100,\n data: new Uint8Array([0xa1]),\n timestamp: 1100n,\n rapFlag: true,\n })\n ).toBeNull()\n\n // An unrelated complete unit of the same MPU consumes sequence 101,\n // *between* the fragmented unit's head and its next fragment.\n const interleaved = reassembler.addFragment({\n fragmentationIndicator: 0,\n mpuSequenceNumber: 7,\n fragmentCounter: 0,\n movieFragmentSequenceNumber: 9,\n sampleNumber: 18,\n sampleOffset: 0,\n packetSequenceNumber: 101,\n data: new Uint8Array([0xff]),\n timestamp: 1101n,\n rapFlag: true,\n })\n expect(interleaved?.data).toEqual(new Uint8Array([0xff]))\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 7,\n fragmentCounter: 1,\n packetSequenceNumber: 102,\n data: new Uint8Array([0xa2]),\n timestamp: 1102n,\n rapFlag: false,\n })\n ).toBeNull()\n\n const output = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 7,\n fragmentCounter: 0,\n packetSequenceNumber: 103,\n data: new Uint8Array([0xa3]),\n timestamp: 1103n,\n rapFlag: false,\n })\n\n // What we would want, and do not get: `output` carrying a1 a2 a3. The\n // head addresses 100+2=102; both continuations address 102+1 = 103+0 =\n // 103, so they are two data units and neither can complete.\n expect(output).toBeNull()\n\n // Fail-closed, not corrupt: only the interleaver emitted, and no\n // fragment was donated across data units.\n expect(reassembler.stats.reassembledMFUs).toBe(1)\n expect(reassembler.stats.rejectedFragments).toBe(0)\n\n // The loss is invisible at this scale -- since BLO-31513 the bound is\n // enforced by replacement, so at `maxBuffered` this same stranding is\n // charged only to `evictedMemoryLimit` and presents as intermittent\n // missing media rather than an error.\n expect(reassembler.stats.evictedMemoryLimit).toBe(0)\n\n // Two slots for one data unit -- the open run at 102 plus the headless\n // parked queue at 103 -- so sustained profile-violating interleave\n // consumes `maxBuffered` at twice the expected rate and evicts healthy\n // data units alongside the stranded one. Matches the Rust twin's\n // `buffered_count() == 2` / `pending_fragment_count() == 3`.\n expect(reassembler.getStatus().bufferedMFUs).toBe(2)\n expect(reassembler.getStatus().pendingFragments).toBe(3)\n })\n\n test('sibling subsample DUs of one sample reassemble independently by offset', () => {\n // moqcast #37: each subsample DU of a partitioned sample runs its own\n // FI chain and gets its own buffer, keyed in part by its authored base\n // offset -- distinct from a sibling DU sharing (movie, sample).\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n expect(\n reassembler.addFragment({\n mpuSequenceNumber: 3,\n movieFragmentSequenceNumber: 5,\n sampleNumber: 9,\n fragmentationIndicator: 1,\n fragmentCounter: 1,\n sampleOffset: 0,\n data: new Uint8Array([0xa1]),\n timestamp: 2000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(3, 1, 0),\n })\n ).toBeNull()\n // The sibling's head sits at its own packet sequence, so its address\n // (packetSequenceNumber + fragmentCounter) differs from the first's.\n // Both stay open at once and every headerless continuation names exactly\n // one of them by that address. A second head on an already-open address\n // is refused instead ('rejects an overlapping non-timed item without\n // replacing the active run').\n expect(\n reassembler.addFragment({\n mpuSequenceNumber: 3,\n movieFragmentSequenceNumber: 5,\n sampleNumber: 9,\n fragmentationIndicator: 1,\n fragmentCounter: 2,\n sampleOffset: 3,\n data: new Uint8Array([0xb1]),\n timestamp: 2001n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(3, 1, 1),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(2)\n\n const first = reassembler.addFragment({\n mpuSequenceNumber: 3,\n fragmentationIndicator: 3,\n fragmentCounter: 0,\n data: new Uint8Array([0xa2]),\n timestamp: 2000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(3, 0, 0),\n })\n expect(first?.data).toEqual(new Uint8Array([0xa1, 0xa2]))\n expect(first?.sampleOffset).toBe(0)\n\n expect(\n reassembler.addFragment({\n mpuSequenceNumber: 3,\n fragmentationIndicator: 2,\n fragmentCounter: 1,\n data: new Uint8Array([0xb2]),\n timestamp: 2001n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(3, 0, 1),\n })\n ).toBeNull()\n const second = reassembler.addFragment({\n mpuSequenceNumber: 3,\n fragmentationIndicator: 3,\n fragmentCounter: 0,\n data: new Uint8Array([0xb3]),\n timestamp: 2001n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(3, 0, 2),\n })\n expect(second?.data).toEqual(new Uint8Array([0xb1, 0xb2, 0xb3]))\n expect(second?.sampleOffset).toBe(3)\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('isolates colliding fragment coordinates by catalog track identity', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const fragment = (\n catalogTrackName: string,\n fragmentationIndicator: number,\n fragmentCounter: number,\n byte: number\n ): MFUFragment => ({\n itemId: 1,\n fragmentationIndicator,\n fragmentCounter,\n mpuSequenceNumber: 1,\n data: new Uint8Array([byte]),\n timestamp: 1000n,\n rapFlag: fragmentationIndicator === 1,\n catalogTrackName,\n packetSequenceNumber: testPacketSequence(1, fragmentCounter),\n })\n\n expect(reassembler.addFragment(fragment('video/720p', 1, 1, 0x71))).toBeNull()\n expect(reassembler.addFragment(fragment('video/540p', 1, 1, 0x51))).toBeNull()\n const high = reassembler.addFragment(fragment('video/720p', 3, 0, 0x72))\n const low = reassembler.addFragment(fragment('video/540p', 3, 0, 0x52))\n\n expect(high?.data).toEqual(new Uint8Array([0x71, 0x72]))\n expect(high?.catalogTrackName).toBe('video/720p')\n expect(low?.data).toEqual(new Uint8Array([0x51, 0x52]))\n expect(low?.catalogTrackName).toBe('video/540p')\n })\n\n test('rejects an overlapping non-timed item without replacing the active run', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const fragment = (\n itemId: number,\n fragmentationIndicator: number,\n fragmentCounter: number,\n byte: number\n ): MFUFragment => ({\n itemId,\n fragmentationIndicator,\n fragmentCounter,\n mpuSequenceNumber: 1,\n data: new Uint8Array([byte]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, fragmentCounter),\n })\n\n expect(reassembler.addFragment(fragment(10, 1, 1, 0x10))).toBeNull()\n expectRejection(() => reassembler.addFragment(fragment(20, 1, 1, 0x20)), 'overlapping-data-unit')\n expect(reassembler.pendingCount).toBe(1)\n expect(reassembler.addFragment(fragment(10, 3, 0, 0x11))?.data).toEqual(new Uint8Array([0x10, 0x11]))\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('associates continuations by authored state when packet timestamps change', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const fragment = (\n itemId: number | undefined,\n fragmentationIndicator: number,\n fragmentCounter: number,\n byte: number,\n timestamp: bigint\n ): MFUFragment => ({\n ...(itemId === undefined ? {} : { itemId }),\n fragmentationIndicator,\n fragmentCounter,\n mpuSequenceNumber: 1,\n data: new Uint8Array([byte]),\n timestamp,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, fragmentCounter),\n })\n\n expect(reassembler.addFragment(fragment(10, 1, 2, 0x10, 1000n))).toBeNull()\n expect(reassembler.addFragment(fragment(undefined, 2, 1, 0x11, 1001n))).toBeNull()\n const out = reassembler.addFragment(fragment(undefined, 3, 0, 0x12, 1002n))\n expect(out?.data).toEqual(new Uint8Array([0x10, 0x11, 0x12]))\n expect(out?.timestamp).toBe(1000n)\n })\n\n test('should buffer and reassemble 2 fragments', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n // First fragment (FI=1)\n const result1 = reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([1, 2]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n expect(result1).toBeNull()\n\n // Last fragment (FI=3)\n const result2 = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([3, 4]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n expect(result2).not.toBeNull()\n expect(result2?.data).toEqual(new Uint8Array([1, 2, 3, 4]))\n expect(result2?.fragmentCount).toBe(2)\n expect(result2?.rapFlag).toBe(true) // Should use first fragment's rapFlag\n })\n\n test('buffers a terminal continuation until its authored first fragment arrives', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n // Last fragment first (FI=3)\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([3, 4]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(1)\n\n // Then first fragment (FI=1)\n const completed = reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([1, 2]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n\n expect(completed?.data).toEqual(new Uint8Array([1, 2, 3, 4]))\n expect(completed?.rapFlag).toBe(true)\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('clock-free mode keeps an admitted MFU until completion', async () => {\n const reassembler = new MFUReassembler({ timeoutMs: null })\n\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([1, 2]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n\n await new Promise(resolve => setTimeout(resolve, 5))\n\n const result = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([3, 4]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n\n expect(result).not.toBeNull()\n expect(result?.data).toEqual(new Uint8Array([1, 2, 3, 4]))\n expect(reassembler.stats.timedOutMFUs).toBe(0)\n })\n\n test('should track pending count', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n expect(reassembler.pendingCount).toBe(0)\n\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([1]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n expect(reassembler.pendingCount).toBe(1)\n\n reassembler.flush()\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('should reassemble 3 fragments (FI=1, FI=2, FI=3)', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n // First fragment\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 10,\n fragmentCounter: 2,\n itemId: 1,\n data: new Uint8Array([0xaa, 0xbb]),\n timestamp: 5000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(10, 2),\n })\n ).toBeNull()\n\n // Middle fragment\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 10,\n fragmentCounter: 1,\n data: new Uint8Array([0xcc, 0xdd]),\n timestamp: 5000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(10, 1),\n })\n ).toBeNull()\n\n // Last fragment \u2014 triggers reassembly\n const result = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 10,\n fragmentCounter: 0,\n data: new Uint8Array([0xee, 0xff]),\n timestamp: 5000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(10, 0),\n })\n\n expect(result).not.toBeNull()\n expect(result!.data).toEqual(new Uint8Array([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]))\n expect(result!.fragmentCount).toBe(3)\n expect(result!.rapFlag).toBe(true) // from first fragment\n expect(result!.mpuSequenceNumber).toBe(10)\n })\n\n test('should handle interleaved MPU sequences', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n // MPU 1: first fragment\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0x01]),\n timestamp: 100n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n // MPU 2: complete (FI=0)\n const r2 = reassembler.addFragment({\n fragmentationIndicator: 0,\n mpuSequenceNumber: 2,\n fragmentCounter: 0,\n itemId: 2,\n data: new Uint8Array([0x02]),\n timestamp: 200n,\n rapFlag: false,\n })\n expect(r2).not.toBeNull()\n expect(r2!.mpuSequenceNumber).toBe(2)\n\n // MPU 1: last fragment\n const r1 = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([0x03]),\n timestamp: 100n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n expect(r1).not.toBeNull()\n expect(r1!.data).toEqual(new Uint8Array([0x01, 0x03]))\n })\n\n test('rejects overlapping fragmented MFUs sharing one stream and MPU', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const mpuSeq = 5\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: mpuSeq,\n fragmentCounter: 1,\n itemId: 10,\n data: new Uint8Array([0xa1]),\n timestamp: 100n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(mpuSeq, 1),\n })\n ).toBeNull()\n expectRejection(\n () =>\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: mpuSeq,\n fragmentCounter: 1,\n itemId: 20,\n data: new Uint8Array([0xb1]),\n timestamp: 200n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(mpuSeq, 1),\n }),\n 'overlapping-data-unit'\n )\n expect(reassembler.pendingCount).toBe(1)\n\n const a = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: mpuSeq,\n fragmentCounter: 0,\n data: new Uint8Array([0xa2]),\n timestamp: 101n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(mpuSeq, 0),\n })\n expect(a).not.toBeNull()\n expect(a!.data).toEqual(new Uint8Array([0xa1, 0xa2]))\n expect(a!.rapFlag).toBe(true) // from A's first fragment\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: mpuSeq,\n fragmentCounter: 1,\n itemId: 20,\n data: new Uint8Array([0xb1]),\n timestamp: 200n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(mpuSeq, 1, 10),\n })\n ).toBeNull()\n const b = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: mpuSeq,\n fragmentCounter: 0,\n data: new Uint8Array([0xb2]),\n timestamp: 201n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(mpuSeq, 0, 10),\n })\n expect(b).not.toBeNull()\n expect(b!.data).toEqual(new Uint8Array([0xb1, 0xb2]))\n })\n\n test('reassembles FFmpeg timed MFU fragments by sample number and byte offset', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const common = {\n mpuSequenceNumber: 0,\n movieFragmentSequenceNumber: 9,\n sampleNumber: 17,\n }\n\n expect(\n reassembler.addFragment({\n ...common,\n fragmentationIndicator: 1,\n fragmentCounter: 2,\n sampleOffset: 0,\n data: new Uint8Array([0xa1]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(0, 2),\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n ...common,\n fragmentationIndicator: 2,\n fragmentCounter: 1,\n sampleOffset: 1,\n data: new Uint8Array([0xb2]),\n timestamp: 1001n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(0, 1),\n })\n ).toBeNull()\n const out = reassembler.addFragment({\n ...common,\n fragmentationIndicator: 3,\n fragmentCounter: 0,\n sampleOffset: 2,\n data: new Uint8Array([0xc3]),\n timestamp: 1002n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(0, 0),\n })\n\n expect(out).not.toBeNull()\n expect(out!.data).toEqual(new Uint8Array([0xa1, 0xb2, 0xc3]))\n expect(out!.timestamp).toBe(1000n)\n expect(out!.sampleNumber).toBe(17)\n expect(out!.movieFragmentSequenceNumber).toBe(9)\n })\n\n test('buffers a counter gap without advancing the active data unit', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const common = {\n mpuSequenceNumber: 0,\n movieFragmentSequenceNumber: 9,\n sampleNumber: 17,\n timestamp: 1000n,\n rapFlag: true,\n }\n\n expect(\n reassembler.addFragment({\n ...common,\n fragmentationIndicator: 1,\n fragmentCounter: 2,\n sampleOffset: 0,\n data: new Uint8Array([0xa1, 0xa2]),\n packetSequenceNumber: testPacketSequence(0, 2),\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n ...common,\n fragmentationIndicator: 3,\n fragmentCounter: 0,\n sampleOffset: 4,\n data: new Uint8Array([0xc1, 0xc2]),\n packetSequenceNumber: testPacketSequence(0, 0),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(1)\n\n const out = reassembler.addFragment({\n ...common,\n fragmentationIndicator: 2,\n fragmentCounter: 1,\n sampleOffset: 2,\n data: new Uint8Array([0xb1, 0xb2]),\n packetSequenceNumber: testPacketSequence(0, 1),\n })\n expect(out?.data).toEqual(new Uint8Array([0xa1, 0xa2, 0xb1, 0xb2, 0xc1, 0xc2]))\n })\n\n test('reassembles first-only DU-header fragments across changing packet timestamps', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 44,\n fragmentCounter: 2,\n movieFragmentSequenceNumber: 9,\n sampleNumber: 17,\n sampleOffset: 0,\n data: new Uint8Array([0xa1]),\n timestamp: 12345n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(44, 2),\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 44,\n fragmentCounter: 1,\n data: new Uint8Array([0xb2]),\n timestamp: 12346n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(44, 1),\n })\n ).toBeNull()\n const out = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 44,\n fragmentCounter: 0,\n data: new Uint8Array([0xc3]),\n timestamp: 12347n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(44, 0),\n })\n\n expect(out).not.toBeNull()\n expect(out!.data).toEqual(new Uint8Array([0xa1, 0xb2, 0xc3]))\n expect(out!.timestamp).toBe(12345n)\n expect(out!.sampleNumber).toBe(17)\n expect(out!.movieFragmentSequenceNumber).toBe(9)\n })\n\n test('reassembles first-only DU-header fragments with an ISO countdown', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const timestamp = 12345n\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 44,\n fragmentCounter: 3,\n movieFragmentSequenceNumber: 9,\n sampleNumber: 17,\n sampleOffset: 0,\n data: new Uint8Array([0xa1]),\n timestamp,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(44, 3),\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 44,\n fragmentCounter: 2,\n data: new Uint8Array([0xb2]),\n timestamp,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(44, 2),\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 44,\n fragmentCounter: 1,\n data: new Uint8Array([0xc3]),\n timestamp,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(44, 1),\n })\n ).toBeNull()\n const out = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 44,\n fragmentCounter: 0,\n data: new Uint8Array([0xd4]),\n timestamp,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(44, 0),\n })\n\n expect(out).not.toBeNull()\n expect(out!.data).toEqual(new Uint8Array([0xa1, 0xb2, 0xc3, 0xd4]))\n expect(out!.sampleNumber).toBe(17)\n expect(out!.movieFragmentSequenceNumber).toBe(9)\n })\n\n test('rejects fragmented MFUs without an authored DU identity', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const timestamp = 22222n\n\n expectRejection(\n () =>\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 55,\n fragmentCounter: 2,\n data: new Uint8Array([0xa1]),\n timestamp,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(55, 2),\n }),\n 'missing-data-unit-identity'\n )\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('rejects fragmented MFUs without an authored packet sequence', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n expectRejection(\n () =>\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 55,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xa1]),\n timestamp: 22222n,\n rapFlag: true,\n }),\n 'missing-packet-sequence'\n )\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('rejects fragmented MFUs whose authored packet sequence is not u32', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n for (const packetSequenceNumber of [-1, 0x1_0000_0000, 1.5, Number.NaN]) {\n expectRejection(\n () =>\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 55,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xa1]),\n timestamp: 22222n,\n rapFlag: true,\n packetSequenceNumber,\n }),\n 'missing-packet-sequence'\n )\n }\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('a packet sequence gap inside a fragmented DU splits it and never completes', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const timestamp = 66666n\n\n // The head is addressed 1000 + 3 = 1003. One unrelated same-pid packet\n // consumed psn 1001, so every later fragment is addressed 1004: a\n // second data unit on the wire, parked headless and never merged with\n // the head by scope, counter, timestamp, or arrival order.\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 80,\n fragmentCounter: 3,\n movieFragmentSequenceNumber: 241,\n sampleNumber: 1,\n sampleOffset: 0,\n packetSequenceNumber: 1000,\n data: new Uint8Array([0xa0]),\n timestamp,\n rapFlag: true,\n })\n ).toBeNull()\n for (const [counter, psn] of [\n [2, 1002],\n [1, 1003],\n ] as const) {\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 80,\n fragmentCounter: counter,\n packetSequenceNumber: psn,\n data: new Uint8Array([0xa0 + counter]),\n timestamp,\n rapFlag: false,\n })\n ).toBeNull()\n }\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 80,\n fragmentCounter: 0,\n packetSequenceNumber: 1004,\n data: new Uint8Array([0xb1]),\n timestamp,\n rapFlag: false,\n })\n ).toBeNull()\n\n // The open head plus one headless parked data unit, one slot each.\n expect(reassembler.pendingCount).toBe(2)\n expect(reassembler.getStatus().pendingFragments).toBe(4)\n expect(reassembler.stats.reassembledMFUs).toBe(0)\n expect(reassembler.stats.rejectedFragments).toBe(0)\n })\n\n test('keeps adjacent MFUs with equal countdowns in one MPU separate', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const common = { mpuSequenceNumber: 80, packetId: 7, timestamp: 66666n }\n\n // Two adjacent samples of one MPU fragmented into the same countdown are\n // both open at once. Each continuation carries its unit's terminal\n // sequence (100 + 1 and 102 + 1), so each run completes with its own\n // terminal fragment.\n expect(\n reassembler.addFragment({\n ...common,\n fragmentationIndicator: 1,\n fragmentCounter: 1,\n packetSequenceNumber: 100,\n movieFragmentSequenceNumber: 241,\n sampleNumber: 1,\n sampleOffset: 0,\n data: new Uint8Array([0xa1]),\n rapFlag: true,\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n ...common,\n fragmentationIndicator: 1,\n fragmentCounter: 1,\n packetSequenceNumber: 102,\n movieFragmentSequenceNumber: 241,\n sampleNumber: 2,\n sampleOffset: 0,\n data: new Uint8Array([0xb1]),\n rapFlag: false,\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(2)\n\n const earlier = reassembler.addFragment({\n ...common,\n fragmentationIndicator: 3,\n fragmentCounter: 0,\n packetSequenceNumber: 101,\n data: new Uint8Array([0xa2]),\n rapFlag: false,\n })\n expect(earlier?.data).toEqual(new Uint8Array([0xa1, 0xa2]))\n expect(earlier?.packetSequenceNumbers).toEqual([100, 101])\n\n const later = reassembler.addFragment({\n ...common,\n fragmentationIndicator: 3,\n fragmentCounter: 0,\n packetSequenceNumber: 103,\n data: new Uint8Array([0xb2]),\n rapFlag: false,\n })\n expect(later?.data).toEqual(new Uint8Array([0xb1, 0xb2]))\n expect(later?.packetSequenceNumbers).toEqual([102, 103])\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('wraps the fragmented run coordinate at u32 packet sequence', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 81,\n fragmentCounter: 1,\n packetSequenceNumber: 0xffffffff,\n itemId: 1,\n data: new Uint8Array([0xa1]),\n timestamp: 66666n,\n rapFlag: true,\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 81,\n fragmentCounter: 0,\n packetSequenceNumber: 0,\n data: new Uint8Array([0xa2]),\n timestamp: 66667n,\n rapFlag: false,\n })?.data\n ).toEqual(new Uint8Array([0xa1, 0xa2]))\n })\n\n test('dedupes recovered copies by ISO fragment-counter position', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const timestamp = 44444n\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 77,\n fragmentCounter: 2,\n movieFragmentSequenceNumber: 12,\n sampleNumber: 1,\n sampleOffset: 0,\n packetSequenceNumber: 2000,\n data: new Uint8Array([0xa1]),\n timestamp,\n rapFlag: true,\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 77,\n fragmentCounter: 1,\n packetSequenceNumber: 2001,\n data: new Uint8Array([0xb2]),\n timestamp,\n rapFlag: false,\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 77,\n fragmentCounter: 0,\n packetSequenceNumber: 2002,\n data: new Uint8Array([0xc3]),\n timestamp,\n rapFlag: false,\n })\n ).not.toBeNull()\n\n reassembler.flush()\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 78,\n fragmentCounter: 2,\n movieFragmentSequenceNumber: 13,\n sampleNumber: 1,\n sampleOffset: 0,\n packetSequenceNumber: 3000,\n data: new Uint8Array([0xd4]),\n timestamp,\n rapFlag: true,\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 78,\n fragmentCounter: 1,\n packetSequenceNumber: 3001,\n data: new Uint8Array([0xe5]),\n timestamp,\n rapFlag: false,\n })\n ).toBeNull()\n expectRejection(\n () =>\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 78,\n fragmentCounter: 1,\n packetSequenceNumber: 3001,\n data: new Uint8Array([0xe5]),\n timestamp,\n rapFlag: false,\n }),\n 'fragment-counter-mismatch'\n )\n const out = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 78,\n fragmentCounter: 0,\n packetSequenceNumber: 3002,\n data: new Uint8Array([0xf6]),\n timestamp,\n rapFlag: false,\n })\n\n expect(out).not.toBeNull()\n expect(out!.data).toEqual(new Uint8Array([0xd4, 0xe5, 0xf6]))\n })\n\n test('reassembles middle and terminal continuations that arrive before first', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const timestamp = 54321n\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 45,\n fragmentCounter: 1,\n data: new Uint8Array([0xb2]),\n timestamp,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(45, 1),\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 45,\n fragmentCounter: 0,\n data: new Uint8Array([0xc3]),\n timestamp: timestamp + 1n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(45, 0),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(1)\n const out = reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 45,\n fragmentCounter: 2,\n movieFragmentSequenceNumber: 10,\n sampleNumber: 18,\n sampleOffset: 0,\n data: new Uint8Array([0xa1]),\n timestamp,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(45, 2),\n })\n\n expect(out).not.toBeNull()\n expect(out!.data).toEqual(new Uint8Array([0xa1, 0xb2, 0xc3]))\n expect(out!.sampleNumber).toBe(18)\n expect(out!.movieFragmentSequenceNumber).toBe(10)\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('rejects a duplicate continuation before first without replacing retained media', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const timestamp = 54322n\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 46,\n fragmentCounter: 0,\n data: new Uint8Array([0xc3]),\n timestamp,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(46, 0),\n })\n ).toBeNull()\n expectRejection(\n () =>\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 46,\n fragmentCounter: 0,\n data: new Uint8Array([0xff]),\n timestamp: timestamp + 1n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(46, 0),\n }),\n 'fragment-counter-mismatch'\n )\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 46,\n fragmentCounter: 1,\n data: new Uint8Array([0xb2]),\n timestamp: timestamp + 2n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(46, 1),\n })\n ).toBeNull()\n\n const out = reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 46,\n fragmentCounter: 2,\n movieFragmentSequenceNumber: 10,\n sampleNumber: 19,\n sampleOffset: 0,\n data: new Uint8Array([0xa1]),\n timestamp: timestamp + 3n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(46, 2),\n })\n\n expect(out).not.toBeNull()\n expect(out!.data).toEqual(new Uint8Array([0xa1, 0xb2, 0xc3]))\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('rejects invalid ISO fragmentation-indicator and counter combinations', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const invalidHeaders = [\n { fragmentationIndicator: 0, fragmentCounter: 1 },\n { fragmentationIndicator: 3, fragmentCounter: 1 },\n { fragmentationIndicator: 1, fragmentCounter: 0 },\n { fragmentationIndicator: 2, fragmentCounter: 0 },\n { fragmentationIndicator: -1, fragmentCounter: 1 },\n { fragmentationIndicator: 4, fragmentCounter: 1 },\n { fragmentationIndicator: 1.5, fragmentCounter: 1 },\n { fragmentationIndicator: 1, fragmentCounter: -1 },\n { fragmentationIndicator: 1, fragmentCounter: 256 },\n { fragmentationIndicator: 1, fragmentCounter: 1.5 },\n { fragmentationIndicator: 1, fragmentCounter: Number.NaN },\n ]\n\n for (const header of invalidHeaders) {\n expectRejection(\n () =>\n reassembler.addFragment({\n ...header,\n mpuSequenceNumber: 48,\n data: new Uint8Array([0xa1]),\n timestamp: 54324n,\n rapFlag: false,\n }),\n 'invalid-fragment-state'\n )\n }\n\n expect(reassembler.pendingCount).toBe(0)\n expect(reassembler.stats.reassembledMFUs).toBe(0)\n })\n\n test('reassembles the maximum ISO chain of 256 payloads', () => {\n const reassembler = new MFUReassembler({ timeoutMs: null })\n const common = {\n mpuSequenceNumber: 47,\n timestamp: 54323n,\n rapFlag: false,\n }\n\n expect(\n reassembler.addFragment({\n ...common,\n fragmentationIndicator: 1,\n fragmentCounter: 255,\n itemId: 1,\n data: new Uint8Array([0]),\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(47, 255),\n })\n ).toBeNull()\n for (let counter = 254; counter > 0; counter--) {\n expect(\n reassembler.addFragment({\n ...common,\n fragmentationIndicator: 2,\n fragmentCounter: counter,\n data: new Uint8Array([255 - counter]),\n packetSequenceNumber: testPacketSequence(47, counter),\n })\n ).toBeNull()\n }\n const out = reassembler.addFragment({\n ...common,\n fragmentationIndicator: 3,\n fragmentCounter: 0,\n data: new Uint8Array([255]),\n packetSequenceNumber: testPacketSequence(47, 0),\n })\n\n expect(out?.fragmentCount).toBe(256)\n expect(out?.data).toEqual(Uint8Array.from({ length: 256 }, (_, index) => index))\n })\n\n test('should not reassemble without last fragment (FI=3)', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 2,\n itemId: 1,\n data: new Uint8Array([1]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 2),\n })\n const result = reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n data: new Uint8Array([2]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n expect(result).toBeNull()\n expect(reassembler.pendingCount).toBe(1)\n })\n\n test('bounds continuations that arrive before their first fragments', () => {\n const reassembler = new MFUReassembler({\n timeoutMs: null,\n maxBuffered: 1,\n })\n // Data unit 1 parks headless at address 1000 and takes the only slot.\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([1]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(1)\n\n // Data unit 2 parks headless at address 2000. The bound holds by\n // replacement: data unit 1, admitted first, yields its slot.\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 2,\n fragmentCounter: 0,\n data: new Uint8Array([2]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(2, 0),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(1)\n expect(reassembler.getStatus().pendingFragments).toBe(1)\n expect(reassembler.stats.evictedMemoryLimit).toBe(1)\n\n // Data unit 2's head inherits the slot its queue already holds, so it\n // evicts nothing and completes from the parked continuation.\n const completed = reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 2,\n fragmentCounter: 1,\n itemId: 2,\n data: new Uint8Array([4]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(2, 1),\n })\n expect(completed?.data).toEqual(new Uint8Array([4, 2]))\n expect(reassembler.stats.evictedMemoryLimit).toBe(1)\n expect(reassembler.pendingCount).toBe(0)\n\n // Data unit 1's continuation was released with its slot, so its head\n // opens a fresh run and waits rather than completing from evicted state.\n const result = reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([3]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n expect(result).toBeNull()\n expect(reassembler.pendingCount).toBe(1)\n })\n\n test('expires a continuation that never receives its first fragment', () => {\n vi.useFakeTimers()\n try {\n const reassembler = new MFUReassembler({\n timeoutMs: 50,\n maxBuffered: 1,\n })\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([1]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n ).toBeNull()\n vi.advanceTimersByTime(50)\n expect(reassembler.pendingCount).toBe(0)\n expect(reassembler.stats.timedOutMFUs).toBe(1)\n } finally {\n vi.useRealTimers()\n }\n })\n\n test('retimes retained continuations when the timeout policy changes', () => {\n vi.useFakeTimers()\n try {\n const enabled = new MFUReassembler({ timeoutMs: null, maxBuffered: 1 })\n expect(\n enabled.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([1]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n ).toBeNull()\n enabled.timeoutMs = 10\n vi.advanceTimersByTime(10)\n expect(enabled.pendingCount).toBe(0)\n\n const disabled = new MFUReassembler({ timeoutMs: 10, maxBuffered: 1 })\n expect(\n disabled.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 2,\n fragmentCounter: 0,\n data: new Uint8Array([2]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(2, 0),\n })\n ).toBeNull()\n disabled.timeoutMs = null\n vi.advanceTimersByTime(10)\n expect(disabled.pendingCount).toBe(1)\n disabled.flush()\n } finally {\n vi.useRealTimers()\n }\n })\n\n test('bounds active data units by evicting the oldest at maxBuffered', () => {\n const reassembler = new MFUReassembler({\n timeoutMs: 1000,\n maxBuffered: 2,\n })\n\n // Two runs remain active; the third first fragment displaces the run\n // admitted longest ago instead of being refused.\n for (let i = 0; i < 2; i++) {\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: i,\n fragmentCounter: 1,\n itemId: i,\n data: new Uint8Array([i]),\n timestamp: BigInt(i * 100),\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(i, 1),\n })\n }\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 2,\n fragmentCounter: 1,\n itemId: 2,\n data: new Uint8Array([2]),\n timestamp: 200n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(2, 1),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(2)\n // Eviction is a replacement policy, not a deadline: it must not be\n // reported as a timeout, and it must be separately countable.\n expect(reassembler.stats.timedOutMFUs).toBe(0)\n expect(reassembler.stats.evictedMemoryLimit).toBe(1)\n\n // The evicted run's continuation no longer has a run to join; it parks\n // as a fresh headless data unit, which displaces the next-oldest run.\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 0,\n fragmentCounter: 0,\n data: new Uint8Array([9]),\n timestamp: 300n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(0, 0),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(2)\n expect(reassembler.stats.evictedMemoryLimit).toBe(2)\n expect(reassembler.stats.timedOutMFUs).toBe(0)\n })\n\n test('should report stats correctly', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n // One complete MFU\n reassembler.addFragment({\n fragmentationIndicator: 0,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n itemId: 1,\n data: new Uint8Array([1]),\n timestamp: 1000n,\n rapFlag: false,\n })\n\n const status = reassembler.getStatus()\n expect(status.bufferedMFUs).toBe(0)\n expect(status.stats.totalFragments).toBe(1)\n expect(status.stats.reassembledMFUs).toBe(1)\n })\n\n test('rejects an overlapping first without replacing the original fragment', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xaa]),\n timestamp: 1000n,\n rapFlag: true,\n source: 'multicast',\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n // Duplicate of fragmentCounter=1\n expectRejection(\n () =>\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n itemId: 2,\n data: new Uint8Array([0xbb]),\n timestamp: 1000n,\n rapFlag: true,\n source: 'moq',\n packetSequenceNumber: testPacketSequence(1, 1),\n }),\n 'overlapping-data-unit'\n )\n\n const result = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([0xcc]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n expect(result).not.toBeNull()\n expect(result!.data).toEqual(new Uint8Array([0xaa, 0xcc]))\n expect(result!.fragmentCount).toBe(2)\n expect(result!.source).toBe('multicast')\n })\n })\n\n test('should retain an assembly with a gap in fragment counters', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n // First fragment declares three succeeding payloads.\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 3,\n itemId: 1,\n data: new Uint8Array([0xaa]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(1, 3),\n })\n // Counter 1 arrives while counter 2 is missing.\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n data: new Uint8Array([0xcc]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n ).toBeNull()\n // Last fragment has the required zero counter.\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([0xdd]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(1)\n expect(reassembler.getStatus().pendingFragments).toBe(3)\n })\n\n test('accepts a reordered countdown and rejects a stale duplicate', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n // Counters 3, 1, 0 arrive (gap at 2).\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 3,\n itemId: 1,\n data: new Uint8Array([0xaa]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(1, 3),\n })\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n data: new Uint8Array([0xcc]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 1,\n fragmentCounter: 2,\n data: new Uint8Array([0xbb]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 2),\n })\n ).toBeNull()\n expectRejection(\n () =>\n reassembler.addFragment({\n fragmentationIndicator: 2,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n data: new Uint8Array([0xcc]),\n timestamp: 1001n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 1),\n }),\n 'fragment-counter-mismatch'\n )\n const result = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([0xdd]),\n timestamp: 1002n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n expect(result).not.toBeNull()\n expect(result!.data).toEqual(new Uint8Array([0xaa, 0xbb, 0xcc, 0xdd]))\n expect(result!.fragmentCount).toBe(4)\n })\n\n test('rejects an overlapping first without discarding queued continuations', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const fragment = (fragmentationIndicator: number, fragmentCounter: number, byte: number): MFUFragment => ({\n fragmentationIndicator,\n mpuSequenceNumber: 2,\n fragmentCounter,\n data: new Uint8Array([byte]),\n timestamp: 1000n,\n rapFlag: fragmentationIndicator === 1,\n packetSequenceNumber: testPacketSequence(2, fragmentCounter),\n ...(fragmentationIndicator === 1 ? { itemId: 9 } : {}),\n })\n\n expect(reassembler.addFragment(fragment(1, 3, 0xaa))).toBeNull()\n expect(reassembler.addFragment(fragment(2, 1, 0xcc))).toBeNull()\n expectRejection(() => reassembler.addFragment(fragment(1, 1, 0xee)), 'overlapping-data-unit')\n expect(reassembler.addFragment(fragment(2, 2, 0xbb))).toBeNull()\n expect(reassembler.addFragment(fragment(3, 0, 0xdd))?.data).toEqual(new Uint8Array([0xaa, 0xbb, 0xcc, 0xdd]))\n })\n\n test('rejects a duplicate emitted intrinsic identity', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 42,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xaa]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(42, 1),\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 42,\n fragmentCounter: 0,\n data: new Uint8Array([0xab]),\n timestamp: 1001n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(42, 0),\n })\n ).not.toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 42,\n fragmentCounter: 0,\n data: new Uint8Array([0xbc]),\n timestamp: 1002n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(42, 0, 10),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(1)\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 42,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xbb]),\n timestamp: 2000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(42, 1, 10),\n })\n ).toBeNull()\n // The queued FI=3 above is addressed to a data unit of MPU 42 whose own\n // FI=1 has not arrived. This duplicate head names that same address, but\n // its identity already emitted, so it is dropped before it can open a run\n // and touches nothing: the queue is retained and reclaimed by its own\n // arrival timeout.\n expect(reassembler.pendingCount).toBe(1)\n\n const result = reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 43,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xcc]),\n timestamp: 2000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(43, 1),\n })\n expect(result).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 43,\n fragmentCounter: 0,\n data: new Uint8Array([0xdd]),\n timestamp: 2001n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(43, 0),\n })?.data\n ).toEqual(new Uint8Array([0xcc, 0xdd]))\n })\n\n test('does not create active state for an emitted identity duplicate', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 50,\n fragmentCounter: 1,\n movieFragmentSequenceNumber: 10,\n sampleNumber: 18,\n sampleOffset: 0,\n data: new Uint8Array([0xa1]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(50, 1),\n })\n ).toBeNull()\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 50,\n fragmentCounter: 0,\n data: new Uint8Array([0xb2]),\n timestamp: 1000n,\n rapFlag: false,\n packetSequenceNumber: testPacketSequence(50, 0),\n })?.data\n ).toEqual(new Uint8Array([0xa1, 0xb2]))\n expect(reassembler.pendingCount).toBe(0)\n\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 50,\n fragmentCounter: 1,\n movieFragmentSequenceNumber: 10,\n sampleNumber: 18,\n sampleOffset: 0,\n data: new Uint8Array([0xa1]),\n timestamp: 2000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(50, 1, 10),\n })\n ).toBeNull()\n expect(reassembler.pendingCount).toBe(0)\n })\n\n test('flush should clear emitted set and allow re-assembly', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n\n // Use fragmented MFU (FI=1 start + FI=3 end) \u2014 only fragmented MFUs\n // go through the #emitted dedup set. FI=0 (complete) bypasses it.\n const first = reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 42,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xaa]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(42, 1),\n })\n expect(first).toBeNull() // first fragment, not yet complete\n const assembled = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 42,\n fragmentCounter: 0,\n data: new Uint8Array([0xbb]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(42, 0),\n })\n expect(assembled).not.toBeNull() // complete \u2014 mpuSeq 42 now in emitted set\n\n // Rejected before flush \u2014 same mpuSeq already emitted\n expect(\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 42,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xcc]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(42, 1, 10),\n })\n ).toBeNull()\n\n // After flush, same mpuSeq should work again\n reassembler.flush()\n const r2 = reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 42,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xdd]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(42, 1),\n })\n expect(r2).toBeNull() // first fragment of new assembly\n const r3 = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 42,\n fragmentCounter: 0,\n data: new Uint8Array([0xee]),\n timestamp: 1000n,\n rapFlag: true,\n packetSequenceNumber: testPacketSequence(42, 0),\n })\n expect(r3).not.toBeNull()\n expect(r3!.data).toEqual(new Uint8Array([0xdd, 0xee]))\n })\n\n describe('createFragment', () => {\n test('should create fragment from complete timed MMTP packet', () => {\n // Build: MMTP(12) + MPU(8, T=1 FI=0 payloadLength=25) + MFU_DU(14) + data(5)\n const packet = new Uint8Array(12 + 8 + 14 + 5)\n // MMTP header: byte0 = rap=1, byte1 = MPU type\n packet[0] = 0x01 // RAP flag set\n packet[1] = 0x00 // packet_type = MPU\n // packetId = 1\n const view = new DataView(packet.buffer)\n view.setUint16(2, 1, false)\n view.setUint32(4, 42, false) // timestamp\n view.setUint32(8, 100, false) // sequence number\n\n // MPU header at offset 12\n view.setUint16(12, 25, false) // 6 remaining MPU bytes + 14 DU + 5 media\n packet[14] = 0x28 // FT=2, T=1, FI=0, A=0\n packet[15] = 0 // fragment_counter\n view.setUint32(16, 999, false) // mpuSequenceNumber\n\n // MFU DU header (14 bytes) at offset 20.\n view.setUint32(20, 7, false) // movie_fragment_sequence_number\n view.setUint32(24, 1, false) // sample_number\n // Video data at offset 34\n packet[34] = 0xde\n packet[35] = 0xad\n packet[36] = 0xbe\n packet[37] = 0xef\n packet[38] = 0x01\n\n const frag = createFragment(packet)\n expect(frag.mpuSequenceNumber).toBe(999)\n expect(frag.fragmentationIndicator).toBe(0)\n expect(frag.rapFlag).toBe(true)\n expect(frag.movieFragmentSequenceNumber).toBe(7)\n expect(frag.sampleNumber).toBe(1)\n expect(frag.sampleOffset).toBe(0)\n expect(frag.data.length).toBe(5) // 5 bytes of video data\n })\n\n test('reads Item_ID from a non-timed MFU DU header', () => {\n const packet = new Uint8Array(12 + 8 + 4 + 2)\n const view = new DataView(packet.buffer)\n view.setUint16(2, 1, false)\n view.setUint32(4, 42, false)\n view.setUint32(8, 100, false)\n view.setUint16(12, 12, false) // 6 remaining MPU bytes + 4 DU + 2 media\n packet[14] = 0x20 // FT=2, T=0, FI=0\n view.setUint32(16, 999, false)\n view.setUint32(20, 0x01020304, false)\n packet.set([0xde, 0xad], 24)\n\n const fragment = createFragment(packet)\n expect(fragment.itemId).toBe(0x01020304)\n expect(fragment.data).toEqual(new Uint8Array([0xde, 0xad]))\n })\n })\n\n describe('processMMTPWithReassembly', () => {\n test('should skip FEC repair packets', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const packet = new Uint8Array(12 + 8 + 14 + 4)\n packet[0] = 0x10 // fecType=2 (repair)\n const result = processMMTPWithReassembly(packet, reassembler)\n expect(result).toBeNull()\n })\n\n test('should return reassembled MFU for complete packet', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const packet = new Uint8Array(12 + 8 + 14 + 4)\n packet[0] = 0x01 // RAP\n packet[1] = 0x00 // MPU\n const view = new DataView(packet.buffer)\n view.setUint16(2, 1, false) // packetId\n view.setUint32(4, 100, false) // timestamp\n view.setUint32(8, 1, false) // seq\n // MPU: payloadLength=24 (6 + 14 + 4), FT=2 T=1 FI=0 A=0\n view.setUint16(12, 24, false)\n packet[14] = 0x28\n view.setUint32(16, 42, false) // mpuSeq\n view.setUint32(20, 7, false) // movie_fragment_sequence_number\n view.setUint32(24, 1, false) // sample_number\n\n packet[34] = 0x00\n packet[35] = 0x00\n packet[36] = 0x00\n packet[37] = 0x01\n\n const result = processMMTPWithReassembly(packet, reassembler)\n expect(result).not.toBeNull()\n expect(result!.mpuSequenceNumber).toBe(42)\n expect(result!.rapFlag).toBe(true)\n })\n })\n\n describe('strict delivery provenance', () => {\n test('folds to multicast only when every fragment is explicitly multicast', () => {\n expect(deliveryProvenanceOf([{ source: 'multicast' }, { source: 'multicast' }])).toBe('multicast')\n })\n\n test('a single moq fragment makes the whole frame mixed', () => {\n expect(deliveryProvenanceOf([{ source: 'multicast' }, { source: 'moq' }])).toBe('mixed')\n // ...and it does not matter where in the run it lands.\n expect(deliveryProvenanceOf([{ source: 'moq' }, { source: 'multicast' }])).toBe('mixed')\n })\n\n test('an unattributed fragment degrades multicast to unknown, never to proven', () => {\n expect(deliveryProvenanceOf([{ source: 'multicast' }, {}])).toBe('unknown')\n expect(deliveryProvenanceOf([{}])).toBe('unknown')\n })\n\n test('mixed absorbs unknown \u2014 a moq byte is worse news than a missing label', () => {\n expect(deliveryProvenanceOf([{ source: 'moq' }, {}])).toBe('mixed')\n expect(deliveryProvenanceOf([{}, { source: 'moq' }])).toBe('mixed')\n })\n\n test('an empty fragment set proves nothing', () => {\n expect(deliveryProvenanceOf([])).toBe('unknown')\n })\n\n test('the fold never upgrades a verdict', () => {\n expect(foldDeliveryProvenance('mixed', 'multicast')).toBe('mixed')\n expect(foldDeliveryProvenance('unknown', 'multicast')).toBe('unknown')\n expect(foldDeliveryProvenance('unknown', 'moq')).toBe('mixed')\n })\n\n test('a multicast head with a non-multicast tail is NOT multicast-exclusive', () => {\n // This is the leak this field exists to close. `source` legitimately\n // reports 'multicast' \u2014 head-fragment-wins is the routing contract \u2014\n // but the frame contains bytes that never crossed a multicast socket,\n // so it must never be counted as physical-delivery evidence.\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 1,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xaa]),\n timestamp: 1000n,\n rapFlag: true,\n source: 'multicast',\n packetSequenceNumber: testPacketSequence(1, 1),\n })\n const result = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 1,\n fragmentCounter: 0,\n data: new Uint8Array([0xcc]),\n timestamp: 1000n,\n rapFlag: false,\n source: 'moq',\n packetSequenceNumber: testPacketSequence(1, 0),\n })\n expect(result).not.toBeNull()\n expect(result!.fragmentCount).toBe(2)\n expect(result!.source).toBe('multicast')\n expect(result!.deliveryProvenance).toBe('mixed')\n })\n\n test('an all-multicast fragmented frame is multicast-exclusive', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n reassembler.addFragment({\n fragmentationIndicator: 1,\n mpuSequenceNumber: 2,\n fragmentCounter: 1,\n itemId: 1,\n data: new Uint8Array([0xaa]),\n timestamp: 2000n,\n rapFlag: true,\n source: 'multicast',\n packetSequenceNumber: testPacketSequence(2, 1),\n })\n const result = reassembler.addFragment({\n fragmentationIndicator: 3,\n mpuSequenceNumber: 2,\n fragmentCounter: 0,\n data: new Uint8Array([0xcc]),\n timestamp: 2000n,\n rapFlag: false,\n source: 'multicast',\n packetSequenceNumber: testPacketSequence(2, 0),\n })\n expect(result).not.toBeNull()\n expect(result!.deliveryProvenance).toBe('multicast')\n })\n\n test('an unlabelled complete frame proves nothing', () => {\n const reassembler = new MFUReassembler({ timeoutMs: 1000 })\n const result = reassembler.addFragment({\n fragmentationIndicator: 0,\n mpuSequenceNumber: 3,\n fragmentCounter: 0,\n itemId: 1,\n data: new Uint8Array([1]),\n timestamp: 3000n,\n rapFlag: true,\n })\n expect(result).not.toBeNull()\n expect(result!.deliveryProvenance).toBe('unknown')\n })\n })\n}\n", "import type {\n MFUFragment,\n MfuReassemblerBackend,\n PendingMfuDebug,\n ReassembledMFU,\n ReassemblerStats,\n} from './mfu-reassembler.js'\nimport {\n isMfuReassemblyErrorCode,\n MfuReassemblyError,\n} from './mfu-reassembler.js'\n\nexport interface MmtWasmFragment {\n with_sample_identity(movieFragmentSequence: number, sampleNumber: number, offset: number): void\n set_item_id(itemId: number): void\n set_packet_delivery?(packetSequenceNumber: number, recovered: boolean): void\n set_priority(priority: number): void\n set_attribution(attribution: number): void\n set_stream_identity(streamIdentity: number): void\n}\n\nexport interface MmtWasmReassembledMfu {\n readonly mpu_sequence_number: number\n readonly timestamp: number\n readonly fragment_count: number\n readonly rap_flag: boolean\n readonly priority?: number\n readonly sample_number?: number\n readonly movie_fragment_sequence_number?: number\n readonly packet_sequence_numbers?: Uint32Array | number[]\n readonly repaired_packet_sequence_numbers?: Uint32Array | number[]\n readonly attribution?: number\n readonly stream_identity?: number\n readonly data: Uint8Array\n free(): void\n}\n\nexport interface MmtWasmStats {\n readonly total_fragments: bigint | number\n readonly reassembled_mfus: bigint | number\n readonly timed_out_mfus: bigint | number\n readonly evicted_memory_limit: bigint | number\n /**\n * Incomplete data units released by the MMT policy's clock-free replacement\n * at `maxBuffered` (BLO-31513). Distinct from `evicted_memory_limit`, which\n * counts the generic engine's own memory-limit eviction.\n *\n * Optional: `bindings` are host-injected and `mmt-wasm` publishes\n * independently of this package, so a host can load an artifact predating\n * this field. Read it through `?? 0` \u2014 summing `undefined` would silently\n * poison `evictedMemoryLimit` with `NaN` rather than degrading.\n */\n readonly evicted_data_units?: bigint | number\n readonly out_of_order_fragments: bigint | number\n readonly duplicate_fragments: bigint | number\n free?: () => void\n}\n\nexport interface MmtWasmReassembler {\n add_fragment(now: number, fragment: MmtWasmFragment): MmtWasmReassembledMfu | undefined\n cleanup_timeouts(now: number): void\n set_timeout(timeoutMs: number): void\n set_clock_free(): void\n next_timeout_at(): number | undefined\n flush(): void\n reset_stream(streamIdentity: number): void\n reset(): void\n stats(): MmtWasmStats\n readonly buffered_count: number\n readonly pending_fragment_count: number\n}\n\nexport interface MmtWasmBindings {\n MfuFragmentJs: new (\n mpuSequenceNumber: number,\n fragmentationIndicator: number,\n fragmentCounter: number,\n data: Uint8Array,\n timestamp: number,\n rapFlag: boolean,\n ) => MmtWasmFragment\n MmtReassembler: {\n new (timeoutMs: number, maxBuffered: number): MmtWasmReassembler\n clock_free(maxBuffered: number): MmtWasmReassembler\n }\n}\n\nexport interface WasmMfuReassemblerOptions {\n bindings: MmtWasmBindings\n timeoutMs: number | null\n maxBuffered?: number\n now?: () => number\n}\n\nfunction asNumber(value: bigint | number): number {\n return typeof value === 'bigint' ? Number(value) : value\n}\n\nfunction timestampU32(timestamp: bigint): number {\n return Number(BigInt.asUintN(32, timestamp))\n}\n\n/** Thin host adapter over mmt-core's Rust/WASM IFC reassembler. */\nexport class WasmMfuReassembler implements MfuReassemblerBackend {\n readonly #bindings: MmtWasmBindings\n readonly #core: MmtWasmReassembler\n readonly #now: () => number\n #timeoutMs: number | null\n #cleanupTimer: ReturnType<typeof setTimeout> | null = null\n #nextStreamIdentity = 1\n #streamIdentityByKey = new Map<string, number>()\n #streamNameByIdentity = new Map<number, string>()\n #coreTotalFragments = 0\n #rejectedBeforeCore = 0\n #rejectedFragments = 0\n\n readonly stats: ReassemblerStats = {\n totalFragments: 0,\n reassembledMFUs: 0,\n rejectedFragments: 0,\n timedOutMFUs: 0,\n evictedMemoryLimit: 0,\n outOfOrderFragments: 0,\n }\n\n onDeferredAssembly?: (result: ReassembledMFU) => void\n\n constructor(options: WasmMfuReassemblerOptions) {\n const maxBuffered = options.maxBuffered ?? 100\n if (!Number.isInteger(maxBuffered) || maxBuffered <= 0) {\n throw new Error('[WasmMfuReassembler] maxBuffered must be a positive integer')\n }\n this.#bindings = options.bindings\n this.#timeoutMs = options.timeoutMs\n this.#now = options.now ?? (() => globalThis.performance?.now() ?? Date.now())\n this.#core =\n options.timeoutMs === null\n ? options.bindings.MmtReassembler.clock_free(maxBuffered)\n : new options.bindings.MmtReassembler(options.timeoutMs, maxBuffered)\n }\n\n get timeoutMs(): number | null {\n return this.#timeoutMs\n }\n\n set timeoutMs(timeoutMs: number | null) {\n if (timeoutMs !== null && (!Number.isFinite(timeoutMs) || timeoutMs <= 0)) {\n throw new Error('[WasmMfuReassembler] timeoutMs must be a finite number > 0')\n }\n this.#timeoutMs = timeoutMs\n if (timeoutMs === null) this.#core.set_clock_free()\n else this.#core.set_timeout(Math.ceil(timeoutMs))\n this.#scheduleCleanup()\n }\n\n addFragment(fragment: MFUFragment): ReassembledMFU | null {\n const wasmFragment = new this.#bindings.MfuFragmentJs(\n fragment.mpuSequenceNumber,\n fragment.fragmentationIndicator,\n fragment.fragmentCounter,\n fragment.data,\n timestampU32(fragment.timestamp),\n fragment.rapFlag,\n )\n if (\n fragment.movieFragmentSequenceNumber !== undefined &&\n fragment.sampleNumber !== undefined &&\n fragment.sampleNumber > 0 &&\n fragment.sampleOffset !== undefined\n ) {\n wasmFragment.with_sample_identity(\n fragment.movieFragmentSequenceNumber,\n fragment.sampleNumber,\n fragment.sampleOffset,\n )\n }\n if (fragment.itemId !== undefined) wasmFragment.set_item_id(fragment.itemId)\n if (fragment.packetSequenceNumber !== undefined) {\n wasmFragment.set_packet_delivery?.(\n fragment.packetSequenceNumber,\n fragment.recovered === true,\n )\n }\n if (fragment.priority !== undefined) wasmFragment.set_priority(fragment.priority)\n if (fragment.source !== undefined) {\n wasmFragment.set_attribution(fragment.source === 'multicast' ? 1 : 2)\n }\n const streamIdentity = this.#streamIdentityFor(fragment)\n if (streamIdentity !== undefined) {\n wasmFragment.set_stream_identity(streamIdentity)\n }\n\n let output: MmtWasmReassembledMfu | undefined\n const coreTotalBefore = this.#coreTotalFragments\n try {\n output = this.#core.add_fragment(this.#now(), wasmFragment)\n } catch (error) {\n const rejection = this.#asReassemblyError(error)\n if (rejection === null) throw error\n this.#refreshStats()\n if (this.#coreTotalFragments === coreTotalBefore) this.#rejectedBeforeCore++\n this.#rejectedFragments++\n this.stats.totalFragments = this.#coreTotalFragments + this.#rejectedBeforeCore\n this.stats.rejectedFragments = this.#rejectedFragments\n this.#scheduleCleanup()\n throw rejection\n }\n this.#refreshStats()\n this.#scheduleCleanup()\n if (!output) return null\n\n try {\n const catalogTrackName = output.stream_identity === undefined\n ? undefined\n : this.#streamNameByIdentity.get(output.stream_identity)\n const packetSequenceNumbers = output.packet_sequence_numbers === undefined\n ? undefined\n : Array.from(output.packet_sequence_numbers)\n const repairedPacketSequenceNumbers =\n output.repaired_packet_sequence_numbers === undefined\n ? undefined\n : Array.from(output.repaired_packet_sequence_numbers)\n return {\n mpuSequenceNumber: output.mpu_sequence_number,\n ...(packetSequenceNumbers?.[0] === undefined\n ? {}\n : { packetSequenceNumber: packetSequenceNumbers[0] }),\n ...(packetSequenceNumbers === undefined ? {} : { packetSequenceNumbers }),\n ...(repairedPacketSequenceNumbers === undefined\n ? {}\n : { repairedPacketSequenceNumbers }),\n data: output.data,\n timestamp: BigInt(output.timestamp),\n fragmentCount: output.fragment_count,\n rapFlag: output.rap_flag,\n ...(output.priority === undefined ? {} : { priority: output.priority }),\n ...(output.sample_number === undefined ? {} : { sampleNumber: output.sample_number }),\n ...(output.movie_fragment_sequence_number === undefined\n ? {}\n : {\n movieFragmentSequenceNumber: output.movie_fragment_sequence_number,\n }),\n ...(output.attribution === 1\n ? { source: 'multicast' as const }\n : output.attribution === 2\n ? { source: 'moq' as const }\n : {}),\n // The Rust core folds `attribution` over every contributing fragment\n // and reports None unless they were unanimous, so a tag here is\n // already whole-frame evidence. A missing tag means \"not unanimous or\n // never recorded\" \u2014 both unproven, never multicast.\n deliveryProvenance:\n output.attribution === 1\n ? 'multicast'\n : output.attribution === 2\n ? 'mixed'\n : 'unknown',\n ...(catalogTrackName === undefined ? {} : { catalogTrackName }),\n }\n } finally {\n output.free()\n }\n }\n\n flush(): void {\n this.#clearCleanupTimer()\n this.#core.flush()\n }\n\n resetStream(catalogTrackName: string): void {\n const streamIdentity = this.#streamIdentityByKey.get(`catalog:${catalogTrackName}`)\n if (streamIdentity === undefined) return\n this.#clearCleanupTimer()\n this.#core.reset_stream(streamIdentity)\n this.#refreshStats()\n this.#scheduleCleanup()\n }\n\n reset(): void {\n this.#clearCleanupTimer()\n this.#core.reset()\n this.#nextStreamIdentity = 1\n this.#streamIdentityByKey.clear()\n this.#streamNameByIdentity.clear()\n this.#coreTotalFragments = 0\n this.#rejectedBeforeCore = 0\n this.#rejectedFragments = 0\n this.#refreshStats()\n }\n\n get pendingCount(): number {\n return this.#core.buffered_count\n }\n\n getStatus(): {\n bufferedMFUs: number\n pendingFragments: number\n pendingSamples: PendingMfuDebug[]\n stats: ReassemblerStats\n } {\n this.#refreshStats()\n return {\n bufferedMFUs: this.#core.buffered_count,\n pendingFragments: this.#core.pending_fragment_count,\n // Detailed fragment coordinates remain internal to the Rust core. Counts\n // and health statistics stay available to MmtpRouter diagnostics.\n pendingSamples: [],\n stats: { ...this.stats },\n }\n }\n\n #refreshStats(): void {\n const stats = this.#core.stats()\n this.#coreTotalFragments = asNumber(stats.total_fragments)\n this.stats.totalFragments = this.#coreTotalFragments + this.#rejectedBeforeCore\n this.stats.reassembledMFUs = asNumber(stats.reassembled_mfus)\n this.stats.rejectedFragments = this.#rejectedFragments\n this.stats.timedOutMFUs = asNumber(stats.timed_out_mfus)\n // `evictedMemoryLimit` is documented as every incomplete MFU discarded to\n // enforce the memory bound, and the pure-TypeScript engine counts its\n // clock-free evictions there. The Rust core splits the same concept across\n // two counters, so both are summed here \u2014 otherwise the identical stat\n // would mean different things depending on which engine a host loaded.\n this.stats.evictedMemoryLimit =\n asNumber(stats.evicted_memory_limit) + asNumber(stats.evicted_data_units ?? 0)\n this.stats.outOfOrderFragments = asNumber(stats.out_of_order_fragments)\n stats.free?.()\n }\n\n #streamIdentityFor(fragment: MFUFragment): number | undefined {\n const key = fragment.catalogTrackName !== undefined\n ? `catalog:${fragment.catalogTrackName}`\n : fragment.packetId !== undefined\n ? `packet:${fragment.packetId}`\n : undefined\n if (key === undefined) return undefined\n const existing = this.#streamIdentityByKey.get(key)\n if (existing !== undefined) return existing\n const identity = this.#nextStreamIdentity++\n this.#streamIdentityByKey.set(key, identity)\n if (fragment.catalogTrackName !== undefined) {\n this.#streamNameByIdentity.set(identity, fragment.catalogTrackName)\n }\n return identity\n }\n\n #asReassemblyError(error: unknown): MfuReassemblyError | null {\n if ((typeof error !== 'object' && typeof error !== 'function') || error === null) return null\n const code = Reflect.get(error, 'code')\n if (!isMfuReassemblyErrorCode(code)) return null\n const message = Reflect.get(error, 'message')\n return new MfuReassemblyError(\n code,\n typeof message === 'string' ? message : `MFU reassembly rejected: ${code}`,\n )\n }\n\n #scheduleCleanup(): void {\n this.#clearCleanupTimer()\n if (this.#timeoutMs === null) return\n const next = this.#core.next_timeout_at()\n if (next === undefined) return\n const delay = Math.max(0, Math.ceil(next - this.#now()))\n this.#cleanupTimer = setTimeout(() => {\n this.#cleanupTimer = null\n this.#core.cleanup_timeouts(this.#now())\n this.#refreshStats()\n this.#scheduleCleanup()\n }, delay)\n }\n\n #clearCleanupTimer(): void {\n if (this.#cleanupTimer === null) return\n clearTimeout(this.#cleanupTimer)\n this.#cleanupTimer = null\n }\n}\n\nexport function resolveGlobalMmtWasmBindings(): MmtWasmBindings {\n const globals = globalThis as typeof globalThis & {\n __blockcastMmtReassemblerWasmBindgen?: MmtWasmBindings\n __MmtWasmBindings?: MmtWasmBindings\n }\n const bindings = globals.__MmtWasmBindings ?? globals.__blockcastMmtReassemblerWasmBindgen\n if (!bindings?.MmtReassembler || !bindings.MfuFragmentJs) {\n throw new Error('[WasmMfuReassembler] initialized mmt-wasm bindings are unavailable')\n }\n return bindings\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["MPU_HEADER_SIZE", "MPU_LENGTH_FIELD_SIZE", "MPU_HEADER_REMAINDER_SIZE", "MPU_HEADER_SIZE"]
|
|
7
|
+
}
|