@blockcast/mmt-container 0.3.0 → 0.4.0-main.d6f2737828fc

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 (588) hide show
  1. package/README.md +22 -8
  2. package/dist/browser-runtime.js +1185 -1049
  3. package/dist/browser-runtime.js.map +4 -4
  4. package/dist/carp-catalog.d.ts +24 -8
  5. package/dist/carp-catalog.d.ts.map +1 -1
  6. package/dist/carp-catalog.js +21 -8
  7. package/dist/carp-catalog.js.map +2 -2
  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 +59 -17
  11. package/dist/catalog-fec-params.js.map +3 -3
  12. package/dist/catalog-network-source.js +0 -1
  13. package/dist/catalog-network-source.js.map +2 -2
  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/delta-fallback-config.d.ts +42 -0
  19. package/dist/delta-fallback-config.d.ts.map +1 -0
  20. package/dist/delta-fallback-config.js +59 -0
  21. package/dist/delta-fallback-config.js.map +7 -0
  22. package/dist/fec-manager.d.ts +35 -26
  23. package/dist/fec-manager.d.ts.map +1 -1
  24. package/dist/fec-manager.js +300 -273
  25. package/dist/fec-manager.js.map +2 -2
  26. package/dist/fec-state.d.ts +19 -31
  27. package/dist/fec-state.d.ts.map +1 -1
  28. package/dist/fec-state.js +3 -4
  29. package/dist/fec-state.js.map +2 -2
  30. package/dist/fec-worker-setup.d.ts +16 -6
  31. package/dist/fec-worker-setup.d.ts.map +1 -1
  32. package/dist/fec-worker-setup.js +72 -34
  33. package/dist/fec-worker-setup.js.map +2 -2
  34. package/dist/index.d.ts +4 -3
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +12 -0
  37. package/dist/index.js.map +2 -2
  38. package/dist/iso-block-locator.js +0 -1
  39. package/dist/iso-block-locator.js.map +2 -2
  40. package/dist/media-router.d.ts +20 -8
  41. package/dist/media-router.d.ts.map +1 -1
  42. package/dist/media-router.js +46 -21
  43. package/dist/media-router.js.map +2 -2
  44. package/dist/mfu-reassembler.js +0 -1
  45. package/dist/mfu-reassembler.js.map +2 -2
  46. package/dist/mmtp-container-parser.d.ts +5 -0
  47. package/dist/mmtp-container-parser.d.ts.map +1 -1
  48. package/dist/mmtp-container-parser.js +22 -1
  49. package/dist/mmtp-container-parser.js.map +2 -2
  50. package/dist/mmtp-container-parser.no-modules.js +37 -9
  51. package/dist/mmtp-container-parser.no-modules.js.map +2 -2
  52. package/dist/mmtp-decode-pipeline.js +22 -1
  53. package/dist/mmtp-decode-pipeline.js.map +2 -2
  54. package/dist/mmtp-router.d.ts +36 -16
  55. package/dist/mmtp-router.d.ts.map +1 -1
  56. package/dist/mmtp-router.js +126 -213
  57. package/dist/mmtp-router.js.map +2 -2
  58. package/dist/multicast-input.js +0 -1
  59. package/dist/multicast-input.js.map +2 -2
  60. package/dist/multicast-manager.d.ts +33 -3
  61. package/dist/multicast-manager.d.ts.map +1 -1
  62. package/dist/multicast-manager.js +660 -529
  63. package/dist/multicast-manager.js.map +4 -4
  64. package/dist/multicast-transport.d.ts +1 -1
  65. package/dist/multicast-transport.d.ts.map +1 -1
  66. package/dist/multicast-transport.js +42 -14
  67. package/dist/multicast-transport.js.map +3 -3
  68. package/dist/multicast-unwrap.js +0 -1
  69. package/dist/multicast-unwrap.js.map +2 -2
  70. package/dist/no-modules.js +36 -8
  71. package/dist/no-modules.js.map +2 -2
  72. package/dist/repair-fetch.d.ts +30 -19
  73. package/dist/repair-fetch.d.ts.map +1 -1
  74. package/dist/repair-fetch.js +32 -18
  75. package/dist/repair-fetch.js.map +3 -3
  76. package/dist/repair-layers.d.ts +18 -7
  77. package/dist/repair-layers.d.ts.map +1 -1
  78. package/dist/repair-layers.js +11 -4
  79. package/dist/repair-layers.js.map +2 -2
  80. package/dist/signaling.js +0 -4
  81. package/dist/signaling.js.map +2 -2
  82. package/dist/timestamp-codec.js +0 -1
  83. package/dist/timestamp-codec.js.map +2 -2
  84. package/dist/track-session.d.ts +7 -0
  85. package/dist/track-session.d.ts.map +1 -1
  86. package/dist/track-session.js +15 -8
  87. package/dist/track-session.js.map +2 -2
  88. package/dist/wasm-mfu-reassembler.js +0 -1
  89. package/dist/wasm-mfu-reassembler.js.map +2 -2
  90. package/dist/wasm-mmtp-container-parser.js +22 -1
  91. package/dist/wasm-mmtp-container-parser.js.map +2 -2
  92. package/package.json +8 -14
  93. package/schemas/catalog.schema.json +3 -3
  94. package/src/carp-catalog.ts +35 -13
  95. package/src/catalog-fec-params.ts +173 -64
  96. package/src/cmaf-block-assembler.ts +1 -4
  97. package/src/delta-fallback-config.ts +72 -0
  98. package/src/fec-manager.ts +413 -317
  99. package/src/fec-state.ts +20 -32
  100. package/src/fec-worker-setup.ts +105 -55
  101. package/src/index.ts +10 -0
  102. package/src/media-router.ts +59 -21
  103. package/src/mmtp-container-parser.ts +26 -0
  104. package/src/mmtp-router.ts +145 -303
  105. package/src/multicast-manager.ts +172 -60
  106. package/src/multicast-transport.ts +16 -9
  107. package/src/repair-fetch.ts +46 -27
  108. package/src/repair-layers.ts +28 -8
  109. package/src/track-session.ts +28 -9
  110. package/test-vectors/catalog/README.md +34 -17
  111. package/test-vectors/catalog/negative/fec-block-profile-retired.json +52 -0
  112. package/test-vectors/catalog/negative/fec-enhancement-repair-removed.json +1 -1
  113. package/test-vectors/catalog/negative/fec-repair-layer-geometry.json +1 -1
  114. package/test-vectors/catalog/negative/fec-repair-layer-missing-symbols.json +1 -1
  115. package/test-vectors/catalog/negative/fec-repair-multiple-depends.json +1 -1
  116. package/test-vectors/catalog/negative/fec-repair-overlay-with-layer.json +1 -1
  117. package/test-vectors/catalog/negative/fec-repair-priority-out-of-band.json +1 -1
  118. package/test-vectors/catalog/negative/fec-repair-source-symbols-without-scope.json +1 -1
  119. package/test-vectors/catalog/negative/multicast-network-source-empty.json +1 -1
  120. package/test-vectors/catalog/negative/raptorq-source-symbols-over-max.json +1 -1
  121. package/test-vectors/catalog/negative/raptorq-unaligned-symbol.json +1 -1
  122. package/test-vectors/catalog/positive/fec-layered-repair.json +2 -2
  123. package/test-vectors/catalog/positive/fec-multicast.json +2 -1
  124. package/test-vectors/catalog/positive/multicast-native-ssm-no-network-source.json +2 -1
  125. package/test-vectors/catalog-timing.json +356 -0
  126. package/test-vectors/catalog-validation.json +40 -5
  127. package/node_modules/@blockcast/fec-worker/README.md +0 -223
  128. package/node_modules/@blockcast/fec-worker/dist/.build-stamp +0 -0
  129. package/node_modules/@blockcast/fec-worker/dist/block-order.d.ts +0 -38
  130. package/node_modules/@blockcast/fec-worker/dist/block-order.d.ts.map +0 -1
  131. package/node_modules/@blockcast/fec-worker/dist/block-order.js +0 -78
  132. package/node_modules/@blockcast/fec-worker/dist/block-order.js.map +0 -1
  133. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts +0 -93
  134. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts.map +0 -1
  135. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js +0 -346
  136. package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js.map +0 -1
  137. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts +0 -380
  138. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts.map +0 -1
  139. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js +0 -11
  140. package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.js.map +0 -1
  141. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts +0 -14
  142. package/node_modules/@blockcast/fec-worker/dist/fec-worker.d.ts.map +0 -1
  143. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js +0 -859
  144. package/node_modules/@blockcast/fec-worker/dist/fec-worker.js.map +0 -7
  145. package/node_modules/@blockcast/fec-worker/dist/index.d.ts +0 -10
  146. package/node_modules/@blockcast/fec-worker/dist/index.d.ts.map +0 -1
  147. package/node_modules/@blockcast/fec-worker/dist/index.js +0 -8
  148. package/node_modules/@blockcast/fec-worker/dist/index.js.map +0 -1
  149. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts +0 -20
  150. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.d.ts.map +0 -1
  151. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js +0 -370
  152. package/node_modules/@blockcast/fec-worker/dist/shred-fec-worker.js.map +0 -7
  153. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts +0 -104
  154. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.d.ts.map +0 -1
  155. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js +0 -181
  156. package/node_modules/@blockcast/fec-worker/dist/shred-worker-client.js.map +0 -1
  157. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts +0 -194
  158. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.d.ts.map +0 -1
  159. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js +0 -62
  160. package/node_modules/@blockcast/fec-worker/dist/shred-worker-types.js.map +0 -1
  161. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts +0 -9
  162. package/node_modules/@blockcast/fec-worker/dist/transfer.d.ts.map +0 -1
  163. package/node_modules/@blockcast/fec-worker/dist/transfer.js +0 -13
  164. package/node_modules/@blockcast/fec-worker/dist/transfer.js.map +0 -1
  165. package/node_modules/@blockcast/fec-worker/package.json +0 -59
  166. package/node_modules/@blockcast/fec-worker/src/block-order.ts +0 -93
  167. package/node_modules/@blockcast/fec-worker/src/fec-worker-client.ts +0 -392
  168. package/node_modules/@blockcast/fec-worker/src/fec-worker-types.ts +0 -399
  169. package/node_modules/@blockcast/fec-worker/src/fec-worker.ts +0 -1502
  170. package/node_modules/@blockcast/fec-worker/src/index.ts +0 -35
  171. package/node_modules/@blockcast/fec-worker/src/shred-fec-worker.ts +0 -589
  172. package/node_modules/@blockcast/fec-worker/src/shred-worker-client.ts +0 -222
  173. package/node_modules/@blockcast/fec-worker/src/shred-worker-types.ts +0 -209
  174. package/node_modules/@blockcast/fec-worker/src/transfer.ts +0 -12
  175. package/node_modules/@blockcast/mmt-alta-parse/README.md +0 -181
  176. package/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  177. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +0 -57
  178. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +0 -1
  179. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js +0 -115
  180. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +0 -1
  181. package/node_modules/@blockcast/mmt-alta-parse/package.json +0 -50
  182. package/node_modules/@blockcast/mmt-alta-parse/src/index.ts +0 -136
  183. package/node_modules/@blockcast/mmt-base/README.md +0 -134
  184. package/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  185. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts +0 -55
  186. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts.map +0 -1
  187. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js +0 -128
  188. package/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js.map +0 -1
  189. package/node_modules/@blockcast/mmt-base/dist/index.d.ts +0 -7
  190. package/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +0 -1
  191. package/node_modules/@blockcast/mmt-base/dist/index.js +0 -7
  192. package/node_modules/@blockcast/mmt-base/dist/index.js.map +0 -1
  193. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +0 -292
  194. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +0 -1
  195. package/node_modules/@blockcast/mmt-base/dist/mmtp.js +0 -612
  196. package/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +0 -1
  197. package/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts +0 -18
  198. package/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts.map +0 -1
  199. package/node_modules/@blockcast/mmt-base/dist/net-constants.js +0 -18
  200. package/node_modules/@blockcast/mmt-base/dist/net-constants.js.map +0 -1
  201. package/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts +0 -47
  202. package/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts.map +0 -1
  203. package/node_modules/@blockcast/mmt-base/dist/ntp-short.js +0 -54
  204. package/node_modules/@blockcast/mmt-base/dist/ntp-short.js.map +0 -1
  205. package/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts +0 -57
  206. package/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts.map +0 -1
  207. package/node_modules/@blockcast/mmt-base/dist/serial-number.js +0 -98
  208. package/node_modules/@blockcast/mmt-base/dist/serial-number.js.map +0 -1
  209. package/node_modules/@blockcast/mmt-base/dist/signalling.d.ts +0 -312
  210. package/node_modules/@blockcast/mmt-base/dist/signalling.d.ts.map +0 -1
  211. package/node_modules/@blockcast/mmt-base/dist/signalling.js +0 -637
  212. package/node_modules/@blockcast/mmt-base/dist/signalling.js.map +0 -1
  213. package/node_modules/@blockcast/mmt-base/package.json +0 -49
  214. package/node_modules/@blockcast/mmt-base/src/fec-block-profile.ts +0 -145
  215. package/node_modules/@blockcast/mmt-base/src/index.ts +0 -6
  216. package/node_modules/@blockcast/mmt-base/src/mmtp.ts +0 -892
  217. package/node_modules/@blockcast/mmt-base/src/net-constants.ts +0 -17
  218. package/node_modules/@blockcast/mmt-base/src/ntp-short.ts +0 -67
  219. package/node_modules/@blockcast/mmt-base/src/serial-number.ts +0 -107
  220. package/node_modules/@blockcast/mmt-base/src/signalling.ts +0 -862
  221. package/node_modules/@blockcast/mmt-fec/README.md +0 -271
  222. package/node_modules/@blockcast/mmt-fec/dist/.build-stamp +0 -0
  223. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts +0 -82
  224. package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts.map +0 -1
  225. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js +0 -296
  226. package/node_modules/@blockcast/mmt-fec/dist/block-manager.js.map +0 -1
  227. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts +0 -106
  228. package/node_modules/@blockcast/mmt-fec/dist/block-processor.d.ts.map +0 -1
  229. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js +0 -288
  230. package/node_modules/@blockcast/mmt-fec/dist/block-processor.js.map +0 -1
  231. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts +0 -46
  232. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.d.ts.map +0 -1
  233. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js +0 -96
  234. package/node_modules/@blockcast/mmt-fec/dist/cmaf-recovery.js.map +0 -1
  235. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts +0 -33
  236. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.d.ts.map +0 -1
  237. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js +0 -57
  238. package/node_modules/@blockcast/mmt-fec/dist/cmaf-symbols.js.map +0 -1
  239. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts +0 -205
  240. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts.map +0 -1
  241. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js +0 -240
  242. package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js.map +0 -1
  243. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts +0 -28
  244. package/node_modules/@blockcast/mmt-fec/dist/index.d.ts.map +0 -1
  245. package/node_modules/@blockcast/mmt-fec/dist/index.js +0 -28
  246. package/node_modules/@blockcast/mmt-fec/dist/index.js.map +0 -1
  247. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts +0 -20
  248. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.d.ts.map +0 -1
  249. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js +0 -28
  250. package/node_modules/@blockcast/mmt-fec/dist/loc-symbols.js.map +0 -1
  251. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts +0 -258
  252. package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts.map +0 -1
  253. package/node_modules/@blockcast/mmt-fec/dist/oti.js +0 -382
  254. package/node_modules/@blockcast/mmt-fec/dist/oti.js.map +0 -1
  255. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts +0 -41
  256. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.d.ts.map +0 -1
  257. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js +0 -81
  258. package/node_modules/@blockcast/mmt-fec/dist/reorder-buffer.js.map +0 -1
  259. package/node_modules/@blockcast/mmt-fec/dist/repair-group.d.ts +0 -82
  260. package/node_modules/@blockcast/mmt-fec/dist/repair-group.d.ts.map +0 -1
  261. package/node_modules/@blockcast/mmt-fec/dist/repair-group.js +0 -170
  262. package/node_modules/@blockcast/mmt-fec/dist/repair-group.js.map +0 -1
  263. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/README.md +0 -181
  264. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  265. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +0 -57
  266. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +0 -1
  267. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js +0 -115
  268. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +0 -1
  269. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/package.json +0 -50
  270. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-alta-parse/src/index.ts +0 -136
  271. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/README.md +0 -134
  272. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  273. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts +0 -55
  274. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.d.ts.map +0 -1
  275. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js +0 -128
  276. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/fec-block-profile.js.map +0 -1
  277. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts +0 -7
  278. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +0 -1
  279. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js +0 -7
  280. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/index.js.map +0 -1
  281. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +0 -292
  282. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +0 -1
  283. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js +0 -612
  284. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +0 -1
  285. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts +0 -18
  286. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.d.ts.map +0 -1
  287. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.js +0 -18
  288. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/net-constants.js.map +0 -1
  289. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts +0 -47
  290. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.d.ts.map +0 -1
  291. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.js +0 -54
  292. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/ntp-short.js.map +0 -1
  293. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts +0 -57
  294. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.d.ts.map +0 -1
  295. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.js +0 -98
  296. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/serial-number.js.map +0 -1
  297. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.d.ts +0 -312
  298. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.d.ts.map +0 -1
  299. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.js +0 -637
  300. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/signalling.js.map +0 -1
  301. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/package.json +0 -49
  302. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/fec-block-profile.ts +0 -145
  303. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/index.ts +0 -6
  304. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/mmtp.ts +0 -892
  305. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/net-constants.ts +0 -17
  306. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/ntp-short.ts +0 -67
  307. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/serial-number.ts +0 -107
  308. package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/signalling.ts +0 -862
  309. package/node_modules/@blockcast/mmt-fec/package.json +0 -79
  310. package/node_modules/@blockcast/mmt-fec/src/block-manager.ts +0 -371
  311. package/node_modules/@blockcast/mmt-fec/src/block-processor.ts +0 -439
  312. package/node_modules/@blockcast/mmt-fec/src/cmaf-recovery.ts +0 -127
  313. package/node_modules/@blockcast/mmt-fec/src/cmaf-symbols.ts +0 -68
  314. package/node_modules/@blockcast/mmt-fec/src/fec-block-mapper.ts +0 -310
  315. package/node_modules/@blockcast/mmt-fec/src/index.ts +0 -94
  316. package/node_modules/@blockcast/mmt-fec/src/loc-symbols.ts +0 -34
  317. package/node_modules/@blockcast/mmt-fec/src/oti.ts +0 -463
  318. package/node_modules/@blockcast/mmt-fec/src/reorder-buffer.ts +0 -101
  319. package/node_modules/@blockcast/mmt-fec/src/repair-group.ts +0 -195
  320. package/node_modules/@blockcast/mmt-manifest-verify/dist/.build-stamp +0 -0
  321. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts +0 -28
  322. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.d.ts.map +0 -1
  323. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js +0 -128
  324. package/node_modules/@blockcast/mmt-manifest-verify/dist/admission.js.map +0 -1
  325. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts +0 -4
  326. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.d.ts.map +0 -1
  327. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js +0 -4
  328. package/node_modules/@blockcast/mmt-manifest-verify/dist/index.js.map +0 -1
  329. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts +0 -37
  330. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.d.ts.map +0 -1
  331. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js +0 -179
  332. package/node_modules/@blockcast/mmt-manifest-verify/dist/source-auth.js.map +0 -1
  333. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts +0 -99
  334. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts.map +0 -1
  335. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js +0 -492
  336. package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js.map +0 -1
  337. package/node_modules/@blockcast/mmt-manifest-verify/package.json +0 -45
  338. package/node_modules/@blockcast/mmt-manifest-verify/src/admission.ts +0 -171
  339. package/node_modules/@blockcast/mmt-manifest-verify/src/index.ts +0 -3
  340. package/node_modules/@blockcast/mmt-manifest-verify/src/source-auth.ts +0 -230
  341. package/node_modules/@blockcast/mmt-manifest-verify/src/verifier.ts +0 -669
  342. package/node_modules/@noble/hashes/LICENSE +0 -21
  343. package/node_modules/@noble/hashes/README.md +0 -521
  344. package/node_modules/@noble/hashes/_assert.d.ts +0 -17
  345. package/node_modules/@noble/hashes/_assert.d.ts.map +0 -1
  346. package/node_modules/@noble/hashes/_assert.js +0 -18
  347. package/node_modules/@noble/hashes/_assert.js.map +0 -1
  348. package/node_modules/@noble/hashes/_blake.d.ts +0 -14
  349. package/node_modules/@noble/hashes/_blake.d.ts.map +0 -1
  350. package/node_modules/@noble/hashes/_blake.js +0 -50
  351. package/node_modules/@noble/hashes/_blake.js.map +0 -1
  352. package/node_modules/@noble/hashes/_md.d.ts +0 -51
  353. package/node_modules/@noble/hashes/_md.d.ts.map +0 -1
  354. package/node_modules/@noble/hashes/_md.js +0 -162
  355. package/node_modules/@noble/hashes/_md.js.map +0 -1
  356. package/node_modules/@noble/hashes/_u64.d.ts +0 -55
  357. package/node_modules/@noble/hashes/_u64.d.ts.map +0 -1
  358. package/node_modules/@noble/hashes/_u64.js +0 -90
  359. package/node_modules/@noble/hashes/_u64.js.map +0 -1
  360. package/node_modules/@noble/hashes/argon2.d.ts +0 -32
  361. package/node_modules/@noble/hashes/argon2.d.ts.map +0 -1
  362. package/node_modules/@noble/hashes/argon2.js +0 -401
  363. package/node_modules/@noble/hashes/argon2.js.map +0 -1
  364. package/node_modules/@noble/hashes/blake1.d.ts +0 -106
  365. package/node_modules/@noble/hashes/blake1.d.ts.map +0 -1
  366. package/node_modules/@noble/hashes/blake1.js +0 -459
  367. package/node_modules/@noble/hashes/blake1.js.map +0 -1
  368. package/node_modules/@noble/hashes/blake2.d.ts +0 -116
  369. package/node_modules/@noble/hashes/blake2.d.ts.map +0 -1
  370. package/node_modules/@noble/hashes/blake2.js +0 -420
  371. package/node_modules/@noble/hashes/blake2.js.map +0 -1
  372. package/node_modules/@noble/hashes/blake2b.d.ts +0 -11
  373. package/node_modules/@noble/hashes/blake2b.d.ts.map +0 -1
  374. package/node_modules/@noble/hashes/blake2b.js +0 -14
  375. package/node_modules/@noble/hashes/blake2b.js.map +0 -1
  376. package/node_modules/@noble/hashes/blake2s.d.ts +0 -20
  377. package/node_modules/@noble/hashes/blake2s.d.ts.map +0 -1
  378. package/node_modules/@noble/hashes/blake2s.js +0 -24
  379. package/node_modules/@noble/hashes/blake2s.js.map +0 -1
  380. package/node_modules/@noble/hashes/blake3.d.ts +0 -54
  381. package/node_modules/@noble/hashes/blake3.d.ts.map +0 -1
  382. package/node_modules/@noble/hashes/blake3.js +0 -255
  383. package/node_modules/@noble/hashes/blake3.js.map +0 -1
  384. package/node_modules/@noble/hashes/crypto.d.ts +0 -2
  385. package/node_modules/@noble/hashes/crypto.d.ts.map +0 -1
  386. package/node_modules/@noble/hashes/crypto.js +0 -5
  387. package/node_modules/@noble/hashes/crypto.js.map +0 -1
  388. package/node_modules/@noble/hashes/cryptoNode.d.ts +0 -2
  389. package/node_modules/@noble/hashes/cryptoNode.d.ts.map +0 -1
  390. package/node_modules/@noble/hashes/cryptoNode.js +0 -18
  391. package/node_modules/@noble/hashes/cryptoNode.js.map +0 -1
  392. package/node_modules/@noble/hashes/eskdf.d.ts +0 -47
  393. package/node_modules/@noble/hashes/eskdf.d.ts.map +0 -1
  394. package/node_modules/@noble/hashes/eskdf.js +0 -166
  395. package/node_modules/@noble/hashes/eskdf.js.map +0 -1
  396. package/node_modules/@noble/hashes/esm/_assert.d.ts +0 -17
  397. package/node_modules/@noble/hashes/esm/_assert.d.ts.map +0 -1
  398. package/node_modules/@noble/hashes/esm/_assert.js +0 -15
  399. package/node_modules/@noble/hashes/esm/_assert.js.map +0 -1
  400. package/node_modules/@noble/hashes/esm/_blake.d.ts +0 -14
  401. package/node_modules/@noble/hashes/esm/_blake.d.ts.map +0 -1
  402. package/node_modules/@noble/hashes/esm/_blake.js +0 -45
  403. package/node_modules/@noble/hashes/esm/_blake.js.map +0 -1
  404. package/node_modules/@noble/hashes/esm/_md.d.ts +0 -51
  405. package/node_modules/@noble/hashes/esm/_md.d.ts.map +0 -1
  406. package/node_modules/@noble/hashes/esm/_md.js +0 -155
  407. package/node_modules/@noble/hashes/esm/_md.js.map +0 -1
  408. package/node_modules/@noble/hashes/esm/_u64.d.ts +0 -55
  409. package/node_modules/@noble/hashes/esm/_u64.d.ts.map +0 -1
  410. package/node_modules/@noble/hashes/esm/_u64.js +0 -67
  411. package/node_modules/@noble/hashes/esm/_u64.js.map +0 -1
  412. package/node_modules/@noble/hashes/esm/argon2.d.ts +0 -32
  413. package/node_modules/@noble/hashes/esm/argon2.d.ts.map +0 -1
  414. package/node_modules/@noble/hashes/esm/argon2.js +0 -392
  415. package/node_modules/@noble/hashes/esm/argon2.js.map +0 -1
  416. package/node_modules/@noble/hashes/esm/blake1.d.ts +0 -106
  417. package/node_modules/@noble/hashes/esm/blake1.d.ts.map +0 -1
  418. package/node_modules/@noble/hashes/esm/blake1.js +0 -452
  419. package/node_modules/@noble/hashes/esm/blake1.js.map +0 -1
  420. package/node_modules/@noble/hashes/esm/blake2.d.ts +0 -116
  421. package/node_modules/@noble/hashes/esm/blake2.d.ts.map +0 -1
  422. package/node_modules/@noble/hashes/esm/blake2.js +0 -413
  423. package/node_modules/@noble/hashes/esm/blake2.js.map +0 -1
  424. package/node_modules/@noble/hashes/esm/blake2b.d.ts +0 -11
  425. package/node_modules/@noble/hashes/esm/blake2b.d.ts.map +0 -1
  426. package/node_modules/@noble/hashes/esm/blake2b.js +0 -11
  427. package/node_modules/@noble/hashes/esm/blake2b.js.map +0 -1
  428. package/node_modules/@noble/hashes/esm/blake2s.d.ts +0 -20
  429. package/node_modules/@noble/hashes/esm/blake2s.d.ts.map +0 -1
  430. package/node_modules/@noble/hashes/esm/blake2s.js +0 -21
  431. package/node_modules/@noble/hashes/esm/blake2s.js.map +0 -1
  432. package/node_modules/@noble/hashes/esm/blake3.d.ts +0 -54
  433. package/node_modules/@noble/hashes/esm/blake3.d.ts.map +0 -1
  434. package/node_modules/@noble/hashes/esm/blake3.js +0 -251
  435. package/node_modules/@noble/hashes/esm/blake3.js.map +0 -1
  436. package/node_modules/@noble/hashes/esm/crypto.d.ts +0 -2
  437. package/node_modules/@noble/hashes/esm/crypto.d.ts.map +0 -1
  438. package/node_modules/@noble/hashes/esm/crypto.js +0 -2
  439. package/node_modules/@noble/hashes/esm/crypto.js.map +0 -1
  440. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts +0 -2
  441. package/node_modules/@noble/hashes/esm/cryptoNode.d.ts.map +0 -1
  442. package/node_modules/@noble/hashes/esm/cryptoNode.js +0 -15
  443. package/node_modules/@noble/hashes/esm/cryptoNode.js.map +0 -1
  444. package/node_modules/@noble/hashes/esm/eskdf.d.ts +0 -47
  445. package/node_modules/@noble/hashes/esm/eskdf.d.ts.map +0 -1
  446. package/node_modules/@noble/hashes/esm/eskdf.js +0 -160
  447. package/node_modules/@noble/hashes/esm/eskdf.js.map +0 -1
  448. package/node_modules/@noble/hashes/esm/hkdf.d.ts +0 -36
  449. package/node_modules/@noble/hashes/esm/hkdf.d.ts.map +0 -1
  450. package/node_modules/@noble/hashes/esm/hkdf.js +0 -82
  451. package/node_modules/@noble/hashes/esm/hkdf.js.map +0 -1
  452. package/node_modules/@noble/hashes/esm/hmac.d.ts +0 -35
  453. package/node_modules/@noble/hashes/esm/hmac.d.ts.map +0 -1
  454. package/node_modules/@noble/hashes/esm/hmac.js +0 -86
  455. package/node_modules/@noble/hashes/esm/hmac.js.map +0 -1
  456. package/node_modules/@noble/hashes/esm/index.d.ts +0 -2
  457. package/node_modules/@noble/hashes/esm/index.d.ts.map +0 -1
  458. package/node_modules/@noble/hashes/esm/index.js +0 -33
  459. package/node_modules/@noble/hashes/esm/index.js.map +0 -1
  460. package/node_modules/@noble/hashes/esm/legacy.d.ts +0 -71
  461. package/node_modules/@noble/hashes/esm/legacy.d.ts.map +0 -1
  462. package/node_modules/@noble/hashes/esm/legacy.js +0 -281
  463. package/node_modules/@noble/hashes/esm/legacy.js.map +0 -1
  464. package/node_modules/@noble/hashes/esm/package.json +0 -10
  465. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts +0 -23
  466. package/node_modules/@noble/hashes/esm/pbkdf2.d.ts.map +0 -1
  467. package/node_modules/@noble/hashes/esm/pbkdf2.js +0 -97
  468. package/node_modules/@noble/hashes/esm/pbkdf2.js.map +0 -1
  469. package/node_modules/@noble/hashes/esm/ripemd160.d.ts +0 -13
  470. package/node_modules/@noble/hashes/esm/ripemd160.d.ts.map +0 -1
  471. package/node_modules/@noble/hashes/esm/ripemd160.js +0 -13
  472. package/node_modules/@noble/hashes/esm/ripemd160.js.map +0 -1
  473. package/node_modules/@noble/hashes/esm/scrypt.d.ts +0 -34
  474. package/node_modules/@noble/hashes/esm/scrypt.d.ts.map +0 -1
  475. package/node_modules/@noble/hashes/esm/scrypt.js +0 -228
  476. package/node_modules/@noble/hashes/esm/scrypt.js.map +0 -1
  477. package/node_modules/@noble/hashes/esm/sha1.d.ts +0 -11
  478. package/node_modules/@noble/hashes/esm/sha1.d.ts.map +0 -1
  479. package/node_modules/@noble/hashes/esm/sha1.js +0 -11
  480. package/node_modules/@noble/hashes/esm/sha1.js.map +0 -1
  481. package/node_modules/@noble/hashes/esm/sha2.d.ts +0 -159
  482. package/node_modules/@noble/hashes/esm/sha2.d.ts.map +0 -1
  483. package/node_modules/@noble/hashes/esm/sha2.js +0 -375
  484. package/node_modules/@noble/hashes/esm/sha2.js.map +0 -1
  485. package/node_modules/@noble/hashes/esm/sha256.d.ts +0 -20
  486. package/node_modules/@noble/hashes/esm/sha256.d.ts.map +0 -1
  487. package/node_modules/@noble/hashes/esm/sha256.js +0 -20
  488. package/node_modules/@noble/hashes/esm/sha256.js.map +0 -1
  489. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts +0 -142
  490. package/node_modules/@noble/hashes/esm/sha3-addons.d.ts.map +0 -1
  491. package/node_modules/@noble/hashes/esm/sha3-addons.js +0 -393
  492. package/node_modules/@noble/hashes/esm/sha3-addons.js.map +0 -1
  493. package/node_modules/@noble/hashes/esm/sha3.d.ts +0 -53
  494. package/node_modules/@noble/hashes/esm/sha3.d.ts.map +0 -1
  495. package/node_modules/@noble/hashes/esm/sha3.js +0 -234
  496. package/node_modules/@noble/hashes/esm/sha3.js.map +0 -1
  497. package/node_modules/@noble/hashes/esm/sha512.d.ts +0 -26
  498. package/node_modules/@noble/hashes/esm/sha512.d.ts.map +0 -1
  499. package/node_modules/@noble/hashes/esm/sha512.js +0 -26
  500. package/node_modules/@noble/hashes/esm/sha512.js.map +0 -1
  501. package/node_modules/@noble/hashes/esm/utils.d.ts +0 -161
  502. package/node_modules/@noble/hashes/esm/utils.d.ts.map +0 -1
  503. package/node_modules/@noble/hashes/esm/utils.js +0 -281
  504. package/node_modules/@noble/hashes/esm/utils.js.map +0 -1
  505. package/node_modules/@noble/hashes/hkdf.d.ts +0 -36
  506. package/node_modules/@noble/hashes/hkdf.d.ts.map +0 -1
  507. package/node_modules/@noble/hashes/hkdf.js +0 -88
  508. package/node_modules/@noble/hashes/hkdf.js.map +0 -1
  509. package/node_modules/@noble/hashes/hmac.d.ts +0 -35
  510. package/node_modules/@noble/hashes/hmac.d.ts.map +0 -1
  511. package/node_modules/@noble/hashes/hmac.js +0 -91
  512. package/node_modules/@noble/hashes/hmac.js.map +0 -1
  513. package/node_modules/@noble/hashes/index.d.ts +0 -1
  514. package/node_modules/@noble/hashes/index.d.ts.map +0 -1
  515. package/node_modules/@noble/hashes/index.js +0 -33
  516. package/node_modules/@noble/hashes/index.js.map +0 -1
  517. package/node_modules/@noble/hashes/legacy.d.ts +0 -71
  518. package/node_modules/@noble/hashes/legacy.d.ts.map +0 -1
  519. package/node_modules/@noble/hashes/legacy.js +0 -287
  520. package/node_modules/@noble/hashes/legacy.js.map +0 -1
  521. package/node_modules/@noble/hashes/package.json +0 -266
  522. package/node_modules/@noble/hashes/pbkdf2.d.ts +0 -23
  523. package/node_modules/@noble/hashes/pbkdf2.d.ts.map +0 -1
  524. package/node_modules/@noble/hashes/pbkdf2.js +0 -101
  525. package/node_modules/@noble/hashes/pbkdf2.js.map +0 -1
  526. package/node_modules/@noble/hashes/ripemd160.d.ts +0 -13
  527. package/node_modules/@noble/hashes/ripemd160.d.ts.map +0 -1
  528. package/node_modules/@noble/hashes/ripemd160.js +0 -16
  529. package/node_modules/@noble/hashes/ripemd160.js.map +0 -1
  530. package/node_modules/@noble/hashes/scrypt.d.ts +0 -34
  531. package/node_modules/@noble/hashes/scrypt.d.ts.map +0 -1
  532. package/node_modules/@noble/hashes/scrypt.js +0 -232
  533. package/node_modules/@noble/hashes/scrypt.js.map +0 -1
  534. package/node_modules/@noble/hashes/sha1.d.ts +0 -11
  535. package/node_modules/@noble/hashes/sha1.d.ts.map +0 -1
  536. package/node_modules/@noble/hashes/sha1.js +0 -14
  537. package/node_modules/@noble/hashes/sha1.js.map +0 -1
  538. package/node_modules/@noble/hashes/sha2.d.ts +0 -159
  539. package/node_modules/@noble/hashes/sha2.d.ts.map +0 -1
  540. package/node_modules/@noble/hashes/sha2.js +0 -384
  541. package/node_modules/@noble/hashes/sha2.js.map +0 -1
  542. package/node_modules/@noble/hashes/sha256.d.ts +0 -20
  543. package/node_modules/@noble/hashes/sha256.d.ts.map +0 -1
  544. package/node_modules/@noble/hashes/sha256.js +0 -23
  545. package/node_modules/@noble/hashes/sha256.js.map +0 -1
  546. package/node_modules/@noble/hashes/sha3-addons.d.ts +0 -142
  547. package/node_modules/@noble/hashes/sha3-addons.d.ts.map +0 -1
  548. package/node_modules/@noble/hashes/sha3-addons.js +0 -402
  549. package/node_modules/@noble/hashes/sha3-addons.js.map +0 -1
  550. package/node_modules/@noble/hashes/sha3.d.ts +0 -53
  551. package/node_modules/@noble/hashes/sha3.d.ts.map +0 -1
  552. package/node_modules/@noble/hashes/sha3.js +0 -239
  553. package/node_modules/@noble/hashes/sha3.js.map +0 -1
  554. package/node_modules/@noble/hashes/sha512.d.ts +0 -26
  555. package/node_modules/@noble/hashes/sha512.d.ts.map +0 -1
  556. package/node_modules/@noble/hashes/sha512.js +0 -29
  557. package/node_modules/@noble/hashes/sha512.js.map +0 -1
  558. package/node_modules/@noble/hashes/src/_assert.ts +0 -22
  559. package/node_modules/@noble/hashes/src/_blake.ts +0 -50
  560. package/node_modules/@noble/hashes/src/_md.ts +0 -176
  561. package/node_modules/@noble/hashes/src/_u64.ts +0 -91
  562. package/node_modules/@noble/hashes/src/argon2.ts +0 -497
  563. package/node_modules/@noble/hashes/src/blake1.ts +0 -534
  564. package/node_modules/@noble/hashes/src/blake2.ts +0 -486
  565. package/node_modules/@noble/hashes/src/blake2b.ts +0 -10
  566. package/node_modules/@noble/hashes/src/blake2s.ts +0 -20
  567. package/node_modules/@noble/hashes/src/blake3.ts +0 -272
  568. package/node_modules/@noble/hashes/src/crypto.ts +0 -9
  569. package/node_modules/@noble/hashes/src/cryptoNode.ts +0 -15
  570. package/node_modules/@noble/hashes/src/eskdf.ts +0 -187
  571. package/node_modules/@noble/hashes/src/hkdf.ts +0 -88
  572. package/node_modules/@noble/hashes/src/hmac.ts +0 -94
  573. package/node_modules/@noble/hashes/src/index.ts +0 -31
  574. package/node_modules/@noble/hashes/src/legacy.ts +0 -293
  575. package/node_modules/@noble/hashes/src/pbkdf2.ts +0 -122
  576. package/node_modules/@noble/hashes/src/ripemd160.ts +0 -12
  577. package/node_modules/@noble/hashes/src/scrypt.ts +0 -257
  578. package/node_modules/@noble/hashes/src/sha1.ts +0 -10
  579. package/node_modules/@noble/hashes/src/sha2.ts +0 -402
  580. package/node_modules/@noble/hashes/src/sha256.ts +0 -24
  581. package/node_modules/@noble/hashes/src/sha3-addons.ts +0 -499
  582. package/node_modules/@noble/hashes/src/sha3.ts +0 -258
  583. package/node_modules/@noble/hashes/src/sha512.ts +0 -34
  584. package/node_modules/@noble/hashes/src/utils.ts +0 -395
  585. package/node_modules/@noble/hashes/utils.d.ts +0 -161
  586. package/node_modules/@noble/hashes/utils.d.ts.map +0 -1
  587. package/node_modules/@noble/hashes/utils.js +0 -313
  588. package/node_modules/@noble/hashes/utils.js.map +0 -1
@@ -1,17 +1,5 @@
1
- /**
2
- * Minimum valid FEC interleave depth in milliseconds, per spec §5.1
3
- * (the interactive/gaming floor; broadcast/ATSC 3.0 ranges up to 10000ms).
4
- *
5
- * A protocol constant, NOT a tuning knob. Values below this floor are almost
6
- * certainly a raw frame count (e.g. `2`) that a caller passed when no framerate
7
- * was available to convert to ms (common on audio tracks). Feeding such a value
8
- * into computeFecTimeout treats it as `2ms`, yielding a near-zero FEC block
9
- * timeout that abandons startup FEC blocks before their repair symbols arrive
10
- * (measured: moqtail startupFailed=4, shaka dropped=5.8% on startup). Timing
11
- * derivations MUST reject sub-floor values and fall back to the established
12
- * timeout instead of collapsing.
13
- */
14
- export declare const MIN_INTERLEAVE_DEPTH_MS = 33;
1
+ /** Smallest positive catalog elapsed span; zero/absence means no elapsed deadline. */
2
+ export declare const MIN_INTERLEAVE_DEPTH_MS = 1;
15
3
  /** Why FEC is not protecting this session. */
16
4
  export type FecInactiveReason = 'no-catalog-fec' | 'reliable-moq' | 'no-multicast-bridge';
17
5
  /** Every inactive reason, in resolution order. */
@@ -66,8 +54,10 @@ export interface FecStateEvidence<S extends object> {
66
54
  * Whether one catalog `fec` block (a hang rendition's `fec` or an MSF/CMSF
67
55
  * track's `fec`) declares an FEC instance a receiver can configure: source
68
56
  * symbols per block (K) and a symbol size (T) are both positive integers, the
69
- * algorithm is not `none`, and `blockProfile` names a known block profile
70
- * (BLO-35597). Total: it never throws, and answers false for any other value.
57
+ * algorithm is not `none`, `blockProfile` names the implemented profile
58
+ * `iso-ssbg1-v1` (BLO-35597), and the block carries that profile's usable
59
+ * span field (`isoBlockSpanUsable`); zero/absence still permits in-band FEC. Total: it never throws, and answers false for
60
+ * any other value.
71
61
  *
72
62
  * `resolveCatalogTrackFec` reads this predicate, so the players' parameters and
73
63
  * this state never disagree. Of the blocks this rejects, it returns no FEC
@@ -81,14 +71,18 @@ export interface FecStateEvidence<S extends object> {
81
71
  * the track's repair subscription.
82
72
  *
83
73
  * The block profile is part of this predicate because the profile selects the
84
- * block math and is never defaulted: without a known one no receiver can
85
- * configure the instance. `resolveCatalogTrackFec` resolves such a track to no
86
- * FEC with a typed `fecUnavailable` reason (`track_fec_block_profile`), scoped
87
- * to that track, so its parameters and this state still agree. The CMSF
88
- * conformance validator (`validateCmsfMmtpCatalog`) still rejects the catalog
89
- * with `track_fec_block_profile`. An `iso-ssbg1-v1` instance without a usable
90
- * block span (`isoBlockSpanUsable`) is likewise no FEC for its track alone,
91
- * with `fecUnavailable` code `track_fec` on its `interleaveDepthMs`.
74
+ * block math and is never defaulted: draft-ramadan-moq-fec §5.1 defines one,
75
+ * `iso-ssbg1-v1`, and a receiver MUST NOT apply FEC under any other token. The
76
+ * retired `k-padded-v1` is simply not a known profile. `resolveCatalogTrackFec`
77
+ * and `MulticastManager.configureMultiTrack` resolve such a track to no FEC
78
+ * with a typed `TrackFecUnavailable` reason (`catalogFecUnavailable`: code
79
+ * `track_fec_block_profile_unimplemented` for an unimplemented token,
80
+ * `track_fec_block_profile` for an absent one), scoped to that track, so their
81
+ * parameters and this state still agree. The CMSF conformance validator
82
+ * (`validateCmsfMmtpCatalog`) rejects the catalog with the same code. An
83
+ * `iso-ssbg1-v1` instance with an invalid span is likewise unavailable for its
84
+ * track alone. Zero or absence supplies no elapsed deadline and uses bounded
85
+ * receiver-local state while decoding in-band FEC.
92
86
  *
93
87
  * The repair-contract resolvers (`resolveRepairLayers`) gate a `depends` target
94
88
  * on `algorithm` alone and never read a source's `symbolSize`. They check its
@@ -97,13 +91,7 @@ export interface FecStateEvidence<S extends object> {
97
91
  * an overlay beyond layer 0.
98
92
  */
99
93
  export declare function catalogDeclaresFec(fec: unknown): boolean;
100
- /**
101
- * Whether a `fec` block that names `iso-ssbg1-v1` carries the block span its
102
- * block location is bounded by: `interleaveDepthMs`, a finite number of
103
- * milliseconds no smaller than `MIN_INTERLEAVE_DEPTH_MS`. Without it a receiver
104
- * cannot bound the sources it holds before a repair states their block, so the
105
- * instance cannot be configured (BLO-35597). Any other block needs no span here.
106
- */
94
+ /** Validate the optional elapsed span without requiring one for in-band FEC. */
107
95
  export declare function isoBlockSpanUsable(fec: unknown): boolean;
108
96
  /**
109
97
  * Resolve a player's FEC state. First match wins:
@@ -1 +1 @@
1
- {"version":3,"file":"fec-state.d.ts","sourceRoot":"","sources":["../src/fec-state.ts"],"names":[],"mappings":"AAiBA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,KAAK,CAAA;AAEzC,8CAA8C;AAC9C,MAAM,MAAM,iBAAiB,GAAG,gBAAgB,GAAG,cAAc,GAAG,qBAAqB,CAAA;AAEzF,kDAAkD;AAClD,eAAO,MAAM,oBAAoB,EAAE,SAAS,iBAAiB,EAIlD,CAAA;AAEX,4DAA4D;AAC5D,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,UAAU,GAAG,gBAAgB,CAAA;AAIvE,kFAAkF;AAClF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;IAC1B,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAA;CACnC;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAA;CAAE,CAAA;AAE/E,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,MAAM,IAAI,cAAc,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAAA;AAE7E,sEAAsE;AACtE,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;AAEhD,uDAAuD;AACvD,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,MAAM;IAChD;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS,CAAA;IACpC;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;IAC5B,gDAAgD;IAChD,QAAQ,CAAC,eAAe,EAAE,kBAAkB,CAAA;IAC5C,gFAAgF;IAChF,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;IACjC;;;OAGG;IACH,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAA;CACrC;AAkBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CASxD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAKxD;AASD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;AACrH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,aAAa,CAAC,CAAA"}
1
+ {"version":3,"file":"fec-state.d.ts","sourceRoot":"","sources":["../src/fec-state.ts"],"names":[],"mappings":"AAiBA,sFAAsF;AACtF,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC,8CAA8C;AAC9C,MAAM,MAAM,iBAAiB,GAAG,gBAAgB,GAAG,cAAc,GAAG,qBAAqB,CAAA;AAEzF,kDAAkD;AAClD,eAAO,MAAM,oBAAoB,EAAE,SAAS,iBAAiB,EAIlD,CAAA;AAEX,4DAA4D;AAC5D,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,UAAU,GAAG,gBAAgB,CAAA;AAIvE,kFAAkF;AAClF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;IAC1B,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAA;CACnC;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAA;CAAE,CAAA;AAE/E,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,MAAM,IAAI,cAAc,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAAA;AAE7E,sEAAsE;AACtE,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;AAEhD,uDAAuD;AACvD,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,MAAM;IAChD;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS,CAAA;IACpC;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;IAC5B,gDAAgD;IAChD,QAAQ,CAAC,eAAe,EAAE,kBAAkB,CAAA;IAC5C,gFAAgF;IAChF,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;IACjC;;;OAGG;IACH,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAA;CACrC;AAkBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CASxD;AAED,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAKxD;AASD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;AACrH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,aAAa,CAAC,CAAA"}
package/dist/fec-state.js CHANGED
@@ -15,15 +15,14 @@ var NTP_SHORT_HALF_RANGE = 2 ** (NTP_SHORT_BITS - 1);
15
15
 
16
16
  // ../mmt-base/dist/fec-block-profile.js
17
17
  var FEC_BLOCK_PROFILES = Object.freeze([
18
- "k-padded-v1",
19
18
  "iso-ssbg1-v1"
20
19
  ]);
21
20
  function isFecBlockProfile(value) {
22
- return value === "k-padded-v1" || value === "iso-ssbg1-v1";
21
+ return value === "iso-ssbg1-v1";
23
22
  }
24
23
 
25
24
  // src/fec-state.ts
26
- var MIN_INTERLEAVE_DEPTH_MS = 33;
25
+ var MIN_INTERLEAVE_DEPTH_MS = 1;
27
26
  var FEC_INACTIVE_REASONS = Object.freeze([
28
27
  "no-catalog-fec",
29
28
  "reliable-moq",
@@ -50,7 +49,7 @@ function isoBlockSpanUsable(fec) {
50
49
  const block = recordOf(fec);
51
50
  if (block === void 0 || block["blockProfile"] !== "iso-ssbg1-v1") return true;
52
51
  const ms = block["interleaveDepthMs"];
53
- return typeof ms === "number" && Number.isFinite(ms) && ms >= MIN_INTERLEAVE_DEPTH_MS;
52
+ return ms === void 0 || typeof ms === "number" && Number.isSafeInteger(ms) && ms >= 0;
54
53
  }
55
54
  function requireBoolean(value, field) {
56
55
  if (typeof value !== "boolean") {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../mmt-alta-parse/src/index.ts", "../../mmt-base/src/mmtp.ts", "../../mmt-base/src/ntp-short.ts", "../../mmt-base/src/fec-block-profile.ts", "../src/fec-state.ts"],
4
- "sourcesContent": ["/**\n * Zero-dependency helper for extracting the detached ALTA authenticator trailer\n * from an MMTP source packet.\n *\n * Lives in its own leaf package so both `@blockcast/mmt-container` (wire-path\n * parsing) and `@blockcast/fec-worker` (FEC-recovered-source parsing) can share\n * one implementation without creating a package cycle between them.\n *\n * Spec: ISO/IEC 23008-1:2023 + draft-ramadan-moq-mmt \u00A712 (detached trailer, Path 2).\n */\n\n// --- Private MMTP/MPU/MFU constants (ISO/IEC 23008-1 \u00A79.2.2\u20139.2.3) ---\n// Duplicated here as immutable spec values so this package stays a leaf with\n// zero `@blockcast/*` dependencies. Re-deriving from `mmt-container` would\n// reintroduce the package cycle this split exists to avoid.\n\nconst MMTP_HEADER_SIZE = 12\nconst MPU_HEADER_SIZE = 8\nconst MPU_LENGTH_FIELD_SIZE = 2\nconst MPU_HEADER_REMAINDER_SIZE = MPU_HEADER_SIZE - MPU_LENGTH_FIELD_SIZE\nconst TIMED_MFU_DU_HEADER_SIZE = 14\nconst NONTIMED_MFU_DU_HEADER_SIZE = 4\nconst PACKET_TYPE_MPU = 0x00\n\n// --- Public ALTA trailer constants ---\n\n/**\n * MMTP header-extension ext_type assigned to the detached ALTA authenticator\n * trailer (Path 2 / 08-07). Carried in an `[ext_type][ext_length][auth]`\n * block appended after the MPU's declared payload bytes, before the 4-byte\n * Source FEC Payload ID. MMTP X-bit stays 0 \u2014 non-ALTA MMTP receivers treat\n * the trailer as bytes past `payload_length` and skip it (per ISO 23008-1\n * \u00A79.2.2 \"may be discarded without impacting correct processing\").\n */\nexport const MMTP_EXT_TYPE_ALTA_AUTH = 0xf0a1\n\n/** Fixed size of the trailer's `[ext_type][ext_length]` prefix (2 + 2 BE). */\nexport const MMTP_EXT_TRAILER_HEADER_SIZE = 4\n\n/** Result of extracting the detached ALTA authenticator from an MMTP packet. */\nexport interface AltaTrailer {\n /** Authenticator bytes (no ext_type/ext_length prefix, no trailing padding). */\n auth: Uint8Array\n /**\n * Wire-packet byte offset where the signed payload ends, i.e. where the\n * trailer begins. Caller uses this to produce\n * `payload = wirePacket.subarray(0, signedEnd)` for verify_detached.\n */\n signedEnd: number\n}\n\n/**\n * Extract the detached ALTA authenticator trailer from an MMTP source packet.\n *\n * Wire layout:\n * `[MMTP hdr][length(2)][remaining MPU hdr(6)][DU?][media][TRAILER][SS_ID?]`\n * `payload_length = 6 + DU bytes + media bytes`\n *\n * Returns `null` when:\n * - the packet is too small / malformed,\n * - `payload_length` cannot contain the remaining six MPU-header bytes,\n * - the declared payload overruns the packet,\n * - no bytes remain between `payload_length` and the SS_ID,\n * - the trailer's `ext_type` isn't {@link MMTP_EXT_TYPE_ALTA_AUTH},\n * - the declared `ext_length` would extend past the SS_ID boundary.\n *\n * Non-MPU packets (repair, signaling) always return `null`.\n *\n * @param options.skipFecId When true, do NOT reserve 4 trailing bytes for the\n * Source FEC Payload ID. Use on FEC-recovered symbols where the PID has\n * already been stripped upstream.\n */\nexport function extractAltaTrailer(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n): AltaTrailer | null {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) return null\n // ISO/IEC 23008-1:2023 Figure 8 places V=0 packet_type in the low six bits.\n const packetType = mmtpPacket[1]! & 0x3f\n if (packetType !== PACKET_TYPE_MPU) return null\n\n const fecType = (mmtpPacket[0]! >> 3) & 0x03\n let headerLength = MMTP_HEADER_SIZE\n if (fecType < 2 && (mmtpPacket[0]! & 0x20) !== 0) headerLength += 4\n if (fecType < 2 && (mmtpPacket[0]! & 0x02) !== 0) {\n if (headerLength + 4 > mmtpPacket.length) return null\n const extensionLength = new DataView(\n mmtpPacket.buffer,\n mmtpPacket.byteOffset + headerLength + 2,\n 2,\n ).getUint16(0, false)\n headerLength += 4 + extensionLength\n }\n if (headerLength + MPU_HEADER_SIZE > mmtpPacket.length) return null\n\n const payload = mmtpPacket.subarray(headerLength)\n\n // Inline a minimal MPU-header read \u2014 only the fields extractAltaTrailer needs.\n // payloadLength: 16 BE at bytes 0-1.\n // Byte 2 packs FT(4) | T(1) | FI(2) | A(1).\n const payloadLength = (payload[0]! << 8) | payload[1]!\n const flagsByte = payload[2]!\n const fragmentType = (flagsByte >> 4) & 0x0f\n const timed = ((flagsByte >> 3) & 0x01) === 1\n const fragmentationIndicator = (flagsByte >> 1) & 0x03\n\n // Only MFU packets (FT=2) carry the detached trailer.\n if (fragmentType !== 2) return null\n\n // FI=0/1 structurally carries a DU header; FI=2/3 does not. Require at\n // least one media byte after that deterministic layout so the bytes at the\n // declared boundary cannot impersonate ALTA for a header-only fragment.\n const dataUnitHeaderSize = fragmentationIndicator <= 1\n ? (timed ? TIMED_MFU_DU_HEADER_SIZE : NONTIMED_MFU_DU_HEADER_SIZE)\n : 0\n if (payloadLength <= MPU_HEADER_REMAINDER_SIZE + dataUnitHeaderSize) return null\n\n if (fecType > 1) return null\n const hasFecId = !options?.skipFecId && fecType === 1\n const payloadEnd = hasFecId ? payload.length - 4 : payload.length\n const declaredEnd = MPU_LENGTH_FIELD_SIZE + payloadLength\n if (declaredEnd > payloadEnd) return null\n\n if (declaredEnd + MMTP_EXT_TRAILER_HEADER_SIZE > payloadEnd) return null\n const extType = (payload[declaredEnd]! << 8) | payload[declaredEnd + 1]!\n if (extType !== MMTP_EXT_TYPE_ALTA_AUTH) return null\n const extLen = (payload[declaredEnd + 2]! << 8) | payload[declaredEnd + 3]!\n\n const authStart = declaredEnd + MMTP_EXT_TRAILER_HEADER_SIZE\n if (authStart + extLen !== payloadEnd) return null\n\n return {\n auth: payload.subarray(authStart, payloadEnd),\n signedEnd: headerLength + declaredEnd,\n }\n}\n", "import {\n MMTP_EXT_TYPE_ALTA_AUTH as _LEAF_MMTP_EXT_TYPE_ALTA_AUTH,\n MMTP_EXT_TRAILER_HEADER_SIZE as _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE,\n extractAltaTrailer as _leafExtractAltaTrailer,\n type AltaTrailer as _LeafAltaTrailer,\n} from '@blockcast/mmt-alta-parse'\n\n/**\n * @mmt/container - MMTP Parser\n *\n * MMTP (MMT Protocol) packet parser based on ISO/IEC 23008-1:2023 Section 9.2\n *\n * MMTP header format (12 bytes):\n * - Byte 0: V(2) | C(1) | FEC_type(2) | r(1) | X(1) | R(1)\n * - Byte 1: reserved(2) | packet_type(6)\n * - Bytes 2-3: packet_id (big-endian)\n * - Bytes 4-7: timestamp (big-endian, 32-bit)\n * - Bytes 8-11: sequence_number (big-endian)\n *\n * FEC_type values:\n * - 0: No FEC / source packet\n * - 1: FEC mode 0 (source with FEC payload ID)\n * - 2: FEC repair packet mode 0\n * - 3: FEC repair packet mode 1\n *\n * @packageDocumentation\n */\n\nexport interface MMTPHeader {\n version: number\n /** Packet type: 0x00=MPU, 0x02=Signaling, 0x03=Repair */\n packetType: number\n packetId: number\n timestamp: bigint\n sequenceNumber: number\n payloadLength: number\n fecType: number\n /** C flag: header carries a 32-bit packet_counter. */\n packetCounterFlag: boolean\n packetCounter?: number\n /** X flag: header carries an extension header and value. */\n extensionFlag: boolean\n extension?: { type: number; data: Uint8Array }\n /** Byte offset at which the MMTP payload begins. */\n headerLength: number\n /** Random Access Point flag - indicates keyframe (ISO 23008-1, byte 0 bit 0) */\n rapFlag: boolean\n}\n\nexport interface MPUHeader {\n mpuSequenceNumber: number\n fragmentationIndicator: number // FI: 0=complete, 1=first, 2=middle, 3=last\n /** 8-bit MPU fragment_counter from byte 3. */\n fragmentCounter: number\n /** T flag: 1=timed media (14-byte MFU DU header), 0=non-timed (4-byte header) */\n timedFlag: number\n /** A flag: aggregated MPU payload. This strict profile requires A=0. */\n aggregationFlag: boolean\n /** MPU payload_length (bytes 0-1): bytes after the length field, including\n * the remaining six MPU-header bytes and all data-unit bytes. */\n payloadLength: number\n}\n\nexport interface MFUDuHeader {\n movieFragmentSequenceNumber: number\n sampleNumber: number\n offset: number\n priority: number\n dependencyCounter: number\n}\n\nexport interface MMTPPacket {\n header: MMTPHeader\n payload: Uint8Array\n}\n\nexport const MMTP_HEADER_SIZE = 12\nexport const MPU_HEADER_SIZE = 8\nexport const MFU_DU_HEADER_SIZE_TIMED = 14 // T=1: timed media (video/audio samples)\nexport const MFU_DU_HEADER_SIZE_NONTIMED = 4 // T=0: non-timed media (item_id only)\n\nconst MPU_LENGTH_FIELD_SIZE = 2\nconst MPU_HEADER_REMAINDER_SIZE = MPU_HEADER_SIZE - MPU_LENGTH_FIELD_SIZE\nconst SOURCE_FEC_PAYLOAD_ID_SIZE = 4\n\n// MMTP Packet Types (ISO/IEC 23008-1:2023 Section 9.2.2)\nexport const PACKET_TYPE_MPU = 0x00\nexport const PACKET_TYPE_SIGNALING = 0x02\nexport const PACKET_TYPE_REPAIR = 0x03\n\n// AL-FEC Signaling Message ID (ISO/IEC 23008-1:2023 Amendment 1:2025)\nexport const ALFEC_MESSAGE_ID = 0x0203\n\n/**\n * MMTP `packet_id` of the signaling flow, which carries PACKET_TYPE_SIGNALING\n * packets such as the AL-FEC message (ALFEC_MESSAGE_ID). The FFmpeg `moq_mmt`\n * muxer sends that message on this packet_id into the same multicast group as\n * the media, and moqcast-draft (PR #44) makes advertising it for a track a\n * MUST NOT. A track's packet_id is therefore 1..MMTP_PACKET_ID_MAX.\n */\nexport const MMTP_SIGNALING_PACKET_ID = 0\n\n/** Largest value of the 16-bit MMTP `packet_id` header field. */\nexport const MMTP_PACKET_ID_MAX = 0xffff\n\n// FEC Code IDs\nexport const FEC_CODE_XOR = 1\nexport const FEC_CODE_RAPTORQ = 2\n\n/**\n * Parse MMTP header from buffer\n */\nexport function parseMMTPHeader(buffer: Uint8Array): MMTPHeader {\n if (buffer.length < MMTP_HEADER_SIZE) {\n throw new Error(`MMTP packet too small: ${buffer.length} bytes`)\n }\n\n const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength)\n\n const version = (buffer[0]! >> 6) & 0x03\n if (version !== 0) {\n throw new Error(`Unsupported MMTP version: ${version}`)\n }\n\n // Extract FEC type from byte 0, bits 3-4\n const fecType = (buffer[0]! >> 3) & 0x03\n const packetCounterFlag = (buffer[0]! & 0x20) !== 0\n const extensionFlag = (buffer[0]! & 0x02) !== 0\n\n // Extract RAP flag from byte 0, bit 0 (R bit - Random Access Point)\n const rapFlag = (buffer[0]! & 0x01) === 1\n\n // ISO/IEC 23008-1:2023 Figure 8 (V=0) places reserved(2) before\n // packet_type(6), so packet_type occupies the low six bits of byte 1.\n const packetType = buffer[1]! & 0x3F\n\n let offset = 2\n\n // Packet ID (2 bytes, big-endian)\n const packetId = view.getUint16(offset, false)\n offset += 2\n\n // Timestamp (4 bytes, big-endian)\n const timestamp = BigInt(view.getUint32(offset, false))\n offset += 4\n\n // Sequence number (4 bytes, big-endian)\n const sequenceNumber = view.getUint32(offset, false)\n offset += 4\n\n let packetCounter: number | undefined\n if (packetCounterFlag) {\n if (offset + 4 > buffer.length) throw new Error('MMTP packet truncated in packet_counter')\n packetCounter = view.getUint32(offset, false)\n offset += 4\n }\n\n let extension: { type: number; data: Uint8Array } | undefined\n if (extensionFlag) {\n if (offset + 4 > buffer.length) throw new Error('MMTP packet truncated in header extension')\n const type = view.getUint16(offset, false)\n const length = view.getUint16(offset + 2, false)\n offset += 4\n if (offset + length > buffer.length) throw new Error('MMTP packet truncated in header extension value')\n extension = { type, data: buffer.subarray(offset, offset + length) }\n offset += length\n }\n\n return {\n version,\n packetType,\n packetId,\n timestamp,\n sequenceNumber,\n payloadLength: buffer.length - offset,\n fecType,\n packetCounterFlag,\n packetCounter,\n extensionFlag,\n extension,\n headerLength: offset,\n rapFlag,\n }\n}\n\n/**\n * The MMTP `packet_id` of `buffer`, or null exactly when parseMMTPHeader would\n * throw for it (short buffer, V != 0, truncated packet_counter or header\n * extension).\n *\n * This is the per-packet filter a receiver runs before any other work, so it\n * builds no header object, DataView or BigInt timestamp (BLO-36599). Use\n * parseMMTPHeader when any other header field is needed.\n */\nexport function readMMTPPacketId(buffer: Uint8Array): number | null {\n if (buffer.length < MMTP_HEADER_SIZE) return null\n const flags = buffer[0]!\n if (((flags >> 6) & 0x03) !== 0) return null\n let offset = MMTP_HEADER_SIZE\n if ((flags & 0x20) !== 0) {\n if (offset + 4 > buffer.length) return null\n offset += 4\n }\n if ((flags & 0x02) !== 0) {\n if (offset + 4 > buffer.length) return null\n const length = (buffer[offset + 2]! << 8) | buffer[offset + 3]!\n if (offset + 4 + length > buffer.length) return null\n }\n return (buffer[2]! << 8) | buffer[3]!\n}\n\n/**\n * Unwrap MMTP packet to extract payload\n */\nexport function unwrapMMTP(buffer: Uint8Array): MMTPPacket {\n const header = parseMMTPHeader(buffer)\n const payload = buffer.subarray(header.headerLength)\n\n return { header, payload }\n}\n\n/**\n * Parse MPU header from payload\n * ISO/IEC 23008-1:2023 Section 9.2.3.3\n *\n * MPU Header format (8 bytes) as written by FFmpeg/libmmt:\n * - Bytes 0-1: payload_length (16 bits, big-endian)\n * - Byte 2: FT(4) | T(1) | FI(2) | A(1)\n * - Byte 3: fragment_counter (8 bits)\n * - Bytes 4-7: MPU_sequence_number (32 bits, big-endian)\n */\nexport function parseMPUHeader(payload: Uint8Array): MPUHeader {\n if (payload.length < MPU_HEADER_SIZE) {\n throw new Error(`Payload too small for MPU header: ${payload.length} bytes`)\n }\n\n const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength)\n\n // Bytes 0-1: payload_length (16 bits, big-endian)\n const payloadLength = view.getUint16(0, false)\n\n // Byte 2: FT(4) | T(1) | FI(2) | A(1)\n const flagsByte = payload[2]!\n const timedFlag = (flagsByte >> 3) & 0x01\n const fragmentationIndicator = (flagsByte >> 1) & 0x03\n const aggregationFlag = (flagsByte & 0x01) !== 0\n\n // Byte 3: fragment_counter (8 bits). The high bit is part of the counter in\n // the current libmmt/FFmpeg writer ABI.\n const fragmentCounter = payload[3]!\n\n // Bytes 4-7: MPU_sequence_number (32 bits, big-endian)\n const mpuSequenceNumber = view.getUint32(4, false)\n\n return {\n mpuSequenceNumber,\n fragmentationIndicator,\n fragmentCounter,\n timedFlag,\n aggregationFlag,\n payloadLength,\n }\n}\n\nexport function parseTimedMfuDuHeader(payload: Uint8Array, offset: number): MFUDuHeader | null {\n if (offset < 0 || offset + MFU_DU_HEADER_SIZE_TIMED > payload.length) return null\n const view = new DataView(payload.buffer, payload.byteOffset + offset, MFU_DU_HEADER_SIZE_TIMED)\n return {\n movieFragmentSequenceNumber: view.getUint32(0, false),\n sampleNumber: view.getUint32(4, false),\n offset: view.getUint32(8, false),\n priority: payload[offset + 12]!,\n dependencyCounter: payload[offset + 13]!,\n }\n}\n\n/**\n * Why `extractMFUPayload` refused a packet.\n *\n * These identifiers are part of the public contract: callers surface them in\n * diagnostics, so renaming one is a breaking change.\n */\nexport type MFURejectionGate =\n | 'packet-shorter-than-mmtp-plus-mpu-header'\n | 'mmtp-header-unparseable'\n | 'not-an-mpu-packet'\n | 'payload-shorter-than-mpu-header'\n | 'aggregation-flag-set'\n | 'mpu-payload-length-below-header-remainder'\n | 'mpu-declared-end-exceeds-payload'\n | 'mfu-offset-at-or-past-declared-end'\n | 'source-fec-payload-id-width-mismatch'\n | 'declared-trailers-unresolved'\n\nexport interface MFURejection {\n gate: MFURejectionGate\n /** Human-readable detail, including the byte arithmetic that decided it. */\n detail: string\n}\n\ninterface MfuPayloadLayout {\n hasMfuDuHeader: boolean\n mfuOffset: number\n dataEnd: number\n}\n\nfunction resolveMfuPayloadLayout(\n mpu: MPUHeader,\n fragmentType: number,\n payload: Uint8Array,\n): { layout: MfuPayloadLayout } | { rejection: MFURejection } {\n if (mpu.payloadLength < MPU_HEADER_REMAINDER_SIZE) {\n return {\n rejection: {\n gate: 'mpu-payload-length-below-header-remainder',\n detail: `payload_length=${mpu.payloadLength} < ${MPU_HEADER_REMAINDER_SIZE}`,\n },\n }\n }\n\n // ISO/IEC 23008-1 defines payload_length from immediately after its own\n // two-byte field. It therefore includes the remaining six MPU-header bytes.\n const declaredEnd = MPU_LENGTH_FIELD_SIZE + mpu.payloadLength\n if (declaredEnd > payload.length) {\n return {\n rejection: {\n gate: 'mpu-declared-end-exceeds-payload',\n detail:\n `payload_length=${mpu.payloadLength} implies end=${declaredEnd}, ` +\n `payload is ${payload.length} bytes`,\n },\n }\n }\n\n const hasHeader = fragmentType === 2 && mpu.fragmentationIndicator <= 1\n const mfuHeaderSize = mpu.timedFlag ? MFU_DU_HEADER_SIZE_TIMED : MFU_DU_HEADER_SIZE_NONTIMED\n const mfuOffset = MPU_HEADER_SIZE + (hasHeader ? mfuHeaderSize : 0)\n if (mfuOffset >= declaredEnd) {\n return {\n rejection: {\n gate: 'mfu-offset-at-or-past-declared-end',\n detail: `mfuOffset=${mfuOffset} >= declaredEnd=${declaredEnd}`,\n },\n }\n }\n\n return {\n layout: {\n hasMfuDuHeader: hasHeader,\n mfuOffset,\n dataEnd: declaredEnd,\n },\n }\n}\n\nfunction isEmptyOrExactAltaTrailer(payload: Uint8Array, start: number, end: number): boolean {\n if (start === end) return true\n if (start < 0 || end > payload.length || start + _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE > end) {\n return false\n }\n\n const view = new DataView(\n payload.buffer,\n payload.byteOffset + start,\n end - start,\n )\n if (view.getUint16(0, false) !== _LEAF_MMTP_EXT_TYPE_ALTA_AUTH) return false\n\n const authLength = view.getUint16(2, false)\n return start + _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE + authLength === end\n}\n\ninterface DeclaredTrailerLayout {\n hasFecId: boolean\n}\n\nfunction resolveDeclaredTrailers(\n payload: Uint8Array,\n declaredEnd: number,\n fecType: number,\n options?: { skipFecId?: boolean },\n): DeclaredTrailerLayout | null {\n if (declaredEnd > payload.length) return null\n\n if (options?.skipFecId) {\n return isEmptyOrExactAltaTrailer(payload, declaredEnd, payload.length)\n ? { hasFecId: false }\n : null\n }\n\n if (fecType === 1) {\n const fecStart = payload.length - SOURCE_FEC_PAYLOAD_ID_SIZE\n if (fecStart < declaredEnd || !isEmptyOrExactAltaTrailer(payload, declaredEnd, fecStart)) {\n return null\n }\n return { hasFecId: true }\n }\n if (fecType !== 0) return null\n\n if (isEmptyOrExactAltaTrailer(payload, declaredEnd, payload.length)) {\n return { hasFecId: false }\n }\n\n return null\n}\n\n/**\n * Extract NAL unit payload from MMTP packet\n */\nexport function extractNALUnit(mmtpPacket: Uint8Array): Uint8Array {\n const extraction = extractMFUPayload(mmtpPacket)\n if (!extraction) throw new Error('Malformed MMTP MPU payload')\n return extraction.data\n}\n\n/** Result of extracting MFU payload from an MMTP packet */\nexport interface MFUExtraction {\n /** MMTP header fields */\n header: MMTPHeader\n /** MPU header fields */\n mpu: MPUHeader\n /** Fragment type (FT field, 4 bits): 0=metadata, 2=MFU data */\n fragmentType: number\n /** MFU payload data (video/audio bytes, headers stripped, FEC ID excluded) */\n data: Uint8Array\n /** Parsed timed MFU DU header when present, otherwise null */\n mfuDuHeader: MFUDuHeader | null\n /** Non-timed MFU Item_ID when the 4-byte DU header is present. */\n itemId: number | null\n /** Source FEC Payload ID if present (`FEC_type == 1`), or null */\n fecPayloadId: { ssId: number } | null\n}\n\n/**\n * Extract MFU payload from a raw MMTP packet.\n *\n * Handles all the byte arithmetic for:\n * - MMTP header (12 bytes)\n * - MPU sub-header (8 bytes, payloadLength field)\n * - MFU DU header (14 bytes timed / 4 bytes non-timed, only for FI \u2264 1)\n * - Source FEC Payload ID (4 bytes at end, only when `FEC_type == 1`)\n *\n * `payloadLength` is the ISO length-after-field value: six remaining MPU-header\n * bytes, then the MFU DU header when present, then media. ALTA and Source FEC\n * trailers are outside that declared span and must consume the exact suffix.\n *\n * @param mmtpPacket Raw MMTP packet (from multicast or MoQ)\n * @param options.skipFecId When true, do NOT subtract the 4-byte Source FEC Payload ID\n * from the payload boundary, even if fecType >= 1 in the MMTP header. Use this for\n * FEC-recovered symbols where the FEC PID was already stripped before WASM decoding\n * but the fecType bits in byte 0 still reflect the original packet (FEC Type=1 per\n * draft-ramadan-moq-mmt-00 \u00A73.1).\n * @returns Extracted MFU data + metadata, or null if packet is too small\n */\ninterface MfuPlan {\n header: MMTPHeader\n payload: Uint8Array\n mpu: MPUHeader\n fragmentType: number\n layout: MfuPayloadLayout\n trailerLayout: DeclaredTrailerLayout\n}\n\n/**\n * Single source of truth for whether a packet yields an MFU payload.\n *\n * `extractMFUPayload` and `explainMFURejection` both delegate here, so the\n * reason reported to operators can never drift from the decision the parser\n * actually made.\n */\nfunction planMfuExtraction(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n parsedHeader?: MMTPHeader,\n): { plan: MfuPlan } | { rejection: MFURejection } {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) {\n return {\n rejection: {\n gate: 'packet-shorter-than-mmtp-plus-mpu-header',\n detail: `${mmtpPacket.length} bytes < ${MMTP_HEADER_SIZE + MPU_HEADER_SIZE}`,\n },\n }\n }\n\n let header: MMTPHeader\n if (parsedHeader) {\n header = parsedHeader\n } else {\n try {\n header = parseMMTPHeader(mmtpPacket)\n } catch (error) {\n return {\n rejection: {\n gate: 'mmtp-header-unparseable',\n detail: error instanceof Error ? error.message : String(error),\n },\n }\n }\n }\n if (header.packetType !== PACKET_TYPE_MPU) {\n return {\n rejection: {\n gate: 'not-an-mpu-packet',\n detail: `packetType=0x${header.packetType.toString(16).padStart(2, '0')}`,\n },\n }\n }\n\n const payload = mmtpPacket.subarray(header.headerLength)\n if (payload.length < MPU_HEADER_SIZE) {\n return {\n rejection: {\n gate: 'payload-shorter-than-mpu-header',\n detail: `${payload.length} bytes < ${MPU_HEADER_SIZE}`,\n },\n }\n }\n\n const mpu = parseMPUHeader(payload)\n if (mpu.aggregationFlag) {\n return {\n rejection: { gate: 'aggregation-flag-set', detail: 'strict profile requires A=0' },\n }\n }\n\n // Fragment type from byte 2 upper nibble (not returned by parseMPUHeader)\n const fragmentType = (payload[2]! >> 4) & 0x0f\n\n const layoutResult = resolveMfuPayloadLayout(mpu, fragmentType, payload)\n if ('rejection' in layoutResult) return layoutResult\n const { layout } = layoutResult\n\n const trailerLayout = resolveDeclaredTrailers(\n payload,\n layout.dataEnd,\n header.fecType,\n options,\n )\n if (!trailerLayout) {\n // The most common cause is a producer whose Source FEC Payload ID width\n // disagrees with ISO/IEC 23008-1 \u00A7C.5.2. Name that explicitly rather than\n // leaving operators to infer it from a generic trailer error.\n if (!options?.skipFecId && header.fecType === 1) {\n const observed = payload.length - layout.dataEnd\n if (observed !== SOURCE_FEC_PAYLOAD_ID_SIZE) {\n return {\n rejection: {\n gate: 'source-fec-payload-id-width-mismatch',\n detail:\n `producer wrote a ${observed}-byte Source FEC Payload ID, ` +\n `ISO/IEC 23008-1 \u00A7C.5.2 requires ${SOURCE_FEC_PAYLOAD_ID_SIZE} ` +\n `(payload=${payload.length}, declaredEnd=${layout.dataEnd})`,\n },\n }\n }\n }\n return {\n rejection: {\n gate: 'declared-trailers-unresolved',\n detail:\n `trailer bytes [${layout.dataEnd}, ${payload.length}) are neither empty ` +\n `nor an exact ALTA trailer (fecType=${header.fecType})`,\n },\n }\n }\n\n return { plan: { header, payload, mpu, fragmentType, layout, trailerLayout } }\n}\n\n/**\n * Report why `extractMFUPayload` would refuse this packet, or null if it would\n * accept it.\n *\n * `extractMFUPayload` returns bare `null` on ten distinct conditions, which\n * makes a producer/consumer framing disagreement indistinguishable from a\n * publisher that never started \u2014 both render as silence. Use this wherever\n * that distinction matters: diagnostics, contract tests, and operator-facing\n * logs.\n */\nexport function explainMFURejection(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n): MFURejection | null {\n const result = planMfuExtraction(mmtpPacket, options)\n return 'rejection' in result ? result.rejection : null\n}\n\nexport function extractMFUPayload(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n): MFUExtraction | null {\n return extractPlannedMfu(mmtpPacket, planMfuExtraction(mmtpPacket, options))\n}\n\n/**\n * extractMFUPayload for a caller that already holds this packet's parsed MMTP\n * header, so the header is not parsed a second time (BLO-36599).\n *\n * `header` must be `parseMMTPHeader(mmtpPacket)` of this same packet; the\n * result is then identical to `extractMFUPayload(mmtpPacket, options)`.\n */\nexport function extractMFUPayloadWithHeader(\n mmtpPacket: Uint8Array,\n header: MMTPHeader,\n options?: { skipFecId?: boolean },\n): MFUExtraction | null {\n return extractPlannedMfu(mmtpPacket, planMfuExtraction(mmtpPacket, options, header))\n}\n\nfunction extractPlannedMfu(\n mmtpPacket: Uint8Array,\n result: { plan: MfuPlan } | { rejection: MFURejection },\n): MFUExtraction | null {\n if ('rejection' in result) return null\n const { header, payload, mpu, fragmentType, layout, trailerLayout } = result.plan\n\n const mfuDuHeaderPresent = layout.hasMfuDuHeader\n const mfuOffset = layout.mfuOffset\n const dataEnd = layout.dataEnd\n\n const mfuDuHeader =\n mfuDuHeaderPresent && mpu.timedFlag === 1\n ? parseTimedMfuDuHeader(payload, MPU_HEADER_SIZE)\n : null\n const itemId =\n mfuDuHeaderPresent && mpu.timedFlag === 0\n ? new DataView(payload.buffer, payload.byteOffset + MPU_HEADER_SIZE, 4).getUint32(0, false)\n : null\n\n const data = payload.subarray(mfuOffset, dataEnd)\n\n const fecPayloadId = trailerLayout.hasFecId\n ? {\n ssId: new DataView(\n mmtpPacket.buffer,\n mmtpPacket.byteOffset + mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE,\n SOURCE_FEC_PAYLOAD_ID_SIZE,\n ).getUint32(0, false),\n }\n : null\n\n return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId }\n}\n\n/**\n * Extract timestamp from MMTP packet\n */\nexport function extractTimestamp(mmtpPacket: Uint8Array): bigint {\n const { header } = unwrapMMTP(mmtpPacket)\n return header.timestamp\n}\n\n/**\n * Check if MMTP packet is an FEC repair packet\n * FEC_type 2 and 3 are repair packets\n */\nexport function isFecRepairPacket(mmtpPacket: Uint8Array): boolean {\n if (mmtpPacket.length < 1) return false\n const fecType = (mmtpPacket[0]! >> 3) & 0x03\n return fecType >= 2\n}\n\n/**\n * Extract Source FEC Payload ID (SS_ID) from the last 4 bytes of an MMTP\n * source packet per ISO/IEC 23008-1:2023 Section C.5.2 Figure C.10.\n *\n * SS_ID is a flat 32-bit monotonic counter (incremented by 1 per packet\n * for ssbg_mode0 and ssbg_mode1). Block membership and ESI use the\n * configured K; repair SS_Start identifies the first SS_ID of the block and\n * SSB_length records the authored source-symbol count:\n * SBN = floor(SS_ID / K), ESI = SS_ID % K\n * SS_Start = SBN * K (first SS_ID of the block)\n *\n * This matches FFmpeg moqenc_mmt.c, the drafts (draft-ramadan-moq-fec \u00A76.2),\n * and ATSC A/331 \u00A78.1.2.5 which defers to ISO 23008-1 Annex C.\n */\nexport function extractFecPayloadId(mmtpPacket: Uint8Array): { ssId: number } | null {\n const header = parseHeaderForSourceFecId(mmtpPacket)\n return header ? sourceFecPayloadIdOf(mmtpPacket, header) : null\n}\n\n/**\n * Return true when FEC_type=1 and the exact suffix after the ISO-declared MPU\n * payload contains a four-byte Source FEC Payload ID.\n */\nexport function hasSourceFecPayloadId(mmtpPacket: Uint8Array): boolean {\n const header = parseHeaderForSourceFecId(mmtpPacket)\n return header !== null && sourceFecPayloadIdOf(mmtpPacket, header) !== null\n}\n\nfunction parseHeaderForSourceFecId(mmtpPacket: Uint8Array): MMTPHeader | null {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) return null\n try {\n return parseMMTPHeader(mmtpPacket)\n } catch {\n return null\n }\n}\n\n/**\n * The Source FEC Payload ID of a packet whose MMTP header the caller already\n * parsed, or null when the packet carries none: present only when FEC_type=1\n * and the exact suffix after the ISO-declared MPU payload holds the four-byte\n * ID (ISO/IEC 23008-1 \u00A7C.5.2). Same answer as extractFecPayloadId, without a\n * second header parse (BLO-36599).\n *\n * `header` must be `parseMMTPHeader(mmtpPacket)` of this same packet.\n */\nexport function sourceFecPayloadIdOf(\n mmtpPacket: Uint8Array,\n header: MMTPHeader,\n): { ssId: number } | null {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) return null\n if (header.packetType !== PACKET_TYPE_MPU) return null\n\n const payload = mmtpPacket.subarray(header.headerLength)\n if (payload.length < MPU_HEADER_SIZE) return null\n const payloadLength = (payload[0]! << 8) | payload[1]!\n if (payloadLength < MPU_HEADER_REMAINDER_SIZE) return null\n const declaredEnd = MPU_LENGTH_FIELD_SIZE + payloadLength\n if (!resolveDeclaredTrailers(payload, declaredEnd, header.fecType)?.hasFecId) return null\n\n // Flat 4-byte SS_ID: encoder writes block_id * K + symbol_id\n const off = mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE\n return {\n ssId:\n ((mmtpPacket[off]! << 24) |\n (mmtpPacket[off + 1]! << 16) |\n (mmtpPacket[off + 2]! << 8) |\n mmtpPacket[off + 3]!) >>>\n 0,\n }\n}\n\n/**\n * MMTP header-extension ext_type assigned to the detached ALTA authenticator\n * trailer (Path 2 / 08-07). Carried in an `[ext_type][ext_length][auth]`\n * block appended after the MPU's declared payload bytes, before the 4-byte\n * Source FEC Payload ID. MMTP X-bit stays 0 \u2014 non-ALTA MMTP receivers treat\n * the trailer as bytes past `payload_length` and skip it (per ISO 23008-1\n * \u00A79.2.2 \"may be discarded without impacting correct processing\").\n */\n// Re-export the ALTA trailer helper from the zero-dep leaf package. The leaf\n// owns the implementation so container and FEC consumers share one parser\n// without creating a package cycle.\nexport const MMTP_EXT_TYPE_ALTA_AUTH = _LEAF_MMTP_EXT_TYPE_ALTA_AUTH\nexport const MMTP_EXT_TRAILER_HEADER_SIZE = _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE\nexport type AltaTrailer = _LeafAltaTrailer\nexport const extractAltaTrailer = _leafExtractAltaTrailer\n\n/** Size of Repair FEC Payload ID per ISO/IEC 23008-1 Section C.5.3 */\nexport const REPAIR_FEC_PID_SIZE = 13\n\n/**\n * Parse the 13-byte Repair FEC Payload ID after the complete dynamic MMTP header.\n * `parsedHeader`, when given, must be `parseMMTPHeader(mmtpPacket)` of this\n * same packet; it spares the second header parse (BLO-36599).\n */\nfunction readRepairFecPayloadIdFields(mmtpPacket: Uint8Array, parsedHeader?: MMTPHeader): {\n packetId: number; ssStart: number; rsbLength: number; rsId: number; ssbLength: number; dataOffset: number\n} | null {\n let header: MMTPHeader\n if (parsedHeader) {\n header = parsedHeader\n } else {\n try {\n header = parseMMTPHeader(mmtpPacket)\n } catch {\n return null\n }\n }\n if (header.packetType !== PACKET_TYPE_REPAIR || header.fecType !== 2) return null\n\n const off = header.headerLength\n if (mmtpPacket.length < off + REPAIR_FEC_PID_SIZE) return null\n const ssStart =\n ((mmtpPacket[off]! << 24) |\n (mmtpPacket[off + 1]! << 16) |\n (mmtpPacket[off + 2]! << 8) |\n mmtpPacket[off + 3]!) >>>\n 0 // 32 bits\n const rsbLength = (mmtpPacket[off + 4]! << 16) | (mmtpPacket[off + 5]! << 8) | mmtpPacket[off + 6]! // 24 bits\n const rsId = (mmtpPacket[off + 7]! << 16) | (mmtpPacket[off + 8]! << 8) | mmtpPacket[off + 9]! // 24 bits\n const ssbLength = (mmtpPacket[off + 10]! << 16) | (mmtpPacket[off + 11]! << 8) | mmtpPacket[off + 12]! // 24 bits\n return {\n packetId: header.packetId,\n ssStart,\n rsbLength,\n rsId,\n ssbLength,\n dataOffset: off + REPAIR_FEC_PID_SIZE,\n }\n}\n\n/**\n * Extract Repair FEC Payload ID from a raw-multicast MMTP repair packet.\n * Per ISO/IEC 23008-1:2023 Section C.5.3 (ssbg_mode0, one-stage, no FFSRP_TS):\n * [MMTP header][SS_Start:4][RSB_length:3][RS_ID:3][SSB_length:3][repair data]\n * OTI is delivered via AL-FEC signaling message (Table C.3), not per-packet.\n *\n * Byte 1 is reserved(2)|packet_type(6) for V=0, so repair type 0x03 is 0x03.\n * Pre-migration high-bits bytes (0x0c) are rejected \u2014 no compatibility decode.\n */\nexport function extractRepairFecPayloadId(mmtpPacket: Uint8Array): {\n ssStart: number; rsbLength: number; rsId: number; ssbLength: number\n} | null {\n const fields = readRepairFecPayloadIdFields(mmtpPacket)\n if (!fields) return null\n return {\n ssStart: fields.ssStart,\n rsbLength: fields.rsbLength,\n rsId: fields.rsId,\n ssbLength: fields.ssbLength,\n }\n}\n\n/** Parsed MoQ repair frame per ISO/IEC 23008-1 Section C.4.3 + C.5.3 */\nexport interface MoqRepairFrame {\n /** MMTP packetId (1=video, 2=audio) \u2014 for routing to correct FEC decoder */\n packetId: number\n /** SS_Start (32 bits): SS_ID of first source symbol in block */\n ssStart: number\n /** RSB_length (24 bits): number of repair symbols (P) */\n rsbLength: number\n /** RS_ID (24 bits): repair symbol index (0-based) */\n rsId: number\n /** SSB_length (24 bits): authored source-symbol count (1..=configured K) */\n ssbLength: number\n /** Repair symbol data after the dynamic MMTP header and 13-byte FEC payload ID. */\n symbolData: Uint8Array\n}\n\n/**\n * Parse a MoQ repair frame (ISO/IEC 23008-1 Annex C format).\n *\n * Wire format per ISO 23008-1 \u00A7C.4.3 + \u00A7C.5.3:\n * MMTP Header: fixed 12 bytes plus C/X optional fields\n * then SS_Start (4 bytes) \u2192 block identifier\n * then RSB_length (3 bytes) \u2192 P\n * then RS_ID (3 bytes) \u2192 ESI = K + RS_ID\n * then SSB_length (3 bytes) \u2192 authored source-symbol count (1..=K)\n * then Repair Symbol Data (T bytes)\n *\n * @param frame - Raw MoQ repair frame bytes\n * @returns Parsed frame or null if invalid\n */\nexport function parseMoqRepairFrame(frame: Uint8Array): MoqRepairFrame | null {\n return moqRepairFrameFrom(frame, readRepairFecPayloadIdFields(frame))\n}\n\n/**\n * parseMoqRepairFrame for a caller that already holds this frame's parsed MMTP\n * header, so the header is not parsed a second time (BLO-36599). The router's\n * multicast repair branch uses it: one repair packet in every K + P otherwise\n * paid two header parses.\n *\n * `header` must be `parseMMTPHeader(frame)` of this same frame; the result is\n * then identical to `parseMoqRepairFrame(frame)`.\n */\nexport function parseMoqRepairFrameWithHeader(\n frame: Uint8Array,\n header: MMTPHeader,\n): MoqRepairFrame | null {\n return moqRepairFrameFrom(frame, readRepairFecPayloadIdFields(frame, header))\n}\n\nfunction moqRepairFrameFrom(\n frame: Uint8Array,\n repairId: ReturnType<typeof readRepairFecPayloadIdFields>,\n): MoqRepairFrame | null {\n if (!repairId) return null\n\n const symbolData = frame.subarray(repairId.dataOffset)\n if (\n repairId.rsbLength === 0 ||\n repairId.ssbLength === 0 ||\n repairId.rsId >= repairId.rsbLength ||\n symbolData.length === 0\n ) return null\n\n return {\n packetId: repairId.packetId,\n ssStart: repairId.ssStart,\n rsbLength: repairId.rsbLength,\n rsId: repairId.rsId,\n ssbLength: repairId.ssbLength,\n symbolData,\n }\n}\n\n// The signalling-message parsers (9.3.4.2 payload header, AL-FEC message,\n// PA/MPT) live in ./signalling.ts.\n", "/**\n * NTP short format (RFC 5905 \u00A76): an unsigned 32-bit, 16.16 fixed-point count\n * of seconds. The MMTP header `timestamp` field (ISO/IEC 23008-1 \u00A79.2.2)\n * carries each packet's UTC sending instant in this format, so the value wraps\n * to 0 every 2^16 s (~18.2 h).\n *\n * Plain numeric order on these values breaks at every wrap: a retained\n * `Math.max` stays at the pre-wrap value and every later instant compares as\n * older. Order send times only with {@link compareNtpShort}, which applies\n * RFC 1982 serial-number arithmetic over the field's width.\n */\n\n/** Width in bits of the MMTP `timestamp` field (NTP short). */\nexport const NTP_SHORT_BITS = 32\n\n/** Number of distinct NTP-short values; the field wraps to 0 here. */\nexport const NTP_SHORT_MODULUS = 2 ** NTP_SHORT_BITS\n\n/**\n * RFC 1982 \u00A73.2's `2^(SERIAL_BITS - 1)`. Two values whose forward distance is\n * below this are ordered; exactly this far apart they are not ordered at all.\n * Two instants must be less than this far apart (2^15 s, ~9.1 h of send time)\n * for the comparison to give their true order.\n */\nexport const NTP_SHORT_HALF_RANGE = 2 ** (NTP_SHORT_BITS - 1)\n\n/**\n * Serial order of one NTP-short value relative to another.\n *\n * `'unordered'` is RFC 1982's undefined case: the values are exactly\n * {@link NTP_SHORT_HALF_RANGE} apart, so neither precedes the other. It is\n * never folded into `'before'` or `'after'`; each caller decides what an\n * unorderable pair means for it.\n */\nexport type NtpShortOrder = 'before' | 'equal' | 'after' | 'unordered'\n\n/** True when `value` is an NTP-short value: an integer in [0, NTP_SHORT_MODULUS). */\nexport function isNtpShort(value: number): boolean {\n return Number.isInteger(value) && value >= 0 && value < NTP_SHORT_MODULUS\n}\n\n/**\n * RFC 1982 serial comparison of NTP-short `a` against `b`: `'after'` when `a`\n * is the later instant, `'before'` when it is the earlier one.\n *\n * The answer is the true order only while the two instants are less than\n * {@link NTP_SHORT_HALF_RANGE} apart. Past that a 32-bit field cannot tell\n * later from earlier, and the result names whichever direction is nearer.\n * This is not a total order (it is not transitive across the whole space), so\n * it must never be used to sort.\n *\n * @throws {RangeError} if either operand is not an NTP-short value\n */\nexport function compareNtpShort(a: number, b: number): NtpShortOrder {\n if (!isNtpShort(a) || !isNtpShort(b)) {\n throw new RangeError(\n `NTP-short values must be integers in [0, 2^${NTP_SHORT_BITS}), got ${String(a)} and ${String(b)}`,\n )\n }\n if (a === b) return 'equal'\n // Forward distance from b to a, reduced into [1, NTP_SHORT_MODULUS). Both\n // operands are below 2^32, so every step is exact in a double.\n const forward = (a - b + NTP_SHORT_MODULUS) % NTP_SHORT_MODULUS\n if (forward < NTP_SHORT_HALF_RANGE) return 'after'\n if (forward > NTP_SHORT_HALF_RANGE) return 'before'\n return 'unordered'\n}\n", "/**\n * FEC block profile (BLO-35597, ISO/IEC 23008-1 Annex C).\n *\n * Every catalog `fec` object that declares an FEC instance names exactly one\n * block profile in `fec.blockProfile`. A receiver applies all of a profile's\n * rules or none of them, so the token is the one explicit per-stream\n * discriminator: it is never defaulted and never inferred from the wire.\n *\n * - `k-padded-v1` (legacy, Blockcast-private): a source symbol is the\n * protected MMTP packet zero-padded to T with no length field; every block\n * is a K-symbol RaptorQ block whose ESIs `SSB_length..K-1` are implicit\n * zero padding; `SS_ID = SBN*K + ESI`, so `SS_Start = SBN*K`; repair\n * ESI = K + RS_ID; the MoQ repair group id is the SBN.\n * - `iso-ssbg1-v1`: ssbg_mode1 (C.3.3): a source symbol is\n * `u16 length || protected packet || zero padding` to T; a block is a\n * K' = SSB_length RaptorQ block (C.5.3) and repair ESI = SSB_length + RS_ID;\n * SS_ID increments by one per FEC source packet and wraps at 2^32 (C.5.2),\n * so a block is located by membership in `[SS_Start, SS_Start + SSB_length)`\n * under RFC 1982 serial arithmetic; the MoQ repair group id is the 62-bit\n * wrap-epoch-extended SS_Start. The AL-FEC coding byte for this profile is\n * 0x14 (one-stage, ssbg_mode1, fec_payload_id_mode 0, Amd 1:2025 Table C.3).\n */\nimport { serial32Add, serial32Distance, serial32Precedes } from './serial-number.js'\n\n/** The two block profiles a catalog `fec.blockProfile` may name. */\nexport type FecBlockProfile = 'k-padded-v1' | 'iso-ssbg1-v1'\n\n/** Every accepted `fec.blockProfile` token, legacy first. */\nexport const FEC_BLOCK_PROFILES: readonly FecBlockProfile[] = Object.freeze([\n 'k-padded-v1',\n 'iso-ssbg1-v1',\n])\n\n/** True for a known block-profile token. */\nexport function isFecBlockProfile(value: unknown): value is FecBlockProfile {\n return value === 'k-padded-v1' || value === 'iso-ssbg1-v1'\n}\n\n/**\n * Read a required `fec.blockProfile`. Throws on an absent or unknown token:\n * there is no default and nothing is inferred, because the two profiles carry\n * identical wire tuples whose repair math differs.\n *\n * @param where the catalog path of the field, for the error message.\n */\nexport function parseFecBlockProfile(value: unknown, where: string): FecBlockProfile {\n if (isFecBlockProfile(value)) return value\n if (value === undefined) {\n throw new Error(\n `${where} is required: name the FEC block profile (\"k-padded-v1\" or \"iso-ssbg1-v1\")`,\n )\n }\n throw new Error(\n `${where} must be \"k-padded-v1\" or \"iso-ssbg1-v1\" (got ${JSON.stringify(value)})`,\n )\n}\n\n/** ISO/IEC 23008-1 C.3.3 ssbg_mode1: the two-octet packet length field. */\nexport const SSBG1_LENGTH_FIELD_BYTES = 2\n\n/** The largest protected packet an ssbg_mode1 symbol of size T can carry. */\nexport function ssbg1MaxPacketLength(symbolSize: number): number {\n return symbolSize - SSBG1_LENGTH_FIELD_BYTES\n}\n\n/** Whether a protected packet of `packetLength` bytes fits one T-byte ssbg_mode1 symbol. */\nexport function ssbg1FitsSymbol(packetLength: number, symbolSize: number): boolean {\n return (\n Number.isSafeInteger(packetLength) &&\n packetLength >= 0 &&\n packetLength <= ssbg1MaxPacketLength(symbolSize) &&\n packetLength <= 0xffff\n )\n}\n\n/**\n * Build one ssbg_mode1 source symbol (C.3.3): the protected packet's length\n * in two octets, network byte order, then the packet, then zero octets up to\n * T. The protected packet is the MMTP packet before its Source FEC Payload ID\n * is appended (C.4.2).\n *\n * @throws when `length + 2 > T`: the packet cannot be protected under this\n * profile, and silently truncating it would corrupt every repair symbol.\n */\nexport function buildSsbg1SourceSymbol(packet: Uint8Array, symbolSize: number): Uint8Array {\n if (!Number.isSafeInteger(symbolSize) || symbolSize <= SSBG1_LENGTH_FIELD_BYTES) {\n throw new Error(`ssbg_mode1 symbol size must exceed ${SSBG1_LENGTH_FIELD_BYTES} (got ${symbolSize})`)\n }\n if (!ssbg1FitsSymbol(packet.byteLength, symbolSize)) {\n throw new Error(\n `ssbg_mode1 source symbol needs ${packet.byteLength} + ${SSBG1_LENGTH_FIELD_BYTES} bytes but T is ${symbolSize}`,\n )\n }\n const symbol = new Uint8Array(symbolSize)\n symbol[0] = (packet.byteLength >>> 8) & 0xff\n symbol[1] = packet.byteLength & 0xff\n symbol.set(packet, SSBG1_LENGTH_FIELD_BYTES)\n return symbol\n}\n\n/**\n * Recover the protected packet from one ssbg_mode1 source symbol: the bytes\n * the length field names. Returns null, never a guess, when the length field\n * points past the symbol or any padding octet is non-zero (C.3.3 padding is\n * all 00h), since either means the recovered symbol is not what was sent.\n */\nexport function trimSsbg1SourceSymbol(symbol: Uint8Array): Uint8Array | null {\n if (symbol.byteLength < SSBG1_LENGTH_FIELD_BYTES) return null\n const length = (symbol[0]! << 8) | symbol[1]!\n const end = SSBG1_LENGTH_FIELD_BYTES + length\n if (end > symbol.byteLength) return null\n for (let i = end; i < symbol.byteLength; i++) {\n if (symbol[i] !== 0) return null\n }\n return symbol.subarray(SSBG1_LENGTH_FIELD_BYTES, end)\n}\n\n/** The SS_ID after the last source symbol of `[ssStart, ssStart + ssbLength)`. */\nexport function isoBlockEnd(ssStart: number, ssbLength: number): number {\n return serial32Add(ssStart, ssbLength)\n}\n\n/**\n * Range membership (C.5.2, C.5.3): whether `ssId` is one of the\n * `ssbLength` consecutive SS_IDs starting at `ssStart`, modulo 2^32.\n */\nexport function isoBlockContains(ssStart: number, ssbLength: number, ssId: number): boolean {\n return serial32Distance(ssStart, ssId) < ssbLength\n}\n\n/**\n * A lower bound on how many blocks the block starting at `a` lies behind the\n * one starting at `b` (0 when `a` does not precede `b`). An iso-ssbg1-v1\n * block holds at most `kMax` source symbols (the catalog `sourceSymbols`, the\n * AL-FEC maximum_k_for_repair_flow), so `n * kMax` SS_IDs always span at least\n * `n` blocks. Every interleave horizon in the receiver (decoder retention,\n * worker retirement, ordered-relay settlement) uses this one bound, so the\n * manager's horizon can never fall inside the decoder's.\n */\nexport function isoBlocksBehindLowerBound(a: number, b: number, kMax: number): number {\n if (!Number.isSafeInteger(kMax) || kMax <= 0) {\n throw new Error(`iso-ssbg1-v1 block horizon needs a positive maximum K (got ${kMax})`)\n }\n return serial32Precedes(a, b) ? Math.floor(serial32Distance(a, b) / kMax) : 0\n}\n", "// Typed FEC state shared by every player (hang/moq-lib, moqtail, Shaka MSF).\n//\n// A player's FEC counters are `null` in three unrelated situations: the\n// session is on reliable MoQ, where FEC has nothing to protect (FEC protects\n// only lossy multicast/AMT datagrams); no multicast bridge exists, so no lossy\n// leg can be joined; or the catalog declares no FEC for the tracks the player\n// selected. `null` also meant \"nothing decoded yet\" and \"the pipeline broke\",\n// so an idle session and a broken one looked identical.\n//\n// `resolveFecState` names which case holds. Each reason is read from a fact the\n// player already owns -- its catalog, its transport policy, its bridge probe\n// and its physical subscription -- and never from counters, console text,\n// timers or elapsed time. The rules live here so the three players cannot\n// disagree about precedence.\n\nimport { isFecBlockProfile } from '@blockcast/mmt-base'\n\n/**\n * Minimum valid FEC interleave depth in milliseconds, per spec \u00A75.1\n * (the interactive/gaming floor; broadcast/ATSC 3.0 ranges up to 10000ms).\n *\n * A protocol constant, NOT a tuning knob. Values below this floor are almost\n * certainly a raw frame count (e.g. `2`) that a caller passed when no framerate\n * was available to convert to ms (common on audio tracks). Feeding such a value\n * into computeFecTimeout treats it as `2ms`, yielding a near-zero FEC block\n * timeout that abandons startup FEC blocks before their repair symbols arrive\n * (measured: moqtail startupFailed=4, shaka dropped=5.8% on startup). Timing\n * derivations MUST reject sub-floor values and fall back to the established\n * timeout instead of collapsing.\n */\nexport const MIN_INTERLEAVE_DEPTH_MS = 33\n\n/** Why FEC is not protecting this session. */\nexport type FecInactiveReason = 'no-catalog-fec' | 'reliable-moq' | 'no-multicast-bridge'\n\n/** Every inactive reason, in resolution order. */\nexport const FEC_INACTIVE_REASONS: readonly FecInactiveReason[] = Object.freeze([\n 'no-catalog-fec',\n 'reliable-moq',\n 'no-multicast-bridge',\n] as const)\n\n/** The transport policy vocabulary every player accepts. */\nexport type FecTransportPolicy = 'auto' | 'moq-only' | 'multicast-only'\n\nconst TRANSPORT_POLICIES: ReadonlySet<string> = new Set<FecTransportPolicy>(['auto', 'moq-only', 'multicast-only'])\n\n/** FEC is not protecting this session; `reason` says why. Carries no counters. */\nexport interface FecInactiveState {\n readonly state: 'inactive'\n readonly reason: FecInactiveReason\n}\n\n/**\n * FEC is the protection in force for this session's delivery path.\n *\n * Flat on purpose: the player's existing counters stay at the paths every\n * consumer already reads (`fec.blocksRecovered`, `fec.tracks`, `fec.blocks`),\n * with `state` added beside them. A consumer that ignores `state` reads an\n * active report exactly as before and an inactive one as \"no counters\", which\n * is what `null` meant to it.\n */\nexport type FecActiveState<S extends object> = S & { readonly state: 'active' }\n\nexport type FecState<S extends object> = FecActiveState<S> | FecInactiveState\n\n/** An active state whose decoder has not reported any counter yet. */\nexport type NoFecCounters = Record<never, never>\n\n/** The authoritative facts `resolveFecState` reads. */\nexport interface FecStateEvidence<S extends object> {\n /**\n * The counters the player reports today, or null/undefined when it has none.\n * Payload only: it never decides the state, because moqtail and Shaka report\n * a zeroed snapshot as soon as a MulticastManager exists, which is before any\n * bridge or subscription does.\n */\n readonly stats: S | null | undefined\n /**\n * Whether the catalog declares FEC (see `catalogDeclaresFec`) for at least\n * one media track the player selected.\n */\n readonly catalogFec: boolean\n /** The player's configured transport policy. */\n readonly transportPolicy: FecTransportPolicy\n /** Whether a multicast bridge (`window.multicast`) is available to the page. */\n readonly bridgeAvailable: boolean\n /**\n * Whether a physical multicast/AMT subscription is live: the bridge accepted\n * it and it has not been closed. A requested leg is not a joined one.\n */\n readonly multicastLegJoined: boolean\n}\n\nconst INACTIVE: Readonly<Record<FecInactiveReason, FecInactiveState>> = Object.freeze({\n 'no-catalog-fec': Object.freeze({ state: 'inactive', reason: 'no-catalog-fec' }),\n 'reliable-moq': Object.freeze({ state: 'inactive', reason: 'reliable-moq' }),\n 'no-multicast-bridge': Object.freeze({ state: 'inactive', reason: 'no-multicast-bridge' }),\n})\n\nfunction recordOf(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined\n}\n\nfunction isPositiveSafeInteger(value: unknown): boolean {\n return typeof value === 'number' && Number.isSafeInteger(value) && value > 0\n}\n\n/**\n * Whether one catalog `fec` block (a hang rendition's `fec` or an MSF/CMSF\n * track's `fec`) declares an FEC instance a receiver can configure: source\n * symbols per block (K) and a symbol size (T) are both positive integers, the\n * algorithm is not `none`, and `blockProfile` names a known block profile\n * (BLO-35597). Total: it never throws, and answers false for any other value.\n *\n * `resolveCatalogTrackFec` reads this predicate, so the players' parameters and\n * this state never disagree. Of the blocks this rejects, it returns no FEC\n * geometry only for `algorithm: \"none\"`. One with any other algorithm is a\n * malformed catalog (`FecCatalogConfig` marks both fields REQUIRED; the MSF\n * schema and `validateCmsfMmtpCatalog` enforce them on `tracks[]`, but the\n * hang dialect's `renditions` schema is permissive, so this is where a hang\n * block fails closed), and `resolveCatalogTrackFec` throws a `RepairContractError` for\n * it (`track_fec` unless another repair-contract violation fires first),\n * whether or not the source has repair layers, rather than silently dropping\n * the track's repair subscription.\n *\n * The block profile is part of this predicate because the profile selects the\n * block math and is never defaulted: without a known one no receiver can\n * configure the instance. `resolveCatalogTrackFec` resolves such a track to no\n * FEC with a typed `fecUnavailable` reason (`track_fec_block_profile`), scoped\n * to that track, so its parameters and this state still agree. The CMSF\n * conformance validator (`validateCmsfMmtpCatalog`) still rejects the catalog\n * with `track_fec_block_profile`. An `iso-ssbg1-v1` instance without a usable\n * block span (`isoBlockSpanUsable`) is likewise no FEC for its track alone,\n * with `fecUnavailable` code `track_fec` on its `interleaveDepthMs`.\n *\n * The repair-contract resolvers (`resolveRepairLayers`) gate a `depends` target\n * on `algorithm` alone and never read a source's `symbolSize`. They check its\n * `sourceSymbols` for the RaptorQ K bound, and require a positive integer\n * `sourceSymbols` and `repairSymbols` only when the source has repair layers or\n * an overlay beyond layer 0.\n */\nexport function catalogDeclaresFec(fec: unknown): boolean {\n const block = recordOf(fec)\n if (block === undefined || block['algorithm'] === 'none') return false\n return (\n isPositiveSafeInteger(block['sourceSymbols']) &&\n isPositiveSafeInteger(block['symbolSize']) &&\n isFecBlockProfile(block['blockProfile']) &&\n isoBlockSpanUsable(block)\n )\n}\n\n/**\n * Whether a `fec` block that names `iso-ssbg1-v1` carries the block span its\n * block location is bounded by: `interleaveDepthMs`, a finite number of\n * milliseconds no smaller than `MIN_INTERLEAVE_DEPTH_MS`. Without it a receiver\n * cannot bound the sources it holds before a repair states their block, so the\n * instance cannot be configured (BLO-35597). Any other block needs no span here.\n */\nexport function isoBlockSpanUsable(fec: unknown): boolean {\n const block = recordOf(fec)\n if (block === undefined || block['blockProfile'] !== 'iso-ssbg1-v1') return true\n const ms = block['interleaveDepthMs']\n return typeof ms === 'number' && Number.isFinite(ms) && ms >= MIN_INTERLEAVE_DEPTH_MS\n}\n\nfunction requireBoolean(value: unknown, field: string): boolean {\n if (typeof value !== 'boolean') {\n throw new TypeError(`resolveFecState: ${field} must be a boolean, got ${typeof value}`)\n }\n return value\n}\n\n/**\n * Resolve a player's FEC state. First match wins:\n *\n * 1. The catalog declares no FEC for a selected track: `no-catalog-fec`. FEC\n * geometry comes only from the catalog, so there is nothing to configure on\n * any path.\n * 2. The policy is `moq-only`: `reliable-moq`. Media travels only over QUIC,\n * which is reliable, so FEC has no loss to repair, bridge or not.\n * 3. No multicast bridge: `no-multicast-bridge`. No lossy leg can exist, and on\n * `auto` this is the reason the session is on reliable MoQ.\n * 4. The policy is `multicast-only`, or a lossy subscription is live: active.\n * `multicast-only` commits the session to the lossy leg, so FEC is in force\n * before the join completes; whether the join succeeded is the physical\n * attestation's question, not this one. The counters may be absent until\n * the decoder reports.\n * 5. Otherwise (`auto`, a bridge, no live subscription): `reliable-moq`.\n *\n * Throws on malformed evidence rather than picking a reason for it.\n */\nexport function resolveFecState<S extends object>(evidence: FecStateEvidence<S> & { readonly stats: S }): FecState<S>\nexport function resolveFecState<S extends object>(evidence: FecStateEvidence<S>): FecState<S | NoFecCounters>\nexport function resolveFecState<S extends object>(evidence: FecStateEvidence<S>): FecState<S | NoFecCounters> {\n const catalogFec = requireBoolean(evidence.catalogFec, 'catalogFec')\n const bridgeAvailable = requireBoolean(evidence.bridgeAvailable, 'bridgeAvailable')\n const multicastLegJoined = requireBoolean(evidence.multicastLegJoined, 'multicastLegJoined')\n const { transportPolicy, stats } = evidence\n if (typeof transportPolicy !== 'string' || !TRANSPORT_POLICIES.has(transportPolicy)) {\n throw new TypeError(`resolveFecState: unsupported transportPolicy ${JSON.stringify(transportPolicy)}`)\n }\n if (stats !== null && stats !== undefined && recordOf(stats) === undefined) {\n throw new TypeError('resolveFecState: stats must be a plain object, null or undefined')\n }\n\n if (!catalogFec) return INACTIVE['no-catalog-fec']\n if (transportPolicy === 'moq-only') return INACTIVE['reliable-moq']\n if (!bridgeAvailable) return INACTIVE['no-multicast-bridge']\n if (transportPolicy === 'multicast-only' || multicastLegJoined) {\n // The discriminant is written last so a stats field of the same name can\n // never masquerade as a state.\n return { ...(stats ?? {}), state: 'active' } as FecActiveState<S | NoFecCounters>\n }\n return INACTIVE['reliable-moq']\n}\n"],
5
- "mappings": ";AAiBA,IAAM,kBAAkB;AACxB,IAAM,wBAAwB;AAC9B,IAAM,4BAA4B,kBAAkB;;;AC0D7C,IAAMA,mBAAkB;AAI/B,IAAMC,yBAAwB;AAC9B,IAAMC,6BAA4BC,mBAAkBF;;;ACrE7C,IAAM,iBAAiB;AAGvB,IAAM,oBAAoB,KAAK;AAQ/B,IAAM,uBAAuB,MAAM,iBAAiB;;;ACIpD,IAAM,qBAAiD,OAAO,OAAO;EAC1E;EACA;CACD;AAGK,SAAU,kBAAkB,OAAc;AAC9C,SAAO,UAAU,iBAAiB,UAAU;AAC9C;;;ACNO,IAAM,0BAA0B;AAMhC,IAAM,uBAAqD,OAAO,OAAO;AAAA,EAC9E;AAAA,EACA;AAAA,EACA;AACF,CAAU;AAKV,IAAM,qBAA0C,oBAAI,IAAwB,CAAC,QAAQ,YAAY,gBAAgB,CAAC;AAiDlH,IAAM,WAAkE,OAAO,OAAO;AAAA,EACpF,kBAAkB,OAAO,OAAO,EAAE,OAAO,YAAY,QAAQ,iBAAiB,CAAC;AAAA,EAC/E,gBAAgB,OAAO,OAAO,EAAE,OAAO,YAAY,QAAQ,eAAe,CAAC;AAAA,EAC3E,uBAAuB,OAAO,OAAO,EAAE,OAAO,YAAY,QAAQ,sBAAsB,CAAC;AAC3F,CAAC;AAED,SAAS,SAAS,OAAqD;AACrE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK,IACrE,QACD;AACN;AAEA,SAAS,sBAAsB,OAAyB;AACtD,SAAO,OAAO,UAAU,YAAY,OAAO,cAAc,KAAK,KAAK,QAAQ;AAC7E;AAoCO,SAAS,mBAAmB,KAAuB;AACxD,QAAM,QAAQ,SAAS,GAAG;AAC1B,MAAI,UAAU,UAAa,MAAM,WAAW,MAAM,OAAQ,QAAO;AACjE,SACE,sBAAsB,MAAM,eAAe,CAAC,KAC5C,sBAAsB,MAAM,YAAY,CAAC,KACzC,kBAAkB,MAAM,cAAc,CAAC,KACvC,mBAAmB,KAAK;AAE5B;AASO,SAAS,mBAAmB,KAAuB;AACxD,QAAM,QAAQ,SAAS,GAAG;AAC1B,MAAI,UAAU,UAAa,MAAM,cAAc,MAAM,eAAgB,QAAO;AAC5E,QAAM,KAAK,MAAM,mBAAmB;AACpC,SAAO,OAAO,OAAO,YAAY,OAAO,SAAS,EAAE,KAAK,MAAM;AAChE;AAEA,SAAS,eAAe,OAAgB,OAAwB;AAC9D,MAAI,OAAO,UAAU,WAAW;AAC9B,UAAM,IAAI,UAAU,oBAAoB,KAAK,2BAA2B,OAAO,KAAK,EAAE;AAAA,EACxF;AACA,SAAO;AACT;AAuBO,SAAS,gBAAkC,UAA4D;AAC5G,QAAM,aAAa,eAAe,SAAS,YAAY,YAAY;AACnE,QAAM,kBAAkB,eAAe,SAAS,iBAAiB,iBAAiB;AAClF,QAAM,qBAAqB,eAAe,SAAS,oBAAoB,oBAAoB;AAC3F,QAAM,EAAE,iBAAiB,MAAM,IAAI;AACnC,MAAI,OAAO,oBAAoB,YAAY,CAAC,mBAAmB,IAAI,eAAe,GAAG;AACnF,UAAM,IAAI,UAAU,gDAAgD,KAAK,UAAU,eAAe,CAAC,EAAE;AAAA,EACvG;AACA,MAAI,UAAU,QAAQ,UAAU,UAAa,SAAS,KAAK,MAAM,QAAW;AAC1E,UAAM,IAAI,UAAU,kEAAkE;AAAA,EACxF;AAEA,MAAI,CAAC,WAAY,QAAO,SAAS,gBAAgB;AACjD,MAAI,oBAAoB,WAAY,QAAO,SAAS,cAAc;AAClE,MAAI,CAAC,gBAAiB,QAAO,SAAS,qBAAqB;AAC3D,MAAI,oBAAoB,oBAAoB,oBAAoB;AAG9D,WAAO,EAAE,GAAI,SAAS,CAAC,GAAI,OAAO,SAAS;AAAA,EAC7C;AACA,SAAO,SAAS,cAAc;AAChC;",
4
+ "sourcesContent": ["/**\n * Zero-dependency helper for extracting the detached ALTA authenticator trailer\n * from an MMTP source packet.\n *\n * Lives in its own leaf package so both `@blockcast/mmt-container` (wire-path\n * parsing) and `@blockcast/fec-worker` (FEC-recovered-source parsing) can share\n * one implementation without creating a package cycle between them.\n *\n * Spec: ISO/IEC 23008-1:2023 + draft-ramadan-moq-mmt \u00A712 (detached trailer, Path 2).\n */\n\n// --- Private MMTP/MPU/MFU constants (ISO/IEC 23008-1 \u00A79.2.2\u20139.2.3) ---\n// Duplicated here as immutable spec values so this package stays a leaf with\n// zero `@blockcast/*` dependencies. Re-deriving from `mmt-container` would\n// reintroduce the package cycle this split exists to avoid.\n\nconst MMTP_HEADER_SIZE = 12\nconst MPU_HEADER_SIZE = 8\nconst MPU_LENGTH_FIELD_SIZE = 2\nconst MPU_HEADER_REMAINDER_SIZE = MPU_HEADER_SIZE - MPU_LENGTH_FIELD_SIZE\nconst TIMED_MFU_DU_HEADER_SIZE = 14\nconst NONTIMED_MFU_DU_HEADER_SIZE = 4\nconst PACKET_TYPE_MPU = 0x00\n\n// --- Public ALTA trailer constants ---\n\n/**\n * MMTP header-extension ext_type assigned to the detached ALTA authenticator\n * trailer (Path 2 / 08-07). Carried in an `[ext_type][ext_length][auth]`\n * block appended after the MPU's declared payload bytes, before the 4-byte\n * Source FEC Payload ID. MMTP X-bit stays 0 \u2014 non-ALTA MMTP receivers treat\n * the trailer as bytes past `payload_length` and skip it (per ISO 23008-1\n * \u00A79.2.2 \"may be discarded without impacting correct processing\").\n */\nexport const MMTP_EXT_TYPE_ALTA_AUTH = 0xf0a1\n\n/** Fixed size of the trailer's `[ext_type][ext_length]` prefix (2 + 2 BE). */\nexport const MMTP_EXT_TRAILER_HEADER_SIZE = 4\n\n/** Result of extracting the detached ALTA authenticator from an MMTP packet. */\nexport interface AltaTrailer {\n /** Authenticator bytes (no ext_type/ext_length prefix, no trailing padding). */\n auth: Uint8Array\n /**\n * Wire-packet byte offset where the signed payload ends, i.e. where the\n * trailer begins. Caller uses this to produce\n * `payload = wirePacket.subarray(0, signedEnd)` for verify_detached.\n */\n signedEnd: number\n}\n\n/**\n * Extract the detached ALTA authenticator trailer from an MMTP source packet.\n *\n * Wire layout:\n * `[MMTP hdr][length(2)][remaining MPU hdr(6)][DU?][media][TRAILER][SS_ID?]`\n * `payload_length = 6 + DU bytes + media bytes`\n *\n * Returns `null` when:\n * - the packet is too small / malformed,\n * - `payload_length` cannot contain the remaining six MPU-header bytes,\n * - the declared payload overruns the packet,\n * - no bytes remain between `payload_length` and the SS_ID,\n * - the trailer's `ext_type` isn't {@link MMTP_EXT_TYPE_ALTA_AUTH},\n * - the declared `ext_length` would extend past the SS_ID boundary.\n *\n * Non-MPU packets (repair, signaling) always return `null`.\n *\n * @param options.skipFecId When true, do NOT reserve 4 trailing bytes for the\n * Source FEC Payload ID. Use on FEC-recovered symbols where the PID has\n * already been stripped upstream.\n */\nexport function extractAltaTrailer(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n): AltaTrailer | null {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) return null\n // ISO/IEC 23008-1:2023 Figure 8 places V=0 packet_type in the low six bits.\n const packetType = mmtpPacket[1]! & 0x3f\n if (packetType !== PACKET_TYPE_MPU) return null\n\n const fecType = (mmtpPacket[0]! >> 3) & 0x03\n let headerLength = MMTP_HEADER_SIZE\n if (fecType < 2 && (mmtpPacket[0]! & 0x20) !== 0) headerLength += 4\n if (fecType < 2 && (mmtpPacket[0]! & 0x02) !== 0) {\n if (headerLength + 4 > mmtpPacket.length) return null\n const extensionLength = new DataView(\n mmtpPacket.buffer,\n mmtpPacket.byteOffset + headerLength + 2,\n 2,\n ).getUint16(0, false)\n headerLength += 4 + extensionLength\n }\n if (headerLength + MPU_HEADER_SIZE > mmtpPacket.length) return null\n\n const payload = mmtpPacket.subarray(headerLength)\n\n // Inline a minimal MPU-header read \u2014 only the fields extractAltaTrailer needs.\n // payloadLength: 16 BE at bytes 0-1.\n // Byte 2 packs FT(4) | T(1) | FI(2) | A(1).\n const payloadLength = (payload[0]! << 8) | payload[1]!\n const flagsByte = payload[2]!\n const fragmentType = (flagsByte >> 4) & 0x0f\n const timed = ((flagsByte >> 3) & 0x01) === 1\n const fragmentationIndicator = (flagsByte >> 1) & 0x03\n\n // Only MFU packets (FT=2) carry the detached trailer.\n if (fragmentType !== 2) return null\n\n // FI=0/1 structurally carries a DU header; FI=2/3 does not. Require at\n // least one media byte after that deterministic layout so the bytes at the\n // declared boundary cannot impersonate ALTA for a header-only fragment.\n const dataUnitHeaderSize = fragmentationIndicator <= 1\n ? (timed ? TIMED_MFU_DU_HEADER_SIZE : NONTIMED_MFU_DU_HEADER_SIZE)\n : 0\n if (payloadLength <= MPU_HEADER_REMAINDER_SIZE + dataUnitHeaderSize) return null\n\n if (fecType > 1) return null\n const hasFecId = !options?.skipFecId && fecType === 1\n const payloadEnd = hasFecId ? payload.length - 4 : payload.length\n const declaredEnd = MPU_LENGTH_FIELD_SIZE + payloadLength\n if (declaredEnd > payloadEnd) return null\n\n if (declaredEnd + MMTP_EXT_TRAILER_HEADER_SIZE > payloadEnd) return null\n const extType = (payload[declaredEnd]! << 8) | payload[declaredEnd + 1]!\n if (extType !== MMTP_EXT_TYPE_ALTA_AUTH) return null\n const extLen = (payload[declaredEnd + 2]! << 8) | payload[declaredEnd + 3]!\n\n const authStart = declaredEnd + MMTP_EXT_TRAILER_HEADER_SIZE\n if (authStart + extLen !== payloadEnd) return null\n\n return {\n auth: payload.subarray(authStart, payloadEnd),\n signedEnd: headerLength + declaredEnd,\n }\n}\n", "import {\n MMTP_EXT_TYPE_ALTA_AUTH as _LEAF_MMTP_EXT_TYPE_ALTA_AUTH,\n MMTP_EXT_TRAILER_HEADER_SIZE as _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE,\n extractAltaTrailer as _leafExtractAltaTrailer,\n type AltaTrailer as _LeafAltaTrailer,\n} from '@blockcast/mmt-alta-parse'\n\n/**\n * @mmt/container - MMTP Parser\n *\n * MMTP (MMT Protocol) packet parser based on ISO/IEC 23008-1:2023 Section 9.2\n *\n * MMTP header format (12 bytes):\n * - Byte 0: V(2) | C(1) | FEC_type(2) | r(1) | X(1) | R(1)\n * - Byte 1: reserved(2) | packet_type(6)\n * - Bytes 2-3: packet_id (big-endian)\n * - Bytes 4-7: timestamp (big-endian, 32-bit)\n * - Bytes 8-11: sequence_number (big-endian)\n *\n * FEC_type values:\n * - 0: No FEC / source packet\n * - 1: FEC mode 0 (source with FEC payload ID)\n * - 2: FEC repair packet mode 0\n * - 3: FEC repair packet mode 1\n *\n * @packageDocumentation\n */\n\nexport interface MMTPHeader {\n version: number\n /** Packet type: 0x00=MPU, 0x02=Signaling, 0x03=Repair */\n packetType: number\n packetId: number\n timestamp: bigint\n sequenceNumber: number\n payloadLength: number\n fecType: number\n /** C flag: header carries a 32-bit packet_counter. */\n packetCounterFlag: boolean\n packetCounter?: number\n /** X flag: header carries an extension header and value. */\n extensionFlag: boolean\n extension?: { type: number; data: Uint8Array }\n /** Byte offset at which the MMTP payload begins. */\n headerLength: number\n /** Random Access Point flag - indicates keyframe (ISO 23008-1, byte 0 bit 0) */\n rapFlag: boolean\n}\n\nexport interface MPUHeader {\n mpuSequenceNumber: number\n fragmentationIndicator: number // FI: 0=complete, 1=first, 2=middle, 3=last\n /** 8-bit MPU fragment_counter from byte 3. */\n fragmentCounter: number\n /** T flag: 1=timed media (14-byte MFU DU header), 0=non-timed (4-byte header) */\n timedFlag: number\n /** A flag: aggregated MPU payload. This strict profile requires A=0. */\n aggregationFlag: boolean\n /** MPU payload_length (bytes 0-1): bytes after the length field, including\n * the remaining six MPU-header bytes and all data-unit bytes. */\n payloadLength: number\n}\n\nexport interface MFUDuHeader {\n movieFragmentSequenceNumber: number\n sampleNumber: number\n offset: number\n priority: number\n dependencyCounter: number\n}\n\nexport interface MMTPPacket {\n header: MMTPHeader\n payload: Uint8Array\n}\n\nexport const MMTP_HEADER_SIZE = 12\nexport const MPU_HEADER_SIZE = 8\nexport const MFU_DU_HEADER_SIZE_TIMED = 14 // T=1: timed media (video/audio samples)\nexport const MFU_DU_HEADER_SIZE_NONTIMED = 4 // T=0: non-timed media (item_id only)\n\nconst MPU_LENGTH_FIELD_SIZE = 2\nconst MPU_HEADER_REMAINDER_SIZE = MPU_HEADER_SIZE - MPU_LENGTH_FIELD_SIZE\nconst SOURCE_FEC_PAYLOAD_ID_SIZE = 4\n\n// MMTP Packet Types (ISO/IEC 23008-1:2023 Section 9.2.2)\nexport const PACKET_TYPE_MPU = 0x00\nexport const PACKET_TYPE_SIGNALING = 0x02\nexport const PACKET_TYPE_REPAIR = 0x03\n\n// AL-FEC Signaling Message ID (ISO/IEC 23008-1:2023 Amendment 1:2025)\nexport const ALFEC_MESSAGE_ID = 0x0203\n\n/**\n * MMTP `packet_id` of the signaling flow, which carries PACKET_TYPE_SIGNALING\n * packets such as the AL-FEC message (ALFEC_MESSAGE_ID). The FFmpeg `moq_mmt`\n * muxer sends that message on this packet_id into the same multicast group as\n * the media, and moqcast-draft (PR #44) makes advertising it for a track a\n * MUST NOT. A track's packet_id is therefore 1..MMTP_PACKET_ID_MAX.\n */\nexport const MMTP_SIGNALING_PACKET_ID = 0\n\n/** Largest value of the 16-bit MMTP `packet_id` header field. */\nexport const MMTP_PACKET_ID_MAX = 0xffff\n\n// FEC Code IDs\nexport const FEC_CODE_XOR = 1\nexport const FEC_CODE_RAPTORQ = 2\n\n/**\n * Parse MMTP header from buffer\n */\nexport function parseMMTPHeader(buffer: Uint8Array): MMTPHeader {\n if (buffer.length < MMTP_HEADER_SIZE) {\n throw new Error(`MMTP packet too small: ${buffer.length} bytes`)\n }\n\n const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength)\n\n const version = (buffer[0]! >> 6) & 0x03\n if (version !== 0) {\n throw new Error(`Unsupported MMTP version: ${version}`)\n }\n\n // Extract FEC type from byte 0, bits 3-4\n const fecType = (buffer[0]! >> 3) & 0x03\n const packetCounterFlag = (buffer[0]! & 0x20) !== 0\n const extensionFlag = (buffer[0]! & 0x02) !== 0\n\n // Extract RAP flag from byte 0, bit 0 (R bit - Random Access Point)\n const rapFlag = (buffer[0]! & 0x01) === 1\n\n // ISO/IEC 23008-1:2023 Figure 8 (V=0) places reserved(2) before\n // packet_type(6), so packet_type occupies the low six bits of byte 1.\n const packetType = buffer[1]! & 0x3F\n\n let offset = 2\n\n // Packet ID (2 bytes, big-endian)\n const packetId = view.getUint16(offset, false)\n offset += 2\n\n // Timestamp (4 bytes, big-endian)\n const timestamp = BigInt(view.getUint32(offset, false))\n offset += 4\n\n // Sequence number (4 bytes, big-endian)\n const sequenceNumber = view.getUint32(offset, false)\n offset += 4\n\n let packetCounter: number | undefined\n if (packetCounterFlag) {\n if (offset + 4 > buffer.length) throw new Error('MMTP packet truncated in packet_counter')\n packetCounter = view.getUint32(offset, false)\n offset += 4\n }\n\n let extension: { type: number; data: Uint8Array } | undefined\n if (extensionFlag) {\n if (offset + 4 > buffer.length) throw new Error('MMTP packet truncated in header extension')\n const type = view.getUint16(offset, false)\n const length = view.getUint16(offset + 2, false)\n offset += 4\n if (offset + length > buffer.length) throw new Error('MMTP packet truncated in header extension value')\n extension = { type, data: buffer.subarray(offset, offset + length) }\n offset += length\n }\n\n return {\n version,\n packetType,\n packetId,\n timestamp,\n sequenceNumber,\n payloadLength: buffer.length - offset,\n fecType,\n packetCounterFlag,\n packetCounter,\n extensionFlag,\n extension,\n headerLength: offset,\n rapFlag,\n }\n}\n\n/**\n * The MMTP `packet_id` of `buffer`, or null exactly when parseMMTPHeader would\n * throw for it (short buffer, V != 0, truncated packet_counter or header\n * extension).\n *\n * This is the per-packet filter a receiver runs before any other work, so it\n * builds no header object, DataView or BigInt timestamp (BLO-36599). Use\n * parseMMTPHeader when any other header field is needed.\n */\nexport function readMMTPPacketId(buffer: Uint8Array): number | null {\n if (buffer.length < MMTP_HEADER_SIZE) return null\n const flags = buffer[0]!\n if (((flags >> 6) & 0x03) !== 0) return null\n let offset = MMTP_HEADER_SIZE\n if ((flags & 0x20) !== 0) {\n if (offset + 4 > buffer.length) return null\n offset += 4\n }\n if ((flags & 0x02) !== 0) {\n if (offset + 4 > buffer.length) return null\n const length = (buffer[offset + 2]! << 8) | buffer[offset + 3]!\n if (offset + 4 + length > buffer.length) return null\n }\n return (buffer[2]! << 8) | buffer[3]!\n}\n\n/**\n * Unwrap MMTP packet to extract payload\n */\nexport function unwrapMMTP(buffer: Uint8Array): MMTPPacket {\n const header = parseMMTPHeader(buffer)\n const payload = buffer.subarray(header.headerLength)\n\n return { header, payload }\n}\n\n/**\n * Parse MPU header from payload\n * ISO/IEC 23008-1:2023 Section 9.2.3.3\n *\n * MPU Header format (8 bytes) as written by FFmpeg/libmmt:\n * - Bytes 0-1: payload_length (16 bits, big-endian)\n * - Byte 2: FT(4) | T(1) | FI(2) | A(1)\n * - Byte 3: fragment_counter (8 bits)\n * - Bytes 4-7: MPU_sequence_number (32 bits, big-endian)\n */\nexport function parseMPUHeader(payload: Uint8Array): MPUHeader {\n if (payload.length < MPU_HEADER_SIZE) {\n throw new Error(`Payload too small for MPU header: ${payload.length} bytes`)\n }\n\n const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength)\n\n // Bytes 0-1: payload_length (16 bits, big-endian)\n const payloadLength = view.getUint16(0, false)\n\n // Byte 2: FT(4) | T(1) | FI(2) | A(1)\n const flagsByte = payload[2]!\n const timedFlag = (flagsByte >> 3) & 0x01\n const fragmentationIndicator = (flagsByte >> 1) & 0x03\n const aggregationFlag = (flagsByte & 0x01) !== 0\n\n // Byte 3: fragment_counter (8 bits). The high bit is part of the counter in\n // the current libmmt/FFmpeg writer ABI.\n const fragmentCounter = payload[3]!\n\n // Bytes 4-7: MPU_sequence_number (32 bits, big-endian)\n const mpuSequenceNumber = view.getUint32(4, false)\n\n return {\n mpuSequenceNumber,\n fragmentationIndicator,\n fragmentCounter,\n timedFlag,\n aggregationFlag,\n payloadLength,\n }\n}\n\nexport function parseTimedMfuDuHeader(payload: Uint8Array, offset: number): MFUDuHeader | null {\n if (offset < 0 || offset + MFU_DU_HEADER_SIZE_TIMED > payload.length) return null\n const view = new DataView(payload.buffer, payload.byteOffset + offset, MFU_DU_HEADER_SIZE_TIMED)\n return {\n movieFragmentSequenceNumber: view.getUint32(0, false),\n sampleNumber: view.getUint32(4, false),\n offset: view.getUint32(8, false),\n priority: payload[offset + 12]!,\n dependencyCounter: payload[offset + 13]!,\n }\n}\n\n/**\n * Why `extractMFUPayload` refused a packet.\n *\n * These identifiers are part of the public contract: callers surface them in\n * diagnostics, so renaming one is a breaking change.\n */\nexport type MFURejectionGate =\n | 'packet-shorter-than-mmtp-plus-mpu-header'\n | 'mmtp-header-unparseable'\n | 'not-an-mpu-packet'\n | 'payload-shorter-than-mpu-header'\n | 'aggregation-flag-set'\n | 'mpu-payload-length-below-header-remainder'\n | 'mpu-declared-end-exceeds-payload'\n | 'mfu-offset-at-or-past-declared-end'\n | 'source-fec-payload-id-width-mismatch'\n | 'declared-trailers-unresolved'\n\nexport interface MFURejection {\n gate: MFURejectionGate\n /** Human-readable detail, including the byte arithmetic that decided it. */\n detail: string\n}\n\ninterface MfuPayloadLayout {\n hasMfuDuHeader: boolean\n mfuOffset: number\n dataEnd: number\n}\n\nfunction resolveMfuPayloadLayout(\n mpu: MPUHeader,\n fragmentType: number,\n payload: Uint8Array,\n): { layout: MfuPayloadLayout } | { rejection: MFURejection } {\n if (mpu.payloadLength < MPU_HEADER_REMAINDER_SIZE) {\n return {\n rejection: {\n gate: 'mpu-payload-length-below-header-remainder',\n detail: `payload_length=${mpu.payloadLength} < ${MPU_HEADER_REMAINDER_SIZE}`,\n },\n }\n }\n\n // ISO/IEC 23008-1 defines payload_length from immediately after its own\n // two-byte field. It therefore includes the remaining six MPU-header bytes.\n const declaredEnd = MPU_LENGTH_FIELD_SIZE + mpu.payloadLength\n if (declaredEnd > payload.length) {\n return {\n rejection: {\n gate: 'mpu-declared-end-exceeds-payload',\n detail:\n `payload_length=${mpu.payloadLength} implies end=${declaredEnd}, ` +\n `payload is ${payload.length} bytes`,\n },\n }\n }\n\n const hasHeader = fragmentType === 2 && mpu.fragmentationIndicator <= 1\n const mfuHeaderSize = mpu.timedFlag ? MFU_DU_HEADER_SIZE_TIMED : MFU_DU_HEADER_SIZE_NONTIMED\n const mfuOffset = MPU_HEADER_SIZE + (hasHeader ? mfuHeaderSize : 0)\n if (mfuOffset >= declaredEnd) {\n return {\n rejection: {\n gate: 'mfu-offset-at-or-past-declared-end',\n detail: `mfuOffset=${mfuOffset} >= declaredEnd=${declaredEnd}`,\n },\n }\n }\n\n return {\n layout: {\n hasMfuDuHeader: hasHeader,\n mfuOffset,\n dataEnd: declaredEnd,\n },\n }\n}\n\nfunction isEmptyOrExactAltaTrailer(payload: Uint8Array, start: number, end: number): boolean {\n if (start === end) return true\n if (start < 0 || end > payload.length || start + _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE > end) {\n return false\n }\n\n const view = new DataView(\n payload.buffer,\n payload.byteOffset + start,\n end - start,\n )\n if (view.getUint16(0, false) !== _LEAF_MMTP_EXT_TYPE_ALTA_AUTH) return false\n\n const authLength = view.getUint16(2, false)\n return start + _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE + authLength === end\n}\n\ninterface DeclaredTrailerLayout {\n hasFecId: boolean\n}\n\nfunction resolveDeclaredTrailers(\n payload: Uint8Array,\n declaredEnd: number,\n fecType: number,\n options?: { skipFecId?: boolean },\n): DeclaredTrailerLayout | null {\n if (declaredEnd > payload.length) return null\n\n if (options?.skipFecId) {\n return isEmptyOrExactAltaTrailer(payload, declaredEnd, payload.length)\n ? { hasFecId: false }\n : null\n }\n\n if (fecType === 1) {\n const fecStart = payload.length - SOURCE_FEC_PAYLOAD_ID_SIZE\n if (fecStart < declaredEnd || !isEmptyOrExactAltaTrailer(payload, declaredEnd, fecStart)) {\n return null\n }\n return { hasFecId: true }\n }\n if (fecType !== 0) return null\n\n if (isEmptyOrExactAltaTrailer(payload, declaredEnd, payload.length)) {\n return { hasFecId: false }\n }\n\n return null\n}\n\n/**\n * Extract NAL unit payload from MMTP packet\n */\nexport function extractNALUnit(mmtpPacket: Uint8Array): Uint8Array {\n const extraction = extractMFUPayload(mmtpPacket)\n if (!extraction) throw new Error('Malformed MMTP MPU payload')\n return extraction.data\n}\n\n/** Result of extracting MFU payload from an MMTP packet */\nexport interface MFUExtraction {\n /** MMTP header fields */\n header: MMTPHeader\n /** MPU header fields */\n mpu: MPUHeader\n /** Fragment type (FT field, 4 bits): 0=metadata, 2=MFU data */\n fragmentType: number\n /** MFU payload data (video/audio bytes, headers stripped, FEC ID excluded) */\n data: Uint8Array\n /** Parsed timed MFU DU header when present, otherwise null */\n mfuDuHeader: MFUDuHeader | null\n /** Non-timed MFU Item_ID when the 4-byte DU header is present. */\n itemId: number | null\n /** Source FEC Payload ID if present (`FEC_type == 1`), or null */\n fecPayloadId: { ssId: number } | null\n}\n\n/**\n * Extract MFU payload from a raw MMTP packet.\n *\n * Handles all the byte arithmetic for:\n * - MMTP header (12 bytes)\n * - MPU sub-header (8 bytes, payloadLength field)\n * - MFU DU header (14 bytes timed / 4 bytes non-timed, only for FI \u2264 1)\n * - Source FEC Payload ID (4 bytes at end, only when `FEC_type == 1`)\n *\n * `payloadLength` is the ISO length-after-field value: six remaining MPU-header\n * bytes, then the MFU DU header when present, then media. ALTA and Source FEC\n * trailers are outside that declared span and must consume the exact suffix.\n *\n * @param mmtpPacket Raw MMTP packet (from multicast or MoQ)\n * @param options.skipFecId When true, do NOT subtract the 4-byte Source FEC Payload ID\n * from the payload boundary, even if fecType >= 1 in the MMTP header. Use this for\n * FEC-recovered symbols where the FEC PID was already stripped before WASM decoding\n * but the fecType bits in byte 0 still reflect the original packet (FEC Type=1 per\n * draft-ramadan-moq-mmt-00 \u00A73.1).\n * @returns Extracted MFU data + metadata, or null if packet is too small\n */\ninterface MfuPlan {\n header: MMTPHeader\n payload: Uint8Array\n mpu: MPUHeader\n fragmentType: number\n layout: MfuPayloadLayout\n trailerLayout: DeclaredTrailerLayout\n}\n\n/**\n * Single source of truth for whether a packet yields an MFU payload.\n *\n * `extractMFUPayload` and `explainMFURejection` both delegate here, so the\n * reason reported to operators can never drift from the decision the parser\n * actually made.\n */\nfunction planMfuExtraction(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n parsedHeader?: MMTPHeader,\n): { plan: MfuPlan } | { rejection: MFURejection } {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) {\n return {\n rejection: {\n gate: 'packet-shorter-than-mmtp-plus-mpu-header',\n detail: `${mmtpPacket.length} bytes < ${MMTP_HEADER_SIZE + MPU_HEADER_SIZE}`,\n },\n }\n }\n\n let header: MMTPHeader\n if (parsedHeader) {\n header = parsedHeader\n } else {\n try {\n header = parseMMTPHeader(mmtpPacket)\n } catch (error) {\n return {\n rejection: {\n gate: 'mmtp-header-unparseable',\n detail: error instanceof Error ? error.message : String(error),\n },\n }\n }\n }\n if (header.packetType !== PACKET_TYPE_MPU) {\n return {\n rejection: {\n gate: 'not-an-mpu-packet',\n detail: `packetType=0x${header.packetType.toString(16).padStart(2, '0')}`,\n },\n }\n }\n\n const payload = mmtpPacket.subarray(header.headerLength)\n if (payload.length < MPU_HEADER_SIZE) {\n return {\n rejection: {\n gate: 'payload-shorter-than-mpu-header',\n detail: `${payload.length} bytes < ${MPU_HEADER_SIZE}`,\n },\n }\n }\n\n const mpu = parseMPUHeader(payload)\n if (mpu.aggregationFlag) {\n return {\n rejection: { gate: 'aggregation-flag-set', detail: 'strict profile requires A=0' },\n }\n }\n\n // Fragment type from byte 2 upper nibble (not returned by parseMPUHeader)\n const fragmentType = (payload[2]! >> 4) & 0x0f\n\n const layoutResult = resolveMfuPayloadLayout(mpu, fragmentType, payload)\n if ('rejection' in layoutResult) return layoutResult\n const { layout } = layoutResult\n\n const trailerLayout = resolveDeclaredTrailers(\n payload,\n layout.dataEnd,\n header.fecType,\n options,\n )\n if (!trailerLayout) {\n // The most common cause is a producer whose Source FEC Payload ID width\n // disagrees with ISO/IEC 23008-1 \u00A7C.5.2. Name that explicitly rather than\n // leaving operators to infer it from a generic trailer error.\n if (!options?.skipFecId && header.fecType === 1) {\n const observed = payload.length - layout.dataEnd\n if (observed !== SOURCE_FEC_PAYLOAD_ID_SIZE) {\n return {\n rejection: {\n gate: 'source-fec-payload-id-width-mismatch',\n detail:\n `producer wrote a ${observed}-byte Source FEC Payload ID, ` +\n `ISO/IEC 23008-1 \u00A7C.5.2 requires ${SOURCE_FEC_PAYLOAD_ID_SIZE} ` +\n `(payload=${payload.length}, declaredEnd=${layout.dataEnd})`,\n },\n }\n }\n }\n return {\n rejection: {\n gate: 'declared-trailers-unresolved',\n detail:\n `trailer bytes [${layout.dataEnd}, ${payload.length}) are neither empty ` +\n `nor an exact ALTA trailer (fecType=${header.fecType})`,\n },\n }\n }\n\n return { plan: { header, payload, mpu, fragmentType, layout, trailerLayout } }\n}\n\n/**\n * Report why `extractMFUPayload` would refuse this packet, or null if it would\n * accept it.\n *\n * `extractMFUPayload` returns bare `null` on ten distinct conditions, which\n * makes a producer/consumer framing disagreement indistinguishable from a\n * publisher that never started \u2014 both render as silence. Use this wherever\n * that distinction matters: diagnostics, contract tests, and operator-facing\n * logs.\n */\nexport function explainMFURejection(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n): MFURejection | null {\n const result = planMfuExtraction(mmtpPacket, options)\n return 'rejection' in result ? result.rejection : null\n}\n\nexport function extractMFUPayload(\n mmtpPacket: Uint8Array,\n options?: { skipFecId?: boolean },\n): MFUExtraction | null {\n return extractPlannedMfu(mmtpPacket, planMfuExtraction(mmtpPacket, options))\n}\n\n/**\n * extractMFUPayload for a caller that already holds this packet's parsed MMTP\n * header, so the header is not parsed a second time (BLO-36599).\n *\n * `header` must be `parseMMTPHeader(mmtpPacket)` of this same packet; the\n * result is then identical to `extractMFUPayload(mmtpPacket, options)`.\n */\nexport function extractMFUPayloadWithHeader(\n mmtpPacket: Uint8Array,\n header: MMTPHeader,\n options?: { skipFecId?: boolean },\n): MFUExtraction | null {\n return extractPlannedMfu(mmtpPacket, planMfuExtraction(mmtpPacket, options, header))\n}\n\nfunction extractPlannedMfu(\n mmtpPacket: Uint8Array,\n result: { plan: MfuPlan } | { rejection: MFURejection },\n): MFUExtraction | null {\n if ('rejection' in result) return null\n const { header, payload, mpu, fragmentType, layout, trailerLayout } = result.plan\n\n const mfuDuHeaderPresent = layout.hasMfuDuHeader\n const mfuOffset = layout.mfuOffset\n const dataEnd = layout.dataEnd\n\n const mfuDuHeader =\n mfuDuHeaderPresent && mpu.timedFlag === 1\n ? parseTimedMfuDuHeader(payload, MPU_HEADER_SIZE)\n : null\n const itemId =\n mfuDuHeaderPresent && mpu.timedFlag === 0\n ? new DataView(payload.buffer, payload.byteOffset + MPU_HEADER_SIZE, 4).getUint32(0, false)\n : null\n\n const data = payload.subarray(mfuOffset, dataEnd)\n\n const fecPayloadId = trailerLayout.hasFecId\n ? {\n ssId: new DataView(\n mmtpPacket.buffer,\n mmtpPacket.byteOffset + mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE,\n SOURCE_FEC_PAYLOAD_ID_SIZE,\n ).getUint32(0, false),\n }\n : null\n\n return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId }\n}\n\n/**\n * Extract timestamp from MMTP packet\n */\nexport function extractTimestamp(mmtpPacket: Uint8Array): bigint {\n const { header } = unwrapMMTP(mmtpPacket)\n return header.timestamp\n}\n\n/**\n * Check if MMTP packet is an FEC repair packet\n * FEC_type 2 and 3 are repair packets\n */\nexport function isFecRepairPacket(mmtpPacket: Uint8Array): boolean {\n if (mmtpPacket.length < 1) return false\n const fecType = (mmtpPacket[0]! >> 3) & 0x03\n return fecType >= 2\n}\n\n/**\n * Extract Source FEC Payload ID (SS_ID) from the last 4 bytes of an MMTP\n * source packet per ISO/IEC 23008-1:2023 Section C.5.2 Figure C.10.\n *\n * SS_ID is a flat 32-bit monotonic counter (incremented by 1 per packet\n * for ssbg_mode0 and ssbg_mode1). Block membership and ESI use the\n * configured K; repair SS_Start identifies the first SS_ID of the block and\n * SSB_length records the authored source-symbol count:\n * SBN = floor(SS_ID / K), ESI = SS_ID % K\n * SS_Start = SBN * K (first SS_ID of the block)\n *\n * This matches FFmpeg moqenc_mmt.c, the drafts (draft-ramadan-moq-fec \u00A76.2),\n * and ATSC A/331 \u00A78.1.2.5 which defers to ISO 23008-1 Annex C.\n */\nexport function extractFecPayloadId(mmtpPacket: Uint8Array): { ssId: number } | null {\n const header = parseHeaderForSourceFecId(mmtpPacket)\n return header ? sourceFecPayloadIdOf(mmtpPacket, header) : null\n}\n\n/**\n * Return true when FEC_type=1 and the exact suffix after the ISO-declared MPU\n * payload contains a four-byte Source FEC Payload ID.\n */\nexport function hasSourceFecPayloadId(mmtpPacket: Uint8Array): boolean {\n const header = parseHeaderForSourceFecId(mmtpPacket)\n return header !== null && sourceFecPayloadIdOf(mmtpPacket, header) !== null\n}\n\nfunction parseHeaderForSourceFecId(mmtpPacket: Uint8Array): MMTPHeader | null {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) return null\n try {\n return parseMMTPHeader(mmtpPacket)\n } catch {\n return null\n }\n}\n\n/**\n * The Source FEC Payload ID of a packet whose MMTP header the caller already\n * parsed, or null when the packet carries none: present only when FEC_type=1\n * and the exact suffix after the ISO-declared MPU payload holds the four-byte\n * ID (ISO/IEC 23008-1 \u00A7C.5.2). Same answer as extractFecPayloadId, without a\n * second header parse (BLO-36599).\n *\n * `header` must be `parseMMTPHeader(mmtpPacket)` of this same packet.\n */\nexport function sourceFecPayloadIdOf(\n mmtpPacket: Uint8Array,\n header: MMTPHeader,\n): { ssId: number } | null {\n if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) return null\n if (header.packetType !== PACKET_TYPE_MPU) return null\n\n const payload = mmtpPacket.subarray(header.headerLength)\n if (payload.length < MPU_HEADER_SIZE) return null\n const payloadLength = (payload[0]! << 8) | payload[1]!\n if (payloadLength < MPU_HEADER_REMAINDER_SIZE) return null\n const declaredEnd = MPU_LENGTH_FIELD_SIZE + payloadLength\n if (!resolveDeclaredTrailers(payload, declaredEnd, header.fecType)?.hasFecId) return null\n\n // Flat 4-byte SS_ID: encoder writes block_id * K + symbol_id\n const off = mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE\n return {\n ssId:\n ((mmtpPacket[off]! << 24) |\n (mmtpPacket[off + 1]! << 16) |\n (mmtpPacket[off + 2]! << 8) |\n mmtpPacket[off + 3]!) >>>\n 0,\n }\n}\n\n/**\n * MMTP header-extension ext_type assigned to the detached ALTA authenticator\n * trailer (Path 2 / 08-07). Carried in an `[ext_type][ext_length][auth]`\n * block appended after the MPU's declared payload bytes, before the 4-byte\n * Source FEC Payload ID. MMTP X-bit stays 0 \u2014 non-ALTA MMTP receivers treat\n * the trailer as bytes past `payload_length` and skip it (per ISO 23008-1\n * \u00A79.2.2 \"may be discarded without impacting correct processing\").\n */\n// Re-export the ALTA trailer helper from the zero-dep leaf package. The leaf\n// owns the implementation so container and FEC consumers share one parser\n// without creating a package cycle.\nexport const MMTP_EXT_TYPE_ALTA_AUTH = _LEAF_MMTP_EXT_TYPE_ALTA_AUTH\nexport const MMTP_EXT_TRAILER_HEADER_SIZE = _LEAF_MMTP_EXT_TRAILER_HEADER_SIZE\nexport type AltaTrailer = _LeafAltaTrailer\nexport const extractAltaTrailer = _leafExtractAltaTrailer\n\n/** Size of Repair FEC Payload ID per ISO/IEC 23008-1 Section C.5.3 */\nexport const REPAIR_FEC_PID_SIZE = 13\n\n/**\n * Parse the 13-byte Repair FEC Payload ID after the complete dynamic MMTP header.\n * `parsedHeader`, when given, must be `parseMMTPHeader(mmtpPacket)` of this\n * same packet; it spares the second header parse (BLO-36599).\n */\nfunction readRepairFecPayloadIdFields(mmtpPacket: Uint8Array, parsedHeader?: MMTPHeader): {\n packetId: number; ssStart: number; rsbLength: number; rsId: number; ssbLength: number; dataOffset: number\n} | null {\n let header: MMTPHeader\n if (parsedHeader) {\n header = parsedHeader\n } else {\n try {\n header = parseMMTPHeader(mmtpPacket)\n } catch {\n return null\n }\n }\n if (header.packetType !== PACKET_TYPE_REPAIR || header.fecType !== 2) return null\n\n const off = header.headerLength\n if (mmtpPacket.length < off + REPAIR_FEC_PID_SIZE) return null\n const ssStart =\n ((mmtpPacket[off]! << 24) |\n (mmtpPacket[off + 1]! << 16) |\n (mmtpPacket[off + 2]! << 8) |\n mmtpPacket[off + 3]!) >>>\n 0 // 32 bits\n const rsbLength = (mmtpPacket[off + 4]! << 16) | (mmtpPacket[off + 5]! << 8) | mmtpPacket[off + 6]! // 24 bits\n const rsId = (mmtpPacket[off + 7]! << 16) | (mmtpPacket[off + 8]! << 8) | mmtpPacket[off + 9]! // 24 bits\n const ssbLength = (mmtpPacket[off + 10]! << 16) | (mmtpPacket[off + 11]! << 8) | mmtpPacket[off + 12]! // 24 bits\n return {\n packetId: header.packetId,\n ssStart,\n rsbLength,\n rsId,\n ssbLength,\n dataOffset: off + REPAIR_FEC_PID_SIZE,\n }\n}\n\n/**\n * Extract Repair FEC Payload ID from a raw-multicast MMTP repair packet.\n * Per ISO/IEC 23008-1:2023 Section C.5.3 (ssbg_mode0, one-stage, no FFSRP_TS):\n * [MMTP header][SS_Start:4][RSB_length:3][RS_ID:3][SSB_length:3][repair data]\n * OTI is delivered via AL-FEC signaling message (Table C.3), not per-packet.\n *\n * Byte 1 is reserved(2)|packet_type(6) for V=0, so repair type 0x03 is 0x03.\n * Pre-migration high-bits bytes (0x0c) are rejected \u2014 no compatibility decode.\n */\nexport function extractRepairFecPayloadId(mmtpPacket: Uint8Array): {\n ssStart: number; rsbLength: number; rsId: number; ssbLength: number\n} | null {\n const fields = readRepairFecPayloadIdFields(mmtpPacket)\n if (!fields) return null\n return {\n ssStart: fields.ssStart,\n rsbLength: fields.rsbLength,\n rsId: fields.rsId,\n ssbLength: fields.ssbLength,\n }\n}\n\n/** Parsed MoQ repair frame per ISO/IEC 23008-1 Section C.4.3 + C.5.3 */\nexport interface MoqRepairFrame {\n /** MMTP packetId (1=video, 2=audio) \u2014 for routing to correct FEC decoder */\n packetId: number\n /** SS_Start (32 bits): SS_ID of first source symbol in block */\n ssStart: number\n /** RSB_length (24 bits): number of repair symbols (P) */\n rsbLength: number\n /** RS_ID (24 bits): repair symbol index (0-based) */\n rsId: number\n /** SSB_length (24 bits): authored source-symbol count (1..=configured K) */\n ssbLength: number\n /** Repair symbol data after the dynamic MMTP header and 13-byte FEC payload ID. */\n symbolData: Uint8Array\n}\n\n/**\n * Parse a MoQ repair frame (ISO/IEC 23008-1 Annex C format).\n *\n * Wire format per ISO 23008-1 \u00A7C.4.3 + \u00A7C.5.3:\n * MMTP Header: fixed 12 bytes plus C/X optional fields\n * then SS_Start (4 bytes) \u2192 block identifier\n * then RSB_length (3 bytes) \u2192 P\n * then RS_ID (3 bytes) \u2192 ESI = K + RS_ID\n * then SSB_length (3 bytes) \u2192 authored source-symbol count (1..=K)\n * then Repair Symbol Data (T bytes)\n *\n * @param frame - Raw MoQ repair frame bytes\n * @returns Parsed frame or null if invalid\n */\nexport function parseMoqRepairFrame(frame: Uint8Array): MoqRepairFrame | null {\n return moqRepairFrameFrom(frame, readRepairFecPayloadIdFields(frame))\n}\n\n/**\n * parseMoqRepairFrame for a caller that already holds this frame's parsed MMTP\n * header, so the header is not parsed a second time (BLO-36599). The router's\n * multicast repair branch uses it: one repair packet in every K + P otherwise\n * paid two header parses.\n *\n * `header` must be `parseMMTPHeader(frame)` of this same frame; the result is\n * then identical to `parseMoqRepairFrame(frame)`.\n */\nexport function parseMoqRepairFrameWithHeader(\n frame: Uint8Array,\n header: MMTPHeader,\n): MoqRepairFrame | null {\n return moqRepairFrameFrom(frame, readRepairFecPayloadIdFields(frame, header))\n}\n\nfunction moqRepairFrameFrom(\n frame: Uint8Array,\n repairId: ReturnType<typeof readRepairFecPayloadIdFields>,\n): MoqRepairFrame | null {\n if (!repairId) return null\n\n const symbolData = frame.subarray(repairId.dataOffset)\n if (\n repairId.rsbLength === 0 ||\n repairId.ssbLength === 0 ||\n repairId.rsId >= repairId.rsbLength ||\n symbolData.length === 0\n ) return null\n\n return {\n packetId: repairId.packetId,\n ssStart: repairId.ssStart,\n rsbLength: repairId.rsbLength,\n rsId: repairId.rsId,\n ssbLength: repairId.ssbLength,\n symbolData,\n }\n}\n\n// The signalling-message parsers (9.3.4.2 payload header, AL-FEC message,\n// PA/MPT) live in ./signalling.ts.\n", "/**\n * NTP short format (RFC 5905 \u00A76): an unsigned 32-bit, 16.16 fixed-point count\n * of seconds. The MMTP header `timestamp` field (ISO/IEC 23008-1 \u00A79.2.2)\n * carries each packet's UTC sending instant in this format, so the value wraps\n * to 0 every 2^16 s (~18.2 h).\n *\n * Plain numeric order on these values breaks at every wrap: a retained\n * `Math.max` stays at the pre-wrap value and every later instant compares as\n * older. Order send times only with {@link compareNtpShort}, which applies\n * RFC 1982 serial-number arithmetic over the field's width.\n */\n\n/** Width in bits of the MMTP `timestamp` field (NTP short). */\nexport const NTP_SHORT_BITS = 32\n\n/** Number of distinct NTP-short values; the field wraps to 0 here. */\nexport const NTP_SHORT_MODULUS = 2 ** NTP_SHORT_BITS\n\n/**\n * RFC 1982 \u00A73.2's `2^(SERIAL_BITS - 1)`. Two values whose forward distance is\n * below this are ordered; exactly this far apart they are not ordered at all.\n * Two instants must be less than this far apart (2^15 s, ~9.1 h of send time)\n * for the comparison to give their true order.\n */\nexport const NTP_SHORT_HALF_RANGE = 2 ** (NTP_SHORT_BITS - 1)\n\n/**\n * Serial order of one NTP-short value relative to another.\n *\n * `'unordered'` is RFC 1982's undefined case: the values are exactly\n * {@link NTP_SHORT_HALF_RANGE} apart, so neither precedes the other. It is\n * never folded into `'before'` or `'after'`; each caller decides what an\n * unorderable pair means for it.\n */\nexport type NtpShortOrder = 'before' | 'equal' | 'after' | 'unordered'\n\n/** True when `value` is an NTP-short value: an integer in [0, NTP_SHORT_MODULUS). */\nexport function isNtpShort(value: number): boolean {\n return Number.isInteger(value) && value >= 0 && value < NTP_SHORT_MODULUS\n}\n\n/**\n * RFC 1982 serial comparison of NTP-short `a` against `b`: `'after'` when `a`\n * is the later instant, `'before'` when it is the earlier one.\n *\n * The answer is the true order only while the two instants are less than\n * {@link NTP_SHORT_HALF_RANGE} apart. Past that a 32-bit field cannot tell\n * later from earlier, and the result names whichever direction is nearer.\n * This is not a total order (it is not transitive across the whole space), so\n * it must never be used to sort.\n *\n * @throws {RangeError} if either operand is not an NTP-short value\n */\nexport function compareNtpShort(a: number, b: number): NtpShortOrder {\n if (!isNtpShort(a) || !isNtpShort(b)) {\n throw new RangeError(\n `NTP-short values must be integers in [0, 2^${NTP_SHORT_BITS}), got ${String(a)} and ${String(b)}`,\n )\n }\n if (a === b) return 'equal'\n // Forward distance from b to a, reduced into [1, NTP_SHORT_MODULUS). Both\n // operands are below 2^32, so every step is exact in a double.\n const forward = (a - b + NTP_SHORT_MODULUS) % NTP_SHORT_MODULUS\n if (forward < NTP_SHORT_HALF_RANGE) return 'after'\n if (forward > NTP_SHORT_HALF_RANGE) return 'before'\n return 'unordered'\n}\n", "/**\n * FEC block profile (BLO-35597, ISO/IEC 23008-1 Annex C).\n *\n * Every catalog `fec` object that declares an FEC instance names its block\n * profile in `fec.blockProfile`. A receiver applies all of a profile's rules\n * or none of them, so the token is the one explicit per-stream discriminator:\n * it is never defaulted and never inferred from the wire.\n *\n * draft-ramadan-moq-fec \u00A75.1 defines exactly one value, and it is the only\n * profile this library implements:\n *\n * - `iso-ssbg1-v1`: ssbg_mode1 (C.3.3): a source symbol is\n * `u16 length || protected packet || zero padding` to T; a block is a\n * K' = SSB_length RaptorQ block (C.5.3) and repair ESI = SSB_length + RS_ID;\n * SS_ID increments by one per FEC source packet and wraps at 2^32 (C.5.2),\n * so a block is located by membership in `[SS_Start, SS_Start + SSB_length)`\n * under RFC 1982 serial arithmetic; the MoQ repair group id is the 62-bit\n * wrap-epoch-extended SS_Start. The AL-FEC coding byte for this profile is\n * 0x14 (one-stage, ssbg_mode1, fec_payload_id_mode 0, Amd 1:2025 Table C.3).\n *\n * A track whose `blockProfile` is absent or names any other token (the\n * retired Blockcast-private `k-padded-v1` included) has no FEC: a receiver\n * consumes its source without FEC and joins none of its repair tracks\n * (draft-ramadan-moq-fec \u00A75.1). `parseFecBlockProfile` throws for such a\n * token; the per-track catalog resolver turns the same rule into a typed\n * per-track \"FEC unavailable\" outcome instead of failing the session.\n */\nimport { serial32Add, serial32Distance, serial32Precedes } from './serial-number.js'\n\n/** The block profile a catalog `fec.blockProfile` may name: the one this library implements. */\nexport type FecBlockProfile = 'iso-ssbg1-v1'\n\n/** Every accepted `fec.blockProfile` token. */\nexport const FEC_BLOCK_PROFILES: readonly FecBlockProfile[] = Object.freeze([\n 'iso-ssbg1-v1',\n])\n\n/** True for a block-profile token this library implements. */\nexport function isFecBlockProfile(value: unknown): value is FecBlockProfile {\n return value === 'iso-ssbg1-v1'\n}\n\n/**\n * Read a required `fec.blockProfile`. Throws on an absent token and on any\n * token other than `iso-ssbg1-v1`: there is no default and nothing is\n * inferred from the wire (draft-ramadan-moq-fec \u00A75.1).\n *\n * @param where the catalog path of the field, for the error message.\n */\nexport function parseFecBlockProfile(value: unknown, where: string): FecBlockProfile {\n if (isFecBlockProfile(value)) return value\n if (value === undefined) {\n throw new Error(`${where} is required: name the FEC block profile (\"iso-ssbg1-v1\")`)\n }\n throw new Error(`${where} must be \"iso-ssbg1-v1\" (got ${JSON.stringify(value)})`)\n}\n\n/** ISO/IEC 23008-1 C.3.3 ssbg_mode1: the two-octet packet length field. */\nexport const SSBG1_LENGTH_FIELD_BYTES = 2\n\n/** The largest protected packet an ssbg_mode1 symbol of size T can carry. */\nexport function ssbg1MaxPacketLength(symbolSize: number): number {\n return symbolSize - SSBG1_LENGTH_FIELD_BYTES\n}\n\n/** Whether a protected packet of `packetLength` bytes fits one T-byte ssbg_mode1 symbol. */\nexport function ssbg1FitsSymbol(packetLength: number, symbolSize: number): boolean {\n return (\n Number.isSafeInteger(packetLength) &&\n packetLength >= 0 &&\n packetLength <= ssbg1MaxPacketLength(symbolSize) &&\n packetLength <= 0xffff\n )\n}\n\n/**\n * Build one ssbg_mode1 source symbol (C.3.3): the protected packet's length\n * in two octets, network byte order, then the packet, then zero octets up to\n * T. The protected packet is the MMTP packet before its Source FEC Payload ID\n * is appended (C.4.2).\n *\n * @throws when `length + 2 > T`: the packet cannot be protected under this\n * profile, and silently truncating it would corrupt every repair symbol.\n */\nexport function buildSsbg1SourceSymbol(packet: Uint8Array, symbolSize: number): Uint8Array {\n if (!Number.isSafeInteger(symbolSize) || symbolSize <= SSBG1_LENGTH_FIELD_BYTES) {\n throw new Error(`ssbg_mode1 symbol size must exceed ${SSBG1_LENGTH_FIELD_BYTES} (got ${symbolSize})`)\n }\n if (!ssbg1FitsSymbol(packet.byteLength, symbolSize)) {\n throw new Error(\n `ssbg_mode1 source symbol needs ${packet.byteLength} + ${SSBG1_LENGTH_FIELD_BYTES} bytes but T is ${symbolSize}`,\n )\n }\n const symbol = new Uint8Array(symbolSize)\n symbol[0] = (packet.byteLength >>> 8) & 0xff\n symbol[1] = packet.byteLength & 0xff\n symbol.set(packet, SSBG1_LENGTH_FIELD_BYTES)\n return symbol\n}\n\n/**\n * Recover the protected packet from one ssbg_mode1 source symbol: the bytes\n * the length field names. Returns null, never a guess, when the length field\n * points past the symbol or any padding octet is non-zero (C.3.3 padding is\n * all 00h), since either means the recovered symbol is not what was sent.\n */\nexport function trimSsbg1SourceSymbol(symbol: Uint8Array): Uint8Array | null {\n if (symbol.byteLength < SSBG1_LENGTH_FIELD_BYTES) return null\n const length = (symbol[0]! << 8) | symbol[1]!\n const end = SSBG1_LENGTH_FIELD_BYTES + length\n if (end > symbol.byteLength) return null\n for (let i = end; i < symbol.byteLength; i++) {\n if (symbol[i] !== 0) return null\n }\n return symbol.subarray(SSBG1_LENGTH_FIELD_BYTES, end)\n}\n\n/** The SS_ID after the last source symbol of `[ssStart, ssStart + ssbLength)`. */\nexport function isoBlockEnd(ssStart: number, ssbLength: number): number {\n return serial32Add(ssStart, ssbLength)\n}\n\n/**\n * Range membership (C.5.2, C.5.3): whether `ssId` is one of the\n * `ssbLength` consecutive SS_IDs starting at `ssStart`, modulo 2^32.\n */\nexport function isoBlockContains(ssStart: number, ssbLength: number, ssId: number): boolean {\n return serial32Distance(ssStart, ssId) < ssbLength\n}\n\n/**\n * A lower bound on how many blocks the block starting at `a` lies behind the\n * one starting at `b` (0 when `a` does not precede `b`). An iso-ssbg1-v1\n * block holds at most `kMax` source symbols (the catalog `sourceSymbols`, the\n * AL-FEC maximum_k_for_repair_flow), so `n * kMax` SS_IDs always span at least\n * `n` blocks. Every interleave horizon in the receiver (decoder retention,\n * worker retirement, ordered-relay settlement) uses this one bound, so the\n * manager's horizon can never fall inside the decoder's.\n */\nexport function isoBlocksBehindLowerBound(a: number, b: number, kMax: number): number {\n if (!Number.isSafeInteger(kMax) || kMax <= 0) {\n throw new Error(`iso-ssbg1-v1 block horizon needs a positive maximum K (got ${kMax})`)\n }\n return serial32Precedes(a, b) ? Math.floor(serial32Distance(a, b) / kMax) : 0\n}\n", "// Typed FEC state shared by every player (hang/moq-lib, moqtail, Shaka MSF).\n//\n// A player's FEC counters are `null` in three unrelated situations: the\n// session is on reliable MoQ, where FEC has nothing to protect (FEC protects\n// only lossy multicast/AMT datagrams); no multicast bridge exists, so no lossy\n// leg can be joined; or the catalog declares no FEC for the tracks the player\n// selected. `null` also meant \"nothing decoded yet\" and \"the pipeline broke\",\n// so an idle session and a broken one looked identical.\n//\n// `resolveFecState` names which case holds. Each reason is read from a fact the\n// player already owns -- its catalog, its transport policy, its bridge probe\n// and its physical subscription -- and never from counters, console text,\n// timers or elapsed time. The rules live here so the three players cannot\n// disagree about precedence.\n\nimport { isFecBlockProfile } from '@blockcast/mmt-base'\n\n/** Smallest positive catalog elapsed span; zero/absence means no elapsed deadline. */\nexport const MIN_INTERLEAVE_DEPTH_MS = 1\n\n/** Why FEC is not protecting this session. */\nexport type FecInactiveReason = 'no-catalog-fec' | 'reliable-moq' | 'no-multicast-bridge'\n\n/** Every inactive reason, in resolution order. */\nexport const FEC_INACTIVE_REASONS: readonly FecInactiveReason[] = Object.freeze([\n 'no-catalog-fec',\n 'reliable-moq',\n 'no-multicast-bridge',\n] as const)\n\n/** The transport policy vocabulary every player accepts. */\nexport type FecTransportPolicy = 'auto' | 'moq-only' | 'multicast-only'\n\nconst TRANSPORT_POLICIES: ReadonlySet<string> = new Set<FecTransportPolicy>(['auto', 'moq-only', 'multicast-only'])\n\n/** FEC is not protecting this session; `reason` says why. Carries no counters. */\nexport interface FecInactiveState {\n readonly state: 'inactive'\n readonly reason: FecInactiveReason\n}\n\n/**\n * FEC is the protection in force for this session's delivery path.\n *\n * Flat on purpose: the player's existing counters stay at the paths every\n * consumer already reads (`fec.blocksRecovered`, `fec.tracks`, `fec.blocks`),\n * with `state` added beside them. A consumer that ignores `state` reads an\n * active report exactly as before and an inactive one as \"no counters\", which\n * is what `null` meant to it.\n */\nexport type FecActiveState<S extends object> = S & { readonly state: 'active' }\n\nexport type FecState<S extends object> = FecActiveState<S> | FecInactiveState\n\n/** An active state whose decoder has not reported any counter yet. */\nexport type NoFecCounters = Record<never, never>\n\n/** The authoritative facts `resolveFecState` reads. */\nexport interface FecStateEvidence<S extends object> {\n /**\n * The counters the player reports today, or null/undefined when it has none.\n * Payload only: it never decides the state, because moqtail and Shaka report\n * a zeroed snapshot as soon as a MulticastManager exists, which is before any\n * bridge or subscription does.\n */\n readonly stats: S | null | undefined\n /**\n * Whether the catalog declares FEC (see `catalogDeclaresFec`) for at least\n * one media track the player selected.\n */\n readonly catalogFec: boolean\n /** The player's configured transport policy. */\n readonly transportPolicy: FecTransportPolicy\n /** Whether a multicast bridge (`window.multicast`) is available to the page. */\n readonly bridgeAvailable: boolean\n /**\n * Whether a physical multicast/AMT subscription is live: the bridge accepted\n * it and it has not been closed. A requested leg is not a joined one.\n */\n readonly multicastLegJoined: boolean\n}\n\nconst INACTIVE: Readonly<Record<FecInactiveReason, FecInactiveState>> = Object.freeze({\n 'no-catalog-fec': Object.freeze({ state: 'inactive', reason: 'no-catalog-fec' }),\n 'reliable-moq': Object.freeze({ state: 'inactive', reason: 'reliable-moq' }),\n 'no-multicast-bridge': Object.freeze({ state: 'inactive', reason: 'no-multicast-bridge' }),\n})\n\nfunction recordOf(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined\n}\n\nfunction isPositiveSafeInteger(value: unknown): boolean {\n return typeof value === 'number' && Number.isSafeInteger(value) && value > 0\n}\n\n/**\n * Whether one catalog `fec` block (a hang rendition's `fec` or an MSF/CMSF\n * track's `fec`) declares an FEC instance a receiver can configure: source\n * symbols per block (K) and a symbol size (T) are both positive integers, the\n * algorithm is not `none`, `blockProfile` names the implemented profile\n * `iso-ssbg1-v1` (BLO-35597), and the block carries that profile's usable\n * span field (`isoBlockSpanUsable`); zero/absence still permits in-band FEC. Total: it never throws, and answers false for\n * any other value.\n *\n * `resolveCatalogTrackFec` reads this predicate, so the players' parameters and\n * this state never disagree. Of the blocks this rejects, it returns no FEC\n * geometry only for `algorithm: \"none\"`. One with any other algorithm is a\n * malformed catalog (`FecCatalogConfig` marks both fields REQUIRED; the MSF\n * schema and `validateCmsfMmtpCatalog` enforce them on `tracks[]`, but the\n * hang dialect's `renditions` schema is permissive, so this is where a hang\n * block fails closed), and `resolveCatalogTrackFec` throws a `RepairContractError` for\n * it (`track_fec` unless another repair-contract violation fires first),\n * whether or not the source has repair layers, rather than silently dropping\n * the track's repair subscription.\n *\n * The block profile is part of this predicate because the profile selects the\n * block math and is never defaulted: draft-ramadan-moq-fec \u00A75.1 defines one,\n * `iso-ssbg1-v1`, and a receiver MUST NOT apply FEC under any other token. The\n * retired `k-padded-v1` is simply not a known profile. `resolveCatalogTrackFec`\n * and `MulticastManager.configureMultiTrack` resolve such a track to no FEC\n * with a typed `TrackFecUnavailable` reason (`catalogFecUnavailable`: code\n * `track_fec_block_profile_unimplemented` for an unimplemented token,\n * `track_fec_block_profile` for an absent one), scoped to that track, so their\n * parameters and this state still agree. The CMSF conformance validator\n * (`validateCmsfMmtpCatalog`) rejects the catalog with the same code. An\n * `iso-ssbg1-v1` instance with an invalid span is likewise unavailable for its\n * track alone. Zero or absence supplies no elapsed deadline and uses bounded\n * receiver-local state while decoding in-band FEC.\n *\n * The repair-contract resolvers (`resolveRepairLayers`) gate a `depends` target\n * on `algorithm` alone and never read a source's `symbolSize`. They check its\n * `sourceSymbols` for the RaptorQ K bound, and require a positive integer\n * `sourceSymbols` and `repairSymbols` only when the source has repair layers or\n * an overlay beyond layer 0.\n */\nexport function catalogDeclaresFec(fec: unknown): boolean {\n const block = recordOf(fec)\n if (block === undefined || block['algorithm'] === 'none') return false\n return (\n isPositiveSafeInteger(block['sourceSymbols']) &&\n isPositiveSafeInteger(block['symbolSize']) &&\n isFecBlockProfile(block['blockProfile']) &&\n isoBlockSpanUsable(block)\n )\n}\n\n/** Validate the optional elapsed span without requiring one for in-band FEC. */\nexport function isoBlockSpanUsable(fec: unknown): boolean {\n const block = recordOf(fec)\n if (block === undefined || block['blockProfile'] !== 'iso-ssbg1-v1') return true\n const ms = block['interleaveDepthMs']\n return ms === undefined || (typeof ms === 'number' && Number.isSafeInteger(ms) && ms >= 0)\n}\n\nfunction requireBoolean(value: unknown, field: string): boolean {\n if (typeof value !== 'boolean') {\n throw new TypeError(`resolveFecState: ${field} must be a boolean, got ${typeof value}`)\n }\n return value\n}\n\n/**\n * Resolve a player's FEC state. First match wins:\n *\n * 1. The catalog declares no FEC for a selected track: `no-catalog-fec`. FEC\n * geometry comes only from the catalog, so there is nothing to configure on\n * any path.\n * 2. The policy is `moq-only`: `reliable-moq`. Media travels only over QUIC,\n * which is reliable, so FEC has no loss to repair, bridge or not.\n * 3. No multicast bridge: `no-multicast-bridge`. No lossy leg can exist, and on\n * `auto` this is the reason the session is on reliable MoQ.\n * 4. The policy is `multicast-only`, or a lossy subscription is live: active.\n * `multicast-only` commits the session to the lossy leg, so FEC is in force\n * before the join completes; whether the join succeeded is the physical\n * attestation's question, not this one. The counters may be absent until\n * the decoder reports.\n * 5. Otherwise (`auto`, a bridge, no live subscription): `reliable-moq`.\n *\n * Throws on malformed evidence rather than picking a reason for it.\n */\nexport function resolveFecState<S extends object>(evidence: FecStateEvidence<S> & { readonly stats: S }): FecState<S>\nexport function resolveFecState<S extends object>(evidence: FecStateEvidence<S>): FecState<S | NoFecCounters>\nexport function resolveFecState<S extends object>(evidence: FecStateEvidence<S>): FecState<S | NoFecCounters> {\n const catalogFec = requireBoolean(evidence.catalogFec, 'catalogFec')\n const bridgeAvailable = requireBoolean(evidence.bridgeAvailable, 'bridgeAvailable')\n const multicastLegJoined = requireBoolean(evidence.multicastLegJoined, 'multicastLegJoined')\n const { transportPolicy, stats } = evidence\n if (typeof transportPolicy !== 'string' || !TRANSPORT_POLICIES.has(transportPolicy)) {\n throw new TypeError(`resolveFecState: unsupported transportPolicy ${JSON.stringify(transportPolicy)}`)\n }\n if (stats !== null && stats !== undefined && recordOf(stats) === undefined) {\n throw new TypeError('resolveFecState: stats must be a plain object, null or undefined')\n }\n\n if (!catalogFec) return INACTIVE['no-catalog-fec']\n if (transportPolicy === 'moq-only') return INACTIVE['reliable-moq']\n if (!bridgeAvailable) return INACTIVE['no-multicast-bridge']\n if (transportPolicy === 'multicast-only' || multicastLegJoined) {\n // The discriminant is written last so a stats field of the same name can\n // never masquerade as a state.\n return { ...(stats ?? {}), state: 'active' } as FecActiveState<S | NoFecCounters>\n }\n return INACTIVE['reliable-moq']\n}\n"],
5
+ "mappings": ";AAiBA,IAAM,kBAAkB;AACxB,IAAM,wBAAwB;AAC9B,IAAM,4BAA4B,kBAAkB;;;AC0D7C,IAAMA,mBAAkB;AAI/B,IAAMC,yBAAwB;AAC9B,IAAMC,6BAA4BC,mBAAkBF;;;ACrE7C,IAAM,iBAAiB;AAGvB,IAAM,oBAAoB,KAAK;AAQ/B,IAAM,uBAAuB,MAAM,iBAAiB;;;ACSpD,IAAM,qBAAiD,OAAO,OAAO;EAC1E;CACD;AAGK,SAAU,kBAAkB,OAAc;AAC9C,SAAO,UAAU;AACnB;;;ACtBO,IAAM,0BAA0B;AAMhC,IAAM,uBAAqD,OAAO,OAAO;AAAA,EAC9E;AAAA,EACA;AAAA,EACA;AACF,CAAU;AAKV,IAAM,qBAA0C,oBAAI,IAAwB,CAAC,QAAQ,YAAY,gBAAgB,CAAC;AAiDlH,IAAM,WAAkE,OAAO,OAAO;AAAA,EACpF,kBAAkB,OAAO,OAAO,EAAE,OAAO,YAAY,QAAQ,iBAAiB,CAAC;AAAA,EAC/E,gBAAgB,OAAO,OAAO,EAAE,OAAO,YAAY,QAAQ,eAAe,CAAC;AAAA,EAC3E,uBAAuB,OAAO,OAAO,EAAE,OAAO,YAAY,QAAQ,sBAAsB,CAAC;AAC3F,CAAC;AAED,SAAS,SAAS,OAAqD;AACrE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK,IACrE,QACD;AACN;AAEA,SAAS,sBAAsB,OAAyB;AACtD,SAAO,OAAO,UAAU,YAAY,OAAO,cAAc,KAAK,KAAK,QAAQ;AAC7E;AA0CO,SAAS,mBAAmB,KAAuB;AACxD,QAAM,QAAQ,SAAS,GAAG;AAC1B,MAAI,UAAU,UAAa,MAAM,WAAW,MAAM,OAAQ,QAAO;AACjE,SACE,sBAAsB,MAAM,eAAe,CAAC,KAC5C,sBAAsB,MAAM,YAAY,CAAC,KACzC,kBAAkB,MAAM,cAAc,CAAC,KACvC,mBAAmB,KAAK;AAE5B;AAGO,SAAS,mBAAmB,KAAuB;AACxD,QAAM,QAAQ,SAAS,GAAG;AAC1B,MAAI,UAAU,UAAa,MAAM,cAAc,MAAM,eAAgB,QAAO;AAC5E,QAAM,KAAK,MAAM,mBAAmB;AACpC,SAAO,OAAO,UAAc,OAAO,OAAO,YAAY,OAAO,cAAc,EAAE,KAAK,MAAM;AAC1F;AAEA,SAAS,eAAe,OAAgB,OAAwB;AAC9D,MAAI,OAAO,UAAU,WAAW;AAC9B,UAAM,IAAI,UAAU,oBAAoB,KAAK,2BAA2B,OAAO,KAAK,EAAE;AAAA,EACxF;AACA,SAAO;AACT;AAuBO,SAAS,gBAAkC,UAA4D;AAC5G,QAAM,aAAa,eAAe,SAAS,YAAY,YAAY;AACnE,QAAM,kBAAkB,eAAe,SAAS,iBAAiB,iBAAiB;AAClF,QAAM,qBAAqB,eAAe,SAAS,oBAAoB,oBAAoB;AAC3F,QAAM,EAAE,iBAAiB,MAAM,IAAI;AACnC,MAAI,OAAO,oBAAoB,YAAY,CAAC,mBAAmB,IAAI,eAAe,GAAG;AACnF,UAAM,IAAI,UAAU,gDAAgD,KAAK,UAAU,eAAe,CAAC,EAAE;AAAA,EACvG;AACA,MAAI,UAAU,QAAQ,UAAU,UAAa,SAAS,KAAK,MAAM,QAAW;AAC1E,UAAM,IAAI,UAAU,kEAAkE;AAAA,EACxF;AAEA,MAAI,CAAC,WAAY,QAAO,SAAS,gBAAgB;AACjD,MAAI,oBAAoB,WAAY,QAAO,SAAS,cAAc;AAClE,MAAI,CAAC,gBAAiB,QAAO,SAAS,qBAAqB;AAC3D,MAAI,oBAAoB,oBAAoB,oBAAoB;AAG9D,WAAO,EAAE,GAAI,SAAS,CAAC,GAAI,OAAO,SAAS;AAAA,EAC7C;AACA,SAAO,SAAS,cAAc;AAChC;",
6
6
  "names": ["MPU_HEADER_SIZE", "MPU_LENGTH_FIELD_SIZE", "MPU_HEADER_REMAINDER_SIZE", "MPU_HEADER_SIZE"]
7
7
  }
@@ -34,11 +34,17 @@ export interface FecWorkerSetupOptions {
34
34
  /** Track type from catalog. Never inferred from name. */
35
35
  trackType: 'video' | 'audio';
36
36
  /**
37
- * The catalog `fec.blockProfile` (BLO-35597). Required: `k-padded-v1` or
38
- * `iso-ssbg1-v1`; never defaulted, never inferred.
37
+ * The catalog `fec.blockProfile` (BLO-35597). Required and validated with
38
+ * `parseFecBlockProfile`: `iso-ssbg1-v1` is the only FEC block profile, and
39
+ * any other token (the retired `k-padded-v1` included) throws before a
40
+ * Worker is created. Never defaulted, never inferred.
39
41
  */
40
42
  fecBlockProfile: FecBlockProfile;
41
- /** FEC K from catalog (source symbols per block). Under iso-ssbg1-v1 the maximum SSB_length. */
43
+ /**
44
+ * FEC K from catalog: the maximum SSB_length (K_max) of any block. Blocks
45
+ * are variable-length ssbg_mode1 blocks keyed by SS_Start, so K bounds
46
+ * block distance from below; it never strides SS_IDs into blocks.
47
+ */
42
48
  fecK: number;
43
49
  /** Symbol size in bytes (T) — the ALTA-adjusted value if ALTA is signed. */
44
50
  fecSymbolSize: number;
@@ -52,12 +58,16 @@ export interface FecWorkerSetupOptions {
52
58
  * `fecRepairCount`, which is the same value on a single-layer track.
53
59
  */
54
60
  joinedRepairCount?: number;
55
- /** Interleave depth in ms. */
61
+ /** Catalog interleave span in integer milliseconds; zero means no span. */
56
62
  fecInterleaveMs: number;
57
63
  /** Explicit catalog jitter/reorder allowance in ms. */
58
64
  fecReorderToleranceMs?: number;
59
- /** Duration of one FEC media unit in ms (video frame or audio access unit). */
60
- mediaUnitDurationMs: number;
65
+ /**
66
+ * Duration of one FEC media unit in ms (video frame or audio access unit).
67
+ * Required only when the catalog supplies a positive elapsed FEC span. A
68
+ * missing/zero span must not invent a media cadence for its local bound.
69
+ */
70
+ mediaUnitDurationMs?: number;
61
71
  /** Optional repair track name (MoQ). Forwarded to FecTrackConfig. */
62
72
  repairTrackName?: string;
63
73
  /** Audio sample rate (optional — audio tracks only). */
@@ -1 +1 @@
1
- {"version":3,"file":"fec-worker-setup.d.ts","sourceRoot":"","sources":["../src/fec-worker-setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAChF,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,uBAAuB,CAAA;AAC5E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAI9D,0EAA0E;AAC1E,MAAM,WAAW,kBAAkB;IACjC,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAA;IACjB,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAA;IACd,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,gBAAgB,EAAE,gBAAgB,CAAA;IAClC,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAA;IAChB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAA;IACjB,yDAAyD;IACzD,SAAS,EAAE,OAAO,GAAG,OAAO,CAAA;IAC5B;;;OAGG;IACH,eAAe,EAAE,eAAe,CAAA;IAChC,gGAAgG;IAChG,IAAI,EAAE,MAAM,CAAA;IACZ,4EAA4E;IAC5E,aAAa,EAAE,MAAM,CAAA;IACrB,oCAAoC;IACpC,cAAc,EAAE,MAAM,CAAA;IACtB;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,8BAA8B;IAC9B,eAAe,EAAE,MAAM,CAAA;IACvB,uDAAuD;IACvD,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,+EAA+E;IAC/E,mBAAmB,EAAE,MAAM,CAAA;IAC3B,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4FAA4F;IAC5F,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,8CAA8C;IAC9C,SAAS,EAAE,kBAAkB,CAAA;IAC7B,qEAAqE;IACrE,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,KAAK,eAAe,CAAA;CACpD;AAED,MAAM,WAAW,oBAAoB;IACnC,wEAAwE;IACxE,MAAM,EAAE,eAAe,CAAA;IACvB,wEAAwE;IACxE,WAAW,EAAE,OAAO,CAAA;IACpB,0EAA0E;IAC1E,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,qBAAqB,GAC1B,OAAO,CAAC,oBAAoB,CAAC,CAqL/B"}
1
+ {"version":3,"file":"fec-worker-setup.d.ts","sourceRoot":"","sources":["../src/fec-worker-setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAChF,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,uBAAuB,CAAA;AAC5E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAQ9D,0EAA0E;AAC1E,MAAM,WAAW,kBAAkB;IACjC,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAA;IACjB,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAA;IACd,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,gBAAgB,EAAE,gBAAgB,CAAA;IAClC,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAA;IAChB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAA;IACjB,yDAAyD;IACzD,SAAS,EAAE,OAAO,GAAG,OAAO,CAAA;IAC5B;;;;;OAKG;IACH,eAAe,EAAE,eAAe,CAAA;IAChC;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ,4EAA4E;IAC5E,aAAa,EAAE,MAAM,CAAA;IACrB,oCAAoC;IACpC,cAAc,EAAE,MAAM,CAAA;IACtB;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,2EAA2E;IAC3E,eAAe,EAAE,MAAM,CAAA;IACvB,uDAAuD;IACvD,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4FAA4F;IAC5F,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,8CAA8C;IAC9C,SAAS,EAAE,kBAAkB,CAAA;IAC7B,qEAAqE;IACrE,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,KAAK,eAAe,CAAA;CACpD;AAED,MAAM,WAAW,oBAAoB;IACnC,wEAAwE;IACxE,MAAM,EAAE,eAAe,CAAA;IACvB,wEAAwE;IACxE,WAAW,EAAE,OAAO,CAAA;IACpB,0EAA0E;IAC1E,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,qBAAqB,GAC1B,OAAO,CAAC,oBAAoB,CAAC,CAyN/B"}