@blockcast/mmt-container 0.1.0 → 0.3.0-main.288475b64b3e

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.
Files changed (800) hide show
  1. package/README.md +538 -0
  2. package/dist/.build-stamp +0 -0
  3. package/dist/__tests__/test-utils.d.ts +71 -0
  4. package/dist/__tests__/test-utils.d.ts.map +1 -0
  5. package/dist/align.d.ts +152 -0
  6. package/dist/align.d.ts.map +1 -0
  7. package/dist/align.js +88 -0
  8. package/dist/align.js.map +7 -0
  9. package/dist/audio-init-parser.d.ts +66 -0
  10. package/dist/audio-init-parser.d.ts.map +1 -0
  11. package/dist/audio-init-parser.js +118 -0
  12. package/dist/audio-init-parser.js.map +7 -0
  13. package/dist/audio-ring-buffer.d.ts +50 -0
  14. package/dist/audio-ring-buffer.d.ts.map +1 -0
  15. package/dist/audio-ring-buffer.js +80 -0
  16. package/dist/audio-ring-buffer.js.map +7 -0
  17. package/dist/authored-timing.d.ts +32 -0
  18. package/dist/authored-timing.d.ts.map +1 -0
  19. package/dist/authored-timing.js +32 -0
  20. package/dist/authored-timing.js.map +7 -0
  21. package/dist/block-policy.d.ts +314 -0
  22. package/dist/block-policy.d.ts.map +1 -0
  23. package/dist/block-policy.js +466 -0
  24. package/dist/block-policy.js.map +7 -0
  25. package/dist/browser-runtime.js +26457 -0
  26. package/dist/browser-runtime.js.map +7 -0
  27. package/dist/carp-catalog.d.ts +445 -0
  28. package/dist/carp-catalog.d.ts.map +1 -0
  29. package/dist/carp-catalog.js +998 -0
  30. package/dist/carp-catalog.js.map +7 -0
  31. package/dist/catalog-fec-params.d.ts +315 -0
  32. package/dist/catalog-fec-params.d.ts.map +1 -0
  33. package/dist/catalog-fec-params.js +730 -0
  34. package/dist/catalog-fec-params.js.map +7 -0
  35. package/dist/catalog-network-source.d.ts +42 -0
  36. package/dist/catalog-network-source.d.ts.map +1 -0
  37. package/dist/catalog-network-source.js +46 -0
  38. package/dist/catalog-network-source.js.map +7 -0
  39. package/dist/catalog-validator.d.ts +18 -0
  40. package/dist/catalog-validator.d.ts.map +1 -0
  41. package/dist/catalog-validator.js +7747 -0
  42. package/dist/catalog-validator.js.map +7 -0
  43. package/dist/cmaf-block-assembler.d.ts +285 -0
  44. package/dist/cmaf-block-assembler.d.ts.map +1 -0
  45. package/dist/cmaf-block-assembler.js +1966 -0
  46. package/dist/cmaf-block-assembler.js.map +7 -0
  47. package/dist/cmaf-group-batcher.d.ts +38 -0
  48. package/dist/cmaf-group-batcher.d.ts.map +1 -0
  49. package/dist/cmaf-group-batcher.js +176 -0
  50. package/dist/cmaf-group-batcher.js.map +7 -0
  51. package/dist/cmcd-producer.d.ts +104 -0
  52. package/dist/cmcd-producer.d.ts.map +1 -0
  53. package/dist/cmcd-producer.js +183 -0
  54. package/dist/cmcd-producer.js.map +7 -0
  55. package/dist/codec-utils.d.ts +384 -0
  56. package/dist/codec-utils.d.ts.map +1 -0
  57. package/dist/codec-utils.js +1546 -0
  58. package/dist/codec-utils.js.map +7 -0
  59. package/dist/conceal-timeline.d.ts +146 -0
  60. package/dist/conceal-timeline.d.ts.map +1 -0
  61. package/dist/conceal-timeline.js +137 -0
  62. package/dist/conceal-timeline.js.map +7 -0
  63. package/dist/delivery-gap.d.ts +75 -0
  64. package/dist/delivery-gap.d.ts.map +1 -0
  65. package/dist/delivery-gap.js +88 -0
  66. package/dist/delivery-gap.js.map +7 -0
  67. package/dist/duplicate-ledger.d.ts +197 -0
  68. package/dist/duplicate-ledger.d.ts.map +1 -0
  69. package/dist/duplicate-ledger.js +226 -0
  70. package/dist/duplicate-ledger.js.map +7 -0
  71. package/dist/fec-codec.d.ts +32 -0
  72. package/dist/fec-codec.d.ts.map +1 -0
  73. package/dist/fec-codec.js +47 -0
  74. package/dist/fec-codec.js.map +7 -0
  75. package/dist/fec-manager.d.ts +589 -0
  76. package/dist/fec-manager.d.ts.map +1 -0
  77. package/dist/fec-manager.js +3840 -0
  78. package/dist/fec-manager.js.map +7 -0
  79. package/dist/fec-starvation-detector.d.ts +59 -0
  80. package/dist/fec-starvation-detector.d.ts.map +1 -0
  81. package/dist/fec-starvation-detector.js +60 -0
  82. package/dist/fec-starvation-detector.js.map +7 -0
  83. package/dist/fec-state.d.ts +140 -0
  84. package/dist/fec-state.d.ts.map +1 -0
  85. package/dist/fec-state.js +86 -0
  86. package/dist/fec-state.js.map +7 -0
  87. package/dist/fec-worker-setup.d.ts +100 -0
  88. package/dist/fec-worker-setup.d.ts.map +1 -0
  89. package/dist/fec-worker-setup.js +671 -0
  90. package/dist/fec-worker-setup.js.map +7 -0
  91. package/dist/fmp4-completion.d.ts +30 -0
  92. package/dist/fmp4-completion.d.ts.map +1 -0
  93. package/dist/fmp4-completion.js +21 -0
  94. package/dist/fmp4-completion.js.map +7 -0
  95. package/dist/fmp4-reader.d.ts +37 -0
  96. package/dist/fmp4-reader.d.ts.map +1 -0
  97. package/dist/fmp4-reader.js +100 -0
  98. package/dist/fmp4-reader.js.map +7 -0
  99. package/dist/index.d.ts +44 -3
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +464 -14
  102. package/dist/index.js.map +7 -1
  103. package/dist/iso-block-locator.d.ts +118 -0
  104. package/dist/iso-block-locator.d.ts.map +1 -0
  105. package/dist/iso-block-locator.js +288 -0
  106. package/dist/iso-block-locator.js.map +7 -0
  107. package/dist/iso-bmff-timing.d.ts +78 -0
  108. package/dist/iso-bmff-timing.d.ts.map +1 -0
  109. package/dist/iso-bmff-timing.js +593 -0
  110. package/dist/iso-bmff-timing.js.map +7 -0
  111. package/dist/loc.d.ts +70 -75
  112. package/dist/loc.d.ts.map +1 -1
  113. package/dist/loc.js +106 -192
  114. package/dist/loc.js.map +7 -1
  115. package/dist/manifest-auth-plan.d.ts +30 -0
  116. package/dist/manifest-auth-plan.d.ts.map +1 -0
  117. package/dist/manifest-auth-plan.js +1266 -0
  118. package/dist/manifest-auth-plan.js.map +7 -0
  119. package/dist/media-router.d.ts +262 -0
  120. package/dist/media-router.d.ts.map +1 -0
  121. package/dist/media-router.js +584 -0
  122. package/dist/media-router.js.map +7 -0
  123. package/dist/mfu-reassembler.d.ts +313 -9
  124. package/dist/mfu-reassembler.d.ts.map +1 -1
  125. package/dist/mfu-reassembler.js +3050 -285
  126. package/dist/mfu-reassembler.js.map +7 -1
  127. package/dist/mfu-reassembly-policy.d.ts +24 -0
  128. package/dist/mfu-reassembly-policy.d.ts.map +1 -0
  129. package/dist/mfu-reassembly-policy.js +99 -0
  130. package/dist/mfu-reassembly-policy.js.map +7 -0
  131. package/dist/mmtp-container-parser.d.ts +573 -0
  132. package/dist/mmtp-container-parser.d.ts.map +1 -0
  133. package/dist/mmtp-container-parser.js +6601 -0
  134. package/dist/mmtp-container-parser.js.map +7 -0
  135. package/dist/mmtp-container-parser.no-modules.js +9645 -0
  136. package/dist/mmtp-container-parser.no-modules.js.map +7 -0
  137. package/dist/mmtp-decode-pipeline.d.ts +138 -0
  138. package/dist/mmtp-decode-pipeline.d.ts.map +1 -0
  139. package/dist/mmtp-decode-pipeline.js +6835 -0
  140. package/dist/mmtp-decode-pipeline.js.map +7 -0
  141. package/dist/mmtp-router.d.ts +702 -0
  142. package/dist/mmtp-router.d.ts.map +1 -0
  143. package/dist/mmtp-router.js +11193 -0
  144. package/dist/mmtp-router.js.map +7 -0
  145. package/dist/mmtp-to-cmaf.d.ts +187 -0
  146. package/dist/mmtp-to-cmaf.d.ts.map +1 -0
  147. package/dist/mmtp-to-cmaf.js +716 -0
  148. package/dist/mmtp-to-cmaf.js.map +7 -0
  149. package/dist/multicast-auth-catalog.d.ts +115 -0
  150. package/dist/multicast-auth-catalog.d.ts.map +1 -0
  151. package/dist/multicast-auth-catalog.js +202 -0
  152. package/dist/multicast-auth-catalog.js.map +7 -0
  153. package/dist/multicast-input.d.ts +141 -0
  154. package/dist/multicast-input.d.ts.map +1 -0
  155. package/dist/multicast-input.js +389 -0
  156. package/dist/multicast-input.js.map +7 -0
  157. package/dist/multicast-manager.d.ts +699 -0
  158. package/dist/multicast-manager.d.ts.map +1 -0
  159. package/dist/multicast-manager.js +19120 -0
  160. package/dist/multicast-manager.js.map +7 -0
  161. package/dist/multicast-transport.d.ts +271 -0
  162. package/dist/multicast-transport.d.ts.map +1 -0
  163. package/dist/multicast-transport.js +2262 -0
  164. package/dist/multicast-transport.js.map +7 -0
  165. package/dist/multicast-unwrap.d.ts +57 -0
  166. package/dist/multicast-unwrap.d.ts.map +1 -0
  167. package/dist/multicast-unwrap.js +417 -0
  168. package/dist/multicast-unwrap.js.map +7 -0
  169. package/dist/no-modules.d.ts +10 -0
  170. package/dist/no-modules.d.ts.map +1 -0
  171. package/dist/no-modules.js +9520 -0
  172. package/dist/no-modules.js.map +7 -0
  173. package/dist/phase14-catalog-override.d.ts +126 -0
  174. package/dist/phase14-catalog-override.d.ts.map +1 -0
  175. package/dist/phase14-catalog-override.js +155 -0
  176. package/dist/phase14-catalog-override.js.map +7 -0
  177. package/dist/repair-fetch.d.ts +281 -0
  178. package/dist/repair-fetch.d.ts.map +1 -0
  179. package/dist/repair-fetch.js +391 -0
  180. package/dist/repair-fetch.js.map +7 -0
  181. package/dist/repair-layers.d.ts +169 -0
  182. package/dist/repair-layers.d.ts.map +1 -0
  183. package/dist/repair-layers.js +351 -0
  184. package/dist/repair-layers.js.map +7 -0
  185. package/dist/sample-combiner.d.ts +212 -0
  186. package/dist/sample-combiner.d.ts.map +1 -0
  187. package/dist/sample-combiner.js +595 -0
  188. package/dist/sample-combiner.js.map +7 -0
  189. package/dist/signaling.d.ts +29 -0
  190. package/dist/signaling.d.ts.map +1 -0
  191. package/dist/signaling.js +568 -0
  192. package/dist/signaling.js.map +7 -0
  193. package/dist/source-auth.d.ts +2 -0
  194. package/dist/source-auth.d.ts.map +1 -0
  195. package/dist/source-auth.js +1209 -0
  196. package/dist/source-auth.js.map +7 -0
  197. package/dist/switching-set-alignment.d.ts +61 -0
  198. package/dist/switching-set-alignment.d.ts.map +1 -0
  199. package/dist/switching-set-alignment.js +70 -0
  200. package/dist/switching-set-alignment.js.map +7 -0
  201. package/dist/timestamp-codec.d.ts +34 -0
  202. package/dist/timestamp-codec.d.ts.map +1 -0
  203. package/dist/timestamp-codec.js +472 -0
  204. package/dist/timestamp-codec.js.map +7 -0
  205. package/dist/track-pipeline-setup.d.ts +83 -0
  206. package/dist/track-pipeline-setup.d.ts.map +1 -0
  207. package/dist/track-pipeline-setup.js +291 -0
  208. package/dist/track-pipeline-setup.js.map +7 -0
  209. package/dist/track-pipeline.d.ts +121 -0
  210. package/dist/track-pipeline.d.ts.map +1 -0
  211. package/dist/track-pipeline.js +209 -0
  212. package/dist/track-pipeline.js.map +7 -0
  213. package/dist/track-session.d.ts +414 -0
  214. package/dist/track-session.d.ts.map +1 -0
  215. package/dist/track-session.js +754 -0
  216. package/dist/track-session.js.map +7 -0
  217. package/dist/transport-pool.d.ts +69 -0
  218. package/dist/transport-pool.d.ts.map +1 -0
  219. package/dist/transport-pool.js +74 -0
  220. package/dist/transport-pool.js.map +7 -0
  221. package/dist/wasm-mfu-reassembler.d.ts +97 -0
  222. package/dist/wasm-mfu-reassembler.d.ts.map +1 -0
  223. package/dist/wasm-mfu-reassembler.js +2132 -0
  224. package/dist/wasm-mfu-reassembler.js.map +7 -0
  225. package/dist/wasm-mmtp-container-parser.d.ts +10 -0
  226. package/dist/wasm-mmtp-container-parser.d.ts.map +1 -0
  227. package/dist/wasm-mmtp-container-parser.js +6855 -0
  228. package/dist/wasm-mmtp-container-parser.js.map +7 -0
  229. package/node_modules/@blockcast/fec-worker/README.md +223 -0
  230. package/node_modules/@blockcast/fec-worker/dist/.build-stamp +0 -0
  231. package/node_modules/@blockcast/fec-worker/dist/block-order.d.ts +22 -0
  232. package/node_modules/@blockcast/fec-worker/dist/block-order.d.ts.map +1 -0
  233. package/node_modules/@blockcast/fec-worker/dist/block-order.js +48 -0
  234. package/node_modules/@blockcast/fec-worker/dist/block-order.js.map +1 -0
  235. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts +92 -0
  236. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts.map +1 -0
  237. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js +345 -0
  238. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js.map +1 -0
  239. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts +373 -0
  240. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts.map +1 -0
  241. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js +11 -0
  242. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js.map +1 -0
  243. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts +14 -0
  244. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts.map +1 -0
  245. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js +764 -0
  246. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js.map +7 -0
  247. package/node_modules/@blockcast/fec-worker/dist/index.d.ts +10 -0
  248. package/node_modules/@blockcast/fec-worker/dist/index.d.ts.map +1 -0
  249. package/node_modules/@blockcast/fec-worker/dist/index.js +8 -0
  250. package/node_modules/@blockcast/fec-worker/dist/index.js.map +1 -0
  251. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts +20 -0
  252. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts.map +1 -0
  253. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js +370 -0
  254. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js.map +7 -0
  255. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts +104 -0
  256. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts.map +1 -0
  257. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js +181 -0
  258. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js.map +1 -0
  259. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts +194 -0
  260. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts.map +1 -0
  261. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js +62 -0
  262. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js.map +1 -0
  263. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts +9 -0
  264. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts.map +1 -0
  265. package/node_modules/@blockcast/fec-worker/dist/transfer.js +13 -0
  266. package/node_modules/@blockcast/fec-worker/dist/transfer.js.map +1 -0
  267. package/node_modules/@blockcast/fec-worker/package.json +59 -0
  268. package/node_modules/@blockcast/fec-worker/src/block-order.ts +59 -0
  269. package/node_modules/@blockcast/fec-worker/src/fec-worker-client.ts +391 -0
  270. package/node_modules/@blockcast/fec-worker/src/fec-worker-types.ts +392 -0
  271. package/node_modules/@blockcast/fec-worker/src/fec-worker.ts +1386 -0
  272. package/node_modules/@blockcast/fec-worker/src/index.ts +35 -0
  273. package/node_modules/@blockcast/fec-worker/src/shred-fec-worker.ts +589 -0
  274. package/node_modules/@blockcast/fec-worker/src/shred-worker-client.ts +222 -0
  275. package/node_modules/@blockcast/fec-worker/src/shred-worker-types.ts +209 -0
  276. package/node_modules/@blockcast/fec-worker/src/transfer.ts +12 -0
  277. package/node_modules/@blockcast/mmt-alta-parse/README.md +181 -0
  278. package/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  279. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +57 -0
  280. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +1 -0
  281. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js +115 -0
  282. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +1 -0
  283. package/node_modules/@blockcast/mmt-alta-parse/package.json +50 -0
  284. package/node_modules/@blockcast/mmt-alta-parse/src/index.ts +136 -0
  285. package/node_modules/@blockcast/mmt-base/README.md +134 -0
  286. package/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  287. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts +55 -0
  288. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts.map +1 -0
  289. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js +132 -0
  290. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js.map +1 -0
  291. package/node_modules/@blockcast/mmt-base/dist/index.d.ts +7 -0
  292. package/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +1 -0
  293. package/node_modules/@blockcast/mmt-base/dist/index.js +7 -0
  294. package/node_modules/@blockcast/mmt-base/dist/index.js.map +1 -0
  295. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +292 -0
  296. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -0
  297. package/node_modules/@blockcast/mmt-base/dist/mmtp.js +612 -0
  298. package/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +1 -0
  299. package/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts +18 -0
  300. package/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts.map +1 -0
  301. package/node_modules/@blockcast/mmt-base/dist/net-constants.js +18 -0
  302. package/node_modules/@blockcast/mmt-base/dist/net-constants.js.map +1 -0
  303. package/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts +47 -0
  304. package/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts.map +1 -0
  305. package/node_modules/@blockcast/mmt-base/dist/ntp-short.js +54 -0
  306. package/node_modules/@blockcast/mmt-base/dist/ntp-short.js.map +1 -0
  307. package/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts +28 -0
  308. package/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts.map +1 -0
  309. package/node_modules/@blockcast/mmt-base/dist/serial-number.js +44 -0
  310. package/node_modules/@blockcast/mmt-base/dist/serial-number.js.map +1 -0
  311. package/node_modules/@blockcast/mmt-base/dist/signalling.d.ts +314 -0
  312. package/node_modules/@blockcast/mmt-base/dist/signalling.d.ts.map +1 -0
  313. package/node_modules/@blockcast/mmt-base/dist/signalling.js +637 -0
  314. package/node_modules/@blockcast/mmt-base/dist/signalling.js.map +1 -0
  315. package/node_modules/@blockcast/mmt-base/package.json +49 -0
  316. package/node_modules/@blockcast/mmt-base/src/fec-block-profile.ts +145 -0
  317. package/node_modules/@blockcast/mmt-base/src/index.ts +6 -0
  318. package/node_modules/@blockcast/mmt-base/src/mmtp.ts +892 -0
  319. package/node_modules/@blockcast/mmt-base/src/net-constants.ts +17 -0
  320. package/node_modules/@blockcast/mmt-base/src/ntp-short.ts +67 -0
  321. package/node_modules/@blockcast/mmt-base/src/serial-number.ts +49 -0
  322. package/node_modules/@blockcast/mmt-base/src/signalling.ts +860 -0
  323. package/node_modules/@blockcast/mmt-fec/README.md +271 -0
  324. package/node_modules/@blockcast/mmt-fec/dist/.build-stamp +0 -0
  325. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts +82 -0
  326. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts.map +1 -0
  327. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js +296 -0
  328. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js.map +1 -0
  329. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts +105 -0
  330. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts.map +1 -0
  331. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js +222 -0
  332. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js.map +1 -0
  333. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts +46 -0
  334. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts.map +1 -0
  335. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js +96 -0
  336. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js.map +1 -0
  337. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts +33 -0
  338. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts.map +1 -0
  339. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js +57 -0
  340. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js.map +1 -0
  341. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts +149 -0
  342. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts.map +1 -0
  343. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js +161 -0
  344. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js.map +1 -0
  345. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts +28 -0
  346. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts.map +1 -0
  347. package/node_modules/@blockcast/mmt-fec/dist/index.js +28 -0
  348. package/node_modules/@blockcast/mmt-fec/dist/index.js.map +1 -0
  349. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts +20 -0
  350. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts.map +1 -0
  351. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js +28 -0
  352. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js.map +1 -0
  353. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts +258 -0
  354. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts.map +1 -0
  355. package/node_modules/@blockcast/mmt-fec/dist/oti.js +382 -0
  356. package/node_modules/@blockcast/mmt-fec/dist/oti.js.map +1 -0
  357. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts +41 -0
  358. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts.map +1 -0
  359. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js +81 -0
  360. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js.map +1 -0
  361. package/node_modules/@blockcast/mmt-fec/dist/repair-group.d.ts +82 -0
  362. package/node_modules/@blockcast/mmt-fec/dist/repair-group.d.ts.map +1 -0
  363. package/node_modules/@blockcast/mmt-fec/dist/repair-group.js +170 -0
  364. package/node_modules/@blockcast/mmt-fec/dist/repair-group.js.map +1 -0
  365. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/README.md +181 -0
  366. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  367. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +57 -0
  368. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +1 -0
  369. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js +115 -0
  370. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +1 -0
  371. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/package.json +50 -0
  372. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/src/index.ts +136 -0
  373. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/README.md +134 -0
  374. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  375. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts +55 -0
  376. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts.map +1 -0
  377. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js +132 -0
  378. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js.map +1 -0
  379. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts +7 -0
  380. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +1 -0
  381. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js +7 -0
  382. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js.map +1 -0
  383. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +292 -0
  384. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -0
  385. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js +612 -0
  386. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +1 -0
  387. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts +18 -0
  388. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts.map +1 -0
  389. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.js +18 -0
  390. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.js.map +1 -0
  391. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts +47 -0
  392. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts.map +1 -0
  393. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.js +54 -0
  394. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.js.map +1 -0
  395. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts +28 -0
  396. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts.map +1 -0
  397. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.js +44 -0
  398. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.js.map +1 -0
  399. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.d.ts +314 -0
  400. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.d.ts.map +1 -0
  401. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.js +637 -0
  402. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.js.map +1 -0
  403. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/package.json +49 -0
  404. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/fec-block-profile.ts +145 -0
  405. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/index.ts +6 -0
  406. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/mmtp.ts +892 -0
  407. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/net-constants.ts +17 -0
  408. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/ntp-short.ts +67 -0
  409. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/serial-number.ts +49 -0
  410. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/signalling.ts +860 -0
  411. package/node_modules/@blockcast/mmt-fec/package.json +79 -0
  412. package/node_modules/@blockcast/mmt-fec/src/block-manager.ts +371 -0
  413. package/node_modules/@blockcast/mmt-fec/src/block-processor.ts +360 -0
  414. package/node_modules/@blockcast/mmt-fec/src/cmaf-recovery.ts +127 -0
  415. package/node_modules/@blockcast/mmt-fec/src/cmaf-symbols.ts +68 -0
  416. package/node_modules/@blockcast/mmt-fec/src/fec-block-mapper.ts +225 -0
  417. package/node_modules/@blockcast/mmt-fec/src/index.ts +93 -0
  418. package/node_modules/@blockcast/mmt-fec/src/loc-symbols.ts +34 -0
  419. package/node_modules/@blockcast/mmt-fec/src/oti.ts +463 -0
  420. package/node_modules/@blockcast/mmt-fec/src/reorder-buffer.ts +101 -0
  421. package/node_modules/@blockcast/mmt-fec/src/repair-group.ts +195 -0
  422. package/node_modules/@blockcast/mmt-manifest-verify/dist/.build-stamp +0 -0
  423. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts +28 -0
  424. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts.map +1 -0
  425. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js +128 -0
  426. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js.map +1 -0
  427. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts +4 -0
  428. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts.map +1 -0
  429. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js +4 -0
  430. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js.map +1 -0
  431. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts +37 -0
  432. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts.map +1 -0
  433. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js +179 -0
  434. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js.map +1 -0
  435. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts +99 -0
  436. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts.map +1 -0
  437. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js +492 -0
  438. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js.map +1 -0
  439. package/node_modules/@blockcast/mmt-manifest-verify/package.json +45 -0
  440. package/node_modules/@blockcast/mmt-manifest-verify/src/admission.ts +171 -0
  441. package/node_modules/@blockcast/mmt-manifest-verify/src/index.ts +3 -0
  442. package/node_modules/@blockcast/mmt-manifest-verify/src/source-auth.ts +230 -0
  443. package/node_modules/@blockcast/mmt-manifest-verify/src/verifier.ts +669 -0
  444. package/node_modules/@noble/hashes/LICENSE +21 -0
  445. package/node_modules/@noble/hashes/README.md +521 -0
  446. package/node_modules/@noble/hashes/_assert.d.ts +17 -0
  447. package/node_modules/@noble/hashes/_assert.d.ts.map +1 -0
  448. package/node_modules/@noble/hashes/_assert.js +18 -0
  449. package/node_modules/@noble/hashes/_assert.js.map +1 -0
  450. package/node_modules/@noble/hashes/_blake.d.ts +14 -0
  451. package/node_modules/@noble/hashes/_blake.d.ts.map +1 -0
  452. package/node_modules/@noble/hashes/_blake.js +50 -0
  453. package/node_modules/@noble/hashes/_blake.js.map +1 -0
  454. package/node_modules/@noble/hashes/_md.d.ts +51 -0
  455. package/node_modules/@noble/hashes/_md.d.ts.map +1 -0
  456. package/node_modules/@noble/hashes/_md.js +162 -0
  457. package/node_modules/@noble/hashes/_md.js.map +1 -0
  458. package/node_modules/@noble/hashes/_u64.d.ts +55 -0
  459. package/node_modules/@noble/hashes/_u64.d.ts.map +1 -0
  460. package/node_modules/@noble/hashes/_u64.js +90 -0
  461. package/node_modules/@noble/hashes/_u64.js.map +1 -0
  462. package/node_modules/@noble/hashes/argon2.d.ts +32 -0
  463. package/node_modules/@noble/hashes/argon2.d.ts.map +1 -0
  464. package/node_modules/@noble/hashes/argon2.js +401 -0
  465. package/node_modules/@noble/hashes/argon2.js.map +1 -0
  466. package/node_modules/@noble/hashes/blake1.d.ts +106 -0
  467. package/node_modules/@noble/hashes/blake1.d.ts.map +1 -0
  468. package/node_modules/@noble/hashes/blake1.js +459 -0
  469. package/node_modules/@noble/hashes/blake1.js.map +1 -0
  470. package/node_modules/@noble/hashes/blake2.d.ts +116 -0
  471. package/node_modules/@noble/hashes/blake2.d.ts.map +1 -0
  472. package/node_modules/@noble/hashes/blake2.js +420 -0
  473. package/node_modules/@noble/hashes/blake2.js.map +1 -0
  474. package/node_modules/@noble/hashes/blake2b.d.ts +11 -0
  475. package/node_modules/@noble/hashes/blake2b.d.ts.map +1 -0
  476. package/node_modules/@noble/hashes/blake2b.js +14 -0
  477. package/node_modules/@noble/hashes/blake2b.js.map +1 -0
  478. package/node_modules/@noble/hashes/blake2s.d.ts +20 -0
  479. package/node_modules/@noble/hashes/blake2s.d.ts.map +1 -0
  480. package/node_modules/@noble/hashes/blake2s.js +24 -0
  481. package/node_modules/@noble/hashes/blake2s.js.map +1 -0
  482. package/node_modules/@noble/hashes/blake3.d.ts +54 -0
  483. package/node_modules/@noble/hashes/blake3.d.ts.map +1 -0
  484. package/node_modules/@noble/hashes/blake3.js +255 -0
  485. package/node_modules/@noble/hashes/blake3.js.map +1 -0
  486. package/node_modules/@noble/hashes/crypto.d.ts +2 -0
  487. package/node_modules/@noble/hashes/crypto.d.ts.map +1 -0
  488. package/node_modules/@noble/hashes/crypto.js +5 -0
  489. package/node_modules/@noble/hashes/crypto.js.map +1 -0
  490. package/node_modules/@noble/hashes/cryptoNode.d.ts +2 -0
  491. package/node_modules/@noble/hashes/cryptoNode.d.ts.map +1 -0
  492. package/node_modules/@noble/hashes/cryptoNode.js +18 -0
  493. package/node_modules/@noble/hashes/cryptoNode.js.map +1 -0
  494. package/node_modules/@noble/hashes/eskdf.d.ts +47 -0
  495. package/node_modules/@noble/hashes/eskdf.d.ts.map +1 -0
  496. package/node_modules/@noble/hashes/eskdf.js +166 -0
  497. package/node_modules/@noble/hashes/eskdf.js.map +1 -0
  498. package/node_modules/@noble/hashes/esm/_assert.d.ts +17 -0
  499. package/node_modules/@noble/hashes/esm/_assert.d.ts.map +1 -0
  500. package/node_modules/@noble/hashes/esm/_assert.js +15 -0
  501. package/node_modules/@noble/hashes/esm/_assert.js.map +1 -0
  502. package/node_modules/@noble/hashes/esm/_blake.d.ts +14 -0
  503. package/node_modules/@noble/hashes/esm/_blake.d.ts.map +1 -0
  504. package/node_modules/@noble/hashes/esm/_blake.js +45 -0
  505. package/node_modules/@noble/hashes/esm/_blake.js.map +1 -0
  506. package/node_modules/@noble/hashes/esm/_md.d.ts +51 -0
  507. package/node_modules/@noble/hashes/esm/_md.d.ts.map +1 -0
  508. package/node_modules/@noble/hashes/esm/_md.js +155 -0
  509. package/node_modules/@noble/hashes/esm/_md.js.map +1 -0
  510. package/node_modules/@noble/hashes/esm/_u64.d.ts +55 -0
  511. package/node_modules/@noble/hashes/esm/_u64.d.ts.map +1 -0
  512. package/node_modules/@noble/hashes/esm/_u64.js +67 -0
  513. package/node_modules/@noble/hashes/esm/_u64.js.map +1 -0
  514. package/node_modules/@noble/hashes/esm/argon2.d.ts +32 -0
  515. package/node_modules/@noble/hashes/esm/argon2.d.ts.map +1 -0
  516. package/node_modules/@noble/hashes/esm/argon2.js +392 -0
  517. package/node_modules/@noble/hashes/esm/argon2.js.map +1 -0
  518. package/node_modules/@noble/hashes/esm/blake1.d.ts +106 -0
  519. package/node_modules/@noble/hashes/esm/blake1.d.ts.map +1 -0
  520. package/node_modules/@noble/hashes/esm/blake1.js +452 -0
  521. package/node_modules/@noble/hashes/esm/blake1.js.map +1 -0
  522. package/node_modules/@noble/hashes/esm/blake2.d.ts +116 -0
  523. package/node_modules/@noble/hashes/esm/blake2.d.ts.map +1 -0
  524. package/node_modules/@noble/hashes/esm/blake2.js +413 -0
  525. package/node_modules/@noble/hashes/esm/blake2.js.map +1 -0
  526. package/node_modules/@noble/hashes/esm/blake2b.d.ts +11 -0
  527. package/node_modules/@noble/hashes/esm/blake2b.d.ts.map +1 -0
  528. package/node_modules/@noble/hashes/esm/blake2b.js +11 -0
  529. package/node_modules/@noble/hashes/esm/blake2b.js.map +1 -0
  530. package/node_modules/@noble/hashes/esm/blake2s.d.ts +20 -0
  531. package/node_modules/@noble/hashes/esm/blake2s.d.ts.map +1 -0
  532. package/node_modules/@noble/hashes/esm/blake2s.js +21 -0
  533. package/node_modules/@noble/hashes/esm/blake2s.js.map +1 -0
  534. package/node_modules/@noble/hashes/esm/blake3.d.ts +54 -0
  535. package/node_modules/@noble/hashes/esm/blake3.d.ts.map +1 -0
  536. package/node_modules/@noble/hashes/esm/blake3.js +251 -0
  537. package/node_modules/@noble/hashes/esm/blake3.js.map +1 -0
  538. package/node_modules/@noble/hashes/esm/crypto.d.ts +2 -0
  539. package/node_modules/@noble/hashes/esm/crypto.d.ts.map +1 -0
  540. package/node_modules/@noble/hashes/esm/crypto.js +2 -0
  541. package/node_modules/@noble/hashes/esm/crypto.js.map +1 -0
  542. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts +2 -0
  543. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts.map +1 -0
  544. package/node_modules/@noble/hashes/esm/cryptoNode.js +15 -0
  545. package/node_modules/@noble/hashes/esm/cryptoNode.js.map +1 -0
  546. package/node_modules/@noble/hashes/esm/eskdf.d.ts +47 -0
  547. package/node_modules/@noble/hashes/esm/eskdf.d.ts.map +1 -0
  548. package/node_modules/@noble/hashes/esm/eskdf.js +160 -0
  549. package/node_modules/@noble/hashes/esm/eskdf.js.map +1 -0
  550. package/node_modules/@noble/hashes/esm/hkdf.d.ts +36 -0
  551. package/node_modules/@noble/hashes/esm/hkdf.d.ts.map +1 -0
  552. package/node_modules/@noble/hashes/esm/hkdf.js +82 -0
  553. package/node_modules/@noble/hashes/esm/hkdf.js.map +1 -0
  554. package/node_modules/@noble/hashes/esm/hmac.d.ts +35 -0
  555. package/node_modules/@noble/hashes/esm/hmac.d.ts.map +1 -0
  556. package/node_modules/@noble/hashes/esm/hmac.js +86 -0
  557. package/node_modules/@noble/hashes/esm/hmac.js.map +1 -0
  558. package/node_modules/@noble/hashes/esm/index.d.ts +2 -0
  559. package/node_modules/@noble/hashes/esm/index.d.ts.map +1 -0
  560. package/node_modules/@noble/hashes/esm/index.js +33 -0
  561. package/node_modules/@noble/hashes/esm/index.js.map +1 -0
  562. package/node_modules/@noble/hashes/esm/legacy.d.ts +71 -0
  563. package/node_modules/@noble/hashes/esm/legacy.d.ts.map +1 -0
  564. package/node_modules/@noble/hashes/esm/legacy.js +281 -0
  565. package/node_modules/@noble/hashes/esm/legacy.js.map +1 -0
  566. package/node_modules/@noble/hashes/esm/package.json +10 -0
  567. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts +23 -0
  568. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts.map +1 -0
  569. package/node_modules/@noble/hashes/esm/pbkdf2.js +97 -0
  570. package/node_modules/@noble/hashes/esm/pbkdf2.js.map +1 -0
  571. package/node_modules/@noble/hashes/esm/ripemd160.d.ts +13 -0
  572. package/node_modules/@noble/hashes/esm/ripemd160.d.ts.map +1 -0
  573. package/node_modules/@noble/hashes/esm/ripemd160.js +13 -0
  574. package/node_modules/@noble/hashes/esm/ripemd160.js.map +1 -0
  575. package/node_modules/@noble/hashes/esm/scrypt.d.ts +34 -0
  576. package/node_modules/@noble/hashes/esm/scrypt.d.ts.map +1 -0
  577. package/node_modules/@noble/hashes/esm/scrypt.js +228 -0
  578. package/node_modules/@noble/hashes/esm/scrypt.js.map +1 -0
  579. package/node_modules/@noble/hashes/esm/sha1.d.ts +11 -0
  580. package/node_modules/@noble/hashes/esm/sha1.d.ts.map +1 -0
  581. package/node_modules/@noble/hashes/esm/sha1.js +11 -0
  582. package/node_modules/@noble/hashes/esm/sha1.js.map +1 -0
  583. package/node_modules/@noble/hashes/esm/sha2.d.ts +159 -0
  584. package/node_modules/@noble/hashes/esm/sha2.d.ts.map +1 -0
  585. package/node_modules/@noble/hashes/esm/sha2.js +375 -0
  586. package/node_modules/@noble/hashes/esm/sha2.js.map +1 -0
  587. package/node_modules/@noble/hashes/esm/sha256.d.ts +20 -0
  588. package/node_modules/@noble/hashes/esm/sha256.d.ts.map +1 -0
  589. package/node_modules/@noble/hashes/esm/sha256.js +20 -0
  590. package/node_modules/@noble/hashes/esm/sha256.js.map +1 -0
  591. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts +142 -0
  592. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts.map +1 -0
  593. package/node_modules/@noble/hashes/esm/sha3-addons.js +393 -0
  594. package/node_modules/@noble/hashes/esm/sha3-addons.js.map +1 -0
  595. package/node_modules/@noble/hashes/esm/sha3.d.ts +53 -0
  596. package/node_modules/@noble/hashes/esm/sha3.d.ts.map +1 -0
  597. package/node_modules/@noble/hashes/esm/sha3.js +234 -0
  598. package/node_modules/@noble/hashes/esm/sha3.js.map +1 -0
  599. package/node_modules/@noble/hashes/esm/sha512.d.ts +26 -0
  600. package/node_modules/@noble/hashes/esm/sha512.d.ts.map +1 -0
  601. package/node_modules/@noble/hashes/esm/sha512.js +26 -0
  602. package/node_modules/@noble/hashes/esm/sha512.js.map +1 -0
  603. package/node_modules/@noble/hashes/esm/utils.d.ts +161 -0
  604. package/node_modules/@noble/hashes/esm/utils.d.ts.map +1 -0
  605. package/node_modules/@noble/hashes/esm/utils.js +281 -0
  606. package/node_modules/@noble/hashes/esm/utils.js.map +1 -0
  607. package/node_modules/@noble/hashes/hkdf.d.ts +36 -0
  608. package/node_modules/@noble/hashes/hkdf.d.ts.map +1 -0
  609. package/node_modules/@noble/hashes/hkdf.js +88 -0
  610. package/node_modules/@noble/hashes/hkdf.js.map +1 -0
  611. package/node_modules/@noble/hashes/hmac.d.ts +35 -0
  612. package/node_modules/@noble/hashes/hmac.d.ts.map +1 -0
  613. package/node_modules/@noble/hashes/hmac.js +91 -0
  614. package/node_modules/@noble/hashes/hmac.js.map +1 -0
  615. package/node_modules/@noble/hashes/index.d.ts +1 -0
  616. package/node_modules/@noble/hashes/index.d.ts.map +1 -0
  617. package/node_modules/@noble/hashes/index.js +33 -0
  618. package/node_modules/@noble/hashes/index.js.map +1 -0
  619. package/node_modules/@noble/hashes/legacy.d.ts +71 -0
  620. package/node_modules/@noble/hashes/legacy.d.ts.map +1 -0
  621. package/node_modules/@noble/hashes/legacy.js +287 -0
  622. package/node_modules/@noble/hashes/legacy.js.map +1 -0
  623. package/node_modules/@noble/hashes/package.json +266 -0
  624. package/node_modules/@noble/hashes/pbkdf2.d.ts +23 -0
  625. package/node_modules/@noble/hashes/pbkdf2.d.ts.map +1 -0
  626. package/node_modules/@noble/hashes/pbkdf2.js +101 -0
  627. package/node_modules/@noble/hashes/pbkdf2.js.map +1 -0
  628. package/node_modules/@noble/hashes/ripemd160.d.ts +13 -0
  629. package/node_modules/@noble/hashes/ripemd160.d.ts.map +1 -0
  630. package/node_modules/@noble/hashes/ripemd160.js +16 -0
  631. package/node_modules/@noble/hashes/ripemd160.js.map +1 -0
  632. package/node_modules/@noble/hashes/scrypt.d.ts +34 -0
  633. package/node_modules/@noble/hashes/scrypt.d.ts.map +1 -0
  634. package/node_modules/@noble/hashes/scrypt.js +232 -0
  635. package/node_modules/@noble/hashes/scrypt.js.map +1 -0
  636. package/node_modules/@noble/hashes/sha1.d.ts +11 -0
  637. package/node_modules/@noble/hashes/sha1.d.ts.map +1 -0
  638. package/node_modules/@noble/hashes/sha1.js +14 -0
  639. package/node_modules/@noble/hashes/sha1.js.map +1 -0
  640. package/node_modules/@noble/hashes/sha2.d.ts +159 -0
  641. package/node_modules/@noble/hashes/sha2.d.ts.map +1 -0
  642. package/node_modules/@noble/hashes/sha2.js +384 -0
  643. package/node_modules/@noble/hashes/sha2.js.map +1 -0
  644. package/node_modules/@noble/hashes/sha256.d.ts +20 -0
  645. package/node_modules/@noble/hashes/sha256.d.ts.map +1 -0
  646. package/node_modules/@noble/hashes/sha256.js +23 -0
  647. package/node_modules/@noble/hashes/sha256.js.map +1 -0
  648. package/node_modules/@noble/hashes/sha3-addons.d.ts +142 -0
  649. package/node_modules/@noble/hashes/sha3-addons.d.ts.map +1 -0
  650. package/node_modules/@noble/hashes/sha3-addons.js +402 -0
  651. package/node_modules/@noble/hashes/sha3-addons.js.map +1 -0
  652. package/node_modules/@noble/hashes/sha3.d.ts +53 -0
  653. package/node_modules/@noble/hashes/sha3.d.ts.map +1 -0
  654. package/node_modules/@noble/hashes/sha3.js +239 -0
  655. package/node_modules/@noble/hashes/sha3.js.map +1 -0
  656. package/node_modules/@noble/hashes/sha512.d.ts +26 -0
  657. package/node_modules/@noble/hashes/sha512.d.ts.map +1 -0
  658. package/node_modules/@noble/hashes/sha512.js +29 -0
  659. package/node_modules/@noble/hashes/sha512.js.map +1 -0
  660. package/node_modules/@noble/hashes/src/_assert.ts +22 -0
  661. package/node_modules/@noble/hashes/src/_blake.ts +50 -0
  662. package/node_modules/@noble/hashes/src/_md.ts +176 -0
  663. package/node_modules/@noble/hashes/src/_u64.ts +91 -0
  664. package/node_modules/@noble/hashes/src/argon2.ts +497 -0
  665. package/node_modules/@noble/hashes/src/blake1.ts +534 -0
  666. package/node_modules/@noble/hashes/src/blake2.ts +486 -0
  667. package/node_modules/@noble/hashes/src/blake2b.ts +10 -0
  668. package/node_modules/@noble/hashes/src/blake2s.ts +20 -0
  669. package/node_modules/@noble/hashes/src/blake3.ts +272 -0
  670. package/node_modules/@noble/hashes/src/crypto.ts +9 -0
  671. package/node_modules/@noble/hashes/src/cryptoNode.ts +15 -0
  672. package/node_modules/@noble/hashes/src/eskdf.ts +187 -0
  673. package/node_modules/@noble/hashes/src/hkdf.ts +88 -0
  674. package/node_modules/@noble/hashes/src/hmac.ts +94 -0
  675. package/node_modules/@noble/hashes/src/index.ts +31 -0
  676. package/node_modules/@noble/hashes/src/legacy.ts +293 -0
  677. package/node_modules/@noble/hashes/src/pbkdf2.ts +122 -0
  678. package/node_modules/@noble/hashes/src/ripemd160.ts +12 -0
  679. package/node_modules/@noble/hashes/src/scrypt.ts +257 -0
  680. package/node_modules/@noble/hashes/src/sha1.ts +10 -0
  681. package/node_modules/@noble/hashes/src/sha2.ts +402 -0
  682. package/node_modules/@noble/hashes/src/sha256.ts +24 -0
  683. package/node_modules/@noble/hashes/src/sha3-addons.ts +499 -0
  684. package/node_modules/@noble/hashes/src/sha3.ts +258 -0
  685. package/node_modules/@noble/hashes/src/sha512.ts +34 -0
  686. package/node_modules/@noble/hashes/src/utils.ts +395 -0
  687. package/node_modules/@noble/hashes/utils.d.ts +161 -0
  688. package/node_modules/@noble/hashes/utils.d.ts.map +1 -0
  689. package/node_modules/@noble/hashes/utils.js +313 -0
  690. package/node_modules/@noble/hashes/utils.js.map +1 -0
  691. package/package.json +179 -8
  692. package/schemas/catalog.schema.json +357 -0
  693. package/src/align.ts +239 -0
  694. package/src/audio-init-parser.ts +189 -0
  695. package/src/audio-ring-buffer.ts +120 -0
  696. package/src/authored-timing.ts +63 -0
  697. package/src/block-policy.ts +903 -0
  698. package/src/carp-catalog.ts +1086 -0
  699. package/src/catalog-fec-params.ts +803 -0
  700. package/src/catalog-network-source.ts +74 -0
  701. package/src/catalog-validator.ts +34 -0
  702. package/src/cmaf-block-assembler.ts +1255 -0
  703. package/src/cmaf-group-batcher.ts +104 -0
  704. package/src/cmcd-producer.ts +329 -0
  705. package/src/codec-utils.ts +1984 -0
  706. package/src/conceal-timeline.ts +293 -0
  707. package/src/delivery-gap.ts +147 -0
  708. package/src/duplicate-ledger.ts +372 -0
  709. package/src/fec-codec.ts +61 -0
  710. package/src/fec-manager.ts +4241 -0
  711. package/src/fec-starvation-detector.ts +128 -0
  712. package/src/fec-state.ts +228 -0
  713. package/src/fec-worker-setup.ts +291 -0
  714. package/src/fmp4-completion.ts +47 -0
  715. package/src/fmp4-reader.ts +112 -0
  716. package/src/index.ts +592 -21
  717. package/src/iso-block-locator.ts +329 -0
  718. package/src/iso-bmff-timing.ts +813 -0
  719. package/src/loc.ts +128 -154
  720. package/src/manifest-auth-plan.ts +107 -0
  721. package/src/media-router.ts +841 -0
  722. package/src/mfu-reassembler.ts +3480 -155
  723. package/src/mfu-reassembly-policy.ts +134 -0
  724. package/src/mmtp-container-parser.ts +2406 -0
  725. package/src/mmtp-decode-pipeline.ts +406 -0
  726. package/src/mmtp-router.ts +4712 -0
  727. package/src/mmtp-to-cmaf.ts +977 -0
  728. package/src/multicast-auth-catalog.ts +340 -0
  729. package/src/multicast-input.ts +793 -0
  730. package/src/multicast-manager.ts +2895 -0
  731. package/src/multicast-transport.ts +1058 -0
  732. package/src/multicast-unwrap.ts +132 -0
  733. package/src/no-modules.ts +21 -0
  734. package/src/phase14-catalog-override.ts +336 -0
  735. package/src/repair-fetch.ts +786 -0
  736. package/src/repair-layers.ts +553 -0
  737. package/src/sample-combiner.ts +938 -0
  738. package/src/signaling.ts +78 -0
  739. package/src/source-auth.ts +1 -0
  740. package/src/switching-set-alignment.ts +109 -0
  741. package/src/timestamp-codec.ts +265 -0
  742. package/src/track-pipeline-setup.ts +205 -0
  743. package/src/track-pipeline.ts +332 -0
  744. package/src/track-session.ts +1010 -0
  745. package/src/transport-pool.ts +128 -0
  746. package/src/wasm-mfu-reassembler.ts +405 -0
  747. package/src/wasm-mmtp-container-parser.ts +23 -0
  748. package/test-vectors/align.json +199 -0
  749. package/test-vectors/catalog/README.md +123 -0
  750. package/test-vectors/catalog/negative/archive-origin-with-path.json +33 -0
  751. package/test-vectors/catalog/negative/archive-unknown-field.json +34 -0
  752. package/test-vectors/catalog/negative/fec-block-profile-missing.json +51 -0
  753. package/test-vectors/catalog/negative/fec-block-profile-retired.json +52 -0
  754. package/test-vectors/catalog/negative/fec-block-profile-unknown.json +52 -0
  755. package/test-vectors/catalog/negative/fec-enhancement-repair-removed.json +28 -0
  756. package/test-vectors/catalog/negative/fec-repair-layer-geometry.json +33 -0
  757. package/test-vectors/catalog/negative/fec-repair-layer-missing-symbols.json +31 -0
  758. package/test-vectors/catalog/negative/fec-repair-multiple-depends.json +32 -0
  759. package/test-vectors/catalog/negative/fec-repair-overlay-with-layer.json +34 -0
  760. package/test-vectors/catalog/negative/fec-repair-priority-out-of-band.json +24 -0
  761. package/test-vectors/catalog/negative/fec-repair-source-symbols-without-scope.json +33 -0
  762. package/test-vectors/catalog/negative/legacy-selection-params.json +15 -0
  763. package/test-vectors/catalog/negative/media-track-repair-layer.json +16 -0
  764. package/test-vectors/catalog/negative/missing-mmtp-fields.json +12 -0
  765. package/test-vectors/catalog/negative/multicast-auth-padded-key.json +37 -0
  766. package/test-vectors/catalog/negative/multicast-auth-reused-track-format.json +53 -0
  767. package/test-vectors/catalog/negative/multicast-auth-trailing-pad-bits.json +37 -0
  768. package/test-vectors/catalog/negative/multicast-endpoint-missing-protocol-and-source.json +24 -0
  769. package/test-vectors/catalog/negative/multicast-network-source-empty.json +44 -0
  770. package/test-vectors/catalog/negative/multicast-packet-id-signaling.json +26 -0
  771. package/test-vectors/catalog/negative/network-source-object.json +25 -0
  772. package/test-vectors/catalog/negative/raptorq-source-symbols-over-max.json +24 -0
  773. package/test-vectors/catalog/negative/raptorq-unaligned-symbol.json +27 -0
  774. package/test-vectors/catalog/negative/repair-track-legacy-shape.json +19 -0
  775. package/test-vectors/catalog/negative/track-role-empty.json +16 -0
  776. package/test-vectors/catalog/negative/track-role-non-string.json +16 -0
  777. package/test-vectors/catalog/positive/archive-origin.json +34 -0
  778. package/test-vectors/catalog/positive/fec-iso-ssbg1.json +52 -0
  779. package/test-vectors/catalog/positive/fec-layered-repair.json +106 -0
  780. package/test-vectors/catalog/positive/fec-multicast.json +53 -0
  781. package/test-vectors/catalog/positive/flat-av.json +30 -0
  782. package/test-vectors/catalog/positive/multicast-auth-rotation.json +55 -0
  783. package/test-vectors/catalog/positive/multicast-native-ssm-no-network-source.json +44 -0
  784. package/test-vectors/catalog/positive/ticks-and-mixed-timescales.json +39 -0
  785. package/test-vectors/catalog/positive/track-role.json +49 -0
  786. package/test-vectors/catalog/releases/moqcast-native-v16-v1/catalog.json +65 -0
  787. package/test-vectors/catalog/releases/moqcast-native-v16-v1.archive.sha256 +1 -0
  788. package/test-vectors/catalog-multicast-auth-v1.json +26 -0
  789. package/test-vectors/catalog-validation.json +767 -0
  790. package/test-vectors/catalog-validator-public-api.mjs +25 -0
  791. package/test-vectors/generate-source-auth-fixture.mjs +114 -0
  792. package/test-vectors/hevc-x265-repeat-headers-v1.json +40 -0
  793. package/test-vectors/source-auth-v1.json +61 -0
  794. package/test-vectors/source-contiguity-v1.json +131 -0
  795. package/test-vectors/subsample-placement-v1.json +230 -0
  796. package/dist/mmtp.d.ts +0 -73
  797. package/dist/mmtp.d.ts.map +0 -1
  798. package/dist/mmtp.js +0 -210
  799. package/dist/mmtp.js.map +0 -1
  800. package/src/mmtp.ts +0 -277
@@ -1,320 +1,3085 @@
1
- /**
2
- * @mmt/container - MFU Fragment Reassembler
3
- *
4
- * Handles out-of-order delivery of fragmented MFUs (Media Fragment Units)
5
- * according to ISO/IEC 23008-1:2023 Section 9.2.3.3
6
- *
7
- * Fragmentation Indicator (FI) values:
8
- * - 0: Complete MFU (single packet, no fragmentation)
9
- * - 1: First fragment
10
- * - 2: Middle fragment
11
- * - 3: Last fragment
12
- *
13
- * @packageDocumentation
14
- */
15
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
16
- if (kind === "m") throw new TypeError("Private method is not writable");
17
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
18
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
19
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
20
- };
21
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
22
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
23
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
24
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
25
- };
26
- var _MFUReassembler_instances, _MFUReassembler_buffers, _MFUReassembler_timeouts, _MFUReassembler_timeoutMs, _MFUReassembler_maxBuffered, _MFUReassembler_tryReassemble, _MFUReassembler_setTimeout, _MFUReassembler_clearTimeout, _MFUReassembler_onTimeout, _MFUReassembler_enforceMemoryLimit;
27
- import { unwrapMMTP, parseMPUHeader, isFecRepairPacket, MPU_HEADER_SIZE, MFU_DU_HEADER_SIZE, } from './mmtp.js';
28
- /**
29
- * MFU Reassembler for out-of-order fragment delivery
30
- * Based on ISO/IEC 23008-1:2023 Section 9.2.3.3
31
- */
32
- export class MFUReassembler {
33
- constructor(options) {
34
- _MFUReassembler_instances.add(this);
35
- _MFUReassembler_buffers.set(this, new Map());
36
- _MFUReassembler_timeouts.set(this, new Map());
37
- _MFUReassembler_timeoutMs.set(this, void 0);
38
- _MFUReassembler_maxBuffered.set(this, void 0);
39
- /** Statistics for debugging */
40
- this.stats = {
41
- totalFragments: 0,
42
- reassembledMFUs: 0,
43
- timedOutMFUs: 0,
44
- outOfOrderFragments: 0,
45
- };
46
- __classPrivateFieldSet(this, _MFUReassembler_timeoutMs, options?.timeoutMs ?? 1000, "f");
47
- __classPrivateFieldSet(this, _MFUReassembler_maxBuffered, options?.maxBuffered ?? 100, "f");
1
+ // ../mmt-alta-parse/dist/index.js
2
+ var MPU_HEADER_SIZE = 8;
3
+ var MPU_LENGTH_FIELD_SIZE = 2;
4
+ var MPU_HEADER_REMAINDER_SIZE = MPU_HEADER_SIZE - MPU_LENGTH_FIELD_SIZE;
5
+ var MMTP_EXT_TYPE_ALTA_AUTH = 61601;
6
+ var MMTP_EXT_TRAILER_HEADER_SIZE = 4;
7
+
8
+ // ../mmt-base/dist/mmtp.js
9
+ var MMTP_HEADER_SIZE = 12;
10
+ var MPU_HEADER_SIZE2 = 8;
11
+ var MFU_DU_HEADER_SIZE_TIMED = 14;
12
+ var MFU_DU_HEADER_SIZE_NONTIMED = 4;
13
+ var MPU_LENGTH_FIELD_SIZE2 = 2;
14
+ var MPU_HEADER_REMAINDER_SIZE2 = MPU_HEADER_SIZE2 - MPU_LENGTH_FIELD_SIZE2;
15
+ var SOURCE_FEC_PAYLOAD_ID_SIZE = 4;
16
+ var PACKET_TYPE_MPU = 0;
17
+ function parseMMTPHeader(buffer) {
18
+ if (buffer.length < MMTP_HEADER_SIZE) {
19
+ throw new Error(`MMTP packet too small: ${buffer.length} bytes`);
20
+ }
21
+ const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
22
+ const version = buffer[0] >> 6 & 3;
23
+ if (version !== 0) {
24
+ throw new Error(`Unsupported MMTP version: ${version}`);
25
+ }
26
+ const fecType = buffer[0] >> 3 & 3;
27
+ const packetCounterFlag = (buffer[0] & 32) !== 0;
28
+ const extensionFlag = (buffer[0] & 2) !== 0;
29
+ const rapFlag = (buffer[0] & 1) === 1;
30
+ const packetType = buffer[1] & 63;
31
+ let offset = 2;
32
+ const packetId = view.getUint16(offset, false);
33
+ offset += 2;
34
+ const timestamp = BigInt(view.getUint32(offset, false));
35
+ offset += 4;
36
+ const sequenceNumber = view.getUint32(offset, false);
37
+ offset += 4;
38
+ let packetCounter;
39
+ if (packetCounterFlag) {
40
+ if (offset + 4 > buffer.length)
41
+ throw new Error("MMTP packet truncated in packet_counter");
42
+ packetCounter = view.getUint32(offset, false);
43
+ offset += 4;
44
+ }
45
+ let extension;
46
+ if (extensionFlag) {
47
+ if (offset + 4 > buffer.length)
48
+ throw new Error("MMTP packet truncated in header extension");
49
+ const type = view.getUint16(offset, false);
50
+ const length = view.getUint16(offset + 2, false);
51
+ offset += 4;
52
+ if (offset + length > buffer.length)
53
+ throw new Error("MMTP packet truncated in header extension value");
54
+ extension = { type, data: buffer.subarray(offset, offset + length) };
55
+ offset += length;
56
+ }
57
+ return {
58
+ version,
59
+ packetType,
60
+ packetId,
61
+ timestamp,
62
+ sequenceNumber,
63
+ payloadLength: buffer.length - offset,
64
+ fecType,
65
+ packetCounterFlag,
66
+ packetCounter,
67
+ extensionFlag,
68
+ extension,
69
+ headerLength: offset,
70
+ rapFlag
71
+ };
72
+ }
73
+ function parseMPUHeader(payload) {
74
+ if (payload.length < MPU_HEADER_SIZE2) {
75
+ throw new Error(`Payload too small for MPU header: ${payload.length} bytes`);
76
+ }
77
+ const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
78
+ const payloadLength = view.getUint16(0, false);
79
+ const flagsByte = payload[2];
80
+ const timedFlag = flagsByte >> 3 & 1;
81
+ const fragmentationIndicator = flagsByte >> 1 & 3;
82
+ const aggregationFlag = (flagsByte & 1) !== 0;
83
+ const fragmentCounter = payload[3];
84
+ const mpuSequenceNumber = view.getUint32(4, false);
85
+ return {
86
+ mpuSequenceNumber,
87
+ fragmentationIndicator,
88
+ fragmentCounter,
89
+ timedFlag,
90
+ aggregationFlag,
91
+ payloadLength
92
+ };
93
+ }
94
+ function parseTimedMfuDuHeader(payload, offset) {
95
+ if (offset < 0 || offset + MFU_DU_HEADER_SIZE_TIMED > payload.length)
96
+ return null;
97
+ const view = new DataView(payload.buffer, payload.byteOffset + offset, MFU_DU_HEADER_SIZE_TIMED);
98
+ return {
99
+ movieFragmentSequenceNumber: view.getUint32(0, false),
100
+ sampleNumber: view.getUint32(4, false),
101
+ offset: view.getUint32(8, false),
102
+ priority: payload[offset + 12],
103
+ dependencyCounter: payload[offset + 13]
104
+ };
105
+ }
106
+ function resolveMfuPayloadLayout(mpu, fragmentType, payload) {
107
+ if (mpu.payloadLength < MPU_HEADER_REMAINDER_SIZE2) {
108
+ return {
109
+ rejection: {
110
+ gate: "mpu-payload-length-below-header-remainder",
111
+ detail: `payload_length=${mpu.payloadLength} < ${MPU_HEADER_REMAINDER_SIZE2}`
112
+ }
113
+ };
114
+ }
115
+ const declaredEnd = MPU_LENGTH_FIELD_SIZE2 + mpu.payloadLength;
116
+ if (declaredEnd > payload.length) {
117
+ return {
118
+ rejection: {
119
+ gate: "mpu-declared-end-exceeds-payload",
120
+ detail: `payload_length=${mpu.payloadLength} implies end=${declaredEnd}, payload is ${payload.length} bytes`
121
+ }
122
+ };
123
+ }
124
+ const hasHeader = fragmentType === 2 && mpu.fragmentationIndicator <= 1;
125
+ const mfuHeaderSize = mpu.timedFlag ? MFU_DU_HEADER_SIZE_TIMED : MFU_DU_HEADER_SIZE_NONTIMED;
126
+ const mfuOffset = MPU_HEADER_SIZE2 + (hasHeader ? mfuHeaderSize : 0);
127
+ if (mfuOffset >= declaredEnd) {
128
+ return {
129
+ rejection: {
130
+ gate: "mfu-offset-at-or-past-declared-end",
131
+ detail: `mfuOffset=${mfuOffset} >= declaredEnd=${declaredEnd}`
132
+ }
133
+ };
134
+ }
135
+ return {
136
+ layout: {
137
+ hasMfuDuHeader: hasHeader,
138
+ mfuOffset,
139
+ dataEnd: declaredEnd
48
140
  }
49
- /**
50
- * Add a fragment and return reassembled MFU if complete
51
- */
52
- addFragment(fragment) {
53
- this.stats.totalFragments++;
54
- const { mpuSequenceNumber, fragmentationIndicator, fragmentCounter } = fragment;
55
- // Handle complete MFU (FI=0)
56
- if (fragmentationIndicator === 0) {
57
- this.stats.reassembledMFUs++;
58
- return {
59
- mpuSequenceNumber,
60
- data: fragment.data,
61
- timestamp: fragment.timestamp,
62
- fragmentCount: 1,
63
- rapFlag: fragment.rapFlag,
64
- };
65
- }
66
- // Get or create fragment buffer for this MPU
67
- let fragments = __classPrivateFieldGet(this, _MFUReassembler_buffers, "f").get(mpuSequenceNumber);
68
- if (!fragments) {
69
- fragments = [];
70
- __classPrivateFieldGet(this, _MFUReassembler_buffers, "f").set(mpuSequenceNumber, fragments);
71
- __classPrivateFieldGet(this, _MFUReassembler_instances, "m", _MFUReassembler_setTimeout).call(this, mpuSequenceNumber);
72
- }
73
- // Check for duplicate fragment
74
- const existingIdx = fragments.findIndex((f) => f.fragmentCounter === fragmentCounter);
75
- if (existingIdx >= 0) {
76
- fragments[existingIdx] = fragment;
77
- }
78
- else {
79
- fragments.push(fragment);
80
- }
81
- // Track out-of-order delivery
82
- if (fragments.length > 1) {
83
- const lastFI = fragments[fragments.length - 2].fragmentationIndicator;
84
- if (fragmentationIndicator !== lastFI + 1) {
85
- this.stats.outOfOrderFragments++;
86
- }
141
+ };
142
+ }
143
+ function isEmptyOrExactAltaTrailer(payload, start, end) {
144
+ if (start === end)
145
+ return true;
146
+ if (start < 0 || end > payload.length || start + MMTP_EXT_TRAILER_HEADER_SIZE > end) {
147
+ return false;
148
+ }
149
+ const view = new DataView(payload.buffer, payload.byteOffset + start, end - start);
150
+ if (view.getUint16(0, false) !== MMTP_EXT_TYPE_ALTA_AUTH)
151
+ return false;
152
+ const authLength = view.getUint16(2, false);
153
+ return start + MMTP_EXT_TRAILER_HEADER_SIZE + authLength === end;
154
+ }
155
+ function resolveDeclaredTrailers(payload, declaredEnd, fecType, options) {
156
+ if (declaredEnd > payload.length)
157
+ return null;
158
+ if (options?.skipFecId) {
159
+ return isEmptyOrExactAltaTrailer(payload, declaredEnd, payload.length) ? { hasFecId: false } : null;
160
+ }
161
+ if (fecType === 1) {
162
+ const fecStart = payload.length - SOURCE_FEC_PAYLOAD_ID_SIZE;
163
+ if (fecStart < declaredEnd || !isEmptyOrExactAltaTrailer(payload, declaredEnd, fecStart)) {
164
+ return null;
165
+ }
166
+ return { hasFecId: true };
167
+ }
168
+ if (fecType !== 0)
169
+ return null;
170
+ if (isEmptyOrExactAltaTrailer(payload, declaredEnd, payload.length)) {
171
+ return { hasFecId: false };
172
+ }
173
+ return null;
174
+ }
175
+ function planMfuExtraction(mmtpPacket, options, parsedHeader) {
176
+ if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE2) {
177
+ return {
178
+ rejection: {
179
+ gate: "packet-shorter-than-mmtp-plus-mpu-header",
180
+ detail: `${mmtpPacket.length} bytes < ${MMTP_HEADER_SIZE + MPU_HEADER_SIZE2}`
181
+ }
182
+ };
183
+ }
184
+ let header;
185
+ if (parsedHeader) {
186
+ header = parsedHeader;
187
+ } else {
188
+ try {
189
+ header = parseMMTPHeader(mmtpPacket);
190
+ } catch (error) {
191
+ return {
192
+ rejection: {
193
+ gate: "mmtp-header-unparseable",
194
+ detail: error instanceof Error ? error.message : String(error)
87
195
  }
88
- // Enforce memory limits
89
- __classPrivateFieldGet(this, _MFUReassembler_instances, "m", _MFUReassembler_enforceMemoryLimit).call(this);
90
- // Try to reassemble
91
- return __classPrivateFieldGet(this, _MFUReassembler_instances, "m", _MFUReassembler_tryReassemble).call(this, mpuSequenceNumber);
196
+ };
92
197
  }
198
+ }
199
+ if (header.packetType !== PACKET_TYPE_MPU) {
200
+ return {
201
+ rejection: {
202
+ gate: "not-an-mpu-packet",
203
+ detail: `packetType=0x${header.packetType.toString(16).padStart(2, "0")}`
204
+ }
205
+ };
206
+ }
207
+ const payload = mmtpPacket.subarray(header.headerLength);
208
+ if (payload.length < MPU_HEADER_SIZE2) {
209
+ return {
210
+ rejection: {
211
+ gate: "payload-shorter-than-mpu-header",
212
+ detail: `${payload.length} bytes < ${MPU_HEADER_SIZE2}`
213
+ }
214
+ };
215
+ }
216
+ const mpu = parseMPUHeader(payload);
217
+ if (mpu.aggregationFlag) {
218
+ return {
219
+ rejection: { gate: "aggregation-flag-set", detail: "strict profile requires A=0" }
220
+ };
221
+ }
222
+ const fragmentType = payload[2] >> 4 & 15;
223
+ const layoutResult = resolveMfuPayloadLayout(mpu, fragmentType, payload);
224
+ if ("rejection" in layoutResult)
225
+ return layoutResult;
226
+ const { layout } = layoutResult;
227
+ const trailerLayout = resolveDeclaredTrailers(payload, layout.dataEnd, header.fecType, options);
228
+ if (!trailerLayout) {
229
+ if (!options?.skipFecId && header.fecType === 1) {
230
+ const observed = payload.length - layout.dataEnd;
231
+ if (observed !== SOURCE_FEC_PAYLOAD_ID_SIZE) {
232
+ return {
233
+ rejection: {
234
+ gate: "source-fec-payload-id-width-mismatch",
235
+ detail: `producer wrote a ${observed}-byte Source FEC Payload ID, ISO/IEC 23008-1 \xA7C.5.2 requires ${SOURCE_FEC_PAYLOAD_ID_SIZE} (payload=${payload.length}, declaredEnd=${layout.dataEnd})`
236
+ }
237
+ };
238
+ }
239
+ }
240
+ return {
241
+ rejection: {
242
+ gate: "declared-trailers-unresolved",
243
+ detail: `trailer bytes [${layout.dataEnd}, ${payload.length}) are neither empty nor an exact ALTA trailer (fecType=${header.fecType})`
244
+ }
245
+ };
246
+ }
247
+ return { plan: { header, payload, mpu, fragmentType, layout, trailerLayout } };
248
+ }
249
+ function extractMFUPayload(mmtpPacket, options) {
250
+ return extractPlannedMfu(mmtpPacket, planMfuExtraction(mmtpPacket, options));
251
+ }
252
+ function extractPlannedMfu(mmtpPacket, result) {
253
+ if ("rejection" in result)
254
+ return null;
255
+ const { header, payload, mpu, fragmentType, layout, trailerLayout } = result.plan;
256
+ const mfuDuHeaderPresent = layout.hasMfuDuHeader;
257
+ const mfuOffset = layout.mfuOffset;
258
+ const dataEnd = layout.dataEnd;
259
+ const mfuDuHeader = mfuDuHeaderPresent && mpu.timedFlag === 1 ? parseTimedMfuDuHeader(payload, MPU_HEADER_SIZE2) : null;
260
+ const itemId = mfuDuHeaderPresent && mpu.timedFlag === 0 ? new DataView(payload.buffer, payload.byteOffset + MPU_HEADER_SIZE2, 4).getUint32(0, false) : null;
261
+ const data = payload.subarray(mfuOffset, dataEnd);
262
+ const fecPayloadId = trailerLayout.hasFecId ? {
263
+ ssId: new DataView(mmtpPacket.buffer, mmtpPacket.byteOffset + mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE, SOURCE_FEC_PAYLOAD_ID_SIZE).getUint32(0, false)
264
+ } : null;
265
+ return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId };
266
+ }
267
+ function isFecRepairPacket(mmtpPacket) {
268
+ if (mmtpPacket.length < 1)
269
+ return false;
270
+ const fecType = mmtpPacket[0] >> 3 & 3;
271
+ return fecType >= 2;
272
+ }
273
+
274
+ // ../mmt-base/dist/ntp-short.js
275
+ var NTP_SHORT_BITS = 32;
276
+ var NTP_SHORT_MODULUS = 2 ** NTP_SHORT_BITS;
277
+ var NTP_SHORT_HALF_RANGE = 2 ** (NTP_SHORT_BITS - 1);
278
+
279
+ // ../mmt-base/dist/fec-block-profile.js
280
+ var FEC_BLOCK_PROFILES = Object.freeze([
281
+ "iso-ssbg1-v1"
282
+ ]);
283
+
284
+ // src/mfu-reassembler.ts
285
+ function foldDeliveryProvenance(verdict, source) {
286
+ if (verdict === "mixed") return "mixed";
287
+ if (source === "moq") return "mixed";
288
+ if (source === void 0) return "unknown";
289
+ return verdict;
290
+ }
291
+ function fragmentProvenance(fragment) {
292
+ if (fragment.deliveryProvenance !== void 0) return fragment.deliveryProvenance;
293
+ if (fragment.source === "moq") return "mixed";
294
+ if (fragment.source === "multicast") return "multicast";
295
+ return "unknown";
296
+ }
297
+ function deliveryProvenanceOf(fragments) {
298
+ if (fragments.length === 0) return "unknown";
299
+ let verdict = "multicast";
300
+ for (const fragment of fragments) {
301
+ const own = fragmentProvenance(fragment);
302
+ verdict = verdict === "mixed" || own === "mixed" ? "mixed" : verdict === "unknown" || own === "unknown" ? "unknown" : "multicast";
303
+ }
304
+ return verdict;
305
+ }
306
+ var MFU_REASSEMBLY_ERROR_CODES = [
307
+ "invalid-fragment-state",
308
+ "missing-data-unit-identity",
309
+ "missing-packet-sequence",
310
+ "conflicting-data-unit-identity",
311
+ "overlapping-data-unit",
312
+ "fragment-counter-mismatch",
313
+ "data-unit-identity-mismatch",
314
+ "offset-overflow",
315
+ "active-data-unit-limit",
316
+ "incomplete-terminal-fragment",
317
+ "ambiguous-continuation"
318
+ ];
319
+ var mfuReassemblyErrorCodes = new Set(MFU_REASSEMBLY_ERROR_CODES);
320
+ function isMfuReassemblyErrorCode(value) {
321
+ return typeof value === "string" && mfuReassemblyErrorCodes.has(value);
322
+ }
323
+ var MfuReassemblyError = class extends Error {
324
+ constructor(code, message) {
325
+ super(message);
326
+ this.code = code;
327
+ this.name = "MfuReassemblyError";
328
+ }
329
+ };
330
+ function isMfuReassemblyError(error) {
331
+ if (error instanceof MfuReassemblyError) return true;
332
+ if (typeof error !== "object" || error === null) return false;
333
+ return Reflect.get(error, "name") === "MfuReassemblyError" && isMfuReassemblyErrorCode(Reflect.get(error, "code"));
334
+ }
335
+ function assembledPacketSequences(fragments) {
336
+ const packetSequenceNumbers = [];
337
+ const repairedPacketSequenceNumbers = [];
338
+ const seen = /* @__PURE__ */ new Set();
339
+ const repaired = /* @__PURE__ */ new Set();
340
+ for (const fragment of fragments) {
341
+ const sequence = fragment.packetSequenceNumber;
342
+ if (typeof sequence !== "number" || !Number.isSafeInteger(sequence) || sequence < 0 || sequence > 4294967295) {
343
+ return {};
344
+ }
345
+ if (!seen.has(sequence)) {
346
+ seen.add(sequence);
347
+ packetSequenceNumbers.push(sequence);
348
+ }
349
+ if (fragment.recovered === true && !repaired.has(sequence)) {
350
+ repaired.add(sequence);
351
+ repairedPacketSequenceNumbers.push(sequence);
352
+ }
353
+ }
354
+ return { packetSequenceNumbers, repairedPacketSequenceNumbers };
355
+ }
356
+ function streamScope(fragment) {
357
+ if (fragment.catalogTrackName !== void 0) return ["catalog", fragment.catalogTrackName];
358
+ if (fragment.packetId !== void 0) return ["packet", fragment.packetId];
359
+ return ["unspecified", null];
360
+ }
361
+ function isU32(value) {
362
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 4294967295;
363
+ }
364
+ function dataUnitAddress(fragment, packetSequenceNumber) {
365
+ return JSON.stringify([
366
+ ...streamScope(fragment),
367
+ fragment.mpuSequenceNumber,
368
+ packetSequenceNumber + fragment.fragmentCounter >>> 0
369
+ ]);
370
+ }
371
+ function hasAnySampleIdentityField(fragment) {
372
+ return fragment.movieFragmentSequenceNumber !== void 0 || fragment.sampleNumber !== void 0 || fragment.sampleOffset !== void 0;
373
+ }
374
+ function hasSampleIdentity(fragment) {
375
+ return isU32(fragment.movieFragmentSequenceNumber) && fragment.movieFragmentSequenceNumber > 0 && isU32(fragment.sampleNumber) && fragment.sampleNumber > 0 && isU32(fragment.sampleOffset);
376
+ }
377
+ function hasValidFragmentHeader(fragment) {
378
+ const { fragmentationIndicator: fi, fragmentCounter: counter } = fragment;
379
+ if (!Number.isInteger(fi) || fi < 0 || fi > 3) return false;
380
+ if (!Number.isInteger(counter) || counter < 0 || counter > 255) return false;
381
+ return fi === 0 || fi === 3 ? counter === 0 : counter > 0;
382
+ }
383
+ function fragmentCounterOrder(fragment) {
384
+ return 255 - fragment.fragmentCounter;
385
+ }
386
+ function objectKey(fragment) {
387
+ const scope = streamScope(fragment);
388
+ if (fragment.itemId !== void 0) {
389
+ return JSON.stringify([...scope, fragment.mpuSequenceNumber, "item", fragment.itemId]);
390
+ }
391
+ if (hasSampleIdentity(fragment)) {
392
+ return JSON.stringify([
393
+ ...scope,
394
+ fragment.mpuSequenceNumber,
395
+ "sample",
396
+ fragment.movieFragmentSequenceNumber,
397
+ fragment.sampleNumber,
398
+ // The DU's authored base offset (moqcast #37): sibling subsample DUs
399
+ // of one sample get distinct keys instead of colliding. Only ever
400
+ // called with the unmutated FI=1 (or FI=0) fragment, whose
401
+ // sampleOffset is that authored base -- never the accumulating
402
+ // per-continuation value written by #associateDataUnit.
403
+ fragment.sampleOffset
404
+ ]);
405
+ }
406
+ return JSON.stringify([...scope, fragment.mpuSequenceNumber, "unidentified"]);
407
+ }
408
+ function ordersByOffset(fragments) {
409
+ return fragments.every((fragment) => hasSampleIdentity(fragment));
410
+ }
411
+ function checkedSampleEnd(offset, byteLength) {
412
+ const end = offset + byteLength;
413
+ return Number.isSafeInteger(end) && end <= 4294967295 ? end : null;
414
+ }
415
+ var COMPLETED_ADDRESS_WINDOW_MULTIPLE = 2;
416
+ var EVICTION_GUARD_MAX_BUFFERED = 4;
417
+ var EVICTION_GUARD_DATA_UNITS = 12;
418
+ function parkedRunHoldsASlot(hasOpenRun, fragmentCount) {
419
+ return !hasOpenRun && fragmentCount > 0;
420
+ }
421
+ var MFUReassembler = class {
422
+ constructor(options) {
423
+ // Fragment buffers and active runs use the authored MFU DU identity.
424
+ // Continuations may wait in one counter-indexed queue per data-unit address
425
+ // until FI=1 supplies that identity. The address is transport association
426
+ // only; the object key stays the intrinsic DU identity.
427
+ this.#buffers = /* @__PURE__ */ new Map();
428
+ this.#timeouts = /* @__PURE__ */ new Map();
429
+ this.#activeDataUnits = /* @__PURE__ */ new Map();
430
+ this.#pendingContinuations = /* @__PURE__ */ new Map();
431
+ this.#emitted = /* @__PURE__ */ new Set();
432
+ // same sample keys, so FEC-recovered duplicate heads are dropped
433
+ this.#emittedOrder = [];
434
+ // Addresses of emitted data units. A continuation addressed to one of them
435
+ // is a late copy of a fragment the run already consumed (FEC recovery
436
+ // supplied it first), so it is rejected as stale rather than parked as a
437
+ // headless data unit that would hold a slot until reset. Bounded at
438
+ // COMPLETED_ADDRESS_WINDOW_MULTIPLE x maxBuffered, the same window the Rust
439
+ // engine keeps, so both engines forget an address after the same number of
440
+ // completions. (#emitted is a separate, per-engine window: this engine keeps
441
+ // maxBuffered sample keys where Rust keeps 2x; the completed set does not
442
+ // derive from it.)
443
+ this.#completed = /* @__PURE__ */ new Set();
444
+ this.#completedOrder = [];
93
445
  /**
94
- * Flush all buffered fragments
446
+ * Monotonic admission ordinal. Counts slot admissions, never reads a clock,
447
+ * so eviction order survives on hosts that run `timeoutMs: null`.
95
448
  */
96
- flush() {
97
- for (const mpuSeq of __classPrivateFieldGet(this, _MFUReassembler_buffers, "f").keys()) {
98
- __classPrivateFieldGet(this, _MFUReassembler_instances, "m", _MFUReassembler_clearTimeout).call(this, mpuSeq);
449
+ this.#nextAdmissionSeq = 0;
450
+ /** Statistics for debugging */
451
+ this.stats = {
452
+ totalFragments: 0,
453
+ reassembledMFUs: 0,
454
+ rejectedFragments: 0,
455
+ timedOutMFUs: 0,
456
+ evictedMemoryLimit: 0,
457
+ outOfOrderFragments: 0
458
+ };
459
+ if (options.timeoutMs !== null && (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0)) {
460
+ throw new Error("[MFUReassembler] timeoutMs must be > 0 (derive from frame duration or FEC config)");
461
+ }
462
+ const maxBuffered = options.maxBuffered ?? 100;
463
+ if (!Number.isInteger(maxBuffered) || maxBuffered <= 0) {
464
+ throw new Error("[MFUReassembler] maxBuffered must be a positive integer");
465
+ }
466
+ this.#timeoutMs = options.timeoutMs;
467
+ this.#maxBuffered = maxBuffered;
468
+ }
469
+ #buffers;
470
+ #timeouts;
471
+ #activeDataUnits;
472
+ #pendingContinuations;
473
+ #emitted;
474
+ #emittedOrder;
475
+ #completed;
476
+ #completedOrder;
477
+ #timeoutMs;
478
+ #maxBuffered;
479
+ #nextAdmissionSeq;
480
+ /** Update the expiry policy for all retained and future incomplete MFUs. */
481
+ set timeoutMs(ms) {
482
+ if (ms !== null && (!Number.isFinite(ms) || ms <= 0)) {
483
+ throw new Error("[MFUReassembler] timeoutMs must be > 0 (derive from frame duration or FEC config)");
484
+ }
485
+ this.#timeoutMs = ms;
486
+ const now = Date.now();
487
+ for (const [bufferKey, state] of this.#timeouts) {
488
+ if (state.timeoutId !== void 0) clearTimeout(state.timeoutId);
489
+ delete state.timeoutId;
490
+ if (ms !== null) {
491
+ state.timeoutId = setTimeout(() => this.#onTimeout(bufferKey), Math.max(0, state.startedAt + ms - now));
492
+ }
493
+ }
494
+ for (const address of this.#pendingContinuations.keys()) {
495
+ this.#clearPendingTimeout(address);
496
+ this.#armHeadlessPendingTimeout(address);
497
+ }
498
+ }
499
+ get timeoutMs() {
500
+ return this.#timeoutMs;
501
+ }
502
+ /**
503
+ * Add a fragment and return reassembled MFU if complete
504
+ */
505
+ addFragment(fragment) {
506
+ this.stats.totalFragments++;
507
+ if (!hasValidFragmentHeader(fragment)) {
508
+ this.#reject("invalid-fragment-state", "invalid MFU fragmentation indicator or counter");
509
+ }
510
+ if (fragment.itemId !== void 0 && hasAnySampleIdentityField(fragment)) {
511
+ this.#reject("conflicting-data-unit-identity", "MFU carries timed and non-timed DU identities");
512
+ }
513
+ const { mpuSequenceNumber, fragmentationIndicator } = fragment;
514
+ if (fragmentationIndicator === 0) {
515
+ const sampleIdentity = hasSampleIdentity(fragment);
516
+ const hasSampleFields = hasAnySampleIdentityField(fragment);
517
+ const hasItemIdentity = fragment.itemId !== void 0;
518
+ if (!sampleIdentity && !hasItemIdentity) {
519
+ this.#reject("missing-data-unit-identity", "complete MFU has no intrinsic DU identity");
520
+ }
521
+ if (hasSampleFields && !sampleIdentity) {
522
+ this.#reject("missing-data-unit-identity", "complete MFU has an incomplete sample identity");
523
+ }
524
+ if (hasItemIdentity && !isU32(fragment.itemId)) {
525
+ this.#reject("missing-data-unit-identity", "complete MFU has an invalid Item_ID");
526
+ }
527
+ this.stats.reassembledMFUs++;
528
+ return {
529
+ mpuSequenceNumber,
530
+ packetSequenceNumber: fragment.packetSequenceNumber,
531
+ ...assembledPacketSequences([fragment]),
532
+ data: fragment.data,
533
+ timestamp: fragment.timestamp,
534
+ fragmentCount: 1,
535
+ rapFlag: fragment.rapFlag,
536
+ priority: fragment.priority,
537
+ sampleNumber: fragment.sampleNumber,
538
+ movieFragmentSequenceNumber: fragment.movieFragmentSequenceNumber,
539
+ sampleOffset: fragment.sampleOffset,
540
+ source: fragment.source,
541
+ deliveryProvenance: deliveryProvenanceOf([fragment]),
542
+ catalogTrackName: fragment.catalogTrackName
543
+ };
544
+ }
545
+ const sequence = fragment.packetSequenceNumber;
546
+ if (!isU32(sequence)) {
547
+ this.#reject("missing-packet-sequence", "fragmented MFU has no MMTP packet sequence number");
548
+ }
549
+ const address = dataUnitAddress(fragment, sequence);
550
+ if (fragmentationIndicator !== 1) {
551
+ const open = this.#openRunAt(address);
552
+ if (open === void 0) {
553
+ if (this.#completed.has(address)) {
554
+ this.#reject("fragment-counter-mismatch", "MFU continuation names a data unit that already completed");
99
555
  }
100
- __classPrivateFieldGet(this, _MFUReassembler_buffers, "f").clear();
101
- __classPrivateFieldGet(this, _MFUReassembler_timeouts, "f").clear();
556
+ this.#parkContinuation(address, fragment);
557
+ return null;
558
+ }
559
+ const [runKey2, active] = open;
560
+ if (active.nextFragmentCounter > fragment.fragmentCounter) {
561
+ this.#parkContinuation(address, fragment);
562
+ return null;
563
+ }
564
+ if (active.nextFragmentCounter < fragment.fragmentCounter) {
565
+ this.#reject("fragment-counter-mismatch", "MFU continuation is behind the authored fragment-counter countdown");
566
+ }
567
+ const result2 = this.#addAssociatedFragment(fragment, runKey2, address);
568
+ if (result2 !== null) return result2;
569
+ return this.#drainPendingContinuations(address, runKey2);
102
570
  }
103
- /**
104
- * Get number of pending MFUs
105
- */
106
- get pendingCount() {
107
- return __classPrivateFieldGet(this, _MFUReassembler_buffers, "f").size;
571
+ const runKey = objectKey(fragment);
572
+ if ((hasSampleIdentity(fragment) || isU32(fragment.itemId)) && this.#emitted.has(runKey)) {
573
+ return null;
108
574
  }
109
- /**
110
- * Get current buffer status
111
- */
112
- getStatus() {
113
- let pendingFragments = 0;
114
- for (const fragments of __classPrivateFieldGet(this, _MFUReassembler_buffers, "f").values()) {
115
- pendingFragments += fragments.length;
575
+ if (this.#activeDataUnits.has(runKey)) {
576
+ this.#reject("overlapping-data-unit", "fragmented MFU overlaps an active data unit");
577
+ }
578
+ if (this.#openRunAt(address) !== void 0) {
579
+ this.#reject("overlapping-data-unit", "fragmented MFU opens on an address held by an open data unit");
580
+ }
581
+ const pending = this.#pendingContinuations.get(address);
582
+ if (pending !== void 0 && [...pending.fragments.keys()].some((counter) => counter >= fragment.fragmentCounter)) {
583
+ this.#reject(
584
+ "fragment-counter-mismatch",
585
+ "queued continuation counter is not below the authored first-fragment counter"
586
+ );
587
+ }
588
+ const result = this.#addAssociatedFragment(fragment, runKey, address, pending?.startedAt);
589
+ this.#clearPendingTimeout(address);
590
+ if (result !== null) return result;
591
+ return this.#drainPendingContinuations(address, runKey);
592
+ }
593
+ #addAssociatedFragment(fragment, runKey, address, startedAt) {
594
+ const association = this.#associateDataUnit(fragment, runKey, address);
595
+ const { bufferKey, first, terminal } = association;
596
+ let fragments = this.#buffers.get(bufferKey);
597
+ if (first) {
598
+ if (fragments !== void 0) {
599
+ this.#reject("overlapping-data-unit", "intrinsic data-unit buffer already exists");
600
+ }
601
+ fragments = [];
602
+ this.#buffers.set(bufferKey, fragments);
603
+ } else if (fragments === void 0) {
604
+ throw new Error("[MFUReassembler] active data unit has no fragment buffer");
605
+ }
606
+ if (first) this.#setTimeout(bufferKey, startedAt);
607
+ fragments.push(fragment);
608
+ if (!terminal) return null;
609
+ const result = this.#tryReassemble(bufferKey);
610
+ this.#activeDataUnits.delete(runKey);
611
+ if (result === null) {
612
+ this.#clearTimeout(bufferKey);
613
+ this.#buffers.delete(bufferKey);
614
+ this.#reject("incomplete-terminal-fragment", "validated terminal fragment did not complete its data unit");
615
+ }
616
+ this.#markCompleted(address);
617
+ return result;
618
+ }
619
+ #parkContinuation(address, fragment) {
620
+ let pending = this.#pendingContinuations.get(address);
621
+ const open = this.#openRunAt(address);
622
+ const alreadyCharged = open !== void 0 || parkedRunHoldsASlot(open !== void 0, pending?.fragments.size ?? 0);
623
+ if (!alreadyCharged && this.#incompleteRunCount() >= this.#maxBuffered) {
624
+ if (!this.#evictLeastRecentlyAdmitted()) {
625
+ this.#reject("active-data-unit-limit", "active MFU data-unit limit reached");
626
+ }
627
+ }
628
+ let armAfterInsert = false;
629
+ if (pending === void 0) {
630
+ pending = {
631
+ fragments: /* @__PURE__ */ new Map(),
632
+ startedAt: Date.now(),
633
+ admissionSeq: open !== void 0 ? open[1].admissionSeq : this.#nextAdmissionSeq++
634
+ };
635
+ this.#pendingContinuations.set(address, pending);
636
+ armAfterInsert = true;
637
+ } else if (!alreadyCharged) {
638
+ pending.admissionSeq = this.#nextAdmissionSeq++;
639
+ pending.startedAt = Date.now();
640
+ this.#clearPendingTimeout(address);
641
+ armAfterInsert = true;
642
+ }
643
+ if (pending.fragments.has(fragment.fragmentCounter)) {
644
+ this.#reject("fragment-counter-mismatch", "duplicate queued continuation counter would replace retained media");
645
+ }
646
+ pending.fragments.set(fragment.fragmentCounter, fragment);
647
+ if (armAfterInsert) this.#armHeadlessPendingTimeout(address);
648
+ }
649
+ #drainPendingContinuations(address, runKey) {
650
+ while (true) {
651
+ const active = this.#activeDataUnits.get(runKey);
652
+ const pending = this.#pendingContinuations.get(address);
653
+ if (active === void 0) {
654
+ if (pending?.fragments.size === 0) this.#deletePendingRun(address);
655
+ return null;
656
+ }
657
+ const fragment = pending?.fragments.get(active.nextFragmentCounter);
658
+ if (fragment === void 0) {
659
+ if (pending?.fragments.size === 0) this.#deletePendingRun(address);
660
+ return null;
661
+ }
662
+ pending.fragments.delete(active.nextFragmentCounter);
663
+ if (pending.fragments.size === 0) this.#deletePendingRun(address);
664
+ const result = this.#addAssociatedFragment(fragment, runKey, address);
665
+ if (result !== null) {
666
+ return result;
667
+ }
668
+ }
669
+ }
670
+ /** Parked data units that hold a `maxBuffered` slot of their own. Membership
671
+ * is `parkedRunHoldsASlot`, the same rule #evictLeastRecentlyAdmitted offers
672
+ * as candidates, so nothing can be counted here that eviction cannot reclaim.
673
+ *
674
+ * One site, two callers — `#incompleteRunCount` and `pendingCount` — mirroring
675
+ * Rust's `unopened_parked_run_count`, which `incomplete_run_count` and
676
+ * `buffered_count` both delegate to. Both callers used to inline this loop
677
+ * verbatim, so converging the *rule* moved one copy and left the other as the
678
+ * divergence (Ally's review of #400). The rule can no longer drift now that
679
+ * every site routes through `parkedRunHoldsASlot`; keeping the loop itself at
680
+ * one site is what stops the scaffolding drifting the next time this question
681
+ * grows an argument. */
682
+ #unopenedParkedRunCount() {
683
+ let count = 0;
684
+ for (const [address, pending] of this.#pendingContinuations) {
685
+ if (parkedRunHoldsASlot(this.#openRunAt(address) !== void 0, pending.fragments.size)) {
686
+ count++;
687
+ }
688
+ }
689
+ return count;
690
+ }
691
+ /** Every open run plus every headless parked data unit that holds a slot,
692
+ * one slot each. This is the quantity maxBuffered bounds. The parked term is
693
+ * `#unopenedParkedRunCount`, whose membership is `parkedRunHoldsASlot` — the
694
+ * same rule #evictLeastRecentlyAdmitted offers as candidates, so nothing can
695
+ * be counted here that eviction cannot reclaim. */
696
+ #incompleteRunCount() {
697
+ return this.#activeDataUnits.size + this.#unopenedParkedRunCount();
698
+ }
699
+ /**
700
+ * Drop the incomplete data unit admitted longest ago so a newly arriving one
701
+ * can take its slot. Selection is by monotonic admission ordinal, never by
702
+ * wall clock, so this is a bounded-capacity replacement policy and not a
703
+ * timeout: it behaves identically on hosts that never drive `cleanup_timeouts`
704
+ * (`timeoutMs: null`). Only incomplete data units are candidates — a data
705
+ * unit that completed has already left both maps.
706
+ *
707
+ * Returns false only if no slot is charged, which cannot happen at the bound
708
+ * (`maxBuffered` is a positive integer); admission then falls back to the
709
+ * fail-closed `active-data-unit-limit` reject, which stays a stable host code.
710
+ * That holds because the candidate set here is built from the same
711
+ * `parkedRunHoldsASlot` rule `#incompleteRunCount` counts by, so reaching the
712
+ * bound implies at least one candidate. Filtering one and not the other would
713
+ * make the claim false: a queue counted but not offered charges a slot no
714
+ * eviction can reclaim, and the receiver refuses every later data unit
715
+ * permanently.
716
+ */
717
+ #evictLeastRecentlyAdmitted() {
718
+ let victimAddress;
719
+ let victimSeq = Number.POSITIVE_INFINITY;
720
+ for (const [, active] of this.#activeDataUnits) {
721
+ if (active.admissionSeq < victimSeq) {
722
+ victimSeq = active.admissionSeq;
723
+ victimAddress = active.address;
724
+ }
725
+ }
726
+ for (const [address, pending] of this.#pendingContinuations) {
727
+ if (!parkedRunHoldsASlot(this.#openRunAt(address) !== void 0, pending.fragments.size)) {
728
+ continue;
729
+ }
730
+ if (pending.admissionSeq < victimSeq) {
731
+ victimSeq = pending.admissionSeq;
732
+ victimAddress = address;
733
+ }
734
+ }
735
+ if (victimAddress === void 0) return false;
736
+ this.#releaseSlot(victimAddress);
737
+ this.stats.evictedMemoryLimit++;
738
+ return true;
739
+ }
740
+ /**
741
+ * Release every form of state one address holds: its open run, that run's
742
+ * buffered fragments and deadline, and its continuation queue. The address is
743
+ * deliberately not marked completed — the data unit never emitted, so a later
744
+ * fragment of it may open a fresh slot rather than being rejected as stale.
745
+ */
746
+ #releaseSlot(address) {
747
+ const open = this.#openRunAt(address);
748
+ if (open !== void 0) {
749
+ const [runKey, active] = open;
750
+ this.#activeDataUnits.delete(runKey);
751
+ this.#clearTimeout(active.bufferKey);
752
+ this.#buffers.delete(active.bufferKey);
753
+ }
754
+ this.#deletePendingRun(address);
755
+ }
756
+ #reject(code, message) {
757
+ this.stats.rejectedFragments++;
758
+ throw new MfuReassemblyError(code, message);
759
+ }
760
+ #associateDataUnit(fragment, runKey, address) {
761
+ const fi = fragment.fragmentationIndicator;
762
+ if (fi === 1) {
763
+ const parked = this.#pendingContinuations.get(address);
764
+ const inheritsParkedSlot = parkedRunHoldsASlot(
765
+ this.#openRunAt(address) !== void 0,
766
+ parked?.fragments.size ?? 0
767
+ );
768
+ const sampleIdentity = hasSampleIdentity(fragment);
769
+ const hasSampleFields = hasAnySampleIdentityField(fragment);
770
+ const hasItemIdentity = fragment.itemId !== void 0;
771
+ if (sampleIdentity && hasItemIdentity) {
772
+ this.#reject("conflicting-data-unit-identity", "fragmented MFU carries timed and non-timed DU identities");
773
+ }
774
+ if (!sampleIdentity && !hasItemIdentity) {
775
+ this.#reject("missing-data-unit-identity", "first fragment has no intrinsic DU identity");
776
+ }
777
+ if (hasSampleFields && !sampleIdentity) {
778
+ this.#reject("missing-data-unit-identity", "first fragment has an incomplete sample identity");
779
+ }
780
+ if (hasItemIdentity && !isU32(fragment.itemId)) {
781
+ this.#reject("missing-data-unit-identity", "first fragment has an invalid Item_ID");
782
+ }
783
+ const bufferKey = objectKey(fragment);
784
+ if (this.#buffers.has(bufferKey)) {
785
+ this.#reject("overlapping-data-unit", "intrinsic data-unit buffer already exists");
786
+ }
787
+ let sampleEnd = null;
788
+ if (sampleIdentity) {
789
+ sampleEnd = checkedSampleEnd(fragment.sampleOffset, fragment.data.byteLength);
790
+ if (sampleEnd === null) this.#reject("offset-overflow", "sample byte offset overflow");
791
+ }
792
+ if (!inheritsParkedSlot && this.#incompleteRunCount() >= this.#maxBuffered) {
793
+ if (!this.#evictLeastRecentlyAdmitted()) {
794
+ this.#reject("active-data-unit-limit", "active MFU data-unit limit reached");
116
795
  }
117
- return {
118
- bufferedMFUs: __classPrivateFieldGet(this, _MFUReassembler_buffers, "f").size,
119
- pendingFragments,
120
- stats: { ...this.stats },
796
+ }
797
+ const admissionSeq = inheritsParkedSlot ? parked.admissionSeq : this.#nextAdmissionSeq++;
798
+ let active2;
799
+ if (sampleIdentity) {
800
+ active2 = {
801
+ address,
802
+ kind: "sample",
803
+ bufferKey,
804
+ nextFragmentCounter: fragment.fragmentCounter - 1,
805
+ admissionSeq,
806
+ movieFragmentSequenceNumber: fragment.movieFragmentSequenceNumber,
807
+ sampleNumber: fragment.sampleNumber,
808
+ nextOffset: sampleEnd,
809
+ ...fragment.catalogTrackName === void 0 ? {} : { catalogTrackName: fragment.catalogTrackName }
121
810
  };
811
+ } else {
812
+ active2 = {
813
+ address,
814
+ kind: "item",
815
+ bufferKey,
816
+ nextFragmentCounter: fragment.fragmentCounter - 1,
817
+ admissionSeq,
818
+ itemId: fragment.itemId,
819
+ ...fragment.catalogTrackName === void 0 ? {} : { catalogTrackName: fragment.catalogTrackName }
820
+ };
821
+ }
822
+ this.#activeDataUnits.set(objectKey(fragment), active2);
823
+ return { bufferKey, first: true, terminal: false };
122
824
  }
123
- }
124
- _MFUReassembler_buffers = new WeakMap(), _MFUReassembler_timeouts = new WeakMap(), _MFUReassembler_timeoutMs = new WeakMap(), _MFUReassembler_maxBuffered = new WeakMap(), _MFUReassembler_instances = new WeakSet(), _MFUReassembler_tryReassemble = function _MFUReassembler_tryReassemble(mpuSequenceNumber) {
125
- const fragments = __classPrivateFieldGet(this, _MFUReassembler_buffers, "f").get(mpuSequenceNumber);
825
+ const active = this.#activeDataUnits.get(runKey);
826
+ if (active === void 0) {
827
+ throw new Error("[MFUReassembler] continuation was not parked before association");
828
+ }
829
+ if (fragment.fragmentCounter !== active.nextFragmentCounter) {
830
+ this.#reject(
831
+ "fragment-counter-mismatch",
832
+ "MFU continuation does not follow the authored fragment-counter countdown"
833
+ );
834
+ }
835
+ if (active.kind === "sample") {
836
+ if (fragment.itemId !== void 0) {
837
+ this.#reject("data-unit-identity-mismatch", "sample continuation carries an Item_ID");
838
+ }
839
+ if (hasAnySampleIdentityField(fragment) && (!hasSampleIdentity(fragment) || fragment.movieFragmentSequenceNumber !== active.movieFragmentSequenceNumber || fragment.sampleNumber !== active.sampleNumber || fragment.sampleOffset !== active.nextOffset)) {
840
+ this.#reject("data-unit-identity-mismatch", "sample continuation identity does not match");
841
+ }
842
+ const nextOffset = checkedSampleEnd(active.nextOffset, fragment.data.byteLength);
843
+ if (nextOffset === null) this.#reject("offset-overflow", "sample byte offset overflow");
844
+ fragment.movieFragmentSequenceNumber = active.movieFragmentSequenceNumber;
845
+ fragment.sampleNumber = active.sampleNumber;
846
+ fragment.sampleOffset = active.nextOffset;
847
+ if (fi === 2) {
848
+ active.nextOffset = nextOffset;
849
+ active.nextFragmentCounter = fragment.fragmentCounter - 1;
850
+ }
851
+ } else {
852
+ if (hasAnySampleIdentityField(fragment) || fragment.itemId !== void 0 && fragment.itemId !== active.itemId) {
853
+ this.#reject("data-unit-identity-mismatch", "Item_ID continuation identity does not match");
854
+ }
855
+ fragment.itemId = active.itemId;
856
+ if (fi === 2) active.nextFragmentCounter = fragment.fragmentCounter - 1;
857
+ }
858
+ return {
859
+ bufferKey: active.bufferKey,
860
+ first: false,
861
+ terminal: fi === 3
862
+ };
863
+ }
864
+ #tryReassemble(bufferKey) {
865
+ const fragments = this.#buffers.get(bufferKey);
126
866
  if (!fragments || fragments.length === 0) {
127
- return null;
867
+ return null;
868
+ }
869
+ const sorted = [...fragments].sort((a, b) => this.#orderValue(a) - this.#orderValue(b));
870
+ if (sorted[0].fragmentationIndicator !== 1 || sorted[sorted.length - 1].fragmentationIndicator !== 3) {
871
+ return null;
128
872
  }
129
- // Sort by fragment counter
130
- fragments.sort((a, b) => a.fragmentCounter - b.fragmentCounter);
131
- const firstFI = fragments[0].fragmentationIndicator;
132
- const lastFI = fragments[fragments.length - 1].fragmentationIndicator;
133
- // Need first (FI=1) and last (FI=3) fragments
134
- if (firstFI !== 1 || lastFI !== 3) {
873
+ if (sorted.slice(1, -1).some((fragment) => fragment.fragmentationIndicator !== 2)) {
874
+ return null;
875
+ }
876
+ for (let i = 1; i < sorted.length; i++) {
877
+ if (this.#orderValue(sorted[i]) !== this.#orderValue(sorted[i - 1]) + 1) {
135
878
  return null;
879
+ }
136
880
  }
137
- // Verify middle fragments have FI=2
138
- for (let i = 1; i < fragments.length - 1; i++) {
139
- if (fragments[i].fragmentationIndicator !== 2) {
140
- return null;
881
+ if (ordersByOffset(sorted)) {
882
+ for (let i = 1; i < sorted.length; i++) {
883
+ const previous = sorted[i - 1];
884
+ if (sorted[i].sampleOffset !== previous.sampleOffset + previous.data.byteLength) {
885
+ return null;
141
886
  }
887
+ }
142
888
  }
143
- // Reassemble: concatenate all fragment data
889
+ fragments.splice(0, fragments.length, ...sorted);
890
+ return this.#assemble(bufferKey, fragments);
891
+ }
892
+ #assemble(bufferKey, fragments) {
144
893
  const totalSize = fragments.reduce((sum, f) => sum + f.data.length, 0);
145
894
  const data = new Uint8Array(totalSize);
146
895
  let offset = 0;
147
896
  for (const fragment of fragments) {
148
- data.set(fragment.data, offset);
149
- offset += fragment.data.length;
897
+ data.set(fragment.data, offset);
898
+ offset += fragment.data.length;
150
899
  }
151
- // Cleanup
152
- __classPrivateFieldGet(this, _MFUReassembler_instances, "m", _MFUReassembler_clearTimeout).call(this, mpuSequenceNumber);
153
- __classPrivateFieldGet(this, _MFUReassembler_buffers, "f").delete(mpuSequenceNumber);
900
+ this.#clearTimeout(bufferKey);
901
+ this.#buffers.delete(bufferKey);
902
+ this.#markEmitted(bufferKey);
154
903
  this.stats.reassembledMFUs++;
155
904
  return {
156
- mpuSequenceNumber,
157
- data,
158
- timestamp: fragments[0].timestamp,
159
- fragmentCount: fragments.length,
160
- rapFlag: fragments[0].rapFlag,
905
+ mpuSequenceNumber: fragments[0].mpuSequenceNumber,
906
+ packetSequenceNumber: fragments[0].packetSequenceNumber,
907
+ ...assembledPacketSequences(fragments),
908
+ data,
909
+ timestamp: fragments[0].timestamp,
910
+ fragmentCount: fragments.length,
911
+ rapFlag: fragments[0].rapFlag,
912
+ priority: fragments.find((f) => f.priority !== void 0)?.priority,
913
+ sampleNumber: fragments[0].sampleNumber,
914
+ movieFragmentSequenceNumber: fragments[0].movieFragmentSequenceNumber,
915
+ sampleOffset: fragments[0].sampleOffset,
916
+ source: fragments[0].source,
917
+ deliveryProvenance: deliveryProvenanceOf(fragments),
918
+ catalogTrackName: fragments[0].catalogTrackName
161
919
  };
162
- }, _MFUReassembler_setTimeout = function _MFUReassembler_setTimeout(mpuSequenceNumber) {
163
- const timeoutId = setTimeout(() => {
164
- __classPrivateFieldGet(this, _MFUReassembler_instances, "m", _MFUReassembler_onTimeout).call(this, mpuSequenceNumber);
165
- }, __classPrivateFieldGet(this, _MFUReassembler_timeoutMs, "f"));
166
- __classPrivateFieldGet(this, _MFUReassembler_timeouts, "f").set(mpuSequenceNumber, timeoutId);
167
- }, _MFUReassembler_clearTimeout = function _MFUReassembler_clearTimeout(mpuSequenceNumber) {
168
- const timeoutId = __classPrivateFieldGet(this, _MFUReassembler_timeouts, "f").get(mpuSequenceNumber);
169
- if (timeoutId !== undefined) {
170
- clearTimeout(timeoutId);
171
- __classPrivateFieldGet(this, _MFUReassembler_timeouts, "f").delete(mpuSequenceNumber);
172
- }
173
- }, _MFUReassembler_onTimeout = function _MFUReassembler_onTimeout(mpuSequenceNumber) {
174
- const fragments = __classPrivateFieldGet(this, _MFUReassembler_buffers, "f").get(mpuSequenceNumber);
175
- if (!fragments)
176
- return;
920
+ }
921
+ #setTimeout(bufferKey, startedAt = Date.now()) {
922
+ const state = { startedAt };
923
+ if (this.#timeoutMs !== null) {
924
+ state.timeoutId = setTimeout(
925
+ () => this.#onTimeout(bufferKey),
926
+ Math.max(0, startedAt + this.#timeoutMs - Date.now())
927
+ );
928
+ }
929
+ this.#timeouts.set(bufferKey, state);
930
+ }
931
+ #clearPendingTimeout(address) {
932
+ const pending = this.#pendingContinuations.get(address);
933
+ if (pending?.timeoutId !== void 0) clearTimeout(pending.timeoutId);
934
+ if (pending !== void 0) {
935
+ delete pending.timeoutId;
936
+ }
937
+ }
938
+ /**
939
+ * A queue whose run closed without completing is headless again and expires
940
+ * from its own first arrival, exactly as it would had the head never come.
941
+ *
942
+ * An empty queue is not armed: it holds no data unit that could fail to
943
+ * complete, so there is nothing for a deadline to expire. Every arming site
944
+ * reaches this function, which takes that test through `parkedRunHoldsASlot`
945
+ * rather than filtering on the open run alone, which is how this engine
946
+ * reaches Rust's answer: its
947
+ * `next_timeout_at` derives candidate deadlines from the queue's live
948
+ * entries, so an empty queue yields none (BLO-32420).
949
+ */
950
+ #armHeadlessPendingTimeout(address) {
951
+ const pending = this.#pendingContinuations.get(address);
952
+ if (pending === void 0 || pending.timeoutId !== void 0 || this.#timeoutMs === null) return;
953
+ if (!parkedRunHoldsASlot(this.#openRunAt(address) !== void 0, pending.fragments.size)) return;
954
+ pending.timeoutId = setTimeout(
955
+ () => this.#onPendingTimeout(address),
956
+ Math.max(0, pending.startedAt + this.#timeoutMs - Date.now())
957
+ );
958
+ }
959
+ #deletePendingRun(address) {
960
+ this.#clearPendingTimeout(address);
961
+ this.#pendingContinuations.delete(address);
962
+ }
963
+ #onPendingTimeout(address) {
964
+ const pending = this.#pendingContinuations.get(address);
965
+ if (pending === void 0) return;
966
+ if (!parkedRunHoldsASlot(this.#openRunAt(address) !== void 0, pending.fragments.size)) {
967
+ this.#clearPendingTimeout(address);
968
+ return;
969
+ }
177
970
  this.stats.timedOutMFUs++;
178
- __classPrivateFieldGet(this, _MFUReassembler_buffers, "f").delete(mpuSequenceNumber);
179
- __classPrivateFieldGet(this, _MFUReassembler_timeouts, "f").delete(mpuSequenceNumber);
180
- }, _MFUReassembler_enforceMemoryLimit = function _MFUReassembler_enforceMemoryLimit() {
181
- if (__classPrivateFieldGet(this, _MFUReassembler_buffers, "f").size <= __classPrivateFieldGet(this, _MFUReassembler_maxBuffered, "f"))
182
- return;
183
- const sequences = Array.from(__classPrivateFieldGet(this, _MFUReassembler_buffers, "f").keys()).sort((a, b) => a - b);
184
- const toRemove = sequences.slice(0, sequences.length - __classPrivateFieldGet(this, _MFUReassembler_maxBuffered, "f"));
185
- for (const mpuSeq of toRemove) {
186
- __classPrivateFieldGet(this, _MFUReassembler_instances, "m", _MFUReassembler_clearTimeout).call(this, mpuSeq);
187
- __classPrivateFieldGet(this, _MFUReassembler_buffers, "f").delete(mpuSeq);
188
- this.stats.timedOutMFUs++;
971
+ console.warn(
972
+ `[MfuReassembler] timeout: run=${address} fragments=${pending.fragments.size} (continuations awaiting first fragment, discarded)`
973
+ );
974
+ this.#pendingContinuations.delete(address);
975
+ }
976
+ #clearTimeout(bufferKey) {
977
+ const state = this.#timeouts.get(bufferKey);
978
+ if (state !== void 0) {
979
+ if (state.timeoutId !== void 0) clearTimeout(state.timeoutId);
980
+ this.#timeouts.delete(bufferKey);
981
+ }
982
+ }
983
+ #markEmitted(bufferKey) {
984
+ if (this.#emitted.has(bufferKey)) return;
985
+ this.#emitted.add(bufferKey);
986
+ this.#emittedOrder.push(bufferKey);
987
+ while (this.#emittedOrder.length > this.#maxBuffered) {
988
+ const oldest = this.#emittedOrder.shift();
989
+ if (oldest !== void 0) this.#emitted.delete(oldest);
990
+ }
991
+ }
992
+ #markCompleted(address) {
993
+ if (this.#completed.has(address)) return;
994
+ this.#completed.add(address);
995
+ this.#completedOrder.push(address);
996
+ const cap = this.#maxBuffered * COMPLETED_ADDRESS_WINDOW_MULTIPLE;
997
+ while (this.#completedOrder.length > cap) {
998
+ const oldest = this.#completedOrder.shift();
999
+ if (oldest !== void 0) this.#completed.delete(oldest);
1000
+ }
1001
+ }
1002
+ #orderValue(fragment) {
1003
+ return fragmentCounterOrder(fragment);
1004
+ }
1005
+ /** Close the run behind an expired buffer. Early arrivals parked under its
1006
+ * address stay, now headless, and expire from their own arrival. */
1007
+ #removeActiveDataUnitForBuffer(bufferKey) {
1008
+ for (const [objectKey2, active] of this.#activeDataUnits) {
1009
+ if (active.bufferKey === bufferKey) {
1010
+ this.#activeDataUnits.delete(objectKey2);
1011
+ this.#armHeadlessPendingTimeout(active.address);
1012
+ }
1013
+ }
1014
+ }
1015
+ /** The one open run holding this address, with its object key. Admission
1016
+ * rejects a second head on an open address, so at most one run can match. */
1017
+ #openRunAt(address) {
1018
+ for (const entry of this.#activeDataUnits) {
1019
+ if (entry[1].address === address) return entry;
1020
+ }
1021
+ return void 0;
1022
+ }
1023
+ #onTimeout(bufferKey) {
1024
+ const fragments = this.#buffers.get(bufferKey);
1025
+ if (!fragments || fragments.length === 0) {
1026
+ this.#buffers.delete(bufferKey);
1027
+ this.#timeouts.delete(bufferKey);
1028
+ this.#removeActiveDataUnitForBuffer(bufferKey);
1029
+ this.stats.timedOutMFUs++;
1030
+ console.warn(`[MfuReassembler] timeout: key=${bufferKey} fragments=0 (no data buffered)`);
1031
+ return;
1032
+ }
1033
+ this.stats.timedOutMFUs++;
1034
+ console.warn(`[MfuReassembler] timeout: key=${bufferKey} fragments=${fragments.length} (partial, discarded)`);
1035
+ this.#buffers.delete(bufferKey);
1036
+ this.#timeouts.delete(bufferKey);
1037
+ this.#removeActiveDataUnitForBuffer(bufferKey);
1038
+ }
1039
+ /**
1040
+ * Flush all buffered fragments
1041
+ */
1042
+ flush() {
1043
+ for (const key of this.#buffers.keys()) {
1044
+ this.#clearTimeout(key);
1045
+ }
1046
+ this.#buffers.clear();
1047
+ this.#timeouts.clear();
1048
+ this.#activeDataUnits.clear();
1049
+ for (const address of this.#pendingContinuations.keys()) {
1050
+ this.#clearPendingTimeout(address);
1051
+ }
1052
+ this.#pendingContinuations.clear();
1053
+ this.#emitted.clear();
1054
+ this.#emittedOrder.length = 0;
1055
+ this.#completed.clear();
1056
+ this.#completedOrder.length = 0;
1057
+ }
1058
+ /** Drop one catalog stream's incomplete and dedup state. */
1059
+ resetStream(catalogTrackName) {
1060
+ const belongsToStream = (key) => {
1061
+ try {
1062
+ const decoded = JSON.parse(key);
1063
+ return Array.isArray(decoded) && decoded[0] === "catalog" && decoded[1] === catalogTrackName;
1064
+ } catch {
1065
+ return false;
1066
+ }
1067
+ };
1068
+ const discarded = /* @__PURE__ */ new Set();
1069
+ for (const [key, fragments] of this.#buffers) {
1070
+ if (fragments.some((fragment) => fragment.catalogTrackName === catalogTrackName)) {
1071
+ discarded.add(key);
1072
+ }
1073
+ }
1074
+ for (const [objectKey2, active] of this.#activeDataUnits) {
1075
+ if (active.catalogTrackName === catalogTrackName) {
1076
+ discarded.add(active.bufferKey);
1077
+ this.#activeDataUnits.delete(objectKey2);
1078
+ }
1079
+ }
1080
+ for (const [address, pending] of this.#pendingContinuations) {
1081
+ if ([...pending.fragments.values()].some((fragment) => fragment.catalogTrackName === catalogTrackName) || belongsToStream(address))
1082
+ this.#deletePendingRun(address);
1083
+ }
1084
+ for (const key of this.#emitted) {
1085
+ if (belongsToStream(key)) discarded.add(key);
1086
+ }
1087
+ for (const key of discarded) {
1088
+ this.#clearTimeout(key);
1089
+ this.#buffers.delete(key);
1090
+ this.#emitted.delete(key);
1091
+ }
1092
+ this.#emittedOrder = this.#emittedOrder.filter((key) => !discarded.has(key));
1093
+ for (const address of this.#completed) {
1094
+ if (belongsToStream(address)) this.#completed.delete(address);
1095
+ }
1096
+ this.#completedOrder = this.#completedOrder.filter((address) => this.#completed.has(address));
1097
+ }
1098
+ /** Reset all state — flush buffers and zero stats */
1099
+ reset() {
1100
+ this.flush();
1101
+ this.stats.totalFragments = 0;
1102
+ this.stats.reassembledMFUs = 0;
1103
+ this.stats.rejectedFragments = 0;
1104
+ this.stats.timedOutMFUs = 0;
1105
+ this.stats.evictedMemoryLimit = 0;
1106
+ this.stats.outOfOrderFragments = 0;
1107
+ }
1108
+ /**
1109
+ * Get number of pending MFUs: engine buffers plus every headless parked data
1110
+ * unit that holds a slot.
1111
+ *
1112
+ * This is not one of the four charging sites, but it shares their second term,
1113
+ * so it takes the same rule — by delegating to `#unopenedParkedRunCount`, the
1114
+ * one site that applies it. Rust's public `buffered_count()` is
1115
+ * `inner.buffered_count() + unopened_parked_run_count()`, and that count is
1116
+ * `parked_run_holds_a_slot` — so filtering here on the open run alone would
1117
+ * report a phantom slot for an empty published queue in TypeScript and not in
1118
+ * Rust, out of a *host-observable* counter (`getStatus().bufferedMFUs`). Same
1119
+ * state, two numbers, decided by which engine the host loaded: the divergence
1120
+ * class this file exists to close, so the public counters have to agree too.
1121
+ *
1122
+ * Distinct from `#incompleteRunCount`, which sums `#activeDataUnits` rather
1123
+ * than `#buffers`. Those are different quantities by design and are not being
1124
+ * merged; only the parked term they share is required to agree, which is what
1125
+ * routing both through one method makes structural rather than conventional.
1126
+ */
1127
+ get pendingCount() {
1128
+ return this.#buffers.size + this.#unopenedParkedRunCount();
1129
+ }
1130
+ /**
1131
+ * Get current buffer status
1132
+ */
1133
+ getStatus() {
1134
+ let pendingFragments = 0;
1135
+ for (const fragments of this.#buffers.values()) {
1136
+ pendingFragments += fragments.length;
1137
+ }
1138
+ for (const pending of this.#pendingContinuations.values()) {
1139
+ pendingFragments += pending.fragments.size;
189
1140
  }
190
- };
191
- /**
192
- * Create MFU fragment from MMTP packet for reassembly
193
- */
194
- export function createFragment(mmtpPacket) {
195
- const { header, payload } = unwrapMMTP(mmtpPacket);
196
- const mpuHeader = parseMPUHeader(payload);
197
- const { mpuSequenceNumber, fragmentationIndicator, fragmentCounter } = mpuHeader;
198
- // MFU DU header is present only for FI = 0 (complete) or FI = 1 (first)
199
- const hasMfuHeader = fragmentationIndicator === 0 || fragmentationIndicator === 1;
200
- const headerSize = MPU_HEADER_SIZE + (hasMfuHeader ? MFU_DU_HEADER_SIZE : 0);
201
- if (payload.length < headerSize) {
202
- throw new Error(`Payload too small for headers: ${payload.length} bytes (need ${headerSize})`);
203
- }
204
- const data = payload.subarray(headerSize);
205
1141
  return {
206
- fragmentationIndicator,
207
- mpuSequenceNumber,
208
- fragmentCounter,
209
- data,
210
- timestamp: header.timestamp,
211
- receivedAt: typeof performance !== 'undefined' ? performance.now() : Date.now(),
212
- rapFlag: header.rapFlag,
1142
+ bufferedMFUs: this.pendingCount,
1143
+ pendingFragments,
1144
+ pendingSamples: this.#pendingDebugSamples(),
1145
+ stats: { ...this.stats }
213
1146
  };
214
- }
215
- /**
216
- * Process MMTP packet with fragment reassembly
217
- */
218
- export function processMMTPWithReassembly(mmtpPacket, reassembler) {
219
- // Skip FEC repair packets
220
- if (isFecRepairPacket(mmtpPacket)) {
221
- return null;
1147
+ }
1148
+ #pendingDebugSamples() {
1149
+ const out = [];
1150
+ for (const [key, fragments] of this.#buffers) {
1151
+ if (fragments.length === 0) continue;
1152
+ const sorted = [...fragments].sort((a, b) => this.#orderValue(a) - this.#orderValue(b));
1153
+ const fiCounts = [0, 0, 0, 0];
1154
+ let minCounter = Infinity;
1155
+ let maxCounter = -Infinity;
1156
+ let minOffset = Infinity;
1157
+ let maxOffset = -Infinity;
1158
+ for (const fragment of sorted) {
1159
+ fiCounts[fragment.fragmentationIndicator] = (fiCounts[fragment.fragmentationIndicator] ?? 0) + 1;
1160
+ minCounter = Math.min(minCounter, fragment.fragmentCounter);
1161
+ maxCounter = Math.max(maxCounter, fragment.fragmentCounter);
1162
+ if (typeof fragment.sampleOffset === "number") {
1163
+ minOffset = Math.min(minOffset, fragment.sampleOffset);
1164
+ maxOffset = Math.max(maxOffset, fragment.sampleOffset);
1165
+ }
1166
+ }
1167
+ const point = (fragment) => ({
1168
+ fi: fragment.fragmentationIndicator,
1169
+ counter: fragment.fragmentCounter,
1170
+ order: this.#orderValue(fragment),
1171
+ ...typeof fragment.packetSequenceNumber === "number" ? { seq: fragment.packetSequenceNumber } : {},
1172
+ ...typeof fragment.sampleOffset === "number" ? { offset: fragment.sampleOffset } : {}
1173
+ });
1174
+ const summary = {
1175
+ key,
1176
+ fragments: sorted.length,
1177
+ fi0: fiCounts[0] ?? 0,
1178
+ fi1: fiCounts[1] ?? 0,
1179
+ fi2: fiCounts[2] ?? 0,
1180
+ fi3: fiCounts[3] ?? 0,
1181
+ firstFi: sorted[0].fragmentationIndicator,
1182
+ lastFi: sorted[sorted.length - 1].fragmentationIndicator,
1183
+ firstOrder: this.#orderValue(sorted[0]),
1184
+ lastOrder: this.#orderValue(sorted[sorted.length - 1]),
1185
+ minCounter,
1186
+ maxCounter,
1187
+ movieFragmentSequenceNumber: sorted.find((f) => f.movieFragmentSequenceNumber !== void 0)?.movieFragmentSequenceNumber,
1188
+ sampleNumber: sorted.find((f) => f.sampleNumber !== void 0)?.sampleNumber,
1189
+ mpuSequenceNumber: sorted[0].mpuSequenceNumber,
1190
+ sortedHead: sorted.slice(0, 6).map(point),
1191
+ sortedTail: sorted.slice(-6).map(point)
1192
+ };
1193
+ if (minOffset !== Infinity) summary.minOffset = minOffset;
1194
+ if (maxOffset !== -Infinity) summary.maxOffset = maxOffset;
1195
+ out.push(summary);
1196
+ if (out.length >= 12) break;
222
1197
  }
223
- const fragment = createFragment(mmtpPacket);
224
- return reassembler.addFragment(fragment);
1198
+ return out;
1199
+ }
1200
+ };
1201
+ function createFragment(mmtpPacket) {
1202
+ const extraction = extractMFUPayload(mmtpPacket);
1203
+ if (!extraction) throw new Error("Unable to extract MFU payload from MMTP packet");
1204
+ const { header, mpu, data, mfuDuHeader, itemId } = extraction;
1205
+ return {
1206
+ fragmentationIndicator: mpu.fragmentationIndicator,
1207
+ mpuSequenceNumber: mpu.mpuSequenceNumber,
1208
+ fragmentCounter: mpu.fragmentCounter,
1209
+ data,
1210
+ timestamp: header.timestamp,
1211
+ rapFlag: header.rapFlag,
1212
+ sampleNumber: mfuDuHeader?.sampleNumber,
1213
+ movieFragmentSequenceNumber: mfuDuHeader?.movieFragmentSequenceNumber,
1214
+ sampleOffset: mfuDuHeader?.offset,
1215
+ priority: mfuDuHeader?.priority,
1216
+ itemId: itemId ?? void 0,
1217
+ packetSequenceNumber: header.sequenceNumber,
1218
+ packetId: header.packetId
1219
+ };
225
1220
  }
226
- // =============================================================================
227
- // Inline Tests (vitest)
228
- // =============================================================================
229
- if (import.meta.vitest) {
230
- const { describe, test, expect } = import.meta.vitest;
231
- describe('MFUReassembler', () => {
232
- test('should return complete MFU for FI=0', () => {
233
- const reassembler = new MFUReassembler();
234
- const result = reassembler.addFragment({
235
- fragmentationIndicator: 0,
236
- mpuSequenceNumber: 1,
237
- fragmentCounter: 0,
238
- data: new Uint8Array([1, 2, 3, 4]),
239
- timestamp: 1000n,
240
- receivedAt: 0,
241
- rapFlag: true,
242
- });
243
- expect(result).not.toBeNull();
244
- expect(result?.data).toEqual(new Uint8Array([1, 2, 3, 4]));
245
- expect(result?.fragmentCount).toBe(1);
246
- expect(result?.rapFlag).toBe(true);
247
- });
248
- test('should buffer and reassemble 2 fragments', () => {
249
- const reassembler = new MFUReassembler();
250
- // First fragment (FI=1)
251
- const result1 = reassembler.addFragment({
252
- fragmentationIndicator: 1,
253
- mpuSequenceNumber: 1,
254
- fragmentCounter: 0,
255
- data: new Uint8Array([1, 2]),
256
- timestamp: 1000n,
257
- receivedAt: 0,
258
- rapFlag: true,
259
- });
260
- expect(result1).toBeNull();
261
- // Last fragment (FI=3)
262
- const result2 = reassembler.addFragment({
263
- fragmentationIndicator: 3,
264
- mpuSequenceNumber: 1,
265
- fragmentCounter: 1,
266
- data: new Uint8Array([3, 4]),
267
- timestamp: 1000n,
268
- receivedAt: 0,
269
- rapFlag: false,
270
- });
271
- expect(result2).not.toBeNull();
272
- expect(result2?.data).toEqual(new Uint8Array([1, 2, 3, 4]));
273
- expect(result2?.fragmentCount).toBe(2);
274
- expect(result2?.rapFlag).toBe(true); // Should use first fragment's rapFlag
275
- });
276
- test('should handle out-of-order fragments', () => {
277
- const reassembler = new MFUReassembler();
278
- // Last fragment first (FI=3)
279
- reassembler.addFragment({
280
- fragmentationIndicator: 3,
281
- mpuSequenceNumber: 1,
282
- fragmentCounter: 1,
283
- data: new Uint8Array([3, 4]),
284
- timestamp: 1000n,
285
- receivedAt: 0,
286
- rapFlag: false,
287
- });
288
- // Then first fragment (FI=1)
289
- const result = reassembler.addFragment({
290
- fragmentationIndicator: 1,
291
- mpuSequenceNumber: 1,
292
- fragmentCounter: 0,
293
- data: new Uint8Array([1, 2]),
294
- timestamp: 1000n,
295
- receivedAt: 0,
296
- rapFlag: true,
297
- });
298
- expect(result).not.toBeNull();
299
- expect(result?.data).toEqual(new Uint8Array([1, 2, 3, 4]));
300
- expect(result?.rapFlag).toBe(true);
1221
+ function processMMTPWithReassembly(mmtpPacket, reassembler) {
1222
+ if (isFecRepairPacket(mmtpPacket)) {
1223
+ return null;
1224
+ }
1225
+ const fragment = createFragment(mmtpPacket);
1226
+ return reassembler.addFragment(fragment);
1227
+ }
1228
+ if (void 0) {
1229
+ const { describe, test, expect, vi } = void 0;
1230
+ const expectRejection = (call, code) => {
1231
+ let rejection;
1232
+ try {
1233
+ call();
1234
+ } catch (error) {
1235
+ rejection = error;
1236
+ }
1237
+ expect(rejection).toBeInstanceOf(MfuReassemblyError);
1238
+ expect(rejection.code).toBe(code);
1239
+ };
1240
+ const testPacketSequence = (mpu, counter, run = 0) => (mpu * 1e3 + run - counter + 4294967296) % 4294967296;
1241
+ describe("MFUReassembler", () => {
1242
+ test("should return complete MFU for FI=0", () => {
1243
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1244
+ const result = reassembler.addFragment({
1245
+ fragmentationIndicator: 0,
1246
+ mpuSequenceNumber: 1,
1247
+ fragmentCounter: 0,
1248
+ itemId: 1,
1249
+ data: new Uint8Array([1, 2, 3, 4]),
1250
+ timestamp: 1000n,
1251
+ rapFlag: true
1252
+ });
1253
+ expect(result).not.toBeNull();
1254
+ expect(result?.data).toEqual(new Uint8Array([1, 2, 3, 4]));
1255
+ expect(result?.fragmentCount).toBe(1);
1256
+ expect(result?.rapFlag).toBe(true);
1257
+ });
1258
+ test("rejects a complete MFU without a valid sample identity", () => {
1259
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1260
+ const complete = (identity) => ({
1261
+ fragmentationIndicator: 0,
1262
+ mpuSequenceNumber: 1,
1263
+ fragmentCounter: 0,
1264
+ data: new Uint8Array([1]),
1265
+ timestamp: 1000n,
1266
+ rapFlag: true,
1267
+ ...identity
1268
+ });
1269
+ expectRejection(() => reassembler.addFragment(complete({})), "missing-data-unit-identity");
1270
+ expectRejection(
1271
+ () => reassembler.addFragment(
1272
+ complete({
1273
+ movieFragmentSequenceNumber: 2,
1274
+ sampleNumber: 1
1275
+ })
1276
+ ),
1277
+ "missing-data-unit-identity"
1278
+ );
1279
+ });
1280
+ test("accepts a complete subsample MFU with a nonzero authored offset", () => {
1281
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1282
+ const result = reassembler.addFragment({
1283
+ fragmentationIndicator: 0,
1284
+ mpuSequenceNumber: 1,
1285
+ fragmentCounter: 0,
1286
+ movieFragmentSequenceNumber: 2,
1287
+ sampleNumber: 1,
1288
+ sampleOffset: 4,
1289
+ data: new Uint8Array([1]),
1290
+ timestamp: 1000n,
1291
+ rapFlag: true
1292
+ });
1293
+ expect(result).not.toBeNull();
1294
+ expect(result?.sampleOffset).toBe(4);
1295
+ });
1296
+ test("accepts a fragmented subsample DU whose first fragment has a nonzero authored offset", () => {
1297
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1298
+ expect(
1299
+ reassembler.addFragment({
1300
+ fragmentationIndicator: 1,
1301
+ mpuSequenceNumber: 2,
1302
+ fragmentCounter: 1,
1303
+ movieFragmentSequenceNumber: 2,
1304
+ sampleNumber: 1,
1305
+ sampleOffset: 4,
1306
+ packetSequenceNumber: testPacketSequence(2, 1),
1307
+ data: new Uint8Array([161]),
1308
+ timestamp: 1000n,
1309
+ rapFlag: true
1310
+ })
1311
+ ).toBeNull();
1312
+ expect(reassembler.pendingCount).toBe(1);
1313
+ expect(reassembler.getStatus().pendingFragments).toBe(1);
1314
+ const out = reassembler.addFragment({
1315
+ fragmentationIndicator: 3,
1316
+ mpuSequenceNumber: 2,
1317
+ fragmentCounter: 0,
1318
+ data: new Uint8Array([162]),
1319
+ timestamp: 1001n,
1320
+ rapFlag: false,
1321
+ packetSequenceNumber: testPacketSequence(2, 0)
1322
+ });
1323
+ expect(out?.data).toEqual(new Uint8Array([161, 162]));
1324
+ expect(out?.sampleOffset).toBe(4);
1325
+ });
1326
+ test("a mid-data-unit packet sequence gap strands the data unit", () => {
1327
+ const reassembler = new MFUReassembler({ timeoutMs: null, maxBuffered: 10 });
1328
+ expect(
1329
+ reassembler.addFragment({
1330
+ fragmentationIndicator: 1,
1331
+ mpuSequenceNumber: 7,
1332
+ fragmentCounter: 2,
1333
+ movieFragmentSequenceNumber: 9,
1334
+ sampleNumber: 17,
1335
+ sampleOffset: 0,
1336
+ packetSequenceNumber: 100,
1337
+ data: new Uint8Array([161]),
1338
+ timestamp: 1100n,
1339
+ rapFlag: true
1340
+ })
1341
+ ).toBeNull();
1342
+ const interleaved = reassembler.addFragment({
1343
+ fragmentationIndicator: 0,
1344
+ mpuSequenceNumber: 7,
1345
+ fragmentCounter: 0,
1346
+ movieFragmentSequenceNumber: 9,
1347
+ sampleNumber: 18,
1348
+ sampleOffset: 0,
1349
+ packetSequenceNumber: 101,
1350
+ data: new Uint8Array([255]),
1351
+ timestamp: 1101n,
1352
+ rapFlag: true
1353
+ });
1354
+ expect(interleaved?.data).toEqual(new Uint8Array([255]));
1355
+ expect(
1356
+ reassembler.addFragment({
1357
+ fragmentationIndicator: 2,
1358
+ mpuSequenceNumber: 7,
1359
+ fragmentCounter: 1,
1360
+ packetSequenceNumber: 102,
1361
+ data: new Uint8Array([162]),
1362
+ timestamp: 1102n,
1363
+ rapFlag: false
1364
+ })
1365
+ ).toBeNull();
1366
+ const output = reassembler.addFragment({
1367
+ fragmentationIndicator: 3,
1368
+ mpuSequenceNumber: 7,
1369
+ fragmentCounter: 0,
1370
+ packetSequenceNumber: 103,
1371
+ data: new Uint8Array([163]),
1372
+ timestamp: 1103n,
1373
+ rapFlag: false
1374
+ });
1375
+ expect(output).toBeNull();
1376
+ expect(reassembler.stats.reassembledMFUs).toBe(1);
1377
+ expect(reassembler.stats.rejectedFragments).toBe(0);
1378
+ expect(reassembler.stats.evictedMemoryLimit).toBe(0);
1379
+ expect(reassembler.getStatus().bufferedMFUs).toBe(2);
1380
+ expect(reassembler.getStatus().pendingFragments).toBe(3);
1381
+ });
1382
+ test("sibling subsample DUs of one sample reassemble independently by offset", () => {
1383
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1384
+ expect(
1385
+ reassembler.addFragment({
1386
+ mpuSequenceNumber: 3,
1387
+ movieFragmentSequenceNumber: 5,
1388
+ sampleNumber: 9,
1389
+ fragmentationIndicator: 1,
1390
+ fragmentCounter: 1,
1391
+ sampleOffset: 0,
1392
+ data: new Uint8Array([161]),
1393
+ timestamp: 2000n,
1394
+ rapFlag: true,
1395
+ packetSequenceNumber: testPacketSequence(3, 1, 0)
1396
+ })
1397
+ ).toBeNull();
1398
+ expect(
1399
+ reassembler.addFragment({
1400
+ mpuSequenceNumber: 3,
1401
+ movieFragmentSequenceNumber: 5,
1402
+ sampleNumber: 9,
1403
+ fragmentationIndicator: 1,
1404
+ fragmentCounter: 2,
1405
+ sampleOffset: 3,
1406
+ data: new Uint8Array([177]),
1407
+ timestamp: 2001n,
1408
+ rapFlag: false,
1409
+ packetSequenceNumber: testPacketSequence(3, 1, 1)
1410
+ })
1411
+ ).toBeNull();
1412
+ expect(reassembler.pendingCount).toBe(2);
1413
+ const first = reassembler.addFragment({
1414
+ mpuSequenceNumber: 3,
1415
+ fragmentationIndicator: 3,
1416
+ fragmentCounter: 0,
1417
+ data: new Uint8Array([162]),
1418
+ timestamp: 2000n,
1419
+ rapFlag: false,
1420
+ packetSequenceNumber: testPacketSequence(3, 0, 0)
1421
+ });
1422
+ expect(first?.data).toEqual(new Uint8Array([161, 162]));
1423
+ expect(first?.sampleOffset).toBe(0);
1424
+ expect(
1425
+ reassembler.addFragment({
1426
+ mpuSequenceNumber: 3,
1427
+ fragmentationIndicator: 2,
1428
+ fragmentCounter: 1,
1429
+ data: new Uint8Array([178]),
1430
+ timestamp: 2001n,
1431
+ rapFlag: false,
1432
+ packetSequenceNumber: testPacketSequence(3, 0, 1)
1433
+ })
1434
+ ).toBeNull();
1435
+ const second = reassembler.addFragment({
1436
+ mpuSequenceNumber: 3,
1437
+ fragmentationIndicator: 3,
1438
+ fragmentCounter: 0,
1439
+ data: new Uint8Array([179]),
1440
+ timestamp: 2001n,
1441
+ rapFlag: false,
1442
+ packetSequenceNumber: testPacketSequence(3, 0, 2)
1443
+ });
1444
+ expect(second?.data).toEqual(new Uint8Array([177, 178, 179]));
1445
+ expect(second?.sampleOffset).toBe(3);
1446
+ expect(reassembler.pendingCount).toBe(0);
1447
+ });
1448
+ test("isolates colliding fragment coordinates by catalog track identity", () => {
1449
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1450
+ const fragment = (catalogTrackName, fragmentationIndicator, fragmentCounter, byte) => ({
1451
+ itemId: 1,
1452
+ fragmentationIndicator,
1453
+ fragmentCounter,
1454
+ mpuSequenceNumber: 1,
1455
+ data: new Uint8Array([byte]),
1456
+ timestamp: 1000n,
1457
+ rapFlag: fragmentationIndicator === 1,
1458
+ catalogTrackName,
1459
+ packetSequenceNumber: testPacketSequence(1, fragmentCounter)
1460
+ });
1461
+ expect(reassembler.addFragment(fragment("video/720p", 1, 1, 113))).toBeNull();
1462
+ expect(reassembler.addFragment(fragment("video/540p", 1, 1, 81))).toBeNull();
1463
+ const high = reassembler.addFragment(fragment("video/720p", 3, 0, 114));
1464
+ const low = reassembler.addFragment(fragment("video/540p", 3, 0, 82));
1465
+ expect(high?.data).toEqual(new Uint8Array([113, 114]));
1466
+ expect(high?.catalogTrackName).toBe("video/720p");
1467
+ expect(low?.data).toEqual(new Uint8Array([81, 82]));
1468
+ expect(low?.catalogTrackName).toBe("video/540p");
1469
+ });
1470
+ test("rejects an overlapping non-timed item without replacing the active run", () => {
1471
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1472
+ const fragment = (itemId, fragmentationIndicator, fragmentCounter, byte) => ({
1473
+ itemId,
1474
+ fragmentationIndicator,
1475
+ fragmentCounter,
1476
+ mpuSequenceNumber: 1,
1477
+ data: new Uint8Array([byte]),
1478
+ timestamp: 1000n,
1479
+ rapFlag: false,
1480
+ packetSequenceNumber: testPacketSequence(1, fragmentCounter)
1481
+ });
1482
+ expect(reassembler.addFragment(fragment(10, 1, 1, 16))).toBeNull();
1483
+ expectRejection(() => reassembler.addFragment(fragment(20, 1, 1, 32)), "overlapping-data-unit");
1484
+ expect(reassembler.pendingCount).toBe(1);
1485
+ expect(reassembler.addFragment(fragment(10, 3, 0, 17))?.data).toEqual(new Uint8Array([16, 17]));
1486
+ expect(reassembler.pendingCount).toBe(0);
1487
+ });
1488
+ test("associates continuations by authored state when packet timestamps change", () => {
1489
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1490
+ const fragment = (itemId, fragmentationIndicator, fragmentCounter, byte, timestamp) => ({
1491
+ ...itemId === void 0 ? {} : { itemId },
1492
+ fragmentationIndicator,
1493
+ fragmentCounter,
1494
+ mpuSequenceNumber: 1,
1495
+ data: new Uint8Array([byte]),
1496
+ timestamp,
1497
+ rapFlag: false,
1498
+ packetSequenceNumber: testPacketSequence(1, fragmentCounter)
1499
+ });
1500
+ expect(reassembler.addFragment(fragment(10, 1, 2, 16, 1000n))).toBeNull();
1501
+ expect(reassembler.addFragment(fragment(void 0, 2, 1, 17, 1001n))).toBeNull();
1502
+ const out = reassembler.addFragment(fragment(void 0, 3, 0, 18, 1002n));
1503
+ expect(out?.data).toEqual(new Uint8Array([16, 17, 18]));
1504
+ expect(out?.timestamp).toBe(1000n);
1505
+ });
1506
+ test("should buffer and reassemble 2 fragments", () => {
1507
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1508
+ const result1 = reassembler.addFragment({
1509
+ fragmentationIndicator: 1,
1510
+ mpuSequenceNumber: 1,
1511
+ fragmentCounter: 1,
1512
+ itemId: 1,
1513
+ data: new Uint8Array([1, 2]),
1514
+ timestamp: 1000n,
1515
+ rapFlag: true,
1516
+ packetSequenceNumber: testPacketSequence(1, 1)
1517
+ });
1518
+ expect(result1).toBeNull();
1519
+ const result2 = reassembler.addFragment({
1520
+ fragmentationIndicator: 3,
1521
+ mpuSequenceNumber: 1,
1522
+ fragmentCounter: 0,
1523
+ data: new Uint8Array([3, 4]),
1524
+ timestamp: 1000n,
1525
+ rapFlag: false,
1526
+ packetSequenceNumber: testPacketSequence(1, 0)
1527
+ });
1528
+ expect(result2).not.toBeNull();
1529
+ expect(result2?.data).toEqual(new Uint8Array([1, 2, 3, 4]));
1530
+ expect(result2?.fragmentCount).toBe(2);
1531
+ expect(result2?.rapFlag).toBe(true);
1532
+ });
1533
+ test("buffers a terminal continuation until its authored first fragment arrives", () => {
1534
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1535
+ expect(
1536
+ reassembler.addFragment({
1537
+ fragmentationIndicator: 3,
1538
+ mpuSequenceNumber: 1,
1539
+ fragmentCounter: 0,
1540
+ data: new Uint8Array([3, 4]),
1541
+ timestamp: 1000n,
1542
+ rapFlag: false,
1543
+ packetSequenceNumber: testPacketSequence(1, 0)
1544
+ })
1545
+ ).toBeNull();
1546
+ expect(reassembler.pendingCount).toBe(1);
1547
+ const completed = reassembler.addFragment({
1548
+ fragmentationIndicator: 1,
1549
+ mpuSequenceNumber: 1,
1550
+ fragmentCounter: 1,
1551
+ itemId: 1,
1552
+ data: new Uint8Array([1, 2]),
1553
+ timestamp: 1000n,
1554
+ rapFlag: true,
1555
+ packetSequenceNumber: testPacketSequence(1, 1)
1556
+ });
1557
+ expect(completed?.data).toEqual(new Uint8Array([1, 2, 3, 4]));
1558
+ expect(completed?.rapFlag).toBe(true);
1559
+ expect(reassembler.pendingCount).toBe(0);
1560
+ });
1561
+ test("clock-free mode keeps an admitted MFU until completion", async () => {
1562
+ const reassembler = new MFUReassembler({ timeoutMs: null });
1563
+ reassembler.addFragment({
1564
+ fragmentationIndicator: 1,
1565
+ mpuSequenceNumber: 1,
1566
+ fragmentCounter: 1,
1567
+ itemId: 1,
1568
+ data: new Uint8Array([1, 2]),
1569
+ timestamp: 1000n,
1570
+ rapFlag: true,
1571
+ packetSequenceNumber: testPacketSequence(1, 1)
1572
+ });
1573
+ await new Promise((resolve) => setTimeout(resolve, 5));
1574
+ const result = reassembler.addFragment({
1575
+ fragmentationIndicator: 3,
1576
+ mpuSequenceNumber: 1,
1577
+ fragmentCounter: 0,
1578
+ data: new Uint8Array([3, 4]),
1579
+ timestamp: 1000n,
1580
+ rapFlag: false,
1581
+ packetSequenceNumber: testPacketSequence(1, 0)
1582
+ });
1583
+ expect(result).not.toBeNull();
1584
+ expect(result?.data).toEqual(new Uint8Array([1, 2, 3, 4]));
1585
+ expect(reassembler.stats.timedOutMFUs).toBe(0);
1586
+ });
1587
+ test("should track pending count", () => {
1588
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1589
+ expect(reassembler.pendingCount).toBe(0);
1590
+ reassembler.addFragment({
1591
+ fragmentationIndicator: 1,
1592
+ mpuSequenceNumber: 1,
1593
+ fragmentCounter: 1,
1594
+ itemId: 1,
1595
+ data: new Uint8Array([1]),
1596
+ timestamp: 1000n,
1597
+ rapFlag: false,
1598
+ packetSequenceNumber: testPacketSequence(1, 1)
1599
+ });
1600
+ expect(reassembler.pendingCount).toBe(1);
1601
+ reassembler.flush();
1602
+ expect(reassembler.pendingCount).toBe(0);
1603
+ });
1604
+ test("should reassemble 3 fragments (FI=1, FI=2, FI=3)", () => {
1605
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1606
+ expect(
1607
+ reassembler.addFragment({
1608
+ fragmentationIndicator: 1,
1609
+ mpuSequenceNumber: 10,
1610
+ fragmentCounter: 2,
1611
+ itemId: 1,
1612
+ data: new Uint8Array([170, 187]),
1613
+ timestamp: 5000n,
1614
+ rapFlag: true,
1615
+ packetSequenceNumber: testPacketSequence(10, 2)
1616
+ })
1617
+ ).toBeNull();
1618
+ expect(
1619
+ reassembler.addFragment({
1620
+ fragmentationIndicator: 2,
1621
+ mpuSequenceNumber: 10,
1622
+ fragmentCounter: 1,
1623
+ data: new Uint8Array([204, 221]),
1624
+ timestamp: 5000n,
1625
+ rapFlag: false,
1626
+ packetSequenceNumber: testPacketSequence(10, 1)
1627
+ })
1628
+ ).toBeNull();
1629
+ const result = reassembler.addFragment({
1630
+ fragmentationIndicator: 3,
1631
+ mpuSequenceNumber: 10,
1632
+ fragmentCounter: 0,
1633
+ data: new Uint8Array([238, 255]),
1634
+ timestamp: 5000n,
1635
+ rapFlag: false,
1636
+ packetSequenceNumber: testPacketSequence(10, 0)
1637
+ });
1638
+ expect(result).not.toBeNull();
1639
+ expect(result.data).toEqual(new Uint8Array([170, 187, 204, 221, 238, 255]));
1640
+ expect(result.fragmentCount).toBe(3);
1641
+ expect(result.rapFlag).toBe(true);
1642
+ expect(result.mpuSequenceNumber).toBe(10);
1643
+ });
1644
+ test("should handle interleaved MPU sequences", () => {
1645
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1646
+ reassembler.addFragment({
1647
+ fragmentationIndicator: 1,
1648
+ mpuSequenceNumber: 1,
1649
+ fragmentCounter: 1,
1650
+ itemId: 1,
1651
+ data: new Uint8Array([1]),
1652
+ timestamp: 100n,
1653
+ rapFlag: false,
1654
+ packetSequenceNumber: testPacketSequence(1, 1)
1655
+ });
1656
+ const r2 = reassembler.addFragment({
1657
+ fragmentationIndicator: 0,
1658
+ mpuSequenceNumber: 2,
1659
+ fragmentCounter: 0,
1660
+ itemId: 2,
1661
+ data: new Uint8Array([2]),
1662
+ timestamp: 200n,
1663
+ rapFlag: false
1664
+ });
1665
+ expect(r2).not.toBeNull();
1666
+ expect(r2.mpuSequenceNumber).toBe(2);
1667
+ const r1 = reassembler.addFragment({
1668
+ fragmentationIndicator: 3,
1669
+ mpuSequenceNumber: 1,
1670
+ fragmentCounter: 0,
1671
+ data: new Uint8Array([3]),
1672
+ timestamp: 100n,
1673
+ rapFlag: false,
1674
+ packetSequenceNumber: testPacketSequence(1, 0)
1675
+ });
1676
+ expect(r1).not.toBeNull();
1677
+ expect(r1.data).toEqual(new Uint8Array([1, 3]));
1678
+ });
1679
+ test("rejects overlapping fragmented MFUs sharing one stream and MPU", () => {
1680
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1681
+ const mpuSeq = 5;
1682
+ expect(
1683
+ reassembler.addFragment({
1684
+ fragmentationIndicator: 1,
1685
+ mpuSequenceNumber: mpuSeq,
1686
+ fragmentCounter: 1,
1687
+ itemId: 10,
1688
+ data: new Uint8Array([161]),
1689
+ timestamp: 100n,
1690
+ rapFlag: true,
1691
+ packetSequenceNumber: testPacketSequence(mpuSeq, 1)
1692
+ })
1693
+ ).toBeNull();
1694
+ expectRejection(
1695
+ () => reassembler.addFragment({
1696
+ fragmentationIndicator: 1,
1697
+ mpuSequenceNumber: mpuSeq,
1698
+ fragmentCounter: 1,
1699
+ itemId: 20,
1700
+ data: new Uint8Array([177]),
1701
+ timestamp: 200n,
1702
+ rapFlag: false,
1703
+ packetSequenceNumber: testPacketSequence(mpuSeq, 1)
1704
+ }),
1705
+ "overlapping-data-unit"
1706
+ );
1707
+ expect(reassembler.pendingCount).toBe(1);
1708
+ const a = reassembler.addFragment({
1709
+ fragmentationIndicator: 3,
1710
+ mpuSequenceNumber: mpuSeq,
1711
+ fragmentCounter: 0,
1712
+ data: new Uint8Array([162]),
1713
+ timestamp: 101n,
1714
+ rapFlag: false,
1715
+ packetSequenceNumber: testPacketSequence(mpuSeq, 0)
1716
+ });
1717
+ expect(a).not.toBeNull();
1718
+ expect(a.data).toEqual(new Uint8Array([161, 162]));
1719
+ expect(a.rapFlag).toBe(true);
1720
+ expect(
1721
+ reassembler.addFragment({
1722
+ fragmentationIndicator: 1,
1723
+ mpuSequenceNumber: mpuSeq,
1724
+ fragmentCounter: 1,
1725
+ itemId: 20,
1726
+ data: new Uint8Array([177]),
1727
+ timestamp: 200n,
1728
+ rapFlag: false,
1729
+ packetSequenceNumber: testPacketSequence(mpuSeq, 1, 10)
1730
+ })
1731
+ ).toBeNull();
1732
+ const b = reassembler.addFragment({
1733
+ fragmentationIndicator: 3,
1734
+ mpuSequenceNumber: mpuSeq,
1735
+ fragmentCounter: 0,
1736
+ data: new Uint8Array([178]),
1737
+ timestamp: 201n,
1738
+ rapFlag: false,
1739
+ packetSequenceNumber: testPacketSequence(mpuSeq, 0, 10)
1740
+ });
1741
+ expect(b).not.toBeNull();
1742
+ expect(b.data).toEqual(new Uint8Array([177, 178]));
1743
+ });
1744
+ test("reassembles FFmpeg timed MFU fragments by sample number and byte offset", () => {
1745
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1746
+ const common = {
1747
+ mpuSequenceNumber: 0,
1748
+ movieFragmentSequenceNumber: 9,
1749
+ sampleNumber: 17
1750
+ };
1751
+ expect(
1752
+ reassembler.addFragment({
1753
+ ...common,
1754
+ fragmentationIndicator: 1,
1755
+ fragmentCounter: 2,
1756
+ sampleOffset: 0,
1757
+ data: new Uint8Array([161]),
1758
+ timestamp: 1000n,
1759
+ rapFlag: true,
1760
+ packetSequenceNumber: testPacketSequence(0, 2)
1761
+ })
1762
+ ).toBeNull();
1763
+ expect(
1764
+ reassembler.addFragment({
1765
+ ...common,
1766
+ fragmentationIndicator: 2,
1767
+ fragmentCounter: 1,
1768
+ sampleOffset: 1,
1769
+ data: new Uint8Array([178]),
1770
+ timestamp: 1001n,
1771
+ rapFlag: false,
1772
+ packetSequenceNumber: testPacketSequence(0, 1)
1773
+ })
1774
+ ).toBeNull();
1775
+ const out = reassembler.addFragment({
1776
+ ...common,
1777
+ fragmentationIndicator: 3,
1778
+ fragmentCounter: 0,
1779
+ sampleOffset: 2,
1780
+ data: new Uint8Array([195]),
1781
+ timestamp: 1002n,
1782
+ rapFlag: false,
1783
+ packetSequenceNumber: testPacketSequence(0, 0)
1784
+ });
1785
+ expect(out).not.toBeNull();
1786
+ expect(out.data).toEqual(new Uint8Array([161, 178, 195]));
1787
+ expect(out.timestamp).toBe(1000n);
1788
+ expect(out.sampleNumber).toBe(17);
1789
+ expect(out.movieFragmentSequenceNumber).toBe(9);
1790
+ });
1791
+ test("buffers a counter gap without advancing the active data unit", () => {
1792
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1793
+ const common = {
1794
+ mpuSequenceNumber: 0,
1795
+ movieFragmentSequenceNumber: 9,
1796
+ sampleNumber: 17,
1797
+ timestamp: 1000n,
1798
+ rapFlag: true
1799
+ };
1800
+ expect(
1801
+ reassembler.addFragment({
1802
+ ...common,
1803
+ fragmentationIndicator: 1,
1804
+ fragmentCounter: 2,
1805
+ sampleOffset: 0,
1806
+ data: new Uint8Array([161, 162]),
1807
+ packetSequenceNumber: testPacketSequence(0, 2)
1808
+ })
1809
+ ).toBeNull();
1810
+ expect(
1811
+ reassembler.addFragment({
1812
+ ...common,
1813
+ fragmentationIndicator: 3,
1814
+ fragmentCounter: 0,
1815
+ sampleOffset: 4,
1816
+ data: new Uint8Array([193, 194]),
1817
+ packetSequenceNumber: testPacketSequence(0, 0)
1818
+ })
1819
+ ).toBeNull();
1820
+ expect(reassembler.pendingCount).toBe(1);
1821
+ const out = reassembler.addFragment({
1822
+ ...common,
1823
+ fragmentationIndicator: 2,
1824
+ fragmentCounter: 1,
1825
+ sampleOffset: 2,
1826
+ data: new Uint8Array([177, 178]),
1827
+ packetSequenceNumber: testPacketSequence(0, 1)
1828
+ });
1829
+ expect(out?.data).toEqual(new Uint8Array([161, 162, 177, 178, 193, 194]));
1830
+ });
1831
+ test("reassembles first-only DU-header fragments across changing packet timestamps", () => {
1832
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1833
+ expect(
1834
+ reassembler.addFragment({
1835
+ fragmentationIndicator: 1,
1836
+ mpuSequenceNumber: 44,
1837
+ fragmentCounter: 2,
1838
+ movieFragmentSequenceNumber: 9,
1839
+ sampleNumber: 17,
1840
+ sampleOffset: 0,
1841
+ data: new Uint8Array([161]),
1842
+ timestamp: 12345n,
1843
+ rapFlag: true,
1844
+ packetSequenceNumber: testPacketSequence(44, 2)
1845
+ })
1846
+ ).toBeNull();
1847
+ expect(
1848
+ reassembler.addFragment({
1849
+ fragmentationIndicator: 2,
1850
+ mpuSequenceNumber: 44,
1851
+ fragmentCounter: 1,
1852
+ data: new Uint8Array([178]),
1853
+ timestamp: 12346n,
1854
+ rapFlag: false,
1855
+ packetSequenceNumber: testPacketSequence(44, 1)
1856
+ })
1857
+ ).toBeNull();
1858
+ const out = reassembler.addFragment({
1859
+ fragmentationIndicator: 3,
1860
+ mpuSequenceNumber: 44,
1861
+ fragmentCounter: 0,
1862
+ data: new Uint8Array([195]),
1863
+ timestamp: 12347n,
1864
+ rapFlag: false,
1865
+ packetSequenceNumber: testPacketSequence(44, 0)
1866
+ });
1867
+ expect(out).not.toBeNull();
1868
+ expect(out.data).toEqual(new Uint8Array([161, 178, 195]));
1869
+ expect(out.timestamp).toBe(12345n);
1870
+ expect(out.sampleNumber).toBe(17);
1871
+ expect(out.movieFragmentSequenceNumber).toBe(9);
1872
+ });
1873
+ test("reassembles first-only DU-header fragments with an ISO countdown", () => {
1874
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1875
+ const timestamp = 12345n;
1876
+ expect(
1877
+ reassembler.addFragment({
1878
+ fragmentationIndicator: 1,
1879
+ mpuSequenceNumber: 44,
1880
+ fragmentCounter: 3,
1881
+ movieFragmentSequenceNumber: 9,
1882
+ sampleNumber: 17,
1883
+ sampleOffset: 0,
1884
+ data: new Uint8Array([161]),
1885
+ timestamp,
1886
+ rapFlag: true,
1887
+ packetSequenceNumber: testPacketSequence(44, 3)
1888
+ })
1889
+ ).toBeNull();
1890
+ expect(
1891
+ reassembler.addFragment({
1892
+ fragmentationIndicator: 2,
1893
+ mpuSequenceNumber: 44,
1894
+ fragmentCounter: 2,
1895
+ data: new Uint8Array([178]),
1896
+ timestamp,
1897
+ rapFlag: false,
1898
+ packetSequenceNumber: testPacketSequence(44, 2)
1899
+ })
1900
+ ).toBeNull();
1901
+ expect(
1902
+ reassembler.addFragment({
1903
+ fragmentationIndicator: 2,
1904
+ mpuSequenceNumber: 44,
1905
+ fragmentCounter: 1,
1906
+ data: new Uint8Array([195]),
1907
+ timestamp,
1908
+ rapFlag: false,
1909
+ packetSequenceNumber: testPacketSequence(44, 1)
1910
+ })
1911
+ ).toBeNull();
1912
+ const out = reassembler.addFragment({
1913
+ fragmentationIndicator: 3,
1914
+ mpuSequenceNumber: 44,
1915
+ fragmentCounter: 0,
1916
+ data: new Uint8Array([212]),
1917
+ timestamp,
1918
+ rapFlag: false,
1919
+ packetSequenceNumber: testPacketSequence(44, 0)
1920
+ });
1921
+ expect(out).not.toBeNull();
1922
+ expect(out.data).toEqual(new Uint8Array([161, 178, 195, 212]));
1923
+ expect(out.sampleNumber).toBe(17);
1924
+ expect(out.movieFragmentSequenceNumber).toBe(9);
1925
+ });
1926
+ test("rejects fragmented MFUs without an authored DU identity", () => {
1927
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1928
+ const timestamp = 22222n;
1929
+ expectRejection(
1930
+ () => reassembler.addFragment({
1931
+ fragmentationIndicator: 1,
1932
+ mpuSequenceNumber: 55,
1933
+ fragmentCounter: 2,
1934
+ data: new Uint8Array([161]),
1935
+ timestamp,
1936
+ rapFlag: true,
1937
+ packetSequenceNumber: testPacketSequence(55, 2)
1938
+ }),
1939
+ "missing-data-unit-identity"
1940
+ );
1941
+ expect(reassembler.pendingCount).toBe(0);
1942
+ });
1943
+ test("rejects fragmented MFUs without an authored packet sequence", () => {
1944
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1945
+ expectRejection(
1946
+ () => reassembler.addFragment({
1947
+ fragmentationIndicator: 1,
1948
+ mpuSequenceNumber: 55,
1949
+ fragmentCounter: 1,
1950
+ itemId: 1,
1951
+ data: new Uint8Array([161]),
1952
+ timestamp: 22222n,
1953
+ rapFlag: true
1954
+ }),
1955
+ "missing-packet-sequence"
1956
+ );
1957
+ expect(reassembler.pendingCount).toBe(0);
1958
+ });
1959
+ test("rejects fragmented MFUs whose authored packet sequence is not u32", () => {
1960
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1961
+ for (const packetSequenceNumber of [-1, 4294967296, 1.5, Number.NaN]) {
1962
+ expectRejection(
1963
+ () => reassembler.addFragment({
1964
+ fragmentationIndicator: 1,
1965
+ mpuSequenceNumber: 55,
1966
+ fragmentCounter: 1,
1967
+ itemId: 1,
1968
+ data: new Uint8Array([161]),
1969
+ timestamp: 22222n,
1970
+ rapFlag: true,
1971
+ packetSequenceNumber
1972
+ }),
1973
+ "missing-packet-sequence"
1974
+ );
1975
+ }
1976
+ expect(reassembler.pendingCount).toBe(0);
1977
+ });
1978
+ test("a packet sequence gap inside a fragmented DU splits it and never completes", () => {
1979
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
1980
+ const timestamp = 66666n;
1981
+ expect(
1982
+ reassembler.addFragment({
1983
+ fragmentationIndicator: 1,
1984
+ mpuSequenceNumber: 80,
1985
+ fragmentCounter: 3,
1986
+ movieFragmentSequenceNumber: 241,
1987
+ sampleNumber: 1,
1988
+ sampleOffset: 0,
1989
+ packetSequenceNumber: 1e3,
1990
+ data: new Uint8Array([160]),
1991
+ timestamp,
1992
+ rapFlag: true
1993
+ })
1994
+ ).toBeNull();
1995
+ for (const [counter, psn] of [
1996
+ [2, 1002],
1997
+ [1, 1003]
1998
+ ]) {
1999
+ expect(
2000
+ reassembler.addFragment({
2001
+ fragmentationIndicator: 2,
2002
+ mpuSequenceNumber: 80,
2003
+ fragmentCounter: counter,
2004
+ packetSequenceNumber: psn,
2005
+ data: new Uint8Array([160 + counter]),
2006
+ timestamp,
2007
+ rapFlag: false
2008
+ })
2009
+ ).toBeNull();
2010
+ }
2011
+ expect(
2012
+ reassembler.addFragment({
2013
+ fragmentationIndicator: 3,
2014
+ mpuSequenceNumber: 80,
2015
+ fragmentCounter: 0,
2016
+ packetSequenceNumber: 1004,
2017
+ data: new Uint8Array([177]),
2018
+ timestamp,
2019
+ rapFlag: false
2020
+ })
2021
+ ).toBeNull();
2022
+ expect(reassembler.pendingCount).toBe(2);
2023
+ expect(reassembler.getStatus().pendingFragments).toBe(4);
2024
+ expect(reassembler.stats.reassembledMFUs).toBe(0);
2025
+ expect(reassembler.stats.rejectedFragments).toBe(0);
2026
+ });
2027
+ test("keeps adjacent MFUs with equal countdowns in one MPU separate", () => {
2028
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
2029
+ const common = { mpuSequenceNumber: 80, packetId: 7, timestamp: 66666n };
2030
+ expect(
2031
+ reassembler.addFragment({
2032
+ ...common,
2033
+ fragmentationIndicator: 1,
2034
+ fragmentCounter: 1,
2035
+ packetSequenceNumber: 100,
2036
+ movieFragmentSequenceNumber: 241,
2037
+ sampleNumber: 1,
2038
+ sampleOffset: 0,
2039
+ data: new Uint8Array([161]),
2040
+ rapFlag: true
2041
+ })
2042
+ ).toBeNull();
2043
+ expect(
2044
+ reassembler.addFragment({
2045
+ ...common,
2046
+ fragmentationIndicator: 1,
2047
+ fragmentCounter: 1,
2048
+ packetSequenceNumber: 102,
2049
+ movieFragmentSequenceNumber: 241,
2050
+ sampleNumber: 2,
2051
+ sampleOffset: 0,
2052
+ data: new Uint8Array([177]),
2053
+ rapFlag: false
2054
+ })
2055
+ ).toBeNull();
2056
+ expect(reassembler.pendingCount).toBe(2);
2057
+ const earlier = reassembler.addFragment({
2058
+ ...common,
2059
+ fragmentationIndicator: 3,
2060
+ fragmentCounter: 0,
2061
+ packetSequenceNumber: 101,
2062
+ data: new Uint8Array([162]),
2063
+ rapFlag: false
2064
+ });
2065
+ expect(earlier?.data).toEqual(new Uint8Array([161, 162]));
2066
+ expect(earlier?.packetSequenceNumbers).toEqual([100, 101]);
2067
+ const later = reassembler.addFragment({
2068
+ ...common,
2069
+ fragmentationIndicator: 3,
2070
+ fragmentCounter: 0,
2071
+ packetSequenceNumber: 103,
2072
+ data: new Uint8Array([178]),
2073
+ rapFlag: false
2074
+ });
2075
+ expect(later?.data).toEqual(new Uint8Array([177, 178]));
2076
+ expect(later?.packetSequenceNumbers).toEqual([102, 103]);
2077
+ expect(reassembler.pendingCount).toBe(0);
2078
+ });
2079
+ test("wraps the fragmented run coordinate at u32 packet sequence", () => {
2080
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
2081
+ expect(
2082
+ reassembler.addFragment({
2083
+ fragmentationIndicator: 1,
2084
+ mpuSequenceNumber: 81,
2085
+ fragmentCounter: 1,
2086
+ packetSequenceNumber: 4294967295,
2087
+ itemId: 1,
2088
+ data: new Uint8Array([161]),
2089
+ timestamp: 66666n,
2090
+ rapFlag: true
2091
+ })
2092
+ ).toBeNull();
2093
+ expect(
2094
+ reassembler.addFragment({
2095
+ fragmentationIndicator: 3,
2096
+ mpuSequenceNumber: 81,
2097
+ fragmentCounter: 0,
2098
+ packetSequenceNumber: 0,
2099
+ data: new Uint8Array([162]),
2100
+ timestamp: 66667n,
2101
+ rapFlag: false
2102
+ })?.data
2103
+ ).toEqual(new Uint8Array([161, 162]));
2104
+ });
2105
+ test("dedupes recovered copies by ISO fragment-counter position", () => {
2106
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
2107
+ const timestamp = 44444n;
2108
+ expect(
2109
+ reassembler.addFragment({
2110
+ fragmentationIndicator: 1,
2111
+ mpuSequenceNumber: 77,
2112
+ fragmentCounter: 2,
2113
+ movieFragmentSequenceNumber: 12,
2114
+ sampleNumber: 1,
2115
+ sampleOffset: 0,
2116
+ packetSequenceNumber: 2e3,
2117
+ data: new Uint8Array([161]),
2118
+ timestamp,
2119
+ rapFlag: true
2120
+ })
2121
+ ).toBeNull();
2122
+ expect(
2123
+ reassembler.addFragment({
2124
+ fragmentationIndicator: 2,
2125
+ mpuSequenceNumber: 77,
2126
+ fragmentCounter: 1,
2127
+ packetSequenceNumber: 2001,
2128
+ data: new Uint8Array([178]),
2129
+ timestamp,
2130
+ rapFlag: false
2131
+ })
2132
+ ).toBeNull();
2133
+ expect(
2134
+ reassembler.addFragment({
2135
+ fragmentationIndicator: 3,
2136
+ mpuSequenceNumber: 77,
2137
+ fragmentCounter: 0,
2138
+ packetSequenceNumber: 2002,
2139
+ data: new Uint8Array([195]),
2140
+ timestamp,
2141
+ rapFlag: false
2142
+ })
2143
+ ).not.toBeNull();
2144
+ reassembler.flush();
2145
+ expect(
2146
+ reassembler.addFragment({
2147
+ fragmentationIndicator: 1,
2148
+ mpuSequenceNumber: 78,
2149
+ fragmentCounter: 2,
2150
+ movieFragmentSequenceNumber: 13,
2151
+ sampleNumber: 1,
2152
+ sampleOffset: 0,
2153
+ packetSequenceNumber: 3e3,
2154
+ data: new Uint8Array([212]),
2155
+ timestamp,
2156
+ rapFlag: true
2157
+ })
2158
+ ).toBeNull();
2159
+ expect(
2160
+ reassembler.addFragment({
2161
+ fragmentationIndicator: 2,
2162
+ mpuSequenceNumber: 78,
2163
+ fragmentCounter: 1,
2164
+ packetSequenceNumber: 3001,
2165
+ data: new Uint8Array([229]),
2166
+ timestamp,
2167
+ rapFlag: false
2168
+ })
2169
+ ).toBeNull();
2170
+ expectRejection(
2171
+ () => reassembler.addFragment({
2172
+ fragmentationIndicator: 2,
2173
+ mpuSequenceNumber: 78,
2174
+ fragmentCounter: 1,
2175
+ packetSequenceNumber: 3001,
2176
+ data: new Uint8Array([229]),
2177
+ timestamp,
2178
+ rapFlag: false
2179
+ }),
2180
+ "fragment-counter-mismatch"
2181
+ );
2182
+ const out = reassembler.addFragment({
2183
+ fragmentationIndicator: 3,
2184
+ mpuSequenceNumber: 78,
2185
+ fragmentCounter: 0,
2186
+ packetSequenceNumber: 3002,
2187
+ data: new Uint8Array([246]),
2188
+ timestamp,
2189
+ rapFlag: false
2190
+ });
2191
+ expect(out).not.toBeNull();
2192
+ expect(out.data).toEqual(new Uint8Array([212, 229, 246]));
2193
+ });
2194
+ test("reassembles middle and terminal continuations that arrive before first", () => {
2195
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
2196
+ const timestamp = 54321n;
2197
+ expect(
2198
+ reassembler.addFragment({
2199
+ fragmentationIndicator: 2,
2200
+ mpuSequenceNumber: 45,
2201
+ fragmentCounter: 1,
2202
+ data: new Uint8Array([178]),
2203
+ timestamp,
2204
+ rapFlag: false,
2205
+ packetSequenceNumber: testPacketSequence(45, 1)
2206
+ })
2207
+ ).toBeNull();
2208
+ expect(
2209
+ reassembler.addFragment({
2210
+ fragmentationIndicator: 3,
2211
+ mpuSequenceNumber: 45,
2212
+ fragmentCounter: 0,
2213
+ data: new Uint8Array([195]),
2214
+ timestamp: timestamp + 1n,
2215
+ rapFlag: false,
2216
+ packetSequenceNumber: testPacketSequence(45, 0)
2217
+ })
2218
+ ).toBeNull();
2219
+ expect(reassembler.pendingCount).toBe(1);
2220
+ const out = reassembler.addFragment({
2221
+ fragmentationIndicator: 1,
2222
+ mpuSequenceNumber: 45,
2223
+ fragmentCounter: 2,
2224
+ movieFragmentSequenceNumber: 10,
2225
+ sampleNumber: 18,
2226
+ sampleOffset: 0,
2227
+ data: new Uint8Array([161]),
2228
+ timestamp,
2229
+ rapFlag: true,
2230
+ packetSequenceNumber: testPacketSequence(45, 2)
2231
+ });
2232
+ expect(out).not.toBeNull();
2233
+ expect(out.data).toEqual(new Uint8Array([161, 178, 195]));
2234
+ expect(out.sampleNumber).toBe(18);
2235
+ expect(out.movieFragmentSequenceNumber).toBe(10);
2236
+ expect(reassembler.pendingCount).toBe(0);
2237
+ });
2238
+ test("rejects a duplicate continuation before first without replacing retained media", () => {
2239
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
2240
+ const timestamp = 54322n;
2241
+ expect(
2242
+ reassembler.addFragment({
2243
+ fragmentationIndicator: 3,
2244
+ mpuSequenceNumber: 46,
2245
+ fragmentCounter: 0,
2246
+ data: new Uint8Array([195]),
2247
+ timestamp,
2248
+ rapFlag: false,
2249
+ packetSequenceNumber: testPacketSequence(46, 0)
2250
+ })
2251
+ ).toBeNull();
2252
+ expectRejection(
2253
+ () => reassembler.addFragment({
2254
+ fragmentationIndicator: 3,
2255
+ mpuSequenceNumber: 46,
2256
+ fragmentCounter: 0,
2257
+ data: new Uint8Array([255]),
2258
+ timestamp: timestamp + 1n,
2259
+ rapFlag: false,
2260
+ packetSequenceNumber: testPacketSequence(46, 0)
2261
+ }),
2262
+ "fragment-counter-mismatch"
2263
+ );
2264
+ expect(
2265
+ reassembler.addFragment({
2266
+ fragmentationIndicator: 2,
2267
+ mpuSequenceNumber: 46,
2268
+ fragmentCounter: 1,
2269
+ data: new Uint8Array([178]),
2270
+ timestamp: timestamp + 2n,
2271
+ rapFlag: false,
2272
+ packetSequenceNumber: testPacketSequence(46, 1)
2273
+ })
2274
+ ).toBeNull();
2275
+ const out = reassembler.addFragment({
2276
+ fragmentationIndicator: 1,
2277
+ mpuSequenceNumber: 46,
2278
+ fragmentCounter: 2,
2279
+ movieFragmentSequenceNumber: 10,
2280
+ sampleNumber: 19,
2281
+ sampleOffset: 0,
2282
+ data: new Uint8Array([161]),
2283
+ timestamp: timestamp + 3n,
2284
+ rapFlag: true,
2285
+ packetSequenceNumber: testPacketSequence(46, 2)
2286
+ });
2287
+ expect(out).not.toBeNull();
2288
+ expect(out.data).toEqual(new Uint8Array([161, 178, 195]));
2289
+ expect(reassembler.pendingCount).toBe(0);
2290
+ });
2291
+ test("rejects invalid ISO fragmentation-indicator and counter combinations", () => {
2292
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
2293
+ const invalidHeaders = [
2294
+ { fragmentationIndicator: 0, fragmentCounter: 1 },
2295
+ { fragmentationIndicator: 3, fragmentCounter: 1 },
2296
+ { fragmentationIndicator: 1, fragmentCounter: 0 },
2297
+ { fragmentationIndicator: 2, fragmentCounter: 0 },
2298
+ { fragmentationIndicator: -1, fragmentCounter: 1 },
2299
+ { fragmentationIndicator: 4, fragmentCounter: 1 },
2300
+ { fragmentationIndicator: 1.5, fragmentCounter: 1 },
2301
+ { fragmentationIndicator: 1, fragmentCounter: -1 },
2302
+ { fragmentationIndicator: 1, fragmentCounter: 256 },
2303
+ { fragmentationIndicator: 1, fragmentCounter: 1.5 },
2304
+ { fragmentationIndicator: 1, fragmentCounter: Number.NaN }
2305
+ ];
2306
+ for (const header of invalidHeaders) {
2307
+ expectRejection(
2308
+ () => reassembler.addFragment({
2309
+ ...header,
2310
+ mpuSequenceNumber: 48,
2311
+ data: new Uint8Array([161]),
2312
+ timestamp: 54324n,
2313
+ rapFlag: false
2314
+ }),
2315
+ "invalid-fragment-state"
2316
+ );
2317
+ }
2318
+ expect(reassembler.pendingCount).toBe(0);
2319
+ expect(reassembler.stats.reassembledMFUs).toBe(0);
2320
+ });
2321
+ test("reassembles the maximum ISO chain of 256 payloads", () => {
2322
+ const reassembler = new MFUReassembler({ timeoutMs: null });
2323
+ const common = {
2324
+ mpuSequenceNumber: 47,
2325
+ timestamp: 54323n,
2326
+ rapFlag: false
2327
+ };
2328
+ expect(
2329
+ reassembler.addFragment({
2330
+ ...common,
2331
+ fragmentationIndicator: 1,
2332
+ fragmentCounter: 255,
2333
+ itemId: 1,
2334
+ data: new Uint8Array([0]),
2335
+ rapFlag: true,
2336
+ packetSequenceNumber: testPacketSequence(47, 255)
2337
+ })
2338
+ ).toBeNull();
2339
+ for (let counter = 254; counter > 0; counter--) {
2340
+ expect(
2341
+ reassembler.addFragment({
2342
+ ...common,
2343
+ fragmentationIndicator: 2,
2344
+ fragmentCounter: counter,
2345
+ data: new Uint8Array([255 - counter]),
2346
+ packetSequenceNumber: testPacketSequence(47, counter)
2347
+ })
2348
+ ).toBeNull();
2349
+ }
2350
+ const out = reassembler.addFragment({
2351
+ ...common,
2352
+ fragmentationIndicator: 3,
2353
+ fragmentCounter: 0,
2354
+ data: new Uint8Array([255]),
2355
+ packetSequenceNumber: testPacketSequence(47, 0)
2356
+ });
2357
+ expect(out?.fragmentCount).toBe(256);
2358
+ expect(out?.data).toEqual(Uint8Array.from({ length: 256 }, (_, index) => index));
2359
+ });
2360
+ test("should not reassemble without last fragment (FI=3)", () => {
2361
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
2362
+ reassembler.addFragment({
2363
+ fragmentationIndicator: 1,
2364
+ mpuSequenceNumber: 1,
2365
+ fragmentCounter: 2,
2366
+ itemId: 1,
2367
+ data: new Uint8Array([1]),
2368
+ timestamp: 1000n,
2369
+ rapFlag: false,
2370
+ packetSequenceNumber: testPacketSequence(1, 2)
2371
+ });
2372
+ const result = reassembler.addFragment({
2373
+ fragmentationIndicator: 2,
2374
+ mpuSequenceNumber: 1,
2375
+ fragmentCounter: 1,
2376
+ data: new Uint8Array([2]),
2377
+ timestamp: 1000n,
2378
+ rapFlag: false,
2379
+ packetSequenceNumber: testPacketSequence(1, 1)
2380
+ });
2381
+ expect(result).toBeNull();
2382
+ expect(reassembler.pendingCount).toBe(1);
2383
+ });
2384
+ test("bounds continuations that arrive before their first fragments", () => {
2385
+ const reassembler = new MFUReassembler({
2386
+ timeoutMs: null,
2387
+ maxBuffered: 1
2388
+ });
2389
+ expect(
2390
+ reassembler.addFragment({
2391
+ fragmentationIndicator: 3,
2392
+ mpuSequenceNumber: 1,
2393
+ fragmentCounter: 0,
2394
+ data: new Uint8Array([1]),
2395
+ timestamp: 1000n,
2396
+ rapFlag: false,
2397
+ packetSequenceNumber: testPacketSequence(1, 0)
2398
+ })
2399
+ ).toBeNull();
2400
+ expect(reassembler.pendingCount).toBe(1);
2401
+ expect(
2402
+ reassembler.addFragment({
2403
+ fragmentationIndicator: 3,
2404
+ mpuSequenceNumber: 2,
2405
+ fragmentCounter: 0,
2406
+ data: new Uint8Array([2]),
2407
+ timestamp: 1000n,
2408
+ rapFlag: false,
2409
+ packetSequenceNumber: testPacketSequence(2, 0)
2410
+ })
2411
+ ).toBeNull();
2412
+ expect(reassembler.pendingCount).toBe(1);
2413
+ expect(reassembler.getStatus().pendingFragments).toBe(1);
2414
+ expect(reassembler.stats.evictedMemoryLimit).toBe(1);
2415
+ const completed = reassembler.addFragment({
2416
+ fragmentationIndicator: 1,
2417
+ mpuSequenceNumber: 2,
2418
+ fragmentCounter: 1,
2419
+ itemId: 2,
2420
+ data: new Uint8Array([4]),
2421
+ timestamp: 1000n,
2422
+ rapFlag: true,
2423
+ packetSequenceNumber: testPacketSequence(2, 1)
2424
+ });
2425
+ expect(completed?.data).toEqual(new Uint8Array([4, 2]));
2426
+ expect(reassembler.stats.evictedMemoryLimit).toBe(1);
2427
+ expect(reassembler.pendingCount).toBe(0);
2428
+ const result = reassembler.addFragment({
2429
+ fragmentationIndicator: 1,
2430
+ mpuSequenceNumber: 1,
2431
+ fragmentCounter: 1,
2432
+ itemId: 1,
2433
+ data: new Uint8Array([3]),
2434
+ timestamp: 1000n,
2435
+ rapFlag: true,
2436
+ packetSequenceNumber: testPacketSequence(1, 1)
2437
+ });
2438
+ expect(result).toBeNull();
2439
+ expect(reassembler.pendingCount).toBe(1);
2440
+ });
2441
+ test("expires a continuation that never receives its first fragment", () => {
2442
+ vi.useFakeTimers();
2443
+ try {
2444
+ const reassembler = new MFUReassembler({
2445
+ timeoutMs: 50,
2446
+ maxBuffered: 1
301
2447
  });
302
- test('should track pending count', () => {
303
- const reassembler = new MFUReassembler();
304
- expect(reassembler.pendingCount).toBe(0);
305
- reassembler.addFragment({
306
- fragmentationIndicator: 1,
307
- mpuSequenceNumber: 1,
308
- fragmentCounter: 0,
309
- data: new Uint8Array([1]),
310
- timestamp: 1000n,
311
- receivedAt: 0,
312
- rapFlag: false,
313
- });
314
- expect(reassembler.pendingCount).toBe(1);
315
- reassembler.flush();
316
- expect(reassembler.pendingCount).toBe(0);
2448
+ expect(
2449
+ reassembler.addFragment({
2450
+ fragmentationIndicator: 3,
2451
+ mpuSequenceNumber: 1,
2452
+ fragmentCounter: 0,
2453
+ data: new Uint8Array([1]),
2454
+ timestamp: 1000n,
2455
+ rapFlag: false,
2456
+ packetSequenceNumber: testPacketSequence(1, 0)
2457
+ })
2458
+ ).toBeNull();
2459
+ vi.advanceTimersByTime(50);
2460
+ expect(reassembler.pendingCount).toBe(0);
2461
+ expect(reassembler.stats.timedOutMFUs).toBe(1);
2462
+ } finally {
2463
+ vi.useRealTimers();
2464
+ }
2465
+ });
2466
+ test("retimes retained continuations when the timeout policy changes", () => {
2467
+ vi.useFakeTimers();
2468
+ try {
2469
+ const enabled = new MFUReassembler({ timeoutMs: null, maxBuffered: 1 });
2470
+ expect(
2471
+ enabled.addFragment({
2472
+ fragmentationIndicator: 3,
2473
+ mpuSequenceNumber: 1,
2474
+ fragmentCounter: 0,
2475
+ data: new Uint8Array([1]),
2476
+ timestamp: 1000n,
2477
+ rapFlag: false,
2478
+ packetSequenceNumber: testPacketSequence(1, 0)
2479
+ })
2480
+ ).toBeNull();
2481
+ enabled.timeoutMs = 10;
2482
+ vi.advanceTimersByTime(10);
2483
+ expect(enabled.pendingCount).toBe(0);
2484
+ const disabled = new MFUReassembler({ timeoutMs: 10, maxBuffered: 1 });
2485
+ expect(
2486
+ disabled.addFragment({
2487
+ fragmentationIndicator: 3,
2488
+ mpuSequenceNumber: 2,
2489
+ fragmentCounter: 0,
2490
+ data: new Uint8Array([2]),
2491
+ timestamp: 1000n,
2492
+ rapFlag: false,
2493
+ packetSequenceNumber: testPacketSequence(2, 0)
2494
+ })
2495
+ ).toBeNull();
2496
+ disabled.timeoutMs = null;
2497
+ vi.advanceTimersByTime(10);
2498
+ expect(disabled.pendingCount).toBe(1);
2499
+ disabled.flush();
2500
+ } finally {
2501
+ vi.useRealTimers();
2502
+ }
2503
+ });
2504
+ test("bounds active data units by evicting the oldest at maxBuffered", () => {
2505
+ const reassembler = new MFUReassembler({
2506
+ timeoutMs: 1e3,
2507
+ maxBuffered: 2
2508
+ });
2509
+ for (let i = 0; i < 2; i++) {
2510
+ reassembler.addFragment({
2511
+ fragmentationIndicator: 1,
2512
+ mpuSequenceNumber: i,
2513
+ fragmentCounter: 1,
2514
+ itemId: i,
2515
+ data: new Uint8Array([i]),
2516
+ timestamp: BigInt(i * 100),
2517
+ rapFlag: false,
2518
+ packetSequenceNumber: testPacketSequence(i, 1)
317
2519
  });
2520
+ }
2521
+ expect(
2522
+ reassembler.addFragment({
2523
+ fragmentationIndicator: 1,
2524
+ mpuSequenceNumber: 2,
2525
+ fragmentCounter: 1,
2526
+ itemId: 2,
2527
+ data: new Uint8Array([2]),
2528
+ timestamp: 200n,
2529
+ rapFlag: false,
2530
+ packetSequenceNumber: testPacketSequence(2, 1)
2531
+ })
2532
+ ).toBeNull();
2533
+ expect(reassembler.pendingCount).toBe(2);
2534
+ expect(reassembler.stats.timedOutMFUs).toBe(0);
2535
+ expect(reassembler.stats.evictedMemoryLimit).toBe(1);
2536
+ expect(
2537
+ reassembler.addFragment({
2538
+ fragmentationIndicator: 3,
2539
+ mpuSequenceNumber: 0,
2540
+ fragmentCounter: 0,
2541
+ data: new Uint8Array([9]),
2542
+ timestamp: 300n,
2543
+ rapFlag: false,
2544
+ packetSequenceNumber: testPacketSequence(0, 0)
2545
+ })
2546
+ ).toBeNull();
2547
+ expect(reassembler.pendingCount).toBe(2);
2548
+ expect(reassembler.stats.evictedMemoryLimit).toBe(2);
2549
+ expect(reassembler.stats.timedOutMFUs).toBe(0);
318
2550
  });
2551
+ test("should report stats correctly", () => {
2552
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
2553
+ reassembler.addFragment({
2554
+ fragmentationIndicator: 0,
2555
+ mpuSequenceNumber: 1,
2556
+ fragmentCounter: 0,
2557
+ itemId: 1,
2558
+ data: new Uint8Array([1]),
2559
+ timestamp: 1000n,
2560
+ rapFlag: false
2561
+ });
2562
+ const status = reassembler.getStatus();
2563
+ expect(status.bufferedMFUs).toBe(0);
2564
+ expect(status.stats.totalFragments).toBe(1);
2565
+ expect(status.stats.reassembledMFUs).toBe(1);
2566
+ });
2567
+ test("rejects an overlapping first without replacing the original fragment", () => {
2568
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
2569
+ reassembler.addFragment({
2570
+ fragmentationIndicator: 1,
2571
+ mpuSequenceNumber: 1,
2572
+ fragmentCounter: 1,
2573
+ itemId: 1,
2574
+ data: new Uint8Array([170]),
2575
+ timestamp: 1000n,
2576
+ rapFlag: true,
2577
+ source: "multicast",
2578
+ packetSequenceNumber: testPacketSequence(1, 1)
2579
+ });
2580
+ expectRejection(
2581
+ () => reassembler.addFragment({
2582
+ fragmentationIndicator: 1,
2583
+ mpuSequenceNumber: 1,
2584
+ fragmentCounter: 1,
2585
+ itemId: 2,
2586
+ data: new Uint8Array([187]),
2587
+ timestamp: 1000n,
2588
+ rapFlag: true,
2589
+ source: "moq",
2590
+ packetSequenceNumber: testPacketSequence(1, 1)
2591
+ }),
2592
+ "overlapping-data-unit"
2593
+ );
2594
+ const result = reassembler.addFragment({
2595
+ fragmentationIndicator: 3,
2596
+ mpuSequenceNumber: 1,
2597
+ fragmentCounter: 0,
2598
+ data: new Uint8Array([204]),
2599
+ timestamp: 1000n,
2600
+ rapFlag: false,
2601
+ packetSequenceNumber: testPacketSequence(1, 0)
2602
+ });
2603
+ expect(result).not.toBeNull();
2604
+ expect(result.data).toEqual(new Uint8Array([170, 204]));
2605
+ expect(result.fragmentCount).toBe(2);
2606
+ expect(result.source).toBe("multicast");
2607
+ });
2608
+ });
2609
+ test("should retain an assembly with a gap in fragment counters", () => {
2610
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
2611
+ reassembler.addFragment({
2612
+ fragmentationIndicator: 1,
2613
+ mpuSequenceNumber: 1,
2614
+ fragmentCounter: 3,
2615
+ itemId: 1,
2616
+ data: new Uint8Array([170]),
2617
+ timestamp: 1000n,
2618
+ rapFlag: true,
2619
+ packetSequenceNumber: testPacketSequence(1, 3)
2620
+ });
2621
+ expect(
2622
+ reassembler.addFragment({
2623
+ fragmentationIndicator: 2,
2624
+ mpuSequenceNumber: 1,
2625
+ fragmentCounter: 1,
2626
+ data: new Uint8Array([204]),
2627
+ timestamp: 1000n,
2628
+ rapFlag: false,
2629
+ packetSequenceNumber: testPacketSequence(1, 1)
2630
+ })
2631
+ ).toBeNull();
2632
+ expect(
2633
+ reassembler.addFragment({
2634
+ fragmentationIndicator: 3,
2635
+ mpuSequenceNumber: 1,
2636
+ fragmentCounter: 0,
2637
+ data: new Uint8Array([221]),
2638
+ timestamp: 1000n,
2639
+ rapFlag: false,
2640
+ packetSequenceNumber: testPacketSequence(1, 0)
2641
+ })
2642
+ ).toBeNull();
2643
+ expect(reassembler.pendingCount).toBe(1);
2644
+ expect(reassembler.getStatus().pendingFragments).toBe(3);
2645
+ });
2646
+ test("accepts a reordered countdown and rejects a stale duplicate", () => {
2647
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
2648
+ reassembler.addFragment({
2649
+ fragmentationIndicator: 1,
2650
+ mpuSequenceNumber: 1,
2651
+ fragmentCounter: 3,
2652
+ itemId: 1,
2653
+ data: new Uint8Array([170]),
2654
+ timestamp: 1000n,
2655
+ rapFlag: true,
2656
+ packetSequenceNumber: testPacketSequence(1, 3)
2657
+ });
2658
+ expect(
2659
+ reassembler.addFragment({
2660
+ fragmentationIndicator: 2,
2661
+ mpuSequenceNumber: 1,
2662
+ fragmentCounter: 1,
2663
+ data: new Uint8Array([204]),
2664
+ timestamp: 1000n,
2665
+ rapFlag: false,
2666
+ packetSequenceNumber: testPacketSequence(1, 1)
2667
+ })
2668
+ ).toBeNull();
2669
+ expect(
2670
+ reassembler.addFragment({
2671
+ fragmentationIndicator: 2,
2672
+ mpuSequenceNumber: 1,
2673
+ fragmentCounter: 2,
2674
+ data: new Uint8Array([187]),
2675
+ timestamp: 1000n,
2676
+ rapFlag: false,
2677
+ packetSequenceNumber: testPacketSequence(1, 2)
2678
+ })
2679
+ ).toBeNull();
2680
+ expectRejection(
2681
+ () => reassembler.addFragment({
2682
+ fragmentationIndicator: 2,
2683
+ mpuSequenceNumber: 1,
2684
+ fragmentCounter: 1,
2685
+ data: new Uint8Array([204]),
2686
+ timestamp: 1001n,
2687
+ rapFlag: false,
2688
+ packetSequenceNumber: testPacketSequence(1, 1)
2689
+ }),
2690
+ "fragment-counter-mismatch"
2691
+ );
2692
+ const result = reassembler.addFragment({
2693
+ fragmentationIndicator: 3,
2694
+ mpuSequenceNumber: 1,
2695
+ fragmentCounter: 0,
2696
+ data: new Uint8Array([221]),
2697
+ timestamp: 1002n,
2698
+ rapFlag: false,
2699
+ packetSequenceNumber: testPacketSequence(1, 0)
2700
+ });
2701
+ expect(result).not.toBeNull();
2702
+ expect(result.data).toEqual(new Uint8Array([170, 187, 204, 221]));
2703
+ expect(result.fragmentCount).toBe(4);
2704
+ });
2705
+ test("rejects an overlapping first without discarding queued continuations", () => {
2706
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
2707
+ const fragment = (fragmentationIndicator, fragmentCounter, byte) => ({
2708
+ fragmentationIndicator,
2709
+ mpuSequenceNumber: 2,
2710
+ fragmentCounter,
2711
+ data: new Uint8Array([byte]),
2712
+ timestamp: 1000n,
2713
+ rapFlag: fragmentationIndicator === 1,
2714
+ packetSequenceNumber: testPacketSequence(2, fragmentCounter),
2715
+ ...fragmentationIndicator === 1 ? { itemId: 9 } : {}
2716
+ });
2717
+ expect(reassembler.addFragment(fragment(1, 3, 170))).toBeNull();
2718
+ expect(reassembler.addFragment(fragment(2, 1, 204))).toBeNull();
2719
+ expectRejection(() => reassembler.addFragment(fragment(1, 1, 238)), "overlapping-data-unit");
2720
+ expect(reassembler.addFragment(fragment(2, 2, 187))).toBeNull();
2721
+ expect(reassembler.addFragment(fragment(3, 0, 221))?.data).toEqual(new Uint8Array([170, 187, 204, 221]));
2722
+ });
2723
+ test("rejects a duplicate emitted intrinsic identity", () => {
2724
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
2725
+ expect(
2726
+ reassembler.addFragment({
2727
+ fragmentationIndicator: 1,
2728
+ mpuSequenceNumber: 42,
2729
+ fragmentCounter: 1,
2730
+ itemId: 1,
2731
+ data: new Uint8Array([170]),
2732
+ timestamp: 1000n,
2733
+ rapFlag: true,
2734
+ packetSequenceNumber: testPacketSequence(42, 1)
2735
+ })
2736
+ ).toBeNull();
2737
+ expect(
2738
+ reassembler.addFragment({
2739
+ fragmentationIndicator: 3,
2740
+ mpuSequenceNumber: 42,
2741
+ fragmentCounter: 0,
2742
+ data: new Uint8Array([171]),
2743
+ timestamp: 1001n,
2744
+ rapFlag: false,
2745
+ packetSequenceNumber: testPacketSequence(42, 0)
2746
+ })
2747
+ ).not.toBeNull();
2748
+ expect(
2749
+ reassembler.addFragment({
2750
+ fragmentationIndicator: 3,
2751
+ mpuSequenceNumber: 42,
2752
+ fragmentCounter: 0,
2753
+ data: new Uint8Array([188]),
2754
+ timestamp: 1002n,
2755
+ rapFlag: false,
2756
+ packetSequenceNumber: testPacketSequence(42, 0, 10)
2757
+ })
2758
+ ).toBeNull();
2759
+ expect(reassembler.pendingCount).toBe(1);
2760
+ expect(
2761
+ reassembler.addFragment({
2762
+ fragmentationIndicator: 1,
2763
+ mpuSequenceNumber: 42,
2764
+ fragmentCounter: 1,
2765
+ itemId: 1,
2766
+ data: new Uint8Array([187]),
2767
+ timestamp: 2000n,
2768
+ rapFlag: true,
2769
+ packetSequenceNumber: testPacketSequence(42, 1, 10)
2770
+ })
2771
+ ).toBeNull();
2772
+ expect(reassembler.pendingCount).toBe(1);
2773
+ const result = reassembler.addFragment({
2774
+ fragmentationIndicator: 1,
2775
+ mpuSequenceNumber: 43,
2776
+ fragmentCounter: 1,
2777
+ itemId: 1,
2778
+ data: new Uint8Array([204]),
2779
+ timestamp: 2000n,
2780
+ rapFlag: false,
2781
+ packetSequenceNumber: testPacketSequence(43, 1)
2782
+ });
2783
+ expect(result).toBeNull();
2784
+ expect(
2785
+ reassembler.addFragment({
2786
+ fragmentationIndicator: 3,
2787
+ mpuSequenceNumber: 43,
2788
+ fragmentCounter: 0,
2789
+ data: new Uint8Array([221]),
2790
+ timestamp: 2001n,
2791
+ rapFlag: false,
2792
+ packetSequenceNumber: testPacketSequence(43, 0)
2793
+ })?.data
2794
+ ).toEqual(new Uint8Array([204, 221]));
2795
+ });
2796
+ test("does not create active state for an emitted identity duplicate", () => {
2797
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
2798
+ expect(
2799
+ reassembler.addFragment({
2800
+ fragmentationIndicator: 1,
2801
+ mpuSequenceNumber: 50,
2802
+ fragmentCounter: 1,
2803
+ movieFragmentSequenceNumber: 10,
2804
+ sampleNumber: 18,
2805
+ sampleOffset: 0,
2806
+ data: new Uint8Array([161]),
2807
+ timestamp: 1000n,
2808
+ rapFlag: true,
2809
+ packetSequenceNumber: testPacketSequence(50, 1)
2810
+ })
2811
+ ).toBeNull();
2812
+ expect(
2813
+ reassembler.addFragment({
2814
+ fragmentationIndicator: 3,
2815
+ mpuSequenceNumber: 50,
2816
+ fragmentCounter: 0,
2817
+ data: new Uint8Array([178]),
2818
+ timestamp: 1000n,
2819
+ rapFlag: false,
2820
+ packetSequenceNumber: testPacketSequence(50, 0)
2821
+ })?.data
2822
+ ).toEqual(new Uint8Array([161, 178]));
2823
+ expect(reassembler.pendingCount).toBe(0);
2824
+ expect(
2825
+ reassembler.addFragment({
2826
+ fragmentationIndicator: 1,
2827
+ mpuSequenceNumber: 50,
2828
+ fragmentCounter: 1,
2829
+ movieFragmentSequenceNumber: 10,
2830
+ sampleNumber: 18,
2831
+ sampleOffset: 0,
2832
+ data: new Uint8Array([161]),
2833
+ timestamp: 2000n,
2834
+ rapFlag: true,
2835
+ packetSequenceNumber: testPacketSequence(50, 1, 10)
2836
+ })
2837
+ ).toBeNull();
2838
+ expect(reassembler.pendingCount).toBe(0);
2839
+ });
2840
+ test("flush should clear emitted set and allow re-assembly", () => {
2841
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
2842
+ const first = reassembler.addFragment({
2843
+ fragmentationIndicator: 1,
2844
+ mpuSequenceNumber: 42,
2845
+ fragmentCounter: 1,
2846
+ itemId: 1,
2847
+ data: new Uint8Array([170]),
2848
+ timestamp: 1000n,
2849
+ rapFlag: true,
2850
+ packetSequenceNumber: testPacketSequence(42, 1)
2851
+ });
2852
+ expect(first).toBeNull();
2853
+ const assembled = reassembler.addFragment({
2854
+ fragmentationIndicator: 3,
2855
+ mpuSequenceNumber: 42,
2856
+ fragmentCounter: 0,
2857
+ data: new Uint8Array([187]),
2858
+ timestamp: 1000n,
2859
+ rapFlag: true,
2860
+ packetSequenceNumber: testPacketSequence(42, 0)
2861
+ });
2862
+ expect(assembled).not.toBeNull();
2863
+ expect(
2864
+ reassembler.addFragment({
2865
+ fragmentationIndicator: 1,
2866
+ mpuSequenceNumber: 42,
2867
+ fragmentCounter: 1,
2868
+ itemId: 1,
2869
+ data: new Uint8Array([204]),
2870
+ timestamp: 1000n,
2871
+ rapFlag: true,
2872
+ packetSequenceNumber: testPacketSequence(42, 1, 10)
2873
+ })
2874
+ ).toBeNull();
2875
+ reassembler.flush();
2876
+ const r2 = reassembler.addFragment({
2877
+ fragmentationIndicator: 1,
2878
+ mpuSequenceNumber: 42,
2879
+ fragmentCounter: 1,
2880
+ itemId: 1,
2881
+ data: new Uint8Array([221]),
2882
+ timestamp: 1000n,
2883
+ rapFlag: true,
2884
+ packetSequenceNumber: testPacketSequence(42, 1)
2885
+ });
2886
+ expect(r2).toBeNull();
2887
+ const r3 = reassembler.addFragment({
2888
+ fragmentationIndicator: 3,
2889
+ mpuSequenceNumber: 42,
2890
+ fragmentCounter: 0,
2891
+ data: new Uint8Array([238]),
2892
+ timestamp: 1000n,
2893
+ rapFlag: true,
2894
+ packetSequenceNumber: testPacketSequence(42, 0)
2895
+ });
2896
+ expect(r3).not.toBeNull();
2897
+ expect(r3.data).toEqual(new Uint8Array([221, 238]));
2898
+ });
2899
+ describe("createFragment", () => {
2900
+ test("should create fragment from complete timed MMTP packet", () => {
2901
+ const packet = new Uint8Array(12 + 8 + 14 + 5);
2902
+ packet[0] = 1;
2903
+ packet[1] = 0;
2904
+ const view = new DataView(packet.buffer);
2905
+ view.setUint16(2, 1, false);
2906
+ view.setUint32(4, 42, false);
2907
+ view.setUint32(8, 100, false);
2908
+ view.setUint16(12, 25, false);
2909
+ packet[14] = 40;
2910
+ packet[15] = 0;
2911
+ view.setUint32(16, 999, false);
2912
+ view.setUint32(20, 7, false);
2913
+ view.setUint32(24, 1, false);
2914
+ packet[34] = 222;
2915
+ packet[35] = 173;
2916
+ packet[36] = 190;
2917
+ packet[37] = 239;
2918
+ packet[38] = 1;
2919
+ const frag = createFragment(packet);
2920
+ expect(frag.mpuSequenceNumber).toBe(999);
2921
+ expect(frag.fragmentationIndicator).toBe(0);
2922
+ expect(frag.rapFlag).toBe(true);
2923
+ expect(frag.movieFragmentSequenceNumber).toBe(7);
2924
+ expect(frag.sampleNumber).toBe(1);
2925
+ expect(frag.sampleOffset).toBe(0);
2926
+ expect(frag.data.length).toBe(5);
2927
+ });
2928
+ test("reads Item_ID from a non-timed MFU DU header", () => {
2929
+ const packet = new Uint8Array(12 + 8 + 4 + 2);
2930
+ const view = new DataView(packet.buffer);
2931
+ view.setUint16(2, 1, false);
2932
+ view.setUint32(4, 42, false);
2933
+ view.setUint32(8, 100, false);
2934
+ view.setUint16(12, 12, false);
2935
+ packet[14] = 32;
2936
+ view.setUint32(16, 999, false);
2937
+ view.setUint32(20, 16909060, false);
2938
+ packet.set([222, 173], 24);
2939
+ const fragment = createFragment(packet);
2940
+ expect(fragment.itemId).toBe(16909060);
2941
+ expect(fragment.data).toEqual(new Uint8Array([222, 173]));
2942
+ });
2943
+ });
2944
+ describe("processMMTPWithReassembly", () => {
2945
+ test("should skip FEC repair packets", () => {
2946
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
2947
+ const packet = new Uint8Array(12 + 8 + 14 + 4);
2948
+ packet[0] = 16;
2949
+ const result = processMMTPWithReassembly(packet, reassembler);
2950
+ expect(result).toBeNull();
2951
+ });
2952
+ test("should return reassembled MFU for complete packet", () => {
2953
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
2954
+ const packet = new Uint8Array(12 + 8 + 14 + 4);
2955
+ packet[0] = 1;
2956
+ packet[1] = 0;
2957
+ const view = new DataView(packet.buffer);
2958
+ view.setUint16(2, 1, false);
2959
+ view.setUint32(4, 100, false);
2960
+ view.setUint32(8, 1, false);
2961
+ view.setUint16(12, 24, false);
2962
+ packet[14] = 40;
2963
+ view.setUint32(16, 42, false);
2964
+ view.setUint32(20, 7, false);
2965
+ view.setUint32(24, 1, false);
2966
+ packet[34] = 0;
2967
+ packet[35] = 0;
2968
+ packet[36] = 0;
2969
+ packet[37] = 1;
2970
+ const result = processMMTPWithReassembly(packet, reassembler);
2971
+ expect(result).not.toBeNull();
2972
+ expect(result.mpuSequenceNumber).toBe(42);
2973
+ expect(result.rapFlag).toBe(true);
2974
+ });
2975
+ });
2976
+ describe("strict delivery provenance", () => {
2977
+ test("folds to multicast only when every fragment is explicitly multicast", () => {
2978
+ expect(deliveryProvenanceOf([{ source: "multicast" }, { source: "multicast" }])).toBe("multicast");
2979
+ });
2980
+ test("a single moq fragment makes the whole frame mixed", () => {
2981
+ expect(deliveryProvenanceOf([{ source: "multicast" }, { source: "moq" }])).toBe("mixed");
2982
+ expect(deliveryProvenanceOf([{ source: "moq" }, { source: "multicast" }])).toBe("mixed");
2983
+ });
2984
+ test("an unattributed fragment degrades multicast to unknown, never to proven", () => {
2985
+ expect(deliveryProvenanceOf([{ source: "multicast" }, {}])).toBe("unknown");
2986
+ expect(deliveryProvenanceOf([{}])).toBe("unknown");
2987
+ });
2988
+ test("mixed absorbs unknown \u2014 a moq byte is worse news than a missing label", () => {
2989
+ expect(deliveryProvenanceOf([{ source: "moq" }, {}])).toBe("mixed");
2990
+ expect(deliveryProvenanceOf([{}, { source: "moq" }])).toBe("mixed");
2991
+ });
2992
+ test("an empty fragment set proves nothing", () => {
2993
+ expect(deliveryProvenanceOf([])).toBe("unknown");
2994
+ });
2995
+ test("the fold never upgrades a verdict", () => {
2996
+ expect(foldDeliveryProvenance("mixed", "multicast")).toBe("mixed");
2997
+ expect(foldDeliveryProvenance("unknown", "multicast")).toBe("unknown");
2998
+ expect(foldDeliveryProvenance("unknown", "moq")).toBe("mixed");
2999
+ });
3000
+ test("a multicast head with a non-multicast tail is NOT multicast-exclusive", () => {
3001
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
3002
+ reassembler.addFragment({
3003
+ fragmentationIndicator: 1,
3004
+ mpuSequenceNumber: 1,
3005
+ fragmentCounter: 1,
3006
+ itemId: 1,
3007
+ data: new Uint8Array([170]),
3008
+ timestamp: 1000n,
3009
+ rapFlag: true,
3010
+ source: "multicast",
3011
+ packetSequenceNumber: testPacketSequence(1, 1)
3012
+ });
3013
+ const result = reassembler.addFragment({
3014
+ fragmentationIndicator: 3,
3015
+ mpuSequenceNumber: 1,
3016
+ fragmentCounter: 0,
3017
+ data: new Uint8Array([204]),
3018
+ timestamp: 1000n,
3019
+ rapFlag: false,
3020
+ source: "moq",
3021
+ packetSequenceNumber: testPacketSequence(1, 0)
3022
+ });
3023
+ expect(result).not.toBeNull();
3024
+ expect(result.fragmentCount).toBe(2);
3025
+ expect(result.source).toBe("multicast");
3026
+ expect(result.deliveryProvenance).toBe("mixed");
3027
+ });
3028
+ test("an all-multicast fragmented frame is multicast-exclusive", () => {
3029
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
3030
+ reassembler.addFragment({
3031
+ fragmentationIndicator: 1,
3032
+ mpuSequenceNumber: 2,
3033
+ fragmentCounter: 1,
3034
+ itemId: 1,
3035
+ data: new Uint8Array([170]),
3036
+ timestamp: 2000n,
3037
+ rapFlag: true,
3038
+ source: "multicast",
3039
+ packetSequenceNumber: testPacketSequence(2, 1)
3040
+ });
3041
+ const result = reassembler.addFragment({
3042
+ fragmentationIndicator: 3,
3043
+ mpuSequenceNumber: 2,
3044
+ fragmentCounter: 0,
3045
+ data: new Uint8Array([204]),
3046
+ timestamp: 2000n,
3047
+ rapFlag: false,
3048
+ source: "multicast",
3049
+ packetSequenceNumber: testPacketSequence(2, 0)
3050
+ });
3051
+ expect(result).not.toBeNull();
3052
+ expect(result.deliveryProvenance).toBe("multicast");
3053
+ });
3054
+ test("an unlabelled complete frame proves nothing", () => {
3055
+ const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
3056
+ const result = reassembler.addFragment({
3057
+ fragmentationIndicator: 0,
3058
+ mpuSequenceNumber: 3,
3059
+ fragmentCounter: 0,
3060
+ itemId: 1,
3061
+ data: new Uint8Array([1]),
3062
+ timestamp: 3000n,
3063
+ rapFlag: true
3064
+ });
3065
+ expect(result).not.toBeNull();
3066
+ expect(result.deliveryProvenance).toBe("unknown");
3067
+ });
3068
+ });
319
3069
  }
320
- //# sourceMappingURL=mfu-reassembler.js.map
3070
+ export {
3071
+ COMPLETED_ADDRESS_WINDOW_MULTIPLE,
3072
+ EVICTION_GUARD_DATA_UNITS,
3073
+ EVICTION_GUARD_MAX_BUFFERED,
3074
+ MFUReassembler,
3075
+ MFU_REASSEMBLY_ERROR_CODES,
3076
+ MfuReassemblyError,
3077
+ createFragment,
3078
+ deliveryProvenanceOf,
3079
+ foldDeliveryProvenance,
3080
+ isMfuReassemblyError,
3081
+ isMfuReassemblyErrorCode,
3082
+ parkedRunHoldsASlot,
3083
+ processMMTPWithReassembly
3084
+ };
3085
+ //# sourceMappingURL=mfu-reassembler.js.map