@blockcast/mmt-container 0.3.0 → 0.4.0-main.1cd3cdea89da

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 (605) hide show
  1. package/README.md +33 -16
  2. package/dist/browser-runtime.js +14814 -14255
  3. package/dist/browser-runtime.js.map +4 -4
  4. package/dist/carp-catalog.d.ts +24 -19
  5. package/dist/carp-catalog.d.ts.map +1 -1
  6. package/dist/carp-catalog.js +29 -14
  7. package/dist/carp-catalog.js.map +4 -4
  8. package/dist/catalog-fec-params.d.ts +88 -28
  9. package/dist/catalog-fec-params.d.ts.map +1 -1
  10. package/dist/catalog-fec-params.js +60 -23
  11. package/dist/catalog-fec-params.js.map +4 -4
  12. package/dist/catalog-network-source.js +1 -7
  13. package/dist/catalog-network-source.js.map +4 -4
  14. package/dist/cmaf-block-assembler.d.ts +0 -2
  15. package/dist/cmaf-block-assembler.d.ts.map +1 -1
  16. package/dist/cmaf-block-assembler.js +0 -1
  17. package/dist/cmaf-block-assembler.js.map +2 -2
  18. package/dist/cmcd-producer.d.ts.map +1 -1
  19. package/dist/cmcd-producer.js +0 -4
  20. package/dist/cmcd-producer.js.map +2 -2
  21. package/dist/delivery-catalog.d.ts +124 -0
  22. package/dist/delivery-catalog.d.ts.map +1 -0
  23. package/dist/delivery-catalog.js +1857 -0
  24. package/dist/delivery-catalog.js.map +7 -0
  25. package/dist/delta-fallback-config.d.ts +42 -0
  26. package/dist/delta-fallback-config.d.ts.map +1 -0
  27. package/dist/delta-fallback-config.js +59 -0
  28. package/dist/delta-fallback-config.js.map +7 -0
  29. package/dist/fec-manager.d.ts +52 -30
  30. package/dist/fec-manager.d.ts.map +1 -1
  31. package/dist/fec-manager.js +338 -293
  32. package/dist/fec-manager.js.map +4 -4
  33. package/dist/fec-state.d.ts +19 -31
  34. package/dist/fec-state.d.ts.map +1 -1
  35. package/dist/fec-state.js +4 -10
  36. package/dist/fec-state.js.map +4 -4
  37. package/dist/fec-worker-setup.d.ts +18 -14
  38. package/dist/fec-worker-setup.d.ts.map +1 -1
  39. package/dist/fec-worker-setup.js +81 -96
  40. package/dist/fec-worker-setup.js.map +4 -4
  41. package/dist/index.d.ts +5 -3
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +16 -0
  44. package/dist/index.js.map +2 -2
  45. package/dist/iso-block-locator.js +1 -7
  46. package/dist/iso-block-locator.js.map +4 -4
  47. package/dist/media-router.d.ts +20 -8
  48. package/dist/media-router.d.ts.map +1 -1
  49. package/dist/media-router.js +46 -21
  50. package/dist/media-router.js.map +2 -2
  51. package/dist/mfu-reassembler.js +24 -30
  52. package/dist/mfu-reassembler.js.map +4 -4
  53. package/dist/mmtp-container-parser.d.ts +14 -11
  54. package/dist/mmtp-container-parser.d.ts.map +1 -1
  55. package/dist/mmtp-container-parser.js +126 -99
  56. package/dist/mmtp-container-parser.js.map +4 -4
  57. package/dist/mmtp-container-parser.no-modules.js +151 -108
  58. package/dist/mmtp-container-parser.no-modules.js.map +4 -4
  59. package/dist/mmtp-decode-pipeline.js +126 -99
  60. package/dist/mmtp-decode-pipeline.js.map +4 -4
  61. package/dist/mmtp-router.d.ts +51 -29
  62. package/dist/mmtp-router.d.ts.map +1 -1
  63. package/dist/mmtp-router.js +260 -326
  64. package/dist/mmtp-router.js.map +4 -4
  65. package/dist/multicast-input.js +1 -7
  66. package/dist/multicast-input.js.map +4 -4
  67. package/dist/multicast-manager.d.ts +72 -6
  68. package/dist/multicast-manager.d.ts.map +1 -1
  69. package/dist/multicast-manager.js +882 -673
  70. package/dist/multicast-manager.js.map +4 -4
  71. package/dist/multicast-transport.d.ts +1 -1
  72. package/dist/multicast-transport.d.ts.map +1 -1
  73. package/dist/multicast-transport.js +44 -26
  74. package/dist/multicast-transport.js.map +4 -4
  75. package/dist/multicast-unwrap.js +29 -35
  76. package/dist/multicast-unwrap.js.map +4 -4
  77. package/dist/no-modules.js +147 -106
  78. package/dist/no-modules.js.map +4 -4
  79. package/dist/repair-fetch.d.ts +116 -21
  80. package/dist/repair-fetch.d.ts.map +1 -1
  81. package/dist/repair-fetch.js +114 -34
  82. package/dist/repair-fetch.js.map +4 -4
  83. package/dist/repair-layers.d.ts +18 -7
  84. package/dist/repair-layers.d.ts.map +1 -1
  85. package/dist/repair-layers.js +12 -10
  86. package/dist/repair-layers.js.map +4 -4
  87. package/dist/signaling.js +2 -11
  88. package/dist/signaling.js.map +4 -4
  89. package/dist/timestamp-codec.js +24 -30
  90. package/dist/timestamp-codec.js.map +4 -4
  91. package/dist/track-session.d.ts +7 -0
  92. package/dist/track-session.d.ts.map +1 -1
  93. package/dist/track-session.js +15 -8
  94. package/dist/track-session.js.map +2 -2
  95. package/dist/wasm-mfu-reassembler.js +1 -7
  96. package/dist/wasm-mfu-reassembler.js.map +4 -4
  97. package/dist/wasm-mmtp-container-parser.js +126 -99
  98. package/dist/wasm-mmtp-container-parser.js.map +4 -4
  99. package/package.json +7 -14
  100. package/schemas/catalog.schema.json +3 -3
  101. package/src/carp-catalog.ts +46 -24
  102. package/src/catalog-fec-params.ts +173 -64
  103. package/src/cmaf-block-assembler.ts +1 -4
  104. package/src/cmcd-producer.ts +0 -6
  105. package/src/delivery-catalog.ts +498 -0
  106. package/src/delta-fallback-config.ts +72 -0
  107. package/src/fec-manager.ts +457 -334
  108. package/src/fec-state.ts +20 -32
  109. package/src/fec-worker-setup.ts +109 -90
  110. package/src/index.ts +32 -0
  111. package/src/media-router.ts +59 -21
  112. package/src/mmtp-container-parser.ts +106 -20
  113. package/src/mmtp-router.ts +189 -330
  114. package/src/multicast-manager.ts +265 -86
  115. package/src/multicast-transport.ts +16 -9
  116. package/src/repair-fetch.ts +273 -43
  117. package/src/repair-layers.ts +28 -8
  118. package/src/track-session.ts +28 -9
  119. package/test-vectors/catalog/README.md +37 -17
  120. package/test-vectors/catalog/negative/fec-block-profile-retired.json +52 -0
  121. package/test-vectors/catalog/negative/fec-enhancement-repair-removed.json +1 -1
  122. package/test-vectors/catalog/negative/fec-repair-layer-geometry.json +1 -1
  123. package/test-vectors/catalog/negative/fec-repair-layer-missing-symbols.json +1 -1
  124. package/test-vectors/catalog/negative/fec-repair-multiple-depends.json +1 -1
  125. package/test-vectors/catalog/negative/fec-repair-overlay-with-layer.json +1 -1
  126. package/test-vectors/catalog/negative/fec-repair-priority-out-of-band.json +1 -1
  127. package/test-vectors/catalog/negative/fec-repair-source-symbols-without-scope.json +1 -1
  128. package/test-vectors/catalog/negative/multicast-endpoint-relay-address.json +39 -0
  129. package/test-vectors/catalog/negative/multicast-network-source-empty.json +1 -1
  130. package/test-vectors/catalog/negative/raptorq-source-symbols-over-max.json +1 -1
  131. package/test-vectors/catalog/negative/raptorq-unaligned-symbol.json +1 -1
  132. package/test-vectors/catalog/positive/fec-layered-repair.json +2 -2
  133. package/test-vectors/catalog/positive/fec-multicast.json +2 -1
  134. package/test-vectors/catalog/positive/multicast-amt-manual-relay.json +38 -0
  135. package/test-vectors/catalog/positive/multicast-native-ssm-no-network-source.json +2 -1
  136. package/test-vectors/catalog-timing.json +356 -0
  137. package/test-vectors/catalog-validation.json +40 -5
  138. package/test-vectors/delivery-catalog/README.md +11 -0
  139. package/test-vectors/delivery-catalog/negative/missing-block-profile.json +58 -0
  140. package/test-vectors/delivery-catalog/negative/packet-id-signaling.json +59 -0
  141. package/test-vectors/delivery-catalog/negative/retired-k-padded-block-profile.json +59 -0
  142. package/test-vectors/delivery-catalog/positive/dash.json +56 -0
  143. package/test-vectors/delivery-catalog/positive/mmtp-source-only.json +43 -0
  144. package/node_modules/@blockcast/fec-worker/README.md +0 -223
  145. package/node_modules/@blockcast/fec-worker/dist/.build-stamp +0 -0
  146. package/node_modules/@blockcast/fec-worker/dist/block-order.d.ts +0 -38
  147. package/node_modules/@blockcast/fec-worker/dist/block-order.d.ts.map +0 -1
  148. package/node_modules/@blockcast/fec-worker/dist/block-order.js +0 -78
  149. package/node_modules/@blockcast/fec-worker/dist/block-order.js.map +0 -1
  150. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts +0 -93
  151. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts.map +0 -1
  152. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js +0 -346
  153. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js.map +0 -1
  154. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts +0 -380
  155. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts.map +0 -1
  156. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js +0 -11
  157. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js.map +0 -1
  158. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts +0 -14
  159. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts.map +0 -1
  160. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js +0 -859
  161. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js.map +0 -7
  162. package/node_modules/@blockcast/fec-worker/dist/index.d.ts +0 -10
  163. package/node_modules/@blockcast/fec-worker/dist/index.d.ts.map +0 -1
  164. package/node_modules/@blockcast/fec-worker/dist/index.js +0 -8
  165. package/node_modules/@blockcast/fec-worker/dist/index.js.map +0 -1
  166. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts +0 -20
  167. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts.map +0 -1
  168. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js +0 -370
  169. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js.map +0 -7
  170. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts +0 -104
  171. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts.map +0 -1
  172. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js +0 -181
  173. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js.map +0 -1
  174. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts +0 -194
  175. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts.map +0 -1
  176. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js +0 -62
  177. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js.map +0 -1
  178. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts +0 -9
  179. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts.map +0 -1
  180. package/node_modules/@blockcast/fec-worker/dist/transfer.js +0 -13
  181. package/node_modules/@blockcast/fec-worker/dist/transfer.js.map +0 -1
  182. package/node_modules/@blockcast/fec-worker/package.json +0 -59
  183. package/node_modules/@blockcast/fec-worker/src/block-order.ts +0 -93
  184. package/node_modules/@blockcast/fec-worker/src/fec-worker-client.ts +0 -392
  185. package/node_modules/@blockcast/fec-worker/src/fec-worker-types.ts +0 -399
  186. package/node_modules/@blockcast/fec-worker/src/fec-worker.ts +0 -1502
  187. package/node_modules/@blockcast/fec-worker/src/index.ts +0 -35
  188. package/node_modules/@blockcast/fec-worker/src/shred-fec-worker.ts +0 -589
  189. package/node_modules/@blockcast/fec-worker/src/shred-worker-client.ts +0 -222
  190. package/node_modules/@blockcast/fec-worker/src/shred-worker-types.ts +0 -209
  191. package/node_modules/@blockcast/fec-worker/src/transfer.ts +0 -12
  192. package/node_modules/@blockcast/mmt-alta-parse/README.md +0 -181
  193. package/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  194. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +0 -57
  195. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +0 -1
  196. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js +0 -115
  197. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +0 -1
  198. package/node_modules/@blockcast/mmt-alta-parse/package.json +0 -50
  199. package/node_modules/@blockcast/mmt-alta-parse/src/index.ts +0 -136
  200. package/node_modules/@blockcast/mmt-base/README.md +0 -134
  201. package/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  202. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts +0 -55
  203. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts.map +0 -1
  204. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js +0 -128
  205. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js.map +0 -1
  206. package/node_modules/@blockcast/mmt-base/dist/index.d.ts +0 -7
  207. package/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +0 -1
  208. package/node_modules/@blockcast/mmt-base/dist/index.js +0 -7
  209. package/node_modules/@blockcast/mmt-base/dist/index.js.map +0 -1
  210. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +0 -292
  211. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +0 -1
  212. package/node_modules/@blockcast/mmt-base/dist/mmtp.js +0 -612
  213. package/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +0 -1
  214. package/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts +0 -18
  215. package/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts.map +0 -1
  216. package/node_modules/@blockcast/mmt-base/dist/net-constants.js +0 -18
  217. package/node_modules/@blockcast/mmt-base/dist/net-constants.js.map +0 -1
  218. package/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts +0 -47
  219. package/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts.map +0 -1
  220. package/node_modules/@blockcast/mmt-base/dist/ntp-short.js +0 -54
  221. package/node_modules/@blockcast/mmt-base/dist/ntp-short.js.map +0 -1
  222. package/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts +0 -57
  223. package/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts.map +0 -1
  224. package/node_modules/@blockcast/mmt-base/dist/serial-number.js +0 -98
  225. package/node_modules/@blockcast/mmt-base/dist/serial-number.js.map +0 -1
  226. package/node_modules/@blockcast/mmt-base/dist/signalling.d.ts +0 -312
  227. package/node_modules/@blockcast/mmt-base/dist/signalling.d.ts.map +0 -1
  228. package/node_modules/@blockcast/mmt-base/dist/signalling.js +0 -637
  229. package/node_modules/@blockcast/mmt-base/dist/signalling.js.map +0 -1
  230. package/node_modules/@blockcast/mmt-base/package.json +0 -49
  231. package/node_modules/@blockcast/mmt-base/src/fec-block-profile.ts +0 -145
  232. package/node_modules/@blockcast/mmt-base/src/index.ts +0 -6
  233. package/node_modules/@blockcast/mmt-base/src/mmtp.ts +0 -892
  234. package/node_modules/@blockcast/mmt-base/src/net-constants.ts +0 -17
  235. package/node_modules/@blockcast/mmt-base/src/ntp-short.ts +0 -67
  236. package/node_modules/@blockcast/mmt-base/src/serial-number.ts +0 -107
  237. package/node_modules/@blockcast/mmt-base/src/signalling.ts +0 -862
  238. package/node_modules/@blockcast/mmt-fec/README.md +0 -271
  239. package/node_modules/@blockcast/mmt-fec/dist/.build-stamp +0 -0
  240. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts +0 -82
  241. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts.map +0 -1
  242. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js +0 -296
  243. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js.map +0 -1
  244. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts +0 -106
  245. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts.map +0 -1
  246. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js +0 -288
  247. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js.map +0 -1
  248. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts +0 -46
  249. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts.map +0 -1
  250. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js +0 -96
  251. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js.map +0 -1
  252. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts +0 -33
  253. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts.map +0 -1
  254. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js +0 -57
  255. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js.map +0 -1
  256. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts +0 -205
  257. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts.map +0 -1
  258. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js +0 -240
  259. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js.map +0 -1
  260. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts +0 -28
  261. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts.map +0 -1
  262. package/node_modules/@blockcast/mmt-fec/dist/index.js +0 -28
  263. package/node_modules/@blockcast/mmt-fec/dist/index.js.map +0 -1
  264. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts +0 -20
  265. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts.map +0 -1
  266. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js +0 -28
  267. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js.map +0 -1
  268. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts +0 -258
  269. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts.map +0 -1
  270. package/node_modules/@blockcast/mmt-fec/dist/oti.js +0 -382
  271. package/node_modules/@blockcast/mmt-fec/dist/oti.js.map +0 -1
  272. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts +0 -41
  273. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts.map +0 -1
  274. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js +0 -81
  275. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js.map +0 -1
  276. package/node_modules/@blockcast/mmt-fec/dist/repair-group.d.ts +0 -82
  277. package/node_modules/@blockcast/mmt-fec/dist/repair-group.d.ts.map +0 -1
  278. package/node_modules/@blockcast/mmt-fec/dist/repair-group.js +0 -170
  279. package/node_modules/@blockcast/mmt-fec/dist/repair-group.js.map +0 -1
  280. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/README.md +0 -181
  281. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  282. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +0 -57
  283. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +0 -1
  284. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js +0 -115
  285. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +0 -1
  286. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/package.json +0 -50
  287. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/src/index.ts +0 -136
  288. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/README.md +0 -134
  289. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  290. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts +0 -55
  291. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts.map +0 -1
  292. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js +0 -128
  293. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js.map +0 -1
  294. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts +0 -7
  295. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +0 -1
  296. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js +0 -7
  297. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js.map +0 -1
  298. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +0 -292
  299. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +0 -1
  300. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js +0 -612
  301. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +0 -1
  302. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts +0 -18
  303. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts.map +0 -1
  304. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.js +0 -18
  305. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.js.map +0 -1
  306. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts +0 -47
  307. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts.map +0 -1
  308. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.js +0 -54
  309. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.js.map +0 -1
  310. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts +0 -57
  311. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts.map +0 -1
  312. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.js +0 -98
  313. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.js.map +0 -1
  314. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.d.ts +0 -312
  315. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.d.ts.map +0 -1
  316. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.js +0 -637
  317. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.js.map +0 -1
  318. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/package.json +0 -49
  319. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/fec-block-profile.ts +0 -145
  320. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/index.ts +0 -6
  321. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/mmtp.ts +0 -892
  322. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/net-constants.ts +0 -17
  323. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/ntp-short.ts +0 -67
  324. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/serial-number.ts +0 -107
  325. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/signalling.ts +0 -862
  326. package/node_modules/@blockcast/mmt-fec/package.json +0 -79
  327. package/node_modules/@blockcast/mmt-fec/src/block-manager.ts +0 -371
  328. package/node_modules/@blockcast/mmt-fec/src/block-processor.ts +0 -439
  329. package/node_modules/@blockcast/mmt-fec/src/cmaf-recovery.ts +0 -127
  330. package/node_modules/@blockcast/mmt-fec/src/cmaf-symbols.ts +0 -68
  331. package/node_modules/@blockcast/mmt-fec/src/fec-block-mapper.ts +0 -310
  332. package/node_modules/@blockcast/mmt-fec/src/index.ts +0 -94
  333. package/node_modules/@blockcast/mmt-fec/src/loc-symbols.ts +0 -34
  334. package/node_modules/@blockcast/mmt-fec/src/oti.ts +0 -463
  335. package/node_modules/@blockcast/mmt-fec/src/reorder-buffer.ts +0 -101
  336. package/node_modules/@blockcast/mmt-fec/src/repair-group.ts +0 -195
  337. package/node_modules/@blockcast/mmt-manifest-verify/dist/.build-stamp +0 -0
  338. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts +0 -28
  339. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts.map +0 -1
  340. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js +0 -128
  341. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js.map +0 -1
  342. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts +0 -4
  343. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts.map +0 -1
  344. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js +0 -4
  345. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js.map +0 -1
  346. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts +0 -37
  347. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts.map +0 -1
  348. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js +0 -179
  349. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js.map +0 -1
  350. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts +0 -99
  351. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts.map +0 -1
  352. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js +0 -492
  353. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js.map +0 -1
  354. package/node_modules/@blockcast/mmt-manifest-verify/package.json +0 -45
  355. package/node_modules/@blockcast/mmt-manifest-verify/src/admission.ts +0 -171
  356. package/node_modules/@blockcast/mmt-manifest-verify/src/index.ts +0 -3
  357. package/node_modules/@blockcast/mmt-manifest-verify/src/source-auth.ts +0 -230
  358. package/node_modules/@blockcast/mmt-manifest-verify/src/verifier.ts +0 -669
  359. package/node_modules/@noble/hashes/LICENSE +0 -21
  360. package/node_modules/@noble/hashes/README.md +0 -521
  361. package/node_modules/@noble/hashes/_assert.d.ts +0 -17
  362. package/node_modules/@noble/hashes/_assert.d.ts.map +0 -1
  363. package/node_modules/@noble/hashes/_assert.js +0 -18
  364. package/node_modules/@noble/hashes/_assert.js.map +0 -1
  365. package/node_modules/@noble/hashes/_blake.d.ts +0 -14
  366. package/node_modules/@noble/hashes/_blake.d.ts.map +0 -1
  367. package/node_modules/@noble/hashes/_blake.js +0 -50
  368. package/node_modules/@noble/hashes/_blake.js.map +0 -1
  369. package/node_modules/@noble/hashes/_md.d.ts +0 -51
  370. package/node_modules/@noble/hashes/_md.d.ts.map +0 -1
  371. package/node_modules/@noble/hashes/_md.js +0 -162
  372. package/node_modules/@noble/hashes/_md.js.map +0 -1
  373. package/node_modules/@noble/hashes/_u64.d.ts +0 -55
  374. package/node_modules/@noble/hashes/_u64.d.ts.map +0 -1
  375. package/node_modules/@noble/hashes/_u64.js +0 -90
  376. package/node_modules/@noble/hashes/_u64.js.map +0 -1
  377. package/node_modules/@noble/hashes/argon2.d.ts +0 -32
  378. package/node_modules/@noble/hashes/argon2.d.ts.map +0 -1
  379. package/node_modules/@noble/hashes/argon2.js +0 -401
  380. package/node_modules/@noble/hashes/argon2.js.map +0 -1
  381. package/node_modules/@noble/hashes/blake1.d.ts +0 -106
  382. package/node_modules/@noble/hashes/blake1.d.ts.map +0 -1
  383. package/node_modules/@noble/hashes/blake1.js +0 -459
  384. package/node_modules/@noble/hashes/blake1.js.map +0 -1
  385. package/node_modules/@noble/hashes/blake2.d.ts +0 -116
  386. package/node_modules/@noble/hashes/blake2.d.ts.map +0 -1
  387. package/node_modules/@noble/hashes/blake2.js +0 -420
  388. package/node_modules/@noble/hashes/blake2.js.map +0 -1
  389. package/node_modules/@noble/hashes/blake2b.d.ts +0 -11
  390. package/node_modules/@noble/hashes/blake2b.d.ts.map +0 -1
  391. package/node_modules/@noble/hashes/blake2b.js +0 -14
  392. package/node_modules/@noble/hashes/blake2b.js.map +0 -1
  393. package/node_modules/@noble/hashes/blake2s.d.ts +0 -20
  394. package/node_modules/@noble/hashes/blake2s.d.ts.map +0 -1
  395. package/node_modules/@noble/hashes/blake2s.js +0 -24
  396. package/node_modules/@noble/hashes/blake2s.js.map +0 -1
  397. package/node_modules/@noble/hashes/blake3.d.ts +0 -54
  398. package/node_modules/@noble/hashes/blake3.d.ts.map +0 -1
  399. package/node_modules/@noble/hashes/blake3.js +0 -255
  400. package/node_modules/@noble/hashes/blake3.js.map +0 -1
  401. package/node_modules/@noble/hashes/crypto.d.ts +0 -2
  402. package/node_modules/@noble/hashes/crypto.d.ts.map +0 -1
  403. package/node_modules/@noble/hashes/crypto.js +0 -5
  404. package/node_modules/@noble/hashes/crypto.js.map +0 -1
  405. package/node_modules/@noble/hashes/cryptoNode.d.ts +0 -2
  406. package/node_modules/@noble/hashes/cryptoNode.d.ts.map +0 -1
  407. package/node_modules/@noble/hashes/cryptoNode.js +0 -18
  408. package/node_modules/@noble/hashes/cryptoNode.js.map +0 -1
  409. package/node_modules/@noble/hashes/eskdf.d.ts +0 -47
  410. package/node_modules/@noble/hashes/eskdf.d.ts.map +0 -1
  411. package/node_modules/@noble/hashes/eskdf.js +0 -166
  412. package/node_modules/@noble/hashes/eskdf.js.map +0 -1
  413. package/node_modules/@noble/hashes/esm/_assert.d.ts +0 -17
  414. package/node_modules/@noble/hashes/esm/_assert.d.ts.map +0 -1
  415. package/node_modules/@noble/hashes/esm/_assert.js +0 -15
  416. package/node_modules/@noble/hashes/esm/_assert.js.map +0 -1
  417. package/node_modules/@noble/hashes/esm/_blake.d.ts +0 -14
  418. package/node_modules/@noble/hashes/esm/_blake.d.ts.map +0 -1
  419. package/node_modules/@noble/hashes/esm/_blake.js +0 -45
  420. package/node_modules/@noble/hashes/esm/_blake.js.map +0 -1
  421. package/node_modules/@noble/hashes/esm/_md.d.ts +0 -51
  422. package/node_modules/@noble/hashes/esm/_md.d.ts.map +0 -1
  423. package/node_modules/@noble/hashes/esm/_md.js +0 -155
  424. package/node_modules/@noble/hashes/esm/_md.js.map +0 -1
  425. package/node_modules/@noble/hashes/esm/_u64.d.ts +0 -55
  426. package/node_modules/@noble/hashes/esm/_u64.d.ts.map +0 -1
  427. package/node_modules/@noble/hashes/esm/_u64.js +0 -67
  428. package/node_modules/@noble/hashes/esm/_u64.js.map +0 -1
  429. package/node_modules/@noble/hashes/esm/argon2.d.ts +0 -32
  430. package/node_modules/@noble/hashes/esm/argon2.d.ts.map +0 -1
  431. package/node_modules/@noble/hashes/esm/argon2.js +0 -392
  432. package/node_modules/@noble/hashes/esm/argon2.js.map +0 -1
  433. package/node_modules/@noble/hashes/esm/blake1.d.ts +0 -106
  434. package/node_modules/@noble/hashes/esm/blake1.d.ts.map +0 -1
  435. package/node_modules/@noble/hashes/esm/blake1.js +0 -452
  436. package/node_modules/@noble/hashes/esm/blake1.js.map +0 -1
  437. package/node_modules/@noble/hashes/esm/blake2.d.ts +0 -116
  438. package/node_modules/@noble/hashes/esm/blake2.d.ts.map +0 -1
  439. package/node_modules/@noble/hashes/esm/blake2.js +0 -413
  440. package/node_modules/@noble/hashes/esm/blake2.js.map +0 -1
  441. package/node_modules/@noble/hashes/esm/blake2b.d.ts +0 -11
  442. package/node_modules/@noble/hashes/esm/blake2b.d.ts.map +0 -1
  443. package/node_modules/@noble/hashes/esm/blake2b.js +0 -11
  444. package/node_modules/@noble/hashes/esm/blake2b.js.map +0 -1
  445. package/node_modules/@noble/hashes/esm/blake2s.d.ts +0 -20
  446. package/node_modules/@noble/hashes/esm/blake2s.d.ts.map +0 -1
  447. package/node_modules/@noble/hashes/esm/blake2s.js +0 -21
  448. package/node_modules/@noble/hashes/esm/blake2s.js.map +0 -1
  449. package/node_modules/@noble/hashes/esm/blake3.d.ts +0 -54
  450. package/node_modules/@noble/hashes/esm/blake3.d.ts.map +0 -1
  451. package/node_modules/@noble/hashes/esm/blake3.js +0 -251
  452. package/node_modules/@noble/hashes/esm/blake3.js.map +0 -1
  453. package/node_modules/@noble/hashes/esm/crypto.d.ts +0 -2
  454. package/node_modules/@noble/hashes/esm/crypto.d.ts.map +0 -1
  455. package/node_modules/@noble/hashes/esm/crypto.js +0 -2
  456. package/node_modules/@noble/hashes/esm/crypto.js.map +0 -1
  457. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts +0 -2
  458. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts.map +0 -1
  459. package/node_modules/@noble/hashes/esm/cryptoNode.js +0 -15
  460. package/node_modules/@noble/hashes/esm/cryptoNode.js.map +0 -1
  461. package/node_modules/@noble/hashes/esm/eskdf.d.ts +0 -47
  462. package/node_modules/@noble/hashes/esm/eskdf.d.ts.map +0 -1
  463. package/node_modules/@noble/hashes/esm/eskdf.js +0 -160
  464. package/node_modules/@noble/hashes/esm/eskdf.js.map +0 -1
  465. package/node_modules/@noble/hashes/esm/hkdf.d.ts +0 -36
  466. package/node_modules/@noble/hashes/esm/hkdf.d.ts.map +0 -1
  467. package/node_modules/@noble/hashes/esm/hkdf.js +0 -82
  468. package/node_modules/@noble/hashes/esm/hkdf.js.map +0 -1
  469. package/node_modules/@noble/hashes/esm/hmac.d.ts +0 -35
  470. package/node_modules/@noble/hashes/esm/hmac.d.ts.map +0 -1
  471. package/node_modules/@noble/hashes/esm/hmac.js +0 -86
  472. package/node_modules/@noble/hashes/esm/hmac.js.map +0 -1
  473. package/node_modules/@noble/hashes/esm/index.d.ts +0 -2
  474. package/node_modules/@noble/hashes/esm/index.d.ts.map +0 -1
  475. package/node_modules/@noble/hashes/esm/index.js +0 -33
  476. package/node_modules/@noble/hashes/esm/index.js.map +0 -1
  477. package/node_modules/@noble/hashes/esm/legacy.d.ts +0 -71
  478. package/node_modules/@noble/hashes/esm/legacy.d.ts.map +0 -1
  479. package/node_modules/@noble/hashes/esm/legacy.js +0 -281
  480. package/node_modules/@noble/hashes/esm/legacy.js.map +0 -1
  481. package/node_modules/@noble/hashes/esm/package.json +0 -10
  482. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts +0 -23
  483. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts.map +0 -1
  484. package/node_modules/@noble/hashes/esm/pbkdf2.js +0 -97
  485. package/node_modules/@noble/hashes/esm/pbkdf2.js.map +0 -1
  486. package/node_modules/@noble/hashes/esm/ripemd160.d.ts +0 -13
  487. package/node_modules/@noble/hashes/esm/ripemd160.d.ts.map +0 -1
  488. package/node_modules/@noble/hashes/esm/ripemd160.js +0 -13
  489. package/node_modules/@noble/hashes/esm/ripemd160.js.map +0 -1
  490. package/node_modules/@noble/hashes/esm/scrypt.d.ts +0 -34
  491. package/node_modules/@noble/hashes/esm/scrypt.d.ts.map +0 -1
  492. package/node_modules/@noble/hashes/esm/scrypt.js +0 -228
  493. package/node_modules/@noble/hashes/esm/scrypt.js.map +0 -1
  494. package/node_modules/@noble/hashes/esm/sha1.d.ts +0 -11
  495. package/node_modules/@noble/hashes/esm/sha1.d.ts.map +0 -1
  496. package/node_modules/@noble/hashes/esm/sha1.js +0 -11
  497. package/node_modules/@noble/hashes/esm/sha1.js.map +0 -1
  498. package/node_modules/@noble/hashes/esm/sha2.d.ts +0 -159
  499. package/node_modules/@noble/hashes/esm/sha2.d.ts.map +0 -1
  500. package/node_modules/@noble/hashes/esm/sha2.js +0 -375
  501. package/node_modules/@noble/hashes/esm/sha2.js.map +0 -1
  502. package/node_modules/@noble/hashes/esm/sha256.d.ts +0 -20
  503. package/node_modules/@noble/hashes/esm/sha256.d.ts.map +0 -1
  504. package/node_modules/@noble/hashes/esm/sha256.js +0 -20
  505. package/node_modules/@noble/hashes/esm/sha256.js.map +0 -1
  506. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts +0 -142
  507. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts.map +0 -1
  508. package/node_modules/@noble/hashes/esm/sha3-addons.js +0 -393
  509. package/node_modules/@noble/hashes/esm/sha3-addons.js.map +0 -1
  510. package/node_modules/@noble/hashes/esm/sha3.d.ts +0 -53
  511. package/node_modules/@noble/hashes/esm/sha3.d.ts.map +0 -1
  512. package/node_modules/@noble/hashes/esm/sha3.js +0 -234
  513. package/node_modules/@noble/hashes/esm/sha3.js.map +0 -1
  514. package/node_modules/@noble/hashes/esm/sha512.d.ts +0 -26
  515. package/node_modules/@noble/hashes/esm/sha512.d.ts.map +0 -1
  516. package/node_modules/@noble/hashes/esm/sha512.js +0 -26
  517. package/node_modules/@noble/hashes/esm/sha512.js.map +0 -1
  518. package/node_modules/@noble/hashes/esm/utils.d.ts +0 -161
  519. package/node_modules/@noble/hashes/esm/utils.d.ts.map +0 -1
  520. package/node_modules/@noble/hashes/esm/utils.js +0 -281
  521. package/node_modules/@noble/hashes/esm/utils.js.map +0 -1
  522. package/node_modules/@noble/hashes/hkdf.d.ts +0 -36
  523. package/node_modules/@noble/hashes/hkdf.d.ts.map +0 -1
  524. package/node_modules/@noble/hashes/hkdf.js +0 -88
  525. package/node_modules/@noble/hashes/hkdf.js.map +0 -1
  526. package/node_modules/@noble/hashes/hmac.d.ts +0 -35
  527. package/node_modules/@noble/hashes/hmac.d.ts.map +0 -1
  528. package/node_modules/@noble/hashes/hmac.js +0 -91
  529. package/node_modules/@noble/hashes/hmac.js.map +0 -1
  530. package/node_modules/@noble/hashes/index.d.ts +0 -1
  531. package/node_modules/@noble/hashes/index.d.ts.map +0 -1
  532. package/node_modules/@noble/hashes/index.js +0 -33
  533. package/node_modules/@noble/hashes/index.js.map +0 -1
  534. package/node_modules/@noble/hashes/legacy.d.ts +0 -71
  535. package/node_modules/@noble/hashes/legacy.d.ts.map +0 -1
  536. package/node_modules/@noble/hashes/legacy.js +0 -287
  537. package/node_modules/@noble/hashes/legacy.js.map +0 -1
  538. package/node_modules/@noble/hashes/package.json +0 -266
  539. package/node_modules/@noble/hashes/pbkdf2.d.ts +0 -23
  540. package/node_modules/@noble/hashes/pbkdf2.d.ts.map +0 -1
  541. package/node_modules/@noble/hashes/pbkdf2.js +0 -101
  542. package/node_modules/@noble/hashes/pbkdf2.js.map +0 -1
  543. package/node_modules/@noble/hashes/ripemd160.d.ts +0 -13
  544. package/node_modules/@noble/hashes/ripemd160.d.ts.map +0 -1
  545. package/node_modules/@noble/hashes/ripemd160.js +0 -16
  546. package/node_modules/@noble/hashes/ripemd160.js.map +0 -1
  547. package/node_modules/@noble/hashes/scrypt.d.ts +0 -34
  548. package/node_modules/@noble/hashes/scrypt.d.ts.map +0 -1
  549. package/node_modules/@noble/hashes/scrypt.js +0 -232
  550. package/node_modules/@noble/hashes/scrypt.js.map +0 -1
  551. package/node_modules/@noble/hashes/sha1.d.ts +0 -11
  552. package/node_modules/@noble/hashes/sha1.d.ts.map +0 -1
  553. package/node_modules/@noble/hashes/sha1.js +0 -14
  554. package/node_modules/@noble/hashes/sha1.js.map +0 -1
  555. package/node_modules/@noble/hashes/sha2.d.ts +0 -159
  556. package/node_modules/@noble/hashes/sha2.d.ts.map +0 -1
  557. package/node_modules/@noble/hashes/sha2.js +0 -384
  558. package/node_modules/@noble/hashes/sha2.js.map +0 -1
  559. package/node_modules/@noble/hashes/sha256.d.ts +0 -20
  560. package/node_modules/@noble/hashes/sha256.d.ts.map +0 -1
  561. package/node_modules/@noble/hashes/sha256.js +0 -23
  562. package/node_modules/@noble/hashes/sha256.js.map +0 -1
  563. package/node_modules/@noble/hashes/sha3-addons.d.ts +0 -142
  564. package/node_modules/@noble/hashes/sha3-addons.d.ts.map +0 -1
  565. package/node_modules/@noble/hashes/sha3-addons.js +0 -402
  566. package/node_modules/@noble/hashes/sha3-addons.js.map +0 -1
  567. package/node_modules/@noble/hashes/sha3.d.ts +0 -53
  568. package/node_modules/@noble/hashes/sha3.d.ts.map +0 -1
  569. package/node_modules/@noble/hashes/sha3.js +0 -239
  570. package/node_modules/@noble/hashes/sha3.js.map +0 -1
  571. package/node_modules/@noble/hashes/sha512.d.ts +0 -26
  572. package/node_modules/@noble/hashes/sha512.d.ts.map +0 -1
  573. package/node_modules/@noble/hashes/sha512.js +0 -29
  574. package/node_modules/@noble/hashes/sha512.js.map +0 -1
  575. package/node_modules/@noble/hashes/src/_assert.ts +0 -22
  576. package/node_modules/@noble/hashes/src/_blake.ts +0 -50
  577. package/node_modules/@noble/hashes/src/_md.ts +0 -176
  578. package/node_modules/@noble/hashes/src/_u64.ts +0 -91
  579. package/node_modules/@noble/hashes/src/argon2.ts +0 -497
  580. package/node_modules/@noble/hashes/src/blake1.ts +0 -534
  581. package/node_modules/@noble/hashes/src/blake2.ts +0 -486
  582. package/node_modules/@noble/hashes/src/blake2b.ts +0 -10
  583. package/node_modules/@noble/hashes/src/blake2s.ts +0 -20
  584. package/node_modules/@noble/hashes/src/blake3.ts +0 -272
  585. package/node_modules/@noble/hashes/src/crypto.ts +0 -9
  586. package/node_modules/@noble/hashes/src/cryptoNode.ts +0 -15
  587. package/node_modules/@noble/hashes/src/eskdf.ts +0 -187
  588. package/node_modules/@noble/hashes/src/hkdf.ts +0 -88
  589. package/node_modules/@noble/hashes/src/hmac.ts +0 -94
  590. package/node_modules/@noble/hashes/src/index.ts +0 -31
  591. package/node_modules/@noble/hashes/src/legacy.ts +0 -293
  592. package/node_modules/@noble/hashes/src/pbkdf2.ts +0 -122
  593. package/node_modules/@noble/hashes/src/ripemd160.ts +0 -12
  594. package/node_modules/@noble/hashes/src/scrypt.ts +0 -257
  595. package/node_modules/@noble/hashes/src/sha1.ts +0 -10
  596. package/node_modules/@noble/hashes/src/sha2.ts +0 -402
  597. package/node_modules/@noble/hashes/src/sha256.ts +0 -24
  598. package/node_modules/@noble/hashes/src/sha3-addons.ts +0 -499
  599. package/node_modules/@noble/hashes/src/sha3.ts +0 -258
  600. package/node_modules/@noble/hashes/src/sha512.ts +0 -34
  601. package/node_modules/@noble/hashes/src/utils.ts +0 -395
  602. package/node_modules/@noble/hashes/utils.d.ts +0 -161
  603. package/node_modules/@noble/hashes/utils.d.ts.map +0 -1
  604. package/node_modules/@noble/hashes/utils.js +0 -313
  605. package/node_modules/@noble/hashes/utils.js.map +0 -1
@@ -9,20 +9,18 @@
9
9
  * MMTP packet parsing and frame routing.
10
10
  */
11
11
 
12
- import { computeFecDeliveryWindowMs } from './track-session.js'
12
+ import { computeFecDeliveryWindowMs, fecSpanDeliveryWindowMs } from './track-session.js'
13
13
  import { DeliveryGapEstimator } from './delivery-gap.js'
14
14
  import {
15
15
  deriveFecInterleaveDepth,
16
16
  deriveFecRetentionBlocks,
17
17
  tryDeriveFecRetentionBlocks,
18
+ LOCAL_FEC_RETENTION_BLOCKS,
19
+ LOCAL_FEC_TIMEOUT_MS,
18
20
  } from '@blockcast/mmt-fec'
19
21
  import {
20
- isKStrideBlock,
21
22
  isoBlockContains,
22
23
  isoBlocksBehindLowerBound,
23
- kStrideBlockPrecedes,
24
- kStrideBlocksBehind,
25
- kStrideNextBlock,
26
24
  parseFecBlockProfile,
27
25
  serial32Distance,
28
26
  serial32Precedes,
@@ -86,7 +84,7 @@ export type WasmFecDecoder = {
86
84
  configure(oti: Uint8Array): void
87
85
  cleanup(current_time: number): Uint8Array
88
86
  /** Retire the named blocks from the decoder (last-chance decode + stats). */
89
- cleanup_blocks_detailed(sbns: Uint32Array): unknown
87
+ cleanup_blocks_detailed(ss_starts: Uint32Array): unknown
90
88
  /**
91
89
  * iso-ssbg1-v1 only: retire the source symbols the decoder holds before a
92
90
  * repair states their block, once no repair can place them any more, by the
@@ -112,7 +110,8 @@ export interface FecStats {
112
110
  blocksComplete: number
113
111
  mixedPathBlocks: number
114
112
  recentBlockPathCounts: FecBlockPathCount[]
115
- k: number
113
+ /** The stream-global K a configure() call stated; undefined when none did. Per-track K is tracks[].k. */
114
+ k: number | undefined
116
115
  /** Of blocks with loss, what percentage were recovered (0-100). */
117
116
  recoveryRate: number
118
117
  /** What percentage of blocks had any loss (0-100). */
@@ -136,7 +135,8 @@ export interface FecPacketStats {
136
135
  blocksRecovered: number
137
136
  blocksFailed: number
138
137
  blocksComplete: number
139
- k: number
138
+ /** The track's configured K; undefined for a packetId never configured (it does no FEC work). */
139
+ k: number | undefined
140
140
  recoveryRate: number
141
141
  packetLossRate: number
142
142
  }
@@ -217,10 +217,11 @@ type FecLifecycleTrace = {
217
217
  export interface FECManagerConfig {
218
218
  /**
219
219
  * The catalog `fec.blockProfile` of the track (BLO-35597). Required for every
220
- * configured track: there is no default, and nothing infers it.
221
- * `k-padded-v1` keeps the legacy K-stride geometry; `iso-ssbg1-v1` is
222
- * ISO/IEC 23008-1 ssbg_mode1 with K' = SSB_length blocks located by
223
- * `(SS_Start, SSB_length)` range membership, `k` being only their maximum.
220
+ * configured track: there is no default, and nothing infers it. The only
221
+ * profile is `iso-ssbg1-v1`: ISO/IEC 23008-1 ssbg_mode1 with K' = SSB_length
222
+ * blocks keyed by SS_Start and located by `(SS_Start, SSB_length)` range
223
+ * membership, `k` being only their maximum. The retired `k-padded-v1` token
224
+ * is refused.
224
225
  */
225
226
  blockProfile?: FecBlockProfile
226
227
  k?: number
@@ -373,7 +374,6 @@ export interface FecDecoderUnavailableEvent {
373
374
  export class FECManager {
374
375
  // Per-track WASM RaptorQ decoders, keyed by physical stream identity.
375
376
  #fecDecoders = new Map<string, { client: WasmFecDecoder; k: number }>()
376
- /** @deprecated Use setFecDecoder(packetId, ...). Backwards-compat shim. */
377
377
 
378
378
  // Per-track Worker-backed FEC decoders, keyed by physical stream identity.
379
379
  // Worker-backed tracks use async decode — recovered frames arrive via onFrame callback,
@@ -393,9 +393,12 @@ export class FECManager {
393
393
  conflicted: boolean
394
394
  }>>()
395
395
 
396
- #fecK = 4
396
+ // The stream-global K a configure() call stated, reported as fecStats.k.
397
+ // It never resolves a packetId: a packetId has only its own track config
398
+ // (BLO-45953), and K does not feed the delivery-window math.
399
+ #streamK: number | undefined
397
400
  #fecSymbolSize = 0
398
- #fecTimeoutMs = 500 // default 500ms; refined by setFramerate() when fps known
401
+ #fecTimeoutMs = LOCAL_FEC_TIMEOUT_MS // local default; refined by setFramerate() when fps known
399
402
  #trackConfigs = new Map<number, ResolvedFECTrackConfig>()
400
403
  #fecStats = newFecCounters()
401
404
  #fecTrackStats = new Map<number, MutableFecCounters>()
@@ -436,14 +439,17 @@ export class FECManager {
436
439
  #blockSourceEsis = new Map<string, Set<number>>()
437
440
  // Authored SSB_length per block (ISO/IEC 23008-1 §C.5.3), learned from the
438
441
  // first repair symbol. The FFmpeg moq_mmt publisher closes a block every D
439
- // media units, usually before it holds K symbols, and fec-raptorq
440
- // force_complete() encodes it as a K-symbol RaptorQ block whose ESIs
441
- // SSB_length..K-1 are zero padding that is never sent. Such a block is clean
442
- // at SSB_length source arrivals and recoverable from SSB_length received
442
+ // media units, usually before it holds K symbols; under iso-ssbg1-v1
443
+ // (ssbg_mode1) that block is K' = SSB_length symbols, its repair ESIs start
444
+ // at SSB_length, and K is only the maximum. Such a block is clean at
445
+ // SSB_length source arrivals and recoverable from SSB_length received
443
446
  // symbols; waiting for K fails every one of them.
444
447
  #blockAuthoredLengths = new Map<string, number>()
445
448
  #ssbLengthConflictCount = 0
446
449
  #blockRepairRsIds = new Map<string, Set<number>>()
450
+ // RSB_length per block (C.5.3 Repair FEC Payload ID): how many repair
451
+ // symbols its repair group carries. Same lifecycle as #blockRepairRsIds.
452
+ #blockRsbLengths = new Map<string, number>()
447
453
  // Blocks already recovered — prevents late source symbols from restarting timeouts.
448
454
  // WASM removes blocks after recovery; late symbols create empty blocks returning null,
449
455
  // which would falsely start new timeouts without this guard.
@@ -558,14 +564,16 @@ export class FECManager {
558
564
  #resolvedBlockClasses = new Map<string, BlockClass>()
559
565
  // Blocks already reported through onBlockRepaired (once per block).
560
566
  #repairedBlocks = new Set<string>()
561
- // Blocks created without any symbol because every symbol vanished.
567
+ // Blocks created before any symbol of theirs was tracked: the repair-less
568
+ // spans placeIsoRepair materializes (onBlockVanished).
562
569
  #materializedBlocks = new Set<string>()
563
570
  // packetIds whose ordered-relay horizon became underivable and fell back to
564
571
  // the datagram deadline. Bounds the warn in #derivableOrderedHorizon to once
565
572
  // per stream. Not only throws: the ordinary out-of-range case is detected by
566
573
  // arithmetic and never throws at all.
567
574
  #orderedHorizonWarnedFor = new Set<number>()
568
- // Highest SBN seen per stream and when, for vanished-block detection.
575
+ // Newest block start (SS_Start) seen per stream and when: the frontier for
576
+ // source-discontinuity detection and isoSpanResolution.
569
577
  #highestSbn = new Map<string, { sbn: number; atMs: number }>()
570
578
  // iso-ssbg1-v1 block location per stream (streamKey). A source belongs to a
571
579
  // block only once a repair symbol stated the block's (SS_Start, SSB_length).
@@ -829,7 +837,8 @@ export class FECManager {
829
837
  this.#completedBlocks.add(key)
830
838
  if (clean) this.#cleanlyCompletedBlocks.add(key)
831
839
  this.#indexBlockKey(key, sbn, packetId)
832
- const expiresAtMs = nowMs + this.terminalRetentionMsForPacket(packetId)
840
+ const config = this.#trackConfig(packetId)
841
+ const expiresAtMs = nowMs + config.interleaveDepthMs + 2 * this.#blockLifetimeMs(config)
833
842
  this.#terminalExpiryMs.set(key, expiresAtMs)
834
843
  let queue = this.#terminalQueues.get(packetId)
835
844
  if (!queue) {
@@ -877,8 +886,9 @@ export class FECManager {
877
886
  * retention the Worker and the WASM decoder are configured with, because it
878
887
  * comes from the same function they do (`deriveFecRetentionBlocks`, over the
879
888
  * block's delivery window and its span). The decoder retires every block
880
- * more than this many SBNs behind the newest one it saw (`cleanup_by_sbn`),
881
- * so settling earlier than this would decide a block the decoder would still
889
+ * more than this many blocks behind the newest SS_Start it saw, counted
890
+ * from below by `isoBlocksBehindLowerBound` (`cleanup_by_ss_start`), so
891
+ * settling earlier than this would decide a block the decoder would still
882
892
  * have recovered.
883
893
  *
884
894
  * This used to call `deriveFecInterleaveDepth`, which returns D -- MEDIA
@@ -891,6 +901,18 @@ export class FECManager {
891
901
  * derivation; do not re-derive a horizon at either call site.
892
902
  */
893
903
  #orderedHorizonBlocksFor(packetId: number, config: ResolvedFECTrackConfig): number {
904
+ // An absent/zero catalog span has no elapsed-time horizon. Ordered relay
905
+ // still needs a bounded local cleanup distance, so use the shared local
906
+ // count without treating it as a catalog-derived deadline.
907
+ if (config.interleaveDepthMs === 0) {
908
+ if (Number.isFinite(config.timeoutMs) && config.timeoutMs > 0) {
909
+ return LOCAL_FEC_RETENTION_BLOCKS
910
+ }
911
+ throw new Error(
912
+ `[FECManager] packetId=${packetId}: local FEC cleanup requires a positive timeout ` +
913
+ `(got ${config.timeoutMs})`,
914
+ )
915
+ }
894
916
  if (!FECManager.#hasOrderedGeometry(config)) {
895
917
  throw new Error(
896
918
  `[FECManager] packetId=${packetId}: ordered-relay settlement derives the ` +
@@ -913,7 +935,7 @@ export class FECManager {
913
935
  */
914
936
  static #hasOrderedGeometry(config: ResolvedFECTrackConfig): boolean {
915
937
  return (
916
- config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS &&
938
+ FECManager.#hasCatalogTiming(config) &&
917
939
  // `fps` is no longer an input to the horizon, but it still gates WHICH
918
940
  // streams settle by stream order at all: a track with no usable
919
941
  // media-unit rate fell back to datagram semantics before this and must
@@ -960,6 +982,11 @@ export class FECManager {
960
982
  * argument: narrower IS the failing direction.
961
983
  */
962
984
  #derivableOrderedHorizon(config: ResolvedFECTrackConfig, packetId: number): number | undefined {
985
+ if (config.interleaveDepthMs === 0) {
986
+ return Number.isFinite(config.timeoutMs) && config.timeoutMs > 0
987
+ ? LOCAL_FEC_RETENTION_BLOCKS
988
+ : undefined
989
+ }
963
990
  if (!FECManager.#hasOrderedGeometry(config)) return undefined
964
991
  try {
965
992
  // `tryDeriveFecRetentionBlocks`, not the throwing variant: this is the
@@ -1016,19 +1043,20 @@ export class FECManager {
1016
1043
  /**
1017
1044
  * The stream's interleave horizon, or undefined when its geometry cannot
1018
1045
  * define one. Two different streams reach that case, with different causes:
1019
- * a packetId that was never configured, and so resolves to the manager
1020
- * defaults (explicitly configured tracks are refused an underivable geometry
1046
+ * a packetId that was never configured, which has no geometry at all
1047
+ * (explicitly configured tracks are refused an underivable geometry
1021
1048
  * at config time — #upsertTrackConfig, setMulticastSymbolDelivery); or a
1022
1049
  * configured track whose timeoutMs was later widened past the 255-block
1023
1050
  * ceiling without re-validating, which #derivableOrderedHorizon catches
1024
1051
  * rather than throwing from the packet-feed path. Either way the stream
1025
- * keeps datagram semantics instead of throwing. The manager defaults can
1052
+ * keeps datagram semantics instead of throwing. A track's configuration can
1026
1053
  * also change under a stream that already holds blocks, so a caller cannot
1027
1054
  * assume a horizon that was defined when a block was held is still defined
1028
1055
  * (see #advanceOrderedFrontier).
1029
1056
  */
1030
1057
  #orderedHorizonBlocks(packetId: number): number | undefined {
1031
- return this.#derivableOrderedHorizon(this.#resolveTrackConfig(packetId), packetId)
1058
+ const config = this.#trackConfigs.get(packetId)
1059
+ return config === undefined ? undefined : this.#derivableOrderedHorizon(config, packetId)
1032
1060
  }
1033
1061
 
1034
1062
  /** True while a block of an ordered-relay stream can still receive symbols. */
@@ -1073,7 +1101,7 @@ export class FECManager {
1073
1101
  catalogTrackName?: string,
1074
1102
  ): void {
1075
1103
  const previous = this.#orderedFrontiers.get(streamKey)
1076
- const frontier = previous === undefined || this.#blockPrecedes(previous, sbn, packetId)
1104
+ const frontier = previous === undefined || this.#blockPrecedes(previous, sbn)
1077
1105
  ? sbn
1078
1106
  : previous
1079
1107
  if (frontier !== previous) this.#orderedFrontiers.set(streamKey, frontier)
@@ -1081,10 +1109,9 @@ export class FECManager {
1081
1109
  if (!pending) return
1082
1110
  const blocks = this.#orderedHorizonBlocks(packetId)
1083
1111
  if (blocks === undefined) {
1084
- // Either an unconfigured stream (it resolves to the manager defaults,
1085
- // and configure() accepts a sub-floor interleaveDepthMs) or a configured
1086
- // one whose timeoutMs was widened past the 255-block ceiling after
1087
- // validation (#derivableOrderedHorizon). Stream
1112
+ // A configured stream whose timeoutMs was widened past the 255-block
1113
+ // ceiling after validation (#derivableOrderedHorizon); an unconfigured
1114
+ // one never gets here (feedSymbolFromSource refuses it). Stream
1088
1115
  // order can no longer settle its held blocks, so hand them to the
1089
1116
  // datagram deadline -- as setMulticastSymbolDelivery('datagram') does --
1090
1117
  // rather than settle them a horizon early or hold them with no timer.
@@ -1130,6 +1157,7 @@ export class FECManager {
1130
1157
  (this.#blockSourceCounts.get(key) ?? 0) +
1131
1158
  (this.#blockRepairRsIds.get(key)?.size ?? 0)
1132
1159
  if (
1160
+ this.#hasCatalogTimingForPacket(packetId) &&
1133
1161
  this.onRepairNeeded &&
1134
1162
  (authoredLength === undefined || received < authoredLength)
1135
1163
  ) {
@@ -1343,6 +1371,7 @@ export class FECManager {
1343
1371
  const key = this.#blockKey(sbn, packetId, catalogTrackName)
1344
1372
  if (
1345
1373
  this.#completedBlocks.has(key) ||
1374
+ !this.#hasCatalogTimingForPacket(packetId) ||
1346
1375
  !this.onRepairNeeded ||
1347
1376
  this.#repairRequestedBlocks.has(key)
1348
1377
  ) return
@@ -1450,6 +1479,7 @@ export class FECManager {
1450
1479
 
1451
1480
  /** A block's live class without creating classifier state for the stream. */
1452
1481
  #peekClass(sbn: number, packetId: number, catalogTrackName?: string): BlockClass {
1482
+ if (!this.#hasCatalogTimingForPacket(packetId)) return 'unknown'
1453
1483
  const classifier = this.#classifiers.get(this.#streamKey(packetId, catalogTrackName))
1454
1484
  if (classifier) return classifier.classOf(sbn)
1455
1485
  return this.#geometryFor(packetId, catalogTrackName).trackKind === 'audio' ? 'audio' : 'unknown'
@@ -1479,8 +1509,8 @@ export class FECManager {
1479
1509
  // Deliberately one block tighter than deriveFecRetentionBlocks over the same
1480
1510
  // inputs (that function's `+ 1`). Different question: the retention horizon
1481
1511
  // asks how long the decoder still ACCEPTS symbols for a block, this asks how
1482
- // far back a block that vanished entirely may still be repairable (see
1483
- // #noteSbnSeen).
1512
+ // many blocks a stream can live behind (or ahead of) its frontier before a
1513
+ // source symbol there is a discontinuity rather than loss (see #noteSbnSeen).
1484
1514
  // The extra block of retention is slack for a block still being fed; a block
1485
1515
  // nothing has been heard from does not get it. Not the units defect — but it
1486
1516
  // is the next place the two can drift, so change both or neither.
@@ -1490,27 +1520,28 @@ export class FECManager {
1490
1520
  }
1491
1521
 
1492
1522
  /**
1493
- * Record that a symbol for block `sbn` arrived and materialise any blocks
1494
- * that vanished entirely between the stream's highest SBN and this one.
1523
+ * Record that a symbol for block `sbn` (the block's SS_Start) arrived and
1524
+ * advance the stream's frontier, the newest block start seen.
1495
1525
  *
1496
- * The newest ceil(timeoutMs / interleaveDepthMs) vanished blocks may still
1497
- * be repairable, so they get the normal repair and fail deadlines, armed
1498
- * from now. Older ones are already past their fail bound and fail now, as
1499
- * class `unknown`. A SOURCE symbol whose SBN goes back further than one
1500
- * live window, or jumps further than the stream could have produced since
1501
- * its last symbol, is a source discontinuity, not loss. A repair symbol can
1502
- * only advance the frontier: a D-28 fetch legitimately answers for an old
1503
- * block, so it never signals a discontinuity.
1526
+ * A SOURCE symbol whose block goes back further than one live window, or
1527
+ * jumps further than the stream could have produced since its last symbol,
1528
+ * is a source discontinuity, not loss. A repair symbol can only advance the
1529
+ * frontier: a D-28 fetch legitimately answers for an old block, so it never
1530
+ * signals a discontinuity.
1504
1531
  *
1505
1532
  * The producer closes a block at its Dth media-unit boundary OR when K
1506
- * source symbols filled it, so one interleave span can start several
1507
- * blocks (every large keyframe does). What bounds a forward jump is
1508
- * therefore packet evidence, not a block rate: with the MMTP
1509
- * packet_sequence_number (contiguous per packet_id, ISO/IEC 23008-1) of
1510
- * both source symbols, every block strictly between them holds at least one
1511
- * of the packets sent strictly between them, and K-closes can add at most
1512
- * one block per K of those packets on top of the D-closes elapsed time
1513
- * allows. Without it, only the D-close rate is known.
1533
+ * source symbols filled it, so one interleave span can start several blocks
1534
+ * (every large keyframe does). What bounds a forward jump is therefore
1535
+ * packet evidence, not a block rate: SS_ID advances by one per FEC source
1536
+ * packet (ISO/IEC 23008-1 C.5.2) and the MMTP packet_sequence_number by one
1537
+ * per packet of the packet_id, so with both source symbols'
1538
+ * packet_sequence_number a block start can move at most the packets sent
1539
+ * between them plus the anchor's own offset into its block (< K). Without
1540
+ * it, only the D-close rate is known.
1541
+ *
1542
+ * Vanished blocks are not enumerated here: their starts are unknown until a
1543
+ * repair states them. The SS_IDs between two stated blocks become one
1544
+ * repair-less span when the later block's repair arrives (placeIsoRepair).
1514
1545
  */
1515
1546
  #noteSbnSeen(
1516
1547
  sbn: number,
@@ -1531,7 +1562,7 @@ export class FECManager {
1531
1562
  ? packetSequenceNumber
1532
1563
  : undefined
1533
1564
  const anchorHere = (): void => {
1534
- if (psn !== undefined && (anchor === undefined || !this.#blockPrecedes(sbn, anchor.sbn, packetId))) {
1565
+ if (psn !== undefined && (anchor === undefined || !this.#blockPrecedes(sbn, anchor.sbn))) {
1535
1566
  this.#sourceAnchors.set(streamKey, { sbn, psn, atMs: now })
1536
1567
  }
1537
1568
  }
@@ -1549,9 +1580,9 @@ export class FECManager {
1549
1580
  anchorHere()
1550
1581
  return
1551
1582
  }
1552
- const config = this.#resolveTrackConfig(packetId)
1583
+ const config = this.#trackConfig(packetId)
1553
1584
  const window = this.#liveWindowBlocks(config)
1554
- if (!this.#blockPrecedes(previous.sbn, sbn, packetId)) {
1585
+ if (!this.#blockPrecedes(previous.sbn, sbn)) {
1555
1586
  if (symbol === 'source' && window !== null &&
1556
1587
  this.#blocksBehind(sbn, previous.sbn, packetId) > window) {
1557
1588
  discontinuity()
@@ -1566,30 +1597,16 @@ export class FECManager {
1566
1597
  // A packet_sequence_number that did not advance says nothing (a
1567
1598
  // producer that does not maintain it); only then is the D-close rate
1568
1599
  // the whole bound.
1569
- if (this.#isIso(packetId) && psn !== undefined && anchor !== undefined &&
1570
- this.#blockPrecedes(anchor.sbn, sbn, packetId) && psn !== anchor.psn) {
1571
- // iso-ssbg1-v1: SS_ID advances by one per FEC source packet (C.5.2)
1572
- // and packet_sequence_number by one per packet of the packet_id, so a
1573
- // block start can move at most the packets sent since the anchor,
1574
- // plus the anchor's own offset into its block (< K).
1600
+ if (psn !== undefined && anchor !== undefined &&
1601
+ this.#blockPrecedes(anchor.sbn, sbn) && psn !== anchor.psn) {
1602
+ // A block start can move at most the packets sent since the anchor,
1603
+ // plus the anchor's own offset into its block (< K). Packets sent
1604
+ // from the anchor packet to this one, serial arithmetic (RFC 1982):
1605
+ // more than half the space means it went backwards.
1575
1606
  const sent = (psn - anchor.psn) >>> 0
1576
1607
  plausible =
1577
1608
  sent <= 0x7fff_ffff &&
1578
1609
  serial32Distance(anchor.sbn, sbn) <= sent + config.k
1579
- } else if (psn !== undefined && anchor !== undefined &&
1580
- this.#blockPrecedes(anchor.sbn, sbn, packetId) && psn !== anchor.psn) {
1581
- // Packets sent from the anchor packet to this one, serial arithmetic
1582
- // (RFC 1982): more than half the space means it went backwards.
1583
- const sent = (psn - anchor.psn) >>> 0
1584
- const between = this.#blocksBehind(anchor.sbn, sbn, packetId) - 1
1585
- const dCloses = window + Math.ceil((now - anchor.atMs) / config.interleaveDepthMs)
1586
- // Each K-close consumes K of the packets sent; with K unknown,
1587
- // every packet may have closed one.
1588
- const kCloses = config.k > 0 ? Math.ceil(sent / config.k) : sent
1589
- plausible =
1590
- sent <= 0x7fff_ffff &&
1591
- between <= sent - 1 &&
1592
- between <= dCloses + kCloses
1593
1610
  } else {
1594
1611
  plausible = missing <= window + Math.ceil((now - previous.atMs) / config.interleaveDepthMs)
1595
1612
  }
@@ -1601,53 +1618,25 @@ export class FECManager {
1601
1618
  }
1602
1619
  this.#highestSbn.set(streamKey, { sbn, atMs: now })
1603
1620
  anchorHere()
1604
- // iso-ssbg1-v1 cannot enumerate vanished blocks by key: their starts are
1605
- // unknown. The SS_IDs between two stated blocks become one repair-less
1606
- // span when the later block's repair arrives (placeIsoRepair).
1607
- if (this.#isIso(packetId)) return
1608
- if (missing <= 0 || window === null) return
1609
- let vanished = previous.sbn
1610
- for (let i = 0; i < missing; i++) {
1611
- vanished = this.#nextBlock(vanished, packetId)
1612
- this.#materializeBlock(
1613
- vanished,
1614
- packetId,
1615
- catalogTrackName,
1616
- this.#blocksBehind(vanished, sbn, packetId) <= window,
1617
- )
1618
- }
1619
1621
  }
1620
1622
 
1621
1623
  /*
1622
- * Block order for one stream (BLO-35597 iso:5). SS_ID is a 32-bit serial
1623
- * number (ISO/IEC 23008-1 C.5.2), so blocks are ordered by RFC 1982
1624
- * comparison of their first SS_ID (sbn * K mod 2^32). That is identical to
1625
- * comparing SBNs with < for every stream that has not wrapped SS_ID; only
1626
- * the order across the wrap changes. A block outside K-stride geometry has
1627
- * no SS_ID position, so it keeps the SBN order it always had.
1624
+ * Block order for one stream (BLO-35597 iso:5). A block is keyed by its
1625
+ * SS_Start and SS_ID is a 32-bit serial number (ISO/IEC 23008-1 C.5.2), so
1626
+ * blocks are ordered by RFC 1982 comparison of their starts. Only configured
1627
+ * streams hold blocks: every entry point refuses an unconfigured packetId.
1628
1628
  */
1629
- #blockPrecedes(a: number, b: number, packetId: number): boolean {
1630
- // iso-ssbg1-v1 keys a block by its SS_Start: plain RFC 1982 order.
1631
- if (this.#isIso(packetId)) return serial32Precedes(a, b)
1632
- const k = this.kForPacket(packetId)
1633
- return isKStrideBlock(a, k) && isKStrideBlock(b, k) ? kStrideBlockPrecedes(a, b, k) : a < b
1629
+ #blockPrecedes(a: number, b: number): boolean {
1630
+ return serial32Precedes(a, b)
1634
1631
  }
1635
1632
 
1636
- /** Blocks `a` lies behind `b` (0 when it does not precede it). */
1633
+ /**
1634
+ * Blocks `a` lies behind `b` (0 when it does not precede it). Blocks vary
1635
+ * in length (K' = SSB_length <= K), so only a lower bound is known: the one
1636
+ * metric the decoder and the worker retire by too.
1637
+ */
1637
1638
  #blocksBehind(a: number, b: number, packetId: number): number {
1638
- // iso-ssbg1-v1 blocks vary in length, so only a lower bound is known: the
1639
- // one metric the decoder and the worker retire by too.
1640
- if (this.#isIso(packetId)) return isoBlocksBehindLowerBound(a, b, this.kForPacket(packetId))
1641
- const k = this.kForPacket(packetId)
1642
- return isKStrideBlock(a, k) && isKStrideBlock(b, k)
1643
- ? kStrideBlocksBehind(a, b, k)
1644
- : Math.max(0, b - a)
1645
- }
1646
-
1647
- /** The block after `sbn`, stepping to block 0 where SS_ID wraps. */
1648
- #nextBlock(sbn: number, packetId: number): number {
1649
- const k = this.kForPacket(packetId)
1650
- return isKStrideBlock(sbn, k) ? kStrideNextBlock(sbn, k) : sbn + 1
1639
+ return isoBlocksBehindLowerBound(a, b, this.#trackConfig(packetId).k)
1651
1640
  }
1652
1641
 
1653
1642
  #materializeBlock(
@@ -1659,7 +1648,9 @@ export class FECManager {
1659
1648
  const key = this.#blockKey(sbn, packetId, catalogTrackName)
1660
1649
  if (this.#blockResolutionFor(key) !== 'untracked') return
1661
1650
  this.#materializedBlocks.add(key)
1662
- this.#classifierFor(packetId, catalogTrackName).touch(sbn)
1651
+ if (this.#hasCatalogTimingForPacket(packetId)) {
1652
+ this.#classifierFor(packetId, catalogTrackName).touch(sbn)
1653
+ }
1663
1654
  this.onBlockVanished?.(sbn, packetId, catalogTrackName)
1664
1655
  if (repairable) {
1665
1656
  this.noteBlockActivity(sbn, packetId, catalogTrackName, true)
@@ -1723,18 +1714,37 @@ export class FECManager {
1723
1714
  if (this.#lifecycleTrace.length > 64) this.#lifecycleTrace.splice(0, 32)
1724
1715
  }
1725
1716
 
1726
- #defaultTrackConfig(): ResolvedFECTrackConfig {
1727
- return {
1728
- k: this.#fecK,
1729
- symbolSize: this.#fecSymbolSize,
1730
- interleaveDepthMs: this.#fecInterleaveDepthMs,
1731
- fps: this.#fecFps,
1732
- timeoutMs: this.#fecTimeoutMs,
1717
+ /**
1718
+ * A configured packetId's track config. There is no manager-wide default
1719
+ * (BLO-45953): an unconfigured packetId does no FEC work, so every caller
1720
+ * here is reached only behind a configuration gate and this throw marks a
1721
+ * broken invariant, never a packet-path outcome.
1722
+ */
1723
+ #trackConfig(packetId: number): ResolvedFECTrackConfig {
1724
+ const config = this.#trackConfigs.get(packetId)
1725
+ if (config === undefined) {
1726
+ throw new Error(
1727
+ `[FECManager] packetId=${packetId} was never configured; ` +
1728
+ 'configure the track from its catalog (configureTrack/setFecWorker/setFecDecoder) first',
1729
+ )
1733
1730
  }
1731
+ return config
1734
1732
  }
1735
1733
 
1736
- #resolveTrackConfig(packetId: number): ResolvedFECTrackConfig {
1737
- return this.#trackConfigs.get(packetId) ?? this.#defaultTrackConfig()
1734
+ /**
1735
+ * Whether this track has the catalog's elapsed-time FEC policy. A zero span
1736
+ * is a deliberate no-deadline signal; it still leaves the wire geometry
1737
+ * usable for in-band decode, but it cannot drive classification, repair
1738
+ * fetches or layer adaptation.
1739
+ */
1740
+ static #hasCatalogTiming(config: Pick<ResolvedFECTrackConfig, 'interleaveDepthMs'>): boolean {
1741
+ return Number.isSafeInteger(config.interleaveDepthMs) &&
1742
+ config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS
1743
+ }
1744
+
1745
+ #hasCatalogTimingForPacket(packetId: number): boolean {
1746
+ const config = this.#trackConfigs.get(packetId)
1747
+ return config !== undefined && FECManager.#hasCatalogTiming(config)
1738
1748
  }
1739
1749
 
1740
1750
  #computeTimeout(config: {
@@ -1743,11 +1753,15 @@ export class FECManager {
1743
1753
  fps?: number
1744
1754
  timeoutMs?: number
1745
1755
  }): number {
1746
- if (config.timeoutMs && config.timeoutMs > 0) return config.timeoutMs
1747
- // Only a valid §5.1 ms interleave (>= MIN_INTERLEAVE_DEPTH_MS) drives the
1748
- // timeout. A sub-floor value is a raw frame count (no fps to convert with);
1749
- // fall back to the established #fecTimeoutMs rather than collapse to ~0.
1750
- if (config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
1756
+ if (typeof config.timeoutMs === 'number' && Number.isFinite(config.timeoutMs) && config.timeoutMs > 0) {
1757
+ return config.timeoutMs
1758
+ }
1759
+ // Only a valid non-negative integer §5.1 ms interleave drives the timeout;
1760
+ // zero has no catalog deadline, so retain the local manager default.
1761
+ if (
1762
+ Number.isSafeInteger(config.interleaveDepthMs) &&
1763
+ config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS
1764
+ ) {
1751
1765
  return Math.max(
1752
1766
  this.#fecTimeoutMs,
1753
1767
  computeFecDeliveryWindowMs({
@@ -1773,14 +1787,17 @@ export class FECManager {
1773
1787
  */
1774
1788
  #refreshDeliveryGapWindow(): void {
1775
1789
  let windowMs: number | undefined
1776
- const consider = (geometry: BlockClassifierGeometry | undefined): void => {
1790
+ const consider = (geometry: BlockClassifierGeometry | undefined, packetId: number): void => {
1791
+ if (!this.#hasCatalogTimingForPacket(packetId)) return
1777
1792
  if (geometry?.trackKind !== 'video' || geometry.mode !== 'classifying') return
1778
1793
  const { ticks, timescale } = geometry.keyframeInterval
1779
1794
  const ms = (ticks / timescale) * 1000
1780
1795
  if (Number.isFinite(ms) && ms > 0 && (windowMs === undefined || ms > windowMs)) windowMs = ms
1781
1796
  }
1782
- for (const geometry of this.#streamGeometries.values()) consider(geometry)
1783
- for (const config of this.#trackConfigs.values()) consider(config.geometry)
1797
+ for (const [streamKey, geometry] of this.#streamGeometries) {
1798
+ consider(geometry, this.#parseStreamKey(streamKey).packetId)
1799
+ }
1800
+ for (const [packetId, config] of this.#trackConfigs) consider(config.geometry, packetId)
1784
1801
  this.#deliveryGaps.setWindowMs(windowMs)
1785
1802
  }
1786
1803
 
@@ -1815,6 +1832,10 @@ export class FECManager {
1815
1832
  config: ResolvedFECTrackConfig,
1816
1833
  waitMs = this.#multicastWaitMs(config),
1817
1834
  ): number {
1835
+ // No catalog span means no D-28 repair deadline. The local timeout still
1836
+ // bounds cleanup, and returning the whole wait makes the derived grace
1837
+ // zero (rather than exposing half of a local timeout as a repair budget).
1838
+ if (!FECManager.#hasCatalogTiming(config)) return waitMs
1818
1839
  const halfFailBoundMs = Math.ceil(waitMs / 2)
1819
1840
  if (
1820
1841
  config.k > 0 &&
@@ -1836,10 +1857,12 @@ export class FECManager {
1836
1857
 
1837
1858
  /**
1838
1859
  * One block lifetime: the inactivity fail bound plus the D-28 repair grace,
1839
- * at the widest wait the catalog allows (#maxMulticastWaitMs). Everything
1840
- * derived from it (terminal retention, the release gate's hold ceiling, the
1841
- * duplicate ledgers, the owed-cleanup bound) is compared across time, so it
1842
- * must not follow the live measurement.
1860
+ * at the widest wait the catalog allows (#maxMulticastWaitMs). For a track
1861
+ * without a catalog span this is receiver-local cleanup time only; no repair
1862
+ * grace is added. Everything derived from it (terminal retention, the
1863
+ * release gate's hold ceiling, the duplicate ledgers, the owed-cleanup
1864
+ * bound) is compared across time, so it must not follow the live
1865
+ * measurement.
1843
1866
  */
1844
1867
  #blockLifetimeMs(config: ResolvedFECTrackConfig): number {
1845
1868
  const waitMs = this.#maxMulticastWaitMs(config)
@@ -1857,6 +1880,7 @@ export class FECManager {
1857
1880
  }
1858
1881
 
1859
1882
  #repairGraceMs(config: ResolvedFECTrackConfig, waitMs: number): number {
1883
+ if (!FECManager.#hasCatalogTiming(config)) return 0
1860
1884
  return Math.max(1, waitMs - this.#repairDeadlineMs(config, waitMs))
1861
1885
  }
1862
1886
 
@@ -1977,7 +2001,15 @@ export class FECManager {
1977
2001
  config: FECManagerConfig,
1978
2002
  catalogTrackName?: string,
1979
2003
  ): ResolvedFECTrackConfig {
1980
- const previous = this.#trackConfigs.get(packetId) ?? this.#defaultTrackConfig()
2004
+ const previousTrack = this.#trackConfigs.get(packetId)
2005
+ const k = config.k ?? previousTrack?.k
2006
+ if (k === undefined || !Number.isSafeInteger(k) || k <= 0) {
2007
+ throw new Error(
2008
+ `[FECManager] packetId=${packetId}: fec.k=${String(k)} must be a positive integer; ` +
2009
+ 'configure the track from its catalog',
2010
+ )
2011
+ }
2012
+ const previous: Partial<ResolvedFECTrackConfig> = previousTrack ?? {}
1981
2013
  const streamScopedGeometry =
1982
2014
  catalogTrackName !== undefined && config.geometry !== undefined ? config.geometry : undefined
1983
2015
  if (streamScopedGeometry !== undefined) {
@@ -1988,6 +2020,14 @@ export class FECManager {
1988
2020
  config.k !== undefined ||
1989
2021
  config.interleaveDepthMs !== undefined ||
1990
2022
  config.fps !== undefined
2023
+ if (
2024
+ config.timeoutMs !== undefined &&
2025
+ (!Number.isFinite(config.timeoutMs) || config.timeoutMs < 0)
2026
+ ) {
2027
+ throw new Error(
2028
+ `[FECManager] packetId=${packetId}: timeoutMs=${config.timeoutMs} must be a finite non-negative cleanup budget`,
2029
+ )
2030
+ }
1991
2031
  if (config.blockProfile !== undefined) {
1992
2032
  parseFecBlockProfile(config.blockProfile, `packetId=${packetId} fec.blockProfile`)
1993
2033
  }
@@ -1995,38 +2035,35 @@ export class FECManager {
1995
2035
  if (blockProfile === undefined) {
1996
2036
  throw new Error(
1997
2037
  `[FECManager] packetId=${packetId}: fec.blockProfile is required ` +
1998
- '("k-padded-v1" or "iso-ssbg1-v1"); configure the track from its catalog',
2038
+ '("iso-ssbg1-v1"); configure the track from its catalog',
1999
2039
  )
2000
2040
  }
2001
- if (blockProfile === 'iso-ssbg1-v1') {
2002
- // iso-ssbg1-v1 block location keeps what it holds for a horizon derived
2003
- // from this packet's catalog block span (#isoRetentionBlocks): without
2004
- // one the locator would grow for the life of the session. The span is
2005
- // this packet's own; the manager-wide default is never borrowed.
2006
- const ownInterleaveDepthMs =
2007
- config.interleaveDepthMs ?? this.#trackConfigs.get(packetId)?.interleaveDepthMs
2008
- if (
2009
- ownInterleaveDepthMs === undefined ||
2010
- !Number.isFinite(ownInterleaveDepthMs) ||
2011
- ownInterleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS
2012
- ) {
2013
- throw new Error(
2014
- `[FECManager] packetId=${packetId}: iso-ssbg1-v1 requires the catalog fec.interleaveDepthMs ` +
2015
- `(>= ${MIN_INTERLEAVE_DEPTH_MS} ms; got ${String(ownInterleaveDepthMs)}): it bounds how long ` +
2016
- 'block location keeps stated blocks and held sources',
2017
- )
2018
- }
2041
+ // iso-ssbg1-v1 block location keeps what it holds for a horizon derived
2042
+ // from this packet's own catalog span, or from a fixed receiver-local
2043
+ // count when the catalog has no elapsed-time span. The manager-wide span
2044
+ // is never borrowed by a track whose catalog omitted it.
2045
+ const ownInterleaveDepthMs =
2046
+ config.interleaveDepthMs ?? previousTrack?.interleaveDepthMs ?? 0
2047
+ if (
2048
+ !Number.isSafeInteger(ownInterleaveDepthMs) ||
2049
+ (ownInterleaveDepthMs !== 0 && ownInterleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS)
2050
+ ) {
2051
+ throw new Error(
2052
+ `[FECManager] packetId=${packetId}: fec.interleaveDepthMs must be absent, 0, or ` +
2053
+ `a finite value >= ${MIN_INTERLEAVE_DEPTH_MS} ms (got ${String(ownInterleaveDepthMs)})`,
2054
+ )
2019
2055
  }
2056
+ const hasCatalogTiming = FECManager.#hasCatalogTiming({ interleaveDepthMs: ownInterleaveDepthMs })
2020
2057
  const next: ResolvedFECTrackConfig = {
2021
2058
  blockProfile,
2022
- k: config.k ?? previous.k,
2059
+ k,
2023
2060
  symbolSize:
2024
2061
  config.symbolSize != null && config.symbolSize > 0
2025
2062
  ? config.symbolSize
2026
- : previous.symbolSize,
2027
- interleaveDepthMs: config.interleaveDepthMs ?? previous.interleaveDepthMs,
2028
- fps: config.fps ?? previous.fps,
2029
- timeoutMs: config.timeoutMs ?? (timingChanged ? 0 : previous.timeoutMs),
2063
+ : previousTrack?.symbolSize ?? 0,
2064
+ interleaveDepthMs: ownInterleaveDepthMs,
2065
+ fps: config.fps ?? previous.fps ?? this.#fecFps,
2066
+ timeoutMs: config.timeoutMs ?? (timingChanged ? 0 : previous.timeoutMs ?? 0),
2030
2067
  ...((config.trackKind ?? previous.trackKind) === undefined
2031
2068
  ? {}
2032
2069
  : { trackKind: config.trackKind ?? previous.trackKind }),
@@ -2037,24 +2074,44 @@ export class FECManager {
2037
2074
  ? {}
2038
2075
  : { geometry: config.geometry ?? previous.geometry }),
2039
2076
  }
2040
- next.timeoutMs = this.#computeTimeout(next)
2077
+ if (!hasCatalogTiming) {
2078
+ // A missing/zero span has no catalog deadline. Keep an explicitly
2079
+ // supplied local cleanup budget, preserve a prior local budget on a
2080
+ // metadata-only update, and otherwise use the manager's fixed local
2081
+ // default. A positive catalog track's timeout must never leak here.
2082
+ next.timeoutMs = config.timeoutMs !== undefined && config.timeoutMs > 0
2083
+ ? config.timeoutMs
2084
+ : previousTrack !== undefined && previousTrack.interleaveDepthMs === 0
2085
+ ? previousTrack.timeoutMs
2086
+ : LOCAL_FEC_TIMEOUT_MS
2087
+ } else {
2088
+ next.timeoutMs = this.#computeTimeout(next)
2089
+ }
2041
2090
  if (this.#multicastSymbolDelivery === 'ordered-relay') {
2042
2091
  // Error on bad init: an ordered-relay stream settles its blocks by the
2043
2092
  // decoder's interleave horizon, which this geometry must define.
2044
2093
  this.#orderedHorizonBlocksFor(packetId, next)
2045
2094
  }
2046
- if (
2047
- previous.blockProfile !== undefined &&
2048
- (previous.blockProfile !== blockProfile || (blockProfile === 'iso-ssbg1-v1' && previous.k !== next.k))
2049
- ) {
2050
- // A different profile, or a different maximum K under iso-ssbg1-v1, is
2051
- // a different block geometry: the stream's blocks must not be read
2052
- // under the old one's rules.
2095
+ const timingPolicyChanged =
2096
+ FECManager.#hasCatalogTiming({ interleaveDepthMs: previous.interleaveDepthMs ?? 0 }) !== hasCatalogTiming
2097
+ if (timingPolicyChanged && !hasCatalogTiming) {
2098
+ // A D-28 timer belongs to the positive catalog timing policy. Once a
2099
+ // track becomes local-only, cancel it instead of merely relying on the
2100
+ // request callback's policy guard; the local cleanup timer is the only
2101
+ // deadline allowed to remain armed for the block.
2102
+ for (const key of [...this.#repairDeadlines.keys()]) {
2103
+ if (this.#belongsToPacket(key, packetId)) this.#clearRepairDeadline(key)
2104
+ }
2105
+ }
2106
+ if (previous.blockProfile !== undefined && previous.k !== next.k) {
2107
+ // A different maximum K is a different block geometry: the stream's
2108
+ // blocks must not be read under the old one's rules.
2053
2109
  this.#dropIsoLocators(packetId)
2054
2110
  }
2055
2111
  this.#trackConfigs.set(packetId, next)
2056
2112
  this.#refreshDeliveryGapWindow()
2057
2113
  let policyChanged =
2114
+ timingPolicyChanged ||
2058
2115
  (config.geometry !== undefined &&
2059
2116
  JSON.stringify(config.geometry) !== JSON.stringify(previous.geometry)) ||
2060
2117
  (config.trackKind !== undefined && config.trackKind !== previous.trackKind)
@@ -2077,8 +2134,12 @@ export class FECManager {
2077
2134
  if (!this.#belongsToPacket(key, packetId)) continue
2078
2135
  const stream = this.#parseStreamKey(key)
2079
2136
  const geometry = this.#geometryFor(stream.packetId, stream.catalogTrackName)
2080
- if (JSON.stringify(geometry) === JSON.stringify(classifier.geometry)) continue
2137
+ if (
2138
+ hasCatalogTiming &&
2139
+ JSON.stringify(geometry) === JSON.stringify(classifier.geometry)
2140
+ ) continue
2081
2141
  this.#classifiers.delete(key)
2142
+ if (!hasCatalogTiming) continue
2082
2143
  // Blocks still unresolved lose the evidence their class rested on:
2083
2144
  // under the fresh classifier they read `unknown`. Say so, so a queued
2084
2145
  // or running P2 repair for a former delta block is promoted to P0
@@ -2089,14 +2150,6 @@ export class FECManager {
2089
2150
  return next
2090
2151
  }
2091
2152
 
2092
- #normalizeSymbolData(packetId: number, data: Uint8Array): Uint8Array {
2093
- const symbolSize = this.symbolSizeForPacket(packetId)
2094
- if (symbolSize <= 0 || data.byteLength === symbolSize) return data
2095
- const normalized = new Uint8Array(symbolSize)
2096
- normalized.set(data.subarray(0, symbolSize))
2097
- return normalized
2098
- }
2099
-
2100
2153
  #blockKeysFor(sbn: number, packetId?: number, catalogTrackName?: string): string[] {
2101
2154
  if (packetId != null && catalogTrackName !== undefined) {
2102
2155
  return [this.#blockKey(sbn, packetId, catalogTrackName)]
@@ -2169,7 +2222,7 @@ export class FECManager {
2169
2222
  ...stats,
2170
2223
  packetId,
2171
2224
  blocksComplete: trackTotal,
2172
- k: this.#resolveTrackConfig(packetId).k,
2225
+ k: this.#trackConfigs.get(packetId)?.k,
2173
2226
  recoveryRate: trackAffected > 0
2174
2227
  ? (stats.blocksRecovered / trackAffected) * 100
2175
2228
  : 100,
@@ -2183,7 +2236,7 @@ export class FECManager {
2183
2236
  (block) => block.multicastSymbols > 0 && block.moqSymbols > 0,
2184
2237
  ).length,
2185
2238
  recentBlockPathCounts: Array.from(this.#blockPathCounts.values()).slice(-24),
2186
- k: this.#fecK,
2239
+ k: this.#streamK,
2187
2240
  // True recovery rate: of blocks that HAD loss, how many were recovered?
2188
2241
  recoveryRate: affected > 0 ? (s.blocksRecovered / affected) * 100 : 100,
2189
2242
  packetLossRate: total > 0 ? (affected / total) * 100 : 0,
@@ -2198,7 +2251,6 @@ export class FECManager {
2198
2251
  },
2199
2252
  blocks: stats.blocks.map((block) => ({ ...block })),
2200
2253
  decoder: stats.decoder ? { ...stats.decoder } : undefined,
2201
- altaFailedDetails: stats.altaFailedDetails?.map((detail) => ({ ...detail })),
2202
2254
  packetId,
2203
2255
  })),
2204
2256
  repairSymbolSizeMismatches: this.#repairSymbolSizeMismatchCount,
@@ -2322,6 +2374,7 @@ export class FECManager {
2322
2374
  resetMap(this.#blockSourceEsis)
2323
2375
  resetMap(this.#blockAuthoredLengths)
2324
2376
  resetMap(this.#blockRepairRsIds)
2377
+ resetMap(this.#blockRsbLengths)
2325
2378
  resetMap(this.#blockPathCounts)
2326
2379
  resetMap(this.#blockAnchorsMs)
2327
2380
  resetMap(this.#blockCatalogTrackNames)
@@ -2447,6 +2500,7 @@ export class FECManager {
2447
2500
  dropMap(this.#blockSourceEsis)
2448
2501
  dropMap(this.#blockAuthoredLengths)
2449
2502
  dropMap(this.#blockRepairRsIds)
2503
+ dropMap(this.#blockRsbLengths)
2450
2504
  dropMap(this.#blockPathCounts)
2451
2505
  dropMap(this.#blockAnchorsMs)
2452
2506
  dropMap(this.#blockCatalogTrackNames)
@@ -2521,7 +2575,12 @@ export class FECManager {
2521
2575
  }
2522
2576
  }
2523
2577
 
2524
- /** Record a transport symbol and reject later duplicates of the same packetId/SBN/ESI. */
2578
+ /**
2579
+ * Record a transport symbol and reject later duplicates of the same
2580
+ * packetId/SBN/ESI. `sbn` is the block's SS_Start. A packetId with no
2581
+ * configured block profile has no block geometry: its symbol is refused
2582
+ * (false) and no block state is created for it.
2583
+ */
2525
2584
  feedSymbolFromSource(
2526
2585
  packetId: number,
2527
2586
  sbn: number,
@@ -2530,6 +2589,7 @@ export class FECManager {
2530
2589
  source: FecSymbolSource,
2531
2590
  catalogTrackName?: string,
2532
2591
  ): boolean {
2592
+ if (!this.#hasBlockProfile(packetId)) return false
2533
2593
  const blockKey = this.#blockKey(sbn, packetId, catalogTrackName)
2534
2594
  const streamKey = this.#streamKey(packetId, catalogTrackName)
2535
2595
  if (source === 'multicast' && this.#multicastSymbolDelivery === 'ordered-relay') {
@@ -2537,12 +2597,10 @@ export class FECManager {
2537
2597
  // one refused below as a join partial or a duplicate.
2538
2598
  this.#advanceOrderedFrontier(streamKey, sbn, packetId, catalogTrackName)
2539
2599
  }
2540
- const k = this.kForPacket(packetId)
2541
- // iso-ssbg1-v1 repair ESIs start at the block's own SSB_length (K'), which
2542
- // its locator states; k-padded-v1 repair ESIs start at K.
2543
- const sourceSymbols = this.#isIso(packetId)
2544
- ? this.#isoLocatorFor(packetId, catalogTrackName).blockAt(sbn)?.length ?? k
2545
- : k
2600
+ // Repair ESIs start at the block's own SSB_length (K'), which its locator
2601
+ // states once a repair placed it; before that, K is the only bound.
2602
+ const sourceSymbols =
2603
+ this.#isoLocatorFor(packetId, catalogTrackName).blockAt(sbn)?.length ?? this.#trackConfig(packetId).k
2546
2604
  const isSourceSymbol = esi >= 0 && esi < sourceSymbols
2547
2605
  const isStartupPartial = this.#startupPartialBlocks.has(blockKey)
2548
2606
  const isReadyMulticastRepair =
@@ -2711,7 +2769,7 @@ export class FECManager {
2711
2769
  for (const [key] of this.#activeBlocks) {
2712
2770
  const { packetId, sbn } = this.#parseBlockKey(key);
2713
2771
  const count = this.#blockSourceCounts.get(key) ?? 0;
2714
- const k = this.#blockAuthoredLengths.get(key) ?? this.kForPacket(packetId);
2772
+ const k = this.#blockAuthoredLengths.get(key) ?? this.#trackConfig(packetId).k;
2715
2773
  blocks.push({ sbn, packetId, state: 'collecting', sourceCount: count, k, progress: k > 0 ? Math.min(count / k, 1) : 0 });
2716
2774
  }
2717
2775
  // Recently completed blocks (last 16)
@@ -2719,7 +2777,7 @@ export class FECManager {
2719
2777
  for (const key of completed) {
2720
2778
  const { packetId, sbn } = this.#parseBlockKey(key);
2721
2779
  if (!this.#activeBlocks.has(key)) {
2722
- const k = this.kForPacket(packetId);
2780
+ const k = this.#trackConfig(packetId).k;
2723
2781
  blocks.push({ sbn, packetId, state: 'complete', sourceCount: k, k, progress: 1 });
2724
2782
  }
2725
2783
  }
@@ -2732,14 +2790,16 @@ export class FECManager {
2732
2790
  return this.#fecDecoders.values().next().value?.client ?? null
2733
2791
  }
2734
2792
 
2735
- get k(): number { return this.#fecK }
2736
- kForPacket(packetId: number): number { return this.#resolveTrackConfig(packetId).k }
2793
+ /** A configured packetId's K; undefined for an unconfigured one (no FEC work). */
2794
+ kForPacket(packetId: number): number | undefined { return this.#trackConfigs.get(packetId)?.k }
2737
2795
  /** FEC symbol size in bytes. */
2738
2796
  get symbolSize(): number { return this.#fecSymbolSize }
2739
- symbolSizeForPacket(packetId: number): number { return this.#resolveTrackConfig(packetId).symbolSize }
2797
+ symbolSizeForPacket(packetId: number): number | undefined { return this.#trackConfigs.get(packetId)?.symbolSize }
2740
2798
  /** FEC block span in milliseconds (0 = no interleaving). */
2741
2799
  get interleaveDepthMs(): number { return this.#fecInterleaveDepthMs }
2742
- interleaveDepthMsForPacket(packetId: number): number { return this.#resolveTrackConfig(packetId).interleaveDepthMs }
2800
+ interleaveDepthMsForPacket(packetId: number): number | undefined {
2801
+ return this.#trackConfigs.get(packetId)?.interleaveDepthMs
2802
+ }
2743
2803
  /**
2744
2804
  * Record one multicast MMTP packet arrival (every packet of the leg, FEC
2745
2805
  * symbol or not) for the delivery-gap measurement. `packetSequenceNumber`
@@ -2755,8 +2815,9 @@ export class FECManager {
2755
2815
  * Resolved per-track multicast late-symbol wait in milliseconds: the span
2756
2816
  * plus the larger of the catalog tolerance and the measured delivery gap.
2757
2817
  */
2758
- timeoutMsForPacket(packetId: number): number {
2759
- return this.#multicastWaitMs(this.#resolveTrackConfig(packetId))
2818
+ timeoutMsForPacket(packetId: number): number | undefined {
2819
+ const config = this.#trackConfigs.get(packetId)
2820
+ return config === undefined ? undefined : this.#multicastWaitMs(config)
2760
2821
  }
2761
2822
  /**
2762
2823
  * Largest multicast delivery gap measured over the current window (ms), and
@@ -2768,6 +2829,40 @@ export class FECManager {
2768
2829
  windowMs: this.#deliveryGaps.windowMs,
2769
2830
  }
2770
2831
  }
2832
+ /**
2833
+ * An open block's accepted sources, distinct repair RS_IDs, authored K'
2834
+ * (SSB_length) and RSB_length (each undefined until a repair stated it).
2835
+ * Undefined for a block that is not open.
2836
+ */
2837
+ blockSymbolCounts(
2838
+ sbn: number,
2839
+ packetId: number,
2840
+ catalogTrackName?: string,
2841
+ ): {
2842
+ readonly sources: number
2843
+ readonly repairs: number
2844
+ readonly authoredLength: number | undefined
2845
+ readonly repairGroupLength: number | undefined
2846
+ } | undefined {
2847
+ const key = this.#blockKey(sbn, packetId, catalogTrackName)
2848
+ if (!this.#activeBlocks.has(key)) return undefined
2849
+ return {
2850
+ sources: this.#blockSourceCounts.get(key) ?? 0,
2851
+ repairs: this.#blockRepairRsIds.get(key)?.size ?? 0,
2852
+ authoredLength: this.#blockAuthoredLengths.get(key),
2853
+ repairGroupLength: this.#blockRsbLengths.get(key),
2854
+ }
2855
+ }
2856
+ /**
2857
+ * Record a block's RSB_length from one of its repair symbols (the router
2858
+ * parses it from the Repair FEC Payload ID). The largest stated value wins.
2859
+ */
2860
+ noteRepairGroupLength(sbn: number, packetId: number, rsbLength: number, catalogTrackName?: string): void {
2861
+ if (!Number.isSafeInteger(rsbLength) || rsbLength <= 0) return
2862
+ const key = this.#blockKey(sbn, packetId, catalogTrackName)
2863
+ if (!this.#activeBlocks.has(key)) return
2864
+ if ((this.#blockRsbLengths.get(key) ?? 0) < rsbLength) this.#blockRsbLengths.set(key, rsbLength)
2865
+ }
2771
2866
  /**
2772
2867
  * Receiver-local time of a block's first accepted symbol, or undefined for
2773
2868
  * a block no symbol reached (one materialized because a later SBN arrived).
@@ -2817,28 +2912,32 @@ export class FECManager {
2817
2912
  this.#applyBlockDeadline(key, sbn, packetId, catalogTrackName)
2818
2913
  }
2819
2914
  /** Resolved per-track D-28 repair request deadline in milliseconds. */
2820
- repairDeadlineMsForPacket(packetId: number): number {
2821
- return this.#repairDeadlineMs(this.#resolveTrackConfig(packetId))
2915
+ repairDeadlineMsForPacket(packetId: number): number | undefined {
2916
+ const config = this.#trackConfigs.get(packetId)
2917
+ return config === undefined ? undefined : this.#repairDeadlineMs(config)
2822
2918
  }
2823
2919
  /**
2824
2920
  * One block lifetime: the longest a block stays open after its last symbol
2825
2921
  * (inactivity fail bound plus D-28 repair grace).
2826
2922
  */
2827
- blockLifetimeMsForPacket(packetId: number): number {
2828
- return this.#blockLifetimeMs(this.#resolveTrackConfig(packetId))
2923
+ blockLifetimeMsForPacket(packetId: number): number | undefined {
2924
+ const config = this.#trackConfigs.get(packetId)
2925
+ return config === undefined ? undefined : this.#blockLifetimeMs(config)
2829
2926
  }
2830
2927
  /**
2831
2928
  * How long a terminal (clean, recovered or failed) block stays authoritative,
2832
2929
  * for late symbols and for the in-order release gate. It is also the gate's
2833
2930
  * hold ceiling (MmtpRouter's holdLimitMs): a retired block reads back as
2834
2931
  * untracked, which the gate counts as lost, so no frame may still be waiting
2835
- * on a block that can retire. The window is one interleave depth for a gap's
2836
- * last block to finish arriving, one block lifetime to fail it, and one more
2837
- * for an owed Worker cleanup that can keep a failing block pending. Every
2838
- * term comes from the track's catalog geometry; there is no separate cap.
2932
+ * on a block that can retire. With a positive catalog span the window is one
2933
+ * interleave depth for a gap's last block to finish arriving, one block
2934
+ * lifetime to fail it, and one more for an owed Worker cleanup. With an
2935
+ * absent/zero span it is a receiver-local cleanup bound only; it creates no
2936
+ * catalog FEC deadline.
2839
2937
  */
2840
- terminalRetentionMsForPacket(packetId: number): number {
2841
- const config = this.#resolveTrackConfig(packetId)
2938
+ terminalRetentionMsForPacket(packetId: number): number | undefined {
2939
+ const config = this.#trackConfigs.get(packetId)
2940
+ if (config === undefined) return undefined
2842
2941
  return config.interleaveDepthMs + 2 * this.#blockLifetimeMs(config)
2843
2942
  }
2844
2943
  /** Remaining fetch budget for a specific repair-requested block. */
@@ -2846,14 +2945,15 @@ export class FECManager {
2846
2945
  sbn: number,
2847
2946
  packetId: number,
2848
2947
  catalogTrackName?: string,
2849
- ): number {
2948
+ ): number | undefined {
2850
2949
  const deadlineMs = this.#activeBlockDeadlinesMs.get(
2851
2950
  this.#blockKey(sbn, packetId, catalogTrackName),
2852
2951
  )
2853
2952
  if (deadlineMs !== undefined) {
2854
2953
  return Math.max(1, Math.ceil(deadlineMs - performance.now()))
2855
2954
  }
2856
- const trackConfig = this.#resolveTrackConfig(packetId)
2955
+ const trackConfig = this.#trackConfigs.get(packetId)
2956
+ if (trackConfig === undefined) return undefined
2857
2957
  return Math.max(1, trackConfig.timeoutMs - this.#repairDeadlineMs(trackConfig))
2858
2958
  }
2859
2959
 
@@ -2867,15 +2967,20 @@ export class FECManager {
2867
2967
  return this.#trackConfigs.get(packetId)?.blockProfile
2868
2968
  }
2869
2969
 
2870
- #isIso(packetId: number): boolean {
2871
- return this.#trackConfigs.get(packetId)?.blockProfile === 'iso-ssbg1-v1'
2970
+ /**
2971
+ * Whether a packetId has a configured block geometry. iso-ssbg1-v1 is the
2972
+ * only profile, so every configured track has it; a packetId never
2973
+ * configured (or released) has none and gets no FEC block tracking.
2974
+ */
2975
+ #hasBlockProfile(packetId: number): boolean {
2976
+ return this.#trackConfigs.get(packetId)?.blockProfile !== undefined
2872
2977
  }
2873
2978
 
2874
2979
  #isoLocatorFor(packetId: number, catalogTrackName?: string): IsoBlockLocator<IsoHeldSource> {
2875
2980
  const streamKey = this.#streamKey(packetId, catalogTrackName)
2876
2981
  let locator = this.#isoLocators.get(streamKey)
2877
2982
  if (!locator) {
2878
- locator = new IsoBlockLocator<IsoHeldSource>(this.kForPacket(packetId), this.#isoRetentionBlocks(packetId))
2983
+ locator = new IsoBlockLocator<IsoHeldSource>(this.#trackConfig(packetId).k, this.#isoRetentionBlocks(packetId))
2879
2984
  this.#isoLocators.set(streamKey, locator)
2880
2985
  }
2881
2986
  return locator
@@ -2909,12 +3014,14 @@ export class FECManager {
2909
3014
  */
2910
3015
  #isoRetentionBlocks(packetId: number): number {
2911
3016
  const config = this.#trackConfigs.get(packetId)
2912
- if (config?.blockProfile !== 'iso-ssbg1-v1' || !(config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS)) {
3017
+ if (config?.blockProfile === undefined) {
2913
3018
  throw new Error(
2914
- `[FECManager] packetId=${packetId} has no iso-ssbg1-v1 block span to derive block-location retention from`,
3019
+ `[FECManager] packetId=${packetId} has no configured iso-ssbg1-v1 block profile`,
2915
3020
  )
2916
3021
  }
2917
- const blocks = Math.ceil(this.terminalRetentionMsForPacket(packetId) / config.interleaveDepthMs) + 1
3022
+ if (!FECManager.#hasCatalogTiming(config)) return LOCAL_FEC_RETENTION_BLOCKS
3023
+ const terminalRetentionMs = config.interleaveDepthMs + 2 * this.#blockLifetimeMs(config)
3024
+ const blocks = Math.ceil(terminalRetentionMs / config.interleaveDepthMs) + 1
2918
3025
  if (!Number.isSafeInteger(blocks) || blocks <= 0) {
2919
3026
  throw new Error(`[FECManager] packetId=${packetId} iso-ssbg1-v1 retention of ${blocks} blocks is not derivable`)
2920
3027
  }
@@ -2923,7 +3030,8 @@ export class FECManager {
2923
3030
 
2924
3031
  /** Whether an iso-ssbg1-v1 protected packet fits one ssbg_mode1 symbol (len + 2 <= T). */
2925
3032
  isoSourceFits(packetId: number, packetLength: number): boolean {
2926
- return ssbg1FitsSymbol(packetLength, this.symbolSizeForPacket(packetId))
3033
+ const symbolSize = this.#trackConfigs.get(packetId)?.symbolSize
3034
+ return symbolSize !== undefined && ssbg1FitsSymbol(packetLength, symbolSize)
2927
3035
  }
2928
3036
 
2929
3037
  /**
@@ -3010,11 +3118,11 @@ export class FECManager {
3010
3118
  * that could hold them is pending or not yet stated by a repair, else the
3011
3119
  * starts of the blocks that failed or vanished.
3012
3120
  *
3013
- * `untracked` reads exactly as in the k-padded-v1 release gate
3014
- * (MmtpRouter #createFecReleaseGate): a block holding either released
3015
- * frame's SS_ID (`fromSsId` or `toSsId`) delivered that frame, so untracked
3016
- * there means it resolved and retired; a block starting after the stream's frontier (`highestSbn`,
3017
- * an SS_Start here) may still be heard from; a block at or behind the
3121
+ * `untracked` reads as the release gate reads it (MmtpRouter
3122
+ * #createFecReleaseGate): a block holding either released frame's SS_ID
3123
+ * (`fromSsId` or `toSsId`) delivered that frame, so untracked there means it
3124
+ * resolved and retired; a block starting after the stream's frontier
3125
+ * (`highestSbn`, an SS_Start) may still be heard from; a block at or behind the
3018
3126
  * frontier that no symbol of this manager ever reached vanished, and its
3019
3127
  * media is lost.
3020
3128
  */
@@ -3107,6 +3215,10 @@ export class FECManager {
3107
3215
  packetId: number,
3108
3216
  catalogTrackName?: string,
3109
3217
  ): { mode: ClassifierMode; reason: ClassifierReason | null } {
3218
+ // A no-span stream has no catalog timing with which to classify blocks.
3219
+ // Keep the existing fail-closed shape for callers that still query this
3220
+ // accessor, while the span sentinel tells them this mode is local-only.
3221
+ if (!this.#hasCatalogTimingForPacket(packetId)) return { mode: 'all-protected', reason: null }
3110
3222
  const classifier = this.#classifierFor(packetId, catalogTrackName)
3111
3223
  return { mode: classifier.mode, reason: classifier.reason }
3112
3224
  }
@@ -3117,11 +3229,13 @@ export class FECManager {
3117
3229
  * it does not. A stream with no classifier yet has shown nothing against it.
3118
3230
  */
3119
3231
  mpuBoundsRapsFor(packetId: number, catalogTrackName?: string): boolean {
3232
+ if (!this.#hasCatalogTimingForPacket(packetId)) return false
3120
3233
  return this.#classifiers.get(this.#streamKey(packetId, catalogTrackName))?.mpuBoundsRaps ?? true
3121
3234
  }
3122
3235
 
3123
3236
  /** A block's class now, or frozen at its resolution. */
3124
3237
  blockClassFor(sbn: number, packetId: number, catalogTrackName?: string): BlockClass {
3238
+ if (!this.#hasCatalogTimingForPacket(packetId)) return 'unknown'
3125
3239
  const frozen = this.#resolvedBlockClasses.get(this.#blockKey(sbn, packetId, catalogTrackName))
3126
3240
  if (frozen !== undefined) return frozen
3127
3241
  return this.#peekClass(sbn, packetId, catalogTrackName)
@@ -3152,7 +3266,7 @@ export class FECManager {
3152
3266
  presentationTime: bigint,
3153
3267
  mediaTimescale: number,
3154
3268
  ): void {
3155
- if (!this.#trackConfigs.has(packetId)) return
3269
+ if (!this.#trackConfigs.has(packetId) || !this.#hasCatalogTimingForPacket(packetId)) return
3156
3270
  this.#applyClassifierChanges(
3157
3271
  packetId,
3158
3272
  catalogTrackName,
@@ -3170,36 +3284,56 @@ export class FECManager {
3170
3284
 
3171
3285
  /** Configure FEC parameters. */
3172
3286
  configure(config: FECManagerConfig): void {
3173
- if (config.k != null) this.#fecK = config.k
3287
+ // Validate timing before changing any manager-global field. A malformed
3288
+ // value must not leave a partially applied configuration (especially an
3289
+ // infinite local cleanup budget, which would never expire).
3290
+ if (
3291
+ config.interleaveDepthMs != null &&
3292
+ (!Number.isSafeInteger(config.interleaveDepthMs) || config.interleaveDepthMs < 0)
3293
+ ) {
3294
+ throw new Error(
3295
+ `[FECManager] interleaveDepthMs=${config.interleaveDepthMs} must be a non-negative safe integer in milliseconds`,
3296
+ )
3297
+ }
3298
+ if (
3299
+ config.timeoutMs != null &&
3300
+ (!Number.isFinite(config.timeoutMs) || config.timeoutMs < 0)
3301
+ ) {
3302
+ throw new Error(
3303
+ `[FECManager] timeoutMs=${config.timeoutMs} must be a finite non-negative cleanup budget`,
3304
+ )
3305
+ }
3306
+ if (config.k != null && (!Number.isSafeInteger(config.k) || config.k <= 0)) {
3307
+ throw new Error(`[FECManager] k=${config.k} must be a positive integer`)
3308
+ }
3309
+ if (config.k != null) this.#streamK = config.k
3174
3310
  if (config.symbolSize != null && config.symbolSize > 0) this.#fecSymbolSize = config.symbolSize
3175
3311
  if (config.interleaveDepthMs != null) this.#fecInterleaveDepthMs = config.interleaveDepthMs
3176
3312
  if (config.fps != null && Number.isFinite(config.fps) && config.fps > 0) {
3177
3313
  this.#fecFps = config.fps
3178
3314
  }
3179
- // interleaveDepthMs is in milliseconds per spec §5.1.
3180
- // Valid range: 33ms (interactive/gaming) to 10000ms (broadcast ATSC 3.0).
3181
- // Warn if value looks like frame count instead of ms (common misconfiguration).
3182
- if (config.interleaveDepthMs != null && config.interleaveDepthMs > 0 && config.interleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) {
3183
- console.warn(`[FECManager] interleaveDepthMs=${config.interleaveDepthMs} looks like frame count, not milliseconds (§5.1 requires ms). Expected range: ${MIN_INTERLEAVE_DEPTH_MS}-10000ms.`)
3184
- }
3185
3315
  if (config.timeoutMs != null && config.timeoutMs > 0) {
3186
3316
  this.#fecTimeoutMs = config.timeoutMs
3317
+ } else if (config.interleaveDepthMs === 0) {
3318
+ // An explicit zero span removes the catalog deadline. Reset the global
3319
+ // budget to the bounded receiver-local fallback unless this call also
3320
+ // supplied an explicit positive local timeout above.
3321
+ this.#fecTimeoutMs = LOCAL_FEC_TIMEOUT_MS
3187
3322
  } else if (
3188
3323
  config.interleaveDepthMs != null &&
3324
+ Number.isSafeInteger(config.interleaveDepthMs) &&
3189
3325
  config.interleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS
3190
3326
  ) {
3191
- this.#fecTimeoutMs = computeFecDeliveryWindowMs({
3192
- interleaveDepthMs: config.interleaveDepthMs,
3193
- sourceSymbols: this.#fecK,
3194
- symbolSize: this.#fecSymbolSize,
3195
- mediaUnitsPerSecond: config.fps,
3196
- })
3327
+ // The stream has no K (a K is per track, BLO-45953); none feeds the window.
3328
+ this.#fecTimeoutMs = fecSpanDeliveryWindowMs(config.interleaveDepthMs)
3197
3329
  }
3198
3330
  // A shared multicast parser receives every packetId. Keep existing track
3199
3331
  // deadlines at least as wide as the selected video delivery envelope so a
3200
3332
  // short audio interleave cannot expire during browser/AMT scheduling gaps.
3201
3333
  for (const [packetId, track] of this.#trackConfigs) {
3202
- if (track.timeoutMs < this.#fecTimeoutMs) {
3334
+ // A track without a positive catalog span owns only its receiver-local
3335
+ // cleanup budget; never widen it with another track's catalog timing.
3336
+ if (FECManager.#hasCatalogTiming(track) && track.timeoutMs < this.#fecTimeoutMs) {
3203
3337
  this.#trackConfigs.set(packetId, { ...track, timeoutMs: this.#fecTimeoutMs })
3204
3338
  }
3205
3339
  }
@@ -3277,7 +3411,7 @@ export class FECManager {
3277
3411
  )
3278
3412
  }
3279
3413
  if (config) this.#upsertTrackConfig(packetId, config, catalogTrackName)
3280
- const workerK = this.#resolveTrackConfig(packetId).k
3414
+ const workerK = this.#trackConfig(packetId).k
3281
3415
  this.#workerClients.set(streamKey, client)
3282
3416
  this.#releasedWorkerClients.delete(client)
3283
3417
 
@@ -3289,7 +3423,8 @@ export class FECManager {
3289
3423
  // ESI-less event as recovered would report healthy FEC while dropping
3290
3424
  // the media. Enforce the Worker contract before any recovery metric.
3291
3425
  // The Worker names the recovered block's authored SSB_length; ESIs
3292
- // SSB_length..K-1 are encoder padding and are never emitted.
3426
+ // at or past it are not source symbols of the block and are never
3427
+ // emitted.
3293
3428
  if (
3294
3429
  !Number.isInteger(meta.ssbLength) ||
3295
3430
  meta.ssbLength! <= 0 ||
@@ -3520,7 +3655,7 @@ export class FECManager {
3520
3655
  return
3521
3656
  }
3522
3657
  this.#settleOwedCleanup(key, 'cleanup-ack-timeout')
3523
- }, Math.ceil(this.#liveBlockLifetimeMs(this.#resolveTrackConfig(packetId)))),
3658
+ }, Math.ceil(this.#liveBlockLifetimeMs(this.#trackConfig(packetId)))),
3524
3659
  }
3525
3660
  this.#pendingWorkerCleanupBounds.set(key, bound)
3526
3661
  }
@@ -3568,18 +3703,20 @@ export class FECManager {
3568
3703
  if (this.#fpsDetected || fps <= 0) return
3569
3704
  this.#fpsDetected = true
3570
3705
  this.#fecFps = fps
3571
- // Only a valid §5.1 ms interleave refines the timeout; a sub-floor value is
3572
- // a raw frame count and would collapse it (see MIN_INTERLEAVE_DEPTH_MS).
3573
- if (this.#fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS) {
3574
- this.#fecTimeoutMs = computeFecDeliveryWindowMs({
3575
- interleaveDepthMs: this.#fecInterleaveDepthMs,
3576
- sourceSymbols: this.#fecK,
3577
- symbolSize: this.#fecSymbolSize,
3578
- mediaUnitsPerSecond: fps,
3579
- })
3706
+ // Only a valid non-negative integer §5.1 ms interleave refines the
3707
+ // timeout; zero is the no-deadline sentinel.
3708
+ if (
3709
+ Number.isSafeInteger(this.#fecInterleaveDepthMs) &&
3710
+ this.#fecInterleaveDepthMs >= MIN_INTERLEAVE_DEPTH_MS
3711
+ ) {
3712
+ this.#fecTimeoutMs = fecSpanDeliveryWindowMs(this.#fecInterleaveDepthMs)
3580
3713
  }
3581
3714
  for (const [packetId, config] of this.#trackConfigs) {
3582
- if (config.fps !== undefined || config.interleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS) continue
3715
+ if (
3716
+ config.fps !== undefined ||
3717
+ !Number.isSafeInteger(config.interleaveDepthMs) ||
3718
+ config.interleaveDepthMs < MIN_INTERLEAVE_DEPTH_MS
3719
+ ) continue
3583
3720
  this.#trackConfigs.set(packetId, {
3584
3721
  ...config,
3585
3722
  fps,
@@ -3614,36 +3751,31 @@ export class FECManager {
3614
3751
  packetId: number,
3615
3752
  ssId: number,
3616
3753
  symbolData: Uint8Array,
3617
- alta?: { auth: Uint8Array; payloadEnd: number },
3618
3754
  catalogTrackName?: string,
3619
3755
  ): Uint8Array | null {
3620
3756
  this.#fecStats.sourcePackets++
3621
3757
  this.#statsForPacket(packetId).sourcePackets++
3622
- let sbn: number
3623
- let fecData: Uint8Array
3624
- if (this.#isIso(packetId)) {
3625
- // iso-ssbg1-v1: the decoder builds the ssbg_mode1 symbol from the exact
3626
- // protected packet, so the packet travels unpadded. Its block must
3627
- // already be stated (MmtpRouter holds a source until a repair places it).
3628
- const locator = this.#isoLocatorFor(packetId, catalogTrackName)
3629
- const located = locator.locate(ssId)
3630
- if (located === null) {
3631
- throw new Error(
3632
- `[FECManager] iso-ssbg1-v1 source SS_ID=${ssId} (packetId=${packetId}) has no stated block; ` +
3633
- 'hold it until a repair places it (placeIsoRepair)',
3634
- )
3635
- }
3636
- locator.observe(ssId)
3637
- if (!ssbg1FitsSymbol(symbolData.byteLength, this.symbolSizeForPacket(packetId))) {
3638
- this.#isoSourceOversizeCount++
3639
- return null
3640
- }
3641
- sbn = located.block.start
3642
- fecData = symbolData
3643
- } else {
3644
- fecData = this.#normalizeSymbolData(packetId, symbolData)
3645
- sbn = Math.floor(ssId / this.kForPacket(packetId))
3758
+ // A packetId with no configured block profile has no block geometry and
3759
+ // no decoder (setFecDecoder/setFecWorker configure the track): nothing to
3760
+ // decode.
3761
+ if (!this.#hasBlockProfile(packetId)) return null
3762
+ // The decoder builds the ssbg_mode1 symbol from the exact protected
3763
+ // packet, so the packet travels unpadded. Its block must already be
3764
+ // stated (MmtpRouter holds a source until a repair places it).
3765
+ const locator = this.#isoLocatorFor(packetId, catalogTrackName)
3766
+ const located = locator.locate(ssId)
3767
+ if (located === null) {
3768
+ throw new Error(
3769
+ `[FECManager] iso-ssbg1-v1 source SS_ID=${ssId} (packetId=${packetId}) has no stated block; ` +
3770
+ 'hold it until a repair places it (placeIsoRepair)',
3771
+ )
3646
3772
  }
3773
+ locator.observe(ssId)
3774
+ if (!ssbg1FitsSymbol(symbolData.byteLength, this.#trackConfig(packetId).symbolSize)) {
3775
+ this.#isoSourceOversizeCount++
3776
+ return null
3777
+ }
3778
+ const sbn = located.block.start
3647
3779
  const blockKey = this.#blockKey(sbn, packetId, catalogTrackName)
3648
3780
  const syncBlockOwner = this.#syncDecoderBlockOwners.get(blockKey)
3649
3781
 
@@ -3651,36 +3783,19 @@ export class FECManager {
3651
3783
  const workerClient = this.#routableWorkerForPacket(packetId, catalogTrackName)
3652
3784
  if (workerClient && !syncBlockOwner) {
3653
3785
  if (!this.#claimDecoderBlock(workerClient, sbn, catalogTrackName)) return null
3654
- // Transfer an ArrayBuffer nothing else references. A symbol this manager
3655
- // just zero-padded to T already is one; copying it again cost a second
3656
- // T-byte allocation per short packet (BLO-36599). A caller's buffer may
3657
- // be a view into a larger packet, so it is copied first (T-03-06).
3658
- const buf = fecData !== symbolData
3659
- ? fecData.buffer as ArrayBuffer
3660
- : fecData.buffer.slice(
3661
- fecData.byteOffset,
3662
- fecData.byteOffset + fecData.byteLength,
3663
- ) as ArrayBuffer
3664
- // Pass the flat SS_ID through to the worker. The WASM decoder derives
3665
- // SBN/ESI internally per ISO 23008-1 §C.5.2; the WASM add_source
3786
+ // Transfer an ArrayBuffer nothing else references. The source travels
3787
+ // unpadded, and a caller's buffer may be a view into a larger packet,
3788
+ // so it is copied first (T-03-06).
3789
+ const buf = symbolData.buffer.slice(
3790
+ symbolData.byteOffset,
3791
+ symbolData.byteOffset + symbolData.byteLength,
3792
+ ) as ArrayBuffer
3793
+ // Pass the flat SS_ID through to the worker. The WASM decoder places
3794
+ // the source in its block by SS_ID (ISO 23008-1 §C.5.2); the WASM add_source
3666
3795
  // signature is (ss_id, data, timestamp) — pre-computing sbn/esi on this
3667
3796
  // side produces wrong WASM argument alignment and silently breaks
3668
3797
  // block assembly.
3669
- if (alta) {
3670
- // Copy auth into a fresh ArrayBuffer to transfer zero-copy. The
3671
- // underlying buffer of the parsed trailer view belongs to the wire
3672
- // packet, which may be transferred separately or reused.
3673
- const authBuf = alta.auth.buffer.slice(
3674
- alta.auth.byteOffset,
3675
- alta.auth.byteOffset + alta.auth.byteLength,
3676
- ) as ArrayBuffer
3677
- workerClient.feedSource(ssId, buf, performance.now(), {
3678
- auth: authBuf,
3679
- payloadEnd: alta.payloadEnd,
3680
- })
3681
- } else {
3682
- workerClient.feedSource(ssId, buf, performance.now())
3683
- }
3798
+ workerClient.feedSource(ssId, buf, performance.now())
3684
3799
  return null
3685
3800
  }
3686
3801
 
@@ -3693,7 +3808,7 @@ export class FECManager {
3693
3808
 
3694
3809
  let recovered: Uint8Array | null = null
3695
3810
  try {
3696
- recovered = client.add_source(ssId, fecData, performance.now())
3811
+ recovered = client.add_source(ssId, symbolData, performance.now())
3697
3812
  } catch {
3698
3813
  // WASM RefCell panic — decoder instance shared with multicast repair path
3699
3814
  }
@@ -3722,7 +3837,10 @@ export class FECManager {
3722
3837
  ): Uint8Array | null {
3723
3838
  this.#fecStats.repairPackets++
3724
3839
  this.#statsForPacket(packetId).repairPackets++
3725
- const symbolSize = this.symbolSizeForPacket(packetId)
3840
+ // A packetId with no configured block profile has no block geometry: its
3841
+ // repair names no block this manager can track or decode.
3842
+ if (!this.#hasBlockProfile(packetId)) return null
3843
+ const symbolSize = this.#trackConfig(packetId).symbolSize
3726
3844
  if (symbolSize > 0 && repairData.byteLength !== symbolSize) {
3727
3845
  this.#repairSymbolSizeMismatchCount++
3728
3846
  if (this.#repairSymbolSizeMismatchCount <= 5 || this.#repairSymbolSizeMismatchCount % 100 === 0) {
@@ -3734,25 +3852,17 @@ export class FECManager {
3734
3852
  }
3735
3853
  const fecData = repairData
3736
3854
 
3737
- // SSB_length is the authored source-symbol count in this block, not the
3738
- // decoder's configured block width. Short authored blocks are padded to
3739
- // the configured K, so all internal keys and lifecycle coordinates use K.
3740
- // Keep the wire tuple unchanged when calling the decoder below.
3741
- const k = this.kForPacket(packetId)
3742
- let sbn: number
3743
- if (this.#isIso(packetId)) {
3744
- // iso-ssbg1-v1 (C.5.3): the block is [SS_Start, SS_Start + SSB_length),
3745
- // K' = SSB_length, and the catalog K is only its maximum. MmtpRouter
3746
- // placed the block before feeding its repair (placeIsoRepair).
3747
- const block = this.#isoLocatorFor(packetId, catalogTrackName).blockAt(ssStart)
3748
- if (ssbLength <= 0 || ssbLength > k || block === undefined || block.span || block.length !== ssbLength) {
3749
- return null
3750
- }
3751
- sbn = ssStart
3752
- } else {
3753
- if (ssbLength <= 0 || ssbLength > k || ssStart % k !== 0) return null
3754
- sbn = Math.floor(ssStart / k)
3855
+ // ISO/IEC 23008-1 C.5.3: the block is [SS_Start, SS_Start + SSB_length),
3856
+ // K' = SSB_length, and the catalog K is only its maximum. The block is
3857
+ // keyed by its SS_Start. MmtpRouter placed the block before feeding its
3858
+ // repair (placeIsoRepair). Keep the wire tuple unchanged when calling the
3859
+ // decoder below.
3860
+ const k = this.#trackConfig(packetId).k
3861
+ const block = this.#isoLocatorFor(packetId, catalogTrackName).blockAt(ssStart)
3862
+ if (ssbLength <= 0 || ssbLength > k || block === undefined || block.span || block.length !== ssbLength) {
3863
+ return null
3755
3864
  }
3865
+ const sbn = ssStart
3756
3866
  this.#noteSbnSeen(sbn, packetId, catalogTrackName, 'repair')
3757
3867
 
3758
3868
  // Repair symbols are block activity: refresh the inactivity fail bound so
@@ -3802,8 +3912,9 @@ export class FECManager {
3802
3912
  * Record a block's authored SSB_length from a repair symbol. Returns false,
3803
3913
  * leaving the block unchanged, when the symbol contradicts the block: a
3804
3914
  * different SSB_length than an earlier repair symbol declared is dropped,
3805
- * and an SSB_length that places an already-received source ESI inside the
3806
- * zero padding fails the block, since its geometry can no longer be trusted.
3915
+ * and an SSB_length that places an already-received source ESI at or past
3916
+ * the block's end fails the block, since its geometry can no longer be
3917
+ * trusted.
3807
3918
  */
3808
3919
  #noteAuthoredLength(
3809
3920
  blockKey: string,
@@ -3910,7 +4021,7 @@ export class FECManager {
3910
4021
  packetId: number,
3911
4022
  catalogTrackName: string | undefined,
3912
4023
  ): void {
3913
- if (!this.#isIso(packetId)) return
4024
+ if (!this.#hasBlockProfile(packetId)) return
3914
4025
  const newest = this.#isoLocators.get(this.#streamKey(packetId, catalogTrackName))?.newest
3915
4026
  if (newest === undefined) return
3916
4027
  try {
@@ -3963,19 +4074,21 @@ export class FECManager {
3963
4074
 
3964
4075
  this.#noteSbnSeen(sbn, packetId, catalogTrackName, 'source', evidence.packetSequenceNumber)
3965
4076
 
3966
- // A source ESI inside the zero padding contradicts the block's authored
3967
- // SSB_length; its geometry can no longer be trusted, so fail it closed.
4077
+ // A source ESI at or past the block's authored SSB_length contradicts it;
4078
+ // its geometry can no longer be trusted, so fail it closed.
3968
4079
  const authoredLength = this.#blockAuthoredLengths.get(key) ?? k
3969
4080
  if (Number.isInteger(esi) && esi! >= authoredLength) {
3970
4081
  this.#failBlock(sbn, packetId, catalogTrackName)
3971
4082
  return
3972
4083
  }
3973
4084
 
3974
- this.#applyClassifierChanges(
3975
- packetId,
3976
- catalogTrackName,
3977
- this.#classifierFor(packetId, catalogTrackName).observe(sbn, evidence),
3978
- )
4085
+ if (FECManager.#hasCatalogTiming(trackConfig)) {
4086
+ this.#applyClassifierChanges(
4087
+ packetId,
4088
+ catalogTrackName,
4089
+ this.#classifierFor(packetId, catalogTrackName).observe(sbn, evidence),
4090
+ )
4091
+ }
3979
4092
 
3980
4093
  // Count source symbols per block
3981
4094
  const count = (this.#blockSourceCounts.get(key) ?? 0) + 1
@@ -4024,6 +4137,7 @@ export class FECManager {
4024
4137
  this.#blockSourceEsis.delete(key)
4025
4138
  this.#blockAuthoredLengths.delete(key)
4026
4139
  this.#blockRepairRsIds.delete(key)
4140
+ this.#blockRsbLengths.delete(key)
4027
4141
  // BLO-3323 L15: track which SBNs reached K source-symbol arrivals so the
4028
4142
  // worker recovery suppression can distinguish "block completed by source"
4029
4143
  // (legitimately suppress duplicate recovery) from "block touched by any
@@ -4049,6 +4163,9 @@ export class FECManager {
4049
4163
  catalogTrackName?: string,
4050
4164
  sourceProgress = false,
4051
4165
  ): void {
4166
+ // An unconfigured packetId does no FEC work: no deadline is armed at an
4167
+ // assumed geometry (BLO-45953).
4168
+ if (!this.#trackConfigs.has(packetId)) return
4052
4169
  const key = this.#blockKey(sbn, packetId, catalogTrackName)
4053
4170
 
4054
4171
  // Late symbols must not resurrect a block that already completed or failed.
@@ -4070,7 +4187,7 @@ export class FECManager {
4070
4187
  return
4071
4188
  }
4072
4189
 
4073
- const trackConfig = this.#resolveTrackConfig(packetId)
4190
+ const trackConfig = this.#trackConfig(packetId)
4074
4191
  const timeoutMs = this.#multicastWaitMs(trackConfig)
4075
4192
  // #computeTimeout never yields <= 0 (computeFecDeliveryWindowMs throws on
4076
4193
  // a non-positive interleave or K). Returning here would leave a block
@@ -4087,6 +4204,7 @@ export class FECManager {
4087
4204
  // most K+P accepted symbols can move the wait, so the block stays bounded.
4088
4205
  const repairDeadline = this.#repairDeadlines.get(key)
4089
4206
  if (
4207
+ this.#hasCatalogTimingForPacket(packetId) &&
4090
4208
  this.onRepairNeeded &&
4091
4209
  !repairRequested &&
4092
4210
  (repairDeadline === undefined || sourceProgress)
@@ -4120,6 +4238,7 @@ export class FECManager {
4120
4238
  this.#blockSourceEsis.delete(key)
4121
4239
  this.#blockAuthoredLengths.delete(key)
4122
4240
  this.#blockRepairRsIds.delete(key)
4241
+ this.#blockRsbLengths.delete(key)
4123
4242
  this.#syncDecoderBlockOwners.delete(key)
4124
4243
  this.#markTerminal(key, sbn, packetId, false)
4125
4244
  this.#failedBlocks.add(key)
@@ -4158,6 +4277,7 @@ export class FECManager {
4158
4277
  this.#blockSourceEsis.delete(key)
4159
4278
  this.#blockAuthoredLengths.delete(key)
4160
4279
  this.#blockRepairRsIds.delete(key)
4280
+ this.#blockRsbLengths.delete(key)
4161
4281
  this.#clearBlockTimer(key)
4162
4282
  this.onBlockResolved?.(block.sbn, block.packetId, block.catalogTrackName)
4163
4283
  }
@@ -4169,6 +4289,8 @@ export class FECManager {
4169
4289
 
4170
4290
  /** Mark a block as completed externally (e.g., after routing recovered data). */
4171
4291
  markBlockCompleted(sbn: number, packetId?: number, catalogTrackName?: string): void {
4292
+ // An unconfigured packetId has no FEC blocks to complete (BLO-45953).
4293
+ if (packetId != null && !this.#trackConfigs.has(packetId)) return
4172
4294
  for (const key of this.#blockKeysFor(sbn, packetId, catalogTrackName)) {
4173
4295
  this.#resolveCompleted(key)
4174
4296
  }
@@ -4263,6 +4385,7 @@ export class FECManager {
4263
4385
  this.#blockSourceEsis.clear()
4264
4386
  this.#blockAuthoredLengths.clear()
4265
4387
  this.#blockRepairRsIds.clear()
4388
+ this.#blockRsbLengths.clear()
4266
4389
  this.#completedBlocks.clear()
4267
4390
  this.#cleanlyCompletedBlocks.clear()
4268
4391
  this.#repairRequestedBlocks.clear()